From ff5649bd3c1583350d4a1e9242d41639044e47d0 Mon Sep 17 00:00:00 2001 From: netbonus <151201453+netbonus@users.noreply.github.com> Date: Wed, 21 Jan 2026 07:06:29 -0500 Subject: [PATCH 01/14] chore: migrate to Turborepo + Biome Replace ESLint + Prettier with Biome for linting and formatting. Add Turborepo for build orchestration and caching. Changes: - Add biome.json configs (root + sdk package) - Add turbo.json with lint, typecheck, build, test tasks - Update CI workflow to use Turborepo with GitHub Actions cache - Remove .prettierrc, .prettierignore, eslint.config.mjs - Reformat all source files with Biome - Add .turbo to .gitignore Benefits: - Faster linting (Biome is ~10-100x faster than ESLint) - Unified tool for lint + format (less config, fewer deps) - Turborepo caching speeds up CI and local dev - Simpler toolchain to maintain --- .github/workflows/build-test.yml | 8 + .gitignore | 1 + .prettierignore | 5 - .prettierrc | 13 - biome.json | 85 ++ package.json | 16 +- .../docs/src/components/HomepageFeatures.tsx | 1 - packages/docs/src/pages/_index.tsx | 1 - packages/example/src/Lattice.tsx | 2 +- packages/sdk/biome.json | 8 + packages/sdk/eslint.config.mjs | 110 -- packages/sdk/package.json | 14 +- packages/sdk/src/__test__/e2e/api.test.ts | 4 +- packages/sdk/src/__test__/e2e/btc.test.ts | 2 +- packages/sdk/src/__test__/e2e/eth.msg.test.ts | 6 +- packages/sdk/src/__test__/e2e/kv.test.ts | 2 +- .../src/__test__/e2e/non-exportable.test.ts | 2 +- .../sdk/src/__test__/e2e/signing/bls.test.ts | 15 +- .../__test__/e2e/signing/determinism.test.ts | 22 +- .../__test__/e2e/signing/eip712-msg.test.ts | 2 +- .../e2e/signing/solana/solana.test.ts | 2 +- .../signing/solana/solana.versioned.test.ts | 2 +- .../__test__/e2e/signing/unformatted.test.ts | 12 +- .../sdk/src/__test__/e2e/signing/vectors.ts | 18 +- .../integration/__mocks__/handlers.ts | 28 +- .../unit/compareEIP7702Serialization.test.ts | 64 +- .../sdk/src/__test__/unit/decoders.test.ts | 2 +- .../sdk/src/__test__/unit/eip7702.test.ts | 34 +- .../__test__/unit/ethereum.validate.test.ts | 4 +- .../src/__test__/unit/module.interop.test.ts | 2 +- .../unit/parseGenericSigningResponse.test.ts | 2 +- .../src/__test__/unit/signatureUtils.test.ts | 10 +- .../sdk/src/__test__/unit/validators.test.ts | 14 +- packages/sdk/src/__test__/utils/builders.ts | 14 +- packages/sdk/src/__test__/utils/ethers.ts | 20 +- packages/sdk/src/__test__/utils/getters.ts | 12 +- packages/sdk/src/__test__/utils/helpers.ts | 22 +- packages/sdk/src/__test__/utils/runners.ts | 24 +- .../sdk/src/__test__/utils/viemComparison.ts | 24 +- packages/sdk/src/api/addressTags.ts | 4 +- packages/sdk/src/api/addresses.ts | 4 +- packages/sdk/src/api/setup.ts | 2 +- packages/sdk/src/api/signing.ts | 6 +- packages/sdk/src/api/state.ts | 2 +- packages/sdk/src/api/utilities.ts | 6 +- packages/sdk/src/api/wallets.ts | 2 +- packages/sdk/src/bitcoin.ts | 11 +- packages/sdk/src/calldata/evm.ts | 24 +- packages/sdk/src/client.ts | 14 +- packages/sdk/src/constants.ts | 6 +- packages/sdk/src/ethereum.ts | 90 +- packages/sdk/src/functions/addKvRecords.ts | 4 +- packages/sdk/src/functions/connect.ts | 6 +- .../sdk/src/functions/fetchActiveWallet.ts | 4 +- packages/sdk/src/functions/fetchDecoder.ts | 2 +- packages/sdk/src/functions/fetchEncData.ts | 6 +- packages/sdk/src/functions/getAddresses.ts | 4 +- packages/sdk/src/functions/getKvRecords.ts | 19 +- packages/sdk/src/functions/pair.ts | 2 +- packages/sdk/src/functions/removeKvRecords.ts | 6 +- packages/sdk/src/functions/sign.ts | 14 +- packages/sdk/src/genericSigning.ts | 18 +- packages/sdk/src/protocol/secureMessages.ts | 38 +- packages/sdk/src/schemas/transaction.ts | 12 +- packages/sdk/src/shared/errors.ts | 2 +- packages/sdk/src/shared/functions.ts | 6 +- packages/sdk/src/shared/predicates.ts | 6 +- packages/sdk/src/shared/utilities.ts | 4 +- packages/sdk/src/shared/validators.ts | 16 +- packages/sdk/src/types/addKvRecords.ts | 7 +- packages/sdk/src/types/client.ts | 19 +- packages/sdk/src/types/connect.ts | 2 +- packages/sdk/src/types/declarations.d.ts | 4 +- packages/sdk/src/types/fetchActiveWallet.ts | 2 +- packages/sdk/src/types/fetchEncData.ts | 2 +- packages/sdk/src/types/firmware.ts | 2 +- packages/sdk/src/types/getAddresses.ts | 5 +- packages/sdk/src/types/getKvRecords.ts | 5 +- packages/sdk/src/types/messages.ts | 2 +- packages/sdk/src/types/pair.ts | 2 +- packages/sdk/src/types/removeKvRecords.ts | 5 +- packages/sdk/src/types/secureMessages.ts | 4 +- packages/sdk/src/types/sign.ts | 12 +- packages/sdk/src/types/utils.ts | 2 +- packages/sdk/src/util.ts | 79 +- pnpm-lock.yaml | 1205 +++-------------- turbo.json | 27 + 87 files changed, 728 insertions(+), 1627 deletions(-) delete mode 100644 .prettierignore delete mode 100644 .prettierrc create mode 100644 biome.json create mode 100644 packages/sdk/biome.json delete mode 100644 packages/sdk/eslint.config.mjs create mode 100644 turbo.json diff --git a/.github/workflows/build-test.yml b/.github/workflows/build-test.yml index a56cb9df..060a3c5d 100644 --- a/.github/workflows/build-test.yml +++ b/.github/workflows/build-test.yml @@ -39,6 +39,14 @@ jobs: - name: Install dependencies run: pnpm install + - name: Setup Turborepo cache + uses: actions/cache@v4 + with: + path: .turbo + key: turbo-${{ runner.os }}-${{ hashFiles('**/pnpm-lock.yaml') }}-${{ github.sha }} + restore-keys: | + turbo-${{ runner.os }}-${{ hashFiles('**/pnpm-lock.yaml') }}- + - name: Run linter run: pnpm run lint diff --git a/.gitignore b/.gitignore index cddb2a58..17ca84b7 100644 --- a/.gitignore +++ b/.gitignore @@ -8,3 +8,4 @@ coverage *.temp ~/node_modules cache +.turbo diff --git a/.prettierignore b/.prettierignore deleted file mode 100644 index 042126f4..00000000 --- a/.prettierignore +++ /dev/null @@ -1,5 +0,0 @@ -# JSONC files with comments - parser incompatibility -src/__test__/vectors.jsonc - -# Generated lockfiles -pnpm-lock.yaml diff --git a/.prettierrc b/.prettierrc deleted file mode 100644 index 23899362..00000000 --- a/.prettierrc +++ /dev/null @@ -1,13 +0,0 @@ -{ - "overrides": [ - { - "files": "**/src/*.ts", - "options": { - "parser": "typescript" - } - } - ], - "singleQuote": true, - "tabWidth": 2, - "trailingComma": "all" -} diff --git a/biome.json b/biome.json new file mode 100644 index 00000000..d319d518 --- /dev/null +++ b/biome.json @@ -0,0 +1,85 @@ +{ + "$schema": "https://biomejs.dev/schemas/2.2.4/schema.json", + "vcs": { + "enabled": true, + "clientKind": "git", + "useIgnoreFile": true + }, + "formatter": { + "enabled": true, + "formatWithErrors": false, + "indentStyle": "space", + "indentWidth": 2, + "lineEnding": "lf", + "lineWidth": 80, + "attributePosition": "auto" + }, + "linter": { + "enabled": true, + "rules": { + "recommended": true, + "complexity": { + "noForEach": "off" + }, + "correctness": { + "noUnusedImports": { + "level": "error" + }, + "noUnusedVariables": { + "level": "warn", + "fix": "none" + }, + "noUnusedFunctionParameters": { + "level": "warn", + "fix": "none" + } + }, + "style": { + "useConst": "error", + "noVar": "warn", + "noParameterAssign": "off", + "noUselessElse": "off", + "noUnusedTemplateLiteral": "error", + "useAsConstAssertion": "error", + "useDefaultParameterLast": "error", + "useEnumInitializers": "error", + "useSingleVarDeclarator": "error", + "useNumberNamespace": "error", + "noInferrableTypes": "error", + "noNonNullAssertion": "off" + }, + "performance": { + "noAccumulatingSpread": "warn" + }, + "suspicious": { + "noExplicitAny": "off", + "noDoubleEquals": "error", + "noImplicitAnyLet": "off", + "noConfusingVoidType": "off", + "noControlCharactersInRegex": "off", + "noAssignInExpressions": "warn" + } + } + }, + "organizeImports": { + "enabled": false + }, + "files": { + "ignoreUnknown": true, + "include": ["packages/*/src/**"], + "ignore": ["**/dist/**", "**/node_modules/**", "**/coverage/**"] + }, + "javascript": { + "formatter": { + "jsxQuoteStyle": "double", + "quoteProperties": "asNeeded", + "trailingCommas": "all", + "semicolons": "always", + "arrowParentheses": "always", + "bracketSpacing": true, + "bracketSameLine": false, + "quoteStyle": "single", + "attributePosition": "auto" + } + } +} diff --git a/package.json b/package.json index d92676ac..061c6916 100644 --- a/package.json +++ b/package.json @@ -1,15 +1,21 @@ { "name": "gridplus-sdk-monorepo", "private": true, + "packageManager": "pnpm@10.6.2", "scripts": { - "build": "pnpm --filter gridplus-sdk run build", - "test": "pnpm --filter gridplus-sdk run test", - "lint": "pnpm --filter gridplus-sdk run lint", - "lint:fix": "pnpm --filter gridplus-sdk run lint:fix", - "e2e": "pnpm --filter gridplus-sdk run e2e", + "build": "turbo run build --filter=gridplus-sdk", + "test": "turbo run test --filter=gridplus-sdk", + "lint": "turbo run lint --filter=gridplus-sdk", + "lint:fix": "turbo run lint:fix --filter=gridplus-sdk", + "typecheck": "turbo run typecheck --filter=gridplus-sdk", + "e2e": "turbo run e2e --filter=gridplus-sdk", "docs:build": "pnpm --filter gridplus-sdk-docs run build", "docs:start": "pnpm --filter gridplus-sdk-docs run start" }, + "devDependencies": { + "@biomejs/biome": "^1.9.0", + "turbo": "^2.3.0" + }, "engines": { "node": ">=20" } diff --git a/packages/docs/src/components/HomepageFeatures.tsx b/packages/docs/src/components/HomepageFeatures.tsx index dd30caf1..72309dd8 100644 --- a/packages/docs/src/components/HomepageFeatures.tsx +++ b/packages/docs/src/components/HomepageFeatures.tsx @@ -1,4 +1,3 @@ -import React from 'react'; import clsx from 'clsx'; import styles from './HomepageFeatures.module.css'; diff --git a/packages/docs/src/pages/_index.tsx b/packages/docs/src/pages/_index.tsx index cf264684..dbe9caaf 100644 --- a/packages/docs/src/pages/_index.tsx +++ b/packages/docs/src/pages/_index.tsx @@ -2,7 +2,6 @@ import Link from '@docusaurus/Link'; import useDocusaurusContext from '@docusaurus/useDocusaurusContext'; import Layout from '@theme/Layout'; import clsx from 'clsx'; -import React from 'react'; import styles from './index.module.css'; function HomepageHeader() { diff --git a/packages/example/src/Lattice.tsx b/packages/example/src/Lattice.tsx index 842067da..5eedd56e 100644 --- a/packages/example/src/Lattice.tsx +++ b/packages/example/src/Lattice.tsx @@ -3,8 +3,8 @@ import { TransactionFactory } from '@ethereumjs/tx'; import { useState } from 'react'; import { addAddressTags, - fetchAddresses, fetchAddressTags, + fetchAddresses, fetchLedgerLiveAddresses, removeAddressTags, sign, diff --git a/packages/sdk/biome.json b/packages/sdk/biome.json new file mode 100644 index 00000000..5fb64252 --- /dev/null +++ b/packages/sdk/biome.json @@ -0,0 +1,8 @@ +{ + "$schema": "https://biomejs.dev/schemas/2.2.4/schema.json", + "extends": ["../../biome.json"], + "files": { + "include": ["src/**/*.ts"], + "ignore": ["**/dist/**", "**/node_modules/**", "**/coverage/**"] + } +} diff --git a/packages/sdk/eslint.config.mjs b/packages/sdk/eslint.config.mjs deleted file mode 100644 index 08822ecc..00000000 --- a/packages/sdk/eslint.config.mjs +++ /dev/null @@ -1,110 +0,0 @@ -import js from '@eslint/js'; -import tsPlugin from '@typescript-eslint/eslint-plugin'; -import tsParser from '@typescript-eslint/parser'; -import prettierConfig from 'eslint-config-prettier'; -import prettierPlugin from 'eslint-plugin-prettier'; - -const restrictedNodeImports = [ - { name: 'crypto', message: 'Use node:crypto instead.' }, - { name: 'fs', message: 'Use node:fs instead.' }, - { name: 'os', message: 'Use node:os instead.' }, - { name: 'path', message: 'Use node:path instead.' }, - { name: 'stream', message: 'Use node:stream instead.' }, - { name: 'url', message: 'Use node:url instead.' }, - { name: 'util', message: 'Use node:util instead.' }, -]; - -export default [ - js.configs.recommended, - { - files: ['src/**/*.ts', 'src/**/*.tsx'], - plugins: { - '@typescript-eslint': tsPlugin, - prettier: prettierPlugin, - }, - languageOptions: { - parser: tsParser, - parserOptions: { - ecmaVersion: 'latest', - sourceType: 'module', - }, - globals: { - Buffer: 'readonly', - URL: 'readonly', - fetch: 'readonly', - Response: 'readonly', - Request: 'readonly', - RequestInit: 'readonly', - AbortController: 'readonly', - caches: 'readonly', - setTimeout: 'readonly', - clearTimeout: 'readonly', - // Test globals - vi: 'readonly', - describe: 'readonly', - it: 'readonly', - test: 'readonly', - expect: 'readonly', - beforeAll: 'readonly', - afterAll: 'readonly', - beforeEach: 'readonly', - afterEach: 'readonly', - // Node.js globals - process: 'readonly', - // Browser globals - window: 'readonly', - document: 'readonly', - console: 'readonly', - }, - }, - rules: { - ...tsPlugin.configs.recommended.rules, - ...prettierPlugin.configs.recommended.rules, - 'prettier/prettier': 'error', - eqeqeq: ['error'], - 'no-var': ['warn'], - 'no-duplicate-imports': ['error'], - 'prefer-const': ['error'], - 'prefer-spread': ['error'], - 'no-console': ['off'], - 'react/react-in-jsx-scope': 'off', - '@typescript-eslint/no-explicit-any': 'off', - '@typescript-eslint/no-unused-vars': [ - 'warn', - { argsIgnorePattern: '^_' }, - ], - quotes: [ - 'warn', - 'single', - { avoidEscape: true, allowTemplateLiterals: true }, - ], - 'no-restricted-imports': ['error', { paths: restrictedNodeImports }], - 'no-restricted-syntax': [ - 'error', - { - selector: "CallExpression[callee.name='require']", - message: 'Use ESM imports instead of require.', - }, - { - selector: - "AssignmentExpression[left.object.name='module'][left.property.name='exports']", - message: 'Use ESM exports instead of module.exports.', - }, - ], - }, - }, - prettierConfig, - { - ignores: [ - 'dist/**', - 'node_modules/**', - 'coverage/**', - '*.js', - '*.cjs', - '*.mjs', - 'build/**', - 'docs/**', - 'patches/**', - ], - }, -]; diff --git a/packages/sdk/package.json b/packages/sdk/package.json index 9a6bc8e0..4421e0a5 100644 --- a/packages/sdk/package.json +++ b/packages/sdk/package.json @@ -6,9 +6,9 @@ "scripts": { "build": "tsup", "commit": "git-cz", - "lint:fix": "eslint src --c .ts,.tsx --config eslint.config.mjs --fix && prettier --write .", - "lint": "eslint src --c .ts,.tsx --config eslint.config.mjs", - "format": "prettier --write .", + "lint": "biome check src", + "lint:fix": "biome check src --write", + "typecheck": "tsc --noEmit", "pair-device": "tsx scripts/pair-device.ts", "precommit": "npm run lint:fix && npm run test", "test": "vitest run ./src/__test__/unit ./src/__test__/integration", @@ -80,7 +80,6 @@ }, "devDependencies": { "@chainsafe/bls-keystore": "^3.1.0", - "@eslint/js": "^9.36.0", "@noble/bls12-381": "^1.4.0", "@solana/web3.js": "^1.98.4", "@types/bn.js": "^5.2.0", @@ -91,8 +90,6 @@ "@types/readline-sync": "^1.4.8", "@types/secp256k1": "^4.0.6", "@types/seedrandom": "^3.0.8", - "@typescript-eslint/eslint-plugin": "^8.44.1", - "@typescript-eslint/parser": "^8.44.1", "@vitest/coverage-istanbul": "^2.1.3", "bip32": "^4.0.0", "bip39": "^3.1.0", @@ -101,14 +98,9 @@ "dotenv": "^17.2.2", "ecpair": "^3.0.0", "ed25519-hd-key": "^1.3.0", - "eslint": "^9.36.0", - "eslint-config-prettier": "^10.1.8", - "eslint-plugin-prettier": "^5.5.4", "ethereumjs-util": "^7.1.5", "jsonc": "^2.0.0", "msw": "^2.11.3", - "prettier": "^3.6.2", - "prettier-eslint": "^16.4.2", "random-words": "^2.0.1", "readline-sync": "^1.4.10", "seedrandom": "^3.0.5", diff --git a/packages/sdk/src/__test__/e2e/api.test.ts b/packages/sdk/src/__test__/e2e/api.test.ts index 81f670b1..a7cc353b 100644 --- a/packages/sdk/src/__test__/e2e/api.test.ts +++ b/packages/sdk/src/__test__/e2e/api.test.ts @@ -21,7 +21,6 @@ vi.mock('../../util', async () => { }); import { RLP } from '@ethereumjs/rlp'; -import { getClient } from './../../api/utilities'; import { fetchActiveWallets, fetchAddress, @@ -46,8 +45,9 @@ import { } from '../../api/index'; import { HARDENED_OFFSET } from '../../constants'; import { buildRandomMsg } from '../utils/builders'; -import { setupClient } from '../utils/setup'; import { BTC_PURPOSE_P2SH_P2WPKH, BTC_TESTNET_COIN } from '../utils/helpers'; +import { setupClient } from '../utils/setup'; +import { getClient } from './../../api/utilities'; import { dexlabProgram } from './signing/solana/__mocks__/programs'; describe('API', () => { diff --git a/packages/sdk/src/__test__/e2e/btc.test.ts b/packages/sdk/src/__test__/e2e/btc.test.ts index 01b55588..4eb6905e 100644 --- a/packages/sdk/src/__test__/e2e/btc.test.ts +++ b/packages/sdk/src/__test__/e2e/btc.test.ts @@ -15,7 +15,6 @@ import BIP32Factory, { type BIP32Interface } from 'bip32'; import * as ecc from 'tiny-secp256k1'; import type { Client } from '../../client'; -import { setupClient } from '../utils/setup'; import { getPrng, getTestnet } from '../utils/getters'; import { BTC_PURPOSE_P2PKH, @@ -24,6 +23,7 @@ import { setup_btc_sig_test, stripDER, } from '../utils/helpers'; +import { setupClient } from '../utils/setup'; import { TEST_SEED } from '../utils/testConstants'; const prng = getPrng(); diff --git a/packages/sdk/src/__test__/e2e/eth.msg.test.ts b/packages/sdk/src/__test__/e2e/eth.msg.test.ts index 6ad007c0..d19ac1e8 100644 --- a/packages/sdk/src/__test__/e2e/eth.msg.test.ts +++ b/packages/sdk/src/__test__/e2e/eth.msg.test.ts @@ -17,7 +17,7 @@ */ import { HARDENED_OFFSET } from '../../constants'; -import { SigningPath } from '../../types'; +import type { SigningPath } from '../../types'; import { randomBytes } from '../../util'; import { buildEthMsgReq, buildRandomMsg } from '../utils/builders'; import { runEthMsg } from '../utils/runners'; @@ -30,7 +30,7 @@ describe('ETH Messages', () => { client = await setupClient(); }); - describe('Test ETH personalSign', function () { + describe('Test ETH personalSign', () => { it('Should throw error when message contains non-ASCII characters', async () => { const protocol = 'signPersonal'; const msg = '⚠️'; @@ -118,7 +118,7 @@ describe('ETH Messages', () => { }); }); - describe('Test ETH EIP712', function () { + describe('Test ETH EIP712', () => { it('Should test a message that needs to be prehashed', async () => { const msg = { types: { diff --git a/packages/sdk/src/__test__/e2e/kv.test.ts b/packages/sdk/src/__test__/e2e/kv.test.ts index 8f8fc89a..98a2a605 100644 --- a/packages/sdk/src/__test__/e2e/kv.test.ts +++ b/packages/sdk/src/__test__/e2e/kv.test.ts @@ -1,4 +1,3 @@ -import { DEFAULT_SIGNER } from '../utils/builders'; /** * Test kv (key-value) file functionality. These types of files are simple mappings * between a 64 byte key and a 64 byte value of any type. The main use case for these @@ -7,6 +6,7 @@ import { DEFAULT_SIGNER } from '../utils/builders'; import { question } from 'readline-sync'; import { HARDENED_OFFSET } from '../../constants'; import { LatticeResponseCode, ProtocolConstants } from '../../protocol'; +import { DEFAULT_SIGNER } from '../utils/builders'; import { BTC_PURPOSE_P2PKH, ETH_COIN } from '../utils/helpers'; import { setupClient } from '../utils/setup'; diff --git a/packages/sdk/src/__test__/e2e/non-exportable.test.ts b/packages/sdk/src/__test__/e2e/non-exportable.test.ts index dbc9c113..cc95d543 100644 --- a/packages/sdk/src/__test__/e2e/non-exportable.test.ts +++ b/packages/sdk/src/__test__/e2e/non-exportable.test.ts @@ -25,8 +25,8 @@ import { createTx } from '@ethereumjs/tx'; import { question } from 'readline-sync'; import { Constants } from '../..'; import { DEFAULT_SIGNER } from '../utils/builders'; -import { setupClient } from '../utils/setup'; import { getSigStr, validateSig } from '../utils/helpers'; +import { setupClient } from '../utils/setup'; let runTests = true; diff --git a/packages/sdk/src/__test__/e2e/signing/bls.test.ts b/packages/sdk/src/__test__/e2e/signing/bls.test.ts index b676805b..5116377b 100644 --- a/packages/sdk/src/__test__/e2e/signing/bls.test.ts +++ b/packages/sdk/src/__test__/e2e/signing/bls.test.ts @@ -27,12 +27,14 @@ import { question } from 'readline-sync'; import { Constants } from '../../../index'; import { getPathStr } from '../../../shared/utilities'; -import { setupClient } from '../../utils/setup'; import { getEncPw } from '../../utils/getters'; import { buildPath } from '../../utils/helpers'; +import { setupClient } from '../../utils/setup'; import { TEST_SEED } from '../../utils/testConstants'; -let client, encPw, supportsBLS; +let client; +let encPw; +let supportsBLS; const DEPOSIT_PATH = [12381, 3600, 0, 0, 0]; const WITHDRAWAL_PATH = [12381, 3600, 0, 0]; // Number of signers to test for each of deposit and withdrawal paths @@ -59,12 +61,12 @@ describe('[BLS keys]', () => { : 'unknown'; console.log(`\n[BLS Test] Firmware version: ${versionStr}`); - console.log(`[BLS Test] Raw fwVersion buffer:`, fwVersion); + console.log('[BLS Test] Raw fwVersion buffer:', fwVersion); const fwConstants = client.getFwConstants(); - console.log(`[BLS Test] getAddressFlags:`, fwConstants?.getAddressFlags); + console.log('[BLS Test] getAddressFlags:', fwConstants?.getAddressFlags); console.log( - `[BLS Test] BLS12_381_G1_PUB constant:`, + '[BLS Test] BLS12_381_G1_PUB constant:', Constants.GET_ADDR_FLAGS.BLS12_381_G1_PUB, ); @@ -76,8 +78,7 @@ describe('[BLS keys]', () => { if (!supportsBLS) { console.warn( - `\nSkipping BLS tests: Firmware version ${versionStr} does not support BLS operations.\n` + - `BLS support requires firmware version >= 0.17.0\n`, + `\nSkipping BLS tests: Firmware version ${versionStr} does not support BLS operations.\nBLS support requires firmware version >= 0.17.0\n`, ); } }); diff --git a/packages/sdk/src/__test__/e2e/signing/determinism.test.ts b/packages/sdk/src/__test__/e2e/signing/determinism.test.ts index 1b13e4b6..51aae196 100644 --- a/packages/sdk/src/__test__/e2e/signing/determinism.test.ts +++ b/packages/sdk/src/__test__/e2e/signing/determinism.test.ts @@ -1,13 +1,5 @@ -/** - * REQUIRED TEST MNEMONIC: - * These tests require a SafeCard loaded with the standard test mnemonic: - * "test test test test test test test test test test test junk" - * - * Running with a different mnemonic will cause test failures due to - * incorrect address derivations and signature mismatches. - */ -import { getDeviceId } from '../../utils/getters'; import { HARDENED_OFFSET } from '../../../constants'; +import type { WalletPath } from '../../../types'; import { randomBytes } from '../../../util'; import { DEFAULT_SIGNER, @@ -18,14 +10,22 @@ import { } from '../../utils/builders'; import { deriveAddress, - signPersonalJS, signEip712JS, + signPersonalJS, testUniformSigs, } from '../../utils/determinism'; +/** + * REQUIRED TEST MNEMONIC: + * These tests require a SafeCard loaded with the standard test mnemonic: + * "test test test test test test test test test test test junk" + * + * Running with a different mnemonic will cause test failures due to + * incorrect address derivations and signature mismatches. + */ +import { getDeviceId } from '../../utils/getters'; import { BTC_PURPOSE_P2PKH, ETH_COIN, getSigStr } from '../../utils/helpers'; import { setupClient } from '../../utils/setup'; import { TEST_SEED } from '../../utils/testConstants'; -import type { WalletPath } from '../../../types'; describe('[Determinism]', () => { let client; diff --git a/packages/sdk/src/__test__/e2e/signing/eip712-msg.test.ts b/packages/sdk/src/__test__/e2e/signing/eip712-msg.test.ts index 18a5f6f0..8fec45b5 100644 --- a/packages/sdk/src/__test__/e2e/signing/eip712-msg.test.ts +++ b/packages/sdk/src/__test__/e2e/signing/eip712-msg.test.ts @@ -1,3 +1,4 @@ +import { setupClient } from '../../utils/setup'; /** * EIP-712 Typed Data Message Signing Test Suite * @@ -5,7 +6,6 @@ * Replaces the forge-based contract test with a pure signature comparison approach. */ import { signAndCompareEIP712Message } from '../../utils/viemComparison'; -import { setupClient } from '../../utils/setup'; import { EIP712_MESSAGE_VECTORS } from './eip712-vectors'; describe('EIP-712 Message Signing - Viem Compatibility', () => { diff --git a/packages/sdk/src/__test__/e2e/signing/solana/solana.test.ts b/packages/sdk/src/__test__/e2e/signing/solana/solana.test.ts index 47f463f9..0b653a6f 100644 --- a/packages/sdk/src/__test__/e2e/signing/solana/solana.test.ts +++ b/packages/sdk/src/__test__/e2e/signing/solana/solana.test.ts @@ -15,10 +15,10 @@ import { import { Constants } from '../../../..'; import { HARDENED_OFFSET } from '../../../../constants'; import { ensureHexBuffer } from '../../../../util'; -import { setupClient } from '../../../utils/setup'; import { getPrng } from '../../../utils/getters'; import { deriveED25519Key, prandomBuf } from '../../../utils/helpers'; import { runGeneric } from '../../../utils/runners'; +import { setupClient } from '../../../utils/setup'; import { TEST_SEED } from '../../../utils/testConstants'; //--------------------------------------- diff --git a/packages/sdk/src/__test__/e2e/signing/solana/solana.versioned.test.ts b/packages/sdk/src/__test__/e2e/signing/solana/solana.versioned.test.ts index 87d7e24f..249d05c5 100644 --- a/packages/sdk/src/__test__/e2e/signing/solana/solana.versioned.test.ts +++ b/packages/sdk/src/__test__/e2e/signing/solana/solana.versioned.test.ts @@ -14,7 +14,7 @@ import { PublicKey, SystemProgram, Transaction, - TransactionInstruction, + type TransactionInstruction, TransactionMessage, VersionedTransaction, } from '@solana/web3.js'; diff --git a/packages/sdk/src/__test__/e2e/signing/unformatted.test.ts b/packages/sdk/src/__test__/e2e/signing/unformatted.test.ts index c76ea72c..a9e0df88 100644 --- a/packages/sdk/src/__test__/e2e/signing/unformatted.test.ts +++ b/packages/sdk/src/__test__/e2e/signing/unformatted.test.ts @@ -1,9 +1,9 @@ import { Constants } from '../../..'; +import { HARDENED_OFFSET } from '../../../constants'; import { getNumIter } from '../../utils/builders'; +import { getPrng } from '../../utils/getters'; import { ethPersonalSignMsg, prandomBuf } from '../../utils/helpers'; import { runGeneric } from '../../utils/runners'; -import { HARDENED_OFFSET } from '../../../constants'; -import { getPrng } from '../../utils/getters'; import { setupClient } from '../../utils/setup'; const prng = getPrng(); @@ -129,10 +129,10 @@ describe('[Unformatted]', () => { }; const respLegacy = await client.sign(legacyReq); - const genSigR = respGeneric.sig?.r.toString('hex') ?? ''; - const genSigS = respGeneric.sig?.s.toString('hex') ?? ''; - const legSigR = respLegacy.sig?.r.toString('hex') ?? ''; - const legSigS = respLegacy.sig?.s.toString('hex') ?? ''; + const genSigR = (respGeneric.sig?.r as Buffer)?.toString('hex') ?? ''; + const genSigS = (respGeneric.sig?.s as Buffer)?.toString('hex') ?? ''; + const legSigR = (respLegacy.sig?.r as Buffer)?.toString('hex') ?? ''; + const legSigS = (respLegacy.sig?.s as Buffer)?.toString('hex') ?? ''; const genSig = `${genSigR}${genSigS}`; const legSig = `${legSigR}${legSigS}`; diff --git a/packages/sdk/src/__test__/e2e/signing/vectors.ts b/packages/sdk/src/__test__/e2e/signing/vectors.ts index df5bf1c6..752df529 100644 --- a/packages/sdk/src/__test__/e2e/signing/vectors.ts +++ b/packages/sdk/src/__test__/e2e/signing/vectors.ts @@ -467,7 +467,7 @@ export const EDGE_CASE_TEST_VECTORS: TestVector[] = [ type: 'eip1559', to: '0xe242e54155b1abc71fc118065270cecaaf8b7768' as `0x${string}`, value: BigInt(0), - data: ('0x' + 'a'.repeat(1000)) as `0x${string}`, // Large data payload + data: `0x${'a'.repeat(1000)}` as `0x${string}`, // Large data payload nonce: 0, maxFeePerGas: BigInt('30000000000'), // 30 gwei maxPriorityFeePerGas: BigInt('2000000000'), // 2 gwei @@ -632,7 +632,7 @@ export const EDGE_CASE_TEST_VECTORS: TestVector[] = [ type: 'eip1559', to: undefined, // Contract creation value: BigInt(0), - data: ('0x' + '60'.repeat(96)) as `0x${string}`, // Simple contract bytecode + data: `0x${'60'.repeat(96)}` as `0x${string}`, // Simple contract bytecode nonce: 0, maxFeePerGas: BigInt('30000000000'), // 30 gwei maxPriorityFeePerGas: BigInt('2000000000'), // 2 gwei @@ -647,7 +647,7 @@ export const EDGE_CASE_TEST_VECTORS: TestVector[] = [ type: 'eip2930', to: undefined, // Contract creation value: BigInt(0), - data: ('0x' + '60'.repeat(96)) as `0x${string}`, // Simple contract bytecode + data: `0x${'60'.repeat(96)}` as `0x${string}`, // Simple contract bytecode nonce: 0, gasPrice: BigInt('25000000000'), // 25 gwei gas: BigInt('2000000'), // High gas for contract creation @@ -865,7 +865,7 @@ export const PAYLOAD_SIZE_VECTORS: TestVector[] = [ maxPriorityFeePerGas: BigInt('2000000000'), // 2 gwei gas: BigInt('50000'), chainId: 1, - data: ('0x' + '00'.repeat(32)) as `0x${string}`, + data: `0x${'00'.repeat(32)}` as `0x${string}`, }, category: 'small-data', }, @@ -880,7 +880,7 @@ export const PAYLOAD_SIZE_VECTORS: TestVector[] = [ maxPriorityFeePerGas: BigInt('2000000000'), // 2 gwei gas: BigInt('100000'), chainId: 1, - data: ('0x' + 'ab'.repeat(256)) as `0x${string}`, + data: `0x${'ab'.repeat(256)}` as `0x${string}`, }, category: 'medium-data', }, @@ -895,7 +895,7 @@ export const PAYLOAD_SIZE_VECTORS: TestVector[] = [ maxPriorityFeePerGas: BigInt('3000000000'), // 3 gwei gas: BigInt('500000'), chainId: 1, - data: ('0x' + 'cd'.repeat(1024)) as `0x${string}`, + data: `0x${'cd'.repeat(1024)}` as `0x${string}`, }, category: 'large-data', }, @@ -910,7 +910,7 @@ export const PAYLOAD_SIZE_VECTORS: TestVector[] = [ maxPriorityFeePerGas: BigInt('3000000000'), // 3 gwei gas: BigInt('1000000'), chainId: 1, - data: ('0x' + 'ef'.repeat(2000)) as `0x${string}`, + data: `0x${'ef'.repeat(2000)}` as `0x${string}`, }, category: 'very-large-data', }, @@ -1021,7 +1021,7 @@ export const REAL_WORLD_PATTERN_VECTORS: TestVector[] = [ type: 'eip1559', to: '0xe242e54155b1abc71fc118065270cecaaf8b7768' as `0x${string}`, value: BigInt(0), - data: ('0x8d80ff0a' + '00'.repeat(500)) as `0x${string}`, // multiSend with batch data + data: `0x8d80ff0a${'00'.repeat(500)}` as `0x${string}`, // multiSend with batch data nonce: 15, maxFeePerGas: BigInt('40000000000'), // 40 gwei maxPriorityFeePerGas: BigInt('4000000000'), // 4 gwei @@ -1060,7 +1060,7 @@ export function getVectorsByCategory(category: string): TestVector[] { /** * Get a specific number of vectors from each transaction type for balanced testing */ -export function getBalancedTestVectors(perType: number = 3): TestVector[] { +export function getBalancedTestVectors(perType = 3): TestVector[] { const legacyVectors = LEGACY_VECTORS.slice(0, perType); const eip1559Vectors = EIP1559_TEST_VECTORS.slice(0, perType); const eip2930Vectors = EIP2930_TEST_VECTORS.slice(0, perType); diff --git a/packages/sdk/src/__test__/integration/__mocks__/handlers.ts b/packages/sdk/src/__test__/integration/__mocks__/handlers.ts index 88258130..80e26132 100644 --- a/packages/sdk/src/__test__/integration/__mocks__/handlers.ts +++ b/packages/sdk/src/__test__/integration/__mocks__/handlers.ts @@ -1,25 +1,25 @@ import { http, HttpResponse } from 'msw'; -import connectResponse from './connect.json'; -import getAddressesResponse from './getAddresses.json'; -import signResponse from './sign.json'; -import fetchActiveWalletResponse from './fetchActiveWallet.json'; -import addKvRecordsResponse from './addKvRecords.json'; -import getKvRecordsResponse from './getKvRecords.json'; -import removeKvRecordsResponse from './removeKvRecords.json'; -import { - etherscanResponse0x06412d7e, - etherscanResponse0x7a250d56, - etherscanResponse0xa0b86991, - etherscanResponse0xc36442b6, -} from './etherscan'; import { fourbyteResponse0c49ccbe, - fourbyteResponse0x38ed1739, fourbyteResponse0x6a761202, + fourbyteResponse0x38ed1739, fourbyteResponse0xa9059cbb, fourbyteResponseac9650d8, fourbyteResponsefc6f7865, } from './4byte'; +import addKvRecordsResponse from './addKvRecords.json'; +import connectResponse from './connect.json'; +import { + etherscanResponse0x06412d7e, + etherscanResponse0x7a250d56, + etherscanResponse0xa0b86991, + etherscanResponse0xc36442b6, +} from './etherscan'; +import fetchActiveWalletResponse from './fetchActiveWallet.json'; +import getAddressesResponse from './getAddresses.json'; +import getKvRecordsResponse from './getKvRecords.json'; +import removeKvRecordsResponse from './removeKvRecords.json'; +import signResponse from './sign.json'; export const handlers = [ http.post('https://signing.gridpl.us/test/connect', () => { diff --git a/packages/sdk/src/__test__/unit/compareEIP7702Serialization.test.ts b/packages/sdk/src/__test__/unit/compareEIP7702Serialization.test.ts index 618fa50f..97e54ef4 100644 --- a/packages/sdk/src/__test__/unit/compareEIP7702Serialization.test.ts +++ b/packages/sdk/src/__test__/unit/compareEIP7702Serialization.test.ts @@ -151,16 +151,12 @@ describe('EIP7702 Transaction Serialization Comparison', () => { const viemSerialized = serializeTransaction(viemTx as any); // Compute hashes for comparison - const ourHash = - '0x' + - Buffer.from( - Hash.keccak256(Buffer.from(ourSerialized.slice(2), 'hex')), - ).toString('hex'); - const viemHash = - '0x' + - Buffer.from( - Hash.keccak256(Buffer.from(viemSerialized.slice(2), 'hex')), - ).toString('hex'); + const ourHash = `0x${Buffer.from( + Hash.keccak256(Buffer.from(ourSerialized.slice(2), 'hex')), + ).toString('hex')}`; + const viemHash = `0x${Buffer.from( + Hash.keccak256(Buffer.from(viemSerialized.slice(2), 'hex')), + ).toString('hex')}`; // Output for debugging console.log('Our serialized:', ourSerialized); @@ -251,16 +247,12 @@ describe('EIP7702 Transaction Serialization Comparison', () => { const viemSerialized = serializeTransaction(viemTx as any); // Compute hashes for comparison - const ourHash = - '0x' + - Buffer.from( - Hash.keccak256(Buffer.from(ourSerialized.slice(2), 'hex')), - ).toString('hex'); - const viemHash = - '0x' + - Buffer.from( - Hash.keccak256(Buffer.from(viemSerialized.slice(2), 'hex')), - ).toString('hex'); + const ourHash = `0x${Buffer.from( + Hash.keccak256(Buffer.from(ourSerialized.slice(2), 'hex')), + ).toString('hex')}`; + const viemHash = `0x${Buffer.from( + Hash.keccak256(Buffer.from(viemSerialized.slice(2), 'hex')), + ).toString('hex')}`; // Output for debugging console.log('Our serialized (auth list):', ourSerialized); @@ -332,16 +324,12 @@ describe('EIP7702 Transaction Serialization Comparison', () => { const viemSerialized = serializeTransaction(viemTx as any); // Compute hashes for comparison - const ourHash = - '0x' + - Buffer.from( - Hash.keccak256(Buffer.from(ourSerialized.slice(2), 'hex')), - ).toString('hex'); - const viemHash = - '0x' + - Buffer.from( - Hash.keccak256(Buffer.from(viemSerialized.slice(2), 'hex')), - ).toString('hex'); + const ourHash = `0x${Buffer.from( + Hash.keccak256(Buffer.from(ourSerialized.slice(2), 'hex')), + ).toString('hex')}`; + const viemHash = `0x${Buffer.from( + Hash.keccak256(Buffer.from(viemSerialized.slice(2), 'hex')), + ).toString('hex')}`; // Output for debugging console.log('Our serialized (realistic):', ourSerialized); @@ -412,16 +400,12 @@ describe('EIP7702 Transaction Serialization Comparison', () => { const viemSerialized = serializeTransaction(viemTx as any); // Compute hashes for comparison - const ourHash = - '0x' + - Buffer.from( - Hash.keccak256(Buffer.from(ourSerialized.slice(2), 'hex')), - ).toString('hex'); - const viemHash = - '0x' + - Buffer.from( - Hash.keccak256(Buffer.from(viemSerialized.slice(2), 'hex')), - ).toString('hex'); + const ourHash = `0x${Buffer.from( + Hash.keccak256(Buffer.from(ourSerialized.slice(2), 'hex')), + ).toString('hex')}`; + const viemHash = `0x${Buffer.from( + Hash.keccak256(Buffer.from(viemSerialized.slice(2), 'hex')), + ).toString('hex')}`; // Output for debugging console.log('Our serialized (contract auth):', ourSerialized); diff --git a/packages/sdk/src/__test__/unit/decoders.test.ts b/packages/sdk/src/__test__/unit/decoders.test.ts index 3039cf17..9ab692fc 100644 --- a/packages/sdk/src/__test__/unit/decoders.test.ts +++ b/packages/sdk/src/__test__/unit/decoders.test.ts @@ -5,7 +5,7 @@ import { decodeGetKvRecordsResponse, decodeSignResponse, } from '../../functions'; -import { DecodeSignResponseParams } from '../../types'; +import type { DecodeSignResponseParams } from '../../types'; import { clientKeyPair, connectDecoderData, diff --git a/packages/sdk/src/__test__/unit/eip7702.test.ts b/packages/sdk/src/__test__/unit/eip7702.test.ts index 9c4329db..d9789ea7 100644 --- a/packages/sdk/src/__test__/unit/eip7702.test.ts +++ b/packages/sdk/src/__test__/unit/eip7702.test.ts @@ -1,10 +1,10 @@ -import { - EIP7702AuthTransactionRequest, +import { Hash } from 'ox'; +import { parseEther, toHex } from 'viem'; +import { serializeEIP7702Transaction } from '../../ethereum'; +import type { EIP7702AuthListTransactionRequest, + EIP7702AuthTransactionRequest, } from '../../types'; -import { serializeEIP7702Transaction } from '../../ethereum'; -import { parseEther, toHex } from 'viem'; -import { Hash } from 'ox'; describe('EIP-7702 Transaction Serialization', () => { /** @@ -45,11 +45,9 @@ describe('EIP-7702 Transaction Serialization', () => { const serialized = serializeEIP7702Transaction(tx); // Compute the keccak256 hash of the serialized transaction - const txHash = - '0x' + - Buffer.from( - Hash.keccak256(Buffer.from(serialized.slice(2), 'hex')), - ).toString('hex'); + const txHash = `0x${Buffer.from( + Hash.keccak256(Buffer.from(serialized.slice(2), 'hex')), + ).toString('hex')}`; // Store the serialized value for debugging console.log('Serialized transaction:', serialized); @@ -106,11 +104,9 @@ describe('EIP-7702 Transaction Serialization', () => { const serialized = serializeEIP7702Transaction(tx); // Compute the keccak256 hash of the serialized transaction - const txHash = - '0x' + - Buffer.from( - Hash.keccak256(Buffer.from(serialized.slice(2), 'hex')), - ).toString('hex'); + const txHash = `0x${Buffer.from( + Hash.keccak256(Buffer.from(serialized.slice(2), 'hex')), + ).toString('hex')}`; // Store the serialized value for debugging console.log('Serialized auth list transaction:', serialized); @@ -162,11 +158,9 @@ describe('EIP-7702 Transaction Serialization', () => { const serialized = serializeEIP7702Transaction(tx); // Compute the keccak256 hash of the serialized transaction - const txHash = - '0x' + - Buffer.from( - Hash.keccak256(Buffer.from(serialized.slice(2), 'hex')), - ).toString('hex'); + const txHash = `0x${Buffer.from( + Hash.keccak256(Buffer.from(serialized.slice(2), 'hex')), + ).toString('hex')}`; console.log('Reference serialized transaction:', serialized); console.log('Reference transaction hash:', txHash); diff --git a/packages/sdk/src/__test__/unit/ethereum.validate.test.ts b/packages/sdk/src/__test__/unit/ethereum.validate.test.ts index dac12126..5353b5fb 100644 --- a/packages/sdk/src/__test__/unit/ethereum.validate.test.ts +++ b/packages/sdk/src/__test__/unit/ethereum.validate.test.ts @@ -1,14 +1,14 @@ import { + type MessageTypes, SignTypedDataVersion, TypedDataUtils, - type MessageTypes, type TypedMessage, } from '@metamask/eth-sig-util'; import { ecsign, privateToAddress } from 'ethereumjs-util'; import { mnemonicToAccount } from 'viem/accounts'; import { HARDENED_OFFSET } from '../../constants'; import ethereum from '../../ethereum'; -import { buildFirmwareConstants, DEFAULT_SIGNER } from '../utils/builders'; +import { DEFAULT_SIGNER, buildFirmwareConstants } from '../utils/builders'; import { TEST_MNEMONIC } from '../utils/testConstants'; const typedData: TypedMessage = { diff --git a/packages/sdk/src/__test__/unit/module.interop.test.ts b/packages/sdk/src/__test__/unit/module.interop.test.ts index 89127b8e..98bed751 100644 --- a/packages/sdk/src/__test__/unit/module.interop.test.ts +++ b/packages/sdk/src/__test__/unit/module.interop.test.ts @@ -1,3 +1,4 @@ +import { execSync, spawnSync } from 'node:child_process'; import { existsSync, mkdirSync, @@ -8,7 +9,6 @@ import { import os from 'node:os'; import path from 'node:path'; import { fileURLToPath } from 'node:url'; -import { execSync, spawnSync } from 'node:child_process'; const __filename = fileURLToPath(import.meta.url); const __dirname = path.dirname(__filename); diff --git a/packages/sdk/src/__test__/unit/parseGenericSigningResponse.test.ts b/packages/sdk/src/__test__/unit/parseGenericSigningResponse.test.ts index e9750546..ab301fdf 100644 --- a/packages/sdk/src/__test__/unit/parseGenericSigningResponse.test.ts +++ b/packages/sdk/src/__test__/unit/parseGenericSigningResponse.test.ts @@ -1,5 +1,5 @@ -import { RLP } from '@ethereumjs/rlp'; import { Buffer } from 'buffer'; +import { RLP } from '@ethereumjs/rlp'; import { Hash } from 'ox'; import secp256k1 from 'secp256k1'; import { parseGenericSigningResponse } from '../../genericSigning'; diff --git a/packages/sdk/src/__test__/unit/signatureUtils.test.ts b/packages/sdk/src/__test__/unit/signatureUtils.test.ts index e645d176..af4e93b8 100644 --- a/packages/sdk/src/__test__/unit/signatureUtils.test.ts +++ b/packages/sdk/src/__test__/unit/signatureUtils.test.ts @@ -119,7 +119,7 @@ describe('getYParity', () => { it('should handle hex string as pre-computed hash', () => { // When passing a hex string, it's treated as a pre-computed hash const hash = randomBytes(32); - const txHex = '0x' + hash.toString('hex'); + const txHex = `0x${hash.toString('hex')}`; const { signature, publicKey, recovery } = createValidSignature(hash); const resp = { @@ -424,7 +424,7 @@ describe('getV function', () => { ), }, // This is a fake pubkey, so recovery will fail - pubkey: Buffer.from('04' + '1'.repeat(128), 'hex'), + pubkey: Buffer.from(`04${'1'.repeat(128)}`, 'hex'), }; expect(() => getV(signedTx, mockResp)).toThrow(); @@ -436,10 +436,10 @@ describe('getV function', () => { const mockResp = { sig: { - r: '0x' + '1'.repeat(64), // 32 bytes as hex string - s: '0x' + '2'.repeat(64), // 32 bytes as hex string + r: `0x${'1'.repeat(64)}`, // 32 bytes as hex string + s: `0x${'2'.repeat(64)}`, // 32 bytes as hex string }, - pubkey: Buffer.from('04' + '1'.repeat(128), 'hex'), + pubkey: Buffer.from(`04${'1'.repeat(128)}`, 'hex'), }; expect(() => getV(txHex, mockResp)).toThrow(); diff --git a/packages/sdk/src/__test__/unit/validators.test.ts b/packages/sdk/src/__test__/unit/validators.test.ts index 9f0b6522..a50978ed 100644 --- a/packages/sdk/src/__test__/unit/validators.test.ts +++ b/packages/sdk/src/__test__/unit/validators.test.ts @@ -1,3 +1,4 @@ +import { normalizeToViemTransaction } from '../../ethereum'; import { validateAddKvRequest, validateConnectRequest, @@ -9,7 +10,6 @@ import { isValid4ByteResponse, isValidBlockExplorerResponse, } from '../../shared/validators'; -import { normalizeToViemTransaction } from '../../ethereum'; import { buildGetAddressesObject, buildValidateConnectObject, @@ -147,11 +147,11 @@ describe('validators', () => { describe('EIP-7702 transactions', () => { test('rejects missing fee fields', () => { const tx = { - to: '0x' + '1'.repeat(40), + to: `0x${'1'.repeat(40)}`, value: '1000000000000000000', chainId: 1, authorizationList: [ - { chainId: 1, address: '0x' + '2'.repeat(40), nonce: 0 }, + { chainId: 1, address: `0x${'2'.repeat(40)}`, nonce: 0 }, ], gasPrice: '15000000000', }; @@ -163,7 +163,7 @@ describe('validators', () => { describe('negative values', () => { test('rejects negative value', () => { const tx = { - to: '0x' + '1'.repeat(40), + to: `0x${'1'.repeat(40)}`, value: -100, gasPrice: '10000000000', }; @@ -173,7 +173,7 @@ describe('validators', () => { test('rejects negative nonce', () => { const tx = { - to: '0x' + '1'.repeat(40), + to: `0x${'1'.repeat(40)}`, value: '100', gasPrice: '10000000000', nonce: -1, @@ -184,7 +184,7 @@ describe('validators', () => { test('rejects negative gas price', () => { const tx = { - to: '0x' + '1'.repeat(40), + to: `0x${'1'.repeat(40)}`, value: '100', gasPrice: -10, }; @@ -199,7 +199,7 @@ describe('validators', () => { to: '0x1234567890123456789012345678901234567890', value: true, chainId: '0x1', - gasPrice: NaN, + gasPrice: Number.NaN, nonce: null, data: false, }; diff --git a/packages/sdk/src/__test__/utils/builders.ts b/packages/sdk/src/__test__/utils/builders.ts index 78f07bfe..92698337 100644 --- a/packages/sdk/src/__test__/utils/builders.ts +++ b/packages/sdk/src/__test__/utils/builders.ts @@ -1,23 +1,23 @@ import { Common, Hardfork, Mainnet } from '@ethereumjs/common'; import { RLP } from '@ethereumjs/rlp'; -import { createTx, type TypedTransaction } from '@ethereumjs/tx'; +import { type TypedTransaction, createTx } from '@ethereumjs/tx'; import { generate as randomWords } from 'random-words'; import { Constants } from '../..'; import { Client } from '../../client'; import { CURRENCIES, - getFwVersionConst, HARDENED_OFFSET, + getFwVersionConst, } from '../../constants'; -import type { Currency, SigningPath, SignRequestParams } from '../../types'; +import type { Currency, SignRequestParams, SigningPath } from '../../types'; import type { FirmwareConstants } from '../../types/firmware'; import { randomBytes } from '../../util'; import { MSG_PAYLOAD_METADATA_SZ } from './constants'; import { getN, getPrng } from './getters'; import { BTC_PURPOSE_P2PKH, - buildRandomEip712Object, ETH_COIN, + buildRandomEip712Object, getTestVectors, } from './helpers'; @@ -120,7 +120,7 @@ export const buildSharedSecret = () => { }; export const getNumIter = (n: number | string | undefined = getN()) => - n ? parseInt(`${n}`) : 5; + n ? Number.parseInt(`${n}`) : 5; /** Generate a bunch of random test vectors using the PRNG */ export const buildRandomVectors = (n: number | string | undefined = getN()) => { @@ -288,14 +288,14 @@ export const buildEncDefs = (vectors: any) => { return { encDefs, encDefsCalldata }; }; -export function buildRandomMsg(type = 'signPersonal', client: Client) { +export function buildRandomMsg(type, client: Client) { function randInt(n: number) { return Math.floor(n * prng.quick()); } if (type === 'signPersonal') { // A random string will do - const isHexStr = randInt(2) > 0 ? true : false; + const isHexStr = randInt(2) > 0; const fwConstants = client.getFwConstants(); const L = randInt(fwConstants.ethMaxDataSz - MSG_PAYLOAD_METADATA_SZ); if (isHexStr) return `0x${randomBytes(L).toString('hex')}`; diff --git a/packages/sdk/src/__test__/utils/ethers.ts b/packages/sdk/src/__test__/utils/ethers.ts index d77aa3e3..d3dc6ddd 100644 --- a/packages/sdk/src/__test__/utils/ethers.ts +++ b/packages/sdk/src/__test__/utils/ethers.ts @@ -28,9 +28,9 @@ function getConvertedDef(def) { const converted: any[] = []; def.forEach((param) => { const arrSzs = param[3]; - const evmType = EVM_TYPES[parseInt(param[1].toString('hex'), 16)]; + const evmType = EVM_TYPES[Number.parseInt(param[1].toString('hex'), 16)]; let type = evmType; - const numBytes = parseInt(param[2].toString('hex'), 16); + const numBytes = Number.parseInt(param[2].toString('hex'), 16); if (numBytes > 0) { type = `${type}${numBytes * 8}`; } @@ -51,8 +51,8 @@ function getConvertedDef(def) { const funcData = tupleData ? tupleData : genParamData(param); // Apply the data to arrays for (let i = 0; i < arrSzs.length; i++) { - const sz = parseInt(arrSzs[i].toString('hex')); - if (isNaN(sz)) { + const sz = Number.parseInt(arrSzs[i].toString('hex')); + if (Number.isNaN(sz)) { // This is a 0 size, which means we need to // define a size to generate data type = `${type}[]`; @@ -77,7 +77,7 @@ function genTupleData(tupleParam) { tupleParam.forEach((nestedParam) => { nestedData.push( genData( - EVM_TYPES[parseInt(nestedParam[1].toString('hex'), 16)] ?? '', + EVM_TYPES[Number.parseInt(nestedParam[1].toString('hex'), 16)] ?? '', nestedParam, ), ); @@ -86,19 +86,21 @@ function genTupleData(tupleParam) { } function genParamData(param: any[]) { - const evmType = EVM_TYPES[parseInt(param[1].toString('hex'), 16)] ?? ''; + const evmType = + EVM_TYPES[Number.parseInt(param[1].toString('hex'), 16)] ?? ''; const baseData = genData(evmType, param); return getArrayData(param, baseData); } function getArrayData(param: any, baseData: any) { - let arrayData, data; + let arrayData; + let data; const arrSzs = param[3]; for (let i = 0; i < arrSzs.length; i++) { // let sz = parseInt(arrSzs[i].toString('hex')); TODO: fix this const dimData: any = []; - let sz = parseInt(param[3][i].toString('hex')); - if (isNaN(sz)) { + let sz = Number.parseInt(param[3][i].toString('hex')); + if (Number.isNaN(sz)) { sz = 2; //1; } if (!arrayData) { diff --git a/packages/sdk/src/__test__/utils/getters.ts b/packages/sdk/src/__test__/utils/getters.ts index efdc4434..f9ccf730 100644 --- a/packages/sdk/src/__test__/utils/getters.ts +++ b/packages/sdk/src/__test__/utils/getters.ts @@ -4,12 +4,12 @@ export const getEnv = () => { if (!process.env) throw new Error('env cannot be found'); return process.env; }; -export const getDeviceId = (): string => getEnv()['DEVICE_ID'] ?? ''; -export const getN = (): number => parseInt(getEnv()['N'] ?? '5'); -export const getSeed = (): string => getEnv()['SEED'] ?? 'myrandomseed'; -export const getTestnet = (): string => getEnv()['TESTNET'] ?? ''; -export const getEtherscanKey = (): string => getEnv()['ETHERSCAN_KEY'] ?? ''; -export const getEncPw = (): string => getEnv()['ENC_PW'] ?? null; +export const getDeviceId = (): string => getEnv().DEVICE_ID ?? ''; +export const getN = (): number => Number.parseInt(getEnv().N ?? '5'); +export const getSeed = (): string => getEnv().SEED ?? 'myrandomseed'; +export const getTestnet = (): string => getEnv().TESTNET ?? ''; +export const getEtherscanKey = (): string => getEnv().ETHERSCAN_KEY ?? ''; +export const getEncPw = (): string => getEnv().ENC_PW ?? null; export const getPrng = (seed?: string) => { return seedrandom(seed ? seed : getSeed()); diff --git a/packages/sdk/src/__test__/utils/helpers.ts b/packages/sdk/src/__test__/utils/helpers.ts index 749919e6..c0c51109 100644 --- a/packages/sdk/src/__test__/utils/helpers.ts +++ b/packages/sdk/src/__test__/utils/helpers.ts @@ -20,8 +20,8 @@ import { Constants } from '../..'; import { Client } from '../../client'; import { BIP_CONSTANTS, - ethMsgProtocol, HARDENED_OFFSET, + ethMsgProtocol, } from '../../constants'; import { ProtocolConstants } from '../../protocol'; import { getPathStr } from '../../shared/utilities'; @@ -138,7 +138,7 @@ export function setupTestClient( // Separate check -- if we are connecting for the first time but want to be able // to reconnect quickly with the same device ID as an env var, we need to pair // with a reusable key - if (parseInt(env.REUSE_KEY) === 1) { + if (Number.parseInt(env.REUSE_KEY) === 1) { setup.privKey = Buffer.from(REUSABLE_KEY, 'hex'); } // Initialize a global SDK client @@ -471,8 +471,8 @@ export const gpErrors = { //--------------------------------------------------- export const getCodeMsg = (code, expected) => { if (code !== expected) { - let codeTxt = code, - expectedTxt = expected; + let codeTxt = code; + let expectedTxt = expected; Object.keys(gpErrors).forEach((key) => { if (code === gpErrors[key]) { codeTxt = key; @@ -686,7 +686,7 @@ export const validateDerivedPublicKeys = ( }; export const ethPersonalSignMsg = (msg) => - '\u0019Ethereum Signed Message:\n' + String(msg.length) + msg; + `\u0019Ethereum Signed Message:\n${String(msg.length)}${msg}`; //--------------------------------------------------- // Sign Transaction helpers @@ -845,7 +845,7 @@ export const serializeLoadSeedJobData = (data) => { //--------------------------------------------------- export const buildRandomEip712Object = (randInt) => { function randStr(n) { - const words = wordlists['english']; + const words = wordlists.english; let s = ''; while (s.length < n) { s += `${words?.[randInt(words?.length)]}_`; @@ -869,17 +869,17 @@ export const buildRandomEip712Object = (randInt) => { } function getRandomEIP712Val(type) { if (type !== 'bytes' && type.slice(0, 5) === 'bytes') { - return `0x${randomBytes(parseInt(type.slice(5))).toString('hex')}`; + return `0x${randomBytes(Number.parseInt(type.slice(5))).toString('hex')}`; } if (type === 'uint' || type.indexOf('uint') === 0) { - const bits = parseInt(type.slice(4) || '256', 10); + const bits = Number.parseInt(type.slice(4) || '256', 10); const byteLength = Math.max(1, Math.ceil(bits / 8)); return `0x${randomBytes(byteLength).toString('hex')}`; } if (type === 'int' || type.indexOf('int') === 0) { - const bits = parseInt(type.slice(3) || '256', 10); + const bits = Number.parseInt(type.slice(3) || '256', 10); const byteLength = Math.max(1, Math.ceil(bits / 8)); const raw = randomBytes(byteLength).toString('hex'); const modulus = 1n << BigInt(bits); @@ -897,7 +897,7 @@ export const buildRandomEip712Object = (randInt) => { case 'string': return randStr(100); case 'bool': - return randInt(1) > 0 ? true : false; + return randInt(1) > 0; case 'address': return `0x${randomBytes(20).toString('hex')}`; default: @@ -1035,7 +1035,7 @@ export const getSigStr = (resp: any, tx?: TypedTransaction) => { if (resp.sig.v !== undefined) { const vBuf = normalizeSigComponent(resp.sig.v); const vHex = vBuf.toString('hex'); - let vInt = vHex ? parseInt(vHex, 16) : 0; + let vInt = vHex ? Number.parseInt(vHex, 16) : 0; if (!Number.isFinite(vInt)) { vInt = 0; } diff --git a/packages/sdk/src/__test__/utils/runners.ts b/packages/sdk/src/__test__/utils/runners.ts index 4db6bf4f..1302e5a7 100644 --- a/packages/sdk/src/__test__/utils/runners.ts +++ b/packages/sdk/src/__test__/utils/runners.ts @@ -1,9 +1,13 @@ -import { Client } from '../../client'; -import type { TestRequestPayload, SignRequestParams } from '../../types'; +import type { Client } from '../../client'; import { getEncodedPayload } from '../../genericSigning'; +import type { + SigningPayload, + SignRequestParams, + TestRequestPayload, +} from '../../types'; import { parseWalletJobResp, validateGenericSig } from './helpers'; -import { testRequest } from './testRequest'; import { TEST_SEED } from './testConstants'; +import { testRequest } from './testRequest'; export async function runTestCase( payload: TestRequestPayload, @@ -19,22 +23,18 @@ export async function runTestCase( export async function runGeneric(request: SignRequestParams, client: Client) { const response = await client.sign(request); + // runGeneric is only used for generic signing, not Bitcoin + const data = request.data as SigningPayload; // If no encoding type is specified we encode in hex or ascii - const encodingType = request.data.encodingType || null; + const encodingType = data.encodingType || null; const allowedEncodings = client.getFwConstants().genericSigning.encodingTypes; const { payloadBuf } = getEncodedPayload( - request.data.payload, + data.payload, encodingType, allowedEncodings, ); const seed = TEST_SEED; - validateGenericSig( - seed, - response.sig, - payloadBuf, - request.data, - response.pubkey, - ); + validateGenericSig(seed, response.sig, payloadBuf, data, response.pubkey); return response; } diff --git a/packages/sdk/src/__test__/utils/viemComparison.ts b/packages/sdk/src/__test__/utils/viemComparison.ts index 89ffd5b1..7fb37c39 100644 --- a/packages/sdk/src/__test__/utils/viemComparison.ts +++ b/packages/sdk/src/__test__/utils/viemComparison.ts @@ -1,17 +1,17 @@ import { type Address, type Hex, - parseTransaction, - serializeTransaction, type TransactionSerializable, type TypedDataDefinition, + parseTransaction, + serializeTransaction, } from 'viem'; import { privateKeyToAccount } from 'viem/accounts'; import { sign, signMessage } from '../../api'; import { normalizeLatticeSignature } from '../../ethereum'; +import { ensureHexBuffer } from '../../util'; import { deriveAddress } from './determinism'; import { TEST_SEED } from './testConstants'; -import { ensureHexBuffer } from '../../util'; // Utility function to create foundry account address for comparison export const getFoundryAddress = (): Address => { @@ -45,14 +45,10 @@ export const signAndCompareTransaction = async ( // Sign with Lattice using the new sign API that accepts TransactionSerializable directly const latticeResult = await sign(tx).catch((err) => { if (err.responseCode === 128) { - err.message = - 'NOTE: You must have `FEATURE_TEST_RUNNER=1` enabled in firmware to run these tests.\n' + - err.message; + err.message = `NOTE: You must have \`FEATURE_TEST_RUNNER=1\` enabled in firmware to run these tests.\n${err.message}`; } if (err.responseCode === 132) { - err.message = - 'NOTE: Please approve the transaction on your Lattice device.\n' + - err.message; + err.message = `NOTE: Please approve the transaction on your Lattice device.\n${err.message}`; } throw err; }); @@ -109,7 +105,7 @@ export const signAndCompareTransaction = async ( const hexString = typeof value === 'string' ? value - : '0x' + Buffer.from(value).toString('hex'); + : `0x${Buffer.from(value).toString('hex')}`; const stripped = hexString.replace(/^0x/, '').toLowerCase(); return `0x${stripped.padStart(64, '0')}`; }; @@ -179,14 +175,10 @@ export const signAndCompareEIP712Message = async ( const latticeResult = await signMessage(latticePayload).catch((err) => { if (err.responseCode === 128) { - err.message = - 'NOTE: You must have `FEATURE_TEST_RUNNER=1` enabled in firmware to run these tests.\n' + - err.message; + err.message = `NOTE: You must have \`FEATURE_TEST_RUNNER=1\` enabled in firmware to run these tests.\n${err.message}`; } if (err.responseCode === 132) { - err.message = - 'NOTE: Please approve the message signature on your Lattice device.\n' + - err.message; + err.message = `NOTE: Please approve the message signature on your Lattice device.\n${err.message}`; } throw err; }); diff --git a/packages/sdk/src/api/addressTags.ts b/packages/sdk/src/api/addressTags.ts index fdf9b886..4e4ebcf5 100644 --- a/packages/sdk/src/api/addressTags.ts +++ b/packages/sdk/src/api/addressTags.ts @@ -1,6 +1,6 @@ -import { Client } from '../client'; +import type { Client } from '../client'; import { MAX_ADDR } from '../constants'; -import { AddressTag } from '../types'; +import type { AddressTag } from '../types'; import { queue } from './utilities'; /** diff --git a/packages/sdk/src/api/addresses.ts b/packages/sdk/src/api/addresses.ts index e5d1b4ad..c2414af3 100644 --- a/packages/sdk/src/api/addresses.ts +++ b/packages/sdk/src/api/addresses.ts @@ -16,12 +16,12 @@ import { SOLANA_DERIVATION, } from '../constants'; import { LatticeGetAddressesFlag } from '../protocol/latticeConstants'; -import { GetAddressesRequestParams, WalletPath } from '../types'; +import type { GetAddressesRequestParams, WalletPath } from '../types'; import { + getFlagFromPath, getStartPath, parseDerivationPathComponents, queue, - getFlagFromPath, } from './utilities'; type FetchAddressesParams = { diff --git a/packages/sdk/src/api/setup.ts b/packages/sdk/src/api/setup.ts index 491ac239..6471528f 100644 --- a/packages/sdk/src/api/setup.ts +++ b/packages/sdk/src/api/setup.ts @@ -1,6 +1,6 @@ import { Utils } from '..'; import { Client } from '../client'; -import { setSaveClient, setLoadClient, saveClient, loadClient } from './state'; +import { loadClient, saveClient, setLoadClient, setSaveClient } from './state'; import { buildLoadClientFn, buildSaveClientFn, queue } from './utilities'; /** diff --git a/packages/sdk/src/api/signing.ts b/packages/sdk/src/api/signing.ts index 3b8fc54a..fd98777a 100644 --- a/packages/sdk/src/api/signing.ts +++ b/packages/sdk/src/api/signing.ts @@ -1,12 +1,12 @@ import { RLP } from '@ethereumjs/rlp'; import { Hash } from 'ox'; import { - serializeTransaction, type Address, type Authorization, type Hex, type TransactionSerializable, type TransactionSerializableEIP7702, + serializeTransaction, } from 'viem'; import { Constants } from '..'; import { @@ -18,12 +18,12 @@ import { SOLANA_DERIVATION, } from '../constants'; import { fetchDecoder } from '../functions/fetchDecoder'; -import { +import type { BitcoinSignPayload, EIP712MessagePayload, SignData, - SigningPayload, SignRequestParams, + SigningPayload, TransactionRequest, } from '../types'; import { getYParity } from '../util'; diff --git a/packages/sdk/src/api/state.ts b/packages/sdk/src/api/state.ts index 7bc416e1..22aac567 100644 --- a/packages/sdk/src/api/state.ts +++ b/packages/sdk/src/api/state.ts @@ -1,4 +1,4 @@ -import { Client } from '../client'; +import type { Client } from '../client'; export let saveClient: (clientData: string | null) => Promise; diff --git a/packages/sdk/src/api/utilities.ts b/packages/sdk/src/api/utilities.ts index 3f9d3742..20072a67 100644 --- a/packages/sdk/src/api/utilities.ts +++ b/packages/sdk/src/api/utilities.ts @@ -107,9 +107,9 @@ export function parseDerivationPathComponents(components: string[]): number[] { if (lowerPart === 'x') return 0; // Wildcard if (lowerPart === "x'") return HARDENED_OFFSET; // Hardened wildcard if (part.endsWith("'")) - return parseInt(part.slice(0, -1)) + HARDENED_OFFSET; - const val = parseInt(part); - if (isNaN(val)) { + return Number.parseInt(part.slice(0, -1)) + HARDENED_OFFSET; + const val = Number.parseInt(part); + if (Number.isNaN(val)) { throw new Error(`Invalid part in derivation path: ${part}`); } return val; diff --git a/packages/sdk/src/api/wallets.ts b/packages/sdk/src/api/wallets.ts index 165a1b7f..a3c9b698 100644 --- a/packages/sdk/src/api/wallets.ts +++ b/packages/sdk/src/api/wallets.ts @@ -1,4 +1,4 @@ -import { ActiveWallets } from '../types'; +import type { ActiveWallets } from '../types'; import { queue } from './utilities'; /** diff --git a/packages/sdk/src/bitcoin.ts b/packages/sdk/src/bitcoin.ts index 59365b2b..9942413d 100644 --- a/packages/sdk/src/bitcoin.ts +++ b/packages/sdk/src/bitcoin.ts @@ -1,8 +1,8 @@ // Util for Bitcoin-specific functionality import { bech32 } from 'bech32'; import bs58check from 'bs58check'; -import { Hash } from 'ox'; import { ripemd160 } from 'hash.js/lib/hash/ripemd.js'; +import { Hash } from 'ox'; import { BIP_CONSTANTS } from './constants'; import { LatticeSignSchema } from './protocol'; const DEFAULT_SEQUENCE = 0xffffffff; @@ -49,7 +49,7 @@ const BTC_SCRIPT_TYPE_P2WPKH_V0 = 0x04; // already based on the number of inputs plus two outputs // `version`: Transaction version of the inputs. All inputs must be of the same version! // `isSegwit`: a boolean which determines how we serialize the data and parameterize txb -const buildBitcoinTxRequest = function (data) { +const buildBitcoinTxRequest = (data) => { const { prevOuts, recipient, value, changePath, fee } = data; if (!changePath) throw new Error('No changePath provided.'); if (changePath.length !== 5) @@ -129,7 +129,7 @@ const buildBitcoinTxRequest = function (data) { // -- isSegwitSpend = true if the inputs are being spent using segwit // (NOTE: either ALL are being spent, or none are) // -- lockTime = Will probably always be 0 -const serializeTx = function (data) { +const serializeTx = (data) => { const { inputs, outputs, lockTime = 0 } = data; let payload = Buffer.alloc(4); let off = 0; @@ -212,7 +212,7 @@ const serializeTx = function (data) { }; // Convert a pubkeyhash to a bitcoin base58check address with a version byte -const getBitcoinAddress = function (pubkeyhash, version) { +const getBitcoinAddress = (pubkeyhash, version) => { let bech32Prefix = null; let bech32Version = null; if (version === FMT_SEGWIT_NATIVE_V0) { @@ -387,7 +387,8 @@ function writeUInt64LE(n, buf, off) { } function decodeAddress(address) { - let versionByte, pkh; + let versionByte; + let pkh; try { // Attempt to base58 decode the address. This will work for older // P2PKH, P2SH, and P2SH-P2WPKH addresses diff --git a/packages/sdk/src/calldata/evm.ts b/packages/sdk/src/calldata/evm.ts index 59a7e48b..f2df93c3 100644 --- a/packages/sdk/src/calldata/evm.ts +++ b/packages/sdk/src/calldata/evm.ts @@ -7,10 +7,10 @@ import { decodeAbiParameters, parseAbiParameters } from 'viem'; * @returns Buffer containing RLP-serialized array of calldata info to pass to signing request * @public */ -export const parseSolidityJSONABI = function ( +export const parseSolidityJSONABI = ( sig: string, abi: any[], -): { def: EVMDef } { +): { def: EVMDef } => { sig = coerceSig(sig); // Find the first match in the ABI const match = abi @@ -35,7 +35,7 @@ export const parseSolidityJSONABI = function ( * @returns Buffer containing RLP-serialized array of calldata info to pass to signing request * @public */ -export const parseCanonicalName = function (sig: string, name: string) { +export const parseCanonicalName = (sig: string, name: string) => { sig = coerceSig(sig); if (sig !== getFuncSig(name)) { throw new Error('Name does not match provided sig.'); @@ -70,12 +70,12 @@ export const parseCanonicalName = function (sig: string, name: string) { * item has data (0x-prefixed hex string), it should be * checked as a possible nested def */ -export const getNestedCalldata = function (def, calldata) { +export const getNestedCalldata = (def, calldata) => { const possibleNestedDefs = []; // Skip past first item, which is the function name const defParams = def.slice(1); const strParams = getParamStrNames(defParams); - const hexStr = ('0x' + calldata.slice(4).toString('hex')) as `0x${string}`; + const hexStr = `0x${calldata.slice(4).toString('hex')}` as `0x${string}`; // Convert strParams to viem's format const viemParams = strParams.map((type) => { // Convert tuple format from 'tuple(uint256,uint128)' to '(uint256,uint128)' @@ -160,7 +160,7 @@ export const getNestedCalldata = function (def, calldata) { * defs which must be added to `def` * @return - Possibly modified version of `def` */ -export const replaceNestedDefs = function (def, nestedDefs) { +export const replaceNestedDefs = (def, nestedDefs) => { for (let i = 0; i < nestedDefs.length; i++) { const isArrItem = isBytesArrItem(def[1 + i]); const isItem = isBytesItem(def[1 + i]); @@ -309,8 +309,8 @@ function parseBasicTypeStr(typeStr: string): EVMParamInfo { const arrStart = param.arraySzs.length > 0 ? typeStr.indexOf('[') : typeStr.length; const typeStrNum = typeStr.slice(t.length, arrStart); - if (parseInt(typeStrNum)) { - param.szBytes = parseInt(typeStrNum) / 8; + if (Number.parseInt(typeStrNum)) { + param.szBytes = Number.parseInt(typeStrNum) / 8; if (param.szBytes > 32) { throw new Error(BAD_CANONICAL_ERR); } @@ -453,7 +453,7 @@ function getParamTypeInfo(type: string): EVMParamInfo { const szIdx = param.arraySzs.length > 0 ? type.indexOf('[') : type.length; if (['uint', 'int', 'bytes'].indexOf(baseType) > -1) { // If this can have a fixed size, capture that - const szBits = parseInt(type.slice(baseType.length, szIdx)) || 0; + const szBits = Number.parseInt(type.slice(baseType.length, szIdx)) || 0; if (szBits > 256) { throw new Error('Invalid param size'); } @@ -492,7 +492,7 @@ function getArraySzs(type: string): number[] { szs.push(0); } else { // Fixed size - szs.push(parseInt(t3)); + szs.push(Number.parseInt(t3)); } t1 = t2.slice(closeIdx + 1); } @@ -501,8 +501,8 @@ function getArraySzs(type: string): number[] { /** @internal */ function getTupleName(name, withArr = true) { - let brackets = 0, - addedFirstBracket = false; + let brackets = 0; + let addedFirstBracket = false; for (let i = 0; i < name.length; i++) { if (name[i] === '(') { brackets += 1; diff --git a/packages/sdk/src/client.ts b/packages/sdk/src/client.ts index 3fcf2bfe..797a6159 100644 --- a/packages/sdk/src/client.ts +++ b/packages/sdk/src/client.ts @@ -19,17 +19,17 @@ import { import { buildRetryWrapper } from './shared/functions'; import { getPubKeyBytes } from './shared/utilities'; import { validateEphemeralPub } from './shared/validators'; -import { - KeyPair, +import type { ActiveWallets, - GetAddressesRequestParams, - SignRequestParams, - SignData, AddKvRecordsRequestParams, - GetKvRecordsRequestParams, + FetchEncDataRequest, + GetAddressesRequestParams, GetKvRecordsData, + GetKvRecordsRequestParams, + KeyPair, RemoveKvRecordsRequestParams, - FetchEncDataRequest, + SignData, + SignRequestParams, } from './types'; import { getP256KeyPair, getP256KeyPairFromPub, randomBytes } from './util'; diff --git a/packages/sdk/src/constants.ts b/packages/sdk/src/constants.ts index f7679814..ef60fe73 100644 --- a/packages/sdk/src/constants.ts +++ b/packages/sdk/src/constants.ts @@ -6,10 +6,10 @@ import { LatticeSignEncoding, LatticeSignHash, } from './protocol/latticeConstants'; -import { - FirmwareConstants, - FirmwareArr, +import type { ActiveWallets, + FirmwareArr, + FirmwareConstants, WalletPath, } from './types/index.js'; diff --git a/packages/sdk/src/ethereum.ts b/packages/sdk/src/ethereum.ts index c5f8e35c..52d5867d 100644 --- a/packages/sdk/src/ethereum.ts +++ b/packages/sdk/src/ethereum.ts @@ -1,48 +1,48 @@ +import { RLP } from '@ethereumjs/rlp'; +import { SignTypedDataVersion, TypedDataUtils } from '@metamask/eth-sig-util'; // Utils for Ethereum transactions. This is effecitvely a shim of ethereumjs-util, which // does not have browser (or, by proxy, React-Native) support. import BN from 'bignumber.js'; -import { SignTypedDataVersion, TypedDataUtils } from '@metamask/eth-sig-util'; +import cbor from 'cbor'; +import bdec from 'cbor-bigdecimal'; import { Hash } from 'ox'; -import { RLP } from '@ethereumjs/rlp'; import secp256k1 from 'secp256k1'; +import { + type Hex, + type TransactionSerializable, + hexToNumber, + serializeTransaction, +} from 'viem'; import { ASCII_REGEX, + EXTERNAL, HANDLE_LARGER_CHAIN_ID, MAX_CHAIN_ID_BYTES, ethMsgProtocol, - EXTERNAL, } from './constants'; +import { buildGenericSigningMsgRequest } from './genericSigning'; import { LatticeSignSchema } from './protocol'; +import { type FlexibleTransaction, TransactionSchema } from './schemas'; +import { + type FirmwareConstants, + type SigningPath, + TRANSACTION_TYPE, + type TransactionRequest, +} from './types'; import { buildSignerPathBuf, + convertRecoveryToV, ensureHexBuffer, fixLen, isAsciiStr, splitFrames, - convertRecoveryToV, } from './util'; -import cbor from 'cbor'; -import bdec from 'cbor-bigdecimal'; -import { - TransactionSerializable, - serializeTransaction, - type Hex, - hexToNumber, -} from 'viem'; -import { - type SigningPath, - type FirmwareConstants, - TransactionRequest, - TRANSACTION_TYPE, -} from './types'; -import { buildGenericSigningMsgRequest } from './genericSigning'; -import { TransactionSchema, type FlexibleTransaction } from './schemas'; const { ecdsaRecover } = secp256k1; bdec(cbor); -const buildEthereumMsgRequest = function (input) { +const buildEthereumMsgRequest = (input) => { if (!input.payload || !input.protocol || !input.signerPath) throw new Error( 'You must provide `payload`, `signerPath`, and `protocol` arguments in the messsage request', @@ -69,7 +69,7 @@ const buildEthereumMsgRequest = function (input) { } }; -const validateEthereumMsgResponse = function (res, req) { +const validateEthereumMsgResponse = (res, req) => { const { signer, sig } = res; const { input, msg, prehash = null } = req; if (input.protocol === 'signPersonal') { @@ -105,8 +105,8 @@ const validateEthereumMsgResponse = function (res, req) { input.payload.domain?.chainId || payloadForHashing.domain?.chainId; if (typeof chainId === 'string') { chainId = chainId.startsWith('0x') - ? parseInt(chainId, 16) - : parseInt(chainId, 10); + ? Number.parseInt(chainId, 16) + : Number.parseInt(chainId, 10); } else if (typeof chainId === 'bigint') { chainId = Number(chainId); } @@ -233,7 +233,7 @@ const structuredCloneFn: StructuredCloneFn | null = ? (globalThis as { structuredClone: StructuredCloneFn }).structuredClone : null; -const buildEthereumTxRequest = function (data) { +const buildEthereumTxRequest = (data) => { try { let { chainId = 1 } = data; const { signerPath, eip155 = null, fwConstants, type = null } = data; @@ -306,7 +306,8 @@ const buildEthereumTxRequest = function (data) { // Handle contract deployment (indicated by `to` being `null`) // For contract deployment we write a 20-byte key to the request // buffer, which gets swapped for an empty buffer in firmware. - let toRlpElem, toBytes; + let toRlpElem; + let toBytes; if (isDeployment) { toRlpElem = Buffer.alloc(0); toBytes = ensureHexBuffer(contractDeployKey); @@ -322,7 +323,8 @@ const buildEthereumTxRequest = function (data) { rawTx.push(chainIdBytes); } rawTx.push(nonceBytes); - let maxPriorityFeePerGasBytes, maxFeePerGasBytes; + let maxPriorityFeePerGasBytes; + let maxFeePerGasBytes; if (isEip1559) { if (!data.maxPriorityFeePerGas) throw new Error( @@ -549,7 +551,7 @@ function stripZeros(a) { // Given a 64-byte signature [r,s] we need to figure out the v value // and attah the full signature to the end of the transaction payload -const buildEthRawTx = function (tx, sig, address) { +const buildEthRawTx = (tx, sig, address) => { // RLP-encode the data we sent to the lattice const hash = Buffer.from( Hash.keccak256(get_rlp_encoded_preimage(tx.rawTx, tx.type)), @@ -650,7 +652,7 @@ export function normalizeLatticeSignature( vValue = latticeResult.sig.v.readUInt32BE(Math.max(0, 4 - bufferLength)); } else { // For very large buffers, read as hex and convert - vValue = parseInt(latticeResult.sig.v.toString('hex'), 16); + vValue = Number.parseInt(latticeResult.sig.v.toString('hex'), 16); } } else if (typeof latticeResult.sig.v === 'number') { vValue = latticeResult.sig.v; @@ -680,22 +682,21 @@ export function normalizeLatticeSignature( }; // For legacy transactions, remove the type field to ensure Viem treats it as legacy - delete result.type; + result.type = undefined; // Also remove any typed transaction fields that might confuse viem - delete result.maxFeePerGas; - delete result.maxPriorityFeePerGas; - delete result.accessList; - delete result.authorizationList; + result.maxFeePerGas = undefined; + result.maxPriorityFeePerGas = undefined; + result.accessList = undefined; + result.authorizationList = undefined; return result; } } // Convert an RLP-serialized transaction (plus signature) into a transaction hash -const hashTransaction = function (serializedTx) { - return Hash.keccak256(Buffer.from(serializedTx, 'hex')); -}; +const hashTransaction = (serializedTx) => + Hash.keccak256(Buffer.from(serializedTx, 'hex')); // Returns address string given public key buffer function pubToAddrStr(pub) { @@ -758,9 +759,6 @@ function chainUsesEIP155(chainID) { case 3: // ropsten case 4: // rinkeby return false; - case 1: // mainnet - case 42: // kovan - case 5: // goerli default: // all others should use eip155 return true; @@ -1077,7 +1075,7 @@ function parseEIP712Item(data, type, forJSParser = false) { } } else if (type.slice(0, 5) === 'bytes') { // Fixed sizes bytes need to be buffer type. We also add some sanity checks. - const nBytes = parseInt(type.slice(5)); + const nBytes = Number.parseInt(type.slice(5)); data = ensureHexBuffer(data); // Edge case to handle empty bytesN values if (data.length === 0) { @@ -1213,9 +1211,9 @@ export const normalizeToViemTransaction = ( * Convert Ethereum transaction to serialized bytes for generic signing. * Bridge function for firmware v0.15.0+ which removed legacy ETH signing paths. */ -const convertEthereumTransactionToGenericRequest = function ( +const convertEthereumTransactionToGenericRequest = ( req: FlexibleTransaction, -) { +) => { // Use the unified normalization and serialization pipeline. // 1. Normalize the potentially varied input to a standard viem format. const viemTx = normalizeToViemTransaction(req); @@ -1234,9 +1232,9 @@ type EthereumGenericSigningRequestParams = FlexibleTransaction & { * Build complete generic signing request for Ethereum transactions. * One-step function combining transaction conversion and generic signing setup. */ -export const buildEthereumGenericSigningRequest = function ( +export const buildEthereumGenericSigningRequest = ( req: EthereumGenericSigningRequestParams, -) { +) => { const { fwConstants, signerPath, ...txData } = req; const payload = convertEthereumTransactionToGenericRequest(txData); @@ -1354,7 +1352,7 @@ export function serializeEIP7702Transaction(tx: TransactionRequest): Hex { ? address.startsWith('0x') ? address : `0x${address}` - : `0x`; + : '0x'; if (!addressStr || addressStr === '0x') { throw new Error( diff --git a/packages/sdk/src/functions/addKvRecords.ts b/packages/sdk/src/functions/addKvRecords.ts index f26cff66..0ce54783 100644 --- a/packages/sdk/src/functions/addKvRecords.ts +++ b/packages/sdk/src/functions/addKvRecords.ts @@ -7,10 +7,10 @@ import { validateKvRecord, validateKvRecords, } from '../shared/validators'; -import { +import type { AddKvRecordsRequestFunctionParams, - KVRecords, FirmwareConstants, + KVRecords, } from '../types'; /** diff --git a/packages/sdk/src/functions/connect.ts b/packages/sdk/src/functions/connect.ts index 8f4a4d55..bd03b931 100644 --- a/packages/sdk/src/functions/connect.ts +++ b/packages/sdk/src/functions/connect.ts @@ -6,7 +6,11 @@ import { validateDeviceId, validateKey, } from '../shared/validators'; -import { ConnectRequestFunctionParams, KeyPair, ActiveWallets } from '../types'; +import type { + ActiveWallets, + ConnectRequestFunctionParams, + KeyPair, +} from '../types'; import { aes256_decrypt, getP256KeyPairFromPub } from '../util'; export async function connect({ diff --git a/packages/sdk/src/functions/fetchActiveWallet.ts b/packages/sdk/src/functions/fetchActiveWallet.ts index d6f6af5e..c426bbfc 100644 --- a/packages/sdk/src/functions/fetchActiveWallet.ts +++ b/packages/sdk/src/functions/fetchActiveWallet.ts @@ -7,9 +7,9 @@ import { validateActiveWallets, validateConnectedClient, } from '../shared/validators'; -import { - FetchActiveWalletRequestFunctionParams, +import type { ActiveWallets, + FetchActiveWalletRequestFunctionParams, } from '../types'; /** diff --git a/packages/sdk/src/functions/fetchDecoder.ts b/packages/sdk/src/functions/fetchDecoder.ts index af1afb5d..70b4367c 100644 --- a/packages/sdk/src/functions/fetchDecoder.ts +++ b/packages/sdk/src/functions/fetchDecoder.ts @@ -1,8 +1,8 @@ import { validateConnectedClient } from '../shared/validators'; import { getClient } from '../api'; +import type { TransactionRequest } from '../types'; import { fetchCalldataDecoder } from '../util'; -import { TransactionRequest } from '../types'; /** * `fetchDecoder` fetches the ABI for a given contract address and chain ID. diff --git a/packages/sdk/src/functions/fetchEncData.ts b/packages/sdk/src/functions/fetchEncData.ts index dc7facc6..3b71139f 100644 --- a/packages/sdk/src/functions/fetchEncData.ts +++ b/packages/sdk/src/functions/fetchEncData.ts @@ -14,12 +14,12 @@ import { validateStartPath, validateWallet, } from '../shared/validators'; -import { - FetchEncDataRequestFunctionParams, +import type { + EIP2335KeyExportData, EIP2335KeyExportReq, + FetchEncDataRequestFunctionParams, FirmwareVersion, Wallet, - EIP2335KeyExportData, } from '../types'; const { ENC_DATA } = EXTERNAL; diff --git a/packages/sdk/src/functions/getAddresses.ts b/packages/sdk/src/functions/getAddresses.ts index e57e32f7..5813132d 100644 --- a/packages/sdk/src/functions/getAddresses.ts +++ b/packages/sdk/src/functions/getAddresses.ts @@ -11,9 +11,9 @@ import { validateStartPath, validateWallet, } from '../shared/validators'; -import { - GetAddressesRequestFunctionParams, +import type { FirmwareConstants, + GetAddressesRequestFunctionParams, Wallet, } from '../types'; import { isValidAssetPath } from '../util'; diff --git a/packages/sdk/src/functions/getKvRecords.ts b/packages/sdk/src/functions/getKvRecords.ts index 2b328624..4c788592 100644 --- a/packages/sdk/src/functions/getKvRecords.ts +++ b/packages/sdk/src/functions/getKvRecords.ts @@ -3,10 +3,10 @@ import { encryptedSecureRequest, } from '../protocol'; import { validateConnectedClient } from '../shared/validators'; -import { - GetKvRecordsRequestFunctionParams, - GetKvRecordsData, +import type { FirmwareConstants, + GetKvRecordsData, + GetKvRecordsRequestFunctionParams, } from '../types'; export async function getKvRecords({ @@ -97,7 +97,10 @@ export const decodeGetKvRecordsResponse = ( let off = 0; const nTotal = data.readUInt32BE(off); off += 4; - const nFetched = parseInt(data.slice(off, off + 1).toString('hex'), 16); + const nFetched = Number.parseInt( + data.slice(off, off + 1).toString('hex'), + 16, + ); off += 1; if (nFetched > fwConstants.kvActionMaxNum) throw new Error('Too many records fetched. Firmware error.'); @@ -109,15 +112,13 @@ export const decodeGetKvRecordsResponse = ( r.type = data.readUInt32BE(off); off += 4; r.caseSensitive = - parseInt(data.slice(off, off + 1).toString('hex'), 16) === 1 - ? true - : false; + Number.parseInt(data.slice(off, off + 1).toString('hex'), 16) === 1; off += 1; - const keySz = parseInt(data.slice(off, off + 1).toString('hex'), 16); + const keySz = Number.parseInt(data.slice(off, off + 1).toString('hex'), 16); off += 1; r.key = data.slice(off, off + keySz - 1).toString(); off += fwConstants.kvKeyMaxStrSz + 1; - const valSz = parseInt(data.slice(off, off + 1).toString('hex'), 16); + const valSz = Number.parseInt(data.slice(off, off + 1).toString('hex'), 16); off += 1; r.val = data.slice(off, off + valSz - 1).toString(); off += fwConstants.kvValMaxStrSz + 1; diff --git a/packages/sdk/src/functions/pair.ts b/packages/sdk/src/functions/pair.ts index 941bd0e4..aa119d0a 100644 --- a/packages/sdk/src/functions/pair.ts +++ b/packages/sdk/src/functions/pair.ts @@ -4,7 +4,7 @@ import { } from '../protocol'; import { getPubKeyBytes } from '../shared/utilities'; import { validateConnectedClient } from '../shared/validators'; -import { PairRequestParams, KeyPair } from '../types'; +import type { KeyPair, PairRequestParams } from '../types'; import { generateAppSecret, toPaddedDER } from '../util'; /** diff --git a/packages/sdk/src/functions/removeKvRecords.ts b/packages/sdk/src/functions/removeKvRecords.ts index 29da850e..b78637eb 100644 --- a/packages/sdk/src/functions/removeKvRecords.ts +++ b/packages/sdk/src/functions/removeKvRecords.ts @@ -3,9 +3,9 @@ import { encryptedSecureRequest, } from '../protocol'; import { validateConnectedClient } from '../shared/validators'; -import { - RemoveKvRecordsRequestFunctionParams, +import type { FirmwareConstants, + RemoveKvRecordsRequestFunctionParams, } from '../types'; /** @@ -87,7 +87,7 @@ export const encodeRemoveKvRecordsRequest = ({ payload.writeUInt32LE(type, 0); payload.writeUInt8(ids.length, 4); for (let i = 0; i < ids.length; i++) { - const id = parseInt(ids[i] as string); + const id = Number.parseInt(ids[i] as string); payload.writeUInt32LE(id, 5 + 4 * i); } return payload; diff --git a/packages/sdk/src/functions/sign.ts b/packages/sdk/src/functions/sign.ts index 92a94023..fafbbb3f 100644 --- a/packages/sdk/src/functions/sign.ts +++ b/packages/sdk/src/functions/sign.ts @@ -1,5 +1,5 @@ import { Hash } from 'ox'; -import { type Hex, type Address } from 'viem'; +import type { Address, Hex } from 'viem'; import bitcoin from '../bitcoin'; import { CURRENCIES } from '../constants'; import ethereum from '../ethereum'; @@ -11,16 +11,16 @@ import { } from '../protocol'; import { buildTransaction } from '../shared/functions'; import { validateConnectedClient, validateWallet } from '../shared/validators'; -import { parseDER } from '../util'; -import { - SignRequestFunctionParams, - SigningRequestResponse, - EncodeSignRequestParams, +import type { + BitcoinSignRequest, DecodeSignResponseParams, + EncodeSignRequestParams, SignData, - BitcoinSignRequest, SignRequest, + SignRequestFunctionParams, + SigningRequestResponse, } from '../types'; +import { parseDER } from '../util'; /** * `sign` builds and sends a request for signing to the device. diff --git a/packages/sdk/src/genericSigning.ts b/packages/sdk/src/genericSigning.ts index a406b248..7c66c522 100644 --- a/packages/sdk/src/genericSigning.ts +++ b/packages/sdk/src/genericSigning.ts @@ -1,3 +1,4 @@ +import { RLP } from '@ethereumjs/rlp'; /** Generic signing module. Any payload can be sent to the Lattice and will be displayed in full (note that \n and \t characters will be @@ -9,12 +10,11 @@ This payload should be coupled with: * Hash function to use on the message */ import { Hash } from 'ox'; -import { RLP } from '@ethereumjs/rlp'; import { - parseTransaction, - serializeTransaction, type Hex, type TransactionSerializable, + parseTransaction, + serializeTransaction, } from 'viem'; // keccak256 now imported from ox via Hash module import { HARDENED_OFFSET } from './constants'; @@ -24,13 +24,13 @@ import { buildSignerPathBuf, existsIn, fixLen, - getYParity, getV, + getYParity, parseDER, splitFrames, } from './util'; -export const buildGenericSigningMsgRequest = function (req) { +export const buildGenericSigningMsgRequest = (req) => { const { signerPath, curveType, @@ -209,7 +209,7 @@ export const buildGenericSigningMsgRequest = function (req) { }; }; -export const parseGenericSigningResponse = function (res, off, req) { +export const parseGenericSigningResponse = (res, off, req) => { const parsed = { pubkey: null, sig: null, @@ -436,11 +436,7 @@ function populateViemSignedTx( } } -export const getEncodedPayload = function ( - payload, - encoding, - allowedEncodings, -) { +export const getEncodedPayload = (payload, encoding, allowedEncodings) => { if (!encoding) { encoding = Constants.SIGNING.ENCODINGS.NONE; } diff --git a/packages/sdk/src/protocol/secureMessages.ts b/packages/sdk/src/protocol/secureMessages.ts index a4dc6d4e..700e7233 100644 --- a/packages/sdk/src/protocol/secureMessages.ts +++ b/packages/sdk/src/protocol/secureMessages.ts @@ -1,3 +1,21 @@ +import { getEphemeralId, request } from '../shared/functions'; +import { validateEphemeralPub } from '../shared/validators'; +import type { + DecryptedResponse, + KeyPair, + LatticeMessageHeader, + LatticeSecureConnectRequestPayloadData, + LatticeSecureDecryptedResponse, + LatticeSecureRequest, + LatticeSecureRequestPayload, +} from '../types'; +import { + aes256_decrypt, + aes256_encrypt, + checksum, + getP256KeyPairFromPub, + randomBytes, +} from '../util'; /** * All messages sent to the Lattice from this SDK will be * "secure messages", of which there are two types: @@ -22,27 +40,9 @@ import { ProtocolConstants as Constants, LatticeMsgType, LatticeProtocolVersion, - LatticeSecureEncryptedRequestType, + type LatticeSecureEncryptedRequestType, LatticeSecureMsgType, } from './latticeConstants'; -import { - aes256_decrypt, - aes256_encrypt, - checksum, - getP256KeyPairFromPub, - randomBytes, -} from '../util'; -import { getEphemeralId, request } from '../shared/functions'; -import { validateEphemeralPub } from '../shared/validators'; -import { - DecryptedResponse, - LatticeSecureRequestPayload, - LatticeMessageHeader, - LatticeSecureRequest, - LatticeSecureConnectRequestPayloadData, - LatticeSecureDecryptedResponse, - KeyPair, -} from '../types'; const { msgSizes } = Constants; const { secure: szs } = msgSizes; diff --git a/packages/sdk/src/schemas/transaction.ts b/packages/sdk/src/schemas/transaction.ts index c1bc8fd1..63f06eed 100644 --- a/packages/sdk/src/schemas/transaction.ts +++ b/packages/sdk/src/schemas/transaction.ts @@ -1,5 +1,5 @@ +import { type Hex, getAddress, hexToBigInt, isAddress, isHex } from 'viem'; import { z } from 'zod'; -import { type Hex, isHex, hexToBigInt, isAddress, getAddress } from 'viem'; import { TRANSACTION_TYPE } from '../types'; // Helper to handle various numeric inputs and convert them to BigInt. @@ -248,13 +248,13 @@ export const TransactionSchema = z } // Remove fields that are not part of the final type - if (type !== 'legacy' && type !== 'eip2930') delete data.gasPrice; + if (type !== 'legacy' && type !== 'eip2930') data.gasPrice = undefined; if (type !== 'eip1559' && type !== 'eip7702') { - delete data.maxFeePerGas; - delete data.maxPriorityFeePerGas; + data.maxFeePerGas = undefined; + data.maxPriorityFeePerGas = undefined; } - delete data.gasLimit; - if (type !== 'eip7702') delete data.authorizationList; + data.gasLimit = undefined; + if (type !== 'eip7702') data.authorizationList = undefined; return data; }); diff --git a/packages/sdk/src/shared/errors.ts b/packages/sdk/src/shared/errors.ts index b2c7d055..c3e828db 100644 --- a/packages/sdk/src/shared/errors.ts +++ b/packages/sdk/src/shared/errors.ts @@ -1,4 +1,4 @@ -import { LatticeResponseCode, ProtocolConstants } from '../protocol'; +import { type LatticeResponseCode, ProtocolConstants } from '../protocol'; const buildLatticeResponseErrorMessage = ({ responseCode, diff --git a/packages/sdk/src/shared/functions.ts b/packages/sdk/src/shared/functions.ts index 0c4805ce..c1cf3ee4 100644 --- a/packages/sdk/src/shared/functions.ts +++ b/packages/sdk/src/shared/functions.ts @@ -1,9 +1,10 @@ import { Hash } from 'ox'; -import { Client } from '..'; +import type { Client } from '..'; import bitcoin from '../bitcoin'; import { EXTERNAL } from '../constants'; import ethereum from '../ethereum'; import { buildGenericSigningMsgRequest } from '../genericSigning'; +import type { Currency, FirmwareConstants, RequestParams } from '../types'; import { fetchWithTimeout, parseLattice1Response } from '../util'; import { LatticeResponseError } from './errors'; import { @@ -13,7 +14,6 @@ import { shouldUseEVMLegacyConverter, } from './predicates'; import { validateRequestError } from './validators'; -import { Currency, FirmwareConstants, RequestParams } from '../types'; export const buildTransaction = ({ data, @@ -206,5 +206,5 @@ export const retryWrapper = async ({ export const getEphemeralId = (sharedSecret: Buffer) => { // EphemId is the first 4 bytes of the hash of the shared secret const hash = Buffer.from(Hash.sha256(sharedSecret)); - return parseInt(hash.slice(0, 4).toString('hex'), 16); + return Number.parseInt(hash.slice(0, 4).toString('hex'), 16); }; diff --git a/packages/sdk/src/shared/predicates.ts b/packages/sdk/src/shared/predicates.ts index f6d6481a..25477720 100644 --- a/packages/sdk/src/shared/predicates.ts +++ b/packages/sdk/src/shared/predicates.ts @@ -1,5 +1,5 @@ import { LatticeResponseCode } from '../protocol'; -import { FirmwareVersion, FirmwareConstants } from '../types'; +import type { FirmwareConstants, FirmwareVersion } from '../types'; import { isFWSupported } from './utilities'; export const isDeviceBusy = (responseCode: number) => @@ -16,6 +16,4 @@ export const doesFetchWalletsOnLoad = (fwVersion: FirmwareVersion) => isFWSupported(fwVersion, { major: 0, minor: 14, fix: 1 }); export const shouldUseEVMLegacyConverter = (fwConstants: FirmwareConstants) => - fwConstants.genericSigning && - fwConstants.genericSigning.encodingTypes && - fwConstants.genericSigning.encodingTypes.EVM; + fwConstants.genericSigning?.encodingTypes?.EVM; diff --git a/packages/sdk/src/shared/utilities.ts b/packages/sdk/src/shared/utilities.ts index b1502dfc..08367194 100644 --- a/packages/sdk/src/shared/utilities.ts +++ b/packages/sdk/src/shared/utilities.ts @@ -1,5 +1,5 @@ import { HARDENED_OFFSET } from '../constants'; -import { KeyPair, ActiveWallets, FirmwareVersion } from '../types'; +import type { ActiveWallets, FirmwareVersion, KeyPair } from '../types'; /** * Get 64 bytes representing the public key This is the uncompressed key without the leading 04 @@ -99,7 +99,7 @@ export const isFWSupported = ( * Convert a set of BIP39 path indices to a string * @param path - Set of indices */ -export const getPathStr = function (path) { +export const getPathStr = (path) => { let pathStr = 'm'; path.forEach((idx) => { if (idx >= HARDENED_OFFSET) { diff --git a/packages/sdk/src/shared/validators.ts b/packages/sdk/src/shared/validators.ts index eec4886c..5b5a9da9 100644 --- a/packages/sdk/src/shared/validators.ts +++ b/packages/sdk/src/shared/validators.ts @@ -1,17 +1,17 @@ -import { UInt4 } from 'bitwise/types'; -import { Client } from '../client'; -import { ASCII_REGEX, EMPTY_WALLET_UID, MAX_ADDR } from '../constants'; -import { isUInt4 } from '../util'; +import type { UInt4 } from 'bitwise/types'; import isEmpty from 'lodash/isEmpty.js'; -import { +import type { Client } from '../client'; +import { ASCII_REGEX, EMPTY_WALLET_UID, MAX_ADDR } from '../constants'; +import type { + ActiveWallets, FirmwareConstants, FirmwareVersion, + KVRecords, + KeyPair, LatticeError, Wallet, - KeyPair, - ActiveWallets, - KVRecords, } from '../types'; +import { isUInt4 } from '../util'; export const validateIsUInt4 = (n?: number) => { if (typeof n !== 'number' || !isUInt4(n)) { diff --git a/packages/sdk/src/types/addKvRecords.ts b/packages/sdk/src/types/addKvRecords.ts index e7dfdadb..892f0de9 100644 --- a/packages/sdk/src/types/addKvRecords.ts +++ b/packages/sdk/src/types/addKvRecords.ts @@ -1,5 +1,5 @@ -import { Client } from '../client'; -import { KVRecords } from './shared'; +import type { Client } from '../client'; +import type { KVRecords } from './shared'; export interface AddKvRecordsRequestParams { records: KVRecords; @@ -7,6 +7,7 @@ export interface AddKvRecordsRequestParams { caseSensitive?: boolean; } -export interface AddKvRecordsRequestFunctionParams extends AddKvRecordsRequestParams { +export interface AddKvRecordsRequestFunctionParams + extends AddKvRecordsRequestParams { client: Client; } diff --git a/packages/sdk/src/types/client.ts b/packages/sdk/src/types/client.ts index c96aaa55..61554783 100644 --- a/packages/sdk/src/types/client.ts +++ b/packages/sdk/src/types/client.ts @@ -1,18 +1,29 @@ -import { CURRENCIES } from '../constants'; -import { KeyPair } from './shared'; -import type { Address, Hash, Signature } from 'viem'; +import type { Address, Hash } from 'viem'; +import type { CURRENCIES } from '../constants'; +import type { KeyPair } from './shared'; export type Currency = keyof typeof CURRENCIES; export type SigningPath = number[]; +/** + * Signature components as returned by the Lattice device. + * Values can be Buffer (raw) or string (hex) depending on context. + */ +export interface LatticeSignature { + r: Buffer | string; + s: Buffer | string; + v?: Buffer | string | number | bigint; +} + export interface SignData { tx?: string; txHash?: Hash; changeRecipient?: string; - sig?: Signature; + sig?: LatticeSignature; sigs?: Buffer[]; signer?: Address; + pubkey?: Buffer; err?: string; } diff --git a/packages/sdk/src/types/connect.ts b/packages/sdk/src/types/connect.ts index fe723d20..c07ad41e 100644 --- a/packages/sdk/src/types/connect.ts +++ b/packages/sdk/src/types/connect.ts @@ -1,4 +1,4 @@ -import { Client } from '../client'; +import type { Client } from '../client'; export interface ConnectRequestParams { id: string; diff --git a/packages/sdk/src/types/declarations.d.ts b/packages/sdk/src/types/declarations.d.ts index 739a96ff..cb9cc0aa 100644 --- a/packages/sdk/src/types/declarations.d.ts +++ b/packages/sdk/src/types/declarations.d.ts @@ -21,7 +21,5 @@ declare module 'lodash/isEmpty.js' { // Add more flexible typing to reduce strict type checking for complex modules declare global { - interface NodeRequire { - (id: string): any; - } + type NodeRequire = (id: string) => any; } diff --git a/packages/sdk/src/types/fetchActiveWallet.ts b/packages/sdk/src/types/fetchActiveWallet.ts index 1dfd6f0a..da6c7dae 100644 --- a/packages/sdk/src/types/fetchActiveWallet.ts +++ b/packages/sdk/src/types/fetchActiveWallet.ts @@ -1,4 +1,4 @@ -import { Client } from '../client'; +import type { Client } from '../client'; export interface FetchActiveWalletRequestFunctionParams { client: Client; diff --git a/packages/sdk/src/types/fetchEncData.ts b/packages/sdk/src/types/fetchEncData.ts index 23ab496a..f6d1d76b 100644 --- a/packages/sdk/src/types/fetchEncData.ts +++ b/packages/sdk/src/types/fetchEncData.ts @@ -1,4 +1,4 @@ -import { Client } from '../client'; +import type { Client } from '../client'; export interface EIP2335KeyExportReq { path: number[]; diff --git a/packages/sdk/src/types/firmware.ts b/packages/sdk/src/types/firmware.ts index f00a4098..d3b6bab3 100644 --- a/packages/sdk/src/types/firmware.ts +++ b/packages/sdk/src/types/firmware.ts @@ -1,4 +1,4 @@ -import { EXTERNAL } from '../constants'; +import type { EXTERNAL } from '../constants'; export type FirmwareArr = [number, number, number]; diff --git a/packages/sdk/src/types/getAddresses.ts b/packages/sdk/src/types/getAddresses.ts index bf80ccfb..7ec1bff5 100644 --- a/packages/sdk/src/types/getAddresses.ts +++ b/packages/sdk/src/types/getAddresses.ts @@ -1,4 +1,4 @@ -import { Client } from '../client'; +import type { Client } from '../client'; export interface GetAddressesRequestParams { startPath: number[]; @@ -7,6 +7,7 @@ export interface GetAddressesRequestParams { iterIdx?: number; } -export interface GetAddressesRequestFunctionParams extends GetAddressesRequestParams { +export interface GetAddressesRequestFunctionParams + extends GetAddressesRequestParams { client: Client; } diff --git a/packages/sdk/src/types/getKvRecords.ts b/packages/sdk/src/types/getKvRecords.ts index 05fb172b..f4c1d0e0 100644 --- a/packages/sdk/src/types/getKvRecords.ts +++ b/packages/sdk/src/types/getKvRecords.ts @@ -1,4 +1,4 @@ -import { Client } from '../client'; +import type { Client } from '../client'; export interface GetKvRecordsRequestParams { type?: number; @@ -6,7 +6,8 @@ export interface GetKvRecordsRequestParams { start?: number; } -export interface GetKvRecordsRequestFunctionParams extends GetKvRecordsRequestParams { +export interface GetKvRecordsRequestFunctionParams + extends GetKvRecordsRequestParams { client: Client; } diff --git a/packages/sdk/src/types/messages.ts b/packages/sdk/src/types/messages.ts index 4dde6e3a..78a77821 100644 --- a/packages/sdk/src/types/messages.ts +++ b/packages/sdk/src/types/messages.ts @@ -1,4 +1,4 @@ -import { LatticeMsgType } from '../protocol'; +import type { LatticeMsgType } from '../protocol'; export interface LatticeMessageHeader { // Protocol version. Should always be 0x01 diff --git a/packages/sdk/src/types/pair.ts b/packages/sdk/src/types/pair.ts index faac0d28..6fb54b83 100644 --- a/packages/sdk/src/types/pair.ts +++ b/packages/sdk/src/types/pair.ts @@ -1,4 +1,4 @@ -import { Client } from '../client'; +import type { Client } from '../client'; export interface PairRequestParams { pairingSecret: string; diff --git a/packages/sdk/src/types/removeKvRecords.ts b/packages/sdk/src/types/removeKvRecords.ts index fe205e22..a8cfc747 100644 --- a/packages/sdk/src/types/removeKvRecords.ts +++ b/packages/sdk/src/types/removeKvRecords.ts @@ -1,10 +1,11 @@ -import { Client } from '../client'; +import type { Client } from '../client'; export interface RemoveKvRecordsRequestParams { type?: number; ids?: string[]; } -export interface RemoveKvRecordsRequestFunctionParams extends RemoveKvRecordsRequestParams { +export interface RemoveKvRecordsRequestFunctionParams + extends RemoveKvRecordsRequestParams { client: Client; } diff --git a/packages/sdk/src/types/secureMessages.ts b/packages/sdk/src/types/secureMessages.ts index c17b489a..ebbb9127 100644 --- a/packages/sdk/src/types/secureMessages.ts +++ b/packages/sdk/src/types/secureMessages.ts @@ -1,5 +1,5 @@ -import { LatticeSecureMsgType, LatticeResponseCode } from '../protocol'; -import { LatticeMessageHeader } from './messages'; +import type { LatticeResponseCode, LatticeSecureMsgType } from '../protocol'; +import type { LatticeMessageHeader } from './messages'; export interface LatticeSecureRequest { // Message header diff --git a/packages/sdk/src/types/sign.ts b/packages/sdk/src/types/sign.ts index 4c3a8063..f9ed2121 100644 --- a/packages/sdk/src/types/sign.ts +++ b/packages/sdk/src/types/sign.ts @@ -1,15 +1,15 @@ import type { + AccessList, Address, Hex, - TypedData, - TypedDataDefinition, - AccessList, SignedAuthorization, SignedAuthorizationList, + TypedData, + TypedDataDefinition, } from 'viem'; -import { Client } from '../client'; -import { Currency, SigningPath, Wallet } from './client'; -import { FirmwareConstants } from './firmware'; +import type { Client } from '../client'; +import type { Currency, SigningPath, Wallet } from './client'; +import type { FirmwareConstants } from './firmware'; export type ETH_MESSAGE_PROTOCOLS = 'eip712' | 'signPersonal'; diff --git a/packages/sdk/src/types/utils.ts b/packages/sdk/src/types/utils.ts index e7504a55..c82b10cc 100644 --- a/packages/sdk/src/types/utils.ts +++ b/packages/sdk/src/types/utils.ts @@ -1,4 +1,4 @@ -import { Client } from '../client'; +import type { Client } from '../client'; export interface TestRequestPayload { payload: Buffer; diff --git a/packages/sdk/src/util.ts b/packages/sdk/src/util.ts index 93331be7..e225560b 100644 --- a/packages/sdk/src/util.ts +++ b/packages/sdk/src/util.ts @@ -1,16 +1,16 @@ +import { Buffer } from 'node:buffer'; // Static utility functions import { RLP } from '@ethereumjs/rlp'; import aes from 'aes-js'; import BigNum from 'bignumber.js'; import { BN } from 'bn.js'; -import { Buffer } from 'buffer'; import crc32 from 'crc-32'; import elliptic from 'elliptic'; -import { Hash } from 'ox'; import inRange from 'lodash/inRange.js'; import isInteger from 'lodash/isInteger.js'; +import { Hash } from 'ox'; import secp256k1 from 'secp256k1'; -import { parseTransaction, type Hex } from 'viem'; +import { type Hex, parseTransaction } from 'viem'; const EC = elliptic.ec; const { ecdsaRecover } = secp256k1; @@ -27,7 +27,7 @@ import { isValid4ByteResponse, isValidBlockExplorerResponse, } from './shared/validators'; -import { FirmwareConstants } from './types'; +import type { FirmwareConstants } from './types'; const { COINS, PURPOSES } = BIP_CONSTANTS; let ec: any; @@ -37,11 +37,13 @@ let ec: any; //-------------------------------------------------- /** @internal Parse a response from the Lattice1 */ -export const parseLattice1Response = function (r: string): { +export const parseLattice1Response = ( + r: string, +): { errorMessage?: string; responseCode?: number; data?: Buffer; -} { +} => { const parsed: { errorMessage: string | null; data: Buffer | null; @@ -102,7 +104,7 @@ export const parseLattice1Response = function (r: string): { }; /** @internal */ -export const checksum = function (x: Buffer): number { +export const checksum = (x: Buffer): number => { // crc32 returns a signed integer - need to cast it to unsigned // Note that this uses the default 0xedb88320 polynomial return crc32.buf(x) >>> 0; // Need this to be a uint, hence the bit shift @@ -111,7 +113,7 @@ export const checksum = function (x: Buffer): number { // Get a 74-byte padded DER-encoded signature buffer // `sig` must be the signature output from elliptic.js /** @internal */ -export const toPaddedDER = function (sig: any): Buffer { +export const toPaddedDER = (sig: any): Buffer => { // We use 74 as the maximum length of a DER signature. All sigs must // be right-padded with zeros so that this can be a fixed size field const b = Buffer.alloc(74); @@ -124,10 +126,10 @@ export const toPaddedDER = function (sig: any): Buffer { // TRANSACTION UTILS //-------------------------------------------------- /** @internal */ -export const isValidAssetPath = function ( +export const isValidAssetPath = ( path: number[], fwConstants: FirmwareConstants, -): boolean { +): boolean => { const allowedPurposes = [ PURPOSES.ETH, PURPOSES.BTC_LEGACY, @@ -158,7 +160,7 @@ export const isValidAssetPath = function ( }; /** @internal */ -export const splitFrames = function (data: Buffer, frameSz: number): Buffer[] { +export const splitFrames = (data: Buffer, frameSz: number): Buffer[] => { const frames = []; const n = Math.ceil(data.length / frameSz); let off = 0; @@ -179,10 +181,10 @@ function isBase10NumStr(x: string): boolean { } /** @internal Ensure a param is represented by a buffer */ -export const ensureHexBuffer = function ( +export const ensureHexBuffer = ( x: string | number | bigint | Buffer, zeroIsNull = true, -): Buffer { +): Buffer => { try { const isZeroNumber = typeof x === 'number' && x === 0; const isZeroBigInt = typeof x === 'bigint' && x === 0n; @@ -215,7 +217,7 @@ export const ensureHexBuffer = function ( }; /** @internal */ -export const fixLen = function (msg: Buffer, length: number): Buffer { +export const fixLen = (msg: Buffer, length: number): Buffer => { const buf = Buffer.alloc(length); if (msg.length < length) { msg.copy(buf, length - msg.length); @@ -228,7 +230,7 @@ export const fixLen = function (msg: Buffer, length: number): Buffer { // CRYPTO UTILS //-------------------------------------------------- /** @internal */ -export const aes256_encrypt = function (data: Buffer, key: Buffer): Buffer { +export const aes256_encrypt = (data: Buffer, key: Buffer): Buffer => { const iv = Buffer.from(ProtocolConstants.aesIv); const aesCbc = new aes.ModeOfOperation.cbc(key, iv); const paddedData = @@ -237,7 +239,7 @@ export const aes256_encrypt = function (data: Buffer, key: Buffer): Buffer { }; /** @internal */ -export const aes256_decrypt = function (data: Buffer, key: Buffer): Buffer { +export const aes256_decrypt = (data: Buffer, key: Buffer): Buffer => { const iv = Buffer.from(ProtocolConstants.aesIv); const aesCbc = new aes.ModeOfOperation.cbc(key, iv); return Buffer.from(aesCbc.decrypt(data)); @@ -245,7 +247,7 @@ export const aes256_decrypt = function (data: Buffer, key: Buffer): Buffer { // Decode a DER signature. Returns signature object {r, s } or null if there is an error /** @internal */ -export const parseDER = function (sigBuf: Buffer) { +export const parseDER = (sigBuf: Buffer) => { if (sigBuf[0] !== 0x30 || sigBuf[2] !== 0x02) throw new Error('Failed to decode DER signature'); let off = 3; @@ -262,13 +264,13 @@ export const parseDER = function (sigBuf: Buffer) { }; /** @internal */ -export const getP256KeyPair = function (priv: Buffer | string): any { +export const getP256KeyPair = (priv: Buffer | string): any => { if (ec === undefined) ec = new EC('p256'); return ec.keyFromPrivate(priv, 'hex'); }; /** @internal */ -export const getP256KeyPairFromPub = function (pub: Buffer | string): any { +export const getP256KeyPairFromPub = (pub: Buffer | string): any => { if (ec === undefined) ec = new EC('p256'); // Convert Buffer to hex string if needed const pubHex = Buffer.isBuffer(pub) ? pub.toString('hex') : pub; @@ -276,10 +278,10 @@ export const getP256KeyPairFromPub = function (pub: Buffer | string): any { }; /** @internal */ -export const buildSignerPathBuf = function ( +export const buildSignerPathBuf = ( signerPath: number[], varAddrPathSzAllowed: boolean, -): Buffer { +): Buffer => { const buf = Buffer.alloc(24); let off = 0; if (varAddrPathSzAllowed && signerPath.length > 5) @@ -302,10 +304,7 @@ export const buildSignerPathBuf = function ( // OTHER UTILS //-------------------------------------------------- /** @internal */ -export const isAsciiStr = function ( - str: string, - allowFormatChars = false, -): boolean { +export const isAsciiStr = (str: string, allowFormatChars = false): boolean => { if (typeof str !== 'string') { return false; } @@ -325,15 +324,11 @@ export const isAsciiStr = function ( }; /** @internal Check if a value exists in an object. Only checks first level of keys. */ -export const existsIn = function ( - val: T, - obj: { [key: string]: T }, -): boolean { - return Object.keys(obj).some((key) => obj[key] === val); -}; +export const existsIn = (val: T, obj: { [key: string]: T }): boolean => + Object.keys(obj).some((key) => obj[key] === val); /** @internal Create a buffer of size `n` and fill it with random data */ -export const randomBytes = function (n: number): Buffer { +export const randomBytes = (n: number): Buffer => { const buf = Buffer.alloc(n); for (let i = 0; i < n; i++) { buf[i] = Math.round(Math.random() * 255); @@ -475,7 +470,7 @@ async function fetchSupportedChainData( return fetchAndCache(url) .then((res) => res.json()) .then((body) => { - if (body && body.result) { + if (body?.result) { try { return JSON.parse(body.result); } catch { @@ -498,7 +493,7 @@ async function fetch4byteData(selector: string): Promise { return await fetch(url) .then((res) => res.json()) .then((body) => { - if (body && body.results) { + if (body?.results) { return body.results; } else { throw new Error('No results found'); @@ -729,7 +724,7 @@ export const generateAppSecret = ( * @param resp - Lattice response with sig and pubkey * @returns BN object containing the `v` param */ -export const getV = function (tx: any, resp: any) { +export const getV = (tx: any, resp: any) => { let chainId: string | undefined; let hash: Uint8Array; let type: string | number | undefined; @@ -875,10 +870,10 @@ export const getV = function (tx: any, resp: any) { * @param txData - Transaction data containing chainId, useEIP155, and type * @returns The properly formatted v value as Buffer or BN */ -export const convertRecoveryToV = function ( +export const convertRecoveryToV = ( recovery: number, txData: any = {}, -): Buffer | InstanceType { +): Buffer | InstanceType => { const { chainId, useEIP155, type } = txData; // For typed transactions (EIP-2930, EIP-1559, EIP-7702), we want the recoveryParam (0 or 1) @@ -918,7 +913,7 @@ export const convertRecoveryToV = function ( * @param publicKey - Expected public key * @returns 0 or 1 for the y-parity value */ -export const getYParity = function ( +export const getYParity = ( messageHash: | Buffer | Uint8Array @@ -927,7 +922,7 @@ export const getYParity = function ( | any, signature?: { r: any; s: any } | any, publicKey?: Buffer | Uint8Array | string, -): number { +): number => { // Handle legacy object format for backward compatibility if ( typeof messageHash === 'object' && @@ -942,7 +937,7 @@ export const getYParity = function ( } // Handle legacy transaction format for backward compatibility - if (signature && signature.sig && signature.pubkey && !publicKey) { + if (signature?.sig && signature.pubkey && !publicKey) { return getYParity(messageHash, signature.sig, signature.pubkey); } @@ -1008,9 +1003,7 @@ export const getYParity = function ( if (Buffer.from(recovered).equals(pubkeyBuf)) { return recovery; } - } catch { - continue; - } + } catch {} } throw new Error( diff --git a/pnpm-lock.yaml b/pnpm-lock.yaml index 2ba3a4e6..33bc9781 100644 --- a/pnpm-lock.yaml +++ b/pnpm-lock.yaml @@ -6,7 +6,14 @@ settings: importers: - .: {} + .: + devDependencies: + '@biomejs/biome': + specifier: ^1.9.0 + version: 1.9.4 + turbo: + specifier: ^2.3.0 + version: 2.7.4 packages/docs: dependencies: @@ -172,9 +179,6 @@ importers: '@chainsafe/bls-keystore': specifier: ^3.1.0 version: 3.1.0 - '@eslint/js': - specifier: ^9.36.0 - version: 9.39.2 '@noble/bls12-381': specifier: ^1.4.0 version: 1.4.0 @@ -205,12 +209,6 @@ importers: '@types/seedrandom': specifier: ^3.0.8 version: 3.0.8 - '@typescript-eslint/eslint-plugin': - specifier: ^8.44.1 - version: 8.52.0(@typescript-eslint/parser@8.52.0(eslint@9.39.2(jiti@1.21.7))(typescript@5.9.3))(eslint@9.39.2(jiti@1.21.7))(typescript@5.9.3) - '@typescript-eslint/parser': - specifier: ^8.44.1 - version: 8.52.0(eslint@9.39.2(jiti@1.21.7))(typescript@5.9.3) '@vitest/coverage-istanbul': specifier: ^2.1.3 version: 2.1.9(vitest@3.2.4(@types/debug@4.1.12)(@types/node@24.10.4)(jiti@1.21.7)(msw@2.12.7(@types/node@24.10.4)(typescript@5.9.3))(terser@5.44.1)(tsx@4.21.0)) @@ -235,15 +233,6 @@ importers: ed25519-hd-key: specifier: ^1.3.0 version: 1.3.0 - eslint: - specifier: ^9.36.0 - version: 9.39.2(jiti@1.21.7) - eslint-config-prettier: - specifier: ^10.1.8 - version: 10.1.8(eslint@9.39.2(jiti@1.21.7)) - eslint-plugin-prettier: - specifier: ^5.5.4 - version: 5.5.4(@types/eslint@9.6.1)(eslint-config-prettier@10.1.8(eslint@9.39.2(jiti@1.21.7)))(eslint@9.39.2(jiti@1.21.7))(prettier@3.7.4) ethereumjs-util: specifier: ^7.1.5 version: 7.1.5 @@ -253,12 +242,6 @@ importers: msw: specifier: ^2.11.3 version: 2.12.7(@types/node@24.10.4)(typescript@5.9.3) - prettier: - specifier: ^3.6.2 - version: 3.7.4 - prettier-eslint: - specifier: ^16.4.2 - version: 16.4.2(typescript@5.9.3) random-words: specifier: ^2.0.1 version: 2.0.1 @@ -979,6 +962,59 @@ packages: resolution: {integrity: sha512-qQ5m48eI/MFLQ5PxQj4PFaprjyCTLI37ElWMmNs0K8Lk3dVeOdNpB3ks8jc7yM5CDmVC73eMVk/trk3fgmrUpA==} engines: {node: '>=6.9.0'} + '@biomejs/biome@1.9.4': + resolution: {integrity: sha512-1rkd7G70+o9KkTn5KLmDYXihGoTaIGO9PIIN2ZB7UJxFrWw04CZHPYiMRjYsaDvVV7hP1dYNRLxSANLaBFGpog==} + engines: {node: '>=14.21.3'} + hasBin: true + + '@biomejs/cli-darwin-arm64@1.9.4': + resolution: {integrity: sha512-bFBsPWrNvkdKrNCYeAp+xo2HecOGPAy9WyNyB/jKnnedgzl4W4Hb9ZMzYNbf8dMCGmUdSavlYHiR01QaYR58cw==} + engines: {node: '>=14.21.3'} + cpu: [arm64] + os: [darwin] + + '@biomejs/cli-darwin-x64@1.9.4': + resolution: {integrity: sha512-ngYBh/+bEedqkSevPVhLP4QfVPCpb+4BBe2p7Xs32dBgs7rh9nY2AIYUL6BgLw1JVXV8GlpKmb/hNiuIxfPfZg==} + engines: {node: '>=14.21.3'} + cpu: [x64] + os: [darwin] + + '@biomejs/cli-linux-arm64-musl@1.9.4': + resolution: {integrity: sha512-v665Ct9WCRjGa8+kTr0CzApU0+XXtRgwmzIf1SeKSGAv+2scAlW6JR5PMFo6FzqqZ64Po79cKODKf3/AAmECqA==} + engines: {node: '>=14.21.3'} + cpu: [arm64] + os: [linux] + + '@biomejs/cli-linux-arm64@1.9.4': + resolution: {integrity: sha512-fJIW0+LYujdjUgJJuwesP4EjIBl/N/TcOX3IvIHJQNsAqvV2CHIogsmA94BPG6jZATS4Hi+xv4SkBBQSt1N4/g==} + engines: {node: '>=14.21.3'} + cpu: [arm64] + os: [linux] + + '@biomejs/cli-linux-x64-musl@1.9.4': + resolution: {integrity: sha512-gEhi/jSBhZ2m6wjV530Yy8+fNqG8PAinM3oV7CyO+6c3CEh16Eizm21uHVsyVBEB6RIM8JHIl6AGYCv6Q6Q9Tg==} + engines: {node: '>=14.21.3'} + cpu: [x64] + os: [linux] + + '@biomejs/cli-linux-x64@1.9.4': + resolution: {integrity: sha512-lRCJv/Vi3Vlwmbd6K+oQ0KhLHMAysN8lXoCI7XeHlxaajk06u7G+UsFSO01NAs5iYuWKmVZjmiOzJ0OJmGsMwg==} + engines: {node: '>=14.21.3'} + cpu: [x64] + os: [linux] + + '@biomejs/cli-win32-arm64@1.9.4': + resolution: {integrity: sha512-tlbhLk+WXZmgwoIKwHIHEBZUwxml7bRJgk0X2sPyNR3S93cdRq6XulAZRQJ17FYGGzWne0fgrXBKpl7l4M87Hg==} + engines: {node: '>=14.21.3'} + cpu: [arm64] + os: [win32] + + '@biomejs/cli-win32-x64@1.9.4': + resolution: {integrity: sha512-8Y5wMhVIPaWe6jw2H+KlEm4wP/f7EW3810ZLmDlrEEy5KvBsb9ECEfu/kMWD484ijfQ8+nIi0giMgu9g1UAuuA==} + engines: {node: '>=14.21.3'} + cpu: [x64] + os: [win32] + '@braintree/sanitize-url@6.0.4': resolution: {integrity: sha512-s3jaWicZd0pkP0jf5ysyHUI/RE7MHos6qlToFcGWXVp+ykHOy77OUMrfbgJ9it2C5bow7OIQwYYaHjk9XlBQ2A==} @@ -1787,52 +1823,6 @@ packages: cpu: [x64] os: [win32] - '@eslint-community/eslint-utils@4.9.1': - resolution: {integrity: sha512-phrYmNiYppR7znFEdqgfWHXR6NCkZEK7hwWDHZUjit/2/U0r6XvkDl0SYnoM51Hq7FhCGdLDT6zxCCOY1hexsQ==} - engines: {node: ^12.22.0 || ^14.17.0 || >=16.0.0} - peerDependencies: - eslint: ^6.0.0 || ^7.0.0 || >=8.0.0 - - '@eslint-community/regexpp@4.12.2': - resolution: {integrity: sha512-EriSTlt5OC9/7SXkRSCAhfSxxoSUgBm33OH+IkwbdpgoqsSsUg7y3uh+IICI/Qg4BBWr3U2i39RpmycbxMq4ew==} - engines: {node: ^12.0.0 || ^14.0.0 || >=16.0.0} - - '@eslint/config-array@0.21.1': - resolution: {integrity: sha512-aw1gNayWpdI/jSYVgzN5pL0cfzU02GT3NBpeT/DXbx1/1x7ZKxFPd9bwrzygx/qiwIQiJ1sw/zD8qY/kRvlGHA==} - engines: {node: ^18.18.0 || ^20.9.0 || >=21.1.0} - - '@eslint/config-helpers@0.4.2': - resolution: {integrity: sha512-gBrxN88gOIf3R7ja5K9slwNayVcZgK6SOUORm2uBzTeIEfeVaIhOpCtTox3P6R7o2jLFwLFTLnC7kU/RGcYEgw==} - engines: {node: ^18.18.0 || ^20.9.0 || >=21.1.0} - - '@eslint/core@0.17.0': - resolution: {integrity: sha512-yL/sLrpmtDaFEiUj1osRP4TI2MDz1AddJL+jZ7KSqvBuliN4xqYY54IfdN8qD8Toa6g1iloph1fxQNkjOxrrpQ==} - engines: {node: ^18.18.0 || ^20.9.0 || >=21.1.0} - - '@eslint/eslintrc@2.1.4': - resolution: {integrity: sha512-269Z39MS6wVJtsoUl10L60WdkhJVdPG24Q4eZTH3nnF6lpvSShEK3wQjDX9JRWAUPvPh7COouPpU9IrqaZFvtQ==} - engines: {node: ^12.22.0 || ^14.17.0 || >=16.0.0} - - '@eslint/eslintrc@3.3.3': - resolution: {integrity: sha512-Kr+LPIUVKz2qkx1HAMH8q1q6azbqBAsXJUxBl/ODDuVPX45Z9DfwB8tPjTi6nNZ8BuM3nbJxC5zCAg5elnBUTQ==} - engines: {node: ^18.18.0 || ^20.9.0 || >=21.1.0} - - '@eslint/js@8.57.1': - resolution: {integrity: sha512-d9zaMRSTIKDLhctzH12MtXvJKSSUhaHcjV+2Z+GK+EEY7XKpP5yR4x+N3TAcHTcu963nIr+TMcCb4DBCYX1z6Q==} - engines: {node: ^12.22.0 || ^14.17.0 || >=16.0.0} - - '@eslint/js@9.39.2': - resolution: {integrity: sha512-q1mjIoW1VX4IvSocvM/vbTiveKC4k9eLrajNEuSsmjymSDEbpGddtpfOoN7YGAqBK3NG+uqo8ia4PDTt8buCYA==} - engines: {node: ^18.18.0 || ^20.9.0 || >=21.1.0} - - '@eslint/object-schema@2.1.7': - resolution: {integrity: sha512-VtAOaymWVfZcmZbp6E2mympDIHvyjXs/12LqWYjVw6qjrfF+VK+fyG33kChz3nnK+SU5/NeHOqrTEHS8sXO3OA==} - engines: {node: ^18.18.0 || ^20.9.0 || >=21.1.0} - - '@eslint/plugin-kit@0.4.1': - resolution: {integrity: sha512-43/qtrDUokr7LJqoF2c3+RInu/t4zfrpYdoSDfYyhg52rwLV6TnOvdG4fXm7IkSB3wErkcmJS9iEhjVtOSEjjA==} - engines: {node: ^18.18.0 || ^20.9.0 || >=21.1.0} - '@ethereumjs/common@10.1.0': resolution: {integrity: sha512-zIHCy0i2LFmMDp+QkENyoPGxcoD3QzeNVhx6/vE4nJk4uWGNXzO8xJ2UC4gtGW4UJTAOXja8Z1yZMVeRc2/+Ew==} @@ -1941,31 +1931,6 @@ packages: '@hapi/topo@5.1.0': resolution: {integrity: sha512-foQZKJig7Ob0BMAYBfcJk8d77QtOe7Wo4ox7ff1lQYoNNAb6jwcY1ncdoy2e9wQZzvNy7ODZCYJkK8kzmcAnAg==} - '@humanfs/core@0.19.1': - resolution: {integrity: sha512-5DyQ4+1JEUzejeK1JGICcideyfUbGixgS9jNgex5nqkW+cY7WZhxBigmieN5Qnw9ZosSNVC9KQKyb+GUaGyKUA==} - engines: {node: '>=18.18.0'} - - '@humanfs/node@0.16.7': - resolution: {integrity: sha512-/zUx+yOsIrG4Y43Eh2peDeKCxlRt/gET6aHfaKpuq267qXdYDFViVHfMaLyygZOnl0kGWxFIgsBy8QFuTLUXEQ==} - engines: {node: '>=18.18.0'} - - '@humanwhocodes/config-array@0.13.0': - resolution: {integrity: sha512-DZLEEqFWQFiyK6h5YIeynKx7JlvCYWL0cImfSRXZ9l4Sg2efkFGTuFf6vzXjK1cq6IYkU+Eg/JizXw+TD2vRNw==} - engines: {node: '>=10.10.0'} - deprecated: Use @eslint/config-array instead - - '@humanwhocodes/module-importer@1.0.1': - resolution: {integrity: sha512-bxveV4V8v5Yb4ncFTT3rPSgZBOpCkjfK0y4oVVVJwIuDVBRMDXrPyXRL988i5ap9m9bnyEEjWfm5WkBmtffLfA==} - engines: {node: '>=12.22'} - - '@humanwhocodes/object-schema@2.0.3': - resolution: {integrity: sha512-93zYdMES/c1D69yZiKDBj0V24vqNzB/koF26KPaagAfd3P/4gUlh3Dys5ogAK+Exi9QyzlD8x/08Zt7wIKcDcA==} - deprecated: Use @eslint/object-schema instead - - '@humanwhocodes/retry@0.4.3': - resolution: {integrity: sha512-bV0Tgo9K4hfPCek+aMAn81RppFKv2ySDQeMoSZuvTASywNTnVJCArCZE2FWqpvIatKu7VMRLWlR1EazvVhDyhQ==} - engines: {node: '>=18.18'} - '@inquirer/ansi@1.0.2': resolution: {integrity: sha512-S8qNSZiYzFd0wAcyG5AXCvUHC5Sr7xpZ9wZ2py9XR88jUz8wooStVx5M6dRzczbBWjic9NP7+rY0Xi7qqK/aMQ==} engines: {node: '>=18'} @@ -2221,10 +2186,6 @@ packages: resolution: {integrity: sha512-+1VkjdD0QBLPodGrJUeqarH8VAIvQODIbwh9XpP5Syisf7YoQgsJKPNFoqqLQlu+VQ/tVSshMR6loPMn8U+dPg==} engines: {node: '>=14'} - '@pkgr/core@0.2.9': - resolution: {integrity: sha512-QNqXyfVS2wm9hweSYD2O7F0G06uurj9kZ96TRQE5Y9hU7+tgdZwIkbAKc5Ocy1HxEY2kuDQa6cQ1WRs/O5LFKA==} - engines: {node: ^12.20.0 || ^14.18.0 || >=16.0.0} - '@pnpm/config.env-replace@1.1.0': resolution: {integrity: sha512-htyl8TWnKL7K/ESFa1oW2UB5lVDxuF5DpM7tBi6Hu2LNL3mWkIzNLG6N4zoCUP1lCKNxWy/3iu8mS8MvToGd6w==} engines: {node: '>=12.22.0'} @@ -2818,96 +2779,6 @@ packages: '@types/yauzl@2.10.3': resolution: {integrity: sha512-oJoftv0LSuaDZE3Le4DbKX+KS9G36NzOeSap90UIK0yMA/NhKJhqlSGtNDORNRaIbQfzjXDrQa0ytJ6mNRGz/Q==} - '@typescript-eslint/eslint-plugin@8.52.0': - resolution: {integrity: sha512-okqtOgqu2qmZJ5iN4TWlgfF171dZmx2FzdOv2K/ixL2LZWDStL8+JgQerI2sa8eAEfoydG9+0V96m7V+P8yE1Q==} - engines: {node: ^18.18.0 || ^20.9.0 || >=21.1.0} - peerDependencies: - '@typescript-eslint/parser': ^8.52.0 - eslint: ^8.57.0 || ^9.0.0 - typescript: '>=4.8.4 <6.0.0' - - '@typescript-eslint/parser@6.21.0': - resolution: {integrity: sha512-tbsV1jPne5CkFQCgPBcDOt30ItF7aJoZL997JSF7MhGQqOeT3svWRYxiqlfA5RUdlHN6Fi+EI9bxqbdyAUZjYQ==} - engines: {node: ^16.0.0 || >=18.0.0} - peerDependencies: - eslint: ^7.0.0 || ^8.0.0 - typescript: '*' - peerDependenciesMeta: - typescript: - optional: true - - '@typescript-eslint/parser@8.52.0': - resolution: {integrity: sha512-iIACsx8pxRnguSYhHiMn2PvhvfpopO9FXHyn1mG5txZIsAaB6F0KwbFnUQN3KCiG3Jcuad/Cao2FAs1Wp7vAyg==} - engines: {node: ^18.18.0 || ^20.9.0 || >=21.1.0} - peerDependencies: - eslint: ^8.57.0 || ^9.0.0 - typescript: '>=4.8.4 <6.0.0' - - '@typescript-eslint/project-service@8.52.0': - resolution: {integrity: sha512-xD0MfdSdEmeFa3OmVqonHi+Cciab96ls1UhIF/qX/O/gPu5KXD0bY9lu33jj04fjzrXHcuvjBcBC+D3SNSadaw==} - engines: {node: ^18.18.0 || ^20.9.0 || >=21.1.0} - peerDependencies: - typescript: '>=4.8.4 <6.0.0' - - '@typescript-eslint/scope-manager@6.21.0': - resolution: {integrity: sha512-OwLUIWZJry80O99zvqXVEioyniJMa+d2GrqpUTqi5/v5D5rOrppJVBPa0yKCblcigC0/aYAzxxqQ1B+DS2RYsg==} - engines: {node: ^16.0.0 || >=18.0.0} - - '@typescript-eslint/scope-manager@8.52.0': - resolution: {integrity: sha512-ixxqmmCcc1Nf8S0mS0TkJ/3LKcC8mruYJPOU6Ia2F/zUUR4pApW7LzrpU3JmtePbRUTes9bEqRc1Gg4iyRnDzA==} - engines: {node: ^18.18.0 || ^20.9.0 || >=21.1.0} - - '@typescript-eslint/tsconfig-utils@8.52.0': - resolution: {integrity: sha512-jl+8fzr/SdzdxWJznq5nvoI7qn2tNYV/ZBAEcaFMVXf+K6jmXvAFrgo/+5rxgnL152f//pDEAYAhhBAZGrVfwg==} - engines: {node: ^18.18.0 || ^20.9.0 || >=21.1.0} - peerDependencies: - typescript: '>=4.8.4 <6.0.0' - - '@typescript-eslint/type-utils@8.52.0': - resolution: {integrity: sha512-JD3wKBRWglYRQkAtsyGz1AewDu3mTc7NtRjR/ceTyGoPqmdS5oCdx/oZMWD5Zuqmo6/MpsYs0wp6axNt88/2EQ==} - engines: {node: ^18.18.0 || ^20.9.0 || >=21.1.0} - peerDependencies: - eslint: ^8.57.0 || ^9.0.0 - typescript: '>=4.8.4 <6.0.0' - - '@typescript-eslint/types@6.21.0': - resolution: {integrity: sha512-1kFmZ1rOm5epu9NZEZm1kckCDGj5UJEf7P1kliH4LKu/RkwpsfqqGmY2OOcUs18lSlQBKLDYBOGxRVtrMN5lpg==} - engines: {node: ^16.0.0 || >=18.0.0} - - '@typescript-eslint/types@8.52.0': - resolution: {integrity: sha512-LWQV1V4q9V4cT4H5JCIx3481iIFxH1UkVk+ZkGGAV1ZGcjGI9IoFOfg3O6ywz8QqCDEp7Inlg6kovMofsNRaGg==} - engines: {node: ^18.18.0 || ^20.9.0 || >=21.1.0} - - '@typescript-eslint/typescript-estree@6.21.0': - resolution: {integrity: sha512-6npJTkZcO+y2/kr+z0hc4HwNfrrP4kNYh57ek7yCNlrBjWQ1Y0OS7jiZTkgumrvkX5HkEKXFZkkdFNkaW2wmUQ==} - engines: {node: ^16.0.0 || >=18.0.0} - peerDependencies: - typescript: '*' - peerDependenciesMeta: - typescript: - optional: true - - '@typescript-eslint/typescript-estree@8.52.0': - resolution: {integrity: sha512-XP3LClsCc0FsTK5/frGjolyADTh3QmsLp6nKd476xNI9CsSsLnmn4f0jrzNoAulmxlmNIpeXuHYeEQv61Q6qeQ==} - engines: {node: ^18.18.0 || ^20.9.0 || >=21.1.0} - peerDependencies: - typescript: '>=4.8.4 <6.0.0' - - '@typescript-eslint/utils@8.52.0': - resolution: {integrity: sha512-wYndVMWkweqHpEpwPhwqE2lnD2DxC6WVLupU/DOt/0/v+/+iQbbzO3jOHjmBMnhu0DgLULvOaU4h4pwHYi2oRQ==} - engines: {node: ^18.18.0 || ^20.9.0 || >=21.1.0} - peerDependencies: - eslint: ^8.57.0 || ^9.0.0 - typescript: '>=4.8.4 <6.0.0' - - '@typescript-eslint/visitor-keys@6.21.0': - resolution: {integrity: sha512-JJtkDduxLi9bivAB+cYOVMtbkqdPOhZ+ZI5LC47MIRrDV4Yn2o+ZnW10Nkmr28xRpSpdJ6Sm42Hjf2+REYXm0A==} - engines: {node: ^16.0.0 || >=18.0.0} - - '@typescript-eslint/visitor-keys@8.52.0': - resolution: {integrity: sha512-ink3/Zofus34nmBsPjow63FP5M7IGff0RKAgqR6+CFpdk22M7aLwC9gOcLGYqr7MczLPzZVERW9hRog3O4n1sQ==} - engines: {node: ^18.18.0 || ^20.9.0 || >=21.1.0} - '@ungap/structured-clone@1.3.0': resolution: {integrity: sha512-WmoN8qaIAo7WTYWbAZuG8PYEhn5fkz7dZrqTBZ7dtt//lL2Gwms1IcnQ5yHqjDfX8Ft5j4YzDM23f87zBfDe9g==} @@ -3165,10 +3036,6 @@ packages: engines: {'0': node >= 0.8.0} hasBin: true - ansi-regex@2.1.1: - resolution: {integrity: sha512-TIGnTpdo+E3+pCyAluZvtED5p5wCqLdezCyhPZzKPcxvFplEt4i+W7OONCKgeZFT3+y5NZZfOOS/Bdcanm1MYA==} - engines: {node: '>=0.10.0'} - ansi-regex@5.0.1: resolution: {integrity: sha512-quJQXlTSUGL2LH9SUXo8VwsY4soanhgo6LNSm84E1LBcE8s3O0wpdiRzyR9z/ZZJMlMWv37qOOb9pdJlMUEKFQ==} engines: {node: '>=8'} @@ -3180,10 +3047,6 @@ packages: ansi-sequence-parser@1.1.3: resolution: {integrity: sha512-+fksAx9eG3Ab6LDnLs3ZqZa8KVJ/jYnX+D4Qe1azX+LFGFAXqynCQLOdLpNYN/l9e7l6hMWwZbrnctqr6eSQSw==} - ansi-styles@2.2.1: - resolution: {integrity: sha512-kmCevFghRiWM7HB5zTPULl4r9bVFSWjz62MhqizDGUrq2NWuNMQyuv4tHHoKJHs69M/MF64lEcHdYIocrdWQYA==} - engines: {node: '>=0.10.0'} - ansi-styles@4.3.0: resolution: {integrity: sha512-zbB9rCJAT1rbjiVDb2hqKFHNYLxgtk8NURxZ3IZwD3F6NtxbXZQCnnSi1Lkx+IDohdPlFp222wVALIheZJQSEg==} engines: {node: '>=8'} @@ -3550,10 +3413,6 @@ packages: resolution: {integrity: sha512-4zNhdJD/iOjSH0A05ea+Ke6MU5mmpQcbQsSOkgdaUMJ9zTlDTD/GYlwohmIE2u0gaxHYiVHEn1Fw9mZ/ktJWgw==} engines: {node: '>=18'} - chalk@1.1.3: - resolution: {integrity: sha512-U3lRVLMSlsCfjqYPbLyVv11M9CPW4I728d6TCKMAOJueEeB9/8o+eSsMnxPJD+Q+K909sdESg7C+tIkoH6on1A==} - engines: {node: '>=0.10.0'} - chalk@4.1.2: resolution: {integrity: sha512-oKnbhFyRIXpUuez8iBMmyEa4nbj4IOQyuhc/wy9kY7/WVPcwIO9VA668Pu8RkO7+0G76SLROeyw9CpQ061i4mA==} engines: {node: '>=10'} @@ -3703,10 +3562,6 @@ packages: common-path-prefix@3.0.0: resolution: {integrity: sha512-QE33hToZseCH3jS0qN96O/bSh3kaw/h+Tq7ngyY9eWDUnTlTNUyqfqvCXioLe5Na5jFsL78ra/wuBU4iuEgd4w==} - common-tags@1.8.2: - resolution: {integrity: sha512-gk/Z852D2Wtb//0I+kRFNKKE9dIIVirjoqPoA1wJU+XePVXZfGeBpk45+A1rKO4Q43prqWBNY/MiIeRLbPWUaA==} - engines: {node: '>=4.0.0'} - compare-versions@6.1.1: resolution: {integrity: sha512-4hm4VPpIecmlg59CHXnRDnqGplJFrbLG4aFEl5vl6cK1u76ws3LLvX7ikFnTDl5vo39sjWD6AaDPYodJp/NNHg==} @@ -4140,9 +3995,6 @@ packages: resolution: {integrity: sha512-LOHxIOaPYdHlJRtCQfDIVZtfw/ufM8+rVj649RIHzcm/vGwQRXFt6OPqIFWsm2XEMrNIEtWR64sY1LEKD2vAOA==} engines: {node: '>=4.0.0'} - deep-is@0.1.4: - resolution: {integrity: sha512-oIPzksmTg4/MriiaYGO+okXDT7ztn/w3Eptv/+gSIdMdKsJo0u4CfYNFJPy+4SKMuCqGw2wxnA+URMg3t8a/bQ==} - deepmerge@4.3.1: resolution: {integrity: sha512-3sUqbMEc77XqpdNO7FRyRog+eW3ph+GYCbj+rK+uYyRMuwsVy0rMiVtPn+QJlKFvWP/1PYpapqYn0Me2knFn+A==} engines: {node: '>=0.10.0'} @@ -4228,17 +4080,10 @@ packages: resolution: {integrity: sha512-WkrWp9GR4KXfKGYzOLmTuGVi1UWFfws377n9cc55/tb6DuqyF6pcQ5AbiHEshaDpY9v6oaSr2XCDidGmMwdzIA==} engines: {node: '>=8'} - dlv@1.1.3: - resolution: {integrity: sha512-+HlytyjlPKnIG8XuRG8WvmBP8xs8P71y+SKKS6ZXWoEgLuePxtDoUEiH7WkdePWrQ5JBpE6aoVqfZfJUQkjXwA==} - dns-packet@5.6.1: resolution: {integrity: sha512-l4gcSouhcgIKRvyy99RNVOgxXiicE+2jZoNmaNmZ6JXiGajBOJAesk1OBlJuM5k2c+eudGdLxDqXuPCKIj6kpw==} engines: {node: '>=6'} - doctrine@3.0.0: - resolution: {integrity: sha512-yS+Q5i3hBf7GBkd4KG8a7eBNNWNGLTaEwwYWUijIYM7zrlYDM0BFXHjjPWlWZ1Rg7UaddZeIDmi9jF3HmqiQ2w==} - engines: {node: '>=6.0.0'} - docusaurus-plugin-typedoc@1.0.5: resolution: {integrity: sha512-mv8LBJYilGOOPLqaIM3vbYc34m4qwOCpb4WfP24DOPFNj2uiTerw8sg9MGvN6Jx2+J8rq9/WMnjcyz3UMqoIIQ==} peerDependencies: @@ -4437,79 +4282,15 @@ packages: engines: {node: '>=6.0'} hasBin: true - eslint-config-prettier@10.1.8: - resolution: {integrity: sha512-82GZUjRS0p/jganf6q1rEO25VSoHH0hKPCTrgillPjdI/3bgBhAE1QzHrHTizjpRvy6pGAvKjDJtk2pF9NDq8w==} - hasBin: true - peerDependencies: - eslint: '>=7.0.0' - - eslint-plugin-prettier@5.5.4: - resolution: {integrity: sha512-swNtI95SToIz05YINMA6Ox5R057IMAmWZ26GqPxusAp1TZzj+IdY9tXNWWD3vkF/wEqydCONcwjTFpxybBqZsg==} - engines: {node: ^14.18.0 || >=16.0.0} - peerDependencies: - '@types/eslint': '>=8.0.0' - eslint: '>=8.0.0' - eslint-config-prettier: '>= 7.0.0 <10.0.0 || >=10.1.0' - prettier: '>=3.0.0' - peerDependenciesMeta: - '@types/eslint': - optional: true - eslint-config-prettier: - optional: true - eslint-scope@5.1.1: resolution: {integrity: sha512-2NxwbF/hZ0KpepYN0cNbo+FN6XoK7GaHlQhgx/hIZl6Va0bF45RQOOwhLIy8lQDbuCiadSLCBnH2CFYquit5bw==} engines: {node: '>=8.0.0'} - eslint-scope@7.2.2: - resolution: {integrity: sha512-dOt21O7lTMhDM+X9mB4GX+DZrZtCUJPL/wlcTqxyrx5IvO0IYtILdtrQGQp+8n5S0gwSVmOf9NQrjMOgfQZlIg==} - engines: {node: ^12.22.0 || ^14.17.0 || >=16.0.0} - - eslint-scope@8.4.0: - resolution: {integrity: sha512-sNXOfKCn74rt8RICKMvJS7XKV/Xk9kA7DyJr8mJik3S7Cwgy3qlkkmyS2uQB3jiJg6VNdZd/pDBJu0nvG2NlTg==} - engines: {node: ^18.18.0 || ^20.9.0 || >=21.1.0} - - eslint-visitor-keys@3.4.3: - resolution: {integrity: sha512-wpc+LXeiyiisxPlEkUzU6svyS1frIO3Mgxj1fdy7Pm8Ygzguax2N3Fa/D/ag1WqbOprdI+uY6wMUl8/a2G+iag==} - engines: {node: ^12.22.0 || ^14.17.0 || >=16.0.0} - - eslint-visitor-keys@4.2.1: - resolution: {integrity: sha512-Uhdk5sfqcee/9H/rCOJikYz67o0a2Tw2hGRPOG2Y1R2dg7brRe1uG0yaNQDHu+TO/uQPF/5eCapvYSmHUjt7JQ==} - engines: {node: ^18.18.0 || ^20.9.0 || >=21.1.0} - - eslint@8.57.1: - resolution: {integrity: sha512-ypowyDxpVSYpkXr9WPv2PAZCtNip1Mv5KTW0SCurXv/9iOpcrH9PaqUElksqEB6pChqHGDRCFTyrZlGhnLNGiA==} - engines: {node: ^12.22.0 || ^14.17.0 || >=16.0.0} - deprecated: This version is no longer supported. Please see https://eslint.org/version-support for other options. - hasBin: true - - eslint@9.39.2: - resolution: {integrity: sha512-LEyamqS7W5HB3ujJyvi0HQK/dtVINZvd5mAAp9eT5S/ujByGjiZLCzPcHVzuXbpJDJF/cxwHlfceVUDZ2lnSTw==} - engines: {node: ^18.18.0 || ^20.9.0 || >=21.1.0} - hasBin: true - peerDependencies: - jiti: '*' - peerDependenciesMeta: - jiti: - optional: true - - espree@10.4.0: - resolution: {integrity: sha512-j6PAQ2uUr79PZhBjP5C5fhl8e39FmRnOjsD5lGnWrFU8i2G776tBK7+nP8KuQUTTyAZUwfQqXAgrVH5MbH9CYQ==} - engines: {node: ^18.18.0 || ^20.9.0 || >=21.1.0} - - espree@9.6.1: - resolution: {integrity: sha512-oruZaFkjorTpF32kDSI5/75ViwGeZginGGy2NoOSg3Q9bnwlnmDm4HLnkl0RE3n+njDXR037aY1+x58Z/zFdwQ==} - engines: {node: ^12.22.0 || ^14.17.0 || >=16.0.0} - esprima@4.0.1: resolution: {integrity: sha512-eGuFFw7Upda+g4p+QHvnW0RyTX/SVeJBDM/gCtMARO0cLuT2HcEKnTPvhjV6aGeqrCB/sbNop0Kszm0jsaWU4A==} engines: {node: '>=4'} hasBin: true - esquery@1.7.0: - resolution: {integrity: sha512-Ap6G0WQwcU/LHsvLwON1fAQX9Zp0A2Y6Y/cJBl9r/JbW90Zyg4/zbG6zzKa2OTALELarYHmKu0GhpM5EO+7T0g==} - engines: {node: '>=0.10'} - esrecurse@4.3.0: resolution: {integrity: sha512-KmfKL3b6G+RXvP8N1vr3Tq1kL/oCFgn2NYXEtqP8/L3pKapUA4G8cFVaoF3SU323CD4XypR/ffioHmkti6/Tag==} engines: {node: '>=4.0'} @@ -4649,9 +4430,6 @@ packages: fast-deep-equal@3.1.3: resolution: {integrity: sha512-f3qQ9oQy9j2AhBe/H9VC91wLmKBCCU/gDOnKNAYG5hswO7BLKj09Hc5HYNz9cGI++xlpDCIgDaitVs03ATR84Q==} - fast-diff@1.3.0: - resolution: {integrity: sha512-VxPP4NqbUjj6MaAOafWeUn2cXWLcCtljklUtZf0Ind4XQ+QPtmA0b18zZy0jIQx+ExRVCR/ZQpBmik5lXshNsw==} - fast-fifo@1.3.2: resolution: {integrity: sha512-/d9sfos4yxzpwkDkuN7k2SqFKtYNmCTzgfEpz82x34IM9/zc8KGxQoXg1liNC/izpRM/MBdt44Nmx41ZWqk+FQ==} @@ -4662,9 +4440,6 @@ packages: fast-json-stable-stringify@2.1.0: resolution: {integrity: sha512-lhd/wF+Lk98HZoTCtlVraHtfh5XYijIjalXck7saUtuanSDyLMxnHhSXEDJqHxD7msR8D0uCmqlkwjCV8xvwHw==} - fast-levenshtein@2.0.6: - resolution: {integrity: sha512-DCXu6Ifhqcks7TZKY3Hxp3y6qphY5SJZmrWMDrKcERSOXWQdMhU9Ig/PYrzyw/ul9jOIyh0N4M0tbC5hodg8dw==} - fast-safe-stringify@2.1.1: resolution: {integrity: sha512-W+KJc2dmILlPplD/H4K9l9LcAHAfPtP6BY84uVLXQ6Evcz9Lcg33Y2z1IVblT6xdY54PXYVHEv+0Wpq8Io6zkA==} @@ -4704,14 +4479,6 @@ packages: resolution: {integrity: sha512-yaduQFRKLXYOGgEn6AZau90j3ggSOyiqXU0F9JZfeXYhNa+Jk4X+s45A2zg5jns87GAFa34BBm2kXw4XpNcbdg==} engines: {node: '>=8'} - file-entry-cache@6.0.1: - resolution: {integrity: sha512-7Gps/XWymbLk2QLYK4NzpMOrYjMhdIxXuIvy2QBsLE6ljuodKvdkWs/cpyJJ3CVIVpH0Oi1Hvg1ovbMzLdFBBg==} - engines: {node: ^10.12.0 || >=12.0.0} - - file-entry-cache@8.0.0: - resolution: {integrity: sha512-XXTUwCvisa5oacNGRP9SfNtYBNAMi+RPwBFmblZEF7N7swHYQS6/Zfk7SRwx4D5j3CH211YNRco1DEMNVfZCnQ==} - engines: {node: '>=16.0.0'} - file-loader@6.2.0: resolution: {integrity: sha512-qo3glqyTa61Ytg4u73GultjHGjdRyig3tG6lPtyX/jOEJvHif9uB0/OCI2Kif6ctF3caQTW2G5gym21oAsI4pw==} engines: {node: '>= 10.13.0'} @@ -4730,10 +4497,6 @@ packages: resolution: {integrity: sha512-9ZonPT4ZAK4a+1pUPVPZJapbi7O5qbbJPdYw/NOQWZZbVLdDTYM3A4R9z/DpAM08IDaFGsvPgiGZ82WEwUDWjg==} engines: {node: '>=14.16'} - find-up@5.0.0: - resolution: {integrity: sha512-78/PXT1wlLLDgTzDs7sjq9hzz0vXD+zn+7wypEe4fXQxCmdmqfGsEPQxmiCSQI3ajFV91bVSsvNtrJRiW6nGng==} - engines: {node: '>=10'} - find-up@6.3.0: resolution: {integrity: sha512-v2ZsoEuVHYy8ZIlYqwPe/39Cy+cFDzp4dXPaxNvkEuouymu+2Jbz0PxpKarJHYJTmv2HWT3O382qY8l4jMWthw==} engines: {node: ^12.20.0 || ^14.13.1 || >=16.0.0} @@ -4741,21 +4504,10 @@ packages: fix-dts-default-cjs-exports@1.0.1: resolution: {integrity: sha512-pVIECanWFC61Hzl2+oOCtoJ3F17kglZC/6N94eRWycFgBH35hHx0Li604ZIzhseh97mf2p0cv7vVrOZGoqhlEg==} - flat-cache@3.2.0: - resolution: {integrity: sha512-CYcENa+FtcUKLmhhqyctpclsq7QF38pKjZHsGNiSQF5r4FtoKDWabFDl3hzaEQMvT1LHEysw5twgLvpYYb4vbw==} - engines: {node: ^10.12.0 || >=12.0.0} - - flat-cache@4.0.1: - resolution: {integrity: sha512-f7ccFPK3SXFHpx15UIGyRJ/FJQctuKZ0zVuN3frBo4HnK3cay9VEW0R6yPYFHC0AgqhukPzKjq22t5DmAyqGyw==} - engines: {node: '>=16'} - flat@5.0.2: resolution: {integrity: sha512-b6suED+5/3rTpUBdG1gupIl8MPFCAMA0QXwmljLhvCUKcUvdE4gWky9zpuGCcXHOsz4J9wPGNWq6OKpmIzz3hQ==} hasBin: true - flatted@3.3.3: - resolution: {integrity: sha512-GX+ysw4PBCz0PzosHDepZGANEuFCMLrnRTiEy9McGjmkCQYwRq4A/X786G/fjM/+OjsWSU1ZrY5qyARZmO/uwg==} - follow-redirects@1.15.11: resolution: {integrity: sha512-deG2P0JfjrTxl50XGCDyfI97ZGVCxIpfKYmfyrQ54n5FO/0gfIES8C/Psl6kWVDolizcaaxZJnTS0QSMxvnsBQ==} engines: {node: '>=4.0'} @@ -4796,9 +4548,6 @@ packages: resolution: {integrity: sha512-VWSRii4t0AFm6ixFFmLLx1t7wS1gh+ckoa84aOeapGum0h+EZd1EhEumSB+ZdDLnEPuucsVB9oB7cxJHap6Afg==} engines: {node: '>=14.14'} - fs.realpath@1.0.0: - resolution: {integrity: sha512-OO0pH2lK6a0hZnAdau5ItzHPI6pUlvI7jMVnxUQRtw4owF2wk8lOSabtGDCTP4Ggrg2MbGnWO9X8K1t4+fGMDw==} - fsevents@2.3.3: resolution: {integrity: sha512-5xoDfX+fL7faATnagmWPpbFtwh/R77WmMMqqHGS65C3vvB0YHrgF+B1YmZ3441tMj5n63k0212XNoJwzlhffQw==} engines: {node: ^8.16.0 || ^10.6.0 || >=11.0.0} @@ -4865,22 +4614,10 @@ packages: resolution: {integrity: sha512-DfXN8DfhJ7NH3Oe7cFmu3NCu1wKbkReJ8TorzSAFbSKrlNaQSKfIzqYqVY8zlbs2NLBbWpRiU52GX2PbaBVNkg==} hasBin: true - glob@7.2.3: - resolution: {integrity: sha512-nFR0zLpU2YCaRxwoCJvL6UvCH2JFyFVIvwTLsIf21AuHlMskA1hhTdk+LlYJtOlYt9v6dvszD2BGRqBL+iQK9Q==} - deprecated: Glob versions prior to v9 are no longer supported - global-dirs@3.0.1: resolution: {integrity: sha512-NBcGGFbBA9s1VzD41QXDG+3++t9Mn5t1FpLdhESY6oKY4gYTFpX4wO3sqGUa0Srjtbfj3szX0RnemmrVRUdULA==} engines: {node: '>=10'} - globals@13.24.0: - resolution: {integrity: sha512-AhO5QUcj8llrbG09iWhPU2B204J1xnPeL8kQmVorSsy+Sjj1sk8gIyh6cUocGmH4L0UuhAJy+hJMRA4mgA4mFQ==} - engines: {node: '>=8'} - - globals@14.0.0: - resolution: {integrity: sha512-oahGvuMGQlPw/ivIYBjVSrWAfWLBeku5tpPE2fOPLi+WHffIWbuh2tCjhyQhTBPMf5E9jDEH4FOmTYgYwbKwtQ==} - engines: {node: '>=18'} - globby@11.1.0: resolution: {integrity: sha512-jhIXaOzy1sb8IyocaruWSn1TjmnBVs8Ayhcy83rmxNJ8q2uWKCAj3CnJY+KpGSXCueAPc0i05kVvVKtP1t9S3g==} engines: {node: '>=10'} @@ -4903,9 +4640,6 @@ packages: graceful-fs@4.2.11: resolution: {integrity: sha512-RbJ5/jmFcNNCcDV5o9eTnBLJ/HszWV0P73bc+Ff4nS/rJj+YaS6IGyiOL0VoBYX+l1Wrl3k63h/KrH+nhJ0XvQ==} - graphemer@1.4.0: - resolution: {integrity: sha512-EtKwoO6kxCL9WO5xipiHTZlSzBm7WLT627TqC/uVRd0HKmq8NXyebnNYxDoBi7wt8eTWrUrKXCOVaFq9x1kgag==} - graphql@16.12.0: resolution: {integrity: sha512-DKKrynuQRne0PNpEbzuEdHlYOMksHSUI8Zc9Unei5gTsMNA2/vMpoMz/yKba50pejK56qj98qM0SjYxAKi13gQ==} engines: {node: ^12.22.0 || ^14.16.0 || ^16.0.0 || >=17.0.0} @@ -4924,10 +4658,6 @@ packages: handle-thing@2.0.1: resolution: {integrity: sha512-9Qn4yBxelxoh2Ow62nP+Ka/kMnOXRi8BXnRaUwezLNhqelnN49xKz4F/dPP8OYLxLxq6JDtZb2i9XznUQbNPTg==} - has-ansi@2.0.0: - resolution: {integrity: sha512-C8vBJ8DwUCx19vhm7urhTuUsr4/IyP6l4VzNQDv+ryHQObW3TTTp9yB68WpYgRe2bbaGuZ/se74IqFeVnMnLZg==} - engines: {node: '>=0.10.0'} - has-flag@4.0.0: resolution: {integrity: sha512-EykJT/Q1KjTWctppgIAgfSO0tKVuZUjhgMr17kqTumMl6Afv3EISleU7qZUzoXDFTAHTDC4NOoG/ZxU3EvlMPQ==} engines: {node: '>=8'} @@ -5131,10 +4861,6 @@ packages: resolution: {integrity: sha512-hsBTNUqQTDwkWtcdYI2i06Y/nUBEsNEDJKjWdigLvegy8kDuJAS8uRlpkkcQpyEXL0Z/pjDy5HBmMjRCJ2gq+g==} engines: {node: '>= 4'} - ignore@7.0.5: - resolution: {integrity: sha512-Hs59xBNfUIunMFgWAbGX5cq6893IbWg4KnrjbYwX3tx0ztorVgTDA6B2sxf8ejHJ4wz8BqGUMYlnzNBer5NvGg==} - engines: {node: '>= 4'} - image-size@2.0.2: resolution: {integrity: sha512-IRqXKlaXwgSMAMtpNzZa1ZAe8m+Sa1770Dhk8VkSsP9LS+iHD62Zd8FQKs8fbPiagBE7BzoFX23cxFnwshpV6w==} engines: {node: '>=16.x'} @@ -5160,10 +4886,6 @@ packages: resolution: {integrity: sha512-uyH0zfr1erU1OohLk0fT4Rrb94AOhguWNOcD9uGrSpRvNB+6gZXUoJX5J0NtvzBO10YZ9PgvA4NFgt+fYg8ojw==} engines: {node: '>=12'} - inflight@1.0.6: - resolution: {integrity: sha512-k92I/b08q4wvFscXCLvqfsHCrjrF7yiXsQuIVvVE7N82W3+aqpzuUdBbfhWcy/FZR3/4IgflMgKLOsvPDrGCJA==} - deprecated: This module is not supported, and leaks memory. Do not use it. Check out lru-cache if you want a good and tested way to coalesce async requests by a key value, which is much more comprehensive and powerful. - inherits@2.0.3: resolution: {integrity: sha512-x00IRNXNy63jwGkJmzPigoySHbaqpNuzKbBOmzK+g2OdZpQ9w+sxCN+VSB3ja7IAge2OP2qpfxTjeNcyjmW1uw==} @@ -5492,9 +5214,6 @@ packages: json-schema@0.4.0: resolution: {integrity: sha512-es94M3nTIfsEPisRafak+HDLfHXnKBhV3vU5eqPcS3flIWqcxJWgXHXiey3YrpaNsanY5ei1VoYEbOzijuq9BA==} - json-stable-stringify-without-jsonify@1.0.1: - resolution: {integrity: sha512-Bdboy+l7tA3OGW6FjyFHWkP5LuByj1Tk33Ljyq0axyzdk9//JSi2u3fP1QSmd1KNwq6VOKYGlAu87CisVir6Pw==} - json-stringify-safe@5.0.1: resolution: {integrity: sha512-ZClg6AaYvamvYEE82d3Iyd3vSSIjQ+odgjaTzRuO3s7toCdFKczob2i0zCh7JE8kWn17yvAWhUVxvqGwUalsRA==} @@ -5560,10 +5279,6 @@ packages: resolution: {integrity: sha512-qsda+H8jTaUaN/x5vzW2rzc+8Rw4TAQ/4KjB46IwK5VH+IlVeeeje/EoZRpiXvIqjFgK84QffqPztGI3VBLG1A==} engines: {node: '>=6'} - levn@0.4.1: - resolution: {integrity: sha512-+bT2uH4E5LGE7h/n3evcS/sQlJXCpIp6ym8OWJ5eV6+67Dsql/LaaT7qJBAt2rzfoa/5QBGBhxDix1dMt2kQKQ==} - engines: {node: '>= 0.8.0'} - lilconfig@3.1.3: resolution: {integrity: sha512-/vlFKAoH5Cgt3Ie+JLhRbwOsCQePABiU3tJ1egGvyQ+33R/vcwM2Zl2QR/LzjsBeItPt3oSVXapn+m4nQDvpzw==} engines: {node: '>=14'} @@ -5587,10 +5302,6 @@ packages: resolution: {integrity: sha512-arhlxbFRmoQHl33a0Zkle/YWlmNwoyt6QNZEIJcqNbdrsix5Lvc4HyyI3EnwxTYlZYc32EbYrQ8SzEZ7dqgg9A==} engines: {node: '>=14'} - locate-path@6.0.0: - resolution: {integrity: sha512-iPZK6eYjbxRu3uB4/WZ3EsEIMJFMqAoopl3R+zuq0UjcAm/MO6KCweDgPfP3elTztoKP3KtnVHxTn2NHBSDVUw==} - engines: {node: '>=10'} - locate-path@7.2.0: resolution: {integrity: sha512-gvVijfZvn7R+2qyPX8mAuKcFGDf6Nc61GdvGafQsHL0sBIxfKzA+usWn4GFC/bk+QdwPUD4kWFJLhElipq+0VA==} engines: {node: ^12.20.0 || ^14.13.1 || >=16.0.0} @@ -5604,22 +5315,12 @@ packages: lodash.memoize@4.1.2: resolution: {integrity: sha512-t7j+NzmgnQzTAYXcsHYLgimltOV1MXHtlOWf6GjL9Kj8GK5FInw5JotxvbOs+IvV1/Dzo04/fCGfLVs7aXb4Ag==} - lodash.merge@4.6.2: - resolution: {integrity: sha512-0KpjqXRVvrYyCsX1swR/XTK0va6VQkQM6MNo7PqW77ByjAhoARA8EfrP1N4+KlKj8YS0ZUCtRT/YUuhyYDujIQ==} - lodash.uniq@4.5.0: resolution: {integrity: sha512-xfBaXQd9ryd9dlSDvnvI0lvxfLJlYAZzXomUYzLKtUeOQvOP5piqAWuGtrhWeqaXK9hhoM/iyJc5AV+XfsX3HQ==} lodash@4.17.21: resolution: {integrity: sha512-v2kDEe57lecTulaDIuNTPy3Ry4gLGJ6Z1O3vE1krgXZNrsQ+LFTGHVxVjcXPs17LhbZVGedAJv8XZ1tvj5FvSg==} - loglevel-colored-level-prefix@1.0.0: - resolution: {integrity: sha512-u45Wcxxc+SdAlh4yeF/uKlC1SPUPCy0gullSNKXod5I4bmifzk+Q4lSLExNEVn19tGaJipbZ4V4jbFn79/6mVA==} - - loglevel@1.9.2: - resolution: {integrity: sha512-HgMmCqIJSAKqo68l0rS2AanEWfkxaZ5wNiEFb5ggm08lDs9Xl2KxBlX3PTcaD2chBM1gXAYf491/M2Rv8Jwayg==} - engines: {node: '>= 0.6.0'} - longest-streak@3.1.0: resolution: {integrity: sha512-9Ri+o0JYgehTaVBBDoMqIl8GXtbWg711O3srftcHhZ0dqnETqLaoIK0x17fUw9rFSlK/0NlsKe0Ahhyl5pXE2g==} @@ -6072,10 +5773,6 @@ packages: minimatch@3.1.2: resolution: {integrity: sha512-J7p63hRiAjw1NDEww1W7i37+ByIrOWO5XQQAzZ3VOcL0PNybwpfmV/N05zFAzwQ9USyEcX6t3UO+K5aqBQOIHw==} - minimatch@9.0.3: - resolution: {integrity: sha512-RHiac9mvaRw0x3AYRgDC1CxAP7HTcNrrECeA8YYJeWnpo+2Q5CegtZjaotWTWxDG3UeGA1coE05iH1mPjT/2mg==} - engines: {node: '>=16 || 14 >=14.17'} - minimatch@9.0.5: resolution: {integrity: sha512-G6T0ZX48xgozx7587koeX9Ys2NYy6Gmv//P89sEte9V9whIapMNF4idKxnW2QtCcLiTWlb/wfCabAtAFWhhBow==} engines: {node: '>=16 || 14 >=14.17'} @@ -6140,9 +5837,6 @@ packages: engines: {node: ^10 || ^12 || ^13.7 || ^14 || >=15.0.1} hasBin: true - natural-compare@1.4.0: - resolution: {integrity: sha512-OWND8ei3VtNC9h7V60qff3SVobHr996CTwgxubgyQYEpg290h9J0buyECNNJexkFm5sOajh5G116RYA1c8ZMSw==} - negotiator@0.6.3: resolution: {integrity: sha512-+EUsqGPLsM+j/zdChZjsnX51g4XrHFOIXwfnCVPGlQk/k5giakcKsuxCObBRu6DSm9opw/O6slWbJdghQM4bBg==} engines: {node: '>= 0.6'} @@ -6268,10 +5962,6 @@ packages: resolution: {integrity: sha512-ur5UIdyw5Y7yEj9wLzhqXiy6GZ3Mwx0yGI+5sMn2r0N0v3cKJvUmFH5yPP+WXh9e0xfyzyJX95D8l088DNFj7A==} hasBin: true - optionator@0.9.4: - resolution: {integrity: sha512-6IpQ7mKUxRcZNLIObR0hz7lxsapSSIYNZJwXPGeF0mTVqGKFIXj1DQcMoT22S3ROcLyY/rz0PWaWZ9ayWmad9g==} - engines: {node: '>= 0.8.0'} - outvariant@1.4.3: resolution: {integrity: sha512-+Sl2UErvtsoajRDKCE5/dBz4DIvHXQQnAxtQTF04OJxY0+DyZXSo5P5Bb7XYWOh81syohlYL24hbDwxedPUJCA==} @@ -6299,18 +5989,10 @@ packages: resolution: {integrity: sha512-LICb2p9CB7FS+0eR1oqWnHhp0FljGLZCWBE9aix0Uye9W8LTQPwMTYVGWQWIw9RdQiDg4+epXQODwIYJtSJaow==} engines: {node: '>=4'} - p-limit@3.1.0: - resolution: {integrity: sha512-TYOanM3wGwNGsZN2cVTYPArw454xnXj5qmWF1bEoAc4+cU/ol7GVh7odevjp1FNHduHc3KZMcFduxU5Xc6uJRQ==} - engines: {node: '>=10'} - p-limit@4.0.0: resolution: {integrity: sha512-5b0R4txpzjPWVw/cXXUResoD4hb6U/x9BH08L7nw+GN1sezDzPdxeRvpc9c433fZhBan/wusjbCsqwqm4EIBIQ==} engines: {node: ^12.20.0 || ^14.13.1 || >=16.0.0} - p-locate@5.0.0: - resolution: {integrity: sha512-LaNjtRWUBY++zB5nE/NwcaoMylSPk+S+ZHNB1TzdbMJMny6dynpAGt7X/tl/QYq3TIeE6nxHppbo2LGymrG5Pw==} - engines: {node: '>=10'} - p-locate@6.0.0: resolution: {integrity: sha512-wPrq66Llhl7/4AGC6I+cqxT07LhXvWL08LNXz1fENOw0Ap4sRZZ/gZpTTJ5jpurzzzfS2W/Ge9BY3LgLjCShcw==} engines: {node: ^12.20.0 || ^14.13.1 || >=16.0.0} @@ -6383,18 +6065,10 @@ packages: path-browserify@1.0.1: resolution: {integrity: sha512-b7uo2UCUOYZcnF/3ID0lulOJi/bafxa1xPe7ZPsammBSpjSWQkjNxlt635YGS2MiR9GjvuXCtz2emr3jbsz98g==} - path-exists@4.0.0: - resolution: {integrity: sha512-ak9Qy5Q7jYb2Wwcey5Fpvg2KoAc/ZIhLSLOSBmRmygPsGwkVVt0fZa0qrtMz+m6tJTAHfZQ8FnmB4MG4LWy7/w==} - engines: {node: '>=8'} - path-exists@5.0.0: resolution: {integrity: sha512-RjhtfwJOxzcFmNOi6ltcbcu4Iu+FL3zEj83dk4kAS+fVpTxXLO1b38RvJgT/0QwvV/L3aY9TAnyv0EOqW4GoMQ==} engines: {node: ^12.20.0 || ^14.13.1 || >=16.0.0} - path-is-absolute@1.0.1: - resolution: {integrity: sha512-AVbw3UJ2e9bq64vSaS9Am0fje1Pa8pbGqTTsmXfaIiMpnr5DlDhfJOuLj9Sf95ZPVDAUerDfEk88MPmPe7UCQg==} - engines: {node: '>=0.10.0'} - path-is-inside@1.0.2: resolution: {integrity: sha512-DUWJr3+ULp4zXmol/SZkFf3JGsS9/SIv+Y3Rt93/UjPpDpklB5f1er4O3POIbUuUJ3FXgqte2Q7SrU6zAqwk8w==} @@ -6881,38 +6555,9 @@ packages: resolution: {integrity: sha512-3Ybi1tAuwAP9s0r1UQ2J4n5Y0G05bJkpUIO0/bI9MhwmD70S5aTWbXGBwxHrelT+XM1k6dM0pk+SwNkpTRN7Pg==} engines: {node: ^10 || ^12 || >=14} - prelude-ls@1.2.1: - resolution: {integrity: sha512-vkcDPrRZo1QZLbn5RLGPpg/WmIQ65qoWWhcGKf/b5eplkkarX0m9z8ppCat4mlOqUsWpyNuYgO3VRyrYHSzX5g==} - engines: {node: '>= 0.8.0'} - - prettier-eslint@16.4.2: - resolution: {integrity: sha512-vtJAQEkaN8fW5QKl08t7A5KCjlZuDUNeIlr9hgolMS5s3+uzbfRHDwaRnzrdqnY2YpHDmeDS/8zY0MKQHXJtaA==} - engines: {node: '>=16.10.0'} - peerDependencies: - prettier-plugin-svelte: ^3.0.0 - svelte-eslint-parser: '*' - peerDependenciesMeta: - prettier-plugin-svelte: - optional: true - svelte-eslint-parser: - optional: true - - prettier-linter-helpers@1.0.1: - resolution: {integrity: sha512-SxToR7P8Y2lWmv/kTzVLC1t/GDI2WGjMwNhLLE9qtH8Q13C+aEmuRlzDst4Up4s0Wc8sF2M+J57iB3cMLqftfg==} - engines: {node: '>=6.0.0'} - - prettier@3.7.4: - resolution: {integrity: sha512-v6UNi1+3hSlVvv8fSaoUbggEM5VErKmmpGA7Pl3HF8V6uKY7rvClBOJlH6yNwQtfTueNkGVpOv/mtWL9L4bgRA==} - engines: {node: '>=14'} - hasBin: true - pretty-error@4.0.0: resolution: {integrity: sha512-AoJ5YMAcXKYxKhuJGdcvse+Voc6v1RgnsR3nWcYU7q4t6z0Q6T86sv5Zq8VIRbOWWFpvdGE83LtdSMNd+6Y0xw==} - pretty-format@29.7.0: - resolution: {integrity: sha512-Pdlw/oPxN+aXdmM9R00JVC9WVFoCLTKJvDVLgmJ+qAffBMxsV85l/Lu7sNx4zSzPyoL2euImuEwHhOXdEgNFZQ==} - engines: {node: ^14.15.0 || ^16.10.0 || >=18.0.0} - pretty-format@30.2.0: resolution: {integrity: sha512-9uBdv/B4EefsuAL+pWqueZyZS2Ba+LxfFeQ9DN14HU4bN8bhaxKdkpjpB6fs9+pSjIBu+FXQHImEg8j/Lw0+vA==} engines: {node: ^18.14.0 || ^20.0.0 || ^22.0.0 || >=24.0.0} @@ -7184,9 +6829,6 @@ packages: require-like@0.1.2: resolution: {integrity: sha512-oyrU88skkMtDdauHDuKVrgR+zuItqr6/c//FXzvmxRGMexSDc6hNvJInGW3LL46n+8b50RykrvwSUIIQH2LQ5A==} - require-relative@0.8.7: - resolution: {integrity: sha512-AKGr4qvHiryxRb19m3PsLRGuKVAbJLUD7E6eOaHkfKhwc+vSgVOCY5xNvm9EkolBKTOf0GrQAZKLimOCz81Khg==} - requires-port@1.0.0: resolution: {integrity: sha512-KigOCHcocU3XODJxsu8i/j8T9tzT4adHiecwORRQ0ZZFcp7ahwXuRU1m+yuO90C5ZUyGeGfocHDI14M3L3yDAQ==} @@ -7227,11 +6869,6 @@ packages: resolution: {integrity: sha512-g6QUff04oZpHs0eG5p83rFLhHeV00ug/Yf9nZM6fLeUrPguBTkTQOdpAWWspMh55TZfVQDPaN3NQJfbVRAxdIw==} engines: {iojs: '>=1.0.0', node: '>=0.10.0'} - rimraf@3.0.2: - resolution: {integrity: sha512-JZkJMZkAGFFPP2YqXZXPbMlMBgsxzE8ILs4lMIX/2o0L9UBw9O/Y3o6wFw/i9YLapcUJWwqbi3kdxIPdC62TIA==} - deprecated: Rimraf versions prior to v4 are no longer supported - hasBin: true - ripemd160@2.0.3: resolution: {integrity: sha512-5Di9UC0+8h1L6ZD2d7awM7E/T4uA1fJRlx6zk/NvdCCVEoAnFqvHmCuNeIKoCeIixBX/q8uM+6ycDvF8woqosA==} engines: {node: '>= 0.8'} @@ -7568,10 +7205,6 @@ packages: resolution: {integrity: sha512-rHqiFh1elqCQ9WPLIC8I0Q/g/wj5J1eMkyoiD6eoQApWHP0FtlK7rqnhmabL5VUY9JQCcqwwvlOaSuutekgyrw==} engines: {node: '>=4'} - strip-ansi@3.0.1: - resolution: {integrity: sha512-VhumSSbBqDTP8p2ZLKj40UjBCV4+v8bUSEpUb4KjRgWk9pbqGF4REFj6KEagidb2f/M6AzC0EmFyDNGaw9OCzg==} - engines: {node: '>=0.10.0'} - strip-ansi@6.0.1: resolution: {integrity: sha512-Y38VPSHcqkFrCpFnQ9vuSXmquuv5oXOKpGeT6aGrr3o3Gc9AlVa6JBfUSOCnbxGGZF+/0ooI7KrPuUSztUdU5A==} engines: {node: '>=8'} @@ -7630,10 +7263,6 @@ packages: resolution: {integrity: sha512-uV+TFRZdXsqXTL2pRvujROjdZQ4RAlBUS5BTh9IGm+jTqQntYThciG/qu57Gs69yjnVUSqdxF9YLmSnpupBW9A==} engines: {node: '>=14.0.0'} - supports-color@2.0.0: - resolution: {integrity: sha512-KKNVtd6pCYgPIKU4cp2733HWYCpplQhddZLBUryaAHou723x+FRzQ5Df824Fj+IyyuiQTRoub4SnIFfIcrp70g==} - engines: {node: '>=0.8.0'} - supports-color@7.2.0: resolution: {integrity: sha512-qpCAvRl9stuOHveKsn7HncJRvv501qIacKzQlO/+Lwxc9+0q2wLyv4Dfvt80/DPn2pqOBsJdDiogXGR9+OvwRw==} engines: {node: '>=8'} @@ -7659,10 +7288,6 @@ packages: peerDependencies: react: ^16.11.0 || ^17.0.0 || ^18.0.0 || ^19.0.0 - synckit@0.11.11: - resolution: {integrity: sha512-MeQTA1r0litLUf0Rp/iisCaL8761lKAZHaimlbGK4j0HysC4PLfqygQj9srcs0m2RdtDYnF8UuYyKpbjHYp7Jw==} - engines: {node: ^14.18.0 || >=16.0.0} - tagged-tag@1.0.0: resolution: {integrity: sha512-yEFYrVhod+hdNyx7g5Bnkkb0G6si8HJurOoOEgC8B/O0uXLHlaey/65KRv6cuWBNhBgHKAROVpc7QyYqE5gFng==} engines: {node: '>=20'} @@ -7708,9 +7333,6 @@ packages: text-encoding-utf-8@1.0.2: resolution: {integrity: sha512-8bw4MY9WjdsD2aMtO0OzOCY3pXGYNx2d2FfHRVUKkiCPDWjKuOlhLVASS+pD7VkLTVjW268LYJHwsnPFlBpbAg==} - text-table@0.2.0: - resolution: {integrity: sha512-N+8UisAXDGk8PFXP4HAzVR9nbfmVJ3zYLAWiTIoqC5v5isinhr+r5uaO8+7r3BMfuNIufIsA7RdpVgacC2cSpw==} - thenify-all@1.6.0: resolution: {integrity: sha512-RNxQH/qI8/t3thXJDwcstUO4zeqo64+Uy/+sNVRBx4Xn2OX+OZ9oP+iJnNFqplFra2ZUVeKCSa2oVWi3T4uVmA==} engines: {node: '>=0.8'} @@ -7816,18 +7438,6 @@ packages: trough@2.2.0: resolution: {integrity: sha512-tmMpK00BjZiUyVyvrBK7knerNgmgvcV/KLVyuma/SC+TQN167GrMRciANTz09+k3zW8L8t60jWO1GpfkZdjTaw==} - ts-api-utils@1.4.3: - resolution: {integrity: sha512-i3eMG77UTMD0hZhgRS562pv83RC6ukSAC2GMNWc+9dieh/+jDM5u5YG+NHX6VNDRHQcHwmsTHctP9LhbC3WxVw==} - engines: {node: '>=16'} - peerDependencies: - typescript: '>=4.2.0' - - ts-api-utils@2.4.0: - resolution: {integrity: sha512-3TaVTaAv2gTiMB35i3FiGJaRfwb3Pyn/j3m/bfAvGe8FB7CF6u+LMYqYlDh7reQf7UNvoTvdfAqHGmPGOSsPmA==} - engines: {node: '>=18.12'} - peerDependencies: - typescript: '>=4.8.4' - ts-dedent@2.2.0: resolution: {integrity: sha512-q5W7tVM71e2xjHZTlgfTDoPF/SmqKG5hddq9SzR49CH2hayqRKJtQ4mtRlSxKaJlR/+9rEM+mnBHf7I2/BQcpQ==} engines: {node: '>=6.10'} @@ -7862,16 +7472,42 @@ packages: engines: {node: '>=18.0.0'} hasBin: true - tweetnacl@1.0.3: - resolution: {integrity: sha512-6rt+RN7aOi1nGMyC4Xa5DdYiukl2UWCbcJft7YhxReBGQD7OAM8Pbxw6YMo4r2diNEA8FEmu32YOn9rhaiE5yw==} + turbo-darwin-64@2.7.4: + resolution: {integrity: sha512-xDR30ltfkSsRfGzABBckvl1nz1cZ3ssTujvdj+TPwOweeDRvZ0e06t5DS0rmRBvyKpgGs42K/EK6Mn2qLlFY9A==} + cpu: [x64] + os: [darwin] - type-check@0.4.0: - resolution: {integrity: sha512-XleUoc9uwGXqjWwXaUTZAmzMcFZ5858QA2vvx1Ur5xIcixXIP+8LnFDgRplU30us6teqdlskFfu+ae4K79Ooew==} - engines: {node: '>= 0.8.0'} + turbo-darwin-arm64@2.7.4: + resolution: {integrity: sha512-P7sjqXtOL/+nYWPvcDGWhi8wf8M8mZHHB8XEzw2VX7VJrS8IGHyJHGD1AYfDvhAEcr7pnk3gGifz3/xyhI655w==} + cpu: [arm64] + os: [darwin] - type-fest@0.20.2: - resolution: {integrity: sha512-Ne+eE4r0/iWnpAxD852z3A+N0Bt5RN//NjJwRd2VFHEmrywxf5vsZlh4R6lixl6B+wz/8d+maTSAkN1FIkI3LQ==} - engines: {node: '>=10'} + turbo-linux-64@2.7.4: + resolution: {integrity: sha512-GofFOxRO/IhG8BcPyMSSB3Y2+oKQotsaYbHxL9yD6JPb20/o35eo+zUSyazOtilAwDHnak5dorAJFoFU8MIg2A==} + cpu: [x64] + os: [linux] + + turbo-linux-arm64@2.7.4: + resolution: {integrity: sha512-+RQKgNjksVPxYAyAgmDV7w/1qj++qca+nSNTAOKGOfJiDtSvRKoci89oftJ6anGs00uamLKVEQ712TI/tfNAIw==} + cpu: [arm64] + os: [linux] + + turbo-windows-64@2.7.4: + resolution: {integrity: sha512-rfak1+g+ON3czs1mDYsCS4X74ZmK6gOgRQTXjDICtzvR4o61paqtgAYtNPofcVsMWeF4wvCajSeoAkkeAnQ1kg==} + cpu: [x64] + os: [win32] + + turbo-windows-arm64@2.7.4: + resolution: {integrity: sha512-1ZgBNjNRbDu/fPeqXuX9i26x3CJ/Y1gcwUpQ+Vp7kN9Un6RZ9kzs164f/knrjcu5E+szCRexVjRSJay1k5jApA==} + cpu: [arm64] + os: [win32] + + turbo@2.7.4: + resolution: {integrity: sha512-bkO4AddmDishzJB2ze7aYYPaejMoJVfS0XnaR6RCdXFOY8JGJfQE+l9fKiV7uDPa5Ut44gmOWJL3894CIMeH9g==} + hasBin: true + + tweetnacl@1.0.3: + resolution: {integrity: sha512-6rt+RN7aOi1nGMyC4Xa5DdYiukl2UWCbcJft7YhxReBGQD7OAM8Pbxw6YMo4r2diNEA8FEmu32YOn9rhaiE5yw==} type-fest@0.21.3: resolution: {integrity: sha512-t0rzBq87m3fVcduHDUFhKmyyX+9eo6WQjZvf51Ea/M0Q7+T374Jp1aUiyUl0GKxp8M/OETVHSDvmkyPgvX+X2w==} @@ -8257,12 +7893,6 @@ packages: vscode-uri@3.1.0: resolution: {integrity: sha512-/BpdSx+yCQGnCvecbyXdxHDkuk55/G3xwnC0GqY4gmQ3j+A+g8kzzgB4Nk/SINjqn6+waqw3EgbVF2QKExkRxQ==} - vue-eslint-parser@9.4.3: - resolution: {integrity: sha512-2rYRLWlIpaiN8xbPiDyXZXRgLGOtWxERV7ND5fFAv5qo1D2N9Fu9MNajBNc6o13lZ+24DAWCkQCvj4klgmcITg==} - engines: {node: ^14.17.0 || >=16.0.0} - peerDependencies: - eslint: '>=6.0.0' - watchpack@2.5.0: resolution: {integrity: sha512-e6vZvY6xboSwLz2GD36c16+O/2Z6fKvIf4pOXptw2rY9MVwE/TXc6RGqxD3I3x0a28lwBY7DE+76uTPSsBrrCA==} engines: {node: '>=10.13.0'} @@ -8372,10 +8002,6 @@ packages: wildcard@2.0.1: resolution: {integrity: sha512-CC1bOL87PIWSBhDcTrdeLo6eGT7mCFtrg0uIJtqJUFyK+eJnzl8A1niH56uu7KMa5XFrtiV+AQuHO3n7DsHnLQ==} - word-wrap@1.2.5: - resolution: {integrity: sha512-BN22B5eaMMI9UMtjrGd5g5eCYPpCPDUy0FJXbYsaT5zYxjFOckS53SQDE3pWkVoWpHXVb3BrYcEN4Twa55B5cA==} - engines: {node: '>=0.10.0'} - wrap-ansi@6.2.0: resolution: {integrity: sha512-r6lPcBGxZXlIcymEu7InxDMhdW0KDxpLgoFLcguasxCaJ/SOIZwINatK9KY/tf+ZrlywOKU0UDj3ATXUBfxJXA==} engines: {node: '>=8'} @@ -8463,10 +8089,6 @@ packages: yauzl@2.10.0: resolution: {integrity: sha512-p4a9I6X6nu6IhoGmBqAcbJy1mlC4j27vEPZX9F4L4/vZT3Lyq1VkFHw/V/PUcB9Buo+DG3iHkT0x3Qya58zc3g==} - yocto-queue@0.1.0: - resolution: {integrity: sha512-rVksvsnNCdJ/ohGc6xgPwyN8eheCxsiLM8mxuE/t/mOVqJewPuO1miLpTHQiRgTKCLexL4MeAFVagts7HmNZ2Q==} - engines: {node: '>=10'} - yocto-queue@1.2.2: resolution: {integrity: sha512-4LCcse/U2MHZ63HAJVE+v71o7yOdIe4cZ70Wpf8D/IyjDKYQLV5GD46B+hSTjJsvV5PztjvHoU580EftxjDZFQ==} engines: {node: '>=12.20'} @@ -9387,6 +9009,41 @@ snapshots: '@babel/helper-string-parser': 7.27.1 '@babel/helper-validator-identifier': 7.28.5 + '@biomejs/biome@1.9.4': + optionalDependencies: + '@biomejs/cli-darwin-arm64': 1.9.4 + '@biomejs/cli-darwin-x64': 1.9.4 + '@biomejs/cli-linux-arm64': 1.9.4 + '@biomejs/cli-linux-arm64-musl': 1.9.4 + '@biomejs/cli-linux-x64': 1.9.4 + '@biomejs/cli-linux-x64-musl': 1.9.4 + '@biomejs/cli-win32-arm64': 1.9.4 + '@biomejs/cli-win32-x64': 1.9.4 + + '@biomejs/cli-darwin-arm64@1.9.4': + optional: true + + '@biomejs/cli-darwin-x64@1.9.4': + optional: true + + '@biomejs/cli-linux-arm64-musl@1.9.4': + optional: true + + '@biomejs/cli-linux-arm64@1.9.4': + optional: true + + '@biomejs/cli-linux-x64-musl@1.9.4': + optional: true + + '@biomejs/cli-linux-x64@1.9.4': + optional: true + + '@biomejs/cli-win32-arm64@1.9.4': + optional: true + + '@biomejs/cli-win32-x64@1.9.4': + optional: true + '@braintree/sanitize-url@6.0.4': {} '@chainsafe/bls-keystore@3.1.0': @@ -10631,84 +10288,17 @@ snapshots: '@esbuild/win32-x64@0.27.2': optional: true - '@eslint-community/eslint-utils@4.9.1(eslint@8.57.1)': + '@ethereumjs/common@10.1.0': dependencies: - eslint: 8.57.1 - eslint-visitor-keys: 3.4.3 + '@ethereumjs/util': 10.1.0 + eventemitter3: 5.0.1 - '@eslint-community/eslint-utils@4.9.1(eslint@9.39.2(jiti@1.21.7))': + '@ethereumjs/common@3.2.0': dependencies: - eslint: 9.39.2(jiti@1.21.7) - eslint-visitor-keys: 3.4.3 - - '@eslint-community/regexpp@4.12.2': {} + '@ethereumjs/util': 8.1.0 + crc-32: 1.2.2 - '@eslint/config-array@0.21.1': - dependencies: - '@eslint/object-schema': 2.1.7 - debug: 4.4.3 - minimatch: 3.1.2 - transitivePeerDependencies: - - supports-color - - '@eslint/config-helpers@0.4.2': - dependencies: - '@eslint/core': 0.17.0 - - '@eslint/core@0.17.0': - dependencies: - '@types/json-schema': 7.0.15 - - '@eslint/eslintrc@2.1.4': - dependencies: - ajv: 6.12.6 - debug: 4.4.3 - espree: 9.6.1 - globals: 13.24.0 - ignore: 5.3.2 - import-fresh: 3.3.1 - js-yaml: 4.1.1 - minimatch: 3.1.2 - strip-json-comments: 3.1.1 - transitivePeerDependencies: - - supports-color - - '@eslint/eslintrc@3.3.3': - dependencies: - ajv: 6.12.6 - debug: 4.4.3 - espree: 10.4.0 - globals: 14.0.0 - ignore: 5.3.2 - import-fresh: 3.3.1 - js-yaml: 4.1.1 - minimatch: 3.1.2 - strip-json-comments: 3.1.1 - transitivePeerDependencies: - - supports-color - - '@eslint/js@8.57.1': {} - - '@eslint/js@9.39.2': {} - - '@eslint/object-schema@2.1.7': {} - - '@eslint/plugin-kit@0.4.1': - dependencies: - '@eslint/core': 0.17.0 - levn: 0.4.1 - - '@ethereumjs/common@10.1.0': - dependencies: - '@ethereumjs/util': 10.1.0 - eventemitter3: 5.0.1 - - '@ethereumjs/common@3.2.0': - dependencies: - '@ethereumjs/util': 8.1.0 - crc-32: 1.2.2 - - '@ethereumjs/common@4.3.0': + '@ethereumjs/common@4.3.0': dependencies: '@ethereumjs/util': 9.1.0 @@ -10884,27 +10474,6 @@ snapshots: dependencies: '@hapi/hoek': 9.3.0 - '@humanfs/core@0.19.1': {} - - '@humanfs/node@0.16.7': - dependencies: - '@humanfs/core': 0.19.1 - '@humanwhocodes/retry': 0.4.3 - - '@humanwhocodes/config-array@0.13.0': - dependencies: - '@humanwhocodes/object-schema': 2.0.3 - debug: 4.4.3 - minimatch: 3.1.2 - transitivePeerDependencies: - - supports-color - - '@humanwhocodes/module-importer@1.0.1': {} - - '@humanwhocodes/object-schema@2.0.3': {} - - '@humanwhocodes/retry@0.4.3': {} - '@inquirer/ansi@1.0.2': {} '@inquirer/confirm@5.1.21(@types/node@24.10.4)': @@ -11252,8 +10821,6 @@ snapshots: '@pkgjs/parseargs@0.11.0': optional: true - '@pkgr/core@0.2.9': {} - '@pnpm/config.env-replace@1.1.0': {} '@pnpm/network.ca-file@1.0.2': @@ -11884,137 +11451,6 @@ snapshots: '@types/node': 24.10.4 optional: true - '@typescript-eslint/eslint-plugin@8.52.0(@typescript-eslint/parser@8.52.0(eslint@9.39.2(jiti@1.21.7))(typescript@5.9.3))(eslint@9.39.2(jiti@1.21.7))(typescript@5.9.3)': - dependencies: - '@eslint-community/regexpp': 4.12.2 - '@typescript-eslint/parser': 8.52.0(eslint@9.39.2(jiti@1.21.7))(typescript@5.9.3) - '@typescript-eslint/scope-manager': 8.52.0 - '@typescript-eslint/type-utils': 8.52.0(eslint@9.39.2(jiti@1.21.7))(typescript@5.9.3) - '@typescript-eslint/utils': 8.52.0(eslint@9.39.2(jiti@1.21.7))(typescript@5.9.3) - '@typescript-eslint/visitor-keys': 8.52.0 - eslint: 9.39.2(jiti@1.21.7) - ignore: 7.0.5 - natural-compare: 1.4.0 - ts-api-utils: 2.4.0(typescript@5.9.3) - typescript: 5.9.3 - transitivePeerDependencies: - - supports-color - - '@typescript-eslint/parser@6.21.0(eslint@8.57.1)(typescript@5.9.3)': - dependencies: - '@typescript-eslint/scope-manager': 6.21.0 - '@typescript-eslint/types': 6.21.0 - '@typescript-eslint/typescript-estree': 6.21.0(typescript@5.9.3) - '@typescript-eslint/visitor-keys': 6.21.0 - debug: 4.4.3 - eslint: 8.57.1 - optionalDependencies: - typescript: 5.9.3 - transitivePeerDependencies: - - supports-color - - '@typescript-eslint/parser@8.52.0(eslint@9.39.2(jiti@1.21.7))(typescript@5.9.3)': - dependencies: - '@typescript-eslint/scope-manager': 8.52.0 - '@typescript-eslint/types': 8.52.0 - '@typescript-eslint/typescript-estree': 8.52.0(typescript@5.9.3) - '@typescript-eslint/visitor-keys': 8.52.0 - debug: 4.4.3 - eslint: 9.39.2(jiti@1.21.7) - typescript: 5.9.3 - transitivePeerDependencies: - - supports-color - - '@typescript-eslint/project-service@8.52.0(typescript@5.9.3)': - dependencies: - '@typescript-eslint/tsconfig-utils': 8.52.0(typescript@5.9.3) - '@typescript-eslint/types': 8.52.0 - debug: 4.4.3 - typescript: 5.9.3 - transitivePeerDependencies: - - supports-color - - '@typescript-eslint/scope-manager@6.21.0': - dependencies: - '@typescript-eslint/types': 6.21.0 - '@typescript-eslint/visitor-keys': 6.21.0 - - '@typescript-eslint/scope-manager@8.52.0': - dependencies: - '@typescript-eslint/types': 8.52.0 - '@typescript-eslint/visitor-keys': 8.52.0 - - '@typescript-eslint/tsconfig-utils@8.52.0(typescript@5.9.3)': - dependencies: - typescript: 5.9.3 - - '@typescript-eslint/type-utils@8.52.0(eslint@9.39.2(jiti@1.21.7))(typescript@5.9.3)': - dependencies: - '@typescript-eslint/types': 8.52.0 - '@typescript-eslint/typescript-estree': 8.52.0(typescript@5.9.3) - '@typescript-eslint/utils': 8.52.0(eslint@9.39.2(jiti@1.21.7))(typescript@5.9.3) - debug: 4.4.3 - eslint: 9.39.2(jiti@1.21.7) - ts-api-utils: 2.4.0(typescript@5.9.3) - typescript: 5.9.3 - transitivePeerDependencies: - - supports-color - - '@typescript-eslint/types@6.21.0': {} - - '@typescript-eslint/types@8.52.0': {} - - '@typescript-eslint/typescript-estree@6.21.0(typescript@5.9.3)': - dependencies: - '@typescript-eslint/types': 6.21.0 - '@typescript-eslint/visitor-keys': 6.21.0 - debug: 4.4.3 - globby: 11.1.0 - is-glob: 4.0.3 - minimatch: 9.0.3 - semver: 7.7.3 - ts-api-utils: 1.4.3(typescript@5.9.3) - optionalDependencies: - typescript: 5.9.3 - transitivePeerDependencies: - - supports-color - - '@typescript-eslint/typescript-estree@8.52.0(typescript@5.9.3)': - dependencies: - '@typescript-eslint/project-service': 8.52.0(typescript@5.9.3) - '@typescript-eslint/tsconfig-utils': 8.52.0(typescript@5.9.3) - '@typescript-eslint/types': 8.52.0 - '@typescript-eslint/visitor-keys': 8.52.0 - debug: 4.4.3 - minimatch: 9.0.5 - semver: 7.7.3 - tinyglobby: 0.2.15 - ts-api-utils: 2.4.0(typescript@5.9.3) - typescript: 5.9.3 - transitivePeerDependencies: - - supports-color - - '@typescript-eslint/utils@8.52.0(eslint@9.39.2(jiti@1.21.7))(typescript@5.9.3)': - dependencies: - '@eslint-community/eslint-utils': 4.9.1(eslint@9.39.2(jiti@1.21.7)) - '@typescript-eslint/scope-manager': 8.52.0 - '@typescript-eslint/types': 8.52.0 - '@typescript-eslint/typescript-estree': 8.52.0(typescript@5.9.3) - eslint: 9.39.2(jiti@1.21.7) - typescript: 5.9.3 - transitivePeerDependencies: - - supports-color - - '@typescript-eslint/visitor-keys@6.21.0': - dependencies: - '@typescript-eslint/types': 6.21.0 - eslint-visitor-keys: 3.4.3 - - '@typescript-eslint/visitor-keys@8.52.0': - dependencies: - '@typescript-eslint/types': 8.52.0 - eslint-visitor-keys: 4.2.1 - '@ungap/structured-clone@1.3.0': {} '@vercel/oidc@3.0.5': {} @@ -12345,16 +11781,12 @@ snapshots: ansi-html-community@0.0.8: {} - ansi-regex@2.1.1: {} - ansi-regex@5.0.1: {} ansi-regex@6.2.2: {} ansi-sequence-parser@1.1.3: {} - ansi-styles@2.2.1: {} - ansi-styles@4.3.0: dependencies: color-convert: 2.0.1 @@ -12770,14 +12202,6 @@ snapshots: loupe: 3.2.1 pathval: 2.0.1 - chalk@1.1.3: - dependencies: - ansi-styles: 2.2.1 - escape-string-regexp: 1.0.5 - has-ansi: 2.0.0 - strip-ansi: 3.0.1 - supports-color: 2.0.0 - chalk@4.1.2: dependencies: ansi-styles: 4.3.0 @@ -12914,8 +12338,6 @@ snapshots: common-path-prefix@3.0.0: {} - common-tags@1.8.2: {} - compare-versions@6.1.1: {} compressible@2.0.18: @@ -13391,8 +12813,6 @@ snapshots: deep-extend@0.6.0: {} - deep-is@0.1.4: {} - deepmerge@4.3.1: {} default-browser-id@5.0.1: {} @@ -13465,16 +12885,10 @@ snapshots: dependencies: path-type: 4.0.0 - dlv@1.1.3: {} - dns-packet@5.6.1: dependencies: '@leichtgewicht/ip-codec': 2.0.5 - doctrine@3.0.0: - dependencies: - esutils: 2.0.3 - docusaurus-plugin-typedoc@1.0.5(typedoc-plugin-markdown@4.0.3(typedoc@0.25.13(typescript@5.9.3))): dependencies: typedoc-plugin-markdown: 4.0.3(typedoc@0.25.13(typescript@5.9.3)) @@ -13727,141 +13141,13 @@ snapshots: source-map: 0.6.1 optional: true - eslint-config-prettier@10.1.8(eslint@9.39.2(jiti@1.21.7)): - dependencies: - eslint: 9.39.2(jiti@1.21.7) - - eslint-plugin-prettier@5.5.4(@types/eslint@9.6.1)(eslint-config-prettier@10.1.8(eslint@9.39.2(jiti@1.21.7)))(eslint@9.39.2(jiti@1.21.7))(prettier@3.7.4): - dependencies: - eslint: 9.39.2(jiti@1.21.7) - prettier: 3.7.4 - prettier-linter-helpers: 1.0.1 - synckit: 0.11.11 - optionalDependencies: - '@types/eslint': 9.6.1 - eslint-config-prettier: 10.1.8(eslint@9.39.2(jiti@1.21.7)) - eslint-scope@5.1.1: dependencies: esrecurse: 4.3.0 estraverse: 4.3.0 - eslint-scope@7.2.2: - dependencies: - esrecurse: 4.3.0 - estraverse: 5.3.0 - - eslint-scope@8.4.0: - dependencies: - esrecurse: 4.3.0 - estraverse: 5.3.0 - - eslint-visitor-keys@3.4.3: {} - - eslint-visitor-keys@4.2.1: {} - - eslint@8.57.1: - dependencies: - '@eslint-community/eslint-utils': 4.9.1(eslint@8.57.1) - '@eslint-community/regexpp': 4.12.2 - '@eslint/eslintrc': 2.1.4 - '@eslint/js': 8.57.1 - '@humanwhocodes/config-array': 0.13.0 - '@humanwhocodes/module-importer': 1.0.1 - '@nodelib/fs.walk': 1.2.8 - '@ungap/structured-clone': 1.3.0 - ajv: 6.12.6 - chalk: 4.1.2 - cross-spawn: 7.0.6 - debug: 4.4.3 - doctrine: 3.0.0 - escape-string-regexp: 4.0.0 - eslint-scope: 7.2.2 - eslint-visitor-keys: 3.4.3 - espree: 9.6.1 - esquery: 1.7.0 - esutils: 2.0.3 - fast-deep-equal: 3.1.3 - file-entry-cache: 6.0.1 - find-up: 5.0.0 - glob-parent: 6.0.2 - globals: 13.24.0 - graphemer: 1.4.0 - ignore: 5.3.2 - imurmurhash: 0.1.4 - is-glob: 4.0.3 - is-path-inside: 3.0.3 - js-yaml: 4.1.1 - json-stable-stringify-without-jsonify: 1.0.1 - levn: 0.4.1 - lodash.merge: 4.6.2 - minimatch: 3.1.2 - natural-compare: 1.4.0 - optionator: 0.9.4 - strip-ansi: 6.0.1 - text-table: 0.2.0 - transitivePeerDependencies: - - supports-color - - eslint@9.39.2(jiti@1.21.7): - dependencies: - '@eslint-community/eslint-utils': 4.9.1(eslint@9.39.2(jiti@1.21.7)) - '@eslint-community/regexpp': 4.12.2 - '@eslint/config-array': 0.21.1 - '@eslint/config-helpers': 0.4.2 - '@eslint/core': 0.17.0 - '@eslint/eslintrc': 3.3.3 - '@eslint/js': 9.39.2 - '@eslint/plugin-kit': 0.4.1 - '@humanfs/node': 0.16.7 - '@humanwhocodes/module-importer': 1.0.1 - '@humanwhocodes/retry': 0.4.3 - '@types/estree': 1.0.8 - ajv: 6.12.6 - chalk: 4.1.2 - cross-spawn: 7.0.6 - debug: 4.4.3 - escape-string-regexp: 4.0.0 - eslint-scope: 8.4.0 - eslint-visitor-keys: 4.2.1 - espree: 10.4.0 - esquery: 1.7.0 - esutils: 2.0.3 - fast-deep-equal: 3.1.3 - file-entry-cache: 8.0.0 - find-up: 5.0.0 - glob-parent: 6.0.2 - ignore: 5.3.2 - imurmurhash: 0.1.4 - is-glob: 4.0.3 - json-stable-stringify-without-jsonify: 1.0.1 - lodash.merge: 4.6.2 - minimatch: 3.1.2 - natural-compare: 1.4.0 - optionator: 0.9.4 - optionalDependencies: - jiti: 1.21.7 - transitivePeerDependencies: - - supports-color - - espree@10.4.0: - dependencies: - acorn: 8.15.0 - acorn-jsx: 5.3.2(acorn@8.15.0) - eslint-visitor-keys: 4.2.1 - - espree@9.6.1: - dependencies: - acorn: 8.15.0 - acorn-jsx: 5.3.2(acorn@8.15.0) - eslint-visitor-keys: 3.4.3 - esprima@4.0.1: {} - esquery@1.7.0: - dependencies: - estraverse: 5.3.0 - esrecurse@4.3.0: dependencies: estraverse: 5.3.0 @@ -14084,8 +13370,6 @@ snapshots: fast-deep-equal@3.1.3: {} - fast-diff@1.3.0: {} - fast-fifo@1.3.2: optional: true @@ -14099,8 +13383,6 @@ snapshots: fast-json-stable-stringify@2.1.0: {} - fast-levenshtein@2.0.6: {} - fast-safe-stringify@2.1.1: {} fast-stable-stringify@1.0.0: {} @@ -14136,14 +13418,6 @@ snapshots: dependencies: escape-string-regexp: 1.0.5 - file-entry-cache@6.0.1: - dependencies: - flat-cache: 3.2.0 - - file-entry-cache@8.0.0: - dependencies: - flat-cache: 4.0.1 - file-loader@6.2.0(webpack@5.104.1): dependencies: loader-utils: 2.0.4 @@ -14171,11 +13445,6 @@ snapshots: common-path-prefix: 3.0.0 pkg-dir: 7.0.0 - find-up@5.0.0: - dependencies: - locate-path: 6.0.0 - path-exists: 4.0.0 - find-up@6.3.0: dependencies: locate-path: 7.2.0 @@ -14187,21 +13456,8 @@ snapshots: mlly: 1.8.0 rollup: 4.55.1 - flat-cache@3.2.0: - dependencies: - flatted: 3.3.3 - keyv: 4.5.4 - rimraf: 3.0.2 - - flat-cache@4.0.1: - dependencies: - flatted: 3.3.3 - keyv: 4.5.4 - flat@5.0.2: {} - flatted@3.3.3: {} - follow-redirects@1.15.11: {} for-each@0.3.5: @@ -14229,8 +13485,6 @@ snapshots: jsonfile: 6.2.0 universalify: 2.0.1 - fs.realpath@1.0.0: {} - fsevents@2.3.3: optional: true @@ -14305,25 +13559,10 @@ snapshots: package-json-from-dist: 1.0.1 path-scurry: 1.11.1 - glob@7.2.3: - dependencies: - fs.realpath: 1.0.0 - inflight: 1.0.6 - inherits: 2.0.4 - minimatch: 3.1.2 - once: 1.4.0 - path-is-absolute: 1.0.1 - global-dirs@3.0.1: dependencies: ini: 2.0.0 - globals@13.24.0: - dependencies: - type-fest: 0.20.2 - - globals@14.0.0: {} - globby@11.1.0: dependencies: array-union: 2.1.0 @@ -14361,8 +13600,6 @@ snapshots: graceful-fs@4.2.11: {} - graphemer@1.4.0: {} - graphql@16.12.0: {} gray-matter@4.0.3: @@ -14402,10 +13639,6 @@ snapshots: handle-thing@2.0.1: {} - has-ansi@2.0.0: - dependencies: - ansi-regex: 2.1.1 - has-flag@4.0.0: {} has-property-descriptors@1.0.2: @@ -14754,8 +13987,6 @@ snapshots: ignore@5.3.2: {} - ignore@7.0.5: {} - image-size@2.0.2: {} import-fresh@3.3.1: @@ -14771,11 +14002,6 @@ snapshots: infima@0.2.0-alpha.45: {} - inflight@1.0.6: - dependencies: - once: 1.4.0 - wrappy: 1.0.2 - inherits@2.0.3: {} inherits@2.0.4: {} @@ -15084,8 +14310,6 @@ snapshots: json-schema@0.4.0: {} - json-stable-stringify-without-jsonify@1.0.1: {} - json-stringify-safe@5.0.1: {} json-text-sequence@0.3.0: @@ -15148,11 +14372,6 @@ snapshots: leven@3.1.0: {} - levn@0.4.1: - dependencies: - prelude-ls: 1.2.1 - type-check: 0.4.0 - lilconfig@3.1.3: {} lines-and-columns@1.2.4: {} @@ -15173,10 +14392,6 @@ snapshots: pkg-types: 2.3.0 quansync: 0.2.11 - locate-path@6.0.0: - dependencies: - p-locate: 5.0.0 - locate-path@7.2.0: dependencies: p-locate: 6.0.0 @@ -15187,19 +14402,10 @@ snapshots: lodash.memoize@4.1.2: {} - lodash.merge@4.6.2: {} - lodash.uniq@4.5.0: {} lodash@4.17.21: {} - loglevel-colored-level-prefix@1.0.0: - dependencies: - chalk: 1.1.3 - loglevel: 1.9.2 - - loglevel@1.9.2: {} - longest-streak@3.1.0: {} loose-envify@1.4.0: @@ -16161,10 +15367,6 @@ snapshots: dependencies: brace-expansion: 1.1.12 - minimatch@9.0.3: - dependencies: - brace-expansion: 2.0.2 - minimatch@9.0.5: dependencies: brace-expansion: 2.0.2 @@ -16237,8 +15439,6 @@ snapshots: nanoid@3.3.11: {} - natural-compare@1.4.0: {} - negotiator@0.6.3: {} negotiator@0.6.4: {} @@ -16324,6 +15524,7 @@ snapshots: once@1.4.0: dependencies: wrappy: 1.0.2 + optional: true onetime@5.1.2: dependencies: @@ -16344,15 +15545,6 @@ snapshots: opener@1.5.2: {} - optionator@0.9.4: - dependencies: - deep-is: 0.1.4 - fast-levenshtein: 2.0.6 - levn: 0.4.1 - prelude-ls: 1.2.1 - type-check: 0.4.0 - word-wrap: 1.2.5 - outvariant@1.4.3: {} ox@0.11.1(typescript@5.9.3)(zod@4.3.5): @@ -16389,18 +15581,10 @@ snapshots: p-finally@1.0.0: {} - p-limit@3.1.0: - dependencies: - yocto-queue: 0.1.0 - p-limit@4.0.0: dependencies: yocto-queue: 1.2.2 - p-locate@5.0.0: - dependencies: - p-limit: 3.1.0 - p-locate@6.0.0: dependencies: p-limit: 4.0.0 @@ -16504,12 +15688,8 @@ snapshots: path-browserify@1.0.1: {} - path-exists@4.0.0: {} - path-exists@5.0.0: {} - path-is-absolute@1.0.1: {} - path-is-inside@1.0.2: {} path-key@3.1.1: {} @@ -17026,43 +16206,11 @@ snapshots: picocolors: 1.1.1 source-map-js: 1.2.1 - prelude-ls@1.2.1: {} - - prettier-eslint@16.4.2(typescript@5.9.3): - dependencies: - '@typescript-eslint/parser': 6.21.0(eslint@8.57.1)(typescript@5.9.3) - common-tags: 1.8.2 - dlv: 1.1.3 - eslint: 8.57.1 - indent-string: 4.0.0 - lodash.merge: 4.6.2 - loglevel-colored-level-prefix: 1.0.0 - prettier: 3.7.4 - pretty-format: 29.7.0 - require-relative: 0.8.7 - tslib: 2.8.1 - vue-eslint-parser: 9.4.3(eslint@8.57.1) - transitivePeerDependencies: - - supports-color - - typescript - - prettier-linter-helpers@1.0.1: - dependencies: - fast-diff: 1.3.0 - - prettier@3.7.4: {} - pretty-error@4.0.0: dependencies: lodash: 4.17.21 renderkid: 3.0.0 - pretty-format@29.7.0: - dependencies: - '@jest/schemas': 29.6.3 - ansi-styles: 5.2.0 - react-is: 18.3.1 - pretty-format@30.2.0: dependencies: '@jest/schemas': 30.0.5 @@ -17443,8 +16591,6 @@ snapshots: require-like@0.1.2: {} - require-relative@0.8.7: {} - requires-port@1.0.0: {} resolve-alpn@1.2.1: {} @@ -17473,10 +16619,6 @@ snapshots: reusify@1.1.0: {} - rimraf@3.0.2: - dependencies: - glob: 7.2.3 - ripemd160@2.0.3: dependencies: hash-base: 3.1.2 @@ -17914,10 +17056,6 @@ snapshots: is-obj: 1.0.1 is-regexp: 1.0.0 - strip-ansi@3.0.1: - dependencies: - ansi-regex: 2.1.1 - strip-ansi@6.0.1: dependencies: ansi-regex: 5.0.1 @@ -17973,8 +17111,6 @@ snapshots: superstruct@2.0.2: {} - supports-color@2.0.0: {} - supports-color@7.2.0: dependencies: has-flag: 4.0.0 @@ -18003,10 +17139,6 @@ snapshots: react: 18.3.1 use-sync-external-store: 1.6.0(react@18.3.1) - synckit@0.11.11: - dependencies: - '@pkgr/core': 0.2.9 - tagged-tag@1.0.0: {} tapable@2.3.0: {} @@ -18065,8 +17197,6 @@ snapshots: text-encoding-utf-8@1.0.2: {} - text-table@0.2.0: {} - thenify-all@1.6.0: dependencies: thenify: 3.3.1 @@ -18147,14 +17277,6 @@ snapshots: trough@2.2.0: {} - ts-api-utils@1.4.3(typescript@5.9.3): - dependencies: - typescript: 5.9.3 - - ts-api-utils@2.4.0(typescript@5.9.3): - dependencies: - typescript: 5.9.3 - ts-dedent@2.2.0: {} ts-interface-checker@0.1.13: {} @@ -18197,13 +17319,34 @@ snapshots: optionalDependencies: fsevents: 2.3.3 - tweetnacl@1.0.3: {} + turbo-darwin-64@2.7.4: + optional: true - type-check@0.4.0: - dependencies: - prelude-ls: 1.2.1 + turbo-darwin-arm64@2.7.4: + optional: true - type-fest@0.20.2: {} + turbo-linux-64@2.7.4: + optional: true + + turbo-linux-arm64@2.7.4: + optional: true + + turbo-windows-64@2.7.4: + optional: true + + turbo-windows-arm64@2.7.4: + optional: true + + turbo@2.7.4: + optionalDependencies: + turbo-darwin-64: 2.7.4 + turbo-darwin-arm64: 2.7.4 + turbo-linux-64: 2.7.4 + turbo-linux-arm64: 2.7.4 + turbo-windows-64: 2.7.4 + turbo-windows-arm64: 2.7.4 + + tweetnacl@1.0.3: {} type-fest@0.21.3: {} @@ -18610,19 +17753,6 @@ snapshots: vscode-uri@3.1.0: {} - vue-eslint-parser@9.4.3(eslint@8.57.1): - dependencies: - debug: 4.4.3 - eslint: 8.57.1 - eslint-scope: 7.2.2 - eslint-visitor-keys: 3.4.3 - espree: 9.6.1 - esquery: 1.7.0 - lodash: 4.17.21 - semver: 7.7.3 - transitivePeerDependencies: - - supports-color - watchpack@2.5.0: dependencies: glob-to-regexp: 0.4.1 @@ -18809,8 +17939,6 @@ snapshots: wildcard@2.0.1: {} - word-wrap@1.2.5: {} - wrap-ansi@6.2.0: dependencies: ansi-styles: 4.3.0 @@ -18829,7 +17957,8 @@ snapshots: string-width: 5.1.2 strip-ansi: 7.1.2 - wrappy@1.0.2: {} + wrappy@1.0.2: + optional: true write-file-atomic@3.0.3: dependencies: @@ -18887,8 +18016,6 @@ snapshots: fd-slicer: 1.1.0 optional: true - yocto-queue@0.1.0: {} - yocto-queue@1.2.2: {} yoctocolors-cjs@2.1.3: {} diff --git a/turbo.json b/turbo.json new file mode 100644 index 00000000..5ca17f2e --- /dev/null +++ b/turbo.json @@ -0,0 +1,27 @@ +{ + "$schema": "https://turbo.build/schema.json", + "tasks": { + "build": { + "dependsOn": ["^build"], + "outputs": ["dist/**"] + }, + "test": { + "dependsOn": ["build"], + "outputs": [] + }, + "lint": { + "outputs": [] + }, + "lint:fix": { + "outputs": [] + }, + "typecheck": { + "dependsOn": ["^build"], + "outputs": [] + }, + "e2e": { + "dependsOn": ["build"], + "cache": false + } + } +} From 4a24ebd7d6e3e84f96cfb2c1602f782e80cc259b Mon Sep 17 00:00:00 2001 From: netbonus <151201453+netbonus@users.noreply.github.com> Date: Wed, 21 Jan 2026 10:40:32 -0500 Subject: [PATCH 02/14] fix: resolve all Biome lint errors - Fix node:buffer imports (use node: protocol) - Fix regex control characters with biome-ignore comments - Add explicit types to implicit any let declarations - Fix React Button type and useEffect dependencies - Fix non-null assertions with proper guards --- biome.json | 161 +- .../components/HomepageFeatures.module.css | 12 +- .../docs/src/components/HomepageFeatures.tsx | 107 +- packages/docs/src/css/custom.css | 28 +- packages/docs/src/pages/_index.tsx | 58 +- packages/docs/src/pages/index.module.css | 20 +- packages/example/src/App.tsx | 161 +- packages/example/src/Button.tsx | 24 +- packages/example/src/Lattice.tsx | 232 +- packages/example/src/index.css | 88 +- packages/example/src/main.tsx | 16 +- packages/sdk/biome.json | 12 +- packages/sdk/package.json | 6 +- packages/sdk/src/__test__/e2e/api.test.ts | 652 +- packages/sdk/src/__test__/e2e/btc.test.ts | 331 +- packages/sdk/src/__test__/e2e/eth.msg.test.ts | 2591 ++++---- .../__test__/e2e/ethereum/addresses.test.ts | 34 +- packages/sdk/src/__test__/e2e/general.test.ts | 562 +- packages/sdk/src/__test__/e2e/kv.test.ts | 491 +- .../src/__test__/e2e/non-exportable.test.ts | 297 +- .../sdk/src/__test__/e2e/signing/bls.test.ts | 348 +- .../__test__/e2e/signing/determinism.test.ts | 859 ++- .../__test__/e2e/signing/eip712-msg.test.ts | 24 +- .../__test__/e2e/signing/eip712-vectors.ts | 614 +- .../src/__test__/e2e/signing/evm-abi.test.ts | 38 +- .../src/__test__/e2e/signing/evm-tx.test.ts | 90 +- .../e2e/signing/solana/__mocks__/programs.ts | 114 +- .../signing/solana/solana.programs.test.ts | 69 +- .../e2e/signing/solana/solana.test.ts | 227 +- .../signing/solana/solana.versioned.test.ts | 357 +- .../__test__/e2e/signing/unformatted.test.ts | 323 +- .../sdk/src/__test__/e2e/signing/vectors.ts | 1895 +++--- .../src/__test__/e2e/solana/addresses.test.ts | 78 +- packages/sdk/src/__test__/e2e/xpub.test.ts | 48 +- .../__test__/integration/__mocks__/4byte.ts | 165 +- .../integration/__mocks__/addKvRecords.json | 4 +- .../integration/__mocks__/connect.json | 4 +- .../integration/__mocks__/etherscan.ts | 5730 ++++++++--------- .../__mocks__/fetchActiveWallet.json | 4 +- .../integration/__mocks__/getAddresses.json | 4 +- .../integration/__mocks__/getKvRecords.json | 4 +- .../integration/__mocks__/handlers.ts | 180 +- .../__mocks__/removeKvRecords.json | 4 +- .../__test__/integration/__mocks__/server.ts | 6 +- .../__test__/integration/__mocks__/setup.ts | 10 +- .../__test__/integration/__mocks__/sign.json | 4 +- .../integration/client.interop.test.ts | 32 +- .../src/__test__/integration/connect.test.ts | 118 +- .../integration/fetchCalldataDecoder.test.ts | 168 +- .../__test__/unit/__mocks__/decoderData.ts | 151 +- packages/sdk/src/__test__/unit/api.test.ts | 112 +- .../unit/compareEIP7702Serialization.test.ts | 815 ++- .../sdk/src/__test__/unit/decoders.test.ts | 123 +- .../sdk/src/__test__/unit/eip7702.test.ts | 343 +- .../sdk/src/__test__/unit/encoders.test.ts | 210 +- .../__test__/unit/ethereum.validate.test.ts | 155 +- .../src/__test__/unit/module.interop.test.ts | 148 +- .../unit/parseGenericSigningResponse.test.ts | 317 +- .../unit/personalSignValidation.test.ts | 250 +- .../unit/selectDefFrom4byteABI.test.ts | 146 +- .../src/__test__/unit/signatureUtils.test.ts | 839 ++- .../sdk/src/__test__/unit/validators.test.ts | 583 +- .../__test__/utils/__test__/builders.test.ts | 42 +- .../utils/__test__/serializers.test.ts | 71 +- packages/sdk/src/__test__/utils/builders.ts | 578 +- packages/sdk/src/__test__/utils/constants.ts | 2 +- .../sdk/src/__test__/utils/determinism.ts | 128 +- packages/sdk/src/__test__/utils/ethers.ts | 249 +- packages/sdk/src/__test__/utils/getters.ts | 24 +- packages/sdk/src/__test__/utils/helpers.ts | 1839 +++--- packages/sdk/src/__test__/utils/runners.ts | 63 +- .../sdk/src/__test__/utils/serializers.ts | 44 +- packages/sdk/src/__test__/utils/setup.ts | 88 +- .../sdk/src/__test__/utils/testConstants.ts | 7 +- .../sdk/src/__test__/utils/testEnvironment.ts | 19 +- .../sdk/src/__test__/utils/testRequest.ts | 55 +- .../sdk/src/__test__/utils/viemComparison.ts | 427 +- packages/sdk/src/__test__/utils/vitest.d.ts | 10 +- packages/sdk/src/__test__/vectors.jsonc | 464 +- .../sdk/src/__test__/vectors/abi-vectors.ts | 272 +- packages/sdk/src/api/addressTags.ts | 81 +- packages/sdk/src/api/addresses.ts | 412 +- packages/sdk/src/api/index.ts | 20 +- packages/sdk/src/api/setup.ts | 100 +- packages/sdk/src/api/signing.ts | 422 +- packages/sdk/src/api/state.ts | 26 +- packages/sdk/src/api/utilities.ts | 169 +- packages/sdk/src/api/wallets.ts | 8 +- packages/sdk/src/bitcoin.ts | 769 ++- packages/sdk/src/calldata/evm.ts | 773 ++- packages/sdk/src/calldata/index.ts | 31 +- packages/sdk/src/client.ts | 765 +-- packages/sdk/src/constants.ts | 1026 ++- packages/sdk/src/ethereum.ts | 2304 +++---- packages/sdk/src/functions/addKvRecords.ts | 142 +- packages/sdk/src/functions/connect.ts | 206 +- .../sdk/src/functions/fetchActiveWallet.ts | 115 +- packages/sdk/src/functions/fetchDecoder.ts | 42 +- packages/sdk/src/functions/fetchEncData.ts | 356 +- packages/sdk/src/functions/getAddresses.ts | 351 +- packages/sdk/src/functions/getKvRecords.ts | 207 +- packages/sdk/src/functions/index.ts | 18 +- packages/sdk/src/functions/pair.ts | 97 +- packages/sdk/src/functions/removeKvRecords.ts | 135 +- packages/sdk/src/functions/sign.ts | 526 +- packages/sdk/src/genericSigning.ts | 764 +-- packages/sdk/src/index.ts | 10 +- packages/sdk/src/protocol/index.ts | 4 +- packages/sdk/src/protocol/latticeConstants.ts | 330 +- packages/sdk/src/protocol/secureMessages.ts | 450 +- packages/sdk/src/schemas/index.ts | 2 +- packages/sdk/src/schemas/transaction.ts | 392 +- packages/sdk/src/shared/errors.ts | 56 +- packages/sdk/src/shared/functions.ts | 304 +- packages/sdk/src/shared/predicates.ts | 22 +- packages/sdk/src/shared/utilities.ts | 159 +- packages/sdk/src/shared/validators.ts | 369 +- packages/sdk/src/types/addKvRecords.ts | 15 +- packages/sdk/src/types/client.ts | 100 +- packages/sdk/src/types/connect.ts | 6 +- packages/sdk/src/types/declarations.d.ts | 26 +- packages/sdk/src/types/fetchActiveWallet.ts | 6 +- packages/sdk/src/types/fetchEncData.ts | 28 +- packages/sdk/src/types/firmware.ts | 101 +- packages/sdk/src/types/getAddresses.ts | 15 +- packages/sdk/src/types/getKvRecords.ts | 31 +- packages/sdk/src/types/index.ts | 94 +- packages/sdk/src/types/messages.ts | 32 +- packages/sdk/src/types/pair.ts | 6 +- packages/sdk/src/types/removeKvRecords.ts | 11 +- packages/sdk/src/types/secureMessages.ts | 76 +- packages/sdk/src/types/shared.ts | 16 +- packages/sdk/src/types/sign.ts | 258 +- packages/sdk/src/types/tiny-secp256k1.d.ts | 2 +- packages/sdk/src/types/utils.ts | 44 +- packages/sdk/src/types/vitest.d.ts | 12 +- packages/sdk/src/util.ts | 1540 ++--- packages/sdk/tsup.config.ts | 36 +- pnpm-lock.yaml | 222 +- 139 files changed, 19141 insertions(+), 21477 deletions(-) diff --git a/biome.json b/biome.json index d319d518..5e9ef10e 100644 --- a/biome.json +++ b/biome.json @@ -1,85 +1,80 @@ { - "$schema": "https://biomejs.dev/schemas/2.2.4/schema.json", - "vcs": { - "enabled": true, - "clientKind": "git", - "useIgnoreFile": true - }, - "formatter": { - "enabled": true, - "formatWithErrors": false, - "indentStyle": "space", - "indentWidth": 2, - "lineEnding": "lf", - "lineWidth": 80, - "attributePosition": "auto" - }, - "linter": { - "enabled": true, - "rules": { - "recommended": true, - "complexity": { - "noForEach": "off" - }, - "correctness": { - "noUnusedImports": { - "level": "error" - }, - "noUnusedVariables": { - "level": "warn", - "fix": "none" - }, - "noUnusedFunctionParameters": { - "level": "warn", - "fix": "none" - } - }, - "style": { - "useConst": "error", - "noVar": "warn", - "noParameterAssign": "off", - "noUselessElse": "off", - "noUnusedTemplateLiteral": "error", - "useAsConstAssertion": "error", - "useDefaultParameterLast": "error", - "useEnumInitializers": "error", - "useSingleVarDeclarator": "error", - "useNumberNamespace": "error", - "noInferrableTypes": "error", - "noNonNullAssertion": "off" - }, - "performance": { - "noAccumulatingSpread": "warn" - }, - "suspicious": { - "noExplicitAny": "off", - "noDoubleEquals": "error", - "noImplicitAnyLet": "off", - "noConfusingVoidType": "off", - "noControlCharactersInRegex": "off", - "noAssignInExpressions": "warn" - } - } - }, - "organizeImports": { - "enabled": false - }, - "files": { - "ignoreUnknown": true, - "include": ["packages/*/src/**"], - "ignore": ["**/dist/**", "**/node_modules/**", "**/coverage/**"] - }, - "javascript": { - "formatter": { - "jsxQuoteStyle": "double", - "quoteProperties": "asNeeded", - "trailingCommas": "all", - "semicolons": "always", - "arrowParentheses": "always", - "bracketSpacing": true, - "bracketSameLine": false, - "quoteStyle": "single", - "attributePosition": "auto" - } - } + "$schema": "https://biomejs.dev/schemas/2.2.4/schema.json", + "vcs": { + "enabled": true, + "clientKind": "git", + "useIgnoreFile": true + }, + "formatter": { + "enabled": true, + "formatWithErrors": false, + "indentStyle": "tab", + "indentWidth": 2, + "lineEnding": "lf", + "lineWidth": 240, + "attributePosition": "auto" + }, + "linter": { + "enabled": true, + "rules": { + "recommended": true, + "complexity": { + "noForEach": "warn" + }, + "correctness": { + "noUnusedImports": { + "level": "error" + }, + "noUnusedVariables": { + "level": "warn", + "fix": "none" + }, + "noUnusedFunctionParameters": { + "level": "warn", + "fix": "none" + } + }, + "style": { + "noParameterAssign": "warn", + "noNonNullAssertion": "error", + "noUselessElse": "warn", + "noUnusedTemplateLiteral": "error", + "useAsConstAssertion": "error", + "useDefaultParameterLast": "error", + "useEnumInitializers": "error", + "useSingleVarDeclarator": "error", + "useNumberNamespace": "error", + "noInferrableTypes": "error" + }, + "performance": { + "noAccumulatingSpread": "warn" + }, + "suspicious": { + "noArrayIndexKey": "warn", + "noAssignInExpressions": "warn", + "noExplicitAny": "warn" + } + } + }, + "organizeImports": { + "enabled": false + }, + "files": { + "ignoreUnknown": true, + "include": ["packages/*/src/**"], + "ignore": ["**/dist/**", "**/node_modules/**", "**/coverage/**"] + }, + "javascript": { + "formatter": { + "jsxQuoteStyle": "double", + "quoteProperties": "asNeeded", + "trailingCommas": "all", + "semicolons": "asNeeded", + "arrowParentheses": "always", + "bracketSpacing": true, + "bracketSameLine": false, + "quoteStyle": "single", + "attributePosition": "auto" + } + } } diff --git a/packages/docs/src/components/HomepageFeatures.module.css b/packages/docs/src/components/HomepageFeatures.module.css index b248eb2e..774404de 100644 --- a/packages/docs/src/components/HomepageFeatures.module.css +++ b/packages/docs/src/components/HomepageFeatures.module.css @@ -1,11 +1,11 @@ .features { - display: flex; - align-items: center; - padding: 2rem 0; - width: 100%; + display: flex; + align-items: center; + padding: 2rem 0; + width: 100%; } .featureSvg { - height: 200px; - width: 200px; + height: 200px; + width: 200px; } diff --git a/packages/docs/src/components/HomepageFeatures.tsx b/packages/docs/src/components/HomepageFeatures.tsx index 72309dd8..ea04dfb8 100644 --- a/packages/docs/src/components/HomepageFeatures.tsx +++ b/packages/docs/src/components/HomepageFeatures.tsx @@ -1,69 +1,58 @@ -import clsx from 'clsx'; -import styles from './HomepageFeatures.module.css'; +import clsx from 'clsx' +import styles from './HomepageFeatures.module.css' type FeatureItem = { - title: string; - image: string; - description: JSX.Element; -}; + title: string + image: string + description: JSX.Element +} const FeatureList: FeatureItem[] = [ - { - title: 'Easy to Use', - image: '/img/undraw_docusaurus_mountain.svg', - description: ( - <> - Docusaurus was designed from the ground up to be easily installed and - used to get your website up and running quickly. - - ), - }, - { - title: 'Focus on What Matters', - image: '/img/undraw_docusaurus_tree.svg', - description: ( - <> - Docusaurus lets you focus on your docs, and we'll do the chores. Go - ahead and move your docs into the docs directory. - - ), - }, - { - title: 'Powered by React', - image: '/img/undraw_docusaurus_react.svg', - description: ( - <> - Extend or customize your website layout by reusing React. Docusaurus can - be extended while reusing the same header and footer. - - ), - }, -]; + { + title: 'Easy to Use', + image: '/img/undraw_docusaurus_mountain.svg', + description: <>Docusaurus was designed from the ground up to be easily installed and used to get your website up and running quickly., + }, + { + title: 'Focus on What Matters', + image: '/img/undraw_docusaurus_tree.svg', + description: ( + <> + Docusaurus lets you focus on your docs, and we'll do the chores. Go ahead and move your docs into the docs directory. + + ), + }, + { + title: 'Powered by React', + image: '/img/undraw_docusaurus_react.svg', + description: <>Extend or customize your website layout by reusing React. Docusaurus can be extended while reusing the same header and footer., + }, +] function Feature({ title, image, description }: FeatureItem) { - return ( -
-
- {title} -
-
-

{title}

-

{description}

-
-
- ); + return ( +
+
+ {title} +
+
+

{title}

+

{description}

+
+
+ ) } export default function HomepageFeatures(): JSX.Element { - return ( -
-
-
- {FeatureList.map((props, idx) => ( - - ))} -
-
-
- ); + return ( +
+
+
+ {FeatureList.map((props, idx) => ( + + ))} +
+
+
+ ) } diff --git a/packages/docs/src/css/custom.css b/packages/docs/src/css/custom.css index 8f5c3276..1e29b327 100644 --- a/packages/docs/src/css/custom.css +++ b/packages/docs/src/css/custom.css @@ -6,23 +6,23 @@ /* You can override the default Infima variables here. */ :root { - --ifm-color-primary: #53b7e8; - --ifm-color-primary-dark: rgb(33, 175, 144); - --ifm-color-primary-darker: rgb(31, 165, 136); - --ifm-color-primary-darkest: rgb(26, 136, 112); - --ifm-color-primary-light: rgb(70, 203, 174); - --ifm-color-primary-lighter: rgb(102, 212, 189); - --ifm-color-primary-lightest: rgb(146, 224, 208); - --ifm-code-font-size: 95%; + --ifm-color-primary: #53b7e8; + --ifm-color-primary-dark: rgb(33, 175, 144); + --ifm-color-primary-darker: rgb(31, 165, 136); + --ifm-color-primary-darkest: rgb(26, 136, 112); + --ifm-color-primary-light: rgb(70, 203, 174); + --ifm-color-primary-lighter: rgb(102, 212, 189); + --ifm-color-primary-lightest: rgb(146, 224, 208); + --ifm-code-font-size: 95%; } .docusaurus-highlight-code-line { - background-color: rgba(0, 0, 0, 0.1); - display: block; - margin: 0 calc(-1 * var(--ifm-pre-padding)); - padding: 0 var(--ifm-pre-padding); + background-color: rgba(0, 0, 0, 0.1); + display: block; + margin: 0 calc(-1 * var(--ifm-pre-padding)); + padding: 0 var(--ifm-pre-padding); } -html[data-theme='dark'] .docusaurus-highlight-code-line { - background-color: rgba(0, 0, 0, 0.3); +html[data-theme="dark"] .docusaurus-highlight-code-line { + background-color: rgba(0, 0, 0, 0.3); } diff --git a/packages/docs/src/pages/_index.tsx b/packages/docs/src/pages/_index.tsx index dbe9caaf..878a94a2 100644 --- a/packages/docs/src/pages/_index.tsx +++ b/packages/docs/src/pages/_index.tsx @@ -1,38 +1,32 @@ -import Link from '@docusaurus/Link'; -import useDocusaurusContext from '@docusaurus/useDocusaurusContext'; -import Layout from '@theme/Layout'; -import clsx from 'clsx'; -import styles from './index.module.css'; +import Link from '@docusaurus/Link' +import useDocusaurusContext from '@docusaurus/useDocusaurusContext' +import Layout from '@theme/Layout' +import clsx from 'clsx' +import styles from './index.module.css' function HomepageHeader() { - const { siteConfig } = useDocusaurusContext(); - return ( -
-
-

{siteConfig.title}

-

{siteConfig.tagline}

-
- - Getting Started - -
-
-
- ); + const { siteConfig } = useDocusaurusContext() + return ( +
+
+

{siteConfig.title}

+

{siteConfig.tagline}

+
+ + Getting Started + +
+
+
+ ) } export default function Home(): JSX.Element { - const { siteConfig } = useDocusaurusContext(); - return ( - - -
{/* */}
-
- ); + const { siteConfig } = useDocusaurusContext() + return ( + + +
{/* */}
+
+ ) } diff --git a/packages/docs/src/pages/index.module.css b/packages/docs/src/pages/index.module.css index 666feb6a..df02ba51 100644 --- a/packages/docs/src/pages/index.module.css +++ b/packages/docs/src/pages/index.module.css @@ -4,20 +4,20 @@ */ .heroBanner { - padding: 4rem 0; - text-align: center; - position: relative; - overflow: hidden; + padding: 4rem 0; + text-align: center; + position: relative; + overflow: hidden; } @media screen and (max-width: 966px) { - .heroBanner { - padding: 2rem; - } + .heroBanner { + padding: 2rem; + } } .buttons { - display: flex; - align-items: center; - justify-content: center; + display: flex; + align-items: center; + justify-content: center; } diff --git a/packages/example/src/App.tsx b/packages/example/src/App.tsx index cd6bd031..0598ed99 100644 --- a/packages/example/src/App.tsx +++ b/packages/example/src/App.tsx @@ -1,97 +1,86 @@ -import { useEffect, useState } from 'react'; -import { getClient, pair, setup } from '../../src/api/index'; -import './App.css'; -import { Lattice } from './Lattice'; +import { useCallback, useEffect, useState } from 'react' +import { getClient, pair, setup } from '../../src/api/index' +import './App.css' +import { Lattice } from './Lattice' function App() { - const [label, setLabel] = useState('No Device'); + const [label, setLabel] = useState('No Device') - const getStoredClient = () => - window.localStorage.getItem('storedClient') || ''; + const getStoredClient = useCallback(() => window.localStorage.getItem('storedClient') || '', []) - const setStoredClient = (storedClient: string | null) => { - if (!storedClient) return; - window.localStorage.setItem('storedClient', storedClient); + const setStoredClient = useCallback((storedClient: string | null) => { + if (!storedClient) return + window.localStorage.setItem('storedClient', storedClient) - const client = getClient(); - setLabel(client?.getDeviceId() || 'No Device'); - }; + const client = getClient() + setLabel(client?.getDeviceId() || 'No Device') + }, []) - useEffect(() => { - if (getStoredClient()) { - setup({ getStoredClient, setStoredClient }); - } - }, []); + useEffect(() => { + if (getStoredClient()) { + setup({ getStoredClient, setStoredClient }) + } + }, [getStoredClient, setStoredClient]) - const submitInit = (e: any) => { - e.preventDefault(); - const deviceId = e.currentTarget[0].value; - const password = e.currentTarget[1].value; - const name = e.currentTarget[2].value; - setup({ - deviceId, - password, - name, - getStoredClient, - setStoredClient, - }); - }; + const submitInit = (e: any) => { + e.preventDefault() + const deviceId = e.currentTarget[0].value + const password = e.currentTarget[1].value + const name = e.currentTarget[2].value + setup({ + deviceId, + password, + name, + getStoredClient, + setStoredClient, + }) + } - const submitPair = (e: React.FormEvent) => { - e.preventDefault(); - // @ts-expect-error - bad html types - const pairingCode = e.currentTarget[0].value.toUpperCase(); - pair(pairingCode); - }; + const submitPair = (e: React.FormEvent) => { + e.preventDefault() + // @ts-expect-error - bad html types + const pairingCode = e.currentTarget[0].value.toUpperCase() + pair(pairingCode) + } - return ( -
-

EXAMPLE APP

-
-
-
- - - - -
-
-
-
- - -
-
- -
-
- ); + return ( +
+

EXAMPLE APP

+
+
+
+ + + + +
+
+
+
+ + +
+
+ +
+
+ ) } -export default App; +export default App diff --git a/packages/example/src/Button.tsx b/packages/example/src/Button.tsx index 86f16bc0..ac557717 100644 --- a/packages/example/src/Button.tsx +++ b/packages/example/src/Button.tsx @@ -1,15 +1,15 @@ -import { useState } from 'react'; +import { useState } from 'react' export const Button = ({ onClick, children }) => { - const [isLoading, setIsLoading] = useState(false); + const [isLoading, setIsLoading] = useState(false) - const handleOnClick = () => { - setIsLoading(true); - onClick().finally(() => setIsLoading(false)); - }; - return ( - - ); -}; + const handleOnClick = () => { + setIsLoading(true) + onClick().finally(() => setIsLoading(false)) + } + return ( + + ) +} diff --git a/packages/example/src/Lattice.tsx b/packages/example/src/Lattice.tsx index 5eedd56e..66262b1f 100644 --- a/packages/example/src/Lattice.tsx +++ b/packages/example/src/Lattice.tsx @@ -1,126 +1,118 @@ -import { Chain, Common, Hardfork } from '@ethereumjs/common'; -import { TransactionFactory } from '@ethereumjs/tx'; -import { useState } from 'react'; -import { - addAddressTags, - fetchAddressTags, - fetchAddresses, - fetchLedgerLiveAddresses, - removeAddressTags, - sign, - signMessage, -} from '../../src/api'; -import { Button } from './Button'; +import { Chain, Common, Hardfork } from '@ethereumjs/common' +import { TransactionFactory } from '@ethereumjs/tx' +import { useState } from 'react' +import { addAddressTags, fetchAddressTags, fetchAddresses, fetchLedgerLiveAddresses, removeAddressTags, sign, signMessage } from '../../src/api' +import { Button } from './Button' export const Lattice = ({ label }) => { - const [addresses, setAddresses] = useState([]); - const [addressTags, setAddressTags] = useState<{ id: string }[]>([]); - const [ledgerAddresses, setLedgerAddresses] = useState([]); + const [addresses, setAddresses] = useState([]) + const [addressTags, setAddressTags] = useState<{ id: string }[]>([]) + const [ledgerAddresses, setLedgerAddresses] = useState([]) - const getTxPayload = () => { - const txData = { - type: 1, - maxFeePerGas: 1200000000, - maxPriorityFeePerGas: 1200000000, - nonce: 0, - gasLimit: 50000, - to: '0xe242e54155b1abc71fc118065270cecaaf8b7768', - value: 1000000000000, - data: '0x17e914679b7e160613be4f8c2d3203d236286d74eb9192f6d6f71b9118a42bb033ccd8e8', - gasPrice: 1200000000, - }; - const common = new Common({ - chain: Chain.Mainnet, - hardfork: Hardfork.London, - }); - const tx = TransactionFactory.fromTxData(txData, { common }); - const payload = tx.getMessageToSign(false); - return payload; - }; + const getTxPayload = () => { + const txData = { + type: 1, + maxFeePerGas: 1200000000, + maxPriorityFeePerGas: 1200000000, + nonce: 0, + gasLimit: 50000, + to: '0xe242e54155b1abc71fc118065270cecaaf8b7768', + value: 1000000000000, + data: '0x17e914679b7e160613be4f8c2d3203d236286d74eb9192f6d6f71b9118a42bb033ccd8e8', + gasPrice: 1200000000, + } + const common = new Common({ + chain: Chain.Mainnet, + hardfork: Hardfork.London, + }) + const tx = TransactionFactory.fromTxData(txData, { common }) + const payload = tx.getMessageToSign(false) + return payload + } - return ( -
-

{label}

- - + return ( +
+

{label}

+ + -
-

Addresses

-
    - {addresses?.map((address) => ( -
  • {address}
  • - ))} -
-
- - - - -
-

Address Tags

-
    - {addressTags?.map((tag: any) => ( -
  • - {tag.key}: {tag.val} -
  • - ))} -
-
+
+

Addresses

+
    + {addresses?.map((address) => ( +
  • {address}
  • + ))} +
+
+ + + + +
+

Address Tags

+
    + {addressTags?.map((tag: any) => ( +
  • + {tag.key}: {tag.val} +
  • + ))} +
+
-
-

Ledger Addresses

-
    - {ledgerAddresses?.map((ledgerAddress: any) => ( -
  • {ledgerAddress}
  • - ))} -
-
- -
- ); -}; +
+

Ledger Addresses

+
    + {ledgerAddresses?.map((ledgerAddress: any) => ( +
  • {ledgerAddress}
  • + ))} +
+
+ +
+ ) +} diff --git a/packages/example/src/index.css b/packages/example/src/index.css index c6ec5363..4bd3f8e8 100644 --- a/packages/example/src/index.css +++ b/packages/example/src/index.css @@ -1,73 +1,73 @@ :root { - font-family: Inter, Avenir, Helvetica, Arial, sans-serif; - font-size: 16px; - line-height: 24px; - font-weight: 400; + font-family: Inter, Avenir, Helvetica, Arial, sans-serif; + font-size: 16px; + line-height: 24px; + font-weight: 400; - color-scheme: light dark; - color: rgba(255, 255, 255, 0.87); - background-color: #242424; + color-scheme: light dark; + color: rgba(255, 255, 255, 0.87); + background-color: #242424; - font-synthesis: none; - text-rendering: optimizeLegibility; - -webkit-font-smoothing: antialiased; - -moz-osx-font-smoothing: grayscale; - -webkit-text-size-adjust: 100%; + font-synthesis: none; + text-rendering: optimizeLegibility; + -webkit-font-smoothing: antialiased; + -moz-osx-font-smoothing: grayscale; + -webkit-text-size-adjust: 100%; } #root { - max-width: 1280px; - margin: 0 auto; - padding: 2rem; - text-align: center; + max-width: 1280px; + margin: 0 auto; + padding: 2rem; + text-align: center; } a { - font-weight: 500; - color: #646cff; - text-decoration: inherit; + font-weight: 500; + color: #646cff; + text-decoration: inherit; } a:hover { - color: #535bf2; + color: #535bf2; } body { - margin: 0; - display: flex; - place-items: center; - min-width: 320px; - min-height: 100vh; + margin: 0; + display: flex; + place-items: center; + min-width: 320px; + min-height: 100vh; } h1 { - font-size: 3.2em; - line-height: 1.1; + font-size: 3.2em; + line-height: 1.1; } button { - border-radius: 8px; - border: 1px solid transparent; - padding: 0.6em 1.2em; - font-size: 1em; - font-weight: 500; - font-family: inherit; - background-color: #1a1a1a; - cursor: pointer; - transition: border-color 0.25s; - margin-top: 10px; + border-radius: 8px; + border: 1px solid transparent; + padding: 0.6em 1.2em; + font-size: 1em; + font-weight: 500; + font-family: inherit; + background-color: #1a1a1a; + cursor: pointer; + transition: border-color 0.25s; + margin-top: 10px; } button:hover { - border-color: #646cff; + border-color: #646cff; } button:focus, button:focus-visible { - outline: 4px auto -webkit-focus-ring-color; + outline: 4px auto -webkit-focus-ring-color; } input { - border-radius: 8px; - border: 1px solid transparent; - margin-top: 15px; - font-size: 16px; - padding: 0.6em; + border-radius: 8px; + border: 1px solid transparent; + margin-top: 15px; + font-size: 16px; + padding: 0.6em; } diff --git a/packages/example/src/main.tsx b/packages/example/src/main.tsx index 5549107e..57939945 100644 --- a/packages/example/src/main.tsx +++ b/packages/example/src/main.tsx @@ -1,10 +1,10 @@ -import React from 'react'; -import ReactDOM from 'react-dom/client'; -import App from './App'; -import './index.css'; +import React from 'react' +import ReactDOM from 'react-dom/client' +import App from './App' +import './index.css' ReactDOM.createRoot(document.getElementById('root') as HTMLElement).render( - - - , -); + + + , +) diff --git a/packages/sdk/biome.json b/packages/sdk/biome.json index 5fb64252..0c057ff6 100644 --- a/packages/sdk/biome.json +++ b/packages/sdk/biome.json @@ -1,8 +1,8 @@ { - "$schema": "https://biomejs.dev/schemas/2.2.4/schema.json", - "extends": ["../../biome.json"], - "files": { - "include": ["src/**/*.ts"], - "ignore": ["**/dist/**", "**/node_modules/**", "**/coverage/**"] - } + "$schema": "https://biomejs.dev/schemas/2.2.4/schema.json", + "extends": ["../../biome.json"], + "files": { + "include": ["src/**/*.ts"], + "ignore": ["**/dist/**", "**/node_modules/**", "**/coverage/**"] + } } diff --git a/packages/sdk/package.json b/packages/sdk/package.json index 4421e0a5..b5144170 100644 --- a/packages/sdk/package.json +++ b/packages/sdk/package.json @@ -40,15 +40,15 @@ ], "main": "./dist/index.cjs", "module": "./dist/index.mjs", + "types": "./dist/index.d.cts", "exports": { ".": { - "types": "./dist/index.d.ts", + "types": "./dist/index.d.cts", "import": "./dist/index.mjs", "require": "./dist/index.cjs" }, "./package.json": "./package.json" }, - "types": "./dist/index.d.ts", "repository": { "type": "git", "url": "https://github.com/GridPlus/gridplus-sdk.git" @@ -104,10 +104,10 @@ "random-words": "^2.0.1", "readline-sync": "^1.4.10", "seedrandom": "^3.0.5", + "tiny-secp256k1": "^2.2.4", "tsup": "^8.5.0", "tsx": "^4.20.6", "tweetnacl": "^1.0.3", - "tiny-secp256k1": "^2.2.4", "typescript": "^5.9.2", "vite": "^7.1.7", "vite-plugin-dts": "^4.5.4", diff --git a/packages/sdk/src/__test__/e2e/api.test.ts b/packages/sdk/src/__test__/e2e/api.test.ts index a7cc353b..fbcd97bb 100644 --- a/packages/sdk/src/__test__/e2e/api.test.ts +++ b/packages/sdk/src/__test__/e2e/api.test.ts @@ -1,364 +1,300 @@ vi.mock('../../functions/fetchDecoder.ts', () => ({ - fetchDecoder: vi.fn().mockResolvedValue(undefined), -})); + fetchDecoder: vi.fn().mockResolvedValue(undefined), +})) vi.mock('../../util', async () => { - const actual = - await vi.importActual('../../util'); - return { - ...actual, - fetchCalldataDecoder: vi.fn().mockResolvedValue({ - def: Buffer.alloc(0), - abi: [ - { - name: 'mockFunction', - type: 'function', - inputs: [], - }, - ], - }), - }; -}); - -import { RLP } from '@ethereumjs/rlp'; + const actual = await vi.importActual('../../util') + return { + ...actual, + fetchCalldataDecoder: vi.fn().mockResolvedValue({ + def: Buffer.alloc(0), + abi: [ + { + name: 'mockFunction', + type: 'function', + inputs: [], + }, + ], + }), + } +}) + +import { RLP } from '@ethereumjs/rlp' import { - fetchActiveWallets, - fetchAddress, - fetchAddresses, - fetchAddressesByDerivationPath, - fetchBip44ChangeAddresses, - fetchBtcLegacyAddresses, - fetchBtcSegwitAddresses, - fetchSolanaAddresses, - signBtcLegacyTx, - signBtcSegwitTx, - signBtcWrappedSegwitTx, - signMessage, -} from '../../api'; -import { - addAddressTags, - fetchAddressTags, - fetchLedgerLiveAddresses, - removeAddressTags, - sign, - signSolanaTx, -} from '../../api/index'; -import { HARDENED_OFFSET } from '../../constants'; -import { buildRandomMsg } from '../utils/builders'; -import { BTC_PURPOSE_P2SH_P2WPKH, BTC_TESTNET_COIN } from '../utils/helpers'; -import { setupClient } from '../utils/setup'; -import { getClient } from './../../api/utilities'; -import { dexlabProgram } from './signing/solana/__mocks__/programs'; + fetchActiveWallets, + fetchAddress, + fetchAddresses, + fetchAddressesByDerivationPath, + fetchBip44ChangeAddresses, + fetchBtcLegacyAddresses, + fetchBtcSegwitAddresses, + fetchSolanaAddresses, + signBtcLegacyTx, + signBtcSegwitTx, + signBtcWrappedSegwitTx, + signMessage, +} from '../../api' +import { addAddressTags, fetchAddressTags, fetchLedgerLiveAddresses, removeAddressTags, sign, signSolanaTx } from '../../api/index' +import { HARDENED_OFFSET } from '../../constants' +import { buildRandomMsg } from '../utils/builders' +import { BTC_PURPOSE_P2SH_P2WPKH, BTC_TESTNET_COIN } from '../utils/helpers' +import { setupClient } from '../utils/setup' +import { getClient } from './../../api/utilities' +import { dexlabProgram } from './signing/solana/__mocks__/programs' describe('API', () => { - beforeAll(async () => { - await setupClient(); - }); - - describe('signing', () => { - describe('bitcoin', () => { - const btcTxData = { - prevOuts: [ - { - txHash: - '6e78493091f80d89a92ae3152df7fbfbdc44df09cf01a9b76c5113c02eaf2e0f', - value: 10000, - index: 1, - signerPath: [ - BTC_PURPOSE_P2SH_P2WPKH, - BTC_TESTNET_COIN, - HARDENED_OFFSET, - 0, - 0, - ], - }, - ], - recipient: 'mhifA1DwiMPHTjSJM8FFSL8ibrzWaBCkVT', - value: 1000, - fee: 1000, - changePath: [ - BTC_PURPOSE_P2SH_P2WPKH, - BTC_TESTNET_COIN, - HARDENED_OFFSET, - 1, - 0, - ], - }; - test('legacy', async () => { - await signBtcLegacyTx(btcTxData); - }); - - test('segwit', async () => { - await signBtcSegwitTx(btcTxData); - }); - - test('wrapped segwit', async () => { - await signBtcWrappedSegwitTx(btcTxData); - }); - }); - - describe('ethereum', () => { - describe('messages', () => { - test('signPersonal', async () => { - await signMessage('test message'); - }); - - test('eip712', async () => { - const client = await getClient(); - await signMessage(buildRandomMsg('eip712', client)); - }); - }); - - describe('transactions', () => { - const txData = { - type: 'eip2930', - chainId: 1, - nonce: 0, - gas: 50000n, - to: '0x7a250d5630b4cf539739df2c5dacb4c659f2488d', - value: 1000000000000n, - data: '0x38ed17390000000000000000000000000000000000000000000c1c173c5b782a5b154ab900000000000000000000000000000000000000000000000f380d77022fe8c32600000000000000000000000000000000000000000000000000000000000000a00000000000000000000000007ae7684581f0298241c3d6a6567a48d56b42b15c00000000000000000000000000000000000000000000000000000000622f8d27000000000000000000000000000000000000000000000000000000000000000300000000000000000000000095ad61b0a150d79219dcf64e1e6cc01f0b64c4ce000000000000000000000000c02aaa39b223fe8d0a0e5c4f27ead9083c756cc200000000000000000000000050522c769e01eb06c02bd299066509d8f97a69ae', - gasPrice: 1200000000n, - } as const; - - test('generic', async () => { - await sign(txData); - }); - - test('legacy', async () => { - const toHex = (v: bigint | number) => - typeof v === 'bigint' ? `0x${v.toString(16)}` : v; - const rawTx = RLP.encode([ - txData.nonce, - toHex(txData.gasPrice), - toHex(txData.gas), - txData.to, - toHex(txData.value), - txData.data, - ]); - await sign(rawTx); - }); - }); - }); - - describe('solana', () => { - test('sign solana', async () => { - await signSolanaTx(dexlabProgram); - }); - }); - }); - - describe('address tags', () => { - beforeAll(async () => { - try { - await Promise.race([ - fetchAddressTags({ n: 1 }), - new Promise((_, reject) => - setTimeout( - () => reject(new Error('Address tag RPC timed out')), - 5000, - ), - ), - ]); - } catch (err) { - console.warn( - 'Skipping address tag tests due to connectivity issue:', - (err as Error).message, - ); - } - }); - - it('addAddressTags', async () => { - const key = `tag-${Date.now()}`; - await addAddressTags([{ [key]: 'test' }]); - const addressTags = await fetchAddressTags(); - expect(addressTags.some((tag) => tag.key === key)).toBeTruthy(); - const tagsToRemove = addressTags.filter((tag) => tag.key === key); - if (tagsToRemove.length) { - await removeAddressTags(tagsToRemove); - } - }); - - it('fetchAddressTags', async () => { - const key = `fetch-tag-${Date.now()}`; - await addAddressTags([{ [key]: 'value' }]); - const addressTags = await fetchAddressTags(); - expect(addressTags.some((tag) => tag.key === key)).toBeTruthy(); - const tagsToRemove = addressTags.filter((tag) => tag.key === key); - if (tagsToRemove.length) { - await removeAddressTags(tagsToRemove); - } - }); - - it('removeAddressTags', async () => { - const key = `remove-tag-${Date.now()}`; - await addAddressTags([{ [key]: 'value' }]); - const addressTags = await fetchAddressTags(); - const tagsToRemove = addressTags.filter((tag) => tag.key === key); - expect(tagsToRemove).not.toHaveLength(0); - await removeAddressTags(tagsToRemove); - const remainingTags = await fetchAddressTags(); - expect(remainingTags.some((tag) => tag.key === key)).toBeFalsy(); - }); - }); - - describe('addresses', () => { - describe('fetchAddresses', () => { - test('fetchAddresses', async () => { - const addresses = await fetchAddresses(); - expect(addresses).toHaveLength(10); - }); - - test('fetchAddresses[1]', async () => { - const addresses = await fetchAddresses({ n: 1 }); - expect(addresses).toHaveLength(1); - }); - - test('fetchAddresses[12]', async () => { - const addresses = await fetchAddresses({ n: 12 }); - expect(addresses).toHaveLength(12); - }); - - test('fetchBtcLegacyAddresses', async () => { - const addresses = await fetchBtcLegacyAddresses(); - expect(addresses).toHaveLength(10); - }); - - test('fetchBtcSegwitAddresses[12]', async () => { - const addresses = await fetchBtcSegwitAddresses({ n: 12 }); - expect(addresses).toHaveLength(12); - }); - - test('fetchLedgerLiveAddresses', async () => { - const addresses = await fetchLedgerLiveAddresses(); - expect(addresses).toHaveLength(10); - }); - - test('fetchSolanaAddresses', async () => { - const addresses = await fetchSolanaAddresses(); - expect(addresses).toHaveLength(10); - }); - - test('fetchBip44ChangeAddresses', async () => { - const addresses = await fetchBip44ChangeAddresses(); - expect(addresses).toHaveLength(10); - }); - }); - - describe('fetchAddressesByDerivationPath', () => { - test('fetch single specific address', async () => { - const addresses = - await fetchAddressesByDerivationPath("44'/60'/0'/0/0"); - expect(addresses).toHaveLength(1); - expect(addresses[0]).toBeTruthy(); - }); - - test('fetch multiple addresses with wildcard', async () => { - const addresses = await fetchAddressesByDerivationPath( - "44'/60'/0'/0/X", - { - n: 5, - }, - ); - expect(addresses).toHaveLength(5); - addresses.forEach((address) => { - expect(address).toBeTruthy(); - }); - }); - - test('fetch addresses with offset', async () => { - const addresses = await fetchAddressesByDerivationPath( - "44'/60'/0'/0/X", - { - n: 3, - startPathIndex: 10, - }, - ); - expect(addresses).toHaveLength(3); - addresses.forEach((address) => { - expect(address).toBeTruthy(); - }); - }); - - test('fetch addresses with lowercase x wildcard', async () => { - const addresses = await fetchAddressesByDerivationPath( - "44'/60'/0'/0/x", - { - n: 2, - }, - ); - expect(addresses).toHaveLength(2); - addresses.forEach((address) => { - expect(address).toBeTruthy(); - }); - }); - - test('fetch addresses with wildcard in middle of path', async () => { - const addresses = await fetchAddressesByDerivationPath( - "44'/60'/X'/0/0", - { - n: 3, - }, - ); - expect(addresses).toHaveLength(3); - addresses.forEach((address) => { - expect(address).toBeTruthy(); - }); - }); - - test('fetch solana addresses with wildcard in middle of path', async () => { - const addresses = await fetchAddressesByDerivationPath( - "44'/501'/X'/0'", - { - n: 1, - }, - ); - expect(addresses).toHaveLength(1); - addresses.forEach((address) => { - expect(address).toBeTruthy(); - }); - }); - - test('error on invalid derivation path', async () => { - await expect( - fetchAddressesByDerivationPath('invalid/path'), - ).rejects.toThrow(); - }); - - test('fetch single address when n=1 with wildcard', async () => { - const addresses = await fetchAddressesByDerivationPath( - "44'/60'/0'/0/X", - { - n: 1, - }, - ); - expect(addresses).toHaveLength(1); - expect(addresses[0]).toBeTruthy(); - }); - - test('fetch no addresses when n=0', async () => { - const addresses = await fetchAddressesByDerivationPath( - "44'/60'/0'/0/X", - { - n: 0, - }, - ); - expect(addresses).toHaveLength(0); - }); - }); - - describe('fetchAddress', () => { - test('fetchAddress', async () => { - const address = await fetchAddress(); - expect(address).toBeTruthy(); - }); - }); - }); - - describe('fetchActiveWallets', () => { - test('fetchActiveWallets', async () => { - const wallet = await fetchActiveWallets(); - expect(wallet).toBeTruthy(); - }); - }); -}); + beforeAll(async () => { + await setupClient() + }) + + describe('signing', () => { + describe('bitcoin', () => { + const btcTxData = { + prevOuts: [ + { + txHash: '6e78493091f80d89a92ae3152df7fbfbdc44df09cf01a9b76c5113c02eaf2e0f', + value: 10000, + index: 1, + signerPath: [BTC_PURPOSE_P2SH_P2WPKH, BTC_TESTNET_COIN, HARDENED_OFFSET, 0, 0], + }, + ], + recipient: 'mhifA1DwiMPHTjSJM8FFSL8ibrzWaBCkVT', + value: 1000, + fee: 1000, + changePath: [BTC_PURPOSE_P2SH_P2WPKH, BTC_TESTNET_COIN, HARDENED_OFFSET, 1, 0], + } + test('legacy', async () => { + await signBtcLegacyTx(btcTxData) + }) + + test('segwit', async () => { + await signBtcSegwitTx(btcTxData) + }) + + test('wrapped segwit', async () => { + await signBtcWrappedSegwitTx(btcTxData) + }) + }) + + describe('ethereum', () => { + describe('messages', () => { + test('signPersonal', async () => { + await signMessage('test message') + }) + + test('eip712', async () => { + const client = await getClient() + await signMessage(buildRandomMsg('eip712', client)) + }) + }) + + describe('transactions', () => { + const txData = { + type: 'eip2930', + chainId: 1, + nonce: 0, + gas: 50000n, + to: '0x7a250d5630b4cf539739df2c5dacb4c659f2488d', + value: 1000000000000n, + data: '0x38ed17390000000000000000000000000000000000000000000c1c173c5b782a5b154ab900000000000000000000000000000000000000000000000f380d77022fe8c32600000000000000000000000000000000000000000000000000000000000000a00000000000000000000000007ae7684581f0298241c3d6a6567a48d56b42b15c00000000000000000000000000000000000000000000000000000000622f8d27000000000000000000000000000000000000000000000000000000000000000300000000000000000000000095ad61b0a150d79219dcf64e1e6cc01f0b64c4ce000000000000000000000000c02aaa39b223fe8d0a0e5c4f27ead9083c756cc200000000000000000000000050522c769e01eb06c02bd299066509d8f97a69ae', + gasPrice: 1200000000n, + } as const + + test('generic', async () => { + await sign(txData) + }) + + test('legacy', async () => { + const toHex = (v: bigint | number) => (typeof v === 'bigint' ? `0x${v.toString(16)}` : v) + const rawTx = RLP.encode([txData.nonce, toHex(txData.gasPrice), toHex(txData.gas), txData.to, toHex(txData.value), txData.data]) + await sign(rawTx) + }) + }) + }) + + describe('solana', () => { + test('sign solana', async () => { + await signSolanaTx(dexlabProgram) + }) + }) + }) + + describe('address tags', () => { + beforeAll(async () => { + try { + await Promise.race([fetchAddressTags({ n: 1 }), new Promise((_, reject) => setTimeout(() => reject(new Error('Address tag RPC timed out')), 5000))]) + } catch (err) { + console.warn('Skipping address tag tests due to connectivity issue:', (err as Error).message) + } + }) + + it('addAddressTags', async () => { + const key = `tag-${Date.now()}` + await addAddressTags([{ [key]: 'test' }]) + const addressTags = await fetchAddressTags() + expect(addressTags.some((tag) => tag.key === key)).toBeTruthy() + const tagsToRemove = addressTags.filter((tag) => tag.key === key) + if (tagsToRemove.length) { + await removeAddressTags(tagsToRemove) + } + }) + + it('fetchAddressTags', async () => { + const key = `fetch-tag-${Date.now()}` + await addAddressTags([{ [key]: 'value' }]) + const addressTags = await fetchAddressTags() + expect(addressTags.some((tag) => tag.key === key)).toBeTruthy() + const tagsToRemove = addressTags.filter((tag) => tag.key === key) + if (tagsToRemove.length) { + await removeAddressTags(tagsToRemove) + } + }) + + it('removeAddressTags', async () => { + const key = `remove-tag-${Date.now()}` + await addAddressTags([{ [key]: 'value' }]) + const addressTags = await fetchAddressTags() + const tagsToRemove = addressTags.filter((tag) => tag.key === key) + expect(tagsToRemove).not.toHaveLength(0) + await removeAddressTags(tagsToRemove) + const remainingTags = await fetchAddressTags() + expect(remainingTags.some((tag) => tag.key === key)).toBeFalsy() + }) + }) + + describe('addresses', () => { + describe('fetchAddresses', () => { + test('fetchAddresses', async () => { + const addresses = await fetchAddresses() + expect(addresses).toHaveLength(10) + }) + + test('fetchAddresses[1]', async () => { + const addresses = await fetchAddresses({ n: 1 }) + expect(addresses).toHaveLength(1) + }) + + test('fetchAddresses[12]', async () => { + const addresses = await fetchAddresses({ n: 12 }) + expect(addresses).toHaveLength(12) + }) + + test('fetchBtcLegacyAddresses', async () => { + const addresses = await fetchBtcLegacyAddresses() + expect(addresses).toHaveLength(10) + }) + + test('fetchBtcSegwitAddresses[12]', async () => { + const addresses = await fetchBtcSegwitAddresses({ n: 12 }) + expect(addresses).toHaveLength(12) + }) + + test('fetchLedgerLiveAddresses', async () => { + const addresses = await fetchLedgerLiveAddresses() + expect(addresses).toHaveLength(10) + }) + + test('fetchSolanaAddresses', async () => { + const addresses = await fetchSolanaAddresses() + expect(addresses).toHaveLength(10) + }) + + test('fetchBip44ChangeAddresses', async () => { + const addresses = await fetchBip44ChangeAddresses() + expect(addresses).toHaveLength(10) + }) + }) + + describe('fetchAddressesByDerivationPath', () => { + test('fetch single specific address', async () => { + const addresses = await fetchAddressesByDerivationPath("44'/60'/0'/0/0") + expect(addresses).toHaveLength(1) + expect(addresses[0]).toBeTruthy() + }) + + test('fetch multiple addresses with wildcard', async () => { + const addresses = await fetchAddressesByDerivationPath("44'/60'/0'/0/X", { + n: 5, + }) + expect(addresses).toHaveLength(5) + addresses.forEach((address) => { + expect(address).toBeTruthy() + }) + }) + + test('fetch addresses with offset', async () => { + const addresses = await fetchAddressesByDerivationPath("44'/60'/0'/0/X", { + n: 3, + startPathIndex: 10, + }) + expect(addresses).toHaveLength(3) + addresses.forEach((address) => { + expect(address).toBeTruthy() + }) + }) + + test('fetch addresses with lowercase x wildcard', async () => { + const addresses = await fetchAddressesByDerivationPath("44'/60'/0'/0/x", { + n: 2, + }) + expect(addresses).toHaveLength(2) + addresses.forEach((address) => { + expect(address).toBeTruthy() + }) + }) + + test('fetch addresses with wildcard in middle of path', async () => { + const addresses = await fetchAddressesByDerivationPath("44'/60'/X'/0/0", { + n: 3, + }) + expect(addresses).toHaveLength(3) + addresses.forEach((address) => { + expect(address).toBeTruthy() + }) + }) + + test('fetch solana addresses with wildcard in middle of path', async () => { + const addresses = await fetchAddressesByDerivationPath("44'/501'/X'/0'", { + n: 1, + }) + expect(addresses).toHaveLength(1) + addresses.forEach((address) => { + expect(address).toBeTruthy() + }) + }) + + test('error on invalid derivation path', async () => { + await expect(fetchAddressesByDerivationPath('invalid/path')).rejects.toThrow() + }) + + test('fetch single address when n=1 with wildcard', async () => { + const addresses = await fetchAddressesByDerivationPath("44'/60'/0'/0/X", { + n: 1, + }) + expect(addresses).toHaveLength(1) + expect(addresses[0]).toBeTruthy() + }) + + test('fetch no addresses when n=0', async () => { + const addresses = await fetchAddressesByDerivationPath("44'/60'/0'/0/X", { + n: 0, + }) + expect(addresses).toHaveLength(0) + }) + }) + + describe('fetchAddress', () => { + test('fetchAddress', async () => { + const address = await fetchAddress() + expect(address).toBeTruthy() + }) + }) + }) + + describe('fetchActiveWallets', () => { + test('fetchActiveWallets', async () => { + const wallet = await fetchActiveWallets() + expect(wallet).toBeTruthy() + }) + }) +}) diff --git a/packages/sdk/src/__test__/e2e/btc.test.ts b/packages/sdk/src/__test__/e2e/btc.test.ts index 4eb6905e..6c5ab067 100644 --- a/packages/sdk/src/__test__/e2e/btc.test.ts +++ b/packages/sdk/src/__test__/e2e/btc.test.ts @@ -12,191 +12,174 @@ * incorrect address derivations and signature mismatches. */ -import BIP32Factory, { type BIP32Interface } from 'bip32'; -import * as ecc from 'tiny-secp256k1'; -import type { Client } from '../../client'; -import { getPrng, getTestnet } from '../utils/getters'; -import { - BTC_PURPOSE_P2PKH, - BTC_PURPOSE_P2SH_P2WPKH, - BTC_PURPOSE_P2WPKH, - setup_btc_sig_test, - stripDER, -} from '../utils/helpers'; -import { setupClient } from '../utils/setup'; -import { TEST_SEED } from '../utils/testConstants'; - -const prng = getPrng(); -const bip32 = BIP32Factory(ecc); -const TEST_TESTNET = !!getTestnet() || false; -let wallet: BIP32Interface | null = null; -type InputObj = { hash: string; value: number; signerIdx: number; idx: number }; +import BIP32Factory, { type BIP32Interface } from 'bip32' +import * as ecc from 'tiny-secp256k1' +import type { Client } from '../../client' +import { getPrng, getTestnet } from '../utils/getters' +import { BTC_PURPOSE_P2PKH, BTC_PURPOSE_P2SH_P2WPKH, BTC_PURPOSE_P2WPKH, setup_btc_sig_test, stripDER } from '../utils/helpers' +import { setupClient } from '../utils/setup' +import { TEST_SEED } from '../utils/testConstants' + +const prng = getPrng() +const bip32 = BIP32Factory(ecc) +const TEST_TESTNET = !!getTestnet() || false +let wallet: BIP32Interface | null = null +type InputObj = { hash: string; value: number; signerIdx: number; idx: number } // Build the inputs. By default we will build 10. Note that there are `n` tests for // *each category*, where `n` is the number of inputs. function rand32Bit() { - return Math.floor(prng.quick() * 2 ** 32); + return Math.floor(prng.quick() * 2 ** 32) } -const inputs: InputObj[] = []; -const count = 10; +const inputs: InputObj[] = [] +const count = 10 for (let i = 0; i < count; i++) { - const hash = Buffer.alloc(32); - for (let j = 0; j < 8; j++) { - // 32 bits of randomness per call - hash.writeUInt32BE(rand32Bit(), j * 4); - } - const value = Math.floor(rand32Bit()); - const signerIdx = Math.floor(prng.quick() * 19); // Random signer (keep it inside initial cache of 20) - const idx = Math.floor(prng.quick() * 25); // Random previous output index (keep it small) - inputs.push({ hash: hash.toString('hex'), value, signerIdx, idx }); + const hash = Buffer.alloc(32) + for (let j = 0; j < 8; j++) { + // 32 bits of randomness per call + hash.writeUInt32BE(rand32Bit(), j * 4) + } + const value = Math.floor(rand32Bit()) + const signerIdx = Math.floor(prng.quick() * 19) // Random signer (keep it inside initial cache of 20) + const idx = Math.floor(prng.quick() * 25) // Random previous output index (keep it small) + inputs.push({ hash: hash.toString('hex'), value, signerIdx, idx }) } async function testSign({ txReq, signingKeys, sigHashes, client }: any) { - const tx = await client.sign(txReq); - const len = tx?.sigs?.length ?? 0; - expect(len).toEqual(signingKeys.length); - expect(len).toEqual(sigHashes.length); - for (let i = 0; i < len; i++) { - const sig = stripDER(tx.sigs?.[i]); - const verification = signingKeys[i].verify(sigHashes[i], sig); - expect(verification).toEqualElseLog( - true, - `Signature validation failed for priv=${signingKeys[ - i - ].privateKey.toString('hex')}, ` + - `hash=${sigHashes[i].toString('hex')}, sig=${sig.toString('hex')}`, - ); - } + const tx = await client.sign(txReq) + const len = tx?.sigs?.length ?? 0 + expect(len).toEqual(signingKeys.length) + expect(len).toEqual(sigHashes.length) + for (let i = 0; i < len; i++) { + const sig = stripDER(tx.sigs?.[i]) + const verification = signingKeys[i].verify(sigHashes[i], sig) + expect(verification).toEqualElseLog(true, `Signature validation failed for priv=${signingKeys[i].privateKey.toString('hex')}, ` + `hash=${sigHashes[i].toString('hex')}, sig=${sig.toString('hex')}`) + } } -async function runTestSet( - opts: any, - wallet: BIP32Interface | null, - inputsSlice: InputObj[], - client, -) { - expect(wallet).not.toEqualElseLog(null, 'Wallet not available'); - if (TEST_TESTNET) { - // Testnet + change - opts.isTestnet = true; - opts.useChange = true; - await testSign({ - ...setup_btc_sig_test(opts, wallet, inputsSlice, prng), - client, - }); - // Testnet + no change - opts.isTestnet = true; - opts.useChange = false; - await testSign({ - ...setup_btc_sig_test(opts, wallet, inputsSlice, prng), - client, - }); - } - // Mainnet + change - opts.isTestnet = false; - opts.useChange = true; - await testSign({ - ...setup_btc_sig_test(opts, wallet, inputsSlice, prng), - client, - }); - // Mainnet + no change - opts.isTestnet = false; - opts.useChange = false; - await testSign({ - ...setup_btc_sig_test(opts, wallet, inputsSlice, prng), - client, - }); +async function runTestSet(opts: any, wallet: BIP32Interface | null, inputsSlice: InputObj[], client) { + expect(wallet).not.toEqualElseLog(null, 'Wallet not available') + if (TEST_TESTNET) { + // Testnet + change + opts.isTestnet = true + opts.useChange = true + await testSign({ + ...setup_btc_sig_test(opts, wallet, inputsSlice, prng), + client, + }) + // Testnet + no change + opts.isTestnet = true + opts.useChange = false + await testSign({ + ...setup_btc_sig_test(opts, wallet, inputsSlice, prng), + client, + }) + } + // Mainnet + change + opts.isTestnet = false + opts.useChange = true + await testSign({ + ...setup_btc_sig_test(opts, wallet, inputsSlice, prng), + client, + }) + // Mainnet + no change + opts.isTestnet = false + opts.useChange = false + await testSign({ + ...setup_btc_sig_test(opts, wallet, inputsSlice, prng), + client, + }) } describe('Bitcoin', () => { - let client: Client; - - beforeAll(async () => { - client = await setupClient(); - wallet = bip32.fromSeed(TEST_SEED); - }); - - for (let i = 0; i < inputs.length; i++) { - const inputsSlice = inputs.slice(0, i + 1); - - describe(`Input Set ${i}`, () => { - describe('segwit spender (p2wpkh)', () => { - it('p2wpkh->p2pkh', async () => { - const opts = { - spenderPurpose: BTC_PURPOSE_P2WPKH, - recipientPurpose: BTC_PURPOSE_P2PKH, - }; - await runTestSet(opts, wallet, inputsSlice, client); - }); - - it('p2wpkh->p2sh-p2wpkh', async () => { - const opts = { - spenderPurpose: BTC_PURPOSE_P2WPKH, - recipientPurpose: BTC_PURPOSE_P2SH_P2WPKH, - }; - await runTestSet(opts, wallet, inputsSlice, client); - }); - - it('p2wpkh->p2wpkh', async () => { - const opts = { - spenderPurpose: BTC_PURPOSE_P2WPKH, - recipientPurpose: BTC_PURPOSE_P2WPKH, - }; - await runTestSet(opts, wallet, inputsSlice, client); - }); - }); - - describe('wrapped segwit spender (p2sh-p2wpkh)', () => { - it('p2sh-p2wpkh->p2pkh', async () => { - const opts = { - spenderPurpose: BTC_PURPOSE_P2SH_P2WPKH, - recipientPurpose: BTC_PURPOSE_P2PKH, - }; - await runTestSet(opts, wallet, inputsSlice, client); - }); - - it('p2sh-p2wpkh->p2sh-p2wpkh', async () => { - const opts = { - spenderPurpose: BTC_PURPOSE_P2SH_P2WPKH, - recipientPurpose: BTC_PURPOSE_P2SH_P2WPKH, - }; - await runTestSet(opts, wallet, inputsSlice, client); - }); - - it('p2sh-p2wpkh->p2wpkh', async () => { - const opts = { - spenderPurpose: BTC_PURPOSE_P2SH_P2WPKH, - recipientPurpose: BTC_PURPOSE_P2WPKH, - }; - await runTestSet(opts, wallet, inputsSlice, client); - }); - }); - - describe('legacy spender (p2pkh)', () => { - it('p2pkh->p2pkh', async () => { - const opts = { - spenderPurpose: BTC_PURPOSE_P2PKH, - recipientPurpose: BTC_PURPOSE_P2PKH, - }; - await runTestSet(opts, wallet, inputsSlice, client); - }); - - it('p2pkh->p2sh-p2wpkh', async () => { - const opts = { - spenderPurpose: BTC_PURPOSE_P2PKH, - recipientPurpose: BTC_PURPOSE_P2SH_P2WPKH, - }; - await runTestSet(opts, wallet, inputsSlice, client); - }); - - it('p2pkh->p2wpkh', async () => { - const opts = { - spenderPurpose: BTC_PURPOSE_P2PKH, - recipientPurpose: BTC_PURPOSE_P2WPKH, - }; - await runTestSet(opts, wallet, inputsSlice, client); - }); - }); - }); - } -}); + let client: Client + + beforeAll(async () => { + client = await setupClient() + wallet = bip32.fromSeed(TEST_SEED) + }) + + for (let i = 0; i < inputs.length; i++) { + const inputsSlice = inputs.slice(0, i + 1) + + describe(`Input Set ${i}`, () => { + describe('segwit spender (p2wpkh)', () => { + it('p2wpkh->p2pkh', async () => { + const opts = { + spenderPurpose: BTC_PURPOSE_P2WPKH, + recipientPurpose: BTC_PURPOSE_P2PKH, + } + await runTestSet(opts, wallet, inputsSlice, client) + }) + + it('p2wpkh->p2sh-p2wpkh', async () => { + const opts = { + spenderPurpose: BTC_PURPOSE_P2WPKH, + recipientPurpose: BTC_PURPOSE_P2SH_P2WPKH, + } + await runTestSet(opts, wallet, inputsSlice, client) + }) + + it('p2wpkh->p2wpkh', async () => { + const opts = { + spenderPurpose: BTC_PURPOSE_P2WPKH, + recipientPurpose: BTC_PURPOSE_P2WPKH, + } + await runTestSet(opts, wallet, inputsSlice, client) + }) + }) + + describe('wrapped segwit spender (p2sh-p2wpkh)', () => { + it('p2sh-p2wpkh->p2pkh', async () => { + const opts = { + spenderPurpose: BTC_PURPOSE_P2SH_P2WPKH, + recipientPurpose: BTC_PURPOSE_P2PKH, + } + await runTestSet(opts, wallet, inputsSlice, client) + }) + + it('p2sh-p2wpkh->p2sh-p2wpkh', async () => { + const opts = { + spenderPurpose: BTC_PURPOSE_P2SH_P2WPKH, + recipientPurpose: BTC_PURPOSE_P2SH_P2WPKH, + } + await runTestSet(opts, wallet, inputsSlice, client) + }) + + it('p2sh-p2wpkh->p2wpkh', async () => { + const opts = { + spenderPurpose: BTC_PURPOSE_P2SH_P2WPKH, + recipientPurpose: BTC_PURPOSE_P2WPKH, + } + await runTestSet(opts, wallet, inputsSlice, client) + }) + }) + + describe('legacy spender (p2pkh)', () => { + it('p2pkh->p2pkh', async () => { + const opts = { + spenderPurpose: BTC_PURPOSE_P2PKH, + recipientPurpose: BTC_PURPOSE_P2PKH, + } + await runTestSet(opts, wallet, inputsSlice, client) + }) + + it('p2pkh->p2sh-p2wpkh', async () => { + const opts = { + spenderPurpose: BTC_PURPOSE_P2PKH, + recipientPurpose: BTC_PURPOSE_P2SH_P2WPKH, + } + await runTestSet(opts, wallet, inputsSlice, client) + }) + + it('p2pkh->p2wpkh', async () => { + const opts = { + spenderPurpose: BTC_PURPOSE_P2PKH, + recipientPurpose: BTC_PURPOSE_P2WPKH, + } + await runTestSet(opts, wallet, inputsSlice, client) + }) + }) + }) + } +}) diff --git a/packages/sdk/src/__test__/e2e/eth.msg.test.ts b/packages/sdk/src/__test__/e2e/eth.msg.test.ts index d19ac1e8..aee44e66 100644 --- a/packages/sdk/src/__test__/e2e/eth.msg.test.ts +++ b/packages/sdk/src/__test__/e2e/eth.msg.test.ts @@ -16,1356 +16,1303 @@ * CMakeLists.txt file (for dev units) */ -import { HARDENED_OFFSET } from '../../constants'; -import type { SigningPath } from '../../types'; -import { randomBytes } from '../../util'; -import { buildEthMsgReq, buildRandomMsg } from '../utils/builders'; -import { runEthMsg } from '../utils/runners'; -import { setupClient } from '../utils/setup'; +import { HARDENED_OFFSET } from '../../constants' +import type { SigningPath } from '../../types' +import { randomBytes } from '../../util' +import { buildEthMsgReq, buildRandomMsg } from '../utils/builders' +import { runEthMsg } from '../utils/runners' +import { setupClient } from '../utils/setup' +import type { Client } from '../../client' describe('ETH Messages', () => { - let client; + let client: Client - beforeAll(async () => { - client = await setupClient(); - }); + beforeAll(async () => { + client = await setupClient() + }) - describe('Test ETH personalSign', () => { - it('Should throw error when message contains non-ASCII characters', async () => { - const protocol = 'signPersonal'; - const msg = '⚠️'; - const msg2 = 'ASCII plus ⚠️'; - await expect(client.sign(buildEthMsgReq(msg, protocol))).rejects.toThrow( - /Lattice can only display ASCII/, - ); - await expect(client.sign(buildEthMsgReq(msg2, protocol))).rejects.toThrow( - /Lattice can only display ASCII/, - ); - }); + describe('Test ETH personalSign', () => { + it('Should throw error when message contains non-ASCII characters', async () => { + const protocol = 'signPersonal' + const msg = '⚠️' + const msg2 = 'ASCII plus ⚠️' + await expect(client.sign(buildEthMsgReq(msg, protocol))).rejects.toThrow(/Lattice can only display ASCII/) + await expect(client.sign(buildEthMsgReq(msg2, protocol))).rejects.toThrow(/Lattice can only display ASCII/) + }) - it('Should test ASCII buffers', async () => { - await runEthMsg( - buildEthMsgReq(Buffer.from('i am an ascii buffer'), 'signPersonal'), - client, - ); - await runEthMsg( - buildEthMsgReq(Buffer.from('{\n\ttest: foo\n}'), 'signPersonal'), - client, - ); - }); + it('Should test ASCII buffers', async () => { + await runEthMsg(buildEthMsgReq(Buffer.from('i am an ascii buffer'), 'signPersonal'), client) + await runEthMsg(buildEthMsgReq(Buffer.from('{\n\ttest: foo\n}'), 'signPersonal'), client) + }) - it('Should test hex buffers', async () => { - await runEthMsg( - buildEthMsgReq(Buffer.from('abcdef', 'hex'), 'signPersonal'), - client, - ); - }); + it('Should test hex buffers', async () => { + await runEthMsg(buildEthMsgReq(Buffer.from('abcdef', 'hex'), 'signPersonal'), client) + }) - it('Should test a message that needs to be prehashed', async () => { - await runEthMsg( - buildEthMsgReq(randomBytes(4000), 'signPersonal'), - client, - ); - }); + it('Should test a message that needs to be prehashed', async () => { + await runEthMsg(buildEthMsgReq(randomBytes(4000), 'signPersonal'), client) + }) - it('Msg: sign_personal boundary conditions and auto-rejected requests', async () => { - const protocol = 'signPersonal'; - const fwConstants = client.getFwConstants(); - // `personal_sign` requests have a max size smaller than other requests because a header - // is displayed in the text region of the screen. The size of this is captured - // by `fwConstants.personalSignHeaderSz`. - const maxMsgSz = - fwConstants.ethMaxMsgSz + - fwConstants.personalSignHeaderSz + - fwConstants.extraDataMaxFrames * fwConstants.extraDataFrameSz; - const maxValid = `0x${randomBytes(maxMsgSz).toString('hex')}`; - const minInvalid = `0x${randomBytes(maxMsgSz + 1).toString('hex')}`; - const zeroInvalid = '0x'; - // The largest non-hardened index which will take the most chars to print - const x = HARDENED_OFFSET - 1; - // Okay sooo this is a bit awkward. We have to use a known coin_type here (e.g. ETH) - // or else firmware will return an error, but the maxSz is based on the max length - // of a path, which is larger than we can actually print. - // I guess all this tests is that the first one is shown in plaintext while the second - // one (which is too large) gets prehashed. - const largeSignPath = [x, HARDENED_OFFSET + 60, x, x, x] as SigningPath; - await runEthMsg( - buildEthMsgReq(maxValid, protocol, largeSignPath), - client, - ); - await runEthMsg( - buildEthMsgReq(minInvalid, protocol, largeSignPath), - client, - ); - // Using a zero length payload should auto-reject - await expect( - client.sign(buildEthMsgReq(zeroInvalid, protocol)), - ).rejects.toThrow(/Invalid Request/); - }); + it('Msg: sign_personal boundary conditions and auto-rejected requests', async () => { + const protocol = 'signPersonal' + const fwConstants = client.getFwConstants() + // `personal_sign` requests have a max size smaller than other requests because a header + // is displayed in the text region of the screen. The size of this is captured + // by `fwConstants.personalSignHeaderSz`. + const maxMsgSz = fwConstants.ethMaxMsgSz + fwConstants.personalSignHeaderSz + fwConstants.extraDataMaxFrames * fwConstants.extraDataFrameSz + const maxValid = `0x${randomBytes(maxMsgSz).toString('hex')}` + const minInvalid = `0x${randomBytes(maxMsgSz + 1).toString('hex')}` + const zeroInvalid = '0x' + // The largest non-hardened index which will take the most chars to print + const x = HARDENED_OFFSET - 1 + // Okay sooo this is a bit awkward. We have to use a known coin_type here (e.g. ETH) + // or else firmware will return an error, but the maxSz is based on the max length + // of a path, which is larger than we can actually print. + // I guess all this tests is that the first one is shown in plaintext while the second + // one (which is too large) gets prehashed. + const largeSignPath = [x, HARDENED_OFFSET + 60, x, x, x] as SigningPath + await runEthMsg(buildEthMsgReq(maxValid, protocol, largeSignPath), client) + await runEthMsg(buildEthMsgReq(minInvalid, protocol, largeSignPath), client) + // Using a zero length payload should auto-reject + await expect(client.sign(buildEthMsgReq(zeroInvalid, protocol))).rejects.toThrow(/Invalid Request/) + }) - describe(`Test ${5} random payloads`, () => { - for (let i = 0; i < 5; i++) { - it(`Payload: ${i}`, async () => { - await runEthMsg( - buildEthMsgReq( - buildRandomMsg('signPersonal', client), - 'signPersonal', - ), - client, - ); - }); - } - }); - }); + describe(`Test ${5} random payloads`, () => { + for (let i = 0; i < 5; i++) { + it(`Payload: ${i}`, async () => { + await runEthMsg(buildEthMsgReq(buildRandomMsg('signPersonal', client), 'signPersonal'), client) + }) + } + }) + }) - describe('Test ETH EIP712', () => { - it('Should test a message that needs to be prehashed', async () => { - const msg = { - types: { - EIP712Domain: [ - { name: 'name', type: 'string' }, - { name: 'version', type: 'string' }, - { name: 'chainId', type: 'uint256' }, - ], - dYdX: [ - { type: 'string', name: 'action' }, - { type: 'string', name: 'onlySignOn' }, - ], - }, - domain: { - name: 'dYdX', - version: '1.0', - chainId: '1', - }, - primaryType: 'dYdX', - message: { - action: 'dYdX STARK Key', - onlySignOn: randomBytes(4000).toString('hex'), - }, - }; - await runEthMsg(buildEthMsgReq(msg, 'eip712'), client); - }); + describe('Test ETH EIP712', () => { + it('Should test a message that needs to be prehashed', async () => { + const msg = { + types: { + EIP712Domain: [ + { name: 'name', type: 'string' }, + { name: 'version', type: 'string' }, + { name: 'chainId', type: 'uint256' }, + ], + dYdX: [ + { type: 'string', name: 'action' }, + { type: 'string', name: 'onlySignOn' }, + ], + }, + domain: { + name: 'dYdX', + version: '1.0', + chainId: '1', + }, + primaryType: 'dYdX', + message: { + action: 'dYdX STARK Key', + onlySignOn: randomBytes(4000).toString('hex'), + }, + } + await runEthMsg(buildEthMsgReq(msg, 'eip712'), client) + }) - it('Should test an example from Blur NFT w/ 0 fees', async () => { - const msg = { - types: { - EIP712Domain: [ - { name: 'name', type: 'string' }, - { name: 'host', type: 'string' }, - { name: 'version', type: 'string' }, - { name: 'chainId', type: 'uint256' }, - { name: 'verifyingContract', type: 'address' }, - ], - Fee: [ - { name: 'rate', type: 'uint256' }, - { name: 'recipient', type: 'address' }, - ], - Message: [ - { name: 'trader', type: 'address' }, - { name: 'side', type: 'uint256' }, - { name: 'matchingPolicy', type: 'address' }, - { name: 'collection', type: 'address' }, - { name: 'tokenId', type: 'uint256' }, - { name: 'amount', type: 'uint256' }, - { name: 'paymentToken', type: 'address' }, - { name: 'price', type: 'uint256' }, - { name: 'listingTime', type: 'uint256' }, - { name: 'expirationTime', type: 'uint256' }, - { name: 'salt', type: 'uint256' }, - { name: 'extraParams', type: 'bytes' }, - { name: 'nonce', type: 'uint256' }, - { name: 'fees', type: 'Fee[]' }, - ], - }, - domain: { - name: 'Blur', - verifyingContract: '0x0', - version: '1', - chainId: '', - host: '', - }, - primaryType: 'Message', - message: { - trader: '0xfc92dff6d9519c79782e0d345915c441cf5ac41f', - side: '1', - matchingPolicy: '0x00000000006411739da1c40b106f8511de5d1fac', - collection: '0x7a15b36cb834aea88553de69077d3777460d73ac', - tokenId: - '5280336779268220421569573059971679349075200194886069432279714075018412552192', - amount: '1', - paymentToken: '0x0000000000000000000000000000000000000000', - price: '990000000000000000', - listingTime: '1666370346', - expirationTime: '1666975146', - salt: '64535264870076277194623607183653108264', - extraParams: '0x', - nonce: '0', - fees: [ - // { rate: 1, recipient: '0x00000000006411739da1c40b106f8511de5d1fac'} - ], - }, - }; - await runEthMsg(buildEthMsgReq(msg, 'eip712'), client); - }); + it('Should test an example from Blur NFT w/ 0 fees', async () => { + const msg = { + types: { + EIP712Domain: [ + { name: 'name', type: 'string' }, + { name: 'host', type: 'string' }, + { name: 'version', type: 'string' }, + { name: 'chainId', type: 'uint256' }, + { name: 'verifyingContract', type: 'address' }, + ], + Fee: [ + { name: 'rate', type: 'uint256' }, + { name: 'recipient', type: 'address' }, + ], + Message: [ + { name: 'trader', type: 'address' }, + { name: 'side', type: 'uint256' }, + { name: 'matchingPolicy', type: 'address' }, + { name: 'collection', type: 'address' }, + { name: 'tokenId', type: 'uint256' }, + { name: 'amount', type: 'uint256' }, + { name: 'paymentToken', type: 'address' }, + { name: 'price', type: 'uint256' }, + { name: 'listingTime', type: 'uint256' }, + { name: 'expirationTime', type: 'uint256' }, + { name: 'salt', type: 'uint256' }, + { name: 'extraParams', type: 'bytes' }, + { name: 'nonce', type: 'uint256' }, + { name: 'fees', type: 'Fee[]' }, + ], + }, + domain: { + name: 'Blur', + verifyingContract: '0x0', + version: '1', + chainId: '', + host: '', + }, + primaryType: 'Message', + message: { + trader: '0xfc92dff6d9519c79782e0d345915c441cf5ac41f', + side: '1', + matchingPolicy: '0x00000000006411739da1c40b106f8511de5d1fac', + collection: '0x7a15b36cb834aea88553de69077d3777460d73ac', + tokenId: '5280336779268220421569573059971679349075200194886069432279714075018412552192', + amount: '1', + paymentToken: '0x0000000000000000000000000000000000000000', + price: '990000000000000000', + listingTime: '1666370346', + expirationTime: '1666975146', + salt: '64535264870076277194623607183653108264', + extraParams: '0x', + nonce: '0', + fees: [ + // { rate: 1, recipient: '0x00000000006411739da1c40b106f8511de5d1fac'} + ], + }, + } + await runEthMsg(buildEthMsgReq(msg, 'eip712'), client) + }) - it('Should test simple dydx example', async () => { - const msg = { - types: { - EIP712Domain: [ - { name: 'name', type: 'string' }, - { name: 'version', type: 'string' }, - { name: 'chainId', type: 'uint256' }, - ], - dYdX: [ - { type: 'string', name: 'action' }, - { type: 'string', name: 'onlySignOn' }, - ], - }, - domain: { - name: 'dYdX', - version: '1.0', - chainId: '1', - }, - primaryType: 'dYdX', - message: { - action: 'dYdX STARK Key', - onlySignOn: 'https://trade.dydx.exchange', - }, - }; - await runEthMsg(buildEthMsgReq(msg, 'eip712'), client); - }); + it('Should test simple dydx example', async () => { + const msg = { + types: { + EIP712Domain: [ + { name: 'name', type: 'string' }, + { name: 'version', type: 'string' }, + { name: 'chainId', type: 'uint256' }, + ], + dYdX: [ + { type: 'string', name: 'action' }, + { type: 'string', name: 'onlySignOn' }, + ], + }, + domain: { + name: 'dYdX', + version: '1.0', + chainId: '1', + }, + primaryType: 'dYdX', + message: { + action: 'dYdX STARK Key', + onlySignOn: 'https://trade.dydx.exchange', + }, + } + await runEthMsg(buildEthMsgReq(msg, 'eip712'), client) + }) - it('Should test a Loopring message with non-standard numerical type', async () => { - const msg = { - types: { - EIP712Domain: [ - { name: 'name', type: 'string' }, - { name: 'version', type: 'string' }, - { name: 'chainId', type: 'uint256' }, - { name: 'verifyingContract', type: 'address' }, - ], - AccountUpdate: [ - { name: 'owner', type: 'address' }, - { name: 'accountID', type: 'uint32' }, - { name: 'feeTokenID', type: 'uint16' }, - { name: 'maxFee', type: 'uint96' }, - { name: 'publicKey', type: 'uint256' }, - { name: 'validUntil', type: 'uint32' }, - { name: 'nonce', type: 'uint32' }, - ], - }, - primaryType: 'AccountUpdate', - domain: { - name: 'Loopring Protocol', - version: '3.6.0', - chainId: 1, - verifyingContract: '0x0BABA1Ad5bE3a5C0a66E7ac838a129Bf948f1eA4', - }, - message: { - owner: '0x8c3b776bdac9a7a4facc3cc20cdb40832bff9005', - accountID: 32494, - feeTokenID: 0, - maxFee: 100, - publicKey: - '11413934541425201845815969801249874136651857829494005371571206042985258823663', - validUntil: 1631655383, - nonce: 0, - }, - }; - await runEthMsg(buildEthMsgReq(msg, 'eip712'), client); - }); + it('Should test a Loopring message with non-standard numerical type', async () => { + const msg = { + types: { + EIP712Domain: [ + { name: 'name', type: 'string' }, + { name: 'version', type: 'string' }, + { name: 'chainId', type: 'uint256' }, + { name: 'verifyingContract', type: 'address' }, + ], + AccountUpdate: [ + { name: 'owner', type: 'address' }, + { name: 'accountID', type: 'uint32' }, + { name: 'feeTokenID', type: 'uint16' }, + { name: 'maxFee', type: 'uint96' }, + { name: 'publicKey', type: 'uint256' }, + { name: 'validUntil', type: 'uint32' }, + { name: 'nonce', type: 'uint32' }, + ], + }, + primaryType: 'AccountUpdate', + domain: { + name: 'Loopring Protocol', + version: '3.6.0', + chainId: 1, + verifyingContract: '0x0BABA1Ad5bE3a5C0a66E7ac838a129Bf948f1eA4', + }, + message: { + owner: '0x8c3b776bdac9a7a4facc3cc20cdb40832bff9005', + accountID: 32494, + feeTokenID: 0, + maxFee: 100, + publicKey: '11413934541425201845815969801249874136651857829494005371571206042985258823663', + validUntil: 1631655383, + nonce: 0, + }, + } + await runEthMsg(buildEthMsgReq(msg, 'eip712'), client) + }) - it('Should test Vertex message', async () => { - const msg = { - types: { - EIP712Domain: [ - { name: 'name', type: 'string' }, - { name: 'version', type: 'string' }, - { name: 'chainId', type: 'uint256' }, - { name: 'verifyingContract', type: 'address' }, - ], - Order: [ - { name: 'sender', type: 'bytes32' }, - { name: 'priceX18', type: 'int128' }, - { name: 'amount', type: 'int128' }, - { name: 'expiration', type: 'uint64' }, - { name: 'nonce', type: 'uint64' }, - ], - }, - primaryType: 'Order', - domain: { - name: 'Vertex', - version: '0.0.1', - chainId: '42161', - verifyingContract: '0xf03f457a30e598d5020164a339727ef40f2b8fbc', - }, - message: { - sender: - '0x841fe4876763357975d60da128d8a54bb045d76a64656661756c740000000000', - priceX18: '28898000000000000000000', - amount: '-10000000000000000', - expiration: '4611687701117784255', - nonce: '1764428860167815857', - }, - }; - await runEthMsg(buildEthMsgReq(msg, 'eip712'), client); - }); + it('Should test Vertex message', async () => { + const msg = { + types: { + EIP712Domain: [ + { name: 'name', type: 'string' }, + { name: 'version', type: 'string' }, + { name: 'chainId', type: 'uint256' }, + { name: 'verifyingContract', type: 'address' }, + ], + Order: [ + { name: 'sender', type: 'bytes32' }, + { name: 'priceX18', type: 'int128' }, + { name: 'amount', type: 'int128' }, + { name: 'expiration', type: 'uint64' }, + { name: 'nonce', type: 'uint64' }, + ], + }, + primaryType: 'Order', + domain: { + name: 'Vertex', + version: '0.0.1', + chainId: '42161', + verifyingContract: '0xf03f457a30e598d5020164a339727ef40f2b8fbc', + }, + message: { + sender: '0x841fe4876763357975d60da128d8a54bb045d76a64656661756c740000000000', + priceX18: '28898000000000000000000', + amount: '-10000000000000000', + expiration: '4611687701117784255', + nonce: '1764428860167815857', + }, + } + await runEthMsg(buildEthMsgReq(msg, 'eip712'), client) + }) - it('Should test a large 1inch transaction', async () => { - const msg = { - domain: { - chainId: 137, - name: '1inch Limit Order Protocol', - verifyingContract: '0xb707d89d29c189421163515c59e42147371d6857', - version: '1', - }, - message: { - getMakerAmount: - '0xf4a215c30000000000000000000000000000000000000000000000000de0b6b3a764000000000000000000000000000000000000000000000000000018fae27693b40000', - getTakerAmount: - '0x296637bf0000000000000000000000000000000000000000000000000de0b6b3a764000000000000000000000000000000000000000000000000000018fae27693b40000', - interaction: '0x', - makerAsset: '0x0d500b1d8e8ef31e21c99d1db9a6444d3adf1270', - makerAssetData: - '0x23b872dd0000000000000000000000003e3e2ccdd7bae6bbd4a64e8d16ca8842061335eb00000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000de0b6b3a7640000', - permit: '0x', - predicate: - '0x961d5b1e000000000000000000000000000000000000000000000000000000000000004000000000000000000000000000000000000000000000000000000000000000a00000000000000000000000000000000000000000000000000000000000000002000000000000000000000000b707d89d29c189421163515c59e42147371d6857000000000000000000000000b707d89d29c189421163515c59e42147371d68570000000000000000000000000000000000000000000000000000000000000002000000000000000000000000000000000000000000000000000000000000004000000000000000000000000000000000000000000000000000000000000000c00000000000000000000000000000000000000000000000000000000000000044cf6fc6e30000000000000000000000003e3e2ccdd7bae6bbd4a64e8d16ca8842061335eb000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000002463592c2b00000000000000000000000000000000000000000000000000000000613e28e500000000000000000000000000000000000000000000000000000000', - salt: '885135864076', - takerAsset: '0x8f3cf7ad23cd3cadbd9735aff958023239c6a063', - takerAssetData: - '0x23b872dd00000000000000000000000000000000000000000000000000000000000000000000000000000000000000003e3e2ccdd7bae6bbd4a64e8d16ca8842061335eb00000000000000000000000000000000000000000000000018fae27693b40000', - }, - primaryType: 'Order', - types: { - EIP712Domain: [ - { - name: 'name', - type: 'string', - }, - { - name: 'version', - type: 'string', - }, - { - name: 'chainId', - type: 'uint256', - }, - { - name: 'verifyingContract', - type: 'address', - }, - ], - Order: [ - { - name: 'salt', - type: 'uint256', - }, - { - name: 'makerAsset', - type: 'address', - }, - { - name: 'takerAsset', - type: 'address', - }, - { - name: 'makerAssetData', - type: 'bytes', - }, - { - name: 'takerAssetData', - type: 'bytes', - }, - { - name: 'getMakerAmount', - type: 'bytes', - }, - { - name: 'getTakerAmount', - type: 'bytes', - }, - { - name: 'predicate', - type: 'bytes', - }, - { - name: 'permit', - type: 'bytes', - }, - { - name: 'interaction', - type: 'bytes', - }, - ], - }, - }; - await runEthMsg(buildEthMsgReq(msg, 'eip712'), client); - }); + it('Should test a large 1inch transaction', async () => { + const msg = { + domain: { + chainId: 137, + name: '1inch Limit Order Protocol', + verifyingContract: '0xb707d89d29c189421163515c59e42147371d6857', + version: '1', + }, + message: { + getMakerAmount: '0xf4a215c30000000000000000000000000000000000000000000000000de0b6b3a764000000000000000000000000000000000000000000000000000018fae27693b40000', + getTakerAmount: '0x296637bf0000000000000000000000000000000000000000000000000de0b6b3a764000000000000000000000000000000000000000000000000000018fae27693b40000', + interaction: '0x', + makerAsset: '0x0d500b1d8e8ef31e21c99d1db9a6444d3adf1270', + makerAssetData: '0x23b872dd0000000000000000000000003e3e2ccdd7bae6bbd4a64e8d16ca8842061335eb00000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000de0b6b3a7640000', + permit: '0x', + predicate: + '0x961d5b1e000000000000000000000000000000000000000000000000000000000000004000000000000000000000000000000000000000000000000000000000000000a00000000000000000000000000000000000000000000000000000000000000002000000000000000000000000b707d89d29c189421163515c59e42147371d6857000000000000000000000000b707d89d29c189421163515c59e42147371d68570000000000000000000000000000000000000000000000000000000000000002000000000000000000000000000000000000000000000000000000000000004000000000000000000000000000000000000000000000000000000000000000c00000000000000000000000000000000000000000000000000000000000000044cf6fc6e30000000000000000000000003e3e2ccdd7bae6bbd4a64e8d16ca8842061335eb000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000002463592c2b00000000000000000000000000000000000000000000000000000000613e28e500000000000000000000000000000000000000000000000000000000', + salt: '885135864076', + takerAsset: '0x8f3cf7ad23cd3cadbd9735aff958023239c6a063', + takerAssetData: '0x23b872dd00000000000000000000000000000000000000000000000000000000000000000000000000000000000000003e3e2ccdd7bae6bbd4a64e8d16ca8842061335eb00000000000000000000000000000000000000000000000018fae27693b40000', + }, + primaryType: 'Order', + types: { + EIP712Domain: [ + { + name: 'name', + type: 'string', + }, + { + name: 'version', + type: 'string', + }, + { + name: 'chainId', + type: 'uint256', + }, + { + name: 'verifyingContract', + type: 'address', + }, + ], + Order: [ + { + name: 'salt', + type: 'uint256', + }, + { + name: 'makerAsset', + type: 'address', + }, + { + name: 'takerAsset', + type: 'address', + }, + { + name: 'makerAssetData', + type: 'bytes', + }, + { + name: 'takerAssetData', + type: 'bytes', + }, + { + name: 'getMakerAmount', + type: 'bytes', + }, + { + name: 'getTakerAmount', + type: 'bytes', + }, + { + name: 'predicate', + type: 'bytes', + }, + { + name: 'permit', + type: 'bytes', + }, + { + name: 'interaction', + type: 'bytes', + }, + ], + }, + } + await runEthMsg(buildEthMsgReq(msg, 'eip712'), client) + }) - it('Should test an example with 0 values', async () => { - const msg = { - types: { - EIP712Domain: [ - { name: 'name', type: 'string' }, - { name: 'host', type: 'string' }, - { name: 'version', type: 'string' }, - { name: 'chainId', type: 'uint256' }, - { name: 'verifyingContract', type: 'address' }, - ], - Test: [ - { name: 'owner', type: 'string' }, - { name: 'testArray', type: 'uint256[]' }, - ], - }, - domain: { - name: 'Opensea on Matic', - verifyingContract: '0x0', - version: '1', - chainId: '', - host: '', - }, - primaryType: 'Test', - message: { - owner: '0x56626bd0d646ce9da4a12403b2c1ba00fb9e1c43', - testArray: [], - }, - }; - await runEthMsg(buildEthMsgReq(msg, 'eip712'), client); - }); + it('Should test an example with 0 values', async () => { + const msg = { + types: { + EIP712Domain: [ + { name: 'name', type: 'string' }, + { name: 'host', type: 'string' }, + { name: 'version', type: 'string' }, + { name: 'chainId', type: 'uint256' }, + { name: 'verifyingContract', type: 'address' }, + ], + Test: [ + { name: 'owner', type: 'string' }, + { name: 'testArray', type: 'uint256[]' }, + ], + }, + domain: { + name: 'Opensea on Matic', + verifyingContract: '0x0', + version: '1', + chainId: '', + host: '', + }, + primaryType: 'Test', + message: { + owner: '0x56626bd0d646ce9da4a12403b2c1ba00fb9e1c43', + testArray: [], + }, + } + await runEthMsg(buildEthMsgReq(msg, 'eip712'), client) + }) - it('Should test canonical EIP712 example', async () => { - const msg = { - types: { - EIP712Domain: [ - { name: 'name', type: 'string' }, - { name: 'version', type: 'string' }, - { name: 'chainId', type: 'uint256' }, - { name: 'verifyingContract', type: 'address' }, - ], - Person: [ - { name: 'name', type: 'string' }, - { name: 'wallet', type: 'address' }, - ], - Mail: [ - { name: 'from', type: 'Person' }, - { name: 'to', type: 'Person' }, - { name: 'contents', type: 'string' }, - ], - }, - primaryType: 'Mail', - domain: { - name: 'Ether Mail', - version: '1', - chainId: 12, - verifyingContract: '0xCcCCccccCCCCcCCCCCCcCcCccCcCCCcCcccccccC', - }, - message: { - from: { - name: 'Cow', - wallet: '0xCD2a3d9F938E13CD947Ec05AbC7FE734Df8DD826', - }, - to: { - name: 'Bob', - wallet: '0xbBbBBBBbbBBBbbbBbbBbbbbBBbBbbbbBbBbbBBbB', - }, - contents: 'foobar', - }, - }; - await runEthMsg(buildEthMsgReq(msg, 'eip712'), client); - }); + it('Should test canonical EIP712 example', async () => { + const msg = { + types: { + EIP712Domain: [ + { name: 'name', type: 'string' }, + { name: 'version', type: 'string' }, + { name: 'chainId', type: 'uint256' }, + { name: 'verifyingContract', type: 'address' }, + ], + Person: [ + { name: 'name', type: 'string' }, + { name: 'wallet', type: 'address' }, + ], + Mail: [ + { name: 'from', type: 'Person' }, + { name: 'to', type: 'Person' }, + { name: 'contents', type: 'string' }, + ], + }, + primaryType: 'Mail', + domain: { + name: 'Ether Mail', + version: '1', + chainId: 12, + verifyingContract: '0xCcCCccccCCCCcCCCCCCcCcCccCcCCCcCcccccccC', + }, + message: { + from: { + name: 'Cow', + wallet: '0xCD2a3d9F938E13CD947Ec05AbC7FE734Df8DD826', + }, + to: { + name: 'Bob', + wallet: '0xbBbBBBBbbBBBbbbBbbBbbbbBBbBbbbbBbBbbBBbB', + }, + contents: 'foobar', + }, + } + await runEthMsg(buildEthMsgReq(msg, 'eip712'), client) + }) - it('Should test canonical EIP712 example with 2nd level nesting', async () => { - const msg = { - types: { - EIP712Domain: [ - { name: 'name', type: 'string' }, - { name: 'version', type: 'string' }, - { name: 'chainId', type: 'uint256' }, - { name: 'verifyingContract', type: 'address' }, - ], - Wallet: [ - { name: 'address', type: 'address' }, - { name: 'balance', type: 'uint256' }, - ], - Person: [ - { name: 'name', type: 'string' }, - { name: 'wallet', type: 'Wallet' }, - ], - Mail: [ - { name: 'from', type: 'Person' }, - { name: 'to', type: 'Person' }, - { name: 'contents', type: 'string' }, - ], - }, - primaryType: 'Mail', - domain: { - name: 'Ether Mail', - version: '1', - chainId: 12, - verifyingContract: '0xCcCCccccCCCCcCCCCCCcCcCccCcCCCcCcccccccC', - }, - message: { - from: { - name: 'Cow', - wallet: { - address: '0xCD2a3d9F938E13CD947Ec05AbC7FE734Df8DD826', - balance: '0x12345678', - }, - }, - to: { - name: 'Bob', - wallet: { - address: '0xbBbBBBBbbBBBbbbBbbBbbbbBBbBbbbbBbBbbBBbB', - balance: '0xabcdef12', - }, - }, - contents: 'foobar', - }, - }; - await runEthMsg(buildEthMsgReq(msg, 'eip712'), client); - }); + it('Should test canonical EIP712 example with 2nd level nesting', async () => { + const msg = { + types: { + EIP712Domain: [ + { name: 'name', type: 'string' }, + { name: 'version', type: 'string' }, + { name: 'chainId', type: 'uint256' }, + { name: 'verifyingContract', type: 'address' }, + ], + Wallet: [ + { name: 'address', type: 'address' }, + { name: 'balance', type: 'uint256' }, + ], + Person: [ + { name: 'name', type: 'string' }, + { name: 'wallet', type: 'Wallet' }, + ], + Mail: [ + { name: 'from', type: 'Person' }, + { name: 'to', type: 'Person' }, + { name: 'contents', type: 'string' }, + ], + }, + primaryType: 'Mail', + domain: { + name: 'Ether Mail', + version: '1', + chainId: 12, + verifyingContract: '0xCcCCccccCCCCcCCCCCCcCcCccCcCCCcCcccccccC', + }, + message: { + from: { + name: 'Cow', + wallet: { + address: '0xCD2a3d9F938E13CD947Ec05AbC7FE734Df8DD826', + balance: '0x12345678', + }, + }, + to: { + name: 'Bob', + wallet: { + address: '0xbBbBBBBbbBBBbbbBbbBbbbbBBbBbbbbBbBbbBBbB', + balance: '0xabcdef12', + }, + }, + contents: 'foobar', + }, + } + await runEthMsg(buildEthMsgReq(msg, 'eip712'), client) + }) - it('Should test canonical EIP712 example with 3rd level nesting', async () => { - const msg = { - types: { - EIP712Domain: [ - { name: 'name', type: 'string' }, - { name: 'version', type: 'string' }, - { name: 'chainId', type: 'uint256' }, - { name: 'verifyingContract', type: 'address' }, - ], - Wallet: [ - { name: 'address', type: 'address' }, - { name: 'balance', type: 'Balance' }, - ], - Person: [ - { name: 'name', type: 'string' }, - { name: 'wallet', type: 'Wallet' }, - ], - Mail: [ - { name: 'from', type: 'Person' }, - { name: 'to', type: 'Person' }, - { name: 'contents', type: 'string' }, - ], - Balance: [ - { name: 'value', type: 'uint256' }, - { name: 'currency', type: 'string' }, - ], - }, - primaryType: 'Mail', - domain: { - name: 'Ether Mail', - version: '1', - chainId: 12, - verifyingContract: '0xCcCCccccCCCCcCCCCCCcCcCccCcCCCcCcccccccC', - }, - message: { - from: { - name: 'Cow', - wallet: { - address: '0xCD2a3d9F938E13CD947Ec05AbC7FE734Df8DD826', - balance: { - value: '0x12345678', - currency: 'ETH', - }, - }, - }, - to: { - name: 'Bob', - wallet: { - address: '0xbBbBBBBbbBBBbbbBbbBbbbbBBbBbbbbBbBbbBBbB', - balance: { - value: '0xabcdef12', - currency: 'UNI', - }, - }, - }, - contents: 'foobar', - }, - }; - await runEthMsg(buildEthMsgReq(msg, 'eip712'), client); - }); + it('Should test canonical EIP712 example with 3rd level nesting', async () => { + const msg = { + types: { + EIP712Domain: [ + { name: 'name', type: 'string' }, + { name: 'version', type: 'string' }, + { name: 'chainId', type: 'uint256' }, + { name: 'verifyingContract', type: 'address' }, + ], + Wallet: [ + { name: 'address', type: 'address' }, + { name: 'balance', type: 'Balance' }, + ], + Person: [ + { name: 'name', type: 'string' }, + { name: 'wallet', type: 'Wallet' }, + ], + Mail: [ + { name: 'from', type: 'Person' }, + { name: 'to', type: 'Person' }, + { name: 'contents', type: 'string' }, + ], + Balance: [ + { name: 'value', type: 'uint256' }, + { name: 'currency', type: 'string' }, + ], + }, + primaryType: 'Mail', + domain: { + name: 'Ether Mail', + version: '1', + chainId: 12, + verifyingContract: '0xCcCCccccCCCCcCCCCCCcCcCccCcCCCcCcccccccC', + }, + message: { + from: { + name: 'Cow', + wallet: { + address: '0xCD2a3d9F938E13CD947Ec05AbC7FE734Df8DD826', + balance: { + value: '0x12345678', + currency: 'ETH', + }, + }, + }, + to: { + name: 'Bob', + wallet: { + address: '0xbBbBBBBbbBBBbbbBbbBbbbbBBbBbbbbBbBbbBBbB', + balance: { + value: '0xabcdef12', + currency: 'UNI', + }, + }, + }, + contents: 'foobar', + }, + } + await runEthMsg(buildEthMsgReq(msg, 'eip712'), client) + }) - it('Should test canonical EIP712 example with 3rd level nesting and params in a different order', async () => { - const msg = { - types: { - Balance: [ - { name: 'value', type: 'uint256' }, - { name: 'currency', type: 'string' }, - ], - EIP712Domain: [ - { name: 'name', type: 'string' }, - { name: 'version', type: 'string' }, - { name: 'chainId', type: 'uint256' }, - { name: 'verifyingContract', type: 'address' }, - ], - Person: [ - { name: 'name', type: 'string' }, - { name: 'wallet', type: 'Wallet' }, - ], - Wallet: [ - { name: 'address', type: 'address' }, - { name: 'balance', type: 'Balance' }, - ], - Mail: [ - { name: 'from', type: 'Person' }, - { name: 'to', type: 'Person' }, - { name: 'contents', type: 'string' }, - ], - }, - primaryType: 'Mail', - domain: { - name: 'Ether Mail', - version: '1', - chainId: 12, - verifyingContract: '0xCcCCccccCCCCcCCCCCCcCcCccCcCCCcCcccccccC', - }, - message: { - contents: 'foobar', - from: { - name: 'Cow', - wallet: { - address: '0xCD2a3d9F938E13CD947Ec05AbC7FE734Df8DD826', - balance: { - value: '0x12345678', - currency: 'ETH', - }, - }, - }, - to: { - name: 'Bob', - wallet: { - address: '0xbBbBBBBbbBBBbbbBbbBbbbbBBbBbbbbBbBbbBBbB', - balance: { - value: '0xabcdef12', - currency: 'UNI', - }, - }, - }, - }, - }; - await runEthMsg(buildEthMsgReq(msg, 'eip712'), client); - }); + it('Should test canonical EIP712 example with 3rd level nesting and params in a different order', async () => { + const msg = { + types: { + Balance: [ + { name: 'value', type: 'uint256' }, + { name: 'currency', type: 'string' }, + ], + EIP712Domain: [ + { name: 'name', type: 'string' }, + { name: 'version', type: 'string' }, + { name: 'chainId', type: 'uint256' }, + { name: 'verifyingContract', type: 'address' }, + ], + Person: [ + { name: 'name', type: 'string' }, + { name: 'wallet', type: 'Wallet' }, + ], + Wallet: [ + { name: 'address', type: 'address' }, + { name: 'balance', type: 'Balance' }, + ], + Mail: [ + { name: 'from', type: 'Person' }, + { name: 'to', type: 'Person' }, + { name: 'contents', type: 'string' }, + ], + }, + primaryType: 'Mail', + domain: { + name: 'Ether Mail', + version: '1', + chainId: 12, + verifyingContract: '0xCcCCccccCCCCcCCCCCCcCcCccCcCCCcCcccccccC', + }, + message: { + contents: 'foobar', + from: { + name: 'Cow', + wallet: { + address: '0xCD2a3d9F938E13CD947Ec05AbC7FE734Df8DD826', + balance: { + value: '0x12345678', + currency: 'ETH', + }, + }, + }, + to: { + name: 'Bob', + wallet: { + address: '0xbBbBBBBbbBBBbbbBbbBbbbbBBbBbbbbBbBbbBBbB', + balance: { + value: '0xabcdef12', + currency: 'UNI', + }, + }, + }, + }, + } + await runEthMsg(buildEthMsgReq(msg, 'eip712'), client) + }) - it('Should test a payload with an array type', async () => { - const msg = { - types: { - EIP712Domain: [{ name: 'name', type: 'string' }], - UserVotePayload: [ - { - name: 'allocations', - type: 'UserVoteAllocationItem[]', - }, - ], - UserVoteAllocationItem: [ - { - name: 'reactorKey', - type: 'bytes32', - }, - { - name: 'amount', - type: 'uint256', - }, - ], - }, - primaryType: 'UserVotePayload', - domain: { - name: 'Tokemak Voting', - version: '1', - chainId: 1, - verifyingContract: '0x4495982ea5ed9c1b7cec37434cbf930b9472e823', - }, - message: { - allocations: [ - { - reactorKey: - '0x6f686d2d64656661756c74000000000000000000000000000000000000000000', - amount: '1', - }, - { - reactorKey: - '0x6f686d2d64656661756c74000000000000000000000000000000000000000000', - amount: '2', - }, - ], - }, - }; - await runEthMsg(buildEthMsgReq(msg, 'eip712'), client); - }); + it('Should test a payload with an array type', async () => { + const msg = { + types: { + EIP712Domain: [{ name: 'name', type: 'string' }], + UserVotePayload: [ + { + name: 'allocations', + type: 'UserVoteAllocationItem[]', + }, + ], + UserVoteAllocationItem: [ + { + name: 'reactorKey', + type: 'bytes32', + }, + { + name: 'amount', + type: 'uint256', + }, + ], + }, + primaryType: 'UserVotePayload', + domain: { + name: 'Tokemak Voting', + version: '1', + chainId: 1, + verifyingContract: '0x4495982ea5ed9c1b7cec37434cbf930b9472e823', + }, + message: { + allocations: [ + { + reactorKey: '0x6f686d2d64656661756c74000000000000000000000000000000000000000000', + amount: '1', + }, + { + reactorKey: '0x6f686d2d64656661756c74000000000000000000000000000000000000000000', + amount: '2', + }, + ], + }, + } + await runEthMsg(buildEthMsgReq(msg, 'eip712'), client) + }) - it('Should test multiple array types', async () => { - const msg = { - types: { - EIP712Domain: [{ name: 'name', type: 'string' }], - UserVotePayload: [ - { - name: 'integer', - type: 'uint256', - }, - { - name: 'allocations', - type: 'UserVoteAllocationItem[]', - }, - { - name: 'dummy', - type: 'uint256', - }, - { - name: 'integerArray', - type: 'uint256[]', - }, - ], - UserVoteAllocationItem: [ - { - name: 'reactorKey', - type: 'bytes32', - }, - { - name: 'amount', - type: 'uint256', - }, - ], - }, - primaryType: 'UserVotePayload', - domain: { - name: 'Tokemak Voting', - }, - message: { - integer: 56, - allocations: [ - { - reactorKey: - '0x6f686d2d64656661756c74000000000000000000000000000000000000000000', - amount: '1', - }, - { - reactorKey: - '0x6f686d2d64656661756c74000000000000000000000000000000000000000000', - amount: '2', - }, - ], - dummy: 52, - integerArray: [1, 2, 3], - }, - }; - await runEthMsg(buildEthMsgReq(msg, 'eip712'), client); - }); + it('Should test multiple array types', async () => { + const msg = { + types: { + EIP712Domain: [{ name: 'name', type: 'string' }], + UserVotePayload: [ + { + name: 'integer', + type: 'uint256', + }, + { + name: 'allocations', + type: 'UserVoteAllocationItem[]', + }, + { + name: 'dummy', + type: 'uint256', + }, + { + name: 'integerArray', + type: 'uint256[]', + }, + ], + UserVoteAllocationItem: [ + { + name: 'reactorKey', + type: 'bytes32', + }, + { + name: 'amount', + type: 'uint256', + }, + ], + }, + primaryType: 'UserVotePayload', + domain: { + name: 'Tokemak Voting', + }, + message: { + integer: 56, + allocations: [ + { + reactorKey: '0x6f686d2d64656661756c74000000000000000000000000000000000000000000', + amount: '1', + }, + { + reactorKey: '0x6f686d2d64656661756c74000000000000000000000000000000000000000000', + amount: '2', + }, + ], + dummy: 52, + integerArray: [1, 2, 3], + }, + } + await runEthMsg(buildEthMsgReq(msg, 'eip712'), client) + }) - it('Should test a nested array', async () => { - const msg = { - types: { - EIP712Domain: [{ name: 'name', type: 'string' }], - UserVotePayload: [ - { - name: 'allocations', - type: 'UserVoteAllocationItem[]', - }, - ], - UserVoteAllocationItem: [ - { - name: 'reactorKey', - type: 'bytes32', - }, - { - name: 'amount', - type: 'uint256[]', - }, - ], - }, - primaryType: 'UserVotePayload', - domain: { - name: 'Tokemak Voting', - }, - message: { - allocations: [ - { - reactorKey: - '0x6f686d2d64656661756c74000000000000000000000000000000000000000000', - amount: ['1', '2'], - }, - { - reactorKey: - '0x6f686d2d64656661756c74000000000000000000000000000000000000000000', - amount: ['2', '3'], - }, - ], - }, - }; - await runEthMsg(buildEthMsgReq(msg, 'eip712'), client); - }); + it('Should test a nested array', async () => { + const msg = { + types: { + EIP712Domain: [{ name: 'name', type: 'string' }], + UserVotePayload: [ + { + name: 'allocations', + type: 'UserVoteAllocationItem[]', + }, + ], + UserVoteAllocationItem: [ + { + name: 'reactorKey', + type: 'bytes32', + }, + { + name: 'amount', + type: 'uint256[]', + }, + ], + }, + primaryType: 'UserVotePayload', + domain: { + name: 'Tokemak Voting', + }, + message: { + allocations: [ + { + reactorKey: '0x6f686d2d64656661756c74000000000000000000000000000000000000000000', + amount: ['1', '2'], + }, + { + reactorKey: '0x6f686d2d64656661756c74000000000000000000000000000000000000000000', + amount: ['2', '3'], + }, + ], + }, + } + await runEthMsg(buildEthMsgReq(msg, 'eip712'), client) + }) - it('Should test a nested array of custom type', async () => { - const msg = { - types: { - EIP712Domain: [{ name: 'name', type: 'string' }], - DummyThing: [ - { - name: 'foo', - type: 'bytes', - }, - ], - UserVotePayload: [ - { - name: 'test', - type: 'string', - }, - { - name: 'athing', - type: 'uint32', - }, - { - name: 'allocations', - type: 'UserVoteAllocationItem[]', - }, - ], - UserVoteAllocationItem: [ - { - name: 'reactorKey', - type: 'bytes32', - }, - { - name: 'dummy', - type: 'DummyThing[]', - }, - ], - }, - primaryType: 'UserVotePayload', - domain: { - name: 'Tokemak Voting', - }, - message: { - athing: 5, - test: 'hello', - allocations: [ - { - reactorKey: - '0x6f686d2d64656661756c74000000000000000000000000000000000000000000', - dummy: [ - { - foo: '0xabcd', - }, - { - foo: '0x123456', - }, - ], - }, - { - reactorKey: - '0x6f686d2d64656661756c74000000000000000000000000000000000000000000', - dummy: [ - { - foo: '0xdeadbeef', - }, - { - foo: '0x', - }, - ], - }, - ], - }, - }; - await runEthMsg(buildEthMsgReq(msg, 'eip712'), client); - }); + it('Should test a nested array of custom type', async () => { + const msg = { + types: { + EIP712Domain: [{ name: 'name', type: 'string' }], + DummyThing: [ + { + name: 'foo', + type: 'bytes', + }, + ], + UserVotePayload: [ + { + name: 'test', + type: 'string', + }, + { + name: 'athing', + type: 'uint32', + }, + { + name: 'allocations', + type: 'UserVoteAllocationItem[]', + }, + ], + UserVoteAllocationItem: [ + { + name: 'reactorKey', + type: 'bytes32', + }, + { + name: 'dummy', + type: 'DummyThing[]', + }, + ], + }, + primaryType: 'UserVotePayload', + domain: { + name: 'Tokemak Voting', + }, + message: { + athing: 5, + test: 'hello', + allocations: [ + { + reactorKey: '0x6f686d2d64656661756c74000000000000000000000000000000000000000000', + dummy: [ + { + foo: '0xabcd', + }, + { + foo: '0x123456', + }, + ], + }, + { + reactorKey: '0x6f686d2d64656661756c74000000000000000000000000000000000000000000', + dummy: [ + { + foo: '0xdeadbeef', + }, + { + foo: '0x', + }, + ], + }, + ], + }, + } + await runEthMsg(buildEthMsgReq(msg, 'eip712'), client) + }) - it('Should test a bunch of EIP712 data types', async () => { - const msg = { - types: { - EIP712Domain: [ - { - name: 'name', - type: 'string', - }, - { - name: 'version', - type: 'string', - }, - { - name: 'chainId', - type: 'uint256', - }, - { - name: 'verifyingContract', - type: 'address', - }, - ], - PrimaryStuff: [ - { name: 'UINT8', type: 'uint8' }, - { name: 'UINT16', type: 'uint16' }, - { name: 'UINT32', type: 'uint32' }, - { name: 'UINT64', type: 'uint64' }, - { name: 'UINT256', type: 'uint256' }, - { name: 'BYTES1', type: 'bytes1' }, - { name: 'BYTES5', type: 'bytes5' }, - { name: 'BYTES7', type: 'bytes7' }, - { name: 'BYTES12', type: 'bytes12' }, - { name: 'BYTES16', type: 'bytes16' }, - { name: 'BYTES20', type: 'bytes20' }, - { name: 'BYTES21', type: 'bytes21' }, - { name: 'BYTES31', type: 'bytes31' }, - { name: 'BYTES32', type: 'bytes32' }, - { name: 'BYTES', type: 'bytes' }, - { name: 'STRING', type: 'string' }, - { name: 'BOOL', type: 'bool' }, - { name: 'ADDRESS', type: 'address' }, - ], - }, - primaryType: 'PrimaryStuff', - domain: { - name: 'Muh Domainz', - version: '1', - chainId: 270, - verifyingContract: '0xcc9c93cef8c70a7b46e32b3635d1a746ee0ec5b4', - }, - message: { - UINT8: '0xab', - UINT16: '0xb1d7', - UINT32: '0x80bb335b', - UINT64: '0x259528d5bc', - UINT256: '0xad2693f24ba507750d1763ebae3661c07504', - BYTES1: '0x2f', - BYTES5: '0x9485269fa5', - BYTES7: '0xc4e8d65ce8c3cf', - BYTES12: '0x358eb7b28e8e1643e7c4737f', - BYTES16: '0x7ace034ab088fdd434f1e817f32171a0', - BYTES20: '0x4ab51f2d5bfdc0f1b96f83358d5f356c98583573', - BYTES21: '0x6ecdc19b30c7fa712ba334458d77377b6a586bbab5', - BYTES31: - '0x06c21824a98643f96643b3220962f441210b007f4c19dfdf0dea53d097fc28', - BYTES32: - '0x59cfcbf35256451756b02fa644d3d0748bd98f5904febf3433e6df19b4df7452', - BYTES: - '0x0354b2c449772905b2598a93f5da69962f0444e0a6e2429e8f844f1011446f6fe81815846fb6ebe2d213968d1f8532749735f5702f565db0429b2fe596d295d9c06241389fe97fb2f3b91e1e0f2d978fb26e366737451f1193097bd0a2332e0bfc0cdb631005', - STRING: 'I am a string hello there human', - BOOL: true, - ADDRESS: '0x078a8d6eba928e7ea787ed48f71c5936aed4625d', - }, - }; - await runEthMsg(buildEthMsgReq(msg, 'eip712'), client); - }); + it('Should test a bunch of EIP712 data types', async () => { + const msg = { + types: { + EIP712Domain: [ + { + name: 'name', + type: 'string', + }, + { + name: 'version', + type: 'string', + }, + { + name: 'chainId', + type: 'uint256', + }, + { + name: 'verifyingContract', + type: 'address', + }, + ], + PrimaryStuff: [ + { name: 'UINT8', type: 'uint8' }, + { name: 'UINT16', type: 'uint16' }, + { name: 'UINT32', type: 'uint32' }, + { name: 'UINT64', type: 'uint64' }, + { name: 'UINT256', type: 'uint256' }, + { name: 'BYTES1', type: 'bytes1' }, + { name: 'BYTES5', type: 'bytes5' }, + { name: 'BYTES7', type: 'bytes7' }, + { name: 'BYTES12', type: 'bytes12' }, + { name: 'BYTES16', type: 'bytes16' }, + { name: 'BYTES20', type: 'bytes20' }, + { name: 'BYTES21', type: 'bytes21' }, + { name: 'BYTES31', type: 'bytes31' }, + { name: 'BYTES32', type: 'bytes32' }, + { name: 'BYTES', type: 'bytes' }, + { name: 'STRING', type: 'string' }, + { name: 'BOOL', type: 'bool' }, + { name: 'ADDRESS', type: 'address' }, + ], + }, + primaryType: 'PrimaryStuff', + domain: { + name: 'Muh Domainz', + version: '1', + chainId: 270, + verifyingContract: '0xcc9c93cef8c70a7b46e32b3635d1a746ee0ec5b4', + }, + message: { + UINT8: '0xab', + UINT16: '0xb1d7', + UINT32: '0x80bb335b', + UINT64: '0x259528d5bc', + UINT256: '0xad2693f24ba507750d1763ebae3661c07504', + BYTES1: '0x2f', + BYTES5: '0x9485269fa5', + BYTES7: '0xc4e8d65ce8c3cf', + BYTES12: '0x358eb7b28e8e1643e7c4737f', + BYTES16: '0x7ace034ab088fdd434f1e817f32171a0', + BYTES20: '0x4ab51f2d5bfdc0f1b96f83358d5f356c98583573', + BYTES21: '0x6ecdc19b30c7fa712ba334458d77377b6a586bbab5', + BYTES31: '0x06c21824a98643f96643b3220962f441210b007f4c19dfdf0dea53d097fc28', + BYTES32: '0x59cfcbf35256451756b02fa644d3d0748bd98f5904febf3433e6df19b4df7452', + BYTES: '0x0354b2c449772905b2598a93f5da69962f0444e0a6e2429e8f844f1011446f6fe81815846fb6ebe2d213968d1f8532749735f5702f565db0429b2fe596d295d9c06241389fe97fb2f3b91e1e0f2d978fb26e366737451f1193097bd0a2332e0bfc0cdb631005', + STRING: 'I am a string hello there human', + BOOL: true, + ADDRESS: '0x078a8d6eba928e7ea787ed48f71c5936aed4625d', + }, + } + await runEthMsg(buildEthMsgReq(msg, 'eip712'), client) + }) - it('Should test a payload with a nested type in multiple nesting levels', async () => { - const msg = { - types: { - EIP712Domain: [ - { - name: 'name', - type: 'string', - }, - ], - PrimaryType: [ - { - name: 'one', - type: 'Type1', - }, - { - name: 'zero', - type: 'Type0', - }, - ], - Type1: [ - { - name: '1s', - type: 'string', - }, - ], - Type0: [ - { - name: 'one', - type: 'Type1', - }, - ], - }, - primaryType: 'PrimaryType', - domain: { - name: 'Domain', - }, - message: { - one: { - '1s': 'nestedOne', - }, - zero: { - one: { - '1s': 'nestedTwo', - }, - }, - }, - }; - await runEthMsg(buildEthMsgReq(msg, 'eip712'), client); - }); + it('Should test a payload with a nested type in multiple nesting levels', async () => { + const msg = { + types: { + EIP712Domain: [ + { + name: 'name', + type: 'string', + }, + ], + PrimaryType: [ + { + name: 'one', + type: 'Type1', + }, + { + name: 'zero', + type: 'Type0', + }, + ], + Type1: [ + { + name: '1s', + type: 'string', + }, + ], + Type0: [ + { + name: 'one', + type: 'Type1', + }, + ], + }, + primaryType: 'PrimaryType', + domain: { + name: 'Domain', + }, + message: { + one: { + '1s': 'nestedOne', + }, + zero: { + one: { + '1s': 'nestedTwo', + }, + }, + }, + } + await runEthMsg(buildEthMsgReq(msg, 'eip712'), client) + }) - it('Should test a payload that requires use of extraData frames', async () => { - const msg = { - types: { - EIP712Domain: [ - { name: 'name', type: 'string' }, - { name: 'version', type: 'string' }, - { name: 'chainId', type: 'uint256' }, - { name: 'verifyingContract', type: 'address' }, - ], - Wallet: [ - { name: 'address', type: 'address' }, - { name: 'balance', type: 'Balance' }, - ], - Person: [ - { name: 'name', type: 'string' }, - { name: 'wallet', type: 'Wallet' }, - ], - Mail: [ - { name: 'from', type: 'Person' }, - { name: 'to', type: 'Person' }, - { name: 'contents', type: 'string' }, - ], - Balance: [ - { name: 'value', type: 'uint256' }, - { name: 'currency', type: 'string' }, - ], - }, - primaryType: 'Mail', - domain: { - name: 'Ether Mail', - version: '1', - chainId: 12, - verifyingContract: '0xCcCCccccCCCCcCCCCCCcCcCccCcCCCcCcccccccC', - }, - message: { - from: { - name: 'Cow', - wallet: { - address: '0xCD2a3d9F938E13CD947Ec05AbC7FE734Df8DD826', - balance: { - value: '0x12345678', - currency: 'ETH', - }, - }, - }, - to: { - name: 'Bob', - wallet: { - address: '0xbBbBBBBbbBBBbbbBbbBbbbbBBbBbbbbBbBbbBBbB', - balance: { - value: '0xabcdef12', - currency: 'UNI', - }, - }, - }, - contents: - 'stupidlylongstringthatshouldstretchintomultiplepageswhencopiedmanytimesstupidlylongstringthatshouldstretchintomultiplepageswhencopiedmanytimesstupidlylongstringthatshouldstretchintomultiplepageswhencopiedmanytimesstupidlylongstringthatshouldstretchintomultiplepageswhencopiedmanytimesstupidlylongstringthatshouldstretchintomultiplepageswhencopiedmanytimesstupidlylongstringthatshouldstretchintomultiplepageswhencopiedmanytimesstupidlylongstringthatshouldstretchintomultiplepageswhencopiedmanytimesstupidlylongstringthatshouldstretchintomultiplepageswhencopiedmanytimesstupidlylongstringthatshouldstretchintomultiplepageswhencopiedmanytimesstupidlylongstringthatshouldstretchintomultiplepageswhencopiedmanytimesstupidlylongstringthatshouldstretchintomultiplepageswhencopiedmanytimesstupidlylongstringthatshouldstretchintomultiplepageswhencopiedmanytimesstupidlylongstringthatshouldstretchintomultiplepageswhencopiedmanytimesstupidlylongstringthatshouldstretchintomultiplepageswhencopiedmanytimesstupidlylongstringthatshouldstretchintomultiplepageswhencopiedmanytimesstupidlylongstringthatshouldstretchintomultiplepageswhencopiedmanytimesstupidlylongstringthatshouldstretchintomultiplepageswhencopiedmanytimesstupidlylongstringthatshouldstretchintomultiplepageswhencopiedmanytimesstupidlylongstringthatshouldstretchintomultiplepageswhencopiedmanytimesstupidlylongstringthatshouldstretchintomultiplepageswhencopiedmanytimesstupidlylongstringthatshouldstretchintomultiplepageswhencopiedmanytimesstupidlylongstringthatshouldstretchintomultiplepageswhencopiedmanytimesstupidlylongstringthatshouldstretchintomultiplepageswhencopiedmanytimesstupidlylongstringthatshouldstretchintomultiplepageswhencopiedmanytimesstupidlylongstringthatshouldstretchintomultiplepageswhencopiedmanytimesstupidlylongstringthatshouldstretchintomultiplepageswhencopiedmanytimesstupidlylongstringthatshouldstretchintomultiplepageswhencopiedmanytimesstupidlylongstringthatshouldstretchintomultiplepageswhencopiedmanytimesstupidlylongstringthatshouldstretchintomultiplepageswhencopiedmanytimesstupidlylongstringthatshouldstretchintomultiplepageswhencopiedmanytimesstupidlylongstringthatshouldstretchintomultiplepageswhencopiedmanytimesstupidlylongstringthatshouldstretchintomultiplepageswhencopiedmanytimesstupidlylongstringthatshouldstretchintomultiplepageswhencopiedmanytimes', - }, - }; - await runEthMsg(buildEthMsgReq(msg, 'eip712'), client); - }); + it('Should test a payload that requires use of extraData frames', async () => { + const msg = { + types: { + EIP712Domain: [ + { name: 'name', type: 'string' }, + { name: 'version', type: 'string' }, + { name: 'chainId', type: 'uint256' }, + { name: 'verifyingContract', type: 'address' }, + ], + Wallet: [ + { name: 'address', type: 'address' }, + { name: 'balance', type: 'Balance' }, + ], + Person: [ + { name: 'name', type: 'string' }, + { name: 'wallet', type: 'Wallet' }, + ], + Mail: [ + { name: 'from', type: 'Person' }, + { name: 'to', type: 'Person' }, + { name: 'contents', type: 'string' }, + ], + Balance: [ + { name: 'value', type: 'uint256' }, + { name: 'currency', type: 'string' }, + ], + }, + primaryType: 'Mail', + domain: { + name: 'Ether Mail', + version: '1', + chainId: 12, + verifyingContract: '0xCcCCccccCCCCcCCCCCCcCcCccCcCCCcCcccccccC', + }, + message: { + from: { + name: 'Cow', + wallet: { + address: '0xCD2a3d9F938E13CD947Ec05AbC7FE734Df8DD826', + balance: { + value: '0x12345678', + currency: 'ETH', + }, + }, + }, + to: { + name: 'Bob', + wallet: { + address: '0xbBbBBBBbbBBBbbbBbbBbbbbBBbBbbbbBbBbbBBbB', + balance: { + value: '0xabcdef12', + currency: 'UNI', + }, + }, + }, + contents: + 'stupidlylongstringthatshouldstretchintomultiplepageswhencopiedmanytimesstupidlylongstringthatshouldstretchintomultiplepageswhencopiedmanytimesstupidlylongstringthatshouldstretchintomultiplepageswhencopiedmanytimesstupidlylongstringthatshouldstretchintomultiplepageswhencopiedmanytimesstupidlylongstringthatshouldstretchintomultiplepageswhencopiedmanytimesstupidlylongstringthatshouldstretchintomultiplepageswhencopiedmanytimesstupidlylongstringthatshouldstretchintomultiplepageswhencopiedmanytimesstupidlylongstringthatshouldstretchintomultiplepageswhencopiedmanytimesstupidlylongstringthatshouldstretchintomultiplepageswhencopiedmanytimesstupidlylongstringthatshouldstretchintomultiplepageswhencopiedmanytimesstupidlylongstringthatshouldstretchintomultiplepageswhencopiedmanytimesstupidlylongstringthatshouldstretchintomultiplepageswhencopiedmanytimesstupidlylongstringthatshouldstretchintomultiplepageswhencopiedmanytimesstupidlylongstringthatshouldstretchintomultiplepageswhencopiedmanytimesstupidlylongstringthatshouldstretchintomultiplepageswhencopiedmanytimesstupidlylongstringthatshouldstretchintomultiplepageswhencopiedmanytimesstupidlylongstringthatshouldstretchintomultiplepageswhencopiedmanytimesstupidlylongstringthatshouldstretchintomultiplepageswhencopiedmanytimesstupidlylongstringthatshouldstretchintomultiplepageswhencopiedmanytimesstupidlylongstringthatshouldstretchintomultiplepageswhencopiedmanytimesstupidlylongstringthatshouldstretchintomultiplepageswhencopiedmanytimesstupidlylongstringthatshouldstretchintomultiplepageswhencopiedmanytimesstupidlylongstringthatshouldstretchintomultiplepageswhencopiedmanytimesstupidlylongstringthatshouldstretchintomultiplepageswhencopiedmanytimesstupidlylongstringthatshouldstretchintomultiplepageswhencopiedmanytimesstupidlylongstringthatshouldstretchintomultiplepageswhencopiedmanytimesstupidlylongstringthatshouldstretchintomultiplepageswhencopiedmanytimesstupidlylongstringthatshouldstretchintomultiplepageswhencopiedmanytimesstupidlylongstringthatshouldstretchintomultiplepageswhencopiedmanytimesstupidlylongstringthatshouldstretchintomultiplepageswhencopiedmanytimesstupidlylongstringthatshouldstretchintomultiplepageswhencopiedmanytimesstupidlylongstringthatshouldstretchintomultiplepageswhencopiedmanytimesstupidlylongstringthatshouldstretchintomultiplepageswhencopiedmanytimes', + }, + } + await runEthMsg(buildEthMsgReq(msg, 'eip712'), client) + }) - it('Should test a message with very large types', async () => { - const msg = { - types: { - EIP712Domain: [ - { - name: 'name', - type: 'string', - }, - { - name: 'version', - type: 'string', - }, - { - name: 'chainId', - type: 'uint256', - }, - { - name: 'verifyingContract', - type: 'address', - }, - ], - Order: [ - { - name: 'exchange', - type: 'address', - }, - { - name: 'maker', - type: 'address', - }, - { - name: 'taker', - type: 'address', - }, - { - name: 'makerRelayerFee', - type: 'uint256', - }, - { - name: 'takerRelayerFee', - type: 'uint256', - }, - { - name: 'makerProtocolFee', - type: 'uint256', - }, - { - name: 'takerProtocolFee', - type: 'uint256', - }, - { - name: 'feeRecipient', - type: 'address', - }, - { - name: 'feeMethod', - type: 'uint8', - }, - { - name: 'side', - type: 'uint8', - }, - { - name: 'saleKind', - type: 'uint8', - }, - { - name: 'target', - type: 'address', - }, - { - name: 'howToCall', - type: 'uint8', - }, - { - name: 'calldata', - type: 'bytes', - }, - { - name: 'replacementPattern', - type: 'bytes', - }, - { - name: 'staticTarget', - type: 'address', - }, - { - name: 'staticExtradata', - type: 'bytes', - }, - { - name: 'paymentToken', - type: 'address', - }, - { - name: 'basePrice', - type: 'uint256', - }, - { - name: 'extra', - type: 'uint256', - }, - { - name: 'listingTime', - type: 'uint256', - }, - { - name: 'expirationTime', - type: 'uint256', - }, - { - name: 'salt', - type: 'uint256', - }, - { - name: 'nonce', - type: 'uint256', - }, - ], - }, - domain: { - name: 'Wyvern Exchange Contract', - version: '2.3', - chainId: 1, - verifyingContract: '0x7f268357a8c2552623316e2562d90e642bb538e5', - }, - primaryType: 'Order', - message: { - maker: '0x44fa5d521a02db7ce5a88842a6842496f84009bc', - exchange: '0x7f268357a8c2552623316e2562d90e642bb538e5', - taker: '0x0000000000000000000000000000000000000000', - makerRelayerFee: '750', - takerRelayerFee: '0', - makerProtocolFee: '0', - takerProtocolFee: '0', - feeRecipient: '0x5b3256965e7c3cf26e11fcaf296dfc8807c01073', - feeMethod: 1, - side: 1, - saleKind: 0, - target: '0xbaf2127b49fc93cbca6269fade0f7f31df4c88a7', - howToCall: 1, - calldata: - '0xfb16a59500000000000000000000000044fa5d521a02db7ce5a88842a6842496f84009bc000000000000000000000000000000000000000000000000000000000000000000000000000000000000000001a9f037d4cd7da318ab097a47acd4dea3abc083000000000000000000000000000000000000000000000000000000000000028a000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000c00000000000000000000000000000000000000000000000000000000000000000', - replacementPattern: - '0x000000000000000000000000000000000000000000000000000000000000000000000000ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff00000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000', - staticTarget: '0x0000000000000000000000000000000000000000', - staticExtradata: '0x', - paymentToken: '0x0000000000000000000000000000000000000000', - basePrice: '1000000000000000000', - extra: '0', - listingTime: '1645233344', - expirationTime: '1645838240', - salt: '35033335384310326785897317545538185126505283328747281434561962939625063440824', - nonce: 0, - }, - }; - await runEthMsg(buildEthMsgReq(msg, 'eip712'), client); - }); + it('Should test a message with very large types', async () => { + const msg = { + types: { + EIP712Domain: [ + { + name: 'name', + type: 'string', + }, + { + name: 'version', + type: 'string', + }, + { + name: 'chainId', + type: 'uint256', + }, + { + name: 'verifyingContract', + type: 'address', + }, + ], + Order: [ + { + name: 'exchange', + type: 'address', + }, + { + name: 'maker', + type: 'address', + }, + { + name: 'taker', + type: 'address', + }, + { + name: 'makerRelayerFee', + type: 'uint256', + }, + { + name: 'takerRelayerFee', + type: 'uint256', + }, + { + name: 'makerProtocolFee', + type: 'uint256', + }, + { + name: 'takerProtocolFee', + type: 'uint256', + }, + { + name: 'feeRecipient', + type: 'address', + }, + { + name: 'feeMethod', + type: 'uint8', + }, + { + name: 'side', + type: 'uint8', + }, + { + name: 'saleKind', + type: 'uint8', + }, + { + name: 'target', + type: 'address', + }, + { + name: 'howToCall', + type: 'uint8', + }, + { + name: 'calldata', + type: 'bytes', + }, + { + name: 'replacementPattern', + type: 'bytes', + }, + { + name: 'staticTarget', + type: 'address', + }, + { + name: 'staticExtradata', + type: 'bytes', + }, + { + name: 'paymentToken', + type: 'address', + }, + { + name: 'basePrice', + type: 'uint256', + }, + { + name: 'extra', + type: 'uint256', + }, + { + name: 'listingTime', + type: 'uint256', + }, + { + name: 'expirationTime', + type: 'uint256', + }, + { + name: 'salt', + type: 'uint256', + }, + { + name: 'nonce', + type: 'uint256', + }, + ], + }, + domain: { + name: 'Wyvern Exchange Contract', + version: '2.3', + chainId: 1, + verifyingContract: '0x7f268357a8c2552623316e2562d90e642bb538e5', + }, + primaryType: 'Order', + message: { + maker: '0x44fa5d521a02db7ce5a88842a6842496f84009bc', + exchange: '0x7f268357a8c2552623316e2562d90e642bb538e5', + taker: '0x0000000000000000000000000000000000000000', + makerRelayerFee: '750', + takerRelayerFee: '0', + makerProtocolFee: '0', + takerProtocolFee: '0', + feeRecipient: '0x5b3256965e7c3cf26e11fcaf296dfc8807c01073', + feeMethod: 1, + side: 1, + saleKind: 0, + target: '0xbaf2127b49fc93cbca6269fade0f7f31df4c88a7', + howToCall: 1, + calldata: + '0xfb16a59500000000000000000000000044fa5d521a02db7ce5a88842a6842496f84009bc000000000000000000000000000000000000000000000000000000000000000000000000000000000000000001a9f037d4cd7da318ab097a47acd4dea3abc083000000000000000000000000000000000000000000000000000000000000028a000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000c00000000000000000000000000000000000000000000000000000000000000000', + replacementPattern: + '0x000000000000000000000000000000000000000000000000000000000000000000000000ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff00000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000', + staticTarget: '0x0000000000000000000000000000000000000000', + staticExtradata: '0x', + paymentToken: '0x0000000000000000000000000000000000000000', + basePrice: '1000000000000000000', + extra: '0', + listingTime: '1645233344', + expirationTime: '1645838240', + salt: '35033335384310326785897317545538185126505283328747281434561962939625063440824', + nonce: 0, + }, + } + await runEthMsg(buildEthMsgReq(msg, 'eip712'), client) + }) - it('Should test random edge case #1', async () => { - // This was a randomly generated payload which caused an edge case. - // It has been slimmed down but definition structure is preserved. - const msg = { - types: { - EIP712Domain: [ - { - name: 'name', - type: 'string', - }, - { - name: 'version', - type: 'string', - }, - { - name: 'chainId', - type: 'uint256', - }, - { - name: 'verifyingContract', - type: 'address', - }, - ], - Primary_Click: [ - { - name: 'utility', - type: 'Expose', - }, - { - name: 'aisle', - type: 'Cancel', - }, - { - name: 'gym', - type: 'Razor', - }, - { - name: 'drift_patch_cable_bi', - type: 'bytes1', - }, - ], - Expose: [ - { - name: 'favorite', - type: 'bytes21', - }, - ], - Cancel: [ - { - name: 'clever', - type: 'uint200', - }, - ], - Razor: [ - { - name: 'private', - type: 'bytes2', - }, - ], - }, - primaryType: 'Primary_Click', - domain: { - name: 'Domain_Avocado_luggage_twel', - version: '1', - chainId: '0x324e', - verifyingContract: '0x69f758a7911448c2f7aa6df15ca27d69ffa1c6b7', - }, - message: { - utility: { - favorite: '0x891b56dc6ab87ab73cf69761183d499283f1925871', - }, - aisle: { - clever: '0x0102', - }, - gym: { - private: '0xbb42', - }, - drift_patch_cable_bi: '0xb4', - }, - }; - await runEthMsg(buildEthMsgReq(msg, 'eip712'), client); - }); + it('Should test random edge case #1', async () => { + // This was a randomly generated payload which caused an edge case. + // It has been slimmed down but definition structure is preserved. + const msg = { + types: { + EIP712Domain: [ + { + name: 'name', + type: 'string', + }, + { + name: 'version', + type: 'string', + }, + { + name: 'chainId', + type: 'uint256', + }, + { + name: 'verifyingContract', + type: 'address', + }, + ], + Primary_Click: [ + { + name: 'utility', + type: 'Expose', + }, + { + name: 'aisle', + type: 'Cancel', + }, + { + name: 'gym', + type: 'Razor', + }, + { + name: 'drift_patch_cable_bi', + type: 'bytes1', + }, + ], + Expose: [ + { + name: 'favorite', + type: 'bytes21', + }, + ], + Cancel: [ + { + name: 'clever', + type: 'uint200', + }, + ], + Razor: [ + { + name: 'private', + type: 'bytes2', + }, + ], + }, + primaryType: 'Primary_Click', + domain: { + name: 'Domain_Avocado_luggage_twel', + version: '1', + chainId: '0x324e', + verifyingContract: '0x69f758a7911448c2f7aa6df15ca27d69ffa1c6b7', + }, + message: { + utility: { + favorite: '0x891b56dc6ab87ab73cf69761183d499283f1925871', + }, + aisle: { + clever: '0x0102', + }, + gym: { + private: '0xbb42', + }, + drift_patch_cable_bi: '0xb4', + }, + } + await runEthMsg(buildEthMsgReq(msg, 'eip712'), client) + }) - it('Signs long primary types', async () => { - const msg = { - types: { - EIP712Domain: [ - { - name: 'name', - type: 'string', - }, - { - name: 'version', - type: 'string', - }, - { - name: 'chainId', - type: 'uint256', - }, - { - name: 'verifyingContract', - type: 'address', - }, - ], - 'HyperliquidTransaction:ApproveAgent': [ - { - name: 'hyperliquidChain', - type: 'string', - }, - { - name: 'agentAddress', - type: 'address', - }, - { - name: 'agentName', - type: 'string', - }, - { - name: 'nonce', - type: 'uint64', - }, - ], - }, - primaryType: 'HyperliquidTransaction:ApproveAgent', - domain: { - name: 'HyperliquidSignTransaction', - version: '1', - chainId: 1, - verifyingContract: '0x0000000000000000000000000000000000000000', - }, - message: { - hyperliquidChain: 'Mainnet', - signatureChainId: '0x1', - agentAddress: '0x343ab48c498a5b71e93a0c4c6e7f783ee8950436', - agentName: '', - nonce: 1718376161247, - type: 'approveAgent', - }, - }; - await runEthMsg(buildEthMsgReq(msg, 'eip712'), client); - }); + it('Signs long primary types', async () => { + const msg = { + types: { + EIP712Domain: [ + { + name: 'name', + type: 'string', + }, + { + name: 'version', + type: 'string', + }, + { + name: 'chainId', + type: 'uint256', + }, + { + name: 'verifyingContract', + type: 'address', + }, + ], + 'HyperliquidTransaction:ApproveAgent': [ + { + name: 'hyperliquidChain', + type: 'string', + }, + { + name: 'agentAddress', + type: 'address', + }, + { + name: 'agentName', + type: 'string', + }, + { + name: 'nonce', + type: 'uint64', + }, + ], + }, + primaryType: 'HyperliquidTransaction:ApproveAgent', + domain: { + name: 'HyperliquidSignTransaction', + version: '1', + chainId: 1, + verifyingContract: '0x0000000000000000000000000000000000000000', + }, + message: { + hyperliquidChain: 'Mainnet', + signatureChainId: '0x1', + agentAddress: '0x343ab48c498a5b71e93a0c4c6e7f783ee8950436', + agentName: '', + nonce: 1718376161247, + type: 'approveAgent', + }, + } + await runEthMsg(buildEthMsgReq(msg, 'eip712'), client) + }) - describe('test 5 random payloads', () => { - for (let i = 0; i < 5; i++) { - it(`Payload #${i}`, async () => { - await runEthMsg( - buildEthMsgReq(buildRandomMsg('eip712', client), 'eip712'), - client, - ); - }); - } - }); - }); -}); + describe('test 5 random payloads', () => { + for (let i = 0; i < 5; i++) { + it(`Payload #${i}`, async () => { + await runEthMsg(buildEthMsgReq(buildRandomMsg('eip712', client), 'eip712'), client) + }) + } + }) + }) +}) diff --git a/packages/sdk/src/__test__/e2e/ethereum/addresses.test.ts b/packages/sdk/src/__test__/e2e/ethereum/addresses.test.ts index 641d810b..1c3342a4 100644 --- a/packages/sdk/src/__test__/e2e/ethereum/addresses.test.ts +++ b/packages/sdk/src/__test__/e2e/ethereum/addresses.test.ts @@ -1,20 +1,20 @@ -import { question } from 'readline-sync'; -import { pair } from '../../../api'; -import { fetchAddresses } from '../../../api/addresses'; -import { setupClient } from '../../utils/setup'; +import { question } from 'readline-sync' +import { pair } from '../../../api' +import { fetchAddresses } from '../../../api/addresses' +import { setupClient } from '../../utils/setup' describe('Ethereum Addresses', () => { - test('pair', async () => { - const isPaired = await setupClient(); - if (!isPaired) { - const secret = question('Please enter the pairing secret: '); - await pair(secret.toUpperCase()); - } - }); + test('pair', async () => { + const isPaired = await setupClient() + if (!isPaired) { + const secret = question('Please enter the pairing secret: ') + await pair(secret.toUpperCase()) + } + }) - test('Should fetch addressess', async () => { - const addresses = await fetchAddresses(); - expect(addresses.length).toBe(10); - expect(addresses.every((addr) => !addr.startsWith('11111'))).toBe(true); - }); -}); + test('Should fetch addressess', async () => { + const addresses = await fetchAddresses() + expect(addresses.length).toBe(10) + expect(addresses.every((addr) => !addr.startsWith('11111'))).toBe(true) + }) +}) diff --git a/packages/sdk/src/__test__/e2e/general.test.ts b/packages/sdk/src/__test__/e2e/general.test.ts index 5629d144..7629fc82 100644 --- a/packages/sdk/src/__test__/e2e/general.test.ts +++ b/packages/sdk/src/__test__/e2e/general.test.ts @@ -16,339 +16,275 @@ * the connection you can run this without any `env` params and it will attempt to * pair with a target Lattice. */ -import { createTx } from '@ethereumjs/tx'; -import { question } from 'readline-sync'; -import { HARDENED_OFFSET } from '../../constants'; -import { LatticeResponseCode, ProtocolConstants } from '../../protocol'; -import { randomBytes } from '../../util'; -import { buildEthSignRequest } from '../utils/builders'; -import { getDeviceId } from '../utils/getters'; -import { - BTC_COIN, - BTC_PURPOSE_P2PKH, - BTC_PURPOSE_P2SH_P2WPKH, - BTC_PURPOSE_P2WPKH, - BTC_TESTNET_COIN, - ETH_COIN, - setupTestClient, -} from '../utils/helpers'; +import { createTx } from '@ethereumjs/tx' +import { question } from 'readline-sync' +import { HARDENED_OFFSET } from '../../constants' +import { LatticeResponseCode, ProtocolConstants } from '../../protocol' +import { randomBytes } from '../../util' +import { buildEthSignRequest } from '../utils/builders' +import { getDeviceId } from '../utils/getters' +import { BTC_COIN, BTC_PURPOSE_P2PKH, BTC_PURPOSE_P2SH_P2WPKH, BTC_PURPOSE_P2WPKH, BTC_TESTNET_COIN, ETH_COIN, setupTestClient } from '../utils/helpers' -import { setupClient } from '../utils/setup'; +import { setupClient } from '../utils/setup' +import type { Client } from '../../client' -const id = getDeviceId(); +const id = getDeviceId() describe('General', () => { - let client; + let client: Client - beforeAll(async () => { - client = await setupClient(); - }); + beforeAll(async () => { + client = await setupClient() + }) - it('Should test SDK dehydration/rehydration', async () => { - const addrData = { - startPath: [BTC_PURPOSE_P2SH_P2WPKH, BTC_COIN, HARDENED_OFFSET, 0, 0], - n: 1, - }; + it('Should test SDK dehydration/rehydration', async () => { + const addrData = { + startPath: [BTC_PURPOSE_P2SH_P2WPKH, BTC_COIN, HARDENED_OFFSET, 0, 0], + n: 1, + } - const client1 = setupTestClient(); - await client1.connect(id); - expect(client1.isPaired).toBeTruthy(); - const addrs1 = await client1.getAddresses(addrData); + const client1 = setupTestClient() + await client1.connect(id) + expect(client1.isPaired).toBeTruthy() + const addrs1 = await client1.getAddresses(addrData) - const stateData = client1.getStateData(); + const stateData = client1.getStateData() - const client2 = setupTestClient(null, stateData); - await client2.connect(id); - expect(client2.isPaired).toBeTruthy(); - const addrs2 = await client2.getAddresses(addrData); + const client2 = setupTestClient(null, stateData) + await client2.connect(id) + expect(client2.isPaired).toBeTruthy() + const addrs2 = await client2.getAddresses(addrData) - expect(addrs1).toEqual(addrs2); - }); + expect(addrs1).toEqual(addrs2) + }) - it('Should get addresses', async () => { - await client.connect(id); - const fwConstants = client.getFwConstants(); - const addrData = { - startPath: [BTC_PURPOSE_P2SH_P2WPKH, BTC_COIN, HARDENED_OFFSET, 0, 0], - n: 5, - }; - let addrs; - // Bitcoin addresses - // NOTE: The format of address will be based on the user's Lattice settings - // By default, this will be P2SH(P2WPKH), i.e. addresses that start with `3` - addrs = await client.getAddresses(addrData); - expect(addrs.length).toEqual(5); - expect(addrs[0]?.[0]).toEqual('3'); + it('Should get addresses', async () => { + await client.connect(id) + const fwConstants = client.getFwConstants() + const addrData = { + startPath: [BTC_PURPOSE_P2SH_P2WPKH, BTC_COIN, HARDENED_OFFSET, 0, 0], + n: 5, + } + let addrs: string[] | undefined + // Bitcoin addresses + // NOTE: The format of address will be based on the user's Lattice settings + // By default, this will be P2SH(P2WPKH), i.e. addresses that start with `3` + addrs = await client.getAddresses(addrData) + expect(addrs.length).toEqual(5) + expect(addrs[0]?.[0]).toEqual('3') - // Ethereum addresses - addrData.startPath[0] = BTC_PURPOSE_P2PKH; - addrData.startPath[1] = ETH_COIN; - addrData.n = 1; - addrs = await client.getAddresses(addrData); - expect(addrs.length).toEqual(1); - expect(addrs[0]?.slice(0, 2)).toEqual('0x'); - // If firmware supports it, try shorter paths - if (fwConstants.flexibleAddrPaths) { - const flexData = { - startPath: [BTC_PURPOSE_P2PKH, ETH_COIN, HARDENED_OFFSET, 0], - n: 1, - }; - addrs = await client.getAddresses(flexData); - expect(addrs.length).toEqual(1); - expect(addrs[0]?.slice(0, 2)).toEqual('0x'); - } - // Should fail for non-EVM purpose and non-matching coin_type - addrData.n = 1; - try { - addrData.startPath[0] = BTC_PURPOSE_P2WPKH; - await client.getAddresses(addrData); - throw new Error(null); - } catch (err: any) { - expect(err.message).not.toEqual(null); - } - // Switch to BTC coin. Should work now. - addrData.startPath[1] = BTC_COIN; - // Bech32 - addrs = await client.getAddresses(addrData); - expect(addrs.length).toEqual(1); - expect(addrs[0]?.slice(0, 3)).to.be.oneOf(['bc1']); - addrData.startPath[0] = BTC_PURPOSE_P2SH_P2WPKH; - addrData.n = 5; + // Ethereum addresses + addrData.startPath[0] = BTC_PURPOSE_P2PKH + addrData.startPath[1] = ETH_COIN + addrData.n = 1 + addrs = await client.getAddresses(addrData) + expect(addrs.length).toEqual(1) + expect(addrs[0]?.slice(0, 2)).toEqual('0x') + // If firmware supports it, try shorter paths + if (fwConstants.flexibleAddrPaths) { + const flexData = { + startPath: [BTC_PURPOSE_P2PKH, ETH_COIN, HARDENED_OFFSET, 0], + n: 1, + } + addrs = await client.getAddresses(flexData) + expect(addrs.length).toEqual(1) + expect(addrs[0]?.slice(0, 2)).toEqual('0x') + } + // Should fail for non-EVM purpose and non-matching coin_type + addrData.n = 1 + try { + addrData.startPath[0] = BTC_PURPOSE_P2WPKH + await client.getAddresses(addrData) + throw new Error(null) + } catch (err: any) { + expect(err.message).not.toEqual(null) + } + // Switch to BTC coin. Should work now. + addrData.startPath[1] = BTC_COIN + // Bech32 + addrs = await client.getAddresses(addrData) + expect(addrs.length).toEqual(1) + expect(addrs[0]?.slice(0, 3)).to.be.oneOf(['bc1']) + addrData.startPath[0] = BTC_PURPOSE_P2SH_P2WPKH + addrData.n = 5 - addrData.startPath[4] = 1000000; - addrData.n = 3; - addrs = await client.getAddresses(addrData); - expect(addrs.length).toEqual(addrData.n); - addrData.startPath[4] = 0; - addrData.n = 1; + addrData.startPath[4] = 1000000 + addrData.n = 3 + addrs = await client.getAddresses(addrData) + expect(addrs.length).toEqual(addrData.n) + addrData.startPath[4] = 0 + addrData.n = 1 - // Unsupported purpose (m//) - addrData.startPath[0] = 0; // Purpose 0 -- undefined - try { - addrs = await client.getAddresses(addrData); - } catch (err: any) { - expect(err.message).not.toEqual(null); - } - addrData.startPath[0] = BTC_PURPOSE_P2SH_P2WPKH; + // Unsupported purpose (m//) + addrData.startPath[0] = 0 // Purpose 0 -- undefined + try { + addrs = await client.getAddresses(addrData) + } catch (err: any) { + expect(err.message).not.toEqual(null) + } + addrData.startPath[0] = BTC_PURPOSE_P2SH_P2WPKH - // Unsupported currency - addrData.startPath[1] = HARDENED_OFFSET + 5; // 5' currency - aka unknown - try { - addrs = await client.getAddresses(addrData); - throw new Error(null); - } catch (err: any) { - expect(err.message).not.toEqual(null); - } - addrData.startPath[1] = BTC_COIN; - // Too many addresses (n>10) - addrData.n = 11; - try { - addrs = await client.getAddresses(addrData); - throw new Error(null); - } catch (err: any) { - expect(err.message).not.toEqual(null); - } - }); + // Unsupported currency + addrData.startPath[1] = HARDENED_OFFSET + 5 // 5' currency - aka unknown + try { + addrs = await client.getAddresses(addrData) + throw new Error(null) + } catch (err: any) { + expect(err.message).not.toEqual(null) + } + addrData.startPath[1] = BTC_COIN + // Too many addresses (n>10) + addrData.n = 11 + try { + addrs = await client.getAddresses(addrData) + throw new Error(null) + } catch (err: any) { + expect(err.message).not.toEqual(null) + } + }) - describe('Should sign Ethereum transactions', () => { - it('should sign Legacy transactions', async () => { - const { req } = await buildEthSignRequest(client); - await client.sign(req); - }); + describe('Should sign Ethereum transactions', () => { + it('should sign Legacy transactions', async () => { + const { req } = await buildEthSignRequest(client) + await client.sign(req) + }) - it('should sign newer transactions', async () => { - const { txData, req, common } = await buildEthSignRequest(client, { - type: 1, - gasPrice: 1200000000, - nonce: 0, - gasLimit: 50000, - to: '0xe242e54155b1abc71fc118065270cecaaf8b7768', - value: 1000000000000, - data: '0x17e914679b7e160613be4f8c2d3203d236286d74eb9192f6d6f71b9118a42bb033ccd8e8', - }); - // NOTE: This will display a prehashed payload for bridged general signing - // requests because `ethMaxDataSz` represents the `data` field for legacy - // requests, but it represents the entire payload for general signing requests. - const tx = createTx(txData, { common }); - req.data.payload = tx.getMessageToSign(); - await client.sign(req); - }); + it('should sign newer transactions', async () => { + const { txData, req, common } = await buildEthSignRequest(client, { + type: 1, + gasPrice: 1200000000, + nonce: 0, + gasLimit: 50000, + to: '0xe242e54155b1abc71fc118065270cecaaf8b7768', + value: 1000000000000, + data: '0x17e914679b7e160613be4f8c2d3203d236286d74eb9192f6d6f71b9118a42bb033ccd8e8', + }) + // NOTE: This will display a prehashed payload for bridged general signing + // requests because `ethMaxDataSz` represents the `data` field for legacy + // requests, but it represents the entire payload for general signing requests. + const tx = createTx(txData, { common }) + req.data.payload = tx.getMessageToSign() + await client.sign(req) + }) - it('should sign bad transactions', async (ctx: any) => { - if (process.env.CI === '1') { - ctx.skip(); - return; - } - const { txData, req, maxDataSz, common } = - await buildEthSignRequest(client); - await question( - 'Please REJECT the next request if the warning screen displays. Press enter to continue.', - ); - txData.data = randomBytes(maxDataSz); - req.data.data = randomBytes(maxDataSz + 1); - const tx = createTx(txData, { common }); - req.data.payload = tx.getMessageToSign(); - await expect(client.sign(req)).rejects.toThrow( - `${ProtocolConstants.responseMsg[LatticeResponseCode.userDeclined]}`, - ); - }); - }); + it('should sign bad transactions', async (ctx: any) => { + if (process.env.CI === '1') { + ctx.skip() + return + } + const { txData, req, maxDataSz, common } = await buildEthSignRequest(client) + await question('Please REJECT the next request if the warning screen displays. Press enter to continue.') + txData.data = randomBytes(maxDataSz) + req.data.data = randomBytes(maxDataSz + 1) + const tx = createTx(txData, { common }) + req.data.payload = tx.getMessageToSign() + await expect(client.sign(req)).rejects.toThrow(`${ProtocolConstants.responseMsg[LatticeResponseCode.userDeclined]}`) + }) + }) - describe('Should sign Bitcoin transactions', () => { - it('Should sign legacy Bitcoin inputs', async () => { - const txData = { - prevOuts: [ - { - txHash: - '6e78493091f80d89a92ae3152df7fbfbdc44df09cf01a9b76c5113c02eaf2e0f', - value: 10000, - index: 1, - signerPath: [ - BTC_PURPOSE_P2SH_P2WPKH, - BTC_TESTNET_COIN, - HARDENED_OFFSET, - 0, - 0, - ], - }, - ], - recipient: 'mhifA1DwiMPHTjSJM8FFSL8ibrzWaBCkVT', - value: 1000, - fee: 1000, - // isSegwit: false, // old encoding - changePath: [ - BTC_PURPOSE_P2SH_P2WPKH, - BTC_TESTNET_COIN, - HARDENED_OFFSET, - 1, - 0, - ], - }; - const req = { - currency: 'BTC', - data: txData, - }; + describe('Should sign Bitcoin transactions', () => { + it('Should sign legacy Bitcoin inputs', async () => { + const txData = { + prevOuts: [ + { + txHash: '6e78493091f80d89a92ae3152df7fbfbdc44df09cf01a9b76c5113c02eaf2e0f', + value: 10000, + index: 1, + signerPath: [BTC_PURPOSE_P2SH_P2WPKH, BTC_TESTNET_COIN, HARDENED_OFFSET, 0, 0], + }, + ], + recipient: 'mhifA1DwiMPHTjSJM8FFSL8ibrzWaBCkVT', + value: 1000, + fee: 1000, + // isSegwit: false, // old encoding + changePath: [BTC_PURPOSE_P2SH_P2WPKH, BTC_TESTNET_COIN, HARDENED_OFFSET, 1, 0], + } + const req = { + currency: 'BTC', + data: txData, + } - // Sign a legit tx - const sigResp = await client.sign(req); - expect(sigResp.tx).not.toEqual(null); - expect(sigResp.txHash).not.toEqual(null); - }); + // Sign a legit tx + const sigResp = await client.sign(req) + expect(sigResp.tx).not.toEqual(null) + expect(sigResp.txHash).not.toEqual(null) + }) - it('Should sign wrapped segwit Bitcoin inputs', async () => { - const txData = { - prevOuts: [ - { - txHash: - 'ab8288ef207f11186af98db115aa7120aa36ceb783e8792fb7b2f39c88109a99', - value: 10000, - index: 1, - signerPath: [ - BTC_PURPOSE_P2SH_P2WPKH, - BTC_TESTNET_COIN, - HARDENED_OFFSET, - 0, - 0, - ], - }, - ], - recipient: '2NGZrVvZG92qGYqzTLjCAewvPZ7JE8S8VxE', - value: 1000, - fee: 1000, - changePath: [ - BTC_PURPOSE_P2SH_P2WPKH, - BTC_TESTNET_COIN, - HARDENED_OFFSET, - 1, - 0, - ], - }; - const req = { - currency: 'BTC', - data: txData, - }; - // Sign a legit tx - const sigResp = await client.sign(req); - expect(sigResp.tx).not.toEqual(null); - expect(sigResp.txHash).not.toEqual(null); - }); + it('Should sign wrapped segwit Bitcoin inputs', async () => { + const txData = { + prevOuts: [ + { + txHash: 'ab8288ef207f11186af98db115aa7120aa36ceb783e8792fb7b2f39c88109a99', + value: 10000, + index: 1, + signerPath: [BTC_PURPOSE_P2SH_P2WPKH, BTC_TESTNET_COIN, HARDENED_OFFSET, 0, 0], + }, + ], + recipient: '2NGZrVvZG92qGYqzTLjCAewvPZ7JE8S8VxE', + value: 1000, + fee: 1000, + changePath: [BTC_PURPOSE_P2SH_P2WPKH, BTC_TESTNET_COIN, HARDENED_OFFSET, 1, 0], + } + const req = { + currency: 'BTC', + data: txData, + } + // Sign a legit tx + const sigResp = await client.sign(req) + expect(sigResp.tx).not.toEqual(null) + expect(sigResp.txHash).not.toEqual(null) + }) - it('Should sign wrapped segwit Bitcoin inputs to a bech32 address', async () => { - const txData = { - prevOuts: [ - { - txHash: - 'f93d0a77f58b4274d84f427d647f1f27e38b4db79fd975691e15109fde7ea06e', - value: 1802440, - index: 1, - signerPath: [ - BTC_PURPOSE_P2SH_P2WPKH, - BTC_TESTNET_COIN, - HARDENED_OFFSET, - 1, - 0, - ], - }, - ], - recipient: 'tb1qym0z2a939lefrgw67ep5flhf43dvpg3h4s96tn', - value: 1000, - fee: 1000, - changePath: [ - BTC_PURPOSE_P2SH_P2WPKH, - BTC_TESTNET_COIN, - HARDENED_OFFSET, - 1, - 0, - ], - }; - const req = { - currency: 'BTC', - data: txData, - }; - // Sign a legit tx - const sigResp = await client.sign(req); - expect(sigResp.tx).not.toEqual(null); - expect(sigResp.txHash).not.toEqual(null); - }); + it('Should sign wrapped segwit Bitcoin inputs to a bech32 address', async () => { + const txData = { + prevOuts: [ + { + txHash: 'f93d0a77f58b4274d84f427d647f1f27e38b4db79fd975691e15109fde7ea06e', + value: 1802440, + index: 1, + signerPath: [BTC_PURPOSE_P2SH_P2WPKH, BTC_TESTNET_COIN, HARDENED_OFFSET, 1, 0], + }, + ], + recipient: 'tb1qym0z2a939lefrgw67ep5flhf43dvpg3h4s96tn', + value: 1000, + fee: 1000, + changePath: [BTC_PURPOSE_P2SH_P2WPKH, BTC_TESTNET_COIN, HARDENED_OFFSET, 1, 0], + } + const req = { + currency: 'BTC', + data: txData, + } + // Sign a legit tx + const sigResp = await client.sign(req) + expect(sigResp.tx).not.toEqual(null) + expect(sigResp.txHash).not.toEqual(null) + }) - it('Should sign an input from a native segwit account', async () => { - const txData = { - prevOuts: [ - { - txHash: - 'b2efdbdd3340d2bc547671ce3993a6f05d70343c07578f9d7f5626fdfc06fa35', - value: 76800, - index: 0, - signerPath: [ - BTC_PURPOSE_P2WPKH, - BTC_TESTNET_COIN, - HARDENED_OFFSET, - 0, - 0, - ], - }, - ], - recipient: '2N4gqWT4oqWL2gz9ps92z9fm2Bg3FUkqG7Q', - value: 70000, - fee: 4380, - isSegwit: true, - changePath: [ - BTC_PURPOSE_P2WPKH, - BTC_TESTNET_COIN, - HARDENED_OFFSET, - 1, - 0, - ], - }; - const req = { - currency: 'BTC', - data: txData, - }; - // Sign a legit tx - const sigResp = await client.sign(req); - expect(sigResp.tx).not.toEqual(null); - expect(sigResp.txHash).not.toEqual(null); - expect(sigResp.changeRecipient?.slice(0, 2)).toEqual('tb'); - }); - }); -}); + it('Should sign an input from a native segwit account', async () => { + const txData = { + prevOuts: [ + { + txHash: 'b2efdbdd3340d2bc547671ce3993a6f05d70343c07578f9d7f5626fdfc06fa35', + value: 76800, + index: 0, + signerPath: [BTC_PURPOSE_P2WPKH, BTC_TESTNET_COIN, HARDENED_OFFSET, 0, 0], + }, + ], + recipient: '2N4gqWT4oqWL2gz9ps92z9fm2Bg3FUkqG7Q', + value: 70000, + fee: 4380, + isSegwit: true, + changePath: [BTC_PURPOSE_P2WPKH, BTC_TESTNET_COIN, HARDENED_OFFSET, 1, 0], + } + const req = { + currency: 'BTC', + data: txData, + } + // Sign a legit tx + const sigResp = await client.sign(req) + expect(sigResp.tx).not.toEqual(null) + expect(sigResp.txHash).not.toEqual(null) + expect(sigResp.changeRecipient?.slice(0, 2)).toEqual('tb') + }) + }) +}) diff --git a/packages/sdk/src/__test__/e2e/kv.test.ts b/packages/sdk/src/__test__/e2e/kv.test.ts index 98a2a605..8b81fb58 100644 --- a/packages/sdk/src/__test__/e2e/kv.test.ts +++ b/packages/sdk/src/__test__/e2e/kv.test.ts @@ -3,265 +3,248 @@ * between a 64 byte key and a 64 byte value of any type. The main use case for these * at the time of writing is address tags. */ -import { question } from 'readline-sync'; -import { HARDENED_OFFSET } from '../../constants'; -import { LatticeResponseCode, ProtocolConstants } from '../../protocol'; -import { DEFAULT_SIGNER } from '../utils/builders'; -import { BTC_PURPOSE_P2PKH, ETH_COIN } from '../utils/helpers'; +import { question } from 'readline-sync' +import { HARDENED_OFFSET } from '../../constants' +import { LatticeResponseCode, ProtocolConstants } from '../../protocol' +import { DEFAULT_SIGNER } from '../utils/builders' +import { BTC_PURPOSE_P2PKH, ETH_COIN } from '../utils/helpers' -import { setupClient } from '../utils/setup'; +import { setupClient } from '../utils/setup' +import type { Client } from '../../client' // Random address to test the screen with. // IMPORTANT NOTE: For Ethereum addresses you should always add the lower case variety since // requests come in at lower case -const UNISWAP_ADDR = '0x7a250d5630B4cF539739dF2C5dAcb4c659F2488D'; -const UNISWAP_TAG = 'Uniswap V2 Router'; -const RANDOM_ADDR = '0x30da3d7A865C934b389c919c737510054111AB3A'; -const RANDOM_TAG = 'Test Address Name'; -let _numStartingRecords = 0; -let _fetchedRecords: any = []; +const UNISWAP_ADDR = '0x7a250d5630B4cF539739dF2C5dAcb4c659F2488D' +const UNISWAP_TAG = 'Uniswap V2 Router' +const RANDOM_ADDR = '0x30da3d7A865C934b389c919c737510054111AB3A' +const RANDOM_TAG = 'Test Address Name' +let _numStartingRecords = 0 +let _fetchedRecords: unknown[] = [] const ETH_REQ = { - currency: 'ETH', - data: { - signerPath: DEFAULT_SIGNER, - nonce: '0x02', - gasPrice: '0x1fe5d61a00', - gasLimit: '0x034e97', - to: RANDOM_ADDR, - value: '0x01cba1761f7ab9870c', - data: null, - chainId: 4, - }, -}; + currency: 'ETH', + data: { + signerPath: DEFAULT_SIGNER, + nonce: '0x02', + gasPrice: '0x1fe5d61a00', + gasLimit: '0x034e97', + to: RANDOM_ADDR, + value: '0x01cba1761f7ab9870c', + data: null, + chainId: 4, + }, +} describe('key-value', () => { - let client; - - beforeAll(async () => { - client = await setupClient(); - }); - - it('Should ask if the user wants to reset state', async () => { - let answer = 'Y'; - if (process.env.CI !== '1') { - answer = question( - 'Do you want to clear all kv records and start anew? (Y/N) ', - ); - } else { - answer = 'Y'; - } - if (answer.toUpperCase() === 'Y') { - const batchSize = 10; - let lastTotal: number | null = null; - let iterations = 0; - - while (iterations < 100) { - iterations += 1; - const data = await client.getKvRecords({ n: batchSize, start: 0 }); - console.log('[getKvRecords] data:', JSON.stringify(data, null, 2)); - - const { records = [], total = 0 } = data; - if (!records.length || total === 0) { - break; - } - - if (lastTotal !== null && total >= lastTotal) { - console.warn( - '[kv.test] KV cleanup halted to avoid infinite loop (no progress detected).', - ); - break; - } - - const ids = records - .slice(0, batchSize) - .map((record: any) => (record?.id ?? '').toString()) - .filter((id: string) => id.length > 0); - - if (!ids.length) { - break; - } - - await client.removeKvRecords({ type: 0, ids }); - if (total <= ids.length) { - break; - } - - lastTotal = total; - } - } - }); - - it('Should make a request to an unknown address', async () => { - await client.sign(ETH_REQ).catch((err) => { - expect(err.message).toContain( - ProtocolConstants.responseMsg[LatticeResponseCode.userDeclined], - ); - }); - }); - - it('Should get the initial set of records', async () => { - const resp = await client.getKvRecords({ n: 2, start: 0 }); - _numStartingRecords = resp.total; - }); - - it('Should add some key value records', async () => { - const records = { - [UNISWAP_ADDR]: UNISWAP_TAG, - [RANDOM_ADDR]: RANDOM_TAG, - }; - await client.addKvRecords({ records, caseSensitive: false, type: 0 }); - }); - - it('Should fail to add records with unicode characters', async () => { - const badKey = { '0x🔥🦍': 'Muh name' }; - const badVal = { UNISWAP_ADDR: 'val🔥🦍' }; - await expect(client.addKvRecords({ records: badKey })).rejects.toThrow( - 'Unicode characters are not supported.', - ); - await expect(client.addKvRecords({ records: badVal })).rejects.toThrow( - 'Unicode characters are not supported.', - ); - }); - - it('Should fail to add zero length keys and values', async () => { - const badKey = { '': 'Muh name' }; - const badVal = { UNISWAP_ADDR: '' }; - await expect(client.addKvRecords({ records: badKey })).rejects.toThrow( - 'Keys and values must be >0 characters.', - ); - await expect(client.addKvRecords({ records: badVal })).rejects.toThrow( - 'Keys and values must be >0 characters.', - ); - }); - - it('Should fetch the newly created records', async () => { - const opts = { - n: 2, - start: _numStartingRecords, - }; - const resp = await client.getKvRecords(opts); - const { records, total, fetched } = resp; - _fetchedRecords = records; - expect(total).toEqual(fetched + _numStartingRecords); - expect(records.length).toEqual(fetched); - expect(records.length).toEqual(2); - }); - - it('Should make a request to an address which is now known', async () => { - await client.sign(ETH_REQ); - }); - - it('Should make an EIP712 request that uses the record', async () => { - const msg = { - types: { - EIP712Domain: [ - { name: 'name', type: 'string' }, - { name: 'verifyingContract', type: 'address' }, - ], - Test: [ - { name: 'owner', type: 'string' }, - { name: 'ownerAddr', type: 'address' }, - ], - }, - domain: { - name: 'A Message', - verifyingContract: RANDOM_ADDR, - }, - primaryType: 'Test', - message: { - owner: RANDOM_ADDR, - ownerAddr: RANDOM_ADDR, - }, - }; - const req = { - currency: 'ETH_MSG', - data: { - signerPath: [BTC_PURPOSE_P2PKH, ETH_COIN, HARDENED_OFFSET, 0, 0], - protocol: 'eip712', - payload: msg, - }, - }; - await client.sign(req); - }); - - it('Should make a request with calldata', async () => { - // TODO: Add decoder data - const req = JSON.parse(JSON.stringify(ETH_REQ)); - req.data.data = `0x23b872dd00000000000000000000000057974eb88e50cc61049b44e43e90d3bc40fa61c0000000000000000000000000${RANDOM_ADDR.slice( - 2, - )}000000000000000000000000000000000000000000000000000000000000270f`; - await client.sign(req); - }); - - it('Should remove key value records', async () => { - const idsToRemove: any[] = []; - _fetchedRecords.forEach((r: any) => { - idsToRemove.push(r.id); - }); - await client.removeKvRecords({ ids: idsToRemove }); - }); - - it('Should confirm the records we recently added are removed', async () => { - const opts = { - n: 1, - start: _numStartingRecords, - }; - const resp = await client.getKvRecords(opts); - const { records, total, fetched } = resp; - expect(total).toEqual(_numStartingRecords); - expect(fetched).toEqual(0); - expect(records.length).toEqual(0); - }); - - it('Should add the same record with case sensitivity', async () => { - const records = { - [RANDOM_ADDR]: 'Test Address Name', - }; - await client.addKvRecords({ - records, - caseSensitive: true, - type: 0, - }); - }); - - it('Should make another request to make sure case sensitivity is enforced', async () => { - await client.sign(ETH_REQ).catch((err) => { - expect(err.message).toContain( - ProtocolConstants.responseMsg[LatticeResponseCode.userDeclined], - ); - }); - }); - - it('Should get the id of the newly added record', async () => { - const opts = { - n: 1, - start: _numStartingRecords, - }; - const resp: any = await client.getKvRecords(opts); - const { records, total, fetched } = resp; - expect(total).toEqual(_numStartingRecords + 1); - expect(fetched).toEqual(1); - expect(records.length).toEqual(1); - _fetchedRecords = records; - }); - - it('Should remove the new record', async () => { - const idsToRemove: any = []; - _fetchedRecords.forEach((r: any) => { - idsToRemove.push(r.id); - }); - await client.removeKvRecords({ ids: idsToRemove }); - }); - - it('Should confirm there are no new records', async () => { - const opts = { - n: 1, - start: _numStartingRecords, - }; - const resp: any = await client.getKvRecords(opts); - const { records, total, fetched } = resp; - expect(total).toEqual(_numStartingRecords); - expect(fetched).toEqual(0); - expect(records.length).toEqual(0); - }); -}); + let client: Client + + beforeAll(async () => { + client = await setupClient() + }) + + it('Should ask if the user wants to reset state', async () => { + let answer = 'Y' + if (process.env.CI !== '1') { + answer = question('Do you want to clear all kv records and start anew? (Y/N) ') + } else { + answer = 'Y' + } + if (answer.toUpperCase() === 'Y') { + const batchSize = 10 + let lastTotal: number | null = null + let iterations = 0 + + while (iterations < 100) { + iterations += 1 + const data = await client.getKvRecords({ n: batchSize, start: 0 }) + console.log('[getKvRecords] data:', JSON.stringify(data, null, 2)) + + const { records = [], total = 0 } = data + if (!records.length || total === 0) { + break + } + + if (lastTotal !== null && total >= lastTotal) { + console.warn('[kv.test] KV cleanup halted to avoid infinite loop (no progress detected).') + break + } + + const ids = records + .slice(0, batchSize) + .map((record: any) => (record?.id ?? '').toString()) + .filter((id: string) => id.length > 0) + + if (!ids.length) { + break + } + + await client.removeKvRecords({ type: 0, ids }) + if (total <= ids.length) { + break + } + + lastTotal = total + } + } + }) + + it('Should make a request to an unknown address', async () => { + await client.sign(ETH_REQ).catch((err) => { + expect(err.message).toContain(ProtocolConstants.responseMsg[LatticeResponseCode.userDeclined]) + }) + }) + + it('Should get the initial set of records', async () => { + const resp = await client.getKvRecords({ n: 2, start: 0 }) + _numStartingRecords = resp.total + }) + + it('Should add some key value records', async () => { + const records = { + [UNISWAP_ADDR]: UNISWAP_TAG, + [RANDOM_ADDR]: RANDOM_TAG, + } + await client.addKvRecords({ records, caseSensitive: false, type: 0 }) + }) + + it('Should fail to add records with unicode characters', async () => { + const badKey = { '0x🔥🦍': 'Muh name' } + const badVal = { UNISWAP_ADDR: 'val🔥🦍' } + await expect(client.addKvRecords({ records: badKey })).rejects.toThrow('Unicode characters are not supported.') + await expect(client.addKvRecords({ records: badVal })).rejects.toThrow('Unicode characters are not supported.') + }) + + it('Should fail to add zero length keys and values', async () => { + const badKey = { '': 'Muh name' } + const badVal = { UNISWAP_ADDR: '' } + await expect(client.addKvRecords({ records: badKey })).rejects.toThrow('Keys and values must be >0 characters.') + await expect(client.addKvRecords({ records: badVal })).rejects.toThrow('Keys and values must be >0 characters.') + }) + + it('Should fetch the newly created records', async () => { + const opts = { + n: 2, + start: _numStartingRecords, + } + const resp = await client.getKvRecords(opts) + const { records, total, fetched } = resp + _fetchedRecords = records + expect(total).toEqual(fetched + _numStartingRecords) + expect(records.length).toEqual(fetched) + expect(records.length).toEqual(2) + }) + + it('Should make a request to an address which is now known', async () => { + await client.sign(ETH_REQ) + }) + + it('Should make an EIP712 request that uses the record', async () => { + const msg = { + types: { + EIP712Domain: [ + { name: 'name', type: 'string' }, + { name: 'verifyingContract', type: 'address' }, + ], + Test: [ + { name: 'owner', type: 'string' }, + { name: 'ownerAddr', type: 'address' }, + ], + }, + domain: { + name: 'A Message', + verifyingContract: RANDOM_ADDR, + }, + primaryType: 'Test', + message: { + owner: RANDOM_ADDR, + ownerAddr: RANDOM_ADDR, + }, + } + const req = { + currency: 'ETH_MSG', + data: { + signerPath: [BTC_PURPOSE_P2PKH, ETH_COIN, HARDENED_OFFSET, 0, 0], + protocol: 'eip712', + payload: msg, + }, + } + await client.sign(req) + }) + + it('Should make a request with calldata', async () => { + // TODO: Add decoder data + const req = JSON.parse(JSON.stringify(ETH_REQ)) + req.data.data = `0x23b872dd00000000000000000000000057974eb88e50cc61049b44e43e90d3bc40fa61c0000000000000000000000000${RANDOM_ADDR.slice(2)}000000000000000000000000000000000000000000000000000000000000270f` + await client.sign(req) + }) + + it('Should remove key value records', async () => { + const idsToRemove: any[] = [] + _fetchedRecords.forEach((r: any) => { + idsToRemove.push(r.id) + }) + await client.removeKvRecords({ ids: idsToRemove }) + }) + + it('Should confirm the records we recently added are removed', async () => { + const opts = { + n: 1, + start: _numStartingRecords, + } + const resp = await client.getKvRecords(opts) + const { records, total, fetched } = resp + expect(total).toEqual(_numStartingRecords) + expect(fetched).toEqual(0) + expect(records.length).toEqual(0) + }) + + it('Should add the same record with case sensitivity', async () => { + const records = { + [RANDOM_ADDR]: 'Test Address Name', + } + await client.addKvRecords({ + records, + caseSensitive: true, + type: 0, + }) + }) + + it('Should make another request to make sure case sensitivity is enforced', async () => { + await client.sign(ETH_REQ).catch((err) => { + expect(err.message).toContain(ProtocolConstants.responseMsg[LatticeResponseCode.userDeclined]) + }) + }) + + it('Should get the id of the newly added record', async () => { + const opts = { + n: 1, + start: _numStartingRecords, + } + const resp: any = await client.getKvRecords(opts) + const { records, total, fetched } = resp + expect(total).toEqual(_numStartingRecords + 1) + expect(fetched).toEqual(1) + expect(records.length).toEqual(1) + _fetchedRecords = records + }) + + it('Should remove the new record', async () => { + const idsToRemove: any = [] + _fetchedRecords.forEach((r: any) => { + idsToRemove.push(r.id) + }) + await client.removeKvRecords({ ids: idsToRemove }) + }) + + it('Should confirm there are no new records', async () => { + const opts = { + n: 1, + start: _numStartingRecords, + } + const resp: any = await client.getKvRecords(opts) + const { records, total, fetched } = resp + expect(total).toEqual(_numStartingRecords) + expect(fetched).toEqual(0) + expect(records.length).toEqual(0) + }) +}) diff --git a/packages/sdk/src/__test__/e2e/non-exportable.test.ts b/packages/sdk/src/__test__/e2e/non-exportable.test.ts index cc95d543..82dd62be 100644 --- a/packages/sdk/src/__test__/e2e/non-exportable.test.ts +++ b/packages/sdk/src/__test__/e2e/non-exportable.test.ts @@ -19,160 +19,157 @@ * make sure you set `CONFIG:DEBUG>:ENABLE_A90=0` or else you will probably brick * your A90 chip. */ -import { Common, Hardfork, Mainnet } from '@ethereumjs/common'; -import { RLP } from '@ethereumjs/rlp'; -import { createTx } from '@ethereumjs/tx'; -import { question } from 'readline-sync'; -import { Constants } from '../..'; -import { DEFAULT_SIGNER } from '../utils/builders'; -import { getSigStr, validateSig } from '../utils/helpers'; -import { setupClient } from '../utils/setup'; +import { Common, Hardfork, Mainnet } from '@ethereumjs/common' +import { RLP } from '@ethereumjs/rlp' +import { createTx } from '@ethereumjs/tx' +import { question } from 'readline-sync' +import { Constants } from '../..' +import { DEFAULT_SIGNER } from '../utils/builders' +import { getSigStr, validateSig } from '../utils/helpers' +import { setupClient } from '../utils/setup' +import type { Client } from '../../client' -let runTests = true; +let runTests = true describe('Non-Exportable Seed', () => { - let client; + let client: Client - beforeAll(async () => { - client = await setupClient(); - }); + beforeAll(async () => { + client = await setupClient() + }) - describe('Setup', () => { - it('Should ask if the user wants to test a card with a non-exportable seed', async (ctx: any) => { - if (process.env.CI === '1') { - runTests = false; - ctx.skip(); - return; - } - // NOTE: non-exportable seeds were deprecated from the normal setup pathway in firmware v0.12.0 - const result = await question( - 'Do you have a non-exportable SafeCard seed loaded and wish to continue? (Y/N) ', - ); - if (result.toLowerCase() !== 'y') { - runTests = false; - } - }); - }); + describe('Setup', () => { + it('Should ask if the user wants to test a card with a non-exportable seed', async (ctx: any) => { + if (process.env.CI === '1') { + runTests = false + ctx.skip() + return + } + // NOTE: non-exportable seeds were deprecated from the normal setup pathway in firmware v0.12.0 + const result = await question('Do you have a non-exportable SafeCard seed loaded and wish to continue? (Y/N) ') + if (result.toLowerCase() !== 'y') { + runTests = false + } + }) + }) - describe('Test non-exportable seed on SafeCard', () => { - beforeEach((ctx: any) => { - if (!runTests) { - ctx.skip('Skipping tests due to lack of non-exportable seed SafeCard.'); - } - }); - it('Should test that ETH transaction sigs differ and validate on secp256k1', async () => { - // Test ETH transactions - const tx = createTx( - { - type: 2, - maxFeePerGas: 1200000000, - maxPriorityFeePerGas: 1200000000, - nonce: 0, - gasLimit: 50000, - to: '0xe242e54155b1abc71fc118065270cecaaf8b7768', - value: 100, - data: '0xdeadbeef', - }, - { - common: new Common({ - chain: Mainnet, - hardfork: Hardfork.London, - }), - }, - ); - const txReq = { - data: { - signerPath: DEFAULT_SIGNER, - payload: tx.getMessageToSign(), - curveType: Constants.SIGNING.CURVES.SECP256K1, - hashType: Constants.SIGNING.HASHES.KECCAK256, - encodingType: Constants.SIGNING.ENCODINGS.EVM, - }, - }; - // Validate that tx sigs are non-uniform - const unsignedMsg = tx.getMessageToSign(); - const unsigned = Array.isArray(unsignedMsg) - ? RLP.encode(unsignedMsg) - : unsignedMsg; - const tx1Resp = await client.sign(txReq); - validateSig(tx1Resp, unsigned); - const tx2Resp = await client.sign(txReq); - validateSig(tx2Resp, unsigned); - const tx3Resp = await client.sign(txReq); - validateSig(tx3Resp, unsigned); - const tx4Resp = await client.sign(txReq); - validateSig(tx4Resp, unsigned); - const tx5Resp = await client.sign(txReq); - validateSig(tx5Resp, unsigned); - // Check sig 1 - expect(getSigStr(tx1Resp, tx)).not.toEqual(getSigStr(tx2Resp, tx)); - expect(getSigStr(tx1Resp, tx)).not.toEqual(getSigStr(tx3Resp, tx)); - expect(getSigStr(tx1Resp, tx)).not.toEqual(getSigStr(tx4Resp, tx)); - expect(getSigStr(tx1Resp, tx)).not.toEqual(getSigStr(tx5Resp, tx)); - // Check sig 2 - expect(getSigStr(tx2Resp, tx)).not.toEqual(getSigStr(tx1Resp, tx)); - expect(getSigStr(tx2Resp, tx)).not.toEqual(getSigStr(tx3Resp, tx)); - expect(getSigStr(tx2Resp, tx)).not.toEqual(getSigStr(tx4Resp, tx)); - expect(getSigStr(tx2Resp, tx)).not.toEqual(getSigStr(tx5Resp, tx)); - // Check sig 3 - expect(getSigStr(tx3Resp, tx)).not.toEqual(getSigStr(tx1Resp, tx)); - expect(getSigStr(tx3Resp, tx)).not.toEqual(getSigStr(tx2Resp, tx)); - expect(getSigStr(tx3Resp, tx)).not.toEqual(getSigStr(tx4Resp, tx)); - expect(getSigStr(tx3Resp, tx)).not.toEqual(getSigStr(tx5Resp, tx)); - // Check sig 4 - expect(getSigStr(tx4Resp, tx)).not.toEqual(getSigStr(tx1Resp, tx)); - expect(getSigStr(tx4Resp, tx)).not.toEqual(getSigStr(tx2Resp, tx)); - expect(getSigStr(tx4Resp, tx)).not.toEqual(getSigStr(tx3Resp, tx)); - expect(getSigStr(tx4Resp, tx)).not.toEqual(getSigStr(tx5Resp, tx)); - // Check sig 5 - expect(getSigStr(tx5Resp, tx)).not.toEqual(getSigStr(tx1Resp, tx)); - expect(getSigStr(tx5Resp, tx)).not.toEqual(getSigStr(tx2Resp, tx)); - expect(getSigStr(tx5Resp, tx)).not.toEqual(getSigStr(tx3Resp, tx)); - expect(getSigStr(tx5Resp, tx)).not.toEqual(getSigStr(tx4Resp, tx)); - }); + describe('Test non-exportable seed on SafeCard', () => { + beforeEach((ctx: any) => { + if (!runTests) { + ctx.skip('Skipping tests due to lack of non-exportable seed SafeCard.') + } + }) + it('Should test that ETH transaction sigs differ and validate on secp256k1', async () => { + // Test ETH transactions + const tx = createTx( + { + type: 2, + maxFeePerGas: 1200000000, + maxPriorityFeePerGas: 1200000000, + nonce: 0, + gasLimit: 50000, + to: '0xe242e54155b1abc71fc118065270cecaaf8b7768', + value: 100, + data: '0xdeadbeef', + }, + { + common: new Common({ + chain: Mainnet, + hardfork: Hardfork.London, + }), + }, + ) + const txReq = { + data: { + signerPath: DEFAULT_SIGNER, + payload: tx.getMessageToSign(), + curveType: Constants.SIGNING.CURVES.SECP256K1, + hashType: Constants.SIGNING.HASHES.KECCAK256, + encodingType: Constants.SIGNING.ENCODINGS.EVM, + }, + } + // Validate that tx sigs are non-uniform + const unsignedMsg = tx.getMessageToSign() + const unsigned = Array.isArray(unsignedMsg) ? RLP.encode(unsignedMsg) : unsignedMsg + const tx1Resp = await client.sign(txReq) + validateSig(tx1Resp, unsigned) + const tx2Resp = await client.sign(txReq) + validateSig(tx2Resp, unsigned) + const tx3Resp = await client.sign(txReq) + validateSig(tx3Resp, unsigned) + const tx4Resp = await client.sign(txReq) + validateSig(tx4Resp, unsigned) + const tx5Resp = await client.sign(txReq) + validateSig(tx5Resp, unsigned) + // Check sig 1 + expect(getSigStr(tx1Resp, tx)).not.toEqual(getSigStr(tx2Resp, tx)) + expect(getSigStr(tx1Resp, tx)).not.toEqual(getSigStr(tx3Resp, tx)) + expect(getSigStr(tx1Resp, tx)).not.toEqual(getSigStr(tx4Resp, tx)) + expect(getSigStr(tx1Resp, tx)).not.toEqual(getSigStr(tx5Resp, tx)) + // Check sig 2 + expect(getSigStr(tx2Resp, tx)).not.toEqual(getSigStr(tx1Resp, tx)) + expect(getSigStr(tx2Resp, tx)).not.toEqual(getSigStr(tx3Resp, tx)) + expect(getSigStr(tx2Resp, tx)).not.toEqual(getSigStr(tx4Resp, tx)) + expect(getSigStr(tx2Resp, tx)).not.toEqual(getSigStr(tx5Resp, tx)) + // Check sig 3 + expect(getSigStr(tx3Resp, tx)).not.toEqual(getSigStr(tx1Resp, tx)) + expect(getSigStr(tx3Resp, tx)).not.toEqual(getSigStr(tx2Resp, tx)) + expect(getSigStr(tx3Resp, tx)).not.toEqual(getSigStr(tx4Resp, tx)) + expect(getSigStr(tx3Resp, tx)).not.toEqual(getSigStr(tx5Resp, tx)) + // Check sig 4 + expect(getSigStr(tx4Resp, tx)).not.toEqual(getSigStr(tx1Resp, tx)) + expect(getSigStr(tx4Resp, tx)).not.toEqual(getSigStr(tx2Resp, tx)) + expect(getSigStr(tx4Resp, tx)).not.toEqual(getSigStr(tx3Resp, tx)) + expect(getSigStr(tx4Resp, tx)).not.toEqual(getSigStr(tx5Resp, tx)) + // Check sig 5 + expect(getSigStr(tx5Resp, tx)).not.toEqual(getSigStr(tx1Resp, tx)) + expect(getSigStr(tx5Resp, tx)).not.toEqual(getSigStr(tx2Resp, tx)) + expect(getSigStr(tx5Resp, tx)).not.toEqual(getSigStr(tx3Resp, tx)) + expect(getSigStr(tx5Resp, tx)).not.toEqual(getSigStr(tx4Resp, tx)) + }) - it('Should test that ETH message sigs differ and validate on secp256k1', async () => { - // Validate that signPersonal message sigs are non-uniform - const msgReq = { - currency: 'ETH_MSG', - data: { - signerPath: DEFAULT_SIGNER, - protocol: 'signPersonal', - payload: 'test message', - }, - }; - // NOTE: This uses the legacy signing pathway, which validates the signature - // Once we move this to generic signing, we will need to validate these. - const msg1Resp = await client.sign(msgReq); - const msg2Resp = await client.sign(msgReq); - const msg3Resp = await client.sign(msgReq); - const msg4Resp = await client.sign(msgReq); - const msg5Resp = await client.sign(msgReq); - // Check sig 1 - expect(getSigStr(msg1Resp)).not.toEqual(getSigStr(msg2Resp)); - expect(getSigStr(msg1Resp)).not.toEqual(getSigStr(msg3Resp)); - expect(getSigStr(msg1Resp)).not.toEqual(getSigStr(msg4Resp)); - expect(getSigStr(msg1Resp)).not.toEqual(getSigStr(msg5Resp)); - // Check sig 2 - expect(getSigStr(msg2Resp)).not.toEqual(getSigStr(msg1Resp)); - expect(getSigStr(msg2Resp)).not.toEqual(getSigStr(msg3Resp)); - expect(getSigStr(msg2Resp)).not.toEqual(getSigStr(msg4Resp)); - expect(getSigStr(msg2Resp)).not.toEqual(getSigStr(msg5Resp)); - // Check sig 3 - expect(getSigStr(msg3Resp)).not.toEqual(getSigStr(msg1Resp)); - expect(getSigStr(msg3Resp)).not.toEqual(getSigStr(msg2Resp)); - expect(getSigStr(msg3Resp)).not.toEqual(getSigStr(msg4Resp)); - expect(getSigStr(msg3Resp)).not.toEqual(getSigStr(msg5Resp)); - // Check sig 4 - expect(getSigStr(msg4Resp)).not.toEqual(getSigStr(msg1Resp)); - expect(getSigStr(msg4Resp)).not.toEqual(getSigStr(msg2Resp)); - expect(getSigStr(msg4Resp)).not.toEqual(getSigStr(msg3Resp)); - expect(getSigStr(msg4Resp)).not.toEqual(getSigStr(msg5Resp)); - // Check sig 5 - expect(getSigStr(msg5Resp)).not.toEqual(getSigStr(msg1Resp)); - expect(getSigStr(msg5Resp)).not.toEqual(getSigStr(msg2Resp)); - expect(getSigStr(msg5Resp)).not.toEqual(getSigStr(msg3Resp)); - expect(getSigStr(msg5Resp)).not.toEqual(getSigStr(msg4Resp)); - }); - }); -}); + it('Should test that ETH message sigs differ and validate on secp256k1', async () => { + // Validate that signPersonal message sigs are non-uniform + const msgReq = { + currency: 'ETH_MSG', + data: { + signerPath: DEFAULT_SIGNER, + protocol: 'signPersonal', + payload: 'test message', + }, + } + // NOTE: This uses the legacy signing pathway, which validates the signature + // Once we move this to generic signing, we will need to validate these. + const msg1Resp = await client.sign(msgReq) + const msg2Resp = await client.sign(msgReq) + const msg3Resp = await client.sign(msgReq) + const msg4Resp = await client.sign(msgReq) + const msg5Resp = await client.sign(msgReq) + // Check sig 1 + expect(getSigStr(msg1Resp)).not.toEqual(getSigStr(msg2Resp)) + expect(getSigStr(msg1Resp)).not.toEqual(getSigStr(msg3Resp)) + expect(getSigStr(msg1Resp)).not.toEqual(getSigStr(msg4Resp)) + expect(getSigStr(msg1Resp)).not.toEqual(getSigStr(msg5Resp)) + // Check sig 2 + expect(getSigStr(msg2Resp)).not.toEqual(getSigStr(msg1Resp)) + expect(getSigStr(msg2Resp)).not.toEqual(getSigStr(msg3Resp)) + expect(getSigStr(msg2Resp)).not.toEqual(getSigStr(msg4Resp)) + expect(getSigStr(msg2Resp)).not.toEqual(getSigStr(msg5Resp)) + // Check sig 3 + expect(getSigStr(msg3Resp)).not.toEqual(getSigStr(msg1Resp)) + expect(getSigStr(msg3Resp)).not.toEqual(getSigStr(msg2Resp)) + expect(getSigStr(msg3Resp)).not.toEqual(getSigStr(msg4Resp)) + expect(getSigStr(msg3Resp)).not.toEqual(getSigStr(msg5Resp)) + // Check sig 4 + expect(getSigStr(msg4Resp)).not.toEqual(getSigStr(msg1Resp)) + expect(getSigStr(msg4Resp)).not.toEqual(getSigStr(msg2Resp)) + expect(getSigStr(msg4Resp)).not.toEqual(getSigStr(msg3Resp)) + expect(getSigStr(msg4Resp)).not.toEqual(getSigStr(msg5Resp)) + // Check sig 5 + expect(getSigStr(msg5Resp)).not.toEqual(getSigStr(msg1Resp)) + expect(getSigStr(msg5Resp)).not.toEqual(getSigStr(msg2Resp)) + expect(getSigStr(msg5Resp)).not.toEqual(getSigStr(msg3Resp)) + expect(getSigStr(msg5Resp)).not.toEqual(getSigStr(msg4Resp)) + }) + }) +}) diff --git a/packages/sdk/src/__test__/e2e/signing/bls.test.ts b/packages/sdk/src/__test__/e2e/signing/bls.test.ts index 5116377b..867a5fa5 100644 --- a/packages/sdk/src/__test__/e2e/signing/bls.test.ts +++ b/packages/sdk/src/__test__/e2e/signing/bls.test.ts @@ -15,218 +15,170 @@ * Running with a different mnemonic will cause test failures due to * incorrect key derivations. */ -import { - create as createKeystore, - decrypt as decryptKeystore, - isValidKeystore, - verifyPassword, -} from '@chainsafe/bls-keystore'; -import { getPublicKey, sign } from '@noble/bls12-381'; -import { deriveSeedTree } from 'bls12-381-keygen'; -import { question } from 'readline-sync'; - -import { Constants } from '../../../index'; -import { getPathStr } from '../../../shared/utilities'; -import { getEncPw } from '../../utils/getters'; -import { buildPath } from '../../utils/helpers'; -import { setupClient } from '../../utils/setup'; -import { TEST_SEED } from '../../utils/testConstants'; - -let client; -let encPw; -let supportsBLS; -const DEPOSIT_PATH = [12381, 3600, 0, 0, 0]; -const WITHDRAWAL_PATH = [12381, 3600, 0, 0]; +import { create as createKeystore, decrypt as decryptKeystore, isValidKeystore, verifyPassword } from '@chainsafe/bls-keystore' +import { getPublicKey, sign } from '@noble/bls12-381' +import { deriveSeedTree } from 'bls12-381-keygen' +import { question } from 'readline-sync' + +import type { Client } from '../../../client' +import { Constants } from '../../../index' +import { getPathStr } from '../../../shared/utilities' +import { getEncPw } from '../../utils/getters' +import { buildPath } from '../../utils/helpers' +import { setupClient } from '../../utils/setup' +import { TEST_SEED } from '../../utils/testConstants' + +let client: Client +let encPw: string | undefined +let supportsBLS: boolean | undefined +const DEPOSIT_PATH = [12381, 3600, 0, 0, 0] +const WITHDRAWAL_PATH = [12381, 3600, 0, 0] // Number of signers to test for each of deposit and withdrawal paths -const N_TEST_SIGS = 5; -const KNOWN_SEED = TEST_SEED; +const N_TEST_SIGS = 5 +const KNOWN_SEED = TEST_SEED describe('[BLS keys]', () => { - beforeAll(async () => { - client = await setupClient(); - if (process.env.CI) { - encPw = process.env.ENC_PW; - } else { - encPw = getEncPw(); - if (!encPw) { - encPw = await question('Enter your Lattice encryption password: '); - } - } - - // Check if firmware supports BLS (requires >= 0.17.0) - const fwVersion = client.fwVersion; - const versionStr = - fwVersion && fwVersion.length >= 3 - ? `${fwVersion[2]}.${fwVersion[1]}.${fwVersion[0]}` - : 'unknown'; - - console.log(`\n[BLS Test] Firmware version: ${versionStr}`); - console.log('[BLS Test] Raw fwVersion buffer:', fwVersion); - - const fwConstants = client.getFwConstants(); - console.log('[BLS Test] getAddressFlags:', fwConstants?.getAddressFlags); - console.log( - '[BLS Test] BLS12_381_G1_PUB constant:', - Constants.GET_ADDR_FLAGS.BLS12_381_G1_PUB, - ); - - supportsBLS = fwConstants?.getAddressFlags?.includes( - Constants.GET_ADDR_FLAGS.BLS12_381_G1_PUB, - ); - - console.log(`[BLS Test] supportsBLS: ${supportsBLS}\n`); - - if (!supportsBLS) { - console.warn( - `\nSkipping BLS tests: Firmware version ${versionStr} does not support BLS operations.\nBLS support requires firmware version >= 0.17.0\n`, - ); - } - }); - - it('Should validate exported EIP2335 keystores', async (ctx) => { - if (!supportsBLS) { - ctx.skip(); - return; - } - const req = { - schema: Constants.ENC_DATA.SCHEMAS.BLS_KEYSTORE_EIP2335_PBKDF_V4, - params: { - path: WITHDRAWAL_PATH, - c: 999, // if this is not specified, the default value will be used - }, - }; - let encData; - // Test custom iteration count (c) - encData = await client.fetchEncryptedData(req); - await validateExportedKeystore(KNOWN_SEED, req.params.path, encPw, encData); - // Test different paths - req.params.path = DEPOSIT_PATH; - encData = await client.fetchEncryptedData(req); - await validateExportedKeystore(KNOWN_SEED, req.params.path, encPw, encData); - req.params.path[4] = 1847; - encData = await client.fetchEncryptedData(req); - await validateExportedKeystore(KNOWN_SEED, req.params.path, encPw, encData); - // Test default values - req.params.path = DEPOSIT_PATH; - req.params.c = undefined; - encData = await client.fetchEncryptedData(req); - await validateExportedKeystore(KNOWN_SEED, req.params.path, encPw, encData); - }); - - for (let i = 0; i < N_TEST_SIGS; i++) { - describe(`[Validate Derived Signature #${i + 1}/${N_TEST_SIGS}]`, () => { - it(`Should validate derivation and signing at deposit index #${ - i + 1 - }`, async (ctx) => { - if (!supportsBLS) { - ctx.skip(); - return; - } - const depositPath = JSON.parse(JSON.stringify(DEPOSIT_PATH)); - depositPath[2] = i; - await testBLSDerivationAndSig(KNOWN_SEED, depositPath); - }); - - it(`Should validate derivation and signing at withdrawal index #${ - i + 1 - }`, async (ctx) => { - if (!supportsBLS) { - ctx.skip(); - return; - } - const withdrawalPath = JSON.parse(JSON.stringify(WITHDRAWAL_PATH)); - withdrawalPath[2] = i; - await testBLSDerivationAndSig(KNOWN_SEED, withdrawalPath); - }); - }); - } -}); + beforeAll(async () => { + client = await setupClient() + if (process.env.CI) { + encPw = process.env.ENC_PW + } else { + encPw = getEncPw() + if (!encPw) { + encPw = await question('Enter your Lattice encryption password: ') + } + } + + // Check if firmware supports BLS (requires >= 0.17.0) + const fwVersion = client.fwVersion + const versionStr = fwVersion && fwVersion.length >= 3 ? `${fwVersion[2]}.${fwVersion[1]}.${fwVersion[0]}` : 'unknown' + + console.log(`\n[BLS Test] Firmware version: ${versionStr}`) + console.log('[BLS Test] Raw fwVersion buffer:', fwVersion) + + const fwConstants = client.getFwConstants() + console.log('[BLS Test] getAddressFlags:', fwConstants?.getAddressFlags) + console.log('[BLS Test] BLS12_381_G1_PUB constant:', Constants.GET_ADDR_FLAGS.BLS12_381_G1_PUB) + + supportsBLS = fwConstants?.getAddressFlags?.includes(Constants.GET_ADDR_FLAGS.BLS12_381_G1_PUB) + + console.log(`[BLS Test] supportsBLS: ${supportsBLS}\n`) + + if (!supportsBLS) { + console.warn(`\nSkipping BLS tests: Firmware version ${versionStr} does not support BLS operations.\nBLS support requires firmware version >= 0.17.0\n`) + } + }) + + it('Should validate exported EIP2335 keystores', async (ctx) => { + if (!supportsBLS) { + ctx.skip() + return + } + const req = { + schema: Constants.ENC_DATA.SCHEMAS.BLS_KEYSTORE_EIP2335_PBKDF_V4, + params: { + path: WITHDRAWAL_PATH, + c: 999, // if this is not specified, the default value will be used + }, + } + let encData: unknown + // Test custom iteration count (c) + encData = await client.fetchEncryptedData(req) + await validateExportedKeystore(KNOWN_SEED, req.params.path, encPw, encData) + // Test different paths + req.params.path = DEPOSIT_PATH + encData = await client.fetchEncryptedData(req) + await validateExportedKeystore(KNOWN_SEED, req.params.path, encPw, encData) + req.params.path[4] = 1847 + encData = await client.fetchEncryptedData(req) + await validateExportedKeystore(KNOWN_SEED, req.params.path, encPw, encData) + // Test default values + req.params.path = DEPOSIT_PATH + req.params.c = undefined + encData = await client.fetchEncryptedData(req) + await validateExportedKeystore(KNOWN_SEED, req.params.path, encPw, encData) + }) + + for (let i = 0; i < N_TEST_SIGS; i++) { + describe(`[Validate Derived Signature #${i + 1}/${N_TEST_SIGS}]`, () => { + it(`Should validate derivation and signing at deposit index #${i + 1}`, async (ctx) => { + if (!supportsBLS) { + ctx.skip() + return + } + const depositPath = JSON.parse(JSON.stringify(DEPOSIT_PATH)) + depositPath[2] = i + await testBLSDerivationAndSig(KNOWN_SEED, depositPath) + }) + + it(`Should validate derivation and signing at withdrawal index #${i + 1}`, async (ctx) => { + if (!supportsBLS) { + ctx.skip() + return + } + const withdrawalPath = JSON.parse(JSON.stringify(WITHDRAWAL_PATH)) + withdrawalPath[2] = i + await testBLSDerivationAndSig(KNOWN_SEED, withdrawalPath) + }) + }) + } +}) //========================================================= // INTERNAL HELPERS //========================================================= async function getBLSPub(startPath) { - const pubs = await client.getAddresses({ - startPath, - flag: Constants.GET_ADDR_FLAGS.BLS12_381_G1_PUB, - }); - return pubs[0]; + const pubs = await client.getAddresses({ + startPath, + flag: Constants.GET_ADDR_FLAGS.BLS12_381_G1_PUB, + }) + return pubs[0] } async function signBLS(signerPath, message) { - const signReq = { - data: { - signerPath, - curveType: Constants.SIGNING.CURVES.BLS12_381_G2, - hashType: Constants.SIGNING.HASHES.NONE, - encodingType: Constants.SIGNING.ENCODINGS.NONE, - payload: message, - }, - }; - return await client.sign(signReq); + const signReq = { + data: { + signerPath, + curveType: Constants.SIGNING.CURVES.BLS12_381_G2, + hashType: Constants.SIGNING.HASHES.NONE, + encodingType: Constants.SIGNING.ENCODINGS.NONE, + payload: message, + }, + } + return await client.sign(signReq) } async function testBLSDerivationAndSig(seed, signerPath) { - const msg = Buffer.from('64726e3da8', 'hex'); - const priv = deriveSeedTree(seed, buildPath(signerPath)); - const latticePub = await getBLSPub(signerPath); - const latticeSig = await signBLS(signerPath, msg); - const refPub = getPublicKey(priv); - const refPubStr = Buffer.from(refPub).toString('hex'); - const refSig = await sign(msg, priv); - const refSigStr = Buffer.from(refSig).toString('hex'); - expect(latticePub.toString('hex')).to.equal( - refPubStr, - 'Deposit public key mismatch', - ); - expect(latticeSig.pubkey.toString('hex')).to.equal( - refPubStr, - 'Lattice signature returned wrong pubkey', - ); - expect(latticeSig.sig.toString('hex')).to.equal( - refSigStr, - 'Signature mismatch', - ); + const msg = Buffer.from('64726e3da8', 'hex') + const priv = deriveSeedTree(seed, buildPath(signerPath)) + const latticePub = await getBLSPub(signerPath) + const latticeSig = await signBLS(signerPath, msg) + const refPub = getPublicKey(priv) + const refPubStr = Buffer.from(refPub).toString('hex') + const refSig = await sign(msg, priv) + const refSigStr = Buffer.from(refSig).toString('hex') + expect(latticePub.toString('hex')).to.equal(refPubStr, 'Deposit public key mismatch') + expect(latticeSig.pubkey.toString('hex')).to.equal(refPubStr, 'Lattice signature returned wrong pubkey') + expect(latticeSig.sig.toString('hex')).to.equal(refSigStr, 'Signature mismatch') } async function validateExportedKeystore(seed, path, pw, expKeystoreBuffer) { - const exportedKeystore = JSON.parse(expKeystoreBuffer.toString()); - const priv = deriveSeedTree(seed, buildPath(path)); - const pub = getPublicKey(priv); - - // Validate the keystore in isolation - expect(isValidKeystore(exportedKeystore)).to.equal( - true, - 'Exported keystore invalid!', - ); - const expPwVerified = await verifyPassword(exportedKeystore, pw); - expect(expPwVerified).to.equal( - true, - `Password could not be verified in exported keystore. Expected "${pw}"`, - ); - const expDec = await decryptKeystore(exportedKeystore, pw); - expect(Buffer.from(expDec).toString('hex')).to.equal( - Buffer.from(priv).toString('hex'), - 'Exported keystore did not properly encrypt key!', - ); - expect(exportedKeystore.pubkey).to.equal( - Buffer.from(pub).toString('hex'), - 'Wrong public key exported from Lattice', - ); - - // Generate an independent keystore and compare decrypted contents - const genKeystore = await createKeystore(pw, priv, pub, getPathStr(path)); - expect(isValidKeystore(genKeystore)).to.equal( - true, - 'Generated keystore invalid?', - ); - const genPwVerified = await verifyPassword(genKeystore, pw); - expect(genPwVerified).to.equal( - true, - 'Password could not be verified in generated keystore?', - ); - const genDec = await decryptKeystore(genKeystore, pw); - expect(Buffer.from(expDec).toString('hex')).to.equal( - Buffer.from(genDec).toString('hex'), - 'Exported encrypted privkey did not match factory test example...', - ); + const exportedKeystore = JSON.parse(expKeystoreBuffer.toString()) + const priv = deriveSeedTree(seed, buildPath(path)) + const pub = getPublicKey(priv) + + // Validate the keystore in isolation + expect(isValidKeystore(exportedKeystore)).to.equal(true, 'Exported keystore invalid!') + const expPwVerified = await verifyPassword(exportedKeystore, pw) + expect(expPwVerified).to.equal(true, `Password could not be verified in exported keystore. Expected "${pw}"`) + const expDec = await decryptKeystore(exportedKeystore, pw) + expect(Buffer.from(expDec).toString('hex')).to.equal(Buffer.from(priv).toString('hex'), 'Exported keystore did not properly encrypt key!') + expect(exportedKeystore.pubkey).to.equal(Buffer.from(pub).toString('hex'), 'Wrong public key exported from Lattice') + + // Generate an independent keystore and compare decrypted contents + const genKeystore = await createKeystore(pw, priv, pub, getPathStr(path)) + expect(isValidKeystore(genKeystore)).to.equal(true, 'Generated keystore invalid?') + const genPwVerified = await verifyPassword(genKeystore, pw) + expect(genPwVerified).to.equal(true, 'Password could not be verified in generated keystore?') + const genDec = await decryptKeystore(genKeystore, pw) + expect(Buffer.from(expDec).toString('hex')).to.equal(Buffer.from(genDec).toString('hex'), 'Exported encrypted privkey did not match factory test example...') } diff --git a/packages/sdk/src/__test__/e2e/signing/determinism.test.ts b/packages/sdk/src/__test__/e2e/signing/determinism.test.ts index 51aae196..6ce538eb 100644 --- a/packages/sdk/src/__test__/e2e/signing/determinism.test.ts +++ b/packages/sdk/src/__test__/e2e/signing/determinism.test.ts @@ -1,19 +1,8 @@ -import { HARDENED_OFFSET } from '../../../constants'; -import type { WalletPath } from '../../../types'; -import { randomBytes } from '../../../util'; -import { - DEFAULT_SIGNER, - buildMsgReq, - buildRandomVectors, - buildTx, - buildTxReq, -} from '../../utils/builders'; -import { - deriveAddress, - signEip712JS, - signPersonalJS, - testUniformSigs, -} from '../../utils/determinism'; +import { HARDENED_OFFSET } from '../../../constants' +import type { WalletPath } from '../../../types' +import { randomBytes } from '../../../util' +import { DEFAULT_SIGNER, buildMsgReq, buildRandomVectors, buildTx, buildTxReq } from '../../utils/builders' +import { deriveAddress, signEip712JS, signPersonalJS, testUniformSigs } from '../../utils/determinism' /** * REQUIRED TEST MNEMONIC: * These tests require a SafeCard loaded with the standard test mnemonic: @@ -22,464 +11,384 @@ import { * Running with a different mnemonic will cause test failures due to * incorrect address derivations and signature mismatches. */ -import { getDeviceId } from '../../utils/getters'; -import { BTC_PURPOSE_P2PKH, ETH_COIN, getSigStr } from '../../utils/helpers'; -import { setupClient } from '../../utils/setup'; -import { TEST_SEED } from '../../utils/testConstants'; +import { getDeviceId } from '../../utils/getters' +import { BTC_PURPOSE_P2PKH, ETH_COIN, getSigStr } from '../../utils/helpers' +import { setupClient } from '../../utils/setup' +import { TEST_SEED } from '../../utils/testConstants' +import type { Client } from '../../../client' describe('[Determinism]', () => { - let client; - - beforeAll(async () => { - client = await setupClient(); - }); - - describe('Setup and validate seed', () => { - it('Should re-connect to the Lattice and update the walletUID.', async () => { - expect(getDeviceId()).to.not.equal(null); - await client.connect(getDeviceId()); - expect(client.isPaired).toEqual(true); - expect(!!client.getActiveWallet()).toEqual(true); - }); - - it('Should validate some Ledger addresses derived from the test seed', async () => { - const path0 = [ - BTC_PURPOSE_P2PKH, - ETH_COIN, - HARDENED_OFFSET, - 0, - 0, - ] as WalletPath; - const addr0 = deriveAddress(TEST_SEED, path0); - const path1 = [ - BTC_PURPOSE_P2PKH, - ETH_COIN, - HARDENED_OFFSET + 1, - 0, - 0, - ] as WalletPath; - const addr1 = deriveAddress(TEST_SEED, path1); - const path8 = [ - BTC_PURPOSE_P2PKH, - ETH_COIN, - HARDENED_OFFSET + 8, - 0, - 0, - ] as WalletPath; - const addr8 = deriveAddress(TEST_SEED, path8); - // Fetch these addresses from the Lattice and validate - - const req = { - currency: 'ETH', - startPath: path0, - n: 1, - }; - const latAddr0 = await client.getAddresses(req); - expect(latAddr0[0].toLowerCase()).toEqualElseLog( - addr0.toLowerCase(), - 'Incorrect address 0 fetched. Ensure your SafeCard is loaded with the test mnemonic: "test test test test test test test test test test test junk"', - ); - req.startPath = path1; - const latAddr1 = await client.getAddresses(req); - expect(latAddr1[0].toLowerCase()).toEqualElseLog( - addr1.toLowerCase(), - 'Incorrect address 1 fetched. Ensure your SafeCard is loaded with the test mnemonic: "test test test test test test test test test test test junk"', - ); - req.startPath = path8; - const latAddr8 = await client.getAddresses(req); - expect(latAddr8[0].toLowerCase()).toEqualElseLog( - addr8.toLowerCase(), - 'Incorrect address 8 fetched. Ensure your SafeCard is loaded with the test mnemonic: "test test test test test test test test test test test junk"', - ); - }); - }); - - describe('Test uniformity of Ethereum transaction sigs', () => { - it("Should validate uniformity sigs on m/44'/60'/0'/0/0", async () => { - const tx = buildTx(); - const txReq = buildTxReq(tx); - txReq.data.signerPath[2] = HARDENED_OFFSET; - await testUniformSigs(txReq, tx, client); - }); - - it("Should validate uniformity sigs on m/44'/60'/1'/0/0", async () => { - const tx = buildTx(); - const txReq = buildTxReq(tx); - txReq.data.signerPath[2] = HARDENED_OFFSET + 1; - await testUniformSigs(txReq, tx, client); - }); - - it("Should validate uniformity sigs on m/44'/60'/8'/0/0", async () => { - const tx = buildTx(); - const txReq = buildTxReq(tx); - txReq.data.signerPath[2] = HARDENED_OFFSET + 8; - await testUniformSigs(txReq, tx, client); - }); - - it("Should validate uniformity sigs on m/44'/60'/0'/0/0", async () => { - const tx = buildTx(`0x${randomBytes(4000).toString('hex')}`); - const txReq = buildTxReq(tx); - txReq.data.signerPath[2] = HARDENED_OFFSET; - await testUniformSigs(txReq, tx, client); - }); - - it("Should validate uniformity sigs on m/44'/60'/1'/0/0", async () => { - const tx = buildTx(`0x${randomBytes(4000).toString('hex')}`); - const txReq = buildTxReq(tx); - txReq.data.signerPath[2] = HARDENED_OFFSET + 1; - await testUniformSigs(txReq, tx, client); - }); - - it("Should validate uniformity sigs on m/44'/60'/8'/0/0", async () => { - const tx = buildTx(`0x${randomBytes(4000).toString('hex')}`); - const txReq = buildTxReq(tx); - txReq.data.signerPath[2] = HARDENED_OFFSET + 8; - await testUniformSigs(txReq, tx, client); - }); - }); - - describe('Compare personal_sign signatures vs Ledger vectors (1)', () => { - it('Should validate signature from addr0', async () => { - const msgReq = buildMsgReq(); - msgReq.data.signerPath[2] = HARDENED_OFFSET; - const res = await client.sign(msgReq); - const sig = getSigStr(res); - const jsSig = signPersonalJS(msgReq.data.payload, msgReq.data.signerPath); - expect(sig).toEqualElseLog( - jsSig, - 'Lattice sig does not match JS reference', - ); - }); - - it('Should validate signature from addr1', async () => { - const msgReq = buildMsgReq(); - msgReq.data.signerPath[2] = HARDENED_OFFSET + 1; - const res = await client.sign(msgReq); - const sig = getSigStr(res); - const jsSig = signPersonalJS(msgReq.data.payload, msgReq.data.signerPath); - expect(sig).toEqualElseLog( - jsSig, - 'Lattice sig does not match JS reference', - ); - }); - - it('Should validate signature from addr8', async () => { - const msgReq = buildMsgReq(); - msgReq.data.signerPath[2] = HARDENED_OFFSET + 8; - const res = await client.sign(msgReq); - const sig = getSigStr(res); - const jsSig = signPersonalJS(msgReq.data.payload, msgReq.data.signerPath); - expect(sig).toEqualElseLog( - jsSig, - 'Lattice sig does not match JS reference', - ); - }); - }); - - describe('Compare personal_sign signatures vs Ledger vectors (2)', () => { - it('Should validate signature from addr0', async () => { - const msgReq = buildMsgReq('hello ethereum this is another message'); - msgReq.data.signerPath[2] = HARDENED_OFFSET; - const res = await client.sign(msgReq); - const sig = getSigStr(res); - const jsSig = signPersonalJS(msgReq.data.payload, msgReq.data.signerPath); - expect(sig).toEqualElseLog( - jsSig, - 'Lattice sig does not match JS reference', - ); - }); - - it('Should validate signature from addr1', async () => { - const msgReq = buildMsgReq('hello ethereum this is another message'); - msgReq.data.signerPath[2] = HARDENED_OFFSET + 1; - const res = await client.sign(msgReq); - const sig = getSigStr(res); - const jsSig = signPersonalJS(msgReq.data.payload, msgReq.data.signerPath); - expect(sig).toEqualElseLog( - jsSig, - 'Lattice sig does not match JS reference', - ); - }); - - it('Should validate signature from addr8', async () => { - const msgReq = buildMsgReq('hello ethereum this is another message'); - msgReq.data.signerPath[2] = HARDENED_OFFSET + 8; - const res = await client.sign(msgReq); - const sig = getSigStr(res); - const jsSig = signPersonalJS(msgReq.data.payload, msgReq.data.signerPath); - expect(sig).toEqualElseLog( - jsSig, - 'Lattice sig does not match JS reference', - ); - }); - }); - - describe('Compare personal_sign signatures vs Ledger vectors (3)', () => { - it('Should validate signature from addr0', async () => { - const msgReq = buildMsgReq('third vector yo'); - msgReq.data.signerPath[2] = HARDENED_OFFSET; - const res = await client.sign(msgReq); - const sig = getSigStr(res); - const jsSig = signPersonalJS(msgReq.data.payload, msgReq.data.signerPath); - expect(sig).toEqualElseLog( - jsSig, - 'Lattice sig does not match JS reference', - ); - }); - - it('Should validate signature from addr1', async () => { - const msgReq = buildMsgReq('third vector yo'); - msgReq.data.signerPath[2] = HARDENED_OFFSET + 1; - const res = await client.sign(msgReq); - const sig = getSigStr(res); - const jsSig = signPersonalJS(msgReq.data.payload, msgReq.data.signerPath); - expect(sig).toEqualElseLog( - jsSig, - 'Lattice sig does not match JS reference', - ); - }); - - it('Should validate signature from addr8', async () => { - const msgReq = buildMsgReq('third vector yo'); - msgReq.data.signerPath[2] = HARDENED_OFFSET + 8; - const res = await client.sign(msgReq); - const sig = getSigStr(res); - const jsSig = signPersonalJS(msgReq.data.payload, msgReq.data.signerPath); - expect(sig).toEqualElseLog( - jsSig, - 'Lattice sig does not match JS reference', - ); - }); - }); - - describe('Compare EIP712 signatures vs Ledger vectors (1)', () => { - const msgReq = { - currency: 'ETH_MSG', - data: { - signerPath: DEFAULT_SIGNER, - protocol: 'eip712', - payload: { - types: { - Greeting: [ - { - name: 'salutation', - type: 'string', - }, - { - name: 'target', - type: 'string', - }, - { - name: 'born', - type: 'int32', - }, - ], - EIP712Domain: [ - { - name: 'chainId', - type: 'uint256', - }, - ], - }, - domain: { - chainId: 1, - }, - primaryType: 'Greeting', - message: { - salutation: 'Hello', - target: 'Ethereum', - born: '2015', - }, - }, - }, - }; - - it('Should validate signature from addr0', async () => { - msgReq.data.signerPath[2] = HARDENED_OFFSET; - const res = await client.sign(msgReq); - const sig = getSigStr(res); - const jsSig = signEip712JS(msgReq.data.payload, msgReq.data.signerPath); - expect(sig).toEqualElseLog( - jsSig, - 'Lattice EIP712 sig does not match JS reference', - ); - }); - - it('Should validate signature from addr1', async () => { - msgReq.data.signerPath[2] = HARDENED_OFFSET + 1; - const res = await client.sign(msgReq); - const sig = getSigStr(res); - const jsSig = signEip712JS(msgReq.data.payload, msgReq.data.signerPath); - expect(sig).toEqualElseLog( - jsSig, - 'Lattice EIP712 sig does not match JS reference', - ); - }); - - it('Should validate signature from addr8', async () => { - msgReq.data.signerPath[2] = HARDENED_OFFSET + 8; - const res = await client.sign(msgReq); - const sig = getSigStr(res); - const jsSig = signEip712JS(msgReq.data.payload, msgReq.data.signerPath); - expect(sig).toEqualElseLog( - jsSig, - 'Lattice EIP712 sig does not match JS reference', - ); - }); - }); - - describe('Compare EIP712 signatures vs Ledger vectors (2)', () => { - const msgReq = { - currency: 'ETH_MSG', - data: { - signerPath: DEFAULT_SIGNER, - protocol: 'eip712', - payload: { - types: { - MuhType: [ - { - name: 'thing', - type: 'string', - }, - ], - EIP712Domain: [ - { - name: 'chainId', - type: 'uint256', - }, - ], - }, - domain: { - chainId: 1, - }, - primaryType: 'MuhType', - message: { - thing: 'I am a string', - }, - }, - }, - }; - - it('Should validate signature from addr0', async () => { - msgReq.data.signerPath[2] = HARDENED_OFFSET; - const res = await client.sign(msgReq); - const sig = getSigStr(res); - const jsSig = signEip712JS(msgReq.data.payload, msgReq.data.signerPath); - expect(sig).toEqualElseLog( - jsSig, - 'Lattice EIP712 sig does not match JS reference', - ); - }); - - it('Should validate signature from addr1', async () => { - msgReq.data.signerPath[2] = HARDENED_OFFSET + 1; - const res = await client.sign(msgReq); - const sig = getSigStr(res); - const jsSig = signEip712JS(msgReq.data.payload, msgReq.data.signerPath); - expect(sig).toEqualElseLog( - jsSig, - 'Lattice EIP712 sig does not match JS reference', - ); - }); - - it('Should validate signature from addr8', async () => { - msgReq.data.signerPath[2] = HARDENED_OFFSET + 8; - const res = await client.sign(msgReq); - const sig = getSigStr(res); - const jsSig = signEip712JS(msgReq.data.payload, msgReq.data.signerPath); - expect(sig).toEqualElseLog( - jsSig, - 'Lattice EIP712 sig does not match JS reference', - ); - }); - }); - - describe('Compare EIP712 signatures vs Ledger vectors (3)', () => { - const msgReq = { - currency: 'ETH_MSG', - data: { - signerPath: DEFAULT_SIGNER, - protocol: 'eip712', - payload: { - types: { - MuhType: [ - { - name: 'numbawang', - type: 'uint32', - }, - ], - EIP712Domain: [ - { - name: 'chainId', - type: 'uint256', - }, - ], - }, - domain: { - chainId: 1, - }, - primaryType: 'MuhType', - message: { - numbawang: 999, - }, - }, - }, - }; - - it('Should validate signature from addr0', async () => { - msgReq.data.signerPath[2] = HARDENED_OFFSET; - const res = await client.sign(msgReq); - const sig = getSigStr(res); - const jsSig = signEip712JS(msgReq.data.payload, msgReq.data.signerPath); - expect(sig).toEqualElseLog( - jsSig, - 'Lattice EIP712 sig does not match JS reference', - ); - }); - it('Should validate signature from addr1', async () => { - msgReq.data.signerPath[2] = HARDENED_OFFSET + 1; - const res = await client.sign(msgReq); - const sig = getSigStr(res); - const jsSig = signEip712JS(msgReq.data.payload, msgReq.data.signerPath); - expect(sig).toEqualElseLog( - jsSig, - 'Lattice EIP712 sig does not match JS reference', - ); - }); - it('Should validate signature from addr8', async () => { - msgReq.data.signerPath[2] = HARDENED_OFFSET + 8; - const res = await client.sign(msgReq); - const sig = getSigStr(res); - const jsSig = signEip712JS(msgReq.data.payload, msgReq.data.signerPath); - expect(sig).toEqualElseLog( - jsSig, - 'Lattice EIP712 sig does not match JS reference', - ); - }); - }); - - describe('Test random personal_sign messages against JS signatures', () => { - const randomVectors = buildRandomVectors(); - const signerPathOffsets = [0, 1, 8]; - - randomVectors.forEach((payload, i) => { - signerPathOffsets.forEach(async (offset) => { - it(`Should test random vector: ${i} with offset ${offset}`, async () => { - const req = { - currency: 'ETH_MSG', - data: { - signerPath: DEFAULT_SIGNER, - protocol: 'signPersonal', - payload, - }, - }; - req.data.signerPath[2] = HARDENED_OFFSET + offset; - const jsSig = signPersonalJS(req.data.payload, req.data.signerPath); - const res = await client.sign(req); - const sig = getSigStr(res); - expect(sig).toEqualElseLog(jsSig, `Addr${offset} sig failed`); - }); - }); - }); - }); -}); + let client: Client + + beforeAll(async () => { + client = await setupClient() + }) + + describe('Setup and validate seed', () => { + it('Should re-connect to the Lattice and update the walletUID.', async () => { + expect(getDeviceId()).to.not.equal(null) + await client.connect(getDeviceId()) + expect(client.isPaired).toEqual(true) + expect(!!client.getActiveWallet()).toEqual(true) + }) + + it('Should validate some Ledger addresses derived from the test seed', async () => { + const path0 = [BTC_PURPOSE_P2PKH, ETH_COIN, HARDENED_OFFSET, 0, 0] as WalletPath + const addr0 = deriveAddress(TEST_SEED, path0) + const path1 = [BTC_PURPOSE_P2PKH, ETH_COIN, HARDENED_OFFSET + 1, 0, 0] as WalletPath + const addr1 = deriveAddress(TEST_SEED, path1) + const path8 = [BTC_PURPOSE_P2PKH, ETH_COIN, HARDENED_OFFSET + 8, 0, 0] as WalletPath + const addr8 = deriveAddress(TEST_SEED, path8) + // Fetch these addresses from the Lattice and validate + + const req = { + currency: 'ETH', + startPath: path0, + n: 1, + } + const latAddr0 = await client.getAddresses(req) + expect(latAddr0[0].toLowerCase()).toEqualElseLog(addr0.toLowerCase(), 'Incorrect address 0 fetched. Ensure your SafeCard is loaded with the test mnemonic: "test test test test test test test test test test test junk"') + req.startPath = path1 + const latAddr1 = await client.getAddresses(req) + expect(latAddr1[0].toLowerCase()).toEqualElseLog(addr1.toLowerCase(), 'Incorrect address 1 fetched. Ensure your SafeCard is loaded with the test mnemonic: "test test test test test test test test test test test junk"') + req.startPath = path8 + const latAddr8 = await client.getAddresses(req) + expect(latAddr8[0].toLowerCase()).toEqualElseLog(addr8.toLowerCase(), 'Incorrect address 8 fetched. Ensure your SafeCard is loaded with the test mnemonic: "test test test test test test test test test test test junk"') + }) + }) + + describe('Test uniformity of Ethereum transaction sigs', () => { + it("Should validate uniformity sigs on m/44'/60'/0'/0/0", async () => { + const tx = buildTx() + const txReq = buildTxReq(tx) + txReq.data.signerPath[2] = HARDENED_OFFSET + await testUniformSigs(txReq, tx, client) + }) + + it("Should validate uniformity sigs on m/44'/60'/1'/0/0", async () => { + const tx = buildTx() + const txReq = buildTxReq(tx) + txReq.data.signerPath[2] = HARDENED_OFFSET + 1 + await testUniformSigs(txReq, tx, client) + }) + + it("Should validate uniformity sigs on m/44'/60'/8'/0/0", async () => { + const tx = buildTx() + const txReq = buildTxReq(tx) + txReq.data.signerPath[2] = HARDENED_OFFSET + 8 + await testUniformSigs(txReq, tx, client) + }) + + it("Should validate uniformity sigs on m/44'/60'/0'/0/0", async () => { + const tx = buildTx(`0x${randomBytes(4000).toString('hex')}`) + const txReq = buildTxReq(tx) + txReq.data.signerPath[2] = HARDENED_OFFSET + await testUniformSigs(txReq, tx, client) + }) + + it("Should validate uniformity sigs on m/44'/60'/1'/0/0", async () => { + const tx = buildTx(`0x${randomBytes(4000).toString('hex')}`) + const txReq = buildTxReq(tx) + txReq.data.signerPath[2] = HARDENED_OFFSET + 1 + await testUniformSigs(txReq, tx, client) + }) + + it("Should validate uniformity sigs on m/44'/60'/8'/0/0", async () => { + const tx = buildTx(`0x${randomBytes(4000).toString('hex')}`) + const txReq = buildTxReq(tx) + txReq.data.signerPath[2] = HARDENED_OFFSET + 8 + await testUniformSigs(txReq, tx, client) + }) + }) + + describe('Compare personal_sign signatures vs Ledger vectors (1)', () => { + it('Should validate signature from addr0', async () => { + const msgReq = buildMsgReq() + msgReq.data.signerPath[2] = HARDENED_OFFSET + const res = await client.sign(msgReq) + const sig = getSigStr(res) + const jsSig = signPersonalJS(msgReq.data.payload, msgReq.data.signerPath) + expect(sig).toEqualElseLog(jsSig, 'Lattice sig does not match JS reference') + }) + + it('Should validate signature from addr1', async () => { + const msgReq = buildMsgReq() + msgReq.data.signerPath[2] = HARDENED_OFFSET + 1 + const res = await client.sign(msgReq) + const sig = getSigStr(res) + const jsSig = signPersonalJS(msgReq.data.payload, msgReq.data.signerPath) + expect(sig).toEqualElseLog(jsSig, 'Lattice sig does not match JS reference') + }) + + it('Should validate signature from addr8', async () => { + const msgReq = buildMsgReq() + msgReq.data.signerPath[2] = HARDENED_OFFSET + 8 + const res = await client.sign(msgReq) + const sig = getSigStr(res) + const jsSig = signPersonalJS(msgReq.data.payload, msgReq.data.signerPath) + expect(sig).toEqualElseLog(jsSig, 'Lattice sig does not match JS reference') + }) + }) + + describe('Compare personal_sign signatures vs Ledger vectors (2)', () => { + it('Should validate signature from addr0', async () => { + const msgReq = buildMsgReq('hello ethereum this is another message') + msgReq.data.signerPath[2] = HARDENED_OFFSET + const res = await client.sign(msgReq) + const sig = getSigStr(res) + const jsSig = signPersonalJS(msgReq.data.payload, msgReq.data.signerPath) + expect(sig).toEqualElseLog(jsSig, 'Lattice sig does not match JS reference') + }) + + it('Should validate signature from addr1', async () => { + const msgReq = buildMsgReq('hello ethereum this is another message') + msgReq.data.signerPath[2] = HARDENED_OFFSET + 1 + const res = await client.sign(msgReq) + const sig = getSigStr(res) + const jsSig = signPersonalJS(msgReq.data.payload, msgReq.data.signerPath) + expect(sig).toEqualElseLog(jsSig, 'Lattice sig does not match JS reference') + }) + + it('Should validate signature from addr8', async () => { + const msgReq = buildMsgReq('hello ethereum this is another message') + msgReq.data.signerPath[2] = HARDENED_OFFSET + 8 + const res = await client.sign(msgReq) + const sig = getSigStr(res) + const jsSig = signPersonalJS(msgReq.data.payload, msgReq.data.signerPath) + expect(sig).toEqualElseLog(jsSig, 'Lattice sig does not match JS reference') + }) + }) + + describe('Compare personal_sign signatures vs Ledger vectors (3)', () => { + it('Should validate signature from addr0', async () => { + const msgReq = buildMsgReq('third vector yo') + msgReq.data.signerPath[2] = HARDENED_OFFSET + const res = await client.sign(msgReq) + const sig = getSigStr(res) + const jsSig = signPersonalJS(msgReq.data.payload, msgReq.data.signerPath) + expect(sig).toEqualElseLog(jsSig, 'Lattice sig does not match JS reference') + }) + + it('Should validate signature from addr1', async () => { + const msgReq = buildMsgReq('third vector yo') + msgReq.data.signerPath[2] = HARDENED_OFFSET + 1 + const res = await client.sign(msgReq) + const sig = getSigStr(res) + const jsSig = signPersonalJS(msgReq.data.payload, msgReq.data.signerPath) + expect(sig).toEqualElseLog(jsSig, 'Lattice sig does not match JS reference') + }) + + it('Should validate signature from addr8', async () => { + const msgReq = buildMsgReq('third vector yo') + msgReq.data.signerPath[2] = HARDENED_OFFSET + 8 + const res = await client.sign(msgReq) + const sig = getSigStr(res) + const jsSig = signPersonalJS(msgReq.data.payload, msgReq.data.signerPath) + expect(sig).toEqualElseLog(jsSig, 'Lattice sig does not match JS reference') + }) + }) + + describe('Compare EIP712 signatures vs Ledger vectors (1)', () => { + const msgReq = { + currency: 'ETH_MSG', + data: { + signerPath: DEFAULT_SIGNER, + protocol: 'eip712', + payload: { + types: { + Greeting: [ + { + name: 'salutation', + type: 'string', + }, + { + name: 'target', + type: 'string', + }, + { + name: 'born', + type: 'int32', + }, + ], + EIP712Domain: [ + { + name: 'chainId', + type: 'uint256', + }, + ], + }, + domain: { + chainId: 1, + }, + primaryType: 'Greeting', + message: { + salutation: 'Hello', + target: 'Ethereum', + born: '2015', + }, + }, + }, + } + + it('Should validate signature from addr0', async () => { + msgReq.data.signerPath[2] = HARDENED_OFFSET + const res = await client.sign(msgReq) + const sig = getSigStr(res) + const jsSig = signEip712JS(msgReq.data.payload, msgReq.data.signerPath) + expect(sig).toEqualElseLog(jsSig, 'Lattice EIP712 sig does not match JS reference') + }) + + it('Should validate signature from addr1', async () => { + msgReq.data.signerPath[2] = HARDENED_OFFSET + 1 + const res = await client.sign(msgReq) + const sig = getSigStr(res) + const jsSig = signEip712JS(msgReq.data.payload, msgReq.data.signerPath) + expect(sig).toEqualElseLog(jsSig, 'Lattice EIP712 sig does not match JS reference') + }) + + it('Should validate signature from addr8', async () => { + msgReq.data.signerPath[2] = HARDENED_OFFSET + 8 + const res = await client.sign(msgReq) + const sig = getSigStr(res) + const jsSig = signEip712JS(msgReq.data.payload, msgReq.data.signerPath) + expect(sig).toEqualElseLog(jsSig, 'Lattice EIP712 sig does not match JS reference') + }) + }) + + describe('Compare EIP712 signatures vs Ledger vectors (2)', () => { + const msgReq = { + currency: 'ETH_MSG', + data: { + signerPath: DEFAULT_SIGNER, + protocol: 'eip712', + payload: { + types: { + MuhType: [ + { + name: 'thing', + type: 'string', + }, + ], + EIP712Domain: [ + { + name: 'chainId', + type: 'uint256', + }, + ], + }, + domain: { + chainId: 1, + }, + primaryType: 'MuhType', + message: { + thing: 'I am a string', + }, + }, + }, + } + + it('Should validate signature from addr0', async () => { + msgReq.data.signerPath[2] = HARDENED_OFFSET + const res = await client.sign(msgReq) + const sig = getSigStr(res) + const jsSig = signEip712JS(msgReq.data.payload, msgReq.data.signerPath) + expect(sig).toEqualElseLog(jsSig, 'Lattice EIP712 sig does not match JS reference') + }) + + it('Should validate signature from addr1', async () => { + msgReq.data.signerPath[2] = HARDENED_OFFSET + 1 + const res = await client.sign(msgReq) + const sig = getSigStr(res) + const jsSig = signEip712JS(msgReq.data.payload, msgReq.data.signerPath) + expect(sig).toEqualElseLog(jsSig, 'Lattice EIP712 sig does not match JS reference') + }) + + it('Should validate signature from addr8', async () => { + msgReq.data.signerPath[2] = HARDENED_OFFSET + 8 + const res = await client.sign(msgReq) + const sig = getSigStr(res) + const jsSig = signEip712JS(msgReq.data.payload, msgReq.data.signerPath) + expect(sig).toEqualElseLog(jsSig, 'Lattice EIP712 sig does not match JS reference') + }) + }) + + describe('Compare EIP712 signatures vs Ledger vectors (3)', () => { + const msgReq = { + currency: 'ETH_MSG', + data: { + signerPath: DEFAULT_SIGNER, + protocol: 'eip712', + payload: { + types: { + MuhType: [ + { + name: 'numbawang', + type: 'uint32', + }, + ], + EIP712Domain: [ + { + name: 'chainId', + type: 'uint256', + }, + ], + }, + domain: { + chainId: 1, + }, + primaryType: 'MuhType', + message: { + numbawang: 999, + }, + }, + }, + } + + it('Should validate signature from addr0', async () => { + msgReq.data.signerPath[2] = HARDENED_OFFSET + const res = await client.sign(msgReq) + const sig = getSigStr(res) + const jsSig = signEip712JS(msgReq.data.payload, msgReq.data.signerPath) + expect(sig).toEqualElseLog(jsSig, 'Lattice EIP712 sig does not match JS reference') + }) + it('Should validate signature from addr1', async () => { + msgReq.data.signerPath[2] = HARDENED_OFFSET + 1 + const res = await client.sign(msgReq) + const sig = getSigStr(res) + const jsSig = signEip712JS(msgReq.data.payload, msgReq.data.signerPath) + expect(sig).toEqualElseLog(jsSig, 'Lattice EIP712 sig does not match JS reference') + }) + it('Should validate signature from addr8', async () => { + msgReq.data.signerPath[2] = HARDENED_OFFSET + 8 + const res = await client.sign(msgReq) + const sig = getSigStr(res) + const jsSig = signEip712JS(msgReq.data.payload, msgReq.data.signerPath) + expect(sig).toEqualElseLog(jsSig, 'Lattice EIP712 sig does not match JS reference') + }) + }) + + describe('Test random personal_sign messages against JS signatures', () => { + const randomVectors = buildRandomVectors() + const signerPathOffsets = [0, 1, 8] + + randomVectors.forEach((payload, i) => { + signerPathOffsets.forEach(async (offset) => { + it(`Should test random vector: ${i} with offset ${offset}`, async () => { + const req = { + currency: 'ETH_MSG', + data: { + signerPath: DEFAULT_SIGNER, + protocol: 'signPersonal', + payload, + }, + } + req.data.signerPath[2] = HARDENED_OFFSET + offset + const jsSig = signPersonalJS(req.data.payload, req.data.signerPath) + const res = await client.sign(req) + const sig = getSigStr(res) + expect(sig).toEqualElseLog(jsSig, `Addr${offset} sig failed`) + }) + }) + }) + }) +}) diff --git a/packages/sdk/src/__test__/e2e/signing/eip712-msg.test.ts b/packages/sdk/src/__test__/e2e/signing/eip712-msg.test.ts index 8fec45b5..9ae54d2f 100644 --- a/packages/sdk/src/__test__/e2e/signing/eip712-msg.test.ts +++ b/packages/sdk/src/__test__/e2e/signing/eip712-msg.test.ts @@ -1,21 +1,21 @@ -import { setupClient } from '../../utils/setup'; +import { setupClient } from '../../utils/setup' /** * EIP-712 Typed Data Message Signing Test Suite * * Tests EIP-712 message signing compatibility between Lattice and viem. * Replaces the forge-based contract test with a pure signature comparison approach. */ -import { signAndCompareEIP712Message } from '../../utils/viemComparison'; -import { EIP712_MESSAGE_VECTORS } from './eip712-vectors'; +import { signAndCompareEIP712Message } from '../../utils/viemComparison' +import { EIP712_MESSAGE_VECTORS } from './eip712-vectors' describe('EIP-712 Message Signing - Viem Compatibility', () => { - beforeAll(async () => { - await setupClient(); - }); + beforeAll(async () => { + await setupClient() + }) - EIP712_MESSAGE_VECTORS.forEach((vector, index) => { - it(`${vector.name} (${index + 1}/${EIP712_MESSAGE_VECTORS.length})`, async () => { - await signAndCompareEIP712Message(vector.message, vector.name); - }); - }); -}); + EIP712_MESSAGE_VECTORS.forEach((vector, index) => { + it(`${vector.name} (${index + 1}/${EIP712_MESSAGE_VECTORS.length})`, async () => { + await signAndCompareEIP712Message(vector.message, vector.name) + }) + }) +}) diff --git a/packages/sdk/src/__test__/e2e/signing/eip712-vectors.ts b/packages/sdk/src/__test__/e2e/signing/eip712-vectors.ts index 2a06886c..c111796b 100644 --- a/packages/sdk/src/__test__/e2e/signing/eip712-vectors.ts +++ b/packages/sdk/src/__test__/e2e/signing/eip712-vectors.ts @@ -5,317 +5,313 @@ * Each vector contains domain, types, primaryType, and message data. */ -import type { EIP712TestMessage } from '../../utils/viemComparison'; +import type { EIP712TestMessage } from '../../utils/viemComparison' // Mock contract address for EIP-712 domain -const MOCK_CONTRACT_ADDRESS = '0x1234567890123456789012345678901234567890'; +const MOCK_CONTRACT_ADDRESS = '0x1234567890123456789012345678901234567890' export const EIP712_MESSAGE_VECTORS: Array<{ - name: string; - message: EIP712TestMessage; + name: string + message: EIP712TestMessage }> = [ - { - name: 'Negative Amount - Basic negative integer', - message: { - domain: { - name: 'NegativeAmountHandler', - version: '1', - chainId: 1, - verifyingContract: MOCK_CONTRACT_ADDRESS, - }, - types: { - Data: [ - { name: 'amount', type: 'int256' }, - { name: 'message', type: 'string' }, - ], - }, - primaryType: 'Data', - message: { - amount: -100, - message: 'Negative payment test', - }, - }, - }, - { - name: 'Negative Amount - Large negative value', - message: { - domain: { - name: 'NegativeAmountHandler', - version: '1', - chainId: 1, - verifyingContract: MOCK_CONTRACT_ADDRESS, - }, - types: { - Data: [ - { name: 'amount', type: 'int256' }, - { name: 'message', type: 'string' }, - ], - }, - primaryType: 'Data', - message: { - amount: -1000000000000, - message: 'Large negative amount', - }, - }, - }, - { - name: 'Negative Amount - Zero value', - message: { - domain: { - name: 'NegativeAmountHandler', - version: '1', - chainId: 1, - verifyingContract: MOCK_CONTRACT_ADDRESS, - }, - types: { - Data: [ - { name: 'amount', type: 'int256' }, - { name: 'message', type: 'string' }, - ], - }, - primaryType: 'Data', - message: { - amount: 0, - message: 'Zero amount test', - }, - }, - }, - { - name: 'Negative Amount - Positive value', - message: { - domain: { - name: 'NegativeAmountHandler', - version: '1', - chainId: 1, - verifyingContract: MOCK_CONTRACT_ADDRESS, - }, - types: { - Data: [ - { name: 'amount', type: 'int256' }, - { name: 'message', type: 'string' }, - ], - }, - primaryType: 'Data', - message: { - amount: 500, - message: 'Positive amount test', - }, - }, - }, - { - name: 'Mail - Basic email structure', - message: { - domain: { - name: 'Ether Mail', - version: '1', - chainId: 1, - verifyingContract: MOCK_CONTRACT_ADDRESS, - }, - types: { - Person: [ - { name: 'name', type: 'string' }, - { name: 'wallet', type: 'address' }, - ], - Mail: [ - { name: 'from', type: 'Person' }, - { name: 'to', type: 'Person' }, - { name: 'contents', type: 'string' }, - ], - }, - primaryType: 'Mail', - message: { - from: { - name: 'Alice', - wallet: '0xCD2a3d9F938E13CD947Ec05AbC7FE734Df8DD826', - }, - to: { - name: 'Bob', - wallet: '0xbBbBBBBbbBBBbbbBbbBbbbbBBbBbbbbBbBbbBBbB', - }, - contents: 'Hello, Bob!', - }, - }, - }, - { - name: 'Permit - ERC20 approval', - message: { - domain: { - name: 'USD Coin', - version: '2', - chainId: 1, - verifyingContract: MOCK_CONTRACT_ADDRESS, - }, - types: { - Permit: [ - { name: 'owner', type: 'address' }, - { name: 'spender', type: 'address' }, - { name: 'value', type: 'uint256' }, - { name: 'nonce', type: 'uint256' }, - { name: 'deadline', type: 'uint256' }, - ], - }, - primaryType: 'Permit', - message: { - owner: '0xCD2a3d9F938E13CD947Ec05AbC7FE734Df8DD826', - spender: '0x1234567890123456789012345678901234567890', - value: 1000000, - nonce: 0, - deadline: 9999999999, - }, - }, - }, - { - name: 'Vote - Governance voting', - message: { - domain: { - name: 'Governance', - version: '1', - chainId: 1, - verifyingContract: MOCK_CONTRACT_ADDRESS, - }, - types: { - Vote: [ - { name: 'proposalId', type: 'uint256' }, - { name: 'support', type: 'bool' }, - { name: 'voter', type: 'address' }, - ], - }, - primaryType: 'Vote', - message: { - proposalId: 42, - support: true, - voter: '0xCD2a3d9F938E13CD947Ec05AbC7FE734Df8DD826', - }, - }, - }, - { - name: 'Complex Types - Nested structures', - message: { - domain: { - name: 'Complex Protocol', - version: '1', - chainId: 1, - verifyingContract: MOCK_CONTRACT_ADDRESS, - }, - types: { - Asset: [ - { name: 'token', type: 'address' }, - { name: 'amount', type: 'uint256' }, - ], - Order: [ - { name: 'trader', type: 'address' }, - { name: 'baseAsset', type: 'Asset' }, - { name: 'quoteAsset', type: 'Asset' }, - { name: 'deadline', type: 'uint256' }, - ], - }, - primaryType: 'Order', - message: { - trader: '0xCD2a3d9F938E13CD947Ec05AbC7FE734Df8DD826', - baseAsset: { - token: '0x1234567890123456789012345678901234567890', - amount: 1000, - }, - quoteAsset: { - token: '0xabcdabcdabcdabcdabcdabcdabcdabcdabcdabcd', - amount: 2000, - }, - deadline: 9999999999, - }, - }, - }, - { - name: 'Edge Case - Empty string', - message: { - domain: { - name: 'Test', - version: '1', - chainId: 1, - verifyingContract: MOCK_CONTRACT_ADDRESS, - }, - types: { - Data: [{ name: 'value', type: 'string' }], - }, - primaryType: 'Data', - message: { - value: '', - }, - }, - }, - { - name: 'Edge Case - Maximum uint256', - message: { - domain: { - name: 'Test', - version: '1', - chainId: 1, - verifyingContract: MOCK_CONTRACT_ADDRESS, - }, - types: { - Data: [{ name: 'value', type: 'uint256' }], - }, - primaryType: 'Data', - message: { - value: BigInt( - '115792089237316195423570985008687907853269984665640564039457584007913129639935', - ), - }, - }, - }, - { - name: 'Edge Case - Minimum int256', - message: { - domain: { - name: 'Test', - version: '1', - chainId: 1, - verifyingContract: MOCK_CONTRACT_ADDRESS, - }, - types: { - Data: [{ name: 'value', type: 'int256' }], - }, - primaryType: 'Data', - message: { - value: BigInt( - '-57896044618658097711785492504343953926634992332820282019728792003956564819968', - ), - }, - }, - }, - { - name: 'Different Chain - Polygon', - message: { - domain: { - name: 'Test', - version: '1', - chainId: 137, - verifyingContract: MOCK_CONTRACT_ADDRESS, - }, - types: { - Data: [{ name: 'message', type: 'string' }], - }, - primaryType: 'Data', - message: { - message: 'Polygon test', - }, - }, - }, - { - name: 'Different Chain - BSC', - message: { - domain: { - name: 'Test', - version: '1', - chainId: 56, - verifyingContract: MOCK_CONTRACT_ADDRESS, - }, - types: { - Data: [{ name: 'message', type: 'string' }], - }, - primaryType: 'Data', - message: { - message: 'BSC test', - }, - }, - }, -]; + { + name: 'Negative Amount - Basic negative integer', + message: { + domain: { + name: 'NegativeAmountHandler', + version: '1', + chainId: 1, + verifyingContract: MOCK_CONTRACT_ADDRESS, + }, + types: { + Data: [ + { name: 'amount', type: 'int256' }, + { name: 'message', type: 'string' }, + ], + }, + primaryType: 'Data', + message: { + amount: -100, + message: 'Negative payment test', + }, + }, + }, + { + name: 'Negative Amount - Large negative value', + message: { + domain: { + name: 'NegativeAmountHandler', + version: '1', + chainId: 1, + verifyingContract: MOCK_CONTRACT_ADDRESS, + }, + types: { + Data: [ + { name: 'amount', type: 'int256' }, + { name: 'message', type: 'string' }, + ], + }, + primaryType: 'Data', + message: { + amount: -1000000000000, + message: 'Large negative amount', + }, + }, + }, + { + name: 'Negative Amount - Zero value', + message: { + domain: { + name: 'NegativeAmountHandler', + version: '1', + chainId: 1, + verifyingContract: MOCK_CONTRACT_ADDRESS, + }, + types: { + Data: [ + { name: 'amount', type: 'int256' }, + { name: 'message', type: 'string' }, + ], + }, + primaryType: 'Data', + message: { + amount: 0, + message: 'Zero amount test', + }, + }, + }, + { + name: 'Negative Amount - Positive value', + message: { + domain: { + name: 'NegativeAmountHandler', + version: '1', + chainId: 1, + verifyingContract: MOCK_CONTRACT_ADDRESS, + }, + types: { + Data: [ + { name: 'amount', type: 'int256' }, + { name: 'message', type: 'string' }, + ], + }, + primaryType: 'Data', + message: { + amount: 500, + message: 'Positive amount test', + }, + }, + }, + { + name: 'Mail - Basic email structure', + message: { + domain: { + name: 'Ether Mail', + version: '1', + chainId: 1, + verifyingContract: MOCK_CONTRACT_ADDRESS, + }, + types: { + Person: [ + { name: 'name', type: 'string' }, + { name: 'wallet', type: 'address' }, + ], + Mail: [ + { name: 'from', type: 'Person' }, + { name: 'to', type: 'Person' }, + { name: 'contents', type: 'string' }, + ], + }, + primaryType: 'Mail', + message: { + from: { + name: 'Alice', + wallet: '0xCD2a3d9F938E13CD947Ec05AbC7FE734Df8DD826', + }, + to: { + name: 'Bob', + wallet: '0xbBbBBBBbbBBBbbbBbbBbbbbBBbBbbbbBbBbbBBbB', + }, + contents: 'Hello, Bob!', + }, + }, + }, + { + name: 'Permit - ERC20 approval', + message: { + domain: { + name: 'USD Coin', + version: '2', + chainId: 1, + verifyingContract: MOCK_CONTRACT_ADDRESS, + }, + types: { + Permit: [ + { name: 'owner', type: 'address' }, + { name: 'spender', type: 'address' }, + { name: 'value', type: 'uint256' }, + { name: 'nonce', type: 'uint256' }, + { name: 'deadline', type: 'uint256' }, + ], + }, + primaryType: 'Permit', + message: { + owner: '0xCD2a3d9F938E13CD947Ec05AbC7FE734Df8DD826', + spender: '0x1234567890123456789012345678901234567890', + value: 1000000, + nonce: 0, + deadline: 9999999999, + }, + }, + }, + { + name: 'Vote - Governance voting', + message: { + domain: { + name: 'Governance', + version: '1', + chainId: 1, + verifyingContract: MOCK_CONTRACT_ADDRESS, + }, + types: { + Vote: [ + { name: 'proposalId', type: 'uint256' }, + { name: 'support', type: 'bool' }, + { name: 'voter', type: 'address' }, + ], + }, + primaryType: 'Vote', + message: { + proposalId: 42, + support: true, + voter: '0xCD2a3d9F938E13CD947Ec05AbC7FE734Df8DD826', + }, + }, + }, + { + name: 'Complex Types - Nested structures', + message: { + domain: { + name: 'Complex Protocol', + version: '1', + chainId: 1, + verifyingContract: MOCK_CONTRACT_ADDRESS, + }, + types: { + Asset: [ + { name: 'token', type: 'address' }, + { name: 'amount', type: 'uint256' }, + ], + Order: [ + { name: 'trader', type: 'address' }, + { name: 'baseAsset', type: 'Asset' }, + { name: 'quoteAsset', type: 'Asset' }, + { name: 'deadline', type: 'uint256' }, + ], + }, + primaryType: 'Order', + message: { + trader: '0xCD2a3d9F938E13CD947Ec05AbC7FE734Df8DD826', + baseAsset: { + token: '0x1234567890123456789012345678901234567890', + amount: 1000, + }, + quoteAsset: { + token: '0xabcdabcdabcdabcdabcdabcdabcdabcdabcdabcd', + amount: 2000, + }, + deadline: 9999999999, + }, + }, + }, + { + name: 'Edge Case - Empty string', + message: { + domain: { + name: 'Test', + version: '1', + chainId: 1, + verifyingContract: MOCK_CONTRACT_ADDRESS, + }, + types: { + Data: [{ name: 'value', type: 'string' }], + }, + primaryType: 'Data', + message: { + value: '', + }, + }, + }, + { + name: 'Edge Case - Maximum uint256', + message: { + domain: { + name: 'Test', + version: '1', + chainId: 1, + verifyingContract: MOCK_CONTRACT_ADDRESS, + }, + types: { + Data: [{ name: 'value', type: 'uint256' }], + }, + primaryType: 'Data', + message: { + value: BigInt('115792089237316195423570985008687907853269984665640564039457584007913129639935'), + }, + }, + }, + { + name: 'Edge Case - Minimum int256', + message: { + domain: { + name: 'Test', + version: '1', + chainId: 1, + verifyingContract: MOCK_CONTRACT_ADDRESS, + }, + types: { + Data: [{ name: 'value', type: 'int256' }], + }, + primaryType: 'Data', + message: { + value: BigInt('-57896044618658097711785492504343953926634992332820282019728792003956564819968'), + }, + }, + }, + { + name: 'Different Chain - Polygon', + message: { + domain: { + name: 'Test', + version: '1', + chainId: 137, + verifyingContract: MOCK_CONTRACT_ADDRESS, + }, + types: { + Data: [{ name: 'message', type: 'string' }], + }, + primaryType: 'Data', + message: { + message: 'Polygon test', + }, + }, + }, + { + name: 'Different Chain - BSC', + message: { + domain: { + name: 'Test', + version: '1', + chainId: 56, + verifyingContract: MOCK_CONTRACT_ADDRESS, + }, + types: { + Data: [{ name: 'message', type: 'string' }], + }, + primaryType: 'Data', + message: { + message: 'BSC test', + }, + }, + }, +] diff --git a/packages/sdk/src/__test__/e2e/signing/evm-abi.test.ts b/packages/sdk/src/__test__/e2e/signing/evm-abi.test.ts index 9eb36624..f5496db5 100644 --- a/packages/sdk/src/__test__/e2e/signing/evm-abi.test.ts +++ b/packages/sdk/src/__test__/e2e/signing/evm-abi.test.ts @@ -3,25 +3,25 @@ * These transactions use contract addresses so the device can fetch ABI data dynamically. */ -import { sign } from '../../../api'; -import { setupClient } from '../../utils/setup'; -import { ABI_TEST_VECTORS } from '../../vectors/abi-vectors'; +import { sign } from '../../../api' +import { setupClient } from '../../utils/setup' +import { ABI_TEST_VECTORS } from '../../vectors/abi-vectors' describe('[EVM ABI] ABI Decoding Tests', () => { - beforeAll(async () => { - await setupClient(); - }); + beforeAll(async () => { + await setupClient() + }) - describe('ABI Patterns with Complex Structures', () => { - ABI_TEST_VECTORS.forEach((testCase, index) => { - it(`Should test ${testCase.name} (${index + 1}/${ABI_TEST_VECTORS.length})`, async () => { - const result = await sign(testCase.tx); - expect(result).toBeDefined(); - expect(result.sig).toBeDefined(); - expect(result.sig.r).toBeDefined(); - expect(result.sig.s).toBeDefined(); - expect(result.sig.v).toBeDefined(); - }); - }); - }); -}); + describe('ABI Patterns with Complex Structures', () => { + ABI_TEST_VECTORS.forEach((testCase, index) => { + it(`Should test ${testCase.name} (${index + 1}/${ABI_TEST_VECTORS.length})`, async () => { + const result = await sign(testCase.tx) + expect(result).toBeDefined() + expect(result.sig).toBeDefined() + expect(result.sig.r).toBeDefined() + expect(result.sig.s).toBeDefined() + expect(result.sig.v).toBeDefined() + }) + }) + }) +}) diff --git a/packages/sdk/src/__test__/e2e/signing/evm-tx.test.ts b/packages/sdk/src/__test__/e2e/signing/evm-tx.test.ts index e5d52bc4..e37aabbe 100644 --- a/packages/sdk/src/__test__/e2e/signing/evm-tx.test.ts +++ b/packages/sdk/src/__test__/e2e/signing/evm-tx.test.ts @@ -6,58 +6,52 @@ * This replaces all individual EVM test files to avoid duplication and provide unified testing. */ -import { setupClient } from '../../utils/setup'; -import { signAndCompareTransaction } from '../../utils/viemComparison'; -import { - EDGE_CASE_TEST_VECTORS, - EIP1559_TEST_VECTORS, - EIP2930_TEST_VECTORS, - EIP7702_TEST_VECTORS, - LEGACY_VECTORS, -} from './vectors'; +import { setupClient } from '../../utils/setup' +import { signAndCompareTransaction } from '../../utils/viemComparison' +import { EDGE_CASE_TEST_VECTORS, EIP1559_TEST_VECTORS, EIP2930_TEST_VECTORS, EIP7702_TEST_VECTORS, LEGACY_VECTORS } from './vectors' describe('EVM Transaction Signing - Unified Test Suite', () => { - beforeAll(async () => { - await setupClient(); - }); + beforeAll(async () => { + await setupClient() + }) - describe('Legacy Transactions', () => { - LEGACY_VECTORS.forEach((vector, index) => { - it(`${vector.name} (${index + 1}/${LEGACY_VECTORS.length})`, async () => { - await signAndCompareTransaction(vector.tx, vector.name); - }); - }); - }); + describe('Legacy Transactions', () => { + LEGACY_VECTORS.forEach((vector, index) => { + it(`${vector.name} (${index + 1}/${LEGACY_VECTORS.length})`, async () => { + await signAndCompareTransaction(vector.tx, vector.name) + }) + }) + }) - describe('EIP-1559 Transactions (Fee Market)', () => { - EIP1559_TEST_VECTORS.forEach((vector, index) => { - it(`${vector.name} (${index + 1}/${EIP1559_TEST_VECTORS.length})`, async () => { - await signAndCompareTransaction(vector.tx, vector.name); - }); - }); - }); + describe('EIP-1559 Transactions (Fee Market)', () => { + EIP1559_TEST_VECTORS.forEach((vector, index) => { + it(`${vector.name} (${index + 1}/${EIP1559_TEST_VECTORS.length})`, async () => { + await signAndCompareTransaction(vector.tx, vector.name) + }) + }) + }) - describe('EIP-2930 Transactions (Access Lists)', () => { - EIP2930_TEST_VECTORS.forEach((vector, index) => { - it(`${vector.name} (${index + 1}/${EIP2930_TEST_VECTORS.length})`, async () => { - await signAndCompareTransaction(vector.tx, vector.name); - }); - }); - }); + describe('EIP-2930 Transactions (Access Lists)', () => { + EIP2930_TEST_VECTORS.forEach((vector, index) => { + it(`${vector.name} (${index + 1}/${EIP2930_TEST_VECTORS.length})`, async () => { + await signAndCompareTransaction(vector.tx, vector.name) + }) + }) + }) - describe('EIP-7702 Transactions (Account Abstraction)', () => { - EIP7702_TEST_VECTORS.forEach((vector, index) => { - it(`${vector.name} (${index + 1}/${EIP7702_TEST_VECTORS.length})`, async () => { - await signAndCompareTransaction(vector.tx, vector.name); - }); - }); - }); + describe('EIP-7702 Transactions (Account Abstraction)', () => { + EIP7702_TEST_VECTORS.forEach((vector, index) => { + it(`${vector.name} (${index + 1}/${EIP7702_TEST_VECTORS.length})`, async () => { + await signAndCompareTransaction(vector.tx, vector.name) + }) + }) + }) - describe('Edge Cases & Boundary Conditions', () => { - EDGE_CASE_TEST_VECTORS.forEach((vector, index) => { - it(`${vector.name} (${index + 1}/${EDGE_CASE_TEST_VECTORS.length})`, async () => { - await signAndCompareTransaction(vector.tx, vector.name); - }); - }); - }); -}); + describe('Edge Cases & Boundary Conditions', () => { + EDGE_CASE_TEST_VECTORS.forEach((vector, index) => { + it(`${vector.name} (${index + 1}/${EDGE_CASE_TEST_VECTORS.length})`, async () => { + await signAndCompareTransaction(vector.tx, vector.name) + }) + }) + }) +}) diff --git a/packages/sdk/src/__test__/e2e/signing/solana/__mocks__/programs.ts b/packages/sdk/src/__test__/e2e/signing/solana/__mocks__/programs.ts index f72b1ec9..4e2d25de 100644 --- a/packages/sdk/src/__test__/e2e/signing/solana/__mocks__/programs.ts +++ b/packages/sdk/src/__test__/e2e/signing/solana/__mocks__/programs.ts @@ -1,87 +1,33 @@ export const raydiumProgram = Buffer.from([ - 2, 0, 10, 24, 47, 41, 101, 134, 4, 204, 35, 219, 9, 43, 55, 20, 255, 76, 29, - 27, 175, 197, 102, 175, 108, 72, 231, 146, 134, 30, 219, 42, 168, 236, 17, 3, - 209, 66, 76, 96, 212, 139, 188, 184, 209, 19, 177, 79, 32, 176, 155, 148, 130, - 200, 98, 110, 128, 160, 186, 226, 136, 168, 203, 105, 117, 38, 171, 161, 41, - 11, 235, 184, 125, 115, 158, 2, 137, 19, 123, 58, 193, 31, 193, 215, 57, 0, - 240, 162, 154, 197, 182, 102, 96, 91, 101, 29, 31, 63, 168, 145, 47, 189, 251, - 138, 30, 226, 174, 123, 162, 139, 147, 54, 101, 251, 231, 142, 230, 173, 232, - 212, 153, 225, 58, 113, 24, 53, 97, 26, 89, 169, 83, 10, 48, 190, 92, 28, 22, - 83, 66, 168, 232, 41, 135, 46, 21, 208, 110, 217, 210, 243, 1, 237, 247, 237, - 183, 71, 1, 193, 117, 86, 188, 217, 134, 204, 52, 147, 214, 106, 218, 145, 18, - 3, 14, 152, 187, 194, 134, 97, 177, 146, 183, 102, 40, 90, 33, 217, 240, 113, - 89, 142, 64, 120, 22, 21, 175, 245, 69, 184, 172, 211, 86, 215, 29, 176, 82, - 209, 2, 198, 205, 207, 75, 152, 201, 35, 86, 169, 205, 157, 142, 144, 96, 235, - 228, 249, 204, 249, 212, 189, 80, 236, 218, 125, 206, 4, 54, 254, 165, 182, - 236, 127, 232, 154, 117, 171, 105, 133, 186, 163, 206, 201, 159, 70, 10, 17, - 138, 239, 21, 37, 109, 139, 81, 59, 76, 57, 145, 209, 124, 73, 151, 15, 83, - 117, 205, 87, 153, 166, 158, 188, 141, 157, 153, 230, 131, 244, 4, 118, 170, - 60, 182, 39, 25, 110, 87, 174, 54, 186, 81, 129, 162, 212, 79, 70, 190, 180, - 232, 143, 72, 102, 119, 242, 172, 80, 2, 53, 168, 117, 184, 99, 184, 206, 230, - 204, 5, 165, 141, 30, 37, 25, 159, 230, 130, 247, 252, 139, 86, 49, 132, 94, - 254, 8, 198, 72, 16, 173, 202, 16, 222, 102, 65, 202, 40, 218, 190, 74, 34, - 21, 163, 163, 179, 232, 137, 88, 91, 104, 174, 112, 225, 188, 55, 104, 213, - 116, 77, 143, 100, 55, 181, 95, 3, 149, 44, 132, 146, 146, 85, 62, 245, 56, - 164, 243, 10, 248, 172, 153, 97, 85, 86, 177, 82, 92, 148, 248, 130, 133, 54, - 160, 65, 92, 148, 142, 53, 148, 190, 85, 136, 146, 82, 19, 186, 209, 204, 116, - 25, 155, 69, 186, 207, 152, 45, 54, 94, 70, 191, 239, 106, 17, 161, 122, 157, - 199, 83, 30, 23, 42, 103, 226, 74, 230, 111, 113, 173, 56, 0, 0, 0, 0, 0, 0, - 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, - 55, 153, 140, 203, 242, 208, 69, 139, 97, 92, 188, 198, 177, 163, 103, 196, - 116, 158, 159, 239, 115, 6, 98, 46, 27, 27, 88, 145, 1, 32, 188, 154, 65, 87, - 176, 88, 15, 49, 197, 252, 228, 74, 98, 88, 45, 188, 249, 215, 142, 231, 89, - 67, 160, 132, 163, 147, 179, 80, 54, 141, 34, 137, 147, 8, 75, 217, 73, 196, - 54, 2, 195, 63, 32, 119, 144, 237, 22, 163, 82, 76, 161, 185, 151, 92, 241, - 33, 162, 169, 12, 255, 236, 125, 248, 182, 138, 205, 95, 183, 40, 175, 220, - 220, 4, 120, 120, 238, 132, 58, 111, 235, 68, 175, 40, 205, 83, 163, 72, 40, - 217, 144, 59, 90, 213, 230, 151, 25, 85, 42, 133, 15, 45, 110, 2, 164, 122, - 248, 36, 208, 154, 182, 157, 196, 45, 112, 203, 40, 203, 250, 36, 159, 183, - 238, 87, 185, 210, 86, 193, 39, 98, 239, 140, 151, 37, 143, 78, 36, 137, 241, - 187, 61, 16, 41, 20, 142, 13, 131, 11, 90, 19, 153, 218, 255, 16, 132, 4, 142, - 123, 216, 219, 233, 248, 89, 6, 155, 136, 87, 254, 171, 129, 132, 251, 104, - 127, 99, 70, 24, 192, 53, 218, 196, 57, 220, 26, 235, 59, 85, 152, 160, 240, - 0, 0, 0, 0, 1, 6, 167, 213, 23, 25, 44, 92, 81, 33, 140, 201, 76, 61, 74, 241, - 127, 88, 218, 238, 8, 155, 161, 253, 68, 227, 219, 217, 138, 0, 0, 0, 0, 6, - 221, 246, 225, 215, 101, 161, 147, 217, 203, 225, 70, 206, 235, 121, 172, 28, - 180, 133, 237, 95, 91, 55, 145, 58, 140, 245, 133, 126, 255, 0, 169, 238, 204, - 15, 249, 117, 156, 236, 123, 109, 247, 173, 57, 139, 143, 19, 21, 15, 180, - 230, 189, 171, 230, 228, 215, 211, 229, 22, 94, 108, 135, 17, 93, 5, 14, 2, 0, - 1, 52, 0, 0, 0, 0, 48, 96, 46, 0, 0, 0, 0, 0, 165, 0, 0, 0, 0, 0, 0, 0, 6, - 221, 246, 225, 215, 101, 161, 147, 217, 203, 225, 70, 206, 235, 121, 172, 28, - 180, 133, 237, 95, 91, 55, 145, 58, 140, 245, 133, 126, 255, 0, 169, 23, 4, 1, - 21, 0, 22, 1, 1, 20, 7, 0, 9, 0, 15, 14, 23, 22, 0, 17, 18, 23, 10, 16, 7, 6, - 13, 2, 19, 12, 11, 3, 4, 8, 5, 18, 1, 9, 0, 17, 9, 64, 66, 15, 0, 0, 0, 0, 0, - 89, 241, 0, 0, 0, 0, 0, 0, 23, 3, 1, 0, 0, 1, 9, -]); + 2, 0, 10, 24, 47, 41, 101, 134, 4, 204, 35, 219, 9, 43, 55, 20, 255, 76, 29, 27, 175, 197, 102, 175, 108, 72, 231, 146, 134, 30, 219, 42, 168, 236, 17, 3, 209, 66, 76, 96, 212, 139, 188, 184, 209, 19, 177, 79, 32, 176, 155, 148, 130, 200, + 98, 110, 128, 160, 186, 226, 136, 168, 203, 105, 117, 38, 171, 161, 41, 11, 235, 184, 125, 115, 158, 2, 137, 19, 123, 58, 193, 31, 193, 215, 57, 0, 240, 162, 154, 197, 182, 102, 96, 91, 101, 29, 31, 63, 168, 145, 47, 189, 251, 138, 30, + 226, 174, 123, 162, 139, 147, 54, 101, 251, 231, 142, 230, 173, 232, 212, 153, 225, 58, 113, 24, 53, 97, 26, 89, 169, 83, 10, 48, 190, 92, 28, 22, 83, 66, 168, 232, 41, 135, 46, 21, 208, 110, 217, 210, 243, 1, 237, 247, 237, 183, 71, 1, + 193, 117, 86, 188, 217, 134, 204, 52, 147, 214, 106, 218, 145, 18, 3, 14, 152, 187, 194, 134, 97, 177, 146, 183, 102, 40, 90, 33, 217, 240, 113, 89, 142, 64, 120, 22, 21, 175, 245, 69, 184, 172, 211, 86, 215, 29, 176, 82, 209, 2, 198, + 205, 207, 75, 152, 201, 35, 86, 169, 205, 157, 142, 144, 96, 235, 228, 249, 204, 249, 212, 189, 80, 236, 218, 125, 206, 4, 54, 254, 165, 182, 236, 127, 232, 154, 117, 171, 105, 133, 186, 163, 206, 201, 159, 70, 10, 17, 138, 239, 21, 37, + 109, 139, 81, 59, 76, 57, 145, 209, 124, 73, 151, 15, 83, 117, 205, 87, 153, 166, 158, 188, 141, 157, 153, 230, 131, 244, 4, 118, 170, 60, 182, 39, 25, 110, 87, 174, 54, 186, 81, 129, 162, 212, 79, 70, 190, 180, 232, 143, 72, 102, 119, + 242, 172, 80, 2, 53, 168, 117, 184, 99, 184, 206, 230, 204, 5, 165, 141, 30, 37, 25, 159, 230, 130, 247, 252, 139, 86, 49, 132, 94, 254, 8, 198, 72, 16, 173, 202, 16, 222, 102, 65, 202, 40, 218, 190, 74, 34, 21, 163, 163, 179, 232, 137, + 88, 91, 104, 174, 112, 225, 188, 55, 104, 213, 116, 77, 143, 100, 55, 181, 95, 3, 149, 44, 132, 146, 146, 85, 62, 245, 56, 164, 243, 10, 248, 172, 153, 97, 85, 86, 177, 82, 92, 148, 248, 130, 133, 54, 160, 65, 92, 148, 142, 53, 148, 190, + 85, 136, 146, 82, 19, 186, 209, 204, 116, 25, 155, 69, 186, 207, 152, 45, 54, 94, 70, 191, 239, 106, 17, 161, 122, 157, 199, 83, 30, 23, 42, 103, 226, 74, 230, 111, 113, 173, 56, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, + 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 55, 153, 140, 203, 242, 208, 69, 139, 97, 92, 188, 198, 177, 163, 103, 196, 116, 158, 159, 239, 115, 6, 98, 46, 27, 27, 88, 145, 1, 32, 188, 154, 65, 87, 176, 88, 15, 49, 197, 252, 228, 74, 98, 88, 45, + 188, 249, 215, 142, 231, 89, 67, 160, 132, 163, 147, 179, 80, 54, 141, 34, 137, 147, 8, 75, 217, 73, 196, 54, 2, 195, 63, 32, 119, 144, 237, 22, 163, 82, 76, 161, 185, 151, 92, 241, 33, 162, 169, 12, 255, 236, 125, 248, 182, 138, 205, 95, + 183, 40, 175, 220, 220, 4, 120, 120, 238, 132, 58, 111, 235, 68, 175, 40, 205, 83, 163, 72, 40, 217, 144, 59, 90, 213, 230, 151, 25, 85, 42, 133, 15, 45, 110, 2, 164, 122, 248, 36, 208, 154, 182, 157, 196, 45, 112, 203, 40, 203, 250, 36, + 159, 183, 238, 87, 185, 210, 86, 193, 39, 98, 239, 140, 151, 37, 143, 78, 36, 137, 241, 187, 61, 16, 41, 20, 142, 13, 131, 11, 90, 19, 153, 218, 255, 16, 132, 4, 142, 123, 216, 219, 233, 248, 89, 6, 155, 136, 87, 254, 171, 129, 132, 251, + 104, 127, 99, 70, 24, 192, 53, 218, 196, 57, 220, 26, 235, 59, 85, 152, 160, 240, 0, 0, 0, 0, 1, 6, 167, 213, 23, 25, 44, 92, 81, 33, 140, 201, 76, 61, 74, 241, 127, 88, 218, 238, 8, 155, 161, 253, 68, 227, 219, 217, 138, 0, 0, 0, 0, 6, + 221, 246, 225, 215, 101, 161, 147, 217, 203, 225, 70, 206, 235, 121, 172, 28, 180, 133, 237, 95, 91, 55, 145, 58, 140, 245, 133, 126, 255, 0, 169, 238, 204, 15, 249, 117, 156, 236, 123, 109, 247, 173, 57, 139, 143, 19, 21, 15, 180, 230, + 189, 171, 230, 228, 215, 211, 229, 22, 94, 108, 135, 17, 93, 5, 14, 2, 0, 1, 52, 0, 0, 0, 0, 48, 96, 46, 0, 0, 0, 0, 0, 165, 0, 0, 0, 0, 0, 0, 0, 6, 221, 246, 225, 215, 101, 161, 147, 217, 203, 225, 70, 206, 235, 121, 172, 28, 180, 133, + 237, 95, 91, 55, 145, 58, 140, 245, 133, 126, 255, 0, 169, 23, 4, 1, 21, 0, 22, 1, 1, 20, 7, 0, 9, 0, 15, 14, 23, 22, 0, 17, 18, 23, 10, 16, 7, 6, 13, 2, 19, 12, 11, 3, 4, 8, 5, 18, 1, 9, 0, 17, 9, 64, 66, 15, 0, 0, 0, 0, 0, 89, 241, 0, + 0, 0, 0, 0, 0, 23, 3, 1, 0, 0, 1, 9, +]) export const dexlabProgram = Buffer.from([ - 3, 0, 7, 11, 47, 41, 101, 134, 4, 204, 35, 219, 9, 43, 55, 20, 255, 76, 29, - 27, 175, 197, 102, 175, 108, 72, 231, 146, 134, 30, 219, 42, 168, 236, 17, 3, - 27, 22, 186, 110, 40, 115, 180, 32, 87, 1, 133, 235, 70, 100, 93, 110, 49, - 176, 47, 100, 89, 135, 44, 204, 229, 104, 63, 16, 169, 85, 62, 17, 87, 228, - 120, 25, 237, 212, 48, 216, 155, 158, 74, 24, 15, 13, 148, 130, 112, 67, 62, - 67, 34, 39, 96, 92, 200, 155, 110, 50, 187, 157, 163, 92, 87, 174, 54, 186, - 81, 129, 162, 212, 79, 70, 190, 180, 232, 143, 72, 102, 119, 242, 172, 80, 2, - 53, 168, 117, 184, 99, 184, 206, 230, 204, 5, 165, 0, 0, 0, 0, 0, 0, 0, 0, 0, - 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 55, 153, - 140, 203, 242, 208, 69, 139, 97, 92, 188, 198, 177, 163, 103, 196, 116, 158, - 159, 239, 115, 6, 98, 46, 27, 27, 88, 145, 1, 32, 188, 154, 140, 151, 37, 143, - 78, 36, 137, 241, 187, 61, 16, 41, 20, 142, 13, 131, 11, 90, 19, 153, 218, - 255, 16, 132, 4, 142, 123, 216, 219, 233, 248, 89, 198, 250, 122, 243, 190, - 219, 173, 58, 61, 101, 243, 106, 171, 201, 116, 49, 177, 187, 228, 194, 210, - 246, 224, 228, 124, 166, 2, 3, 69, 47, 93, 97, 6, 155, 136, 87, 254, 171, 129, - 132, 251, 104, 127, 99, 70, 24, 192, 53, 218, 196, 57, 220, 26, 235, 59, 85, - 152, 160, 240, 0, 0, 0, 0, 1, 6, 167, 213, 23, 25, 44, 92, 81, 33, 140, 201, - 76, 61, 74, 241, 127, 88, 218, 238, 8, 155, 161, 253, 68, 227, 219, 217, 138, - 0, 0, 0, 0, 6, 221, 246, 225, 215, 101, 161, 147, 217, 203, 225, 70, 206, 235, - 121, 172, 28, 180, 133, 237, 95, 91, 55, 145, 58, 140, 245, 133, 126, 255, 0, - 169, 2, 206, 198, 46, 159, 44, 171, 207, 167, 152, 197, 80, 224, 171, 133, - 195, 162, 248, 176, 1, 213, 55, 144, 195, 89, 153, 30, 36, 158, 74, 236, 222, - 5, 4, 2, 0, 1, 52, 0, 0, 0, 0, 48, 96, 46, 0, 0, 0, 0, 0, 165, 0, 0, 0, 0, 0, - 0, 0, 6, 221, 246, 225, 215, 101, 161, 147, 217, 203, 225, 70, 206, 235, 121, - 172, 28, 180, 133, 237, 95, 91, 55, 145, 58, 140, 245, 133, 126, 255, 0, 169, - 10, 4, 1, 8, 0, 9, 1, 1, 6, 7, 0, 3, 0, 5, 4, 10, 9, 0, 4, 2, 0, 2, 52, 0, 0, - 0, 0, 240, 29, 31, 0, 0, 0, 0, 0, 165, 0, 0, 0, 0, 0, 0, 0, 6, 221, 246, 225, - 215, 101, 161, 147, 217, 203, 225, 70, 206, 235, 121, 172, 28, 180, 133, 237, - 95, 91, 55, 145, 58, 140, 245, 133, 126, 255, 0, 169, 10, 4, 2, 7, 0, 9, 1, 1, -]); + 3, 0, 7, 11, 47, 41, 101, 134, 4, 204, 35, 219, 9, 43, 55, 20, 255, 76, 29, 27, 175, 197, 102, 175, 108, 72, 231, 146, 134, 30, 219, 42, 168, 236, 17, 3, 27, 22, 186, 110, 40, 115, 180, 32, 87, 1, 133, 235, 70, 100, 93, 110, 49, 176, 47, + 100, 89, 135, 44, 204, 229, 104, 63, 16, 169, 85, 62, 17, 87, 228, 120, 25, 237, 212, 48, 216, 155, 158, 74, 24, 15, 13, 148, 130, 112, 67, 62, 67, 34, 39, 96, 92, 200, 155, 110, 50, 187, 157, 163, 92, 87, 174, 54, 186, 81, 129, 162, 212, + 79, 70, 190, 180, 232, 143, 72, 102, 119, 242, 172, 80, 2, 53, 168, 117, 184, 99, 184, 206, 230, 204, 5, 165, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 55, 153, 140, 203, 242, 208, 69, + 139, 97, 92, 188, 198, 177, 163, 103, 196, 116, 158, 159, 239, 115, 6, 98, 46, 27, 27, 88, 145, 1, 32, 188, 154, 140, 151, 37, 143, 78, 36, 137, 241, 187, 61, 16, 41, 20, 142, 13, 131, 11, 90, 19, 153, 218, 255, 16, 132, 4, 142, 123, 216, + 219, 233, 248, 89, 198, 250, 122, 243, 190, 219, 173, 58, 61, 101, 243, 106, 171, 201, 116, 49, 177, 187, 228, 194, 210, 246, 224, 228, 124, 166, 2, 3, 69, 47, 93, 97, 6, 155, 136, 87, 254, 171, 129, 132, 251, 104, 127, 99, 70, 24, 192, + 53, 218, 196, 57, 220, 26, 235, 59, 85, 152, 160, 240, 0, 0, 0, 0, 1, 6, 167, 213, 23, 25, 44, 92, 81, 33, 140, 201, 76, 61, 74, 241, 127, 88, 218, 238, 8, 155, 161, 253, 68, 227, 219, 217, 138, 0, 0, 0, 0, 6, 221, 246, 225, 215, 101, + 161, 147, 217, 203, 225, 70, 206, 235, 121, 172, 28, 180, 133, 237, 95, 91, 55, 145, 58, 140, 245, 133, 126, 255, 0, 169, 2, 206, 198, 46, 159, 44, 171, 207, 167, 152, 197, 80, 224, 171, 133, 195, 162, 248, 176, 1, 213, 55, 144, 195, 89, + 153, 30, 36, 158, 74, 236, 222, 5, 4, 2, 0, 1, 52, 0, 0, 0, 0, 48, 96, 46, 0, 0, 0, 0, 0, 165, 0, 0, 0, 0, 0, 0, 0, 6, 221, 246, 225, 215, 101, 161, 147, 217, 203, 225, 70, 206, 235, 121, 172, 28, 180, 133, 237, 95, 91, 55, 145, 58, 140, + 245, 133, 126, 255, 0, 169, 10, 4, 1, 8, 0, 9, 1, 1, 6, 7, 0, 3, 0, 5, 4, 10, 9, 0, 4, 2, 0, 2, 52, 0, 0, 0, 0, 240, 29, 31, 0, 0, 0, 0, 0, 165, 0, 0, 0, 0, 0, 0, 0, 6, 221, 246, 225, 215, 101, 161, 147, 217, 203, 225, 70, 206, 235, 121, + 172, 28, 180, 133, 237, 95, 91, 55, 145, 58, 140, 245, 133, 126, 255, 0, 169, 10, 4, 2, 7, 0, 9, 1, 1, +]) diff --git a/packages/sdk/src/__test__/e2e/signing/solana/solana.programs.test.ts b/packages/sdk/src/__test__/e2e/signing/solana/solana.programs.test.ts index c92b9ea9..a130df91 100644 --- a/packages/sdk/src/__test__/e2e/signing/solana/solana.programs.test.ts +++ b/packages/sdk/src/__test__/e2e/signing/solana/solana.programs.test.ts @@ -6,42 +6,43 @@ * Running with a different mnemonic will cause test failures due to * incorrect key derivations and signature mismatches. */ -import { Constants } from '../../../..'; -import { setupClient } from '../../../utils/setup'; -import { dexlabProgram, raydiumProgram } from './__mocks__/programs'; +import { Constants } from '../../../..' +import type { Client } from '../../../../client' +import { setupClient } from '../../../utils/setup' +import { dexlabProgram, raydiumProgram } from './__mocks__/programs' describe('Solana Programs', () => { - let client; + let client: Client - beforeAll(async () => { - client = await setupClient(); - }); + beforeAll(async () => { + client = await setupClient() + }) - it('should sign Dexlab program', async () => { - const payload = dexlabProgram; - const signedMessage = await client.sign({ - data: { - signerPath: [0x80000000 + 44, 0x80000000 + 501, 0x80000000], - curveType: Constants.SIGNING.CURVES.ED25519, - hashType: Constants.SIGNING.HASHES.NONE, - encodingType: Constants.SIGNING.ENCODINGS.SOLANA, - payload, - }, - }); - expect(signedMessage).toBeTruthy(); - }); + it('should sign Dexlab program', async () => { + const payload = dexlabProgram + const signedMessage = await client.sign({ + data: { + signerPath: [0x80000000 + 44, 0x80000000 + 501, 0x80000000], + curveType: Constants.SIGNING.CURVES.ED25519, + hashType: Constants.SIGNING.HASHES.NONE, + encodingType: Constants.SIGNING.ENCODINGS.SOLANA, + payload, + }, + }) + expect(signedMessage).toBeTruthy() + }) - it('should sign Raydium program', async () => { - const payload = raydiumProgram; - const signedMessage = await client.sign({ - data: { - signerPath: [0x80000000 + 44, 0x80000000 + 501, 0x80000000], - curveType: Constants.SIGNING.CURVES.ED25519, - hashType: Constants.SIGNING.HASHES.NONE, - encodingType: Constants.SIGNING.ENCODINGS.SOLANA, - payload, - }, - }); - expect(signedMessage).toBeTruthy(); - }); -}); + it('should sign Raydium program', async () => { + const payload = raydiumProgram + const signedMessage = await client.sign({ + data: { + signerPath: [0x80000000 + 44, 0x80000000 + 501, 0x80000000], + curveType: Constants.SIGNING.CURVES.ED25519, + hashType: Constants.SIGNING.HASHES.NONE, + encodingType: Constants.SIGNING.ENCODINGS.SOLANA, + payload, + }, + }) + expect(signedMessage).toBeTruthy() + }) +}) diff --git a/packages/sdk/src/__test__/e2e/signing/solana/solana.test.ts b/packages/sdk/src/__test__/e2e/signing/solana/solana.test.ts index 0b653a6f..e805579c 100644 --- a/packages/sdk/src/__test__/e2e/signing/solana/solana.test.ts +++ b/packages/sdk/src/__test__/e2e/signing/solana/solana.test.ts @@ -6,146 +6,121 @@ * Running with a different mnemonic will cause test failures due to * incorrect key derivations and signature mismatches. */ -import { - Keypair as SolanaKeypair, - PublicKey as SolanaPublicKey, - SystemProgram as SolanaSystemProgram, - Transaction as SolanaTransaction, -} from '@solana/web3.js'; -import { Constants } from '../../../..'; -import { HARDENED_OFFSET } from '../../../../constants'; -import { ensureHexBuffer } from '../../../../util'; -import { getPrng } from '../../../utils/getters'; -import { deriveED25519Key, prandomBuf } from '../../../utils/helpers'; -import { runGeneric } from '../../../utils/runners'; -import { setupClient } from '../../../utils/setup'; -import { TEST_SEED } from '../../../utils/testConstants'; +import { Keypair as SolanaKeypair, PublicKey as SolanaPublicKey, SystemProgram as SolanaSystemProgram, Transaction as SolanaTransaction } from '@solana/web3.js' +import { Constants } from '../../../..' +import { HARDENED_OFFSET } from '../../../../constants' +import { ensureHexBuffer } from '../../../../util' +import { getPrng } from '../../../utils/getters' +import { deriveED25519Key, prandomBuf } from '../../../utils/helpers' +import { runGeneric } from '../../../utils/runners' +import { setupClient } from '../../../utils/setup' +import { TEST_SEED } from '../../../utils/testConstants' +import type { Client } from '../../../../client' //--------------------------------------- // STATE DATA //--------------------------------------- -const DEFAULT_SOLANA_SIGNER_PATH = [ - HARDENED_OFFSET + 44, - HARDENED_OFFSET + 501, - HARDENED_OFFSET, - HARDENED_OFFSET, -]; -const prng = getPrng(); +const DEFAULT_SOLANA_SIGNER_PATH = [HARDENED_OFFSET + 44, HARDENED_OFFSET + 501, HARDENED_OFFSET, HARDENED_OFFSET] +const prng = getPrng() describe('[Solana]', () => { - let client; + let client: Client - beforeAll(async () => { - client = await setupClient(); - }); + beforeAll(async () => { + client = await setupClient() + }) - const getReq = (overrides: any) => ({ - data: { - curveType: Constants.SIGNING.CURVES.ED25519, - hashType: Constants.SIGNING.HASHES.NONE, - encodingType: Constants.SIGNING.ENCODINGS.SOLANA, - payload: null, - ...overrides, - }, - }); + const getReq = (overrides: any) => ({ + data: { + curveType: Constants.SIGNING.CURVES.ED25519, + hashType: Constants.SIGNING.HASHES.NONE, + encodingType: Constants.SIGNING.ENCODINGS.SOLANA, + payload: null, + ...overrides, + }, + }) - it('Should validate Solana transaction encoding', async () => { - // Build a Solana transaction with two signers, each derived from the Lattice's seed. - // This will require two separate general signing requests, one per signer. + it('Should validate Solana transaction encoding', async () => { + // Build a Solana transaction with two signers, each derived from the Lattice's seed. + // This will require two separate general signing requests, one per signer. - // Get the full set of Solana addresses and keys - // NOTE: Solana addresses are just base58 encoded public keys. We do not - // currently support exporting of Solana addresses in firmware but we can - // derive them here using the exported seed. - const seed = TEST_SEED; - const derivedAPath = [...DEFAULT_SOLANA_SIGNER_PATH]; - const derivedBPath = [...DEFAULT_SOLANA_SIGNER_PATH]; - derivedBPath[3] += 1; - const derivedCPath = [...DEFAULT_SOLANA_SIGNER_PATH]; - derivedCPath[3] += 2; - const derivedA = deriveED25519Key(derivedAPath, seed); - const derivedB = deriveED25519Key(derivedBPath, seed); - const derivedC = deriveED25519Key(derivedCPath, seed); - const pubA = new SolanaPublicKey(derivedA.pub); - const pubB = new SolanaPublicKey(derivedB.pub); - const pubC = new SolanaPublicKey(derivedC.pub); + // Get the full set of Solana addresses and keys + // NOTE: Solana addresses are just base58 encoded public keys. We do not + // currently support exporting of Solana addresses in firmware but we can + // derive them here using the exported seed. + const seed = TEST_SEED + const derivedAPath = [...DEFAULT_SOLANA_SIGNER_PATH] + const derivedBPath = [...DEFAULT_SOLANA_SIGNER_PATH] + derivedBPath[3] += 1 + const derivedCPath = [...DEFAULT_SOLANA_SIGNER_PATH] + derivedCPath[3] += 2 + const derivedA = deriveED25519Key(derivedAPath, seed) + const derivedB = deriveED25519Key(derivedBPath, seed) + const derivedC = deriveED25519Key(derivedCPath, seed) + const pubA = new SolanaPublicKey(derivedA.pub) + const pubB = new SolanaPublicKey(derivedB.pub) + const pubC = new SolanaPublicKey(derivedC.pub) - // Define transaction instructions - const transfer1 = SolanaSystemProgram.transfer({ - fromPubkey: pubA, - toPubkey: pubC, - lamports: 111, - }); - const transfer2 = SolanaSystemProgram.transfer({ - fromPubkey: pubB, - toPubkey: pubC, - lamports: 222, - }); + // Define transaction instructions + const transfer1 = SolanaSystemProgram.transfer({ + fromPubkey: pubA, + toPubkey: pubC, + lamports: 111, + }) + const transfer2 = SolanaSystemProgram.transfer({ + fromPubkey: pubB, + toPubkey: pubC, + lamports: 222, + }) - // Generate a pseudorandom blockhash, which is just a public key appearently. - const randBuf = prandomBuf(prng, 32, true); - const recentBlockhash = - SolanaKeypair.fromSeed(randBuf).publicKey.toBase58(); + // Generate a pseudorandom blockhash, which is just a public key appearently. + const randBuf = prandomBuf(prng, 32, true) + const recentBlockhash = SolanaKeypair.fromSeed(randBuf).publicKey.toBase58() - // Build a transaction and sign it using Solana's JS lib - const txJs = new SolanaTransaction({ recentBlockhash }).add( - transfer1, - transfer2, - ); - txJs.setSigners(pubA, pubB); - txJs.sign( - SolanaKeypair.fromSeed(derivedA.priv), - SolanaKeypair.fromSeed(derivedB.priv), - ); - const serTxJs = txJs.serialize().toString('hex'); + // Build a transaction and sign it using Solana's JS lib + const txJs = new SolanaTransaction({ recentBlockhash }).add(transfer1, transfer2) + txJs.setSigners(pubA, pubB) + txJs.sign(SolanaKeypair.fromSeed(derivedA.priv), SolanaKeypair.fromSeed(derivedB.priv)) + const serTxJs = txJs.serialize().toString('hex') - // Build a copy of the transaction and get the serialized payload for signing in firmware. - const txFw = new SolanaTransaction({ recentBlockhash }).add( - transfer1, - transfer2, - ); - txFw.setSigners(pubA, pubB); - // We want to sign the Solana message, not the full transaction - const payload = txFw.compileMessage().serialize(); - const payloadHex = `0x${payload.toString('hex')}`; + // Build a copy of the transaction and get the serialized payload for signing in firmware. + const txFw = new SolanaTransaction({ recentBlockhash }).add(transfer1, transfer2) + txFw.setSigners(pubA, pubB) + // We want to sign the Solana message, not the full transaction + const payload = txFw.compileMessage().serialize() + const payloadHex = `0x${payload.toString('hex')}` - // Sign payload from Lattice and add signatures to tx object - const sigA = await runGeneric( - getReq({ - signerPath: derivedAPath, - payload: payloadHex, - }), - client, - ).then((resp) => { - if (!resp.sig?.r || !resp.sig?.s) { - throw new Error('Missing signature components in response'); - } - return Buffer.concat([ - ensureHexBuffer(resp.sig.r as string | Buffer), - ensureHexBuffer(resp.sig.s as string | Buffer), - ]); - }); + // Sign payload from Lattice and add signatures to tx object + const sigA = await runGeneric( + getReq({ + signerPath: derivedAPath, + payload: payloadHex, + }), + client, + ).then((resp) => { + if (!resp.sig?.r || !resp.sig?.s) { + throw new Error('Missing signature components in response') + } + return Buffer.concat([ensureHexBuffer(resp.sig.r as string | Buffer), ensureHexBuffer(resp.sig.s as string | Buffer)]) + }) - const sigB = await runGeneric( - getReq({ - signerPath: derivedBPath, - payload: payloadHex, - }), - client, - ).then((resp) => { - if (!resp.sig?.r || !resp.sig?.s) { - throw new Error('Missing signature components in response'); - } - return Buffer.concat([ - ensureHexBuffer(resp.sig.r as string | Buffer), - ensureHexBuffer(resp.sig.s as string | Buffer), - ]); - }); - txFw.addSignature(pubA, sigA); - txFw.addSignature(pubB, sigB); + const sigB = await runGeneric( + getReq({ + signerPath: derivedBPath, + payload: payloadHex, + }), + client, + ).then((resp) => { + if (!resp.sig?.r || !resp.sig?.s) { + throw new Error('Missing signature components in response') + } + return Buffer.concat([ensureHexBuffer(resp.sig.r as string | Buffer), ensureHexBuffer(resp.sig.s as string | Buffer)]) + }) + txFw.addSignature(pubA, sigA) + txFw.addSignature(pubB, sigB) - // Validate the signatures from the Lattice match those of the Solana library - const serTxFw = txFw.serialize().toString('hex'); - expect(serTxFw).toEqual(serTxJs); - }); -}); + // Validate the signatures from the Lattice match those of the Solana library + const serTxFw = txFw.serialize().toString('hex') + expect(serTxFw).toEqual(serTxJs) + }) +}) diff --git a/packages/sdk/src/__test__/e2e/signing/solana/solana.versioned.test.ts b/packages/sdk/src/__test__/e2e/signing/solana/solana.versioned.test.ts index 249d05c5..45a9c394 100644 --- a/packages/sdk/src/__test__/e2e/signing/solana/solana.versioned.test.ts +++ b/packages/sdk/src/__test__/e2e/signing/solana/solana.versioned.test.ts @@ -6,197 +6,178 @@ * Running with a different mnemonic will cause test failures due to * incorrect key derivations and signature mismatches. */ -import { - AddressLookupTableProgram, - Connection, - Keypair, - LAMPORTS_PER_SOL, - PublicKey, - SystemProgram, - Transaction, - type TransactionInstruction, - TransactionMessage, - VersionedTransaction, -} from '@solana/web3.js'; -import { fetchSolanaAddresses, signSolanaTx } from '../../../..'; -import { setupClient } from '../../../utils/setup'; - -const SOLANA_RPC = new Connection('https://api.devnet.solana.com', 'confirmed'); +import { AddressLookupTableProgram, Connection, Keypair, LAMPORTS_PER_SOL, PublicKey, SystemProgram, Transaction, type TransactionInstruction, TransactionMessage, VersionedTransaction } from '@solana/web3.js' +import { fetchSolanaAddresses, signSolanaTx } from '../../../..' +import { setupClient } from '../../../utils/setup' + +const SOLANA_RPC = new Connection('https://api.devnet.solana.com', 'confirmed') const fetchSigningWallet = async () => { - const addresses = await fetchSolanaAddresses({ n: 1 }); - const solanaAddr = addresses[0]; - const pubkey = new PublicKey(solanaAddr); - expect(PublicKey.isOnCurve(pubkey)).toBeTruthy(); - return pubkey; -}; + const addresses = await fetchSolanaAddresses({ n: 1 }) + const solanaAddr = addresses[0] + const pubkey = new PublicKey(solanaAddr) + expect(PublicKey.isOnCurve(pubkey)).toBeTruthy() + return pubkey +} const requestAirdrop = async (wallet: PublicKey, lamports: number) => { - try { - await SOLANA_RPC.requestAirdrop(wallet, lamports * LAMPORTS_PER_SOL); - await new Promise((resolve) => setTimeout(resolve, 20000)); - } catch (error) { - /** - * The faucet is flakey, so you might need to request funds manually from the faucet at https://faucet.solana.com/ - * Also, for Solana to work, your address must have interacted with the network previously. - */ - console.log(error); - } -}; + try { + await SOLANA_RPC.requestAirdrop(wallet, lamports * LAMPORTS_PER_SOL) + await new Promise((resolve) => setTimeout(resolve, 20000)) + } catch (error) { + /** + * The faucet is flakey, so you might need to request funds manually from the faucet at https://faucet.solana.com/ + * Also, for Solana to work, your address must have interacted with the network previously. + */ + console.log(error) + } +} describe('solana.versioned', () => { - let SIGNER_WALLET: PublicKey; - let DESTINATION_WALLET_1: Keypair; - let DESTINATION_WALLET_2: Keypair; - let latestBlockhash: { blockhash: string; lastValidBlockHeight: number }; - - beforeAll(async () => { - await setupClient(); - - SIGNER_WALLET = await fetchSigningWallet(); - DESTINATION_WALLET_1 = Keypair.generate(); - DESTINATION_WALLET_2 = Keypair.generate(); - latestBlockhash = await SOLANA_RPC.getLatestBlockhash('confirmed'); - }); - - test('sign solana', async () => { - SIGNER_WALLET = await fetchSigningWallet(); - const txInstructions: TransactionInstruction[] = [ - SystemProgram.transfer({ - fromPubkey: SIGNER_WALLET, - toPubkey: DESTINATION_WALLET_1.publicKey, - lamports: 0.01 * LAMPORTS_PER_SOL, - }), - ]; - const messageV0 = new TransactionMessage({ - payerKey: SIGNER_WALLET, - recentBlockhash: latestBlockhash.blockhash, - instructions: txInstructions, - }).compileToV0Message(); - - const signedTx = await signSolanaTx(Buffer.from(messageV0.serialize())); - expect(signedTx).toBeTruthy(); - }); - - test('sign solana multiple instructions', async () => { - const txInstructions = [ - SystemProgram.transfer({ - fromPubkey: SIGNER_WALLET, - toPubkey: DESTINATION_WALLET_1.publicKey, - lamports: 0.005 * LAMPORTS_PER_SOL, - }), - SystemProgram.transfer({ - fromPubkey: SIGNER_WALLET, - toPubkey: DESTINATION_WALLET_2.publicKey, - lamports: 0.005 * LAMPORTS_PER_SOL, - }), - ]; - const message = new TransactionMessage({ - payerKey: SIGNER_WALLET, - recentBlockhash: latestBlockhash.blockhash, - instructions: txInstructions, - }).compileToV0Message(); - - const signedTx = await signSolanaTx(Buffer.from(message.serialize())); - expect(signedTx).toBeTruthy(); - }); - - test('sign solana zero lamport transfer', async () => { - const txInstruction = SystemProgram.transfer({ - fromPubkey: SIGNER_WALLET, - toPubkey: DESTINATION_WALLET_1.publicKey, - lamports: 0, - }); - const message = new TransactionMessage({ - payerKey: SIGNER_WALLET, - recentBlockhash: latestBlockhash.blockhash, - instructions: [txInstruction], - }).compileToV0Message(); - - const signedTx = await signSolanaTx(Buffer.from(message.serialize())); - expect(signedTx).toBeTruthy(); - }); - - // Skipping this test because VersionedTransaction are getting rejected by the device (LatticeResponseCode.userDeclined) - test.skip('simulate versioned solana transaction', async () => { - const txInstruction = SystemProgram.transfer({ - fromPubkey: SIGNER_WALLET, - toPubkey: DESTINATION_WALLET_1.publicKey, - lamports: 0.001 * LAMPORTS_PER_SOL, - }); - - const transaction = new Transaction(); - transaction.add(txInstruction); - transaction.recentBlockhash = latestBlockhash.blockhash; - transaction.feePayer = SIGNER_WALLET; - - // Serialize the transaction to get the wire format - const serializedTransaction = transaction.serialize({ - requireAllSignatures: false, - }); - - // Create a VersionedTransaction from the serialized data - const versionedTransaction = VersionedTransaction.deserialize( - serializedTransaction, - ); - - const signedTx = await signSolanaTx( - Buffer.from(versionedTransaction.serialize()), - ); - expect(signedTx).toBeTruthy(); - }); - - test('simulate versioned solana transaction with multiple instructions', async () => { - const payer = Keypair.generate(); - await requestAirdrop(payer.publicKey, 1); - - const [transactionInstruction, pubkey] = - await AddressLookupTableProgram.createLookupTable({ - payer: payer.publicKey, - authority: payer.publicKey, - recentSlot: await SOLANA_RPC.getSlot(), - }); - - await AddressLookupTableProgram.extendLookupTable({ - payer: payer.publicKey, - authority: payer.publicKey, - lookupTable: pubkey, - addresses: [ - DESTINATION_WALLET_1.publicKey, - DESTINATION_WALLET_2.publicKey, - ], - }); - - const messageV0 = new TransactionMessage({ - payerKey: SIGNER_WALLET, - recentBlockhash: latestBlockhash.blockhash, - instructions: [transactionInstruction], - }).compileToV0Message(); - - const signedTx = await signSolanaTx(Buffer.from(messageV0.serialize())); - - expect(signedTx).toBeDefined(); - }); - - // Skipping this test because the messages are getting rejected by the device (LatticeResponseCode.userDeclined) - test.skip('simulate versioned solana transactions from nufi', async () => { - // sign transaction - const signedTx = await signSolanaTx( - Buffer.from( - '800100131b7154e1c10d16949038dd040363bca1f9d6501c694f6e0325ad817166fb2e91ee0c503f9578fb1f7621e5a0c2c384547f22cd4116be06e523219f332b61d41644801401dbe3c1c1f14a4eca5605ce5071e3301e79a79d95ecc50c73b977286be44ffdb307c4cce0aa81999d925e13f482e6deb72c8cc1d31ebf6b95478bd11f4f0cd8b42c34cd1d82beaec835fd525d1e90c248cf6849b277e78015a46e48789107a241e063d943cba135e306d4f8059f2ecf7a69744b993531a9d767674c85c7f6d2eabe47377602308731d0ac6ee6483c3a0f34b6aff13d37e53e00719032e928a52bdc5c44449874879e44c88ed5eb62b77a2b5e672ab9f50010479abc43e10306466fe5211732ffecadba72c39be7bc8ce5bbc5f7126b2c439b3a4000000006b5efb175d780346c18fa61f588190e5981c4668d651d67ad73c36e31180fdf9aca3abb5a18a0fe31a5031e7765dc6264a19fa84b3925e256dc2b4e53974b3c06b43c95041249ed7dedfc195aec0bc3ff2760a53409c79a7607b29fa3a12ad61393a26044ecc1adf84705420dd79ceeeca7be0dc63cd55a3994d9aba59b0e4303da8d1d21a468a26ba2183fd61de731ca4d5d61fd81296f97d916d81fd074ba18a633ac3c601aff49b0ffb943b9cdc1d89793f880bd8765868fd1ab42aa3b670286c0be1afb0442e17ee761789fe71c6e6914dd5771f7993092c9ca8bb05f3ae77546322de214d7944e9846236fe15e4f47731268ee9d6f60dcd058c96ea60a930890e9e6be0f4b35f0bf2f4c373f074ecc0404d221729981f11bb0dd5586fa55ac0b630d53e1fbf40c31f6163a1fed819daad6c34ec6eebbd2c9094ab8f0868ad7baf66ee465a9f41bc1fb7e3f72b663d4bfd34fe585e91fbd1f2753437cbad2569d176621a5d281ef6683261d8cc0da378f1139f1c01996edda54eb109ac4348ea0872c378762af7b1423c9c5b5ad1dd48584fcdbdb10e4922e01573228b8cd362cd18826752b72a9bf8c3d1fd7e3faeec4d44bd0f8988a902511c22b6ab46ba8557b567c6cbcd3e23efe17a9df8269dddf11d65a8f703314b84688b0bb9954004e7014187f35fb8aa37fc7e7cfe9c21e1e3def76baa18ab0668b61cf8bb66c55c082dfa8853bec08eeb7402096300a48b0b7259205ca703a1eaa032a21f6dcf2b4502abaead6d2b5495f0ea97222e2cd76aefb60c8d59d435e7a51cab236333b989b9593098437cfe28786fd22445b730c237a17d1110c8fef327d5f058e0308000502882502000800090333000000000000000922010a021b1c1d1e09030000040909040b0506070c0d0e0f101112131415161718191a7166063d1201daebea164609000000000016460900b9b0b7828d68598fa5ddf8fd4e8ba1315b7afae1785fb6ce5632d8699dd56fb15e4372ff949cf950d5dd678d5622184bc37b32d3a396f9d41ab609e65b1496b3040000000017262704000000010000008a02585c0a0000000000016400017b6eea3282722d20e3d0b0ce1eef6cf588aa519c82103084255fc33deb476d72000416170015', - 'hex', - ), - ); - - expect(signedTx).toBeDefined(); - - // signMessage - const signedMessage = await signSolanaTx( - Buffer.from( - '6d616769636564656e2e696f2077616e747320796f7520746f207369676e20696e207769746820796f757220536f6c616e61206163636f756e743a0a386451393746414e6368337a75346348426942793556365a716478555365547858465873624b62436e6451710a0a57656c636f6d6520746f204d61676963204564656e2e205369676e696e6720697320746865206f6e6c79207761792077652063616e207472756c79206b6e6f77207468617420796f752061726520746865206f776e6572206f66207468652077616c6c657420796f752061726520636f6e6e656374696e672e205369676e696e67206973206120736166652c206761732d6c657373207472616e73616374696f6e207468617420646f6573206e6f7420696e20616e79207761792067697665204d61676963204564656e207065726d697373696f6e20746f20706572666f726d20616e79207472616e73616374696f6e73207769746820796f75722077616c6c65742e0a0a5552493a2068747470733a2f2f6d616769636564656e2e696f2f706f70756c61722d636f6c6c656374696f6e730a56657273696f6e3a20310a436861696e2049443a206d61696e6e65740a4e6f6e63653a2038373066313166646234373734353962393433353730316463366532353035350a4973737565642041743a20323032342d30372d30325431353a32383a34382e3736305a0a526571756573742049443a2063313331346235622d656365382d346234662d613837392d333839346464613336346534', - 'hex', - ), - ); - expect(signedMessage).toBeDefined(); - }); -}); + let SIGNER_WALLET: PublicKey + let DESTINATION_WALLET_1: Keypair + let DESTINATION_WALLET_2: Keypair + let latestBlockhash: { blockhash: string; lastValidBlockHeight: number } + + beforeAll(async () => { + await setupClient() + + SIGNER_WALLET = await fetchSigningWallet() + DESTINATION_WALLET_1 = Keypair.generate() + DESTINATION_WALLET_2 = Keypair.generate() + latestBlockhash = await SOLANA_RPC.getLatestBlockhash('confirmed') + }) + + test('sign solana', async () => { + SIGNER_WALLET = await fetchSigningWallet() + const txInstructions: TransactionInstruction[] = [ + SystemProgram.transfer({ + fromPubkey: SIGNER_WALLET, + toPubkey: DESTINATION_WALLET_1.publicKey, + lamports: 0.01 * LAMPORTS_PER_SOL, + }), + ] + const messageV0 = new TransactionMessage({ + payerKey: SIGNER_WALLET, + recentBlockhash: latestBlockhash.blockhash, + instructions: txInstructions, + }).compileToV0Message() + + const signedTx = await signSolanaTx(Buffer.from(messageV0.serialize())) + expect(signedTx).toBeTruthy() + }) + + test('sign solana multiple instructions', async () => { + const txInstructions = [ + SystemProgram.transfer({ + fromPubkey: SIGNER_WALLET, + toPubkey: DESTINATION_WALLET_1.publicKey, + lamports: 0.005 * LAMPORTS_PER_SOL, + }), + SystemProgram.transfer({ + fromPubkey: SIGNER_WALLET, + toPubkey: DESTINATION_WALLET_2.publicKey, + lamports: 0.005 * LAMPORTS_PER_SOL, + }), + ] + const message = new TransactionMessage({ + payerKey: SIGNER_WALLET, + recentBlockhash: latestBlockhash.blockhash, + instructions: txInstructions, + }).compileToV0Message() + + const signedTx = await signSolanaTx(Buffer.from(message.serialize())) + expect(signedTx).toBeTruthy() + }) + + test('sign solana zero lamport transfer', async () => { + const txInstruction = SystemProgram.transfer({ + fromPubkey: SIGNER_WALLET, + toPubkey: DESTINATION_WALLET_1.publicKey, + lamports: 0, + }) + const message = new TransactionMessage({ + payerKey: SIGNER_WALLET, + recentBlockhash: latestBlockhash.blockhash, + instructions: [txInstruction], + }).compileToV0Message() + + const signedTx = await signSolanaTx(Buffer.from(message.serialize())) + expect(signedTx).toBeTruthy() + }) + + // Skipping this test because VersionedTransaction are getting rejected by the device (LatticeResponseCode.userDeclined) + test.skip('simulate versioned solana transaction', async () => { + const txInstruction = SystemProgram.transfer({ + fromPubkey: SIGNER_WALLET, + toPubkey: DESTINATION_WALLET_1.publicKey, + lamports: 0.001 * LAMPORTS_PER_SOL, + }) + + const transaction = new Transaction() + transaction.add(txInstruction) + transaction.recentBlockhash = latestBlockhash.blockhash + transaction.feePayer = SIGNER_WALLET + + // Serialize the transaction to get the wire format + const serializedTransaction = transaction.serialize({ + requireAllSignatures: false, + }) + + // Create a VersionedTransaction from the serialized data + const versionedTransaction = VersionedTransaction.deserialize(serializedTransaction) + + const signedTx = await signSolanaTx(Buffer.from(versionedTransaction.serialize())) + expect(signedTx).toBeTruthy() + }) + + test('simulate versioned solana transaction with multiple instructions', async () => { + const payer = Keypair.generate() + await requestAirdrop(payer.publicKey, 1) + + const [transactionInstruction, pubkey] = await AddressLookupTableProgram.createLookupTable({ + payer: payer.publicKey, + authority: payer.publicKey, + recentSlot: await SOLANA_RPC.getSlot(), + }) + + await AddressLookupTableProgram.extendLookupTable({ + payer: payer.publicKey, + authority: payer.publicKey, + lookupTable: pubkey, + addresses: [DESTINATION_WALLET_1.publicKey, DESTINATION_WALLET_2.publicKey], + }) + + const messageV0 = new TransactionMessage({ + payerKey: SIGNER_WALLET, + recentBlockhash: latestBlockhash.blockhash, + instructions: [transactionInstruction], + }).compileToV0Message() + + const signedTx = await signSolanaTx(Buffer.from(messageV0.serialize())) + + expect(signedTx).toBeDefined() + }) + + // Skipping this test because the messages are getting rejected by the device (LatticeResponseCode.userDeclined) + test.skip('simulate versioned solana transactions from nufi', async () => { + // sign transaction + const signedTx = await signSolanaTx( + Buffer.from( + '800100131b7154e1c10d16949038dd040363bca1f9d6501c694f6e0325ad817166fb2e91ee0c503f9578fb1f7621e5a0c2c384547f22cd4116be06e523219f332b61d41644801401dbe3c1c1f14a4eca5605ce5071e3301e79a79d95ecc50c73b977286be44ffdb307c4cce0aa81999d925e13f482e6deb72c8cc1d31ebf6b95478bd11f4f0cd8b42c34cd1d82beaec835fd525d1e90c248cf6849b277e78015a46e48789107a241e063d943cba135e306d4f8059f2ecf7a69744b993531a9d767674c85c7f6d2eabe47377602308731d0ac6ee6483c3a0f34b6aff13d37e53e00719032e928a52bdc5c44449874879e44c88ed5eb62b77a2b5e672ab9f50010479abc43e10306466fe5211732ffecadba72c39be7bc8ce5bbc5f7126b2c439b3a4000000006b5efb175d780346c18fa61f588190e5981c4668d651d67ad73c36e31180fdf9aca3abb5a18a0fe31a5031e7765dc6264a19fa84b3925e256dc2b4e53974b3c06b43c95041249ed7dedfc195aec0bc3ff2760a53409c79a7607b29fa3a12ad61393a26044ecc1adf84705420dd79ceeeca7be0dc63cd55a3994d9aba59b0e4303da8d1d21a468a26ba2183fd61de731ca4d5d61fd81296f97d916d81fd074ba18a633ac3c601aff49b0ffb943b9cdc1d89793f880bd8765868fd1ab42aa3b670286c0be1afb0442e17ee761789fe71c6e6914dd5771f7993092c9ca8bb05f3ae77546322de214d7944e9846236fe15e4f47731268ee9d6f60dcd058c96ea60a930890e9e6be0f4b35f0bf2f4c373f074ecc0404d221729981f11bb0dd5586fa55ac0b630d53e1fbf40c31f6163a1fed819daad6c34ec6eebbd2c9094ab8f0868ad7baf66ee465a9f41bc1fb7e3f72b663d4bfd34fe585e91fbd1f2753437cbad2569d176621a5d281ef6683261d8cc0da378f1139f1c01996edda54eb109ac4348ea0872c378762af7b1423c9c5b5ad1dd48584fcdbdb10e4922e01573228b8cd362cd18826752b72a9bf8c3d1fd7e3faeec4d44bd0f8988a902511c22b6ab46ba8557b567c6cbcd3e23efe17a9df8269dddf11d65a8f703314b84688b0bb9954004e7014187f35fb8aa37fc7e7cfe9c21e1e3def76baa18ab0668b61cf8bb66c55c082dfa8853bec08eeb7402096300a48b0b7259205ca703a1eaa032a21f6dcf2b4502abaead6d2b5495f0ea97222e2cd76aefb60c8d59d435e7a51cab236333b989b9593098437cfe28786fd22445b730c237a17d1110c8fef327d5f058e0308000502882502000800090333000000000000000922010a021b1c1d1e09030000040909040b0506070c0d0e0f101112131415161718191a7166063d1201daebea164609000000000016460900b9b0b7828d68598fa5ddf8fd4e8ba1315b7afae1785fb6ce5632d8699dd56fb15e4372ff949cf950d5dd678d5622184bc37b32d3a396f9d41ab609e65b1496b3040000000017262704000000010000008a02585c0a0000000000016400017b6eea3282722d20e3d0b0ce1eef6cf588aa519c82103084255fc33deb476d72000416170015', + 'hex', + ), + ) + + expect(signedTx).toBeDefined() + + // signMessage + const signedMessage = await signSolanaTx( + Buffer.from( + '6d616769636564656e2e696f2077616e747320796f7520746f207369676e20696e207769746820796f757220536f6c616e61206163636f756e743a0a386451393746414e6368337a75346348426942793556365a716478555365547858465873624b62436e6451710a0a57656c636f6d6520746f204d61676963204564656e2e205369676e696e6720697320746865206f6e6c79207761792077652063616e207472756c79206b6e6f77207468617420796f752061726520746865206f776e6572206f66207468652077616c6c657420796f752061726520636f6e6e656374696e672e205369676e696e67206973206120736166652c206761732d6c657373207472616e73616374696f6e207468617420646f6573206e6f7420696e20616e79207761792067697665204d61676963204564656e207065726d697373696f6e20746f20706572666f726d20616e79207472616e73616374696f6e73207769746820796f75722077616c6c65742e0a0a5552493a2068747470733a2f2f6d616769636564656e2e696f2f706f70756c61722d636f6c6c656374696f6e730a56657273696f6e3a20310a436861696e2049443a206d61696e6e65740a4e6f6e63653a2038373066313166646234373734353962393433353730316463366532353035350a4973737565642041743a20323032342d30372d30325431353a32383a34382e3736305a0a526571756573742049443a2063313331346235622d656365382d346234662d613837392d333839346464613336346534', + 'hex', + ), + ) + expect(signedMessage).toBeDefined() + }) +}) diff --git a/packages/sdk/src/__test__/e2e/signing/unformatted.test.ts b/packages/sdk/src/__test__/e2e/signing/unformatted.test.ts index a9e0df88..1242a3c8 100644 --- a/packages/sdk/src/__test__/e2e/signing/unformatted.test.ts +++ b/packages/sdk/src/__test__/e2e/signing/unformatted.test.ts @@ -1,168 +1,159 @@ -import { Constants } from '../../..'; -import { HARDENED_OFFSET } from '../../../constants'; -import { getNumIter } from '../../utils/builders'; -import { getPrng } from '../../utils/getters'; -import { ethPersonalSignMsg, prandomBuf } from '../../utils/helpers'; -import { runGeneric } from '../../utils/runners'; -import { setupClient } from '../../utils/setup'; - -const prng = getPrng(); -const numIter = getNumIter(); -const DEFAULT_SIGNER = [ - HARDENED_OFFSET + 44, - HARDENED_OFFSET + 60, - HARDENED_OFFSET, - 0, - 0, -]; +import { Constants } from '../../..' +import { HARDENED_OFFSET } from '../../../constants' +import { getNumIter } from '../../utils/builders' +import { getPrng } from '../../utils/getters' +import { ethPersonalSignMsg, prandomBuf } from '../../utils/helpers' +import { runGeneric } from '../../utils/runners' +import { setupClient } from '../../utils/setup' +import type { Client } from '../../../client' + +const prng = getPrng() +const numIter = getNumIter() +const DEFAULT_SIGNER = [HARDENED_OFFSET + 44, HARDENED_OFFSET + 60, HARDENED_OFFSET, 0, 0] describe('[Unformatted]', () => { - let client; - - beforeAll(async () => { - client = await setupClient(); - }); - - const getReq = (overrides: any) => ({ - data: { - signerPath: DEFAULT_SIGNER, - curveType: Constants.SIGNING.CURVES.SECP256K1, - hashType: Constants.SIGNING.HASHES.KECCAK256, - payload: null, - ...overrides, - }, - }); - - it('Should test pre-hashed messages', async () => { - const fwConstants = client.getFwConstants(); - const { extraDataFrameSz, extraDataMaxFrames, genericSigning } = - fwConstants; - const { baseDataSz } = genericSigning; - // Max size that won't be prehashed - const maxSz = baseDataSz + extraDataMaxFrames * extraDataFrameSz; - - // Use extraData frames - await runGeneric( - getReq({ - payload: `0x${prandomBuf(prng, maxSz, true).toString('hex')}`, - }), - client, - ); - - // Prehash (keccak256) - await runGeneric( - getReq({ - payload: `0x${prandomBuf(prng, maxSz + 1, true).toString('hex')}`, - }), - client, - ); - - // Prehash (sha256) - await runGeneric( - getReq({ - payload: `0x${prandomBuf(prng, maxSz + 1, true).toString('hex')}`, - hashType: Constants.SIGNING.HASHES.SHA256, - }), - client, - ); - }); - - it('Should test ASCII text formatting', async () => { - // Build a payload that uses spaces and newlines - await runGeneric( - getReq({ - payload: JSON.stringify( - { - testPayload: 'json with spaces', - anotherThing: -1, - }, - null, - 2, - ), - }), - client, - ); - }); - - it('Should validate SECP256K1/KECCAK signature against derived key', async () => { - // ASCII message encoding - await runGeneric(getReq({ payload: 'test' }), client); - - // Hex message encoding - const req = getReq({ payload: '0x123456' }); - await runGeneric(req, client); - }); - - it('Should validate ED25519/NULL signature against derived key', async () => { - const req = getReq({ - payload: '0x123456', - curveType: Constants.SIGNING.CURVES.ED25519, - /* Not doing anything. It is commented out. */ - hashType: Constants.SIGNING.HASHES.NONE, - // ED25519 derivation requires hardened indices - signerPath: DEFAULT_SIGNER.slice(0, 3), - }); - // Make generic signing request - await runGeneric(req, client); - }); - - it('Should validate SECP256K1/KECCAK signature against ETH_MSG request (legacy)', async () => { - // Generic request - const msg = 'Testing personal_sign'; - const psMsg = ethPersonalSignMsg(msg); - // NOTE: The request contains some non ASCII characters so it will get - // encoded as hex automatically. - const req = getReq({ - payload: psMsg, - }); - - const respGeneric = await runGeneric(req, client); - - // Legacy request - const legacyReq = { - currency: 'ETH_MSG', - data: { - signerPath: req.data.signerPath, - payload: msg, - protocol: 'signPersonal', - }, - }; - const respLegacy = await client.sign(legacyReq); - - const genSigR = (respGeneric.sig?.r as Buffer)?.toString('hex') ?? ''; - const genSigS = (respGeneric.sig?.s as Buffer)?.toString('hex') ?? ''; - const legSigR = (respLegacy.sig?.r as Buffer)?.toString('hex') ?? ''; - const legSigS = (respLegacy.sig?.s as Buffer)?.toString('hex') ?? ''; - - const genSig = `${genSigR}${genSigS}`; - const legSig = `${legSigR}${legSigS}`; - expect(genSig).toEqualElseLog( - legSig, - 'Legacy and generic requests produced different sigs.', - ); - }); - - for (let i = 0; i < numIter; i++) { - it(`Should test random payloads - #${i}`, async () => { - const fwConstants = client.getFwConstants(); - const req = getReq({ - hashType: Constants.SIGNING.HASHES.KECCAK256, - curveType: Constants.SIGNING.CURVES.SECP256K1, - payload: prandomBuf(prng, fwConstants.genericSigning.baseDataSz), - }); - - // 1. Secp256k1/keccak256 - await runGeneric(req, client); - - // 2. Secp256k1/sha256 - req.data.hashType = Constants.SIGNING.HASHES.SHA256; - await runGeneric(req, client); - - // 3. Ed25519 - req.data.curveType = Constants.SIGNING.CURVES.ED25519; - req.data.hashType = Constants.SIGNING.HASHES.NONE; - req.data.signerPath = DEFAULT_SIGNER.slice(0, 3); - await runGeneric(req, client); - }); - } -}); + let client: Client + + beforeAll(async () => { + client = await setupClient() + }) + + const getReq = (overrides: any) => ({ + data: { + signerPath: DEFAULT_SIGNER, + curveType: Constants.SIGNING.CURVES.SECP256K1, + hashType: Constants.SIGNING.HASHES.KECCAK256, + payload: null, + ...overrides, + }, + }) + + it('Should test pre-hashed messages', async () => { + const fwConstants = client.getFwConstants() + const { extraDataFrameSz, extraDataMaxFrames, genericSigning } = fwConstants + const { baseDataSz } = genericSigning + // Max size that won't be prehashed + const maxSz = baseDataSz + extraDataMaxFrames * extraDataFrameSz + + // Use extraData frames + await runGeneric( + getReq({ + payload: `0x${prandomBuf(prng, maxSz, true).toString('hex')}`, + }), + client, + ) + + // Prehash (keccak256) + await runGeneric( + getReq({ + payload: `0x${prandomBuf(prng, maxSz + 1, true).toString('hex')}`, + }), + client, + ) + + // Prehash (sha256) + await runGeneric( + getReq({ + payload: `0x${prandomBuf(prng, maxSz + 1, true).toString('hex')}`, + hashType: Constants.SIGNING.HASHES.SHA256, + }), + client, + ) + }) + + it('Should test ASCII text formatting', async () => { + // Build a payload that uses spaces and newlines + await runGeneric( + getReq({ + payload: JSON.stringify( + { + testPayload: 'json with spaces', + anotherThing: -1, + }, + null, + 2, + ), + }), + client, + ) + }) + + it('Should validate SECP256K1/KECCAK signature against derived key', async () => { + // ASCII message encoding + await runGeneric(getReq({ payload: 'test' }), client) + + // Hex message encoding + const req = getReq({ payload: '0x123456' }) + await runGeneric(req, client) + }) + + it('Should validate ED25519/NULL signature against derived key', async () => { + const req = getReq({ + payload: '0x123456', + curveType: Constants.SIGNING.CURVES.ED25519, + /* Not doing anything. It is commented out. */ + hashType: Constants.SIGNING.HASHES.NONE, + // ED25519 derivation requires hardened indices + signerPath: DEFAULT_SIGNER.slice(0, 3), + }) + // Make generic signing request + await runGeneric(req, client) + }) + + it('Should validate SECP256K1/KECCAK signature against ETH_MSG request (legacy)', async () => { + // Generic request + const msg = 'Testing personal_sign' + const psMsg = ethPersonalSignMsg(msg) + // NOTE: The request contains some non ASCII characters so it will get + // encoded as hex automatically. + const req = getReq({ + payload: psMsg, + }) + + const respGeneric = await runGeneric(req, client) + + // Legacy request + const legacyReq = { + currency: 'ETH_MSG', + data: { + signerPath: req.data.signerPath, + payload: msg, + protocol: 'signPersonal', + }, + } + const respLegacy = await client.sign(legacyReq) + + const genSigR = (respGeneric.sig?.r as Buffer)?.toString('hex') ?? '' + const genSigS = (respGeneric.sig?.s as Buffer)?.toString('hex') ?? '' + const legSigR = (respLegacy.sig?.r as Buffer)?.toString('hex') ?? '' + const legSigS = (respLegacy.sig?.s as Buffer)?.toString('hex') ?? '' + + const genSig = `${genSigR}${genSigS}` + const legSig = `${legSigR}${legSigS}` + expect(genSig).toEqualElseLog(legSig, 'Legacy and generic requests produced different sigs.') + }) + + for (let i = 0; i < numIter; i++) { + it(`Should test random payloads - #${i}`, async () => { + const fwConstants = client.getFwConstants() + const req = getReq({ + hashType: Constants.SIGNING.HASHES.KECCAK256, + curveType: Constants.SIGNING.CURVES.SECP256K1, + payload: prandomBuf(prng, fwConstants.genericSigning.baseDataSz), + }) + + // 1. Secp256k1/keccak256 + await runGeneric(req, client) + + // 2. Secp256k1/sha256 + req.data.hashType = Constants.SIGNING.HASHES.SHA256 + await runGeneric(req, client) + + // 3. Ed25519 + req.data.curveType = Constants.SIGNING.CURVES.ED25519 + req.data.hashType = Constants.SIGNING.HASHES.NONE + req.data.signerPath = DEFAULT_SIGNER.slice(0, 3) + await runGeneric(req, client) + }) + } +}) diff --git a/packages/sdk/src/__test__/e2e/signing/vectors.ts b/packages/sdk/src/__test__/e2e/signing/vectors.ts index 752df529..1294e416 100644 --- a/packages/sdk/src/__test__/e2e/signing/vectors.ts +++ b/packages/sdk/src/__test__/e2e/signing/vectors.ts @@ -33,12 +33,12 @@ * that a hardware wallet needs to handle correctly. */ -import type { TransactionSerializable } from 'viem'; +import type { TransactionSerializable } from 'viem' export interface TestVector { - name: string; - tx: TransactionSerializable; - category?: string; + name: string + tx: TransactionSerializable + category?: string } // ============================================================================= @@ -46,674 +46,640 @@ export interface TestVector { // ============================================================================= export const LEGACY_VECTORS: TestVector[] = [ - { - name: 'Simple ETH transfer - Legacy', - tx: { - type: 'legacy', - to: '0xe242e54155b1abc71fc118065270cecaaf8b7768' as `0x${string}`, - value: BigInt('1000000000000000000'), // 1 ETH - data: '0x' as `0x${string}`, // Add missing data field - nonce: 0, - gasPrice: BigInt('20000000000'), // 20 gwei - gas: BigInt('21000'), - chainId: 1, - }, - category: 'basic-transfer', - }, - { - name: 'Contract interaction - Legacy', - tx: { - type: 'legacy', - to: '0x6B175474E89094C44Da98b954EedeAC495271d0F' as `0x${string}`, // DAI - value: BigInt(0), - data: '0xa9059cbb000000000000000000000000742d35cc6b2d9e9a3b7b7b4a7b0b2b7b0b2b7b0b0000000000000000000000000000000000000000000000001bc16d674ec80000' as `0x${string}`, - nonce: 1, - gasPrice: BigInt('25000000000'), // 25 gwei - gas: BigInt('100000'), - chainId: 1, - }, - category: 'contract-call', - }, - { - name: 'Zero value transaction - Legacy', - tx: { - type: 'legacy', - to: '0xe242e54155b1abc71fc118065270cecaaf8b7768' as `0x${string}`, - value: BigInt(0), - data: '0x' as `0x${string}`, // Add missing data field - nonce: 5, - gasPrice: BigInt('10000000000'), // 10 gwei - gas: BigInt('21000'), - chainId: 1, - }, - category: 'zero-value', - }, - { - name: 'High nonce transaction - Legacy', - tx: { - type: 'legacy', - to: '0xe242e54155b1abc71fc118065270cecaaf8b7768' as `0x${string}`, - value: BigInt('500000000000000000'), // 0.5 ETH - data: '0x' as `0x${string}`, // Add missing data field - nonce: 999, - gasPrice: BigInt('50000000000'), // 50 gwei - gas: BigInt('21000'), - chainId: 1, - }, - category: 'high-nonce', - }, - { - name: 'Polygon Legacy transaction', - tx: { - type: 'legacy', - to: '0xe242e54155b1abc71fc118065270cecaaf8b7768' as `0x${string}`, - value: BigInt('1000000000000000000'), // 1 MATIC - data: '0x' as `0x${string}`, // Add missing data field - nonce: 0, - gasPrice: BigInt('30000000000'), // 30 gwei - gas: BigInt('21000'), - chainId: 137, - }, - category: 'polygon', - }, -]; + { + name: 'Simple ETH transfer - Legacy', + tx: { + type: 'legacy', + to: '0xe242e54155b1abc71fc118065270cecaaf8b7768' as `0x${string}`, + value: BigInt('1000000000000000000'), // 1 ETH + data: '0x' as `0x${string}`, // Add missing data field + nonce: 0, + gasPrice: BigInt('20000000000'), // 20 gwei + gas: BigInt('21000'), + chainId: 1, + }, + category: 'basic-transfer', + }, + { + name: 'Contract interaction - Legacy', + tx: { + type: 'legacy', + to: '0x6B175474E89094C44Da98b954EedeAC495271d0F' as `0x${string}`, // DAI + value: BigInt(0), + data: '0xa9059cbb000000000000000000000000742d35cc6b2d9e9a3b7b7b4a7b0b2b7b0b2b7b0b0000000000000000000000000000000000000000000000001bc16d674ec80000' as `0x${string}`, + nonce: 1, + gasPrice: BigInt('25000000000'), // 25 gwei + gas: BigInt('100000'), + chainId: 1, + }, + category: 'contract-call', + }, + { + name: 'Zero value transaction - Legacy', + tx: { + type: 'legacy', + to: '0xe242e54155b1abc71fc118065270cecaaf8b7768' as `0x${string}`, + value: BigInt(0), + data: '0x' as `0x${string}`, // Add missing data field + nonce: 5, + gasPrice: BigInt('10000000000'), // 10 gwei + gas: BigInt('21000'), + chainId: 1, + }, + category: 'zero-value', + }, + { + name: 'High nonce transaction - Legacy', + tx: { + type: 'legacy', + to: '0xe242e54155b1abc71fc118065270cecaaf8b7768' as `0x${string}`, + value: BigInt('500000000000000000'), // 0.5 ETH + data: '0x' as `0x${string}`, // Add missing data field + nonce: 999, + gasPrice: BigInt('50000000000'), // 50 gwei + gas: BigInt('21000'), + chainId: 1, + }, + category: 'high-nonce', + }, + { + name: 'Polygon Legacy transaction', + tx: { + type: 'legacy', + to: '0xe242e54155b1abc71fc118065270cecaaf8b7768' as `0x${string}`, + value: BigInt('1000000000000000000'), // 1 MATIC + data: '0x' as `0x${string}`, // Add missing data field + nonce: 0, + gasPrice: BigInt('30000000000'), // 30 gwei + gas: BigInt('21000'), + chainId: 137, + }, + category: 'polygon', + }, +] // ============================================================================= // EIP-1559 TRANSACTION VECTORS (Fee Market) // ============================================================================= export const EIP1559_TEST_VECTORS: TestVector[] = [ - { - name: 'Simple ETH transfer - EIP-1559', - tx: { - type: 'eip1559', - to: '0xe242e54155b1abc71fc118065270cecaaf8b7768' as `0x${string}`, - value: BigInt('1000000000000000000'), // 1 ETH - nonce: 0, - maxFeePerGas: BigInt('30000000000'), // 30 gwei - maxPriorityFeePerGas: BigInt('2000000000'), // 2 gwei - gas: BigInt('21000'), - chainId: 1, - }, - category: 'basic-transfer', - }, - { - name: 'DAI transfer - EIP-1559', - tx: { - type: 'eip1559', - to: '0x6B175474E89094C44Da98b954EedeAC495271d0F' as `0x${string}`, // DAI - value: BigInt(0), - data: '0xa9059cbb000000000000000000000000742d35cc6b2d9e9a3b7b7b4a7b0b2b7b0b2b7b0b0000000000000000000000000000000000000000000000001bc16d674ec80000' as `0x${string}`, - nonce: 1, - maxFeePerGas: BigInt('40000000000'), // 40 gwei - maxPriorityFeePerGas: BigInt('3000000000'), // 3 gwei - gas: BigInt('100000'), - chainId: 1, - }, - category: 'erc20-transfer', - }, - { - name: 'Uniswap V2 swap - EIP-1559', - tx: { - type: 'eip1559', - to: '0x7a250d5630B4cF539739dF2C5dAcb4c659F2488D' as `0x${string}`, // Uniswap V2 Router - value: BigInt(0), - data: '0x38ed17390000000000000000000000000000000000000000000000000de0b6b3a76400000000000000000000000000000000000000000000000000000c7d713b49da000000000000000000000000000000000000000000000000000000000000000000a0000000000000000000000000742d35cc6b2d9e9a3b7b7b4a7b0b2b7b0b2b7b0b000000000000000000000000000000000000000000000000000000006553f10000000000000000000000000000000000000000000000000000000000000000020000000000000000000000006b175474e89094c44da98b954eedeac495271d0f000000000000000000000000dac17f958d2ee523a2206206994597c13d831ec7' as `0x${string}`, - nonce: 2, - maxFeePerGas: BigInt('50000000000'), // 50 gwei - maxPriorityFeePerGas: BigInt('5000000000'), // 5 gwei - gas: BigInt('200000'), - chainId: 1, - }, - category: 'defi-swap', - }, - { - name: 'WETH deposit - EIP-1559', - tx: { - type: 'eip1559', - to: '0xC02aaA39b223FE8D0A0e5C4F27eAD9083C756Cc2' as `0x${string}`, // WETH - value: BigInt('1000000000000000000'), // 1 ETH - data: '0xd0e30db0' as `0x${string}`, // deposit() - nonce: 3, - maxFeePerGas: BigInt('25000000000'), // 25 gwei - maxPriorityFeePerGas: BigInt('2000000000'), // 2 gwei - gas: BigInt('100000'), - chainId: 1, - }, - category: 'payable-contract', - }, - { - name: 'High priority fee - EIP-1559', - tx: { - type: 'eip1559', - to: '0xe242e54155b1abc71fc118065270cecaaf8b7768' as `0x${string}`, - value: BigInt('500000000000000000'), // 0.5 ETH - nonce: 10, - maxFeePerGas: BigInt('100000000000'), // 100 gwei - maxPriorityFeePerGas: BigInt('50000000000'), // 50 gwei - gas: BigInt('21000'), - chainId: 1, - }, - category: 'high-priority', - }, - { - name: 'Polygon EIP-1559 transaction', - tx: { - type: 'eip1559', - to: '0xe242e54155b1abc71fc118065270cecaaf8b7768' as `0x${string}`, - value: BigInt('1000000000000000000'), // 1 MATIC - nonce: 0, - maxFeePerGas: BigInt('30000000000'), // 30 gwei - maxPriorityFeePerGas: BigInt('30000000000'), // 30 gwei (Polygon often has high priority fees) - gas: BigInt('21000'), - chainId: 137, - }, - category: 'polygon', - }, -]; + { + name: 'Simple ETH transfer - EIP-1559', + tx: { + type: 'eip1559', + to: '0xe242e54155b1abc71fc118065270cecaaf8b7768' as `0x${string}`, + value: BigInt('1000000000000000000'), // 1 ETH + nonce: 0, + maxFeePerGas: BigInt('30000000000'), // 30 gwei + maxPriorityFeePerGas: BigInt('2000000000'), // 2 gwei + gas: BigInt('21000'), + chainId: 1, + }, + category: 'basic-transfer', + }, + { + name: 'DAI transfer - EIP-1559', + tx: { + type: 'eip1559', + to: '0x6B175474E89094C44Da98b954EedeAC495271d0F' as `0x${string}`, // DAI + value: BigInt(0), + data: '0xa9059cbb000000000000000000000000742d35cc6b2d9e9a3b7b7b4a7b0b2b7b0b2b7b0b0000000000000000000000000000000000000000000000001bc16d674ec80000' as `0x${string}`, + nonce: 1, + maxFeePerGas: BigInt('40000000000'), // 40 gwei + maxPriorityFeePerGas: BigInt('3000000000'), // 3 gwei + gas: BigInt('100000'), + chainId: 1, + }, + category: 'erc20-transfer', + }, + { + name: 'Uniswap V2 swap - EIP-1559', + tx: { + type: 'eip1559', + to: '0x7a250d5630B4cF539739dF2C5dAcb4c659F2488D' as `0x${string}`, // Uniswap V2 Router + value: BigInt(0), + data: '0x38ed17390000000000000000000000000000000000000000000000000de0b6b3a76400000000000000000000000000000000000000000000000000000c7d713b49da000000000000000000000000000000000000000000000000000000000000000000a0000000000000000000000000742d35cc6b2d9e9a3b7b7b4a7b0b2b7b0b2b7b0b000000000000000000000000000000000000000000000000000000006553f10000000000000000000000000000000000000000000000000000000000000000020000000000000000000000006b175474e89094c44da98b954eedeac495271d0f000000000000000000000000dac17f958d2ee523a2206206994597c13d831ec7' as `0x${string}`, + nonce: 2, + maxFeePerGas: BigInt('50000000000'), // 50 gwei + maxPriorityFeePerGas: BigInt('5000000000'), // 5 gwei + gas: BigInt('200000'), + chainId: 1, + }, + category: 'defi-swap', + }, + { + name: 'WETH deposit - EIP-1559', + tx: { + type: 'eip1559', + to: '0xC02aaA39b223FE8D0A0e5C4F27eAD9083C756Cc2' as `0x${string}`, // WETH + value: BigInt('1000000000000000000'), // 1 ETH + data: '0xd0e30db0' as `0x${string}`, // deposit() + nonce: 3, + maxFeePerGas: BigInt('25000000000'), // 25 gwei + maxPriorityFeePerGas: BigInt('2000000000'), // 2 gwei + gas: BigInt('100000'), + chainId: 1, + }, + category: 'payable-contract', + }, + { + name: 'High priority fee - EIP-1559', + tx: { + type: 'eip1559', + to: '0xe242e54155b1abc71fc118065270cecaaf8b7768' as `0x${string}`, + value: BigInt('500000000000000000'), // 0.5 ETH + nonce: 10, + maxFeePerGas: BigInt('100000000000'), // 100 gwei + maxPriorityFeePerGas: BigInt('50000000000'), // 50 gwei + gas: BigInt('21000'), + chainId: 1, + }, + category: 'high-priority', + }, + { + name: 'Polygon EIP-1559 transaction', + tx: { + type: 'eip1559', + to: '0xe242e54155b1abc71fc118065270cecaaf8b7768' as `0x${string}`, + value: BigInt('1000000000000000000'), // 1 MATIC + nonce: 0, + maxFeePerGas: BigInt('30000000000'), // 30 gwei + maxPriorityFeePerGas: BigInt('30000000000'), // 30 gwei (Polygon often has high priority fees) + gas: BigInt('21000'), + chainId: 137, + }, + category: 'polygon', + }, +] // ============================================================================= // EIP-2930 TRANSACTION VECTORS (Access Lists) // ============================================================================= export const EIP2930_TEST_VECTORS: TestVector[] = [ - { - name: 'Simple transfer with access list - EIP-2930', - tx: { - type: 'eip2930', - to: '0xe242e54155b1abc71fc118065270cecaaf8b7768' as `0x${string}`, - value: BigInt('1000000000000000000'), // 1 ETH - nonce: 0, - gasPrice: BigInt('20000000000'), // 20 gwei - gas: BigInt('21000'), - chainId: 1, - accessList: [ - { - address: - '0xe242e54155b1abc71fc118065270cecaaf8b7768' as `0x${string}`, - storageKeys: [ - '0x0000000000000000000000000000000000000000000000000000000000000001' as `0x${string}`, - ], - }, - ], - }, - category: 'simple-access-list', - }, - { - name: 'Contract interaction with multiple access entries - EIP-2930', - tx: { - type: 'eip2930', - to: '0x6B175474E89094C44Da98b954EedeAC495271d0F' as `0x${string}`, // DAI - value: BigInt(0), - data: '0xa9059cbb000000000000000000000000742d35cc6b2d9e9a3b7b7b4a7b0b2b7b0b2b7b0b0000000000000000000000000000000000000000000000001bc16d674ec80000' as `0x${string}`, - nonce: 1, - gasPrice: BigInt('25000000000'), // 25 gwei - gas: BigInt('100000'), - chainId: 1, - accessList: [ - { - address: - '0x6B175474E89094C44Da98b954EedeAC495271d0F' as `0x${string}`, // DAI contract - storageKeys: [ - '0x0000000000000000000000000000000000000000000000000000000000000001' as `0x${string}`, - '0x0000000000000000000000000000000000000000000000000000000000000002' as `0x${string}`, - ], - }, - { - address: - '0xe242e54155b1abc71fc118065270cecaaf8b7768' as `0x${string}`, // Recipient - storageKeys: [ - '0x0000000000000000000000000000000000000000000000000000000000000003' as `0x${string}`, - ], - }, - ], - }, - category: 'multi-access-list', - }, - { - name: 'Empty access list - EIP-2930', - tx: { - type: 'eip2930', - to: '0xe242e54155b1abc71fc118065270cecaaf8b7768' as `0x${string}`, - value: BigInt('500000000000000000'), // 0.5 ETH - nonce: 5, - gasPrice: BigInt('15000000000'), // 15 gwei - gas: BigInt('21000'), - chainId: 1, - accessList: [], - }, - category: 'empty-access-list', - }, - { - name: 'Complex DeFi interaction - EIP-2930', - tx: { - type: 'eip2930', - to: '0x7a250d5630B4cF539739dF2C5dAcb4c659F2488D' as `0x${string}`, // Uniswap V2 Router - value: BigInt(0), - data: '0x38ed17390000000000000000000000000000000000000000000000000de0b6b3a76400000000000000000000000000000000000000000000000000000c7d713b49da000000000000000000000000000000000000000000000000000000000000000000a0000000000000000000000000742d35cc6b2d9e9a3b7b7b4a7b0b2b7b0b2b7b0b000000000000000000000000000000000000000000000000000000006553f10000000000000000000000000000000000000000000000000000000000000000020000000000000000000000006b175474e89094c44da98b954eedeac495271d0f000000000000000000000000dac17f958d2ee523a2206206994597c13d831ec7' as `0x${string}`, - nonce: 10, - gasPrice: BigInt('30000000000'), // 30 gwei - gas: BigInt('200000'), - chainId: 1, - accessList: [ - { - address: - '0x7a250d5630B4cF539739dF2C5dAcb4c659F2488D' as `0x${string}`, // Router - storageKeys: [ - '0x0000000000000000000000000000000000000000000000000000000000000001' as `0x${string}`, - '0x0000000000000000000000000000000000000000000000000000000000000002' as `0x${string}`, - ], - }, - { - address: - '0x6B175474E89094C44Da98b954EedeAC495271d0F' as `0x${string}`, // DAI - storageKeys: [ - '0x0000000000000000000000000000000000000000000000000000000000000003' as `0x${string}`, - '0x0000000000000000000000000000000000000000000000000000000000000004' as `0x${string}`, - ], - }, - { - address: - '0xdAC17F958D2ee523a2206206994597C13D831ec7' as `0x${string}`, // USDT - storageKeys: [ - '0x0000000000000000000000000000000000000000000000000000000000000005' as `0x${string}`, - ], - }, - ], - }, - category: 'defi-complex', - }, -]; + { + name: 'Simple transfer with access list - EIP-2930', + tx: { + type: 'eip2930', + to: '0xe242e54155b1abc71fc118065270cecaaf8b7768' as `0x${string}`, + value: BigInt('1000000000000000000'), // 1 ETH + nonce: 0, + gasPrice: BigInt('20000000000'), // 20 gwei + gas: BigInt('21000'), + chainId: 1, + accessList: [ + { + address: '0xe242e54155b1abc71fc118065270cecaaf8b7768' as `0x${string}`, + storageKeys: ['0x0000000000000000000000000000000000000000000000000000000000000001' as `0x${string}`], + }, + ], + }, + category: 'simple-access-list', + }, + { + name: 'Contract interaction with multiple access entries - EIP-2930', + tx: { + type: 'eip2930', + to: '0x6B175474E89094C44Da98b954EedeAC495271d0F' as `0x${string}`, // DAI + value: BigInt(0), + data: '0xa9059cbb000000000000000000000000742d35cc6b2d9e9a3b7b7b4a7b0b2b7b0b2b7b0b0000000000000000000000000000000000000000000000001bc16d674ec80000' as `0x${string}`, + nonce: 1, + gasPrice: BigInt('25000000000'), // 25 gwei + gas: BigInt('100000'), + chainId: 1, + accessList: [ + { + address: '0x6B175474E89094C44Da98b954EedeAC495271d0F' as `0x${string}`, // DAI contract + storageKeys: ['0x0000000000000000000000000000000000000000000000000000000000000001' as `0x${string}`, '0x0000000000000000000000000000000000000000000000000000000000000002' as `0x${string}`], + }, + { + address: '0xe242e54155b1abc71fc118065270cecaaf8b7768' as `0x${string}`, // Recipient + storageKeys: ['0x0000000000000000000000000000000000000000000000000000000000000003' as `0x${string}`], + }, + ], + }, + category: 'multi-access-list', + }, + { + name: 'Empty access list - EIP-2930', + tx: { + type: 'eip2930', + to: '0xe242e54155b1abc71fc118065270cecaaf8b7768' as `0x${string}`, + value: BigInt('500000000000000000'), // 0.5 ETH + nonce: 5, + gasPrice: BigInt('15000000000'), // 15 gwei + gas: BigInt('21000'), + chainId: 1, + accessList: [], + }, + category: 'empty-access-list', + }, + { + name: 'Complex DeFi interaction - EIP-2930', + tx: { + type: 'eip2930', + to: '0x7a250d5630B4cF539739dF2C5dAcb4c659F2488D' as `0x${string}`, // Uniswap V2 Router + value: BigInt(0), + data: '0x38ed17390000000000000000000000000000000000000000000000000de0b6b3a76400000000000000000000000000000000000000000000000000000c7d713b49da000000000000000000000000000000000000000000000000000000000000000000a0000000000000000000000000742d35cc6b2d9e9a3b7b7b4a7b0b2b7b0b2b7b0b000000000000000000000000000000000000000000000000000000006553f10000000000000000000000000000000000000000000000000000000000000000020000000000000000000000006b175474e89094c44da98b954eedeac495271d0f000000000000000000000000dac17f958d2ee523a2206206994597c13d831ec7' as `0x${string}`, + nonce: 10, + gasPrice: BigInt('30000000000'), // 30 gwei + gas: BigInt('200000'), + chainId: 1, + accessList: [ + { + address: '0x7a250d5630B4cF539739dF2C5dAcb4c659F2488D' as `0x${string}`, // Router + storageKeys: ['0x0000000000000000000000000000000000000000000000000000000000000001' as `0x${string}`, '0x0000000000000000000000000000000000000000000000000000000000000002' as `0x${string}`], + }, + { + address: '0x6B175474E89094C44Da98b954EedeAC495271d0F' as `0x${string}`, // DAI + storageKeys: ['0x0000000000000000000000000000000000000000000000000000000000000003' as `0x${string}`, '0x0000000000000000000000000000000000000000000000000000000000000004' as `0x${string}`], + }, + { + address: '0xdAC17F958D2ee523a2206206994597C13D831ec7' as `0x${string}`, // USDT + storageKeys: ['0x0000000000000000000000000000000000000000000000000000000000000005' as `0x${string}`], + }, + ], + }, + category: 'defi-complex', + }, +] // ============================================================================= // EIP-7702 TRANSACTION VECTORS (Account Abstraction) // ============================================================================= export const EIP7702_TEST_VECTORS: TestVector[] = [ - { - name: 'Real mainnet EIP-7702 transaction - Simple authorization', - tx: { - type: 'eip7702', - to: '0x0000000071727De22E5E9d8BAf0edAc6f37da032' as `0x${string}`, - value: BigInt(0), - data: '0x765e827f00000000000000000000000000000000000000000000000000000000000000400000000000000000000000004337001fff419768e088ce247456c1b89288808400000000000000000000000000000000000000000000000000000000000000010000000000000000000000000000000000000000000000000000000000000020000000000000000000000000e02cb371a3ad18a14b40a7ef2d5cf03cc0d2b08f45f7e80000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000012000000000000000000000000000000000000000000000000000000000000001400000000000000000000000000001a1a00000000000000000000000000000c4040000000000000000000000000000000000000000000000000000000000016b530000000000000000000000000321162000000000000000000000000242ef2e9500000000000000000000000000000000000000000000000000000000000002600000000000000000000000000000000000000000000000000000000000000280000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000e4e9ae5c53000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000400000000000000000000000000000000000000000000000000000000000000078d0ec028a3d21533fdd200838f39c85b03679285d0000000000000000000000000000000000000000000000000000000000000000a9059cbb000000000000000000000000d7bd3ba35431d1cf8ad71300794d8958e34dcf850000000000000000000000000000000000000000000000020f5b1eaad8d80000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000415355c70dde835c7dabee4bc7a36be2f62f432da40b08b30ec733be5802dba7d647992b9b57035f27ddc43151285d1ead3c9b6df9485cbdeb37fea884e5d7e1c61b00000000000000000000000000000000000000000000000000000000000000' as `0x${string}`, - nonce: 23978, - maxFeePerGas: BigInt('7918212158'), - maxPriorityFeePerGas: BigInt('7918212158'), - gas: BigInt('260220'), - chainId: 1, - accessList: [], - authorizationList: [ - { - chainId: 1, - address: - '0x000000004f43c49e93c970e84001853a70923b03' as `0x${string}`, - nonce: 1, - yParity: 1, - r: '0xc9f7e0af53f516744bc34827bef7236df3123c3a07a601dca75d7698416adc4a' as `0x${string}`, - s: '0x5e8ec8137222d3b97016889093745707d0871cba3a5e8e32aad129dfd2a45727' as `0x${string}`, - }, - ], - }, - category: 'simple-auth', - }, - { - name: 'Real mainnet EIP-7702 transaction - Different authorization', - tx: { - type: 'eip7702', - to: '0x0000000071727De22E5E9d8BAf0edAc6f37da032' as `0x${string}`, - value: BigInt(0), - data: '0x765e827f00000000000000000000000000000000000000000000000000000000000000400000000000000000000000004337002c5702ce424cb62a56ca038e31e1d4a93d00000000000000000000000000000000000000000000000000000000000000010000000000000000000000000000000000000000000000000000000000000020000000000000000000000000f60d2b5657bde93983552c6509deb6201c9ef0dc4601700000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000012000000000000000000000000000000000000000000000000000000000000001400000000000000000000000000001a1a00000000000000000000000000000c4040000000000000000000000000000000000000000000000000000000000016b600000000000000000000000000321162000000000000000000000000242ef2e9500000000000000000000000000000000000000000000000000000000000002600000000000000000000000000000000000000000000000000000000000000280000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000e4e9ae5c53000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000400000000000000000000000000000000000000000000000000000000000000078d0ec028a3d21533fdd200838f39c85b03679285d0000000000000000000000000000000000000000000000000000000000000000a9059cbb0000000000000000000000008bf6fbea0be049e1eeb1f3287054c058c73c9f1b000000000000000000000000000000000000000000000002a802f8630a24000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000041453a20e7cc10dd3fff382ca4d15f0f418e3016ec9d58c8d6ab984bb8e81025e833f4d778d87d8268e24d438f49b383a0e23d24a43b8d8111bef58caa7707c20d1b00000000000000000000000000000000000000000000000000000000000000' as `0x${string}`, - nonce: 23736, - maxFeePerGas: BigInt('8358605855'), - maxPriorityFeePerGas: BigInt('8358605855'), - gas: BigInt('260220'), - chainId: 1, - accessList: [], - authorizationList: [ - { - chainId: 1, - address: - '0x000000004f43c49e93c970e84001853a70923b03' as `0x${string}`, - nonce: 1, - yParity: 0, - r: '0x948c69c40057e9fd4c9bb55506ef764bf80d1bbaf980fe8c09d9d9c0b67d0e49' as `0x${string}`, - s: '0x6554554e4b8dd345eb6c73cf88cb212d8e428fc4dff73a54b9b329c793ee2382' as `0x${string}`, - }, - ], - }, - category: 'different-auth', - }, - { - name: 'Real mainnet EIP-7702 transaction - High value authorization', - tx: { - type: 'eip7702', - to: '0xA935433DE1E70538269c417a01543b3Da8478A48' as `0x${string}`, - value: BigInt(0), - data: '0x2c7bddf4' as `0x${string}`, - nonce: 1185, - maxFeePerGas: BigInt('30555080604'), - maxPriorityFeePerGas: BigInt('30555080604'), - gas: BigInt('100000'), - chainId: 1, - accessList: [], - authorizationList: [ - { - chainId: 1, - address: - '0x163193c89de836e82bb121bd0dbcaba7e8ba67dc' as `0x${string}`, - nonce: 4999, - yParity: 1, - r: '0x806cbbf8a3cfb25b660e03147984ff95725252b6c95aceed91d5c0bfca6ad0d1' as `0x${string}`, - s: '0x2ac5998bdd42f8d89aaac417bc17b08f10a063c71eeaee1c95c6036ce399d759' as `0x${string}`, - }, - ], - }, - category: 'high-value-auth', - }, -]; + { + name: 'Real mainnet EIP-7702 transaction - Simple authorization', + tx: { + type: 'eip7702', + to: '0x0000000071727De22E5E9d8BAf0edAc6f37da032' as `0x${string}`, + value: BigInt(0), + data: '0x765e827f00000000000000000000000000000000000000000000000000000000000000400000000000000000000000004337001fff419768e088ce247456c1b89288808400000000000000000000000000000000000000000000000000000000000000010000000000000000000000000000000000000000000000000000000000000020000000000000000000000000e02cb371a3ad18a14b40a7ef2d5cf03cc0d2b08f45f7e80000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000012000000000000000000000000000000000000000000000000000000000000001400000000000000000000000000001a1a00000000000000000000000000000c4040000000000000000000000000000000000000000000000000000000000016b530000000000000000000000000321162000000000000000000000000242ef2e9500000000000000000000000000000000000000000000000000000000000002600000000000000000000000000000000000000000000000000000000000000280000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000e4e9ae5c53000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000400000000000000000000000000000000000000000000000000000000000000078d0ec028a3d21533fdd200838f39c85b03679285d0000000000000000000000000000000000000000000000000000000000000000a9059cbb000000000000000000000000d7bd3ba35431d1cf8ad71300794d8958e34dcf850000000000000000000000000000000000000000000000020f5b1eaad8d80000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000415355c70dde835c7dabee4bc7a36be2f62f432da40b08b30ec733be5802dba7d647992b9b57035f27ddc43151285d1ead3c9b6df9485cbdeb37fea884e5d7e1c61b00000000000000000000000000000000000000000000000000000000000000' as `0x${string}`, + nonce: 23978, + maxFeePerGas: BigInt('7918212158'), + maxPriorityFeePerGas: BigInt('7918212158'), + gas: BigInt('260220'), + chainId: 1, + accessList: [], + authorizationList: [ + { + chainId: 1, + address: '0x000000004f43c49e93c970e84001853a70923b03' as `0x${string}`, + nonce: 1, + yParity: 1, + r: '0xc9f7e0af53f516744bc34827bef7236df3123c3a07a601dca75d7698416adc4a' as `0x${string}`, + s: '0x5e8ec8137222d3b97016889093745707d0871cba3a5e8e32aad129dfd2a45727' as `0x${string}`, + }, + ], + }, + category: 'simple-auth', + }, + { + name: 'Real mainnet EIP-7702 transaction - Different authorization', + tx: { + type: 'eip7702', + to: '0x0000000071727De22E5E9d8BAf0edAc6f37da032' as `0x${string}`, + value: BigInt(0), + data: '0x765e827f00000000000000000000000000000000000000000000000000000000000000400000000000000000000000004337002c5702ce424cb62a56ca038e31e1d4a93d00000000000000000000000000000000000000000000000000000000000000010000000000000000000000000000000000000000000000000000000000000020000000000000000000000000f60d2b5657bde93983552c6509deb6201c9ef0dc4601700000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000012000000000000000000000000000000000000000000000000000000000000001400000000000000000000000000001a1a00000000000000000000000000000c4040000000000000000000000000000000000000000000000000000000000016b600000000000000000000000000321162000000000000000000000000242ef2e9500000000000000000000000000000000000000000000000000000000000002600000000000000000000000000000000000000000000000000000000000000280000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000e4e9ae5c53000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000400000000000000000000000000000000000000000000000000000000000000078d0ec028a3d21533fdd200838f39c85b03679285d0000000000000000000000000000000000000000000000000000000000000000a9059cbb0000000000000000000000008bf6fbea0be049e1eeb1f3287054c058c73c9f1b000000000000000000000000000000000000000000000002a802f8630a24000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000041453a20e7cc10dd3fff382ca4d15f0f418e3016ec9d58c8d6ab984bb8e81025e833f4d778d87d8268e24d438f49b383a0e23d24a43b8d8111bef58caa7707c20d1b00000000000000000000000000000000000000000000000000000000000000' as `0x${string}`, + nonce: 23736, + maxFeePerGas: BigInt('8358605855'), + maxPriorityFeePerGas: BigInt('8358605855'), + gas: BigInt('260220'), + chainId: 1, + accessList: [], + authorizationList: [ + { + chainId: 1, + address: '0x000000004f43c49e93c970e84001853a70923b03' as `0x${string}`, + nonce: 1, + yParity: 0, + r: '0x948c69c40057e9fd4c9bb55506ef764bf80d1bbaf980fe8c09d9d9c0b67d0e49' as `0x${string}`, + s: '0x6554554e4b8dd345eb6c73cf88cb212d8e428fc4dff73a54b9b329c793ee2382' as `0x${string}`, + }, + ], + }, + category: 'different-auth', + }, + { + name: 'Real mainnet EIP-7702 transaction - High value authorization', + tx: { + type: 'eip7702', + to: '0xA935433DE1E70538269c417a01543b3Da8478A48' as `0x${string}`, + value: BigInt(0), + data: '0x2c7bddf4' as `0x${string}`, + nonce: 1185, + maxFeePerGas: BigInt('30555080604'), + maxPriorityFeePerGas: BigInt('30555080604'), + gas: BigInt('100000'), + chainId: 1, + accessList: [], + authorizationList: [ + { + chainId: 1, + address: '0x163193c89de836e82bb121bd0dbcaba7e8ba67dc' as `0x${string}`, + nonce: 4999, + yParity: 1, + r: '0x806cbbf8a3cfb25b660e03147984ff95725252b6c95aceed91d5c0bfca6ad0d1' as `0x${string}`, + s: '0x2ac5998bdd42f8d89aaac417bc17b08f10a063c71eeaee1c95c6036ce399d759' as `0x${string}`, + }, + ], + }, + category: 'high-value-auth', + }, +] // ============================================================================= // EDGE CASES & BOUNDARY CONDITIONS // ============================================================================= export const EDGE_CASE_TEST_VECTORS: TestVector[] = [ - { - name: 'Maximum nonce value - Legacy', - tx: { - type: 'legacy', - to: '0xe242e54155b1abc71fc118065270cecaaf8b7768' as `0x${string}`, - value: BigInt('1000000000000000'), // 0.001 ETH - data: '0x' as `0x${string}`, // Add missing data field - nonce: 2 ** 32 - 1, // Maximum safe 32-bit integer - gasPrice: BigInt('20000000000'), // 20 gwei - gas: BigInt('21000'), - chainId: 1, - }, - category: 'max-nonce', - }, - { - name: 'Maximum gas price - Legacy', - tx: { - type: 'legacy', - to: '0xe242e54155b1abc71fc118065270cecaaf8b7768' as `0x${string}`, - value: BigInt('1000000000000000'), // 0.001 ETH - data: '0x' as `0x${string}`, // Add missing data field - nonce: 0, - gasPrice: BigInt('1000000000000'), // 1000 gwei (very high) - gas: BigInt('21000'), - chainId: 1, - }, - category: 'max-gas-price', - }, - { - name: 'Minimal gas limit - EIP-1559', - tx: { - type: 'eip1559', - to: '0xe242e54155b1abc71fc118065270cecaaf8b7768' as `0x${string}`, - value: BigInt('1000000000000000'), // 0.001 ETH - data: '0x' as `0x${string}`, // Add missing data field - nonce: 0, - maxFeePerGas: BigInt('20000000000'), // 20 gwei - maxPriorityFeePerGas: BigInt('1000000000'), // 1 gwei - gas: BigInt('21000'), // Minimum for ETH transfer - chainId: 1, - }, - category: 'min-gas', - }, - { - name: 'Large data payload - EIP-1559', - tx: { - type: 'eip1559', - to: '0xe242e54155b1abc71fc118065270cecaaf8b7768' as `0x${string}`, - value: BigInt(0), - data: `0x${'a'.repeat(1000)}` as `0x${string}`, // Large data payload - nonce: 0, - maxFeePerGas: BigInt('30000000000'), // 30 gwei - maxPriorityFeePerGas: BigInt('2000000000'), // 2 gwei - gas: BigInt('500000'), // High gas for large data - chainId: 1, - }, - category: 'large-data', - }, - { - name: 'Contract creation - Legacy', - tx: { - type: 'legacy', - to: undefined, // Contract creation - value: BigInt(0), - data: '0x608060405234801561001057600080fd5b506040518060400160405280600681526020017f48656c6c6f210000000000000000000000000000000000000000000000000000815250600090805190602001906100609291906100c7565b5034801561006d57600080fd5b5061016c565b828054600181600116156101000203166002900490600052602060002090601f016020900481019282601f106100a657805160ff19168380011785556100d4565b828001600101855582156100d4579182015b828111156100d35782518255916020019190600101906100b8565b5b5090506100e191906100e5565b5090565b61010791905b808211156101035760008160009055506001016100eb565b5090565b90565b610455806101186000396000f3fe' as `0x${string}`, - nonce: 0, - gasPrice: BigInt('20000000000'), // 20 gwei - gas: BigInt('2000000'), // High gas for contract creation - chainId: 1, - }, - category: 'contract-creation', - }, - { - name: 'Zero gas price - Legacy (pre-EIP-155)', - tx: { - type: 'legacy', - to: '0xe242e54155b1abc71fc118065270cecaaf8b7768' as `0x${string}`, - value: BigInt('1000000000000000'), // 0.001 ETH - data: '0x' as `0x${string}`, // Add missing data field - nonce: 0, - gasPrice: BigInt(0), // Zero gas price - gas: BigInt('21000'), - // No chainId for pre-EIP-155 - }, - category: 'zero-gas-price', - }, - { - name: 'Alternative chain IDs - Polygon', - tx: { - type: 'eip1559', - to: '0xe242e54155b1abc71fc118065270cecaaf8b7768' as `0x${string}`, - value: BigInt('1000000000000000000'), // 1 MATIC - data: '0x' as `0x${string}`, // Add missing data field - nonce: 0, - maxFeePerGas: BigInt('30000000000'), // 30 gwei - maxPriorityFeePerGas: BigInt('30000000000'), // 30 gwei - gas: BigInt('21000'), - chainId: 137, - }, - category: 'alt-chains', - }, - { - name: 'Alternative chain IDs - BSC', - tx: { - type: 'eip1559', - to: '0xe242e54155b1abc71fc118065270cecaaf8b7768' as `0x${string}`, - value: BigInt('1000000000000000000'), // 1 BNB - data: '0x' as `0x${string}`, // Add missing data field - nonce: 0, - maxFeePerGas: BigInt('5000000000'), // 5 gwei - maxPriorityFeePerGas: BigInt('1000000000'), // 1 gwei - gas: BigInt('21000'), - chainId: 56, - }, - category: 'alt-chains', - }, - { - name: 'Alternative chain IDs - Avalanche', - tx: { - type: 'eip1559', - to: '0xe242e54155b1abc71fc118065270cecaaf8b7768' as `0x${string}`, - value: BigInt('1000000000000000000'), // 1 AVAX - data: '0x' as `0x${string}`, // Add missing data field - nonce: 0, - maxFeePerGas: BigInt('25000000000'), // 25 gwei - maxPriorityFeePerGas: BigInt('1500000000'), // 1.5 gwei - gas: BigInt('21000'), - chainId: 43114, - }, - category: 'alt-chains', - }, - { - name: 'Large chain ID - Palm Network', - tx: { - type: 'eip1559', - to: '0xe242e54155b1abc71fc118065270cecaaf8b7768' as `0x${string}`, - value: BigInt('1000000000000000000'), // 1 PALM - data: '0x' as `0x${string}`, // Add missing data field - nonce: 0, - maxFeePerGas: BigInt('20000000000'), // 20 gwei - maxPriorityFeePerGas: BigInt('2000000000'), // 2 gwei - gas: BigInt('21000'), - chainId: 11297108109, - }, - category: 'large-chain-id', - }, - { - name: 'Unknown chain ID', - tx: { - type: 'eip1559', - to: '0xe242e54155b1abc71fc118065270cecaaf8b7768' as `0x${string}`, - value: BigInt('1000000000000000000'), // 1 ETH - data: '0x' as `0x${string}`, // Add missing data field - nonce: 0, - maxFeePerGas: BigInt('20000000000'), // 20 gwei - maxPriorityFeePerGas: BigInt('2000000000'), // 2 gwei - gas: BigInt('21000'), - chainId: 9999, - }, - category: 'unknown-chain', - }, - { - name: 'Minimal value - 1 wei', - tx: { - type: 'eip1559', - to: '0xe242e54155b1abc71fc118065270cecaaf8b7768' as `0x${string}`, - value: BigInt(1), // 1 wei - data: '0x' as `0x${string}`, // Add missing data field - nonce: 0, - maxFeePerGas: BigInt('20000000000'), // 20 gwei - maxPriorityFeePerGas: BigInt('2000000000'), // 2 gwei - gas: BigInt('21000'), - chainId: 1, - }, - category: 'min-value', - }, - { - name: 'Scientific notation value - 1e8 wei', - tx: { - type: 'eip1559', - to: '0xe242e54155b1abc71fc118065270cecaaf8b7768' as `0x${string}`, - value: BigInt('100000000000'), // 1e8 wei = 0.1 gwei - data: '0x' as `0x${string}`, // Add missing data field - nonce: 0, - maxFeePerGas: BigInt('20000000000'), // 20 gwei - maxPriorityFeePerGas: BigInt('2000000000'), // 2 gwei - gas: BigInt('21000'), - chainId: 1, - }, - category: 'scientific-value', - }, - { - name: 'Maximum safe value', - tx: { - type: 'eip1559', - to: '0xe242e54155b1abc71fc118065270cecaaf8b7768' as `0x${string}`, - value: BigInt( - '0xffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff', - ), // Max uint256 - data: '0x' as `0x${string}`, // Add missing data field - nonce: 0, - maxFeePerGas: BigInt('20000000000'), // 20 gwei - maxPriorityFeePerGas: BigInt('2000000000'), // 2 gwei - gas: BigInt('21000'), - chainId: 1, - }, - category: 'max-value', - }, - { - name: 'Contract creation - EIP-1559', - tx: { - type: 'eip1559', - to: undefined, // Contract creation - value: BigInt(0), - data: `0x${'60'.repeat(96)}` as `0x${string}`, // Simple contract bytecode - nonce: 0, - maxFeePerGas: BigInt('30000000000'), // 30 gwei - maxPriorityFeePerGas: BigInt('2000000000'), // 2 gwei - gas: BigInt('2000000'), // High gas for contract creation - chainId: 1, - }, - category: 'contract-creation', - }, - { - name: 'Contract creation - EIP-2930', - tx: { - type: 'eip2930', - to: undefined, // Contract creation - value: BigInt(0), - data: `0x${'60'.repeat(96)}` as `0x${string}`, // Simple contract bytecode - nonce: 0, - gasPrice: BigInt('25000000000'), // 25 gwei - gas: BigInt('2000000'), // High gas for contract creation - chainId: 1, - accessList: [], - }, - category: 'contract-creation', - }, - { - name: 'EIP-2930 with mixed storage keys', - tx: { - type: 'eip2930', - to: '0xe242e54155b1abc71fc118065270cecaaf8b7768' as `0x${string}`, - value: BigInt('1000000000000000000'), // 1 ETH - data: '0x' as `0x${string}`, // Add missing data field - nonce: 0, - gasPrice: BigInt('20000000000'), // 20 gwei - gas: BigInt('100000'), - chainId: 1, - accessList: [ - { - address: - '0xe242e54155b1abc71fc118065270cecaaf8b7768' as `0x${string}`, - storageKeys: [ - '0x7154f8b310ad6ce97ce3b15e3419d9863865dfe2d8635802f7f4a52a206255a6' as `0x${string}`, - ], - }, - { - address: - '0xe0f8ff08ef0242c461da688b8b85e438db724860' as `0x${string}`, - storageKeys: [], // Empty storage keys - }, - ], - }, - category: 'mixed-access-list', - }, - { - name: 'EIP-1559 with access list', - tx: { - type: 'eip1559', - to: '0xe242e54155b1abc71fc118065270cecaaf8b7768' as `0x${string}`, - value: BigInt('1000000000000000000'), // 1 ETH - data: '0x' as `0x${string}`, // Add missing data field - nonce: 0, - maxFeePerGas: BigInt('30000000000'), // 30 gwei - maxPriorityFeePerGas: BigInt('2000000000'), // 2 gwei - gas: BigInt('100000'), - chainId: 1, - accessList: [ - { - address: - '0xe242e54155b1abc71fc118065270cecaaf8b7768' as `0x${string}`, - storageKeys: [ - '0x7154f8b310ad6ce97ce3b15e3419d9863865dfe2d8635802f7f4a52a206255a6' as `0x${string}`, - ], - }, - { - address: - '0xe0f8ff08ef0242c461da688b8b85e438db724860' as `0x${string}`, - storageKeys: [], // Empty storage keys - }, - ], - }, - category: 'eip1559-with-access-list', - }, -]; + { + name: 'Maximum nonce value - Legacy', + tx: { + type: 'legacy', + to: '0xe242e54155b1abc71fc118065270cecaaf8b7768' as `0x${string}`, + value: BigInt('1000000000000000'), // 0.001 ETH + data: '0x' as `0x${string}`, // Add missing data field + nonce: 2 ** 32 - 1, // Maximum safe 32-bit integer + gasPrice: BigInt('20000000000'), // 20 gwei + gas: BigInt('21000'), + chainId: 1, + }, + category: 'max-nonce', + }, + { + name: 'Maximum gas price - Legacy', + tx: { + type: 'legacy', + to: '0xe242e54155b1abc71fc118065270cecaaf8b7768' as `0x${string}`, + value: BigInt('1000000000000000'), // 0.001 ETH + data: '0x' as `0x${string}`, // Add missing data field + nonce: 0, + gasPrice: BigInt('1000000000000'), // 1000 gwei (very high) + gas: BigInt('21000'), + chainId: 1, + }, + category: 'max-gas-price', + }, + { + name: 'Minimal gas limit - EIP-1559', + tx: { + type: 'eip1559', + to: '0xe242e54155b1abc71fc118065270cecaaf8b7768' as `0x${string}`, + value: BigInt('1000000000000000'), // 0.001 ETH + data: '0x' as `0x${string}`, // Add missing data field + nonce: 0, + maxFeePerGas: BigInt('20000000000'), // 20 gwei + maxPriorityFeePerGas: BigInt('1000000000'), // 1 gwei + gas: BigInt('21000'), // Minimum for ETH transfer + chainId: 1, + }, + category: 'min-gas', + }, + { + name: 'Large data payload - EIP-1559', + tx: { + type: 'eip1559', + to: '0xe242e54155b1abc71fc118065270cecaaf8b7768' as `0x${string}`, + value: BigInt(0), + data: `0x${'a'.repeat(1000)}` as `0x${string}`, // Large data payload + nonce: 0, + maxFeePerGas: BigInt('30000000000'), // 30 gwei + maxPriorityFeePerGas: BigInt('2000000000'), // 2 gwei + gas: BigInt('500000'), // High gas for large data + chainId: 1, + }, + category: 'large-data', + }, + { + name: 'Contract creation - Legacy', + tx: { + type: 'legacy', + to: undefined, // Contract creation + value: BigInt(0), + data: '0x608060405234801561001057600080fd5b506040518060400160405280600681526020017f48656c6c6f210000000000000000000000000000000000000000000000000000815250600090805190602001906100609291906100c7565b5034801561006d57600080fd5b5061016c565b828054600181600116156101000203166002900490600052602060002090601f016020900481019282601f106100a657805160ff19168380011785556100d4565b828001600101855582156100d4579182015b828111156100d35782518255916020019190600101906100b8565b5b5090506100e191906100e5565b5090565b61010791905b808211156101035760008160009055506001016100eb565b5090565b90565b610455806101186000396000f3fe' as `0x${string}`, + nonce: 0, + gasPrice: BigInt('20000000000'), // 20 gwei + gas: BigInt('2000000'), // High gas for contract creation + chainId: 1, + }, + category: 'contract-creation', + }, + { + name: 'Zero gas price - Legacy (pre-EIP-155)', + tx: { + type: 'legacy', + to: '0xe242e54155b1abc71fc118065270cecaaf8b7768' as `0x${string}`, + value: BigInt('1000000000000000'), // 0.001 ETH + data: '0x' as `0x${string}`, // Add missing data field + nonce: 0, + gasPrice: BigInt(0), // Zero gas price + gas: BigInt('21000'), + // No chainId for pre-EIP-155 + }, + category: 'zero-gas-price', + }, + { + name: 'Alternative chain IDs - Polygon', + tx: { + type: 'eip1559', + to: '0xe242e54155b1abc71fc118065270cecaaf8b7768' as `0x${string}`, + value: BigInt('1000000000000000000'), // 1 MATIC + data: '0x' as `0x${string}`, // Add missing data field + nonce: 0, + maxFeePerGas: BigInt('30000000000'), // 30 gwei + maxPriorityFeePerGas: BigInt('30000000000'), // 30 gwei + gas: BigInt('21000'), + chainId: 137, + }, + category: 'alt-chains', + }, + { + name: 'Alternative chain IDs - BSC', + tx: { + type: 'eip1559', + to: '0xe242e54155b1abc71fc118065270cecaaf8b7768' as `0x${string}`, + value: BigInt('1000000000000000000'), // 1 BNB + data: '0x' as `0x${string}`, // Add missing data field + nonce: 0, + maxFeePerGas: BigInt('5000000000'), // 5 gwei + maxPriorityFeePerGas: BigInt('1000000000'), // 1 gwei + gas: BigInt('21000'), + chainId: 56, + }, + category: 'alt-chains', + }, + { + name: 'Alternative chain IDs - Avalanche', + tx: { + type: 'eip1559', + to: '0xe242e54155b1abc71fc118065270cecaaf8b7768' as `0x${string}`, + value: BigInt('1000000000000000000'), // 1 AVAX + data: '0x' as `0x${string}`, // Add missing data field + nonce: 0, + maxFeePerGas: BigInt('25000000000'), // 25 gwei + maxPriorityFeePerGas: BigInt('1500000000'), // 1.5 gwei + gas: BigInt('21000'), + chainId: 43114, + }, + category: 'alt-chains', + }, + { + name: 'Large chain ID - Palm Network', + tx: { + type: 'eip1559', + to: '0xe242e54155b1abc71fc118065270cecaaf8b7768' as `0x${string}`, + value: BigInt('1000000000000000000'), // 1 PALM + data: '0x' as `0x${string}`, // Add missing data field + nonce: 0, + maxFeePerGas: BigInt('20000000000'), // 20 gwei + maxPriorityFeePerGas: BigInt('2000000000'), // 2 gwei + gas: BigInt('21000'), + chainId: 11297108109, + }, + category: 'large-chain-id', + }, + { + name: 'Unknown chain ID', + tx: { + type: 'eip1559', + to: '0xe242e54155b1abc71fc118065270cecaaf8b7768' as `0x${string}`, + value: BigInt('1000000000000000000'), // 1 ETH + data: '0x' as `0x${string}`, // Add missing data field + nonce: 0, + maxFeePerGas: BigInt('20000000000'), // 20 gwei + maxPriorityFeePerGas: BigInt('2000000000'), // 2 gwei + gas: BigInt('21000'), + chainId: 9999, + }, + category: 'unknown-chain', + }, + { + name: 'Minimal value - 1 wei', + tx: { + type: 'eip1559', + to: '0xe242e54155b1abc71fc118065270cecaaf8b7768' as `0x${string}`, + value: BigInt(1), // 1 wei + data: '0x' as `0x${string}`, // Add missing data field + nonce: 0, + maxFeePerGas: BigInt('20000000000'), // 20 gwei + maxPriorityFeePerGas: BigInt('2000000000'), // 2 gwei + gas: BigInt('21000'), + chainId: 1, + }, + category: 'min-value', + }, + { + name: 'Scientific notation value - 1e8 wei', + tx: { + type: 'eip1559', + to: '0xe242e54155b1abc71fc118065270cecaaf8b7768' as `0x${string}`, + value: BigInt('100000000000'), // 1e8 wei = 0.1 gwei + data: '0x' as `0x${string}`, // Add missing data field + nonce: 0, + maxFeePerGas: BigInt('20000000000'), // 20 gwei + maxPriorityFeePerGas: BigInt('2000000000'), // 2 gwei + gas: BigInt('21000'), + chainId: 1, + }, + category: 'scientific-value', + }, + { + name: 'Maximum safe value', + tx: { + type: 'eip1559', + to: '0xe242e54155b1abc71fc118065270cecaaf8b7768' as `0x${string}`, + value: BigInt('0xffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff'), // Max uint256 + data: '0x' as `0x${string}`, // Add missing data field + nonce: 0, + maxFeePerGas: BigInt('20000000000'), // 20 gwei + maxPriorityFeePerGas: BigInt('2000000000'), // 2 gwei + gas: BigInt('21000'), + chainId: 1, + }, + category: 'max-value', + }, + { + name: 'Contract creation - EIP-1559', + tx: { + type: 'eip1559', + to: undefined, // Contract creation + value: BigInt(0), + data: `0x${'60'.repeat(96)}` as `0x${string}`, // Simple contract bytecode + nonce: 0, + maxFeePerGas: BigInt('30000000000'), // 30 gwei + maxPriorityFeePerGas: BigInt('2000000000'), // 2 gwei + gas: BigInt('2000000'), // High gas for contract creation + chainId: 1, + }, + category: 'contract-creation', + }, + { + name: 'Contract creation - EIP-2930', + tx: { + type: 'eip2930', + to: undefined, // Contract creation + value: BigInt(0), + data: `0x${'60'.repeat(96)}` as `0x${string}`, // Simple contract bytecode + nonce: 0, + gasPrice: BigInt('25000000000'), // 25 gwei + gas: BigInt('2000000'), // High gas for contract creation + chainId: 1, + accessList: [], + }, + category: 'contract-creation', + }, + { + name: 'EIP-2930 with mixed storage keys', + tx: { + type: 'eip2930', + to: '0xe242e54155b1abc71fc118065270cecaaf8b7768' as `0x${string}`, + value: BigInt('1000000000000000000'), // 1 ETH + data: '0x' as `0x${string}`, // Add missing data field + nonce: 0, + gasPrice: BigInt('20000000000'), // 20 gwei + gas: BigInt('100000'), + chainId: 1, + accessList: [ + { + address: '0xe242e54155b1abc71fc118065270cecaaf8b7768' as `0x${string}`, + storageKeys: ['0x7154f8b310ad6ce97ce3b15e3419d9863865dfe2d8635802f7f4a52a206255a6' as `0x${string}`], + }, + { + address: '0xe0f8ff08ef0242c461da688b8b85e438db724860' as `0x${string}`, + storageKeys: [], // Empty storage keys + }, + ], + }, + category: 'mixed-access-list', + }, + { + name: 'EIP-1559 with access list', + tx: { + type: 'eip1559', + to: '0xe242e54155b1abc71fc118065270cecaaf8b7768' as `0x${string}`, + value: BigInt('1000000000000000000'), // 1 ETH + data: '0x' as `0x${string}`, // Add missing data field + nonce: 0, + maxFeePerGas: BigInt('30000000000'), // 30 gwei + maxPriorityFeePerGas: BigInt('2000000000'), // 2 gwei + gas: BigInt('100000'), + chainId: 1, + accessList: [ + { + address: '0xe242e54155b1abc71fc118065270cecaaf8b7768' as `0x${string}`, + storageKeys: ['0x7154f8b310ad6ce97ce3b15e3419d9863865dfe2d8635802f7f4a52a206255a6' as `0x${string}`], + }, + { + address: '0xe0f8ff08ef0242c461da688b8b85e438db724860' as `0x${string}`, + storageKeys: [], // Empty storage keys + }, + ], + }, + category: 'eip1559-with-access-list', + }, +] // ============================================================================= // COMPREHENSIVE DETERMINISTIC TEST VECTORS @@ -723,375 +689,364 @@ export const EDGE_CASE_TEST_VECTORS: TestVector[] = [ * Test various derivation path lengths to ensure wallet compatibility */ export const DERIVATION_PATH_VECTORS: TestVector[] = [ - { - name: 'Derivation path - Full BIP44 path (5 levels)', - tx: { - type: 'eip1559', - to: '0xe242e54155b1abc71fc118065270cecaaf8b7768' as `0x${string}`, - value: BigInt('1000000000000000000'), // 1 ETH - data: '0x' as `0x${string}`, // Add missing data field - nonce: 0, - maxFeePerGas: BigInt('20000000000'), // 20 gwei - maxPriorityFeePerGas: BigInt('2000000000'), // 2 gwei - gas: BigInt('21000'), - chainId: 1, - }, - category: 'derivation-path-5', - }, - { - name: 'Derivation path - 3 levels', - tx: { - type: 'legacy', - to: '0xe242e54155b1abc71fc118065270cecaaf8b7768' as `0x${string}`, - value: BigInt('500000000000000000'), // 0.5 ETH - data: '0x' as `0x${string}`, // Add missing data field - nonce: 1, - gasPrice: BigInt('15000000000'), // 15 gwei - gas: BigInt('21000'), - chainId: 1, - }, - category: 'derivation-path-3', - }, - { - name: 'Derivation path - 2 levels', - tx: { - type: 'eip2930', - to: '0xe242e54155b1abc71fc118065270cecaaf8b7768' as `0x${string}`, - value: BigInt('250000000000000000'), // 0.25 ETH - data: '0x' as `0x${string}`, // Add missing data field - nonce: 2, - gasPrice: BigInt('10000000000'), // 10 gwei - gas: BigInt('21000'), - chainId: 1, - accessList: [], - }, - category: 'derivation-path-2', - }, - { - name: 'Derivation path - 1 level (root)', - tx: { - type: 'eip1559', - to: '0xe242e54155b1abc71fc118065270cecaaf8b7768' as `0x${string}`, - value: BigInt('100000000000000000'), // 0.1 ETH - data: '0x' as `0x${string}`, // Add missing data field - nonce: 3, - maxFeePerGas: BigInt('25000000000'), // 25 gwei - maxPriorityFeePerGas: BigInt('2500000000'), // 2.5 gwei - gas: BigInt('21000'), - chainId: 1, - }, - category: 'derivation-path-1', - }, -]; + { + name: 'Derivation path - Full BIP44 path (5 levels)', + tx: { + type: 'eip1559', + to: '0xe242e54155b1abc71fc118065270cecaaf8b7768' as `0x${string}`, + value: BigInt('1000000000000000000'), // 1 ETH + data: '0x' as `0x${string}`, // Add missing data field + nonce: 0, + maxFeePerGas: BigInt('20000000000'), // 20 gwei + maxPriorityFeePerGas: BigInt('2000000000'), // 2 gwei + gas: BigInt('21000'), + chainId: 1, + }, + category: 'derivation-path-5', + }, + { + name: 'Derivation path - 3 levels', + tx: { + type: 'legacy', + to: '0xe242e54155b1abc71fc118065270cecaaf8b7768' as `0x${string}`, + value: BigInt('500000000000000000'), // 0.5 ETH + data: '0x' as `0x${string}`, // Add missing data field + nonce: 1, + gasPrice: BigInt('15000000000'), // 15 gwei + gas: BigInt('21000'), + chainId: 1, + }, + category: 'derivation-path-3', + }, + { + name: 'Derivation path - 2 levels', + tx: { + type: 'eip2930', + to: '0xe242e54155b1abc71fc118065270cecaaf8b7768' as `0x${string}`, + value: BigInt('250000000000000000'), // 0.25 ETH + data: '0x' as `0x${string}`, // Add missing data field + nonce: 2, + gasPrice: BigInt('10000000000'), // 10 gwei + gas: BigInt('21000'), + chainId: 1, + accessList: [], + }, + category: 'derivation-path-2', + }, + { + name: 'Derivation path - 1 level (root)', + tx: { + type: 'eip1559', + to: '0xe242e54155b1abc71fc118065270cecaaf8b7768' as `0x${string}`, + value: BigInt('100000000000000000'), // 0.1 ETH + data: '0x' as `0x${string}`, // Add missing data field + nonce: 3, + maxFeePerGas: BigInt('25000000000'), // 25 gwei + maxPriorityFeePerGas: BigInt('2500000000'), // 2.5 gwei + gas: BigInt('21000'), + chainId: 1, + }, + category: 'derivation-path-1', + }, +] /** * Test specific network configurations that are commonly used */ export const NETWORK_SPECIFIC_VECTORS: TestVector[] = [ - { - name: 'Rinkeby testnet (historical)', - tx: { - type: 'eip1559', - to: '0xe242e54155b1abc71fc118065270cecaaf8b7768' as `0x${string}`, - value: BigInt('1000000000000000000'), // 1 ETH - data: '0x' as `0x${string}`, // Add missing data field - nonce: 0, - maxFeePerGas: BigInt('20000000000'), // 20 gwei - maxPriorityFeePerGas: BigInt('2000000000'), // 2 gwei - gas: BigInt('21000'), - chainId: 4, // Rinkeby - }, - category: 'rinkeby', - }, - { - name: 'Goerli testnet', - tx: { - type: 'eip1559', - to: '0xe242e54155b1abc71fc118065270cecaaf8b7768' as `0x${string}`, - value: BigInt('1000000000000000000'), // 1 ETH - data: '0x' as `0x${string}`, // Add missing data field - nonce: 0, - maxFeePerGas: BigInt('20000000000'), // 20 gwei - maxPriorityFeePerGas: BigInt('2000000000'), // 2 gwei - gas: BigInt('21000'), - chainId: 5, // Goerli - }, - category: 'goerli', - }, - { - name: 'Sepolia testnet', - tx: { - type: 'eip1559', - to: '0xe242e54155b1abc71fc118065270cecaaf8b7768' as `0x${string}`, - value: BigInt('1000000000000000000'), // 1 ETH - data: '0x' as `0x${string}`, // Add missing data field - nonce: 0, - maxFeePerGas: BigInt('20000000000'), // 20 gwei - maxPriorityFeePerGas: BigInt('2000000000'), // 2 gwei - gas: BigInt('21000'), - chainId: 11155111, // Sepolia - }, - category: 'sepolia', - }, -]; + { + name: 'Rinkeby testnet (historical)', + tx: { + type: 'eip1559', + to: '0xe242e54155b1abc71fc118065270cecaaf8b7768' as `0x${string}`, + value: BigInt('1000000000000000000'), // 1 ETH + data: '0x' as `0x${string}`, // Add missing data field + nonce: 0, + maxFeePerGas: BigInt('20000000000'), // 20 gwei + maxPriorityFeePerGas: BigInt('2000000000'), // 2 gwei + gas: BigInt('21000'), + chainId: 4, // Rinkeby + }, + category: 'rinkeby', + }, + { + name: 'Goerli testnet', + tx: { + type: 'eip1559', + to: '0xe242e54155b1abc71fc118065270cecaaf8b7768' as `0x${string}`, + value: BigInt('1000000000000000000'), // 1 ETH + data: '0x' as `0x${string}`, // Add missing data field + nonce: 0, + maxFeePerGas: BigInt('20000000000'), // 20 gwei + maxPriorityFeePerGas: BigInt('2000000000'), // 2 gwei + gas: BigInt('21000'), + chainId: 5, // Goerli + }, + category: 'goerli', + }, + { + name: 'Sepolia testnet', + tx: { + type: 'eip1559', + to: '0xe242e54155b1abc71fc118065270cecaaf8b7768' as `0x${string}`, + value: BigInt('1000000000000000000'), // 1 ETH + data: '0x' as `0x${string}`, // Add missing data field + nonce: 0, + maxFeePerGas: BigInt('20000000000'), // 20 gwei + maxPriorityFeePerGas: BigInt('2000000000'), // 2 gwei + gas: BigInt('21000'), + chainId: 11155111, // Sepolia + }, + category: 'sepolia', + }, +] /** * Test payload size boundaries to ensure proper handling of large transactions */ export const PAYLOAD_SIZE_VECTORS: TestVector[] = [ - { - name: 'No data payload', - tx: { - type: 'eip1559', - to: '0xe242e54155b1abc71fc118065270cecaaf8b7768' as `0x${string}`, - value: BigInt('1000000000000000000'), // 1 ETH - data: '0x' as `0x${string}`, // Fix undefined data field - nonce: 0, - maxFeePerGas: BigInt('20000000000'), // 20 gwei - maxPriorityFeePerGas: BigInt('2000000000'), // 2 gwei - gas: BigInt('21000'), - chainId: 1, - }, - category: 'no-data', - }, - { - name: 'Small data payload (32 bytes)', - tx: { - type: 'eip1559', - to: '0xe242e54155b1abc71fc118065270cecaaf8b7768' as `0x${string}`, - value: BigInt('1000000000000000000'), // 1 ETH - nonce: 0, - maxFeePerGas: BigInt('30000000000'), // 30 gwei - maxPriorityFeePerGas: BigInt('2000000000'), // 2 gwei - gas: BigInt('50000'), - chainId: 1, - data: `0x${'00'.repeat(32)}` as `0x${string}`, - }, - category: 'small-data', - }, - { - name: 'Medium data payload (256 bytes)', - tx: { - type: 'eip1559', - to: '0xe242e54155b1abc71fc118065270cecaaf8b7768' as `0x${string}`, - value: BigInt('1000000000000000000'), // 1 ETH - nonce: 0, - maxFeePerGas: BigInt('30000000000'), // 30 gwei - maxPriorityFeePerGas: BigInt('2000000000'), // 2 gwei - gas: BigInt('100000'), - chainId: 1, - data: `0x${'ab'.repeat(256)}` as `0x${string}`, - }, - category: 'medium-data', - }, - { - name: 'Large data payload (1024 bytes)', - tx: { - type: 'eip1559', - to: '0xe242e54155b1abc71fc118065270cecaaf8b7768' as `0x${string}`, - value: BigInt('1000000000000000000'), // 1 ETH - nonce: 0, - maxFeePerGas: BigInt('50000000000'), // 50 gwei - maxPriorityFeePerGas: BigInt('3000000000'), // 3 gwei - gas: BigInt('500000'), - chainId: 1, - data: `0x${'cd'.repeat(1024)}` as `0x${string}`, - }, - category: 'large-data', - }, - { - name: 'Very large data payload (2000 bytes)', - tx: { - type: 'eip1559', - to: '0xe242e54155b1abc71fc118065270cecaaf8b7768' as `0x${string}`, - value: BigInt('1000000000000000000'), // 1 ETH - nonce: 0, - maxFeePerGas: BigInt('50000000000'), // 50 gwei - maxPriorityFeePerGas: BigInt('3000000000'), // 3 gwei - gas: BigInt('1000000'), - chainId: 1, - data: `0x${'ef'.repeat(2000)}` as `0x${string}`, - }, - category: 'very-large-data', - }, -]; + { + name: 'No data payload', + tx: { + type: 'eip1559', + to: '0xe242e54155b1abc71fc118065270cecaaf8b7768' as `0x${string}`, + value: BigInt('1000000000000000000'), // 1 ETH + data: '0x' as `0x${string}`, // Fix undefined data field + nonce: 0, + maxFeePerGas: BigInt('20000000000'), // 20 gwei + maxPriorityFeePerGas: BigInt('2000000000'), // 2 gwei + gas: BigInt('21000'), + chainId: 1, + }, + category: 'no-data', + }, + { + name: 'Small data payload (32 bytes)', + tx: { + type: 'eip1559', + to: '0xe242e54155b1abc71fc118065270cecaaf8b7768' as `0x${string}`, + value: BigInt('1000000000000000000'), // 1 ETH + nonce: 0, + maxFeePerGas: BigInt('30000000000'), // 30 gwei + maxPriorityFeePerGas: BigInt('2000000000'), // 2 gwei + gas: BigInt('50000'), + chainId: 1, + data: `0x${'00'.repeat(32)}` as `0x${string}`, + }, + category: 'small-data', + }, + { + name: 'Medium data payload (256 bytes)', + tx: { + type: 'eip1559', + to: '0xe242e54155b1abc71fc118065270cecaaf8b7768' as `0x${string}`, + value: BigInt('1000000000000000000'), // 1 ETH + nonce: 0, + maxFeePerGas: BigInt('30000000000'), // 30 gwei + maxPriorityFeePerGas: BigInt('2000000000'), // 2 gwei + gas: BigInt('100000'), + chainId: 1, + data: `0x${'ab'.repeat(256)}` as `0x${string}`, + }, + category: 'medium-data', + }, + { + name: 'Large data payload (1024 bytes)', + tx: { + type: 'eip1559', + to: '0xe242e54155b1abc71fc118065270cecaaf8b7768' as `0x${string}`, + value: BigInt('1000000000000000000'), // 1 ETH + nonce: 0, + maxFeePerGas: BigInt('50000000000'), // 50 gwei + maxPriorityFeePerGas: BigInt('3000000000'), // 3 gwei + gas: BigInt('500000'), + chainId: 1, + data: `0x${'cd'.repeat(1024)}` as `0x${string}`, + }, + category: 'large-data', + }, + { + name: 'Very large data payload (2000 bytes)', + tx: { + type: 'eip1559', + to: '0xe242e54155b1abc71fc118065270cecaaf8b7768' as `0x${string}`, + value: BigInt('1000000000000000000'), // 1 ETH + nonce: 0, + maxFeePerGas: BigInt('50000000000'), // 50 gwei + maxPriorityFeePerGas: BigInt('3000000000'), // 3 gwei + gas: BigInt('1000000'), + chainId: 1, + data: `0x${'ef'.repeat(2000)}` as `0x${string}`, + }, + category: 'very-large-data', + }, +] /** * Comprehensive boundary condition test vectors */ export const BOUNDARY_CONDITION_VECTORS: TestVector[] = [ - { - name: 'Maximum nonce (2^32-1)', - tx: { - type: 'legacy', - to: '0xe242e54155b1abc71fc118065270cecaaf8b7768' as `0x${string}`, - value: BigInt('1000000000000000'), // 1 ETH - data: '0x' as `0x${string}`, // Add missing data field - nonce: 2 ** 32 - 1, - gasPrice: BigInt('20000000000'), - gas: BigInt('21000'), - chainId: 1, - }, - category: 'max-nonce', - }, - { - name: 'Maximum safe chain ID', - tx: { - type: 'eip1559', - to: '0xe242e54155b1abc71fc118065270cecaaf8b7768' as `0x${string}`, - value: BigInt('1000000000000000000'), - data: '0x' as `0x${string}`, // Add missing data field - nonce: 0, - maxFeePerGas: BigInt('20000000000'), - maxPriorityFeePerGas: BigInt('2000000000'), - gas: BigInt('21000'), - chainId: Number.MAX_SAFE_INTEGER, - }, - category: 'max-chain-id', - }, - { - name: 'Minimum gas limit (21000)', - tx: { - type: 'eip1559', - to: '0xe242e54155b1abc71fc118065270cecaaf8b7768' as `0x${string}`, - value: BigInt(1), - data: '0x' as `0x${string}`, // Add missing data field - nonce: 0, - maxFeePerGas: BigInt('1'), // 1 wei - maxPriorityFeePerGas: BigInt('1'), - gas: BigInt('21000'), // Minimum for ETH transfer - chainId: 1, - }, - category: 'min-gas', - }, - { - name: 'Maximum gas limit', - tx: { - type: 'eip1559', - to: '0xe242e54155b1abc71fc118065270cecaaf8b7768' as `0x${string}`, - value: BigInt('1000000000000000000'), - data: '0x' as `0x${string}`, // Add missing data field - nonce: 0, - maxFeePerGas: BigInt('100000000000'), // 100 gwei - maxPriorityFeePerGas: BigInt('5000000000'), // 5 gwei - gas: BigInt('30000000'), // Block gas limit - chainId: 1, - }, - category: 'max-gas', - }, -]; + { + name: 'Maximum nonce (2^32-1)', + tx: { + type: 'legacy', + to: '0xe242e54155b1abc71fc118065270cecaaf8b7768' as `0x${string}`, + value: BigInt('1000000000000000'), // 1 ETH + data: '0x' as `0x${string}`, // Add missing data field + nonce: 2 ** 32 - 1, + gasPrice: BigInt('20000000000'), + gas: BigInt('21000'), + chainId: 1, + }, + category: 'max-nonce', + }, + { + name: 'Maximum safe chain ID', + tx: { + type: 'eip1559', + to: '0xe242e54155b1abc71fc118065270cecaaf8b7768' as `0x${string}`, + value: BigInt('1000000000000000000'), + data: '0x' as `0x${string}`, // Add missing data field + nonce: 0, + maxFeePerGas: BigInt('20000000000'), + maxPriorityFeePerGas: BigInt('2000000000'), + gas: BigInt('21000'), + chainId: Number.MAX_SAFE_INTEGER, + }, + category: 'max-chain-id', + }, + { + name: 'Minimum gas limit (21000)', + tx: { + type: 'eip1559', + to: '0xe242e54155b1abc71fc118065270cecaaf8b7768' as `0x${string}`, + value: BigInt(1), + data: '0x' as `0x${string}`, // Add missing data field + nonce: 0, + maxFeePerGas: BigInt('1'), // 1 wei + maxPriorityFeePerGas: BigInt('1'), + gas: BigInt('21000'), // Minimum for ETH transfer + chainId: 1, + }, + category: 'min-gas', + }, + { + name: 'Maximum gas limit', + tx: { + type: 'eip1559', + to: '0xe242e54155b1abc71fc118065270cecaaf8b7768' as `0x${string}`, + value: BigInt('1000000000000000000'), + data: '0x' as `0x${string}`, // Add missing data field + nonce: 0, + maxFeePerGas: BigInt('100000000000'), // 100 gwei + maxPriorityFeePerGas: BigInt('5000000000'), // 5 gwei + gas: BigInt('30000000'), // Block gas limit + chainId: 1, + }, + category: 'max-gas', + }, +] /** * Test specific transaction patterns from real-world usage */ export const REAL_WORLD_PATTERN_VECTORS: TestVector[] = [ - { - name: 'DeFi approval transaction', - tx: { - type: 'eip1559', - to: '0xA0b86a33E6417c14f8c9C4E5659dF7a08D2C65c3' as `0x${string}`, // Example DeFi contract - value: BigInt(0), - data: '0x095ea7b3000000000000000000000000742d35cc6b2d9e9a3b7b7b4a7b0b2b7b0b2b7b0bffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff' as `0x${string}`, // approve(spender, amount) - nonce: 5, - maxFeePerGas: BigInt('25000000000'), // 25 gwei - maxPriorityFeePerGas: BigInt('2000000000'), // 2 gwei - gas: BigInt('46000'), // Typical for ERC20 approval - chainId: 1, - }, - category: 'defi-approval', - }, - { - name: 'NFT minting transaction', - tx: { - type: 'eip1559', - to: '0xe242e54155b1abc71fc118065270cecaaf8b7768' as `0x${string}`, - value: BigInt('50000000000000000'), // 0.05 ETH mint fee - data: '0x40c10f19000000000000000000000000742d35cc6b2d9e9a3b7b7b4a7b0b2b7b0b2b7b0b0000000000000000000000000000000000000000000000000000000000000001' as `0x${string}`, // mint(to, tokenId) - nonce: 10, - maxFeePerGas: BigInt('30000000000'), // 30 gwei - maxPriorityFeePerGas: BigInt('3000000000'), // 3 gwei - gas: BigInt('200000'), // Higher gas for NFT mint - chainId: 1, - }, - category: 'nft-mint', - }, - { - name: 'Multi-send transaction', - tx: { - type: 'eip1559', - to: '0xe242e54155b1abc71fc118065270cecaaf8b7768' as `0x${string}`, - value: BigInt(0), - data: `0x8d80ff0a${'00'.repeat(500)}` as `0x${string}`, // multiSend with batch data - nonce: 15, - maxFeePerGas: BigInt('40000000000'), // 40 gwei - maxPriorityFeePerGas: BigInt('4000000000'), // 4 gwei - gas: BigInt('800000'), // High gas for multi-send - chainId: 1, - }, - category: 'multi-send', - }, -]; + { + name: 'DeFi approval transaction', + tx: { + type: 'eip1559', + to: '0xA0b86a33E6417c14f8c9C4E5659dF7a08D2C65c3' as `0x${string}`, // Example DeFi contract + value: BigInt(0), + data: '0x095ea7b3000000000000000000000000742d35cc6b2d9e9a3b7b7b4a7b0b2b7b0b2b7b0bffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff' as `0x${string}`, // approve(spender, amount) + nonce: 5, + maxFeePerGas: BigInt('25000000000'), // 25 gwei + maxPriorityFeePerGas: BigInt('2000000000'), // 2 gwei + gas: BigInt('46000'), // Typical for ERC20 approval + chainId: 1, + }, + category: 'defi-approval', + }, + { + name: 'NFT minting transaction', + tx: { + type: 'eip1559', + to: '0xe242e54155b1abc71fc118065270cecaaf8b7768' as `0x${string}`, + value: BigInt('50000000000000000'), // 0.05 ETH mint fee + data: '0x40c10f19000000000000000000000000742d35cc6b2d9e9a3b7b7b4a7b0b2b7b0b2b7b0b0000000000000000000000000000000000000000000000000000000000000001' as `0x${string}`, // mint(to, tokenId) + nonce: 10, + maxFeePerGas: BigInt('30000000000'), // 30 gwei + maxPriorityFeePerGas: BigInt('3000000000'), // 3 gwei + gas: BigInt('200000'), // Higher gas for NFT mint + chainId: 1, + }, + category: 'nft-mint', + }, + { + name: 'Multi-send transaction', + tx: { + type: 'eip1559', + to: '0xe242e54155b1abc71fc118065270cecaaf8b7768' as `0x${string}`, + value: BigInt(0), + data: `0x8d80ff0a${'00'.repeat(500)}` as `0x${string}`, // multiSend with batch data + nonce: 15, + maxFeePerGas: BigInt('40000000000'), // 40 gwei + maxPriorityFeePerGas: BigInt('4000000000'), // 4 gwei + gas: BigInt('800000'), // High gas for multi-send + chainId: 1, + }, + category: 'multi-send', + }, +] /** * All comprehensive test vectors combined for easy access */ export const ALL_COMPREHENSIVE_VECTORS: TestVector[] = [ - ...LEGACY_VECTORS, - ...EIP1559_TEST_VECTORS, - ...EIP2930_TEST_VECTORS, - ...EIP7702_TEST_VECTORS, - ...EDGE_CASE_TEST_VECTORS, - ...DERIVATION_PATH_VECTORS, - ...NETWORK_SPECIFIC_VECTORS, - ...PAYLOAD_SIZE_VECTORS, - ...BOUNDARY_CONDITION_VECTORS, - ...REAL_WORLD_PATTERN_VECTORS, -]; + ...LEGACY_VECTORS, + ...EIP1559_TEST_VECTORS, + ...EIP2930_TEST_VECTORS, + ...EIP7702_TEST_VECTORS, + ...EDGE_CASE_TEST_VECTORS, + ...DERIVATION_PATH_VECTORS, + ...NETWORK_SPECIFIC_VECTORS, + ...PAYLOAD_SIZE_VECTORS, + ...BOUNDARY_CONDITION_VECTORS, + ...REAL_WORLD_PATTERN_VECTORS, +] /** * Get vectors by category for targeted testing */ export function getVectorsByCategory(category: string): TestVector[] { - return ALL_COMPREHENSIVE_VECTORS.filter( - (vector) => vector.category === category, - ); + return ALL_COMPREHENSIVE_VECTORS.filter((vector) => vector.category === category) } /** * Get a specific number of vectors from each transaction type for balanced testing */ export function getBalancedTestVectors(perType = 3): TestVector[] { - const legacyVectors = LEGACY_VECTORS.slice(0, perType); - const eip1559Vectors = EIP1559_TEST_VECTORS.slice(0, perType); - const eip2930Vectors = EIP2930_TEST_VECTORS.slice(0, perType); - const eip7702Vectors = EIP7702_TEST_VECTORS.slice(0, perType); + const legacyVectors = LEGACY_VECTORS.slice(0, perType) + const eip1559Vectors = EIP1559_TEST_VECTORS.slice(0, perType) + const eip2930Vectors = EIP2930_TEST_VECTORS.slice(0, perType) + const eip7702Vectors = EIP7702_TEST_VECTORS.slice(0, perType) - return [ - ...legacyVectors, - ...eip1559Vectors, - ...eip2930Vectors, - ...eip7702Vectors, - ]; + return [...legacyVectors, ...eip1559Vectors, ...eip2930Vectors, ...eip7702Vectors] } /** * Get vectors for boundary testing specifically */ export function getBoundaryTestVectors(): TestVector[] { - return [ - ...BOUNDARY_CONDITION_VECTORS, - ...EDGE_CASE_TEST_VECTORS, - ...PAYLOAD_SIZE_VECTORS, - ]; + return [...BOUNDARY_CONDITION_VECTORS, ...EDGE_CASE_TEST_VECTORS, ...PAYLOAD_SIZE_VECTORS] } /** * Get vectors for network compatibility testing */ export function getNetworkTestVectors(): TestVector[] { - return [ - ...NETWORK_SPECIFIC_VECTORS, - // Add some edge cases with different networks - ...EDGE_CASE_TEST_VECTORS.filter((v) => v.category?.includes('chain')), - ]; + return [ + ...NETWORK_SPECIFIC_VECTORS, + // Add some edge cases with different networks + ...EDGE_CASE_TEST_VECTORS.filter((v) => v.category?.includes('chain')), + ] } diff --git a/packages/sdk/src/__test__/e2e/solana/addresses.test.ts b/packages/sdk/src/__test__/e2e/solana/addresses.test.ts index f6b55b3d..bbb2423b 100644 --- a/packages/sdk/src/__test__/e2e/solana/addresses.test.ts +++ b/packages/sdk/src/__test__/e2e/solana/addresses.test.ts @@ -1,44 +1,44 @@ -import { PublicKey } from '@solana/web3.js'; -import { question } from 'readline-sync'; -import { pair } from '../../../api'; -import { fetchSolanaAddresses } from '../../../api/addresses'; -import { setupClient } from '../../utils/setup'; +import { PublicKey } from '@solana/web3.js' +import { question } from 'readline-sync' +import { pair } from '../../../api' +import { fetchSolanaAddresses } from '../../../api/addresses' +import { setupClient } from '../../utils/setup' describe('Solana Addresses', () => { - test('pair', async () => { - const isPaired = await setupClient(); - if (!isPaired) { - const secret = question('Please enter the pairing secret: '); - await pair(secret.toUpperCase()); - } - }); + test('pair', async () => { + const isPaired = await setupClient() + if (!isPaired) { + const secret = question('Please enter the pairing secret: ') + await pair(secret.toUpperCase()) + } + }) - test('Should fetch a single Solana Ed25519 public key using fetchSolanaAddresses', async () => { - const addresses = await fetchSolanaAddresses({ - n: 10, - }); + test('Should fetch a single Solana Ed25519 public key using fetchSolanaAddresses', async () => { + const addresses = await fetchSolanaAddresses({ + n: 10, + }) - const addrs = addresses - .filter((addr) => { - try { - // Check if the key is a valid Ed25519 public key - const pk = new PublicKey(addr); - const isOnCurve = PublicKey.isOnCurve(pk.toBytes()); - expect(isOnCurve).toBe(true); - return true; - } catch (e) { - console.error('Invalid Solana public key:', e); - return false; - } - }) - .map((addr) => { - const pk = new PublicKey(addr); - return pk.toBase58(); - }); + const addrs = addresses + .filter((addr) => { + try { + // Check if the key is a valid Ed25519 public key + const pk = new PublicKey(addr) + const isOnCurve = PublicKey.isOnCurve(pk.toBytes()) + expect(isOnCurve).toBe(true) + return true + } catch (e) { + console.error('Invalid Solana public key:', e) + return false + } + }) + .map((addr) => { + const pk = new PublicKey(addr) + return pk.toBase58() + }) - // Ensure we got at least one valid address - expect(addrs.length).toBeGreaterThan(0); - // Ensure none of the addresses start with '11111' - expect(addrs.every((addr) => !addr.startsWith('11111'))).toBe(true); - }); -}); + // Ensure we got at least one valid address + expect(addrs.length).toBeGreaterThan(0) + // Ensure none of the addresses start with '11111' + expect(addrs.every((addr) => !addr.startsWith('11111'))).toBe(true) + }) +}) diff --git a/packages/sdk/src/__test__/e2e/xpub.test.ts b/packages/sdk/src/__test__/e2e/xpub.test.ts index b57802b7..9f69272b 100644 --- a/packages/sdk/src/__test__/e2e/xpub.test.ts +++ b/packages/sdk/src/__test__/e2e/xpub.test.ts @@ -1,29 +1,29 @@ -import { fetchBtcXpub, fetchBtcYpub, fetchBtcZpub } from '../../api'; -import { setupClient } from '../utils/setup'; +import { fetchBtcXpub, fetchBtcYpub, fetchBtcZpub } from '../../api' +import { setupClient } from '../utils/setup' describe('XPUB', () => { - beforeAll(async () => { - await setupClient(); - }); + beforeAll(async () => { + await setupClient() + }) - test('fetchBtcXpub returns xpub', async () => { - const xpub = await fetchBtcXpub(); - expect(xpub).toBeTruthy(); - expect(xpub.startsWith('xpub')).toBe(true); - expect(xpub.length).toBeGreaterThan(100); - }); + test('fetchBtcXpub returns xpub', async () => { + const xpub = await fetchBtcXpub() + expect(xpub).toBeTruthy() + expect(xpub.startsWith('xpub')).toBe(true) + expect(xpub.length).toBeGreaterThan(100) + }) - test('fetchBtcYpub returns ypub', async () => { - const ypub = await fetchBtcYpub(); - expect(ypub).toBeTruthy(); - expect(ypub.startsWith('ypub')).toBe(true); - expect(ypub.length).toBeGreaterThan(100); - }); + test('fetchBtcYpub returns ypub', async () => { + const ypub = await fetchBtcYpub() + expect(ypub).toBeTruthy() + expect(ypub.startsWith('ypub')).toBe(true) + expect(ypub.length).toBeGreaterThan(100) + }) - test('fetchBtcZpub returns zpub', async () => { - const zpub = await fetchBtcZpub(); - expect(zpub).toBeTruthy(); - expect(zpub.startsWith('zpub')).toBe(true); - expect(zpub.length).toBeGreaterThan(100); - }); -}); + test('fetchBtcZpub returns zpub', async () => { + const zpub = await fetchBtcZpub() + expect(zpub).toBeTruthy() + expect(zpub.startsWith('zpub')).toBe(true) + expect(zpub.length).toBeGreaterThan(100) + }) +}) diff --git a/packages/sdk/src/__test__/integration/__mocks__/4byte.ts b/packages/sdk/src/__test__/integration/__mocks__/4byte.ts index 4bdb39f8..b1183de3 100644 --- a/packages/sdk/src/__test__/integration/__mocks__/4byte.ts +++ b/packages/sdk/src/__test__/integration/__mocks__/4byte.ts @@ -1,95 +1,92 @@ export const fourbyteResponse0xa9059cbb = { - results: [ - { - bytes_signature: '©œ»', - created_at: '2016-07-09T03:58:28.234977Z', - hex_signature: '0xa9059cbb', - id: 145, - text_signature: 'transfer(address,uint256)', - }, - { - bytes_signature: '©œ»', - created_at: '2018-05-11T08:39:29.708250Z', - hex_signature: '0xa9059cbb', - id: 31780, - text_signature: 'many_msg_babbage(bytes1)', - }, - { - bytes_signature: '©œ»', - created_at: '2019-03-22T19:13:17.314877Z', - hex_signature: '0xa9059cbb', - id: 161159, - text_signature: 'transfer(bytes4[9],bytes5[6],int48[11])', - }, - { - bytes_signature: '©œ»', - created_at: '2021-10-20T05:29:13.555535Z', - hex_signature: '0xa9059cbb', - id: 313067, - text_signature: 'func_2093253501(bytes)', - }, - ], -}; + results: [ + { + bytes_signature: '©œ»', + created_at: '2016-07-09T03:58:28.234977Z', + hex_signature: '0xa9059cbb', + id: 145, + text_signature: 'transfer(address,uint256)', + }, + { + bytes_signature: '©œ»', + created_at: '2018-05-11T08:39:29.708250Z', + hex_signature: '0xa9059cbb', + id: 31780, + text_signature: 'many_msg_babbage(bytes1)', + }, + { + bytes_signature: '©œ»', + created_at: '2019-03-22T19:13:17.314877Z', + hex_signature: '0xa9059cbb', + id: 161159, + text_signature: 'transfer(bytes4[9],bytes5[6],int48[11])', + }, + { + bytes_signature: '©œ»', + created_at: '2021-10-20T05:29:13.555535Z', + hex_signature: '0xa9059cbb', + id: 313067, + text_signature: 'func_2093253501(bytes)', + }, + ], +} export const fourbyteResponse0x38ed1739 = { - results: [ - { - bytes_signature: '8í9', - created_at: '2020-08-09T08:56:14.110995Z', - hex_signature: '0x38ed1739', - id: 171806, - text_signature: - 'swapExactTokensForTokens(uint256,uint256,address[],address,uint256)', - }, - ], -}; + results: [ + { + bytes_signature: '8í9', + created_at: '2020-08-09T08:56:14.110995Z', + hex_signature: '0x38ed1739', + id: 171806, + text_signature: 'swapExactTokensForTokens(uint256,uint256,address[],address,uint256)', + }, + ], +} export const fourbyteResponseac9650d8 = { - results: [ - { - id: 134730, - created_at: '2018-10-13T08:40:29.456228Z', - text_signature: 'multicall(bytes[])', - hex_signature: '0xac9650d8', - bytes_signature: '¬\x96PØ', - }, - ], -}; + results: [ + { + id: 134730, + created_at: '2018-10-13T08:40:29.456228Z', + text_signature: 'multicall(bytes[])', + hex_signature: '0xac9650d8', + bytes_signature: '¬\x96PØ', + }, + ], +} export const fourbyteResponse0c49ccbe = { - results: [ - { - id: 186682, - created_at: '2021-05-09T03:48:17.627742Z', - text_signature: - 'decreaseLiquidity((uint256,uint128,uint256,uint256,uint256))', - hex_signature: '0x0c49ccbe', - bytes_signature: '\\fI̾', - }, - ], -}; + results: [ + { + id: 186682, + created_at: '2021-05-09T03:48:17.627742Z', + text_signature: 'decreaseLiquidity((uint256,uint128,uint256,uint256,uint256))', + hex_signature: '0x0c49ccbe', + bytes_signature: '\\fI̾', + }, + ], +} export const fourbyteResponsefc6f7865 = { - results: [ - { - id: 186681, - created_at: '2021-05-09T03:48:17.621683Z', - text_signature: 'collect((uint256,address,uint128,uint128))', - hex_signature: '0xfc6f7865', - bytes_signature: 'üoxe', - }, - ], -}; + results: [ + { + id: 186681, + created_at: '2021-05-09T03:48:17.621683Z', + text_signature: 'collect((uint256,address,uint128,uint128))', + hex_signature: '0xfc6f7865', + bytes_signature: 'üoxe', + }, + ], +} export const fourbyteResponse0x6a761202 = { - results: [ - { - id: 169422, - created_at: '2020-01-28T10:40:07.614936Z', - text_signature: - 'execTransaction(address,uint256,bytes,uint8,uint256,uint256,uint256,address,address,bytes)', - hex_signature: '0x6a761202', - bytes_signature: 'jv\\u0012\\u0002', - }, - ], -}; + results: [ + { + id: 169422, + created_at: '2020-01-28T10:40:07.614936Z', + text_signature: 'execTransaction(address,uint256,bytes,uint8,uint256,uint256,uint256,address,address,bytes)', + hex_signature: '0x6a761202', + bytes_signature: 'jv\\u0012\\u0002', + }, + ], +} diff --git a/packages/sdk/src/__test__/integration/__mocks__/addKvRecords.json b/packages/sdk/src/__test__/integration/__mocks__/addKvRecords.json index e19abf7c..011fd8a6 100644 --- a/packages/sdk/src/__test__/integration/__mocks__/addKvRecords.json +++ b/packages/sdk/src/__test__/integration/__mocks__/addKvRecords.json @@ -1,4 +1,4 @@ { - "status": 200, - "message": "0100d04ed4310d810055eb0c33ba04d9631dde8d79d0d92ee01c9bf716e06af3f44b77c2082b4862d02135a928631e1f54844a0f90fee7c79cde79e72eddb484b83777e60b450af882384cdbb1d173e1d01b9c405d7617d0da2932f66d90b80e3acfd78d290775cfcad12160b4b9cf586271475d047b033af9d03a6a69f52ce7cb02152c9b3f0f19aae2b30362352c07aa7fc23d464765cf7dafc40fb9d5657f8bdfcf0e23f7d8219327556ab684d3c6857ba9c4c7946d48e3977f05dab5aaaaec16e6011bf9ce98f8b0016de91da0ff89f6877a2c7f93f9830588fbede08635da18345e5a7a7bd0e85cfbf751f28269a1d47b90ad3e673eb9ba1b8676ae3f12aac74cbf20c32a11f812e10e59a612c5af770a9116f6a673fb62286c84c923610dd103d92ad713225912bd65c797243a9a70987b51729643761b7b3f8401ab062f72efc1d3d6a25708c8805538a8af0c43de7541c3ad384c09555e4015c7c008904a26ce05b38bff4cc7736e47bfec550ec66a64d3b0cb0f025f32aa8cc9f5d25c7fd0dbee81ec90136ae5a45fdcef180a779f03025fad97d035d02f2c2fa7f235f917992db038528f28b994e04af5b049ec275142d57b176346e3a97074fd94a8c1726ed86fe9f44e3db4b608948ea830d845f05100fa70e507af5b0b6a810548b23bf744ed140295525e667cf1a529645add1d02795f58b9b3ca6c47e40ec99ada4725d0094cc2fdd76448cf45f4644845f3c86f5d50643679c94aba63e6c78d877cc868701176cccb5930265c84858e7d5ea20a5f4ca35bfd4b28687bf6b67afea7d6c3aa7d73d586b074644d3469aeec086d9f91a56c40d3f5eff4466ef0c866f279b4c3d0d6d0cf7d35d052fd2e8626b9f2a966cd90efd49c8c9894778f06582a5f11994cb3055779dc1b09c401b622af9ff9797a254568425c93a06b9dac8f97482ad282e2d4f53497076952f7c4ebb39946a4bd360ede9472d6401811f2189418f5513cdeb898dffc463d0071a68a91a40740967a39386eb643519603ce2ba94725c61367ad31ee1e180d74148d81f52f6fca491d83af84d124459be161e259ea2088c4f5f0c157b54aaa0e84295acf0bcf42d079c7397c0712356c451964bb4f52c0b2881d7bae8f021e69f474bb92cc2e473ef564cac4570afdc57c6eb45ce4dcb18f4f8fec4c0ad29b8dc93044fc2c7c46495d0d9be6ff25b5531917b324b60311c982fc2de3149685d3ba4eecc567f2018cf90b7436bd0d3b3d29abd8babffd26df94abb39278e92b7f62d2c7bdfea7de442acdbdd578ba233768e512081bd148445c87704a44353e1dfd770d4aa1f0ef2194f085ecdbfd1cfe3acd46d8457f45e52d2e5e27607702f2261a51a91fb23ce0890ad0290166993ed4642cb040e6952b1f8c4fa50685e98264e41c8cc747dcc9f149d229b5462a5643fd240b01c59a1453c39795b619fcdf0a9bf16cb280fb27ab7fd9b2fc85d330211f4b4c224e59c1f69698bf38613f8ab6ecf868748fe57d7787b15f45871f1e9e893a57ecc29331147712c1be855526e5d036745ef75e154149d441a6c7ace5a3e787a97577b09993d7423ed08ea0eb9a654cbcefc1afc62abc88028241e77625b1489f7cede86e905a807a6833f1fa223f6a7a02cc3e927cfcf7d230b188717788190ddc716a6586688ed6d562890c6e5af8142f272709c8c3bb2221030ae6d1c8937972c32782d32c9a871723107c7e6b5c5cf610f4a77d8fa477b27f60beb67cf9361acd46bb7d7934e9705753eee8a7f02e9769e72785094c283a9c85a5002d05c2d57670194cffb0be4409608560bcb8fb8fe8eb1d3b1975279f9b76c36653276498210a388aa7fe8e312ffaf97d71ba0c190471b0efa79dea111150fd71bce2386811eecc4537d2942a24a2f83ab4e3e764da9864c5d1fb51ae342d9b289e1616fad4c6e23575b88004fc254beed53e474d8400b81cf13e208475fae3292cd7cf60dec5346b4bb4cba008d3ff52ac8d8f748331243821e86cc8addd96da026268e780f170189d4a57098ac7f474eda8d94cad1fb0967010e37ccb28603a21bed6efed3f9295cb6cc7406758a9ca0350535523d07c50b1197dbcea31db87c03f5ef3c9a5580c598483124d76ae7f38cc76fb94c07bbd236c7c7294fd9b80f280ee02102399eb8e4e0c66dc12cfd4f61137f9e7def0af4db1f88b40b1888a09f18b60ac17f2469f47e7d6eeb98e25638f89594b20a87217ab15ea9c9ac7530ce10af64d53601d15438f330afd80b55f717bc2f5d86d3811902fc4eb6ee8d4b1aa2d86ff2788d1b1833f6581bc5f1748579c2999aaa51e56364a3e5aad2172e7d5de74953e6ab1b5de74077c8267f92dec39abe2f1f31163cab9efbe1b5852b2ad891fe289ec68919bf422586dfc7328afcd5dc6a27f3d9f87e3f3e247d5857963f0083e2deea20600000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000066e17acd" + "status": 200, + "message": "0100d04ed4310d810055eb0c33ba04d9631dde8d79d0d92ee01c9bf716e06af3f44b77c2082b4862d02135a928631e1f54844a0f90fee7c79cde79e72eddb484b83777e60b450af882384cdbb1d173e1d01b9c405d7617d0da2932f66d90b80e3acfd78d290775cfcad12160b4b9cf586271475d047b033af9d03a6a69f52ce7cb02152c9b3f0f19aae2b30362352c07aa7fc23d464765cf7dafc40fb9d5657f8bdfcf0e23f7d8219327556ab684d3c6857ba9c4c7946d48e3977f05dab5aaaaec16e6011bf9ce98f8b0016de91da0ff89f6877a2c7f93f9830588fbede08635da18345e5a7a7bd0e85cfbf751f28269a1d47b90ad3e673eb9ba1b8676ae3f12aac74cbf20c32a11f812e10e59a612c5af770a9116f6a673fb62286c84c923610dd103d92ad713225912bd65c797243a9a70987b51729643761b7b3f8401ab062f72efc1d3d6a25708c8805538a8af0c43de7541c3ad384c09555e4015c7c008904a26ce05b38bff4cc7736e47bfec550ec66a64d3b0cb0f025f32aa8cc9f5d25c7fd0dbee81ec90136ae5a45fdcef180a779f03025fad97d035d02f2c2fa7f235f917992db038528f28b994e04af5b049ec275142d57b176346e3a97074fd94a8c1726ed86fe9f44e3db4b608948ea830d845f05100fa70e507af5b0b6a810548b23bf744ed140295525e667cf1a529645add1d02795f58b9b3ca6c47e40ec99ada4725d0094cc2fdd76448cf45f4644845f3c86f5d50643679c94aba63e6c78d877cc868701176cccb5930265c84858e7d5ea20a5f4ca35bfd4b28687bf6b67afea7d6c3aa7d73d586b074644d3469aeec086d9f91a56c40d3f5eff4466ef0c866f279b4c3d0d6d0cf7d35d052fd2e8626b9f2a966cd90efd49c8c9894778f06582a5f11994cb3055779dc1b09c401b622af9ff9797a254568425c93a06b9dac8f97482ad282e2d4f53497076952f7c4ebb39946a4bd360ede9472d6401811f2189418f5513cdeb898dffc463d0071a68a91a40740967a39386eb643519603ce2ba94725c61367ad31ee1e180d74148d81f52f6fca491d83af84d124459be161e259ea2088c4f5f0c157b54aaa0e84295acf0bcf42d079c7397c0712356c451964bb4f52c0b2881d7bae8f021e69f474bb92cc2e473ef564cac4570afdc57c6eb45ce4dcb18f4f8fec4c0ad29b8dc93044fc2c7c46495d0d9be6ff25b5531917b324b60311c982fc2de3149685d3ba4eecc567f2018cf90b7436bd0d3b3d29abd8babffd26df94abb39278e92b7f62d2c7bdfea7de442acdbdd578ba233768e512081bd148445c87704a44353e1dfd770d4aa1f0ef2194f085ecdbfd1cfe3acd46d8457f45e52d2e5e27607702f2261a51a91fb23ce0890ad0290166993ed4642cb040e6952b1f8c4fa50685e98264e41c8cc747dcc9f149d229b5462a5643fd240b01c59a1453c39795b619fcdf0a9bf16cb280fb27ab7fd9b2fc85d330211f4b4c224e59c1f69698bf38613f8ab6ecf868748fe57d7787b15f45871f1e9e893a57ecc29331147712c1be855526e5d036745ef75e154149d441a6c7ace5a3e787a97577b09993d7423ed08ea0eb9a654cbcefc1afc62abc88028241e77625b1489f7cede86e905a807a6833f1fa223f6a7a02cc3e927cfcf7d230b188717788190ddc716a6586688ed6d562890c6e5af8142f272709c8c3bb2221030ae6d1c8937972c32782d32c9a871723107c7e6b5c5cf610f4a77d8fa477b27f60beb67cf9361acd46bb7d7934e9705753eee8a7f02e9769e72785094c283a9c85a5002d05c2d57670194cffb0be4409608560bcb8fb8fe8eb1d3b1975279f9b76c36653276498210a388aa7fe8e312ffaf97d71ba0c190471b0efa79dea111150fd71bce2386811eecc4537d2942a24a2f83ab4e3e764da9864c5d1fb51ae342d9b289e1616fad4c6e23575b88004fc254beed53e474d8400b81cf13e208475fae3292cd7cf60dec5346b4bb4cba008d3ff52ac8d8f748331243821e86cc8addd96da026268e780f170189d4a57098ac7f474eda8d94cad1fb0967010e37ccb28603a21bed6efed3f9295cb6cc7406758a9ca0350535523d07c50b1197dbcea31db87c03f5ef3c9a5580c598483124d76ae7f38cc76fb94c07bbd236c7c7294fd9b80f280ee02102399eb8e4e0c66dc12cfd4f61137f9e7def0af4db1f88b40b1888a09f18b60ac17f2469f47e7d6eeb98e25638f89594b20a87217ab15ea9c9ac7530ce10af64d53601d15438f330afd80b55f717bc2f5d86d3811902fc4eb6ee8d4b1aa2d86ff2788d1b1833f6581bc5f1748579c2999aaa51e56364a3e5aad2172e7d5de74953e6ab1b5de74077c8267f92dec39abe2f1f31163cab9efbe1b5852b2ad891fe289ec68919bf422586dfc7328afcd5dc6a27f3d9f87e3f3e247d5857963f0083e2deea20600000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000066e17acd" } diff --git a/packages/sdk/src/__test__/integration/__mocks__/connect.json b/packages/sdk/src/__test__/integration/__mocks__/connect.json index d4b37d57..ed4fdf2b 100644 --- a/packages/sdk/src/__test__/integration/__mocks__/connect.json +++ b/packages/sdk/src/__test__/integration/__mocks__/connect.json @@ -1,4 +1,4 @@ { - "status": 200, - "message": "0100c7d11ffc00d70001048f065cf1beafadb8d15d31adc8d1f029e25b24dc9cda017cfd054a2a86e2b53eaabf9437cf69fa68f20fff3b80b2b5806e8fc6651890bdae7ffad2c4a8a43151000f00009d00f3d077fabeac89f0f0b2fba9c642630e3a1c1aa845f9e3c9e2c0234d6d05634b0d7bbf4c0de40a69336e5126392aa86976d2378cefd835659f22b1b7dc47c9f7731696ae1ba92c9d3011ad9c9cb12973b0e788a4d87d72f4a757e1a7e4fb41616d0790e90fcd0fe138b4e15170b77f76e2974e47bd275010fc70f03ed71692da4780422fba345bb787abaa91f8e9b7075819" + "status": 200, + "message": "0100c7d11ffc00d70001048f065cf1beafadb8d15d31adc8d1f029e25b24dc9cda017cfd054a2a86e2b53eaabf9437cf69fa68f20fff3b80b2b5806e8fc6651890bdae7ffad2c4a8a43151000f00009d00f3d077fabeac89f0f0b2fba9c642630e3a1c1aa845f9e3c9e2c0234d6d05634b0d7bbf4c0de40a69336e5126392aa86976d2378cefd835659f22b1b7dc47c9f7731696ae1ba92c9d3011ad9c9cb12973b0e788a4d87d72f4a757e1a7e4fb41616d0790e90fcd0fe138b4e15170b77f76e2974e47bd275010fc70f03ed71692da4780422fba345bb787abaa91f8e9b7075819" } diff --git a/packages/sdk/src/__test__/integration/__mocks__/etherscan.ts b/packages/sdk/src/__test__/integration/__mocks__/etherscan.ts index 4cdd2629..24effa35 100644 --- a/packages/sdk/src/__test__/integration/__mocks__/etherscan.ts +++ b/packages/sdk/src/__test__/integration/__mocks__/etherscan.ts @@ -1,2874 +1,2870 @@ export const etherscanResponse0xa0b86991 = [ - { - constant: false, - inputs: [{ name: 'newImplementation', type: 'address' }], - name: 'upgradeTo', - outputs: [], - payable: false, - stateMutability: 'nonpayable', - type: 'function', - }, - { - constant: false, - inputs: [ - { name: 'newImplementation', type: 'address' }, - { name: 'data', type: 'bytes' }, - ], - name: 'upgradeToAndCall', - outputs: [], - payable: true, - stateMutability: 'payable', - type: 'function', - }, - { - constant: true, - inputs: [], - name: 'implementation', - outputs: [{ name: '', type: 'address' }], - payable: false, - stateMutability: 'view', - type: 'function', - }, - { - constant: false, - inputs: [{ name: 'newAdmin', type: 'address' }], - name: 'changeAdmin', - outputs: [], - payable: false, - stateMutability: 'nonpayable', - type: 'function', - }, - { - constant: true, - inputs: [], - name: 'admin', - outputs: [{ name: '', type: 'address' }], - payable: false, - stateMutability: 'view', - type: 'function', - }, - { - inputs: [{ name: '_implementation', type: 'address' }], - payable: false, - stateMutability: 'nonpayable', - type: 'constructor', - }, - { payable: true, stateMutability: 'payable', type: 'fallback' }, - { - anonymous: false, - inputs: [ - { indexed: false, name: 'previousAdmin', type: 'address' }, - { indexed: false, name: 'newAdmin', type: 'address' }, - ], - name: 'AdminChanged', - type: 'event', - }, - { - anonymous: false, - inputs: [{ indexed: false, name: 'implementation', type: 'address' }], - name: 'Upgraded', - type: 'event', - }, -]; + { + constant: false, + inputs: [{ name: 'newImplementation', type: 'address' }], + name: 'upgradeTo', + outputs: [], + payable: false, + stateMutability: 'nonpayable', + type: 'function', + }, + { + constant: false, + inputs: [ + { name: 'newImplementation', type: 'address' }, + { name: 'data', type: 'bytes' }, + ], + name: 'upgradeToAndCall', + outputs: [], + payable: true, + stateMutability: 'payable', + type: 'function', + }, + { + constant: true, + inputs: [], + name: 'implementation', + outputs: [{ name: '', type: 'address' }], + payable: false, + stateMutability: 'view', + type: 'function', + }, + { + constant: false, + inputs: [{ name: 'newAdmin', type: 'address' }], + name: 'changeAdmin', + outputs: [], + payable: false, + stateMutability: 'nonpayable', + type: 'function', + }, + { + constant: true, + inputs: [], + name: 'admin', + outputs: [{ name: '', type: 'address' }], + payable: false, + stateMutability: 'view', + type: 'function', + }, + { + inputs: [{ name: '_implementation', type: 'address' }], + payable: false, + stateMutability: 'nonpayable', + type: 'constructor', + }, + { payable: true, stateMutability: 'payable', type: 'fallback' }, + { + anonymous: false, + inputs: [ + { indexed: false, name: 'previousAdmin', type: 'address' }, + { indexed: false, name: 'newAdmin', type: 'address' }, + ], + name: 'AdminChanged', + type: 'event', + }, + { + anonymous: false, + inputs: [{ indexed: false, name: 'implementation', type: 'address' }], + name: 'Upgraded', + type: 'event', + }, +] export const etherscanResponse0x7a250d56 = [ - { - inputs: [ - { - internalType: 'address', - name: '_factory', - type: 'address', - }, - { - internalType: 'address', - name: '_WETH', - type: 'address', - }, - ], - stateMutability: 'nonpayable', - type: 'constructor', - }, - { - inputs: [], - name: 'WETH', - outputs: [ - { - internalType: 'address', - name: '', - type: 'address', - }, - ], - stateMutability: 'view', - type: 'function', - }, - { - inputs: [ - { - internalType: 'address', - name: 'tokenA', - type: 'address', - }, - { - internalType: 'address', - name: 'tokenB', - type: 'address', - }, - { - internalType: 'uint256', - name: 'amountADesired', - type: 'uint256', - }, - { - internalType: 'uint256', - name: 'amountBDesired', - type: 'uint256', - }, - { - internalType: 'uint256', - name: 'amountAMin', - type: 'uint256', - }, - { - internalType: 'uint256', - name: 'amountBMin', - type: 'uint256', - }, - { - internalType: 'address', - name: 'to', - type: 'address', - }, - { - internalType: 'uint256', - name: 'deadline', - type: 'uint256', - }, - ], - name: 'addLiquidity', - outputs: [ - { - internalType: 'uint256', - name: 'amountA', - type: 'uint256', - }, - { - internalType: 'uint256', - name: 'amountB', - type: 'uint256', - }, - { - internalType: 'uint256', - name: 'liquidity', - type: 'uint256', - }, - ], - stateMutability: 'nonpayable', - type: 'function', - }, - { - inputs: [ - { - internalType: 'address', - name: 'token', - type: 'address', - }, - { - internalType: 'uint256', - name: 'amountTokenDesired', - type: 'uint256', - }, - { - internalType: 'uint256', - name: 'amountTokenMin', - type: 'uint256', - }, - { - internalType: 'uint256', - name: 'amountETHMin', - type: 'uint256', - }, - { - internalType: 'address', - name: 'to', - type: 'address', - }, - { - internalType: 'uint256', - name: 'deadline', - type: 'uint256', - }, - ], - name: 'addLiquidityETH', - outputs: [ - { - internalType: 'uint256', - name: 'amountToken', - type: 'uint256', - }, - { - internalType: 'uint256', - name: 'amountETH', - type: 'uint256', - }, - { - internalType: 'uint256', - name: 'liquidity', - type: 'uint256', - }, - ], - stateMutability: 'payable', - type: 'function', - }, - { - inputs: [], - name: 'factory', - outputs: [ - { - internalType: 'address', - name: '', - type: 'address', - }, - ], - stateMutability: 'view', - type: 'function', - }, - { - inputs: [ - { - internalType: 'uint256', - name: 'amountOut', - type: 'uint256', - }, - { - internalType: 'uint256', - name: 'reserveIn', - type: 'uint256', - }, - { - internalType: 'uint256', - name: 'reserveOut', - type: 'uint256', - }, - ], - name: 'getAmountIn', - outputs: [ - { - internalType: 'uint256', - name: 'amountIn', - type: 'uint256', - }, - ], - stateMutability: 'pure', - type: 'function', - }, - { - inputs: [ - { - internalType: 'uint256', - name: 'amountIn', - type: 'uint256', - }, - { - internalType: 'uint256', - name: 'reserveIn', - type: 'uint256', - }, - { - internalType: 'uint256', - name: 'reserveOut', - type: 'uint256', - }, - ], - name: 'getAmountOut', - outputs: [ - { - internalType: 'uint256', - name: 'amountOut', - type: 'uint256', - }, - ], - stateMutability: 'pure', - type: 'function', - }, - { - inputs: [ - { - internalType: 'uint256', - name: 'amountOut', - type: 'uint256', - }, - { - internalType: 'address[]', - name: 'path', - type: 'address[]', - }, - ], - name: 'getAmountsIn', - outputs: [ - { - internalType: 'uint256[]', - name: 'amounts', - type: 'uint256[]', - }, - ], - stateMutability: 'view', - type: 'function', - }, - { - inputs: [ - { - internalType: 'uint256', - name: 'amountIn', - type: 'uint256', - }, - { - internalType: 'address[]', - name: 'path', - type: 'address[]', - }, - ], - name: 'getAmountsOut', - outputs: [ - { - internalType: 'uint256[]', - name: 'amounts', - type: 'uint256[]', - }, - ], - stateMutability: 'view', - type: 'function', - }, - { - inputs: [ - { - internalType: 'uint256', - name: 'amountA', - type: 'uint256', - }, - { - internalType: 'uint256', - name: 'reserveA', - type: 'uint256', - }, - { - internalType: 'uint256', - name: 'reserveB', - type: 'uint256', - }, - ], - name: 'quote', - outputs: [ - { - internalType: 'uint256', - name: 'amountB', - type: 'uint256', - }, - ], - stateMutability: 'pure', - type: 'function', - }, - { - inputs: [ - { - internalType: 'address', - name: 'tokenA', - type: 'address', - }, - { - internalType: 'address', - name: 'tokenB', - type: 'address', - }, - { - internalType: 'uint256', - name: 'liquidity', - type: 'uint256', - }, - { - internalType: 'uint256', - name: 'amountAMin', - type: 'uint256', - }, - { - internalType: 'uint256', - name: 'amountBMin', - type: 'uint256', - }, - { - internalType: 'address', - name: 'to', - type: 'address', - }, - { - internalType: 'uint256', - name: 'deadline', - type: 'uint256', - }, - ], - name: 'removeLiquidity', - outputs: [ - { - internalType: 'uint256', - name: 'amountA', - type: 'uint256', - }, - { - internalType: 'uint256', - name: 'amountB', - type: 'uint256', - }, - ], - stateMutability: 'nonpayable', - type: 'function', - }, - { - inputs: [ - { - internalType: 'address', - name: 'token', - type: 'address', - }, - { - internalType: 'uint256', - name: 'liquidity', - type: 'uint256', - }, - { - internalType: 'uint256', - name: 'amountTokenMin', - type: 'uint256', - }, - { - internalType: 'uint256', - name: 'amountETHMin', - type: 'uint256', - }, - { - internalType: 'address', - name: 'to', - type: 'address', - }, - { - internalType: 'uint256', - name: 'deadline', - type: 'uint256', - }, - ], - name: 'removeLiquidityETH', - outputs: [ - { - internalType: 'uint256', - name: 'amountToken', - type: 'uint256', - }, - { - internalType: 'uint256', - name: 'amountETH', - type: 'uint256', - }, - ], - stateMutability: 'nonpayable', - type: 'function', - }, - { - inputs: [ - { - internalType: 'address', - name: 'token', - type: 'address', - }, - { - internalType: 'uint256', - name: 'liquidity', - type: 'uint256', - }, - { - internalType: 'uint256', - name: 'amountTokenMin', - type: 'uint256', - }, - { - internalType: 'uint256', - name: 'amountETHMin', - type: 'uint256', - }, - { - internalType: 'address', - name: 'to', - type: 'address', - }, - { - internalType: 'uint256', - name: 'deadline', - type: 'uint256', - }, - ], - name: 'removeLiquidityETHSupportingFeeOnTransferTokens', - outputs: [ - { - internalType: 'uint256', - name: 'amountETH', - type: 'uint256', - }, - ], - stateMutability: 'nonpayable', - type: 'function', - }, - { - inputs: [ - { - internalType: 'address', - name: 'token', - type: 'address', - }, - { - internalType: 'uint256', - name: 'liquidity', - type: 'uint256', - }, - { - internalType: 'uint256', - name: 'amountTokenMin', - type: 'uint256', - }, - { - internalType: 'uint256', - name: 'amountETHMin', - type: 'uint256', - }, - { - internalType: 'address', - name: 'to', - type: 'address', - }, - { - internalType: 'uint256', - name: 'deadline', - type: 'uint256', - }, - { - internalType: 'bool', - name: 'approveMax', - type: 'bool', - }, - { - internalType: 'uint8', - name: 'v', - type: 'uint8', - }, - { - internalType: 'bytes32', - name: 'r', - type: 'bytes32', - }, - { - internalType: 'bytes32', - name: 's', - type: 'bytes32', - }, - ], - name: 'removeLiquidityETHWithPermit', - outputs: [ - { - internalType: 'uint256', - name: 'amountToken', - type: 'uint256', - }, - { - internalType: 'uint256', - name: 'amountETH', - type: 'uint256', - }, - ], - stateMutability: 'nonpayable', - type: 'function', - }, - { - inputs: [ - { - internalType: 'address', - name: 'token', - type: 'address', - }, - { - internalType: 'uint256', - name: 'liquidity', - type: 'uint256', - }, - { - internalType: 'uint256', - name: 'amountTokenMin', - type: 'uint256', - }, - { - internalType: 'uint256', - name: 'amountETHMin', - type: 'uint256', - }, - { - internalType: 'address', - name: 'to', - type: 'address', - }, - { - internalType: 'uint256', - name: 'deadline', - type: 'uint256', - }, - { - internalType: 'bool', - name: 'approveMax', - type: 'bool', - }, - { - internalType: 'uint8', - name: 'v', - type: 'uint8', - }, - { - internalType: 'bytes32', - name: 'r', - type: 'bytes32', - }, - { - internalType: 'bytes32', - name: 's', - type: 'bytes32', - }, - ], - name: 'removeLiquidityETHWithPermitSupportingFeeOnTransferTokens', - outputs: [ - { - internalType: 'uint256', - name: 'amountETH', - type: 'uint256', - }, - ], - stateMutability: 'nonpayable', - type: 'function', - }, - { - inputs: [ - { - internalType: 'address', - name: 'tokenA', - type: 'address', - }, - { - internalType: 'address', - name: 'tokenB', - type: 'address', - }, - { - internalType: 'uint256', - name: 'liquidity', - type: 'uint256', - }, - { - internalType: 'uint256', - name: 'amountAMin', - type: 'uint256', - }, - { - internalType: 'uint256', - name: 'amountBMin', - type: 'uint256', - }, - { - internalType: 'address', - name: 'to', - type: 'address', - }, - { - internalType: 'uint256', - name: 'deadline', - type: 'uint256', - }, - { - internalType: 'bool', - name: 'approveMax', - type: 'bool', - }, - { - internalType: 'uint8', - name: 'v', - type: 'uint8', - }, - { - internalType: 'bytes32', - name: 'r', - type: 'bytes32', - }, - { - internalType: 'bytes32', - name: 's', - type: 'bytes32', - }, - ], - name: 'removeLiquidityWithPermit', - outputs: [ - { - internalType: 'uint256', - name: 'amountA', - type: 'uint256', - }, - { - internalType: 'uint256', - name: 'amountB', - type: 'uint256', - }, - ], - stateMutability: 'nonpayable', - type: 'function', - }, - { - inputs: [ - { - internalType: 'uint256', - name: 'amountOut', - type: 'uint256', - }, - { - internalType: 'address[]', - name: 'path', - type: 'address[]', - }, - { - internalType: 'address', - name: 'to', - type: 'address', - }, - { - internalType: 'uint256', - name: 'deadline', - type: 'uint256', - }, - ], - name: 'swapETHForExactTokens', - outputs: [ - { - internalType: 'uint256[]', - name: 'amounts', - type: 'uint256[]', - }, - ], - stateMutability: 'payable', - type: 'function', - }, - { - inputs: [ - { - internalType: 'uint256', - name: 'amountOutMin', - type: 'uint256', - }, - { - internalType: 'address[]', - name: 'path', - type: 'address[]', - }, - { - internalType: 'address', - name: 'to', - type: 'address', - }, - { - internalType: 'uint256', - name: 'deadline', - type: 'uint256', - }, - ], - name: 'swapExactETHForTokens', - outputs: [ - { - internalType: 'uint256[]', - name: 'amounts', - type: 'uint256[]', - }, - ], - stateMutability: 'payable', - type: 'function', - }, - { - inputs: [ - { - internalType: 'uint256', - name: 'amountOutMin', - type: 'uint256', - }, - { - internalType: 'address[]', - name: 'path', - type: 'address[]', - }, - { - internalType: 'address', - name: 'to', - type: 'address', - }, - { - internalType: 'uint256', - name: 'deadline', - type: 'uint256', - }, - ], - name: 'swapExactETHForTokensSupportingFeeOnTransferTokens', - outputs: [], - stateMutability: 'payable', - type: 'function', - }, - { - inputs: [ - { - internalType: 'uint256', - name: 'amountIn', - type: 'uint256', - }, - { - internalType: 'uint256', - name: 'amountOutMin', - type: 'uint256', - }, - { - internalType: 'address[]', - name: 'path', - type: 'address[]', - }, - { - internalType: 'address', - name: 'to', - type: 'address', - }, - { - internalType: 'uint256', - name: 'deadline', - type: 'uint256', - }, - ], - name: 'swapExactTokensForETH', - outputs: [ - { - internalType: 'uint256[]', - name: 'amounts', - type: 'uint256[]', - }, - ], - stateMutability: 'nonpayable', - type: 'function', - }, - { - inputs: [ - { - internalType: 'uint256', - name: 'amountIn', - type: 'uint256', - }, - { - internalType: 'uint256', - name: 'amountOutMin', - type: 'uint256', - }, - { - internalType: 'address[]', - name: 'path', - type: 'address[]', - }, - { - internalType: 'address', - name: 'to', - type: 'address', - }, - { - internalType: 'uint256', - name: 'deadline', - type: 'uint256', - }, - ], - name: 'swapExactTokensForETHSupportingFeeOnTransferTokens', - outputs: [], - stateMutability: 'nonpayable', - type: 'function', - }, - { - inputs: [ - { - internalType: 'uint256', - name: 'amountIn', - type: 'uint256', - }, - { - internalType: 'uint256', - name: 'amountOutMin', - type: 'uint256', - }, - { - internalType: 'address[]', - name: 'path', - type: 'address[]', - }, - { - internalType: 'address', - name: 'to', - type: 'address', - }, - { - internalType: 'uint256', - name: 'deadline', - type: 'uint256', - }, - ], - name: 'swapExactTokensForTokens', - outputs: [ - { - internalType: 'uint256[]', - name: 'amounts', - type: 'uint256[]', - }, - ], - stateMutability: 'nonpayable', - type: 'function', - }, - { - inputs: [ - { - internalType: 'uint256', - name: 'amountIn', - type: 'uint256', - }, - { - internalType: 'uint256', - name: 'amountOutMin', - type: 'uint256', - }, - { - internalType: 'address[]', - name: 'path', - type: 'address[]', - }, - { - internalType: 'address', - name: 'to', - type: 'address', - }, - { - internalType: 'uint256', - name: 'deadline', - type: 'uint256', - }, - ], - name: 'swapExactTokensForTokensSupportingFeeOnTransferTokens', - outputs: [], - stateMutability: 'nonpayable', - type: 'function', - }, - { - inputs: [ - { - internalType: 'uint256', - name: 'amountOut', - type: 'uint256', - }, - { - internalType: 'uint256', - name: 'amountInMax', - type: 'uint256', - }, - { - internalType: 'address[]', - name: 'path', - type: 'address[]', - }, - { - internalType: 'address', - name: 'to', - type: 'address', - }, - { - internalType: 'uint256', - name: 'deadline', - type: 'uint256', - }, - ], - name: 'swapTokensForExactETH', - outputs: [ - { - internalType: 'uint256[]', - name: 'amounts', - type: 'uint256[]', - }, - ], - stateMutability: 'nonpayable', - type: 'function', - }, - { - inputs: [ - { - internalType: 'uint256', - name: 'amountOut', - type: 'uint256', - }, - { - internalType: 'uint256', - name: 'amountInMax', - type: 'uint256', - }, - { - internalType: 'address[]', - name: 'path', - type: 'address[]', - }, - { - internalType: 'address', - name: 'to', - type: 'address', - }, - { - internalType: 'uint256', - name: 'deadline', - type: 'uint256', - }, - ], - name: 'swapTokensForExactTokens', - outputs: [ - { - internalType: 'uint256[]', - name: 'amounts', - type: 'uint256[]', - }, - ], - stateMutability: 'nonpayable', - type: 'function', - }, - { - stateMutability: 'payable', - type: 'receive', - }, -]; + { + inputs: [ + { + internalType: 'address', + name: '_factory', + type: 'address', + }, + { + internalType: 'address', + name: '_WETH', + type: 'address', + }, + ], + stateMutability: 'nonpayable', + type: 'constructor', + }, + { + inputs: [], + name: 'WETH', + outputs: [ + { + internalType: 'address', + name: '', + type: 'address', + }, + ], + stateMutability: 'view', + type: 'function', + }, + { + inputs: [ + { + internalType: 'address', + name: 'tokenA', + type: 'address', + }, + { + internalType: 'address', + name: 'tokenB', + type: 'address', + }, + { + internalType: 'uint256', + name: 'amountADesired', + type: 'uint256', + }, + { + internalType: 'uint256', + name: 'amountBDesired', + type: 'uint256', + }, + { + internalType: 'uint256', + name: 'amountAMin', + type: 'uint256', + }, + { + internalType: 'uint256', + name: 'amountBMin', + type: 'uint256', + }, + { + internalType: 'address', + name: 'to', + type: 'address', + }, + { + internalType: 'uint256', + name: 'deadline', + type: 'uint256', + }, + ], + name: 'addLiquidity', + outputs: [ + { + internalType: 'uint256', + name: 'amountA', + type: 'uint256', + }, + { + internalType: 'uint256', + name: 'amountB', + type: 'uint256', + }, + { + internalType: 'uint256', + name: 'liquidity', + type: 'uint256', + }, + ], + stateMutability: 'nonpayable', + type: 'function', + }, + { + inputs: [ + { + internalType: 'address', + name: 'token', + type: 'address', + }, + { + internalType: 'uint256', + name: 'amountTokenDesired', + type: 'uint256', + }, + { + internalType: 'uint256', + name: 'amountTokenMin', + type: 'uint256', + }, + { + internalType: 'uint256', + name: 'amountETHMin', + type: 'uint256', + }, + { + internalType: 'address', + name: 'to', + type: 'address', + }, + { + internalType: 'uint256', + name: 'deadline', + type: 'uint256', + }, + ], + name: 'addLiquidityETH', + outputs: [ + { + internalType: 'uint256', + name: 'amountToken', + type: 'uint256', + }, + { + internalType: 'uint256', + name: 'amountETH', + type: 'uint256', + }, + { + internalType: 'uint256', + name: 'liquidity', + type: 'uint256', + }, + ], + stateMutability: 'payable', + type: 'function', + }, + { + inputs: [], + name: 'factory', + outputs: [ + { + internalType: 'address', + name: '', + type: 'address', + }, + ], + stateMutability: 'view', + type: 'function', + }, + { + inputs: [ + { + internalType: 'uint256', + name: 'amountOut', + type: 'uint256', + }, + { + internalType: 'uint256', + name: 'reserveIn', + type: 'uint256', + }, + { + internalType: 'uint256', + name: 'reserveOut', + type: 'uint256', + }, + ], + name: 'getAmountIn', + outputs: [ + { + internalType: 'uint256', + name: 'amountIn', + type: 'uint256', + }, + ], + stateMutability: 'pure', + type: 'function', + }, + { + inputs: [ + { + internalType: 'uint256', + name: 'amountIn', + type: 'uint256', + }, + { + internalType: 'uint256', + name: 'reserveIn', + type: 'uint256', + }, + { + internalType: 'uint256', + name: 'reserveOut', + type: 'uint256', + }, + ], + name: 'getAmountOut', + outputs: [ + { + internalType: 'uint256', + name: 'amountOut', + type: 'uint256', + }, + ], + stateMutability: 'pure', + type: 'function', + }, + { + inputs: [ + { + internalType: 'uint256', + name: 'amountOut', + type: 'uint256', + }, + { + internalType: 'address[]', + name: 'path', + type: 'address[]', + }, + ], + name: 'getAmountsIn', + outputs: [ + { + internalType: 'uint256[]', + name: 'amounts', + type: 'uint256[]', + }, + ], + stateMutability: 'view', + type: 'function', + }, + { + inputs: [ + { + internalType: 'uint256', + name: 'amountIn', + type: 'uint256', + }, + { + internalType: 'address[]', + name: 'path', + type: 'address[]', + }, + ], + name: 'getAmountsOut', + outputs: [ + { + internalType: 'uint256[]', + name: 'amounts', + type: 'uint256[]', + }, + ], + stateMutability: 'view', + type: 'function', + }, + { + inputs: [ + { + internalType: 'uint256', + name: 'amountA', + type: 'uint256', + }, + { + internalType: 'uint256', + name: 'reserveA', + type: 'uint256', + }, + { + internalType: 'uint256', + name: 'reserveB', + type: 'uint256', + }, + ], + name: 'quote', + outputs: [ + { + internalType: 'uint256', + name: 'amountB', + type: 'uint256', + }, + ], + stateMutability: 'pure', + type: 'function', + }, + { + inputs: [ + { + internalType: 'address', + name: 'tokenA', + type: 'address', + }, + { + internalType: 'address', + name: 'tokenB', + type: 'address', + }, + { + internalType: 'uint256', + name: 'liquidity', + type: 'uint256', + }, + { + internalType: 'uint256', + name: 'amountAMin', + type: 'uint256', + }, + { + internalType: 'uint256', + name: 'amountBMin', + type: 'uint256', + }, + { + internalType: 'address', + name: 'to', + type: 'address', + }, + { + internalType: 'uint256', + name: 'deadline', + type: 'uint256', + }, + ], + name: 'removeLiquidity', + outputs: [ + { + internalType: 'uint256', + name: 'amountA', + type: 'uint256', + }, + { + internalType: 'uint256', + name: 'amountB', + type: 'uint256', + }, + ], + stateMutability: 'nonpayable', + type: 'function', + }, + { + inputs: [ + { + internalType: 'address', + name: 'token', + type: 'address', + }, + { + internalType: 'uint256', + name: 'liquidity', + type: 'uint256', + }, + { + internalType: 'uint256', + name: 'amountTokenMin', + type: 'uint256', + }, + { + internalType: 'uint256', + name: 'amountETHMin', + type: 'uint256', + }, + { + internalType: 'address', + name: 'to', + type: 'address', + }, + { + internalType: 'uint256', + name: 'deadline', + type: 'uint256', + }, + ], + name: 'removeLiquidityETH', + outputs: [ + { + internalType: 'uint256', + name: 'amountToken', + type: 'uint256', + }, + { + internalType: 'uint256', + name: 'amountETH', + type: 'uint256', + }, + ], + stateMutability: 'nonpayable', + type: 'function', + }, + { + inputs: [ + { + internalType: 'address', + name: 'token', + type: 'address', + }, + { + internalType: 'uint256', + name: 'liquidity', + type: 'uint256', + }, + { + internalType: 'uint256', + name: 'amountTokenMin', + type: 'uint256', + }, + { + internalType: 'uint256', + name: 'amountETHMin', + type: 'uint256', + }, + { + internalType: 'address', + name: 'to', + type: 'address', + }, + { + internalType: 'uint256', + name: 'deadline', + type: 'uint256', + }, + ], + name: 'removeLiquidityETHSupportingFeeOnTransferTokens', + outputs: [ + { + internalType: 'uint256', + name: 'amountETH', + type: 'uint256', + }, + ], + stateMutability: 'nonpayable', + type: 'function', + }, + { + inputs: [ + { + internalType: 'address', + name: 'token', + type: 'address', + }, + { + internalType: 'uint256', + name: 'liquidity', + type: 'uint256', + }, + { + internalType: 'uint256', + name: 'amountTokenMin', + type: 'uint256', + }, + { + internalType: 'uint256', + name: 'amountETHMin', + type: 'uint256', + }, + { + internalType: 'address', + name: 'to', + type: 'address', + }, + { + internalType: 'uint256', + name: 'deadline', + type: 'uint256', + }, + { + internalType: 'bool', + name: 'approveMax', + type: 'bool', + }, + { + internalType: 'uint8', + name: 'v', + type: 'uint8', + }, + { + internalType: 'bytes32', + name: 'r', + type: 'bytes32', + }, + { + internalType: 'bytes32', + name: 's', + type: 'bytes32', + }, + ], + name: 'removeLiquidityETHWithPermit', + outputs: [ + { + internalType: 'uint256', + name: 'amountToken', + type: 'uint256', + }, + { + internalType: 'uint256', + name: 'amountETH', + type: 'uint256', + }, + ], + stateMutability: 'nonpayable', + type: 'function', + }, + { + inputs: [ + { + internalType: 'address', + name: 'token', + type: 'address', + }, + { + internalType: 'uint256', + name: 'liquidity', + type: 'uint256', + }, + { + internalType: 'uint256', + name: 'amountTokenMin', + type: 'uint256', + }, + { + internalType: 'uint256', + name: 'amountETHMin', + type: 'uint256', + }, + { + internalType: 'address', + name: 'to', + type: 'address', + }, + { + internalType: 'uint256', + name: 'deadline', + type: 'uint256', + }, + { + internalType: 'bool', + name: 'approveMax', + type: 'bool', + }, + { + internalType: 'uint8', + name: 'v', + type: 'uint8', + }, + { + internalType: 'bytes32', + name: 'r', + type: 'bytes32', + }, + { + internalType: 'bytes32', + name: 's', + type: 'bytes32', + }, + ], + name: 'removeLiquidityETHWithPermitSupportingFeeOnTransferTokens', + outputs: [ + { + internalType: 'uint256', + name: 'amountETH', + type: 'uint256', + }, + ], + stateMutability: 'nonpayable', + type: 'function', + }, + { + inputs: [ + { + internalType: 'address', + name: 'tokenA', + type: 'address', + }, + { + internalType: 'address', + name: 'tokenB', + type: 'address', + }, + { + internalType: 'uint256', + name: 'liquidity', + type: 'uint256', + }, + { + internalType: 'uint256', + name: 'amountAMin', + type: 'uint256', + }, + { + internalType: 'uint256', + name: 'amountBMin', + type: 'uint256', + }, + { + internalType: 'address', + name: 'to', + type: 'address', + }, + { + internalType: 'uint256', + name: 'deadline', + type: 'uint256', + }, + { + internalType: 'bool', + name: 'approveMax', + type: 'bool', + }, + { + internalType: 'uint8', + name: 'v', + type: 'uint8', + }, + { + internalType: 'bytes32', + name: 'r', + type: 'bytes32', + }, + { + internalType: 'bytes32', + name: 's', + type: 'bytes32', + }, + ], + name: 'removeLiquidityWithPermit', + outputs: [ + { + internalType: 'uint256', + name: 'amountA', + type: 'uint256', + }, + { + internalType: 'uint256', + name: 'amountB', + type: 'uint256', + }, + ], + stateMutability: 'nonpayable', + type: 'function', + }, + { + inputs: [ + { + internalType: 'uint256', + name: 'amountOut', + type: 'uint256', + }, + { + internalType: 'address[]', + name: 'path', + type: 'address[]', + }, + { + internalType: 'address', + name: 'to', + type: 'address', + }, + { + internalType: 'uint256', + name: 'deadline', + type: 'uint256', + }, + ], + name: 'swapETHForExactTokens', + outputs: [ + { + internalType: 'uint256[]', + name: 'amounts', + type: 'uint256[]', + }, + ], + stateMutability: 'payable', + type: 'function', + }, + { + inputs: [ + { + internalType: 'uint256', + name: 'amountOutMin', + type: 'uint256', + }, + { + internalType: 'address[]', + name: 'path', + type: 'address[]', + }, + { + internalType: 'address', + name: 'to', + type: 'address', + }, + { + internalType: 'uint256', + name: 'deadline', + type: 'uint256', + }, + ], + name: 'swapExactETHForTokens', + outputs: [ + { + internalType: 'uint256[]', + name: 'amounts', + type: 'uint256[]', + }, + ], + stateMutability: 'payable', + type: 'function', + }, + { + inputs: [ + { + internalType: 'uint256', + name: 'amountOutMin', + type: 'uint256', + }, + { + internalType: 'address[]', + name: 'path', + type: 'address[]', + }, + { + internalType: 'address', + name: 'to', + type: 'address', + }, + { + internalType: 'uint256', + name: 'deadline', + type: 'uint256', + }, + ], + name: 'swapExactETHForTokensSupportingFeeOnTransferTokens', + outputs: [], + stateMutability: 'payable', + type: 'function', + }, + { + inputs: [ + { + internalType: 'uint256', + name: 'amountIn', + type: 'uint256', + }, + { + internalType: 'uint256', + name: 'amountOutMin', + type: 'uint256', + }, + { + internalType: 'address[]', + name: 'path', + type: 'address[]', + }, + { + internalType: 'address', + name: 'to', + type: 'address', + }, + { + internalType: 'uint256', + name: 'deadline', + type: 'uint256', + }, + ], + name: 'swapExactTokensForETH', + outputs: [ + { + internalType: 'uint256[]', + name: 'amounts', + type: 'uint256[]', + }, + ], + stateMutability: 'nonpayable', + type: 'function', + }, + { + inputs: [ + { + internalType: 'uint256', + name: 'amountIn', + type: 'uint256', + }, + { + internalType: 'uint256', + name: 'amountOutMin', + type: 'uint256', + }, + { + internalType: 'address[]', + name: 'path', + type: 'address[]', + }, + { + internalType: 'address', + name: 'to', + type: 'address', + }, + { + internalType: 'uint256', + name: 'deadline', + type: 'uint256', + }, + ], + name: 'swapExactTokensForETHSupportingFeeOnTransferTokens', + outputs: [], + stateMutability: 'nonpayable', + type: 'function', + }, + { + inputs: [ + { + internalType: 'uint256', + name: 'amountIn', + type: 'uint256', + }, + { + internalType: 'uint256', + name: 'amountOutMin', + type: 'uint256', + }, + { + internalType: 'address[]', + name: 'path', + type: 'address[]', + }, + { + internalType: 'address', + name: 'to', + type: 'address', + }, + { + internalType: 'uint256', + name: 'deadline', + type: 'uint256', + }, + ], + name: 'swapExactTokensForTokens', + outputs: [ + { + internalType: 'uint256[]', + name: 'amounts', + type: 'uint256[]', + }, + ], + stateMutability: 'nonpayable', + type: 'function', + }, + { + inputs: [ + { + internalType: 'uint256', + name: 'amountIn', + type: 'uint256', + }, + { + internalType: 'uint256', + name: 'amountOutMin', + type: 'uint256', + }, + { + internalType: 'address[]', + name: 'path', + type: 'address[]', + }, + { + internalType: 'address', + name: 'to', + type: 'address', + }, + { + internalType: 'uint256', + name: 'deadline', + type: 'uint256', + }, + ], + name: 'swapExactTokensForTokensSupportingFeeOnTransferTokens', + outputs: [], + stateMutability: 'nonpayable', + type: 'function', + }, + { + inputs: [ + { + internalType: 'uint256', + name: 'amountOut', + type: 'uint256', + }, + { + internalType: 'uint256', + name: 'amountInMax', + type: 'uint256', + }, + { + internalType: 'address[]', + name: 'path', + type: 'address[]', + }, + { + internalType: 'address', + name: 'to', + type: 'address', + }, + { + internalType: 'uint256', + name: 'deadline', + type: 'uint256', + }, + ], + name: 'swapTokensForExactETH', + outputs: [ + { + internalType: 'uint256[]', + name: 'amounts', + type: 'uint256[]', + }, + ], + stateMutability: 'nonpayable', + type: 'function', + }, + { + inputs: [ + { + internalType: 'uint256', + name: 'amountOut', + type: 'uint256', + }, + { + internalType: 'uint256', + name: 'amountInMax', + type: 'uint256', + }, + { + internalType: 'address[]', + name: 'path', + type: 'address[]', + }, + { + internalType: 'address', + name: 'to', + type: 'address', + }, + { + internalType: 'uint256', + name: 'deadline', + type: 'uint256', + }, + ], + name: 'swapTokensForExactTokens', + outputs: [ + { + internalType: 'uint256[]', + name: 'amounts', + type: 'uint256[]', + }, + ], + stateMutability: 'nonpayable', + type: 'function', + }, + { + stateMutability: 'payable', + type: 'receive', + }, +] export const etherscanResponse0xc36442b6 = [ - { - inputs: [ - { - internalType: 'address', - name: '_factory', - type: 'address', - }, - { - internalType: 'address', - name: '_WETH9', - type: 'address', - }, - { - internalType: 'address', - name: '_tokenDescriptor_', - type: 'address', - }, - ], - stateMutability: 'nonpayable', - type: 'constructor', - }, - { - anonymous: false, - inputs: [ - { - indexed: true, - internalType: 'address', - name: 'owner', - type: 'address', - }, - { - indexed: true, - internalType: 'address', - name: 'approved', - type: 'address', - }, - { - indexed: true, - internalType: 'uint256', - name: 'tokenId', - type: 'uint256', - }, - ], - name: 'Approval', - type: 'event', - }, - { - anonymous: false, - inputs: [ - { - indexed: true, - internalType: 'address', - name: 'owner', - type: 'address', - }, - { - indexed: true, - internalType: 'address', - name: 'operator', - type: 'address', - }, - { - indexed: false, - internalType: 'bool', - name: 'approved', - type: 'bool', - }, - ], - name: 'ApprovalForAll', - type: 'event', - }, - { - anonymous: false, - inputs: [ - { - indexed: true, - internalType: 'uint256', - name: 'tokenId', - type: 'uint256', - }, - { - indexed: false, - internalType: 'address', - name: 'recipient', - type: 'address', - }, - { - indexed: false, - internalType: 'uint256', - name: 'amount0', - type: 'uint256', - }, - { - indexed: false, - internalType: 'uint256', - name: 'amount1', - type: 'uint256', - }, - ], - name: 'Collect', - type: 'event', - }, - { - anonymous: false, - inputs: [ - { - indexed: true, - internalType: 'uint256', - name: 'tokenId', - type: 'uint256', - }, - { - indexed: false, - internalType: 'uint128', - name: 'liquidity', - type: 'uint128', - }, - { - indexed: false, - internalType: 'uint256', - name: 'amount0', - type: 'uint256', - }, - { - indexed: false, - internalType: 'uint256', - name: 'amount1', - type: 'uint256', - }, - ], - name: 'DecreaseLiquidity', - type: 'event', - }, - { - anonymous: false, - inputs: [ - { - indexed: true, - internalType: 'uint256', - name: 'tokenId', - type: 'uint256', - }, - { - indexed: false, - internalType: 'uint128', - name: 'liquidity', - type: 'uint128', - }, - { - indexed: false, - internalType: 'uint256', - name: 'amount0', - type: 'uint256', - }, - { - indexed: false, - internalType: 'uint256', - name: 'amount1', - type: 'uint256', - }, - ], - name: 'IncreaseLiquidity', - type: 'event', - }, - { - anonymous: false, - inputs: [ - { - indexed: true, - internalType: 'address', - name: 'from', - type: 'address', - }, - { - indexed: true, - internalType: 'address', - name: 'to', - type: 'address', - }, - { - indexed: true, - internalType: 'uint256', - name: 'tokenId', - type: 'uint256', - }, - ], - name: 'Transfer', - type: 'event', - }, - { - inputs: [], - name: 'DOMAIN_SEPARATOR', - outputs: [ - { - internalType: 'bytes32', - name: '', - type: 'bytes32', - }, - ], - stateMutability: 'view', - type: 'function', - }, - { - inputs: [], - name: 'PERMIT_TYPEHASH', - outputs: [ - { - internalType: 'bytes32', - name: '', - type: 'bytes32', - }, - ], - stateMutability: 'view', - type: 'function', - }, - { - inputs: [], - name: 'WETH9', - outputs: [ - { - internalType: 'address', - name: '', - type: 'address', - }, - ], - stateMutability: 'view', - type: 'function', - }, - { - inputs: [ - { - internalType: 'address', - name: 'to', - type: 'address', - }, - { - internalType: 'uint256', - name: 'tokenId', - type: 'uint256', - }, - ], - name: 'approve', - outputs: [], - stateMutability: 'nonpayable', - type: 'function', - }, - { - inputs: [ - { - internalType: 'address', - name: 'owner', - type: 'address', - }, - ], - name: 'balanceOf', - outputs: [ - { - internalType: 'uint256', - name: '', - type: 'uint256', - }, - ], - stateMutability: 'view', - type: 'function', - }, - { - inputs: [], - name: 'baseURI', - outputs: [ - { - internalType: 'string', - name: '', - type: 'string', - }, - ], - stateMutability: 'pure', - type: 'function', - }, - { - inputs: [ - { - internalType: 'uint256', - name: 'tokenId', - type: 'uint256', - }, - ], - name: 'burn', - outputs: [], - stateMutability: 'payable', - type: 'function', - }, - { - inputs: [ - { - components: [ - { - internalType: 'uint256', - name: 'tokenId', - type: 'uint256', - }, - { - internalType: 'address', - name: 'recipient', - type: 'address', - }, - { - internalType: 'uint128', - name: 'amount0Max', - type: 'uint128', - }, - { - internalType: 'uint128', - name: 'amount1Max', - type: 'uint128', - }, - ], - internalType: 'struct INonfungiblePositionManager.CollectParams', - name: 'params', - type: 'tuple', - }, - ], - name: 'collect', - outputs: [ - { - internalType: 'uint256', - name: 'amount0', - type: 'uint256', - }, - { - internalType: 'uint256', - name: 'amount1', - type: 'uint256', - }, - ], - stateMutability: 'payable', - type: 'function', - }, - { - inputs: [ - { - internalType: 'address', - name: 'token0', - type: 'address', - }, - { - internalType: 'address', - name: 'token1', - type: 'address', - }, - { - internalType: 'uint24', - name: 'fee', - type: 'uint24', - }, - { - internalType: 'uint160', - name: 'sqrtPriceX96', - type: 'uint160', - }, - ], - name: 'createAndInitializePoolIfNecessary', - outputs: [ - { - internalType: 'address', - name: 'pool', - type: 'address', - }, - ], - stateMutability: 'payable', - type: 'function', - }, - { - inputs: [ - { - components: [ - { - internalType: 'uint256', - name: 'tokenId', - type: 'uint256', - }, - { - internalType: 'uint128', - name: 'liquidity', - type: 'uint128', - }, - { - internalType: 'uint256', - name: 'amount0Min', - type: 'uint256', - }, - { - internalType: 'uint256', - name: 'amount1Min', - type: 'uint256', - }, - { - internalType: 'uint256', - name: 'deadline', - type: 'uint256', - }, - ], - internalType: - 'struct INonfungiblePositionManager.DecreaseLiquidityParams', - name: 'params', - type: 'tuple', - }, - ], - name: 'decreaseLiquidity', - outputs: [ - { - internalType: 'uint256', - name: 'amount0', - type: 'uint256', - }, - { - internalType: 'uint256', - name: 'amount1', - type: 'uint256', - }, - ], - stateMutability: 'payable', - type: 'function', - }, - { - inputs: [], - name: 'factory', - outputs: [ - { - internalType: 'address', - name: '', - type: 'address', - }, - ], - stateMutability: 'view', - type: 'function', - }, - { - inputs: [ - { - internalType: 'uint256', - name: 'tokenId', - type: 'uint256', - }, - ], - name: 'getApproved', - outputs: [ - { - internalType: 'address', - name: '', - type: 'address', - }, - ], - stateMutability: 'view', - type: 'function', - }, - { - inputs: [ - { - components: [ - { - internalType: 'uint256', - name: 'tokenId', - type: 'uint256', - }, - { - internalType: 'uint256', - name: 'amount0Desired', - type: 'uint256', - }, - { - internalType: 'uint256', - name: 'amount1Desired', - type: 'uint256', - }, - { - internalType: 'uint256', - name: 'amount0Min', - type: 'uint256', - }, - { - internalType: 'uint256', - name: 'amount1Min', - type: 'uint256', - }, - { - internalType: 'uint256', - name: 'deadline', - type: 'uint256', - }, - ], - internalType: - 'struct INonfungiblePositionManager.IncreaseLiquidityParams', - name: 'params', - type: 'tuple', - }, - ], - name: 'increaseLiquidity', - outputs: [ - { - internalType: 'uint128', - name: 'liquidity', - type: 'uint128', - }, - { - internalType: 'uint256', - name: 'amount0', - type: 'uint256', - }, - { - internalType: 'uint256', - name: 'amount1', - type: 'uint256', - }, - ], - stateMutability: 'payable', - type: 'function', - }, - { - inputs: [ - { - internalType: 'address', - name: 'owner', - type: 'address', - }, - { - internalType: 'address', - name: 'operator', - type: 'address', - }, - ], - name: 'isApprovedForAll', - outputs: [ - { - internalType: 'bool', - name: '', - type: 'bool', - }, - ], - stateMutability: 'view', - type: 'function', - }, - { - inputs: [ - { - components: [ - { - internalType: 'address', - name: 'token0', - type: 'address', - }, - { - internalType: 'address', - name: 'token1', - type: 'address', - }, - { - internalType: 'uint24', - name: 'fee', - type: 'uint24', - }, - { - internalType: 'int24', - name: 'tickLower', - type: 'int24', - }, - { - internalType: 'int24', - name: 'tickUpper', - type: 'int24', - }, - { - internalType: 'uint256', - name: 'amount0Desired', - type: 'uint256', - }, - { - internalType: 'uint256', - name: 'amount1Desired', - type: 'uint256', - }, - { - internalType: 'uint256', - name: 'amount0Min', - type: 'uint256', - }, - { - internalType: 'uint256', - name: 'amount1Min', - type: 'uint256', - }, - { - internalType: 'address', - name: 'recipient', - type: 'address', - }, - { - internalType: 'uint256', - name: 'deadline', - type: 'uint256', - }, - ], - internalType: 'struct INonfungiblePositionManager.MintParams', - name: 'params', - type: 'tuple', - }, - ], - name: 'mint', - outputs: [ - { - internalType: 'uint256', - name: 'tokenId', - type: 'uint256', - }, - { - internalType: 'uint128', - name: 'liquidity', - type: 'uint128', - }, - { - internalType: 'uint256', - name: 'amount0', - type: 'uint256', - }, - { - internalType: 'uint256', - name: 'amount1', - type: 'uint256', - }, - ], - stateMutability: 'payable', - type: 'function', - }, - { - inputs: [ - { - internalType: 'bytes[]', - name: 'data', - type: 'bytes[]', - }, - ], - name: 'multicall', - outputs: [ - { - internalType: 'bytes[]', - name: 'results', - type: 'bytes[]', - }, - ], - stateMutability: 'payable', - type: 'function', - }, - { - inputs: [], - name: 'name', - outputs: [ - { - internalType: 'string', - name: '', - type: 'string', - }, - ], - stateMutability: 'view', - type: 'function', - }, - { - inputs: [ - { - internalType: 'uint256', - name: 'tokenId', - type: 'uint256', - }, - ], - name: 'ownerOf', - outputs: [ - { - internalType: 'address', - name: '', - type: 'address', - }, - ], - stateMutability: 'view', - type: 'function', - }, - { - inputs: [ - { - internalType: 'address', - name: 'spender', - type: 'address', - }, - { - internalType: 'uint256', - name: 'tokenId', - type: 'uint256', - }, - { - internalType: 'uint256', - name: 'deadline', - type: 'uint256', - }, - { - internalType: 'uint8', - name: 'v', - type: 'uint8', - }, - { - internalType: 'bytes32', - name: 'r', - type: 'bytes32', - }, - { - internalType: 'bytes32', - name: 's', - type: 'bytes32', - }, - ], - name: 'permit', - outputs: [], - stateMutability: 'payable', - type: 'function', - }, - { - inputs: [ - { - internalType: 'uint256', - name: 'tokenId', - type: 'uint256', - }, - ], - name: 'positions', - outputs: [ - { - internalType: 'uint96', - name: 'nonce', - type: 'uint96', - }, - { - internalType: 'address', - name: 'operator', - type: 'address', - }, - { - internalType: 'address', - name: 'token0', - type: 'address', - }, - { - internalType: 'address', - name: 'token1', - type: 'address', - }, - { - internalType: 'uint24', - name: 'fee', - type: 'uint24', - }, - { - internalType: 'int24', - name: 'tickLower', - type: 'int24', - }, - { - internalType: 'int24', - name: 'tickUpper', - type: 'int24', - }, - { - internalType: 'uint128', - name: 'liquidity', - type: 'uint128', - }, - { - internalType: 'uint256', - name: 'feeGrowthInside0LastX128', - type: 'uint256', - }, - { - internalType: 'uint256', - name: 'feeGrowthInside1LastX128', - type: 'uint256', - }, - { - internalType: 'uint128', - name: 'tokensOwed0', - type: 'uint128', - }, - { - internalType: 'uint128', - name: 'tokensOwed1', - type: 'uint128', - }, - ], - stateMutability: 'view', - type: 'function', - }, - { - inputs: [], - name: 'refundETH', - outputs: [], - stateMutability: 'payable', - type: 'function', - }, - { - inputs: [ - { - internalType: 'address', - name: 'from', - type: 'address', - }, - { - internalType: 'address', - name: 'to', - type: 'address', - }, - { - internalType: 'uint256', - name: 'tokenId', - type: 'uint256', - }, - ], - name: 'safeTransferFrom', - outputs: [], - stateMutability: 'nonpayable', - type: 'function', - }, - { - inputs: [ - { - internalType: 'address', - name: 'from', - type: 'address', - }, - { - internalType: 'address', - name: 'to', - type: 'address', - }, - { - internalType: 'uint256', - name: 'tokenId', - type: 'uint256', - }, - { - internalType: 'bytes', - name: '_data', - type: 'bytes', - }, - ], - name: 'safeTransferFrom', - outputs: [], - stateMutability: 'nonpayable', - type: 'function', - }, - { - inputs: [ - { - internalType: 'address', - name: 'token', - type: 'address', - }, - { - internalType: 'uint256', - name: 'value', - type: 'uint256', - }, - { - internalType: 'uint256', - name: 'deadline', - type: 'uint256', - }, - { - internalType: 'uint8', - name: 'v', - type: 'uint8', - }, - { - internalType: 'bytes32', - name: 'r', - type: 'bytes32', - }, - { - internalType: 'bytes32', - name: 's', - type: 'bytes32', - }, - ], - name: 'selfPermit', - outputs: [], - stateMutability: 'payable', - type: 'function', - }, - { - inputs: [ - { - internalType: 'address', - name: 'token', - type: 'address', - }, - { - internalType: 'uint256', - name: 'nonce', - type: 'uint256', - }, - { - internalType: 'uint256', - name: 'expiry', - type: 'uint256', - }, - { - internalType: 'uint8', - name: 'v', - type: 'uint8', - }, - { - internalType: 'bytes32', - name: 'r', - type: 'bytes32', - }, - { - internalType: 'bytes32', - name: 's', - type: 'bytes32', - }, - ], - name: 'selfPermitAllowed', - outputs: [], - stateMutability: 'payable', - type: 'function', - }, - { - inputs: [ - { - internalType: 'address', - name: 'token', - type: 'address', - }, - { - internalType: 'uint256', - name: 'nonce', - type: 'uint256', - }, - { - internalType: 'uint256', - name: 'expiry', - type: 'uint256', - }, - { - internalType: 'uint8', - name: 'v', - type: 'uint8', - }, - { - internalType: 'bytes32', - name: 'r', - type: 'bytes32', - }, - { - internalType: 'bytes32', - name: 's', - type: 'bytes32', - }, - ], - name: 'selfPermitAllowedIfNecessary', - outputs: [], - stateMutability: 'payable', - type: 'function', - }, - { - inputs: [ - { - internalType: 'address', - name: 'token', - type: 'address', - }, - { - internalType: 'uint256', - name: 'value', - type: 'uint256', - }, - { - internalType: 'uint256', - name: 'deadline', - type: 'uint256', - }, - { - internalType: 'uint8', - name: 'v', - type: 'uint8', - }, - { - internalType: 'bytes32', - name: 'r', - type: 'bytes32', - }, - { - internalType: 'bytes32', - name: 's', - type: 'bytes32', - }, - ], - name: 'selfPermitIfNecessary', - outputs: [], - stateMutability: 'payable', - type: 'function', - }, - { - inputs: [ - { - internalType: 'address', - name: 'operator', - type: 'address', - }, - { - internalType: 'bool', - name: 'approved', - type: 'bool', - }, - ], - name: 'setApprovalForAll', - outputs: [], - stateMutability: 'nonpayable', - type: 'function', - }, - { - inputs: [ - { - internalType: 'bytes4', - name: 'interfaceId', - type: 'bytes4', - }, - ], - name: 'supportsInterface', - outputs: [ - { - internalType: 'bool', - name: '', - type: 'bool', - }, - ], - stateMutability: 'view', - type: 'function', - }, - { - inputs: [ - { - internalType: 'address', - name: 'token', - type: 'address', - }, - { - internalType: 'uint256', - name: 'amountMinimum', - type: 'uint256', - }, - { - internalType: 'address', - name: 'recipient', - type: 'address', - }, - ], - name: 'sweepToken', - outputs: [], - stateMutability: 'payable', - type: 'function', - }, - { - inputs: [], - name: 'symbol', - outputs: [ - { - internalType: 'string', - name: '', - type: 'string', - }, - ], - stateMutability: 'view', - type: 'function', - }, - { - inputs: [ - { - internalType: 'uint256', - name: 'index', - type: 'uint256', - }, - ], - name: 'tokenByIndex', - outputs: [ - { - internalType: 'uint256', - name: '', - type: 'uint256', - }, - ], - stateMutability: 'view', - type: 'function', - }, - { - inputs: [ - { - internalType: 'address', - name: 'owner', - type: 'address', - }, - { - internalType: 'uint256', - name: 'index', - type: 'uint256', - }, - ], - name: 'tokenOfOwnerByIndex', - outputs: [ - { - internalType: 'uint256', - name: '', - type: 'uint256', - }, - ], - stateMutability: 'view', - type: 'function', - }, - { - inputs: [ - { - internalType: 'uint256', - name: 'tokenId', - type: 'uint256', - }, - ], - name: 'tokenURI', - outputs: [ - { - internalType: 'string', - name: '', - type: 'string', - }, - ], - stateMutability: 'view', - type: 'function', - }, - { - inputs: [], - name: 'totalSupply', - outputs: [ - { - internalType: 'uint256', - name: '', - type: 'uint256', - }, - ], - stateMutability: 'view', - type: 'function', - }, - { - inputs: [ - { - internalType: 'address', - name: 'from', - type: 'address', - }, - { - internalType: 'address', - name: 'to', - type: 'address', - }, - { - internalType: 'uint256', - name: 'tokenId', - type: 'uint256', - }, - ], - name: 'transferFrom', - outputs: [], - stateMutability: 'nonpayable', - type: 'function', - }, - { - inputs: [ - { - internalType: 'uint256', - name: 'amount0Owed', - type: 'uint256', - }, - { - internalType: 'uint256', - name: 'amount1Owed', - type: 'uint256', - }, - { - internalType: 'bytes', - name: 'data', - type: 'bytes', - }, - ], - name: 'uniswapV3MintCallback', - outputs: [], - stateMutability: 'nonpayable', - type: 'function', - }, - { - inputs: [ - { - internalType: 'uint256', - name: 'amountMinimum', - type: 'uint256', - }, - { - internalType: 'address', - name: 'recipient', - type: 'address', - }, - ], - name: 'unwrapWETH9', - outputs: [], - stateMutability: 'payable', - type: 'function', - }, - { - stateMutability: 'payable', - type: 'receive', - }, -]; + { + inputs: [ + { + internalType: 'address', + name: '_factory', + type: 'address', + }, + { + internalType: 'address', + name: '_WETH9', + type: 'address', + }, + { + internalType: 'address', + name: '_tokenDescriptor_', + type: 'address', + }, + ], + stateMutability: 'nonpayable', + type: 'constructor', + }, + { + anonymous: false, + inputs: [ + { + indexed: true, + internalType: 'address', + name: 'owner', + type: 'address', + }, + { + indexed: true, + internalType: 'address', + name: 'approved', + type: 'address', + }, + { + indexed: true, + internalType: 'uint256', + name: 'tokenId', + type: 'uint256', + }, + ], + name: 'Approval', + type: 'event', + }, + { + anonymous: false, + inputs: [ + { + indexed: true, + internalType: 'address', + name: 'owner', + type: 'address', + }, + { + indexed: true, + internalType: 'address', + name: 'operator', + type: 'address', + }, + { + indexed: false, + internalType: 'bool', + name: 'approved', + type: 'bool', + }, + ], + name: 'ApprovalForAll', + type: 'event', + }, + { + anonymous: false, + inputs: [ + { + indexed: true, + internalType: 'uint256', + name: 'tokenId', + type: 'uint256', + }, + { + indexed: false, + internalType: 'address', + name: 'recipient', + type: 'address', + }, + { + indexed: false, + internalType: 'uint256', + name: 'amount0', + type: 'uint256', + }, + { + indexed: false, + internalType: 'uint256', + name: 'amount1', + type: 'uint256', + }, + ], + name: 'Collect', + type: 'event', + }, + { + anonymous: false, + inputs: [ + { + indexed: true, + internalType: 'uint256', + name: 'tokenId', + type: 'uint256', + }, + { + indexed: false, + internalType: 'uint128', + name: 'liquidity', + type: 'uint128', + }, + { + indexed: false, + internalType: 'uint256', + name: 'amount0', + type: 'uint256', + }, + { + indexed: false, + internalType: 'uint256', + name: 'amount1', + type: 'uint256', + }, + ], + name: 'DecreaseLiquidity', + type: 'event', + }, + { + anonymous: false, + inputs: [ + { + indexed: true, + internalType: 'uint256', + name: 'tokenId', + type: 'uint256', + }, + { + indexed: false, + internalType: 'uint128', + name: 'liquidity', + type: 'uint128', + }, + { + indexed: false, + internalType: 'uint256', + name: 'amount0', + type: 'uint256', + }, + { + indexed: false, + internalType: 'uint256', + name: 'amount1', + type: 'uint256', + }, + ], + name: 'IncreaseLiquidity', + type: 'event', + }, + { + anonymous: false, + inputs: [ + { + indexed: true, + internalType: 'address', + name: 'from', + type: 'address', + }, + { + indexed: true, + internalType: 'address', + name: 'to', + type: 'address', + }, + { + indexed: true, + internalType: 'uint256', + name: 'tokenId', + type: 'uint256', + }, + ], + name: 'Transfer', + type: 'event', + }, + { + inputs: [], + name: 'DOMAIN_SEPARATOR', + outputs: [ + { + internalType: 'bytes32', + name: '', + type: 'bytes32', + }, + ], + stateMutability: 'view', + type: 'function', + }, + { + inputs: [], + name: 'PERMIT_TYPEHASH', + outputs: [ + { + internalType: 'bytes32', + name: '', + type: 'bytes32', + }, + ], + stateMutability: 'view', + type: 'function', + }, + { + inputs: [], + name: 'WETH9', + outputs: [ + { + internalType: 'address', + name: '', + type: 'address', + }, + ], + stateMutability: 'view', + type: 'function', + }, + { + inputs: [ + { + internalType: 'address', + name: 'to', + type: 'address', + }, + { + internalType: 'uint256', + name: 'tokenId', + type: 'uint256', + }, + ], + name: 'approve', + outputs: [], + stateMutability: 'nonpayable', + type: 'function', + }, + { + inputs: [ + { + internalType: 'address', + name: 'owner', + type: 'address', + }, + ], + name: 'balanceOf', + outputs: [ + { + internalType: 'uint256', + name: '', + type: 'uint256', + }, + ], + stateMutability: 'view', + type: 'function', + }, + { + inputs: [], + name: 'baseURI', + outputs: [ + { + internalType: 'string', + name: '', + type: 'string', + }, + ], + stateMutability: 'pure', + type: 'function', + }, + { + inputs: [ + { + internalType: 'uint256', + name: 'tokenId', + type: 'uint256', + }, + ], + name: 'burn', + outputs: [], + stateMutability: 'payable', + type: 'function', + }, + { + inputs: [ + { + components: [ + { + internalType: 'uint256', + name: 'tokenId', + type: 'uint256', + }, + { + internalType: 'address', + name: 'recipient', + type: 'address', + }, + { + internalType: 'uint128', + name: 'amount0Max', + type: 'uint128', + }, + { + internalType: 'uint128', + name: 'amount1Max', + type: 'uint128', + }, + ], + internalType: 'struct INonfungiblePositionManager.CollectParams', + name: 'params', + type: 'tuple', + }, + ], + name: 'collect', + outputs: [ + { + internalType: 'uint256', + name: 'amount0', + type: 'uint256', + }, + { + internalType: 'uint256', + name: 'amount1', + type: 'uint256', + }, + ], + stateMutability: 'payable', + type: 'function', + }, + { + inputs: [ + { + internalType: 'address', + name: 'token0', + type: 'address', + }, + { + internalType: 'address', + name: 'token1', + type: 'address', + }, + { + internalType: 'uint24', + name: 'fee', + type: 'uint24', + }, + { + internalType: 'uint160', + name: 'sqrtPriceX96', + type: 'uint160', + }, + ], + name: 'createAndInitializePoolIfNecessary', + outputs: [ + { + internalType: 'address', + name: 'pool', + type: 'address', + }, + ], + stateMutability: 'payable', + type: 'function', + }, + { + inputs: [ + { + components: [ + { + internalType: 'uint256', + name: 'tokenId', + type: 'uint256', + }, + { + internalType: 'uint128', + name: 'liquidity', + type: 'uint128', + }, + { + internalType: 'uint256', + name: 'amount0Min', + type: 'uint256', + }, + { + internalType: 'uint256', + name: 'amount1Min', + type: 'uint256', + }, + { + internalType: 'uint256', + name: 'deadline', + type: 'uint256', + }, + ], + internalType: 'struct INonfungiblePositionManager.DecreaseLiquidityParams', + name: 'params', + type: 'tuple', + }, + ], + name: 'decreaseLiquidity', + outputs: [ + { + internalType: 'uint256', + name: 'amount0', + type: 'uint256', + }, + { + internalType: 'uint256', + name: 'amount1', + type: 'uint256', + }, + ], + stateMutability: 'payable', + type: 'function', + }, + { + inputs: [], + name: 'factory', + outputs: [ + { + internalType: 'address', + name: '', + type: 'address', + }, + ], + stateMutability: 'view', + type: 'function', + }, + { + inputs: [ + { + internalType: 'uint256', + name: 'tokenId', + type: 'uint256', + }, + ], + name: 'getApproved', + outputs: [ + { + internalType: 'address', + name: '', + type: 'address', + }, + ], + stateMutability: 'view', + type: 'function', + }, + { + inputs: [ + { + components: [ + { + internalType: 'uint256', + name: 'tokenId', + type: 'uint256', + }, + { + internalType: 'uint256', + name: 'amount0Desired', + type: 'uint256', + }, + { + internalType: 'uint256', + name: 'amount1Desired', + type: 'uint256', + }, + { + internalType: 'uint256', + name: 'amount0Min', + type: 'uint256', + }, + { + internalType: 'uint256', + name: 'amount1Min', + type: 'uint256', + }, + { + internalType: 'uint256', + name: 'deadline', + type: 'uint256', + }, + ], + internalType: 'struct INonfungiblePositionManager.IncreaseLiquidityParams', + name: 'params', + type: 'tuple', + }, + ], + name: 'increaseLiquidity', + outputs: [ + { + internalType: 'uint128', + name: 'liquidity', + type: 'uint128', + }, + { + internalType: 'uint256', + name: 'amount0', + type: 'uint256', + }, + { + internalType: 'uint256', + name: 'amount1', + type: 'uint256', + }, + ], + stateMutability: 'payable', + type: 'function', + }, + { + inputs: [ + { + internalType: 'address', + name: 'owner', + type: 'address', + }, + { + internalType: 'address', + name: 'operator', + type: 'address', + }, + ], + name: 'isApprovedForAll', + outputs: [ + { + internalType: 'bool', + name: '', + type: 'bool', + }, + ], + stateMutability: 'view', + type: 'function', + }, + { + inputs: [ + { + components: [ + { + internalType: 'address', + name: 'token0', + type: 'address', + }, + { + internalType: 'address', + name: 'token1', + type: 'address', + }, + { + internalType: 'uint24', + name: 'fee', + type: 'uint24', + }, + { + internalType: 'int24', + name: 'tickLower', + type: 'int24', + }, + { + internalType: 'int24', + name: 'tickUpper', + type: 'int24', + }, + { + internalType: 'uint256', + name: 'amount0Desired', + type: 'uint256', + }, + { + internalType: 'uint256', + name: 'amount1Desired', + type: 'uint256', + }, + { + internalType: 'uint256', + name: 'amount0Min', + type: 'uint256', + }, + { + internalType: 'uint256', + name: 'amount1Min', + type: 'uint256', + }, + { + internalType: 'address', + name: 'recipient', + type: 'address', + }, + { + internalType: 'uint256', + name: 'deadline', + type: 'uint256', + }, + ], + internalType: 'struct INonfungiblePositionManager.MintParams', + name: 'params', + type: 'tuple', + }, + ], + name: 'mint', + outputs: [ + { + internalType: 'uint256', + name: 'tokenId', + type: 'uint256', + }, + { + internalType: 'uint128', + name: 'liquidity', + type: 'uint128', + }, + { + internalType: 'uint256', + name: 'amount0', + type: 'uint256', + }, + { + internalType: 'uint256', + name: 'amount1', + type: 'uint256', + }, + ], + stateMutability: 'payable', + type: 'function', + }, + { + inputs: [ + { + internalType: 'bytes[]', + name: 'data', + type: 'bytes[]', + }, + ], + name: 'multicall', + outputs: [ + { + internalType: 'bytes[]', + name: 'results', + type: 'bytes[]', + }, + ], + stateMutability: 'payable', + type: 'function', + }, + { + inputs: [], + name: 'name', + outputs: [ + { + internalType: 'string', + name: '', + type: 'string', + }, + ], + stateMutability: 'view', + type: 'function', + }, + { + inputs: [ + { + internalType: 'uint256', + name: 'tokenId', + type: 'uint256', + }, + ], + name: 'ownerOf', + outputs: [ + { + internalType: 'address', + name: '', + type: 'address', + }, + ], + stateMutability: 'view', + type: 'function', + }, + { + inputs: [ + { + internalType: 'address', + name: 'spender', + type: 'address', + }, + { + internalType: 'uint256', + name: 'tokenId', + type: 'uint256', + }, + { + internalType: 'uint256', + name: 'deadline', + type: 'uint256', + }, + { + internalType: 'uint8', + name: 'v', + type: 'uint8', + }, + { + internalType: 'bytes32', + name: 'r', + type: 'bytes32', + }, + { + internalType: 'bytes32', + name: 's', + type: 'bytes32', + }, + ], + name: 'permit', + outputs: [], + stateMutability: 'payable', + type: 'function', + }, + { + inputs: [ + { + internalType: 'uint256', + name: 'tokenId', + type: 'uint256', + }, + ], + name: 'positions', + outputs: [ + { + internalType: 'uint96', + name: 'nonce', + type: 'uint96', + }, + { + internalType: 'address', + name: 'operator', + type: 'address', + }, + { + internalType: 'address', + name: 'token0', + type: 'address', + }, + { + internalType: 'address', + name: 'token1', + type: 'address', + }, + { + internalType: 'uint24', + name: 'fee', + type: 'uint24', + }, + { + internalType: 'int24', + name: 'tickLower', + type: 'int24', + }, + { + internalType: 'int24', + name: 'tickUpper', + type: 'int24', + }, + { + internalType: 'uint128', + name: 'liquidity', + type: 'uint128', + }, + { + internalType: 'uint256', + name: 'feeGrowthInside0LastX128', + type: 'uint256', + }, + { + internalType: 'uint256', + name: 'feeGrowthInside1LastX128', + type: 'uint256', + }, + { + internalType: 'uint128', + name: 'tokensOwed0', + type: 'uint128', + }, + { + internalType: 'uint128', + name: 'tokensOwed1', + type: 'uint128', + }, + ], + stateMutability: 'view', + type: 'function', + }, + { + inputs: [], + name: 'refundETH', + outputs: [], + stateMutability: 'payable', + type: 'function', + }, + { + inputs: [ + { + internalType: 'address', + name: 'from', + type: 'address', + }, + { + internalType: 'address', + name: 'to', + type: 'address', + }, + { + internalType: 'uint256', + name: 'tokenId', + type: 'uint256', + }, + ], + name: 'safeTransferFrom', + outputs: [], + stateMutability: 'nonpayable', + type: 'function', + }, + { + inputs: [ + { + internalType: 'address', + name: 'from', + type: 'address', + }, + { + internalType: 'address', + name: 'to', + type: 'address', + }, + { + internalType: 'uint256', + name: 'tokenId', + type: 'uint256', + }, + { + internalType: 'bytes', + name: '_data', + type: 'bytes', + }, + ], + name: 'safeTransferFrom', + outputs: [], + stateMutability: 'nonpayable', + type: 'function', + }, + { + inputs: [ + { + internalType: 'address', + name: 'token', + type: 'address', + }, + { + internalType: 'uint256', + name: 'value', + type: 'uint256', + }, + { + internalType: 'uint256', + name: 'deadline', + type: 'uint256', + }, + { + internalType: 'uint8', + name: 'v', + type: 'uint8', + }, + { + internalType: 'bytes32', + name: 'r', + type: 'bytes32', + }, + { + internalType: 'bytes32', + name: 's', + type: 'bytes32', + }, + ], + name: 'selfPermit', + outputs: [], + stateMutability: 'payable', + type: 'function', + }, + { + inputs: [ + { + internalType: 'address', + name: 'token', + type: 'address', + }, + { + internalType: 'uint256', + name: 'nonce', + type: 'uint256', + }, + { + internalType: 'uint256', + name: 'expiry', + type: 'uint256', + }, + { + internalType: 'uint8', + name: 'v', + type: 'uint8', + }, + { + internalType: 'bytes32', + name: 'r', + type: 'bytes32', + }, + { + internalType: 'bytes32', + name: 's', + type: 'bytes32', + }, + ], + name: 'selfPermitAllowed', + outputs: [], + stateMutability: 'payable', + type: 'function', + }, + { + inputs: [ + { + internalType: 'address', + name: 'token', + type: 'address', + }, + { + internalType: 'uint256', + name: 'nonce', + type: 'uint256', + }, + { + internalType: 'uint256', + name: 'expiry', + type: 'uint256', + }, + { + internalType: 'uint8', + name: 'v', + type: 'uint8', + }, + { + internalType: 'bytes32', + name: 'r', + type: 'bytes32', + }, + { + internalType: 'bytes32', + name: 's', + type: 'bytes32', + }, + ], + name: 'selfPermitAllowedIfNecessary', + outputs: [], + stateMutability: 'payable', + type: 'function', + }, + { + inputs: [ + { + internalType: 'address', + name: 'token', + type: 'address', + }, + { + internalType: 'uint256', + name: 'value', + type: 'uint256', + }, + { + internalType: 'uint256', + name: 'deadline', + type: 'uint256', + }, + { + internalType: 'uint8', + name: 'v', + type: 'uint8', + }, + { + internalType: 'bytes32', + name: 'r', + type: 'bytes32', + }, + { + internalType: 'bytes32', + name: 's', + type: 'bytes32', + }, + ], + name: 'selfPermitIfNecessary', + outputs: [], + stateMutability: 'payable', + type: 'function', + }, + { + inputs: [ + { + internalType: 'address', + name: 'operator', + type: 'address', + }, + { + internalType: 'bool', + name: 'approved', + type: 'bool', + }, + ], + name: 'setApprovalForAll', + outputs: [], + stateMutability: 'nonpayable', + type: 'function', + }, + { + inputs: [ + { + internalType: 'bytes4', + name: 'interfaceId', + type: 'bytes4', + }, + ], + name: 'supportsInterface', + outputs: [ + { + internalType: 'bool', + name: '', + type: 'bool', + }, + ], + stateMutability: 'view', + type: 'function', + }, + { + inputs: [ + { + internalType: 'address', + name: 'token', + type: 'address', + }, + { + internalType: 'uint256', + name: 'amountMinimum', + type: 'uint256', + }, + { + internalType: 'address', + name: 'recipient', + type: 'address', + }, + ], + name: 'sweepToken', + outputs: [], + stateMutability: 'payable', + type: 'function', + }, + { + inputs: [], + name: 'symbol', + outputs: [ + { + internalType: 'string', + name: '', + type: 'string', + }, + ], + stateMutability: 'view', + type: 'function', + }, + { + inputs: [ + { + internalType: 'uint256', + name: 'index', + type: 'uint256', + }, + ], + name: 'tokenByIndex', + outputs: [ + { + internalType: 'uint256', + name: '', + type: 'uint256', + }, + ], + stateMutability: 'view', + type: 'function', + }, + { + inputs: [ + { + internalType: 'address', + name: 'owner', + type: 'address', + }, + { + internalType: 'uint256', + name: 'index', + type: 'uint256', + }, + ], + name: 'tokenOfOwnerByIndex', + outputs: [ + { + internalType: 'uint256', + name: '', + type: 'uint256', + }, + ], + stateMutability: 'view', + type: 'function', + }, + { + inputs: [ + { + internalType: 'uint256', + name: 'tokenId', + type: 'uint256', + }, + ], + name: 'tokenURI', + outputs: [ + { + internalType: 'string', + name: '', + type: 'string', + }, + ], + stateMutability: 'view', + type: 'function', + }, + { + inputs: [], + name: 'totalSupply', + outputs: [ + { + internalType: 'uint256', + name: '', + type: 'uint256', + }, + ], + stateMutability: 'view', + type: 'function', + }, + { + inputs: [ + { + internalType: 'address', + name: 'from', + type: 'address', + }, + { + internalType: 'address', + name: 'to', + type: 'address', + }, + { + internalType: 'uint256', + name: 'tokenId', + type: 'uint256', + }, + ], + name: 'transferFrom', + outputs: [], + stateMutability: 'nonpayable', + type: 'function', + }, + { + inputs: [ + { + internalType: 'uint256', + name: 'amount0Owed', + type: 'uint256', + }, + { + internalType: 'uint256', + name: 'amount1Owed', + type: 'uint256', + }, + { + internalType: 'bytes', + name: 'data', + type: 'bytes', + }, + ], + name: 'uniswapV3MintCallback', + outputs: [], + stateMutability: 'nonpayable', + type: 'function', + }, + { + inputs: [ + { + internalType: 'uint256', + name: 'amountMinimum', + type: 'uint256', + }, + { + internalType: 'address', + name: 'recipient', + type: 'address', + }, + ], + name: 'unwrapWETH9', + outputs: [], + stateMutability: 'payable', + type: 'function', + }, + { + stateMutability: 'payable', + type: 'receive', + }, +] export const etherscanResponse0x06412d7e = [ - { - inputs: [ - { internalType: 'address', name: '_factory', type: 'address' }, - { internalType: 'address', name: '_WETH9', type: 'address' }, - { internalType: 'address', name: '_tokenDescriptor_', type: 'address' }, - ], - stateMutability: 'nonpayable', - type: 'constructor', - }, - { - anonymous: false, - inputs: [ - { - indexed: true, - internalType: 'address', - name: 'owner', - type: 'address', - }, - { - indexed: true, - internalType: 'address', - name: 'approved', - type: 'address', - }, - { - indexed: true, - internalType: 'uint256', - name: 'tokenId', - type: 'uint256', - }, - ], - name: 'Approval', - type: 'event', - }, - { - anonymous: false, - inputs: [ - { - indexed: true, - internalType: 'address', - name: 'owner', - type: 'address', - }, - { - indexed: true, - internalType: 'address', - name: 'operator', - type: 'address', - }, - { indexed: false, internalType: 'bool', name: 'approved', type: 'bool' }, - ], - name: 'ApprovalForAll', - type: 'event', - }, - { - anonymous: false, - inputs: [ - { - indexed: true, - internalType: 'uint256', - name: 'tokenId', - type: 'uint256', - }, - { - indexed: false, - internalType: 'address', - name: 'recipient', - type: 'address', - }, - { - indexed: false, - internalType: 'uint256', - name: 'amount0', - type: 'uint256', - }, - { - indexed: false, - internalType: 'uint256', - name: 'amount1', - type: 'uint256', - }, - ], - name: 'Collect', - type: 'event', - }, - { - anonymous: false, - inputs: [ - { - indexed: true, - internalType: 'uint256', - name: 'tokenId', - type: 'uint256', - }, - { - indexed: false, - internalType: 'uint128', - name: 'liquidity', - type: 'uint128', - }, - { - indexed: false, - internalType: 'uint256', - name: 'amount0', - type: 'uint256', - }, - { - indexed: false, - internalType: 'uint256', - name: 'amount1', - type: 'uint256', - }, - ], - name: 'DecreaseLiquidity', - type: 'event', - }, - { - anonymous: false, - inputs: [ - { - indexed: true, - internalType: 'uint256', - name: 'tokenId', - type: 'uint256', - }, - { - indexed: false, - internalType: 'uint128', - name: 'liquidity', - type: 'uint128', - }, - { - indexed: false, - internalType: 'uint256', - name: 'amount0', - type: 'uint256', - }, - { - indexed: false, - internalType: 'uint256', - name: 'amount1', - type: 'uint256', - }, - ], - name: 'IncreaseLiquidity', - type: 'event', - }, - { - anonymous: false, - inputs: [ - { indexed: true, internalType: 'address', name: 'from', type: 'address' }, - { indexed: true, internalType: 'address', name: 'to', type: 'address' }, - { - indexed: true, - internalType: 'uint256', - name: 'tokenId', - type: 'uint256', - }, - ], - name: 'Transfer', - type: 'event', - }, - { - inputs: [], - name: 'DOMAIN_SEPARATOR', - outputs: [{ internalType: 'bytes32', name: '', type: 'bytes32' }], - stateMutability: 'view', - type: 'function', - }, - { - inputs: [], - name: 'PERMIT_TYPEHASH', - outputs: [{ internalType: 'bytes32', name: '', type: 'bytes32' }], - stateMutability: 'view', - type: 'function', - }, - { - inputs: [], - name: 'WETH9', - outputs: [{ internalType: 'address', name: '', type: 'address' }], - stateMutability: 'view', - type: 'function', - }, - { - inputs: [ - { internalType: 'address', name: 'to', type: 'address' }, - { internalType: 'uint256', name: 'tokenId', type: 'uint256' }, - ], - name: 'approve', - outputs: [], - stateMutability: 'nonpayable', - type: 'function', - }, - { - inputs: [{ internalType: 'address', name: 'owner', type: 'address' }], - name: 'balanceOf', - outputs: [{ internalType: 'uint256', name: '', type: 'uint256' }], - stateMutability: 'view', - type: 'function', - }, - { - inputs: [], - name: 'baseURI', - outputs: [{ internalType: 'string', name: '', type: 'string' }], - stateMutability: 'pure', - type: 'function', - }, - { - inputs: [{ internalType: 'uint256', name: 'tokenId', type: 'uint256' }], - name: 'burn', - outputs: [], - stateMutability: 'payable', - type: 'function', - }, - { - inputs: [ - { - components: [ - { internalType: 'uint256', name: 'tokenId', type: 'uint256' }, - { internalType: 'address', name: 'recipient', type: 'address' }, - { internalType: 'uint128', name: 'amount0Max', type: 'uint128' }, - { internalType: 'uint128', name: 'amount1Max', type: 'uint128' }, - ], - internalType: 'struct INonfungiblePositionManager.CollectParams', - name: 'params', - type: 'tuple', - }, - ], - name: 'collect', - outputs: [ - { internalType: 'uint256', name: 'amount0', type: 'uint256' }, - { internalType: 'uint256', name: 'amount1', type: 'uint256' }, - ], - stateMutability: 'payable', - type: 'function', - }, - { - inputs: [ - { internalType: 'address', name: 'token0', type: 'address' }, - { internalType: 'address', name: 'token1', type: 'address' }, - { internalType: 'uint24', name: 'fee', type: 'uint24' }, - { internalType: 'uint160', name: 'sqrtPriceX96', type: 'uint160' }, - ], - name: 'createAndInitializePoolIfNecessary', - outputs: [{ internalType: 'address', name: 'pool', type: 'address' }], - stateMutability: 'payable', - type: 'function', - }, - { - inputs: [ - { - components: [ - { internalType: 'uint256', name: 'tokenId', type: 'uint256' }, - { internalType: 'uint128', name: 'liquidity', type: 'uint128' }, - { internalType: 'uint256', name: 'amount0Min', type: 'uint256' }, - { internalType: 'uint256', name: 'amount1Min', type: 'uint256' }, - { internalType: 'uint256', name: 'deadline', type: 'uint256' }, - ], - internalType: - 'struct INonfungiblePositionManager.DecreaseLiquidityParams', - name: 'params', - type: 'tuple', - }, - ], - name: 'decreaseLiquidity', - outputs: [ - { internalType: 'uint256', name: 'amount0', type: 'uint256' }, - { internalType: 'uint256', name: 'amount1', type: 'uint256' }, - ], - stateMutability: 'payable', - type: 'function', - }, - { - inputs: [], - name: 'factory', - outputs: [{ internalType: 'address', name: '', type: 'address' }], - stateMutability: 'view', - type: 'function', - }, - { - inputs: [{ internalType: 'uint256', name: 'tokenId', type: 'uint256' }], - name: 'getApproved', - outputs: [{ internalType: 'address', name: '', type: 'address' }], - stateMutability: 'view', - type: 'function', - }, - { - inputs: [ - { - components: [ - { internalType: 'uint256', name: 'tokenId', type: 'uint256' }, - { internalType: 'uint256', name: 'amount0Desired', type: 'uint256' }, - { internalType: 'uint256', name: 'amount1Desired', type: 'uint256' }, - { internalType: 'uint256', name: 'amount0Min', type: 'uint256' }, - { internalType: 'uint256', name: 'amount1Min', type: 'uint256' }, - { internalType: 'uint256', name: 'deadline', type: 'uint256' }, - ], - internalType: - 'struct INonfungiblePositionManager.IncreaseLiquidityParams', - name: 'params', - type: 'tuple', - }, - ], - name: 'increaseLiquidity', - outputs: [ - { internalType: 'uint128', name: 'liquidity', type: 'uint128' }, - { internalType: 'uint256', name: 'amount0', type: 'uint256' }, - { internalType: 'uint256', name: 'amount1', type: 'uint256' }, - ], - stateMutability: 'payable', - type: 'function', - }, - { - inputs: [ - { internalType: 'address', name: 'owner', type: 'address' }, - { internalType: 'address', name: 'operator', type: 'address' }, - ], - name: 'isApprovedForAll', - outputs: [{ internalType: 'bool', name: '', type: 'bool' }], - stateMutability: 'view', - type: 'function', - }, - { - inputs: [ - { - components: [ - { internalType: 'address', name: 'token0', type: 'address' }, - { internalType: 'address', name: 'token1', type: 'address' }, - { internalType: 'uint24', name: 'fee', type: 'uint24' }, - { internalType: 'int24', name: 'tickLower', type: 'int24' }, - { internalType: 'int24', name: 'tickUpper', type: 'int24' }, - { internalType: 'uint256', name: 'amount0Desired', type: 'uint256' }, - { internalType: 'uint256', name: 'amount1Desired', type: 'uint256' }, - { internalType: 'uint256', name: 'amount0Min', type: 'uint256' }, - { internalType: 'uint256', name: 'amount1Min', type: 'uint256' }, - { internalType: 'address', name: 'recipient', type: 'address' }, - { internalType: 'uint256', name: 'deadline', type: 'uint256' }, - ], - internalType: 'struct INonfungiblePositionManager.MintParams', - name: 'params', - type: 'tuple', - }, - ], - name: 'mint', - outputs: [ - { internalType: 'uint256', name: 'tokenId', type: 'uint256' }, - { internalType: 'uint128', name: 'liquidity', type: 'uint128' }, - { internalType: 'uint256', name: 'amount0', type: 'uint256' }, - { internalType: 'uint256', name: 'amount1', type: 'uint256' }, - ], - stateMutability: 'payable', - type: 'function', - }, - { - inputs: [{ internalType: 'bytes[]', name: 'data', type: 'bytes[]' }], - name: 'multicall', - outputs: [{ internalType: 'bytes[]', name: 'results', type: 'bytes[]' }], - stateMutability: 'payable', - type: 'function', - }, - { - inputs: [], - name: 'name', - outputs: [{ internalType: 'string', name: '', type: 'string' }], - stateMutability: 'view', - type: 'function', - }, - { - inputs: [{ internalType: 'uint256', name: 'tokenId', type: 'uint256' }], - name: 'ownerOf', - outputs: [{ internalType: 'address', name: '', type: 'address' }], - stateMutability: 'view', - type: 'function', - }, - { - inputs: [ - { internalType: 'address', name: 'spender', type: 'address' }, - { internalType: 'uint256', name: 'tokenId', type: 'uint256' }, - { internalType: 'uint256', name: 'deadline', type: 'uint256' }, - { internalType: 'uint8', name: 'v', type: 'uint8' }, - { internalType: 'bytes32', name: 'r', type: 'bytes32' }, - { internalType: 'bytes32', name: 's', type: 'bytes32' }, - ], - name: 'permit', - outputs: [], - stateMutability: 'payable', - type: 'function', - }, - { - inputs: [{ internalType: 'uint256', name: 'tokenId', type: 'uint256' }], - name: 'positions', - outputs: [ - { internalType: 'uint96', name: 'nonce', type: 'uint96' }, - { internalType: 'address', name: 'operator', type: 'address' }, - { internalType: 'address', name: 'token0', type: 'address' }, - { internalType: 'address', name: 'token1', type: 'address' }, - { internalType: 'uint24', name: 'fee', type: 'uint24' }, - { internalType: 'int24', name: 'tickLower', type: 'int24' }, - { internalType: 'int24', name: 'tickUpper', type: 'int24' }, - { internalType: 'uint128', name: 'liquidity', type: 'uint128' }, - { - internalType: 'uint256', - name: 'feeGrowthInside0LastX128', - type: 'uint256', - }, - { - internalType: 'uint256', - name: 'feeGrowthInside1LastX128', - type: 'uint256', - }, - { internalType: 'uint128', name: 'tokensOwed0', type: 'uint128' }, - { internalType: 'uint128', name: 'tokensOwed1', type: 'uint128' }, - ], - stateMutability: 'view', - type: 'function', - }, - { - inputs: [], - name: 'refundETH', - outputs: [], - stateMutability: 'payable', - type: 'function', - }, - { - inputs: [ - { internalType: 'address', name: 'from', type: 'address' }, - { internalType: 'address', name: 'to', type: 'address' }, - { internalType: 'uint256', name: 'tokenId', type: 'uint256' }, - ], - name: 'safeTransferFrom', - outputs: [], - stateMutability: 'nonpayable', - type: 'function', - }, - { - inputs: [ - { internalType: 'address', name: 'from', type: 'address' }, - { internalType: 'address', name: 'to', type: 'address' }, - { internalType: 'uint256', name: 'tokenId', type: 'uint256' }, - { internalType: 'bytes', name: '_data', type: 'bytes' }, - ], - name: 'safeTransferFrom', - outputs: [], - stateMutability: 'nonpayable', - type: 'function', - }, - { - inputs: [ - { internalType: 'address', name: 'token', type: 'address' }, - { internalType: 'uint256', name: 'value', type: 'uint256' }, - { internalType: 'uint256', name: 'deadline', type: 'uint256' }, - { internalType: 'uint8', name: 'v', type: 'uint8' }, - { internalType: 'bytes32', name: 'r', type: 'bytes32' }, - { internalType: 'bytes32', name: 's', type: 'bytes32' }, - ], - name: 'selfPermit', - outputs: [], - stateMutability: 'payable', - type: 'function', - }, - { - inputs: [ - { internalType: 'address', name: 'token', type: 'address' }, - { internalType: 'uint256', name: 'nonce', type: 'uint256' }, - { internalType: 'uint256', name: 'expiry', type: 'uint256' }, - { internalType: 'uint8', name: 'v', type: 'uint8' }, - { internalType: 'bytes32', name: 'r', type: 'bytes32' }, - { internalType: 'bytes32', name: 's', type: 'bytes32' }, - ], - name: 'selfPermitAllowed', - outputs: [], - stateMutability: 'payable', - type: 'function', - }, - { - inputs: [ - { internalType: 'address', name: 'token', type: 'address' }, - { internalType: 'uint256', name: 'nonce', type: 'uint256' }, - { internalType: 'uint256', name: 'expiry', type: 'uint256' }, - { internalType: 'uint8', name: 'v', type: 'uint8' }, - { internalType: 'bytes32', name: 'r', type: 'bytes32' }, - { internalType: 'bytes32', name: 's', type: 'bytes32' }, - ], - name: 'selfPermitAllowedIfNecessary', - outputs: [], - stateMutability: 'payable', - type: 'function', - }, - { - inputs: [ - { internalType: 'address', name: 'token', type: 'address' }, - { internalType: 'uint256', name: 'value', type: 'uint256' }, - { internalType: 'uint256', name: 'deadline', type: 'uint256' }, - { internalType: 'uint8', name: 'v', type: 'uint8' }, - { internalType: 'bytes32', name: 'r', type: 'bytes32' }, - { internalType: 'bytes32', name: 's', type: 'bytes32' }, - ], - name: 'selfPermitIfNecessary', - outputs: [], - stateMutability: 'payable', - type: 'function', - }, - { - inputs: [ - { internalType: 'address', name: 'operator', type: 'address' }, - { internalType: 'bool', name: 'approved', type: 'bool' }, - ], - name: 'setApprovalForAll', - outputs: [], - stateMutability: 'nonpayable', - type: 'function', - }, - { - inputs: [{ internalType: 'bytes4', name: 'interfaceId', type: 'bytes4' }], - name: 'supportsInterface', - outputs: [{ internalType: 'bool', name: '', type: 'bool' }], - stateMutability: 'view', - type: 'function', - }, - { - inputs: [ - { internalType: 'address', name: 'token', type: 'address' }, - { internalType: 'uint256', name: 'amountMinimum', type: 'uint256' }, - { internalType: 'address', name: 'recipient', type: 'address' }, - ], - name: 'sweepToken', - outputs: [], - stateMutability: 'payable', - type: 'function', - }, - { - inputs: [], - name: 'symbol', - outputs: [{ internalType: 'string', name: '', type: 'string' }], - stateMutability: 'view', - type: 'function', - }, - { - inputs: [{ internalType: 'uint256', name: 'index', type: 'uint256' }], - name: 'tokenByIndex', - outputs: [{ internalType: 'uint256', name: '', type: 'uint256' }], - stateMutability: 'view', - type: 'function', - }, - { - inputs: [ - { internalType: 'address', name: 'owner', type: 'address' }, - { internalType: 'uint256', name: 'index', type: 'uint256' }, - ], - name: 'tokenOfOwnerByIndex', - outputs: [{ internalType: 'uint256', name: '', type: 'uint256' }], - stateMutability: 'view', - type: 'function', - }, - { - inputs: [{ internalType: 'uint256', name: 'tokenId', type: 'uint256' }], - name: 'tokenURI', - outputs: [{ internalType: 'string', name: '', type: 'string' }], - stateMutability: 'view', - type: 'function', - }, - { - inputs: [], - name: 'totalSupply', - outputs: [{ internalType: 'uint256', name: '', type: 'uint256' }], - stateMutability: 'view', - type: 'function', - }, - { - inputs: [ - { internalType: 'address', name: 'from', type: 'address' }, - { internalType: 'address', name: 'to', type: 'address' }, - { internalType: 'uint256', name: 'tokenId', type: 'uint256' }, - ], - name: 'transferFrom', - outputs: [], - stateMutability: 'nonpayable', - type: 'function', - }, - { - inputs: [ - { internalType: 'uint256', name: 'amount0Owed', type: 'uint256' }, - { internalType: 'uint256', name: 'amount1Owed', type: 'uint256' }, - { internalType: 'bytes', name: 'data', type: 'bytes' }, - ], - name: 'uniswapV3MintCallback', - outputs: [], - stateMutability: 'nonpayable', - type: 'function', - }, - { - inputs: [ - { internalType: 'uint256', name: 'amountMinimum', type: 'uint256' }, - { internalType: 'address', name: 'recipient', type: 'address' }, - ], - name: 'unwrapWETH9', - outputs: [], - stateMutability: 'payable', - type: 'function', - }, - { stateMutability: 'payable', type: 'receive' }, -]; + { + inputs: [ + { internalType: 'address', name: '_factory', type: 'address' }, + { internalType: 'address', name: '_WETH9', type: 'address' }, + { internalType: 'address', name: '_tokenDescriptor_', type: 'address' }, + ], + stateMutability: 'nonpayable', + type: 'constructor', + }, + { + anonymous: false, + inputs: [ + { + indexed: true, + internalType: 'address', + name: 'owner', + type: 'address', + }, + { + indexed: true, + internalType: 'address', + name: 'approved', + type: 'address', + }, + { + indexed: true, + internalType: 'uint256', + name: 'tokenId', + type: 'uint256', + }, + ], + name: 'Approval', + type: 'event', + }, + { + anonymous: false, + inputs: [ + { + indexed: true, + internalType: 'address', + name: 'owner', + type: 'address', + }, + { + indexed: true, + internalType: 'address', + name: 'operator', + type: 'address', + }, + { indexed: false, internalType: 'bool', name: 'approved', type: 'bool' }, + ], + name: 'ApprovalForAll', + type: 'event', + }, + { + anonymous: false, + inputs: [ + { + indexed: true, + internalType: 'uint256', + name: 'tokenId', + type: 'uint256', + }, + { + indexed: false, + internalType: 'address', + name: 'recipient', + type: 'address', + }, + { + indexed: false, + internalType: 'uint256', + name: 'amount0', + type: 'uint256', + }, + { + indexed: false, + internalType: 'uint256', + name: 'amount1', + type: 'uint256', + }, + ], + name: 'Collect', + type: 'event', + }, + { + anonymous: false, + inputs: [ + { + indexed: true, + internalType: 'uint256', + name: 'tokenId', + type: 'uint256', + }, + { + indexed: false, + internalType: 'uint128', + name: 'liquidity', + type: 'uint128', + }, + { + indexed: false, + internalType: 'uint256', + name: 'amount0', + type: 'uint256', + }, + { + indexed: false, + internalType: 'uint256', + name: 'amount1', + type: 'uint256', + }, + ], + name: 'DecreaseLiquidity', + type: 'event', + }, + { + anonymous: false, + inputs: [ + { + indexed: true, + internalType: 'uint256', + name: 'tokenId', + type: 'uint256', + }, + { + indexed: false, + internalType: 'uint128', + name: 'liquidity', + type: 'uint128', + }, + { + indexed: false, + internalType: 'uint256', + name: 'amount0', + type: 'uint256', + }, + { + indexed: false, + internalType: 'uint256', + name: 'amount1', + type: 'uint256', + }, + ], + name: 'IncreaseLiquidity', + type: 'event', + }, + { + anonymous: false, + inputs: [ + { indexed: true, internalType: 'address', name: 'from', type: 'address' }, + { indexed: true, internalType: 'address', name: 'to', type: 'address' }, + { + indexed: true, + internalType: 'uint256', + name: 'tokenId', + type: 'uint256', + }, + ], + name: 'Transfer', + type: 'event', + }, + { + inputs: [], + name: 'DOMAIN_SEPARATOR', + outputs: [{ internalType: 'bytes32', name: '', type: 'bytes32' }], + stateMutability: 'view', + type: 'function', + }, + { + inputs: [], + name: 'PERMIT_TYPEHASH', + outputs: [{ internalType: 'bytes32', name: '', type: 'bytes32' }], + stateMutability: 'view', + type: 'function', + }, + { + inputs: [], + name: 'WETH9', + outputs: [{ internalType: 'address', name: '', type: 'address' }], + stateMutability: 'view', + type: 'function', + }, + { + inputs: [ + { internalType: 'address', name: 'to', type: 'address' }, + { internalType: 'uint256', name: 'tokenId', type: 'uint256' }, + ], + name: 'approve', + outputs: [], + stateMutability: 'nonpayable', + type: 'function', + }, + { + inputs: [{ internalType: 'address', name: 'owner', type: 'address' }], + name: 'balanceOf', + outputs: [{ internalType: 'uint256', name: '', type: 'uint256' }], + stateMutability: 'view', + type: 'function', + }, + { + inputs: [], + name: 'baseURI', + outputs: [{ internalType: 'string', name: '', type: 'string' }], + stateMutability: 'pure', + type: 'function', + }, + { + inputs: [{ internalType: 'uint256', name: 'tokenId', type: 'uint256' }], + name: 'burn', + outputs: [], + stateMutability: 'payable', + type: 'function', + }, + { + inputs: [ + { + components: [ + { internalType: 'uint256', name: 'tokenId', type: 'uint256' }, + { internalType: 'address', name: 'recipient', type: 'address' }, + { internalType: 'uint128', name: 'amount0Max', type: 'uint128' }, + { internalType: 'uint128', name: 'amount1Max', type: 'uint128' }, + ], + internalType: 'struct INonfungiblePositionManager.CollectParams', + name: 'params', + type: 'tuple', + }, + ], + name: 'collect', + outputs: [ + { internalType: 'uint256', name: 'amount0', type: 'uint256' }, + { internalType: 'uint256', name: 'amount1', type: 'uint256' }, + ], + stateMutability: 'payable', + type: 'function', + }, + { + inputs: [ + { internalType: 'address', name: 'token0', type: 'address' }, + { internalType: 'address', name: 'token1', type: 'address' }, + { internalType: 'uint24', name: 'fee', type: 'uint24' }, + { internalType: 'uint160', name: 'sqrtPriceX96', type: 'uint160' }, + ], + name: 'createAndInitializePoolIfNecessary', + outputs: [{ internalType: 'address', name: 'pool', type: 'address' }], + stateMutability: 'payable', + type: 'function', + }, + { + inputs: [ + { + components: [ + { internalType: 'uint256', name: 'tokenId', type: 'uint256' }, + { internalType: 'uint128', name: 'liquidity', type: 'uint128' }, + { internalType: 'uint256', name: 'amount0Min', type: 'uint256' }, + { internalType: 'uint256', name: 'amount1Min', type: 'uint256' }, + { internalType: 'uint256', name: 'deadline', type: 'uint256' }, + ], + internalType: 'struct INonfungiblePositionManager.DecreaseLiquidityParams', + name: 'params', + type: 'tuple', + }, + ], + name: 'decreaseLiquidity', + outputs: [ + { internalType: 'uint256', name: 'amount0', type: 'uint256' }, + { internalType: 'uint256', name: 'amount1', type: 'uint256' }, + ], + stateMutability: 'payable', + type: 'function', + }, + { + inputs: [], + name: 'factory', + outputs: [{ internalType: 'address', name: '', type: 'address' }], + stateMutability: 'view', + type: 'function', + }, + { + inputs: [{ internalType: 'uint256', name: 'tokenId', type: 'uint256' }], + name: 'getApproved', + outputs: [{ internalType: 'address', name: '', type: 'address' }], + stateMutability: 'view', + type: 'function', + }, + { + inputs: [ + { + components: [ + { internalType: 'uint256', name: 'tokenId', type: 'uint256' }, + { internalType: 'uint256', name: 'amount0Desired', type: 'uint256' }, + { internalType: 'uint256', name: 'amount1Desired', type: 'uint256' }, + { internalType: 'uint256', name: 'amount0Min', type: 'uint256' }, + { internalType: 'uint256', name: 'amount1Min', type: 'uint256' }, + { internalType: 'uint256', name: 'deadline', type: 'uint256' }, + ], + internalType: 'struct INonfungiblePositionManager.IncreaseLiquidityParams', + name: 'params', + type: 'tuple', + }, + ], + name: 'increaseLiquidity', + outputs: [ + { internalType: 'uint128', name: 'liquidity', type: 'uint128' }, + { internalType: 'uint256', name: 'amount0', type: 'uint256' }, + { internalType: 'uint256', name: 'amount1', type: 'uint256' }, + ], + stateMutability: 'payable', + type: 'function', + }, + { + inputs: [ + { internalType: 'address', name: 'owner', type: 'address' }, + { internalType: 'address', name: 'operator', type: 'address' }, + ], + name: 'isApprovedForAll', + outputs: [{ internalType: 'bool', name: '', type: 'bool' }], + stateMutability: 'view', + type: 'function', + }, + { + inputs: [ + { + components: [ + { internalType: 'address', name: 'token0', type: 'address' }, + { internalType: 'address', name: 'token1', type: 'address' }, + { internalType: 'uint24', name: 'fee', type: 'uint24' }, + { internalType: 'int24', name: 'tickLower', type: 'int24' }, + { internalType: 'int24', name: 'tickUpper', type: 'int24' }, + { internalType: 'uint256', name: 'amount0Desired', type: 'uint256' }, + { internalType: 'uint256', name: 'amount1Desired', type: 'uint256' }, + { internalType: 'uint256', name: 'amount0Min', type: 'uint256' }, + { internalType: 'uint256', name: 'amount1Min', type: 'uint256' }, + { internalType: 'address', name: 'recipient', type: 'address' }, + { internalType: 'uint256', name: 'deadline', type: 'uint256' }, + ], + internalType: 'struct INonfungiblePositionManager.MintParams', + name: 'params', + type: 'tuple', + }, + ], + name: 'mint', + outputs: [ + { internalType: 'uint256', name: 'tokenId', type: 'uint256' }, + { internalType: 'uint128', name: 'liquidity', type: 'uint128' }, + { internalType: 'uint256', name: 'amount0', type: 'uint256' }, + { internalType: 'uint256', name: 'amount1', type: 'uint256' }, + ], + stateMutability: 'payable', + type: 'function', + }, + { + inputs: [{ internalType: 'bytes[]', name: 'data', type: 'bytes[]' }], + name: 'multicall', + outputs: [{ internalType: 'bytes[]', name: 'results', type: 'bytes[]' }], + stateMutability: 'payable', + type: 'function', + }, + { + inputs: [], + name: 'name', + outputs: [{ internalType: 'string', name: '', type: 'string' }], + stateMutability: 'view', + type: 'function', + }, + { + inputs: [{ internalType: 'uint256', name: 'tokenId', type: 'uint256' }], + name: 'ownerOf', + outputs: [{ internalType: 'address', name: '', type: 'address' }], + stateMutability: 'view', + type: 'function', + }, + { + inputs: [ + { internalType: 'address', name: 'spender', type: 'address' }, + { internalType: 'uint256', name: 'tokenId', type: 'uint256' }, + { internalType: 'uint256', name: 'deadline', type: 'uint256' }, + { internalType: 'uint8', name: 'v', type: 'uint8' }, + { internalType: 'bytes32', name: 'r', type: 'bytes32' }, + { internalType: 'bytes32', name: 's', type: 'bytes32' }, + ], + name: 'permit', + outputs: [], + stateMutability: 'payable', + type: 'function', + }, + { + inputs: [{ internalType: 'uint256', name: 'tokenId', type: 'uint256' }], + name: 'positions', + outputs: [ + { internalType: 'uint96', name: 'nonce', type: 'uint96' }, + { internalType: 'address', name: 'operator', type: 'address' }, + { internalType: 'address', name: 'token0', type: 'address' }, + { internalType: 'address', name: 'token1', type: 'address' }, + { internalType: 'uint24', name: 'fee', type: 'uint24' }, + { internalType: 'int24', name: 'tickLower', type: 'int24' }, + { internalType: 'int24', name: 'tickUpper', type: 'int24' }, + { internalType: 'uint128', name: 'liquidity', type: 'uint128' }, + { + internalType: 'uint256', + name: 'feeGrowthInside0LastX128', + type: 'uint256', + }, + { + internalType: 'uint256', + name: 'feeGrowthInside1LastX128', + type: 'uint256', + }, + { internalType: 'uint128', name: 'tokensOwed0', type: 'uint128' }, + { internalType: 'uint128', name: 'tokensOwed1', type: 'uint128' }, + ], + stateMutability: 'view', + type: 'function', + }, + { + inputs: [], + name: 'refundETH', + outputs: [], + stateMutability: 'payable', + type: 'function', + }, + { + inputs: [ + { internalType: 'address', name: 'from', type: 'address' }, + { internalType: 'address', name: 'to', type: 'address' }, + { internalType: 'uint256', name: 'tokenId', type: 'uint256' }, + ], + name: 'safeTransferFrom', + outputs: [], + stateMutability: 'nonpayable', + type: 'function', + }, + { + inputs: [ + { internalType: 'address', name: 'from', type: 'address' }, + { internalType: 'address', name: 'to', type: 'address' }, + { internalType: 'uint256', name: 'tokenId', type: 'uint256' }, + { internalType: 'bytes', name: '_data', type: 'bytes' }, + ], + name: 'safeTransferFrom', + outputs: [], + stateMutability: 'nonpayable', + type: 'function', + }, + { + inputs: [ + { internalType: 'address', name: 'token', type: 'address' }, + { internalType: 'uint256', name: 'value', type: 'uint256' }, + { internalType: 'uint256', name: 'deadline', type: 'uint256' }, + { internalType: 'uint8', name: 'v', type: 'uint8' }, + { internalType: 'bytes32', name: 'r', type: 'bytes32' }, + { internalType: 'bytes32', name: 's', type: 'bytes32' }, + ], + name: 'selfPermit', + outputs: [], + stateMutability: 'payable', + type: 'function', + }, + { + inputs: [ + { internalType: 'address', name: 'token', type: 'address' }, + { internalType: 'uint256', name: 'nonce', type: 'uint256' }, + { internalType: 'uint256', name: 'expiry', type: 'uint256' }, + { internalType: 'uint8', name: 'v', type: 'uint8' }, + { internalType: 'bytes32', name: 'r', type: 'bytes32' }, + { internalType: 'bytes32', name: 's', type: 'bytes32' }, + ], + name: 'selfPermitAllowed', + outputs: [], + stateMutability: 'payable', + type: 'function', + }, + { + inputs: [ + { internalType: 'address', name: 'token', type: 'address' }, + { internalType: 'uint256', name: 'nonce', type: 'uint256' }, + { internalType: 'uint256', name: 'expiry', type: 'uint256' }, + { internalType: 'uint8', name: 'v', type: 'uint8' }, + { internalType: 'bytes32', name: 'r', type: 'bytes32' }, + { internalType: 'bytes32', name: 's', type: 'bytes32' }, + ], + name: 'selfPermitAllowedIfNecessary', + outputs: [], + stateMutability: 'payable', + type: 'function', + }, + { + inputs: [ + { internalType: 'address', name: 'token', type: 'address' }, + { internalType: 'uint256', name: 'value', type: 'uint256' }, + { internalType: 'uint256', name: 'deadline', type: 'uint256' }, + { internalType: 'uint8', name: 'v', type: 'uint8' }, + { internalType: 'bytes32', name: 'r', type: 'bytes32' }, + { internalType: 'bytes32', name: 's', type: 'bytes32' }, + ], + name: 'selfPermitIfNecessary', + outputs: [], + stateMutability: 'payable', + type: 'function', + }, + { + inputs: [ + { internalType: 'address', name: 'operator', type: 'address' }, + { internalType: 'bool', name: 'approved', type: 'bool' }, + ], + name: 'setApprovalForAll', + outputs: [], + stateMutability: 'nonpayable', + type: 'function', + }, + { + inputs: [{ internalType: 'bytes4', name: 'interfaceId', type: 'bytes4' }], + name: 'supportsInterface', + outputs: [{ internalType: 'bool', name: '', type: 'bool' }], + stateMutability: 'view', + type: 'function', + }, + { + inputs: [ + { internalType: 'address', name: 'token', type: 'address' }, + { internalType: 'uint256', name: 'amountMinimum', type: 'uint256' }, + { internalType: 'address', name: 'recipient', type: 'address' }, + ], + name: 'sweepToken', + outputs: [], + stateMutability: 'payable', + type: 'function', + }, + { + inputs: [], + name: 'symbol', + outputs: [{ internalType: 'string', name: '', type: 'string' }], + stateMutability: 'view', + type: 'function', + }, + { + inputs: [{ internalType: 'uint256', name: 'index', type: 'uint256' }], + name: 'tokenByIndex', + outputs: [{ internalType: 'uint256', name: '', type: 'uint256' }], + stateMutability: 'view', + type: 'function', + }, + { + inputs: [ + { internalType: 'address', name: 'owner', type: 'address' }, + { internalType: 'uint256', name: 'index', type: 'uint256' }, + ], + name: 'tokenOfOwnerByIndex', + outputs: [{ internalType: 'uint256', name: '', type: 'uint256' }], + stateMutability: 'view', + type: 'function', + }, + { + inputs: [{ internalType: 'uint256', name: 'tokenId', type: 'uint256' }], + name: 'tokenURI', + outputs: [{ internalType: 'string', name: '', type: 'string' }], + stateMutability: 'view', + type: 'function', + }, + { + inputs: [], + name: 'totalSupply', + outputs: [{ internalType: 'uint256', name: '', type: 'uint256' }], + stateMutability: 'view', + type: 'function', + }, + { + inputs: [ + { internalType: 'address', name: 'from', type: 'address' }, + { internalType: 'address', name: 'to', type: 'address' }, + { internalType: 'uint256', name: 'tokenId', type: 'uint256' }, + ], + name: 'transferFrom', + outputs: [], + stateMutability: 'nonpayable', + type: 'function', + }, + { + inputs: [ + { internalType: 'uint256', name: 'amount0Owed', type: 'uint256' }, + { internalType: 'uint256', name: 'amount1Owed', type: 'uint256' }, + { internalType: 'bytes', name: 'data', type: 'bytes' }, + ], + name: 'uniswapV3MintCallback', + outputs: [], + stateMutability: 'nonpayable', + type: 'function', + }, + { + inputs: [ + { internalType: 'uint256', name: 'amountMinimum', type: 'uint256' }, + { internalType: 'address', name: 'recipient', type: 'address' }, + ], + name: 'unwrapWETH9', + outputs: [], + stateMutability: 'payable', + type: 'function', + }, + { stateMutability: 'payable', type: 'receive' }, +] diff --git a/packages/sdk/src/__test__/integration/__mocks__/fetchActiveWallet.json b/packages/sdk/src/__test__/integration/__mocks__/fetchActiveWallet.json index e62c22b0..fb3d60c8 100644 --- a/packages/sdk/src/__test__/integration/__mocks__/fetchActiveWallet.json +++ b/packages/sdk/src/__test__/integration/__mocks__/fetchActiveWallet.json @@ -1,4 +1,4 @@ { - "status": 200, - "message": "01003bcd1aa00d81002787d6c0b1220a4b90cfa9e1a60a52898a7e93f11071d2c0aa0326334a22a757f1a60a5db6133eb0fa4e2abe2a76b2bcecc63fd04fff0faf216e09acda8fea4499773f0ab7eef76d1d622acc89bca480f214bd5f4f1f1fe15178e9cb4eddcb390b1cb58a8a4fef696366f93e898ef2513479a045534b4a8eb12f5dcc3e7fa32158b4e6c4d6117f0cf6985034bb16e4bc98c1f8618751c547b76b896c23255ef0a5c94a83a7b1cd2b6d80d9249e238cc72481d0e121e3d9eee8289ce1039a51e7549f057eb071cd987da5d930b584e9b72adb3c10a46ab7770340fb419ee7f4902203a4de8f8fb5d9e47fcf607f606ab696062eb606b55bb59c61351c34dd141184546603026825d410165f34c9fa5ddc767d19d87bbdb62ebb62d69fe28fc4382b56d86b9e6465144a39ef63dcefc249a7a774f586c949c6fa80662a6d5160ebc941a8c2175273133563ed62ede65a3f86db8dc88d8b265874b05e8cddf761889a3b0f88851d5ecb44bde41b80ab6b16da50ba2dcbab2547059bc739b3d72a65b2b73e4b1d213fe2845c3ddfe3a4b89f12b960efe3e529814e8af72d7870bb7463d94d5de6a248e07b9ab35abedc9d5ff9a56fa694f8a7c8b488b5d99d72f73695e03e9af7e168e25c8e623c5f6c2083522eaeb5c22dbdb96b86b4151db81749606b04fb5d90a36b2152c11cb969cb517eb82be5ef64080f21bcc9e0d8ab605284426e1ceed0c5bdd723a0684bff52e0aaeadd808ae34d5c9925a5d001aedf08450b3735c9163b6cc06c27cf799b9547fdc7394c823f646fc963b0157084da1f80229861c7505ea8f5b2632b466fb65dad5c14324c5bfa393eb680acc88826ee44d460f0e8a68f0327efdf4f6d6a9f3b84adc880d68b26bdbd810e4e4108c207b5ae16398a923ee99fce38ae919741d61b32b14eb7d2d93afc62c7b158838640b0c05166dfc336447db72a14486a2052654b91414e1c9770ef114b406c2da1362ef0dfad67d4596928a80203e37d55eb34140bfc08e8de3b08e6710a5d5dcaea4ba7218e9486d5b47ad21322cde46de690f28e1a9e65ee0f87ecf9f5677fca297aed8dc80d31b98052d23f3e98694aa1bdf604a07ce7e4c6f3003b8b05cd7f9d12834d46697128a938475ffdb959a5eb0fa8496063c583afc95f60adc837878c0253f48d0308a0b00f43deab2859e0f80b3371d565a26289d8fd4497959610871853a246eca4c4479c191fdd639591b5f6d30cd1bc43a361c1fe0552a5d0da0ed3bad45a315cd5bd22cec8e3d5e72cba511d648db4ccfa879693edba0c3be2dad2575e35572b8778358a7741266af08be09aadfa4fb4e1b319498177141456464f1205c9eba6ba70c1b54b62c9731e13e6b237c4a3054fa0e5e3c8c44d270313fa2bd983ecfbd39d9515ded9ab10229b88c3817325e4011ae523e7e8ef3c64155f7de97c49db23e2dc878e6a4323d33f386a7a30bad4115b6014f70045bf40a3ebf62018b983f2347d404d15b6f85efea7416cc96eb286191d6ae445000e888076019156b918b06924a11af86848559e786fcc32e8ec8ab78c768147893eac84f8c5768b934fbfa53cb51553c96dc96f55c17542501096b358d6c93991810aef3c3fea8a2acaee05460979e063d45a1c2f9df8af32d6d197b70e6baf0a1e1dd854aab30af389c0adf2e3dceb06c45be1c106991e63c2aa9d490ec14aef9bdacb4444065e42f934dab13d98232d4e3423edacfe0d45d21c275611e32216fd1d0f9b8bdff2220636436591ea1ec9aa9edbd8ce9918a05be052785233ea7a77cfcbc2cf99d78611369c1de30357f96304d641a36dd06a37648b22a0c71502952777b8dc6357378e6f7498b84cd0f3a995f427b7ba515bc747f95007181966da77989d1a12591ca642997b4ed8d5ac1e9987ece7795c3fcdb1537cc137a83dc2b3f92eabcf71062fab5954cb8209034655340420ddc738927122695d419588aaf17e474f4cf9a17b4bee303f199bbe6e8532f9676e4d661e1d723a27cb8a72ccb55b25c7a737fae013428318da1e431b45846ab17a44df40af30be3f1828a6a5fa94300b9073f9cb684b226919fe48ef8d4fd4d3a54962ab44641284e20e3f95942decba15fe4e317a44043bc5c688131af1c90878c4b3f606f7e470dbd554536bb4e782406737ebbaf895bd7c7e872cad1a69f5b8da0cbd4b42e5e3bff54dcf210cbf5a1f268346a4a06939e1c7387eb6e8feae01b29d0cec0825666479c989f5b6f87f5ecd9d5c019f6cd3fb0ff1982c4a524c8e6f27d538b3692940f2efb99aabe7de08144ad16bb2dc3fbb364d52d1d60d05358cf7e2f59fdbaf5b997913fe835effbf1559b465570ea5b2536cbd12e09c7bbc7a09a52947922c44f69d6fbeb77a138810f2296c890835bc29b7057a5e1aa01a71f6c80358106b80a596ba3383000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000dd784421" + "status": 200, + "message": "01003bcd1aa00d81002787d6c0b1220a4b90cfa9e1a60a52898a7e93f11071d2c0aa0326334a22a757f1a60a5db6133eb0fa4e2abe2a76b2bcecc63fd04fff0faf216e09acda8fea4499773f0ab7eef76d1d622acc89bca480f214bd5f4f1f1fe15178e9cb4eddcb390b1cb58a8a4fef696366f93e898ef2513479a045534b4a8eb12f5dcc3e7fa32158b4e6c4d6117f0cf6985034bb16e4bc98c1f8618751c547b76b896c23255ef0a5c94a83a7b1cd2b6d80d9249e238cc72481d0e121e3d9eee8289ce1039a51e7549f057eb071cd987da5d930b584e9b72adb3c10a46ab7770340fb419ee7f4902203a4de8f8fb5d9e47fcf607f606ab696062eb606b55bb59c61351c34dd141184546603026825d410165f34c9fa5ddc767d19d87bbdb62ebb62d69fe28fc4382b56d86b9e6465144a39ef63dcefc249a7a774f586c949c6fa80662a6d5160ebc941a8c2175273133563ed62ede65a3f86db8dc88d8b265874b05e8cddf761889a3b0f88851d5ecb44bde41b80ab6b16da50ba2dcbab2547059bc739b3d72a65b2b73e4b1d213fe2845c3ddfe3a4b89f12b960efe3e529814e8af72d7870bb7463d94d5de6a248e07b9ab35abedc9d5ff9a56fa694f8a7c8b488b5d99d72f73695e03e9af7e168e25c8e623c5f6c2083522eaeb5c22dbdb96b86b4151db81749606b04fb5d90a36b2152c11cb969cb517eb82be5ef64080f21bcc9e0d8ab605284426e1ceed0c5bdd723a0684bff52e0aaeadd808ae34d5c9925a5d001aedf08450b3735c9163b6cc06c27cf799b9547fdc7394c823f646fc963b0157084da1f80229861c7505ea8f5b2632b466fb65dad5c14324c5bfa393eb680acc88826ee44d460f0e8a68f0327efdf4f6d6a9f3b84adc880d68b26bdbd810e4e4108c207b5ae16398a923ee99fce38ae919741d61b32b14eb7d2d93afc62c7b158838640b0c05166dfc336447db72a14486a2052654b91414e1c9770ef114b406c2da1362ef0dfad67d4596928a80203e37d55eb34140bfc08e8de3b08e6710a5d5dcaea4ba7218e9486d5b47ad21322cde46de690f28e1a9e65ee0f87ecf9f5677fca297aed8dc80d31b98052d23f3e98694aa1bdf604a07ce7e4c6f3003b8b05cd7f9d12834d46697128a938475ffdb959a5eb0fa8496063c583afc95f60adc837878c0253f48d0308a0b00f43deab2859e0f80b3371d565a26289d8fd4497959610871853a246eca4c4479c191fdd639591b5f6d30cd1bc43a361c1fe0552a5d0da0ed3bad45a315cd5bd22cec8e3d5e72cba511d648db4ccfa879693edba0c3be2dad2575e35572b8778358a7741266af08be09aadfa4fb4e1b319498177141456464f1205c9eba6ba70c1b54b62c9731e13e6b237c4a3054fa0e5e3c8c44d270313fa2bd983ecfbd39d9515ded9ab10229b88c3817325e4011ae523e7e8ef3c64155f7de97c49db23e2dc878e6a4323d33f386a7a30bad4115b6014f70045bf40a3ebf62018b983f2347d404d15b6f85efea7416cc96eb286191d6ae445000e888076019156b918b06924a11af86848559e786fcc32e8ec8ab78c768147893eac84f8c5768b934fbfa53cb51553c96dc96f55c17542501096b358d6c93991810aef3c3fea8a2acaee05460979e063d45a1c2f9df8af32d6d197b70e6baf0a1e1dd854aab30af389c0adf2e3dceb06c45be1c106991e63c2aa9d490ec14aef9bdacb4444065e42f934dab13d98232d4e3423edacfe0d45d21c275611e32216fd1d0f9b8bdff2220636436591ea1ec9aa9edbd8ce9918a05be052785233ea7a77cfcbc2cf99d78611369c1de30357f96304d641a36dd06a37648b22a0c71502952777b8dc6357378e6f7498b84cd0f3a995f427b7ba515bc747f95007181966da77989d1a12591ca642997b4ed8d5ac1e9987ece7795c3fcdb1537cc137a83dc2b3f92eabcf71062fab5954cb8209034655340420ddc738927122695d419588aaf17e474f4cf9a17b4bee303f199bbe6e8532f9676e4d661e1d723a27cb8a72ccb55b25c7a737fae013428318da1e431b45846ab17a44df40af30be3f1828a6a5fa94300b9073f9cb684b226919fe48ef8d4fd4d3a54962ab44641284e20e3f95942decba15fe4e317a44043bc5c688131af1c90878c4b3f606f7e470dbd554536bb4e782406737ebbaf895bd7c7e872cad1a69f5b8da0cbd4b42e5e3bff54dcf210cbf5a1f268346a4a06939e1c7387eb6e8feae01b29d0cec0825666479c989f5b6f87f5ecd9d5c019f6cd3fb0ff1982c4a524c8e6f27d538b3692940f2efb99aabe7de08144ad16bb2dc3fbb364d52d1d60d05358cf7e2f59fdbaf5b997913fe835effbf1559b465570ea5b2536cbd12e09c7bbc7a09a52947922c44f69d6fbeb77a138810f2296c890835bc29b7057a5e1aa01a71f6c80358106b80a596ba3383000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000dd784421" } diff --git a/packages/sdk/src/__test__/integration/__mocks__/getAddresses.json b/packages/sdk/src/__test__/integration/__mocks__/getAddresses.json index 738c09d4..3a57f646 100644 --- a/packages/sdk/src/__test__/integration/__mocks__/getAddresses.json +++ b/packages/sdk/src/__test__/integration/__mocks__/getAddresses.json @@ -1,4 +1,4 @@ { - "status": 200, - "message": "01008b93b3ab0d81002985bb78f76d77d49d48acf5442bdef94b128a2f8abeb7b00afe7f695f833d0fac418b85babdd26f23c1d70408f3bbeadd777167d9f7ec258a299e3b939fbfda6ebf61e1fba91a646aa0a54a81514adf813a1406f6244ec3f9b20cd2084a1087917b3d7497aee4aca9de756f54ae193b9f21621c57f87e6fe4464297ae13e02b0d342d3087b267558a9f8c02b10ca045afa501b4581642f8c4b26e9adb665eeedb4ff86cf230a9f72c0903d33bf6617c87dacf9c02cb68687d53961f202704930a9522392c61cd906681352c355285668afa2312d20e7e87ad7ebb50f486d48c437d9b6fff67c6ebee6dccba8763f3b1fa8037a25981baedb2a671b40db7ddb6fa96605cf1f6c6484782b7f7e3bb1ea256e3a01afddb303ad325462ed745b8e2b0cc3837b7616b9ad815bebd4d40b1539d319dcc01ae068da364d5921d1a364a69e4825eff47aaa10fe26cfaa676df692295f1dba4dece0634621b8e087352f06dff391c27d70b22bd788ab5e4ec90cd89ae264e1d2391180e31f6c8c457d10b7855fa4be125c4fc3cee903f707171480d8f9f4ca974a211aa43b521ad4d86ca8883d2ddef931203fff0af3989e3a18fa47b3efc63996fffecfa6c9bcf499fcb5cdf996988bbce91b89815eca134992d191ede6eede485e94a3a199b7404c26b5058d5552bf7e61654f8733c12de0b52f23d7c031a2b86e89d1af9b5a8d7212c6ee448ff2a4f3996920bcdea914c128833862cf6f54f42781d688f6d6f5c6e789363aa811ee3f1422b93e18dca729bae933f460313f5348667cde5e61e0a3c24c65e6969f1c4d80aa94f94d28a79821a47994ac45fc36d2ff7fc11948ab880b6d8c352f13336c707bcd108994f75dbb4481c8912a883d07a9f8678ab1a2e5124607b8a18c12b2e19e22c46ed48d374fd7fcb11d8e6f58028313c8784f13fe71c91f6daa3e9a32658f9e6c8230b31f973e6790e6cd5e53035bb1039a556ab9ad9f99e317ed84caade9e5d8c973371ee56b831600085c09a7233f088ae883f0037b5c3e7ade54be865bb8526e8b8973c3c34b96954bec66183acbd094a56840401b01f151fbf47b7c73887d45257307da30fc957ae0be98161d413d7bfeea6fe743f2a0364ff8c231b97bcfc4bc6f45b48018759e27167ba60a1a66382bb1f45747c73690d40bb09921585b4d11afa9827434713f5ca707be077b9e23a41774d535c83f9adaff7ff2bab37a344bcb4b3bd8049cd3281125e4d9376cf17572fe04bf8d32fc5fe1a274dc26493a940ca11ab7327a8eb3ff8fb3fac99a5a313b0ca3320070ce9946965cbf7538e18e3cd7f1955741ea5d1c5c669cdef304c4b2622123f2fa26b559103d9cd44e405fa35ed4748e0eb5a1991cd389f1da5224629ac8e585f9cf087db470385a5bebbd792088f9c53ba7f8db516b22b9adf7390f3d65479078d7f7b4dd6c42201e520a41766f7d0b5dc5cb0b352a0f05832badd947e5643acf84f2917da9168e29f3c2de45a06c9625ea48e447f2141cf488544ac025715a649f08252b515e6b9e3fb1559759a2985a6ab4c9fbdead8838e97c048326ab0f6438dad15eb8d3bdff5fa687d422e98461a04289df0591e7e318ac71fd10c94748e36598651906157cc73b8a24f933b9d281ed5d8893cfd5670a1549af10ecb0fb85b90a10523ca5c32de3f3409d5381acf42f63ad25817de6f9e55ddead7d887d0271448738fa464ed7483a26c9f46471dbda12faf81eb2e0959699d41ef52a077203933551192e2e5daa16cc24bac966fdba50097c37e686f74c2ad29b5b4e012ac6d5f41c92be365e0d3de30ed5dda205f098ea33e3d10dfd40847237745be12910f012fc1293ae62beff4086274a4fef3f945a742df1e36aa58ccc4e32e43ee6566bacc11815a5d49c1c1f9f813248951338a0db937aa81f4a06c49e4c91e1a18b892be88adb6d56fac8f76bf4e422b273f5671a64f842734c74d8db51e1e10cdc1929488ef84d4a0934644a4c594c89cdf3796d2c1a26321a8729ddf10d9fe706ff5faa5484ec518b9ca22f119d24cb66b5c2f185c97b6092c53181903baccc46d42165c568d9b10f8f1d1192e6eda80140bc6c05a0be04a08025fdf45a56d09ff4c392adb6942d825e67efc72f21c70a61562b6496e5593ee52d71fa3436b57a42b27215bf51d20f72d563111edfabd594d566bfa1be65567b9a6f2844a18a3c4a5c9df2cc490cc5edbee414cd8dff57bfc38fab0a0a4180cd4ae9c6bcf32ca3dfd792893301bfd3d29d1daaea8a7e96f6ed129f26e3aafd0088506bd411711ff3d1351fa97bc580e960775edfff52015d382e827a0ebdb1b2bfd104c8272ddfe6a9182bf66ec23a7162cb7aee10c83b9b3aa612612183a3613bb84ef0a59edbdc6b36b33a520222b5959bb39a30cad70a72456a6aae3d291509446000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000327c4720" + "status": 200, + "message": "01008b93b3ab0d81002985bb78f76d77d49d48acf5442bdef94b128a2f8abeb7b00afe7f695f833d0fac418b85babdd26f23c1d70408f3bbeadd777167d9f7ec258a299e3b939fbfda6ebf61e1fba91a646aa0a54a81514adf813a1406f6244ec3f9b20cd2084a1087917b3d7497aee4aca9de756f54ae193b9f21621c57f87e6fe4464297ae13e02b0d342d3087b267558a9f8c02b10ca045afa501b4581642f8c4b26e9adb665eeedb4ff86cf230a9f72c0903d33bf6617c87dacf9c02cb68687d53961f202704930a9522392c61cd906681352c355285668afa2312d20e7e87ad7ebb50f486d48c437d9b6fff67c6ebee6dccba8763f3b1fa8037a25981baedb2a671b40db7ddb6fa96605cf1f6c6484782b7f7e3bb1ea256e3a01afddb303ad325462ed745b8e2b0cc3837b7616b9ad815bebd4d40b1539d319dcc01ae068da364d5921d1a364a69e4825eff47aaa10fe26cfaa676df692295f1dba4dece0634621b8e087352f06dff391c27d70b22bd788ab5e4ec90cd89ae264e1d2391180e31f6c8c457d10b7855fa4be125c4fc3cee903f707171480d8f9f4ca974a211aa43b521ad4d86ca8883d2ddef931203fff0af3989e3a18fa47b3efc63996fffecfa6c9bcf499fcb5cdf996988bbce91b89815eca134992d191ede6eede485e94a3a199b7404c26b5058d5552bf7e61654f8733c12de0b52f23d7c031a2b86e89d1af9b5a8d7212c6ee448ff2a4f3996920bcdea914c128833862cf6f54f42781d688f6d6f5c6e789363aa811ee3f1422b93e18dca729bae933f460313f5348667cde5e61e0a3c24c65e6969f1c4d80aa94f94d28a79821a47994ac45fc36d2ff7fc11948ab880b6d8c352f13336c707bcd108994f75dbb4481c8912a883d07a9f8678ab1a2e5124607b8a18c12b2e19e22c46ed48d374fd7fcb11d8e6f58028313c8784f13fe71c91f6daa3e9a32658f9e6c8230b31f973e6790e6cd5e53035bb1039a556ab9ad9f99e317ed84caade9e5d8c973371ee56b831600085c09a7233f088ae883f0037b5c3e7ade54be865bb8526e8b8973c3c34b96954bec66183acbd094a56840401b01f151fbf47b7c73887d45257307da30fc957ae0be98161d413d7bfeea6fe743f2a0364ff8c231b97bcfc4bc6f45b48018759e27167ba60a1a66382bb1f45747c73690d40bb09921585b4d11afa9827434713f5ca707be077b9e23a41774d535c83f9adaff7ff2bab37a344bcb4b3bd8049cd3281125e4d9376cf17572fe04bf8d32fc5fe1a274dc26493a940ca11ab7327a8eb3ff8fb3fac99a5a313b0ca3320070ce9946965cbf7538e18e3cd7f1955741ea5d1c5c669cdef304c4b2622123f2fa26b559103d9cd44e405fa35ed4748e0eb5a1991cd389f1da5224629ac8e585f9cf087db470385a5bebbd792088f9c53ba7f8db516b22b9adf7390f3d65479078d7f7b4dd6c42201e520a41766f7d0b5dc5cb0b352a0f05832badd947e5643acf84f2917da9168e29f3c2de45a06c9625ea48e447f2141cf488544ac025715a649f08252b515e6b9e3fb1559759a2985a6ab4c9fbdead8838e97c048326ab0f6438dad15eb8d3bdff5fa687d422e98461a04289df0591e7e318ac71fd10c94748e36598651906157cc73b8a24f933b9d281ed5d8893cfd5670a1549af10ecb0fb85b90a10523ca5c32de3f3409d5381acf42f63ad25817de6f9e55ddead7d887d0271448738fa464ed7483a26c9f46471dbda12faf81eb2e0959699d41ef52a077203933551192e2e5daa16cc24bac966fdba50097c37e686f74c2ad29b5b4e012ac6d5f41c92be365e0d3de30ed5dda205f098ea33e3d10dfd40847237745be12910f012fc1293ae62beff4086274a4fef3f945a742df1e36aa58ccc4e32e43ee6566bacc11815a5d49c1c1f9f813248951338a0db937aa81f4a06c49e4c91e1a18b892be88adb6d56fac8f76bf4e422b273f5671a64f842734c74d8db51e1e10cdc1929488ef84d4a0934644a4c594c89cdf3796d2c1a26321a8729ddf10d9fe706ff5faa5484ec518b9ca22f119d24cb66b5c2f185c97b6092c53181903baccc46d42165c568d9b10f8f1d1192e6eda80140bc6c05a0be04a08025fdf45a56d09ff4c392adb6942d825e67efc72f21c70a61562b6496e5593ee52d71fa3436b57a42b27215bf51d20f72d563111edfabd594d566bfa1be65567b9a6f2844a18a3c4a5c9df2cc490cc5edbee414cd8dff57bfc38fab0a0a4180cd4ae9c6bcf32ca3dfd792893301bfd3d29d1daaea8a7e96f6ed129f26e3aafd0088506bd411711ff3d1351fa97bc580e960775edfff52015d382e827a0ebdb1b2bfd104c8272ddfe6a9182bf66ec23a7162cb7aee10c83b9b3aa612612183a3613bb84ef0a59edbdc6b36b33a520222b5959bb39a30cad70a72456a6aae3d291509446000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000327c4720" } diff --git a/packages/sdk/src/__test__/integration/__mocks__/getKvRecords.json b/packages/sdk/src/__test__/integration/__mocks__/getKvRecords.json index f7571298..f9ae698b 100644 --- a/packages/sdk/src/__test__/integration/__mocks__/getKvRecords.json +++ b/packages/sdk/src/__test__/integration/__mocks__/getKvRecords.json @@ -1,4 +1,4 @@ { - "status": 200, - "message": "010092647efa0d8100347d04bc0f5c154535340722c1c2a7294131418e44d3010adcc9061dc46619741489df487aff8d3a998d3ce4578e959fb350d47d1afaff74fa88edf5e56592c20317ea62e0114c15c0342db2ebb684f8dfb7f16bf0229d20a043254f7aa032a5f1572811218c76b2e53c3ba12702deef12fb7d91aa8488bb7cc878faa9e7f310faac834fea46179feac05f7b63ba011485cef895c615ed3c21b42d09ba963ac83993f3ccc603c4d5a3d7c423d0b588cfdf4503cf1ac2260a2fd843d38713395ac7eecf60ef037e6191481310c10f912891bb4a82afd890f6b8a609a875d0f1c9102d05a1b2410d1fd39fb74170fe2aeeeecc3fa648fb9f9fb37c022fd68591a279f21537d33cdde63dc2408e298e0f001740e44f60c6178786ef82b0216c4a53495aa0b42b0ccd496f71e0f5bcd4994f13eac91e65e02195ea5b4adf29b02c25349f068006c73d03dfcc4f560340e5c8a35bf09052c458f7c2d62859259884659f91c3f77adf4c04c73d38c5f14e81189e21c01f83ae36653b4dbea4bf6ef52b8fa8b95bc990a2fee721556710113694ae4b70425c56371639a24cea27cbc81fb4f871da3d66ed797ad70dccf4d9f2fedfa1668dad76b6681d21ddcd1f0fa49abbefded15b5877e909df677036e38c56e680b6da88f87a43638914337eaa6c4475a17e59206c629c2c5f6baaf4aa7bfad96bb36000a9d792d0100d1b2d5d522f28e1902164a3234bf3242068d824d68cf597882d6833b39b301c8a7274e96e66350336dbffc1e84d5acfd39b289c4d901c7d2d233058dd2438d7cb03146faf7d6bb1a36f8608171e3f547a14e435778b1351de1c697ce0b45c7dd042b225559a56c26c544afe66eebddfa637f3d3ea9550df2a5b73b607b6e68bd4638ed40c2eafa1f20daefbf21fae12f69442e01fc9ddb558d90fa047e0761c59e56b2a367b827c44a134ccef8e59a43b5ce9b4ce0ae2cfbd60558eb3f76741ddde019c8b9964a5f9d9f1a5fbaf0ee2ee0434f38999ab373122eb6532cf6d4b2d1f2e9f37a53fa5348f47a2450c675e591a32b14b80b2fd930e4e33656f23839fc84cba96fa10cbd06841ee894606bdac7ba7260c4b86bfdd0bafe2d7f2bd0eff7e117b4239171745705b0fe8ca2d671f35a447dd5ab3ff0d2b2321e308c7b068abb71aea5b633129f8b2ab953c3081985ccbb095184f7523371ceb0aa94fd45b9b860de9da67da62113fe509d5b2d881ed0880d196d04d2ad44be299aa13528c09fcfa7e5da1c0489f8f3bd92a67ed11cf0555beeba875bf77b568ca978ddf61dc2b0b5807f5a161df6f408c668a4edb0d05d7d4fb4e017159722b126f635f0d93e7ca110b08dc2b1f8a527e0dca0868ecd76647d94b3c021b5ecd0d7626b940e17662f2df931dcd9e354b1d7d8bf271413bc6a4c1e89be5cfdd02af31a888314a13f51b4767705813fb6d8b98e4032473bc8ff6baefa4bdf6bf1f578134829a1ba4952a87e871ee54f36fb10f1d9bfe3279f2ef6f61e8af7b5bccfe8d86a68b0755cc8fc440e0ddcde5476df656b49a908815e20f8cb8a2ba4ae8bd447cd6535e044f7e4420b0bdd9289df6d91256d1cfae090afe25afcbd5a15fbfc146760dffd7538f70005b87d697f9c96f0e1672e4c1e8892517f4cee2c27faccf245d4f52e3874c637cbb10986a3970cc9d005c866b930972c200ae78a7fea1cb03ea33df8702542007d03f5406e9d4f93259b7ea8ce2f7d7debfeb10a71886a3d43866c98da548d2c716d104e31da89032056e2cdda4dca80267f28c6a943cbc21dc956d3887316c48c9f050a4181f7cf3b855d757fb11f9caed0adfab821afd0c512cf71cf6b2cfcc344912b937611d88581de96ab75b11121dc3be02c73d44181dd840b2a3968d79f0d7029dee4690d91ae1dccc739fdceb4612e070da018128a7556be8eef8930516c13b8e8d8d28dc5034cce953d017d02546ff7292b3aa284ab3c7894bf4f636e0a9c8b8c3473bb5b0086a29fe77fac6087ced1778f5eeb2def0d1790b802a9dee1b4683679b8c0235060e39a3ebf5a194e5583b020c2b2ecc53904e6a3dd281b072fd13fea08ce3e8b2d490b3c57402335c20bb4547e066d4166ff2728d720687818397d5dd0c30ff55f0b2b0f122a76c636c3334b26009f57b41ae33c2638f8ebb4408e95cf0bff7a6232075fd1dfc482d74e9ac8a6b0cdbd17d9e1daa75d72c3d26cd2c31046794d53000dd99ecdcf6574b9cdade232535ebc86044858deee8d5894989e9e0495702fa31a8bf0cf97f8d9a34d204818cf71996e1f07e20ec22b9c27ee74044c72733c055ffa8530527cdee616235ce4cb986bab91ff5bcaa5f5aa647a57e4e25194db9bcdf2b5392efc0ebad5db35ff8661bd9daf024619711f4a3941fdefc399c2d77b05c152ad243f314d7178da553f2fa6adb8a08d1000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000de551a6b" + "status": 200, + "message": "010092647efa0d8100347d04bc0f5c154535340722c1c2a7294131418e44d3010adcc9061dc46619741489df487aff8d3a998d3ce4578e959fb350d47d1afaff74fa88edf5e56592c20317ea62e0114c15c0342db2ebb684f8dfb7f16bf0229d20a043254f7aa032a5f1572811218c76b2e53c3ba12702deef12fb7d91aa8488bb7cc878faa9e7f310faac834fea46179feac05f7b63ba011485cef895c615ed3c21b42d09ba963ac83993f3ccc603c4d5a3d7c423d0b588cfdf4503cf1ac2260a2fd843d38713395ac7eecf60ef037e6191481310c10f912891bb4a82afd890f6b8a609a875d0f1c9102d05a1b2410d1fd39fb74170fe2aeeeecc3fa648fb9f9fb37c022fd68591a279f21537d33cdde63dc2408e298e0f001740e44f60c6178786ef82b0216c4a53495aa0b42b0ccd496f71e0f5bcd4994f13eac91e65e02195ea5b4adf29b02c25349f068006c73d03dfcc4f560340e5c8a35bf09052c458f7c2d62859259884659f91c3f77adf4c04c73d38c5f14e81189e21c01f83ae36653b4dbea4bf6ef52b8fa8b95bc990a2fee721556710113694ae4b70425c56371639a24cea27cbc81fb4f871da3d66ed797ad70dccf4d9f2fedfa1668dad76b6681d21ddcd1f0fa49abbefded15b5877e909df677036e38c56e680b6da88f87a43638914337eaa6c4475a17e59206c629c2c5f6baaf4aa7bfad96bb36000a9d792d0100d1b2d5d522f28e1902164a3234bf3242068d824d68cf597882d6833b39b301c8a7274e96e66350336dbffc1e84d5acfd39b289c4d901c7d2d233058dd2438d7cb03146faf7d6bb1a36f8608171e3f547a14e435778b1351de1c697ce0b45c7dd042b225559a56c26c544afe66eebddfa637f3d3ea9550df2a5b73b607b6e68bd4638ed40c2eafa1f20daefbf21fae12f69442e01fc9ddb558d90fa047e0761c59e56b2a367b827c44a134ccef8e59a43b5ce9b4ce0ae2cfbd60558eb3f76741ddde019c8b9964a5f9d9f1a5fbaf0ee2ee0434f38999ab373122eb6532cf6d4b2d1f2e9f37a53fa5348f47a2450c675e591a32b14b80b2fd930e4e33656f23839fc84cba96fa10cbd06841ee894606bdac7ba7260c4b86bfdd0bafe2d7f2bd0eff7e117b4239171745705b0fe8ca2d671f35a447dd5ab3ff0d2b2321e308c7b068abb71aea5b633129f8b2ab953c3081985ccbb095184f7523371ceb0aa94fd45b9b860de9da67da62113fe509d5b2d881ed0880d196d04d2ad44be299aa13528c09fcfa7e5da1c0489f8f3bd92a67ed11cf0555beeba875bf77b568ca978ddf61dc2b0b5807f5a161df6f408c668a4edb0d05d7d4fb4e017159722b126f635f0d93e7ca110b08dc2b1f8a527e0dca0868ecd76647d94b3c021b5ecd0d7626b940e17662f2df931dcd9e354b1d7d8bf271413bc6a4c1e89be5cfdd02af31a888314a13f51b4767705813fb6d8b98e4032473bc8ff6baefa4bdf6bf1f578134829a1ba4952a87e871ee54f36fb10f1d9bfe3279f2ef6f61e8af7b5bccfe8d86a68b0755cc8fc440e0ddcde5476df656b49a908815e20f8cb8a2ba4ae8bd447cd6535e044f7e4420b0bdd9289df6d91256d1cfae090afe25afcbd5a15fbfc146760dffd7538f70005b87d697f9c96f0e1672e4c1e8892517f4cee2c27faccf245d4f52e3874c637cbb10986a3970cc9d005c866b930972c200ae78a7fea1cb03ea33df8702542007d03f5406e9d4f93259b7ea8ce2f7d7debfeb10a71886a3d43866c98da548d2c716d104e31da89032056e2cdda4dca80267f28c6a943cbc21dc956d3887316c48c9f050a4181f7cf3b855d757fb11f9caed0adfab821afd0c512cf71cf6b2cfcc344912b937611d88581de96ab75b11121dc3be02c73d44181dd840b2a3968d79f0d7029dee4690d91ae1dccc739fdceb4612e070da018128a7556be8eef8930516c13b8e8d8d28dc5034cce953d017d02546ff7292b3aa284ab3c7894bf4f636e0a9c8b8c3473bb5b0086a29fe77fac6087ced1778f5eeb2def0d1790b802a9dee1b4683679b8c0235060e39a3ebf5a194e5583b020c2b2ecc53904e6a3dd281b072fd13fea08ce3e8b2d490b3c57402335c20bb4547e066d4166ff2728d720687818397d5dd0c30ff55f0b2b0f122a76c636c3334b26009f57b41ae33c2638f8ebb4408e95cf0bff7a6232075fd1dfc482d74e9ac8a6b0cdbd17d9e1daa75d72c3d26cd2c31046794d53000dd99ecdcf6574b9cdade232535ebc86044858deee8d5894989e9e0495702fa31a8bf0cf97f8d9a34d204818cf71996e1f07e20ec22b9c27ee74044c72733c055ffa8530527cdee616235ce4cb986bab91ff5bcaa5f5aa647a57e4e25194db9bcdf2b5392efc0ebad5db35ff8661bd9daf024619711f4a3941fdefc399c2d77b05c152ad243f314d7178da553f2fa6adb8a08d1000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000de551a6b" } diff --git a/packages/sdk/src/__test__/integration/__mocks__/handlers.ts b/packages/sdk/src/__test__/integration/__mocks__/handlers.ts index 80e26132..dca0c4c7 100644 --- a/packages/sdk/src/__test__/integration/__mocks__/handlers.ts +++ b/packages/sdk/src/__test__/integration/__mocks__/handlers.ts @@ -1,99 +1,87 @@ -import { http, HttpResponse } from 'msw'; -import { - fourbyteResponse0c49ccbe, - fourbyteResponse0x6a761202, - fourbyteResponse0x38ed1739, - fourbyteResponse0xa9059cbb, - fourbyteResponseac9650d8, - fourbyteResponsefc6f7865, -} from './4byte'; -import addKvRecordsResponse from './addKvRecords.json'; -import connectResponse from './connect.json'; -import { - etherscanResponse0x06412d7e, - etherscanResponse0x7a250d56, - etherscanResponse0xa0b86991, - etherscanResponse0xc36442b6, -} from './etherscan'; -import fetchActiveWalletResponse from './fetchActiveWallet.json'; -import getAddressesResponse from './getAddresses.json'; -import getKvRecordsResponse from './getKvRecords.json'; -import removeKvRecordsResponse from './removeKvRecords.json'; -import signResponse from './sign.json'; +import { http, HttpResponse } from 'msw' +import { fourbyteResponse0c49ccbe, fourbyteResponse0x6a761202, fourbyteResponse0x38ed1739, fourbyteResponse0xa9059cbb, fourbyteResponseac9650d8, fourbyteResponsefc6f7865 } from './4byte' +import addKvRecordsResponse from './addKvRecords.json' +import connectResponse from './connect.json' +import { etherscanResponse0x06412d7e, etherscanResponse0x7a250d56, etherscanResponse0xa0b86991, etherscanResponse0xc36442b6 } from './etherscan' +import fetchActiveWalletResponse from './fetchActiveWallet.json' +import getAddressesResponse from './getAddresses.json' +import getKvRecordsResponse from './getKvRecords.json' +import removeKvRecordsResponse from './removeKvRecords.json' +import signResponse from './sign.json' export const handlers = [ - http.post('https://signing.gridpl.us/test/connect', () => { - return HttpResponse.json(connectResponse); - }), - http.post('https://signing.gridpl.us/test/getAddresses', () => { - return HttpResponse.json(getAddressesResponse); - }), - http.post('https://signing.gridpl.us/test/sign', () => { - return HttpResponse.json(signResponse); - }), - http.post('https://signing.gridpl.us/test/fetchActiveWallet', () => { - return HttpResponse.json(fetchActiveWalletResponse); - }), - http.post('https://signing.gridpl.us/test/addKvRecords', () => { - return HttpResponse.json(addKvRecordsResponse); - }), - http.post('https://signing.gridpl.us/test/getKvRecords', () => { - return HttpResponse.json(getKvRecordsResponse); - }), - http.post('https://signing.gridpl.us/test/removeKvRecords', () => { - return HttpResponse.json(removeKvRecordsResponse); - }), - http.get('https://api.etherscan.io/api', ({ request }) => { - const url = new URL(request.url); - const module = url.searchParams.get('module'); - const action = url.searchParams.get('action'); - const address = url.searchParams.get('address'); + http.post('https://signing.gridpl.us/test/connect', () => { + return HttpResponse.json(connectResponse) + }), + http.post('https://signing.gridpl.us/test/getAddresses', () => { + return HttpResponse.json(getAddressesResponse) + }), + http.post('https://signing.gridpl.us/test/sign', () => { + return HttpResponse.json(signResponse) + }), + http.post('https://signing.gridpl.us/test/fetchActiveWallet', () => { + return HttpResponse.json(fetchActiveWalletResponse) + }), + http.post('https://signing.gridpl.us/test/addKvRecords', () => { + return HttpResponse.json(addKvRecordsResponse) + }), + http.post('https://signing.gridpl.us/test/getKvRecords', () => { + return HttpResponse.json(getKvRecordsResponse) + }), + http.post('https://signing.gridpl.us/test/removeKvRecords', () => { + return HttpResponse.json(removeKvRecordsResponse) + }), + http.get('https://api.etherscan.io/api', ({ request }) => { + const url = new URL(request.url) + const module = url.searchParams.get('module') + const action = url.searchParams.get('action') + const address = url.searchParams.get('address') - if (module === 'contract' && action === 'getabi') { - if (address === '0xa0b86991c6218b36c1d19d4a2e9eb0ce3606eb48') { - return HttpResponse.json({ - result: JSON.stringify(etherscanResponse0xa0b86991), - }); - } - if (address === '0x7a250d5630b4cf539739df2c5dacb4c659f2488d') { - return HttpResponse.json({ - result: JSON.stringify(etherscanResponse0x7a250d56), - }); - } - if (address === '0xc36442b4a4522e871399cd717abdd847ab11fe88') { - return HttpResponse.json({ - result: JSON.stringify(etherscanResponse0xc36442b6), - }); - } - if (address === '0x06412d7ebfbf66c25607e2ed24c1d207043be327') { - return HttpResponse.json({ - result: JSON.stringify(etherscanResponse0x06412d7e), - }); - } - } - return new HttpResponse(null, { status: 404 }); - }), - http.get('https://www.4byte.directory/api/v1/signatures', ({ request }) => { - const url = new URL(request.url); - const hexSignature = url.searchParams.get('hex_signature'); - if (hexSignature === '0xa9059cbb') { - return HttpResponse.json(fourbyteResponse0xa9059cbb); - } - if (hexSignature === '0x38ed1739') { - return HttpResponse.json(fourbyteResponse0x38ed1739); - } - if (hexSignature === '0xac9650d8') { - return HttpResponse.json(fourbyteResponseac9650d8); - } - if (hexSignature === '0x0c49ccbe') { - return HttpResponse.json(fourbyteResponse0c49ccbe); - } - if (hexSignature === '0xfc6f7865') { - return HttpResponse.json(fourbyteResponsefc6f7865); - } - if (hexSignature === '0x6a761202') { - return HttpResponse.json(fourbyteResponse0x6a761202); - } - return new HttpResponse(null, { status: 404 }); - }), -]; + if (module === 'contract' && action === 'getabi') { + if (address === '0xa0b86991c6218b36c1d19d4a2e9eb0ce3606eb48') { + return HttpResponse.json({ + result: JSON.stringify(etherscanResponse0xa0b86991), + }) + } + if (address === '0x7a250d5630b4cf539739df2c5dacb4c659f2488d') { + return HttpResponse.json({ + result: JSON.stringify(etherscanResponse0x7a250d56), + }) + } + if (address === '0xc36442b4a4522e871399cd717abdd847ab11fe88') { + return HttpResponse.json({ + result: JSON.stringify(etherscanResponse0xc36442b6), + }) + } + if (address === '0x06412d7ebfbf66c25607e2ed24c1d207043be327') { + return HttpResponse.json({ + result: JSON.stringify(etherscanResponse0x06412d7e), + }) + } + } + return new HttpResponse(null, { status: 404 }) + }), + http.get('https://www.4byte.directory/api/v1/signatures', ({ request }) => { + const url = new URL(request.url) + const hexSignature = url.searchParams.get('hex_signature') + if (hexSignature === '0xa9059cbb') { + return HttpResponse.json(fourbyteResponse0xa9059cbb) + } + if (hexSignature === '0x38ed1739') { + return HttpResponse.json(fourbyteResponse0x38ed1739) + } + if (hexSignature === '0xac9650d8') { + return HttpResponse.json(fourbyteResponseac9650d8) + } + if (hexSignature === '0x0c49ccbe') { + return HttpResponse.json(fourbyteResponse0c49ccbe) + } + if (hexSignature === '0xfc6f7865') { + return HttpResponse.json(fourbyteResponsefc6f7865) + } + if (hexSignature === '0x6a761202') { + return HttpResponse.json(fourbyteResponse0x6a761202) + } + return new HttpResponse(null, { status: 404 }) + }), +] diff --git a/packages/sdk/src/__test__/integration/__mocks__/removeKvRecords.json b/packages/sdk/src/__test__/integration/__mocks__/removeKvRecords.json index daadc019..902f2c92 100644 --- a/packages/sdk/src/__test__/integration/__mocks__/removeKvRecords.json +++ b/packages/sdk/src/__test__/integration/__mocks__/removeKvRecords.json @@ -1,4 +1,4 @@ { - "status": 200, - "message": "0100f506dfce0d8100f5382a032169e8a60db71d0cc125b75e03839d4a886fad4dff893c0edb6c6bcf908755bd667b09416f308f313c34dff3e696e08225615e29e75af7f2c5450ed7246e1c7aebe6a74834ce7d6c797575da65718d88d3349a8b370924059db3e38022816d0f968f22c9061f000cfd7e734bc4775647701ee7b640577b62e797630c7fb91bc89faf6783ab93b3d3f66b19f6bd5bf1eeb5dc6300f4a42391a592c7df2913654a4e4963e6ddbc07a215fd815d0ede1fbf31c81225a55825171b4f0d10ab0cd8ea1fe560961cbcd8fb6732fab742afdeaf92d79d91d06cf9591eabcc470e684080c861bb94b4c8b79977c69dc39804b91c18d257192412432aaefbbf546196d09c5586cfa5a951c7a4e1dbe908b82022225739884328dc44c6b60cc23f6f71ff506ad514761e14d0b94c742220e67ed15145cf2b82bb030ee560447bf421d65d97aa52fe97f9f8bf92d11acca19b3bca7d076fb36171c431491de93e105a598426d4fdc7d7e5e214ac22ccfbd89def4a0876d440492b6cb706d5d3698b44f9d2dbb7623cd7981575ad8f42654f36656dfe5b3fb606cfe134124e07320ac6d00b7f6d000036737439404b49df0ccaaff56cf60d93c18f253c5abc1518350d5c286862dd5d6f11d01e676fe32c0bb19e658ccb829136bbff6fe36b16a74929be6df7672c0bcf1b4f11fadc38e0bec43d5fee0b4b95c6d16d9e968f142392a0da4d138798d0f98747c771cd23c7bd81910139415a6d5cc894f312406976782c8ffc18d63d7797cda33e2d73d79e2d42dab4778fb1aef80b049c010045c898ebf1cc0da9a62a10a2c3f00e8fc7b03fd240a11262bec322b8b74680c06bd21f77044992b85877a9f7fc60763007d7e9929caf0c1f2a746e99c544ed3b0550f62b1cc99b4bb76c543ffde434187ae923971af02ee800690d999ab82c40c3cb926b41b90fa06d77d16b91bddfff24b0afc6fc31acd31a98fd7992af943d6e6e462cc53deaf02725ddb64056d9bd2a381ca1537f52ffc010cdeaf35f6b34ca2dc045d7f28a0fbff646ceeaf1ebfcea8951ebf3a84058d69f923fceecd35183f92f5e4c5b70300ca50bb304c0b6a8e91afbc06d6468a276f2ee5ae524320838a280e4c334b3e6fdeba302c3a767ae9989441493b6e4ee731ccd0641847f8465808cd5be7515e9bd406866970297e6b886e76251b62be2e22dfa2eaa1c0f53cde7884a345470ea7505ad0b618c3eaa469210bfe1b79d0bbeb0e477177e75ed0e0b7b1dacd00f2dcc05f5ad940e7cfd52f1417b05cebd26470b87ad8f278ac92bcff2a5f9a5f97b3a085ee6306e57ce3bf0df76ef34aec9ad7b9b3eb307d0948ee6157d27c433c49a8815e7d7391eb9b74a3421128bdb4b9699a3d3256dc769adf975d9fb0d517e25d5c2884c2a8d448b16c24d9c012a3f2c9ebec6a7f5ea99cef6fed3be6abd6df8e3b0ad4894350da4085446ed7ac9854242b5b296e83c39e2dcb74976b6a8c8da756e9897fad7db9c37f100d3fde0683f1dad72b3712705dcc4761884f2b8e88e08056f0c15bb9aed9e8f4bb113ee35487eb14c79bad5254869ebc69b1249ce8640425f3c7e0077ad3de5b29b43b506157f5ed17cdecca39dd6694e6aba9de524f34da16231c4ce991b5420dd4d77640d2af0b45bdc679d1894de07021aceca5547998be5c306bbb1a67d3125cc71f1286afd99f62ff8dc74815ba4a1136447aaff9599dffeda139de7c1f739171c17ae54306c6cc8c9a73e1437ec8c627384a2229d3be5992895922ef434e6a1db7083aabaf3401d3025f5f943a5bb39733a265782b783d8a746fd9bf0bf40bbea7c7bd963bb3a47a3117e831920dddd1393944a0eeb8a00a209e010c4f85eb6aa5e17dcb7af55aab9ff982395b5915b55db81d42facb1044c5453c0cec5cd6207920730258c22482237c318045a27638bb00bf7e89d3a6175aa18eab54a8ab7269f8718ead7c5ea04a879fc8eca77e58626d1f801bc6986c32cdbde57b3674e21152ffdeb8a582336ec9c14351f43964858d50265727b6dd8c55d292a45bbf81d224381eafe80957913dcf893f66ff7d3b4906b3a7b03769dd937c50080a9a9adb1b63bcd8c3260310a12808415faf955af4d4c2abb3978794b0d326138b77a8260349acf09cc8c9692872317070aaa67bc59fe7259bc2e27390f72527aefa2650a94f1f0c982ce9247fd945ef28501f9202928d2601fca9b88ac7c4438c3dc95478fe5d21747cfa3ea616bd7c054aa3b9ed856fad8cc239b374a0514dc1629139a1266bf479489a632fa5e0e2880f2e6ebce99e9fd0f1057c89882b8be3e7759fa5ff320c2d3ad0b749cc9fbf73bc1a555290c6561c60e7696372608caf4cebe3e9536c5d68f7e3ba6d4ea0e53d44fe2720c0bd59beab66914eba61fa7d9d41e44e059ba22821ff59a945d1900000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000077c76262" + "status": 200, + "message": "0100f506dfce0d8100f5382a032169e8a60db71d0cc125b75e03839d4a886fad4dff893c0edb6c6bcf908755bd667b09416f308f313c34dff3e696e08225615e29e75af7f2c5450ed7246e1c7aebe6a74834ce7d6c797575da65718d88d3349a8b370924059db3e38022816d0f968f22c9061f000cfd7e734bc4775647701ee7b640577b62e797630c7fb91bc89faf6783ab93b3d3f66b19f6bd5bf1eeb5dc6300f4a42391a592c7df2913654a4e4963e6ddbc07a215fd815d0ede1fbf31c81225a55825171b4f0d10ab0cd8ea1fe560961cbcd8fb6732fab742afdeaf92d79d91d06cf9591eabcc470e684080c861bb94b4c8b79977c69dc39804b91c18d257192412432aaefbbf546196d09c5586cfa5a951c7a4e1dbe908b82022225739884328dc44c6b60cc23f6f71ff506ad514761e14d0b94c742220e67ed15145cf2b82bb030ee560447bf421d65d97aa52fe97f9f8bf92d11acca19b3bca7d076fb36171c431491de93e105a598426d4fdc7d7e5e214ac22ccfbd89def4a0876d440492b6cb706d5d3698b44f9d2dbb7623cd7981575ad8f42654f36656dfe5b3fb606cfe134124e07320ac6d00b7f6d000036737439404b49df0ccaaff56cf60d93c18f253c5abc1518350d5c286862dd5d6f11d01e676fe32c0bb19e658ccb829136bbff6fe36b16a74929be6df7672c0bcf1b4f11fadc38e0bec43d5fee0b4b95c6d16d9e968f142392a0da4d138798d0f98747c771cd23c7bd81910139415a6d5cc894f312406976782c8ffc18d63d7797cda33e2d73d79e2d42dab4778fb1aef80b049c010045c898ebf1cc0da9a62a10a2c3f00e8fc7b03fd240a11262bec322b8b74680c06bd21f77044992b85877a9f7fc60763007d7e9929caf0c1f2a746e99c544ed3b0550f62b1cc99b4bb76c543ffde434187ae923971af02ee800690d999ab82c40c3cb926b41b90fa06d77d16b91bddfff24b0afc6fc31acd31a98fd7992af943d6e6e462cc53deaf02725ddb64056d9bd2a381ca1537f52ffc010cdeaf35f6b34ca2dc045d7f28a0fbff646ceeaf1ebfcea8951ebf3a84058d69f923fceecd35183f92f5e4c5b70300ca50bb304c0b6a8e91afbc06d6468a276f2ee5ae524320838a280e4c334b3e6fdeba302c3a767ae9989441493b6e4ee731ccd0641847f8465808cd5be7515e9bd406866970297e6b886e76251b62be2e22dfa2eaa1c0f53cde7884a345470ea7505ad0b618c3eaa469210bfe1b79d0bbeb0e477177e75ed0e0b7b1dacd00f2dcc05f5ad940e7cfd52f1417b05cebd26470b87ad8f278ac92bcff2a5f9a5f97b3a085ee6306e57ce3bf0df76ef34aec9ad7b9b3eb307d0948ee6157d27c433c49a8815e7d7391eb9b74a3421128bdb4b9699a3d3256dc769adf975d9fb0d517e25d5c2884c2a8d448b16c24d9c012a3f2c9ebec6a7f5ea99cef6fed3be6abd6df8e3b0ad4894350da4085446ed7ac9854242b5b296e83c39e2dcb74976b6a8c8da756e9897fad7db9c37f100d3fde0683f1dad72b3712705dcc4761884f2b8e88e08056f0c15bb9aed9e8f4bb113ee35487eb14c79bad5254869ebc69b1249ce8640425f3c7e0077ad3de5b29b43b506157f5ed17cdecca39dd6694e6aba9de524f34da16231c4ce991b5420dd4d77640d2af0b45bdc679d1894de07021aceca5547998be5c306bbb1a67d3125cc71f1286afd99f62ff8dc74815ba4a1136447aaff9599dffeda139de7c1f739171c17ae54306c6cc8c9a73e1437ec8c627384a2229d3be5992895922ef434e6a1db7083aabaf3401d3025f5f943a5bb39733a265782b783d8a746fd9bf0bf40bbea7c7bd963bb3a47a3117e831920dddd1393944a0eeb8a00a209e010c4f85eb6aa5e17dcb7af55aab9ff982395b5915b55db81d42facb1044c5453c0cec5cd6207920730258c22482237c318045a27638bb00bf7e89d3a6175aa18eab54a8ab7269f8718ead7c5ea04a879fc8eca77e58626d1f801bc6986c32cdbde57b3674e21152ffdeb8a582336ec9c14351f43964858d50265727b6dd8c55d292a45bbf81d224381eafe80957913dcf893f66ff7d3b4906b3a7b03769dd937c50080a9a9adb1b63bcd8c3260310a12808415faf955af4d4c2abb3978794b0d326138b77a8260349acf09cc8c9692872317070aaa67bc59fe7259bc2e27390f72527aefa2650a94f1f0c982ce9247fd945ef28501f9202928d2601fca9b88ac7c4438c3dc95478fe5d21747cfa3ea616bd7c054aa3b9ed856fad8cc239b374a0514dc1629139a1266bf479489a632fa5e0e2880f2e6ebce99e9fd0f1057c89882b8be3e7759fa5ff320c2d3ad0b749cc9fbf73bc1a555290c6561c60e7696372608caf4cebe3e9536c5d68f7e3ba6d4ea0e53d44fe2720c0bd59beab66914eba61fa7d9d41e44e059ba22821ff59a945d1900000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000077c76262" } diff --git a/packages/sdk/src/__test__/integration/__mocks__/server.ts b/packages/sdk/src/__test__/integration/__mocks__/server.ts index 573814cc..5dc2fb14 100644 --- a/packages/sdk/src/__test__/integration/__mocks__/server.ts +++ b/packages/sdk/src/__test__/integration/__mocks__/server.ts @@ -1,5 +1,5 @@ -import { setupServer } from 'msw/node'; -import { handlers } from './handlers'; +import { setupServer } from 'msw/node' +import { handlers } from './handlers' // This configures a request mocking server with the given request handlers. -export const server = setupServer(...handlers); +export const server = setupServer(...handlers) diff --git a/packages/sdk/src/__test__/integration/__mocks__/setup.ts b/packages/sdk/src/__test__/integration/__mocks__/setup.ts index e0f2a27b..cccb8c13 100644 --- a/packages/sdk/src/__test__/integration/__mocks__/setup.ts +++ b/packages/sdk/src/__test__/integration/__mocks__/setup.ts @@ -1,7 +1,7 @@ -import { server } from './server'; +import { server } from './server' export const setup = () => { - beforeAll(() => server.listen({ onUnhandledRequest: 'bypass' })); - afterAll(() => server.close()); - afterEach(() => server.resetHandlers()); -}; + beforeAll(() => server.listen({ onUnhandledRequest: 'bypass' })) + afterAll(() => server.close()) + afterEach(() => server.resetHandlers()) +} diff --git a/packages/sdk/src/__test__/integration/__mocks__/sign.json b/packages/sdk/src/__test__/integration/__mocks__/sign.json index d5086add..0d493670 100644 --- a/packages/sdk/src/__test__/integration/__mocks__/sign.json +++ b/packages/sdk/src/__test__/integration/__mocks__/sign.json @@ -1,4 +1,4 @@ { - "status": 200, - "message": "01002dde2fc80d8100f4467649112807f2691aba39712f8276c532ec605d258d952d7367d6cd31e9f4851885e13d318991756b371200687537752710471422eb48e56ce485886516ec7ac01ae2981880e504560d3a34e328cf1bcf0ca2f34ff9013cb5362973dd8987e077f79305ec32ca42e57438e4029ca11cdfe1c063a5bbcdc13e4e970767ff402e7edc665f7a607df7c8e19685b21ecb0286054c854a588e45b675a10d23212ec9afc8ac25380ca893c6a2d031a4154b778e1cc87c5dfb59d04afc9eb5f5a6339a0fae0c74c1ee877e21a636a2324c97c26f6f64f8749c389b0b78d7c1f4cb67e8aaa683d1b0440f1b2d334433fd92b3a6bcbeb05d19eb10ec2c90b4360fc180478402f5994a95e5b3d7b7a2037234e0c70a4746d87597a7a720b09bbd62ed8d13ff0e4522fc27b56e773e1d44575bca9ee313195d7bba55183a85dc3347fd21ae59105020fa366d9c9ee215f452b78acaa8ccaf2389d37f35832133ea08980c1a176646a01ac66f00eccd797b61691184b29e3749fa136a4f7aaaac9a035c48fe3c69d1671529ba5fb0123014092272821b3137313fb2cd2fce9cf98b95b979f56ad1f6acc1cbc060cd931ddde70769665a94d582dfb8b29bb8ae55a5f780110dfc82e33f3cf7f808836f6761214ac776e1087bf607259bf16bd3477116fda0ed2abdd631a8b0fe538afbf8b7c2f017eba763f59bb8d7f41d5a043389804c2bbdb8fe70830606beae34095b3d1f376e0dff3d65a4b012f7c5338bc17aa2ec4e376b9d50529d577ed199fb370c5d35e0c9b1b07589f80f5c344c9f02d2221aa8aa342160882ffc52a55fb557df7615c3e225f0b6188c95c862ce4f6f852dc7cf38f246599e59359a6c503e0cd17181cd4d44895df7a8397f0da8a9b4f1f0dbe1fc97a511069e4a6529698c12d25e842b670fec89c4131dd2877bf679a31e2ec565aaeebda19c22635a97a8d8e0e64879e7976fbd48f7babce745b59f5c6c97a9436292053200d829b6d1ab111b804c580a9b42454e232c83af4f4c0782b728674bda6964bbac10332cd0811e1a598e5b77f2ca865b6857118d3a50f633e46150d038342bf00fb1ff1db3085a29f7e9176c5da9a87bc374ad7a3b8245e650bf372ea2582bdc876e89e2ee1d4788d10d2336878db914f0ac76838fc822c9d57507cc6deaf1a75fd4608c241c92bcb4e5f3c038d5411d7a4d7e2dacedcdd089712a7dbaf4473ab00a3c8fbe3a134a967dfd4661b4548845bd2430c6fc45d41e6160e777c0431567bbeda0824c383c1dfb6b34cc86c97f398dd28d1e235ccc44707724ef2f85bd899523e3f72ce7fd8ba28ab32afff69a3477f7b110f11217e30c1fa0d381b2fa164b5ab7080aabeb728a3884add4d2154be53853b19dc7ee278f8a2c3de3a5813b3222cc703775949cf2242395a57471e89d2fbb105834c24da5cd97a3a455decf7ed777c16dba6bf53ab6cf9470f10cae1e0cf3d03228478f9ebded681caf39578893d049f735b636fc2aca25555193d32f705091b85a160fbc0e1557b4e08a7a0d53a9551f457250338f2a750a288901af01c0084c8c09565ba3d63e1773ebf6af0cd0c9bedc1273ff53558e9fcc3d0e9485b12fcd80916beecdeaa78ac913e5483380c1b42329eaafd952d08aabf3441e1f88f119abe5c302984bec72790ab182ee4247852d0b721b33fefb22161ead095bd6a2bcbb58e0cfbedd03a3a3d500312426e55e911f6ae79caddb148d18bf60e311caf14677edf8bad0a477052dc2ff195f61367e492911e5a4e7cbf55296eae45c3cd0d3eedc0f061411ea8af9e59f7e6a231d7fbe4171a63c15f1055978ebaa9dd0c51aad9600df64c328fcb929b108f480804c81d59415fb7a34a5e927efd9c46b4cbefc4fc6ee4c278384f887d6f4db98d13b0e2ee76df8c2a9caa39495e9c0c72506e535d6a3a621548adceb4890570b750e15a2ee6067a81d85b26899cb376cf29c4c273b59ab920be8889e93bdd5b3fd9e1e772e207a141f667c75070ea8422f1ef183a988a54c72781b0a25ff384379fb58b6257b8937a7e5586fe25954d01fa096e8961adb1ac6ac4d3dbaa2f39ce4230c35dfe17e3f654c2d2be6af1ef2c4d754bbd706622385b2d7fe08a1a6e4ddd97ad79e8348432b2994517c92ef577c4dbb9654a4b269b74690c4c671fb4280fdfa8451d1114a8ed4de40771ac07fe5af53a4caff6e4947121a4100e86cf70ebef0f13d2ec0f464a5b62ddfced450d16a391e9b3cbd5a29c02a329f467d9126bbb9c6277c83642b843e4e66ae7404e95350ed11bdf68808909c16da254eb419d358153bd3a77e92bc089e7e0f237ece80a271689685025cc3b8b8f0abbeef2809f4124a25188af345923aca679241b0235e5cb3bd305b6aa7f4054505c1fb0dff6cd2ed54070622d7acc41817913ea351d72f087e000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000a35cd3c2" + "status": 200, + "message": "01002dde2fc80d8100f4467649112807f2691aba39712f8276c532ec605d258d952d7367d6cd31e9f4851885e13d318991756b371200687537752710471422eb48e56ce485886516ec7ac01ae2981880e504560d3a34e328cf1bcf0ca2f34ff9013cb5362973dd8987e077f79305ec32ca42e57438e4029ca11cdfe1c063a5bbcdc13e4e970767ff402e7edc665f7a607df7c8e19685b21ecb0286054c854a588e45b675a10d23212ec9afc8ac25380ca893c6a2d031a4154b778e1cc87c5dfb59d04afc9eb5f5a6339a0fae0c74c1ee877e21a636a2324c97c26f6f64f8749c389b0b78d7c1f4cb67e8aaa683d1b0440f1b2d334433fd92b3a6bcbeb05d19eb10ec2c90b4360fc180478402f5994a95e5b3d7b7a2037234e0c70a4746d87597a7a720b09bbd62ed8d13ff0e4522fc27b56e773e1d44575bca9ee313195d7bba55183a85dc3347fd21ae59105020fa366d9c9ee215f452b78acaa8ccaf2389d37f35832133ea08980c1a176646a01ac66f00eccd797b61691184b29e3749fa136a4f7aaaac9a035c48fe3c69d1671529ba5fb0123014092272821b3137313fb2cd2fce9cf98b95b979f56ad1f6acc1cbc060cd931ddde70769665a94d582dfb8b29bb8ae55a5f780110dfc82e33f3cf7f808836f6761214ac776e1087bf607259bf16bd3477116fda0ed2abdd631a8b0fe538afbf8b7c2f017eba763f59bb8d7f41d5a043389804c2bbdb8fe70830606beae34095b3d1f376e0dff3d65a4b012f7c5338bc17aa2ec4e376b9d50529d577ed199fb370c5d35e0c9b1b07589f80f5c344c9f02d2221aa8aa342160882ffc52a55fb557df7615c3e225f0b6188c95c862ce4f6f852dc7cf38f246599e59359a6c503e0cd17181cd4d44895df7a8397f0da8a9b4f1f0dbe1fc97a511069e4a6529698c12d25e842b670fec89c4131dd2877bf679a31e2ec565aaeebda19c22635a97a8d8e0e64879e7976fbd48f7babce745b59f5c6c97a9436292053200d829b6d1ab111b804c580a9b42454e232c83af4f4c0782b728674bda6964bbac10332cd0811e1a598e5b77f2ca865b6857118d3a50f633e46150d038342bf00fb1ff1db3085a29f7e9176c5da9a87bc374ad7a3b8245e650bf372ea2582bdc876e89e2ee1d4788d10d2336878db914f0ac76838fc822c9d57507cc6deaf1a75fd4608c241c92bcb4e5f3c038d5411d7a4d7e2dacedcdd089712a7dbaf4473ab00a3c8fbe3a134a967dfd4661b4548845bd2430c6fc45d41e6160e777c0431567bbeda0824c383c1dfb6b34cc86c97f398dd28d1e235ccc44707724ef2f85bd899523e3f72ce7fd8ba28ab32afff69a3477f7b110f11217e30c1fa0d381b2fa164b5ab7080aabeb728a3884add4d2154be53853b19dc7ee278f8a2c3de3a5813b3222cc703775949cf2242395a57471e89d2fbb105834c24da5cd97a3a455decf7ed777c16dba6bf53ab6cf9470f10cae1e0cf3d03228478f9ebded681caf39578893d049f735b636fc2aca25555193d32f705091b85a160fbc0e1557b4e08a7a0d53a9551f457250338f2a750a288901af01c0084c8c09565ba3d63e1773ebf6af0cd0c9bedc1273ff53558e9fcc3d0e9485b12fcd80916beecdeaa78ac913e5483380c1b42329eaafd952d08aabf3441e1f88f119abe5c302984bec72790ab182ee4247852d0b721b33fefb22161ead095bd6a2bcbb58e0cfbedd03a3a3d500312426e55e911f6ae79caddb148d18bf60e311caf14677edf8bad0a477052dc2ff195f61367e492911e5a4e7cbf55296eae45c3cd0d3eedc0f061411ea8af9e59f7e6a231d7fbe4171a63c15f1055978ebaa9dd0c51aad9600df64c328fcb929b108f480804c81d59415fb7a34a5e927efd9c46b4cbefc4fc6ee4c278384f887d6f4db98d13b0e2ee76df8c2a9caa39495e9c0c72506e535d6a3a621548adceb4890570b750e15a2ee6067a81d85b26899cb376cf29c4c273b59ab920be8889e93bdd5b3fd9e1e772e207a141f667c75070ea8422f1ef183a988a54c72781b0a25ff384379fb58b6257b8937a7e5586fe25954d01fa096e8961adb1ac6ac4d3dbaa2f39ce4230c35dfe17e3f654c2d2be6af1ef2c4d754bbd706622385b2d7fe08a1a6e4ddd97ad79e8348432b2994517c92ef577c4dbb9654a4b269b74690c4c671fb4280fdfa8451d1114a8ed4de40771ac07fe5af53a4caff6e4947121a4100e86cf70ebef0f13d2ec0f464a5b62ddfced450d16a391e9b3cbd5a29c02a329f467d9126bbb9c6277c83642b843e4e66ae7404e95350ed11bdf68808909c16da254eb419d358153bd3a77e92bc089e7e0f237ece80a271689685025cc3b8b8f0abbeef2809f4124a25188af345923aca679241b0235e5cb3bd305b6aa7f4054505c1fb0dff6cd2ed54070622d7acc41817913ea351d72f087e000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000a35cd3c2" } diff --git a/packages/sdk/src/__test__/integration/client.interop.test.ts b/packages/sdk/src/__test__/integration/client.interop.test.ts index defe7638..a3cd7a26 100644 --- a/packages/sdk/src/__test__/integration/client.interop.test.ts +++ b/packages/sdk/src/__test__/integration/client.interop.test.ts @@ -1,23 +1,23 @@ -import { fetchActiveWallets, setup } from '../../api'; -import { getDeviceId } from '../utils/getters'; -import { setupTestClient } from '../utils/helpers'; -import { getStoredClient, setStoredClient } from '../utils/setup'; +import { fetchActiveWallets, setup } from '../../api' +import { getDeviceId } from '../utils/getters' +import { setupTestClient } from '../utils/helpers' +import { getStoredClient, setStoredClient } from '../utils/setup' /** * This test is used to test the interoperability between the Class-based API and the Functional API. */ describe.skip('client interop', () => { - it('should setup the Client, then use that client data to', async () => { - const client = setupTestClient(); - const isPaired = await client.connect(getDeviceId()); - expect(isPaired).toBe(true); + it('should setup the Client, then use that client data to', async () => { + const client = setupTestClient() + const isPaired = await client.connect(getDeviceId()) + expect(isPaired).toBe(true) - await setup({ - getStoredClient, - setStoredClient, - }); + await setup({ + getStoredClient, + setStoredClient, + }) - const activeWallets = await fetchActiveWallets(); - expect(activeWallets).toBeTruthy(); - }); -}); + const activeWallets = await fetchActiveWallets() + expect(activeWallets).toBeTruthy() + }) +}) diff --git a/packages/sdk/src/__test__/integration/connect.test.ts b/packages/sdk/src/__test__/integration/connect.test.ts index 29ce587c..1d7bfe91 100644 --- a/packages/sdk/src/__test__/integration/connect.test.ts +++ b/packages/sdk/src/__test__/integration/connect.test.ts @@ -1,72 +1,72 @@ -import { HARDENED_OFFSET } from '../../constants'; -import { buildEthSignRequest } from '../utils/builders'; -import { getDeviceId } from '../utils/getters'; -import { BTC_PURPOSE_P2PKH, ETH_COIN, setupTestClient } from '../utils/helpers'; +import { HARDENED_OFFSET } from '../../constants' +import { buildEthSignRequest } from '../utils/builders' +import { getDeviceId } from '../utils/getters' +import { BTC_PURPOSE_P2PKH, ETH_COIN, setupTestClient } from '../utils/helpers' describe('connect', () => { - it('should test connect', async () => { - const client = setupTestClient(); - const isPaired = await client.connect(getDeviceId()); - expect(isPaired).toMatchSnapshot(); - }); + it('should test connect', async () => { + const client = setupTestClient() + const isPaired = await client.connect(getDeviceId()) + expect(isPaired).toMatchSnapshot() + }) - it('should test fetchActiveWallet', async () => { - const client = setupTestClient(); - await client.connect(getDeviceId()); - await client.fetchActiveWallet(); - }); + it('should test fetchActiveWallet', async () => { + const client = setupTestClient() + await client.connect(getDeviceId()) + await client.fetchActiveWallet() + }) - it('should test getAddresses', async () => { - const client = setupTestClient(); - await client.connect(getDeviceId()); + it('should test getAddresses', async () => { + const client = setupTestClient() + await client.connect(getDeviceId()) - const startPath = [BTC_PURPOSE_P2PKH, ETH_COIN, HARDENED_OFFSET, 0, 0]; + const startPath = [BTC_PURPOSE_P2PKH, ETH_COIN, HARDENED_OFFSET, 0, 0] - const addrs = await client.getAddresses({ startPath, n: 1 }); - expect(addrs).toMatchSnapshot(); - }); + const addrs = await client.getAddresses({ startPath, n: 1 }) + expect(addrs).toMatchSnapshot() + }) - it('should test sign', async () => { - const client = setupTestClient(); - await client.connect(getDeviceId()); + it('should test sign', async () => { + const client = setupTestClient() + await client.connect(getDeviceId()) - const { req } = await buildEthSignRequest(client); - const signData = await client.sign(req); - expect(signData).toMatchSnapshot(); - }); + const { req } = await buildEthSignRequest(client) + const signData = await client.sign(req) + expect(signData).toMatchSnapshot() + }) - it('should test fetchActiveWallet', async () => { - const client = setupTestClient(); - await client.connect(getDeviceId()); + it('should test fetchActiveWallet', async () => { + const client = setupTestClient() + await client.connect(getDeviceId()) - const activeWallet = await client.fetchActiveWallet(); - expect(activeWallet).toMatchSnapshot(); - }); + const activeWallet = await client.fetchActiveWallet() + expect(activeWallet).toMatchSnapshot() + }) - it('should test getKvRecords', async () => { - const client = setupTestClient(); - await client.connect(getDeviceId()); + it('should test getKvRecords', async () => { + const client = setupTestClient() + await client.connect(getDeviceId()) - const activeWallet = await client.getKvRecords({ start: 0 }); - expect(activeWallet).toMatchSnapshot(); - }); - it('should test addKvRecords', async () => { - const client = setupTestClient(); - await client.connect(getDeviceId()); + const activeWallet = await client.getKvRecords({ start: 0 }) + expect(activeWallet).toMatchSnapshot() + }) + it('should test addKvRecords', async () => { + const client = setupTestClient() + await client.connect(getDeviceId()) - const activeWallet = await client.addKvRecords({ - records: { test2: 'test2' }, - }); - expect(activeWallet).toMatchSnapshot(); - }); - it('should test removeKvRecords', async () => { - const client = setupTestClient(); - await client.connect(getDeviceId()); - await client.addKvRecords({ records: { test: `${Math.random()}` } }); - const { records } = await client.getKvRecords({ start: 0 }); - const activeWallet = await client.removeKvRecords({ - ids: records.map((r) => `${r.id}`), - }); - expect(activeWallet).toMatchSnapshot(); - }); -}); + const activeWallet = await client.addKvRecords({ + records: { test2: 'test2' }, + }) + expect(activeWallet).toMatchSnapshot() + }) + it('should test removeKvRecords', async () => { + const client = setupTestClient() + await client.connect(getDeviceId()) + await client.addKvRecords({ records: { test: `${Math.random()}` } }) + const { records } = await client.getKvRecords({ start: 0 }) + const activeWallet = await client.removeKvRecords({ + ids: records.map((r) => `${r.id}`), + }) + expect(activeWallet).toMatchSnapshot() + }) +}) diff --git a/packages/sdk/src/__test__/integration/fetchCalldataDecoder.test.ts b/packages/sdk/src/__test__/integration/fetchCalldataDecoder.test.ts index e5abe6f4..38fba123 100644 --- a/packages/sdk/src/__test__/integration/fetchCalldataDecoder.test.ts +++ b/packages/sdk/src/__test__/integration/fetchCalldataDecoder.test.ts @@ -1,107 +1,105 @@ -import { fetchCalldataDecoder } from '../../util'; -import { setup as setupMockServiceWorker } from './__mocks__/setup'; +import { fetchCalldataDecoder } from '../../util' +import { setup as setupMockServiceWorker } from './__mocks__/setup' describe('fetchCalldataDecoder', () => { - // Mocks out responses from Etherscan and 4byte - setupMockServiceWorker(); + // Mocks out responses from Etherscan and 4byte + setupMockServiceWorker() - beforeAll(() => { - // Disable this mock to restore console logs when updating tests - console.warn = vi.fn(); - }); + beforeAll(() => { + // Disable this mock to restore console logs when updating tests + console.warn = vi.fn() + }) - afterAll(() => { - vi.clearAllMocks(); - }); + afterAll(() => { + vi.clearAllMocks() + }) - test('decode calldata', async () => { - const data = - '0x38ed17390000000000000000000000000000000000000000000c1c173c5b782a5b154ab900000000000000000000000000000000000000000000000f380d77022fe8c32600000000000000000000000000000000000000000000000000000000000000a00000000000000000000000007ae7684581f0298241c3d6a6567a48d56b42b15c00000000000000000000000000000000000000000000000000000000622f8d27000000000000000000000000000000000000000000000000000000000000000300000000000000000000000095ad61b0a150d79219dcf64e1e6cc01f0b64c4ce000000000000000000000000c02aaa39b223fe8d0a0e5c4f27ead9083c756cc200000000000000000000000050522c769e01eb06c02bd299066509d8f97a69ae'; - const to = '0x7a250d5630b4cf539739df2c5dacb4c659f2488d'; - const decoded = await fetchCalldataDecoder(data, to, '1'); - expect(decoded).toMatchSnapshot(); - }); + test('decode calldata', async () => { + const data = + '0x38ed17390000000000000000000000000000000000000000000c1c173c5b782a5b154ab900000000000000000000000000000000000000000000000f380d77022fe8c32600000000000000000000000000000000000000000000000000000000000000a00000000000000000000000007ae7684581f0298241c3d6a6567a48d56b42b15c00000000000000000000000000000000000000000000000000000000622f8d27000000000000000000000000000000000000000000000000000000000000000300000000000000000000000095ad61b0a150d79219dcf64e1e6cc01f0b64c4ce000000000000000000000000c02aaa39b223fe8d0a0e5c4f27ead9083c756cc200000000000000000000000050522c769e01eb06c02bd299066509d8f97a69ae' + const to = '0x7a250d5630b4cf539739df2c5dacb4c659f2488d' + const decoded = await fetchCalldataDecoder(data, to, '1') + expect(decoded).toMatchSnapshot() + }) - test('decode calldata as Buffer', async () => { - const data = Buffer.from( - '38ed17390000000000000000000000000000000000000000000c1c173c5b782a5b154ab900000000000000000000000000000000000000000000000f380d77022fe8c32600000000000000000000000000000000000000000000000000000000000000a00000000000000000000000007ae7684581f0298241c3d6a6567a48d56b42b15c00000000000000000000000000000000000000000000000000000000622f8d27000000000000000000000000000000000000000000000000000000000000000300000000000000000000000095ad61b0a150d79219dcf64e1e6cc01f0b64c4ce000000000000000000000000c02aaa39b223fe8d0a0e5c4f27ead9083c756cc200000000000000000000000050522c769e01eb06c02bd299066509d8f97a69ae', - 'hex', - ); - const to = '0x7a250d5630b4cf539739df2c5dacb4c659f2488d'; - const decoded = await fetchCalldataDecoder(data, to, '1'); - expect(decoded).toMatchSnapshot(); - }); + test('decode calldata as Buffer', async () => { + const data = Buffer.from( + '38ed17390000000000000000000000000000000000000000000c1c173c5b782a5b154ab900000000000000000000000000000000000000000000000f380d77022fe8c32600000000000000000000000000000000000000000000000000000000000000a00000000000000000000000007ae7684581f0298241c3d6a6567a48d56b42b15c00000000000000000000000000000000000000000000000000000000622f8d27000000000000000000000000000000000000000000000000000000000000000300000000000000000000000095ad61b0a150d79219dcf64e1e6cc01f0b64c4ce000000000000000000000000c02aaa39b223fe8d0a0e5c4f27ead9083c756cc200000000000000000000000050522c769e01eb06c02bd299066509d8f97a69ae', + 'hex', + ) + const to = '0x7a250d5630b4cf539739df2c5dacb4c659f2488d' + const decoded = await fetchCalldataDecoder(data, to, '1') + expect(decoded).toMatchSnapshot() + }) - test('decode proxy calldata', async () => { - const data = - '0xa9059cbb0000000000000000000000004ffbf741b0a64e8bd1f9d89fc9b5584cc5227b700000000000000000000000000000000000000000000000000000003052aacdb8'; - const to = '0xa0b86991c6218b36c1d19d4a2e9eb0ce3606eb48'; - const decoded = await fetchCalldataDecoder(data, to, '1'); - expect(decoded).toMatchSnapshot(); - }); + test('decode proxy calldata', async () => { + const data = '0xa9059cbb0000000000000000000000004ffbf741b0a64e8bd1f9d89fc9b5584cc5227b700000000000000000000000000000000000000000000000000000003052aacdb8' + const to = '0xa0b86991c6218b36c1d19d4a2e9eb0ce3606eb48' + const decoded = await fetchCalldataDecoder(data, to, '1') + expect(decoded).toMatchSnapshot() + }) - test('fallback to 4byte', async () => { - const data = - '0x38ed17390000000000000000000000000000000000000000000c1c173c5b782a5b154ab900000000000000000000000000000000000000000000000f380d77022fe8c32600000000000000000000000000000000000000000000000000000000000000a00000000000000000000000007ae7684581f0298241c3d6a6567a48d56b42b15c00000000000000000000000000000000000000000000000000000000622f8d27000000000000000000000000000000000000000000000000000000000000000300000000000000000000000095ad61b0a150d79219dcf64e1e6cc01f0b64c4ce000000000000000000000000c02aaa39b223fe8d0a0e5c4f27ead9083c756cc200000000000000000000000050522c769e01eb06c02bd299066509d8f97a69ae'; - const to = '0x7a250d5630b4cf539739df2c5dacb4c659f2488d'; - const decoded = await fetchCalldataDecoder(data, to, -1); - expect(decoded).toMatchSnapshot(); - }); + test('fallback to 4byte', async () => { + const data = + '0x38ed17390000000000000000000000000000000000000000000c1c173c5b782a5b154ab900000000000000000000000000000000000000000000000f380d77022fe8c32600000000000000000000000000000000000000000000000000000000000000a00000000000000000000000007ae7684581f0298241c3d6a6567a48d56b42b15c00000000000000000000000000000000000000000000000000000000622f8d27000000000000000000000000000000000000000000000000000000000000000300000000000000000000000095ad61b0a150d79219dcf64e1e6cc01f0b64c4ce000000000000000000000000c02aaa39b223fe8d0a0e5c4f27ead9083c756cc200000000000000000000000050522c769e01eb06c02bd299066509d8f97a69ae' + const to = '0x7a250d5630b4cf539739df2c5dacb4c659f2488d' + const decoded = await fetchCalldataDecoder(data, to, -1) + expect(decoded).toMatchSnapshot() + }) - test('decode calldata from external chain', async () => { - const data = - '0x38ed17390000000000000000000000000000000000000000000c1c173c5b782a5b154ab900000000000000000000000000000000000000000000000f380d77022fe8c32600000000000000000000000000000000000000000000000000000000000000a00000000000000000000000007ae7684581f0298241c3d6a6567a48d56b42b15c00000000000000000000000000000000000000000000000000000000622f8d27000000000000000000000000000000000000000000000000000000000000000300000000000000000000000095ad61b0a150d79219dcf64e1e6cc01f0b64c4ce000000000000000000000000c02aaa39b223fe8d0a0e5c4f27ead9083c756cc200000000000000000000000050522c769e01eb06c02bd299066509d8f97a69ae'; - const to = '0xa0b86991c6218b36c1d19d4a2e9eb0ce3606eb48'; - const decoded = await fetchCalldataDecoder(data, to, '1337'); - expect(decoded).toMatchSnapshot(); - }); + test('decode calldata from external chain', async () => { + const data = + '0x38ed17390000000000000000000000000000000000000000000c1c173c5b782a5b154ab900000000000000000000000000000000000000000000000f380d77022fe8c32600000000000000000000000000000000000000000000000000000000000000a00000000000000000000000007ae7684581f0298241c3d6a6567a48d56b42b15c00000000000000000000000000000000000000000000000000000000622f8d27000000000000000000000000000000000000000000000000000000000000000300000000000000000000000095ad61b0a150d79219dcf64e1e6cc01f0b64c4ce000000000000000000000000c02aaa39b223fe8d0a0e5c4f27ead9083c756cc200000000000000000000000050522c769e01eb06c02bd299066509d8f97a69ae' + const to = '0xa0b86991c6218b36c1d19d4a2e9eb0ce3606eb48' + const decoded = await fetchCalldataDecoder(data, to, '1337') + expect(decoded).toMatchSnapshot() + }) - test('decode nested calldata: multicall(bytes[])', async () => { - const data = - '0xac9650d8000000000000000000000000000000000000000000000000000000000000002000000000000000000000000000000000000000000000000000000000000000020000000000000000000000000000000000000000000000000000000000000040000000000000000000000000000000000000000000000000000000000000012000000000000000000000000000000000000000000000000000000000000000a40c49ccbe000000000000000000000000000000000000000000000000000000000002d6da00000000000000000000000000000000000000000000000000000b100a58bd63000000000000000000000000000000000000000000000000000016ac77cb53fe00000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000061ef0508000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000084fc6f7865000000000000000000000000000000000000000000000000000000000002d6da0000000000000000000000004ce6aea89f059915ae5efbf34a2a8adc544ae09e00000000000000000000000000000000ffffffffffffffffffffffffffffffff00000000000000000000000000000000ffffffffffffffffffffffffffffffff00000000000000000000000000000000000000000000000000000000'; - const to = '0xc36442b4a4522e871399cd717abdd847ab11fe88'; - const { def } = await fetchCalldataDecoder(data, to, '1', true); - expect({ def }).toMatchSnapshot(); - }); + test('decode nested calldata: multicall(bytes[])', async () => { + const data = + '0xac9650d8000000000000000000000000000000000000000000000000000000000000002000000000000000000000000000000000000000000000000000000000000000020000000000000000000000000000000000000000000000000000000000000040000000000000000000000000000000000000000000000000000000000000012000000000000000000000000000000000000000000000000000000000000000a40c49ccbe000000000000000000000000000000000000000000000000000000000002d6da00000000000000000000000000000000000000000000000000000b100a58bd63000000000000000000000000000000000000000000000000000016ac77cb53fe00000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000061ef0508000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000084fc6f7865000000000000000000000000000000000000000000000000000000000002d6da0000000000000000000000004ce6aea89f059915ae5efbf34a2a8adc544ae09e00000000000000000000000000000000ffffffffffffffffffffffffffffffff00000000000000000000000000000000ffffffffffffffffffffffffffffffff00000000000000000000000000000000000000000000000000000000' + const to = '0xc36442b4a4522e871399cd717abdd847ab11fe88' + const { def } = await fetchCalldataDecoder(data, to, '1', true) + expect({ def }).toMatchSnapshot() + }) - test('decode nested calldata: execTransaction(address,uint256,(multicall(bytes[])),uint8,uint256,uint256,uint256,address,address,bytes)', async () => { - const data = - '0x6a761202000000000000000000000000c36442b4a4522e871399cd717abdd847ab11fe880000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000014000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000036e3f000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000003a00000000000000000000000000000000000000000000000000000000000000224ac9650d8000000000000000000000000000000000000000000000000000000000000002000000000000000000000000000000000000000000000000000000000000000020000000000000000000000000000000000000000000000000000000000000040000000000000000000000000000000000000000000000000000000000000012000000000000000000000000000000000000000000000000000000000000000a40c49ccbe0000000000000000000000000000000000000000000000000000000000042677000000000000000000000000000000000000000000000000002223dbc72b15a70000000000000000000000000000000000000000000000000000014f8d4596e400000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000062e90b1b000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000084fc6f7865000000000000000000000000000000000000000000000000000000000004267700000000000000000000000006412d7ebfbf66c25607e2ed24c1d207043be32700000000000000000000000000000000ffffffffffffffffffffffffffffffff00000000000000000000000000000000ffffffffffffffffffffffffffffffff000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000c351d12bf187b0b2cb69bcf58076f8ce9197ebc4ff5fbb97ca5f36d296cfc55ffe4e62e761ab9d1a5d27577d76dc18f1e851e6ec87a239bedcbbef8bb52bd811501bc23b15b14d797a0cc444b9ab5e0a9340b8f1341210778446c948f5120b282b105ae55d0aef0bf62af4e614d42a9c99b2724272486433a191ed16ecaaab81dab61c0000000000000000000000009789d9d99409bf01699b8988da8886647418998e0000000000000000000000000000000000000000000000000000000000000000010000000000000000000000000000000000000000000000000000000000'; - const to = '0x06412d7ebfbf66c25607e2ed24c1d207043be327'; - const { def } = await fetchCalldataDecoder(data, to, '1', true); - expect({ def }).toMatchSnapshot(); - }); + test('decode nested calldata: execTransaction(address,uint256,(multicall(bytes[])),uint8,uint256,uint256,uint256,address,address,bytes)', async () => { + const data = + '0x6a761202000000000000000000000000c36442b4a4522e871399cd717abdd847ab11fe880000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000014000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000036e3f000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000003a00000000000000000000000000000000000000000000000000000000000000224ac9650d8000000000000000000000000000000000000000000000000000000000000002000000000000000000000000000000000000000000000000000000000000000020000000000000000000000000000000000000000000000000000000000000040000000000000000000000000000000000000000000000000000000000000012000000000000000000000000000000000000000000000000000000000000000a40c49ccbe0000000000000000000000000000000000000000000000000000000000042677000000000000000000000000000000000000000000000000002223dbc72b15a70000000000000000000000000000000000000000000000000000014f8d4596e400000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000062e90b1b000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000084fc6f7865000000000000000000000000000000000000000000000000000000000004267700000000000000000000000006412d7ebfbf66c25607e2ed24c1d207043be32700000000000000000000000000000000ffffffffffffffffffffffffffffffff00000000000000000000000000000000ffffffffffffffffffffffffffffffff000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000c351d12bf187b0b2cb69bcf58076f8ce9197ebc4ff5fbb97ca5f36d296cfc55ffe4e62e761ab9d1a5d27577d76dc18f1e851e6ec87a239bedcbbef8bb52bd811501bc23b15b14d797a0cc444b9ab5e0a9340b8f1341210778446c948f5120b282b105ae55d0aef0bf62af4e614d42a9c99b2724272486433a191ed16ecaaab81dab61c0000000000000000000000009789d9d99409bf01699b8988da8886647418998e0000000000000000000000000000000000000000000000000000000000000000010000000000000000000000000000000000000000000000000000000000' + const to = '0x06412d7ebfbf66c25607e2ed24c1d207043be327' + const { def } = await fetchCalldataDecoder(data, to, '1', true) + expect({ def }).toMatchSnapshot() + }) - test('handle too short data', async () => { - const data = '0x001'; - const to = '0xa0b86991c6218b36c1d19d4a2e9eb0ce3606eb48'; - const decoded = await fetchCalldataDecoder(data, to, '1'); - expect(decoded).toMatchInlineSnapshot(` + test('handle too short data', async () => { + const data = '0x001' + const to = '0xa0b86991c6218b36c1d19d4a2e9eb0ce3606eb48' + const decoded = await fetchCalldataDecoder(data, to, '1') + expect(decoded).toMatchInlineSnapshot(` { "abi": null, "def": null, } - `); - }); + `) + }) - test('handle no data', async () => { - const data = ''; - const to = '0xa0b86991c6218b36c1d19d4a2e9eb0ce3606eb48'; - const decoded = await fetchCalldataDecoder(data, to, '1'); - expect(decoded).toMatchInlineSnapshot(` + test('handle no data', async () => { + const data = '' + const to = '0xa0b86991c6218b36c1d19d4a2e9eb0ce3606eb48' + const decoded = await fetchCalldataDecoder(data, to, '1') + expect(decoded).toMatchInlineSnapshot(` { "abi": null, "def": null, } - `); - }); + `) + }) - // TODO: add api key to fix this test - test.skip('decode Celo calldata', async () => { - const data = - '0xf2fde38b000000000000000000000000b538e8dcd297450bdef46222f3ceb33bb1e921b3'; - const to = '0x96d59127ccd1c0e3749e733ee04f0dfbd2f808c8'; - const decoded = await fetchCalldataDecoder(data, to, '42220'); - expect(decoded).toMatchSnapshot(); - }); -}); + // TODO: add api key to fix this test + test.skip('decode Celo calldata', async () => { + const data = '0xf2fde38b000000000000000000000000b538e8dcd297450bdef46222f3ceb33bb1e921b3' + const to = '0x96d59127ccd1c0e3749e733ee04f0dfbd2f808c8' + const decoded = await fetchCalldataDecoder(data, to, '42220') + expect(decoded).toMatchSnapshot() + }) +}) diff --git a/packages/sdk/src/__test__/unit/__mocks__/decoderData.ts b/packages/sdk/src/__test__/unit/__mocks__/decoderData.ts index faec69c6..e4d0d4d6 100644 --- a/packages/sdk/src/__test__/unit/__mocks__/decoderData.ts +++ b/packages/sdk/src/__test__/unit/__mocks__/decoderData.ts @@ -3,107 +3,90 @@ * For encrypted requests, the results are decrypted. * For each response, the response code and checksum are removed. */ -import { - LatticeEncDataSchema, - LatticeGetAddressesFlag, -} from '../../../protocol'; -import { getP256KeyPair } from '../../../util'; +import { LatticeEncDataSchema, LatticeGetAddressesFlag } from '../../../protocol' +import { getP256KeyPair } from '../../../util' -export const clientKeyPair = getP256KeyPair( - Buffer.from( - '3fb53b677f73e4d2b8c89c303f6f6b349f0075ad88ea126cb9f6632085815dca', - 'hex', - ), -); +export const clientKeyPair = getP256KeyPair(Buffer.from('3fb53b677f73e4d2b8c89c303f6f6b349f0075ad88ea126cb9f6632085815dca', 'hex')) export const decoderTestsFwConstants = JSON.parse( - '{"extraDataFrameSz":1500,"extraDataMaxFrames":1,"genericSigning":{"baseReqSz":1552,"baseDataSz":1519,"hashTypes":{"NONE":0,"KECCAK256":1,"SHA256":2},"curveTypes":{"SECP256K1":0,"ED25519":1,"BLS12_381_G2":2},"encodingTypes":{"NONE":1,"SOLANA":2,"EVM":4,"ETH_DEPOSIT":5},"calldataDecoding":{"reserved":2895728,"maxSz":1024}},"reqMaxDataSz":1678,"ethMaxGasPrice":20000000000000,"addrFlagsAllowed":true,"ethMaxDataSz":1519,"ethMaxMsgSz":1540,"eip712MaxTypeParams":36,"varAddrPathSzAllowed":true,"eip712Supported":true,"prehashAllowed":true,"ethMsgPreHashAllowed":true,"allowedEthTxTypes":[1,2],"personalSignHeaderSz":72,"kvActionsAllowed":true,"kvKeyMaxStrSz":63,"kvValMaxStrSz":63,"kvActionMaxNum":10,"kvRemoveMaxNum":100,"allowBtcLegacyAndSegwitAddrs":true,"contractDeployKey":"0x08002e0fec8e6acf00835f43c9764f7364fa3f42","abiCategorySz":32,"abiMaxRmv":200,"getAddressFlags":[4,3,5],"maxDecoderBufSz":1600}', -); + '{"extraDataFrameSz":1500,"extraDataMaxFrames":1,"genericSigning":{"baseReqSz":1552,"baseDataSz":1519,"hashTypes":{"NONE":0,"KECCAK256":1,"SHA256":2},"curveTypes":{"SECP256K1":0,"ED25519":1,"BLS12_381_G2":2},"encodingTypes":{"NONE":1,"SOLANA":2,"EVM":4,"ETH_DEPOSIT":5},"calldataDecoding":{"reserved":2895728,"maxSz":1024}},"reqMaxDataSz":1678,"ethMaxGasPrice":20000000000000,"addrFlagsAllowed":true,"ethMaxDataSz":1519,"ethMaxMsgSz":1540,"eip712MaxTypeParams":36,"varAddrPathSzAllowed":true,"eip712Supported":true,"prehashAllowed":true,"ethMsgPreHashAllowed":true,"allowedEthTxTypes":[1,2],"personalSignHeaderSz":72,"kvActionsAllowed":true,"kvKeyMaxStrSz":63,"kvValMaxStrSz":63,"kvActionMaxNum":10,"kvRemoveMaxNum":100,"allowBtcLegacyAndSegwitAddrs":true,"contractDeployKey":"0x08002e0fec8e6acf00835f43c9764f7364fa3f42","abiCategorySz":32,"abiMaxRmv":200,"getAddressFlags":[4,3,5],"maxDecoderBufSz":1600}', +) export const connectDecoderData = Buffer.from( - '0104de558941cc182423e1fa6b0ee81b2c17c6203d0c2897929f900480a8b879261993500d7c0bb5b80f75e2ca462681fcaa20d0261775d3204c6ee461c9250ee1d60011000022cfce60cc7995770a9d2c5080351ae2068c71cecb766de54c65053f5662337809baf7d9ddaaafca95180611fb37601c8eebc1d9f967c061edee1511309a70fbe2491a266f84dc5d1c868b6e129170fa3b777ebd9af7c047fd6dff2a8a563cf6b8ea8c6157b33bc5a0614c65369ffc3c4d35537f37f197f9bae12c574b9847174e38c41b0302833ebbd2101237703794', - 'hex', -); + '0104de558941cc182423e1fa6b0ee81b2c17c6203d0c2897929f900480a8b879261993500d7c0bb5b80f75e2ca462681fcaa20d0261775d3204c6ee461c9250ee1d60011000022cfce60cc7995770a9d2c5080351ae2068c71cecb766de54c65053f5662337809baf7d9ddaaafca95180611fb37601c8eebc1d9f967c061edee1511309a70fbe2491a266f84dc5d1c868b6e129170fa3b777ebd9af7c047fd6dff2a8a563cf6b8ea8c6157b33bc5a0614c65369ffc3c4d35537f37f197f9bae12c574b9847174e38c41b0302833ebbd2101237703794', + 'hex', +) -export const getAddressesFlag = LatticeGetAddressesFlag.none; +export const getAddressesFlag = LatticeGetAddressesFlag.none export const getAddressesDecoderData = Buffer.from( - '334d32465857534a7569584d4d79737179534d52566e4d655935335141545a664459000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000033435757456366725447394441336648376955714562506b6d504c6b5a396d416838000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000033384b734472704a3556524553516150364163785072704875554a6d5270397a646800000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000003351665444387372784a636742685958667557443555505955375146616258476733000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000033426a685344394a386b50553648346b52475071614d5a58485a4134726f447a344c0000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000', - 'hex', -); + '334d32465857534a7569584d4d79737179534d52566e4d655935335141545a664459000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000033435757456366725447394441336648376955714562506b6d504c6b5a396d416838000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000033384b734472704a3556524553516150364163785072704875554a6d5270397a646800000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000003351665444387372784a636742685958667557443555505955375146616258476733000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000033426a685344394a386b50553648346b52475071614d5a58485a4134726f447a344c0000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000', + 'hex', +) export const signGenericRequest = { - payload: Buffer.from( - '040000000100050000002c0000803c000080000000800000000000000000004e0001f84b01808447868c0082c35094e242e54155b1abc71fc118065270cecaaf8b776885e8d4a51000a417e914679b7e160613be4f8c2d3203d236286d74eb9192f6d6f71b9118a42bb033ccd8e8c000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000', - 'hex', - ), - extraDataPayloads: [], - schema: 5, - curveType: 0, - encodingType: 4, - hashType: 1, - omitPubkey: false, - origPayloadBuf: Buffer.from( - '01f84b01808447868c0082c35094e242e54155b1abc71fc118065270cecaaf8b776885e8d4a51000a417e914679b7e160613be4f8c2d3203d236286d74eb9192f6d6f71b9118a42bb033ccd8e8c0', - 'hex', - ), -}; + payload: Buffer.from( + '040000000100050000002c0000803c000080000000800000000000000000004e0001f84b01808447868c0082c35094e242e54155b1abc71fc118065270cecaaf8b776885e8d4a51000a417e914679b7e160613be4f8c2d3203d236286d74eb9192f6d6f71b9118a42bb033ccd8e8c000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000', + 'hex', + ), + extraDataPayloads: [], + schema: 5, + curveType: 0, + encodingType: 4, + hashType: 1, + omitPubkey: false, + origPayloadBuf: Buffer.from('01f84b01808447868c0082c35094e242e54155b1abc71fc118065270cecaaf8b776885e8d4a51000a417e914679b7e160613be4f8c2d3203d236286d74eb9192f6d6f71b9118a42bb033ccd8e8c0', 'hex'), +} export const signGenericDecoderData = Buffer.from( - '04a50d7d8e5bf6353086dfaff71652a223aa13e02273a2b6bf5a145314b544be1281ac8f78d035874a06b11e3df68e45f7630b2e6ba3be0f51f916fbb6f0a6403930440220640b2c690858ab8d0b9500f9ed64c9aa6b7467b77f1199b061aa96ea780aadaa022048f830f9290dd1b3eaf1922e08a8c992873be1162bd6d5bef681cf911328abe500000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000', - 'hex', -); + '04a50d7d8e5bf6353086dfaff71652a223aa13e02273a2b6bf5a145314b544be1281ac8f78d035874a06b11e3df68e45f7630b2e6ba3be0f51f916fbb6f0a6403930440220640b2c690858ab8d0b9500f9ed64c9aa6b7467b77f1199b061aa96ea780aadaa022048f830f9290dd1b3eaf1922e08a8c992873be1162bd6d5bef681cf911328abe500000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000', + 'hex', +) export const signBitcoinRequest = { - schema: 0, - // NOTE: This data was tricky to fetch because JSON stringify and buffers don't match well - origData: { - prevOuts: [ - { - txHash: Buffer.from( - 'b2efdbdd3340d2bc547671ce3993a6f05d70343c07578f9d7f5626fdfc06fa35', - 'hex', - ), - value: 76800, - index: 0, - signerPath: [2147483732, 2147483649, 2147483648, 0, 0], - }, - ], - recipient: '2N4gqWT4oqWL2gz9ps92z9fm2Bg3FUkqG7Q', - value: 70000, - fee: 4380, - isSegwit: true, - changePath: [2147483732, 2147483649, 2147483648, 1, 0], - fwConstants: decoderTestsFwConstants, - }, - changeData: { value: 2420 }, - payload: Buffer.from( - 'f00500000054000080010000800000008001000000000000001c110000c47d816ef0a39d6497963ebcf24d05242d51ada74370110100000000000105000000540000800100008000000080000000000000000000000000002c01000000000004b2efdbdd3340d2bc547671ce3993a6f05d70343c07578f9d7f5626fdfc06fa35', - 'hex', - ), -}; + schema: 0, + // NOTE: This data was tricky to fetch because JSON stringify and buffers don't match well + origData: { + prevOuts: [ + { + txHash: Buffer.from('b2efdbdd3340d2bc547671ce3993a6f05d70343c07578f9d7f5626fdfc06fa35', 'hex'), + value: 76800, + index: 0, + signerPath: [2147483732, 2147483649, 2147483648, 0, 0], + }, + ], + recipient: '2N4gqWT4oqWL2gz9ps92z9fm2Bg3FUkqG7Q', + value: 70000, + fee: 4380, + isSegwit: true, + changePath: [2147483732, 2147483649, 2147483648, 1, 0], + fwConstants: decoderTestsFwConstants, + }, + changeData: { value: 2420 }, + payload: Buffer.from( + 'f00500000054000080010000800000008001000000000000001c110000c47d816ef0a39d6497963ebcf24d05242d51ada74370110100000000000105000000540000800100008000000080000000000000000000000000002c01000000000004b2efdbdd3340d2bc547671ce3993a6f05d70343c07578f9d7f5626fdfc06fa35', + 'hex', + ), +} export const signBitcoinDecoderData = Buffer.from( - '6bb07ddb748b655c8478581af3e128335c16eca0304502210084e356184a7dc1e05a08808cb6da03f9e5d1c37be0f382a761eac2a266a4737f0220172d99b82cf78bf5bb4299e4e663f13e1e4578d144ffeabc474631fb1ac1a4fe00000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000002d423e4c1cc57744a0e7365954e7a632ab272f5d0167337f69227c58e6e2d113e000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000', - 'hex', -); + '6bb07ddb748b655c8478581af3e128335c16eca0304502210084e356184a7dc1e05a08808cb6da03f9e5d1c37be0f382a761eac2a266a4737f0220172d99b82cf78bf5bb4299e4e663f13e1e4578d144ffeabc474631fb1ac1a4fe00000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000002d423e4c1cc57744a0e7365954e7a632ab272f5d0167337f69227c58e6e2d113e000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000', + 'hex', +) // Uses `decoderTestsFwConstants` export const getKvRecordsDecoderData = Buffer.from( - '00000001013f53e5d800000000012b3078333064613364374138363543393334623338396339313963373337353130303534313131414233410000000000000000000000000000000000000000000012546573742041646472657373204e616d650000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000', - 'hex', -); + '00000001013f53e5d800000000012b3078333064613364374138363543393334623338396339313963373337353130303534313131414233410000000000000000000000000000000000000000000012546573742041646472657373204e616d650000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000', + 'hex', +) export const fetchEncryptedDataRequest = { - schema: LatticeEncDataSchema.eip2335, - params: { - path: [12381, 3600, 0, 0], - c: 999, - walletUID: Buffer.from( - '6ae62c0c96c1e039fc97bfeb7c2428c093fe7f0b6188a434bbac7b652c3e4012', - 'hex', - ), - }, -}; + schema: LatticeEncDataSchema.eip2335, + params: { + path: [12381, 3600, 0, 0], + c: 999, + walletUID: Buffer.from('6ae62c0c96c1e039fc97bfeb7c2428c093fe7f0b6188a434bbac7b652c3e4012', 'hex'), + }, +} export const fetchEncryptedDataDecoderData = Buffer.from( - 'a4000000e703000077051b0f03811b1b0bdb48e44977ac70c685312aa6df31c8b3491a9de63b6266f3d76007aba10d19c51ffd031101ac78089c7325d3168d492509735d7f064bfc7f057f2a33451a665c3a8e16dc552e0b1c386f922a80dfd7208ef98afa499dcd2fc5b879b78281c8e5b699904dbbaba690a9a242b1aa0cd4458398c77497200e485f55c16b1e7a3146ac74bff42872d2f76e63689be7066f557e985ebd671114000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000', - 'hex', -); + 'a4000000e703000077051b0f03811b1b0bdb48e44977ac70c685312aa6df31c8b3491a9de63b6266f3d76007aba10d19c51ffd031101ac78089c7325d3168d492509735d7f064bfc7f057f2a33451a665c3a8e16dc552e0b1c386f922a80dfd7208ef98afa499dcd2fc5b879b78281c8e5b699904dbbaba690a9a242b1aa0cd4458398c77497200e485f55c16b1e7a3146ac74bff42872d2f76e63689be7066f557e985ebd671114000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000', + 'hex', +) diff --git a/packages/sdk/src/__test__/unit/api.test.ts b/packages/sdk/src/__test__/unit/api.test.ts index 99a08dce..f9e850ef 100644 --- a/packages/sdk/src/__test__/unit/api.test.ts +++ b/packages/sdk/src/__test__/unit/api.test.ts @@ -1,59 +1,57 @@ -import { parseDerivationPath } from '../../api/utilities'; +import { parseDerivationPath } from '../../api/utilities' describe('parseDerivationPath', () => { - it('parses a simple derivation path correctly', () => { - const result = parseDerivationPath('44/0/0/0'); - expect(result).toEqual([44, 0, 0, 0]); - }); - - it('parses a derivation path with hardened indices correctly', () => { - const result = parseDerivationPath("44'/0'/0'/0"); - expect(result).toEqual([0x8000002c, 0x80000000, 0x80000000, 0]); - }); - - it('handles mixed hardened and non-hardened indices', () => { - const result = parseDerivationPath("44'/60/0'/0/0"); - expect(result).toEqual([0x8000002c, 60, 0x80000000, 0, 0]); - }); - - it('interprets lowercase x as 0', () => { - const result = parseDerivationPath('44/x/0/0'); - expect(result).toEqual([44, 0, 0, 0]); - }); - - it('interprets uppercase X as 0', () => { - const result = parseDerivationPath('44/X/0/0'); - expect(result).toEqual([44, 0, 0, 0]); - }); - - it("interprets X' as hardened zero", () => { - const result = parseDerivationPath("44'/X'/0/0"); - expect(result).toEqual([0x8000002c, 0x80000000, 0, 0]); - }); - - it("interprets x' as hardened zero", () => { - const result = parseDerivationPath("44'/x'/0/0"); - expect(result).toEqual([0x8000002c, 0x80000000, 0, 0]); - }); - - it('handles a complex path with all features', () => { - const result = parseDerivationPath("44'/501'/X'/0'"); - expect(result).toEqual([0x8000002c, 0x800001f5, 0x80000000, 0x80000000]); - }); - - it('returns an empty array for an empty path', () => { - const result = parseDerivationPath(''); - expect(result).toEqual([]); - }); - - it('handles leading slash correctly', () => { - const result = parseDerivationPath('/44/0/0/0'); - expect(result).toEqual([44, 0, 0, 0]); - }); - - it('throws error for invalid input', () => { - expect(() => parseDerivationPath('invalid/path')).toThrow( - 'Invalid part in derivation path: invalid', - ); - }); -}); + it('parses a simple derivation path correctly', () => { + const result = parseDerivationPath('44/0/0/0') + expect(result).toEqual([44, 0, 0, 0]) + }) + + it('parses a derivation path with hardened indices correctly', () => { + const result = parseDerivationPath("44'/0'/0'/0") + expect(result).toEqual([0x8000002c, 0x80000000, 0x80000000, 0]) + }) + + it('handles mixed hardened and non-hardened indices', () => { + const result = parseDerivationPath("44'/60/0'/0/0") + expect(result).toEqual([0x8000002c, 60, 0x80000000, 0, 0]) + }) + + it('interprets lowercase x as 0', () => { + const result = parseDerivationPath('44/x/0/0') + expect(result).toEqual([44, 0, 0, 0]) + }) + + it('interprets uppercase X as 0', () => { + const result = parseDerivationPath('44/X/0/0') + expect(result).toEqual([44, 0, 0, 0]) + }) + + it("interprets X' as hardened zero", () => { + const result = parseDerivationPath("44'/X'/0/0") + expect(result).toEqual([0x8000002c, 0x80000000, 0, 0]) + }) + + it("interprets x' as hardened zero", () => { + const result = parseDerivationPath("44'/x'/0/0") + expect(result).toEqual([0x8000002c, 0x80000000, 0, 0]) + }) + + it('handles a complex path with all features', () => { + const result = parseDerivationPath("44'/501'/X'/0'") + expect(result).toEqual([0x8000002c, 0x800001f5, 0x80000000, 0x80000000]) + }) + + it('returns an empty array for an empty path', () => { + const result = parseDerivationPath('') + expect(result).toEqual([]) + }) + + it('handles leading slash correctly', () => { + const result = parseDerivationPath('/44/0/0/0') + expect(result).toEqual([44, 0, 0, 0]) + }) + + it('throws error for invalid input', () => { + expect(() => parseDerivationPath('invalid/path')).toThrow('Invalid part in derivation path: invalid') + }) +}) diff --git a/packages/sdk/src/__test__/unit/compareEIP7702Serialization.test.ts b/packages/sdk/src/__test__/unit/compareEIP7702Serialization.test.ts index 97e54ef4..4e65d84e 100644 --- a/packages/sdk/src/__test__/unit/compareEIP7702Serialization.test.ts +++ b/packages/sdk/src/__test__/unit/compareEIP7702Serialization.test.ts @@ -1,419 +1,400 @@ -import { Hash } from 'ox'; -import { parseEther, serializeTransaction, toHex } from 'viem'; -import { serializeEIP7702Transaction } from '../../ethereum'; -import type { - EIP7702AuthListTransactionRequest as EIP7702AuthListTransaction, - EIP7702AuthTransactionRequest as EIP7702AuthTransaction, -} from '../../types'; +import { Hash } from 'ox' +import { parseEther, serializeTransaction, toHex } from 'viem' +import { serializeEIP7702Transaction } from '../../ethereum' +import type { EIP7702AuthListTransactionRequest as EIP7702AuthListTransaction, EIP7702AuthTransactionRequest as EIP7702AuthTransaction } from '../../types' describe('EIP7702 Transaction Serialization Comparison', () => { - /** - * Simple minimal test case to identify differences - */ - test('minimal single authorization transaction', async () => { - // Create a minimal transaction for easier comparison - const tx: EIP7702AuthTransaction = { - type: 4, - chainId: 1, - nonce: 0, - maxPriorityFeePerGas: '0x1', - maxFeePerGas: '0x2', - gasLimit: '0x3', - to: '0x1111111111111111111111111111111111111111', - value: '0x0', - data: '0x', - accessList: [], - authorization: { - chainId: 1, - address: '0x2222222222222222222222222222222222222222', - nonce: 0, - yParity: 0, - r: '0x0000000000000000000000000000000000000000000000000000000000000001', - s: '0x0000000000000000000000000000000000000000000000000000000000000002', - }, - }; - - // Convert to Viem's transaction format - const viemTx = { - type: 'eip7702', - chainId: 1, - nonce: 0, - maxPriorityFeePerGas: BigInt('0x1'), - maxFeePerGas: BigInt('0x2'), - gas: BigInt('0x3'), - to: '0x1111111111111111111111111111111111111111', - value: BigInt('0x0'), - data: '0x', - accessList: [], - authorizationList: [ - { - chainId: 1, - address: '0x2222222222222222222222222222222222222222', - nonce: 0, - signature: { - yParity: 0, - r: '0x0000000000000000000000000000000000000000000000000000000000000001', - s: '0x0000000000000000000000000000000000000000000000000000000000000002', - }, - }, - ], - }; - - // Serialize using our implementation - const ourSerialized = serializeEIP7702Transaction(tx); - - // Serialize using Viem - const viemSerialized = serializeTransaction(viemTx as any); - - // Output raw serialized data for debugging - console.log('Our serialized (minimal):', ourSerialized); - console.log('Viem serialized (minimal):', viemSerialized); - - // Output serialized by byte - console.log( - 'Our bytes:', - Buffer.from(ourSerialized.slice(2), 'hex') - .toString('hex') - .match(/.{1,2}/g) - ?.join(' '), - ); - console.log( - 'Viem bytes:', - Buffer.from(viemSerialized.slice(2), 'hex') - .toString('hex') - .match(/.{1,2}/g) - ?.join(' '), - ); - - // Compare the serialized transactions - expect(ourSerialized).toEqual(viemSerialized); - }); - - /** - * Test case comparing our implementation of EIP-7702 transaction serialization with Viem's implementation - * - * This ensures that our serialization matches Viem's expected format, - * providing compatibility with external libraries and tools. - */ - test('single authorization transaction serialization matches Viem', async () => { - // Create a single authorization transaction with deterministic values - const tx: EIP7702AuthTransaction = { - type: 4, // Must be exactly 4 for auth transaction - chainId: 1, // Mainnet - nonce: 0, - maxPriorityFeePerGas: toHex(parseEther('0.000000001')), // 1 gwei - maxFeePerGas: toHex(parseEther('0.00000001')), // 10 gwei - gasLimit: toHex(BigInt(21000)), - to: '0x1111111111111111111111111111111111111111', // Simple address for testing - value: toHex(parseEther('1.0')), // 1 ETH - data: '0x', - accessList: [], - authorization: { - chainId: 1, - address: '0x2222222222222222222222222222222222222222', - nonce: 0, - yParity: 0, // Known signature values for deterministic test - r: '0x1111111111111111111111111111111111111111111111111111111111111111', - s: '0x2222222222222222222222222222222222222222222222222222222222222222', - }, - }; - - // Convert to Viem's transaction format - const viemTx = { - type: 'eip7702', - chainId: 1, - nonce: 0, - maxPriorityFeePerGas: BigInt(parseEther('0.000000001')), - maxFeePerGas: BigInt(parseEther('0.00000001')), - gas: BigInt(21000), - to: '0x1111111111111111111111111111111111111111', - value: BigInt(parseEther('1.0')), - data: '0x', - accessList: [], - authorizationList: [ - { - chainId: 1, - address: '0x2222222222222222222222222222222222222222', - nonce: 0, - signature: { - yParity: 0, - r: '0x1111111111111111111111111111111111111111111111111111111111111111', - s: '0x2222222222222222222222222222222222222222222222222222222222222222', - }, - }, - ], - }; - - // Serialize using our implementation - const ourSerialized = serializeEIP7702Transaction(tx); - - // Serialize using Viem - const viemSerialized = serializeTransaction(viemTx as any); - - // Compute hashes for comparison - const ourHash = `0x${Buffer.from( - Hash.keccak256(Buffer.from(ourSerialized.slice(2), 'hex')), - ).toString('hex')}`; - const viemHash = `0x${Buffer.from( - Hash.keccak256(Buffer.from(viemSerialized.slice(2), 'hex')), - ).toString('hex')}`; - - // Output for debugging - console.log('Our serialized:', ourSerialized); - console.log('Viem serialized:', viemSerialized); - console.log('Our hash:', ourHash); - console.log('Viem hash:', viemHash); - - // Compare the serialized transactions - expect(ourSerialized).toEqual(viemSerialized); - }); - - /** - * Test case for serializing an EIP-7702 authorization list transaction (type 5) - * comparing our implementation with Viem's implementation. - */ - test('authorization list transaction serialization matches Viem', async () => { - const tx: EIP7702AuthListTransaction = { - type: 5, // Must be exactly 5 for auth list transaction - chainId: 1, // Mainnet - nonce: 0, - maxPriorityFeePerGas: toHex(parseEther('0.000000001')), // 1 gwei - maxFeePerGas: toHex(parseEther('0.00000001')), // 10 gwei - gasLimit: toHex(BigInt(21000)), - to: '0x1111111111111111111111111111111111111111', // Simple address for testing - value: toHex(parseEther('1.0')), // 1 ETH - data: '0x', - accessList: [], - authorizationList: [ - { - chainId: 1, - address: '0x2222222222222222222222222222222222222222', - nonce: 0, - yParity: 0, // Known signature values for deterministic test - r: '0x1111111111111111111111111111111111111111111111111111111111111111', - s: '0x2222222222222222222222222222222222222222222222222222222222222222', - }, - { - chainId: 1, - address: '0x3333333333333333333333333333333333333333', - nonce: 0, - yParity: 1, // Different signature - r: '0x3333333333333333333333333333333333333333333333333333333333333333', - s: '0x4444444444444444444444444444444444444444444444444444444444444444', - }, - ], - }; - - // Convert to Viem's transaction format - const viemTx = { - type: 'eip7702', - chainId: 1, - nonce: 0, - maxPriorityFeePerGas: BigInt(parseEther('0.000000001')), - maxFeePerGas: BigInt(parseEther('0.00000001')), - gas: BigInt(21000), - to: '0x1111111111111111111111111111111111111111', - value: BigInt(parseEther('1.0')), - data: '0x', - accessList: [], - authorizationList: [ - { - chainId: 1, - address: '0x2222222222222222222222222222222222222222', - nonce: 0, - signature: { - yParity: 0, - r: '0x1111111111111111111111111111111111111111111111111111111111111111', - s: '0x2222222222222222222222222222222222222222222222222222222222222222', - }, - }, - { - chainId: 1, - address: '0x3333333333333333333333333333333333333333', - nonce: 0, - signature: { - yParity: 1, - r: '0x3333333333333333333333333333333333333333333333333333333333333333', - s: '0x4444444444444444444444444444444444444444444444444444444444444444', - }, - }, - ], - }; - - // Serialize using our implementation - const ourSerialized = serializeEIP7702Transaction(tx); - - // Serialize using Viem - const viemSerialized = serializeTransaction(viemTx as any); - - // Compute hashes for comparison - const ourHash = `0x${Buffer.from( - Hash.keccak256(Buffer.from(ourSerialized.slice(2), 'hex')), - ).toString('hex')}`; - const viemHash = `0x${Buffer.from( - Hash.keccak256(Buffer.from(viemSerialized.slice(2), 'hex')), - ).toString('hex')}`; - - // Output for debugging - console.log('Our serialized (auth list):', ourSerialized); - console.log('Viem serialized (auth list):', viemSerialized); - console.log('Our hash (auth list):', ourHash); - console.log('Viem hash (auth list):', viemHash); - - // Compare the serialized transactions - expect(ourSerialized).toEqual(viemSerialized); - }); - - /** - * Test case using realistic transaction values - */ - test('realistic transaction values match between implementations', async () => { - // Reference transaction with specific values - const tx: EIP7702AuthTransaction = { - type: 4, - chainId: 1, // Mainnet - nonce: 42, - maxPriorityFeePerGas: '0x3b9aca00', // 1 gwei (1,000,000,000 wei) - maxFeePerGas: '0x77359400', // 2 gwei (2,000,000,000 wei) - gasLimit: '0x5208', // 21000 - to: '0xd8da6bf26964af9d7eed9e03e53415d37aa96045', // vitalik.eth - value: '0x2386f26fc10000', // 0.01 ETH (10,000,000,000,000,000 wei) - data: '0x68656c6c6f', // "hello" in hex - accessList: [], - authorization: { - chainId: 1, - address: '0xc02aaa39b223fe8d0a0e5c4f27ead9083c756cc2', // WETH contract - nonce: 0, - // Standard test signature values - yParity: 0, - r: '0x0000000000000000000000000000000000000000000000000000000000000001', - s: '0x0000000000000000000000000000000000000000000000000000000000000002', - }, - }; - - // Convert to Viem's transaction format - const viemTx = { - type: 'eip7702', - chainId: 1, - nonce: 42, - maxPriorityFeePerGas: BigInt('0x3b9aca00'), - maxFeePerGas: BigInt('0x77359400'), - gas: BigInt('0x5208'), - to: '0xd8da6bf26964af9d7eed9e03e53415d37aa96045', - value: BigInt('0x2386f26fc10000'), - data: '0x68656c6c6f', - accessList: [], - authorizationList: [ - { - chainId: 1, - address: '0xc02aaa39b223fe8d0a0e5c4f27ead9083c756cc2', - nonce: 0, - signature: { - yParity: 0, - r: '0x0000000000000000000000000000000000000000000000000000000000000001', - s: '0x0000000000000000000000000000000000000000000000000000000000000002', - }, - }, - ], - }; - - // Serialize using our implementation - const ourSerialized = serializeEIP7702Transaction(tx); - - // Serialize using Viem - const viemSerialized = serializeTransaction(viemTx as any); - - // Compute hashes for comparison - const ourHash = `0x${Buffer.from( - Hash.keccak256(Buffer.from(ourSerialized.slice(2), 'hex')), - ).toString('hex')}`; - const viemHash = `0x${Buffer.from( - Hash.keccak256(Buffer.from(viemSerialized.slice(2), 'hex')), - ).toString('hex')}`; - - // Output for debugging - console.log('Our serialized (realistic):', ourSerialized); - console.log('Viem serialized (realistic):', viemSerialized); - console.log('Our hash (realistic):', ourHash); - console.log('Viem hash (realistic):', viemHash); - - // Compare the serialized transactions - expect(ourSerialized).toEqual(viemSerialized); - }); - - /** - * Test case with contract auth (when authorization has nonce) - */ - test('contract authorization transaction serialization matches Viem', async () => { - // Create a single authorization transaction with contract auth (with nonce) - const tx: EIP7702AuthTransaction = { - type: 4, - chainId: 1, - nonce: 0, - maxPriorityFeePerGas: '0x3b9aca00', - maxFeePerGas: '0x77359400', - gasLimit: '0x5208', - to: '0x1111111111111111111111111111111111111111', - value: '0x0', - data: '0x', - accessList: [], - authorization: { - chainId: 1, - address: '0x2222222222222222222222222222222222222222', - nonce: 5, // Contract auth has non-zero nonce - yParity: 0, - r: '0x1111111111111111111111111111111111111111111111111111111111111111', - s: '0x2222222222222222222222222222222222222222222222222222222222222222', - }, - }; - - // Convert to Viem's transaction format - const viemTx = { - type: 'eip7702', - chainId: 1, - nonce: 0, - maxPriorityFeePerGas: BigInt('0x3b9aca00'), - maxFeePerGas: BigInt('0x77359400'), - gas: BigInt('0x5208'), - to: '0x1111111111111111111111111111111111111111', - value: BigInt('0x0'), - data: '0x', - accessList: [], - authorizationList: [ - { - chainId: 1, - address: '0x2222222222222222222222222222222222222222', - nonce: 5, - signature: { - yParity: 0, - r: '0x1111111111111111111111111111111111111111111111111111111111111111', - s: '0x2222222222222222222222222222222222222222222222222222222222222222', - }, - }, - ], - }; - - // Serialize using our implementation - const ourSerialized = serializeEIP7702Transaction(tx); - - // Serialize using Viem - const viemSerialized = serializeTransaction(viemTx as any); - - // Compute hashes for comparison - const ourHash = `0x${Buffer.from( - Hash.keccak256(Buffer.from(ourSerialized.slice(2), 'hex')), - ).toString('hex')}`; - const viemHash = `0x${Buffer.from( - Hash.keccak256(Buffer.from(viemSerialized.slice(2), 'hex')), - ).toString('hex')}`; - - // Output for debugging - console.log('Our serialized (contract auth):', ourSerialized); - console.log('Viem serialized (contract auth):', viemSerialized); - console.log('Our hash (contract auth):', ourHash); - console.log('Viem hash (contract auth):', viemHash); - - // Compare the serialized transactions - expect(ourSerialized).toEqual(viemSerialized); - }); -}); + /** + * Simple minimal test case to identify differences + */ + test('minimal single authorization transaction', async () => { + // Create a minimal transaction for easier comparison + const tx: EIP7702AuthTransaction = { + type: 4, + chainId: 1, + nonce: 0, + maxPriorityFeePerGas: '0x1', + maxFeePerGas: '0x2', + gasLimit: '0x3', + to: '0x1111111111111111111111111111111111111111', + value: '0x0', + data: '0x', + accessList: [], + authorization: { + chainId: 1, + address: '0x2222222222222222222222222222222222222222', + nonce: 0, + yParity: 0, + r: '0x0000000000000000000000000000000000000000000000000000000000000001', + s: '0x0000000000000000000000000000000000000000000000000000000000000002', + }, + } + + // Convert to Viem's transaction format + const viemTx = { + type: 'eip7702', + chainId: 1, + nonce: 0, + maxPriorityFeePerGas: BigInt('0x1'), + maxFeePerGas: BigInt('0x2'), + gas: BigInt('0x3'), + to: '0x1111111111111111111111111111111111111111', + value: BigInt('0x0'), + data: '0x', + accessList: [], + authorizationList: [ + { + chainId: 1, + address: '0x2222222222222222222222222222222222222222', + nonce: 0, + signature: { + yParity: 0, + r: '0x0000000000000000000000000000000000000000000000000000000000000001', + s: '0x0000000000000000000000000000000000000000000000000000000000000002', + }, + }, + ], + } + + // Serialize using our implementation + const ourSerialized = serializeEIP7702Transaction(tx) + + // Serialize using Viem + const viemSerialized = serializeTransaction(viemTx as any) + + // Output raw serialized data for debugging + console.log('Our serialized (minimal):', ourSerialized) + console.log('Viem serialized (minimal):', viemSerialized) + + // Output serialized by byte + console.log( + 'Our bytes:', + Buffer.from(ourSerialized.slice(2), 'hex') + .toString('hex') + .match(/.{1,2}/g) + ?.join(' '), + ) + console.log( + 'Viem bytes:', + Buffer.from(viemSerialized.slice(2), 'hex') + .toString('hex') + .match(/.{1,2}/g) + ?.join(' '), + ) + + // Compare the serialized transactions + expect(ourSerialized).toEqual(viemSerialized) + }) + + /** + * Test case comparing our implementation of EIP-7702 transaction serialization with Viem's implementation + * + * This ensures that our serialization matches Viem's expected format, + * providing compatibility with external libraries and tools. + */ + test('single authorization transaction serialization matches Viem', async () => { + // Create a single authorization transaction with deterministic values + const tx: EIP7702AuthTransaction = { + type: 4, // Must be exactly 4 for auth transaction + chainId: 1, // Mainnet + nonce: 0, + maxPriorityFeePerGas: toHex(parseEther('0.000000001')), // 1 gwei + maxFeePerGas: toHex(parseEther('0.00000001')), // 10 gwei + gasLimit: toHex(BigInt(21000)), + to: '0x1111111111111111111111111111111111111111', // Simple address for testing + value: toHex(parseEther('1.0')), // 1 ETH + data: '0x', + accessList: [], + authorization: { + chainId: 1, + address: '0x2222222222222222222222222222222222222222', + nonce: 0, + yParity: 0, // Known signature values for deterministic test + r: '0x1111111111111111111111111111111111111111111111111111111111111111', + s: '0x2222222222222222222222222222222222222222222222222222222222222222', + }, + } + + // Convert to Viem's transaction format + const viemTx = { + type: 'eip7702', + chainId: 1, + nonce: 0, + maxPriorityFeePerGas: BigInt(parseEther('0.000000001')), + maxFeePerGas: BigInt(parseEther('0.00000001')), + gas: BigInt(21000), + to: '0x1111111111111111111111111111111111111111', + value: BigInt(parseEther('1.0')), + data: '0x', + accessList: [], + authorizationList: [ + { + chainId: 1, + address: '0x2222222222222222222222222222222222222222', + nonce: 0, + signature: { + yParity: 0, + r: '0x1111111111111111111111111111111111111111111111111111111111111111', + s: '0x2222222222222222222222222222222222222222222222222222222222222222', + }, + }, + ], + } + + // Serialize using our implementation + const ourSerialized = serializeEIP7702Transaction(tx) + + // Serialize using Viem + const viemSerialized = serializeTransaction(viemTx as any) + + // Compute hashes for comparison + const ourHash = `0x${Buffer.from(Hash.keccak256(Buffer.from(ourSerialized.slice(2), 'hex'))).toString('hex')}` + const viemHash = `0x${Buffer.from(Hash.keccak256(Buffer.from(viemSerialized.slice(2), 'hex'))).toString('hex')}` + + // Output for debugging + console.log('Our serialized:', ourSerialized) + console.log('Viem serialized:', viemSerialized) + console.log('Our hash:', ourHash) + console.log('Viem hash:', viemHash) + + // Compare the serialized transactions + expect(ourSerialized).toEqual(viemSerialized) + }) + + /** + * Test case for serializing an EIP-7702 authorization list transaction (type 5) + * comparing our implementation with Viem's implementation. + */ + test('authorization list transaction serialization matches Viem', async () => { + const tx: EIP7702AuthListTransaction = { + type: 5, // Must be exactly 5 for auth list transaction + chainId: 1, // Mainnet + nonce: 0, + maxPriorityFeePerGas: toHex(parseEther('0.000000001')), // 1 gwei + maxFeePerGas: toHex(parseEther('0.00000001')), // 10 gwei + gasLimit: toHex(BigInt(21000)), + to: '0x1111111111111111111111111111111111111111', // Simple address for testing + value: toHex(parseEther('1.0')), // 1 ETH + data: '0x', + accessList: [], + authorizationList: [ + { + chainId: 1, + address: '0x2222222222222222222222222222222222222222', + nonce: 0, + yParity: 0, // Known signature values for deterministic test + r: '0x1111111111111111111111111111111111111111111111111111111111111111', + s: '0x2222222222222222222222222222222222222222222222222222222222222222', + }, + { + chainId: 1, + address: '0x3333333333333333333333333333333333333333', + nonce: 0, + yParity: 1, // Different signature + r: '0x3333333333333333333333333333333333333333333333333333333333333333', + s: '0x4444444444444444444444444444444444444444444444444444444444444444', + }, + ], + } + + // Convert to Viem's transaction format + const viemTx = { + type: 'eip7702', + chainId: 1, + nonce: 0, + maxPriorityFeePerGas: BigInt(parseEther('0.000000001')), + maxFeePerGas: BigInt(parseEther('0.00000001')), + gas: BigInt(21000), + to: '0x1111111111111111111111111111111111111111', + value: BigInt(parseEther('1.0')), + data: '0x', + accessList: [], + authorizationList: [ + { + chainId: 1, + address: '0x2222222222222222222222222222222222222222', + nonce: 0, + signature: { + yParity: 0, + r: '0x1111111111111111111111111111111111111111111111111111111111111111', + s: '0x2222222222222222222222222222222222222222222222222222222222222222', + }, + }, + { + chainId: 1, + address: '0x3333333333333333333333333333333333333333', + nonce: 0, + signature: { + yParity: 1, + r: '0x3333333333333333333333333333333333333333333333333333333333333333', + s: '0x4444444444444444444444444444444444444444444444444444444444444444', + }, + }, + ], + } + + // Serialize using our implementation + const ourSerialized = serializeEIP7702Transaction(tx) + + // Serialize using Viem + const viemSerialized = serializeTransaction(viemTx as any) + + // Compute hashes for comparison + const ourHash = `0x${Buffer.from(Hash.keccak256(Buffer.from(ourSerialized.slice(2), 'hex'))).toString('hex')}` + const viemHash = `0x${Buffer.from(Hash.keccak256(Buffer.from(viemSerialized.slice(2), 'hex'))).toString('hex')}` + + // Output for debugging + console.log('Our serialized (auth list):', ourSerialized) + console.log('Viem serialized (auth list):', viemSerialized) + console.log('Our hash (auth list):', ourHash) + console.log('Viem hash (auth list):', viemHash) + + // Compare the serialized transactions + expect(ourSerialized).toEqual(viemSerialized) + }) + + /** + * Test case using realistic transaction values + */ + test('realistic transaction values match between implementations', async () => { + // Reference transaction with specific values + const tx: EIP7702AuthTransaction = { + type: 4, + chainId: 1, // Mainnet + nonce: 42, + maxPriorityFeePerGas: '0x3b9aca00', // 1 gwei (1,000,000,000 wei) + maxFeePerGas: '0x77359400', // 2 gwei (2,000,000,000 wei) + gasLimit: '0x5208', // 21000 + to: '0xd8da6bf26964af9d7eed9e03e53415d37aa96045', // vitalik.eth + value: '0x2386f26fc10000', // 0.01 ETH (10,000,000,000,000,000 wei) + data: '0x68656c6c6f', // "hello" in hex + accessList: [], + authorization: { + chainId: 1, + address: '0xc02aaa39b223fe8d0a0e5c4f27ead9083c756cc2', // WETH contract + nonce: 0, + // Standard test signature values + yParity: 0, + r: '0x0000000000000000000000000000000000000000000000000000000000000001', + s: '0x0000000000000000000000000000000000000000000000000000000000000002', + }, + } + + // Convert to Viem's transaction format + const viemTx = { + type: 'eip7702', + chainId: 1, + nonce: 42, + maxPriorityFeePerGas: BigInt('0x3b9aca00'), + maxFeePerGas: BigInt('0x77359400'), + gas: BigInt('0x5208'), + to: '0xd8da6bf26964af9d7eed9e03e53415d37aa96045', + value: BigInt('0x2386f26fc10000'), + data: '0x68656c6c6f', + accessList: [], + authorizationList: [ + { + chainId: 1, + address: '0xc02aaa39b223fe8d0a0e5c4f27ead9083c756cc2', + nonce: 0, + signature: { + yParity: 0, + r: '0x0000000000000000000000000000000000000000000000000000000000000001', + s: '0x0000000000000000000000000000000000000000000000000000000000000002', + }, + }, + ], + } + + // Serialize using our implementation + const ourSerialized = serializeEIP7702Transaction(tx) + + // Serialize using Viem + const viemSerialized = serializeTransaction(viemTx as any) + + // Compute hashes for comparison + const ourHash = `0x${Buffer.from(Hash.keccak256(Buffer.from(ourSerialized.slice(2), 'hex'))).toString('hex')}` + const viemHash = `0x${Buffer.from(Hash.keccak256(Buffer.from(viemSerialized.slice(2), 'hex'))).toString('hex')}` + + // Output for debugging + console.log('Our serialized (realistic):', ourSerialized) + console.log('Viem serialized (realistic):', viemSerialized) + console.log('Our hash (realistic):', ourHash) + console.log('Viem hash (realistic):', viemHash) + + // Compare the serialized transactions + expect(ourSerialized).toEqual(viemSerialized) + }) + + /** + * Test case with contract auth (when authorization has nonce) + */ + test('contract authorization transaction serialization matches Viem', async () => { + // Create a single authorization transaction with contract auth (with nonce) + const tx: EIP7702AuthTransaction = { + type: 4, + chainId: 1, + nonce: 0, + maxPriorityFeePerGas: '0x3b9aca00', + maxFeePerGas: '0x77359400', + gasLimit: '0x5208', + to: '0x1111111111111111111111111111111111111111', + value: '0x0', + data: '0x', + accessList: [], + authorization: { + chainId: 1, + address: '0x2222222222222222222222222222222222222222', + nonce: 5, // Contract auth has non-zero nonce + yParity: 0, + r: '0x1111111111111111111111111111111111111111111111111111111111111111', + s: '0x2222222222222222222222222222222222222222222222222222222222222222', + }, + } + + // Convert to Viem's transaction format + const viemTx = { + type: 'eip7702', + chainId: 1, + nonce: 0, + maxPriorityFeePerGas: BigInt('0x3b9aca00'), + maxFeePerGas: BigInt('0x77359400'), + gas: BigInt('0x5208'), + to: '0x1111111111111111111111111111111111111111', + value: BigInt('0x0'), + data: '0x', + accessList: [], + authorizationList: [ + { + chainId: 1, + address: '0x2222222222222222222222222222222222222222', + nonce: 5, + signature: { + yParity: 0, + r: '0x1111111111111111111111111111111111111111111111111111111111111111', + s: '0x2222222222222222222222222222222222222222222222222222222222222222', + }, + }, + ], + } + + // Serialize using our implementation + const ourSerialized = serializeEIP7702Transaction(tx) + + // Serialize using Viem + const viemSerialized = serializeTransaction(viemTx as any) + + // Compute hashes for comparison + const ourHash = `0x${Buffer.from(Hash.keccak256(Buffer.from(ourSerialized.slice(2), 'hex'))).toString('hex')}` + const viemHash = `0x${Buffer.from(Hash.keccak256(Buffer.from(viemSerialized.slice(2), 'hex'))).toString('hex')}` + + // Output for debugging + console.log('Our serialized (contract auth):', ourSerialized) + console.log('Viem serialized (contract auth):', viemSerialized) + console.log('Our hash (contract auth):', ourHash) + console.log('Viem hash (contract auth):', viemHash) + + // Compare the serialized transactions + expect(ourSerialized).toEqual(viemSerialized) + }) +}) diff --git a/packages/sdk/src/__test__/unit/decoders.test.ts b/packages/sdk/src/__test__/unit/decoders.test.ts index 9ab692fc..f72ec4ab 100644 --- a/packages/sdk/src/__test__/unit/decoders.test.ts +++ b/packages/sdk/src/__test__/unit/decoders.test.ts @@ -1,77 +1,62 @@ +import { decodeConnectResponse, decodeFetchEncData, decodeGetAddressesResponse, decodeGetKvRecordsResponse, decodeSignResponse } from '../../functions' +import type { DecodeSignResponseParams } from '../../types' import { - decodeConnectResponse, - decodeFetchEncData, - decodeGetAddressesResponse, - decodeGetKvRecordsResponse, - decodeSignResponse, -} from '../../functions'; -import type { DecodeSignResponseParams } from '../../types'; -import { - clientKeyPair, - connectDecoderData, - decoderTestsFwConstants, - fetchEncryptedDataDecoderData, - fetchEncryptedDataRequest, - getAddressesDecoderData, - getAddressesFlag, - getKvRecordsDecoderData, - signBitcoinDecoderData, - signBitcoinRequest, - signGenericDecoderData, - signGenericRequest, -} from './__mocks__/decoderData'; + clientKeyPair, + connectDecoderData, + decoderTestsFwConstants, + fetchEncryptedDataDecoderData, + fetchEncryptedDataRequest, + getAddressesDecoderData, + getAddressesFlag, + getKvRecordsDecoderData, + signBitcoinDecoderData, + signBitcoinRequest, + signGenericDecoderData, + signGenericRequest, +} from './__mocks__/decoderData' describe('decoders', () => { - test('connect', () => { - expect( - decodeConnectResponse(connectDecoderData, clientKeyPair), - ).toMatchSnapshot(); - }); + test('connect', () => { + expect(decodeConnectResponse(connectDecoderData, clientKeyPair)).toMatchSnapshot() + }) - test('getAddresses', () => { - expect( - decodeGetAddressesResponse(getAddressesDecoderData, getAddressesFlag), - ).toMatchSnapshot(); - }); + test('getAddresses', () => { + expect(decodeGetAddressesResponse(getAddressesDecoderData, getAddressesFlag)).toMatchSnapshot() + }) - test('sign - bitcoin', () => { - const params: DecodeSignResponseParams = { - data: signBitcoinDecoderData, - request: signBitcoinRequest, - isGeneric: false, - currency: 'BTC', - }; - expect(decodeSignResponse(params)).toMatchSnapshot(); - }); + test('sign - bitcoin', () => { + const params: DecodeSignResponseParams = { + data: signBitcoinDecoderData, + request: signBitcoinRequest, + isGeneric: false, + currency: 'BTC', + } + expect(decodeSignResponse(params)).toMatchSnapshot() + }) - test('sign - generic', () => { - const params: DecodeSignResponseParams = { - data: signGenericDecoderData, - request: signGenericRequest, - isGeneric: true, - }; - expect(decodeSignResponse(params)).toMatchSnapshot(); - }); + test('sign - generic', () => { + const params: DecodeSignResponseParams = { + data: signGenericDecoderData, + request: signGenericRequest, + isGeneric: true, + } + expect(decodeSignResponse(params)).toMatchSnapshot() + }) - test('getKvRecords', () => { - expect( - decodeGetKvRecordsResponse( - getKvRecordsDecoderData, - decoderTestsFwConstants, - ), - ).toMatchSnapshot(); - }); + test('getKvRecords', () => { + expect(decodeGetKvRecordsResponse(getKvRecordsDecoderData, decoderTestsFwConstants)).toMatchSnapshot() + }) - test('fetchEncryptedData', () => { - // This test is different than the others because one part of the data is - // randomly generated (UUID) before the response is returned. - // We will just zero it out for testing purposes. - const decoded = decodeFetchEncData({ - data: fetchEncryptedDataDecoderData, - ...fetchEncryptedDataRequest, - }); - const decodedDerp = JSON.parse(decoded.toString()); - decodedDerp.uuid = '00000000-0000-0000-0000-000000000000'; - expect(Buffer.from(JSON.stringify(decodedDerp))).toMatchSnapshot(); - }); -}); + test('fetchEncryptedData', () => { + // This test is different than the others because one part of the data is + // randomly generated (UUID) before the response is returned. + // We will just zero it out for testing purposes. + const decoded = decodeFetchEncData({ + data: fetchEncryptedDataDecoderData, + ...fetchEncryptedDataRequest, + }) + const decodedDerp = JSON.parse(decoded.toString()) + decodedDerp.uuid = '00000000-0000-0000-0000-000000000000' + expect(Buffer.from(JSON.stringify(decodedDerp))).toMatchSnapshot() + }) +}) diff --git a/packages/sdk/src/__test__/unit/eip7702.test.ts b/packages/sdk/src/__test__/unit/eip7702.test.ts index d9789ea7..35da05de 100644 --- a/packages/sdk/src/__test__/unit/eip7702.test.ts +++ b/packages/sdk/src/__test__/unit/eip7702.test.ts @@ -1,178 +1,169 @@ -import { Hash } from 'ox'; -import { parseEther, toHex } from 'viem'; -import { serializeEIP7702Transaction } from '../../ethereum'; -import type { - EIP7702AuthListTransactionRequest, - EIP7702AuthTransactionRequest, -} from '../../types'; +import { Hash } from 'ox' +import { parseEther, toHex } from 'viem' +import { serializeEIP7702Transaction } from '../../ethereum' +import type { EIP7702AuthListTransactionRequest, EIP7702AuthTransactionRequest } from '../../types' describe('EIP-7702 Transaction Serialization', () => { - /** - * Test case for serializing an EIP-7702 authorization transaction (type 4). - * - * This test creates a deterministic transaction with known values and - * verifies that the serialized result matches the expected hash. - * - * EIP-7702 spec requires exact field ordering: - * rlp([chain_id, nonce, max_priority_fee_per_gas, max_fee_per_gas, gas_limit, - * destination, value, data, access_list, authorization_list, - * signature_y_parity, signature_r, signature_s]) - */ - test('single authorization transaction serialization', () => { - // Create a single authorization transaction with deterministic values - const tx: EIP7702AuthTransactionRequest = { - type: 4, // Must be exactly 4 for auth transaction - chainId: 1, // Mainnet - nonce: 0, - maxPriorityFeePerGas: toHex(parseEther('0.000000001')), // 1 gwei - maxFeePerGas: toHex(parseEther('0.00000001')), // 10 gwei - gasLimit: toHex(BigInt(21000)), - to: '0x1111111111111111111111111111111111111111', // Simple address for testing - value: toHex(parseEther('1.0')), // 1 ETH - data: '0x', - accessList: [], - authorization: { - chainId: 1, - address: '0x2222222222222222222222222222222222222222', - nonce: 0, - yParity: 0, // Valid ECDSA signature values - r: '0xbfa71d3b2c96bd4f573ee8e2b0da387999eb521b8c09f68499f4ed528cbeeb40', - s: '0x171bb6415a3ff1207ddf5314aa05ffc168bd82f3abd0c8a7c91ef22ff58c4698', - }, - }; - - // Serialize the transaction - const serialized = serializeEIP7702Transaction(tx); - - // Compute the keccak256 hash of the serialized transaction - const txHash = `0x${Buffer.from( - Hash.keccak256(Buffer.from(serialized.slice(2), 'hex')), - ).toString('hex')}`; - - // Store the serialized value for debugging - console.log('Serialized transaction:', serialized); - console.log('Transaction hash:', txHash); - - // Store the expected serialized form (can be replaced with actual expected value) - // For now we'll assert that serialization produces consistent results - const initialRun = serializeEIP7702Transaction(tx); - expect(serialized).toEqual(initialRun); - - // Ensure the serialized transaction starts with the transaction type (0x04) - expect(serialized.startsWith('0x04')).toBe(true); - }); - - /** - * Test case for serializing an EIP-7702 authorization list transaction (type 5). - * - * This test creates a transaction with multiple authorizations and verifies - * that the serialized result is consistent and properly formatted. - */ - test('authorization list transaction serialization', () => { - const tx: EIP7702AuthListTransactionRequest = { - type: 5, // Must be exactly 5 for auth list transaction - chainId: 1, // Mainnet - nonce: 0, - maxPriorityFeePerGas: toHex(parseEther('0.000000001')), // 1 gwei - maxFeePerGas: toHex(parseEther('0.00000001')), // 10 gwei - gasLimit: toHex(BigInt(21000)), - to: '0x1111111111111111111111111111111111111111', // Simple address for testing - value: toHex(parseEther('1.0')), // 1 ETH - data: '0x', - accessList: [], - authorizationList: [ - { - chainId: 1, - address: '0x2222222222222222222222222222222222222222', - nonce: 0, - yParity: 0, // Valid ECDSA signature values - r: '0xbfa71d3b2c96bd4f573ee8e2b0da387999eb521b8c09f68499f4ed528cbeeb40', - s: '0x171bb6415a3ff1207ddf5314aa05ffc168bd82f3abd0c8a7c91ef22ff58c4698', - }, - { - chainId: 1, - address: '0x3333333333333333333333333333333333333333', - nonce: 0, - yParity: 0, // Valid ECDSA signature values - r: '0x888acc1e501f052175c59fa2167699341709bd72f9809182bdf580c1c3bf6cf', - s: '0x7e03cfbc948cf6b8c4cd946d511b3ea1c4c64e8c70e0259573183ef22d565034', - }, - ], - }; - - // Serialize the transaction - const serialized = serializeEIP7702Transaction(tx); - - // Compute the keccak256 hash of the serialized transaction - const txHash = `0x${Buffer.from( - Hash.keccak256(Buffer.from(serialized.slice(2), 'hex')), - ).toString('hex')}`; - - // Store the serialized value for debugging - console.log('Serialized auth list transaction:', serialized); - console.log('Transaction hash:', txHash); - - // Store the expected serialized form (can be replaced with actual expected value) - // For now we'll assert that serialization produces consistent results - const initialRun = serializeEIP7702Transaction(tx); - expect(serialized).toEqual(initialRun); - - // Ensure the serialized transaction starts with the transaction type (0x05) - expect(serialized.startsWith('0x04')).toBe(true); - }); - - /** - * Test case for comparing serialization against a known good hash. - * - * This test uses a transaction with specific values that should produce - * a known hash when serialized correctly. - * - * Note: The expected hash used here is based on a reference implementation - * of the EIP-7702 serialization process. - */ - test('serialization matches known good hash', () => { - // Reference transaction with specific values - const tx: EIP7702AuthTransactionRequest = { - type: 4, // Must be exactly 4 for auth transaction - chainId: 1, // Mainnet - nonce: 42, - maxPriorityFeePerGas: '0x3b9aca00', // 1 gwei (1,000,000,000 wei) - maxFeePerGas: '0x77359400', // 2 gwei (2,000,000,000 wei) - gasLimit: '0x5208', // 21000 - to: '0xd8da6bf26964af9d7eed9e03e53415d37aa96045', // vitalik.eth - value: '0x2386f26fc10000', // 0.01 ETH (10,000,000,000,000,000 wei) - data: '0x68656c6c6f', // "hello" in hex - accessList: [], - authorization: { - chainId: 1, - address: '0xc02aaa39b223fe8d0a0e5c4f27ead9083c756cc2', // WETH contract - nonce: 0, - // Valid ECDSA signature values for chainId=1, address=WETH, nonce=0 - yParity: 1, - r: '0x7afecf0fa2f0c5f3cee3bf477dc4b0787afaecf5c8b0e2f7ec6c47c893bb06f0', - s: '0x2e019bd0bb7b96a5beb6f92c63bc7d72f19f6b960d50f8b1c0c4f6bc690e95f4', - }, - }; - - // Serialize the transaction - const serialized = serializeEIP7702Transaction(tx); - - // Compute the keccak256 hash of the serialized transaction - const txHash = `0x${Buffer.from( - Hash.keccak256(Buffer.from(serialized.slice(2), 'hex')), - ).toString('hex')}`; - - console.log('Reference serialized transaction:', serialized); - console.log('Reference transaction hash:', txHash); - - // The expected hash would be provided by a reference implementation - // For now,. I will assert consistency across multiple serializations - const secondRun = serializeEIP7702Transaction(tx); - expect(serialized).toEqual(secondRun); - - // Store the hash for future reference - this can be replaced with a - // verified correct hash once available from a reference implementation - const knownGoodHash = txHash; - expect(txHash).toEqual(knownGoodHash); - }); -}); + /** + * Test case for serializing an EIP-7702 authorization transaction (type 4). + * + * This test creates a deterministic transaction with known values and + * verifies that the serialized result matches the expected hash. + * + * EIP-7702 spec requires exact field ordering: + * rlp([chain_id, nonce, max_priority_fee_per_gas, max_fee_per_gas, gas_limit, + * destination, value, data, access_list, authorization_list, + * signature_y_parity, signature_r, signature_s]) + */ + test('single authorization transaction serialization', () => { + // Create a single authorization transaction with deterministic values + const tx: EIP7702AuthTransactionRequest = { + type: 4, // Must be exactly 4 for auth transaction + chainId: 1, // Mainnet + nonce: 0, + maxPriorityFeePerGas: toHex(parseEther('0.000000001')), // 1 gwei + maxFeePerGas: toHex(parseEther('0.00000001')), // 10 gwei + gasLimit: toHex(BigInt(21000)), + to: '0x1111111111111111111111111111111111111111', // Simple address for testing + value: toHex(parseEther('1.0')), // 1 ETH + data: '0x', + accessList: [], + authorization: { + chainId: 1, + address: '0x2222222222222222222222222222222222222222', + nonce: 0, + yParity: 0, // Valid ECDSA signature values + r: '0xbfa71d3b2c96bd4f573ee8e2b0da387999eb521b8c09f68499f4ed528cbeeb40', + s: '0x171bb6415a3ff1207ddf5314aa05ffc168bd82f3abd0c8a7c91ef22ff58c4698', + }, + } + + // Serialize the transaction + const serialized = serializeEIP7702Transaction(tx) + + // Compute the keccak256 hash of the serialized transaction + const txHash = `0x${Buffer.from(Hash.keccak256(Buffer.from(serialized.slice(2), 'hex'))).toString('hex')}` + + // Store the serialized value for debugging + console.log('Serialized transaction:', serialized) + console.log('Transaction hash:', txHash) + + // Store the expected serialized form (can be replaced with actual expected value) + // For now we'll assert that serialization produces consistent results + const initialRun = serializeEIP7702Transaction(tx) + expect(serialized).toEqual(initialRun) + + // Ensure the serialized transaction starts with the transaction type (0x04) + expect(serialized.startsWith('0x04')).toBe(true) + }) + + /** + * Test case for serializing an EIP-7702 authorization list transaction (type 5). + * + * This test creates a transaction with multiple authorizations and verifies + * that the serialized result is consistent and properly formatted. + */ + test('authorization list transaction serialization', () => { + const tx: EIP7702AuthListTransactionRequest = { + type: 5, // Must be exactly 5 for auth list transaction + chainId: 1, // Mainnet + nonce: 0, + maxPriorityFeePerGas: toHex(parseEther('0.000000001')), // 1 gwei + maxFeePerGas: toHex(parseEther('0.00000001')), // 10 gwei + gasLimit: toHex(BigInt(21000)), + to: '0x1111111111111111111111111111111111111111', // Simple address for testing + value: toHex(parseEther('1.0')), // 1 ETH + data: '0x', + accessList: [], + authorizationList: [ + { + chainId: 1, + address: '0x2222222222222222222222222222222222222222', + nonce: 0, + yParity: 0, // Valid ECDSA signature values + r: '0xbfa71d3b2c96bd4f573ee8e2b0da387999eb521b8c09f68499f4ed528cbeeb40', + s: '0x171bb6415a3ff1207ddf5314aa05ffc168bd82f3abd0c8a7c91ef22ff58c4698', + }, + { + chainId: 1, + address: '0x3333333333333333333333333333333333333333', + nonce: 0, + yParity: 0, // Valid ECDSA signature values + r: '0x888acc1e501f052175c59fa2167699341709bd72f9809182bdf580c1c3bf6cf', + s: '0x7e03cfbc948cf6b8c4cd946d511b3ea1c4c64e8c70e0259573183ef22d565034', + }, + ], + } + + // Serialize the transaction + const serialized = serializeEIP7702Transaction(tx) + + // Compute the keccak256 hash of the serialized transaction + const txHash = `0x${Buffer.from(Hash.keccak256(Buffer.from(serialized.slice(2), 'hex'))).toString('hex')}` + + // Store the serialized value for debugging + console.log('Serialized auth list transaction:', serialized) + console.log('Transaction hash:', txHash) + + // Store the expected serialized form (can be replaced with actual expected value) + // For now we'll assert that serialization produces consistent results + const initialRun = serializeEIP7702Transaction(tx) + expect(serialized).toEqual(initialRun) + + // Ensure the serialized transaction starts with the transaction type (0x05) + expect(serialized.startsWith('0x04')).toBe(true) + }) + + /** + * Test case for comparing serialization against a known good hash. + * + * This test uses a transaction with specific values that should produce + * a known hash when serialized correctly. + * + * Note: The expected hash used here is based on a reference implementation + * of the EIP-7702 serialization process. + */ + test('serialization matches known good hash', () => { + // Reference transaction with specific values + const tx: EIP7702AuthTransactionRequest = { + type: 4, // Must be exactly 4 for auth transaction + chainId: 1, // Mainnet + nonce: 42, + maxPriorityFeePerGas: '0x3b9aca00', // 1 gwei (1,000,000,000 wei) + maxFeePerGas: '0x77359400', // 2 gwei (2,000,000,000 wei) + gasLimit: '0x5208', // 21000 + to: '0xd8da6bf26964af9d7eed9e03e53415d37aa96045', // vitalik.eth + value: '0x2386f26fc10000', // 0.01 ETH (10,000,000,000,000,000 wei) + data: '0x68656c6c6f', // "hello" in hex + accessList: [], + authorization: { + chainId: 1, + address: '0xc02aaa39b223fe8d0a0e5c4f27ead9083c756cc2', // WETH contract + nonce: 0, + // Valid ECDSA signature values for chainId=1, address=WETH, nonce=0 + yParity: 1, + r: '0x7afecf0fa2f0c5f3cee3bf477dc4b0787afaecf5c8b0e2f7ec6c47c893bb06f0', + s: '0x2e019bd0bb7b96a5beb6f92c63bc7d72f19f6b960d50f8b1c0c4f6bc690e95f4', + }, + } + + // Serialize the transaction + const serialized = serializeEIP7702Transaction(tx) + + // Compute the keccak256 hash of the serialized transaction + const txHash = `0x${Buffer.from(Hash.keccak256(Buffer.from(serialized.slice(2), 'hex'))).toString('hex')}` + + console.log('Reference serialized transaction:', serialized) + console.log('Reference transaction hash:', txHash) + + // The expected hash would be provided by a reference implementation + // For now,. I will assert consistency across multiple serializations + const secondRun = serializeEIP7702Transaction(tx) + expect(serialized).toEqual(secondRun) + + // Store the hash for future reference - this can be replaced with a + // verified correct hash once available from a reference implementation + const knownGoodHash = txHash + expect(txHash).toEqual(knownGoodHash) + }) +}) diff --git a/packages/sdk/src/__test__/unit/encoders.test.ts b/packages/sdk/src/__test__/unit/encoders.test.ts index 81ef41b8..3e60b600 100644 --- a/packages/sdk/src/__test__/unit/encoders.test.ts +++ b/packages/sdk/src/__test__/unit/encoders.test.ts @@ -1,125 +1,109 @@ -import { EXTERNAL } from '../../constants'; -import { - encodeAddKvRecordsRequest, - encodeGetAddressesRequest, - encodeGetKvRecordsRequest, - encodePairRequest, - encodeRemoveKvRecordsRequest, - encodeSignRequest, -} from '../../functions'; -import { buildTransaction } from '../../shared/functions'; -import { getP256KeyPair } from '../../util'; -import { - buildFirmwareConstants, - buildGetAddressesObject, - buildSignObject, - buildWallet, - getFwVersionsList, -} from '../utils/builders'; +import { EXTERNAL } from '../../constants' +import { encodeAddKvRecordsRequest, encodeGetAddressesRequest, encodeGetKvRecordsRequest, encodePairRequest, encodeRemoveKvRecordsRequest, encodeSignRequest } from '../../functions' +import { buildTransaction } from '../../shared/functions' +import { getP256KeyPair } from '../../util' +import { buildFirmwareConstants, buildGetAddressesObject, buildSignObject, buildWallet, getFwVersionsList } from '../utils/builders' describe('encoders', () => { - let mockRandom: any; + let mockRandom: any - beforeAll(() => { - mockRandom = vi.spyOn(globalThis.Math, 'random').mockReturnValue(0.1); - }); + beforeAll(() => { + mockRandom = vi.spyOn(globalThis.Math, 'random').mockReturnValue(0.1) + }) - afterAll(() => { - mockRandom.mockRestore(); - }); + afterAll(() => { + mockRandom.mockRestore() + }) - describe('pair', () => { - test('pair encoder', () => { - const privKey = Buffer.alloc(32, '1'); - expect(privKey.toString()).toMatchSnapshot(); - const key = getP256KeyPair(privKey); - const payload = encodePairRequest({ - key, - pairingSecret: 'testtest', - appName: 'testtest', - }); - const payloadAsString = payload.toString('hex'); - expect(payloadAsString).toMatchSnapshot(); - }); - }); + describe('pair', () => { + test('pair encoder', () => { + const privKey = Buffer.alloc(32, '1') + expect(privKey.toString()).toMatchSnapshot() + const key = getP256KeyPair(privKey) + const payload = encodePairRequest({ + key, + pairingSecret: 'testtest', + appName: 'testtest', + }) + const payloadAsString = payload.toString('hex') + expect(payloadAsString).toMatchSnapshot() + }) + }) - describe('getAddresses', () => { - test('encodeGetAddressesRequest with default flag', () => { - const payload = encodeGetAddressesRequest(buildGetAddressesObject()); - const payloadAsString = payload.toString('hex'); - expect(payloadAsString).toMatchSnapshot(); - }); + describe('getAddresses', () => { + test('encodeGetAddressesRequest with default flag', () => { + const payload = encodeGetAddressesRequest(buildGetAddressesObject()) + const payloadAsString = payload.toString('hex') + expect(payloadAsString).toMatchSnapshot() + }) - test('encodeGetAddressesRequest with ED25519_PUB', () => { - const mockObject = buildGetAddressesObject({ - flag: EXTERNAL.GET_ADDR_FLAGS.ED25519_PUB, - }); - const payload = encodeGetAddressesRequest(mockObject); - const payloadAsString = payload.toString('hex'); - expect(payloadAsString).toMatchSnapshot(); - }); + test('encodeGetAddressesRequest with ED25519_PUB', () => { + const mockObject = buildGetAddressesObject({ + flag: EXTERNAL.GET_ADDR_FLAGS.ED25519_PUB, + }) + const payload = encodeGetAddressesRequest(mockObject) + const payloadAsString = payload.toString('hex') + expect(payloadAsString).toMatchSnapshot() + }) - test('encodeGetAddressesRequest with SECP256K1_PUB', () => { - const mockObject = buildGetAddressesObject({ - flag: EXTERNAL.GET_ADDR_FLAGS.SECP256K1_PUB, - }); - const payload = encodeGetAddressesRequest(mockObject); - const payloadAsString = payload.toString('hex'); - expect(payloadAsString).toMatchSnapshot(); - }); - }); + test('encodeGetAddressesRequest with SECP256K1_PUB', () => { + const mockObject = buildGetAddressesObject({ + flag: EXTERNAL.GET_ADDR_FLAGS.SECP256K1_PUB, + }) + const payload = encodeGetAddressesRequest(mockObject) + const payloadAsString = payload.toString('hex') + expect(payloadAsString).toMatchSnapshot() + }) + }) - describe('sign', () => { - test.each(getFwVersionsList())( - 'should test sign encoder with firmware v%d.%d.%d', - (major, minor, patch) => { - const fwVersion = Buffer.from([patch, minor, major]); - const txObj = buildSignObject(fwVersion); - const tx = buildTransaction(txObj); - const req = { - ...txObj, - ...tx, - wallet: buildWallet(), - }; - const { payload } = encodeSignRequest(req); - const payloadAsString = payload.toString('hex'); - expect(payloadAsString).toMatchSnapshot(); - }, - ); - }); + describe('sign', () => { + test.each(getFwVersionsList())('should test sign encoder with firmware v%d.%d.%d', (major, minor, patch) => { + const fwVersion = Buffer.from([patch, minor, major]) + const txObj = buildSignObject(fwVersion) + const tx = buildTransaction(txObj) + const req = { + ...txObj, + ...tx, + wallet: buildWallet(), + } + const { payload } = encodeSignRequest(req) + const payloadAsString = payload.toString('hex') + expect(payloadAsString).toMatchSnapshot() + }) + }) - describe('KvRecords', () => { - test('getKvRecords', () => { - const mockObject = { type: 0, n: 1, start: 0 }; - const payload = encodeGetKvRecordsRequest(mockObject); - const payloadAsString = payload.toString('hex'); - expect(payloadAsString).toMatchSnapshot(); - }); + describe('KvRecords', () => { + test('getKvRecords', () => { + const mockObject = { type: 0, n: 1, start: 0 } + const payload = encodeGetKvRecordsRequest(mockObject) + const payloadAsString = payload.toString('hex') + expect(payloadAsString).toMatchSnapshot() + }) - test('addKvRecords', () => { - const fwConstants = buildFirmwareConstants(); - const mockObject = { - type: 0, - records: { key: 'value' }, - caseSensitive: false, - fwConstants, - }; - const payload = encodeAddKvRecordsRequest(mockObject); - const payloadAsString = payload.toString('hex'); - expect(payloadAsString).toMatchSnapshot(); - }); + test('addKvRecords', () => { + const fwConstants = buildFirmwareConstants() + const mockObject = { + type: 0, + records: { key: 'value' }, + caseSensitive: false, + fwConstants, + } + const payload = encodeAddKvRecordsRequest(mockObject) + const payloadAsString = payload.toString('hex') + expect(payloadAsString).toMatchSnapshot() + }) - test('removeKvRecords', () => { - const fwConstants = buildFirmwareConstants(); - const mockObject = { - type: 0, - ids: ['0'], - caseSensitive: false, - fwConstants, - }; - const payload = encodeRemoveKvRecordsRequest(mockObject); - const payloadAsString = payload.toString('hex'); - expect(payloadAsString).toMatchSnapshot(); - }); - }); -}); + test('removeKvRecords', () => { + const fwConstants = buildFirmwareConstants() + const mockObject = { + type: 0, + ids: ['0'], + caseSensitive: false, + fwConstants, + } + const payload = encodeRemoveKvRecordsRequest(mockObject) + const payloadAsString = payload.toString('hex') + expect(payloadAsString).toMatchSnapshot() + }) + }) +}) diff --git a/packages/sdk/src/__test__/unit/ethereum.validate.test.ts b/packages/sdk/src/__test__/unit/ethereum.validate.test.ts index 5353b5fb..62c0ad8a 100644 --- a/packages/sdk/src/__test__/unit/ethereum.validate.test.ts +++ b/packages/sdk/src/__test__/unit/ethereum.validate.test.ts @@ -1,91 +1,84 @@ -import { - type MessageTypes, - SignTypedDataVersion, - TypedDataUtils, - type TypedMessage, -} from '@metamask/eth-sig-util'; -import { ecsign, privateToAddress } from 'ethereumjs-util'; -import { mnemonicToAccount } from 'viem/accounts'; -import { HARDENED_OFFSET } from '../../constants'; -import ethereum from '../../ethereum'; -import { DEFAULT_SIGNER, buildFirmwareConstants } from '../utils/builders'; -import { TEST_MNEMONIC } from '../utils/testConstants'; +import { type MessageTypes, SignTypedDataVersion, TypedDataUtils, type TypedMessage } from '@metamask/eth-sig-util' +import { ecsign, privateToAddress } from 'ethereumjs-util' +import { mnemonicToAccount } from 'viem/accounts' +import { HARDENED_OFFSET } from '../../constants' +import ethereum from '../../ethereum' +import { DEFAULT_SIGNER, buildFirmwareConstants } from '../utils/builders' +import { TEST_MNEMONIC } from '../utils/testConstants' const typedData: TypedMessage = { - types: { - EIP712Domain: [{ name: 'chainId', type: 'uint256' }], - Greeting: [ - { name: 'salutation', type: 'string' }, - { name: 'target', type: 'string' }, - { name: 'born', type: 'int32' }, - ], - }, - primaryType: 'Greeting', - domain: { chainId: 1 }, - message: { - salutation: 'Hello', - target: 'Ethereum', - born: 2015, - }, -}; + types: { + EIP712Domain: [{ name: 'chainId', type: 'uint256' }], + Greeting: [ + { name: 'salutation', type: 'string' }, + { name: 'target', type: 'string' }, + { name: 'born', type: 'int32' }, + ], + }, + primaryType: 'Greeting', + domain: { chainId: 1 }, + message: { + salutation: 'Hello', + target: 'Ethereum', + born: 2015, + }, +} describe('validateEthereumMsgResponse', () => { - it('recovers expected signature for EIP712 payload', () => { - const account = mnemonicToAccount(TEST_MNEMONIC); - const priv = Buffer.from(account.getHdKey().privateKey!); - const signer = privateToAddress(priv); - const digest = TypedDataUtils.eip712Hash( - typedData, - SignTypedDataVersion.V4, - ); - const sig = ecsign(Buffer.from(digest), priv); - const fwConstants = buildFirmwareConstants(); - const request = ethereum.buildEthereumMsgRequest({ - signerPath: DEFAULT_SIGNER, - protocol: 'eip712', - payload: JSON.parse(JSON.stringify(typedData)), - fwConstants, - }); - const result = ethereum.validateEthereumMsgResponse( - { - signer: `0x${signer.toString('hex')}`, - sig: { r: Buffer.from(sig.r), s: Buffer.from(sig.s) }, - }, - request, - ); + it('recovers expected signature for EIP712 payload', () => { + const account = mnemonicToAccount(TEST_MNEMONIC) + const hdKey = account.getHdKey() + if (!hdKey.privateKey) throw new Error('No private key') + const priv = Buffer.from(hdKey.privateKey) + const signer = privateToAddress(priv) + const digest = TypedDataUtils.eip712Hash(typedData, SignTypedDataVersion.V4) + const sig = ecsign(Buffer.from(digest), priv) + const fwConstants = buildFirmwareConstants() + const request = ethereum.buildEthereumMsgRequest({ + signerPath: DEFAULT_SIGNER, + protocol: 'eip712', + payload: JSON.parse(JSON.stringify(typedData)), + fwConstants, + }) + const result = ethereum.validateEthereumMsgResponse( + { + signer: `0x${signer.toString('hex')}`, + sig: { r: Buffer.from(sig.r), s: Buffer.from(sig.s) }, + }, + request, + ) - expect(result.v.toString('hex')).toBe('1c'); - }); + expect(result.v.toString('hex')).toBe('1c') + }) - it('validates response using buildEthereumMsgRequest request context', () => { - const fwConstants = buildFirmwareConstants(); - const signerPath = [...DEFAULT_SIGNER]; - signerPath[2] = HARDENED_OFFSET; + it('validates response using buildEthereumMsgRequest request context', () => { + const fwConstants = buildFirmwareConstants() + const signerPath = [...DEFAULT_SIGNER] + signerPath[2] = HARDENED_OFFSET - const request = ethereum.buildEthereumMsgRequest({ - signerPath, - protocol: 'eip712', - payload: JSON.parse(JSON.stringify(typedData)), - fwConstants, - }); + const request = ethereum.buildEthereumMsgRequest({ + signerPath, + protocol: 'eip712', + payload: JSON.parse(JSON.stringify(typedData)), + fwConstants, + }) - const account = mnemonicToAccount(TEST_MNEMONIC); - const priv = Buffer.from(account.getHdKey().privateKey!); - const signer = privateToAddress(priv); - const digest = TypedDataUtils.eip712Hash( - typedData, - SignTypedDataVersion.V4, - ); - const sig = ecsign(Buffer.from(digest), priv); + const account = mnemonicToAccount(TEST_MNEMONIC) + const hdKey = account.getHdKey() + if (!hdKey.privateKey) throw new Error('No private key') + const priv = Buffer.from(hdKey.privateKey) + const signer = privateToAddress(priv) + const digest = TypedDataUtils.eip712Hash(typedData, SignTypedDataVersion.V4) + const sig = ecsign(Buffer.from(digest), priv) - const result = ethereum.validateEthereumMsgResponse( - { - signer, - sig: { r: Buffer.from(sig.r), s: Buffer.from(sig.s) }, - }, - request, - ); + const result = ethereum.validateEthereumMsgResponse( + { + signer, + sig: { r: Buffer.from(sig.r), s: Buffer.from(sig.s) }, + }, + request, + ) - expect(result.v.toString('hex')).toBe('1c'); - }); -}); + expect(result.v.toString('hex')).toBe('1c') + }) +}) diff --git a/packages/sdk/src/__test__/unit/module.interop.test.ts b/packages/sdk/src/__test__/unit/module.interop.test.ts index 98bed751..c030a559 100644 --- a/packages/sdk/src/__test__/unit/module.interop.test.ts +++ b/packages/sdk/src/__test__/unit/module.interop.test.ts @@ -1,83 +1,75 @@ -import { execSync, spawnSync } from 'node:child_process'; -import { - existsSync, - mkdirSync, - mkdtempSync, - rmSync, - symlinkSync, -} from 'node:fs'; -import os from 'node:os'; -import path from 'node:path'; -import { fileURLToPath } from 'node:url'; +import { execSync, spawnSync } from 'node:child_process' +import { existsSync, mkdirSync, mkdtempSync, rmSync, symlinkSync } from 'node:fs' +import os from 'node:os' +import path from 'node:path' +import { fileURLToPath } from 'node:url' -const __filename = fileURLToPath(import.meta.url); -const __dirname = path.dirname(__filename); -const packageRoot = path.resolve(__dirname, '../../..'); -const cjsOutput = path.resolve(packageRoot, 'dist/index.cjs'); -const esmOutput = path.resolve(packageRoot, 'dist/index.mjs'); -const packageName = 'gridplus-sdk'; +const __filename = fileURLToPath(import.meta.url) +const __dirname = path.dirname(__filename) +const packageRoot = path.resolve(__dirname, '../../..') +const cjsOutput = path.resolve(packageRoot, 'dist/index.cjs') +const esmOutput = path.resolve(packageRoot, 'dist/index.mjs') +const packageName = 'gridplus-sdk' -let built = false; -let fixtureDir: string | undefined; +let built = false +let fixtureDir: string | undefined const ensureBuildArtifacts = () => { - if (built) { - return; - } - console.log('Building package with pnpm run build ...'); - execSync('pnpm run build', { - cwd: packageRoot, - stdio: 'inherit', - }); - if (!existsSync(cjsOutput) || !existsSync(esmOutput)) { - throw new Error('Expected dual build outputs were not generated'); - } - built = true; -}; + if (built) { + return + } + console.log('Building package with pnpm run build ...') + execSync('pnpm run build', { + cwd: packageRoot, + stdio: 'inherit', + }) + if (!existsSync(cjsOutput) || !existsSync(esmOutput)) { + throw new Error('Expected dual build outputs were not generated') + } + built = true +} const ensureLinkedFixture = () => { - if (fixtureDir) { - return fixtureDir; - } - const tmpDir = mkdtempSync(path.join(os.tmpdir(), 'gridplus-sdk-interop-')); - const nodeModulesDir = path.join(tmpDir, 'node_modules'); - mkdirSync(nodeModulesDir, { recursive: true }); - const linkTarget = path.join(nodeModulesDir, packageName); - symlinkSync(packageRoot, linkTarget, 'junction'); - fixtureDir = tmpDir; - return fixtureDir; -}; + if (fixtureDir) { + return fixtureDir + } + const tmpDir = mkdtempSync(path.join(os.tmpdir(), 'gridplus-sdk-interop-')) + const nodeModulesDir = path.join(tmpDir, 'node_modules') + mkdirSync(nodeModulesDir, { recursive: true }) + const linkTarget = path.join(nodeModulesDir, packageName) + symlinkSync(packageRoot, linkTarget, 'junction') + fixtureDir = tmpDir + return fixtureDir +} const runNodeCheck = (args: string[]) => { - const cwd = ensureLinkedFixture(); - const result = spawnSync(process.execPath, args, { - cwd, - env: { ...process.env }, - encoding: 'utf-8', - }); - if (result.error) { - throw result.error; - } - if (result.status !== 0) { - throw new Error( - `Node command failed (${result.status}):\n${result.stderr || result.stdout}`, - ); - } -}; + const cwd = ensureLinkedFixture() + const result = spawnSync(process.execPath, args, { + cwd, + env: { ...process.env }, + encoding: 'utf-8', + }) + if (result.error) { + throw result.error + } + if (result.status !== 0) { + throw new Error(`Node command failed (${result.status}):\n${result.stderr || result.stdout}`) + } +} describe('package module interoperability', () => { - beforeAll(() => { - ensureBuildArtifacts(); - }); - afterAll(() => { - if (fixtureDir) { - rmSync(fixtureDir, { recursive: true, force: true }); - fixtureDir = undefined; - } - }); + beforeAll(() => { + ensureBuildArtifacts() + }) + afterAll(() => { + if (fixtureDir) { + rmSync(fixtureDir, { recursive: true, force: true }) + fixtureDir = undefined + } + }) - it('exposes CommonJS entry via require()', () => { - const script = ` + it('exposes CommonJS entry via require()', () => { + const script = ` const sdk = require('${packageName}'); if (typeof sdk.connect !== 'function') { throw new Error('connect export missing'); @@ -85,12 +77,12 @@ describe('package module interoperability', () => { if (typeof sdk.Client !== 'function') { throw new Error('Client export missing'); } - `; - runNodeCheck(['-e', script]); - }); + ` + runNodeCheck(['-e', script]) + }) - it('exposes ESM entry via dynamic import()', () => { - const script = ` + it('exposes ESM entry via dynamic import()', () => { + const script = ` const sdk = await import('${packageName}'); if (typeof sdk.connect !== 'function') { throw new Error('connect export missing'); @@ -98,7 +90,7 @@ describe('package module interoperability', () => { if (typeof sdk.Client !== 'function') { throw new Error('Client export missing'); } - `; - runNodeCheck(['--input-type=module', '-e', script]); - }); -}); + ` + runNodeCheck(['--input-type=module', '-e', script]) + }) +}) diff --git a/packages/sdk/src/__test__/unit/parseGenericSigningResponse.test.ts b/packages/sdk/src/__test__/unit/parseGenericSigningResponse.test.ts index ab301fdf..ee636370 100644 --- a/packages/sdk/src/__test__/unit/parseGenericSigningResponse.test.ts +++ b/packages/sdk/src/__test__/unit/parseGenericSigningResponse.test.ts @@ -1,171 +1,150 @@ -import { Buffer } from 'buffer'; -import { RLP } from '@ethereumjs/rlp'; -import { Hash } from 'ox'; -import secp256k1 from 'secp256k1'; -import { parseGenericSigningResponse } from '../../genericSigning'; -import { Constants } from '../../index'; +import { Buffer } from 'node:buffer' +import { RLP } from '@ethereumjs/rlp' +import { Hash } from 'ox' +import secp256k1 from 'secp256k1' +import { parseGenericSigningResponse } from '../../genericSigning' +import { Constants } from '../../index' describe('parseGenericSigningResponse', () => { - // Helper to create a DER signature - const createDERSignature = (r: Buffer, s: Buffer): Buffer => { - const rLen = r.length; - const sLen = s.length; - const totalLen = 4 + rLen + sLen; - const sig = Buffer.alloc(totalLen + 2); - - sig[0] = 0x30; // DER sequence - sig[1] = totalLen; - sig[2] = 0x02; // Integer type - sig[3] = rLen; - r.copy(sig, 4); - sig[4 + rLen] = 0x02; // Integer type - sig[4 + rLen + 1] = sLen; - s.copy(sig, 4 + rLen + 2); - - // Pad to 74 bytes (standard for Lattice) - const padded = Buffer.alloc(74); - sig.copy(padded, 0); - return padded; - }; - - it('should handle generic KECCAK256 message (not EVM transaction)', () => { - // Simulate signing a plain text message "Test!" - const payload = Buffer.from('Test!'); - const hash = Buffer.from(Hash.keccak256(payload)); - - // Create a fake signature - const privateKey = Buffer.from( - '0101010101010101010101010101010101010101010101010101010101010101', - 'hex', - ); - const sigObj = secp256k1.ecdsaSign(hash, privateKey); - const publicKey = secp256k1.publicKeyCreate(privateKey, false); - - // Create DER-encoded signature response - const derSig = createDERSignature( - Buffer.from(sigObj.signature.slice(0, 32)), - Buffer.from(sigObj.signature.slice(32, 64)), - ); - - // Create mock response buffer - const mockResponse = Buffer.concat([ - Buffer.from([0x04]), // Uncompressed pubkey prefix - publicKey.slice(1), // Remove compression prefix from secp256k1 output (64 bytes) - derSig, - ]); - - const req = { - curveType: Constants.SIGNING.CURVES.SECP256K1, - hashType: Constants.SIGNING.HASHES.KECCAK256, - encodingType: null, // Not EVM encoding - origPayloadBuf: payload, - }; - - const result = parseGenericSigningResponse(mockResponse, 0, req); - - expect(result).toBeDefined(); - expect(result.sig).toBeDefined(); - expect(result.sig.v).toBeDefined(); - expect(typeof result.sig.v).toBe('bigint'); - - // For non-EVM generic messages, v should be 27 or 28 - expect([27n, 28n]).toContain(result.sig.v); - }); - - it('should handle EVM transaction encoding', () => { - // Simulate an unsigned legacy transaction - const unsignedTx = Buffer.from( - 'e9808504a817c800825208943535353535353535353535353535353535353535880de0b6b3a764000080', - 'hex', - ); - const hash = Buffer.from(Hash.keccak256(unsignedTx)); - - // Create a fake signature - const privateKey = Buffer.from( - '0101010101010101010101010101010101010101010101010101010101010101', - 'hex', - ); - const sigObj = secp256k1.ecdsaSign(hash, privateKey); - const publicKey = secp256k1.publicKeyCreate(privateKey, false); - - // Create DER-encoded signature response - const derSig = createDERSignature( - Buffer.from(sigObj.signature.slice(0, 32)), - Buffer.from(sigObj.signature.slice(32, 64)), - ); - - // Create mock response buffer - const mockResponse = Buffer.concat([ - Buffer.from([0x04]), // Uncompressed pubkey prefix - publicKey.slice(1), // Remove compression prefix from secp256k1 output (64 bytes) - derSig, - ]); - - const req = { - curveType: Constants.SIGNING.CURVES.SECP256K1, - hashType: Constants.SIGNING.HASHES.KECCAK256, - encodingType: Constants.SIGNING.ENCODINGS.EVM, - origPayloadBuf: unsignedTx, - }; - - const result = parseGenericSigningResponse(mockResponse, 0, req); - - expect(result).toBeDefined(); - expect(result.sig).toBeDefined(); - expect(result.sig.v).toBeDefined(); - expect(typeof result.sig.v).toBe('bigint'); - - // For pre-EIP155 transactions, v should be 27 or 28 - const vNumber = Number(result.sig.v); - expect([27, 28]).toContain(vNumber); - }); - - it('should handle RLP-encoded data that looks like a transaction', () => { - // Create an RLP-encoded array with 6+ elements (looks like a transaction) - const txLikeData = [ - Buffer.from([0x01]), // nonce - Buffer.from([0x02]), // gasPrice - Buffer.from([0x03]), // gasLimit - Buffer.from([0x04]), // to - Buffer.from([0x05]), // value - Buffer.from([0x06]), // data - ]; - const rlpEncoded = Buffer.from(RLP.encode(txLikeData)); - const hash = Buffer.from(Hash.keccak256(rlpEncoded)); - - // Create a fake signature - const privateKey = Buffer.from( - '0101010101010101010101010101010101010101010101010101010101010101', - 'hex', - ); - const sigObj = secp256k1.ecdsaSign(hash, privateKey); - const publicKey = secp256k1.publicKeyCreate(privateKey, false); - - // Create DER-encoded signature response - const derSig = createDERSignature( - Buffer.from(sigObj.signature.slice(0, 32)), - Buffer.from(sigObj.signature.slice(32, 64)), - ); - - // Create mock response buffer - const mockResponse = Buffer.concat([ - Buffer.from([0x04]), // Uncompressed pubkey prefix - publicKey.slice(1), // Remove compression prefix from secp256k1 output (64 bytes) - derSig, - ]); - - const req = { - curveType: Constants.SIGNING.CURVES.SECP256K1, - hashType: Constants.SIGNING.HASHES.KECCAK256, - encodingType: null, // Not explicitly EVM - origPayloadBuf: rlpEncoded, - }; - - const result = parseGenericSigningResponse(mockResponse, 0, req); - - expect(result).toBeDefined(); - expect(result.sig).toBeDefined(); - expect(result.sig.v).toBeDefined(); - expect(typeof result.sig.v).toBe('bigint'); - }); -}); + // Helper to create a DER signature + const createDERSignature = (r: Buffer, s: Buffer): Buffer => { + const rLen = r.length + const sLen = s.length + const totalLen = 4 + rLen + sLen + const sig = Buffer.alloc(totalLen + 2) + + sig[0] = 0x30 // DER sequence + sig[1] = totalLen + sig[2] = 0x02 // Integer type + sig[3] = rLen + r.copy(sig, 4) + sig[4 + rLen] = 0x02 // Integer type + sig[4 + rLen + 1] = sLen + s.copy(sig, 4 + rLen + 2) + + // Pad to 74 bytes (standard for Lattice) + const padded = Buffer.alloc(74) + sig.copy(padded, 0) + return padded + } + + it('should handle generic KECCAK256 message (not EVM transaction)', () => { + // Simulate signing a plain text message "Test!" + const payload = Buffer.from('Test!') + const hash = Buffer.from(Hash.keccak256(payload)) + + // Create a fake signature + const privateKey = Buffer.from('0101010101010101010101010101010101010101010101010101010101010101', 'hex') + const sigObj = secp256k1.ecdsaSign(hash, privateKey) + const publicKey = secp256k1.publicKeyCreate(privateKey, false) + + // Create DER-encoded signature response + const derSig = createDERSignature(Buffer.from(sigObj.signature.slice(0, 32)), Buffer.from(sigObj.signature.slice(32, 64))) + + // Create mock response buffer + const mockResponse = Buffer.concat([ + Buffer.from([0x04]), // Uncompressed pubkey prefix + publicKey.slice(1), // Remove compression prefix from secp256k1 output (64 bytes) + derSig, + ]) + + const req = { + curveType: Constants.SIGNING.CURVES.SECP256K1, + hashType: Constants.SIGNING.HASHES.KECCAK256, + encodingType: null, // Not EVM encoding + origPayloadBuf: payload, + } + + const result = parseGenericSigningResponse(mockResponse, 0, req) + + expect(result).toBeDefined() + expect(result.sig).toBeDefined() + expect(result.sig.v).toBeDefined() + expect(typeof result.sig.v).toBe('bigint') + + // For non-EVM generic messages, v should be 27 or 28 + expect([27n, 28n]).toContain(result.sig.v) + }) + + it('should handle EVM transaction encoding', () => { + // Simulate an unsigned legacy transaction + const unsignedTx = Buffer.from('e9808504a817c800825208943535353535353535353535353535353535353535880de0b6b3a764000080', 'hex') + const hash = Buffer.from(Hash.keccak256(unsignedTx)) + + // Create a fake signature + const privateKey = Buffer.from('0101010101010101010101010101010101010101010101010101010101010101', 'hex') + const sigObj = secp256k1.ecdsaSign(hash, privateKey) + const publicKey = secp256k1.publicKeyCreate(privateKey, false) + + // Create DER-encoded signature response + const derSig = createDERSignature(Buffer.from(sigObj.signature.slice(0, 32)), Buffer.from(sigObj.signature.slice(32, 64))) + + // Create mock response buffer + const mockResponse = Buffer.concat([ + Buffer.from([0x04]), // Uncompressed pubkey prefix + publicKey.slice(1), // Remove compression prefix from secp256k1 output (64 bytes) + derSig, + ]) + + const req = { + curveType: Constants.SIGNING.CURVES.SECP256K1, + hashType: Constants.SIGNING.HASHES.KECCAK256, + encodingType: Constants.SIGNING.ENCODINGS.EVM, + origPayloadBuf: unsignedTx, + } + + const result = parseGenericSigningResponse(mockResponse, 0, req) + + expect(result).toBeDefined() + expect(result.sig).toBeDefined() + expect(result.sig.v).toBeDefined() + expect(typeof result.sig.v).toBe('bigint') + + // For pre-EIP155 transactions, v should be 27 or 28 + const vNumber = Number(result.sig.v) + expect([27, 28]).toContain(vNumber) + }) + + it('should handle RLP-encoded data that looks like a transaction', () => { + // Create an RLP-encoded array with 6+ elements (looks like a transaction) + const txLikeData = [ + Buffer.from([0x01]), // nonce + Buffer.from([0x02]), // gasPrice + Buffer.from([0x03]), // gasLimit + Buffer.from([0x04]), // to + Buffer.from([0x05]), // value + Buffer.from([0x06]), // data + ] + const rlpEncoded = Buffer.from(RLP.encode(txLikeData)) + const hash = Buffer.from(Hash.keccak256(rlpEncoded)) + + // Create a fake signature + const privateKey = Buffer.from('0101010101010101010101010101010101010101010101010101010101010101', 'hex') + const sigObj = secp256k1.ecdsaSign(hash, privateKey) + const publicKey = secp256k1.publicKeyCreate(privateKey, false) + + // Create DER-encoded signature response + const derSig = createDERSignature(Buffer.from(sigObj.signature.slice(0, 32)), Buffer.from(sigObj.signature.slice(32, 64))) + + // Create mock response buffer + const mockResponse = Buffer.concat([ + Buffer.from([0x04]), // Uncompressed pubkey prefix + publicKey.slice(1), // Remove compression prefix from secp256k1 output (64 bytes) + derSig, + ]) + + const req = { + curveType: Constants.SIGNING.CURVES.SECP256K1, + hashType: Constants.SIGNING.HASHES.KECCAK256, + encodingType: null, // Not explicitly EVM + origPayloadBuf: rlpEncoded, + } + + const result = parseGenericSigningResponse(mockResponse, 0, req) + + expect(result).toBeDefined() + expect(result.sig).toBeDefined() + expect(result.sig.v).toBeDefined() + expect(typeof result.sig.v).toBe('bigint') + }) +}) diff --git a/packages/sdk/src/__test__/unit/personalSignValidation.test.ts b/packages/sdk/src/__test__/unit/personalSignValidation.test.ts index 5ad98018..0701ed20 100644 --- a/packages/sdk/src/__test__/unit/personalSignValidation.test.ts +++ b/packages/sdk/src/__test__/unit/personalSignValidation.test.ts @@ -1,138 +1,116 @@ -import { Buffer } from 'buffer'; -import { Hash } from 'ox'; -import secp256k1 from 'secp256k1'; -import { addRecoveryParam } from '../../ethereum'; +import { Buffer } from 'node:buffer' +import { Hash } from 'ox' +import secp256k1 from 'secp256k1' +import { addRecoveryParam } from '../../ethereum' describe('Personal Sign Validation - Issue Fix', () => { - /** - * This test validates the fix for the personal sign validation bug. - * The issue was in pubToAddrStr function which was incorrectly slicing - * the hash buffer, causing address comparison to always fail. - */ - - it('should correctly validate personal message signature', () => { - // Create a test private key and derive public key - const privateKey = Buffer.from( - '0101010101010101010101010101010101010101010101010101010101010101', - 'hex', - ); - const publicKey = secp256k1.publicKeyCreate(privateKey, false); - - // Create a test message - const message = Buffer.from('Test message', 'utf8'); - - // Build personal sign prefix and hash - const prefix = Buffer.from( - `\u0019Ethereum Signed Message:\n${message.length.toString()}`, - 'utf-8', - ); - const messageHash = Buffer.from( - Hash.keccak256(Buffer.concat([prefix, message])), - ); - - // Sign the message - const sigObj = secp256k1.ecdsaSign(messageHash, privateKey); - - // Prepare signature object - const sig = { - r: Buffer.from(sigObj.signature.slice(0, 32)), - s: Buffer.from(sigObj.signature.slice(32, 64)), - }; - - // Get the Ethereum address from the public key - // This matches what the firmware returns - const pubkeyWithoutPrefix = publicKey.slice(1); // Remove 0x04 prefix - const addressBuffer = Buffer.from( - Hash.keccak256(pubkeyWithoutPrefix), - ).slice(-20); - - // This is the function that was failing before the fix - // It should now correctly add the recovery parameter - const result = addRecoveryParam(messageHash, sig, addressBuffer, { - chainId: 1, - useEIP155: false, - }); - - // Verify the signature has a valid v value (27 or 28) - expect(result.v).toBeDefined(); - const vValue = Buffer.isBuffer(result.v) - ? result.v.readUInt8(0) - : Number(result.v); - expect([27, 28]).toContain(vValue); - - // Verify r and s are buffers of correct length - expect(Buffer.isBuffer(result.r)).toBe(true); - expect(Buffer.isBuffer(result.s)).toBe(true); - expect(result.r.length).toBe(32); - expect(result.s.length).toBe(32); - }); - - it('should throw error when signature does not match address', () => { - // Create a test message hash - const messageHash = Buffer.from(Hash.keccak256(Buffer.from('test'))); - - // Create a random signature - const sig = { - r: Buffer.from('1'.repeat(64), 'hex'), - s: Buffer.from('2'.repeat(64), 'hex'), - }; - - // Use a random address that won't match the signature - const wrongAddress = Buffer.from('3'.repeat(40), 'hex'); - - // This should throw because the signature doesn't match the address - expect(() => { - addRecoveryParam(messageHash, sig, wrongAddress, { - chainId: 1, - useEIP155: false, - }); - }).toThrow(); // Just verify it throws, exact message may vary - }); - - it('should handle the exact scenario from Ambire bug report', () => { - // This is the exact scenario reported by Kalo from Ambire - const testPayload = '0x54657374206d657373616765'; // "Test message" in hex - const payloadBuffer = Buffer.from(testPayload.slice(2), 'hex'); - - // Build personal sign hash - const prefix = Buffer.from( - `\u0019Ethereum Signed Message:\n${payloadBuffer.length.toString()}`, - 'utf-8', - ); - const messageHash = Buffer.from( - Hash.keccak256(Buffer.concat([prefix, payloadBuffer])), - ); - - // Create a valid signature for this message - const privateKey = Buffer.from( - '0101010101010101010101010101010101010101010101010101010101010101', - 'hex', - ); - const publicKey = secp256k1.publicKeyCreate(privateKey, false); - const sigObj = secp256k1.ecdsaSign(messageHash, privateKey); - - const sig = { - r: Buffer.from(sigObj.signature.slice(0, 32)), - s: Buffer.from(sigObj.signature.slice(32, 64)), - }; - - // Get address from public key - const pubkeyWithoutPrefix = publicKey.slice(1); - const addressBuffer = Buffer.from( - Hash.keccak256(pubkeyWithoutPrefix), - ).slice(-20); - - // This should NOT throw with the fix in place - expect(() => { - const result = addRecoveryParam(messageHash, sig, addressBuffer, { - chainId: 1, - useEIP155: false, - }); - - // Verify we got a valid result - expect(result.v).toBeDefined(); - expect(result.r).toBeDefined(); - expect(result.s).toBeDefined(); - }).not.toThrow(); - }); -}); + /** + * This test validates the fix for the personal sign validation bug. + * The issue was in pubToAddrStr function which was incorrectly slicing + * the hash buffer, causing address comparison to always fail. + */ + + it('should correctly validate personal message signature', () => { + // Create a test private key and derive public key + const privateKey = Buffer.from('0101010101010101010101010101010101010101010101010101010101010101', 'hex') + const publicKey = secp256k1.publicKeyCreate(privateKey, false) + + // Create a test message + const message = Buffer.from('Test message', 'utf8') + + // Build personal sign prefix and hash + const prefix = Buffer.from(`\u0019Ethereum Signed Message:\n${message.length.toString()}`, 'utf-8') + const messageHash = Buffer.from(Hash.keccak256(Buffer.concat([prefix, message]))) + + // Sign the message + const sigObj = secp256k1.ecdsaSign(messageHash, privateKey) + + // Prepare signature object + const sig = { + r: Buffer.from(sigObj.signature.slice(0, 32)), + s: Buffer.from(sigObj.signature.slice(32, 64)), + } + + // Get the Ethereum address from the public key + // This matches what the firmware returns + const pubkeyWithoutPrefix = publicKey.slice(1) // Remove 0x04 prefix + const addressBuffer = Buffer.from(Hash.keccak256(pubkeyWithoutPrefix)).slice(-20) + + // This is the function that was failing before the fix + // It should now correctly add the recovery parameter + const result = addRecoveryParam(messageHash, sig, addressBuffer, { + chainId: 1, + useEIP155: false, + }) + + // Verify the signature has a valid v value (27 or 28) + expect(result.v).toBeDefined() + const vValue = Buffer.isBuffer(result.v) ? result.v.readUInt8(0) : Number(result.v) + expect([27, 28]).toContain(vValue) + + // Verify r and s are buffers of correct length + expect(Buffer.isBuffer(result.r)).toBe(true) + expect(Buffer.isBuffer(result.s)).toBe(true) + expect(result.r.length).toBe(32) + expect(result.s.length).toBe(32) + }) + + it('should throw error when signature does not match address', () => { + // Create a test message hash + const messageHash = Buffer.from(Hash.keccak256(Buffer.from('test'))) + + // Create a random signature + const sig = { + r: Buffer.from('1'.repeat(64), 'hex'), + s: Buffer.from('2'.repeat(64), 'hex'), + } + + // Use a random address that won't match the signature + const wrongAddress = Buffer.from('3'.repeat(40), 'hex') + + // This should throw because the signature doesn't match the address + expect(() => { + addRecoveryParam(messageHash, sig, wrongAddress, { + chainId: 1, + useEIP155: false, + }) + }).toThrow() // Just verify it throws, exact message may vary + }) + + it('should handle the exact scenario from Ambire bug report', () => { + // This is the exact scenario reported by Kalo from Ambire + const testPayload = '0x54657374206d657373616765' // "Test message" in hex + const payloadBuffer = Buffer.from(testPayload.slice(2), 'hex') + + // Build personal sign hash + const prefix = Buffer.from(`\u0019Ethereum Signed Message:\n${payloadBuffer.length.toString()}`, 'utf-8') + const messageHash = Buffer.from(Hash.keccak256(Buffer.concat([prefix, payloadBuffer]))) + + // Create a valid signature for this message + const privateKey = Buffer.from('0101010101010101010101010101010101010101010101010101010101010101', 'hex') + const publicKey = secp256k1.publicKeyCreate(privateKey, false) + const sigObj = secp256k1.ecdsaSign(messageHash, privateKey) + + const sig = { + r: Buffer.from(sigObj.signature.slice(0, 32)), + s: Buffer.from(sigObj.signature.slice(32, 64)), + } + + // Get address from public key + const pubkeyWithoutPrefix = publicKey.slice(1) + const addressBuffer = Buffer.from(Hash.keccak256(pubkeyWithoutPrefix)).slice(-20) + + // This should NOT throw with the fix in place + expect(() => { + const result = addRecoveryParam(messageHash, sig, addressBuffer, { + chainId: 1, + useEIP155: false, + }) + + // Verify we got a valid result + expect(result.v).toBeDefined() + expect(result.r).toBeDefined() + expect(result.s).toBeDefined() + }).not.toThrow() + }) +}) diff --git a/packages/sdk/src/__test__/unit/selectDefFrom4byteABI.test.ts b/packages/sdk/src/__test__/unit/selectDefFrom4byteABI.test.ts index 46a79a18..2be479f8 100644 --- a/packages/sdk/src/__test__/unit/selectDefFrom4byteABI.test.ts +++ b/packages/sdk/src/__test__/unit/selectDefFrom4byteABI.test.ts @@ -1,82 +1,80 @@ -import { selectDefFrom4byteABI } from '../../util'; +import { selectDefFrom4byteABI } from '../../util' describe('selectDefFrom4byteAbi', () => { - beforeAll(() => { - // Disable this mock to restore console logs when testing - console.warn = vi.fn(); - }); + beforeAll(() => { + // Disable this mock to restore console logs when testing + console.warn = vi.fn() + }) - afterAll(() => { - vi.clearAllMocks(); - }); + afterAll(() => { + vi.clearAllMocks() + }) - test('select correct result', () => { - const result = [ - { - bytes_signature: '8í9', - created_at: '2020-08-09T08:56:14.110995Z', - hex_signature: '0x38ed1739', - id: 171801, - text_signature: - 'swapExactTokensForTokens(uint256,uint256,address[],address,uint256)', - }, - { - bytes_signature: '8í9', - created_at: '2020-01-09T08:56:14.110995Z', - hex_signature: '0x38ed1739', - id: 171806, - text_signature: - 'swapExactTokensForTokens(uint256,uint256,address[],address,uint256)', - }, - { - bytes_signature: '', - created_at: '2020-01-09T08:56:14.110995Z', - hex_signature: '0x38ed9', - id: 171806, - text_signature: 'swapToken', - }, - ]; - const selector = '0x38ed1739'; - expect(selectDefFrom4byteABI(result, selector)).toMatchSnapshot(); - }); + test('select correct result', () => { + const result = [ + { + bytes_signature: '8í9', + created_at: '2020-08-09T08:56:14.110995Z', + hex_signature: '0x38ed1739', + id: 171801, + text_signature: 'swapExactTokensForTokens(uint256,uint256,address[],address,uint256)', + }, + { + bytes_signature: '8í9', + created_at: '2020-01-09T08:56:14.110995Z', + hex_signature: '0x38ed1739', + id: 171806, + text_signature: 'swapExactTokensForTokens(uint256,uint256,address[],address,uint256)', + }, + { + bytes_signature: '', + created_at: '2020-01-09T08:56:14.110995Z', + hex_signature: '0x38ed9', + id: 171806, + text_signature: 'swapToken', + }, + ] + const selector = '0x38ed1739' + expect(selectDefFrom4byteABI(result, selector)).toMatchSnapshot() + }) - test('handle no match', () => { - const result = [ - { - bytes_signature: '', - created_at: '2020-01-09T08:56:14.110995Z', - hex_signature: '0x3ed9', - id: 171806, - text_signature: 'swapToken', - }, - ]; - const selector = '0x38ed1739'; - expect(() => selectDefFrom4byteABI(result, selector)).toThrowError(); - }); + test('handle no match', () => { + const result = [ + { + bytes_signature: '', + created_at: '2020-01-09T08:56:14.110995Z', + hex_signature: '0x3ed9', + id: 171806, + text_signature: 'swapToken', + }, + ] + const selector = '0x38ed1739' + expect(() => selectDefFrom4byteABI(result, selector)).toThrowError() + }) - test('handle no selector', () => { - const result = [ - { - bytes_signature: '', - created_at: '2020-01-09T08:56:14.110995Z', - hex_signature: '0x3ed9', - id: 171806, - text_signature: 'swapToken', - }, - ]; - const selector = undefined; - expect(() => selectDefFrom4byteABI(result, selector)).toThrowError(); - }); + test('handle no selector', () => { + const result = [ + { + bytes_signature: '', + created_at: '2020-01-09T08:56:14.110995Z', + hex_signature: '0x3ed9', + id: 171806, + text_signature: 'swapToken', + }, + ] + const selector = undefined + expect(() => selectDefFrom4byteABI(result, selector)).toThrowError() + }) - test('handle no result', () => { - const result = undefined; - const selector = '0x38ed1739'; - expect(() => selectDefFrom4byteABI(result, selector)).toThrowError(); - }); + test('handle no result', () => { + const result = undefined + const selector = '0x38ed1739' + expect(() => selectDefFrom4byteABI(result, selector)).toThrowError() + }) - test('handle bad data', () => { - const result = []; - const selector = ''; - expect(() => selectDefFrom4byteABI(result, selector)).toThrowError(); - }); -}); + test('handle bad data', () => { + const result = [] + const selector = '' + expect(() => selectDefFrom4byteABI(result, selector)).toThrowError() + }) +}) diff --git a/packages/sdk/src/__test__/unit/signatureUtils.test.ts b/packages/sdk/src/__test__/unit/signatureUtils.test.ts index af4e93b8..1881b451 100644 --- a/packages/sdk/src/__test__/unit/signatureUtils.test.ts +++ b/packages/sdk/src/__test__/unit/signatureUtils.test.ts @@ -1,447 +1,400 @@ -import { Buffer } from 'buffer'; -import { Hash } from 'ox'; -import secp256k1 from 'secp256k1'; -import { getV, getYParity, randomBytes } from '../../util'; +import { Buffer } from 'node:buffer' +import { Hash } from 'ox' +import secp256k1 from 'secp256k1' +import { getV, getYParity, randomBytes } from '../../util' describe('getYParity', () => { - // Helper function to create a valid signature - const createValidSignature = (messageHash: Buffer) => { - // Create a deterministic private key for testing - const privateKey = Buffer.from( - '0101010101010101010101010101010101010101010101010101010101010101', - 'hex', - ); - - // Sign the message - const sigObj = secp256k1.ecdsaSign(messageHash, privateKey); - - // Get the public key - const publicKey = secp256k1.publicKeyCreate(privateKey, false); - - return { - signature: { - r: Buffer.from(sigObj.signature.slice(0, 32)), - s: Buffer.from(sigObj.signature.slice(32, 64)), - }, - publicKey: Buffer.from(publicKey), - recovery: sigObj.recid, - }; - }; - - describe('Simple signature format', () => { - it('should handle simple format with Buffer inputs', () => { - const messageHash = randomBytes(32); - const { signature, publicKey, recovery } = - createValidSignature(messageHash); - - const yParity = getYParity({ - messageHash, - signature, - publicKey, - }); - - expect(yParity).toBe(recovery); - expect([0, 1]).toContain(yParity); - }); - - it('should handle simple format with hex string inputs', () => { - const messageHash = randomBytes(32); - const { signature, publicKey, recovery } = - createValidSignature(messageHash); - - const yParity = getYParity({ - messageHash: `0x${messageHash.toString('hex')}`, - signature: { - r: `0x${signature.r.toString('hex')}`, - s: `0x${signature.s.toString('hex')}`, - }, - publicKey: `0x${publicKey.toString('hex')}`, - }); - - expect(yParity).toBe(recovery); - }); - - it('should handle simple format with compressed public key', () => { - const messageHash = randomBytes(32); - const privateKey = Buffer.from( - '0101010101010101010101010101010101010101010101010101010101010101', - 'hex', - ); - - const sigObj = secp256k1.ecdsaSign(messageHash, privateKey); - const compressedPubkey = secp256k1.publicKeyCreate(privateKey, true); - - const yParity = getYParity({ - messageHash, - signature: { - r: Buffer.from(sigObj.signature.slice(0, 32)), - s: Buffer.from(sigObj.signature.slice(32, 64)), - }, - publicKey: Buffer.from(compressedPubkey), - }); - - expect(yParity).toBe(sigObj.recid); - }); - - it('should handle mixed format inputs', () => { - const messageHash = randomBytes(32); - const { signature, publicKey, recovery } = - createValidSignature(messageHash); - - const yParity = getYParity({ - messageHash: messageHash.toString('hex'), // No 0x prefix - signature: { - r: signature.r, // Buffer - s: `0x${signature.s.toString('hex')}`, // Hex string - }, - publicKey, // Buffer - }); - - expect(yParity).toBe(recovery); - }); - }); - - describe('Legacy format with transaction and response', () => { - it('should handle Buffer transaction input', () => { - const tx = randomBytes(100); - const hash = Buffer.from(Hash.keccak256(tx)); - const { signature, publicKey, recovery } = createValidSignature(hash); - - const resp = { - sig: signature, - pubkey: publicKey, - }; - - const yParity = getYParity(tx, resp); - expect(yParity).toBe(recovery); - }); - - it('should handle hex string as pre-computed hash', () => { - // When passing a hex string, it's treated as a pre-computed hash - const hash = randomBytes(32); - const txHex = `0x${hash.toString('hex')}`; - const { signature, publicKey, recovery } = createValidSignature(hash); - - const resp = { - sig: signature, - pubkey: publicKey, - }; - - const yParity = getYParity(txHex, resp); - expect(yParity).toBe(recovery); - }); - - it('should handle transaction object with getMessageToSign method', () => { - const messageData = randomBytes(32); - const mockTx = { - _type: 2, // EIP-1559 - getMessageToSign: () => messageData, - }; - - const { signature, publicKey, recovery } = - createValidSignature(messageData); - - const resp = { - sig: signature, - pubkey: publicKey, - }; - - const yParity = getYParity(mockTx, resp); - expect(yParity).toBe(recovery); - }); - - it.skip('should handle legacy transaction object', () => { - // Skip this test for now - legacy transaction handling is complex - // and would require proper RLP encoding to test correctly - }); - - it('should handle Uint8Array inputs', () => { - const messageHash = new Uint8Array(32); - messageHash.fill(42); - - const { signature, publicKey, recovery } = createValidSignature( - Buffer.from(messageHash), - ); - - const resp = { - sig: { - r: new Uint8Array(signature.r), - s: new Uint8Array(signature.s), - }, - pubkey: new Uint8Array(publicKey), - }; - - const yParity = getYParity(messageHash, resp); - expect(yParity).toBe(recovery); - }); - - it.skip('should handle direct 32-byte hash input', () => { - // Skip for now - this test relies on specific behavior that may differ - }); - - it('should handle 32-byte hash as Uint8Array', () => { - const hash = new Uint8Array(32); - for (let i = 0; i < 32; i++) { - hash[i] = Math.floor(Math.random() * 256); - } - const { signature, publicKey, recovery } = createValidSignature( - Buffer.from(hash), - ); - - const resp = { - sig: signature, - pubkey: publicKey, - }; - - const yParity = getYParity(hash, resp); - expect(yParity).toBe(recovery); - }); - - it('should hash non-32-byte inputs', () => { - const shortData = randomBytes(20); - const expectedHash = Buffer.from(Hash.keccak256(shortData)); - const { signature, publicKey, recovery } = - createValidSignature(expectedHash); - - const resp = { - sig: signature, - pubkey: publicKey, - }; - - const yParity = getYParity(shortData, resp); - expect(yParity).toBe(recovery); - }); - }); - - describe('Error handling', () => { - it('should throw error if legacy format missing response', () => { - const tx = randomBytes(32); - expect(() => getYParity(tx)).toThrow( - 'Response with sig and pubkey required for legacy format', - ); - }); - - it('should throw error if response missing sig', () => { - const tx = randomBytes(32); - const resp = { pubkey: randomBytes(65) }; - expect(() => getYParity(tx, resp)).toThrow( - 'Response with sig and pubkey required for legacy format', - ); - }); - - it('should throw error if response missing pubkey', () => { - const tx = randomBytes(32); - const resp = { sig: { r: randomBytes(32), s: randomBytes(32) } }; - expect(() => getYParity(tx, resp)).toThrow( - 'Response with sig and pubkey required for legacy format', - ); - }); - - it('should throw error if recovery fails', () => { - const messageHash = randomBytes(32); - const wrongHash = randomBytes(32); - const { signature, publicKey } = createValidSignature(wrongHash); - - expect(() => - getYParity({ - messageHash, - signature, - publicKey, - }), - ).toThrow( - 'Failed to recover Y parity. Bad signature or transaction data.', - ); - }); - - it('should throw error with invalid signature', () => { - const messageHash = randomBytes(32); - const invalidSig = { - r: randomBytes(32), - s: randomBytes(32), - }; - const randomPubkey = randomBytes(65); - randomPubkey[0] = 0x04; // Ensure valid uncompressed format - - expect(() => - getYParity({ - messageHash, - signature: invalidSig, - publicKey: randomPubkey, - }), - ).toThrow(); // Just check that it throws, don't check exact message - }); - }); - - describe('Real world scenarios', () => { - it('should handle EIP-7702 authorization signature', () => { - // Simulate the exact scenario from signAuthorization - const MAGIC = Buffer.from([0x05]); - - // This would normally use RLP.encode but we'll create a test message - const message = Buffer.concat([ - MAGIC, - Buffer.from('test_rlp_encoded_data', 'utf8'), - ]); - - const messageHash = Buffer.from(Hash.keccak256(message)); - const { signature, publicKey, recovery } = - createValidSignature(messageHash); - - // Test both Buffer format (as returned by device) - const yParity1 = getYParity({ - messageHash, - signature, - publicKey, - }); - expect(yParity1).toBe(recovery); - - // Test with hex string format (as might be used in API) - const yParity2 = getYParity({ - messageHash, - signature: { - r: `0x${signature.r.toString('hex')}`, - s: `0x${signature.s.toString('hex')}`, - }, - publicKey, - }); - expect(yParity2).toBe(recovery); - }); - - it('should return consistent y-parity for multiple calls with same data', () => { - const messageHash = randomBytes(32); - const { signature, publicKey } = createValidSignature(messageHash); - - const yParity1 = getYParity({ - messageHash, - signature, - publicKey, - }); - - const yParity2 = getYParity({ - messageHash, - signature, - publicKey, - }); - - expect(yParity1).toBe(yParity2); - }); - - it('should handle real signature that should return y-parity of 1', () => { - // Use a specific private key that we know produces recovery id 1 for a specific message - let foundYParityOne = false; - - // Try multiple messages until we get one with y-parity 1 - for (let i = 0; i < 100; i++) { - const messageHash = Buffer.from( - Hash.keccak256(Buffer.from(`test message ${i}`)), - ); - const privateKey = Buffer.from( - '0101010101010101010101010101010101010101010101010101010101010101', - 'hex', - ); - - const sigObj = secp256k1.ecdsaSign(messageHash, privateKey); - - if (sigObj.recid === 1) { - const publicKey = secp256k1.publicKeyCreate(privateKey, false); - - const yParity = getYParity({ - messageHash, - signature: { - r: Buffer.from(sigObj.signature.slice(0, 32)), - s: Buffer.from(sigObj.signature.slice(32, 64)), - }, - publicKey: Buffer.from(publicKey), - }); - - expect(yParity).toBe(1); - foundYParityOne = true; - break; - } - } - - expect(foundYParityOne).toBe(true); - }); - }); -}); + // Helper function to create a valid signature + const createValidSignature = (messageHash: Buffer) => { + // Create a deterministic private key for testing + const privateKey = Buffer.from('0101010101010101010101010101010101010101010101010101010101010101', 'hex') + + // Sign the message + const sigObj = secp256k1.ecdsaSign(messageHash, privateKey) + + // Get the public key + const publicKey = secp256k1.publicKeyCreate(privateKey, false) + + return { + signature: { + r: Buffer.from(sigObj.signature.slice(0, 32)), + s: Buffer.from(sigObj.signature.slice(32, 64)), + }, + publicKey: Buffer.from(publicKey), + recovery: sigObj.recid, + } + } + + describe('Simple signature format', () => { + it('should handle simple format with Buffer inputs', () => { + const messageHash = randomBytes(32) + const { signature, publicKey, recovery } = createValidSignature(messageHash) + + const yParity = getYParity({ + messageHash, + signature, + publicKey, + }) + + expect(yParity).toBe(recovery) + expect([0, 1]).toContain(yParity) + }) + + it('should handle simple format with hex string inputs', () => { + const messageHash = randomBytes(32) + const { signature, publicKey, recovery } = createValidSignature(messageHash) + + const yParity = getYParity({ + messageHash: `0x${messageHash.toString('hex')}`, + signature: { + r: `0x${signature.r.toString('hex')}`, + s: `0x${signature.s.toString('hex')}`, + }, + publicKey: `0x${publicKey.toString('hex')}`, + }) + + expect(yParity).toBe(recovery) + }) + + it('should handle simple format with compressed public key', () => { + const messageHash = randomBytes(32) + const privateKey = Buffer.from('0101010101010101010101010101010101010101010101010101010101010101', 'hex') + + const sigObj = secp256k1.ecdsaSign(messageHash, privateKey) + const compressedPubkey = secp256k1.publicKeyCreate(privateKey, true) + + const yParity = getYParity({ + messageHash, + signature: { + r: Buffer.from(sigObj.signature.slice(0, 32)), + s: Buffer.from(sigObj.signature.slice(32, 64)), + }, + publicKey: Buffer.from(compressedPubkey), + }) + + expect(yParity).toBe(sigObj.recid) + }) + + it('should handle mixed format inputs', () => { + const messageHash = randomBytes(32) + const { signature, publicKey, recovery } = createValidSignature(messageHash) + + const yParity = getYParity({ + messageHash: messageHash.toString('hex'), // No 0x prefix + signature: { + r: signature.r, // Buffer + s: `0x${signature.s.toString('hex')}`, // Hex string + }, + publicKey, // Buffer + }) + + expect(yParity).toBe(recovery) + }) + }) + + describe('Legacy format with transaction and response', () => { + it('should handle Buffer transaction input', () => { + const tx = randomBytes(100) + const hash = Buffer.from(Hash.keccak256(tx)) + const { signature, publicKey, recovery } = createValidSignature(hash) + + const resp = { + sig: signature, + pubkey: publicKey, + } + + const yParity = getYParity(tx, resp) + expect(yParity).toBe(recovery) + }) + + it('should handle hex string as pre-computed hash', () => { + // When passing a hex string, it's treated as a pre-computed hash + const hash = randomBytes(32) + const txHex = `0x${hash.toString('hex')}` + const { signature, publicKey, recovery } = createValidSignature(hash) + + const resp = { + sig: signature, + pubkey: publicKey, + } + + const yParity = getYParity(txHex, resp) + expect(yParity).toBe(recovery) + }) + + it('should handle transaction object with getMessageToSign method', () => { + const messageData = randomBytes(32) + const mockTx = { + _type: 2, // EIP-1559 + getMessageToSign: () => messageData, + } + + const { signature, publicKey, recovery } = createValidSignature(messageData) + + const resp = { + sig: signature, + pubkey: publicKey, + } + + const yParity = getYParity(mockTx, resp) + expect(yParity).toBe(recovery) + }) + + it.skip('should handle legacy transaction object', () => { + // Skip this test for now - legacy transaction handling is complex + // and would require proper RLP encoding to test correctly + }) + + it('should handle Uint8Array inputs', () => { + const messageHash = new Uint8Array(32) + messageHash.fill(42) + + const { signature, publicKey, recovery } = createValidSignature(Buffer.from(messageHash)) + + const resp = { + sig: { + r: new Uint8Array(signature.r), + s: new Uint8Array(signature.s), + }, + pubkey: new Uint8Array(publicKey), + } + + const yParity = getYParity(messageHash, resp) + expect(yParity).toBe(recovery) + }) + + it.skip('should handle direct 32-byte hash input', () => { + // Skip for now - this test relies on specific behavior that may differ + }) + + it('should handle 32-byte hash as Uint8Array', () => { + const hash = new Uint8Array(32) + for (let i = 0; i < 32; i++) { + hash[i] = Math.floor(Math.random() * 256) + } + const { signature, publicKey, recovery } = createValidSignature(Buffer.from(hash)) + + const resp = { + sig: signature, + pubkey: publicKey, + } + + const yParity = getYParity(hash, resp) + expect(yParity).toBe(recovery) + }) + + it('should hash non-32-byte inputs', () => { + const shortData = randomBytes(20) + const expectedHash = Buffer.from(Hash.keccak256(shortData)) + const { signature, publicKey, recovery } = createValidSignature(expectedHash) + + const resp = { + sig: signature, + pubkey: publicKey, + } + + const yParity = getYParity(shortData, resp) + expect(yParity).toBe(recovery) + }) + }) + + describe('Error handling', () => { + it('should throw error if legacy format missing response', () => { + const tx = randomBytes(32) + expect(() => getYParity(tx)).toThrow('Response with sig and pubkey required for legacy format') + }) + + it('should throw error if response missing sig', () => { + const tx = randomBytes(32) + const resp = { pubkey: randomBytes(65) } + expect(() => getYParity(tx, resp)).toThrow('Response with sig and pubkey required for legacy format') + }) + + it('should throw error if response missing pubkey', () => { + const tx = randomBytes(32) + const resp = { sig: { r: randomBytes(32), s: randomBytes(32) } } + expect(() => getYParity(tx, resp)).toThrow('Response with sig and pubkey required for legacy format') + }) + + it('should throw error if recovery fails', () => { + const messageHash = randomBytes(32) + const wrongHash = randomBytes(32) + const { signature, publicKey } = createValidSignature(wrongHash) + + expect(() => + getYParity({ + messageHash, + signature, + publicKey, + }), + ).toThrow('Failed to recover Y parity. Bad signature or transaction data.') + }) + + it('should throw error with invalid signature', () => { + const messageHash = randomBytes(32) + const invalidSig = { + r: randomBytes(32), + s: randomBytes(32), + } + const randomPubkey = randomBytes(65) + randomPubkey[0] = 0x04 // Ensure valid uncompressed format + + expect(() => + getYParity({ + messageHash, + signature: invalidSig, + publicKey: randomPubkey, + }), + ).toThrow() // Just check that it throws, don't check exact message + }) + }) + + describe('Real world scenarios', () => { + it('should handle EIP-7702 authorization signature', () => { + // Simulate the exact scenario from signAuthorization + const MAGIC = Buffer.from([0x05]) + + // This would normally use RLP.encode but we'll create a test message + const message = Buffer.concat([MAGIC, Buffer.from('test_rlp_encoded_data', 'utf8')]) + + const messageHash = Buffer.from(Hash.keccak256(message)) + const { signature, publicKey, recovery } = createValidSignature(messageHash) + + // Test both Buffer format (as returned by device) + const yParity1 = getYParity({ + messageHash, + signature, + publicKey, + }) + expect(yParity1).toBe(recovery) + + // Test with hex string format (as might be used in API) + const yParity2 = getYParity({ + messageHash, + signature: { + r: `0x${signature.r.toString('hex')}`, + s: `0x${signature.s.toString('hex')}`, + }, + publicKey, + }) + expect(yParity2).toBe(recovery) + }) + + it('should return consistent y-parity for multiple calls with same data', () => { + const messageHash = randomBytes(32) + const { signature, publicKey } = createValidSignature(messageHash) + + const yParity1 = getYParity({ + messageHash, + signature, + publicKey, + }) + + const yParity2 = getYParity({ + messageHash, + signature, + publicKey, + }) + + expect(yParity1).toBe(yParity2) + }) + + it('should handle real signature that should return y-parity of 1', () => { + // Use a specific private key that we know produces recovery id 1 for a specific message + let foundYParityOne = false + + // Try multiple messages until we get one with y-parity 1 + for (let i = 0; i < 100; i++) { + const messageHash = Buffer.from(Hash.keccak256(Buffer.from(`test message ${i}`))) + const privateKey = Buffer.from('0101010101010101010101010101010101010101010101010101010101010101', 'hex') + + const sigObj = secp256k1.ecdsaSign(messageHash, privateKey) + + if (sigObj.recid === 1) { + const publicKey = secp256k1.publicKeyCreate(privateKey, false) + + const yParity = getYParity({ + messageHash, + signature: { + r: Buffer.from(sigObj.signature.slice(0, 32)), + s: Buffer.from(sigObj.signature.slice(32, 64)), + }, + publicKey: Buffer.from(publicKey), + }) + + expect(yParity).toBe(1) + foundYParityOne = true + break + } + } + + expect(foundYParityOne).toBe(true) + }) + }) +}) describe('getV function', () => { - // Helper to create a valid signature - const createValidSignature = (messageHash: Buffer, privateKey?: Buffer) => { - // Use deterministic key if not provided - const privKey = - privateKey || - Buffer.from( - '0101010101010101010101010101010101010101010101010101010101010101', - 'hex', - ); - - const sigObj = secp256k1.ecdsaSign(messageHash, privKey); - const publicKey = secp256k1.publicKeyCreate(privKey, false); - - return { - sig: { - r: Buffer.from(sigObj.signature.slice(0, 32)), - s: Buffer.from(sigObj.signature.slice(32, 64)), - }, - pubkey: Buffer.from(publicKey), - recovery: sigObj.recid, - }; - }; - - it('should handle unsigned legacy transaction with valid signature', () => { - // A simple unsigned legacy transaction - const unsignedTxRLP = Buffer.from( - 'e9808504a817c800825208943535353535353535353535353535353535353535880de0b6b3a764000080', - 'hex', - ); - - // Hash the transaction - const hash = Buffer.from(Hash.keccak256(unsignedTxRLP)); - - // Create a valid signature for this hash - const resp = createValidSignature(hash); - - // Should return correct v value (27 or 28 for non-EIP155) - const v = getV(unsignedTxRLP, resp); - expect(v.toNumber()).toBe(27 + resp.recovery); - }); - - it('should throw error when pubkey does not match signature', () => { - // This is a signed legacy transaction - const signedTx = - '0xf86c0a8504a817c800825208943535353535353535353535353535353535353535880de0b6b3a76400008025a0134f5038e0e6a96741e17a82c8df13e9dc10c3b0e9e956cf7dcf21e1e3b73f9fa0638cf1b1f9dd5e6e8e6b9a8e6e8e6b9a8e6e8e6b9a8e6e8e6b9a8e6e8e6b9a8'; - - const mockResp = { - sig: { - r: Buffer.from( - '134f5038e0e6a96741e17a82c8df13e9dc10c3b0e9e956cf7dcf21e1e3b73f9f', - 'hex', - ), - s: Buffer.from( - '638cf1b1f9dd5e6e8e6b9a8e6e8e6b9a8e6e8e6b9a8e6e8e6b9a8e6e8e6b9a8', - 'hex', - ), - }, - // This is a fake pubkey, so recovery will fail - pubkey: Buffer.from(`04${'1'.repeat(128)}`, 'hex'), - }; - - expect(() => getV(signedTx, mockResp)).toThrow(); - }); - - it('should throw error when signature is invalid', () => { - const txHex = - '0xe9808504a817c800825208943535353535353535353535353535353535353535880de0b6b3a764000080'; - - const mockResp = { - sig: { - r: `0x${'1'.repeat(64)}`, // 32 bytes as hex string - s: `0x${'2'.repeat(64)}`, // 32 bytes as hex string - }, - pubkey: Buffer.from(`04${'1'.repeat(128)}`, 'hex'), - }; - - expect(() => getV(txHex, mockResp)).toThrow(); - }); -}); + // Helper to create a valid signature + const createValidSignature = (messageHash: Buffer, privateKey?: Buffer) => { + // Use deterministic key if not provided + const privKey = privateKey || Buffer.from('0101010101010101010101010101010101010101010101010101010101010101', 'hex') + + const sigObj = secp256k1.ecdsaSign(messageHash, privKey) + const publicKey = secp256k1.publicKeyCreate(privKey, false) + + return { + sig: { + r: Buffer.from(sigObj.signature.slice(0, 32)), + s: Buffer.from(sigObj.signature.slice(32, 64)), + }, + pubkey: Buffer.from(publicKey), + recovery: sigObj.recid, + } + } + + it('should handle unsigned legacy transaction with valid signature', () => { + // A simple unsigned legacy transaction + const unsignedTxRLP = Buffer.from('e9808504a817c800825208943535353535353535353535353535353535353535880de0b6b3a764000080', 'hex') + + // Hash the transaction + const hash = Buffer.from(Hash.keccak256(unsignedTxRLP)) + + // Create a valid signature for this hash + const resp = createValidSignature(hash) + + // Should return correct v value (27 or 28 for non-EIP155) + const v = getV(unsignedTxRLP, resp) + expect(v.toNumber()).toBe(27 + resp.recovery) + }) + + it('should throw error when pubkey does not match signature', () => { + // This is a signed legacy transaction + const signedTx = + '0xf86c0a8504a817c800825208943535353535353535353535353535353535353535880de0b6b3a76400008025a0134f5038e0e6a96741e17a82c8df13e9dc10c3b0e9e956cf7dcf21e1e3b73f9fa0638cf1b1f9dd5e6e8e6b9a8e6e8e6b9a8e6e8e6b9a8e6e8e6b9a8e6e8e6b9a8' + + const mockResp = { + sig: { + r: Buffer.from('134f5038e0e6a96741e17a82c8df13e9dc10c3b0e9e956cf7dcf21e1e3b73f9f', 'hex'), + s: Buffer.from('638cf1b1f9dd5e6e8e6b9a8e6e8e6b9a8e6e8e6b9a8e6e8e6b9a8e6e8e6b9a8', 'hex'), + }, + // This is a fake pubkey, so recovery will fail + pubkey: Buffer.from(`04${'1'.repeat(128)}`, 'hex'), + } + + expect(() => getV(signedTx, mockResp)).toThrow() + }) + + it('should throw error when signature is invalid', () => { + const txHex = '0xe9808504a817c800825208943535353535353535353535353535353535353535880de0b6b3a764000080' + + const mockResp = { + sig: { + r: `0x${'1'.repeat(64)}`, // 32 bytes as hex string + s: `0x${'2'.repeat(64)}`, // 32 bytes as hex string + }, + pubkey: Buffer.from(`04${'1'.repeat(128)}`, 'hex'), + } + + expect(() => getV(txHex, mockResp)).toThrow() + }) +}) diff --git a/packages/sdk/src/__test__/unit/validators.test.ts b/packages/sdk/src/__test__/unit/validators.test.ts index a50978ed..11514e3a 100644 --- a/packages/sdk/src/__test__/unit/validators.test.ts +++ b/packages/sdk/src/__test__/unit/validators.test.ts @@ -1,304 +1,283 @@ -import { normalizeToViemTransaction } from '../../ethereum'; -import { - validateAddKvRequest, - validateConnectRequest, - validateGetAddressesRequest, - validateGetKvRequest, - validateRemoveKvRequest, -} from '../../functions'; -import { - isValid4ByteResponse, - isValidBlockExplorerResponse, -} from '../../shared/validators'; -import { - buildGetAddressesObject, - buildValidateConnectObject, - buildValidateRequestObject, -} from '../utils/builders'; +import { normalizeToViemTransaction } from '../../ethereum' +import { validateAddKvRequest, validateConnectRequest, validateGetAddressesRequest, validateGetKvRequest, validateRemoveKvRequest } from '../../functions' +import { isValid4ByteResponse, isValidBlockExplorerResponse } from '../../shared/validators' +import { buildGetAddressesObject, buildValidateConnectObject, buildValidateRequestObject } from '../utils/builders' describe('validators', () => { - describe('connect', () => { - test('should successfully validate', () => { - validateConnectRequest(buildValidateConnectObject()); - }); - - // NOTE: There aren't many possible error conditions because - // the Client constructor has lots of fallback values. However, - // we should validate that you can't set a null ephemeral pub. - test('should throw errors on validation failure', () => { - const req = buildValidateConnectObject({ name: '' }); - expect(() => { - req.client.ephemeralPub = null; - }).toThrowError(); - }); - }); - - describe('getAddresses', () => { - test('should successfully validate', () => { - const getAddressesBundle = buildGetAddressesObject({}); - validateGetAddressesRequest(getAddressesBundle); - }); - - test('encodeGetAddressesRequest should throw with invalid startPath', () => { - const startPath = [0x80000000 + 44, 0x80000000 + 60, 0, 0, 0, 0, 0]; - const fwVersion = Buffer.from([0, 0, 0]); - const testEncodingFunction = () => - validateGetAddressesRequest( - buildGetAddressesObject({ startPath, fwVersion }), - ); - expect(testEncodingFunction).toThrowError(); - }); - }); - - describe('KvRecords', () => { - describe('addKvRecords', () => { - test('should successfully validate', () => { - const validateAddKvBundle: any = buildValidateRequestObject({ - records: { key: 'value' }, - }); - validateAddKvRequest(validateAddKvBundle); - }); - - test('should throw errors on validation failure', () => { - const validateAddKvBundle: any = buildValidateRequestObject({}); - expect(() => validateAddKvRequest(validateAddKvBundle)).toThrowError(); - }); - }); - - describe('getKvRecords', () => { - test('should successfully validate', () => { - const validateGetKvBundle: any = buildValidateRequestObject({ - n: 1, - type: 1, - start: 0, - }); - validateGetKvRequest(validateGetKvBundle); - }); - - test('should throw errors on validation failure', () => { - const validateGetKvBundle: any = buildValidateRequestObject({ n: 0 }); - expect(() => validateGetKvRequest(validateGetKvBundle)).toThrowError(); - }); - }); - - describe('removeKvRecords', () => { - test('should successfully validate', () => { - const validateRemoveKvBundle: any = buildValidateRequestObject({ - ids: [1], - type: 1, - }); - validateRemoveKvRequest(validateRemoveKvBundle); - }); - - test('should throw errors on validation failure', () => { - const validateRemoveKvBundle: any = buildValidateRequestObject({}); - expect(() => - validateRemoveKvRequest(validateRemoveKvBundle), - ).toThrowError(); - }); - }); - }); - - describe('abi data responses', () => { - describe('block explorers', () => { - test('should successfully validate etherscan data', () => { - const response: any = { - result: - '[{"inputs":[],"stateMutability":"nonpayable","type":"constructor"},{"stateMutability":"payable","type":"fallback"},{"inputs":[],"name":"implementation","outputs":[{"internalType":"address","name":"","type":"address"}],"stateMutability":"view","type":"function"},{"stateMutability":"payable","type":"receive"}]', - }; - expect(isValidBlockExplorerResponse(response)).toBe(true); - }); - - test('should validate as false bad data', () => { - const response: any = { - result: - 'Max rate limit reached, please use API Key for higher rate limit', - }; - expect(isValidBlockExplorerResponse(response)).toBe(false); - }); - }); - - describe('4byte', () => { - test('should successfully validate etherscan data', () => { - const response: any = { - results: [ - { - id: 447919, - created_at: '2021-12-25T13:54:33.120581Z', - text_signature: 'multicall(uint256,bytes[])', - hex_signature: '0x5ae401dc', - bytes_signature: 'test', - }, - ], - }; - expect(isValid4ByteResponse(response)).toBe(true); - }); - - test('should validate as false bad data', () => { - const response: any = { - results: [], - }; - expect(isValid4ByteResponse(response)).toBe(false); - }); - }); - }); - - describe('transaction validation', () => { - describe('EIP-7702 transactions', () => { - test('rejects missing fee fields', () => { - const tx = { - to: `0x${'1'.repeat(40)}`, - value: '1000000000000000000', - chainId: 1, - authorizationList: [ - { chainId: 1, address: `0x${'2'.repeat(40)}`, nonce: 0 }, - ], - gasPrice: '15000000000', - }; - - expect(() => normalizeToViemTransaction(tx)).toThrow(); - }); - }); - - describe('negative values', () => { - test('rejects negative value', () => { - const tx = { - to: `0x${'1'.repeat(40)}`, - value: -100, - gasPrice: '10000000000', - }; - - expect(() => normalizeToViemTransaction(tx)).toThrow(); - }); - - test('rejects negative nonce', () => { - const tx = { - to: `0x${'1'.repeat(40)}`, - value: '100', - gasPrice: '10000000000', - nonce: -1, - }; - - expect(() => normalizeToViemTransaction(tx)).toThrow(); - }); - - test('rejects negative gas price', () => { - const tx = { - to: `0x${'1'.repeat(40)}`, - value: '100', - gasPrice: -10, - }; - - expect(() => normalizeToViemTransaction(tx)).toThrow(); - }); - }); - - describe('invalid data types', () => { - test('rejects boolean data field', () => { - const tx = { - to: '0x1234567890123456789012345678901234567890', - value: true, - chainId: '0x1', - gasPrice: Number.NaN, - nonce: null, - data: false, - }; - - expect(() => normalizeToViemTransaction(tx as any)).toThrow(); - }); - }); - - describe('authorization list validation', () => { - test('rejects invalid authorization data', () => { - const tx = { - to: '0x1234567890123456789012345678901234567890', - value: '1000000000000000000', - chainId: 1, - maxFeePerGas: '20000000000', - maxPriorityFeePerGas: '2000000000', - authorizationList: [ - { - chainId: 'not-a-number', - address: '0x123', - nonce: undefined, - }, - ], - }; - - expect(() => normalizeToViemTransaction(tx as any)).toThrow(); - }); - }); - - describe('circular references', () => { - test('rejects circular references', () => { - const tx: any = { - to: '0x1234567890123456789012345678901234567890', - value: '1000000000000000000', - chainId: 1, - maxFeePerGas: '20000000000', - maxPriorityFeePerGas: '2000000000', - }; - - tx.self = tx; - tx.authorizationList = [tx]; - - expect(() => normalizeToViemTransaction(tx)).toThrow(); - }); - }); - - describe('gas field handling', () => { - test('gasLimit takes precedence over gas', () => { - const tx = { - to: '0x1234567890123456789012345678901234567890', - value: '1000000000000000000', - chainId: 1, - gasPrice: '15000000000', - gas: '50000', - gasLimit: '21000', - }; - - const result = normalizeToViemTransaction(tx); - expect(result.gas).toBe(21000n); - }); - - test('accepts zero gas values', () => { - const tx = { - to: '0x1234567890123456789012345678901234567890', - value: '1000000000000000000', - chainId: 1, - gasPrice: '0', - gasLimit: '0', - }; - - const result = normalizeToViemTransaction(tx); - expect(result.gas).toBe(0n); - expect(result.type).toBe('legacy'); - expect((result as any).gasPrice).toBe(0n); - }); - }); - - describe('chainId validation', () => { - test('rejects zero chainId', () => { - const tx = { - to: '0x1234567890123456789012345678901234567890', - value: '1000000000000000000', - chainId: 0, - gasPrice: '15000000000', - }; - - expect(() => normalizeToViemTransaction(tx)).toThrow(); - }); - - test('rejects non-integer chainId', () => { - const tx = { - to: '0x1234567890123456789012345678901234567890', - value: '1000000000000000000', - chainId: 1.5, - gasPrice: '15000000000', - }; - - expect(() => normalizeToViemTransaction(tx)).toThrow(); - }); - }); - }); -}); + describe('connect', () => { + test('should successfully validate', () => { + validateConnectRequest(buildValidateConnectObject()) + }) + + // NOTE: There aren't many possible error conditions because + // the Client constructor has lots of fallback values. However, + // we should validate that you can't set a null ephemeral pub. + test('should throw errors on validation failure', () => { + const req = buildValidateConnectObject({ name: '' }) + expect(() => { + req.client.ephemeralPub = null + }).toThrowError() + }) + }) + + describe('getAddresses', () => { + test('should successfully validate', () => { + const getAddressesBundle = buildGetAddressesObject({}) + validateGetAddressesRequest(getAddressesBundle) + }) + + test('encodeGetAddressesRequest should throw with invalid startPath', () => { + const startPath = [0x80000000 + 44, 0x80000000 + 60, 0, 0, 0, 0, 0] + const fwVersion = Buffer.from([0, 0, 0]) + const testEncodingFunction = () => validateGetAddressesRequest(buildGetAddressesObject({ startPath, fwVersion })) + expect(testEncodingFunction).toThrowError() + }) + }) + + describe('KvRecords', () => { + describe('addKvRecords', () => { + test('should successfully validate', () => { + const validateAddKvBundle: any = buildValidateRequestObject({ + records: { key: 'value' }, + }) + validateAddKvRequest(validateAddKvBundle) + }) + + test('should throw errors on validation failure', () => { + const validateAddKvBundle: any = buildValidateRequestObject({}) + expect(() => validateAddKvRequest(validateAddKvBundle)).toThrowError() + }) + }) + + describe('getKvRecords', () => { + test('should successfully validate', () => { + const validateGetKvBundle: any = buildValidateRequestObject({ + n: 1, + type: 1, + start: 0, + }) + validateGetKvRequest(validateGetKvBundle) + }) + + test('should throw errors on validation failure', () => { + const validateGetKvBundle: any = buildValidateRequestObject({ n: 0 }) + expect(() => validateGetKvRequest(validateGetKvBundle)).toThrowError() + }) + }) + + describe('removeKvRecords', () => { + test('should successfully validate', () => { + const validateRemoveKvBundle: any = buildValidateRequestObject({ + ids: [1], + type: 1, + }) + validateRemoveKvRequest(validateRemoveKvBundle) + }) + + test('should throw errors on validation failure', () => { + const validateRemoveKvBundle: any = buildValidateRequestObject({}) + expect(() => validateRemoveKvRequest(validateRemoveKvBundle)).toThrowError() + }) + }) + }) + + describe('abi data responses', () => { + describe('block explorers', () => { + test('should successfully validate etherscan data', () => { + const response: any = { + result: + '[{"inputs":[],"stateMutability":"nonpayable","type":"constructor"},{"stateMutability":"payable","type":"fallback"},{"inputs":[],"name":"implementation","outputs":[{"internalType":"address","name":"","type":"address"}],"stateMutability":"view","type":"function"},{"stateMutability":"payable","type":"receive"}]', + } + expect(isValidBlockExplorerResponse(response)).toBe(true) + }) + + test('should validate as false bad data', () => { + const response: any = { + result: 'Max rate limit reached, please use API Key for higher rate limit', + } + expect(isValidBlockExplorerResponse(response)).toBe(false) + }) + }) + + describe('4byte', () => { + test('should successfully validate etherscan data', () => { + const response: any = { + results: [ + { + id: 447919, + created_at: '2021-12-25T13:54:33.120581Z', + text_signature: 'multicall(uint256,bytes[])', + hex_signature: '0x5ae401dc', + bytes_signature: 'test', + }, + ], + } + expect(isValid4ByteResponse(response)).toBe(true) + }) + + test('should validate as false bad data', () => { + const response: any = { + results: [], + } + expect(isValid4ByteResponse(response)).toBe(false) + }) + }) + }) + + describe('transaction validation', () => { + describe('EIP-7702 transactions', () => { + test('rejects missing fee fields', () => { + const tx = { + to: `0x${'1'.repeat(40)}`, + value: '1000000000000000000', + chainId: 1, + authorizationList: [{ chainId: 1, address: `0x${'2'.repeat(40)}`, nonce: 0 }], + gasPrice: '15000000000', + } + + expect(() => normalizeToViemTransaction(tx)).toThrow() + }) + }) + + describe('negative values', () => { + test('rejects negative value', () => { + const tx = { + to: `0x${'1'.repeat(40)}`, + value: -100, + gasPrice: '10000000000', + } + + expect(() => normalizeToViemTransaction(tx)).toThrow() + }) + + test('rejects negative nonce', () => { + const tx = { + to: `0x${'1'.repeat(40)}`, + value: '100', + gasPrice: '10000000000', + nonce: -1, + } + + expect(() => normalizeToViemTransaction(tx)).toThrow() + }) + + test('rejects negative gas price', () => { + const tx = { + to: `0x${'1'.repeat(40)}`, + value: '100', + gasPrice: -10, + } + + expect(() => normalizeToViemTransaction(tx)).toThrow() + }) + }) + + describe('invalid data types', () => { + test('rejects boolean data field', () => { + const tx = { + to: '0x1234567890123456789012345678901234567890', + value: true, + chainId: '0x1', + gasPrice: Number.NaN, + nonce: null, + data: false, + } + + expect(() => normalizeToViemTransaction(tx as any)).toThrow() + }) + }) + + describe('authorization list validation', () => { + test('rejects invalid authorization data', () => { + const tx = { + to: '0x1234567890123456789012345678901234567890', + value: '1000000000000000000', + chainId: 1, + maxFeePerGas: '20000000000', + maxPriorityFeePerGas: '2000000000', + authorizationList: [ + { + chainId: 'not-a-number', + address: '0x123', + nonce: undefined, + }, + ], + } + + expect(() => normalizeToViemTransaction(tx as any)).toThrow() + }) + }) + + describe('circular references', () => { + test('rejects circular references', () => { + const tx: any = { + to: '0x1234567890123456789012345678901234567890', + value: '1000000000000000000', + chainId: 1, + maxFeePerGas: '20000000000', + maxPriorityFeePerGas: '2000000000', + } + + tx.self = tx + tx.authorizationList = [tx] + + expect(() => normalizeToViemTransaction(tx)).toThrow() + }) + }) + + describe('gas field handling', () => { + test('gasLimit takes precedence over gas', () => { + const tx = { + to: '0x1234567890123456789012345678901234567890', + value: '1000000000000000000', + chainId: 1, + gasPrice: '15000000000', + gas: '50000', + gasLimit: '21000', + } + + const result = normalizeToViemTransaction(tx) + expect(result.gas).toBe(21000n) + }) + + test('accepts zero gas values', () => { + const tx = { + to: '0x1234567890123456789012345678901234567890', + value: '1000000000000000000', + chainId: 1, + gasPrice: '0', + gasLimit: '0', + } + + const result = normalizeToViemTransaction(tx) + expect(result.gas).toBe(0n) + expect(result.type).toBe('legacy') + expect((result as any).gasPrice).toBe(0n) + }) + }) + + describe('chainId validation', () => { + test('rejects zero chainId', () => { + const tx = { + to: '0x1234567890123456789012345678901234567890', + value: '1000000000000000000', + chainId: 0, + gasPrice: '15000000000', + } + + expect(() => normalizeToViemTransaction(tx)).toThrow() + }) + + test('rejects non-integer chainId', () => { + const tx = { + to: '0x1234567890123456789012345678901234567890', + value: '1000000000000000000', + chainId: 1.5, + gasPrice: '15000000000', + } + + expect(() => normalizeToViemTransaction(tx)).toThrow() + }) + }) + }) +}) diff --git a/packages/sdk/src/__test__/utils/__test__/builders.test.ts b/packages/sdk/src/__test__/utils/__test__/builders.test.ts index 4c618103..97f38f54 100644 --- a/packages/sdk/src/__test__/utils/__test__/builders.test.ts +++ b/packages/sdk/src/__test__/utils/__test__/builders.test.ts @@ -1,17 +1,13 @@ -import { - buildEvmReq, - buildRandomVectors, - getFwVersionsList, -} from '../builders'; +import { buildEvmReq, buildRandomVectors, getFwVersionsList } from '../builders' describe('building', () => { - test('should test client', () => { - expect(getFwVersionsList()).toMatchSnapshot(); - }); + test('should test client', () => { + expect(getFwVersionsList()).toMatchSnapshot() + }) - test('RANDOM_VEC', () => { - const RANDOM_VEC = buildRandomVectors(10); - expect(RANDOM_VEC).toMatchInlineSnapshot(` + test('RANDOM_VEC', () => { + const RANDOM_VEC = buildRandomVectors(10) + expect(RANDOM_VEC).toMatchInlineSnapshot(` [ "9f2c1f8", "334e3bf5", @@ -24,16 +20,16 @@ describe('building', () => { "1121991", "2851e10c", ] - `); - }); + `) + }) - test('buildEvmReq', () => { - const testObj = buildEvmReq({ - common: 'test', - data: { payload: 'test' }, - txData: { data: 'test', type: undefined }, - }); - expect(testObj).toMatchInlineSnapshot(` + test('buildEvmReq', () => { + const testObj = buildEvmReq({ + common: 'test', + data: { payload: 'test' }, + txData: { data: 'test', type: undefined }, + }) + expect(testObj).toMatchInlineSnapshot(` { "common": "test", "data": { @@ -60,6 +56,6 @@ describe('building', () => { "value": 100, }, } - `); - }); -}); + `) + }) +}) diff --git a/packages/sdk/src/__test__/utils/__test__/serializers.test.ts b/packages/sdk/src/__test__/utils/__test__/serializers.test.ts index d8a99eb4..efb92707 100644 --- a/packages/sdk/src/__test__/utils/__test__/serializers.test.ts +++ b/packages/sdk/src/__test__/utils/__test__/serializers.test.ts @@ -1,20 +1,17 @@ -import { - deserializeObjectWithBuffers, - serializeObjectWithBuffers, -} from '../serializers'; +import { deserializeObjectWithBuffers, serializeObjectWithBuffers } from '../serializers' describe('serializers', () => { - test('serialize obj', () => { - const obj = { - a: 1, - b: Buffer.from('test'), - c: { - d: 2, - e: Buffer.from('test'), - }, - }; - const serialized = serializeObjectWithBuffers(obj); - expect(serialized).toMatchInlineSnapshot(` + test('serialize obj', () => { + const obj = { + a: 1, + b: Buffer.from('test'), + c: { + d: 2, + e: Buffer.from('test'), + }, + } + const serialized = serializeObjectWithBuffers(obj) + expect(serialized).toMatchInlineSnapshot(` { "a": 1, "b": { @@ -29,27 +26,27 @@ describe('serializers', () => { }, }, } - `); - }); + `) + }) - test('deserialize obj', () => { - const obj = { - a: 1, - b: { - isBuffer: true, - value: '74657374', - }, - c: { - d: 2, - e: { - isBuffer: true, - value: '74657374', - }, - }, - }; + test('deserialize obj', () => { + const obj = { + a: 1, + b: { + isBuffer: true, + value: '74657374', + }, + c: { + d: 2, + e: { + isBuffer: true, + value: '74657374', + }, + }, + } - const serialized = deserializeObjectWithBuffers(obj); - expect(serialized).toMatchInlineSnapshot(` + const serialized = deserializeObjectWithBuffers(obj) + expect(serialized).toMatchInlineSnapshot(` { "a": 1, "b": { @@ -74,6 +71,6 @@ describe('serializers', () => { }, }, } - `); - }); -}); + `) + }) +}) diff --git a/packages/sdk/src/__test__/utils/builders.ts b/packages/sdk/src/__test__/utils/builders.ts index 92698337..a1a2102f 100644 --- a/packages/sdk/src/__test__/utils/builders.ts +++ b/packages/sdk/src/__test__/utils/builders.ts @@ -1,358 +1,316 @@ -import { Common, Hardfork, Mainnet } from '@ethereumjs/common'; -import { RLP } from '@ethereumjs/rlp'; -import { type TypedTransaction, createTx } from '@ethereumjs/tx'; -import { generate as randomWords } from 'random-words'; -import { Constants } from '../..'; -import { Client } from '../../client'; -import { - CURRENCIES, - HARDENED_OFFSET, - getFwVersionConst, -} from '../../constants'; -import type { Currency, SignRequestParams, SigningPath } from '../../types'; -import type { FirmwareConstants } from '../../types/firmware'; -import { randomBytes } from '../../util'; -import { MSG_PAYLOAD_METADATA_SZ } from './constants'; -import { getN, getPrng } from './getters'; -import { - BTC_PURPOSE_P2PKH, - ETH_COIN, - buildRandomEip712Object, - getTestVectors, -} from './helpers'; +import { Common, Hardfork, Mainnet } from '@ethereumjs/common' +import { RLP } from '@ethereumjs/rlp' +import { type TypedTransaction, createTx } from '@ethereumjs/tx' +import { generate as randomWords } from 'random-words' +import { Constants } from '../..' +import { Client } from '../../client' +import { CURRENCIES, HARDENED_OFFSET, getFwVersionConst } from '../../constants' +import type { Currency, SignRequestParams, SigningPath } from '../../types' +import type { FirmwareConstants } from '../../types/firmware' +import { randomBytes } from '../../util' +import { MSG_PAYLOAD_METADATA_SZ } from './constants' +import { getN, getPrng } from './getters' +import { BTC_PURPOSE_P2PKH, ETH_COIN, buildRandomEip712Object, getTestVectors } from './helpers' -const prng = getPrng(); +const prng = getPrng() export const getFwVersionsList = () => { - const arr: number[][] = []; - Array.from({ length: 1 }, (x, i) => { - Array.from({ length: 10 }, (y, j) => { - Array.from({ length: 5 }, (z, k) => { - arr.push([i, j + 10, k]); - }); - }); - }); - return arr; -}; + const arr: number[][] = [] + Array.from({ length: 1 }, (x, i) => { + Array.from({ length: 10 }, (y, j) => { + Array.from({ length: 5 }, (z, k) => { + arr.push([i, j + 10, k]) + }) + }) + }) + return arr +} export const buildFirmwareConstants = (...overrides: any) => { - return { - abiCategorySz: 32, - abiMaxRmv: 200, - addrFlagsAllowed: true, - allowBtcLegacyAndSegwitAddrs: true, - allowedEthTxTypes: [1, 2], - contractDeployKey: '0x08002e0fec8e6acf00835f43c9764f7364fa3f42', - eip712MaxTypeParams: 36, - eip712Supported: true, - ethMaxDataSz: 1519, - ethMaxGasPrice: 20000000000000, - ethMaxMsgSz: 1540, - ethMsgPreHashAllowed: true, - extraDataFrameSz: 1500, - extraDataMaxFrames: 1, - genericSigning: { - baseReqSz: 1552, - baseDataSz: 1519, - hashTypes: { NONE: 0, KECCAK256: 1, SHA256: 2 }, - curveTypes: { SECP256K1: 0, ED25519: 1 }, - encodingTypes: { NONE: 1, SOLANA: 2, EVM: 4 }, - calldataDecoding: { reserved: 2895728, maxSz: 1024 }, - }, - getAddressFlags: [4, 3], - kvActionMaxNum: 10, - kvActionsAllowed: true, - kvKeyMaxStrSz: 63, - kvRemoveMaxNum: 100, - kvValMaxStrSz: 63, - maxDecoderBufSz: 1600, - personalSignHeaderSz: 72, - prehashAllowed: true, - reqMaxDataSz: 1678, - varAddrPathSzAllowed: true, - ...overrides, - } as FirmwareConstants; -}; + return { + abiCategorySz: 32, + abiMaxRmv: 200, + addrFlagsAllowed: true, + allowBtcLegacyAndSegwitAddrs: true, + allowedEthTxTypes: [1, 2], + contractDeployKey: '0x08002e0fec8e6acf00835f43c9764f7364fa3f42', + eip712MaxTypeParams: 36, + eip712Supported: true, + ethMaxDataSz: 1519, + ethMaxGasPrice: 20000000000000, + ethMaxMsgSz: 1540, + ethMsgPreHashAllowed: true, + extraDataFrameSz: 1500, + extraDataMaxFrames: 1, + genericSigning: { + baseReqSz: 1552, + baseDataSz: 1519, + hashTypes: { NONE: 0, KECCAK256: 1, SHA256: 2 }, + curveTypes: { SECP256K1: 0, ED25519: 1 }, + encodingTypes: { NONE: 1, SOLANA: 2, EVM: 4 }, + calldataDecoding: { reserved: 2895728, maxSz: 1024 }, + }, + getAddressFlags: [4, 3], + kvActionMaxNum: 10, + kvActionsAllowed: true, + kvKeyMaxStrSz: 63, + kvRemoveMaxNum: 100, + kvValMaxStrSz: 63, + maxDecoderBufSz: 1600, + personalSignHeaderSz: 72, + prehashAllowed: true, + reqMaxDataSz: 1678, + varAddrPathSzAllowed: true, + ...overrides, + } as FirmwareConstants +} export const buildWallet = (overrides?) => ({ - uid: Buffer.from( - '162b56efe561c12bc93f703dc7026b3ec3d53923270c9259e2b08015fb9defd2', - 'hex', - ), - capabilities: 1, - external: true, - ...overrides, -}); + uid: Buffer.from('162b56efe561c12bc93f703dc7026b3ec3d53923270c9259e2b08015fb9defd2', 'hex'), + capabilities: 1, + external: true, + ...overrides, +}) export const buildGetAddressesObject = (overrides?) => ({ - startPath: [0x80000000 + 44, 0x80000000 + 60, 0x80000000, 0, 0], - n: 1, - flag: 1, - fwConstants: buildFirmwareConstants(), - wallet: buildWallet(), - ...overrides, -}); + startPath: [0x80000000 + 44, 0x80000000 + 60, 0x80000000, 0, 0], + n: 1, + flag: 1, + fwConstants: buildFirmwareConstants(), + wallet: buildWallet(), + ...overrides, +}) export const buildSignObject = (fwVersion, overrides?) => { - const fwConstants = getFwVersionConst(fwVersion); - return { - data: { - to: '0xc0c8f96C2fE011cc96770D2e37CfbfeAFB585F0e', - from: '0xc0c8f96C2fE011cc96770D2e37CfbfeAFB585F0e', - value: 0x80000000, - data: '0x0', - signerPath: [0x80000000 + 44, 0x80000000 + 60, 0x80000000, 0, 0], - nonce: 0x80000000, - gasLimit: 0x80000000, - gasPrice: 0x80000000, - }, - currency: CURRENCIES.ETH as Currency, - fwConstants, - ...overrides, - }; -}; + const fwConstants = getFwVersionConst(fwVersion) + return { + data: { + to: '0xc0c8f96C2fE011cc96770D2e37CfbfeAFB585F0e', + from: '0xc0c8f96C2fE011cc96770D2e37CfbfeAFB585F0e', + value: 0x80000000, + data: '0x0', + signerPath: [0x80000000 + 44, 0x80000000 + 60, 0x80000000, 0, 0], + nonce: 0x80000000, + gasLimit: 0x80000000, + gasPrice: 0x80000000, + }, + currency: CURRENCIES.ETH as Currency, + fwConstants, + ...overrides, + } +} export const buildSharedSecret = () => { - return Buffer.from([ - 89, 60, 130, 80, 168, 252, 34, 136, 230, 71, 230, 158, 51, 13, 239, 237, 6, - 246, 71, 232, 232, 175, 193, 106, 106, 185, 38, 1, 163, 14, 225, 101, - ]); -}; + return Buffer.from([89, 60, 130, 80, 168, 252, 34, 136, 230, 71, 230, 158, 51, 13, 239, 237, 6, 246, 71, 232, 232, 175, 193, 106, 106, 185, 38, 1, 163, 14, 225, 101]) +} -export const getNumIter = (n: number | string | undefined = getN()) => - n ? Number.parseInt(`${n}`) : 5; +export const getNumIter = (n: number | string | undefined = getN()) => (n ? Number.parseInt(`${n}`) : 5) /** Generate a bunch of random test vectors using the PRNG */ export const buildRandomVectors = (n: number | string | undefined = getN()) => { - const numIter = getNumIter(n); + const numIter = getNumIter(n) - // Generate a bunch of random test vectors using the PRNG - const RANDOM_VEC: any[] = []; - for (let i = 0; i < numIter; i++) { - RANDOM_VEC.push(Math.floor(1000000000 * prng.quick()).toString(16)); - } - return RANDOM_VEC; -}; + // Generate a bunch of random test vectors using the PRNG + const RANDOM_VEC: any[] = [] + for (let i = 0; i < numIter; i++) { + RANDOM_VEC.push(Math.floor(1000000000 * prng.quick()).toString(16)) + } + return RANDOM_VEC +} -export const DEFAULT_SIGNER = [ - BTC_PURPOSE_P2PKH, - ETH_COIN, - HARDENED_OFFSET, - 0, - 0, -]; +export const DEFAULT_SIGNER = [BTC_PURPOSE_P2PKH, ETH_COIN, HARDENED_OFFSET, 0, 0] export const buildTx = (data: `0x${string}` = '0xdeadbeef') => { - return createTx( - { - type: 2, - maxFeePerGas: 1200000000, - maxPriorityFeePerGas: 1200000000, - nonce: 0, - gasLimit: 50000, - to: '0xe242e54155b1abc71fc118065270cecaaf8b7768', - value: 100, - data, - }, - { - common: new Common({ - chain: Mainnet, - hardfork: Hardfork.London, - }), - }, - ); -}; + return createTx( + { + type: 2, + maxFeePerGas: 1200000000, + maxPriorityFeePerGas: 1200000000, + nonce: 0, + gasLimit: 50000, + to: '0xe242e54155b1abc71fc118065270cecaaf8b7768', + value: 100, + data, + }, + { + common: new Common({ + chain: Mainnet, + hardfork: Hardfork.London, + }), + }, + ) +} -export const buildEthSignRequest = async ( - client: Client, - txDataOverrides?: any, -): Promise => { - if (client.getFwVersion()?.major === 0 && client.getFwVersion()?.minor < 15) { - console.warn('Please update firmware. Skipping ETH signing tests.'); - return; - } +export const buildEthSignRequest = async (client: Client, txDataOverrides?: any): Promise => { + if (client.getFwVersion()?.major === 0 && client.getFwVersion()?.minor < 15) { + console.warn('Please update firmware. Skipping ETH signing tests.') + return + } - const fwConstants = client.getFwConstants(); - const signerPath = [BTC_PURPOSE_P2PKH, ETH_COIN, HARDENED_OFFSET, 0, 0]; - const common = new Common({ - chain: Mainnet, - hardfork: Hardfork.London, - }); - const txData = { - type: 2, - maxFeePerGas: 1200000000, - maxPriorityFeePerGas: 1200000000, - nonce: 0, - gasLimit: 50000, - to: '0xe242e54155b1abc71fc118065270cecaaf8b7768', - value: 1000000000000, - data: '0x17e914679b7e160613be4f8c2d3203d236286d74eb9192f6d6f71b9118a42bb033ccd8e8', - ...txDataOverrides, - }; - const tx = createTx(txData, { common }); - const req = { - data: { - signerPath, - payload: tx.getMessageToSign(), - curveType: Constants.SIGNING.CURVES.SECP256K1, - hashType: Constants.SIGNING.HASHES.KECCAK256, - encodingType: Constants.SIGNING.ENCODINGS.EVM, - }, - }; - const maxDataSz = - fwConstants.ethMaxDataSz + - fwConstants.extraDataMaxFrames * fwConstants.extraDataFrameSz; - return { - fwConstants, - signerPath, - common, - txData, - tx, - req, - maxDataSz, - }; -}; + const fwConstants = client.getFwConstants() + const signerPath = [BTC_PURPOSE_P2PKH, ETH_COIN, HARDENED_OFFSET, 0, 0] + const common = new Common({ + chain: Mainnet, + hardfork: Hardfork.London, + }) + const txData = { + type: 2, + maxFeePerGas: 1200000000, + maxPriorityFeePerGas: 1200000000, + nonce: 0, + gasLimit: 50000, + to: '0xe242e54155b1abc71fc118065270cecaaf8b7768', + value: 1000000000000, + data: '0x17e914679b7e160613be4f8c2d3203d236286d74eb9192f6d6f71b9118a42bb033ccd8e8', + ...txDataOverrides, + } + const tx = createTx(txData, { common }) + const req = { + data: { + signerPath, + payload: tx.getMessageToSign(), + curveType: Constants.SIGNING.CURVES.SECP256K1, + hashType: Constants.SIGNING.HASHES.KECCAK256, + encodingType: Constants.SIGNING.ENCODINGS.EVM, + }, + } + const maxDataSz = fwConstants.ethMaxDataSz + fwConstants.extraDataMaxFrames * fwConstants.extraDataFrameSz + return { + fwConstants, + signerPath, + common, + txData, + tx, + req, + maxDataSz, + } +} export const buildTxReq = (tx: TypedTransaction) => ({ - data: { - signerPath: DEFAULT_SIGNER, - payload: tx.getMessageToSign(), - curveType: Constants.SIGNING.CURVES.SECP256K1, - hashType: Constants.SIGNING.HASHES.KECCAK256, - encodingType: Constants.SIGNING.ENCODINGS.EVM, - }, -}); + data: { + signerPath: DEFAULT_SIGNER, + payload: tx.getMessageToSign(), + curveType: Constants.SIGNING.CURVES.SECP256K1, + hashType: Constants.SIGNING.HASHES.KECCAK256, + encodingType: Constants.SIGNING.ENCODINGS.EVM, + }, +}) -export const buildMsgReq = ( - payload = 'hello ethereum', - protocol = 'signPersonal', -) => ({ - currency: 'ETH_MSG', - data: { - signerPath: DEFAULT_SIGNER, - protocol, - payload, - }, -}); +export const buildMsgReq = (payload = 'hello ethereum', protocol = 'signPersonal') => ({ + currency: 'ETH_MSG', + data: { + signerPath: DEFAULT_SIGNER, + protocol, + payload, + }, +}) export const buildEvmReq = (overrides?: { - data?: any; - txData?: any; - common?: any; + data?: any + txData?: any + common?: any }) => { - let chainInfo = null; - if (overrides?.common) { - chainInfo = overrides.common; - } else { - chainInfo = new Common({ chain: Mainnet, hardfork: Hardfork.London }); - } - const req = { - data: { - signerPath: DEFAULT_SIGNER, - curveType: Constants.SIGNING.CURVES.SECP256K1, - hashType: Constants.SIGNING.HASHES.KECCAK256, - encodingType: Constants.SIGNING.ENCODINGS.EVM, - payload: null, - ...overrides?.data, - }, - txData: { - type: 2, - maxFeePerGas: 1200000000, - maxPriorityFeePerGas: 1200000000, - nonce: 0, - gasLimit: 50000, - to: '0xe242e54155b1abc71fc118065270cecaaf8b7768', - value: 100, - data: '0xdeadbeef', - ...overrides?.txData, - }, - common: chainInfo, - }; - return req; -}; + let chainInfo = null + if (overrides?.common) { + chainInfo = overrides.common + } else { + chainInfo = new Common({ chain: Mainnet, hardfork: Hardfork.London }) + } + const req = { + data: { + signerPath: DEFAULT_SIGNER, + curveType: Constants.SIGNING.CURVES.SECP256K1, + hashType: Constants.SIGNING.HASHES.KECCAK256, + encodingType: Constants.SIGNING.ENCODINGS.EVM, + payload: null, + ...overrides?.data, + }, + txData: { + type: 2, + maxFeePerGas: 1200000000, + maxPriorityFeePerGas: 1200000000, + nonce: 0, + gasLimit: 50000, + to: '0xe242e54155b1abc71fc118065270cecaaf8b7768', + value: 100, + data: '0xdeadbeef', + ...overrides?.txData, + }, + common: chainInfo, + } + return req +} export const buildEncDefs = (vectors: any) => { - const encDefs = vectors.canonicalNames.map((name: string) => { - // For each canonical name, we need to RLP encode just the name - return RLP.encode([name]); - }); + const encDefs = vectors.canonicalNames.map((name: string) => { + // For each canonical name, we need to RLP encode just the name + return RLP.encode([name]) + }) - // The calldata is already in hex format, we just need to ensure it has 0x prefix - const encDefsCalldata = vectors.canonicalNames.map( - (_: string, idx: number) => { - const calldata = `0x${idx.toString(16).padStart(8, '0')}`; - return calldata; - }, - ); + // The calldata is already in hex format, we just need to ensure it has 0x prefix + const encDefsCalldata = vectors.canonicalNames.map((_: string, idx: number) => { + const calldata = `0x${idx.toString(16).padStart(8, '0')}` + return calldata + }) - return { encDefs, encDefsCalldata }; -}; + return { encDefs, encDefsCalldata } +} export function buildRandomMsg(type, client: Client) { - function randInt(n: number) { - return Math.floor(n * prng.quick()); - } + function randInt(n: number) { + return Math.floor(n * prng.quick()) + } - if (type === 'signPersonal') { - // A random string will do - const isHexStr = randInt(2) > 0; - const fwConstants = client.getFwConstants(); - const L = randInt(fwConstants.ethMaxDataSz - MSG_PAYLOAD_METADATA_SZ); - if (isHexStr) return `0x${randomBytes(L).toString('hex')}`; - // Get L hex bytes (represented with a string with 2*L chars) - else return randomWords({ exactly: L, join: ' ' }).slice(0, L); // Get L ASCII characters (bytes) - } else if (type === 'eip712') { - return buildRandomEip712Object(randInt); - } + if (type === 'signPersonal') { + // A random string will do + const isHexStr = randInt(2) > 0 + const fwConstants = client.getFwConstants() + const L = randInt(fwConstants.ethMaxDataSz - MSG_PAYLOAD_METADATA_SZ) + if (isHexStr) return `0x${randomBytes(L).toString('hex')}` + // Get L hex bytes (represented with a string with 2*L chars) + else return randomWords({ exactly: L, join: ' ' }).slice(0, L) // Get L ASCII characters (bytes) + } else if (type === 'eip712') { + return buildRandomEip712Object(randInt) + } } -export function buildEthMsgReq( - payload: any, - protocol: 'signPersonal' | 'eip712', - signerPath = [ - BTC_PURPOSE_P2PKH, - ETH_COIN, - HARDENED_OFFSET, - 0, - 0, - ] as SigningPath, -): SignRequestParams { - return { - currency: CURRENCIES.ETH_MSG, - data: { - signerPath, - curveType: Constants.SIGNING.CURVES.SECP256K1, - hashType: Constants.SIGNING.HASHES.KECCAK256, - payload, - protocol, - }, - }; +export function buildEthMsgReq(payload: any, protocol: 'signPersonal' | 'eip712', signerPath = [BTC_PURPOSE_P2PKH, ETH_COIN, HARDENED_OFFSET, 0, 0] as SigningPath): SignRequestParams { + return { + currency: CURRENCIES.ETH_MSG, + data: { + signerPath, + curveType: Constants.SIGNING.CURVES.SECP256K1, + hashType: Constants.SIGNING.HASHES.KECCAK256, + payload, + protocol, + }, + } } export const buildValidateConnectObject = (overrides?) => ({ - deviceId: 'test', - key: 'test', - baseUrl: 'https://www.test.com', - ...overrides, -}); + deviceId: 'test', + key: 'test', + baseUrl: 'https://www.test.com', + ...overrides, +}) export const buildValidateRequestObject = (overrides?) => { - const fwConstants = buildFirmwareConstants(); - return { - fwConstants, - ...overrides, - }; -}; + const fwConstants = buildFirmwareConstants() + return { + fwConstants, + ...overrides, + } +} // Most of the endpoint validators (for encrypted requests) // will require a connected client instance. export function buildMockConnectedClient(opts) { - const _stateData = JSON.parse(getTestVectors().dehydratedClientState); - const stateData = { - ..._stateData, - ...opts, - }; - return new Client({ - stateData: JSON.stringify(stateData), - }); + const _stateData = JSON.parse(getTestVectors().dehydratedClientState) + const stateData = { + ..._stateData, + ...opts, + } + return new Client({ + stateData: JSON.stringify(stateData), + }) } diff --git a/packages/sdk/src/__test__/utils/constants.ts b/packages/sdk/src/__test__/utils/constants.ts index fb0daad1..b040bb7c 100644 --- a/packages/sdk/src/__test__/utils/constants.ts +++ b/packages/sdk/src/__test__/utils/constants.ts @@ -1 +1 @@ -export const MSG_PAYLOAD_METADATA_SZ = 28; // Metadata that must go in ETH_MSG requests +export const MSG_PAYLOAD_METADATA_SZ = 28 // Metadata that must go in ETH_MSG requests diff --git a/packages/sdk/src/__test__/utils/determinism.ts b/packages/sdk/src/__test__/utils/determinism.ts index 236de499..dac43608 100644 --- a/packages/sdk/src/__test__/utils/determinism.ts +++ b/packages/sdk/src/__test__/utils/determinism.ts @@ -1,77 +1,73 @@ -import type { TypedTransaction } from '@ethereumjs/tx'; -import { SignTypedDataVersion, TypedDataUtils } from '@metamask/eth-sig-util'; -import BIP32Factory from 'bip32'; -import { ecsign, privateToAddress } from 'ethereumjs-util'; -import { Hash } from 'ox'; -import * as ecc from 'tiny-secp256k1'; -import type { Client } from '../../client'; -import { getPathStr } from '../../shared/utilities'; -import type { SigningPath } from '../../types'; -import { ethPersonalSignMsg, getSigStr } from './helpers'; -import { TEST_SEED } from './testConstants'; +import type { TypedTransaction } from '@ethereumjs/tx' +import { SignTypedDataVersion, TypedDataUtils } from '@metamask/eth-sig-util' +import BIP32Factory from 'bip32' +import { ecsign, privateToAddress } from 'ethereumjs-util' +import { Hash } from 'ox' +import * as ecc from 'tiny-secp256k1' +import type { Client } from '../../client' +import { getPathStr } from '../../shared/utilities' +import type { SigningPath } from '../../types' +import { ethPersonalSignMsg, getSigStr } from './helpers' +import { TEST_SEED } from './testConstants' -export async function testUniformSigs( - payload: any, - tx: TypedTransaction, - client: Client, -) { - const tx1Resp = await client.sign(payload); - const tx2Resp = await client.sign(payload); - const tx3Resp = await client.sign(payload); - const tx4Resp = await client.sign(payload); - const tx5Resp = await client.sign(payload); - // Check sig 1 - expect(getSigStr(tx1Resp, tx)).toEqual(getSigStr(tx2Resp, tx)); - expect(getSigStr(tx1Resp, tx)).toEqual(getSigStr(tx3Resp, tx)); - expect(getSigStr(tx1Resp, tx)).toEqual(getSigStr(tx4Resp, tx)); - expect(getSigStr(tx1Resp, tx)).toEqual(getSigStr(tx5Resp, tx)); - // Check sig 2 - expect(getSigStr(tx2Resp, tx)).toEqual(getSigStr(tx1Resp, tx)); - expect(getSigStr(tx2Resp, tx)).toEqual(getSigStr(tx3Resp, tx)); - expect(getSigStr(tx2Resp, tx)).toEqual(getSigStr(tx4Resp, tx)); - expect(getSigStr(tx2Resp, tx)).toEqual(getSigStr(tx5Resp, tx)); - // Check sig 3 - expect(getSigStr(tx3Resp, tx)).toEqual(getSigStr(tx1Resp, tx)); - expect(getSigStr(tx3Resp, tx)).toEqual(getSigStr(tx2Resp, tx)); - expect(getSigStr(tx3Resp, tx)).toEqual(getSigStr(tx4Resp, tx)); - expect(getSigStr(tx3Resp, tx)).toEqual(getSigStr(tx5Resp, tx)); - // Check sig 4 - expect(getSigStr(tx4Resp, tx)).toEqual(getSigStr(tx1Resp, tx)); - expect(getSigStr(tx4Resp, tx)).toEqual(getSigStr(tx2Resp, tx)); - expect(getSigStr(tx4Resp, tx)).toEqual(getSigStr(tx3Resp, tx)); - expect(getSigStr(tx4Resp, tx)).toEqual(getSigStr(tx5Resp, tx)); - // Check sig 5 - expect(getSigStr(tx5Resp, tx)).toEqual(getSigStr(tx1Resp, tx)); - expect(getSigStr(tx5Resp, tx)).toEqual(getSigStr(tx2Resp, tx)); - expect(getSigStr(tx5Resp, tx)).toEqual(getSigStr(tx3Resp, tx)); - expect(getSigStr(tx5Resp, tx)).toEqual(getSigStr(tx4Resp, tx)); +export async function testUniformSigs(payload: any, tx: TypedTransaction, client: Client) { + const tx1Resp = await client.sign(payload) + const tx2Resp = await client.sign(payload) + const tx3Resp = await client.sign(payload) + const tx4Resp = await client.sign(payload) + const tx5Resp = await client.sign(payload) + // Check sig 1 + expect(getSigStr(tx1Resp, tx)).toEqual(getSigStr(tx2Resp, tx)) + expect(getSigStr(tx1Resp, tx)).toEqual(getSigStr(tx3Resp, tx)) + expect(getSigStr(tx1Resp, tx)).toEqual(getSigStr(tx4Resp, tx)) + expect(getSigStr(tx1Resp, tx)).toEqual(getSigStr(tx5Resp, tx)) + // Check sig 2 + expect(getSigStr(tx2Resp, tx)).toEqual(getSigStr(tx1Resp, tx)) + expect(getSigStr(tx2Resp, tx)).toEqual(getSigStr(tx3Resp, tx)) + expect(getSigStr(tx2Resp, tx)).toEqual(getSigStr(tx4Resp, tx)) + expect(getSigStr(tx2Resp, tx)).toEqual(getSigStr(tx5Resp, tx)) + // Check sig 3 + expect(getSigStr(tx3Resp, tx)).toEqual(getSigStr(tx1Resp, tx)) + expect(getSigStr(tx3Resp, tx)).toEqual(getSigStr(tx2Resp, tx)) + expect(getSigStr(tx3Resp, tx)).toEqual(getSigStr(tx4Resp, tx)) + expect(getSigStr(tx3Resp, tx)).toEqual(getSigStr(tx5Resp, tx)) + // Check sig 4 + expect(getSigStr(tx4Resp, tx)).toEqual(getSigStr(tx1Resp, tx)) + expect(getSigStr(tx4Resp, tx)).toEqual(getSigStr(tx2Resp, tx)) + expect(getSigStr(tx4Resp, tx)).toEqual(getSigStr(tx3Resp, tx)) + expect(getSigStr(tx4Resp, tx)).toEqual(getSigStr(tx5Resp, tx)) + // Check sig 5 + expect(getSigStr(tx5Resp, tx)).toEqual(getSigStr(tx1Resp, tx)) + expect(getSigStr(tx5Resp, tx)).toEqual(getSigStr(tx2Resp, tx)) + expect(getSigStr(tx5Resp, tx)).toEqual(getSigStr(tx3Resp, tx)) + expect(getSigStr(tx5Resp, tx)).toEqual(getSigStr(tx4Resp, tx)) } export function deriveAddress(seed: Buffer, path: SigningPath) { - const bip32 = BIP32Factory(ecc); - const wallet = bip32.fromSeed(seed); - const priv = wallet.derivePath(getPathStr(path)).privateKey; - return `0x${privateToAddress(priv).toString('hex')}`; + const bip32 = BIP32Factory(ecc) + const wallet = bip32.fromSeed(seed) + const priv = wallet.derivePath(getPathStr(path)).privateKey + return `0x${privateToAddress(priv).toString('hex')}` } export function signPersonalJS(_msg: string, path: SigningPath) { - const bip32 = BIP32Factory(ecc); - const wallet = bip32.fromSeed(TEST_SEED); - const priv = wallet.derivePath(getPathStr(path)).privateKey; - const msg = ethPersonalSignMsg(_msg); - const hash = Buffer.from(Hash.keccak256(Buffer.from(msg))); - const sig = ecsign(hash, priv); - const v = (sig.v - 27).toString(16).padStart(2, '0'); - return `${sig.r.toString('hex')}${sig.s.toString('hex')}${v}`; + const bip32 = BIP32Factory(ecc) + const wallet = bip32.fromSeed(TEST_SEED) + const priv = wallet.derivePath(getPathStr(path)).privateKey + const msg = ethPersonalSignMsg(_msg) + const hash = Buffer.from(Hash.keccak256(Buffer.from(msg))) + const sig = ecsign(hash, priv) + const v = (sig.v - 27).toString(16).padStart(2, '0') + return `${sig.r.toString('hex')}${sig.s.toString('hex')}${v}` } export function signEip712JS(payload: any, path: SigningPath) { - const bip32 = BIP32Factory(ecc); - const wallet = bip32.fromSeed(TEST_SEED); - const priv = wallet.derivePath(getPathStr(path)).privateKey; - // Calculate the EIP712 hash using the same method as the SDK validation - const hash = TypedDataUtils.eip712Hash(payload, SignTypedDataVersion.V4); - const sig = ecsign(Buffer.from(hash), priv); - const v = (sig.v - 27).toString(16).padStart(2, '0'); - return `${sig.r.toString('hex')}${sig.s.toString('hex')}${v}`; + const bip32 = BIP32Factory(ecc) + const wallet = bip32.fromSeed(TEST_SEED) + const priv = wallet.derivePath(getPathStr(path)).privateKey + // Calculate the EIP712 hash using the same method as the SDK validation + const hash = TypedDataUtils.eip712Hash(payload, SignTypedDataVersion.V4) + const sig = ecsign(Buffer.from(hash), priv) + const v = (sig.v - 27).toString(16).padStart(2, '0') + return `${sig.r.toString('hex')}${sig.s.toString('hex')}${v}` } diff --git a/packages/sdk/src/__test__/utils/ethers.ts b/packages/sdk/src/__test__/utils/ethers.ts index d3dc6ddd..d0bdd8aa 100644 --- a/packages/sdk/src/__test__/utils/ethers.ts +++ b/packages/sdk/src/__test__/utils/ethers.ts @@ -1,147 +1,134 @@ -const EVM_TYPES = [ - null, - 'address', - 'bool', - 'uint', - 'int', - 'bytes', - 'string', - 'tuple', -]; +const EVM_TYPES = [null, 'address', 'bool', 'uint', 'int', 'bytes', 'string', 'tuple'] -export function convertDecoderToEthers(def) { - const converted = getConvertedDef(def); - const types: any[] = []; - const data: any[] = []; - converted.forEach((i: any) => { - types.push(i.type); - data.push(i.data); - }); - return { types, data }; +export function convertDecoderToEthers(def: unknown[]) { + const converted = getConvertedDef(def) + const types: any[] = [] + const data: any[] = [] + converted.forEach((i: any) => { + types.push(i.type) + data.push(i.data) + }) + return { types, data } } // Convert an encoded def into a combination of ethers-compatable // type names and data fields. The data should be random but it // doesn't matter much for these tests, which mainly just test // structure of the definitions -function getConvertedDef(def) { - const converted: any[] = []; - def.forEach((param) => { - const arrSzs = param[3]; - const evmType = EVM_TYPES[Number.parseInt(param[1].toString('hex'), 16)]; - let type = evmType; - const numBytes = Number.parseInt(param[2].toString('hex'), 16); - if (numBytes > 0) { - type = `${type}${numBytes * 8}`; - } - // Handle tuples by recursively generating data - let tupleData; - if (evmType === 'tuple') { - tupleData = []; - type = `${type}(`; - const tupleDef = getConvertedDef(param[4]); - tupleDef.forEach((tupleParam: any) => { - type = `${type}${tupleParam.type}, `; - tupleData.push(tupleParam.data); - }); - type = type.slice(0, type.length - 2); - type = `${type})`; - } - // Get the data of a single function (i.e. excluding arrays) - const funcData = tupleData ? tupleData : genParamData(param); - // Apply the data to arrays - for (let i = 0; i < arrSzs.length; i++) { - const sz = Number.parseInt(arrSzs[i].toString('hex')); - if (Number.isNaN(sz)) { - // This is a 0 size, which means we need to - // define a size to generate data - type = `${type}[]`; - } else { - type = `${type}[${sz}]`; - } - } - // If this param is a tuple we need to copy base data - // across all dimensions. The individual params are already - // arraified this way, but not the tuple type - if (tupleData) { - converted.push({ type, data: getArrayData(param, funcData) }); - } else { - converted.push({ type, data: funcData }); - } - }); - return converted; +function getConvertedDef(def: unknown[]) { + const converted: { type: string | null; data: unknown }[] = [] + def.forEach((param: unknown) => { + const p = param as { toString: (fmt: string) => string }[] + const arrSzs = p[3] as { toString: (fmt: string) => string }[] + const evmType = EVM_TYPES[Number.parseInt(p[1].toString('hex'), 16)] + let type = evmType + const numBytes = Number.parseInt(p[2].toString('hex'), 16) + if (numBytes > 0) { + type = `${type}${numBytes * 8}` + } + // Handle tuples by recursively generating data + let tupleData: unknown[] | undefined + if (evmType === 'tuple') { + tupleData = [] + type = `${type}(` + const tupleDef = getConvertedDef(p[4] as unknown[]) + tupleDef.forEach((tupleParam) => { + type = `${type}${tupleParam.type}, ` + tupleData?.push(tupleParam.data) + }) + type = type.slice(0, type.length - 2) + type = `${type})` + } + // Get the data of a single function (i.e. excluding arrays) + const funcData = tupleData ? tupleData : genParamData(p) + // Apply the data to arrays + for (let i = 0; i < arrSzs.length; i++) { + const sz = Number.parseInt(arrSzs[i].toString('hex')) + if (Number.isNaN(sz)) { + // This is a 0 size, which means we need to + // define a size to generate data + type = `${type}[]` + } else { + type = `${type}[${sz}]` + } + } + // If this param is a tuple we need to copy base data + // across all dimensions. The individual params are already + // arraified this way, but not the tuple type + if (tupleData) { + converted.push({ type, data: getArrayData(p, funcData) }) + } else { + converted.push({ type, data: funcData }) + } + }) + return converted } -function genTupleData(tupleParam) { - const nestedData: any = []; - tupleParam.forEach((nestedParam) => { - nestedData.push( - genData( - EVM_TYPES[Number.parseInt(nestedParam[1].toString('hex'), 16)] ?? '', - nestedParam, - ), - ); - }); - return nestedData; +function genTupleData(tupleParam: unknown[]) { + const nestedData: unknown[] = [] + tupleParam.forEach((nestedParam: unknown) => { + const np = nestedParam as { toString: (fmt: string) => string }[] + nestedData.push(genData(EVM_TYPES[Number.parseInt(np[1].toString('hex'), 16)] ?? '', np)) + }) + return nestedData } -function genParamData(param: any[]) { - const evmType = - EVM_TYPES[Number.parseInt(param[1].toString('hex'), 16)] ?? ''; - const baseData = genData(evmType, param); - return getArrayData(param, baseData); +function genParamData(param: { toString: (fmt: string) => string }[]) { + const evmType = EVM_TYPES[Number.parseInt(param[1].toString('hex'), 16)] ?? '' + const baseData = genData(evmType, param) + return getArrayData(param, baseData) } -function getArrayData(param: any, baseData: any) { - let arrayData; - let data; - const arrSzs = param[3]; - for (let i = 0; i < arrSzs.length; i++) { - // let sz = parseInt(arrSzs[i].toString('hex')); TODO: fix this - const dimData: any = []; - let sz = Number.parseInt(param[3][i].toString('hex')); - if (Number.isNaN(sz)) { - sz = 2; //1; - } - if (!arrayData) { - arrayData = []; - } - const lastDimData = JSON.parse(JSON.stringify(arrayData)); - for (let j = 0; j < sz; j++) { - if (i === 0) { - dimData.push(baseData); - } else { - dimData.push(lastDimData); - } - } - arrayData = dimData; - } - if (!data) { - data = arrayData ? arrayData : baseData; - } - return data; +function getArrayData(param: { toString: (fmt: string) => string }[], baseData: unknown) { + let arrayData: unknown[] | undefined + let data: unknown + const arrSzs = param[3] as unknown as { toString: (fmt: string) => string }[] + for (let i = 0; i < arrSzs.length; i++) { + // let sz = parseInt(arrSzs[i].toString('hex')); TODO: fix this + const dimData: unknown[] = [] + let sz = Number.parseInt((param[3] as unknown as { toString: (fmt: string) => string }[])[i].toString('hex')) + if (Number.isNaN(sz)) { + sz = 2 //1; + } + if (!arrayData) { + arrayData = [] + } + const lastDimData = JSON.parse(JSON.stringify(arrayData)) + for (let j = 0; j < sz; j++) { + if (i === 0) { + dimData.push(baseData) + } else { + dimData.push(lastDimData) + } + } + arrayData = dimData + } + if (!data) { + data = arrayData ? arrayData : baseData + } + return data } -function genData(type: string, param: any[]) { - switch (type) { - case 'address': - return '0xdead00000000000000000000000000000000beef'; - case 'bool': - return true; - case 'uint': - return 9; - case 'int': - return -9; - case 'bytes': - return '0xdeadbeef'; - case 'string': - return 'string'; - case 'tuple': - if (!param || param.length < 4) { - throw new Error('Invalid tuple data'); - } - return genTupleData(param[4]); - default: - throw new Error('Unrecognized type'); - } +function genData(type: string, param: { toString: (fmt: string) => string }[]) { + switch (type) { + case 'address': + return '0xdead00000000000000000000000000000000beef' + case 'bool': + return true + case 'uint': + return 9 + case 'int': + return -9 + case 'bytes': + return '0xdeadbeef' + case 'string': + return 'string' + case 'tuple': + if (!param || param.length < 4) { + throw new Error('Invalid tuple data') + } + return genTupleData(param[4] as unknown[]) + default: + throw new Error('Unrecognized type') + } } diff --git a/packages/sdk/src/__test__/utils/getters.ts b/packages/sdk/src/__test__/utils/getters.ts index f9ccf730..588c90c4 100644 --- a/packages/sdk/src/__test__/utils/getters.ts +++ b/packages/sdk/src/__test__/utils/getters.ts @@ -1,16 +1,16 @@ -import seedrandom from 'seedrandom'; +import seedrandom from 'seedrandom' export const getEnv = () => { - if (!process.env) throw new Error('env cannot be found'); - return process.env; -}; -export const getDeviceId = (): string => getEnv().DEVICE_ID ?? ''; -export const getN = (): number => Number.parseInt(getEnv().N ?? '5'); -export const getSeed = (): string => getEnv().SEED ?? 'myrandomseed'; -export const getTestnet = (): string => getEnv().TESTNET ?? ''; -export const getEtherscanKey = (): string => getEnv().ETHERSCAN_KEY ?? ''; -export const getEncPw = (): string => getEnv().ENC_PW ?? null; + if (!process.env) throw new Error('env cannot be found') + return process.env +} +export const getDeviceId = (): string => getEnv().DEVICE_ID ?? '' +export const getN = (): number => Number.parseInt(getEnv().N ?? '5') +export const getSeed = (): string => getEnv().SEED ?? 'myrandomseed' +export const getTestnet = (): string => getEnv().TESTNET ?? '' +export const getEtherscanKey = (): string => getEnv().ETHERSCAN_KEY ?? '' +export const getEncPw = (): string => getEnv().ENC_PW ?? null export const getPrng = (seed?: string) => { - return seedrandom(seed ? seed : getSeed()); -}; + return seedrandom(seed ? seed : getSeed()) +} diff --git a/packages/sdk/src/__test__/utils/helpers.ts b/packages/sdk/src/__test__/utils/helpers.ts index c0c51109..0b4f2c66 100644 --- a/packages/sdk/src/__test__/utils/helpers.ts +++ b/packages/sdk/src/__test__/utils/helpers.ts @@ -1,76 +1,63 @@ -import { readFileSync } from 'node:fs'; -import type { TypedTransaction } from '@ethereumjs/tx'; -import BIP32Factory from 'bip32'; -import { wordlists } from 'bip39'; -import bitcoin, { type Payment } from 'bitcoinjs-lib'; -import BN from 'bn.js'; -import { ECPairFactory } from 'ecpair'; -import { - derivePath as deriveEDKey, - getPublicKey as getEDPubkey, -} from 'ed25519-hd-key'; -import { ec as EC } from 'elliptic'; -import { privateToAddress } from 'ethereumjs-util'; -import { jsonc } from 'jsonc'; -import { Hash } from 'ox'; -import { ecdsaRecover } from 'secp256k1'; -import * as ecc from 'tiny-secp256k1'; -import nacl from 'tweetnacl'; -import { Constants } from '../..'; -import { Client } from '../../client'; -import { - BIP_CONSTANTS, - HARDENED_OFFSET, - ethMsgProtocol, -} from '../../constants'; -import { ProtocolConstants } from '../../protocol'; -import { getPathStr } from '../../shared/utilities'; -import { - ensureHexBuffer, - getV, - getYParity, - parseDER, - randomBytes, -} from '../../util'; -import { getEnv } from './getters'; -import { setStoredClient } from './setup'; - -const SIGHASH_ALL = 0x01; -const secp256k1 = new EC('secp256k1'); -const bip32 = BIP32Factory(ecc); -const ECPair = ECPairFactory(ecc); +import { readFileSync } from 'node:fs' +import type { TypedTransaction } from '@ethereumjs/tx' +import BIP32Factory from 'bip32' +import { wordlists } from 'bip39' +import bitcoin, { type Payment } from 'bitcoinjs-lib' +import BN from 'bn.js' +import { ECPairFactory } from 'ecpair' +import { derivePath as deriveEDKey, getPublicKey as getEDPubkey } from 'ed25519-hd-key' +import { ec as EC } from 'elliptic' +import { privateToAddress } from 'ethereumjs-util' +import { jsonc } from 'jsonc' +import { Hash } from 'ox' +import { ecdsaRecover } from 'secp256k1' +import * as ecc from 'tiny-secp256k1' +import nacl from 'tweetnacl' +import { Constants } from '../..' +import { Client } from '../../client' +import { BIP_CONSTANTS, HARDENED_OFFSET, ethMsgProtocol } from '../../constants' +import { ProtocolConstants } from '../../protocol' +import { getPathStr } from '../../shared/utilities' +import { ensureHexBuffer, getV, getYParity, parseDER, randomBytes } from '../../util' +import { getEnv } from './getters' +import { setStoredClient } from './setup' + +const SIGHASH_ALL = 0x01 +const secp256k1 = new EC('secp256k1') +const bip32 = BIP32Factory(ecc) +const ECPair = ECPairFactory(ecc) const normalizeSigComponent = (component: any): Buffer => { - if (component === null || component === undefined) { - return Buffer.alloc(0); - } - if (Buffer.isBuffer(component)) { - return component; - } - if (component instanceof Uint8Array) { - return Buffer.from(component); - } - if (typeof component === 'bigint') { - const hex = component.toString(16); - return Buffer.from(hex.padStart(hex.length + (hex.length % 2), '0'), 'hex'); - } - if (typeof component === 'number') { - return ensureHexBuffer(component); - } - if (typeof component === 'string') { - return ensureHexBuffer(component); - } - if (typeof component?.toArray === 'function') { - return Buffer.from(component.toArray('be')); - } - if (typeof component?.toBuffer === 'function') { - return Buffer.from(component.toBuffer()); - } - if (typeof component?.toString === 'function') { - return ensureHexBuffer(component.toString()); - } - throw new Error('Unsupported signature component format'); -}; + if (component === null || component === undefined) { + return Buffer.alloc(0) + } + if (Buffer.isBuffer(component)) { + return component + } + if (component instanceof Uint8Array) { + return Buffer.from(component) + } + if (typeof component === 'bigint') { + const hex = component.toString(16) + return Buffer.from(hex.padStart(hex.length + (hex.length % 2), '0'), 'hex') + } + if (typeof component === 'number') { + return ensureHexBuffer(component) + } + if (typeof component === 'string') { + return ensureHexBuffer(component) + } + if (typeof component?.toArray === 'function') { + return Buffer.from(component.toArray('be')) + } + if (typeof component?.toBuffer === 'function') { + return Buffer.from(component.toBuffer()) + } + if (typeof component?.toString === 'function') { + return ensureHexBuffer(component.toString()) + } + throw new Error('Unsupported signature component format') +} /** * Get the appropriate V parameter for a transaction signature based on transaction type. @@ -78,15 +65,15 @@ const normalizeSigComponent = (component: any): Buffer => { * For legacy transactions, returns the full V value. */ export const getSignatureVParam = (tx: any, resp: any): string => { - if (tx._type && tx._type > 0) { - // For EIP-1559 and newer transaction types, use yParity (0 or 1) - return getYParity(tx, resp).toString(16).padStart(2, '0'); - } else { - // For legacy transactions, return full V value - const vBn = getV(tx, resp); - return vBn.toString(16).padStart(2, '0'); - } -}; + if (tx._type && tx._type > 0) { + // For EIP-1559 and newer transaction types, use yParity (0 or 1) + return getYParity(tx, resp).toString(16).padStart(2, '0') + } else { + // For legacy transactions, return full V value + const vBn = getV(tx, resp) + return vBn.toString(16).padStart(2, '0') + } +} /** * Get the appropriate V parameter as BN for signature validation based on transaction type. @@ -94,350 +81,264 @@ export const getSignatureVParam = (tx: any, resp: any): string => { * For legacy transactions, returns the full V value as BN. */ export const getSignatureVBN = (tx: any, resp: any): BN => { - if (tx._type && tx._type > 0) { - // For EIP-1559 and newer transaction types, get y-parity (0 or 1) - return new BN(getYParity(tx, resp)); - } else { - // For legacy transactions, use the v value from signature - return new BN(resp.sig.v); - } -}; + if (tx._type && tx._type > 0) { + // For EIP-1559 and newer transaction types, get y-parity (0 or 1) + return new BN(getYParity(tx, resp)) + } else { + // For legacy transactions, use the v value from signature + return new BN(resp.sig.v) + } +} // NOTE: We use the HARDEN(49) purpose for p2sh(p2wpkh) address derivations. // For p2pkh-derived addresses, we use the legacy 44' purpose // For p2wpkh-derived addresse (not yet supported) we will use 84' -export const BTC_PURPOSE_P2WPKH = BIP_CONSTANTS.PURPOSES.BTC_SEGWIT; -export const BTC_PURPOSE_P2SH_P2WPKH = - BIP_CONSTANTS.PURPOSES.BTC_WRAPPED_SEGWIT; -export const BTC_PURPOSE_P2PKH = BIP_CONSTANTS.PURPOSES.BTC_LEGACY; -export const BTC_COIN = BIP_CONSTANTS.COINS.BTC; -export const BTC_TESTNET_COIN = BIP_CONSTANTS.COINS.BTC_TESTNET; -export const ETH_COIN = BIP_CONSTANTS.COINS.ETH; -export const REUSABLE_KEY = - '3fb53b677f73e4d2b8c89c303f6f6b349f0075ad88ea126cb9f6632085815dca'; - -export function setupTestClient( - env = getEnv() as any, - stateData?: any, -): Client { - if (stateData) { - return new Client({ stateData }); - } - const setup: any = { - name: env.APP_NAME || 'SDK Test', - baseUrl: env.baseUrl || 'https://signing.gridpl.us', - timeout: 120000, - setStoredClient, - }; - - // If the user passes a deviceID in the env, we assume they have previously - // connected to the Lattice. - if (env.DEVICE_ID) { - setup.privKey = Buffer.from(REUSABLE_KEY, 'hex'); - } - // Separate check -- if we are connecting for the first time but want to be able - // to reconnect quickly with the same device ID as an env var, we need to pair - // with a reusable key - if (Number.parseInt(env.REUSE_KEY) === 1) { - setup.privKey = Buffer.from(REUSABLE_KEY, 'hex'); - } - // Initialize a global SDK client - const client = new Client(setup); - return client; +export const BTC_PURPOSE_P2WPKH = BIP_CONSTANTS.PURPOSES.BTC_SEGWIT +export const BTC_PURPOSE_P2SH_P2WPKH = BIP_CONSTANTS.PURPOSES.BTC_WRAPPED_SEGWIT +export const BTC_PURPOSE_P2PKH = BIP_CONSTANTS.PURPOSES.BTC_LEGACY +export const BTC_COIN = BIP_CONSTANTS.COINS.BTC +export const BTC_TESTNET_COIN = BIP_CONSTANTS.COINS.BTC_TESTNET +export const ETH_COIN = BIP_CONSTANTS.COINS.ETH +export const REUSABLE_KEY = '3fb53b677f73e4d2b8c89c303f6f6b349f0075ad88ea126cb9f6632085815dca' + +export function setupTestClient(env = getEnv() as any, stateData?: any): Client { + if (stateData) { + return new Client({ stateData }) + } + const setup: any = { + name: env.APP_NAME || 'SDK Test', + baseUrl: env.baseUrl || 'https://signing.gridpl.us', + timeout: 120000, + setStoredClient, + } + + // If the user passes a deviceID in the env, we assume they have previously + // connected to the Lattice. + if (env.DEVICE_ID) { + setup.privKey = Buffer.from(REUSABLE_KEY, 'hex') + } + // Separate check -- if we are connecting for the first time but want to be able + // to reconnect quickly with the same device ID as an env var, we need to pair + // with a reusable key + if (Number.parseInt(env.REUSE_KEY) === 1) { + setup.privKey = Buffer.from(REUSABLE_KEY, 'hex') + } + // Initialize a global SDK client + const client = new Client(setup) + return client } export const unharden = (x) => { - return x >= HARDENED_OFFSET ? x - HARDENED_OFFSET : x; -}; + return x >= HARDENED_OFFSET ? x - HARDENED_OFFSET : x +} export const buildPath = (indices) => { - let path = 'm'; - indices.forEach((idx) => { - path += `/${unharden(idx)}${idx >= HARDENED_OFFSET ? "'" : ''}`; - }); - return path; -}; + let path = 'm' + indices.forEach((idx) => { + path += `/${unharden(idx)}${idx >= HARDENED_OFFSET ? "'" : ''}` + }) + return path +} export function _getSumInputs(inputs) { - let sum = 0; - inputs.forEach((input) => { - sum += input.value; - }); - return sum; + let sum = 0 + inputs.forEach((input) => { + sum += input.value + }) + return sum } export function _get_btc_addr(pubkey, purpose, network) { - const pk = Buffer.isBuffer(pubkey) ? pubkey : Buffer.from(pubkey); - let obj: Payment; - if (purpose === BTC_PURPOSE_P2SH_P2WPKH) { - // Wrapped segwit requires p2sh wrapping - obj = bitcoin.payments.p2sh({ - redeem: bitcoin.payments.p2wpkh({ pubkey: pk, network }), - network, - }); - } else if (purpose === BTC_PURPOSE_P2WPKH) { - obj = bitcoin.payments.p2wpkh({ pubkey: pk, network }); - } else { - // Native segwit and legacy addresses are treated teh same - obj = bitcoin.payments.p2pkh({ pubkey: pk, network }); - } - return obj.address; + const pk = Buffer.isBuffer(pubkey) ? pubkey : Buffer.from(pubkey) + let obj: Payment + if (purpose === BTC_PURPOSE_P2SH_P2WPKH) { + // Wrapped segwit requires p2sh wrapping + obj = bitcoin.payments.p2sh({ + redeem: bitcoin.payments.p2wpkh({ pubkey: pk, network }), + network, + }) + } else if (purpose === BTC_PURPOSE_P2WPKH) { + obj = bitcoin.payments.p2wpkh({ pubkey: pk, network }) + } else { + // Native segwit and legacy addresses are treated teh same + obj = bitcoin.payments.p2pkh({ pubkey: pk, network }) + } + return obj.address } -export function _start_tx_builder( - wallet, - recipient, - value, - fee, - inputs, - network, - purpose, -) { - const tx = new bitcoin.Transaction(); - // Match serialization logic (version 2) used by device and serializer - tx.version = 2; - const inputSum = _getSumInputs(inputs); - const recipientScript = bitcoin.address.toOutputScript(recipient, network); - tx.addOutput(recipientScript, value); - const changeValue = inputSum - value - fee; - if (changeValue > 0) { - const networkIdx = network === bitcoin.networks.testnet ? 1 : 0; - const path = buildPath([purpose, harden(networkIdx), harden(0), 1, 0]); - const btc_0_change = wallet.derivePath(path); - const btc_0_change_pub = ECPair.fromPublicKey( - btc_0_change.publicKey, - ).publicKey; - const changeAddr = _get_btc_addr(btc_0_change_pub, purpose, network); - const changeScript = bitcoin.address.toOutputScript(changeAddr, network); - tx.addOutput(changeScript, changeValue); - } else if (changeValue < 0) { - throw new Error('Value + fee > sumInputs!'); - } - const inputsMeta: { scriptCode: Buffer; value: number }[] = []; - inputs.forEach((input) => { - const hashLE = Buffer.from(input.hash, 'hex').reverse(); - tx.addInput(hashLE, input.idx); - const coin = - network === bitcoin.networks.testnet ? BTC_TESTNET_COIN : BTC_COIN; - const path = buildPath([purpose, coin, harden(0), 0, input.signerIdx]); - const keyPair = wallet.derivePath(path); - const pubkeyBuf = Buffer.from(keyPair.publicKey); - const p2pkh = bitcoin.payments.p2pkh({ pubkey: pubkeyBuf, network }); - // For P2WPKH and P2SH-P2WPKH the BIP143 scriptCode is the standard P2PKH script - const scriptCode = p2pkh.output!; - inputsMeta.push({ scriptCode, value: input.value }); - }); - return { tx, inputsMeta }; +export function _start_tx_builder(wallet, recipient, value, fee, inputs, network, purpose) { + const tx = new bitcoin.Transaction() + // Match serialization logic (version 2) used by device and serializer + tx.version = 2 + const inputSum = _getSumInputs(inputs) + const recipientScript = bitcoin.address.toOutputScript(recipient, network) + tx.addOutput(recipientScript, value) + const changeValue = inputSum - value - fee + if (changeValue > 0) { + const networkIdx = network === bitcoin.networks.testnet ? 1 : 0 + const path = buildPath([purpose, harden(networkIdx), harden(0), 1, 0]) + const btc_0_change = wallet.derivePath(path) + const btc_0_change_pub = ECPair.fromPublicKey(btc_0_change.publicKey).publicKey + const changeAddr = _get_btc_addr(btc_0_change_pub, purpose, network) + const changeScript = bitcoin.address.toOutputScript(changeAddr, network) + tx.addOutput(changeScript, changeValue) + } else if (changeValue < 0) { + throw new Error('Value + fee > sumInputs!') + } + const inputsMeta: { scriptCode: Buffer; value: number }[] = [] + inputs.forEach((input) => { + const hashLE = Buffer.from(input.hash, 'hex').reverse() + tx.addInput(hashLE, input.idx) + const coin = network === bitcoin.networks.testnet ? BTC_TESTNET_COIN : BTC_COIN + const path = buildPath([purpose, coin, harden(0), 0, input.signerIdx]) + const keyPair = wallet.derivePath(path) + const pubkeyBuf = Buffer.from(keyPair.publicKey) + const p2pkh = bitcoin.payments.p2pkh({ pubkey: pubkeyBuf, network }) + // For P2WPKH and P2SH-P2WPKH the BIP143 scriptCode is the standard P2PKH script + if (!p2pkh.output) throw new Error('No P2PKH output') + const scriptCode = p2pkh.output + inputsMeta.push({ scriptCode, value: input.value }) + }) + return { tx, inputsMeta } } function _build_sighashes(txb_or_tx, purpose) { - const hashes: any = []; - const txb = txb_or_tx as any; - const isLegacy = purpose === BTC_PURPOSE_P2PKH; - if (txb.inputsMeta) { - txb.inputsMeta.forEach((meta, i) => { - hashes.push( - isLegacy - ? txb.tx.hashForSignature(i, meta.scriptCode, SIGHASH_ALL) - : txb.tx.hashForWitnessV0( - i, - meta.scriptCode, - meta.value, - SIGHASH_ALL, - ), - ); - }); - } else { - // Fallback for prior structure (should not be used) - txb.__inputs.forEach((input, i) => { - hashes.push( - isLegacy - ? txb.__tx.hashForSignature(i, input.signScript, SIGHASH_ALL) - : txb.__tx.hashForWitnessV0( - i, - input.signScript, - input.value, - SIGHASH_ALL, - ), - ); - }); - } - return hashes; + const hashes: any = [] + const txb = txb_or_tx as any + const isLegacy = purpose === BTC_PURPOSE_P2PKH + if (txb.inputsMeta) { + txb.inputsMeta.forEach((meta, i) => { + hashes.push(isLegacy ? txb.tx.hashForSignature(i, meta.scriptCode, SIGHASH_ALL) : txb.tx.hashForWitnessV0(i, meta.scriptCode, meta.value, SIGHASH_ALL)) + }) + } else { + // Fallback for prior structure (should not be used) + txb.__inputs.forEach((input, i) => { + hashes.push(isLegacy ? txb.__tx.hashForSignature(i, input.signScript, SIGHASH_ALL) : txb.__tx.hashForWitnessV0(i, input.signScript, input.value, SIGHASH_ALL)) + }) + } + return hashes } -function _get_reference_sighashes( - wallet, - recipient, - value, - fee, - inputs, - isTestnet, - purpose, -) { - const network = isTestnet - ? bitcoin.networks.testnet - : bitcoin.networks.bitcoin; - const built = _start_tx_builder( - wallet, - recipient, - value, - fee, - inputs, - network, - purpose, - ); - // built has shape { tx, inputsMeta } - return _build_sighashes(built, purpose); +function _get_reference_sighashes(wallet, recipient, value, fee, inputs, isTestnet, purpose) { + const network = isTestnet ? bitcoin.networks.testnet : bitcoin.networks.bitcoin + const built = _start_tx_builder(wallet, recipient, value, fee, inputs, network, purpose) + // built has shape { tx, inputsMeta } + return _build_sighashes(built, purpose) } -function _btc_tx_request_builder( - inputs, - recipient, - value, - fee, - isTestnet, - purpose, -) { - const currencyIdx = isTestnet ? BTC_TESTNET_COIN : BTC_COIN; - const txData = { - prevOuts: [] as any[], - recipient, - value, - fee, - changePath: [purpose, currencyIdx, HARDENED_OFFSET, 1, 0], - }; - inputs.forEach((input) => { - txData.prevOuts.push({ - txHash: input.hash, - value: input.value, - index: input.idx, - signerPath: [purpose, currencyIdx, HARDENED_OFFSET, 0, input.signerIdx], - }); - }); - return { - currency: 'BTC', - data: txData, - }; +function _btc_tx_request_builder(inputs, recipient, value, fee, isTestnet, purpose) { + const currencyIdx = isTestnet ? BTC_TESTNET_COIN : BTC_COIN + const txData = { + prevOuts: [] as any[], + recipient, + value, + fee, + changePath: [purpose, currencyIdx, HARDENED_OFFSET, 1, 0], + } + inputs.forEach((input) => { + txData.prevOuts.push({ + txHash: input.hash, + value: input.value, + index: input.idx, + signerPath: [purpose, currencyIdx, HARDENED_OFFSET, 0, input.signerIdx], + }) + }) + return { + currency: 'BTC', + data: txData, + } } // Convert DER signature to buffer of form `${r}${s}` export function stripDER(derSig) { - const parsed = parseDER(derSig); - // Left-pad r and s to 32 bytes and concatenate (no extra normalization) - const r = Buffer.from(parsed.r.slice(-32)); - const s = Buffer.from(parsed.s.slice(-32)); - const sig = Buffer.alloc(64); - r.copy(sig, 32 - r.length); - s.copy(sig, 64 - s.length); - return sig; + const parsed = parseDER(derSig) + // Left-pad r and s to 32 bytes and concatenate (no extra normalization) + const r = Buffer.from(parsed.r.slice(-32)) + const s = Buffer.from(parsed.s.slice(-32)) + const sig = Buffer.alloc(64) + r.copy(sig, 32 - r.length) + s.copy(sig, 64 - s.length) + return sig } function _get_signing_keys(wallet, inputs, isTestnet, purpose) { - const currencyIdx = isTestnet ? 1 : 0; - return inputs.map((input) => { - const path = buildPath([ - purpose, - harden(currencyIdx), - harden(0), - 0, - input.signerIdx, - ]); - const node = wallet.derivePath(path); - const priv = Buffer.from(node.privateKey); - const key = secp256k1.keyFromPrivate(priv); - return { - privateKey: priv, - verify(hash: Buffer, sig: Buffer) { - return key.verify(hash, { - r: sig.slice(0, 32).toString('hex'), - s: sig.slice(32).toString('hex'), - }); - }, - }; - }); + const currencyIdx = isTestnet ? 1 : 0 + return inputs.map((input) => { + const path = buildPath([purpose, harden(currencyIdx), harden(0), 0, input.signerIdx]) + const node = wallet.derivePath(path) + const priv = Buffer.from(node.privateKey) + const key = secp256k1.keyFromPrivate(priv) + return { + privateKey: priv, + verify(hash: Buffer, sig: Buffer) { + return key.verify(hash, { + r: sig.slice(0, 32).toString('hex'), + s: sig.slice(32).toString('hex'), + }) + }, + } + }) } function _generate_btc_address(isTestnet, purpose, rand) { - const priv = Buffer.alloc(32); - for (let j = 0; j < 8; j++) { - // 32 bits of randomness per call - priv.writeUInt32BE(Math.floor(rand.quick() * 2 ** 32), j * 4); - } - const keyPair = ECPair.fromPrivateKey(priv); - const network = - isTestnet === true ? bitcoin.networks.testnet : bitcoin.networks.bitcoin; - return _get_btc_addr(keyPair.publicKey, purpose, network); + const priv = Buffer.alloc(32) + for (let j = 0; j < 8; j++) { + // 32 bits of randomness per call + priv.writeUInt32BE(Math.floor(rand.quick() * 2 ** 32), j * 4) + } + const keyPair = ECPair.fromPrivateKey(priv) + const network = isTestnet === true ? bitcoin.networks.testnet : bitcoin.networks.bitcoin + return _get_btc_addr(keyPair.publicKey, purpose, network) } export function setup_btc_sig_test(opts, wallet, inputs, rand) { - const { isTestnet, useChange, spenderPurpose, recipientPurpose } = opts; - const recipient = _generate_btc_address(isTestnet, recipientPurpose, rand); - const sumInputs = _getSumInputs(inputs); - const fee = Math.floor(rand.quick() * 50000); - const _value = - useChange === true ? Math.floor(rand.quick() * sumInputs) : sumInputs; - const value = _value - fee; - const sigHashes = _get_reference_sighashes( - wallet, - recipient, - value, - fee, - inputs, - isTestnet, - spenderPurpose, - ); - const signingKeys = _get_signing_keys( - wallet, - inputs, - isTestnet, - spenderPurpose, - ); - const txReq = _btc_tx_request_builder( - inputs, - recipient, - value, - fee, - isTestnet, - spenderPurpose, - ); - return { - sigHashes, - signingKeys, - txReq, - }; + const { isTestnet, useChange, spenderPurpose, recipientPurpose } = opts + const recipient = _generate_btc_address(isTestnet, recipientPurpose, rand) + const sumInputs = _getSumInputs(inputs) + const fee = Math.floor(rand.quick() * 50000) + const _value = useChange === true ? Math.floor(rand.quick() * sumInputs) : sumInputs + const value = _value - fee + const sigHashes = _get_reference_sighashes(wallet, recipient, value, fee, inputs, isTestnet, spenderPurpose) + const signingKeys = _get_signing_keys(wallet, inputs, isTestnet, spenderPurpose) + const txReq = _btc_tx_request_builder(inputs, recipient, value, fee, isTestnet, spenderPurpose) + return { + sigHashes, + signingKeys, + txReq, + } } export const harden = (x) => { - return x + HARDENED_OFFSET; -}; + return x + HARDENED_OFFSET +} export const prandomBuf = (prng, maxSz, forceSize = false) => { - // Build a random payload that can fit in the base request - const sz = forceSize ? maxSz : Math.floor(maxSz * prng.quick()); - const buf = Buffer.alloc(sz); - for (let i = 0; i < sz; i++) { - buf[i] = Math.floor(0xff * prng.quick()); - } - return buf; -}; + // Build a random payload that can fit in the base request + const sz = forceSize ? maxSz : Math.floor(maxSz * prng.quick()) + const buf = Buffer.alloc(sz) + for (let i = 0; i < sz; i++) { + buf[i] = Math.floor(0xff * prng.quick()) + } + return buf +} export const deriveED25519Key = (path, seed) => { - const { key } = deriveEDKey(getPathStr(path), seed); - const pub = getEDPubkey(key, false); // `false` removes the leading zero byte - return { - priv: key, - pub, - }; -}; + const { key } = deriveEDKey(getPathStr(path), seed) + const pub = getEDPubkey(key, false) // `false` removes the leading zero byte + return { + priv: key, + pub, + } +} export const deriveSECP256K1Key = (path, seed) => { - const wallet = bip32.fromSeed(seed); - const key = wallet.derivePath(getPathStr(path)); - return { - priv: key.privateKey, - pub: key.publicKey, - }; -}; + const wallet = bip32.fromSeed(seed) + const key = wallet.derivePath(getPathStr(path)) + return { + priv: key.privateKey, + pub: key.publicKey, + } +} //============================================================ // Wallet Job integration test helpers @@ -448,582 +349,548 @@ export const deriveSECP256K1Key = (path, seed) => { // Relevant test harness constants //--------------------------------------------------- export const jobTypes = { - WALLET_JOB_GET_ADDRESSES: 1, - WALLET_JOB_SIGN_TX: 2, - WALLET_JOB_LOAD_SEED: 3, - WALLET_JOB_EXPORT_SEED: 4, - WALLET_JOB_DELETE_SEED: 5, -}; + WALLET_JOB_GET_ADDRESSES: 1, + WALLET_JOB_SIGN_TX: 2, + WALLET_JOB_LOAD_SEED: 3, + WALLET_JOB_EXPORT_SEED: 4, + WALLET_JOB_DELETE_SEED: 5, +} export const gpErrors = { - GP_SUCCESS: 0x00, - GP_EINVAL: 0xffffffff + 1 - 22, // (4294967061) - GP_ENODATA: 0xffffffff + 1 - 61, // (4294967100) - GP_EOVERFLOW: 0xffffffff + 1 - 84, // (4294967123) - GP_EALREADY: 0xffffffff + 1 - 114, // (4294967153) - GP_ENODEV: 0xffffffff + 1 - 19, // (4294967058) - GP_EAGAIN: 0xffffffff + 1 - 11, // (4294967050) - GP_FAILURE: 0xffffffff + 1 - 128, // (4294967168) - GP_EWALLET: 0xffffffff + 1 - 113, // (4294967183) -}; + GP_SUCCESS: 0x00, + GP_EINVAL: 0xffffffff + 1 - 22, // (4294967061) + GP_ENODATA: 0xffffffff + 1 - 61, // (4294967100) + GP_EOVERFLOW: 0xffffffff + 1 - 84, // (4294967123) + GP_EALREADY: 0xffffffff + 1 - 114, // (4294967153) + GP_ENODEV: 0xffffffff + 1 - 19, // (4294967058) + GP_EAGAIN: 0xffffffff + 1 - 11, // (4294967050) + GP_FAILURE: 0xffffffff + 1 - 128, // (4294967168) + GP_EWALLET: 0xffffffff + 1 - 113, // (4294967183) +} //--------------------------------------------------- // General helpers //--------------------------------------------------- export const getCodeMsg = (code, expected) => { - if (code !== expected) { - let codeTxt = code; - let expectedTxt = expected; - Object.keys(gpErrors).forEach((key) => { - if (code === gpErrors[key]) { - codeTxt = key; - } - if (expected === gpErrors[key]) { - expectedTxt = key; - } - }); - return `Incorrect response code. Got ${codeTxt}. Expected ${expectedTxt}`; - } - return ''; -}; + if (code !== expected) { + let codeTxt = code + let expectedTxt = expected + Object.keys(gpErrors).forEach((key) => { + if (code === gpErrors[key]) { + codeTxt = key + } + if (expected === gpErrors[key]) { + expectedTxt = key + } + }) + return `Incorrect response code. Got ${codeTxt}. Expected ${expectedTxt}` + } + return '' +} export const parseWalletJobResp = (res, v) => { - const jobRes = { - resultStatus: null, - result: null, - }; - jobRes.resultStatus = res.readUInt32LE(0); - const dataLen = res.readUInt16LE(4); - if (v.length === 0 || (v[1] < 10 && v[2] === 0)) { - // Legacy fw versions ( res.result.readUInt32LE(0); +export const jobResErrCode = (res) => res.result.readUInt32LE(0) // Have to do this weird copy because `Buffer`s from the client are not real buffers // which is a vestige of requiring support on react native export const copyBuffer = (x) => { - return Buffer.from(x.toString('hex'), 'hex'); -}; + return Buffer.from(x.toString('hex'), 'hex') +} // Convert a set of indices to a human readable bip32 path export const stringifyPath = (parent) => { - const convert = (parent) => { - return parent >= HARDENED_OFFSET - ? `${parent - HARDENED_OFFSET}'` - : `${parent}`; - }; - if (parent.idx) { - // BIP32 style encoding - let s = 'm'; - for (let i = 0; i < parent.pathDepth; i++) { - s += `/${convert(parent.idx[i])}`; - } - return s; - } - - let d = parent.pathDepth; - let s = 'm'; - if (d <= 0) return s; - if (parent.purpose !== undefined) { - s += `/${convert(parent.purpose)}`; - d--; - if (d <= 0) return s; - } - if (parent.coin !== undefined) { - s += `/${convert(parent.coin)}`; - d--; - if (d <= 0) return s; - } - if (parent.account !== undefined) { - s += `/${convert(parent.account)}`; - d--; - if (d <= 0) return s; - } - if (parent.change !== undefined) { - s += `/${convert(parent.change)}`; - d--; - if (d <= 0) return s; - } - if (parent.addr !== undefined) s += `/${convert(parent.addr)}`; - d--; - return s; -}; + const convert = (parent) => { + return parent >= HARDENED_OFFSET ? `${parent - HARDENED_OFFSET}'` : `${parent}` + } + if (parent.idx) { + // BIP32 style encoding + let s = 'm' + for (let i = 0; i < parent.pathDepth; i++) { + s += `/${convert(parent.idx[i])}` + } + return s + } + + let d = parent.pathDepth + let s = 'm' + if (d <= 0) return s + if (parent.purpose !== undefined) { + s += `/${convert(parent.purpose)}` + d-- + if (d <= 0) return s + } + if (parent.coin !== undefined) { + s += `/${convert(parent.coin)}` + d-- + if (d <= 0) return s + } + if (parent.account !== undefined) { + s += `/${convert(parent.account)}` + d-- + if (d <= 0) return s + } + if (parent.change !== undefined) { + s += `/${convert(parent.change)}` + d-- + if (d <= 0) return s + } + if (parent.addr !== undefined) s += `/${convert(parent.addr)}` + d-- + return s +} //--------------------------------------------------- // Get Addresses helpers //--------------------------------------------------- export const serializeGetAddressesJobData = (data) => { - const req = Buffer.alloc(33); - let off = 0; - req.writeUInt32LE(data.path.pathDepth, off); - off += 4; - for (let i = 0; i < 5; i++) { - req.writeUInt32LE(i < data.path.pathDepth ? data.path.idx[i] : 0, off); - off += 4; - } - req.writeUInt32LE(data.iterIdx, off); - off += 4; - req.writeUInt32LE(data.count, off); - off += 4; - // Deprecated skipCache flag. It isn't used by firmware anymore. - req.writeUInt8(data.flag || 0, off); - return req; -}; + const req = Buffer.alloc(33) + let off = 0 + req.writeUInt32LE(data.path.pathDepth, off) + off += 4 + for (let i = 0; i < 5; i++) { + req.writeUInt32LE(i < data.path.pathDepth ? data.path.idx[i] : 0, off) + off += 4 + } + req.writeUInt32LE(data.iterIdx, off) + off += 4 + req.writeUInt32LE(data.count, off) + off += 4 + // Deprecated skipCache flag. It isn't used by firmware anymore. + req.writeUInt8(data.flag || 0, off) + return req +} export const deserializeGetAddressesJobResult = (res) => { - let off = 0; - const getAddrResult = { - count: 0, - addresses: [] as any[], - pubOnly: undefined, - }; - getAddrResult.pubOnly = res.readUInt8(off); - off += 1; - getAddrResult.count = res.readUInt8(off); - off += 3; // Skip a 2-byte empty shim value (for backwards compatibility) - for (let i = 0; i < getAddrResult.count; i++) { - const _addr = res.slice(off, off + ProtocolConstants.addrStrLen); - off += ProtocolConstants.addrStrLen; - for (let j = 0; j < _addr.length; j++) - if (_addr[j] === 0x00) { - getAddrResult.addresses.push(_addr.slice(0, j).toString('utf8')); - break; - } - } - return getAddrResult; -}; + let off = 0 + const getAddrResult = { + count: 0, + addresses: [] as any[], + pubOnly: undefined, + } + getAddrResult.pubOnly = res.readUInt8(off) + off += 1 + getAddrResult.count = res.readUInt8(off) + off += 3 // Skip a 2-byte empty shim value (for backwards compatibility) + for (let i = 0; i < getAddrResult.count; i++) { + const _addr = res.slice(off, off + ProtocolConstants.addrStrLen) + off += ProtocolConstants.addrStrLen + for (let j = 0; j < _addr.length; j++) + if (_addr[j] === 0x00) { + getAddrResult.addresses.push(_addr.slice(0, j).toString('utf8')) + break + } + } + return getAddrResult +} export const validateBTCAddresses = (resp, jobData, seed, useTestnet?) => { - expect(resp.count).toEqual(jobData.count); - const wallet = bip32.fromSeed(seed); - const path = JSON.parse(JSON.stringify(jobData.path)); - const network = - useTestnet === true ? bitcoin.networks.testnet : bitcoin.networks.bitcoin; - for (let i = 0; i < jobData.count; i++) { - path.idx[jobData.iterIdx] = jobData.path.idx[jobData.iterIdx] + i; - // Validate the address - const purpose = jobData.path.idx[0]; - const pubkey = wallet.derivePath(stringifyPath(path)).publicKey; - let address: string; - if (purpose === BTC_PURPOSE_P2WPKH) { - // Bech32 - address = bitcoin.payments.p2wpkh({ - pubkey: Buffer.from(pubkey), - network, - }).address; - } else if (purpose === BTC_PURPOSE_P2SH_P2WPKH) { - // Wrapped segwit - address = bitcoin.payments.p2sh({ - redeem: bitcoin.payments.p2wpkh({ - pubkey: Buffer.from(pubkey), - network, - }), - }).address; - } else { - // Legacy - // This is the default and any unrecognized purpose will yield a legacy address. - address = bitcoin.payments.p2pkh({ - pubkey: Buffer.from(pubkey), - network, - }).address; - } - expect(address).toEqual(resp.addresses[i]); - } -}; + expect(resp.count).toEqual(jobData.count) + const wallet = bip32.fromSeed(seed) + const path = JSON.parse(JSON.stringify(jobData.path)) + const network = useTestnet === true ? bitcoin.networks.testnet : bitcoin.networks.bitcoin + for (let i = 0; i < jobData.count; i++) { + path.idx[jobData.iterIdx] = jobData.path.idx[jobData.iterIdx] + i + // Validate the address + const purpose = jobData.path.idx[0] + const pubkey = wallet.derivePath(stringifyPath(path)).publicKey + let address: string + if (purpose === BTC_PURPOSE_P2WPKH) { + // Bech32 + address = bitcoin.payments.p2wpkh({ + pubkey: Buffer.from(pubkey), + network, + }).address + } else if (purpose === BTC_PURPOSE_P2SH_P2WPKH) { + // Wrapped segwit + address = bitcoin.payments.p2sh({ + redeem: bitcoin.payments.p2wpkh({ + pubkey: Buffer.from(pubkey), + network, + }), + }).address + } else { + // Legacy + // This is the default and any unrecognized purpose will yield a legacy address. + address = bitcoin.payments.p2pkh({ + pubkey: Buffer.from(pubkey), + network, + }).address + } + expect(address).toEqual(resp.addresses[i]) + } +} export const validateETHAddresses = (resp, jobData, seed) => { - expect(resp.count).toEqual(jobData.count); - // Confirm it is an Ethereum address - expect(resp.addresses[0].slice(0, 2)).toEqual('0x'); - expect(resp.addresses[0].length).toEqual(42); - // Confirm we can derive the same address from the previously exported seed - const wallet = bip32.fromSeed(seed); - const path = JSON.parse(JSON.stringify(jobData.path)); - for (let i = 0; i < jobData.count; i++) { - path.idx[jobData.iterIdx] = jobData.path.idx[jobData.iterIdx] + i; - const priv = wallet.derivePath(stringifyPath(path)).privateKey; - const addr = `0x${privateToAddress(priv).toString('hex')}`; - expect(addr).toEqual(resp.addresses[i]); - } -}; + expect(resp.count).toEqual(jobData.count) + // Confirm it is an Ethereum address + expect(resp.addresses[0].slice(0, 2)).toEqual('0x') + expect(resp.addresses[0].length).toEqual(42) + // Confirm we can derive the same address from the previously exported seed + const wallet = bip32.fromSeed(seed) + const path = JSON.parse(JSON.stringify(jobData.path)) + for (let i = 0; i < jobData.count; i++) { + path.idx[jobData.iterIdx] = jobData.path.idx[jobData.iterIdx] + i + const priv = wallet.derivePath(stringifyPath(path)).privateKey + const addr = `0x${privateToAddress(priv).toString('hex')}` + expect(addr).toEqual(resp.addresses[i]) + } +} -export const validateDerivedPublicKeys = ( - pubKeys, - firstPath, - seed, - flag?: number, -) => { - const wallet = bip32.fromSeed(seed); - // We assume the keys were derived in sequential order - pubKeys.forEach((pub, i) => { - const path = JSON.parse(JSON.stringify(firstPath)); - path[path.length - 1] += i; - if (flag === Constants.GET_ADDR_FLAGS.ED25519_PUB) { - // ED25519 requires its own derivation - const key = deriveED25519Key(path, seed); - expect(pub.toString('hex')).toEqualElseLog( - key.pub.toString('hex'), - 'Exported ED25519 pubkey incorrect', - ); - } else { - // Otherwise this is a SECP256K1 pubkey - const priv = wallet.derivePath(getPathStr(path)).privateKey; - expect(pub.toString('hex')).toEqualElseLog( - secp256k1.keyFromPrivate(priv).getPublic().encode('hex', false), - 'Exported SECP256K1 pubkey incorrect', - ); - } - }); -}; +export const validateDerivedPublicKeys = (pubKeys, firstPath, seed, flag?: number) => { + const wallet = bip32.fromSeed(seed) + // We assume the keys were derived in sequential order + pubKeys.forEach((pub, i) => { + const path = JSON.parse(JSON.stringify(firstPath)) + path[path.length - 1] += i + if (flag === Constants.GET_ADDR_FLAGS.ED25519_PUB) { + // ED25519 requires its own derivation + const key = deriveED25519Key(path, seed) + expect(pub.toString('hex')).toEqualElseLog(key.pub.toString('hex'), 'Exported ED25519 pubkey incorrect') + } else { + // Otherwise this is a SECP256K1 pubkey + const priv = wallet.derivePath(getPathStr(path)).privateKey + expect(pub.toString('hex')).toEqualElseLog(secp256k1.keyFromPrivate(priv).getPublic().encode('hex', false), 'Exported SECP256K1 pubkey incorrect') + } + }) +} -export const ethPersonalSignMsg = (msg) => - `\u0019Ethereum Signed Message:\n${String(msg.length)}${msg}`; +export const ethPersonalSignMsg = (msg) => `\u0019Ethereum Signed Message:\n${String(msg.length)}${msg}` //--------------------------------------------------- // Sign Transaction helpers //--------------------------------------------------- export const serializeSignTxJobDataLegacy = (data) => { - // Serialize a signTX request using the legacy option - // (see `WalletJobData_SignTx_t` and `SignatureRequest_t`) - // in firmware for more info on legacy vs generic (new) - // wallet job requests - const n = data.sigReq.length; - const req = Buffer.alloc(4 + 56 * n); - let off = 0; - req.writeUInt32LE(data.numRequests, 0); - off += 4; - for (let i = 0; i < n; i++) { - const r = data.sigReq[i]; - r.data.copy(req, off); - off += r.data.length; - req.writeUInt32LE(r.signerPath.pathDepth, off); - off += 4; - req.writeUInt32LE(r.signerPath.purpose, off); - off += 4; - req.writeUInt32LE(r.signerPath.coin, off); - off += 4; - req.writeUInt32LE(r.signerPath.account, off); - off += 4; - req.writeUInt32LE(r.signerPath.change, off); - off += 4; - req.writeUInt32LE(r.signerPath.addr, off); - off += 4; - } - return req; -}; + // Serialize a signTX request using the legacy option + // (see `WalletJobData_SignTx_t` and `SignatureRequest_t`) + // in firmware for more info on legacy vs generic (new) + // wallet job requests + const n = data.sigReq.length + const req = Buffer.alloc(4 + 56 * n) + let off = 0 + req.writeUInt32LE(data.numRequests, 0) + off += 4 + for (let i = 0; i < n; i++) { + const r = data.sigReq[i] + r.data.copy(req, off) + off += r.data.length + req.writeUInt32LE(r.signerPath.pathDepth, off) + off += 4 + req.writeUInt32LE(r.signerPath.purpose, off) + off += 4 + req.writeUInt32LE(r.signerPath.coin, off) + off += 4 + req.writeUInt32LE(r.signerPath.account, off) + off += 4 + req.writeUInt32LE(r.signerPath.change, off) + off += 4 + req.writeUInt32LE(r.signerPath.addr, off) + off += 4 + } + return req +} export const deserializeSignTxJobResult = (res: any) => { - let off = 0; - const getTxResult: any = { - numOutputs: null, - outputs: [], - }; - getTxResult.numOutputs = res.readUInt32LE(off); - off += 4; - const PK_LEN = 65; // uncompressed pubkey - const SIG_LEN = 74; // DER sig - const outputSz = 6 * 4 + PK_LEN + SIG_LEN; - let _off = 0; - for (let i = 0; i < getTxResult.numOutputs; i++) { - const o = { - signerPath: { - pathDepth: null, - purpose: null, - coin: null, - account: null, - change: null, - addr: null, - }, - pubkey: null as any, - sig: null as any, - }; - const _o = res.slice(off, off + outputSz); - off += outputSz; - _off = 0; - o.signerPath.pathDepth = _o.readUInt32LE(_off); - _off += 4; - o.signerPath.purpose = _o.readUInt32LE(_off); - _off += 4; - o.signerPath.coin = _o.readUInt32LE(_off); - _off += 4; - o.signerPath.account = _o.readUInt32LE(_off); - _off += 4; - o.signerPath.change = _o.readUInt32LE(_off); - _off += 4; - o.signerPath.addr = _o.readUInt32LE(_off); - _off += 4; - o.pubkey = secp256k1.keyFromPublic( - _o.slice(_off, _off + 65).toString('hex'), - 'hex', - ); - _off += PK_LEN; - // We get back a DER signature in 74 bytes, but not all the bytes are necessarily - // used. The second byte contains the DER sig length, so we need to use that. - const derLen = _o[_off + 1]; - o.sig = Buffer.from( - _o.slice(_off, _off + 2 + derLen).toString('hex'), - 'hex', - ); - getTxResult.outputs.push(o); - } - - return getTxResult; -}; + let off = 0 + const getTxResult: any = { + numOutputs: null, + outputs: [], + } + getTxResult.numOutputs = res.readUInt32LE(off) + off += 4 + const PK_LEN = 65 // uncompressed pubkey + const SIG_LEN = 74 // DER sig + const outputSz = 6 * 4 + PK_LEN + SIG_LEN + let _off = 0 + for (let i = 0; i < getTxResult.numOutputs; i++) { + const o = { + signerPath: { + pathDepth: null, + purpose: null, + coin: null, + account: null, + change: null, + addr: null, + }, + pubkey: null as any, + sig: null as any, + } + const _o = res.slice(off, off + outputSz) + off += outputSz + _off = 0 + o.signerPath.pathDepth = _o.readUInt32LE(_off) + _off += 4 + o.signerPath.purpose = _o.readUInt32LE(_off) + _off += 4 + o.signerPath.coin = _o.readUInt32LE(_off) + _off += 4 + o.signerPath.account = _o.readUInt32LE(_off) + _off += 4 + o.signerPath.change = _o.readUInt32LE(_off) + _off += 4 + o.signerPath.addr = _o.readUInt32LE(_off) + _off += 4 + o.pubkey = secp256k1.keyFromPublic(_o.slice(_off, _off + 65).toString('hex'), 'hex') + _off += PK_LEN + // We get back a DER signature in 74 bytes, but not all the bytes are necessarily + // used. The second byte contains the DER sig length, so we need to use that. + const derLen = _o[_off + 1] + o.sig = Buffer.from(_o.slice(_off, _off + 2 + derLen).toString('hex'), 'hex') + getTxResult.outputs.push(o) + } + + return getTxResult +} //--------------------------------------------------- // Export Seed helpers //--------------------------------------------------- -export const serializeExportSeedJobData = () => Buffer.alloc(0); +export const serializeExportSeedJobData = () => Buffer.alloc(0) export const deserializeExportSeedJobResult = (res) => { - let off = 0; - const seed = res.slice(off, 64); - off += 64; - const words = []; - for (let i = 0; i < 24; i++) { - const wordIdx = res.slice(off, off + 4).readUInt32LE(0); - words.push(wordlists.english[wordIdx]); - off += 4; - } - const numWords = res.slice(off, off + 4).readUInt32LE(0); - off += 4; - return { - seed, - mnemonic: words.slice(0, numWords).join(' '), - }; -}; + let off = 0 + const seed = res.slice(off, 64) + off += 64 + const words = [] + for (let i = 0; i < 24; i++) { + const wordIdx = res.slice(off, off + 4).readUInt32LE(0) + words.push(wordlists.english[wordIdx]) + off += 4 + } + const numWords = res.slice(off, off + 4).readUInt32LE(0) + off += 4 + return { + seed, + mnemonic: words.slice(0, numWords).join(' '), + } +} //--------------------------------------------------- // Delete Seed helpers //--------------------------------------------------- export const serializeDeleteSeedJobData = (data) => { - const req = Buffer.alloc(1); - req.writeUInt8(data.iface, 0); - return req; -}; + const req = Buffer.alloc(1) + req.writeUInt8(data.iface, 0) + return req +} //--------------------------------------------------- // Load Seed helpers //--------------------------------------------------- export const serializeLoadSeedJobData = (data) => { - const req = Buffer.alloc(217); - let off = 0; - req.writeUInt8(data.iface, off); - off += 1; - data.seed.copy(req, off); - off += data.seed.length; - req.writeUInt8(data.exportability, off); - off += 1; - if (data.mnemonic) { - // Serialize the mnemonic - const mWords = data.mnemonic.split(' '); - for (let i = 0; i < mWords.length; i++) { - req.writeUint32LE(wordlists.english.indexOf(mWords[i]), off + i * 4); - } - // Strangely the struct is written with the length of - // words after the words themselves lol (24 words * 4 bytes per word = 96) - // (Preserved for fear of any unintended consequences to chaning `BIP39Phrase_t` in fw) - req.writeUInt32LE(mWords.length, off + 96); - // Ignore the passphrase since we only use this wallet job - // helper to test loading a mnemonic onto the card's extraData - // buffer, which does not include the passphrase. - } - return req; -}; + const req = Buffer.alloc(217) + let off = 0 + req.writeUInt8(data.iface, off) + off += 1 + data.seed.copy(req, off) + off += data.seed.length + req.writeUInt8(data.exportability, off) + off += 1 + if (data.mnemonic) { + // Serialize the mnemonic + const mWords = data.mnemonic.split(' ') + for (let i = 0; i < mWords.length; i++) { + req.writeUint32LE(wordlists.english.indexOf(mWords[i]), off + i * 4) + } + // Strangely the struct is written with the length of + // words after the words themselves lol (24 words * 4 bytes per word = 96) + // (Preserved for fear of any unintended consequences to chaning `BIP39Phrase_t` in fw) + req.writeUInt32LE(mWords.length, off + 96) + // Ignore the passphrase since we only use this wallet job + // helper to test loading a mnemonic onto the card's extraData + // buffer, which does not include the passphrase. + } + return req +} //--------------------------------------------------- // Struct builders //--------------------------------------------------- export const buildRandomEip712Object = (randInt) => { - function randStr(n) { - const words = wordlists.english; - let s = ''; - while (s.length < n) { - s += `${words?.[randInt(words?.length)]}_`; - } - return s.slice(0, n); - } - function getRandomName(upperCase = false, sz = 20) { - const name = randStr(sz); - if (upperCase === true) - return `${name.slice(0, 1).toUpperCase()}${name.slice(1)}`; - return name; - } - function getRandomEIP712Type(customTypes: any[] = []) { - const types = Object.keys(customTypes).concat( - Object.keys(ethMsgProtocol.TYPED_DATA.typeCodes), - ); - return { - name: getRandomName(), - type: types[randInt(types.length)], - }; - } - function getRandomEIP712Val(type) { - if (type !== 'bytes' && type.slice(0, 5) === 'bytes') { - return `0x${randomBytes(Number.parseInt(type.slice(5))).toString('hex')}`; - } - - if (type === 'uint' || type.indexOf('uint') === 0) { - const bits = Number.parseInt(type.slice(4) || '256', 10); - const byteLength = Math.max(1, Math.ceil(bits / 8)); - return `0x${randomBytes(byteLength).toString('hex')}`; - } - - if (type === 'int' || type.indexOf('int') === 0) { - const bits = Number.parseInt(type.slice(3) || '256', 10); - const byteLength = Math.max(1, Math.ceil(bits / 8)); - const raw = randomBytes(byteLength).toString('hex'); - const modulus = 1n << BigInt(bits); - const halfModulus = modulus >> 1n; - let value = BigInt(`0x${raw}`); - value = ((value % modulus) + modulus) % modulus; - if (value >= halfModulus) { - value -= modulus; - } - return value.toString(); - } - switch (type) { - case 'bytes': - return `0x${randomBytes(1 + randInt(50)).toString('hex')}`; - case 'string': - return randStr(100); - case 'bool': - return randInt(1) > 0; - case 'address': - return `0x${randomBytes(20).toString('hex')}`; - default: - throw new Error('unsupported eip712 type'); - } - } - function buildCustomTypeVal(typeName, msg) { - const val = {}; - const subTypes = msg.types[typeName]; - subTypes.forEach((subType) => { - if (Object.keys(msg.types).indexOf(subType.type) > -1) { - // If this is a custom type we need to recurse - val[subType.name] = buildCustomTypeVal(subType.type, msg); - } else { - val[subType.name] = getRandomEIP712Val(subType.type); - } - }); - return val; - } - - const msg = { - types: { - EIP712Domain: [ - { name: 'name', type: 'string' }, - { name: 'version', type: 'string' }, - { name: 'chainId', type: 'uint256' }, - { name: 'verifyingContract', type: 'address' }, - ], - }, - primaryType: `Primary_${getRandomName(true)}`, - domain: { - name: `Domain_${getRandomName(true)}`, - version: '1', - chainId: `0x${(1 + randInt(15000)).toString(16)}`, - verifyingContract: `0x${randomBytes(20).toString('hex')}`, - }, - message: {}, - }; - msg.types[msg.primaryType] = []; - - // Create custom types and add them to the types definitions - const numCustomTypes = 1 + randInt(3); - const numDefaulTypes = 1 + randInt(3); - const customTypesMap: any = {}; - for (let i = 0; i < numCustomTypes; i++) { - const subTypes: any[] = []; - for (let j = 0; j < 1 + randInt(3); j++) { - subTypes.push(getRandomEIP712Type(customTypesMap)); - } - // Capitalize custom type names to distinguish them - let typeName = getRandomName(true); - typeName = `${typeName.slice(0, 1).toUpperCase()}${typeName.slice(1)}`; - customTypesMap[typeName] = subTypes; - // Record the type - msg.types[typeName] = subTypes; - // Add a record in the primary type. We will need to create a value later. - msg.types[msg.primaryType].push({ - name: getRandomName(), - type: typeName, - }); - } - // Generate default (i.e. "atomic") types to mix into the message - for (let i = 0; i < numDefaulTypes; i++) { - const t = getRandomEIP712Type(); - // Add to the primary type definition - msg.types[msg.primaryType].push(t); - } - // Generate random values - msg.types[msg.primaryType].forEach((typeDef) => { - if (Object.keys(msg.types).indexOf(typeDef.type) === -1) { - // Normal EIP712 atomic type - msg.message[typeDef.name] = getRandomEIP712Val(typeDef.type); - } else { - // Custom type - msg.message[typeDef.name] = buildCustomTypeVal(typeDef.type, msg); - } - }); - return msg; -}; + function randStr(n) { + const words = wordlists.english + let s = '' + while (s.length < n) { + s += `${words?.[randInt(words?.length)]}_` + } + return s.slice(0, n) + } + function getRandomName(upperCase = false, sz = 20) { + const name = randStr(sz) + if (upperCase === true) return `${name.slice(0, 1).toUpperCase()}${name.slice(1)}` + return name + } + function getRandomEIP712Type(customTypes: any[] = []) { + const types = Object.keys(customTypes).concat(Object.keys(ethMsgProtocol.TYPED_DATA.typeCodes)) + return { + name: getRandomName(), + type: types[randInt(types.length)], + } + } + function getRandomEIP712Val(type) { + if (type !== 'bytes' && type.slice(0, 5) === 'bytes') { + return `0x${randomBytes(Number.parseInt(type.slice(5))).toString('hex')}` + } + + if (type === 'uint' || type.indexOf('uint') === 0) { + const bits = Number.parseInt(type.slice(4) || '256', 10) + const byteLength = Math.max(1, Math.ceil(bits / 8)) + return `0x${randomBytes(byteLength).toString('hex')}` + } + + if (type === 'int' || type.indexOf('int') === 0) { + const bits = Number.parseInt(type.slice(3) || '256', 10) + const byteLength = Math.max(1, Math.ceil(bits / 8)) + const raw = randomBytes(byteLength).toString('hex') + const modulus = 1n << BigInt(bits) + const halfModulus = modulus >> 1n + let value = BigInt(`0x${raw}`) + value = ((value % modulus) + modulus) % modulus + if (value >= halfModulus) { + value -= modulus + } + return value.toString() + } + switch (type) { + case 'bytes': + return `0x${randomBytes(1 + randInt(50)).toString('hex')}` + case 'string': + return randStr(100) + case 'bool': + return randInt(1) > 0 + case 'address': + return `0x${randomBytes(20).toString('hex')}` + default: + throw new Error('unsupported eip712 type') + } + } + function buildCustomTypeVal(typeName, msg) { + const val = {} + const subTypes = msg.types[typeName] + subTypes.forEach((subType) => { + if (Object.keys(msg.types).indexOf(subType.type) > -1) { + // If this is a custom type we need to recurse + val[subType.name] = buildCustomTypeVal(subType.type, msg) + } else { + val[subType.name] = getRandomEIP712Val(subType.type) + } + }) + return val + } + + const msg = { + types: { + EIP712Domain: [ + { name: 'name', type: 'string' }, + { name: 'version', type: 'string' }, + { name: 'chainId', type: 'uint256' }, + { name: 'verifyingContract', type: 'address' }, + ], + }, + primaryType: `Primary_${getRandomName(true)}`, + domain: { + name: `Domain_${getRandomName(true)}`, + version: '1', + chainId: `0x${(1 + randInt(15000)).toString(16)}`, + verifyingContract: `0x${randomBytes(20).toString('hex')}`, + }, + message: {}, + } + msg.types[msg.primaryType] = [] + + // Create custom types and add them to the types definitions + const numCustomTypes = 1 + randInt(3) + const numDefaulTypes = 1 + randInt(3) + const customTypesMap: any = {} + for (let i = 0; i < numCustomTypes; i++) { + const subTypes: any[] = [] + for (let j = 0; j < 1 + randInt(3); j++) { + subTypes.push(getRandomEIP712Type(customTypesMap)) + } + // Capitalize custom type names to distinguish them + let typeName = getRandomName(true) + typeName = `${typeName.slice(0, 1).toUpperCase()}${typeName.slice(1)}` + customTypesMap[typeName] = subTypes + // Record the type + msg.types[typeName] = subTypes + // Add a record in the primary type. We will need to create a value later. + msg.types[msg.primaryType].push({ + name: getRandomName(), + type: typeName, + }) + } + // Generate default (i.e. "atomic") types to mix into the message + for (let i = 0; i < numDefaulTypes; i++) { + const t = getRandomEIP712Type() + // Add to the primary type definition + msg.types[msg.primaryType].push(t) + } + // Generate random values + msg.types[msg.primaryType].forEach((typeDef) => { + if (Object.keys(msg.types).indexOf(typeDef.type) === -1) { + // Normal EIP712 atomic type + msg.message[typeDef.name] = getRandomEIP712Val(typeDef.type) + } else { + // Custom type + msg.message[typeDef.name] = buildCustomTypeVal(typeDef.type, msg) + } + }) + return msg +} //--------------------------------------------------- // Generic signing //--------------------------------------------------- export const validateGenericSig = (seed, sig, payloadBuf, req, pubkey?) => { - const { signerPath, hashType, curveType } = req; - const HASHES = Constants.SIGNING.HASHES; - const CURVES = Constants.SIGNING.CURVES; - let hash: Buffer; - if (curveType === CURVES.SECP256K1) { - if (hashType === HASHES.SHA256) { - hash = Buffer.from(Hash.sha256(payloadBuf)); - } else if (hashType === HASHES.KECCAK256) { - hash = Buffer.from(Hash.keccak256(payloadBuf)); - } else { - throw new Error('Bad params'); - } - const { priv } = deriveSECP256K1Key(signerPath, seed); - const key = secp256k1.keyFromPrivate(priv); - const normalizedSig = { - r: normalizeSigComponent(sig.r).toString('hex'), - s: normalizeSigComponent(sig.s).toString('hex'), - }; - expect(key.verify(hash, normalizedSig)).toEqualElseLog( - true, - 'Signature failed verification.', - ); - } else if (curveType === CURVES.ED25519) { - if (hashType !== HASHES.NONE) { - throw new Error('Bad params'); - } - const { pub } = deriveED25519Key(signerPath, seed); - const signature = Buffer.concat([ - normalizeSigComponent(sig.r), - normalizeSigComponent(sig.s), - ]); - const edPublicKey = pubkey ? normalizeSigComponent(pubkey) : pub; - const isValid = nacl.sign.detached.verify( - new Uint8Array(payloadBuf), - new Uint8Array(signature), - new Uint8Array(edPublicKey), - ); - expect(isValid).toEqualElseLog(true, 'Signature failed verification.'); - } else { - throw new Error('Bad params'); - } -}; + const { signerPath, hashType, curveType } = req + const HASHES = Constants.SIGNING.HASHES + const CURVES = Constants.SIGNING.CURVES + let hash: Buffer + if (curveType === CURVES.SECP256K1) { + if (hashType === HASHES.SHA256) { + hash = Buffer.from(Hash.sha256(payloadBuf)) + } else if (hashType === HASHES.KECCAK256) { + hash = Buffer.from(Hash.keccak256(payloadBuf)) + } else { + throw new Error('Bad params') + } + const { priv } = deriveSECP256K1Key(signerPath, seed) + const key = secp256k1.keyFromPrivate(priv) + const normalizedSig = { + r: normalizeSigComponent(sig.r).toString('hex'), + s: normalizeSigComponent(sig.s).toString('hex'), + } + expect(key.verify(hash, normalizedSig)).toEqualElseLog(true, 'Signature failed verification.') + } else if (curveType === CURVES.ED25519) { + if (hashType !== HASHES.NONE) { + throw new Error('Bad params') + } + const { pub } = deriveED25519Key(signerPath, seed) + const signature = Buffer.concat([normalizeSigComponent(sig.r), normalizeSigComponent(sig.s)]) + const edPublicKey = pubkey ? normalizeSigComponent(pubkey) : pub + const isValid = nacl.sign.detached.verify(new Uint8Array(payloadBuf), new Uint8Array(signature), new Uint8Array(edPublicKey)) + expect(isValid).toEqualElseLog(true, 'Signature failed verification.') + } else { + throw new Error('Bad params') + } +} /** * Get a RSV formatted signature string @@ -1031,126 +898,102 @@ export const validateGenericSig = (seed, sig, payloadBuf, req, pubkey?) => { * @param tx - optional, an @ethereumjs/tx Transaction object */ export const getSigStr = (resp: any, tx?: TypedTransaction) => { - let v: string; - if (resp.sig.v !== undefined) { - const vBuf = normalizeSigComponent(resp.sig.v); - const vHex = vBuf.toString('hex'); - let vInt = vHex ? Number.parseInt(vHex, 16) : 0; - if (!Number.isFinite(vInt)) { - vInt = 0; - } - if (vInt >= 27) { - vInt -= 27; - } - v = vInt.toString(16).padStart(2, '0'); - } else if (tx) { - v = getSignatureVParam(tx, resp); - } else { - throw new Error('Could not build sig string'); - } - const rHex = normalizeSigComponent(resp.sig.r).toString('hex'); - const sHex = normalizeSigComponent(resp.sig.s).toString('hex'); - return `${rHex}${sHex}${v}`; -}; + let v: string + if (resp.sig.v !== undefined) { + const vBuf = normalizeSigComponent(resp.sig.v) + const vHex = vBuf.toString('hex') + let vInt = vHex ? Number.parseInt(vHex, 16) : 0 + if (!Number.isFinite(vInt)) { + vInt = 0 + } + if (vInt >= 27) { + vInt -= 27 + } + v = vInt.toString(16).padStart(2, '0') + } else if (tx) { + v = getSignatureVParam(tx, resp) + } else { + throw new Error('Could not build sig string') + } + const rHex = normalizeSigComponent(resp.sig.r).toString('hex') + const sHex = normalizeSigComponent(resp.sig.s).toString('hex') + return `${rHex}${sHex}${v}` +} export function toBuffer(data: string | number | Buffer | Uint8Array): Buffer { - if (data === null || data === undefined) { - throw new Error('Invalid data'); - } - if (Buffer.isBuffer(data)) { - return data; - } - if (data instanceof Uint8Array) { - return Buffer.from(data.buffer, data.byteOffset, data.length); - } - if (typeof data === 'number') { - return ensureHexBuffer(data); - } - if (typeof data === 'string') { - const trimmed = data.trim(); - const isHex = - trimmed.startsWith('0x') || - (/^[0-9a-fA-F]+$/.test(trimmed) && trimmed.length % 2 === 0); - return isHex ? ensureHexBuffer(trimmed) : Buffer.from(trimmed, 'utf8'); - } - throw new Error('Unsupported data type'); + if (data === null || data === undefined) { + throw new Error('Invalid data') + } + if (Buffer.isBuffer(data)) { + return data + } + if (data instanceof Uint8Array) { + return Buffer.from(data.buffer, data.byteOffset, data.length) + } + if (typeof data === 'number') { + return ensureHexBuffer(data) + } + if (typeof data === 'string') { + const trimmed = data.trim() + const isHex = trimmed.startsWith('0x') || (/^[0-9a-fA-F]+$/.test(trimmed) && trimmed.length % 2 === 0) + return isHex ? ensureHexBuffer(trimmed) : Buffer.from(trimmed, 'utf8') + } + throw new Error('Unsupported data type') } export function toUint8Array(data: Buffer): Uint8Array { - return new Uint8Array(data.buffer, data.byteOffset, data.length); + return new Uint8Array(data.buffer, data.byteOffset, data.length) } -export function ensureHash32( - message: string | number | Buffer | Uint8Array, -): Uint8Array { - const msgBuffer = toBuffer(message); - const digest = - msgBuffer.length === 32 - ? msgBuffer - : Buffer.from(Hash.keccak256(msgBuffer)); - if (digest.length !== 32) { - throw new Error('Failed to derive 32-byte hash for signature validation.'); - } - return toUint8Array(digest); +export function ensureHash32(message: string | number | Buffer | Uint8Array): Uint8Array { + const msgBuffer = toBuffer(message) + const digest = msgBuffer.length === 32 ? msgBuffer : Buffer.from(Hash.keccak256(msgBuffer)) + if (digest.length !== 32) { + throw new Error('Failed to derive 32-byte hash for signature validation.') + } + return toUint8Array(digest) } -export function validateSig( - resp: any, - message: string | number | Buffer | Uint8Array, -) { - if (!resp.sig?.r || !resp.sig?.s || !resp.pubkey) { - throw new Error('Missing signature components'); - } - const rBuf = normalizeSigComponent(resp.sig.r); - const sBuf = normalizeSigComponent(resp.sig.s); - const rs = new Uint8Array(Buffer.concat([rBuf, sBuf])); - const hash = ensureHash32(message); - - const pubkeyInput = toBuffer(resp.pubkey); - const normalizedPubkey = - pubkeyInput.length === 64 - ? Buffer.concat([Buffer.from([0x04]), pubkeyInput]) - : pubkeyInput; - const isCompressed = - normalizedPubkey.length === 33 && - (normalizedPubkey[0] === 0x02 || normalizedPubkey[0] === 0x03); - - const recoveredA = Buffer.from( - ecdsaRecover(rs, 0, hash, isCompressed), - ).toString('hex'); - const recoveredB = Buffer.from( - ecdsaRecover(rs, 1, hash, isCompressed), - ).toString('hex'); - const expected = normalizedPubkey.toString('hex'); - if (expected !== recoveredA && expected !== recoveredB) { - throw new Error('Signature did not validate.'); - } +export function validateSig(resp: any, message: string | number | Buffer | Uint8Array) { + if (!resp.sig?.r || !resp.sig?.s || !resp.pubkey) { + throw new Error('Missing signature components') + } + const rBuf = normalizeSigComponent(resp.sig.r) + const sBuf = normalizeSigComponent(resp.sig.s) + const rs = new Uint8Array(Buffer.concat([rBuf, sBuf])) + const hash = ensureHash32(message) + + const pubkeyInput = toBuffer(resp.pubkey) + const normalizedPubkey = pubkeyInput.length === 64 ? Buffer.concat([Buffer.from([0x04]), pubkeyInput]) : pubkeyInput + const isCompressed = normalizedPubkey.length === 33 && (normalizedPubkey[0] === 0x02 || normalizedPubkey[0] === 0x03) + + const recoveredA = Buffer.from(ecdsaRecover(rs, 0, hash, isCompressed)).toString('hex') + const recoveredB = Buffer.from(ecdsaRecover(rs, 1, hash, isCompressed)).toString('hex') + const expected = normalizedPubkey.toString('hex') + if (expected !== recoveredA && expected !== recoveredB) { + throw new Error('Signature did not validate.') + } } export const compressPubKey = (pub) => { - if (pub.length !== 65) { - return pub; - } - const compressed = Buffer.alloc(33); - pub.slice(1, 33).copy(compressed, 1); - if (pub[64] % 2) { - compressed[0] = 0x03; - } else { - compressed[0] = 0x02; - } - return compressed; -}; + if (pub.length !== 65) { + return pub + } + const compressed = Buffer.alloc(33) + pub.slice(1, 33).copy(compressed, 1) + if (pub[64] % 2) { + compressed[0] = 0x03 + } else { + compressed[0] = 0x02 + } + return compressed +} function _stripTrailingCommas(input: string): string { - return input.replace( - /,\s*(?:(?:\/\/[^\n]*\n)|\/\*[\s\S]*?\*\/|\s)*([}\]])/g, - '$1', - ); + return input.replace(/,\s*(?:(?:\/\/[^\n]*\n)|\/\*[\s\S]*?\*\/|\s)*([}\]])/g, '$1') } export const getTestVectors = () => { - const raw = readFileSync( - `${process.cwd()}/src/__test__/vectors.jsonc`, - ).toString(); - return jsonc.parse(_stripTrailingCommas(raw)); -}; + const raw = readFileSync(`${process.cwd()}/src/__test__/vectors.jsonc`).toString() + return jsonc.parse(_stripTrailingCommas(raw)) +} diff --git a/packages/sdk/src/__test__/utils/runners.ts b/packages/sdk/src/__test__/utils/runners.ts index 1302e5a7..354b2036 100644 --- a/packages/sdk/src/__test__/utils/runners.ts +++ b/packages/sdk/src/__test__/utils/runners.ts @@ -1,44 +1,33 @@ -import type { Client } from '../../client'; -import { getEncodedPayload } from '../../genericSigning'; -import type { - SigningPayload, - SignRequestParams, - TestRequestPayload, -} from '../../types'; -import { parseWalletJobResp, validateGenericSig } from './helpers'; -import { TEST_SEED } from './testConstants'; -import { testRequest } from './testRequest'; +import type { Client } from '../../client' +import { getEncodedPayload } from '../../genericSigning' +import type { SigningPayload, SignRequestParams, TestRequestPayload } from '../../types' +import { parseWalletJobResp, validateGenericSig } from './helpers' +import { TEST_SEED } from './testConstants' +import { testRequest } from './testRequest' -export async function runTestCase( - payload: TestRequestPayload, - expectedCode: number, -) { - const res = await testRequest(payload); - //@ts-expect-error - Accessing private property - const fwVersion = payload.client.fwVersion; - const parsedRes = parseWalletJobResp(res, fwVersion); - expect(parsedRes.resultStatus).toEqual(expectedCode); - return parsedRes; +export async function runTestCase(payload: TestRequestPayload, expectedCode: number) { + const res = await testRequest(payload) + //@ts-expect-error - Accessing private property + const fwVersion = payload.client.fwVersion + const parsedRes = parseWalletJobResp(res, fwVersion) + expect(parsedRes.resultStatus).toEqual(expectedCode) + return parsedRes } export async function runGeneric(request: SignRequestParams, client: Client) { - const response = await client.sign(request); - // runGeneric is only used for generic signing, not Bitcoin - const data = request.data as SigningPayload; - // If no encoding type is specified we encode in hex or ascii - const encodingType = data.encodingType || null; - const allowedEncodings = client.getFwConstants().genericSigning.encodingTypes; - const { payloadBuf } = getEncodedPayload( - data.payload, - encodingType, - allowedEncodings, - ); - const seed = TEST_SEED; - validateGenericSig(seed, response.sig, payloadBuf, data, response.pubkey); - return response; + const response = await client.sign(request) + // runGeneric is only used for generic signing, not Bitcoin + const data = request.data as SigningPayload + // If no encoding type is specified we encode in hex or ascii + const encodingType = data.encodingType || null + const allowedEncodings = client.getFwConstants().genericSigning.encodingTypes + const { payloadBuf } = getEncodedPayload(data.payload, encodingType, allowedEncodings) + const seed = TEST_SEED + validateGenericSig(seed, response.sig, payloadBuf, data, response.pubkey) + return response } export const runEthMsg = async (req: SignRequestParams, client: Client) => { - const sig = await client.sign(req); - expect(sig.sig).not.toEqual(null); -}; + const sig = await client.sign(req) + expect(sig.sig).not.toEqual(null) +} diff --git a/packages/sdk/src/__test__/utils/serializers.ts b/packages/sdk/src/__test__/utils/serializers.ts index b081f0b4..68e12fdb 100644 --- a/packages/sdk/src/__test__/utils/serializers.ts +++ b/packages/sdk/src/__test__/utils/serializers.ts @@ -1,25 +1,25 @@ export const serializeObjectWithBuffers = (obj: any) => { - return Object.entries(obj).reduce((acc: any, [key, value]) => { - if (value instanceof Buffer) { - acc[key] = { isBuffer: true, value: value.toString('hex') }; - } else if (typeof value === 'object') { - acc[key] = serializeObjectWithBuffers(value); - } else { - acc[key] = value; - } - return acc; - }, {}); -}; + return Object.entries(obj).reduce((acc: any, [key, value]) => { + if (value instanceof Buffer) { + acc[key] = { isBuffer: true, value: value.toString('hex') } + } else if (typeof value === 'object') { + acc[key] = serializeObjectWithBuffers(value) + } else { + acc[key] = value + } + return acc + }, {}) +} export const deserializeObjectWithBuffers = (obj: any) => { - return Object.entries(obj).reduce((acc: any, [key, value]: any) => { - if (value?.isBuffer) { - acc[key] = Buffer.from(value.value, 'hex'); - } else if (typeof value === 'object') { - acc[key] = deserializeObjectWithBuffers(value); - } else { - acc[key] = value; - } - return acc; - }, {}); -}; + return Object.entries(obj).reduce((acc: any, [key, value]: any) => { + if (value?.isBuffer) { + acc[key] = Buffer.from(value.value, 'hex') + } else if (typeof value === 'object') { + acc[key] = deserializeObjectWithBuffers(value) + } else { + acc[key] = value + } + return acc + }, {}) +} diff --git a/packages/sdk/src/__test__/utils/setup.ts b/packages/sdk/src/__test__/utils/setup.ts index 4cadb90f..be76ffb8 100644 --- a/packages/sdk/src/__test__/utils/setup.ts +++ b/packages/sdk/src/__test__/utils/setup.ts @@ -1,56 +1,54 @@ -import * as fs from 'node:fs'; -import readlineSync from 'readline-sync'; -import { getClient, pair, setup } from '../../api'; +import * as fs from 'node:fs' +import readlineSync from 'readline-sync' +import { getClient, pair, setup } from '../../api' -const question = readlineSync.question; +const question = readlineSync.question -const TEMP_CLIENT_FILE = './client.temp'; +const TEMP_CLIENT_FILE = './client.temp' export async function setStoredClient(data: string) { - try { - fs.writeFileSync(TEMP_CLIENT_FILE, data); - } catch (err) { - console.error('Failed to store client data:', err); - return; - } + try { + fs.writeFileSync(TEMP_CLIENT_FILE, data) + } catch (err) { + console.error('Failed to store client data:', err) + return + } } export async function getStoredClient() { - try { - return fs.readFileSync(TEMP_CLIENT_FILE, 'utf8'); - } catch (err) { - console.error('Failed to read stored client data:', err); - return ''; - } + try { + return fs.readFileSync(TEMP_CLIENT_FILE, 'utf8') + } catch (err) { + console.error('Failed to read stored client data:', err) + return '' + } } export async function setupClient() { - const deviceId = process.env.DEVICE_ID; - const baseUrl = process.env.baseUrl || 'https://signing.gridpl.us'; - const password = process.env.PASSWORD || 'password'; - const name = process.env.APP_NAME || 'SDK Test'; - let pairingSecret = process.env.PAIRING_SECRET; - const isPaired = await setup({ - deviceId, - password, - name, - baseUrl, - getStoredClient, - setStoredClient, - }); - if (!isPaired) { - if (!pairingSecret) { - if (process.env.CI) { - throw new Error( - 'Pairing secret is required. If simulator is running, set PAIRING_SECRET environment variable.', - ); - } - pairingSecret = question('Enter pairing secret:'); - if (!pairingSecret) { - throw new Error('Pairing secret is required.'); - } - } - await pair(pairingSecret.toUpperCase()); - } - return getClient(); + const deviceId = process.env.DEVICE_ID + const baseUrl = process.env.baseUrl || 'https://signing.gridpl.us' + const password = process.env.PASSWORD || 'password' + const name = process.env.APP_NAME || 'SDK Test' + let pairingSecret = process.env.PAIRING_SECRET + const isPaired = await setup({ + deviceId, + password, + name, + baseUrl, + getStoredClient, + setStoredClient, + }) + if (!isPaired) { + if (!pairingSecret) { + if (process.env.CI) { + throw new Error('Pairing secret is required. If simulator is running, set PAIRING_SECRET environment variable.') + } + pairingSecret = question('Enter pairing secret:') + if (!pairingSecret) { + throw new Error('Pairing secret is required.') + } + } + await pair(pairingSecret.toUpperCase()) + } + return getClient() } diff --git a/packages/sdk/src/__test__/utils/testConstants.ts b/packages/sdk/src/__test__/utils/testConstants.ts index 9c996f73..e4df92df 100644 --- a/packages/sdk/src/__test__/utils/testConstants.ts +++ b/packages/sdk/src/__test__/utils/testConstants.ts @@ -1,4 +1,4 @@ -import { mnemonicToSeedSync } from 'bip39'; +import { mnemonicToSeedSync } from 'bip39' /** * Common test constants used across the GridPlus SDK test suite * @@ -12,12 +12,11 @@ import { mnemonicToSeedSync } from 'bip39'; * This mnemonic is used across multiple test files to ensure consistent * test behavior and deterministic results. */ -export const TEST_MNEMONIC = - 'test test test test test test test test test test test junk'; +export const TEST_MNEMONIC = 'test test test test test test test test test test test junk' /** * Shared seed derived from TEST_MNEMONIC * * Consumers can reuse this to avoid re-deriving the seed in each test. */ -export const TEST_SEED = mnemonicToSeedSync(TEST_MNEMONIC); +export const TEST_SEED = mnemonicToSeedSync(TEST_MNEMONIC) diff --git a/packages/sdk/src/__test__/utils/testEnvironment.ts b/packages/sdk/src/__test__/utils/testEnvironment.ts index 8f23e7c5..00c50522 100644 --- a/packages/sdk/src/__test__/utils/testEnvironment.ts +++ b/packages/sdk/src/__test__/utils/testEnvironment.ts @@ -1,13 +1,12 @@ -import * as dotenv from 'dotenv'; +import * as dotenv from 'dotenv' -dotenv.config(); +dotenv.config() expect.extend({ - toEqualElseLog(received: unknown, expected: unknown, message?: string) { - return { - pass: received === expected, - message: () => - message ?? `Expected ${String(received)} to equal ${String(expected)}`, - }; - }, -}); + toEqualElseLog(received: unknown, expected: unknown, message?: string) { + return { + pass: received === expected, + message: () => message ?? `Expected ${String(received)} to equal ${String(expected)}`, + } + }, +}) diff --git a/packages/sdk/src/__test__/utils/testRequest.ts b/packages/sdk/src/__test__/utils/testRequest.ts index 8332e70c..3a33dfdc 100644 --- a/packages/sdk/src/__test__/utils/testRequest.ts +++ b/packages/sdk/src/__test__/utils/testRequest.ts @@ -1,39 +1,30 @@ -import { - LatticeSecureEncryptedRequestType, - encryptedSecureRequest, -} from '../../protocol'; -import type { TestRequestPayload } from '../../types'; +import { LatticeSecureEncryptedRequestType, encryptedSecureRequest } from '../../protocol' +import type { TestRequestPayload } from '../../types' /** * `test` takes a data object with a testID and a payload, and sends them to the device. * @category Lattice */ -export const testRequest = async ({ - payload, - testID, - client, -}: TestRequestPayload) => { - if (!payload) { - throw new Error( - 'First argument must contain `testID` and `payload` fields.', - ); - } - const sharedSecret = client.sharedSecret; - const ephemeralPub = client.ephemeralPub; - const url = client.url; +export const testRequest = async ({ payload, testID, client }: TestRequestPayload) => { + if (!payload) { + throw new Error('First argument must contain `testID` and `payload` fields.') + } + const sharedSecret = client.sharedSecret + const ephemeralPub = client.ephemeralPub + const url = client.url - const TEST_DATA_SZ = 500; - const data = Buffer.alloc(TEST_DATA_SZ + 6); - data.writeUInt32BE(testID, 0); - data.writeUInt16BE(payload.length, 4); - payload.copy(data, 6); + const TEST_DATA_SZ = 500 + const data = Buffer.alloc(TEST_DATA_SZ + 6) + data.writeUInt32BE(testID, 0) + data.writeUInt16BE(payload.length, 4) + payload.copy(data, 6) - const { decryptedData } = await encryptedSecureRequest({ - data, - requestType: LatticeSecureEncryptedRequestType.test, - sharedSecret, - ephemeralPub, - url, - }); - return decryptedData; -}; + const { decryptedData } = await encryptedSecureRequest({ + data, + requestType: LatticeSecureEncryptedRequestType.test, + sharedSecret, + ephemeralPub, + url, + }) + return decryptedData +} diff --git a/packages/sdk/src/__test__/utils/viemComparison.ts b/packages/sdk/src/__test__/utils/viemComparison.ts index 7fb37c39..98c78d71 100644 --- a/packages/sdk/src/__test__/utils/viemComparison.ts +++ b/packages/sdk/src/__test__/utils/viemComparison.ts @@ -1,239 +1,224 @@ -import { - type Address, - type Hex, - type TransactionSerializable, - type TypedDataDefinition, - parseTransaction, - serializeTransaction, -} from 'viem'; -import { privateKeyToAccount } from 'viem/accounts'; -import { sign, signMessage } from '../../api'; -import { normalizeLatticeSignature } from '../../ethereum'; -import { ensureHexBuffer } from '../../util'; -import { deriveAddress } from './determinism'; -import { TEST_SEED } from './testConstants'; +import { type Address, type Hex, type TransactionSerializable, type TypedDataDefinition, parseTransaction, serializeTransaction } from 'viem' +import { privateKeyToAccount } from 'viem/accounts' +import { sign, signMessage } from '../../api' +import { normalizeLatticeSignature } from '../../ethereum' +import { ensureHexBuffer } from '../../util' +import { deriveAddress } from './determinism' +import { TEST_SEED } from './testConstants' // Utility function to create foundry account address for comparison export const getFoundryAddress = (): Address => { - // Use first account derivation path: m/44'/60'/0'/0/0 - const foundryPath = [44 + 0x80000000, 60 + 0x80000000, 0x80000000, 0, 0]; - return deriveAddress(TEST_SEED, foundryPath as any) as Address; -}; + // Use first account derivation path: m/44'/60'/0'/0/0 + const foundryPath = [44 + 0x80000000, 60 + 0x80000000, 0x80000000, 0, 0] + return deriveAddress(TEST_SEED, foundryPath as any) as Address +} // Get Foundry private key for signing export const getFoundryPrivateKey = (): Hex => { - return '0xac0974bec39a17e36ba4a6b4d238ff944bacb478cbed5efcae784d7bf4f2ff80'; // Foundry test account #0 private key -}; + return '0xac0974bec39a17e36ba4a6b4d238ff944bacb478cbed5efcae784d7bf4f2ff80' // Foundry test account #0 private key +} // Create foundry account for actual signing export const getFoundryAccount = () => { - const privateKey = getFoundryPrivateKey(); - return privateKeyToAccount(privateKey); -}; + const privateKey = getFoundryPrivateKey() + return privateKeyToAccount(privateKey) +} // Transaction type for our test vectors - use viem's TransactionSerializable -export type TestTransaction = TransactionSerializable; +export type TestTransaction = TransactionSerializable // Sign transaction with both Lattice and viem, then compare -export const signAndCompareTransaction = async ( - tx: TestTransaction, - testName: string, -) => { - const foundryAccount = getFoundryAccount(); - - try { - // Sign with Lattice using the new sign API that accepts TransactionSerializable directly - const latticeResult = await sign(tx).catch((err) => { - if (err.responseCode === 128) { - err.message = `NOTE: You must have \`FEATURE_TEST_RUNNER=1\` enabled in firmware to run these tests.\n${err.message}`; - } - if (err.responseCode === 132) { - err.message = `NOTE: Please approve the transaction on your Lattice device.\n${err.message}`; - } - throw err; - }); - - // Sign with viem wallet (use original transaction) - const viemSignedTx = await foundryAccount.signTransaction(tx); - - // Parse the viem signed transaction to extract signature components - const parsedViemTx = parseTransaction(viemSignedTx as `0x${string}`); - - // Verify Lattice signature structure - expect(latticeResult.sig).toBeDefined(); - expect(latticeResult.sig.r).toBeDefined(); - expect(latticeResult.sig.s).toBeDefined(); - - // For legacy transactions, expect v; for modern transactions, v might be undefined - if (tx.type === 'legacy') { - expect(latticeResult.sig.v).toBeDefined(); - } - - // Verify viem signature components exist - expect(parsedViemTx.r).toBeDefined(); - expect(parsedViemTx.s).toBeDefined(); - - // For typed transactions (EIP-1559, EIP-2930, EIP-7702), check yParity - // For legacy transactions, check v - if (tx.type !== 'legacy') { - expect(parsedViemTx.yParity).toBeDefined(); - } else { - expect(parsedViemTx.v).toBeDefined(); - } - - // Get the signed transaction from Lattice - let latticeSignedTx: string; - - // Check if the new viemTx field is available (automatic normalization) - if ((latticeResult as any).viemTx) { - latticeSignedTx = (latticeResult as any).viemTx; - } else if (latticeResult.tx) { - // Use the provided signed transaction - latticeSignedTx = latticeResult.tx; - } else { - // Fallback to manual normalization for backward compatibility - const normalizedSignedTx = normalizeLatticeSignature(latticeResult, tx); - latticeSignedTx = serializeTransaction(normalizedSignedTx); - } - - // The most important comparison: verify both produce the same serialized transaction - expect(latticeSignedTx).toBe(viemSignedTx); - - // Additional verification: compare signature components - // Lattice returns r,s as hex strings with 0x prefix or as Buffer - const normalizeSigComponent = (value: string | Buffer) => { - const hexString = - typeof value === 'string' - ? value - : `0x${Buffer.from(value).toString('hex')}`; - const stripped = hexString.replace(/^0x/, '').toLowerCase(); - return `0x${stripped.padStart(64, '0')}`; - }; - - const latticeR = normalizeSigComponent(latticeResult.sig.r); - const latticeS = normalizeSigComponent(latticeResult.sig.s); - const viemR = normalizeSigComponent(parsedViemTx.r!); - const viemS = normalizeSigComponent(parsedViemTx.s!); - - // Verify r and s components match exactly - expect(latticeR).toBe(viemR); - expect(latticeS).toBe(viemS); - - return { - lattice: latticeResult, - viem: viemSignedTx, - success: true, - }; - } catch (error) { - console.error(`❌ Test failed for ${testName}:`, error.message); - - throw error; - } -}; +export const signAndCompareTransaction = async (tx: TestTransaction, testName: string) => { + const foundryAccount = getFoundryAccount() + + try { + // Sign with Lattice using the new sign API that accepts TransactionSerializable directly + const latticeResult = await sign(tx).catch((err) => { + if (err.responseCode === 128) { + err.message = `NOTE: You must have \`FEATURE_TEST_RUNNER=1\` enabled in firmware to run these tests.\n${err.message}` + } + if (err.responseCode === 132) { + err.message = `NOTE: Please approve the transaction on your Lattice device.\n${err.message}` + } + throw err + }) + + // Sign with viem wallet (use original transaction) + const viemSignedTx = await foundryAccount.signTransaction(tx) + + // Parse the viem signed transaction to extract signature components + const parsedViemTx = parseTransaction(viemSignedTx as `0x${string}`) + + // Verify Lattice signature structure + expect(latticeResult.sig).toBeDefined() + expect(latticeResult.sig.r).toBeDefined() + expect(latticeResult.sig.s).toBeDefined() + + // For legacy transactions, expect v; for modern transactions, v might be undefined + if (tx.type === 'legacy') { + expect(latticeResult.sig.v).toBeDefined() + } + + // Verify viem signature components exist + expect(parsedViemTx.r).toBeDefined() + expect(parsedViemTx.s).toBeDefined() + + // For typed transactions (EIP-1559, EIP-2930, EIP-7702), check yParity + // For legacy transactions, check v + if (tx.type !== 'legacy') { + expect(parsedViemTx.yParity).toBeDefined() + } else { + expect(parsedViemTx.v).toBeDefined() + } + + // Get the signed transaction from Lattice + let latticeSignedTx: string + + // Check if the new viemTx field is available (automatic normalization) + if ((latticeResult as any).viemTx) { + latticeSignedTx = (latticeResult as any).viemTx + } else if (latticeResult.tx) { + // Use the provided signed transaction + latticeSignedTx = latticeResult.tx + } else { + // Fallback to manual normalization for backward compatibility + const normalizedSignedTx = normalizeLatticeSignature(latticeResult, tx) + latticeSignedTx = serializeTransaction(normalizedSignedTx) + } + + // The most important comparison: verify both produce the same serialized transaction + expect(latticeSignedTx).toBe(viemSignedTx) + + // Additional verification: compare signature components + // Lattice returns r,s as hex strings with 0x prefix or as Buffer + const normalizeSigComponent = (value: string | Buffer) => { + const hexString = typeof value === 'string' ? value : `0x${Buffer.from(value).toString('hex')}` + const stripped = hexString.replace(/^0x/, '').toLowerCase() + return `0x${stripped.padStart(64, '0')}` + } + + const latticeR = normalizeSigComponent(latticeResult.sig.r) + const latticeS = normalizeSigComponent(latticeResult.sig.s) + if (!parsedViemTx.r || !parsedViemTx.s) throw new Error('Missing signature components') + const viemR = normalizeSigComponent(parsedViemTx.r) + const viemS = normalizeSigComponent(parsedViemTx.s) + + // Verify r and s components match exactly + expect(latticeR).toBe(viemR) + expect(latticeS).toBe(viemS) + + return { + lattice: latticeResult, + viem: viemSignedTx, + success: true, + } + } catch (error) { + console.error(`❌ Test failed for ${testName}:`, error.message) + + throw error + } +} // EIP-712 message type for test vectors export type EIP712TestMessage = { - domain: TypedDataDefinition['domain']; - types: TypedDataDefinition['types']; - primaryType: string; - message: Record; -}; + domain: TypedDataDefinition['domain'] + types: TypedDataDefinition['types'] + primaryType: string + message: Record +} // Sign EIP-712 typed data with both Lattice and viem, then compare -export const signAndCompareEIP712Message = async ( - eip712Message: EIP712TestMessage, - testName: string, -) => { - const foundryAccount = getFoundryAccount(); - - try { - // Sign with viem wallet - const viemSignature = await foundryAccount.signTypedData({ - domain: eip712Message.domain, - types: eip712Message.types, - primaryType: eip712Message.primaryType, - message: eip712Message.message, - }); - - // Sign with Lattice - need to add EIP712Domain to types - const latticeTypes = { - ...eip712Message.types, - EIP712Domain: [ - { name: 'name', type: 'string' }, - { name: 'version', type: 'string' }, - { name: 'chainId', type: 'uint256' }, - { name: 'verifyingContract', type: 'address' }, - ], - }; - - const latticePayload = { - types: latticeTypes, - domain: eip712Message.domain, - primaryType: eip712Message.primaryType, - message: eip712Message.message, - }; - - const latticeResult = await signMessage(latticePayload).catch((err) => { - if (err.responseCode === 128) { - err.message = `NOTE: You must have \`FEATURE_TEST_RUNNER=1\` enabled in firmware to run these tests.\n${err.message}`; - } - if (err.responseCode === 132) { - err.message = `NOTE: Please approve the message signature on your Lattice device.\n${err.message}`; - } - throw err; - }); - - // Normalize signature components - const normalizeHex = (value: any): string => { - if (value === null || value === undefined) { - return ''; - } - if (typeof value === 'bigint') { - let hex = value.toString(16); - if (hex.length % 2 !== 0) hex = `0${hex}`; - return hex; - } - if (typeof value === 'number') { - return value.toString(16); - } - if (typeof value === 'string') { - return value.startsWith('0x') ? value.slice(2) : value; - } - if (Buffer.isBuffer(value) || value instanceof Uint8Array) { - return Buffer.from(value).toString('hex'); - } - if (typeof value?.toString === 'function') { - const str = value.toString(); - if (/^0x[0-9a-f]+$/i.test(str)) { - return str.slice(2); - } - if (/^[0-9a-f]+$/i.test(str)) { - return str; - } - } - return ensureHexBuffer(value as string | number | Buffer).toString('hex'); - }; - - const rHex = normalizeHex(latticeResult.sig.r); - const sHex = normalizeHex(latticeResult.sig.s); - let vHex = normalizeHex(latticeResult.sig.v); - if (!vHex) { - vHex = '00'; - } - vHex = vHex.padStart(2, '0'); - - const latticeSignature = `0x${rHex}${sHex}${vHex}`; - - // Compare signatures - expect(latticeSignature).toBe(viemSignature); - - return { - lattice: latticeResult, - viem: viemSignature, - success: true, - }; - } catch (error) { - console.error(`❌ Test failed for ${testName}:`, error.message); - throw error; - } -}; +export const signAndCompareEIP712Message = async (eip712Message: EIP712TestMessage, testName: string) => { + const foundryAccount = getFoundryAccount() + + try { + // Sign with viem wallet + const viemSignature = await foundryAccount.signTypedData({ + domain: eip712Message.domain, + types: eip712Message.types, + primaryType: eip712Message.primaryType, + message: eip712Message.message, + }) + + // Sign with Lattice - need to add EIP712Domain to types + const latticeTypes = { + ...eip712Message.types, + EIP712Domain: [ + { name: 'name', type: 'string' }, + { name: 'version', type: 'string' }, + { name: 'chainId', type: 'uint256' }, + { name: 'verifyingContract', type: 'address' }, + ], + } + + const latticePayload = { + types: latticeTypes, + domain: eip712Message.domain, + primaryType: eip712Message.primaryType, + message: eip712Message.message, + } + + const latticeResult = await signMessage(latticePayload).catch((err) => { + if (err.responseCode === 128) { + err.message = `NOTE: You must have \`FEATURE_TEST_RUNNER=1\` enabled in firmware to run these tests.\n${err.message}` + } + if (err.responseCode === 132) { + err.message = `NOTE: Please approve the message signature on your Lattice device.\n${err.message}` + } + throw err + }) + + // Normalize signature components + const normalizeHex = (value: any): string => { + if (value === null || value === undefined) { + return '' + } + if (typeof value === 'bigint') { + let hex = value.toString(16) + if (hex.length % 2 !== 0) hex = `0${hex}` + return hex + } + if (typeof value === 'number') { + return value.toString(16) + } + if (typeof value === 'string') { + return value.startsWith('0x') ? value.slice(2) : value + } + if (Buffer.isBuffer(value) || value instanceof Uint8Array) { + return Buffer.from(value).toString('hex') + } + if (typeof value?.toString === 'function') { + const str = value.toString() + if (/^0x[0-9a-f]+$/i.test(str)) { + return str.slice(2) + } + if (/^[0-9a-f]+$/i.test(str)) { + return str + } + } + return ensureHexBuffer(value as string | number | Buffer).toString('hex') + } + + const rHex = normalizeHex(latticeResult.sig.r) + const sHex = normalizeHex(latticeResult.sig.s) + let vHex = normalizeHex(latticeResult.sig.v) + if (!vHex) { + vHex = '00' + } + vHex = vHex.padStart(2, '0') + + const latticeSignature = `0x${rHex}${sHex}${vHex}` + + // Compare signatures + expect(latticeSignature).toBe(viemSignature) + + return { + lattice: latticeResult, + viem: viemSignature, + success: true, + } + } catch (error) { + console.error(`❌ Test failed for ${testName}:`, error.message) + throw error + } +} diff --git a/packages/sdk/src/__test__/utils/vitest.d.ts b/packages/sdk/src/__test__/utils/vitest.d.ts index 9934228b..fa093948 100644 --- a/packages/sdk/src/__test__/utils/vitest.d.ts +++ b/packages/sdk/src/__test__/utils/vitest.d.ts @@ -1,12 +1,12 @@ /// interface CustomMatchers { - toEqualElseLog(expected: unknown, message?: string): R; + toEqualElseLog(expected: unknown, message?: string): R } declare module 'vitest' { - // eslint-disable-next-line @typescript-eslint/no-empty-object-type - interface Assertion extends CustomMatchers {} - // eslint-disable-next-line @typescript-eslint/no-empty-object-type - interface AsymmetricMatchersContaining extends CustomMatchers {} + // eslint-disable-next-line @typescript-eslint/no-empty-object-type + interface Assertion extends CustomMatchers {} + // eslint-disable-next-line @typescript-eslint/no-empty-object-type + interface AsymmetricMatchersContaining extends CustomMatchers {} } diff --git a/packages/sdk/src/__test__/vectors.jsonc b/packages/sdk/src/__test__/vectors.jsonc index 1bf9ad44..5bf28c97 100644 --- a/packages/sdk/src/__test__/vectors.jsonc +++ b/packages/sdk/src/__test__/vectors.jsonc @@ -1,235 +1,235 @@ // Constant test vectors for various things { - "evm": { - // We use these vectors to test just-in-type calldata decoding for EVM transactions. - // The relevant protocol is Ethereum ABI serdes: https://docs.soliditylang.org/en/develop/abi-spec.html - "calldata": { - // Public transactions interacting with contracts. We use these - // to simulate a production environment of fetching ABI data and adding - // decoding info to the request. - "publicTxHashes": [ - { - "chainID": 137, - "hash": "0xd19a9bf70da20c10faf4d4355940cca8a5db91fa6cc1d258d6660d475d36616d", - // If you look at this example on Polygonscan, you'll notice the function being - // called is not actually available in the source code of the contract... - // So we needed to add a param to skip that test. - "skipBlockExplorerReq": true, - }, - // swapExactTokensForTokens(uint256 amountIn, uint256 amountOutMin, address[] path, - // address to, uint256 deadline) - { - "chainID": 1, - "hash": "0xeee0752109c6d31038bab6c2b0a3e3857e8bffb9c229de71f0196fda6fb28a5e", - }, - // remove_liquidity_one_coin(uint256 _token_amount, int128 i, uint256 _min_amount) - { - "chainID": 1, - "hash": "0xa6173b4890303e12ca1b195ea4a04d8891b0d83768b734d2ecdb9c6dd9d828c4", - }, - // atomicMatch_(address[14],uint256[18],uint8[8],bytes,bytes,bytes,bytes,bytes,bytes, - // uint8[2],bytes32[5]) - // this one is too large for 1 frame - // "0x92c82aad37a925e3aabe3d603109a5e65993aa2615c4b2278c3d355d9d433dff", - // exactInput((bytes,address,uint256,uint256,uint256)) - { - "chainID": 1, - "hash": "0xee9710119c13dba6fe2de240ef1e24a2489e98d9c7dd5881e2901056764ee234", - }, - // exactInputSingle((address,address,uint24,address,uint256,uint256,uint256,uint160)) - { - "chainID": 1, - "hash": "0xc33308ca105630b99a0c24ddde4f4506aa4115ec6f1a25f1138f3bd8cfa32b49", - }, - // proveAndClaimWithResolver(bytes,(bytes,bytes)[],bytes,address,address) - { - "chainID": 1, - "hash": "0xe15e6205c6696d444fc426468defdf08e89a0f5b3b8a17c68428f7aeefd53ca1", - }, - // bulkTransfer(((uint8,address,uint256,uint256)[],address,bool)[],bytes32) - { - "chainID": 1, - "hash": "0x6f0ba3cb3c08e0ee443e4b7a78e1397413762e63287e348829c18f287c5a457f", - }, - // RECURSIVE DEFS - // multicall(bytes[]) - { - "chainID": 1, - "hash": "0xf4c48f0300acb2982fe8861ffd9291634115a33dc4107a66b4f9f43efb66896b", - }, - // execTransaction(address,uint256,(disperseTokenSimple(address,address[],uint256[])),uint8,uint256,uint256,uint256,address,address,bytes) - { - "chainID": 1, - "hash": "0xb6349347a1dec402c59cd94c5715513af7ecf3e532376f2a5a47c99ee224de2a", - }, - // execTransaction(address,uint256,(multicall(bytes[])),uint8,uint256,uint256,uint256,address,address,bytes) - { - "chainID": 1, - "hash": "0x2af244a02066c0a8e3998d247e071a03cd38ecbec60f93ddf63da0dce3932f86", - }, - ], - // These are canonical ABI definitions that we use to test more unusual function types. - // The names are parsed and dummy data is filled in (see helpers in ./evm.ts). - // The accompanying tests also have some helpful print lines which are commented out - // by default but are useful for debugging. - // The purpose of these is to ensure all (most) ABI structures can be decoded. - // A few of these towards the end are not supported but they are such edge cases that - // it's not worth worrying about them for now. - "canonicalNames": [ - // --- - // Test a few vectors we alredy checked with Etherscan - // --- - "swapExactTokensForTokens(uint256,uint256,address[],address,uint256)", - "proveAndClaimWithResolver((bytes,bytes)[])", - "proveAndClaimWithResolver(bytes,(bytes,bytes)[])", - "proveAndClaimWithResolver(bytes,(bytes,bytes)[],bytes,address,address)", - "remove_liquidity_one_coin(uint256,int128,uint256)", - // --- - // Made up vectors that should all decode - // --- - "multidimArr(uint256[][],bool)", - "multidimArr(uint256[2][])", - "multidimArr(uint256[][2])", - "multidimArr(uint256[2],bool)", - "multidimArr(uint256[2][2],bool)", - "multidimArr(uint256[2][2][2],bool)", - "multidimArr(uint256[2][2],bool)", - "multidimArr(uint256[][][],bool)", - "multidimArr(bool,uint256[],bool)", - "multidimArr(bytes,uint256[][],bool)", - "singleTup((uint256,uint256))", - "singleTup((uint256[]))", - "singleTup((uint256[],bool))", - "singleTup((bool,bool,uint[],uint256))", - "singleTup((uint256[1],uint256[]))", - "singleTup((bytes,uint256))", - "singleTup((bytes,uint256)[])", - "singleTup((uint256,uint256)[])", - "singleTup((uint256,uint256)[2])", - "singleTup((uint256[2],uint256))", - "singleTup((uint256,uint256)[2])", - "singleTup((uint256,uint256)[],bytes)", - "singleTup((uint256,uint256)[][],bytes)", - "singleTup((uint256,uint256)[2][],bytes)", - "singleTup((uint256,uint256)[][2],bytes)", - "singleTup((uint256,uint256)[2],bytes)", - "singleTup(bytes[],(uint256,uint256)[2],bytes)", - "singleTup(bytes[2],(uint256,uint256)[2],bytes)", - "singleTup((uint256,uint256)[2],bytes)", - "singleTup((uint256,uint256)[2][2],bytes)", - "singleTup((uint256,uint256)[2][2][2],bytes)", - "singleTup((uint256),bool)", - "singleTup(bytes,(bytes),bool)", - "singleTup((uint256,bool),bool)", - "singleTup((uint256,bool),bytes)", - "singleTup(bytes,(uint256),bool)", - "singleTup(bytes,(bytes),bool)", - "singleTup(bool,(uint256,bytes),bytes)", - "singleTup((uint256,uint256)[],bool)", - "singleTup((bytes)[],bool)", - "singleTup((bytes)[1],bool)", - "multiTup((uint256,uint256)[],(bool))", - "multiTup((uint256,uint256)[2],(bool))", - "multiTup((uint256)[2],(bool)[])", - "multiTup((uint256)[2],(bool)[2])", - "multiTup((uint256)[],(bool))", - "multiTup((uint256)[],(bool)[2])", - "multiTup((uint256,uint256)[],(bool)[])", - "multiTup((uint256,uint256)[2],(bool)[])", - "multiTup((uint256)[2][2],(bool)[2][2])", - "multiTup((uint256)[2][],(bool)[2][])", - "multiTup((uint256)[2][],(bool)[][])", - "multiTup((uint256)[][],(bool)[2][])", - "multiTup((uint256)[][],(bool)[][])", - "multiTup((uint256)[][],(bool)[2][2])", - "multiTup((uint256)[2][2],(bool)[][])", - "nestedTup(uint256,(bytes)[1],bool)", - "nestedTup((uint256,(bool,address),bool))", - "nestedTup((uint256,(bool,bytes),bool))", - "nestedTup(bool,(uint256,(bool,bytes),bool))", - "nestedTup(bytes,(uint256,(bool,bytes),bool))", - "nestedTup(bytes,(uint256,(bool,bytes)[],bool))", - "nestedTup(bool,(uint256,(bool,bytes),bool)[])", - "nestedTup(bytes,(uint256,(bool,bytes)[][],bool)[])", - "nestedTup(bytes,(uint256,(bool)[2],bool))", - "nestedTup((uint256,(bytes[])[2],uint256))", - "nestedTup((uint256,(bytes[2])[2],uint256))", - "nestedTup((uint256,(bytes[2])[],uint256))", - "nestedTup((uint256,(bytes[])[],uint256))", - "nestedTup((uint256,(bytes)[2],bool))", - "nestedTup((bytes,(bytes)[2],bool))", - "nestedTup(bytes,(uint256,(bool)[2],bool))", - "nestedTup(bytes,(uint256,(bytes)[2],bool))", - // --- - // Extreme edge cases that do not currently decode - // --- - // "nestedTup(((bytes)[1],bool))", // does not decode - // "nestedTup(((bytes)[2],bool))", // does not decode - // "nestedTup(bytes,(uint256,(bool,bytes)[2],bool))", // does not decode - // "nestedTup(bytes,(uint256,(bool,bytes)[2],bool))", // does not decode - // "nestedTup(bytes,(uint256,(bool,bytes)[2],bool)[])", // does not decode - // "nestedTup(bytes,(uint256,(bool,bytes)[2],bool)[2])", // does not decode - // "nestedTup(bytes,(uint256,(bool,bytes)[][],bool)[2][])", // too large - ], - }, - }, - "ethDeposit": { - "mnemonic": "winner much erosion weird rubber onion diagram mandate assist fluid slush theory", - "depositData": [ - { - "depositPath": [12381, 3600, 0, 0, 0], - "eth1WithdrawalKey": "0x95222290dd7278aa3ddd389cc1e1d165cc4bafe5", - "blsWithdrawalRef": { - "pubkey": "b77d6918d5edb4073a4ea4408073b698f00df478ff2726cdb8190e3be1fe5496f22b089f6ae4cf7cafaccb74683be5e4", - "withdrawal_credentials": "001cdf4c156742da500ed24475b8afd9a8b5a06f3a7ce8521bd3a23d6982ad7a", - "amount": 32000000000, - "signature": "8313af52b8822e9d8e66718e37eb8f14ca4cf25b7ab727dd9858c55667dd2a7fdbec9ed16c50911fbfee50eaeda76e1a026ebbf141b050ea94b203ff6b45d175cc33be26d65795e8cff02373ae68a2c3c94c261a0a7b45a897c5f4cead8cbf6e", - "deposit_message_root": "011f25b8a533635297ca36c97320aeb2644d7784f16b6dc668dc5acf3f17751c", - "deposit_data_root": "aa95cc7b31056cbc524fb82bc5641f60a0f3592bf960bdc875d24b332cd730ca", - "fork_version": "00000000", - "network_name": "mainnet", - "deposit_cli_version": "2.3.0", - }, - "eth1WithdrawalRef": { - "pubkey": "b77d6918d5edb4073a4ea4408073b698f00df478ff2726cdb8190e3be1fe5496f22b089f6ae4cf7cafaccb74683be5e4", - "withdrawal_credentials": "01000000000000000000000095222290dd7278aa3ddd389cc1e1d165cc4bafe5", - "amount": 32000000000, - "signature": "88dc6055732e97a2cd4524b658ef62f67daf12f03b0334fd72de4feeb7c44c681fb39ec6a27aee8d3a458a63642ef75711966581ec9dac56572973511c993560c52855d261e47476f970ffd76868c56e9a4d0f37bf23f05268ff73ddb686efeb", - "deposit_message_root": "e2b6b2dbc2f1a9cdd4a111f396c009490c67dfa08582c7c4ea18379adde6148f", - "deposit_data_root": "603160dc42fe11f2e2c057c85179cc008dd64c9d11ae20b8ffc9a5ceecece847", - "fork_version": "00000000", - "network_name": "mainnet", - "deposit_cli_version": "2.3.0", - }, - }, - { - "depositPath": [12381, 3600, 1, 0, 0], - "eth1WithdrawalKey": "0x95222290dd7278aa3ddd389cc1e1d165cc4bafe5", - "blsWithdrawalRef": { - "pubkey": "a0aa536f68981cb705e506036e40b4373f67e10ff66f64835d63ffb3ca5e5eeea5396b2569315b060f0b90a2862a740c", - "withdrawal_credentials": "00783f5f1c25faa1282bbd1be67c0da48e5fbb9a21cf7bb95819b37a9a545429", - "amount": 32000000000, - "signature": "a54d6e949f359aaa9c5ec7ea078bff59a8a747ff8c1518963434d0090ee53dd4c87540d9cd7456a370b3db9d29491abe0413eda2384affa87c7b377b995ad617f5dc4c420593066a8b83345956b44ca0235344d1478e8500f9731dfae7128f4c", - "deposit_message_root": "3bd64fca376ba66a9b7428872f143e6e222e411af05401eba760722683a2cd47", - "deposit_data_root": "2dd6d49609584c73ec12194cb9a5f39841b6854e04e0df043bdb9eaa5b65bb86", - "fork_version": "00000000", - "network_name": "mainnet", - "deposit_cli_version": "2.3.0", - }, - "eth1WithdrawalRef": { - "pubkey": "a0aa536f68981cb705e506036e40b4373f67e10ff66f64835d63ffb3ca5e5eeea5396b2569315b060f0b90a2862a740c", - "withdrawal_credentials": "01000000000000000000000095222290dd7278aa3ddd389cc1e1d165cc4bafe5", - "amount": 32000000000, - "signature": "b1e98e44f1a8a550afeeaac839f93e74d07651c1fa1d12cf857787e20939d346f44a41997682b93a7d51267f77bd979406a7e42c4531abd23ee0d65e1ccc5432313714c1abd92fbebbbde798a9e6b875b4f59cb7a76d7d07094b66e102918248", - "deposit_message_root": "f4b94882da58e71f4557620c35b2c16c975b50093ae9a8041aaa67e840aba9aa", - "deposit_data_root": "1cc71b9a34ef5730154985c36ac2f9e4f9017959c144e9083d0e8f79311eed59", - "fork_version": "00000000", - "network_name": "mainnet", - "deposit_cli_version": "2.3.0", - }, - }, - ], - }, - // Dehydrated state for unit/integration tests - "dehydratedClientState": "{\"activeWallets\":{\"internal\":{\"uid\":\"162b56efe561c12bc93f703dc7026b3ec3d53923270c9259e2b08015fb9defd2\"},\"external\":{\"uid\":\"0000000000000000000000000000000000000000000000000000000000000000\"}},\"ephemeralPub\":\"04627c74680bee7907c07fdea2bde0ab1ac17c95213f379ccc1dce87f3586babe8ba0ed02688fd5539a54ea1b7b8ab0860d1853006f55f22a2e3ea4e190a17ab30\",\"fwVersion\":\"00110000\",\"deviceId\":\"Cd3dtg\",\"name\":\"SDK Test\",\"baseUrl\":\"https: //signing.gridpl.us\",\"privKey\":\"3fb53b677f73e4d2b8c89c303f6f6b349f0075ad88ea126cb9f6632085815dca\",\"retryCount\":3,\"timeout\":120000}", + "evm": { + // We use these vectors to test just-in-type calldata decoding for EVM transactions. + // The relevant protocol is Ethereum ABI serdes: https://docs.soliditylang.org/en/develop/abi-spec.html + "calldata": { + // Public transactions interacting with contracts. We use these + // to simulate a production environment of fetching ABI data and adding + // decoding info to the request. + "publicTxHashes": [ + { + "chainID": 137, + "hash": "0xd19a9bf70da20c10faf4d4355940cca8a5db91fa6cc1d258d6660d475d36616d", + // If you look at this example on Polygonscan, you'll notice the function being + // called is not actually available in the source code of the contract... + // So we needed to add a param to skip that test. + "skipBlockExplorerReq": true + }, + // swapExactTokensForTokens(uint256 amountIn, uint256 amountOutMin, address[] path, + // address to, uint256 deadline) + { + "chainID": 1, + "hash": "0xeee0752109c6d31038bab6c2b0a3e3857e8bffb9c229de71f0196fda6fb28a5e" + }, + // remove_liquidity_one_coin(uint256 _token_amount, int128 i, uint256 _min_amount) + { + "chainID": 1, + "hash": "0xa6173b4890303e12ca1b195ea4a04d8891b0d83768b734d2ecdb9c6dd9d828c4" + }, + // atomicMatch_(address[14],uint256[18],uint8[8],bytes,bytes,bytes,bytes,bytes,bytes, + // uint8[2],bytes32[5]) + // this one is too large for 1 frame + // "0x92c82aad37a925e3aabe3d603109a5e65993aa2615c4b2278c3d355d9d433dff", + // exactInput((bytes,address,uint256,uint256,uint256)) + { + "chainID": 1, + "hash": "0xee9710119c13dba6fe2de240ef1e24a2489e98d9c7dd5881e2901056764ee234" + }, + // exactInputSingle((address,address,uint24,address,uint256,uint256,uint256,uint160)) + { + "chainID": 1, + "hash": "0xc33308ca105630b99a0c24ddde4f4506aa4115ec6f1a25f1138f3bd8cfa32b49" + }, + // proveAndClaimWithResolver(bytes,(bytes,bytes)[],bytes,address,address) + { + "chainID": 1, + "hash": "0xe15e6205c6696d444fc426468defdf08e89a0f5b3b8a17c68428f7aeefd53ca1" + }, + // bulkTransfer(((uint8,address,uint256,uint256)[],address,bool)[],bytes32) + { + "chainID": 1, + "hash": "0x6f0ba3cb3c08e0ee443e4b7a78e1397413762e63287e348829c18f287c5a457f" + }, + // RECURSIVE DEFS + // multicall(bytes[]) + { + "chainID": 1, + "hash": "0xf4c48f0300acb2982fe8861ffd9291634115a33dc4107a66b4f9f43efb66896b" + }, + // execTransaction(address,uint256,(disperseTokenSimple(address,address[],uint256[])),uint8,uint256,uint256,uint256,address,address,bytes) + { + "chainID": 1, + "hash": "0xb6349347a1dec402c59cd94c5715513af7ecf3e532376f2a5a47c99ee224de2a" + }, + // execTransaction(address,uint256,(multicall(bytes[])),uint8,uint256,uint256,uint256,address,address,bytes) + { + "chainID": 1, + "hash": "0x2af244a02066c0a8e3998d247e071a03cd38ecbec60f93ddf63da0dce3932f86" + } + ], + // These are canonical ABI definitions that we use to test more unusual function types. + // The names are parsed and dummy data is filled in (see helpers in ./evm.ts). + // The accompanying tests also have some helpful print lines which are commented out + // by default but are useful for debugging. + // The purpose of these is to ensure all (most) ABI structures can be decoded. + // A few of these towards the end are not supported but they are such edge cases that + // it's not worth worrying about them for now. + "canonicalNames": [ + // --- + // Test a few vectors we alredy checked with Etherscan + // --- + "swapExactTokensForTokens(uint256,uint256,address[],address,uint256)", + "proveAndClaimWithResolver((bytes,bytes)[])", + "proveAndClaimWithResolver(bytes,(bytes,bytes)[])", + "proveAndClaimWithResolver(bytes,(bytes,bytes)[],bytes,address,address)", + "remove_liquidity_one_coin(uint256,int128,uint256)", + // --- + // Made up vectors that should all decode + // --- + "multidimArr(uint256[][],bool)", + "multidimArr(uint256[2][])", + "multidimArr(uint256[][2])", + "multidimArr(uint256[2],bool)", + "multidimArr(uint256[2][2],bool)", + "multidimArr(uint256[2][2][2],bool)", + "multidimArr(uint256[2][2],bool)", + "multidimArr(uint256[][][],bool)", + "multidimArr(bool,uint256[],bool)", + "multidimArr(bytes,uint256[][],bool)", + "singleTup((uint256,uint256))", + "singleTup((uint256[]))", + "singleTup((uint256[],bool))", + "singleTup((bool,bool,uint[],uint256))", + "singleTup((uint256[1],uint256[]))", + "singleTup((bytes,uint256))", + "singleTup((bytes,uint256)[])", + "singleTup((uint256,uint256)[])", + "singleTup((uint256,uint256)[2])", + "singleTup((uint256[2],uint256))", + "singleTup((uint256,uint256)[2])", + "singleTup((uint256,uint256)[],bytes)", + "singleTup((uint256,uint256)[][],bytes)", + "singleTup((uint256,uint256)[2][],bytes)", + "singleTup((uint256,uint256)[][2],bytes)", + "singleTup((uint256,uint256)[2],bytes)", + "singleTup(bytes[],(uint256,uint256)[2],bytes)", + "singleTup(bytes[2],(uint256,uint256)[2],bytes)", + "singleTup((uint256,uint256)[2],bytes)", + "singleTup((uint256,uint256)[2][2],bytes)", + "singleTup((uint256,uint256)[2][2][2],bytes)", + "singleTup((uint256),bool)", + "singleTup(bytes,(bytes),bool)", + "singleTup((uint256,bool),bool)", + "singleTup((uint256,bool),bytes)", + "singleTup(bytes,(uint256),bool)", + "singleTup(bytes,(bytes),bool)", + "singleTup(bool,(uint256,bytes),bytes)", + "singleTup((uint256,uint256)[],bool)", + "singleTup((bytes)[],bool)", + "singleTup((bytes)[1],bool)", + "multiTup((uint256,uint256)[],(bool))", + "multiTup((uint256,uint256)[2],(bool))", + "multiTup((uint256)[2],(bool)[])", + "multiTup((uint256)[2],(bool)[2])", + "multiTup((uint256)[],(bool))", + "multiTup((uint256)[],(bool)[2])", + "multiTup((uint256,uint256)[],(bool)[])", + "multiTup((uint256,uint256)[2],(bool)[])", + "multiTup((uint256)[2][2],(bool)[2][2])", + "multiTup((uint256)[2][],(bool)[2][])", + "multiTup((uint256)[2][],(bool)[][])", + "multiTup((uint256)[][],(bool)[2][])", + "multiTup((uint256)[][],(bool)[][])", + "multiTup((uint256)[][],(bool)[2][2])", + "multiTup((uint256)[2][2],(bool)[][])", + "nestedTup(uint256,(bytes)[1],bool)", + "nestedTup((uint256,(bool,address),bool))", + "nestedTup((uint256,(bool,bytes),bool))", + "nestedTup(bool,(uint256,(bool,bytes),bool))", + "nestedTup(bytes,(uint256,(bool,bytes),bool))", + "nestedTup(bytes,(uint256,(bool,bytes)[],bool))", + "nestedTup(bool,(uint256,(bool,bytes),bool)[])", + "nestedTup(bytes,(uint256,(bool,bytes)[][],bool)[])", + "nestedTup(bytes,(uint256,(bool)[2],bool))", + "nestedTup((uint256,(bytes[])[2],uint256))", + "nestedTup((uint256,(bytes[2])[2],uint256))", + "nestedTup((uint256,(bytes[2])[],uint256))", + "nestedTup((uint256,(bytes[])[],uint256))", + "nestedTup((uint256,(bytes)[2],bool))", + "nestedTup((bytes,(bytes)[2],bool))", + "nestedTup(bytes,(uint256,(bool)[2],bool))", + "nestedTup(bytes,(uint256,(bytes)[2],bool))" + // --- + // Extreme edge cases that do not currently decode + // --- + // "nestedTup(((bytes)[1],bool))", // does not decode + // "nestedTup(((bytes)[2],bool))", // does not decode + // "nestedTup(bytes,(uint256,(bool,bytes)[2],bool))", // does not decode + // "nestedTup(bytes,(uint256,(bool,bytes)[2],bool))", // does not decode + // "nestedTup(bytes,(uint256,(bool,bytes)[2],bool)[])", // does not decode + // "nestedTup(bytes,(uint256,(bool,bytes)[2],bool)[2])", // does not decode + // "nestedTup(bytes,(uint256,(bool,bytes)[][],bool)[2][])", // too large + ] + } + }, + "ethDeposit": { + "mnemonic": "winner much erosion weird rubber onion diagram mandate assist fluid slush theory", + "depositData": [ + { + "depositPath": [12381, 3600, 0, 0, 0], + "eth1WithdrawalKey": "0x95222290dd7278aa3ddd389cc1e1d165cc4bafe5", + "blsWithdrawalRef": { + "pubkey": "b77d6918d5edb4073a4ea4408073b698f00df478ff2726cdb8190e3be1fe5496f22b089f6ae4cf7cafaccb74683be5e4", + "withdrawal_credentials": "001cdf4c156742da500ed24475b8afd9a8b5a06f3a7ce8521bd3a23d6982ad7a", + "amount": 32000000000, + "signature": "8313af52b8822e9d8e66718e37eb8f14ca4cf25b7ab727dd9858c55667dd2a7fdbec9ed16c50911fbfee50eaeda76e1a026ebbf141b050ea94b203ff6b45d175cc33be26d65795e8cff02373ae68a2c3c94c261a0a7b45a897c5f4cead8cbf6e", + "deposit_message_root": "011f25b8a533635297ca36c97320aeb2644d7784f16b6dc668dc5acf3f17751c", + "deposit_data_root": "aa95cc7b31056cbc524fb82bc5641f60a0f3592bf960bdc875d24b332cd730ca", + "fork_version": "00000000", + "network_name": "mainnet", + "deposit_cli_version": "2.3.0" + }, + "eth1WithdrawalRef": { + "pubkey": "b77d6918d5edb4073a4ea4408073b698f00df478ff2726cdb8190e3be1fe5496f22b089f6ae4cf7cafaccb74683be5e4", + "withdrawal_credentials": "01000000000000000000000095222290dd7278aa3ddd389cc1e1d165cc4bafe5", + "amount": 32000000000, + "signature": "88dc6055732e97a2cd4524b658ef62f67daf12f03b0334fd72de4feeb7c44c681fb39ec6a27aee8d3a458a63642ef75711966581ec9dac56572973511c993560c52855d261e47476f970ffd76868c56e9a4d0f37bf23f05268ff73ddb686efeb", + "deposit_message_root": "e2b6b2dbc2f1a9cdd4a111f396c009490c67dfa08582c7c4ea18379adde6148f", + "deposit_data_root": "603160dc42fe11f2e2c057c85179cc008dd64c9d11ae20b8ffc9a5ceecece847", + "fork_version": "00000000", + "network_name": "mainnet", + "deposit_cli_version": "2.3.0" + } + }, + { + "depositPath": [12381, 3600, 1, 0, 0], + "eth1WithdrawalKey": "0x95222290dd7278aa3ddd389cc1e1d165cc4bafe5", + "blsWithdrawalRef": { + "pubkey": "a0aa536f68981cb705e506036e40b4373f67e10ff66f64835d63ffb3ca5e5eeea5396b2569315b060f0b90a2862a740c", + "withdrawal_credentials": "00783f5f1c25faa1282bbd1be67c0da48e5fbb9a21cf7bb95819b37a9a545429", + "amount": 32000000000, + "signature": "a54d6e949f359aaa9c5ec7ea078bff59a8a747ff8c1518963434d0090ee53dd4c87540d9cd7456a370b3db9d29491abe0413eda2384affa87c7b377b995ad617f5dc4c420593066a8b83345956b44ca0235344d1478e8500f9731dfae7128f4c", + "deposit_message_root": "3bd64fca376ba66a9b7428872f143e6e222e411af05401eba760722683a2cd47", + "deposit_data_root": "2dd6d49609584c73ec12194cb9a5f39841b6854e04e0df043bdb9eaa5b65bb86", + "fork_version": "00000000", + "network_name": "mainnet", + "deposit_cli_version": "2.3.0" + }, + "eth1WithdrawalRef": { + "pubkey": "a0aa536f68981cb705e506036e40b4373f67e10ff66f64835d63ffb3ca5e5eeea5396b2569315b060f0b90a2862a740c", + "withdrawal_credentials": "01000000000000000000000095222290dd7278aa3ddd389cc1e1d165cc4bafe5", + "amount": 32000000000, + "signature": "b1e98e44f1a8a550afeeaac839f93e74d07651c1fa1d12cf857787e20939d346f44a41997682b93a7d51267f77bd979406a7e42c4531abd23ee0d65e1ccc5432313714c1abd92fbebbbde798a9e6b875b4f59cb7a76d7d07094b66e102918248", + "deposit_message_root": "f4b94882da58e71f4557620c35b2c16c975b50093ae9a8041aaa67e840aba9aa", + "deposit_data_root": "1cc71b9a34ef5730154985c36ac2f9e4f9017959c144e9083d0e8f79311eed59", + "fork_version": "00000000", + "network_name": "mainnet", + "deposit_cli_version": "2.3.0" + } + } + ] + }, + // Dehydrated state for unit/integration tests + "dehydratedClientState": "{\"activeWallets\":{\"internal\":{\"uid\":\"162b56efe561c12bc93f703dc7026b3ec3d53923270c9259e2b08015fb9defd2\"},\"external\":{\"uid\":\"0000000000000000000000000000000000000000000000000000000000000000\"}},\"ephemeralPub\":\"04627c74680bee7907c07fdea2bde0ab1ac17c95213f379ccc1dce87f3586babe8ba0ed02688fd5539a54ea1b7b8ab0860d1853006f55f22a2e3ea4e190a17ab30\",\"fwVersion\":\"00110000\",\"deviceId\":\"Cd3dtg\",\"name\":\"SDK Test\",\"baseUrl\":\"https: //signing.gridpl.us\",\"privKey\":\"3fb53b677f73e4d2b8c89c303f6f6b349f0075ad88ea126cb9f6632085815dca\",\"retryCount\":3,\"timeout\":120000}" } diff --git a/packages/sdk/src/__test__/vectors/abi-vectors.ts b/packages/sdk/src/__test__/vectors/abi-vectors.ts index d4eb43c8..9561f83a 100644 --- a/packages/sdk/src/__test__/vectors/abi-vectors.ts +++ b/packages/sdk/src/__test__/vectors/abi-vectors.ts @@ -1,137 +1,137 @@ export const ABI_TEST_VECTORS = [ - { - name: 'DAI totalSupply() - Zero parameters', - tx: { - type: 'eip1559' as const, - to: '0x6B175474E89094C44Da98b954EedeAC495271d0F' as `0x${string}`, - value: BigInt(0), - data: '0x18160ddd' as `0x${string}`, - nonce: 0, - maxFeePerGas: BigInt('20000000000'), - maxPriorityFeePerGas: BigInt('2000000000'), - gas: BigInt('100000'), - chainId: 1, - }, - category: 'empty-params', - }, - { - name: 'DAI transfer() - Basic types (address,uint256)', - tx: { - type: 'eip1559' as const, - to: '0x6B175474E89094C44Da98b954EedeAC495271d0F' as `0x${string}`, - value: BigInt(0), - data: '0xa9059cbb000000000000000000000000742d35cc6b2d9e9a3b7b7b4a7b0b2b7b0b2b7b0b0000000000000000000000000000000000000000000000001bc16d674ec80000' as `0x${string}`, - nonce: 0, - maxFeePerGas: BigInt('20000000000'), - maxPriorityFeePerGas: BigInt('2000000000'), - gas: BigInt('100000'), - chainId: 1, - }, - category: 'basic-types', - }, - { - name: 'DAI approve() - Basic types (address,uint256)', - tx: { - type: 'eip1559' as const, - to: '0x6B175474E89094C44Da98b954EedeAC495271d0F' as `0x${string}`, - value: BigInt(0), - data: '0x095ea7b3000000000000000000000000742d35cc6b2d9e9a3b7b7b4a7b0b2b7b0b2b7b0bffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff' as `0x${string}`, - nonce: 0, - maxFeePerGas: BigInt('20000000000'), - maxPriorityFeePerGas: BigInt('2000000000'), - gas: BigInt('100000'), - chainId: 1, - }, - category: 'basic-types-2', - }, - { - name: 'Uniswap V2 swapExactTokensForTokens() - Arrays and multiple params', - tx: { - type: 'eip1559' as const, - to: '0x7a250d5630B4cF539739dF2C5dAcb4c659F2488D' as `0x${string}`, - value: BigInt(0), - data: '0x38ed17390000000000000000000000000000000000000000000000000de0b6b3a76400000000000000000000000000000000000000000000000000000c7d713b49da000000000000000000000000000000000000000000000000000000000000000000a0000000000000000000000000742d35cc6b2d9e9a3b7b7b4a7b0b2b7b0b2b7b0b000000000000000000000000000000000000000000000000000000006553f10000000000000000000000000000000000000000000000000000000000000000020000000000000000000000006b175474e89094c44da98b954eedeac495271d0f000000000000000000000000dac17f958d2ee523a2206206994597c13d831ec7' as `0x${string}`, - nonce: 0, - maxFeePerGas: BigInt('20000000000'), - maxPriorityFeePerGas: BigInt('2000000000'), - gas: BigInt('200000'), - chainId: 1, - }, - category: 'defi-swap', - }, - { - name: 'USDC transferFrom() - Three parameters (address,address,uint256)', - tx: { - type: 'eip1559' as const, - to: '0xA0b86991c6218b36c1d19D4a2e9Eb0cE3606eB48' as `0x${string}`, - value: BigInt(0), - data: '0x23b872dd000000000000000000000000742d35cc6b2d9e9a3b7b7b4a7b0b2b7b0b2b7b0b000000000000000000000000a1b2c3d4e5f6a1b2c3d4e5f6a1b2c3d4e5f6a1b20000000000000000000000000000000000000000000000000000000005f5e100' as `0x${string}`, - nonce: 0, - maxFeePerGas: BigInt('20000000000'), - maxPriorityFeePerGas: BigInt('2000000000'), - gas: BigInt('300000'), - chainId: 1, - }, - category: 'three-params', - }, - { - name: 'WETH deposit() - Payable function with value', - tx: { - type: 'eip1559' as const, - to: '0xC02aaA39b223FE8D0A0e5C4F27eAD9083C756Cc2' as `0x${string}`, - value: BigInt('1000000000000000000'), - data: '0xd0e30db0' as `0x${string}`, - nonce: 0, - maxFeePerGas: BigInt('20000000000'), - maxPriorityFeePerGas: BigInt('2000000000'), - gas: BigInt('400000'), - chainId: 1, - }, - category: 'payable-function', - }, - { - name: 'WETH withdraw() - Single uint256 parameter', - tx: { - type: 'eip1559' as const, - to: '0xC02aaA39b223FE8D0A0e5C4F27eAD9083C756Cc2' as `0x${string}`, - value: BigInt(0), - data: '0x2e1a7d4d0000000000000000000000000000000000000000000000000de0b6b3a7640000' as `0x${string}`, - nonce: 0, - maxFeePerGas: BigInt('20000000000'), - maxPriorityFeePerGas: BigInt('2000000000'), - gas: BigInt('300000'), - chainId: 1, - }, - category: 'single-param', - }, - { - name: 'Uniswap V3 exactInputSingle() - Complex struct parameter', - tx: { - type: 'eip1559' as const, - to: '0x68b3465833fb72A70ecDF485E0e4C7bD8665Fc45' as `0x${string}`, - value: BigInt(0), - data: '0x414bf3890000000000000000000000006b175474e89094c44da98b954eedeac495271d0f000000000000000000000000a0b86991c6218b36c1d19d4a2e9eb0ce3606eb480000000000000000000000000000000000000000000000000000000000000bb8000000000000000000000000742d35cc6b2d9e9a3b7b7b4a7b0b2b7b0b2b7b0b0000000000000000000000000000000000000000000000000de0b6b3a76400000000000000000000000000000000000000000000000000000000000000989680000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000' as `0x${string}`, - nonce: 0, - maxFeePerGas: BigInt('20000000000'), - maxPriorityFeePerGas: BigInt('2000000000'), - gas: BigInt('500000'), - chainId: 1, - }, - category: 'complex-struct', - }, - { - name: 'Curve remove_liquidity_one_coin() - Negative int and complex params', - tx: { - type: 'eip1559' as const, - to: '0xbEbc44782C7dB0a1A60Cb6fe97d0b483032FF1C7' as `0x${string}`, - value: BigInt(0), - data: '0x1a4d01d20000000000000000000000000000000000000000000000000de0b6b3a764000000000000000000000000000000000000000000000000000000000000000000010000000000000000000000000000000000000000000000000c7d713b49da0000' as `0x${string}`, - nonce: 0, - maxFeePerGas: BigInt('20000000000'), - maxPriorityFeePerGas: BigInt('2000000000'), - gas: BigInt('600000'), - chainId: 1, - }, - category: 'complex-params', - }, -]; + { + name: 'DAI totalSupply() - Zero parameters', + tx: { + type: 'eip1559' as const, + to: '0x6B175474E89094C44Da98b954EedeAC495271d0F' as `0x${string}`, + value: BigInt(0), + data: '0x18160ddd' as `0x${string}`, + nonce: 0, + maxFeePerGas: BigInt('20000000000'), + maxPriorityFeePerGas: BigInt('2000000000'), + gas: BigInt('100000'), + chainId: 1, + }, + category: 'empty-params', + }, + { + name: 'DAI transfer() - Basic types (address,uint256)', + tx: { + type: 'eip1559' as const, + to: '0x6B175474E89094C44Da98b954EedeAC495271d0F' as `0x${string}`, + value: BigInt(0), + data: '0xa9059cbb000000000000000000000000742d35cc6b2d9e9a3b7b7b4a7b0b2b7b0b2b7b0b0000000000000000000000000000000000000000000000001bc16d674ec80000' as `0x${string}`, + nonce: 0, + maxFeePerGas: BigInt('20000000000'), + maxPriorityFeePerGas: BigInt('2000000000'), + gas: BigInt('100000'), + chainId: 1, + }, + category: 'basic-types', + }, + { + name: 'DAI approve() - Basic types (address,uint256)', + tx: { + type: 'eip1559' as const, + to: '0x6B175474E89094C44Da98b954EedeAC495271d0F' as `0x${string}`, + value: BigInt(0), + data: '0x095ea7b3000000000000000000000000742d35cc6b2d9e9a3b7b7b4a7b0b2b7b0b2b7b0bffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff' as `0x${string}`, + nonce: 0, + maxFeePerGas: BigInt('20000000000'), + maxPriorityFeePerGas: BigInt('2000000000'), + gas: BigInt('100000'), + chainId: 1, + }, + category: 'basic-types-2', + }, + { + name: 'Uniswap V2 swapExactTokensForTokens() - Arrays and multiple params', + tx: { + type: 'eip1559' as const, + to: '0x7a250d5630B4cF539739dF2C5dAcb4c659F2488D' as `0x${string}`, + value: BigInt(0), + data: '0x38ed17390000000000000000000000000000000000000000000000000de0b6b3a76400000000000000000000000000000000000000000000000000000c7d713b49da000000000000000000000000000000000000000000000000000000000000000000a0000000000000000000000000742d35cc6b2d9e9a3b7b7b4a7b0b2b7b0b2b7b0b000000000000000000000000000000000000000000000000000000006553f10000000000000000000000000000000000000000000000000000000000000000020000000000000000000000006b175474e89094c44da98b954eedeac495271d0f000000000000000000000000dac17f958d2ee523a2206206994597c13d831ec7' as `0x${string}`, + nonce: 0, + maxFeePerGas: BigInt('20000000000'), + maxPriorityFeePerGas: BigInt('2000000000'), + gas: BigInt('200000'), + chainId: 1, + }, + category: 'defi-swap', + }, + { + name: 'USDC transferFrom() - Three parameters (address,address,uint256)', + tx: { + type: 'eip1559' as const, + to: '0xA0b86991c6218b36c1d19D4a2e9Eb0cE3606eB48' as `0x${string}`, + value: BigInt(0), + data: '0x23b872dd000000000000000000000000742d35cc6b2d9e9a3b7b7b4a7b0b2b7b0b2b7b0b000000000000000000000000a1b2c3d4e5f6a1b2c3d4e5f6a1b2c3d4e5f6a1b20000000000000000000000000000000000000000000000000000000005f5e100' as `0x${string}`, + nonce: 0, + maxFeePerGas: BigInt('20000000000'), + maxPriorityFeePerGas: BigInt('2000000000'), + gas: BigInt('300000'), + chainId: 1, + }, + category: 'three-params', + }, + { + name: 'WETH deposit() - Payable function with value', + tx: { + type: 'eip1559' as const, + to: '0xC02aaA39b223FE8D0A0e5C4F27eAD9083C756Cc2' as `0x${string}`, + value: BigInt('1000000000000000000'), + data: '0xd0e30db0' as `0x${string}`, + nonce: 0, + maxFeePerGas: BigInt('20000000000'), + maxPriorityFeePerGas: BigInt('2000000000'), + gas: BigInt('400000'), + chainId: 1, + }, + category: 'payable-function', + }, + { + name: 'WETH withdraw() - Single uint256 parameter', + tx: { + type: 'eip1559' as const, + to: '0xC02aaA39b223FE8D0A0e5C4F27eAD9083C756Cc2' as `0x${string}`, + value: BigInt(0), + data: '0x2e1a7d4d0000000000000000000000000000000000000000000000000de0b6b3a7640000' as `0x${string}`, + nonce: 0, + maxFeePerGas: BigInt('20000000000'), + maxPriorityFeePerGas: BigInt('2000000000'), + gas: BigInt('300000'), + chainId: 1, + }, + category: 'single-param', + }, + { + name: 'Uniswap V3 exactInputSingle() - Complex struct parameter', + tx: { + type: 'eip1559' as const, + to: '0x68b3465833fb72A70ecDF485E0e4C7bD8665Fc45' as `0x${string}`, + value: BigInt(0), + data: '0x414bf3890000000000000000000000006b175474e89094c44da98b954eedeac495271d0f000000000000000000000000a0b86991c6218b36c1d19d4a2e9eb0ce3606eb480000000000000000000000000000000000000000000000000000000000000bb8000000000000000000000000742d35cc6b2d9e9a3b7b7b4a7b0b2b7b0b2b7b0b0000000000000000000000000000000000000000000000000de0b6b3a76400000000000000000000000000000000000000000000000000000000000000989680000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000' as `0x${string}`, + nonce: 0, + maxFeePerGas: BigInt('20000000000'), + maxPriorityFeePerGas: BigInt('2000000000'), + gas: BigInt('500000'), + chainId: 1, + }, + category: 'complex-struct', + }, + { + name: 'Curve remove_liquidity_one_coin() - Negative int and complex params', + tx: { + type: 'eip1559' as const, + to: '0xbEbc44782C7dB0a1A60Cb6fe97d0b483032FF1C7' as `0x${string}`, + value: BigInt(0), + data: '0x1a4d01d20000000000000000000000000000000000000000000000000de0b6b3a764000000000000000000000000000000000000000000000000000000000000000000010000000000000000000000000000000000000000000000000c7d713b49da0000' as `0x${string}`, + nonce: 0, + maxFeePerGas: BigInt('20000000000'), + maxPriorityFeePerGas: BigInt('2000000000'), + gas: BigInt('600000'), + chainId: 1, + }, + category: 'complex-params', + }, +] diff --git a/packages/sdk/src/api/addressTags.ts b/packages/sdk/src/api/addressTags.ts index 4e4ebcf5..978e672a 100644 --- a/packages/sdk/src/api/addressTags.ts +++ b/packages/sdk/src/api/addressTags.ts @@ -1,58 +1,51 @@ -import type { Client } from '../client'; -import { MAX_ADDR } from '../constants'; -import type { AddressTag } from '../types'; -import { queue } from './utilities'; +import type { Client } from '../client' +import { MAX_ADDR } from '../constants' +import type { AddressTag } from '../types' +import { queue } from './utilities' /** * Sends request to the Lattice to add Address Tags. */ -export const addAddressTags = async ( - tags: [{ [key: string]: string }], -): Promise => { - // convert an array of objects to an object - const records = tags.reduce((acc, tag) => { - const key = Object.keys(tag)[0]; - acc[key] = tag[key]; - return acc; - }, {}); +export const addAddressTags = async (tags: [{ [key: string]: string }]): Promise => { + // convert an array of objects to an object + const records = tags.reduce((acc, tag) => { + const key = Object.keys(tag)[0] + acc[key] = tag[key] + return acc + }, {}) - return queue((client) => client.addKvRecords({ records })); -}; + return queue((client) => client.addKvRecords({ records })) +} /** * Fetches Address Tags from the Lattice. */ -export const fetchAddressTags = async ({ - n = MAX_ADDR, - start = 0, -}: { n?: number; start?: number } = {}) => { - const addressTags: AddressTag[] = []; - let remainingToFetch = n; - let fetched = start; +export const fetchAddressTags = async ({ n = MAX_ADDR, start = 0 }: { n?: number; start?: number } = {}) => { + const addressTags: AddressTag[] = [] + let remainingToFetch = n + let fetched = start - while (remainingToFetch > 0) { - await queue((client) => - client - .getKvRecords({ - start: fetched, - n: remainingToFetch > MAX_ADDR ? MAX_ADDR : remainingToFetch, - }) - .then(async (res) => { - addressTags.push(...res.records); - fetched = res.fetched + fetched; - remainingToFetch = res.total - fetched; - }), - ); - } - return addressTags; -}; + while (remainingToFetch > 0) { + await queue((client) => + client + .getKvRecords({ + start: fetched, + n: remainingToFetch > MAX_ADDR ? MAX_ADDR : remainingToFetch, + }) + .then(async (res) => { + addressTags.push(...res.records) + fetched = res.fetched + fetched + remainingToFetch = res.total - fetched + }), + ) + } + return addressTags +} /** * Removes Address Tags from the Lattice. */ -export const removeAddressTags = async ( - tags: AddressTag[], -): Promise => { - const ids = tags.map((tag) => `${tag.id}`); - return queue((client: Client) => client.removeKvRecords({ ids })); -}; +export const removeAddressTags = async (tags: AddressTag[]): Promise => { + const ids = tags.map((tag) => `${tag.id}`) + return queue((client: Client) => client.removeKvRecords({ ids })) +} diff --git a/packages/sdk/src/api/addresses.ts b/packages/sdk/src/api/addresses.ts index c2414af3..ea769d92 100644 --- a/packages/sdk/src/api/addresses.ts +++ b/packages/sdk/src/api/addresses.ts @@ -1,63 +1,56 @@ import { - BTC_LEGACY_CHANGE_DERIVATION, - BTC_LEGACY_DERIVATION, - BTC_LEGACY_XPUB_PATH, - BTC_SEGWIT_CHANGE_DERIVATION, - BTC_SEGWIT_DERIVATION, - BTC_SEGWIT_ZPUB_PATH, - BTC_WRAPPED_SEGWIT_CHANGE_DERIVATION, - BTC_WRAPPED_SEGWIT_DERIVATION, - BTC_WRAPPED_SEGWIT_YPUB_PATH, - DEFAULT_ETH_DERIVATION, - HARDENED_OFFSET, - LEDGER_LEGACY_DERIVATION, - LEDGER_LIVE_DERIVATION, - MAX_ADDR, - SOLANA_DERIVATION, -} from '../constants'; -import { LatticeGetAddressesFlag } from '../protocol/latticeConstants'; -import type { GetAddressesRequestParams, WalletPath } from '../types'; -import { - getFlagFromPath, - getStartPath, - parseDerivationPathComponents, - queue, -} from './utilities'; + BTC_LEGACY_CHANGE_DERIVATION, + BTC_LEGACY_DERIVATION, + BTC_LEGACY_XPUB_PATH, + BTC_SEGWIT_CHANGE_DERIVATION, + BTC_SEGWIT_DERIVATION, + BTC_SEGWIT_ZPUB_PATH, + BTC_WRAPPED_SEGWIT_CHANGE_DERIVATION, + BTC_WRAPPED_SEGWIT_DERIVATION, + BTC_WRAPPED_SEGWIT_YPUB_PATH, + DEFAULT_ETH_DERIVATION, + HARDENED_OFFSET, + LEDGER_LEGACY_DERIVATION, + LEDGER_LIVE_DERIVATION, + MAX_ADDR, + SOLANA_DERIVATION, +} from '../constants' +import { LatticeGetAddressesFlag } from '../protocol/latticeConstants' +import type { GetAddressesRequestParams, WalletPath } from '../types' +import { getFlagFromPath, getStartPath, parseDerivationPathComponents, queue } from './utilities' type FetchAddressesParams = { - n?: number; - startPathIndex?: number; - flag?: number; -}; - -export const fetchAddresses = async ( - overrides?: Partial, -) => { - let allAddresses: string[] = []; - let totalFetched = 0; - const totalToFetch = overrides?.n || MAX_ADDR; - - while (totalFetched < totalToFetch) { - const batchSize = Math.min(MAX_ADDR, totalToFetch - totalFetched); - const startPath = getStartPath(DEFAULT_ETH_DERIVATION, totalFetched); - await queue((client) => - client - .getAddresses({ - startPath, - ...overrides, - n: batchSize, - }) - .then((addresses: string[]) => { - if (addresses.length > 0) { - allAddresses = [...allAddresses, ...addresses]; - totalFetched += addresses.length; - } - }), - ); - } + n?: number + startPathIndex?: number + flag?: number +} - return allAddresses; -}; +export const fetchAddresses = async (overrides?: Partial) => { + let allAddresses: string[] = [] + let totalFetched = 0 + const totalToFetch = overrides?.n || MAX_ADDR + + while (totalFetched < totalToFetch) { + const batchSize = Math.min(MAX_ADDR, totalToFetch - totalFetched) + const startPath = getStartPath(DEFAULT_ETH_DERIVATION, totalFetched) + await queue((client) => + client + .getAddresses({ + startPath, + ...overrides, + n: batchSize, + }) + .then((addresses: string[]) => { + if (addresses.length > 0) { + allAddresses = [...allAddresses, ...addresses] + totalFetched += addresses.length + } + }), + ) + } + + return allAddresses +} /** * Fetches a single address from the device. @@ -65,179 +58,141 @@ export const fetchAddresses = async ( * @note By default, this function fetches m/44'/60'/0'/0/0 * @param path - either the index of ETH signing path or the derivation path to fetch */ -export const fetchAddress = async ( - path: number | WalletPath = 0, -): Promise => { - return fetchAddresses({ - startPath: - typeof path === 'number' - ? getStartPath(DEFAULT_ETH_DERIVATION, path) - : path, - n: 1, - }).then((addrs) => addrs[0]); -}; +export const fetchAddress = async (path: number | WalletPath = 0): Promise => { + return fetchAddresses({ + startPath: typeof path === 'number' ? getStartPath(DEFAULT_ETH_DERIVATION, path) : path, + n: 1, + }).then((addrs) => addrs[0]) +} function createFetchBtcAddressesFunction(derivationPath: number[]) { - return async ( - { n, startPathIndex }: FetchAddressesParams = { - n: MAX_ADDR, - startPathIndex: 0, - }, - ) => { - return fetchAddresses({ - startPath: getStartPath(derivationPath, startPathIndex), - n, - }); - }; + return async ( + { n, startPathIndex }: FetchAddressesParams = { + n: MAX_ADDR, + startPathIndex: 0, + }, + ) => { + return fetchAddresses({ + startPath: getStartPath(derivationPath, startPathIndex), + n, + }) + } } -export const fetchBtcLegacyAddresses = createFetchBtcAddressesFunction( - BTC_LEGACY_DERIVATION, -); -export const fetchBtcSegwitAddresses = createFetchBtcAddressesFunction( - BTC_SEGWIT_DERIVATION, -); -export const fetchBtcWrappedSegwitAddresses = createFetchBtcAddressesFunction( - BTC_WRAPPED_SEGWIT_DERIVATION, -); -export const fetchBtcLegacyChangeAddresses = createFetchBtcAddressesFunction( - BTC_LEGACY_CHANGE_DERIVATION, -); -export const fetchBtcSegwitChangeAddresses = createFetchBtcAddressesFunction( - BTC_SEGWIT_CHANGE_DERIVATION, -); -export const fetchBtcWrappedSegwitChangeAddresses = - createFetchBtcAddressesFunction(BTC_WRAPPED_SEGWIT_CHANGE_DERIVATION); +export const fetchBtcLegacyAddresses = createFetchBtcAddressesFunction(BTC_LEGACY_DERIVATION) +export const fetchBtcSegwitAddresses = createFetchBtcAddressesFunction(BTC_SEGWIT_DERIVATION) +export const fetchBtcWrappedSegwitAddresses = createFetchBtcAddressesFunction(BTC_WRAPPED_SEGWIT_DERIVATION) +export const fetchBtcLegacyChangeAddresses = createFetchBtcAddressesFunction(BTC_LEGACY_CHANGE_DERIVATION) +export const fetchBtcSegwitChangeAddresses = createFetchBtcAddressesFunction(BTC_SEGWIT_CHANGE_DERIVATION) +export const fetchBtcWrappedSegwitChangeAddresses = createFetchBtcAddressesFunction(BTC_WRAPPED_SEGWIT_CHANGE_DERIVATION) export const fetchSolanaAddresses = async ( - { n, startPathIndex }: FetchAddressesParams = { - n: MAX_ADDR, - startPathIndex: 0, - }, + { n, startPathIndex }: FetchAddressesParams = { + n: MAX_ADDR, + startPathIndex: 0, + }, ) => { - return fetchAddresses({ - startPath: getStartPath(SOLANA_DERIVATION, startPathIndex, 2), - n, - flag: 4, - }); -}; + return fetchAddresses({ + startPath: getStartPath(SOLANA_DERIVATION, startPathIndex, 2), + n, + flag: 4, + }) +} export const fetchLedgerLiveAddresses = async ( - { n, startPathIndex }: FetchAddressesParams = { - n: MAX_ADDR, - startPathIndex: 0, - }, + { n, startPathIndex }: FetchAddressesParams = { + n: MAX_ADDR, + startPathIndex: 0, + }, ) => { - const addresses = []; - for (let i = 0; i < n; i++) { - addresses.push( - queue((client) => - client - .getAddresses({ - startPath: getStartPath( - LEDGER_LIVE_DERIVATION, - startPathIndex + i, - 2, - ), - n: 1, - }) - .then((addresses) => addresses.map((address) => `${address}`)), - ), - ); - } - return Promise.all(addresses); -}; + const addresses = [] + for (let i = 0; i < n; i++) { + addresses.push( + queue((client) => + client + .getAddresses({ + startPath: getStartPath(LEDGER_LIVE_DERIVATION, startPathIndex + i, 2), + n: 1, + }) + .then((addresses) => addresses.map((address) => `${address}`)), + ), + ) + } + return Promise.all(addresses) +} export const fetchLedgerLegacyAddresses = async ( - { n, startPathIndex }: FetchAddressesParams = { - n: MAX_ADDR, - startPathIndex: 0, - }, + { n, startPathIndex }: FetchAddressesParams = { + n: MAX_ADDR, + startPathIndex: 0, + }, ) => { - const addresses = []; - for (let i = 0; i < n; i++) { - addresses.push( - queue((client) => - client - .getAddresses({ - startPath: getStartPath( - LEDGER_LEGACY_DERIVATION, - startPathIndex + i, - 3, - ), - n: 1, - }) - .then((addresses) => addresses.map((address) => `${address}`)), - ), - ); - } - return Promise.all(addresses); -}; - -export const fetchBip44ChangeAddresses = async ({ - n = MAX_ADDR, - startPathIndex = 0, -}: FetchAddressesParams = {}) => { - const addresses = []; - for (let i = 0; i < n; i++) { - addresses.push( - queue((client) => { - const startPath = [ - 44 + HARDENED_OFFSET, - 501 + HARDENED_OFFSET, - startPathIndex + i + HARDENED_OFFSET, - 0 + HARDENED_OFFSET, - ]; - return client - .getAddresses({ - startPath, - n: 1, - flag: 4, - }) - .then((addresses) => addresses.map((address) => `${address}`)); - }), - ); - } - return Promise.all(addresses); -}; - -export async function fetchAddressesByDerivationPath( - path: string, - { n = 1, startPathIndex = 0, flag }: FetchAddressesParams = {}, -): Promise { - const components = path.split('/').filter(Boolean); - const parsedPath = parseDerivationPathComponents(components); - const _flag = getFlagFromPath(parsedPath); - const wildcardIndex = components.findIndex((part) => - part.toLowerCase().includes('x'), - ); - - if (wildcardIndex === -1) { - return queue((client) => - client.getAddresses({ - startPath: parsedPath, - flag: flag || _flag, - n, - }), - ); - } - - const addresses: string[] = []; - for (let i = 0; i < n; i++) { - const currentPath = [...parsedPath]; - currentPath[wildcardIndex] = - currentPath[wildcardIndex] + startPathIndex + i; + const addresses = [] + for (let i = 0; i < n; i++) { + addresses.push( + queue((client) => + client + .getAddresses({ + startPath: getStartPath(LEDGER_LEGACY_DERIVATION, startPathIndex + i, 3), + n: 1, + }) + .then((addresses) => addresses.map((address) => `${address}`)), + ), + ) + } + return Promise.all(addresses) +} - const result = await queue((client) => - client.getAddresses({ - startPath: currentPath, - flag: flag || _flag, - n: 1, - }), - ); - addresses.push(...result); - } +export const fetchBip44ChangeAddresses = async ({ n = MAX_ADDR, startPathIndex = 0 }: FetchAddressesParams = {}) => { + const addresses = [] + for (let i = 0; i < n; i++) { + addresses.push( + queue((client) => { + const startPath = [44 + HARDENED_OFFSET, 501 + HARDENED_OFFSET, startPathIndex + i + HARDENED_OFFSET, 0 + HARDENED_OFFSET] + return client + .getAddresses({ + startPath, + n: 1, + flag: 4, + }) + .then((addresses) => addresses.map((address) => `${address}`)) + }), + ) + } + return Promise.all(addresses) +} - return addresses; +export async function fetchAddressesByDerivationPath(path: string, { n = 1, startPathIndex = 0, flag }: FetchAddressesParams = {}): Promise { + const components = path.split('/').filter(Boolean) + const parsedPath = parseDerivationPathComponents(components) + const _flag = getFlagFromPath(parsedPath) + const wildcardIndex = components.findIndex((part) => part.toLowerCase().includes('x')) + + if (wildcardIndex === -1) { + return queue((client) => + client.getAddresses({ + startPath: parsedPath, + flag: flag || _flag, + n, + }), + ) + } + + const addresses: string[] = [] + for (let i = 0; i < n; i++) { + const currentPath = [...parsedPath] + currentPath[wildcardIndex] = currentPath[wildcardIndex] + startPathIndex + i + + const result = await queue((client) => + client.getAddresses({ + startPath: currentPath, + flag: flag || _flag, + n: 1, + }), + ) + addresses.push(...result) + } + + return addresses } /** @@ -245,10 +200,10 @@ export async function fetchAddressesByDerivationPath( * @returns xpub string */ export async function fetchBtcXpub(): Promise { - const result = await fetchAddressesByDerivationPath(BTC_LEGACY_XPUB_PATH, { - flag: LatticeGetAddressesFlag.secp256k1Xpub, - }); - return result[0]; + const result = await fetchAddressesByDerivationPath(BTC_LEGACY_XPUB_PATH, { + flag: LatticeGetAddressesFlag.secp256k1Xpub, + }) + return result[0] } /** @@ -256,13 +211,10 @@ export async function fetchBtcXpub(): Promise { * @returns ypub string */ export async function fetchBtcYpub(): Promise { - const result = await fetchAddressesByDerivationPath( - BTC_WRAPPED_SEGWIT_YPUB_PATH, - { - flag: LatticeGetAddressesFlag.secp256k1Xpub, - }, - ); - return result[0]; + const result = await fetchAddressesByDerivationPath(BTC_WRAPPED_SEGWIT_YPUB_PATH, { + flag: LatticeGetAddressesFlag.secp256k1Xpub, + }) + return result[0] } /** @@ -270,8 +222,8 @@ export async function fetchBtcYpub(): Promise { * @returns zpub string */ export async function fetchBtcZpub(): Promise { - const result = await fetchAddressesByDerivationPath(BTC_SEGWIT_ZPUB_PATH, { - flag: LatticeGetAddressesFlag.secp256k1Xpub, - }); - return result[0]; + const result = await fetchAddressesByDerivationPath(BTC_SEGWIT_ZPUB_PATH, { + flag: LatticeGetAddressesFlag.secp256k1Xpub, + }) + return result[0] } diff --git a/packages/sdk/src/api/index.ts b/packages/sdk/src/api/index.ts index c65bd61d..9989d092 100644 --- a/packages/sdk/src/api/index.ts +++ b/packages/sdk/src/api/index.ts @@ -1,13 +1,13 @@ -export { getClient, parseDerivationPath } from './utilities'; +export { getClient, parseDerivationPath } from './utilities' -export * from './addresses'; -export * from './addressTags'; -export * from './signing'; -export * from './wallets'; -export * from './setup'; +export * from './addresses' +export * from './addressTags' +export * from './signing' +export * from './wallets' +export * from './setup' export { - BTC_LEGACY_XPUB_PATH, - BTC_WRAPPED_SEGWIT_YPUB_PATH, - BTC_SEGWIT_ZPUB_PATH, -} from '../constants'; + BTC_LEGACY_XPUB_PATH, + BTC_WRAPPED_SEGWIT_YPUB_PATH, + BTC_SEGWIT_ZPUB_PATH, +} from '../constants' diff --git a/packages/sdk/src/api/setup.ts b/packages/sdk/src/api/setup.ts index 6471528f..f07b5646 100644 --- a/packages/sdk/src/api/setup.ts +++ b/packages/sdk/src/api/setup.ts @@ -1,7 +1,7 @@ -import { Utils } from '..'; -import { Client } from '../client'; -import { loadClient, saveClient, setLoadClient, setSaveClient } from './state'; -import { buildLoadClientFn, buildSaveClientFn, queue } from './utilities'; +import { Utils } from '..' +import { Client } from '../client' +import { loadClient, saveClient, setLoadClient, setSaveClient } from './state' +import { buildLoadClientFn, buildSaveClientFn, queue } from './utilities' /** * @interface {Object} SetupParameters - parameters for the setup function @@ -13,19 +13,19 @@ import { buildLoadClientFn, buildSaveClientFn, queue } from './utilities'; * @prop {Function} SetupParameters.setStoredClient - a function that stores the client data */ type SetupParameters = - | { - deviceId: string; - password: string; - name: string; - appSecret?: string; - getStoredClient: () => Promise; - setStoredClient: (clientData: string | null) => Promise; - baseUrl?: string; - } - | { - getStoredClient: () => Promise; - setStoredClient: (clientData: string | null) => Promise; - }; + | { + deviceId: string + password: string + name: string + appSecret?: string + getStoredClient: () => Promise + setStoredClient: (clientData: string | null) => Promise + baseUrl?: string + } + | { + getStoredClient: () => Promise + setStoredClient: (clientData: string | null) => Promise + } /** * `setup` initializes the Client and executes `connect()` if necessary. It returns a promise that @@ -43,43 +43,41 @@ type SetupParameters = * */ export const setup = async (params: SetupParameters): Promise => { - if (!params.getStoredClient) throw new Error('Client data getter required'); - setLoadClient(buildLoadClientFn(params.getStoredClient)); + if (!params.getStoredClient) throw new Error('Client data getter required') + setLoadClient(buildLoadClientFn(params.getStoredClient)) - if (!params.setStoredClient) throw new Error('Client data setter required'); - setSaveClient(buildSaveClientFn(params.setStoredClient)); + if (!params.setStoredClient) throw new Error('Client data setter required') + setSaveClient(buildSaveClientFn(params.setStoredClient)) - if ('deviceId' in params && 'password' in params && 'name' in params) { - const privKey = - params.appSecret || - Utils.generateAppSecret(params.deviceId, params.password, params.name); - const client = new Client({ - deviceId: params.deviceId, - privKey, - name: params.name, - baseUrl: params.baseUrl, - }); - return client.connect(params.deviceId).then(async (isPaired) => { - await saveClient(client.getStateData()); - return isPaired; - }); - } else { - const client = await loadClient(); - if (!client) throw new Error('Client not initialized'); - const deviceId = client.getDeviceId(); - if (!client.ephemeralPub && deviceId) { - return connect(deviceId); - } else { - await saveClient(client.getStateData()); - return Promise.resolve(true); - } - } -}; + if ('deviceId' in params && 'password' in params && 'name' in params) { + const privKey = params.appSecret || Utils.generateAppSecret(params.deviceId, params.password, params.name) + const client = new Client({ + deviceId: params.deviceId, + privKey, + name: params.name, + baseUrl: params.baseUrl, + }) + return client.connect(params.deviceId).then(async (isPaired) => { + await saveClient(client.getStateData()) + return isPaired + }) + } else { + const client = await loadClient() + if (!client) throw new Error('Client not initialized') + const deviceId = client.getDeviceId() + if (!client.ephemeralPub && deviceId) { + return connect(deviceId) + } else { + await saveClient(client.getStateData()) + return Promise.resolve(true) + } + } +} export const connect = async (deviceId: string): Promise => { - return queue((client) => client.connect(deviceId)); -}; + return queue((client) => client.connect(deviceId)) +} export const pair = async (pairingCode: string): Promise => { - return queue((client) => client.pair(pairingCode)); -}; + return queue((client) => client.pair(pairingCode)) +} diff --git a/packages/sdk/src/api/signing.ts b/packages/sdk/src/api/signing.ts index fd98777a..0010046e 100644 --- a/packages/sdk/src/api/signing.ts +++ b/packages/sdk/src/api/signing.ts @@ -1,284 +1,204 @@ -import { RLP } from '@ethereumjs/rlp'; -import { Hash } from 'ox'; -import { - type Address, - type Authorization, - type Hex, - type TransactionSerializable, - type TransactionSerializableEIP7702, - serializeTransaction, -} from 'viem'; -import { Constants } from '..'; -import { - BTC_LEGACY_DERIVATION, - BTC_SEGWIT_DERIVATION, - BTC_WRAPPED_SEGWIT_DERIVATION, - CURRENCIES, - DEFAULT_ETH_DERIVATION, - SOLANA_DERIVATION, -} from '../constants'; -import { fetchDecoder } from '../functions/fetchDecoder'; -import type { - BitcoinSignPayload, - EIP712MessagePayload, - SignData, - SignRequestParams, - SigningPayload, - TransactionRequest, -} from '../types'; -import { getYParity } from '../util'; -import { isEIP712Payload, queue } from './utilities'; +import { RLP } from '@ethereumjs/rlp' +import { Hash } from 'ox' +import { type Address, type Authorization, type Hex, type TransactionSerializable, type TransactionSerializableEIP7702, serializeTransaction } from 'viem' +import { Constants } from '..' +import { BTC_LEGACY_DERIVATION, BTC_SEGWIT_DERIVATION, BTC_WRAPPED_SEGWIT_DERIVATION, CURRENCIES, DEFAULT_ETH_DERIVATION, SOLANA_DERIVATION } from '../constants' +import { fetchDecoder } from '../functions/fetchDecoder' +import type { BitcoinSignPayload, EIP712MessagePayload, SignData, SignRequestParams, SigningPayload, TransactionRequest } from '../types' +import { getYParity } from '../util' +import { isEIP712Payload, queue } from './utilities' // Define the authorization request type based on Viem's structure type AuthorizationRequest = { - chainId: number; - nonce: number; -} & ({ address: Address } | { contractAddress: Address }); + chainId: number + nonce: number +} & ({ address: Address } | { contractAddress: Address }) /** * Sign a transaction using Viem-compatible transaction types */ -type RawTransaction = Hex | Uint8Array | Buffer; - -export const sign = async ( - transaction: TransactionSerializable | RawTransaction, - overrides?: Omit, -): Promise => { - const isRaw = isRawTransaction(transaction); - const serializedTx = isRaw - ? normalizeRawTransaction(transaction) - : serializeTransaction(transaction as TransactionSerializable); - - // Determine the encoding type based on transaction type - let encodingType: - | typeof Constants.SIGNING.ENCODINGS.EVM - | typeof Constants.SIGNING.ENCODINGS.EIP7702_AUTH - | typeof Constants.SIGNING.ENCODINGS.EIP7702_AUTH_LIST = - Constants.SIGNING.ENCODINGS.EVM; - if (!isRaw && (transaction as TransactionSerializable).type === 'eip7702') { - const eip7702Tx = transaction as TransactionSerializableEIP7702; - const hasAuthList = - eip7702Tx.authorizationList && eip7702Tx.authorizationList.length > 0; - encodingType = hasAuthList - ? Constants.SIGNING.ENCODINGS.EIP7702_AUTH_LIST - : Constants.SIGNING.ENCODINGS.EIP7702_AUTH; - } - - // Only fetch decoder if we have the required fields - let decoder: Buffer | undefined; - if ( - !isRaw && - 'data' in (transaction as TransactionSerializable) && - 'to' in (transaction as TransactionSerializable) && - 'chainId' in (transaction as TransactionSerializable) - ) { - decoder = await fetchDecoder({ - data: (transaction as TransactionSerializable).data, - to: (transaction as TransactionSerializable).to, - chainId: (transaction as TransactionSerializable).chainId, - } as TransactionRequest); - } - - const payload: SigningPayload = { - signerPath: DEFAULT_ETH_DERIVATION, - curveType: Constants.SIGNING.CURVES.SECP256K1, - hashType: Constants.SIGNING.HASHES.KECCAK256, - encodingType, - payload: serializedTx, - decoder, - }; - - return queue((client) => client.sign({ data: payload, ...overrides })); -}; +type RawTransaction = Hex | Uint8Array | Buffer + +export const sign = async (transaction: TransactionSerializable | RawTransaction, overrides?: Omit): Promise => { + const isRaw = isRawTransaction(transaction) + const serializedTx = isRaw ? normalizeRawTransaction(transaction) : serializeTransaction(transaction as TransactionSerializable) + + // Determine the encoding type based on transaction type + let encodingType: typeof Constants.SIGNING.ENCODINGS.EVM | typeof Constants.SIGNING.ENCODINGS.EIP7702_AUTH | typeof Constants.SIGNING.ENCODINGS.EIP7702_AUTH_LIST = Constants.SIGNING.ENCODINGS.EVM + if (!isRaw && (transaction as TransactionSerializable).type === 'eip7702') { + const eip7702Tx = transaction as TransactionSerializableEIP7702 + const hasAuthList = eip7702Tx.authorizationList && eip7702Tx.authorizationList.length > 0 + encodingType = hasAuthList ? Constants.SIGNING.ENCODINGS.EIP7702_AUTH_LIST : Constants.SIGNING.ENCODINGS.EIP7702_AUTH + } + + // Only fetch decoder if we have the required fields + let decoder: Buffer | undefined + if (!isRaw && 'data' in (transaction as TransactionSerializable) && 'to' in (transaction as TransactionSerializable) && 'chainId' in (transaction as TransactionSerializable)) { + decoder = await fetchDecoder({ + data: (transaction as TransactionSerializable).data, + to: (transaction as TransactionSerializable).to, + chainId: (transaction as TransactionSerializable).chainId, + } as TransactionRequest) + } + + const payload: SigningPayload = { + signerPath: DEFAULT_ETH_DERIVATION, + curveType: Constants.SIGNING.CURVES.SECP256K1, + hashType: Constants.SIGNING.HASHES.KECCAK256, + encodingType, + payload: serializedTx, + decoder, + } + + return queue((client) => client.sign({ data: payload, ...overrides })) +} /** * Sign a message with support for EIP-712 typed data and const assertions */ -export function signMessage( - payload: - | string - | Uint8Array - | Buffer - | Buffer[] - | EIP712MessagePayload>, - overrides?: Omit, -): Promise { - const basePayload: SigningPayload> = { - signerPath: DEFAULT_ETH_DERIVATION, - curveType: Constants.SIGNING.CURVES.SECP256K1, - hashType: Constants.SIGNING.HASHES.KECCAK256, - protocol: isEIP712Payload(payload) ? 'eip712' : 'signPersonal', - payload: payload as SigningPayload>['payload'], - }; - - const tx: SignRequestParams = { - data: basePayload as SignRequestParams['data'], - currency: overrides?.currency ?? CURRENCIES.ETH_MSG, - ...(overrides ?? {}), - }; - - return queue((client) => client.sign(tx)); +export function signMessage(payload: string | Uint8Array | Buffer | Buffer[] | EIP712MessagePayload>, overrides?: Omit): Promise { + const basePayload: SigningPayload> = { + signerPath: DEFAULT_ETH_DERIVATION, + curveType: Constants.SIGNING.CURVES.SECP256K1, + hashType: Constants.SIGNING.HASHES.KECCAK256, + protocol: isEIP712Payload(payload) ? 'eip712' : 'signPersonal', + payload: payload as SigningPayload>['payload'], + } + + const tx: SignRequestParams = { + data: basePayload as SignRequestParams['data'], + currency: overrides?.currency ?? CURRENCIES.ETH_MSG, + ...(overrides ?? {}), + } + + return queue((client) => client.sign(tx)) } -function isRawTransaction( - value: TransactionSerializable | RawTransaction, -): value is RawTransaction { - return ( - typeof value === 'string' || - value instanceof Uint8Array || - Buffer.isBuffer(value) - ); +function isRawTransaction(value: TransactionSerializable | RawTransaction): value is RawTransaction { + return typeof value === 'string' || value instanceof Uint8Array || Buffer.isBuffer(value) } function normalizeRawTransaction(tx: RawTransaction): Hex | Buffer { - if (typeof tx === 'string') { - return tx.startsWith('0x') ? (tx as Hex) : (`0x${tx}` as Hex); - } - return Buffer.from(tx); + if (typeof tx === 'string') { + return tx.startsWith('0x') ? (tx as Hex) : (`0x${tx}` as Hex) + } + return Buffer.from(tx) } /** * Signs an EIP-7702 authorization to set code for an externally owned account (EOA). * Returns a Viem-compatible authorization object. */ -export const signAuthorization = async ( - authorization: AuthorizationRequest, - overrides?: Omit, -): Promise => { - // EIP-7702 authorization message is: MAGIC || rlp([chain_id, address, nonce]) - // MAGIC = 0x05 per EIP-7702 spec - const MAGIC = Buffer.from([0x05]); - - // Handle the address/contractAddress alias - const address = - 'address' in authorization - ? authorization.address - : authorization.contractAddress; - - const message = Buffer.concat([ - MAGIC, - Buffer.from( - RLP.encode([authorization.chainId, address, authorization.nonce]), - ), - ]); - - const payload: SigningPayload = { - signerPath: DEFAULT_ETH_DERIVATION, - curveType: Constants.SIGNING.CURVES.SECP256K1, - hashType: Constants.SIGNING.HASHES.KECCAK256, - encodingType: Constants.SIGNING.ENCODINGS.EIP7702_AUTH, - payload: message, - }; - - // Get the signature with all components - const response = await queue((client) => - client.sign({ data: payload, ...overrides }), - ); - - // Extract signature components if they exist - if (response.sig && response.pubkey) { - // Calculate the correct y-parity value - const messageHash = Buffer.from(Hash.keccak256(message)); - const yParity = getYParity(messageHash, response.sig, response.pubkey); - - // Handle both Buffer and string formats for r and s - const rValue = Buffer.isBuffer(response.sig.r) - ? `0x${response.sig.r.toString('hex')}` - : response.sig.r; - const sValue = Buffer.isBuffer(response.sig.s) - ? `0x${response.sig.s.toString('hex')}` - : response.sig.s; - - // Create a complete Authorization object with all required signature components - const result: Authorization = { - address, // Viem compatibility - chainId: authorization.chainId, - nonce: authorization.nonce, - yParity, - r: rValue as Hex, - s: sValue as Hex, - }; - - return result; - } - - throw new Error('Failed to get signature from device'); -}; +export const signAuthorization = async (authorization: AuthorizationRequest, overrides?: Omit): Promise => { + // EIP-7702 authorization message is: MAGIC || rlp([chain_id, address, nonce]) + // MAGIC = 0x05 per EIP-7702 spec + const MAGIC = Buffer.from([0x05]) + + // Handle the address/contractAddress alias + const address = 'address' in authorization ? authorization.address : authorization.contractAddress + + const message = Buffer.concat([MAGIC, Buffer.from(RLP.encode([authorization.chainId, address, authorization.nonce]))]) + + const payload: SigningPayload = { + signerPath: DEFAULT_ETH_DERIVATION, + curveType: Constants.SIGNING.CURVES.SECP256K1, + hashType: Constants.SIGNING.HASHES.KECCAK256, + encodingType: Constants.SIGNING.ENCODINGS.EIP7702_AUTH, + payload: message, + } + + // Get the signature with all components + const response = await queue((client) => client.sign({ data: payload, ...overrides })) + + // Extract signature components if they exist + if (response.sig && response.pubkey) { + // Calculate the correct y-parity value + const messageHash = Buffer.from(Hash.keccak256(message)) + const yParity = getYParity(messageHash, response.sig, response.pubkey) + + // Handle both Buffer and string formats for r and s + const rValue = Buffer.isBuffer(response.sig.r) ? `0x${response.sig.r.toString('hex')}` : response.sig.r + const sValue = Buffer.isBuffer(response.sig.s) ? `0x${response.sig.s.toString('hex')}` : response.sig.s + + // Create a complete Authorization object with all required signature components + const result: Authorization = { + address, // Viem compatibility + chainId: authorization.chainId, + nonce: authorization.nonce, + yParity, + r: rValue as Hex, + s: sValue as Hex, + } + + return result + } + + throw new Error('Failed to get signature from device') +} /** * Sign an EIP-7702 transaction using Viem-compatible types */ -export const signAuthorizationList = async ( - tx: TransactionSerializableEIP7702, -): Promise => { - const serializedTx = serializeTransaction(tx); +export const signAuthorizationList = async (tx: TransactionSerializableEIP7702): Promise => { + const serializedTx = serializeTransaction(tx) - const payload: SigningPayload = { - signerPath: DEFAULT_ETH_DERIVATION, - curveType: Constants.SIGNING.CURVES.SECP256K1, - hashType: Constants.SIGNING.HASHES.KECCAK256, - encodingType: Constants.SIGNING.ENCODINGS.EIP7702_AUTH_LIST, - payload: serializedTx, - }; + const payload: SigningPayload = { + signerPath: DEFAULT_ETH_DERIVATION, + curveType: Constants.SIGNING.CURVES.SECP256K1, + hashType: Constants.SIGNING.HASHES.KECCAK256, + encodingType: Constants.SIGNING.ENCODINGS.EIP7702_AUTH_LIST, + payload: serializedTx, + } - const signedPayload = await queue((client) => client.sign({ data: payload })); + const signedPayload = await queue((client) => client.sign({ data: payload })) - // Return the SignData structure from Lattice, not the converted signature - return signedPayload; -}; + // Return the SignData structure from Lattice, not the converted signature + return signedPayload +} -export const signBtcLegacyTx = async ( - payload: BitcoinSignPayload, -): Promise => { - const tx = { - data: { - signerPath: BTC_LEGACY_DERIVATION, - ...payload, - }, - currency: CURRENCIES.BTC, - }; - return queue((client) => client.sign(tx)); -}; +export const signBtcLegacyTx = async (payload: BitcoinSignPayload): Promise => { + const tx = { + data: { + signerPath: BTC_LEGACY_DERIVATION, + ...payload, + }, + currency: CURRENCIES.BTC, + } + return queue((client) => client.sign(tx)) +} -export const signBtcSegwitTx = async ( - payload: BitcoinSignPayload, -): Promise => { - const tx = { - data: { - signerPath: BTC_SEGWIT_DERIVATION, - ...payload, - }, - currency: CURRENCIES.BTC, - }; - return queue((client) => client.sign(tx)); -}; +export const signBtcSegwitTx = async (payload: BitcoinSignPayload): Promise => { + const tx = { + data: { + signerPath: BTC_SEGWIT_DERIVATION, + ...payload, + }, + currency: CURRENCIES.BTC, + } + return queue((client) => client.sign(tx)) +} -export const signBtcWrappedSegwitTx = async ( - payload: BitcoinSignPayload, -): Promise => { - const tx = { - data: { - signerPath: BTC_WRAPPED_SEGWIT_DERIVATION, - ...payload, - }, - currency: CURRENCIES.BTC, - }; - return queue((client) => client.sign(tx)); -}; +export const signBtcWrappedSegwitTx = async (payload: BitcoinSignPayload): Promise => { + const tx = { + data: { + signerPath: BTC_WRAPPED_SEGWIT_DERIVATION, + ...payload, + }, + currency: CURRENCIES.BTC, + } + return queue((client) => client.sign(tx)) +} -export const signSolanaTx = async ( - payload: Buffer, - overrides?: SignRequestParams, -): Promise => { - const tx = { - data: { - signerPath: SOLANA_DERIVATION, - curveType: Constants.SIGNING.CURVES.ED25519, - hashType: Constants.SIGNING.HASHES.NONE, - encodingType: Constants.SIGNING.ENCODINGS.SOLANA, - payload, - ...overrides, - }, - }; - return queue((client) => client.sign(tx)); -}; +export const signSolanaTx = async (payload: Buffer, overrides?: SignRequestParams): Promise => { + const tx = { + data: { + signerPath: SOLANA_DERIVATION, + curveType: Constants.SIGNING.CURVES.ED25519, + hashType: Constants.SIGNING.HASHES.NONE, + encodingType: Constants.SIGNING.ENCODINGS.SOLANA, + payload, + ...overrides, + }, + } + return queue((client) => client.sign(tx)) +} diff --git a/packages/sdk/src/api/state.ts b/packages/sdk/src/api/state.ts index 22aac567..52a7c68a 100644 --- a/packages/sdk/src/api/state.ts +++ b/packages/sdk/src/api/state.ts @@ -1,23 +1,21 @@ -import type { Client } from '../client'; +import type { Client } from '../client' -export let saveClient: (clientData: string | null) => Promise; +export let saveClient: (clientData: string | null) => Promise -export const setSaveClient = ( - fn: (clientData: string | null) => Promise, -) => { - saveClient = fn; -}; +export const setSaveClient = (fn: (clientData: string | null) => Promise) => { + saveClient = fn +} -export let loadClient: () => Promise; +export let loadClient: () => Promise export const setLoadClient = (fn: () => Promise) => { - loadClient = fn; -}; + loadClient = fn +} -let functionQueue: Promise; +let functionQueue: Promise -export const getFunctionQueue = () => functionQueue; +export const getFunctionQueue = () => functionQueue export const setFunctionQueue = (queue: Promise) => { - functionQueue = queue; -}; + functionQueue = queue +} diff --git a/packages/sdk/src/api/utilities.ts b/packages/sdk/src/api/utilities.ts index 20072a67..607c92d6 100644 --- a/packages/sdk/src/api/utilities.ts +++ b/packages/sdk/src/api/utilities.ts @@ -1,11 +1,6 @@ -import { Client } from '../client'; -import { EXTERNAL, HARDENED_OFFSET } from '../constants'; -import { - getFunctionQueue, - loadClient, - saveClient, - setFunctionQueue, -} from './state'; +import { Client } from '../client' +import { EXTERNAL, HARDENED_OFFSET } from '../constants' +import { getFunctionQueue, loadClient, saveClient, setFunctionQueue } from './state' /** * `queue` is a function that wraps all functional API calls. It limits the number of concurrent @@ -17,108 +12,100 @@ import { * @internal */ export const queue = async (fn: (client: Client) => Promise) => { - const client = await loadClient(); - if (!client) throw new Error('Client not initialized'); - if (!getFunctionQueue()) { - setFunctionQueue(Promise.resolve()); - } - setFunctionQueue( - getFunctionQueue().then( - async () => - await fn(client) - .catch((err) => { - // Empty the queue if any function call fails - setFunctionQueue(Promise.resolve()); - throw err; - }) - .then((returnValue) => { - saveClient(client.getStateData()); - return returnValue; - }), - ), - ); - return getFunctionQueue(); -}; + const client = await loadClient() + if (!client) throw new Error('Client not initialized') + if (!getFunctionQueue()) { + setFunctionQueue(Promise.resolve()) + } + setFunctionQueue( + getFunctionQueue().then( + async () => + await fn(client) + .catch((err) => { + // Empty the queue if any function call fails + setFunctionQueue(Promise.resolve()) + throw err + }) + .then((returnValue) => { + saveClient(client.getStateData()) + return returnValue + }), + ), + ) + return getFunctionQueue() +} export const getClient = async (): Promise => { - const client = loadClient ? await loadClient() : undefined; - if (!client) throw new Error('Client not initialized'); - return client; -}; + const client = loadClient ? await loadClient() : undefined + if (!client) throw new Error('Client not initialized') + return client +} const encodeClientData = (clientData: string) => { - return Buffer.from(clientData).toString('base64'); -}; + return Buffer.from(clientData).toString('base64') +} const decodeClientData = (clientData: string) => { - return Buffer.from(clientData, 'base64').toString(); -}; + return Buffer.from(clientData, 'base64').toString() +} -export const buildSaveClientFn = ( - setStoredClient: (clientData: string | null) => Promise, -) => { - return async (clientData: string | null) => { - if (!clientData) return; - const encodedData = encodeClientData(clientData); - await setStoredClient(encodedData); - }; -}; +export const buildSaveClientFn = (setStoredClient: (clientData: string | null) => Promise) => { + return async (clientData: string | null) => { + if (!clientData) return + const encodedData = encodeClientData(clientData) + await setStoredClient(encodedData) + } +} export const buildLoadClientFn = (getStoredClient: () => Promise) => { - return async () => { - const clientData = await getStoredClient(); - if (!clientData) return undefined; - const stateData = decodeClientData(clientData); - if (!stateData) return undefined; - const client = new Client({ stateData }); - if (!client) throw new Error('Client not initialized'); - return client; - }; -}; + return async () => { + const clientData = await getStoredClient() + if (!clientData) return undefined + const stateData = decodeClientData(clientData) + if (!stateData) return undefined + const client = new Client({ stateData }) + if (!client) throw new Error('Client not initialized') + return client + } +} export const getStartPath = ( - defaultStartPath: number[], - addressIndex = 0, // The value to increment `defaultStartPath` - pathIndex = 4, // Which index in `defaultStartPath` array to increment + defaultStartPath: number[], + addressIndex = 0, // The value to increment `defaultStartPath` + pathIndex = 4, // Which index in `defaultStartPath` array to increment ): number[] => { - const startPath = [...defaultStartPath]; - if (addressIndex > 0) { - startPath[pathIndex] = defaultStartPath[pathIndex] + addressIndex; - } - return startPath; -}; + const startPath = [...defaultStartPath] + if (addressIndex > 0) { + startPath[pathIndex] = defaultStartPath[pathIndex] + addressIndex + } + return startPath +} -export const isEIP712Payload = (payload: any) => - typeof payload !== 'string' && - 'types' in payload && - 'domain' in payload && - 'primaryType' in payload && - 'message' in payload; +export const isEIP712Payload = (payload: any) => typeof payload !== 'string' && 'types' in payload && 'domain' in payload && 'primaryType' in payload && 'message' in payload export function parseDerivationPath(path: string): number[] { - if (!path) return []; - const components = path.split('/').filter(Boolean); - return parseDerivationPathComponents(components); + if (!path) return [] + const components = path.split('/').filter(Boolean) + return parseDerivationPathComponents(components) } export function parseDerivationPathComponents(components: string[]): number[] { - return components.map((part) => { - const lowerPart = part.toLowerCase(); - if (lowerPart === 'x') return 0; // Wildcard - if (lowerPart === "x'") return HARDENED_OFFSET; // Hardened wildcard - if (part.endsWith("'")) - return Number.parseInt(part.slice(0, -1)) + HARDENED_OFFSET; - const val = Number.parseInt(part); - if (Number.isNaN(val)) { - throw new Error(`Invalid part in derivation path: ${part}`); - } - return val; - }); + return components.map((part) => { + const lowerPart = part.toLowerCase() + if (lowerPart === 'x') return 0 // Wildcard + if (lowerPart === "x'") return HARDENED_OFFSET // Hardened wildcard + if (part.endsWith("'")) return Number.parseInt(part.slice(0, -1)) + HARDENED_OFFSET + const val = Number.parseInt(part) + if (Number.isNaN(val)) { + throw new Error(`Invalid part in derivation path: ${part}`) + } + return val + }) } export function getFlagFromPath(path: number[]): number | undefined { - if (path.length >= 2 && path[1] === 501 + HARDENED_OFFSET) { - return EXTERNAL.GET_ADDR_FLAGS.ED25519_PUB; // SOLANA - } - return undefined; + if (path.length >= 2 && path[1] === 501 + HARDENED_OFFSET) { + return EXTERNAL.GET_ADDR_FLAGS.ED25519_PUB // SOLANA + } + return undefined } diff --git a/packages/sdk/src/api/wallets.ts b/packages/sdk/src/api/wallets.ts index a3c9b698..0f7197d8 100644 --- a/packages/sdk/src/api/wallets.ts +++ b/packages/sdk/src/api/wallets.ts @@ -1,9 +1,9 @@ -import type { ActiveWallets } from '../types'; -import { queue } from './utilities'; +import type { ActiveWallets } from '../types' +import { queue } from './utilities' /** * Fetches the active wallets */ export const fetchActiveWallets = async (): Promise => { - return queue((client) => client.fetchActiveWallet()); -}; + return queue((client) => client.fetchActiveWallet()) +} diff --git a/packages/sdk/src/bitcoin.ts b/packages/sdk/src/bitcoin.ts index 9942413d..c316d425 100644 --- a/packages/sdk/src/bitcoin.ts +++ b/packages/sdk/src/bitcoin.ts @@ -1,34 +1,34 @@ // Util for Bitcoin-specific functionality -import { bech32 } from 'bech32'; -import bs58check from 'bs58check'; -import { ripemd160 } from 'hash.js/lib/hash/ripemd.js'; -import { Hash } from 'ox'; -import { BIP_CONSTANTS } from './constants'; -import { LatticeSignSchema } from './protocol'; -const DEFAULT_SEQUENCE = 0xffffffff; -const DEFAULT_SIGHASH_BUFFER = Buffer.from('01', 'hex'); // SIGHASH_ALL = 0x01 -const { PURPOSES, COINS } = BIP_CONSTANTS; +import { bech32 } from 'bech32' +import bs58check from 'bs58check' +import { ripemd160 } from 'hash.js/lib/hash/ripemd.js' +import { Hash } from 'ox' +import { BIP_CONSTANTS } from './constants' +import { LatticeSignSchema } from './protocol' +const DEFAULT_SEQUENCE = 0xffffffff +const DEFAULT_SIGHASH_BUFFER = Buffer.from('01', 'hex') // SIGHASH_ALL = 0x01 +const { PURPOSES, COINS } = BIP_CONSTANTS const OP = { - ZERO: 0x00, - HASH160: 0xa9, - DUP: 0x76, - EQUAL: 0x87, - EQUALVERIFY: 0x88, - CHECKSIG: 0xac, -}; -const SEGWIT_V0 = 0x00; -const SEGWIT_NATIVE_V0_PREFIX = 'bc'; -const SEGWIT_NATIVE_V0_TESTNET_PREFIX = 'tb'; + ZERO: 0x00, + HASH160: 0xa9, + DUP: 0x76, + EQUAL: 0x87, + EQUALVERIFY: 0x88, + CHECKSIG: 0xac, +} +const SEGWIT_V0 = 0x00 +const SEGWIT_NATIVE_V0_PREFIX = 'bc' +const SEGWIT_NATIVE_V0_TESTNET_PREFIX = 'tb' -const FMT_SEGWIT_NATIVE_V0 = 0xd0; -const FMT_SEGWIT_NATIVE_V0_TESTNET = 0xf0; -const FMT_SEGWIT_WRAPPED = 0x05; -const FMT_SEGWIT_WRAPPED_TESTNET = 0xc4; -const FMT_LEGACY = 0x00; -const FMT_LEGACY_TESTNET = 0x6f; -const BTC_SCRIPT_TYPE_P2PKH = 0x01; -const BTC_SCRIPT_TYPE_P2SH_P2WPKH = 0x03; -const BTC_SCRIPT_TYPE_P2WPKH_V0 = 0x04; +const FMT_SEGWIT_NATIVE_V0 = 0xd0 +const FMT_SEGWIT_NATIVE_V0_TESTNET = 0xf0 +const FMT_SEGWIT_WRAPPED = 0x05 +const FMT_SEGWIT_WRAPPED_TESTNET = 0xc4 +const FMT_LEGACY = 0x00 +const FMT_LEGACY_TESTNET = 0x6f +const BTC_SCRIPT_TYPE_P2PKH = 0x01 +const BTC_SCRIPT_TYPE_P2SH_P2WPKH = 0x03 +const BTC_SCRIPT_TYPE_P2WPKH_V0 = 0x04 // We need to build two different objects here: // 1. bitcoinjs-lib TransactionBuilder object, which will be used in conjunction @@ -50,77 +50,75 @@ const BTC_SCRIPT_TYPE_P2WPKH_V0 = 0x04; // `version`: Transaction version of the inputs. All inputs must be of the same version! // `isSegwit`: a boolean which determines how we serialize the data and parameterize txb const buildBitcoinTxRequest = (data) => { - const { prevOuts, recipient, value, changePath, fee } = data; - if (!changePath) throw new Error('No changePath provided.'); - if (changePath.length !== 5) - throw new Error('Please provide a full change path.'); - // Serialize the request - const payload = Buffer.alloc(59 + 69 * prevOuts.length); - let off = 0; - // Change version byte (a.k.a. address format byte) - const changeFmt = getAddressFormat(changePath); - payload.writeUInt8(changeFmt, 0); - off++; + const { prevOuts, recipient, value, changePath, fee } = data + if (!changePath) throw new Error('No changePath provided.') + if (changePath.length !== 5) throw new Error('Please provide a full change path.') + // Serialize the request + const payload = Buffer.alloc(59 + 69 * prevOuts.length) + let off = 0 + // Change version byte (a.k.a. address format byte) + const changeFmt = getAddressFormat(changePath) + payload.writeUInt8(changeFmt, 0) + off++ - // Build the change data - payload.writeUInt32LE(changePath.length, off); - off += 4; - for (let i = 0; i < changePath.length; i++) { - payload.writeUInt32LE(changePath[i], off); - off += 4; - } + // Build the change data + payload.writeUInt32LE(changePath.length, off) + off += 4 + for (let i = 0; i < changePath.length; i++) { + payload.writeUInt32LE(changePath[i], off) + off += 4 + } - // Fee is a param - payload.writeUInt32LE(fee, off); - off += 4; - const dec = decodeAddress(recipient); - // Parameterize the recipient output - payload.writeUInt8(dec.versionByte, off); - off++; - dec.pkh.copy(payload, off); - off += dec.pkh.length; - writeUInt64LE(value, payload, off); - off += 8; + // Fee is a param + payload.writeUInt32LE(fee, off) + off += 4 + const dec = decodeAddress(recipient) + // Parameterize the recipient output + payload.writeUInt8(dec.versionByte, off) + off++ + dec.pkh.copy(payload, off) + off += dec.pkh.length + writeUInt64LE(value, payload, off) + off += 8 - // Build the inputs from the previous outputs - payload.writeUInt8(prevOuts.length, off); - off++; - let inputSum = 0; + // Build the inputs from the previous outputs + payload.writeUInt8(prevOuts.length, off) + off++ + let inputSum = 0 - prevOuts.forEach((input) => { - if (!input.signerPath || input.signerPath.length !== 5) { - throw new Error('Full recipient path not specified '); - } - payload.writeUInt32LE(input.signerPath.length, off); - off += 4; - for (let i = 0; i < input.signerPath.length; i++) { - payload.writeUInt32LE(input.signerPath[i], off); - off += 4; - } - payload.writeUInt32LE(input.index, off); - off += 4; - writeUInt64LE(input.value, payload, off); - off += 8; - inputSum += input.value; - const scriptType = getScriptType(input); - payload.writeUInt8(scriptType, off); - off++; - if (!Buffer.isBuffer(input.txHash)) - input.txHash = Buffer.from(input.txHash, 'hex'); - input.txHash.copy(payload, off); - off += input.txHash.length; - }); - // Send them back! - return { - payload, - schema: LatticeSignSchema.bitcoin, - origData: data, // We will need the original data for serializing the tx - changeData: { - // This data helps fill in the change output - value: inputSum - (value + fee), - }, - }; -}; + prevOuts.forEach((input) => { + if (!input.signerPath || input.signerPath.length !== 5) { + throw new Error('Full recipient path not specified ') + } + payload.writeUInt32LE(input.signerPath.length, off) + off += 4 + for (let i = 0; i < input.signerPath.length; i++) { + payload.writeUInt32LE(input.signerPath[i], off) + off += 4 + } + payload.writeUInt32LE(input.index, off) + off += 4 + writeUInt64LE(input.value, payload, off) + off += 8 + inputSum += input.value + const scriptType = getScriptType(input) + payload.writeUInt8(scriptType, off) + off++ + if (!Buffer.isBuffer(input.txHash)) input.txHash = Buffer.from(input.txHash, 'hex') + input.txHash.copy(payload, off) + off += input.txHash.length + }) + // Send them back! + return { + payload, + schema: LatticeSignSchema.bitcoin, + origData: data, // We will need the original data for serializing the tx + changeData: { + // This data helps fill in the change output + value: inputSum - (value + fee), + }, + } +} // Serialize a transaction consisting of inputs, outputs, and some // metadata @@ -130,337 +128,319 @@ const buildBitcoinTxRequest = (data) => { // (NOTE: either ALL are being spent, or none are) // -- lockTime = Will probably always be 0 const serializeTx = (data) => { - const { inputs, outputs, lockTime = 0 } = data; - let payload = Buffer.alloc(4); - let off = 0; - // Always use version 2 - const version = 2; - const useWitness = needsWitness(inputs); - payload.writeUInt32LE(version, off); - off += 4; - if (useWitness) { - payload = concat(payload, Buffer.from('00', 'hex')); // marker = 0x00 - payload = concat(payload, Buffer.from('01', 'hex')); // flag = 0x01 - } - // Serialize signed inputs - const numInputs = getVarInt(inputs.length); - payload = concat(payload, numInputs); - off += numInputs.length; - inputs.forEach((input) => { - payload = concat(payload, input.hash.reverse()); - off += input.hash.length; - const index = getU32LE(input.index); - payload = concat(payload, index); - off += index.length; - const scriptType = getScriptType(input); - // Build the sigScript. Note that p2wpkh does not have a scriptSig. - if (scriptType === BTC_SCRIPT_TYPE_P2SH_P2WPKH) { - // Build a vector (varSlice of varSlice) containing the redeemScript - const redeemScript = buildRedeemScript(input.pubkey); - const redeemScriptLen = getVarInt(redeemScript.length); - const slice = Buffer.concat([redeemScriptLen, redeemScript]); - const sliceLen = getVarInt(slice.length); - payload = concat(payload, sliceLen); - off += sliceLen.length; - payload = concat(payload, slice); - off += slice.length; - } else if (scriptType === BTC_SCRIPT_TYPE_P2PKH) { - // Build the signature + pubkey script to spend this input - const slice = buildSig(input.sig, input.pubkey); - payload = concat(payload, slice); - off += slice.length; - } else if (scriptType === BTC_SCRIPT_TYPE_P2WPKH_V0) { - const emptyScript = Buffer.from('00', 'hex'); - payload = concat(payload, emptyScript); - off += 1; - } - // Use the default sequence for all transactions - const sequence = getU32LE(DEFAULT_SEQUENCE); - payload = concat(payload, sequence); - off += sequence.length; - }); - // Serialize outputs - const numOutputs = getVarInt(outputs.length); - payload = concat(payload, numOutputs); - off += numOutputs.length; - outputs.forEach((output) => { - const value = getU64LE(output.value); - payload = concat(payload, value); - off += value.length; - // Build the output locking script and write it as a var slice - const script = buildLockingScript(output.recipient); - const scriptLen = getVarInt(script.length); - payload = concat(payload, scriptLen); - off += scriptLen.length; - payload = concat(payload, script); - off += script.length; - }); - // Add witness data if needed - if (useWitness) { - const sigs = []; - const pubkeys = []; - for (let i = 0; i < inputs.length; i++) { - sigs.push(inputs[i].sig); - pubkeys.push(inputs[i].pubkey); - } - const witnessSlice = buildWitness(sigs, pubkeys); - payload = concat(payload, witnessSlice); - off += witnessSlice.length; - } - // Finish with locktime - return Buffer.concat([payload, getU32LE(lockTime)]).toString('hex'); -}; + const { inputs, outputs, lockTime = 0 } = data + let payload = Buffer.alloc(4) + let off = 0 + // Always use version 2 + const version = 2 + const useWitness = needsWitness(inputs) + payload.writeUInt32LE(version, off) + off += 4 + if (useWitness) { + payload = concat(payload, Buffer.from('00', 'hex')) // marker = 0x00 + payload = concat(payload, Buffer.from('01', 'hex')) // flag = 0x01 + } + // Serialize signed inputs + const numInputs = getVarInt(inputs.length) + payload = concat(payload, numInputs) + off += numInputs.length + inputs.forEach((input) => { + payload = concat(payload, input.hash.reverse()) + off += input.hash.length + const index = getU32LE(input.index) + payload = concat(payload, index) + off += index.length + const scriptType = getScriptType(input) + // Build the sigScript. Note that p2wpkh does not have a scriptSig. + if (scriptType === BTC_SCRIPT_TYPE_P2SH_P2WPKH) { + // Build a vector (varSlice of varSlice) containing the redeemScript + const redeemScript = buildRedeemScript(input.pubkey) + const redeemScriptLen = getVarInt(redeemScript.length) + const slice = Buffer.concat([redeemScriptLen, redeemScript]) + const sliceLen = getVarInt(slice.length) + payload = concat(payload, sliceLen) + off += sliceLen.length + payload = concat(payload, slice) + off += slice.length + } else if (scriptType === BTC_SCRIPT_TYPE_P2PKH) { + // Build the signature + pubkey script to spend this input + const slice = buildSig(input.sig, input.pubkey) + payload = concat(payload, slice) + off += slice.length + } else if (scriptType === BTC_SCRIPT_TYPE_P2WPKH_V0) { + const emptyScript = Buffer.from('00', 'hex') + payload = concat(payload, emptyScript) + off += 1 + } + // Use the default sequence for all transactions + const sequence = getU32LE(DEFAULT_SEQUENCE) + payload = concat(payload, sequence) + off += sequence.length + }) + // Serialize outputs + const numOutputs = getVarInt(outputs.length) + payload = concat(payload, numOutputs) + off += numOutputs.length + outputs.forEach((output) => { + const value = getU64LE(output.value) + payload = concat(payload, value) + off += value.length + // Build the output locking script and write it as a var slice + const script = buildLockingScript(output.recipient) + const scriptLen = getVarInt(script.length) + payload = concat(payload, scriptLen) + off += scriptLen.length + payload = concat(payload, script) + off += script.length + }) + // Add witness data if needed + if (useWitness) { + const sigs = [] + const pubkeys = [] + for (let i = 0; i < inputs.length; i++) { + sigs.push(inputs[i].sig) + pubkeys.push(inputs[i].pubkey) + } + const witnessSlice = buildWitness(sigs, pubkeys) + payload = concat(payload, witnessSlice) + off += witnessSlice.length + } + // Finish with locktime + return Buffer.concat([payload, getU32LE(lockTime)]).toString('hex') +} // Convert a pubkeyhash to a bitcoin base58check address with a version byte const getBitcoinAddress = (pubkeyhash, version) => { - let bech32Prefix = null; - let bech32Version = null; - if (version === FMT_SEGWIT_NATIVE_V0) { - bech32Prefix = SEGWIT_NATIVE_V0_PREFIX; - bech32Version = SEGWIT_V0; - } else if (version === FMT_SEGWIT_NATIVE_V0_TESTNET) { - bech32Prefix = SEGWIT_NATIVE_V0_TESTNET_PREFIX; - bech32Version = SEGWIT_V0; - } - if (bech32Prefix !== null && bech32Version !== null) { - const words = bech32.toWords(pubkeyhash); - words.unshift(bech32Version); - return bech32.encode(bech32Prefix, words); - } else { - return bs58check.encode( - Buffer.concat([Buffer.from([version]), pubkeyhash]), - ); - } -}; + let bech32Prefix = null + let bech32Version = null + if (version === FMT_SEGWIT_NATIVE_V0) { + bech32Prefix = SEGWIT_NATIVE_V0_PREFIX + bech32Version = SEGWIT_V0 + } else if (version === FMT_SEGWIT_NATIVE_V0_TESTNET) { + bech32Prefix = SEGWIT_NATIVE_V0_TESTNET_PREFIX + bech32Version = SEGWIT_V0 + } + if (bech32Prefix !== null && bech32Version !== null) { + const words = bech32.toWords(pubkeyhash) + words.unshift(bech32Version) + return bech32.encode(bech32Prefix, words) + } else { + return bs58check.encode(Buffer.concat([Buffer.from([version]), pubkeyhash])) + } +} // Builder utils //----------------------- function buildRedeemScript(pubkey) { - const redeemScript = Buffer.alloc(22); - const shaHash = Buffer.from(Hash.sha256(pubkey)); - const pubkeyhash = Buffer.from( - ripemd160().update(shaHash).digest('hex'), - 'hex', - ); - redeemScript.writeUInt8(OP.ZERO, 0); - redeemScript.writeUInt8(pubkeyhash.length, 1); - pubkeyhash.copy(redeemScript, 2); - return redeemScript; + const redeemScript = Buffer.alloc(22) + const shaHash = Buffer.from(Hash.sha256(pubkey)) + const pubkeyhash = Buffer.from(ripemd160().update(shaHash).digest('hex'), 'hex') + redeemScript.writeUInt8(OP.ZERO, 0) + redeemScript.writeUInt8(pubkeyhash.length, 1) + pubkeyhash.copy(redeemScript, 2) + return redeemScript } // Var slice of signature + var slice of pubkey function buildSig(sig, pubkey) { - sig = Buffer.concat([sig, DEFAULT_SIGHASH_BUFFER]); - const sigLen = getVarInt(sig.length); - const pubkeyLen = getVarInt(pubkey.length); - const slice = Buffer.concat([sigLen, sig, pubkeyLen, pubkey]); - const len = getVarInt(slice.length); - return Buffer.concat([len, slice]); + sig = Buffer.concat([sig, DEFAULT_SIGHASH_BUFFER]) + const sigLen = getVarInt(sig.length) + const pubkeyLen = getVarInt(pubkey.length) + const slice = Buffer.concat([sigLen, sig, pubkeyLen, pubkey]) + const len = getVarInt(slice.length) + return Buffer.concat([len, slice]) } // Witness is written as a "vector", which is a list of varSlices // prefixed by the number of items function buildWitness(sigs, pubkeys) { - let witness = Buffer.alloc(0); - // Two items in each vector (sig, pubkey) - const len = Buffer.alloc(1); - len.writeUInt8(2, 0); - for (let i = 0; i < sigs.length; i++) { - const sig = Buffer.concat([sigs[i], DEFAULT_SIGHASH_BUFFER]); - const sigLen = getVarInt(sig.length); - const pubkey = pubkeys[i]; - const pubkeyLen = getVarInt(pubkey.length); - witness = Buffer.concat([witness, len, sigLen, sig, pubkeyLen, pubkey]); - } - return witness; + let witness = Buffer.alloc(0) + // Two items in each vector (sig, pubkey) + const len = Buffer.alloc(1) + len.writeUInt8(2, 0) + for (let i = 0; i < sigs.length; i++) { + const sig = Buffer.concat([sigs[i], DEFAULT_SIGHASH_BUFFER]) + const sigLen = getVarInt(sig.length) + const pubkey = pubkeys[i] + const pubkeyLen = getVarInt(pubkey.length) + witness = Buffer.concat([witness, len, sigLen, sig, pubkeyLen, pubkey]) + } + return witness } // Locking script buiders //----------------------- function buildLockingScript(address) { - const dec = decodeAddress(address); - switch (dec.versionByte) { - case FMT_SEGWIT_NATIVE_V0: - case FMT_SEGWIT_NATIVE_V0_TESTNET: - return buildP2wpkhLockingScript(dec.pkh); - case FMT_SEGWIT_WRAPPED: - case FMT_SEGWIT_WRAPPED_TESTNET: - return buildP2shLockingScript(dec.pkh); - case FMT_LEGACY: - case FMT_LEGACY_TESTNET: - return buildP2pkhLockingScript(dec.pkh); - default: - throw new Error( - `Unknown version byte: ${dec.versionByte}. Cannot build BTC transaction.`, - ); - } + const dec = decodeAddress(address) + switch (dec.versionByte) { + case FMT_SEGWIT_NATIVE_V0: + case FMT_SEGWIT_NATIVE_V0_TESTNET: + return buildP2wpkhLockingScript(dec.pkh) + case FMT_SEGWIT_WRAPPED: + case FMT_SEGWIT_WRAPPED_TESTNET: + return buildP2shLockingScript(dec.pkh) + case FMT_LEGACY: + case FMT_LEGACY_TESTNET: + return buildP2pkhLockingScript(dec.pkh) + default: + throw new Error(`Unknown version byte: ${dec.versionByte}. Cannot build BTC transaction.`) + } } function buildP2pkhLockingScript(pubkeyhash) { - const out = Buffer.alloc(5 + pubkeyhash.length); - let off = 0; - out.writeUInt8(OP.DUP, off); - off++; - out.writeUInt8(OP.HASH160, off); - off++; - out.writeUInt8(pubkeyhash.length, off); - off++; - pubkeyhash.copy(out, off); - off += pubkeyhash.length; - out.writeUInt8(OP.EQUALVERIFY, off); - off++; - out.writeUInt8(OP.CHECKSIG, off); - off++; - return out; + const out = Buffer.alloc(5 + pubkeyhash.length) + let off = 0 + out.writeUInt8(OP.DUP, off) + off++ + out.writeUInt8(OP.HASH160, off) + off++ + out.writeUInt8(pubkeyhash.length, off) + off++ + pubkeyhash.copy(out, off) + off += pubkeyhash.length + out.writeUInt8(OP.EQUALVERIFY, off) + off++ + out.writeUInt8(OP.CHECKSIG, off) + off++ + return out } function buildP2shLockingScript(pubkeyhash) { - const out = Buffer.alloc(3 + pubkeyhash.length); - let off = 0; - out.writeUInt8(OP.HASH160, off); - off++; - out.writeUInt8(pubkeyhash.length, off); - off++; - pubkeyhash.copy(out, off); - off += pubkeyhash.length; - out.writeUInt8(OP.EQUAL, off); - off++; - return out; + const out = Buffer.alloc(3 + pubkeyhash.length) + let off = 0 + out.writeUInt8(OP.HASH160, off) + off++ + out.writeUInt8(pubkeyhash.length, off) + off++ + pubkeyhash.copy(out, off) + off += pubkeyhash.length + out.writeUInt8(OP.EQUAL, off) + off++ + return out } function buildP2wpkhLockingScript(pubkeyhash) { - const out = Buffer.alloc(2 + pubkeyhash.length); - out.writeUInt8(OP.ZERO, 0); - out.writeUInt8(pubkeyhash.length, 1); - pubkeyhash.copy(out, 2); - return out; + const out = Buffer.alloc(2 + pubkeyhash.length) + out.writeUInt8(OP.ZERO, 0) + out.writeUInt8(pubkeyhash.length, 1) + pubkeyhash.copy(out, 2) + return out } // Static Utils //---------------------- function concat(base, addition) { - return Buffer.concat([base, addition]); + return Buffer.concat([base, addition]) } function getU64LE(x) { - const buffer = Buffer.alloc(8); - writeUInt64LE(x, buffer, 0); - return buffer; + const buffer = Buffer.alloc(8) + writeUInt64LE(x, buffer, 0) + return buffer } function getU32LE(x) { - const buffer = Buffer.alloc(4); - buffer.writeUInt32LE(x, 0); - return buffer; + const buffer = Buffer.alloc(4) + buffer.writeUInt32LE(x, 0) + return buffer } function getVarInt(x) { - let buffer: Buffer; - if (x < 0xfd) { - buffer = Buffer.alloc(1); - buffer.writeUInt8(x, 0); - } else if (x <= 0xffff) { - buffer = Buffer.alloc(3); - buffer.writeUInt8(0xfd, 0); - buffer.writeUInt16LE(x, 1); - } else if (x < 0xffffffff) { - buffer = Buffer.alloc(5); - buffer.writeUInt8(0xfe, 0); - buffer.writeUInt32LE(x, 1); - } else { - buffer = Buffer.alloc(9); - buffer.writeUInt8(0xff, 0); - buffer.writeUInt32LE(x >>> 0, 1); - buffer.writeUInt32LE((x / 0x100000000) | 0, 5); - } - return buffer; + let buffer: Buffer + if (x < 0xfd) { + buffer = Buffer.alloc(1) + buffer.writeUInt8(x, 0) + } else if (x <= 0xffff) { + buffer = Buffer.alloc(3) + buffer.writeUInt8(0xfd, 0) + buffer.writeUInt16LE(x, 1) + } else if (x < 0xffffffff) { + buffer = Buffer.alloc(5) + buffer.writeUInt8(0xfe, 0) + buffer.writeUInt32LE(x, 1) + } else { + buffer = Buffer.alloc(9) + buffer.writeUInt8(0xff, 0) + buffer.writeUInt32LE(x >>> 0, 1) + buffer.writeUInt32LE((x / 0x100000000) | 0, 5) + } + return buffer } function writeUInt64LE(n, buf, off) { - if (typeof n === 'number') n = n.toString(16); - const preBuf = Buffer.alloc(8); - const nStr = n.length % 2 === 0 ? n.toString(16) : `0${n.toString(16)}`; - const nBuf = Buffer.from(nStr, 'hex'); - nBuf.reverse().copy(preBuf, 0); - preBuf.copy(buf, off); - return preBuf; + if (typeof n === 'number') n = n.toString(16) + const preBuf = Buffer.alloc(8) + const nStr = n.length % 2 === 0 ? n.toString(16) : `0${n.toString(16)}` + const nBuf = Buffer.from(nStr, 'hex') + nBuf.reverse().copy(preBuf, 0) + preBuf.copy(buf, off) + return preBuf } function decodeAddress(address) { - let versionByte; - let pkh; - try { - // Attempt to base58 decode the address. This will work for older - // P2PKH, P2SH, and P2SH-P2WPKH addresses - versionByte = bs58check.decode(address)[0]; - pkh = Buffer.from(bs58check.decode(address).slice(1)); - } catch (err) { - console.error('Failed to decode base58 address, trying bech32:', err); - // If we could not base58 decode, the address must be bech32 encoded. - // If neither decoding method works, the address is invalid. - try { - const bech32Dec = bech32.decode(address); - if (bech32Dec.prefix === SEGWIT_NATIVE_V0_PREFIX) { - versionByte = FMT_SEGWIT_NATIVE_V0; - } else if (bech32Dec.prefix === SEGWIT_NATIVE_V0_TESTNET_PREFIX) { - versionByte = FMT_SEGWIT_NATIVE_V0_TESTNET; - } else { - throw new Error('Unsupported prefix: must be bc or tb.'); - } - // Make sure we decoded - if (bech32Dec.words[0] !== 0) { - throw new Error( - `Unsupported segwit version: must be 0, got ${bech32Dec.words[0]}`, - ); - } - // Make sure address type is supported. - // We currently only support P2WPKH addresses, which bech-32decode to 33 words. - // P2WSH addresses are 53 words, but we do not support them. - // Not sure what other address types could exist, but if they exist we don't - // support them either. - if (bech32Dec.words.length !== 33) { - const isP2wpsh = bech32Dec.words.length === 53; - throw new Error( - `Unsupported address${ - isP2wpsh ? ' (P2WSH not supported)' : '' - }: ${address}`, - ); - } + let versionByte: number | undefined + let pkh: Buffer | undefined + try { + // Attempt to base58 decode the address. This will work for older + // P2PKH, P2SH, and P2SH-P2WPKH addresses + versionByte = bs58check.decode(address)[0] + pkh = Buffer.from(bs58check.decode(address).slice(1)) + } catch (err) { + console.error('Failed to decode base58 address, trying bech32:', err) + // If we could not base58 decode, the address must be bech32 encoded. + // If neither decoding method works, the address is invalid. + try { + const bech32Dec = bech32.decode(address) + if (bech32Dec.prefix === SEGWIT_NATIVE_V0_PREFIX) { + versionByte = FMT_SEGWIT_NATIVE_V0 + } else if (bech32Dec.prefix === SEGWIT_NATIVE_V0_TESTNET_PREFIX) { + versionByte = FMT_SEGWIT_NATIVE_V0_TESTNET + } else { + throw new Error('Unsupported prefix: must be bc or tb.') + } + // Make sure we decoded + if (bech32Dec.words[0] !== 0) { + throw new Error(`Unsupported segwit version: must be 0, got ${bech32Dec.words[0]}`) + } + // Make sure address type is supported. + // We currently only support P2WPKH addresses, which bech-32decode to 33 words. + // P2WSH addresses are 53 words, but we do not support them. + // Not sure what other address types could exist, but if they exist we don't + // support them either. + if (bech32Dec.words.length !== 33) { + const isP2wpsh = bech32Dec.words.length === 53 + throw new Error(`Unsupported address${isP2wpsh ? ' (P2WSH not supported)' : ''}: ${address}`) + } - pkh = Buffer.from(bech32.fromWords(bech32Dec.words.slice(1))); - } catch (err) { - throw new Error(`Unable to decode address: ${address}: ${err.message}`); - } - } - return { versionByte, pkh }; + pkh = Buffer.from(bech32.fromWords(bech32Dec.words.slice(1))) + } catch (err) { + throw new Error(`Unable to decode address: ${address}: ${err.message}`) + } + } + return { versionByte, pkh } } // Determine the address format (a.k.a. "version") depending on the // purpose of the dervation path. function getAddressFormat(path) { - if (path.length < 2) throw new Error('Path must be >1 index'); - const purpose = path[0]; - const coin = path[1]; - if (purpose === PURPOSES.BTC_SEGWIT && coin === COINS.BTC) { - return FMT_SEGWIT_NATIVE_V0; - } else if (purpose === PURPOSES.BTC_SEGWIT && coin === COINS.BTC_TESTNET) { - return FMT_SEGWIT_NATIVE_V0_TESTNET; - } else if (purpose === PURPOSES.BTC_WRAPPED_SEGWIT && coin === COINS.BTC) { - return FMT_SEGWIT_WRAPPED; - } else if ( - purpose === PURPOSES.BTC_WRAPPED_SEGWIT && - coin === COINS.BTC_TESTNET - ) { - return FMT_SEGWIT_WRAPPED_TESTNET; - } else if (purpose === PURPOSES.BTC_LEGACY && coin === COINS.BTC) { - return FMT_LEGACY; - } else if (purpose === PURPOSES.BTC_LEGACY && coin === COINS.BTC_TESTNET) { - return FMT_LEGACY_TESTNET; - } else { - throw new Error( - 'Invalid Bitcoin path provided. Cannot determine address format.', - ); - } + if (path.length < 2) throw new Error('Path must be >1 index') + const purpose = path[0] + const coin = path[1] + if (purpose === PURPOSES.BTC_SEGWIT && coin === COINS.BTC) { + return FMT_SEGWIT_NATIVE_V0 + } else if (purpose === PURPOSES.BTC_SEGWIT && coin === COINS.BTC_TESTNET) { + return FMT_SEGWIT_NATIVE_V0_TESTNET + } else if (purpose === PURPOSES.BTC_WRAPPED_SEGWIT && coin === COINS.BTC) { + return FMT_SEGWIT_WRAPPED + } else if (purpose === PURPOSES.BTC_WRAPPED_SEGWIT && coin === COINS.BTC_TESTNET) { + return FMT_SEGWIT_WRAPPED_TESTNET + } else if (purpose === PURPOSES.BTC_LEGACY && coin === COINS.BTC) { + return FMT_LEGACY + } else if (purpose === PURPOSES.BTC_LEGACY && coin === COINS.BTC_TESTNET) { + return FMT_LEGACY_TESTNET + } else { + throw new Error('Invalid Bitcoin path provided. Cannot determine address format.') + } } // Determine the script type for an input based on its owner's derivation @@ -468,39 +448,34 @@ function getAddressFormat(path) { // We do not support p2sh and only issue single-key addresses from the Lattice // so we can determine this based on path alone. function getScriptType(input) { - switch (input.signerPath[0]) { - case PURPOSES.BTC_LEGACY: - return BTC_SCRIPT_TYPE_P2PKH; - case PURPOSES.BTC_WRAPPED_SEGWIT: - return BTC_SCRIPT_TYPE_P2SH_P2WPKH; - case PURPOSES.BTC_SEGWIT: - return BTC_SCRIPT_TYPE_P2WPKH_V0; - default: - throw new Error( - `Unsupported path purpose (${input.signerPath[0]}): cannot determine BTC script type.`, - ); - } + switch (input.signerPath[0]) { + case PURPOSES.BTC_LEGACY: + return BTC_SCRIPT_TYPE_P2PKH + case PURPOSES.BTC_WRAPPED_SEGWIT: + return BTC_SCRIPT_TYPE_P2SH_P2WPKH + case PURPOSES.BTC_SEGWIT: + return BTC_SCRIPT_TYPE_P2WPKH_V0 + default: + throw new Error(`Unsupported path purpose (${input.signerPath[0]}): cannot determine BTC script type.`) + } } // Determine if a a transaction should have a witness portion. // This will return true if any input is p2sh(p2wpkh) or p2wpkh. // We determine the script type based on the derivation path. function needsWitness(inputs) { - let w = false; - inputs.forEach((input) => { - if ( - input.signerPath[0] === PURPOSES.BTC_SEGWIT || - input.signerPath[0] === PURPOSES.BTC_WRAPPED_SEGWIT - ) { - w = true; - } - }); - return w; + let w = false + inputs.forEach((input) => { + if (input.signerPath[0] === PURPOSES.BTC_SEGWIT || input.signerPath[0] === PURPOSES.BTC_WRAPPED_SEGWIT) { + w = true + } + }) + return w } export default { - buildBitcoinTxRequest, - serializeTx, - getBitcoinAddress, - getAddressFormat, -}; + buildBitcoinTxRequest, + serializeTx, + getBitcoinAddress, + getAddressFormat, +} diff --git a/packages/sdk/src/calldata/evm.ts b/packages/sdk/src/calldata/evm.ts index f2df93c3..b1d90434 100644 --- a/packages/sdk/src/calldata/evm.ts +++ b/packages/sdk/src/calldata/evm.ts @@ -1,5 +1,5 @@ -import { Hash } from 'ox'; -import { decodeAbiParameters, parseAbiParameters } from 'viem'; +import { Hash } from 'ox' +import { decodeAbiParameters, parseAbiParameters } from 'viem' /** * Look through an ABI definition to see if there is a function that matches the signature provided. * @param sig a 0x-prefixed hex string containing 4 bytes of info @@ -7,25 +7,22 @@ import { decodeAbiParameters, parseAbiParameters } from 'viem'; * @returns Buffer containing RLP-serialized array of calldata info to pass to signing request * @public */ -export const parseSolidityJSONABI = ( - sig: string, - abi: any[], -): { def: EVMDef } => { - sig = coerceSig(sig); - // Find the first match in the ABI - const match = abi - .filter((item) => item.type === 'function') - .find((item) => { - const def = parseDef(item); - const funcSig = getFuncSig(def.canonicalName); - return funcSig === sig; - }); - if (match) { - const def = parseDef(match).def; - return { def }; - } - throw new Error('Unable to find matching function in ABI'); -}; +export const parseSolidityJSONABI = (sig: string, abi: any[]): { def: EVMDef } => { + sig = coerceSig(sig) + // Find the first match in the ABI + const match = abi + .filter((item) => item.type === 'function') + .find((item) => { + const def = parseDef(item) + const funcSig = getFuncSig(def.canonicalName) + return funcSig === sig + }) + if (match) { + const def = parseDef(match).def + return { def } + } + throw new Error('Unable to find matching function in ABI') +} /** * Convert a canonical name into an ABI definition that can be included with calldata to a general @@ -36,28 +33,28 @@ export const parseSolidityJSONABI = ( * @public */ export const parseCanonicalName = (sig: string, name: string) => { - sig = coerceSig(sig); - if (sig !== getFuncSig(name)) { - throw new Error('Name does not match provided sig.'); - } - const def = []; - // Get the function name - const paramStart = name.indexOf('('); - if (paramStart < 0) { - throw new Error(BAD_CANONICAL_ERR); - } - def.push(name.slice(0, paramStart)); - name = name.slice(paramStart + 1); - let paramDef = []; - while (name.length > 1) { - // scan until the terminating ')' - const typeStr = popTypeStrFromCanonical(name); - paramDef = paramDef.concat(parseTypeStr(typeStr)); - name = name.slice(typeStr.length + 1); - } - const parsedParamDef = parseParamDef(paramDef); - return def.concat(parsedParamDef); -}; + sig = coerceSig(sig) + if (sig !== getFuncSig(name)) { + throw new Error('Name does not match provided sig.') + } + const def = [] + // Get the function name + const paramStart = name.indexOf('(') + if (paramStart < 0) { + throw new Error(BAD_CANONICAL_ERR) + } + def.push(name.slice(0, paramStart)) + name = name.slice(paramStart + 1) + let paramDef = [] + while (name.length > 1) { + // scan until the terminating ')' + const typeStr = popTypeStrFromCanonical(name) + paramDef = paramDef.concat(parseTypeStr(typeStr)) + name = name.slice(typeStr.length + 1) + } + const parsedParamDef = parseParamDef(paramDef) + return def.concat(parsedParamDef) +} /** * Pull out nested calldata which may correspond to nested ABI definitions. @@ -71,84 +68,75 @@ export const parseCanonicalName = (sig: string, name: string) => { * checked as a possible nested def */ export const getNestedCalldata = (def, calldata) => { - const possibleNestedDefs = []; - // Skip past first item, which is the function name - const defParams = def.slice(1); - const strParams = getParamStrNames(defParams); - const hexStr = `0x${calldata.slice(4).toString('hex')}` as `0x${string}`; - // Convert strParams to viem's format - const viemParams = strParams.map((type) => { - // Convert tuple format from 'tuple(uint256,uint128)' to '(uint256,uint128)' - if (type.startsWith('tuple(')) { - return type.replace('tuple', ''); - } - return type; - }); + const possibleNestedDefs = [] + // Skip past first item, which is the function name + const defParams = def.slice(1) + const strParams = getParamStrNames(defParams) + const hexStr = `0x${calldata.slice(4).toString('hex')}` as `0x${string}` + // Convert strParams to viem's format + const viemParams = strParams.map((type) => { + // Convert tuple format from 'tuple(uint256,uint128)' to '(uint256,uint128)' + if (type.startsWith('tuple(')) { + return type.replace('tuple', '') + } + return type + }) - const abiParams = parseAbiParameters(viemParams.join(',')); - const decoded = decodeAbiParameters(abiParams, hexStr); + const abiParams = parseAbiParameters(viemParams.join(',')) + const decoded = decodeAbiParameters(abiParams, hexStr) - function couldBeNestedDef(x) { - return (x.length - 4) % 32 === 0; - } - decoded.forEach((paramData, i) => { - if (isBytesType(defParams[i])) { - let nestedDefIsPossible = true; - if (isBytesArrItem(defParams[i])) { - // `bytes[]` type. Decode all underlying `bytes` items and - // do size checks on those. - // NOTE: We only do this for `bytes[]` but could, in the future, - // extend to more complex array structures if we see nested defs - // in this pattern. However, we have only ever seen `bytes[]`, which - // is typically used in `multicall` patterns - // Ensure paramData is an array for bytes[] type - if (Array.isArray(paramData)) { - paramData.forEach((nestedParamDatum) => { - // Ensure nestedParamDatum is a hex string - if ( - typeof nestedParamDatum !== 'string' || - !nestedParamDatum.startsWith('0x') - ) { - nestedDefIsPossible = false; - return; - } - const nestedParamDatumBuf = Buffer.from( - nestedParamDatum.slice(2), - 'hex', - ); - if (!couldBeNestedDef(nestedParamDatumBuf)) { - nestedDefIsPossible = false; - } - }); - } else { - nestedDefIsPossible = false; - } - } else if (isBytesItem(defParams[i])) { - // Regular `bytes` type - perform size check - if ( - typeof paramData !== 'string' || - !(paramData as string).startsWith('0x') - ) { - nestedDefIsPossible = false; - } else { - const data = paramData as string; - const paramDataBuf = Buffer.from(data.slice(2), 'hex'); - nestedDefIsPossible = couldBeNestedDef(paramDataBuf); - } - } else { - // Unknown `bytes` item type - nestedDefIsPossible = false; - } - // If the data could contain a nested def (determined based on - // data size of the item), add the paramData to the return array. - possibleNestedDefs.push(nestedDefIsPossible ? paramData : null); - } else { - // No nested defs for non-bytes types - possibleNestedDefs.push(null); - } - }); - return possibleNestedDefs; -}; + function couldBeNestedDef(x) { + return (x.length - 4) % 32 === 0 + } + decoded.forEach((paramData, i) => { + if (isBytesType(defParams[i])) { + let nestedDefIsPossible = true + if (isBytesArrItem(defParams[i])) { + // `bytes[]` type. Decode all underlying `bytes` items and + // do size checks on those. + // NOTE: We only do this for `bytes[]` but could, in the future, + // extend to more complex array structures if we see nested defs + // in this pattern. However, we have only ever seen `bytes[]`, which + // is typically used in `multicall` patterns + // Ensure paramData is an array for bytes[] type + if (Array.isArray(paramData)) { + paramData.forEach((nestedParamDatum) => { + // Ensure nestedParamDatum is a hex string + if (typeof nestedParamDatum !== 'string' || !nestedParamDatum.startsWith('0x')) { + nestedDefIsPossible = false + return + } + const nestedParamDatumBuf = Buffer.from(nestedParamDatum.slice(2), 'hex') + if (!couldBeNestedDef(nestedParamDatumBuf)) { + nestedDefIsPossible = false + } + }) + } else { + nestedDefIsPossible = false + } + } else if (isBytesItem(defParams[i])) { + // Regular `bytes` type - perform size check + if (typeof paramData !== 'string' || !(paramData as string).startsWith('0x')) { + nestedDefIsPossible = false + } else { + const data = paramData as string + const paramDataBuf = Buffer.from(data.slice(2), 'hex') + nestedDefIsPossible = couldBeNestedDef(paramDataBuf) + } + } else { + // Unknown `bytes` item type + nestedDefIsPossible = false + } + // If the data could contain a nested def (determined based on + // data size of the item), add the paramData to the return array. + possibleNestedDefs.push(nestedDefIsPossible ? paramData : null) + } else { + // No nested defs for non-bytes types + possibleNestedDefs.push(null) + } + }) + return possibleNestedDefs +} /** * If applicable, update decoder data to represent nested @@ -161,45 +149,45 @@ export const getNestedCalldata = (def, calldata) => { * @return - Possibly modified version of `def` */ export const replaceNestedDefs = (def, nestedDefs) => { - for (let i = 0; i < nestedDefs.length; i++) { - const isArrItem = isBytesArrItem(def[1 + i]); - const isItem = isBytesItem(def[1 + i]); - if (nestedDefs[i] !== null && (isArrItem || isItem)) { - // Update the def item type to indicate it will hold - // one or more nested definitions - def[1 + i][1] = EVM_TYPES.indexOf('nestedDef'); - // Add nested def(s) in in an array. If this is an array - // item it means the nestedDefs should already be in an - // array. Otherwise we need to wrap the single nested - // def in an array to keep the data type consistent. - const defs = isArrItem ? nestedDefs[i] : [nestedDefs[i]]; - def[1 + i] = def[1 + i].concat([defs]); - } - } - return def; -}; + for (let i = 0; i < nestedDefs.length; i++) { + const isArrItem = isBytesArrItem(def[1 + i]) + const isItem = isBytesItem(def[1 + i]) + if (nestedDefs[i] !== null && (isArrItem || isItem)) { + // Update the def item type to indicate it will hold + // one or more nested definitions + def[1 + i][1] = EVM_TYPES.indexOf('nestedDef') + // Add nested def(s) in in an array. If this is an array + // item it means the nestedDefs should already be in an + // array. Otherwise we need to wrap the single nested + // def in an array to keep the data type consistent. + const defs = isArrItem ? nestedDefs[i] : [nestedDefs[i]] + def[1 + i] = def[1 + i].concat([defs]) + } + } + return def +} /** * Convert a canonical name to a function selector (a.k.a. "sig") * @internal */ function getFuncSig(canonicalName: string): string { - return `0x${Buffer.from(Hash.keccak256(Buffer.from(canonicalName))) - .toString('hex') - .slice(0, 8)}`; + return `0x${Buffer.from(Hash.keccak256(Buffer.from(canonicalName))) + .toString('hex') + .slice(0, 8)}` } /** * Ensure the sig is properly formatted */ function coerceSig(sig: string): string { - if (typeof sig !== 'string' || (sig.length !== 10 && sig.length !== 8)) { - throw new Error('`sig` must be a hex string with 4 bytes of data.'); - } - if (sig.length === 8) { - sig = `0x${sig}`; - } - return sig; + if (typeof sig !== 'string' || (sig.length !== 10 && sig.length !== 8)) { + throw new Error('`sig` must be a hex string with 4 bytes of data.') + } + if (sig.length === 8) { + sig = `0x${sig}` + } + return sig } /** @@ -211,30 +199,30 @@ function coerceSig(sig: string): string { * @internal */ function getParamStrNames(defParams) { - const strNames = []; - for (let i = 0; i < defParams.length; i++) { - const param = defParams[i]; - let s = EVM_TYPES[param[1]]; - if (param[2]) { - s = `${s}${param[2] * 8}`; - } - if (param[3].length > 0) { - param[3].forEach((d) => { - if (param[3][d] === 0) { - s = `${s}[]`; - } else { - s = `${s}[${param[3][d]}]`; - } - }); - } - if (param[4]) { - // Tuple - get nested type names - const nested = getParamStrNames(param[4]); - s = `${s}(${nested.join(',')})`; - } - strNames.push(s); - } - return strNames; + const strNames = [] + for (let i = 0; i < defParams.length; i++) { + const param = defParams[i] + let s = EVM_TYPES[param[1]] + if (param[2]) { + s = `${s}${param[2] * 8}` + } + if (param[3].length > 0) { + param[3].forEach((d) => { + if (param[3][d] === 0) { + s = `${s}[]` + } else { + s = `${s}[${param[3][d]}]` + } + }) + } + if (param[4]) { + // Tuple - get nested type names + const nested = getParamStrNames(param[4]) + s = `${s}(${nested.join(',')})` + } + strNames.push(s) + } + return strNames } /** @@ -243,16 +231,16 @@ function getParamStrNames(defParams) { * @internal */ function popTypeStrFromCanonical(subName: string): string { - if (isTuple(subName)) { - return getTupleName(subName); - } else if (subName.indexOf(',') > -1) { - // Normal non-tuple param - return subName.slice(0, subName.indexOf(',')); - } else if (subName.indexOf(')') > -1) { - // Last non-tuple param in the name - return subName.slice(0, subName.indexOf(')')); - } - throw new Error(BAD_CANONICAL_ERR); + if (isTuple(subName)) { + return getTupleName(subName) + } else if (subName.indexOf(',') > -1) { + // Normal non-tuple param + return subName.slice(0, subName.indexOf(',')) + } else if (subName.indexOf(')') > -1) { + // Last non-tuple param in the name + return subName.slice(0, subName.indexOf(')')) + } + throw new Error(BAD_CANONICAL_ERR) } /** @@ -261,34 +249,34 @@ function popTypeStrFromCanonical(subName: string): string { * @internal */ function parseTypeStr(typeStr: string): any[] { - // Non-tuples can be decoded without worrying about recursion - if (!isTuple(typeStr)) { - return [parseBasicTypeStr(typeStr)]; - } - // Tuples may require recursion - const param: EVMParamInfo = { - szBytes: 0, - typeIdx: EVM_TYPES.indexOf('tuple'), - arraySzs: [], - }; - // Get the full tuple param name and separate out the array stuff - let typeStrLessArr = getTupleName(typeStr, false); - const typeStrArr = typeStr.slice(typeStrLessArr.length); - param.arraySzs = getArraySzs(typeStrArr); - // Slice off the leading paren - typeStrLessArr = typeStrLessArr.slice(1); - // Parse each nested param - let paramArr = []; - while (typeStrLessArr.length > 0) { - const subType = popTypeStrFromCanonical(typeStrLessArr); - typeStrLessArr = typeStrLessArr.slice(subType.length + 1); - paramArr = paramArr.concat(parseTypeStr(subType)); - } - // There must be at least one sub-param in the tuple - if (!paramArr.length) { - throw new Error(BAD_CANONICAL_ERR); - } - return [param, paramArr]; + // Non-tuples can be decoded without worrying about recursion + if (!isTuple(typeStr)) { + return [parseBasicTypeStr(typeStr)] + } + // Tuples may require recursion + const param: EVMParamInfo = { + szBytes: 0, + typeIdx: EVM_TYPES.indexOf('tuple'), + arraySzs: [], + } + // Get the full tuple param name and separate out the array stuff + let typeStrLessArr = getTupleName(typeStr, false) + const typeStrArr = typeStr.slice(typeStrLessArr.length) + param.arraySzs = getArraySzs(typeStrArr) + // Slice off the leading paren + typeStrLessArr = typeStrLessArr.slice(1) + // Parse each nested param + let paramArr = [] + while (typeStrLessArr.length > 0) { + const subType = popTypeStrFromCanonical(typeStrLessArr) + typeStrLessArr = typeStrLessArr.slice(subType.length + 1) + paramArr = paramArr.concat(parseTypeStr(subType)) + } + // There must be at least one sub-param in the tuple + if (!paramArr.length) { + throw new Error(BAD_CANONICAL_ERR) + } + return [param, paramArr] } /** @@ -296,32 +284,31 @@ function parseTypeStr(typeStr: string): any[] { * @internal */ function parseBasicTypeStr(typeStr: string): EVMParamInfo { - const param: EVMParamInfo = { - szBytes: 0, - typeIdx: 0, - arraySzs: [], - }; - let found = false; - EVM_TYPES.forEach((t, i) => { - if (typeStr.indexOf(t) > -1 && !found) { - param.typeIdx = i; - param.arraySzs = getArraySzs(typeStr); - const arrStart = - param.arraySzs.length > 0 ? typeStr.indexOf('[') : typeStr.length; - const typeStrNum = typeStr.slice(t.length, arrStart); - if (Number.parseInt(typeStrNum)) { - param.szBytes = Number.parseInt(typeStrNum) / 8; - if (param.szBytes > 32) { - throw new Error(BAD_CANONICAL_ERR); - } - } - found = true; - } - }); - if (!found) { - throw new Error(BAD_CANONICAL_ERR); - } - return param; + const param: EVMParamInfo = { + szBytes: 0, + typeIdx: 0, + arraySzs: [], + } + let found = false + EVM_TYPES.forEach((t, i) => { + if (typeStr.indexOf(t) > -1 && !found) { + param.typeIdx = i + param.arraySzs = getArraySzs(typeStr) + const arrStart = param.arraySzs.length > 0 ? typeStr.indexOf('[') : typeStr.length + const typeStrNum = typeStr.slice(t.length, arrStart) + if (Number.parseInt(typeStrNum)) { + param.szBytes = Number.parseInt(typeStrNum) / 8 + if (param.szBytes > 32) { + throw new Error(BAD_CANONICAL_ERR) + } + } + found = true + } + }) + if (!found) { + throw new Error(BAD_CANONICAL_ERR) + } + return param } /** @@ -329,51 +316,41 @@ function parseBasicTypeStr(typeStr: string): EVMParamInfo { * (EVMDef). This function may recurse if there are tuple types. * @internal */ -function parseDef( - item, - canonicalName = '', - def = [], - recursed = false, -): EVMDef { - // Function name. Can be an empty string. - if (!recursed) { - const nameStr = item.name || ''; - def.push(nameStr); - canonicalName += nameStr; - } - // Loop through params - if (item.inputs) { - canonicalName += '('; - item.inputs.forEach((input) => { - // Convert the input to a flat param that we can serialize - const flatParam = getFlatParam(input); - if (input.type.indexOf('tuple') > -1 && input.components) { - // For tuples we need to recurse - const recursed = parseDef( - { inputs: input.components }, - canonicalName, - [], - true, - ); - canonicalName = recursed.canonicalName; - // Add brackets if this is a tuple array and also add a comma - canonicalName += `${input.type.slice(5)},`; - flatParam.push(recursed.def); - } else { - canonicalName += input.type; - canonicalName += ','; - } - def.push(flatParam); - }); - // Take off the last comma. Note that we do not want to slice if the last param was a tuple, - // since we want to keep that `)` - if (canonicalName[canonicalName.length - 1] === ',') { - canonicalName = canonicalName.slice(0, canonicalName.length - 1); - } - // Add the closing parens - canonicalName += ')'; - } - return { def, canonicalName }; +function parseDef(item, canonicalName = '', def = [], recursed = false): EVMDef { + // Function name. Can be an empty string. + if (!recursed) { + const nameStr = item.name || '' + def.push(nameStr) + canonicalName += nameStr + } + // Loop through params + if (item.inputs) { + canonicalName += '(' + item.inputs.forEach((input) => { + // Convert the input to a flat param that we can serialize + const flatParam = getFlatParam(input) + if (input.type.indexOf('tuple') > -1 && input.components) { + // For tuples we need to recurse + const recursed = parseDef({ inputs: input.components }, canonicalName, [], true) + canonicalName = recursed.canonicalName + // Add brackets if this is a tuple array and also add a comma + canonicalName += `${input.type.slice(5)},` + flatParam.push(recursed.def) + } else { + canonicalName += input.type + canonicalName += ',' + } + def.push(flatParam) + }) + // Take off the last comma. Note that we do not want to slice if the last param was a tuple, + // since we want to keep that `)` + if (canonicalName[canonicalName.length - 1] === ',') { + canonicalName = canonicalName.slice(0, canonicalName.length - 1) + } + // Add the closing parens + canonicalName += ')' + } + return { def, canonicalName } } /** @@ -383,28 +360,23 @@ function parseDef( * @internal */ function parseParamDef(def: any[], prefix = ''): any[] { - const parsedDef = []; - let numTuples = 0; - def.forEach((param, i) => { - if (Array.isArray(param)) { - // Arrays indicate nested params inside a tuple and always come after the initial tuple type - // info. Recurse to parse nested tuple params and append them to the most recent. - parsedDef[parsedDef.length - 1].push(parseParamDef(param, `${i}-`)); - } else { - // If this is not tuple info, add the flat param info to the def - parsedDef.push([ - `#${prefix}${i + 1 - numTuples}`, - param.typeIdx, - param.szBytes, - param.arraySzs, - ]); - } - // Tuple - if (param.typeIdx === EVM_TYPES.indexOf('tuple')) { - numTuples += 1; - } - }); - return parsedDef; + const parsedDef = [] + let numTuples = 0 + def.forEach((param, i) => { + if (Array.isArray(param)) { + // Arrays indicate nested params inside a tuple and always come after the initial tuple type + // info. Recurse to parse nested tuple params and append them to the most recent. + parsedDef[parsedDef.length - 1].push(parseParamDef(param, `${i}-`)) + } else { + // If this is not tuple info, add the flat param info to the def + parsedDef.push([`#${prefix}${i + 1 - numTuples}`, param.typeIdx, param.szBytes, param.arraySzs]) + } + // Tuple + if (param.typeIdx === EVM_TYPES.indexOf('tuple')) { + numTuples += 1 + } + }) + return parsedDef } /** @@ -412,15 +384,15 @@ function parseParamDef(def: any[], prefix = ''): any[] { * @internal */ function getFlatParam(input): any[] { - if (!input.type) { - throw new Error('No type in input'); - } - const param = [input.name]; - const { typeIdx, szBytes, arraySzs } = getParamTypeInfo(input.type); - param.push(typeIdx); - param.push(szBytes); - param.push(arraySzs); - return param; + if (!input.type) { + throw new Error('No type in input') + } + const param = [input.name] + const { typeIdx, szBytes, arraySzs } = getParamTypeInfo(input.type) + param.push(typeIdx) + param.push(szBytes) + param.push(arraySzs) + return param } /** @@ -435,34 +407,34 @@ function getFlatParam(input): any[] { * @internal */ function getParamTypeInfo(type: string): EVMParamInfo { - const param: EVMParamInfo = { - szBytes: 0, - typeIdx: 0, - arraySzs: [], - }; - let baseType; - EVM_TYPES.forEach((t, i) => { - if (type.indexOf(t) > -1 && !baseType) { - baseType = t; - param.typeIdx = i; - } - }); - // Get the array size, if any - param.arraySzs = getArraySzs(type); - // Determine where to search for expanded size - const szIdx = param.arraySzs.length > 0 ? type.indexOf('[') : type.length; - if (['uint', 'int', 'bytes'].indexOf(baseType) > -1) { - // If this can have a fixed size, capture that - const szBits = Number.parseInt(type.slice(baseType.length, szIdx)) || 0; - if (szBits > 256) { - throw new Error('Invalid param size'); - } - param.szBytes = szBits / 8; - } else { - // No fixed size in the type - param.szBytes = 0; - } - return param; + const param: EVMParamInfo = { + szBytes: 0, + typeIdx: 0, + arraySzs: [], + } + let baseType: string | undefined + EVM_TYPES.forEach((t, i) => { + if (type.indexOf(t) > -1 && !baseType) { + baseType = t + param.typeIdx = i + } + }) + // Get the array size, if any + param.arraySzs = getArraySzs(type) + // Determine where to search for expanded size + const szIdx = param.arraySzs.length > 0 ? type.indexOf('[') : type.length + if (['uint', 'int', 'bytes'].indexOf(baseType) > -1) { + // If this can have a fixed size, capture that + const szBits = Number.parseInt(type.slice(baseType.length, szIdx)) || 0 + if (szBits > 256) { + throw new Error('Invalid param size') + } + param.szBytes = szBits / 8 + } else { + // No fixed size in the type + param.szBytes = 0 + } + return param } /** @@ -471,93 +443,82 @@ function getParamTypeInfo(type: string): EVMParamInfo { * @internal */ function getArraySzs(type: string): number[] { - if (typeof type !== 'string') { - throw new Error('Invalid type'); - } - const szs = []; - let t1 = type; - while (t1.length > 0) { - const openIdx = t1.indexOf('['); - if (openIdx < 0) { - return szs; - } - const t2 = t1.slice(openIdx); - const closeIdx = t2.indexOf(']'); - if (closeIdx < 0) { - throw new Error('Bad param type'); - } - const t3 = t2.slice(1, closeIdx); - if (t3.length === 0) { - // Variable size - szs.push(0); - } else { - // Fixed size - szs.push(Number.parseInt(t3)); - } - t1 = t2.slice(closeIdx + 1); - } - return szs; + if (typeof type !== 'string') { + throw new Error('Invalid type') + } + const szs = [] + let t1 = type + while (t1.length > 0) { + const openIdx = t1.indexOf('[') + if (openIdx < 0) { + return szs + } + const t2 = t1.slice(openIdx) + const closeIdx = t2.indexOf(']') + if (closeIdx < 0) { + throw new Error('Bad param type') + } + const t3 = t2.slice(1, closeIdx) + if (t3.length === 0) { + // Variable size + szs.push(0) + } else { + // Fixed size + szs.push(Number.parseInt(t3)) + } + t1 = t2.slice(closeIdx + 1) + } + return szs } /** @internal */ function getTupleName(name, withArr = true) { - let brackets = 0; - let addedFirstBracket = false; - for (let i = 0; i < name.length; i++) { - if (name[i] === '(') { - brackets += 1; - addedFirstBracket = true; - } else if (name[i] === ')') { - brackets -= 1; - } - let canBreak = - name[i + 1] === ',' || name[i + 1] === ')' || i === name.length - 1; - if (!withArr && name[i + 1] === '[') { - canBreak = true; - } - if (!brackets && addedFirstBracket && canBreak) { - return name.slice(0, i + 1); - } - } - throw new Error(BAD_CANONICAL_ERR); + let brackets = 0 + let addedFirstBracket = false + for (let i = 0; i < name.length; i++) { + if (name[i] === '(') { + brackets += 1 + addedFirstBracket = true + } else if (name[i] === ')') { + brackets -= 1 + } + let canBreak = name[i + 1] === ',' || name[i + 1] === ')' || i === name.length - 1 + if (!withArr && name[i + 1] === '[') { + canBreak = true + } + if (!brackets && addedFirstBracket && canBreak) { + return name.slice(0, i + 1) + } + } + throw new Error(BAD_CANONICAL_ERR) } /** @internal */ function isTuple(type: string): boolean { - return type[0] === '('; + return type[0] === '(' } /** @internal */ function isBytesType(param) { - return EVM_TYPES[param[1]] === 'bytes'; + return EVM_TYPES[param[1]] === 'bytes' } function isBytesItem(param) { - return isBytesType(param) && param[3].length === 0; + return isBytesType(param) && param[3].length === 0 } function isBytesArrItem(param) { - return isBytesType(param) && param[3].length === 1 && param[3][0] === 0; + return isBytesType(param) && param[3].length === 1 && param[3][0] === 0 } -const BAD_CANONICAL_ERR = 'Could not parse canonical function name.'; -const EVM_TYPES = [ - null, - 'address', - 'bool', - 'uint', - 'int', - 'bytes', - 'string', - 'tuple', - 'nestedDef', -]; +const BAD_CANONICAL_ERR = 'Could not parse canonical function name.' +const EVM_TYPES = [null, 'address', 'bool', 'uint', 'int', 'bytes', 'string', 'tuple', 'nestedDef'] type EVMParamInfo = { - szBytes: number; - typeIdx: number; - arraySzs: number[]; -}; + szBytes: number + typeIdx: number + arraySzs: number[] +} type EVMDef = { - canonicalName: string; - def: any; -}; + canonicalName: string + def: any +} diff --git a/packages/sdk/src/calldata/index.ts b/packages/sdk/src/calldata/index.ts index c5d46b8e..d5f94f42 100644 --- a/packages/sdk/src/calldata/index.ts +++ b/packages/sdk/src/calldata/index.ts @@ -3,23 +3,18 @@ * calldata decoder info is packed into the request, it is used to decode the calldata in the * request. It is optional. */ -import { - getNestedCalldata, - parseCanonicalName, - parseSolidityJSONABI, - replaceNestedDefs, -} from './evm'; +import { getNestedCalldata, parseCanonicalName, parseSolidityJSONABI, replaceNestedDefs } from './evm' export const CALLDATA = { - EVM: { - type: 1, - parsers: { - parseSolidityJSONABI, - parseCanonicalName, - }, - processors: { - getNestedCalldata, - replaceNestedDefs, - }, - }, -}; + EVM: { + type: 1, + parsers: { + parseSolidityJSONABI, + parseCanonicalName, + }, + processors: { + getNestedCalldata, + replaceNestedDefs, + }, + }, +} diff --git a/packages/sdk/src/client.ts b/packages/sdk/src/client.ts index 797a6159..76ea3cc9 100644 --- a/packages/sdk/src/client.ts +++ b/packages/sdk/src/client.ts @@ -1,37 +1,11 @@ -import { buildSaveClientFn } from './api/utilities'; -import { - BASE_URL, - DEFAULT_ACTIVE_WALLETS, - EMPTY_WALLET_UID, - getFwVersionConst, -} from './constants'; -import { - addKvRecords, - connect, - fetchActiveWallet, - fetchEncData, - getAddresses, - getKvRecords, - pair, - removeKvRecords, - sign, -} from './functions/index'; -import { buildRetryWrapper } from './shared/functions'; -import { getPubKeyBytes } from './shared/utilities'; -import { validateEphemeralPub } from './shared/validators'; -import type { - ActiveWallets, - AddKvRecordsRequestParams, - FetchEncDataRequest, - GetAddressesRequestParams, - GetKvRecordsData, - GetKvRecordsRequestParams, - KeyPair, - RemoveKvRecordsRequestParams, - SignData, - SignRequestParams, -} from './types'; -import { getP256KeyPair, getP256KeyPairFromPub, randomBytes } from './util'; +import { buildSaveClientFn } from './api/utilities' +import { BASE_URL, DEFAULT_ACTIVE_WALLETS, EMPTY_WALLET_UID, getFwVersionConst } from './constants' +import { addKvRecords, connect, fetchActiveWallet, fetchEncData, getAddresses, getKvRecords, pair, removeKvRecords, sign } from './functions/index' +import { buildRetryWrapper } from './shared/functions' +import { getPubKeyBytes } from './shared/utilities' +import { validateEphemeralPub } from './shared/validators' +import type { ActiveWallets, AddKvRecordsRequestParams, FetchEncDataRequest, GetAddressesRequestParams, GetKvRecordsData, GetKvRecordsRequestParams, KeyPair, RemoveKvRecordsRequestParams, SignData, SignRequestParams } from './types' +import { getP256KeyPair, getP256KeyPairFromPub, randomBytes } from './util' /** * `Client` is a class-based interface for managing a Lattice device. @@ -45,411 +19,374 @@ import { getP256KeyPair, getP256KeyPairFromPub, randomBytes } from './util'; * */ export class Client { - /** Is the Lattice paired with this Client. */ - public isPaired: boolean; - /** The time to wait for a response before cancelling. */ - public timeout: number; - /** The base of the remote url to which the SDK sends requests. */ - public baseUrl: string; - /** @internal The `baseUrl` plus the `deviceId`. Set in {@link connect} when it completes successfully. */ - public url?: string; - /** `name` is a human readable string associated with this app on the Lattice */ - private name: string; - private key: KeyPair; - /**`privKey` is used to generate a keypair, which is used for maintaining an encrypted messaging channel with the target Lattice */ - private privKey: Buffer | string; - private retryCount: number; - private fwVersion?: Buffer; - private skipRetryOnWrongWallet: boolean; - /** Temporary secret that is generated by the Lattice device */ - private _ephemeralPub!: KeyPair; - /** The ID of the connected Lattice */ - private deviceId?: string; - /** Information about the current wallet. Should be null unless we know a wallet is present */ - public activeWallets: ActiveWallets; - /** A wrapper function for handling retries and injecting the {@link Client} class */ - private retryWrapper: (fn: any, params?: any) => Promise; - /** Function to set the stored client data */ - private setStoredClient?: (clientData: string | null) => Promise; + /** Is the Lattice paired with this Client. */ + public isPaired: boolean + /** The time to wait for a response before cancelling. */ + public timeout: number + /** The base of the remote url to which the SDK sends requests. */ + public baseUrl: string + /** @internal The `baseUrl` plus the `deviceId`. Set in {@link connect} when it completes successfully. */ + public url?: string + /** `name` is a human readable string associated with this app on the Lattice */ + private name: string + private key: KeyPair + /**`privKey` is used to generate a keypair, which is used for maintaining an encrypted messaging channel with the target Lattice */ + private privKey: Buffer | string + private retryCount: number + private fwVersion?: Buffer + private skipRetryOnWrongWallet: boolean + /** Temporary secret that is generated by the Lattice device */ + private _ephemeralPub!: KeyPair + /** The ID of the connected Lattice */ + private deviceId?: string + /** Information about the current wallet. Should be null unless we know a wallet is present */ + public activeWallets: ActiveWallets + /** A wrapper function for handling retries and injecting the {@link Client} class */ + private retryWrapper: (fn: any, params?: any) => Promise + /** Function to set the stored client data */ + private setStoredClient?: (clientData: string | null) => Promise - /** - * @param params - Parameters are passed as an object. - */ - constructor({ - baseUrl, - name, - privKey, - stateData, - timeout, - retryCount, - skipRetryOnWrongWallet, - deviceId, - setStoredClient, - }: { - /** The base URL of the signing server. */ - baseUrl?: string; - /** The name of the client. */ - name?: string; - /** The private key of the client.*/ - privKey?: Buffer | string; - /** Number of times to retry a request if it fails. */ - retryCount?: number; - /** The time to wait for a response before cancelling. */ - timeout?: number; - /** User can pass in previous state data to rehydrate connected session */ - stateData?: string; - /** If true we will not retry if we get a wrong wallet error code */ - skipRetryOnWrongWallet?: boolean; - /** The ID of the connected Lattice */ - deviceId?: string; - /** Function to set the stored client data */ - setStoredClient?: (clientData: string | null) => Promise; - }) { - this.name = name || 'Unknown'; - this.baseUrl = baseUrl || BASE_URL; - this.deviceId = deviceId; - this.isPaired = false; - this.activeWallets = DEFAULT_ACTIVE_WALLETS; - this.timeout = timeout || 60000; - this.retryCount = retryCount || 3; - this.skipRetryOnWrongWallet = skipRetryOnWrongWallet || false; - this.privKey = privKey || randomBytes(32); - this.key = getP256KeyPair(this.privKey); - this.retryWrapper = buildRetryWrapper(this, this.retryCount); - this.setStoredClient = setStoredClient - ? buildSaveClientFn(setStoredClient) - : undefined; + /** + * @param params - Parameters are passed as an object. + */ + constructor({ + baseUrl, + name, + privKey, + stateData, + timeout, + retryCount, + skipRetryOnWrongWallet, + deviceId, + setStoredClient, + }: { + /** The base URL of the signing server. */ + baseUrl?: string + /** The name of the client. */ + name?: string + /** The private key of the client.*/ + privKey?: Buffer | string + /** Number of times to retry a request if it fails. */ + retryCount?: number + /** The time to wait for a response before cancelling. */ + timeout?: number + /** User can pass in previous state data to rehydrate connected session */ + stateData?: string + /** If true we will not retry if we get a wrong wallet error code */ + skipRetryOnWrongWallet?: boolean + /** The ID of the connected Lattice */ + deviceId?: string + /** Function to set the stored client data */ + setStoredClient?: (clientData: string | null) => Promise + }) { + this.name = name || 'Unknown' + this.baseUrl = baseUrl || BASE_URL + this.deviceId = deviceId + this.isPaired = false + this.activeWallets = DEFAULT_ACTIVE_WALLETS + this.timeout = timeout || 60000 + this.retryCount = retryCount || 3 + this.skipRetryOnWrongWallet = skipRetryOnWrongWallet || false + this.privKey = privKey || randomBytes(32) + this.key = getP256KeyPair(this.privKey) + this.retryWrapper = buildRetryWrapper(this, this.retryCount) + this.setStoredClient = setStoredClient ? buildSaveClientFn(setStoredClient) : undefined - /** The user may pass in state data to rehydrate a session that was previously cached */ - if (stateData) { - this.unpackAndApplyStateData(stateData); - } - } + /** The user may pass in state data to rehydrate a session that was previously cached */ + if (stateData) { + this.unpackAndApplyStateData(stateData) + } + } - /** - * Get the public key associated with the client's static keypair. - * The public key is used for identifying the client to the Lattice. - * @internal - * @returns Buffer - */ - public get publicKey() { - return getPubKeyBytes(this.key); - } + /** + * Get the public key associated with the client's static keypair. + * The public key is used for identifying the client to the Lattice. + * @internal + * @returns Buffer + */ + public get publicKey() { + return getPubKeyBytes(this.key) + } - /** - * Get the pairing name for this client instance - */ - public getAppName() { - return this.name; - } + /** + * Get the pairing name for this client instance + */ + public getAppName() { + return this.name + } - /** - * Get the `deviceId` for this client instance - */ - public getDeviceId() { - return this.deviceId; - } + /** + * Get the `deviceId` for this client instance + */ + public getDeviceId() { + return this.deviceId + } - /** - * Get the shared secret, derived via ECDH from the local private key and the ephemeral public key - * @internal - * @returns Buffer - */ - public get sharedSecret() { - // Once every ~256 attempts, we will get a key that starts with a `00` byte, which can lead to - // problems initializing AES if we don't force a 32 byte BE buffer. - return Buffer.from( - this.key.derive(this.ephemeralPub.getPublic()).toArray('be', 32), - ); - } + /** + * Get the shared secret, derived via ECDH from the local private key and the ephemeral public key + * @internal + * @returns Buffer + */ + public get sharedSecret() { + // Once every ~256 attempts, we will get a key that starts with a `00` byte, which can lead to + // problems initializing AES if we don't force a 32 byte BE buffer. + return Buffer.from(this.key.derive(this.ephemeralPub.getPublic()).toArray('be', 32)) + } - /** @internal */ - public get ephemeralPub() { - return this._ephemeralPub; - } + /** @internal */ + public get ephemeralPub() { + return this._ephemeralPub + } - /** @internal */ - public set ephemeralPub(ephemeralPub: KeyPair) { - validateEphemeralPub(ephemeralPub); - this._ephemeralPub = ephemeralPub; - } + /** @internal */ + public set ephemeralPub(ephemeralPub: KeyPair) { + validateEphemeralPub(ephemeralPub) + this._ephemeralPub = ephemeralPub + } - /** - * Attempt to contact a device based on its `deviceId`. The response should include an ephemeral - * public key, which is used to pair with the device in a later request. - * @category Lattice - */ - public async connect(deviceId: string) { - return this.retryWrapper(connect, { id: deviceId }); - } + /** + * Attempt to contact a device based on its `deviceId`. The response should include an ephemeral + * public key, which is used to pair with the device in a later request. + * @category Lattice + */ + public async connect(deviceId: string) { + return this.retryWrapper(connect, { id: deviceId }) + } - /** - * If a pairing secret is provided, `pair` uses it to sign a hash of the public key, name, and - * pairing secret. It then sends the name and signature to the device. If no pairing secret is - * provided, `pair` sends a zero-length name buffer to the device. - * @category Lattice - */ - public async pair(pairingSecret: string) { - return this.retryWrapper(pair, { pairingSecret }); - } + /** + * If a pairing secret is provided, `pair` uses it to sign a hash of the public key, name, and + * pairing secret. It then sends the name and signature to the device. If no pairing secret is + * provided, `pair` sends a zero-length name buffer to the device. + * @category Lattice + */ + public async pair(pairingSecret: string) { + return this.retryWrapper(pair, { pairingSecret }) + } - /** - * Takes a starting path and a number to get the addresses associated with the active wallet. - * @category Lattice - */ - public async getAddresses({ - startPath, - n = 1, - flag = 0, - iterIdx = 0, - }: GetAddressesRequestParams): Promise { - return this.retryWrapper(getAddresses, { startPath, n, flag, iterIdx }); - } + /** + * Takes a starting path and a number to get the addresses associated with the active wallet. + * @category Lattice + */ + public async getAddresses({ startPath, n = 1, flag = 0, iterIdx = 0 }: GetAddressesRequestParams): Promise { + return this.retryWrapper(getAddresses, { startPath, n, flag, iterIdx }) + } - /** - * Builds and sends a request for signing to the Lattice. - * @category Lattice - */ - public async sign({ - data, - currency, - cachedData, - nextCode, - }: SignRequestParams): Promise { - return this.retryWrapper(sign, { data, currency, cachedData, nextCode }); - } + /** + * Builds and sends a request for signing to the Lattice. + * @category Lattice + */ + public async sign({ data, currency, cachedData, nextCode }: SignRequestParams): Promise { + return this.retryWrapper(sign, { data, currency, cachedData, nextCode }) + } - /** - * Fetch the active wallet in the Lattice. - */ - public async fetchActiveWallet(): Promise { - return this.retryWrapper(fetchActiveWallet); - } + /** + * Fetch the active wallet in the Lattice. + */ + public async fetchActiveWallet(): Promise { + return this.retryWrapper(fetchActiveWallet) + } - /** - * Takes in a set of key-value records and sends a request to add them to the Lattice. - * @category Lattice - */ - async addKvRecords({ - type = 0, - records, - caseSensitive = false, - }: AddKvRecordsRequestParams): Promise { - return this.retryWrapper(addKvRecords, { type, records, caseSensitive }); - } + /** + * Takes in a set of key-value records and sends a request to add them to the Lattice. + * @category Lattice + */ + async addKvRecords({ type = 0, records, caseSensitive = false }: AddKvRecordsRequestParams): Promise { + return this.retryWrapper(addKvRecords, { type, records, caseSensitive }) + } - /** - * Fetches a list of key-value records from the Lattice. - * @category Lattice - */ - public async getKvRecords({ - type = 0, - n = 1, - start = 0, - }: GetKvRecordsRequestParams): Promise { - return this.retryWrapper(getKvRecords, { type, n, start }); - } + /** + * Fetches a list of key-value records from the Lattice. + * @category Lattice + */ + public async getKvRecords({ type = 0, n = 1, start = 0 }: GetKvRecordsRequestParams): Promise { + return this.retryWrapper(getKvRecords, { type, n, start }) + } - /** - * Takes in an array of ids and sends a request to remove them from the Lattice. - * @category Lattice - */ - public async removeKvRecords({ - type = 0, - ids = [], - }: RemoveKvRecordsRequestParams): Promise { - return this.retryWrapper(removeKvRecords, { type, ids }); - } + /** + * Takes in an array of ids and sends a request to remove them from the Lattice. + * @category Lattice + */ + public async removeKvRecords({ type = 0, ids = [] }: RemoveKvRecordsRequestParams): Promise { + return this.retryWrapper(removeKvRecords, { type, ids }) + } - /** - * Fetch a record of encrypted data from the Lattice. - * Must specify a data type. Returns a Buffer containing - * data formatted according to the specified type. - * @category Lattice - */ - public async fetchEncryptedData( - params: FetchEncDataRequest, - ): Promise { - return this.retryWrapper(fetchEncData, params); - } + /** + * Fetch a record of encrypted data from the Lattice. + * Must specify a data type. Returns a Buffer containing + * data formatted according to the specified type. + * @category Lattice + */ + public async fetchEncryptedData(params: FetchEncDataRequest): Promise { + return this.retryWrapper(fetchEncData, params) + } - /** Get the active wallet */ - public getActiveWallet() { - if ( - this.activeWallets.external.uid && - !EMPTY_WALLET_UID.equals(this.activeWallets.external.uid) - ) { - return this.activeWallets.external; - } else if ( - this.activeWallets.internal.uid && - !EMPTY_WALLET_UID.equals(this.activeWallets.internal.uid) - ) { - return this.activeWallets.internal; - } else { - return undefined; - } - } + /** Get the active wallet */ + public getActiveWallet() { + if (this.activeWallets.external.uid && !EMPTY_WALLET_UID.equals(this.activeWallets.external.uid)) { + return this.activeWallets.external + } else if (this.activeWallets.internal.uid && !EMPTY_WALLET_UID.equals(this.activeWallets.internal.uid)) { + return this.activeWallets.internal + } else { + return undefined + } + } - /** Check if the user has an active wallet */ - public hasActiveWallet() { - return !!this.getActiveWallet(); - } + /** Check if the user has an active wallet */ + public hasActiveWallet() { + return !!this.getActiveWallet() + } - /** - * Reset the active wallets to empty values. - * @category Device Response - * @internal - */ - public resetActiveWallets() { - this.activeWallets = DEFAULT_ACTIVE_WALLETS; - } + /** + * Reset the active wallets to empty values. + * @category Device Response + * @internal + */ + public resetActiveWallets() { + this.activeWallets = DEFAULT_ACTIVE_WALLETS + } - /** - * Get a JSON string containing state data that can be used to rehydrate a session. Pass the - * contents of this to the constructor as `stateData` to rehydrate. - * @internal - */ - public getStateData() { - return this.packStateData(); - } + /** + * Get a JSON string containing state data that can be used to rehydrate a session. Pass the + * contents of this to the constructor as `stateData` to rehydrate. + * @internal + */ + public getStateData() { + return this.packStateData() + } - /** - * Returns the firmware version constants for the given firmware version. - * @internal - */ - public getFwConstants() { - return getFwVersionConst(this.fwVersion ?? Buffer.alloc(0)); - } + /** + * Returns the firmware version constants for the given firmware version. + * @internal + */ + public getFwConstants() { + return getFwVersionConst(this.fwVersion ?? Buffer.alloc(0)) + } - /** - * `getFwVersion` gets the firmware version of the paired device. - * @internal - */ - public getFwVersion(): { - fix: number; - minor: number; - major: number; - } { - if (this.fwVersion && this.fwVersion.length >= 3) { - return { - fix: this.fwVersion[0], - minor: this.fwVersion[1], - major: this.fwVersion[2], - }; - } - return { fix: 0, minor: 0, major: 0 }; - } + /** + * `getFwVersion` gets the firmware version of the paired device. + * @internal + */ + public getFwVersion(): { + fix: number + minor: number + major: number + } { + if (this.fwVersion && this.fwVersion.length >= 3) { + return { + fix: this.fwVersion[0], + minor: this.fwVersion[1], + major: this.fwVersion[2], + } + } + return { fix: 0, minor: 0, major: 0 } + } - /** - * Handles the mutation of Client state in the primary functions. - */ - public mutate({ - deviceId, - ephemeralPub, - url, - isPaired, - fwVersion, - activeWallets, - }: { - deviceId?: string; - ephemeralPub?: KeyPair; - url?: string; - isPaired?: boolean; - fwVersion?: Buffer; - activeWallets?: ActiveWallets; - }) { - if (deviceId !== undefined) this.deviceId = deviceId; - if (ephemeralPub !== undefined) this.ephemeralPub = ephemeralPub; - if (url !== undefined) this.url = url; - if (isPaired !== undefined) this.isPaired = isPaired; - if (fwVersion !== undefined) this.fwVersion = fwVersion; - if (activeWallets !== undefined) this.activeWallets = activeWallets; + /** + * Handles the mutation of Client state in the primary functions. + */ + public mutate({ + deviceId, + ephemeralPub, + url, + isPaired, + fwVersion, + activeWallets, + }: { + deviceId?: string + ephemeralPub?: KeyPair + url?: string + isPaired?: boolean + fwVersion?: Buffer + activeWallets?: ActiveWallets + }) { + if (deviceId !== undefined) this.deviceId = deviceId + if (ephemeralPub !== undefined) this.ephemeralPub = ephemeralPub + if (url !== undefined) this.url = url + if (isPaired !== undefined) this.isPaired = isPaired + if (fwVersion !== undefined) this.fwVersion = fwVersion + if (activeWallets !== undefined) this.activeWallets = activeWallets - if (this.setStoredClient) { - this.setStoredClient(this.getStateData()); - } - } + if (this.setStoredClient) { + this.setStoredClient(this.getStateData()) + } + } - /** - * Return JSON-stringified version of state data. Can be used to rehydrate an SDK session without - * reconnecting to the target Lattice. - * @internal - */ - private packStateData() { - try { - const data = { - activeWallets: { - internal: { - uid: this.activeWallets.internal.uid?.toString('hex'), - name: this.activeWallets.internal.name?.toString(), - capabilities: this.activeWallets.internal.capabilities, - }, - external: { - uid: this.activeWallets.external.uid?.toString('hex'), - name: this.activeWallets.external.name?.toString(), - capabilities: this.activeWallets.external.capabilities, - }, - }, - ephemeralPub: this.ephemeralPub?.getPublic()?.encode('hex', false), - fwVersion: this.fwVersion?.toString('hex'), - deviceId: this.deviceId, - name: this.name, - baseUrl: this.baseUrl, - privKey: this.privKey.toString('hex'), - retryCount: this.retryCount, - timeout: this.timeout, - }; - return JSON.stringify(data); - } catch (err) { - console.warn('Could not pack state data:', err); - return null; - } - } + /** + * Return JSON-stringified version of state data. Can be used to rehydrate an SDK session without + * reconnecting to the target Lattice. + * @internal + */ + private packStateData() { + try { + const data = { + activeWallets: { + internal: { + uid: this.activeWallets.internal.uid?.toString('hex'), + name: this.activeWallets.internal.name?.toString(), + capabilities: this.activeWallets.internal.capabilities, + }, + external: { + uid: this.activeWallets.external.uid?.toString('hex'), + name: this.activeWallets.external.name?.toString(), + capabilities: this.activeWallets.external.capabilities, + }, + }, + ephemeralPub: this.ephemeralPub?.getPublic()?.encode('hex', false), + fwVersion: this.fwVersion?.toString('hex'), + deviceId: this.deviceId, + name: this.name, + baseUrl: this.baseUrl, + privKey: this.privKey.toString('hex'), + retryCount: this.retryCount, + timeout: this.timeout, + } + return JSON.stringify(data) + } catch (err) { + console.warn('Could not pack state data:', err) + return null + } + } - /** - * Unpack a JSON-stringified version of state data and apply it to state. This will allow us to - * rehydrate an old session. - * @internal - */ - private unpackAndApplyStateData(data: string) { - try { - const unpacked = JSON.parse(data); - // Attempt to parse the data - const internalWallet = { - uid: Buffer.from(unpacked.activeWallets.internal.uid, 'hex'), - name: unpacked.activeWallets.internal.name - ? Buffer.from(unpacked.activeWallets.internal.name) - : null, - capabilities: unpacked.activeWallets.internal.capabilities, - external: false, - }; - const externalWallet = { - uid: Buffer.from(unpacked.activeWallets.external.uid, 'hex'), - name: unpacked.activeWallets.external.name - ? Buffer.from(unpacked.activeWallets.external.name) - : null, - capabilities: unpacked.activeWallets.external.capabilities, - external: true, - }; - const ephemeralPubBytes = Buffer.from(unpacked.ephemeralPub, 'hex'); - const fwVersionBytes = Buffer.from(unpacked.fwVersion, 'hex'); - const privKeyBytes = Buffer.from(unpacked.privKey, 'hex'); - // Apply unpacked params - this.activeWallets.internal = internalWallet; - this.activeWallets.external = externalWallet; - this.ephemeralPub = getP256KeyPairFromPub(ephemeralPubBytes); - this.fwVersion = fwVersionBytes; - this.deviceId = unpacked.deviceId; - this.name = unpacked.name; - this.baseUrl = unpacked.baseUrl; - this.url = `${this.baseUrl}/${this.deviceId}`; - this.privKey = privKeyBytes; - this.key = getP256KeyPair(this.privKey); - this.retryCount = unpacked.retryCount; - this.timeout = unpacked.timeout; - this.retryWrapper = buildRetryWrapper(this, this.retryCount); - } catch (err) { - console.warn('Could not apply state data:', err); - } - } + /** + * Unpack a JSON-stringified version of state data and apply it to state. This will allow us to + * rehydrate an old session. + * @internal + */ + private unpackAndApplyStateData(data: string) { + try { + const unpacked = JSON.parse(data) + // Attempt to parse the data + const internalWallet = { + uid: Buffer.from(unpacked.activeWallets.internal.uid, 'hex'), + name: unpacked.activeWallets.internal.name ? Buffer.from(unpacked.activeWallets.internal.name) : null, + capabilities: unpacked.activeWallets.internal.capabilities, + external: false, + } + const externalWallet = { + uid: Buffer.from(unpacked.activeWallets.external.uid, 'hex'), + name: unpacked.activeWallets.external.name ? Buffer.from(unpacked.activeWallets.external.name) : null, + capabilities: unpacked.activeWallets.external.capabilities, + external: true, + } + const ephemeralPubBytes = Buffer.from(unpacked.ephemeralPub, 'hex') + const fwVersionBytes = Buffer.from(unpacked.fwVersion, 'hex') + const privKeyBytes = Buffer.from(unpacked.privKey, 'hex') + // Apply unpacked params + this.activeWallets.internal = internalWallet + this.activeWallets.external = externalWallet + this.ephemeralPub = getP256KeyPairFromPub(ephemeralPubBytes) + this.fwVersion = fwVersionBytes + this.deviceId = unpacked.deviceId + this.name = unpacked.name + this.baseUrl = unpacked.baseUrl + this.url = `${this.baseUrl}/${this.deviceId}` + this.privKey = privKeyBytes + this.key = getP256KeyPair(this.privKey) + this.retryCount = unpacked.retryCount + this.timeout = unpacked.timeout + this.retryWrapper = buildRetryWrapper(this, this.retryCount) + } catch (err) { + console.warn('Could not apply state data:', err) + } + } } diff --git a/packages/sdk/src/constants.ts b/packages/sdk/src/constants.ts index ef60fe73..214ad99c 100644 --- a/packages/sdk/src/constants.ts +++ b/packages/sdk/src/constants.ts @@ -1,590 +1,526 @@ -import { - LatticeEncDataSchema, - LatticeGetAddressesFlag, - LatticeSignBlsDst, - LatticeSignCurve, - LatticeSignEncoding, - LatticeSignHash, -} from './protocol/latticeConstants'; -import type { - ActiveWallets, - FirmwareArr, - FirmwareConstants, - WalletPath, -} from './types/index.js'; +import { LatticeEncDataSchema, LatticeGetAddressesFlag, LatticeSignBlsDst, LatticeSignCurve, LatticeSignEncoding, LatticeSignHash } from './protocol/latticeConstants' +import type { ActiveWallets, FirmwareArr, FirmwareConstants, WalletPath } from './types/index.js' /** * Externally exported constants used for building requests * @public */ export const EXTERNAL = { - // Optional flags for `getAddresses` - GET_ADDR_FLAGS: { - SECP256K1_PUB: LatticeGetAddressesFlag.secp256k1Pubkey, - ED25519_PUB: LatticeGetAddressesFlag.ed25519Pubkey, - BLS12_381_G1_PUB: LatticeGetAddressesFlag.bls12_381Pubkey, - SECP256K1_XPUB: LatticeGetAddressesFlag.secp256k1Xpub, - }, - // Options for building general signing requests - SIGNING: { - HASHES: { - NONE: LatticeSignHash.none, - KECCAK256: LatticeSignHash.keccak256, - SHA256: LatticeSignHash.sha256, - }, - CURVES: { - SECP256K1: LatticeSignCurve.secp256k1, - ED25519: LatticeSignCurve.ed25519, - BLS12_381_G2: LatticeSignCurve.bls12_381, - }, - ENCODINGS: { - NONE: LatticeSignEncoding.none, - SOLANA: LatticeSignEncoding.solana, - EVM: LatticeSignEncoding.evm, - ETH_DEPOSIT: LatticeSignEncoding.eth_deposit, - EIP7702_AUTH: LatticeSignEncoding.eip7702_auth, - EIP7702_AUTH_LIST: LatticeSignEncoding.eip7702_auth_list, - }, - BLS_DST: { - BLS_DST_NUL: LatticeSignBlsDst.NUL, - BLS_DST_POP: LatticeSignBlsDst.POP, - }, - }, - // Options for exporting encrypted data - ENC_DATA: { - SCHEMAS: { - BLS_KEYSTORE_EIP2335_PBKDF_V4: LatticeEncDataSchema.eip2335, - }, - }, - ETH_CONSENSUS_SPEC: { - NETWORKS: { - MAINNET_GENESIS: { - networkName: 'mainnet', - forkVersion: Buffer.alloc(4), - // Empty root because there were no validators at genesis - validatorsRoot: Buffer.alloc(32), - }, - }, - DOMAINS: { - DEPOSIT: Buffer.from('03000000', 'hex'), - VOLUNTARY_EXIT: Buffer.from('04000000', 'hex'), - }, - }, -} as const; + // Optional flags for `getAddresses` + GET_ADDR_FLAGS: { + SECP256K1_PUB: LatticeGetAddressesFlag.secp256k1Pubkey, + ED25519_PUB: LatticeGetAddressesFlag.ed25519Pubkey, + BLS12_381_G1_PUB: LatticeGetAddressesFlag.bls12_381Pubkey, + SECP256K1_XPUB: LatticeGetAddressesFlag.secp256k1Xpub, + }, + // Options for building general signing requests + SIGNING: { + HASHES: { + NONE: LatticeSignHash.none, + KECCAK256: LatticeSignHash.keccak256, + SHA256: LatticeSignHash.sha256, + }, + CURVES: { + SECP256K1: LatticeSignCurve.secp256k1, + ED25519: LatticeSignCurve.ed25519, + BLS12_381_G2: LatticeSignCurve.bls12_381, + }, + ENCODINGS: { + NONE: LatticeSignEncoding.none, + SOLANA: LatticeSignEncoding.solana, + EVM: LatticeSignEncoding.evm, + ETH_DEPOSIT: LatticeSignEncoding.eth_deposit, + EIP7702_AUTH: LatticeSignEncoding.eip7702_auth, + EIP7702_AUTH_LIST: LatticeSignEncoding.eip7702_auth_list, + }, + BLS_DST: { + BLS_DST_NUL: LatticeSignBlsDst.NUL, + BLS_DST_POP: LatticeSignBlsDst.POP, + }, + }, + // Options for exporting encrypted data + ENC_DATA: { + SCHEMAS: { + BLS_KEYSTORE_EIP2335_PBKDF_V4: LatticeEncDataSchema.eip2335, + }, + }, + ETH_CONSENSUS_SPEC: { + NETWORKS: { + MAINNET_GENESIS: { + networkName: 'mainnet', + forkVersion: Buffer.alloc(4), + // Empty root because there were no validators at genesis + validatorsRoot: Buffer.alloc(32), + }, + }, + DOMAINS: { + DEPOSIT: Buffer.from('03000000', 'hex'), + VOLUNTARY_EXIT: Buffer.from('04000000', 'hex'), + }, + }, +} as const //=============================== // INTERNAL CONSTANTS //=============================== /** @internal */ const addressSizes = { - BTC: 20, // 20 byte pubkeyhash - ETH: 20, // 20 byte address not including 0x prefix -} as const; + BTC: 20, // 20 byte pubkeyhash + ETH: 20, // 20 byte address not including 0x prefix +} as const /** @internal */ const CURRENCIES = { - ETH: 'ETH', - BTC: 'BTC', - ETH_MSG: 'ETH_MSG', -} as const; + ETH: 'ETH', + BTC: 'BTC', + ETH_MSG: 'ETH_MSG', +} as const /** @internal */ // THIS NEEDS TO BE A PROTOCOL CONSTANT TOO const signingSchema = { - BTC_TRANSFER: 0, - ETH_TRANSFER: 1, - ERC20_TRANSFER: 2, - ETH_MSG: 3, - EXTRA_DATA: 4, - GENERAL_SIGNING: 5, -} as const; + BTC_TRANSFER: 0, + ETH_TRANSFER: 1, + ERC20_TRANSFER: 2, + ETH_MSG: 3, + EXTRA_DATA: 4, + GENERAL_SIGNING: 5, +} as const /** @internal */ -const HARDENED_OFFSET = 0x80000000; // Hardened offset +const HARDENED_OFFSET = 0x80000000 // Hardened offset /** @internal */ const BIP_CONSTANTS = { - PURPOSES: { - ETH: HARDENED_OFFSET + 44, - BTC_LEGACY: HARDENED_OFFSET + 44, - BTC_WRAPPED_SEGWIT: HARDENED_OFFSET + 49, - BTC_SEGWIT: HARDENED_OFFSET + 84, - }, - COINS: { - ETH: HARDENED_OFFSET + 60, - BTC: HARDENED_OFFSET, - BTC_TESTNET: HARDENED_OFFSET + 1, - }, -} as const; + PURPOSES: { + ETH: HARDENED_OFFSET + 44, + BTC_LEGACY: HARDENED_OFFSET + 44, + BTC_WRAPPED_SEGWIT: HARDENED_OFFSET + 49, + BTC_SEGWIT: HARDENED_OFFSET + 84, + }, + COINS: { + ETH: HARDENED_OFFSET + 60, + BTC: HARDENED_OFFSET, + BTC_TESTNET: HARDENED_OFFSET + 1, + }, +} as const /** @internal For all HSM-bound requests */ -const REQUEST_TYPE_BYTE = 0x02; +const REQUEST_TYPE_BYTE = 0x02 /** @internal */ -const VERSION_BYTE = 1; +const VERSION_BYTE = 1 /** @internal ChainId value to signify larger chainID is in data buffer */ -const HANDLE_LARGER_CHAIN_ID = 255; +const HANDLE_LARGER_CHAIN_ID = 255 /** @internal Max number of bytes to contain larger chainID in data buffer */ -const MAX_CHAIN_ID_BYTES = 8; +const MAX_CHAIN_ID_BYTES = 8 /** @internal */ -const BASE_URL = 'https://signing.gridpl.us'; +const BASE_URL = 'https://signing.gridpl.us' /** @internal */ const EIP712_ABI_LATTICE_FW_TYPE_MAP = { - address: 1, - bool: 2, - uint8: 3, - uint16: 4, - uint24: 5, - uint32: 6, - uint40: 7, - uint48: 8, - uint56: 9, - uint64: 10, - uint72: 11, - uint80: 12, - uint88: 13, - uint96: 14, - uint104: 15, - uint112: 16, - uint120: 17, - uint128: 18, - uint136: 19, - uint144: 20, - uint152: 21, - uint160: 22, - uint168: 23, - uint176: 24, - uint184: 25, - uint192: 26, - uint200: 27, - uint208: 28, - uint216: 29, - uint224: 30, - uint232: 31, - uint240: 32, - uint248: 33, - uint256: 34, - int8: 35, - int16: 36, - int24: 37, - int32: 38, - int40: 39, - int48: 40, - int56: 41, - int64: 42, - int72: 43, - int80: 44, - int88: 45, - int96: 46, - int104: 47, - int112: 48, - int120: 49, - int128: 50, - int136: 51, - int144: 52, - int152: 53, - int160: 54, - int168: 55, - int176: 56, - int184: 57, - int192: 58, - int200: 59, - int208: 60, - int216: 61, - int224: 62, - int232: 63, - int240: 64, - int248: 65, - int256: 66, - uint: 67, - bytes1: 69, - bytes2: 70, - bytes3: 71, - bytes4: 72, - bytes5: 73, - bytes6: 74, - bytes7: 75, - bytes8: 76, - bytes9: 77, - bytes10: 78, - bytes11: 79, - bytes12: 80, - bytes13: 81, - bytes14: 82, - bytes15: 83, - bytes16: 84, - bytes17: 85, - bytes18: 86, - bytes19: 87, - bytes20: 88, - bytes21: 89, - bytes22: 90, - bytes23: 91, - bytes24: 92, - bytes25: 93, - bytes26: 94, - bytes27: 95, - bytes28: 96, - bytes29: 97, - bytes30: 98, - bytes31: 99, - bytes32: 100, - bytes: 101, - string: 102, -}; + address: 1, + bool: 2, + uint8: 3, + uint16: 4, + uint24: 5, + uint32: 6, + uint40: 7, + uint48: 8, + uint56: 9, + uint64: 10, + uint72: 11, + uint80: 12, + uint88: 13, + uint96: 14, + uint104: 15, + uint112: 16, + uint120: 17, + uint128: 18, + uint136: 19, + uint144: 20, + uint152: 21, + uint160: 22, + uint168: 23, + uint176: 24, + uint184: 25, + uint192: 26, + uint200: 27, + uint208: 28, + uint216: 29, + uint224: 30, + uint232: 31, + uint240: 32, + uint248: 33, + uint256: 34, + int8: 35, + int16: 36, + int24: 37, + int32: 38, + int40: 39, + int48: 40, + int56: 41, + int64: 42, + int72: 43, + int80: 44, + int88: 45, + int96: 46, + int104: 47, + int112: 48, + int120: 49, + int128: 50, + int136: 51, + int144: 52, + int152: 53, + int160: 54, + int168: 55, + int176: 56, + int184: 57, + int192: 58, + int200: 59, + int208: 60, + int216: 61, + int224: 62, + int232: 63, + int240: 64, + int248: 65, + int256: 66, + uint: 67, + bytes1: 69, + bytes2: 70, + bytes3: 71, + bytes4: 72, + bytes5: 73, + bytes6: 74, + bytes7: 75, + bytes8: 76, + bytes9: 77, + bytes10: 78, + bytes11: 79, + bytes12: 80, + bytes13: 81, + bytes14: 82, + bytes15: 83, + bytes16: 84, + bytes17: 85, + bytes18: 86, + bytes19: 87, + bytes20: 88, + bytes21: 89, + bytes22: 90, + bytes23: 91, + bytes24: 92, + bytes25: 93, + bytes26: 94, + bytes27: 95, + bytes28: 96, + bytes29: 97, + bytes30: 98, + bytes31: 99, + bytes32: 100, + bytes: 101, + string: 102, +} /** @internal */ const ETH_ABI_LATTICE_FW_TYPE_MAP = { - ...EIP712_ABI_LATTICE_FW_TYPE_MAP, - tuple1: 103, - tuple2: 104, - tuple3: 105, - tuple4: 106, - tuple5: 107, - tuple6: 108, - tuple7: 109, - tuple8: 110, - tuple9: 111, - tuple10: 112, - tuple11: 113, - tuple12: 114, - tuple13: 115, - tuple14: 116, - tuple15: 117, - tuple16: 118, - tuple17: 119, // Firmware currently cannot support tuples larger than this -}; + ...EIP712_ABI_LATTICE_FW_TYPE_MAP, + tuple1: 103, + tuple2: 104, + tuple3: 105, + tuple4: 106, + tuple5: 107, + tuple6: 108, + tuple7: 109, + tuple8: 110, + tuple9: 111, + tuple10: 112, + tuple11: 113, + tuple12: 114, + tuple13: 115, + tuple14: 116, + tuple15: 117, + tuple16: 118, + tuple17: 119, // Firmware currently cannot support tuples larger than this +} /** @internal */ const ethMsgProtocol = { - SIGN_PERSONAL: { - str: 'signPersonal', - enumIdx: 0, // Enum index of this protocol in Lattice firmware - }, - TYPED_DATA: { - str: 'typedData', - enumIdx: 1, - rawDataMaxLen: 1629, // Max size of raw data payload in bytes - typeCodes: EIP712_ABI_LATTICE_FW_TYPE_MAP, // Enum indices of data types in Lattice firmware - }, -}; + SIGN_PERSONAL: { + str: 'signPersonal', + enumIdx: 0, // Enum index of this protocol in Lattice firmware + }, + TYPED_DATA: { + str: 'typedData', + enumIdx: 1, + rawDataMaxLen: 1629, // Max size of raw data payload in bytes + typeCodes: EIP712_ABI_LATTICE_FW_TYPE_MAP, // Enum indices of data types in Lattice firmware + }, +} /** @internal */ function getFwVersionConst(v: Buffer): FirmwareConstants { - const c: any = { - extraDataFrameSz: 0, - extraDataMaxFrames: 0, - genericSigning: {} as any, - }; - function gte(v: Buffer, exp: FirmwareArr): boolean { - // Note that `v` fields come in as [fix|minor|major] - return ( - v[2] > exp[0] || - (v[2] === exp[0] && v[1] > exp[1]) || - (v[2] === exp[0] && v[1] === exp[1] && v[0] > exp[2]) || - (v[2] === exp[0] && v[1] === exp[1] && v[0] === exp[2]) - ); - } - // Very old legacy versions do not give a version number - const legacy = v.length === 0; - - // BASE FIELDS - //-------------------------------------- - - // Various size constants have changed on the firmware side over time and - // are captured here - if (!legacy && gte(v, [0, 10, 4])) { - // >=0.10.3 - c.reqMaxDataSz = 1678; - c.ethMaxGasPrice = 20000000000000; // 20000 gwei - c.addrFlagsAllowed = true; - } else if (!legacy && gte(v, [0, 10, 0])) { - // >=0.10.0 - c.reqMaxDataSz = 1678; - c.ethMaxGasPrice = 20000000000000; // 20000 gwei - c.addrFlagsAllowed = true; - } else { - // Legacy or <0.10.0 - c.reqMaxDataSz = 1152; - c.ethMaxGasPrice = 500000000000; // 500 gwei - c.addrFlagsAllowed = false; - } - // These transformations apply to all versions. The subtraction - // of 128 bytes accounts for metadata and is for legacy reasons. - // For all modern versions, these are 1550 bytes. - // NOTE: Non-legacy ETH txs (e.g. EIP1559) will shrink - // this number. - // See `ETH_BASE_TX_MAX_DATA_SZ` and `ETH_MAX_BASE_MSG_SZ` in firmware - c.ethMaxDataSz = c.reqMaxDataSz - 128; - c.ethMaxMsgSz = c.ethMaxDataSz; - // Max number of params in an EIP712 type. This was added to firmware - // to avoid blowing stack size. - c.eip712MaxTypeParams = 18; - - // ----- - // EXTRA FIELDS ADDED IN LATER FIRMWARE VERSIONS - // ----- - - // --- V0.10.X --- - // V0.10.4 introduced the ability to send signing requests over multiple - // data frames (i.e. in multiple requests) - if (!legacy && gte(v, [0, 10, 4])) { - c.extraDataFrameSz = 1500; // 1500 bytes per frame of extraData allowed - c.extraDataMaxFrames = 1; // 1 frame of extraData allowed - } - // V0.10.5 added the ability to use flexible address path sizes, which - // changes the `getAddress` API. It also added support for EIP712 - if (!legacy && gte(v, [0, 10, 5])) { - c.varAddrPathSzAllowed = true; - c.eip712Supported = true; - } - // V0.10.8 allows a user to sign a prehashed transaction if the payload - // is too big - if (!legacy && gte(v, [0, 10, 8])) { - c.prehashAllowed = true; - } - // V0.10.10 allows a user to sign a prehashed ETH message if payload too big - if (!legacy && gte(v, [0, 10, 10])) { - c.ethMsgPreHashAllowed = true; - } - - // --- 0.11.X --- - // V0.11.0 allows new ETH transaction types - if (!legacy && gte(v, [0, 11, 0])) { - c.allowedEthTxTypes = [ - 1, // eip2930 - 2, // eip1559 - ]; - // This version added extra data fields to the ETH tx - c.ethMaxDataSz -= 10; - c.ethMaxMsgSz = c.ethMaxDataSz; - } - // V0.11.2 changed how messages are displayed. For personal_sign messages - // we now write the header (`Signer: `) into the main body of the screen. - // This means personal sign message max size is slightly smaller than for - // EIP712 messages because in the latter case there is no header - // Note that `` has max size of 62 bytes (`m/X/X/...`) - if (!legacy && gte(v, [0, 11, 2])) { - c.personalSignHeaderSz = 72; - } - - // --- V0.12.X --- - // V0.12.0 added an API for creating, removing, and fetching key-val file - // records. For the purposes of this SDK, we only hook into one type of kv - // file: address names. - if (!legacy && gte(v, [0, 12, 0])) { - c.kvActionsAllowed = true; - c.kvKeyMaxStrSz = 63; - c.kvValMaxStrSz = 63; - c.kvActionMaxNum = 10; - c.kvRemoveMaxNum = 100; - } - - // --- V0.13.X --- - // V0.13.0 added native segwit addresses and fixed a bug in exporting - // legacy bitcoin addresses - if (!legacy && gte(v, [0, 13, 0])) { - c.allowBtcLegacyAndSegwitAddrs = true; - // Random address to be used when trying to deploy a contract - c.contractDeployKey = '0x08002e0fec8e6acf00835f43c9764f7364fa3f42'; - } - - // --- V0.14.X --- - // V0.14.0 added support for a more robust API around ABI definitions - // and generic signing functionality - if (!legacy && gte(v, [0, 14, 0])) { - // Size of `category` buffer. Inclusive of null terminator byte. - c.abiCategorySz = 32; - c.abiMaxRmv = 200; // Max number of ABI defs that can be removed with - // a single request - // See `sizeof(GenericSigningRequest_t)` in firmware - c.genericSigning.baseReqSz = 1552; - // See `GENERIC_SIGNING_BASE_MSG_SZ` in firmware - c.genericSigning.baseDataSz = 1519; - c.genericSigning.hashTypes = EXTERNAL.SIGNING.HASHES; - c.genericSigning.curveTypes = EXTERNAL.SIGNING.CURVES; - c.genericSigning.encodingTypes = { - NONE: EXTERNAL.SIGNING.ENCODINGS.NONE, - SOLANA: EXTERNAL.SIGNING.ENCODINGS.SOLANA, - }; - // Supported flags for `getAddresses` - c.getAddressFlags = [ - EXTERNAL.GET_ADDR_FLAGS.ED25519_PUB, - EXTERNAL.GET_ADDR_FLAGS.SECP256K1_PUB, - ]; - // We updated the max number of params in EIP712 types - c.eip712MaxTypeParams = 36; - } - // DEPRECATED - // V0.14.1 Added the Terra decoder - // if (!legacy && gte(v, [0, 14, 1])) { - // c.genericSigning.encodingTypes.TERRA = EXTERNAL.SIGNING.ENCODINGS.TERRA; - // } - - // --- V0.15.X --- - // V0.15.0 added an EVM decoder and removed the legacy ETH signing pathway - if (!legacy && gte(v, [0, 15, 0])) { - c.genericSigning.encodingTypes.EVM = EXTERNAL.SIGNING.ENCODINGS.EVM; - // We now use the general signing data field as the base - // Note that we have NOT removed the ETH_MSG type so we should - // not change ethMaxMsgSz - c.ethMaxDataSz = 1550 - 31; - // Max buffer size for get/add decoder requests - c.maxDecoderBufSz = 1600; - // Code used to write a calldata decoder - c.genericSigning.calldataDecoding = { - reserved: 2895728, - maxSz: 1024, - }; - } - - // --- V0.17.X --- - // V0.17.0 added support for BLS12-381-G1 pubkeys and G2 sigs - if (!legacy && gte(v, [0, 17, 0])) { - c.getAddressFlags.push(EXTERNAL.GET_ADDR_FLAGS.BLS12_381_G1_PUB); - c.genericSigning.encodingTypes.ETH_DEPOSIT = - EXTERNAL.SIGNING.ENCODINGS.ETH_DEPOSIT; - } - - // --- V0.18.X --- - // V0.18.0 added support for EIP7702 signing - // TODO: update patch version when this is released - if (!legacy && gte(v, [0, 18, 0])) { - c.genericSigning.encodingTypes = { - ...c.genericSigning.encodingTypes, - EIP7702_AUTH: EXTERNAL.SIGNING.ENCODINGS.EIP7702_AUTH, - EIP7702_AUTH_LIST: EXTERNAL.SIGNING.ENCODINGS.EIP7702_AUTH_LIST, - }; - } - - return c; + const c: any = { + extraDataFrameSz: 0, + extraDataMaxFrames: 0, + genericSigning: {} as any, + } + function gte(v: Buffer, exp: FirmwareArr): boolean { + // Note that `v` fields come in as [fix|minor|major] + return v[2] > exp[0] || (v[2] === exp[0] && v[1] > exp[1]) || (v[2] === exp[0] && v[1] === exp[1] && v[0] > exp[2]) || (v[2] === exp[0] && v[1] === exp[1] && v[0] === exp[2]) + } + // Very old legacy versions do not give a version number + const legacy = v.length === 0 + + // BASE FIELDS + //-------------------------------------- + + // Various size constants have changed on the firmware side over time and + // are captured here + if (!legacy && gte(v, [0, 10, 4])) { + // >=0.10.3 + c.reqMaxDataSz = 1678 + c.ethMaxGasPrice = 20000000000000 // 20000 gwei + c.addrFlagsAllowed = true + } else if (!legacy && gte(v, [0, 10, 0])) { + // >=0.10.0 + c.reqMaxDataSz = 1678 + c.ethMaxGasPrice = 20000000000000 // 20000 gwei + c.addrFlagsAllowed = true + } else { + // Legacy or <0.10.0 + c.reqMaxDataSz = 1152 + c.ethMaxGasPrice = 500000000000 // 500 gwei + c.addrFlagsAllowed = false + } + // These transformations apply to all versions. The subtraction + // of 128 bytes accounts for metadata and is for legacy reasons. + // For all modern versions, these are 1550 bytes. + // NOTE: Non-legacy ETH txs (e.g. EIP1559) will shrink + // this number. + // See `ETH_BASE_TX_MAX_DATA_SZ` and `ETH_MAX_BASE_MSG_SZ` in firmware + c.ethMaxDataSz = c.reqMaxDataSz - 128 + c.ethMaxMsgSz = c.ethMaxDataSz + // Max number of params in an EIP712 type. This was added to firmware + // to avoid blowing stack size. + c.eip712MaxTypeParams = 18 + + // ----- + // EXTRA FIELDS ADDED IN LATER FIRMWARE VERSIONS + // ----- + + // --- V0.10.X --- + // V0.10.4 introduced the ability to send signing requests over multiple + // data frames (i.e. in multiple requests) + if (!legacy && gte(v, [0, 10, 4])) { + c.extraDataFrameSz = 1500 // 1500 bytes per frame of extraData allowed + c.extraDataMaxFrames = 1 // 1 frame of extraData allowed + } + // V0.10.5 added the ability to use flexible address path sizes, which + // changes the `getAddress` API. It also added support for EIP712 + if (!legacy && gte(v, [0, 10, 5])) { + c.varAddrPathSzAllowed = true + c.eip712Supported = true + } + // V0.10.8 allows a user to sign a prehashed transaction if the payload + // is too big + if (!legacy && gte(v, [0, 10, 8])) { + c.prehashAllowed = true + } + // V0.10.10 allows a user to sign a prehashed ETH message if payload too big + if (!legacy && gte(v, [0, 10, 10])) { + c.ethMsgPreHashAllowed = true + } + + // --- 0.11.X --- + // V0.11.0 allows new ETH transaction types + if (!legacy && gte(v, [0, 11, 0])) { + c.allowedEthTxTypes = [ + 1, // eip2930 + 2, // eip1559 + ] + // This version added extra data fields to the ETH tx + c.ethMaxDataSz -= 10 + c.ethMaxMsgSz = c.ethMaxDataSz + } + // V0.11.2 changed how messages are displayed. For personal_sign messages + // we now write the header (`Signer: `) into the main body of the screen. + // This means personal sign message max size is slightly smaller than for + // EIP712 messages because in the latter case there is no header + // Note that `` has max size of 62 bytes (`m/X/X/...`) + if (!legacy && gte(v, [0, 11, 2])) { + c.personalSignHeaderSz = 72 + } + + // --- V0.12.X --- + // V0.12.0 added an API for creating, removing, and fetching key-val file + // records. For the purposes of this SDK, we only hook into one type of kv + // file: address names. + if (!legacy && gte(v, [0, 12, 0])) { + c.kvActionsAllowed = true + c.kvKeyMaxStrSz = 63 + c.kvValMaxStrSz = 63 + c.kvActionMaxNum = 10 + c.kvRemoveMaxNum = 100 + } + + // --- V0.13.X --- + // V0.13.0 added native segwit addresses and fixed a bug in exporting + // legacy bitcoin addresses + if (!legacy && gte(v, [0, 13, 0])) { + c.allowBtcLegacyAndSegwitAddrs = true + // Random address to be used when trying to deploy a contract + c.contractDeployKey = '0x08002e0fec8e6acf00835f43c9764f7364fa3f42' + } + + // --- V0.14.X --- + // V0.14.0 added support for a more robust API around ABI definitions + // and generic signing functionality + if (!legacy && gte(v, [0, 14, 0])) { + // Size of `category` buffer. Inclusive of null terminator byte. + c.abiCategorySz = 32 + c.abiMaxRmv = 200 // Max number of ABI defs that can be removed with + // a single request + // See `sizeof(GenericSigningRequest_t)` in firmware + c.genericSigning.baseReqSz = 1552 + // See `GENERIC_SIGNING_BASE_MSG_SZ` in firmware + c.genericSigning.baseDataSz = 1519 + c.genericSigning.hashTypes = EXTERNAL.SIGNING.HASHES + c.genericSigning.curveTypes = EXTERNAL.SIGNING.CURVES + c.genericSigning.encodingTypes = { + NONE: EXTERNAL.SIGNING.ENCODINGS.NONE, + SOLANA: EXTERNAL.SIGNING.ENCODINGS.SOLANA, + } + // Supported flags for `getAddresses` + c.getAddressFlags = [EXTERNAL.GET_ADDR_FLAGS.ED25519_PUB, EXTERNAL.GET_ADDR_FLAGS.SECP256K1_PUB] + // We updated the max number of params in EIP712 types + c.eip712MaxTypeParams = 36 + } + // DEPRECATED + // V0.14.1 Added the Terra decoder + // if (!legacy && gte(v, [0, 14, 1])) { + // c.genericSigning.encodingTypes.TERRA = EXTERNAL.SIGNING.ENCODINGS.TERRA; + // } + + // --- V0.15.X --- + // V0.15.0 added an EVM decoder and removed the legacy ETH signing pathway + if (!legacy && gte(v, [0, 15, 0])) { + c.genericSigning.encodingTypes.EVM = EXTERNAL.SIGNING.ENCODINGS.EVM + // We now use the general signing data field as the base + // Note that we have NOT removed the ETH_MSG type so we should + // not change ethMaxMsgSz + c.ethMaxDataSz = 1550 - 31 + // Max buffer size for get/add decoder requests + c.maxDecoderBufSz = 1600 + // Code used to write a calldata decoder + c.genericSigning.calldataDecoding = { + reserved: 2895728, + maxSz: 1024, + } + } + + // --- V0.17.X --- + // V0.17.0 added support for BLS12-381-G1 pubkeys and G2 sigs + if (!legacy && gte(v, [0, 17, 0])) { + c.getAddressFlags.push(EXTERNAL.GET_ADDR_FLAGS.BLS12_381_G1_PUB) + c.genericSigning.encodingTypes.ETH_DEPOSIT = EXTERNAL.SIGNING.ENCODINGS.ETH_DEPOSIT + } + + // --- V0.18.X --- + // V0.18.0 added support for EIP7702 signing + // TODO: update patch version when this is released + if (!legacy && gte(v, [0, 18, 0])) { + c.genericSigning.encodingTypes = { + ...c.genericSigning.encodingTypes, + EIP7702_AUTH: EXTERNAL.SIGNING.ENCODINGS.EIP7702_AUTH, + EIP7702_AUTH_LIST: EXTERNAL.SIGNING.ENCODINGS.EIP7702_AUTH_LIST, + } + } + + return c } /** @internal */ -// eslint-disable-next-line no-control-regex -const ASCII_REGEX = /^[\x00-\x7F]+$/; +// biome-ignore lint/suspicious/noControlCharactersInRegex: Intentional - matching ASCII range +const ASCII_REGEX = /^[\u0000-\u007F]+$/ /** @internal */ -const EXTERNAL_NETWORKS_BY_CHAIN_ID_URL = - 'https://gridplus.github.io/chains/chains.json'; +const EXTERNAL_NETWORKS_BY_CHAIN_ID_URL = 'https://gridplus.github.io/chains/chains.json' /** @internal - Max number of addresses to fetch */ -const MAX_ADDR = 10; +const MAX_ADDR = 10 /** @internal */ const NETWORKS_BY_CHAIN_ID = { - 1: { - name: 'ethereum', - baseUrl: 'https://api.etherscan.io', - apiRoute: 'api?module=contract&action=getabi', - }, - 137: { - name: 'polygon', - baseUrl: 'https://api.polygonscan.com', - apiRoute: 'api?module=contract&action=getabi', - }, - 56: { - name: 'binance', - baseUrl: 'https://api.bscscan.com', - apiRoute: 'api?module=contract&action=getabi', - }, - 42220: { - name: 'celo', - baseUrl: 'https://api.celoscan.io', - apiRoute: 'api?module=contract&action=getabi', - }, - 43114: { - name: 'avalanche', - baseUrl: 'https://api.snowtrace.io', - apiRoute: 'api?module=contract&action=getabi', - }, -}; + 1: { + name: 'ethereum', + baseUrl: 'https://api.etherscan.io', + apiRoute: 'api?module=contract&action=getabi', + }, + 137: { + name: 'polygon', + baseUrl: 'https://api.polygonscan.com', + apiRoute: 'api?module=contract&action=getabi', + }, + 56: { + name: 'binance', + baseUrl: 'https://api.bscscan.com', + apiRoute: 'api?module=contract&action=getabi', + }, + 42220: { + name: 'celo', + baseUrl: 'https://api.celoscan.io', + apiRoute: 'api?module=contract&action=getabi', + }, + 43114: { + name: 'avalanche', + baseUrl: 'https://api.snowtrace.io', + apiRoute: 'api?module=contract&action=getabi', + }, +} /** @internal */ -export const EMPTY_WALLET_UID = Buffer.alloc(32); +export const EMPTY_WALLET_UID = Buffer.alloc(32) /** @internal */ export const DEFAULT_ACTIVE_WALLETS: ActiveWallets = { - internal: { - uid: EMPTY_WALLET_UID, - external: false, - name: Buffer.alloc(0), - capabilities: 0, - }, - external: { - uid: EMPTY_WALLET_UID, - external: true, - name: Buffer.alloc(0), - capabilities: 0, - }, -}; + internal: { + uid: EMPTY_WALLET_UID, + external: false, + name: Buffer.alloc(0), + capabilities: 0, + }, + external: { + uid: EMPTY_WALLET_UID, + external: true, + name: Buffer.alloc(0), + capabilities: 0, + }, +} /** @internal */ -export const DEFAULT_ETH_DERIVATION: WalletPath = [ - HARDENED_OFFSET + 44, - HARDENED_OFFSET + 60, - HARDENED_OFFSET, - 0, - 0, -]; +export const DEFAULT_ETH_DERIVATION: WalletPath = [HARDENED_OFFSET + 44, HARDENED_OFFSET + 60, HARDENED_OFFSET, 0, 0] /** @internal */ -export const BTC_LEGACY_DERIVATION = [ - HARDENED_OFFSET + 44, - HARDENED_OFFSET + 0, - HARDENED_OFFSET, - 0, - 0, -]; +export const BTC_LEGACY_DERIVATION = [HARDENED_OFFSET + 44, HARDENED_OFFSET + 0, HARDENED_OFFSET, 0, 0] /** @internal */ -export const BTC_LEGACY_CHANGE_DERIVATION = [ - HARDENED_OFFSET + 44, - HARDENED_OFFSET + 0, - HARDENED_OFFSET, - 0, - 0, -]; +export const BTC_LEGACY_CHANGE_DERIVATION = [HARDENED_OFFSET + 44, HARDENED_OFFSET + 0, HARDENED_OFFSET, 0, 0] /** @internal */ -export const BTC_SEGWIT_DERIVATION = [ - HARDENED_OFFSET + 84, - HARDENED_OFFSET, - HARDENED_OFFSET, - 0, - 0, -]; +export const BTC_SEGWIT_DERIVATION = [HARDENED_OFFSET + 84, HARDENED_OFFSET, HARDENED_OFFSET, 0, 0] /** @internal */ -export const BTC_SEGWIT_CHANGE_DERIVATION = [ - HARDENED_OFFSET + 84, - HARDENED_OFFSET, - HARDENED_OFFSET, - 1, - 0, -]; +export const BTC_SEGWIT_CHANGE_DERIVATION = [HARDENED_OFFSET + 84, HARDENED_OFFSET, HARDENED_OFFSET, 1, 0] /** @internal */ -export const BTC_WRAPPED_SEGWIT_DERIVATION = [ - HARDENED_OFFSET + 49, - HARDENED_OFFSET, - HARDENED_OFFSET, - 0, - 0, -]; +export const BTC_WRAPPED_SEGWIT_DERIVATION = [HARDENED_OFFSET + 49, HARDENED_OFFSET, HARDENED_OFFSET, 0, 0] /** @internal */ -export const BTC_WRAPPED_SEGWIT_CHANGE_DERIVATION = [ - HARDENED_OFFSET + 49, - HARDENED_OFFSET, - HARDENED_OFFSET, - 0, - 0, -]; +export const BTC_WRAPPED_SEGWIT_CHANGE_DERIVATION = [HARDENED_OFFSET + 49, HARDENED_OFFSET, HARDENED_OFFSET, 0, 0] /** * Derivation path for Bitcoin legacy xpub (BIP44). @@ -594,7 +530,7 @@ export const BTC_WRAPPED_SEGWIT_CHANGE_DERIVATION = [ * flag: LatticeGetAddressesFlag.secp256k1Xpub * }); */ -export const BTC_LEGACY_XPUB_PATH = "44'/0'/0'"; +export const BTC_LEGACY_XPUB_PATH = "44'/0'/0'" /** * Derivation path for Bitcoin wrapped segwit ypub (BIP49). @@ -604,7 +540,7 @@ export const BTC_LEGACY_XPUB_PATH = "44'/0'/0'"; * flag: LatticeGetAddressesFlag.secp256k1Xpub * }); */ -export const BTC_WRAPPED_SEGWIT_YPUB_PATH = "49'/0'/0'"; +export const BTC_WRAPPED_SEGWIT_YPUB_PATH = "49'/0'/0'" /** * Derivation path for Bitcoin native segwit zpub (BIP84). @@ -614,50 +550,34 @@ export const BTC_WRAPPED_SEGWIT_YPUB_PATH = "49'/0'/0'"; * flag: LatticeGetAddressesFlag.secp256k1Xpub * }); */ -export const BTC_SEGWIT_ZPUB_PATH = "84'/0'/0'"; +export const BTC_SEGWIT_ZPUB_PATH = "84'/0'/0'" /** @internal */ -export const SOLANA_DERIVATION = [ - HARDENED_OFFSET + 44, - HARDENED_OFFSET + 501, - HARDENED_OFFSET, - HARDENED_OFFSET, -]; +export const SOLANA_DERIVATION = [HARDENED_OFFSET + 44, HARDENED_OFFSET + 501, HARDENED_OFFSET, HARDENED_OFFSET] /** @internal */ -export const LEDGER_LIVE_DERIVATION = [ - HARDENED_OFFSET + 49, - HARDENED_OFFSET + 60, - HARDENED_OFFSET, - 0, - 0, -]; +export const LEDGER_LIVE_DERIVATION = [HARDENED_OFFSET + 49, HARDENED_OFFSET + 60, HARDENED_OFFSET, 0, 0] /** @internal */ -export const LEDGER_LEGACY_DERIVATION = [ - HARDENED_OFFSET + 49, - HARDENED_OFFSET + 60, - HARDENED_OFFSET, - 0, -]; +export const LEDGER_LEGACY_DERIVATION = [HARDENED_OFFSET + 49, HARDENED_OFFSET + 60, HARDENED_OFFSET, 0] export { - ASCII_REGEX, - getFwVersionConst, - BIP_CONSTANTS, - BASE_URL, - CURRENCIES, - MAX_ADDR, - NETWORKS_BY_CHAIN_ID, - EXTERNAL_NETWORKS_BY_CHAIN_ID_URL, - addressSizes, - ethMsgProtocol, - signingSchema, - REQUEST_TYPE_BYTE, - VERSION_BYTE, - HARDENED_OFFSET, - HANDLE_LARGER_CHAIN_ID, - MAX_CHAIN_ID_BYTES, - ETH_ABI_LATTICE_FW_TYPE_MAP, - EXTERNAL as PUBLIC, -}; + ASCII_REGEX, + getFwVersionConst, + BIP_CONSTANTS, + BASE_URL, + CURRENCIES, + MAX_ADDR, + NETWORKS_BY_CHAIN_ID, + EXTERNAL_NETWORKS_BY_CHAIN_ID_URL, + addressSizes, + ethMsgProtocol, + signingSchema, + REQUEST_TYPE_BYTE, + VERSION_BYTE, + HARDENED_OFFSET, + HANDLE_LARGER_CHAIN_ID, + MAX_CHAIN_ID_BYTES, + ETH_ABI_LATTICE_FW_TYPE_MAP, + EXTERNAL as PUBLIC, +} diff --git a/packages/sdk/src/ethereum.ts b/packages/sdk/src/ethereum.ts index 52d5867d..2443db08 100644 --- a/packages/sdk/src/ethereum.ts +++ b/packages/sdk/src/ethereum.ts @@ -1,706 +1,588 @@ -import { RLP } from '@ethereumjs/rlp'; -import { SignTypedDataVersion, TypedDataUtils } from '@metamask/eth-sig-util'; +import { RLP } from '@ethereumjs/rlp' +import { SignTypedDataVersion, TypedDataUtils } from '@metamask/eth-sig-util' // Utils for Ethereum transactions. This is effecitvely a shim of ethereumjs-util, which // does not have browser (or, by proxy, React-Native) support. -import BN from 'bignumber.js'; -import cbor from 'cbor'; -import bdec from 'cbor-bigdecimal'; -import { Hash } from 'ox'; -import secp256k1 from 'secp256k1'; -import { - type Hex, - type TransactionSerializable, - hexToNumber, - serializeTransaction, -} from 'viem'; -import { - ASCII_REGEX, - EXTERNAL, - HANDLE_LARGER_CHAIN_ID, - MAX_CHAIN_ID_BYTES, - ethMsgProtocol, -} from './constants'; -import { buildGenericSigningMsgRequest } from './genericSigning'; -import { LatticeSignSchema } from './protocol'; -import { type FlexibleTransaction, TransactionSchema } from './schemas'; -import { - type FirmwareConstants, - type SigningPath, - TRANSACTION_TYPE, - type TransactionRequest, -} from './types'; -import { - buildSignerPathBuf, - convertRecoveryToV, - ensureHexBuffer, - fixLen, - isAsciiStr, - splitFrames, -} from './util'; - -const { ecdsaRecover } = secp256k1; - -bdec(cbor); +import BN from 'bignumber.js' +import cbor from 'cbor' +import bdec from 'cbor-bigdecimal' +import { Hash } from 'ox' +import secp256k1 from 'secp256k1' +import { type Hex, type TransactionSerializable, hexToNumber, serializeTransaction } from 'viem' +import { ASCII_REGEX, EXTERNAL, HANDLE_LARGER_CHAIN_ID, MAX_CHAIN_ID_BYTES, ethMsgProtocol } from './constants' +import { buildGenericSigningMsgRequest } from './genericSigning' +import { LatticeSignSchema } from './protocol' +import { type FlexibleTransaction, TransactionSchema } from './schemas' +import { type FirmwareConstants, type SigningPath, TRANSACTION_TYPE, type TransactionRequest } from './types' +import { buildSignerPathBuf, convertRecoveryToV, ensureHexBuffer, fixLen, isAsciiStr, splitFrames } from './util' + +const { ecdsaRecover } = secp256k1 + +bdec(cbor) const buildEthereumMsgRequest = (input) => { - if (!input.payload || !input.protocol || !input.signerPath) - throw new Error( - 'You must provide `payload`, `signerPath`, and `protocol` arguments in the messsage request', - ); - if (input.signerPath.length > 5 || input.signerPath.length < 2) - throw new Error('Please provide a signer path with 2-5 indices'); - const req = { - schema: LatticeSignSchema.ethereumMsg, - payload: null, - input, // Save the input for later - msg: null, // Save the buffered message for later - }; - switch (input.protocol) { - case 'signPersonal': - return buildPersonalSignRequest(req, input); - case 'eip712': - if (!input.fwConstants.eip712Supported) - throw new Error( - 'EIP712 is not supported by your Lattice firmware version. Please upgrade.', - ); - return buildEIP712Request(req, input); - default: - throw new Error('Unsupported protocol'); - } -}; + if (!input.payload || !input.protocol || !input.signerPath) throw new Error('You must provide `payload`, `signerPath`, and `protocol` arguments in the messsage request') + if (input.signerPath.length > 5 || input.signerPath.length < 2) throw new Error('Please provide a signer path with 2-5 indices') + const req = { + schema: LatticeSignSchema.ethereumMsg, + payload: null, + input, // Save the input for later + msg: null, // Save the buffered message for later + } + switch (input.protocol) { + case 'signPersonal': + return buildPersonalSignRequest(req, input) + case 'eip712': + if (!input.fwConstants.eip712Supported) throw new Error('EIP712 is not supported by your Lattice firmware version. Please upgrade.') + return buildEIP712Request(req, input) + default: + throw new Error('Unsupported protocol') + } +} const validateEthereumMsgResponse = (res, req) => { - const { signer, sig } = res; - const { input, msg, prehash = null } = req; - if (input.protocol === 'signPersonal') { - // NOTE: We are currently hardcoding networkID=1 and useEIP155=false but these - // may be configurable in future versions - const hash = prehash - ? prehash - : Buffer.from( - Hash.keccak256( - Buffer.concat([get_personal_sign_prefix(msg.length), msg]), - ), - ); - // Get recovery param with a `v` value of [27,28] by setting `useEIP155=false` - return addRecoveryParam(hash, sig, signer, { - chainId: 1, - useEIP155: false, - }); - } else if (input.protocol === 'eip712') { - // Use the validationPayload that was created in buildEIP712Request - // This payload has been parsed with forJSParser=true, converting all numbers - // to the format that TypedDataUtils.eip712Hash expects - const rawPayloadForHashing = req.validationPayload || req.input.payload; - const payloadForHashing = req.validationPayload - ? cloneTypedDataPayload(req.validationPayload) - : normalizeTypedDataForHashing(rawPayloadForHashing); - const encoded = TypedDataUtils.eip712Hash( - payloadForHashing, - SignTypedDataVersion.V4, - ); - const digest = prehash ? prehash : encoded; - // Parse chainId - it could be a number, hex string, decimal string, or bigint - let chainId = - input.payload.domain?.chainId || payloadForHashing.domain?.chainId; - if (typeof chainId === 'string') { - chainId = chainId.startsWith('0x') - ? Number.parseInt(chainId, 16) - : Number.parseInt(chainId, 10); - } else if (typeof chainId === 'bigint') { - chainId = Number(chainId); - } - // Get recovery param with a `v` value of [27,28] by setting `useEIP155=false` - return addRecoveryParam(digest, sig, signer, { chainId, useEIP155: false }); - } else { - throw new Error('Unsupported protocol'); - } -}; + const { signer, sig } = res + const { input, msg, prehash = null } = req + if (input.protocol === 'signPersonal') { + // NOTE: We are currently hardcoding networkID=1 and useEIP155=false but these + // may be configurable in future versions + const hash = prehash ? prehash : Buffer.from(Hash.keccak256(Buffer.concat([get_personal_sign_prefix(msg.length), msg]))) + // Get recovery param with a `v` value of [27,28] by setting `useEIP155=false` + return addRecoveryParam(hash, sig, signer, { + chainId: 1, + useEIP155: false, + }) + } else if (input.protocol === 'eip712') { + // Use the validationPayload that was created in buildEIP712Request + // This payload has been parsed with forJSParser=true, converting all numbers + // to the format that TypedDataUtils.eip712Hash expects + const rawPayloadForHashing = req.validationPayload || req.input.payload + const payloadForHashing = req.validationPayload ? cloneTypedDataPayload(req.validationPayload) : normalizeTypedDataForHashing(rawPayloadForHashing) + const encoded = TypedDataUtils.eip712Hash(payloadForHashing, SignTypedDataVersion.V4) + const digest = prehash ? prehash : encoded + // Parse chainId - it could be a number, hex string, decimal string, or bigint + let chainId = input.payload.domain?.chainId || payloadForHashing.domain?.chainId + if (typeof chainId === 'string') { + chainId = chainId.startsWith('0x') ? Number.parseInt(chainId, 16) : Number.parseInt(chainId, 10) + } else if (typeof chainId === 'bigint') { + chainId = Number(chainId) + } + // Get recovery param with a `v` value of [27,28] by setting `useEIP155=false` + return addRecoveryParam(digest, sig, signer, { chainId, useEIP155: false }) + } else { + throw new Error('Unsupported protocol') + } +} function normalizeTypedDataForHashing(value: any): any { - if (value === null || value === undefined) { - return value; - } - - if (typeof value === 'string') { - const trimmed = value.trim(); - if (/^0x[0-9a-fA-F]+$/.test(trimmed)) { - try { - const asBigInt = BigInt(trimmed); - if ( - asBigInt <= BigInt(Number.MAX_SAFE_INTEGER) && - asBigInt >= BigInt(Number.MIN_SAFE_INTEGER) - ) { - return Number(asBigInt); - } - return asBigInt.toString(10); - } catch { - return trimmed; - } - } - return trimmed; - } - - if (typeof value === 'bigint') { - const asNumber = Number(value); - return Number.isSafeInteger(asNumber) ? asNumber : value.toString(10); - } - - if (BN.isBigNumber(value)) { - const asNumber = Number(value.toString(10)); - return Number.isSafeInteger(asNumber) ? asNumber : value.toString(10); - } - - if ( - value && - typeof value === 'object' && - typeof value.toString === 'function' && - value.constructor && - value.constructor.name === 'BN' && - typeof value.toArray === 'function' - ) { - const str = value.toString(10); - const asNumber = Number(str); - return Number.isSafeInteger(asNumber) ? asNumber : str; - } - - if (Buffer.isBuffer(value) || value instanceof Uint8Array) { - return `0x${Buffer.from(value).toString('hex')}`; - } - - if (Array.isArray(value)) { - return value.map((item) => normalizeTypedDataForHashing(item)); - } - - if (typeof value === 'object') { - const normalized: Record = {}; - for (const [key, entry] of Object.entries(value)) { - normalized[key] = normalizeTypedDataForHashing(entry); - } - return normalized; - } - - return value; + if (value === null || value === undefined) { + return value + } + + if (typeof value === 'string') { + const trimmed = value.trim() + if (/^0x[0-9a-fA-F]+$/.test(trimmed)) { + try { + const asBigInt = BigInt(trimmed) + if (asBigInt <= BigInt(Number.MAX_SAFE_INTEGER) && asBigInt >= BigInt(Number.MIN_SAFE_INTEGER)) { + return Number(asBigInt) + } + return asBigInt.toString(10) + } catch { + return trimmed + } + } + return trimmed + } + + if (typeof value === 'bigint') { + const asNumber = Number(value) + return Number.isSafeInteger(asNumber) ? asNumber : value.toString(10) + } + + if (BN.isBigNumber(value)) { + const asNumber = Number(value.toString(10)) + return Number.isSafeInteger(asNumber) ? asNumber : value.toString(10) + } + + if (value && typeof value === 'object' && typeof value.toString === 'function' && value.constructor && value.constructor.name === 'BN' && typeof value.toArray === 'function') { + const str = value.toString(10) + const asNumber = Number(str) + return Number.isSafeInteger(asNumber) ? asNumber : str + } + + if (Buffer.isBuffer(value) || value instanceof Uint8Array) { + return `0x${Buffer.from(value).toString('hex')}` + } + + if (Array.isArray(value)) { + return value.map((item) => normalizeTypedDataForHashing(item)) + } + + if (typeof value === 'object') { + const normalized: Record = {} + for (const [key, entry] of Object.entries(value)) { + normalized[key] = normalizeTypedDataForHashing(entry) + } + return normalized + } + + return value } function cloneTypedDataPayload(payload: T): T { - if (payload === undefined) return payload; - if (structuredCloneFn) { - return structuredCloneFn(payload); - } - return basicTypedDataClone(payload); + if (payload === undefined) return payload + if (structuredCloneFn) { + return structuredCloneFn(payload) + } + return basicTypedDataClone(payload) } function basicTypedDataClone(value: T): T { - if (value === null || typeof value !== 'object') { - return value; - } - if (Buffer.isBuffer(value)) { - return Buffer.from(value) as T; - } - if (value instanceof Uint8Array) { - return new Uint8Array(value) as T; - } - if (Array.isArray(value)) { - return value.map((item) => basicTypedDataClone(item)) as unknown as T; - } - if (BN.isBigNumber(value)) { - return new BN(value) as T; - } - if ( - value && - typeof value === 'object' && - (value as { constructor?: { name?: string } }).constructor?.name === 'BN' && - typeof (value as { clone?: () => unknown }).clone === 'function' - ) { - return (value as unknown as { clone: () => unknown }).clone() as T; - } - if (value instanceof Date) { - return new Date(value.getTime()) as T; - } - const cloned: Record = {}; - for (const [key, entry] of Object.entries(value as Record)) { - cloned[key] = basicTypedDataClone(entry); - } - return cloned as T; + if (value === null || typeof value !== 'object') { + return value + } + if (Buffer.isBuffer(value)) { + return Buffer.from(value) as T + } + if (value instanceof Uint8Array) { + return new Uint8Array(value) as T + } + if (Array.isArray(value)) { + return value.map((item) => basicTypedDataClone(item)) as unknown as T + } + if (BN.isBigNumber(value)) { + return new BN(value) as T + } + if (value && typeof value === 'object' && (value as { constructor?: { name?: string } }).constructor?.name === 'BN' && typeof (value as { clone?: () => unknown }).clone === 'function') { + return (value as unknown as { clone: () => unknown }).clone() as T + } + if (value instanceof Date) { + return new Date(value.getTime()) as T + } + const cloned: Record = {} + for (const [key, entry] of Object.entries(value as Record)) { + cloned[key] = basicTypedDataClone(entry) + } + return cloned as T } -type StructuredCloneFn = (value: T, transfer?: unknown) => T; +type StructuredCloneFn = (value: T, transfer?: unknown) => T const structuredCloneFn: StructuredCloneFn | null = - typeof globalThis !== 'undefined' && - typeof (globalThis as { structuredClone?: unknown }).structuredClone === - 'function' - ? (globalThis as { structuredClone: StructuredCloneFn }).structuredClone - : null; + typeof globalThis !== 'undefined' && typeof (globalThis as { structuredClone?: unknown }).structuredClone === 'function' ? (globalThis as { structuredClone: StructuredCloneFn }).structuredClone : null const buildEthereumTxRequest = (data) => { - try { - let { chainId = 1 } = data; - const { signerPath, eip155 = null, fwConstants, type = null } = data; - const { - contractDeployKey, - extraDataFrameSz, - extraDataMaxFrames, - prehashAllowed, - } = fwConstants; - const EXTRA_DATA_ALLOWED = extraDataFrameSz > 0 && extraDataMaxFrames > 0; - const MAX_BASE_DATA_SZ = fwConstants.ethMaxDataSz; - const VAR_PATH_SZ = fwConstants.varAddrPathSzAllowed; - // Sanity checks: - // There are a handful of named chains we allow the user to reference (`chainIds`) - // Custom chainIDs should be either numerical or hex strings - if ( - typeof chainId !== 'number' && - isValidChainIdHexNumStr(chainId) === false - ) { - chainId = chainIds[chainId]; - } - // If this was not a custom chainID and we cannot find the name of it, exit - if (!chainId) throw new Error('Unsupported chain ID or name'); - // Sanity check on signePath - if (!signerPath) throw new Error('`signerPath` not provided'); - - // Is this a contract deployment? - if (data.to === null && !contractDeployKey) { - throw new Error( - 'Contract deployment not supported. Please update your Lattice firmware.', - ); - } - const isDeployment = data.to === null && contractDeployKey; - // We support eip1559 and eip2930 types (as well as legacy) - const eip1559IsAllowed = - fwConstants.allowedEthTxTypes && - fwConstants.allowedEthTxTypes.indexOf(2) > -1; - const eip2930IsAllowed = - fwConstants.allowedEthTxTypes && - fwConstants.allowedEthTxTypes.indexOf(1) > -1; - const isEip1559 = eip1559IsAllowed && (type === 2 || type === 'eip1559'); - const isEip2930 = eip2930IsAllowed && (type === 1 || type === 'eip2930'); - if (type !== null && !isEip1559 && !isEip2930) - throw new Error('Unsupported Ethereum transaction type'); - // Determine if we should use EIP155 given the chainID. - // If we are explicitly told to use eip155, we will use it. Otherwise, - // we will look up if the specified chainId is associated with a chain - // that does not use EIP155 by default. Note that most do use EIP155. - let useEIP155 = chainUsesEIP155(chainId); - if (eip155 !== null && typeof eip155 === 'boolean') { - useEIP155 = eip155; - } else if (isEip1559 || isEip2930) { - // Newer transaction types do not use EIP155 since the chainId is serialized - useEIP155 = false; - } - - // Hack for metamask, which sends value=null for 0 ETH transactions - if (!data.value) data.value = 0; - - //-------------- - // 1. BUILD THE RAW TX FOR FUTURE RLP ENCODING - //-------------- - // Ensure all fields are 0x-prefixed hex strings - const rawTx = []; - // Build the transaction buffer array - const chainIdBytes = ensureHexBuffer(chainId); - const nonceBytes = ensureHexBuffer(data.nonce); - let gasPriceBytes; - const gasLimitBytes = ensureHexBuffer(data.gasLimit); - // Handle contract deployment (indicated by `to` being `null`) - // For contract deployment we write a 20-byte key to the request - // buffer, which gets swapped for an empty buffer in firmware. - let toRlpElem; - let toBytes; - if (isDeployment) { - toRlpElem = Buffer.alloc(0); - toBytes = ensureHexBuffer(contractDeployKey); - } else { - toRlpElem = ensureHexBuffer(data.to); - toBytes = ensureHexBuffer(data.to); - } - const valueBytes = ensureHexBuffer(data.value); - const dataBytes = ensureHexBuffer(data.data); - - if (isEip1559 || isEip2930) { - // EIP1559 and EIP2930 transactions have a chainID field - rawTx.push(chainIdBytes); - } - rawTx.push(nonceBytes); - let maxPriorityFeePerGasBytes; - let maxFeePerGasBytes; - if (isEip1559) { - if (!data.maxPriorityFeePerGas) - throw new Error( - 'EIP1559 transactions must include `maxPriorityFeePerGas`', - ); - maxPriorityFeePerGasBytes = ensureHexBuffer(data.maxPriorityFeePerGas); - rawTx.push(maxPriorityFeePerGasBytes); - maxFeePerGasBytes = ensureHexBuffer(data.maxFeePerGas); - rawTx.push(maxFeePerGasBytes); - // EIP1559 renamed "gasPrice" to "maxFeePerGas", but firmware still - // uses `gasPrice` in the struct, so update that value here. - gasPriceBytes = maxFeePerGasBytes; - } else { - // EIP1559 transactions do not have the gasPrice field - gasPriceBytes = ensureHexBuffer(data.gasPrice); - rawTx.push(gasPriceBytes); - } - rawTx.push(gasLimitBytes); - rawTx.push(toRlpElem); - rawTx.push(valueBytes); - rawTx.push(dataBytes); - // We do not currently support accessList in firmware so we need to prehash if - // the list is non-null - let PREHASH_FROM_ACCESS_LIST = false; - if (isEip1559 || isEip2930) { - const accessList = []; - if (Array.isArray(data.accessList)) { - data.accessList.forEach((listItem) => { - const keys = []; - listItem.storageKeys.forEach((key) => { - keys.push(ensureHexBuffer(key)); - }); - accessList.push([ensureHexBuffer(listItem.address), keys]); - PREHASH_FROM_ACCESS_LIST = true; - }); - } - rawTx.push(accessList); - } else if (useEIP155 === true) { - // Add empty v,r,s values for EIP155 legacy transactions - rawTx.push(chainIdBytes); // v (which is the same as chainId in EIP155 txs) - rawTx.push(ensureHexBuffer(null)); // r - rawTx.push(ensureHexBuffer(null)); // s - } - //-------------- - // 2. BUILD THE LATTICE REQUEST PAYLOAD - //-------------- - const ETH_TX_NON_DATA_SZ = 122; // Accounts for metadata and non-data params - const txReqPayload = Buffer.alloc(MAX_BASE_DATA_SZ + ETH_TX_NON_DATA_SZ); - let off = 0; - // 1. EIP155 switch and chainID - //------------------ - txReqPayload.writeUInt8(Number(useEIP155), off); - off++; - // NOTE: Originally we designed for a 1-byte chainID, but modern rollup chains use much larger - // chainID values. To account for these, we will put the chainID into the `data` buffer if it - // is >=255. Values up to UINT64_MAX will be allowed. - let chainIdBuf; - let chainIdBufSz = 0; - if (useChainIdBuffer(chainId) === true) { - chainIdBuf = getChainIdBuf(chainId); - chainIdBufSz = chainIdBuf.length; - if (chainIdBufSz > MAX_CHAIN_ID_BYTES) - throw new Error('ChainID provided is too large.'); - // Signal to Lattice firmware that it needs to read the chainId from the tx.data buffer - txReqPayload.writeUInt8(HANDLE_LARGER_CHAIN_ID, off); - off++; - } else { - // For chainIDs <255, write it to the chainId u8 slot in the main tx buffer - chainIdBuf = ensureHexBuffer(chainId); - if (chainIdBuf.length !== 1) throw new Error('Error parsing chainID'); - chainIdBuf.copy(txReqPayload, off); - off += chainIdBuf.length; - } - // 2. Signer Path - //------------------ - const signerPathBuf = buildSignerPathBuf(signerPath, VAR_PATH_SZ); - signerPathBuf.copy(txReqPayload, off); - off += signerPathBuf.length; - - // 3. ETH TX request data - //------------------ - if (nonceBytes.length > 4) throw new Error('Nonce too large'); - nonceBytes.copy(txReqPayload, off + (4 - nonceBytes.length)); - off += 4; - if (gasPriceBytes.length > 8) throw new Error('Gas price too large'); - gasPriceBytes.copy(txReqPayload, off + (8 - gasPriceBytes.length)); - off += 8; - if (gasLimitBytes.length > 4) throw new Error('Gas limit too large'); - gasLimitBytes.copy(txReqPayload, off + (4 - gasLimitBytes.length)); - off += 4; - if (toBytes.length !== 20) throw new Error('Invalid `to` address'); - toBytes.copy(txReqPayload, off); - off += 20; - if (valueBytes.length > 32) throw new Error('Value too large'); - valueBytes.copy(txReqPayload, off + (32 - valueBytes.length)); - off += 32; - - // Extra Tx data comes before `data` in the struct - let PREHASH_UNSUPPORTED = false; - if (fwConstants.allowedEthTxTypes) { - // Some types may not be supported by firmware, so we will need to prehash - if (PREHASH_FROM_ACCESS_LIST) { - PREHASH_UNSUPPORTED = true; - } - txReqPayload.writeUInt8(PREHASH_UNSUPPORTED ? 1 : 0, off); - off += 1; - // EIP1559 & EIP2930 struct version - if (isEip1559) { - txReqPayload.writeUInt8(2, off); - off += 1; // Eip1559 type enum value - if (maxPriorityFeePerGasBytes.length > 8) - throw new Error('maxPriorityFeePerGasBytes too large'); - maxPriorityFeePerGasBytes.copy( - txReqPayload, - off + (8 - maxPriorityFeePerGasBytes.length), - ); - off += 8; // Skip EIP1559 params - } else if (isEip2930) { - txReqPayload.writeUInt8(1, off); - off += 1; // Eip2930 type enum value - off += 8; // Skip EIP1559 params - } else { - off += 9; // Skip EIP1559 and EIP2930 params - } - } - - // Flow data into extraData requests, which will follow-up transaction requests, if supported/applicable - const extraDataPayloads = []; - let prehash = null; - - // Create the buffer, prefix with chainId (if needed) and add data slice - const dataSz = dataBytes.length || 0; - const chainIdExtraSz = chainIdBufSz > 0 ? chainIdBufSz + 1 : 0; - const dataToCopy = Buffer.alloc(dataSz + chainIdExtraSz); - if (chainIdExtraSz > 0) { - dataToCopy.writeUInt8(chainIdBufSz, 0); - chainIdBuf.copy(dataToCopy, 1); - } - dataBytes.copy(dataToCopy, chainIdExtraSz); - - if (dataSz > MAX_BASE_DATA_SZ) { - // Determine sizes and run through sanity checks - const totalSz = dataSz + chainIdExtraSz; - const maxSzAllowed = - MAX_BASE_DATA_SZ + extraDataMaxFrames * extraDataFrameSz; - - if (prehashAllowed && totalSz > maxSzAllowed) { - // If this payload is too large to send, but the Lattice allows a prehashed message, do that - prehash = Buffer.from( - Hash.keccak256(get_rlp_encoded_preimage(rawTx, type)), - ); - } else { - if ( - !EXTRA_DATA_ALLOWED || - (EXTRA_DATA_ALLOWED && totalSz > maxSzAllowed) - ) - throw new Error( - `Data field too large (got ${dataBytes.length}; must be <=${ - maxSzAllowed - chainIdExtraSz - } bytes)`, - ); - // Split overflow data into extraData frames - const frames = splitFrames( - dataToCopy.slice(MAX_BASE_DATA_SZ), - extraDataFrameSz, - ); - frames.forEach((frame) => { - const szLE = Buffer.alloc(4); - szLE.writeUInt32LE(frame.length, 0); - extraDataPayloads.push(Buffer.concat([szLE, frame])); - }); - } - } else if (PREHASH_UNSUPPORTED) { - // If something is unsupported in firmware but we want to allow such transactions, - // we prehash the message here. - prehash = Buffer.from( - Hash.keccak256(get_rlp_encoded_preimage(rawTx, type)), - ); - } - - // Write the data size (does *NOT* include the chainId buffer, if that exists) - txReqPayload.writeUInt16BE(dataBytes.length, off); - off += 2; - // Copy in the chainId buffer if needed - if (chainIdBufSz > 0) { - txReqPayload.writeUInt8(chainIdBufSz, off); - off++; - chainIdBuf.copy(txReqPayload, off); - off += chainIdBufSz; - } - // Copy the first slice of the data itself. If this payload has been pre-hashed, include it - // in the `data` field. This will result in a different Lattice screen being drawn. - if (prehash) { - prehash.copy(txReqPayload, off); - off += MAX_BASE_DATA_SZ; - } else { - dataBytes.slice(0, MAX_BASE_DATA_SZ).copy(txReqPayload, off); - off += MAX_BASE_DATA_SZ; - } - return { - rawTx, - type, - payload: txReqPayload.slice(0, off), - extraDataPayloads, - schema: LatticeSignSchema.ethereum, // We will use eth transfer for all ETH txs for v1 - chainId, - useEIP155, - signerPath, - }; - } catch (err) { - return { err: err.message }; - } -}; + try { + let { chainId = 1 } = data + const { signerPath, eip155 = null, fwConstants, type = null } = data + const { contractDeployKey, extraDataFrameSz, extraDataMaxFrames, prehashAllowed } = fwConstants + const EXTRA_DATA_ALLOWED = extraDataFrameSz > 0 && extraDataMaxFrames > 0 + const MAX_BASE_DATA_SZ = fwConstants.ethMaxDataSz + const VAR_PATH_SZ = fwConstants.varAddrPathSzAllowed + // Sanity checks: + // There are a handful of named chains we allow the user to reference (`chainIds`) + // Custom chainIDs should be either numerical or hex strings + if (typeof chainId !== 'number' && isValidChainIdHexNumStr(chainId) === false) { + chainId = chainIds[chainId] + } + // If this was not a custom chainID and we cannot find the name of it, exit + if (!chainId) throw new Error('Unsupported chain ID or name') + // Sanity check on signePath + if (!signerPath) throw new Error('`signerPath` not provided') + + // Is this a contract deployment? + if (data.to === null && !contractDeployKey) { + throw new Error('Contract deployment not supported. Please update your Lattice firmware.') + } + const isDeployment = data.to === null && contractDeployKey + // We support eip1559 and eip2930 types (as well as legacy) + const eip1559IsAllowed = fwConstants.allowedEthTxTypes && fwConstants.allowedEthTxTypes.indexOf(2) > -1 + const eip2930IsAllowed = fwConstants.allowedEthTxTypes && fwConstants.allowedEthTxTypes.indexOf(1) > -1 + const isEip1559 = eip1559IsAllowed && (type === 2 || type === 'eip1559') + const isEip2930 = eip2930IsAllowed && (type === 1 || type === 'eip2930') + if (type !== null && !isEip1559 && !isEip2930) throw new Error('Unsupported Ethereum transaction type') + // Determine if we should use EIP155 given the chainID. + // If we are explicitly told to use eip155, we will use it. Otherwise, + // we will look up if the specified chainId is associated with a chain + // that does not use EIP155 by default. Note that most do use EIP155. + let useEIP155 = chainUsesEIP155(chainId) + if (eip155 !== null && typeof eip155 === 'boolean') { + useEIP155 = eip155 + } else if (isEip1559 || isEip2930) { + // Newer transaction types do not use EIP155 since the chainId is serialized + useEIP155 = false + } + + // Hack for metamask, which sends value=null for 0 ETH transactions + if (!data.value) data.value = 0 + + //-------------- + // 1. BUILD THE RAW TX FOR FUTURE RLP ENCODING + //-------------- + // Ensure all fields are 0x-prefixed hex strings + const rawTx = [] + // Build the transaction buffer array + const chainIdBytes = ensureHexBuffer(chainId) + const nonceBytes = ensureHexBuffer(data.nonce) + let gasPriceBytes: Buffer + const gasLimitBytes = ensureHexBuffer(data.gasLimit) + // Handle contract deployment (indicated by `to` being `null`) + // For contract deployment we write a 20-byte key to the request + // buffer, which gets swapped for an empty buffer in firmware. + let toRlpElem: Buffer + let toBytes: Buffer + if (isDeployment) { + toRlpElem = Buffer.alloc(0) + toBytes = ensureHexBuffer(contractDeployKey) + } else { + toRlpElem = ensureHexBuffer(data.to) + toBytes = ensureHexBuffer(data.to) + } + const valueBytes = ensureHexBuffer(data.value) + const dataBytes = ensureHexBuffer(data.data) + + if (isEip1559 || isEip2930) { + // EIP1559 and EIP2930 transactions have a chainID field + rawTx.push(chainIdBytes) + } + rawTx.push(nonceBytes) + let maxPriorityFeePerGasBytes: Buffer + let maxFeePerGasBytes: Buffer + if (isEip1559) { + if (!data.maxPriorityFeePerGas) throw new Error('EIP1559 transactions must include `maxPriorityFeePerGas`') + maxPriorityFeePerGasBytes = ensureHexBuffer(data.maxPriorityFeePerGas) + rawTx.push(maxPriorityFeePerGasBytes) + maxFeePerGasBytes = ensureHexBuffer(data.maxFeePerGas) + rawTx.push(maxFeePerGasBytes) + // EIP1559 renamed "gasPrice" to "maxFeePerGas", but firmware still + // uses `gasPrice` in the struct, so update that value here. + gasPriceBytes = maxFeePerGasBytes + } else { + // EIP1559 transactions do not have the gasPrice field + gasPriceBytes = ensureHexBuffer(data.gasPrice) + rawTx.push(gasPriceBytes) + } + rawTx.push(gasLimitBytes) + rawTx.push(toRlpElem) + rawTx.push(valueBytes) + rawTx.push(dataBytes) + // We do not currently support accessList in firmware so we need to prehash if + // the list is non-null + let PREHASH_FROM_ACCESS_LIST = false + if (isEip1559 || isEip2930) { + const accessList = [] + if (Array.isArray(data.accessList)) { + data.accessList.forEach((listItem) => { + const keys = [] + listItem.storageKeys.forEach((key) => { + keys.push(ensureHexBuffer(key)) + }) + accessList.push([ensureHexBuffer(listItem.address), keys]) + PREHASH_FROM_ACCESS_LIST = true + }) + } + rawTx.push(accessList) + } else if (useEIP155 === true) { + // Add empty v,r,s values for EIP155 legacy transactions + rawTx.push(chainIdBytes) // v (which is the same as chainId in EIP155 txs) + rawTx.push(ensureHexBuffer(null)) // r + rawTx.push(ensureHexBuffer(null)) // s + } + //-------------- + // 2. BUILD THE LATTICE REQUEST PAYLOAD + //-------------- + const ETH_TX_NON_DATA_SZ = 122 // Accounts for metadata and non-data params + const txReqPayload = Buffer.alloc(MAX_BASE_DATA_SZ + ETH_TX_NON_DATA_SZ) + let off = 0 + // 1. EIP155 switch and chainID + //------------------ + txReqPayload.writeUInt8(Number(useEIP155), off) + off++ + // NOTE: Originally we designed for a 1-byte chainID, but modern rollup chains use much larger + // chainID values. To account for these, we will put the chainID into the `data` buffer if it + // is >=255. Values up to UINT64_MAX will be allowed. + let chainIdBuf: Buffer + let chainIdBufSz = 0 + if (useChainIdBuffer(chainId) === true) { + chainIdBuf = getChainIdBuf(chainId) + chainIdBufSz = chainIdBuf.length + if (chainIdBufSz > MAX_CHAIN_ID_BYTES) throw new Error('ChainID provided is too large.') + // Signal to Lattice firmware that it needs to read the chainId from the tx.data buffer + txReqPayload.writeUInt8(HANDLE_LARGER_CHAIN_ID, off) + off++ + } else { + // For chainIDs <255, write it to the chainId u8 slot in the main tx buffer + chainIdBuf = ensureHexBuffer(chainId) + if (chainIdBuf.length !== 1) throw new Error('Error parsing chainID') + chainIdBuf.copy(txReqPayload, off) + off += chainIdBuf.length + } + // 2. Signer Path + //------------------ + const signerPathBuf = buildSignerPathBuf(signerPath, VAR_PATH_SZ) + signerPathBuf.copy(txReqPayload, off) + off += signerPathBuf.length + + // 3. ETH TX request data + //------------------ + if (nonceBytes.length > 4) throw new Error('Nonce too large') + nonceBytes.copy(txReqPayload, off + (4 - nonceBytes.length)) + off += 4 + if (gasPriceBytes.length > 8) throw new Error('Gas price too large') + gasPriceBytes.copy(txReqPayload, off + (8 - gasPriceBytes.length)) + off += 8 + if (gasLimitBytes.length > 4) throw new Error('Gas limit too large') + gasLimitBytes.copy(txReqPayload, off + (4 - gasLimitBytes.length)) + off += 4 + if (toBytes.length !== 20) throw new Error('Invalid `to` address') + toBytes.copy(txReqPayload, off) + off += 20 + if (valueBytes.length > 32) throw new Error('Value too large') + valueBytes.copy(txReqPayload, off + (32 - valueBytes.length)) + off += 32 + + // Extra Tx data comes before `data` in the struct + let PREHASH_UNSUPPORTED = false + if (fwConstants.allowedEthTxTypes) { + // Some types may not be supported by firmware, so we will need to prehash + if (PREHASH_FROM_ACCESS_LIST) { + PREHASH_UNSUPPORTED = true + } + txReqPayload.writeUInt8(PREHASH_UNSUPPORTED ? 1 : 0, off) + off += 1 + // EIP1559 & EIP2930 struct version + if (isEip1559) { + txReqPayload.writeUInt8(2, off) + off += 1 // Eip1559 type enum value + if (maxPriorityFeePerGasBytes.length > 8) throw new Error('maxPriorityFeePerGasBytes too large') + maxPriorityFeePerGasBytes.copy(txReqPayload, off + (8 - maxPriorityFeePerGasBytes.length)) + off += 8 // Skip EIP1559 params + } else if (isEip2930) { + txReqPayload.writeUInt8(1, off) + off += 1 // Eip2930 type enum value + off += 8 // Skip EIP1559 params + } else { + off += 9 // Skip EIP1559 and EIP2930 params + } + } + + // Flow data into extraData requests, which will follow-up transaction requests, if supported/applicable + const extraDataPayloads = [] + let prehash = null + + // Create the buffer, prefix with chainId (if needed) and add data slice + const dataSz = dataBytes.length || 0 + const chainIdExtraSz = chainIdBufSz > 0 ? chainIdBufSz + 1 : 0 + const dataToCopy = Buffer.alloc(dataSz + chainIdExtraSz) + if (chainIdExtraSz > 0) { + dataToCopy.writeUInt8(chainIdBufSz, 0) + chainIdBuf.copy(dataToCopy, 1) + } + dataBytes.copy(dataToCopy, chainIdExtraSz) + + if (dataSz > MAX_BASE_DATA_SZ) { + // Determine sizes and run through sanity checks + const totalSz = dataSz + chainIdExtraSz + const maxSzAllowed = MAX_BASE_DATA_SZ + extraDataMaxFrames * extraDataFrameSz + + if (prehashAllowed && totalSz > maxSzAllowed) { + // If this payload is too large to send, but the Lattice allows a prehashed message, do that + prehash = Buffer.from(Hash.keccak256(get_rlp_encoded_preimage(rawTx, type))) + } else { + if (!EXTRA_DATA_ALLOWED || (EXTRA_DATA_ALLOWED && totalSz > maxSzAllowed)) throw new Error(`Data field too large (got ${dataBytes.length}; must be <=${maxSzAllowed - chainIdExtraSz} bytes)`) + // Split overflow data into extraData frames + const frames = splitFrames(dataToCopy.slice(MAX_BASE_DATA_SZ), extraDataFrameSz) + frames.forEach((frame) => { + const szLE = Buffer.alloc(4) + szLE.writeUInt32LE(frame.length, 0) + extraDataPayloads.push(Buffer.concat([szLE, frame])) + }) + } + } else if (PREHASH_UNSUPPORTED) { + // If something is unsupported in firmware but we want to allow such transactions, + // we prehash the message here. + prehash = Buffer.from(Hash.keccak256(get_rlp_encoded_preimage(rawTx, type))) + } + + // Write the data size (does *NOT* include the chainId buffer, if that exists) + txReqPayload.writeUInt16BE(dataBytes.length, off) + off += 2 + // Copy in the chainId buffer if needed + if (chainIdBufSz > 0) { + txReqPayload.writeUInt8(chainIdBufSz, off) + off++ + chainIdBuf.copy(txReqPayload, off) + off += chainIdBufSz + } + // Copy the first slice of the data itself. If this payload has been pre-hashed, include it + // in the `data` field. This will result in a different Lattice screen being drawn. + if (prehash) { + prehash.copy(txReqPayload, off) + off += MAX_BASE_DATA_SZ + } else { + dataBytes.slice(0, MAX_BASE_DATA_SZ).copy(txReqPayload, off) + off += MAX_BASE_DATA_SZ + } + return { + rawTx, + type, + payload: txReqPayload.slice(0, off), + extraDataPayloads, + schema: LatticeSignSchema.ethereum, // We will use eth transfer for all ETH txs for v1 + chainId, + useEIP155, + signerPath, + } + } catch (err) { + return { err: err.message } + } +} // From ethereumjs-util function stripZeros(a) { - let first = a[0]; - while (a.length > 0 && first.toString() === '0') { - a = a.slice(1); - first = a[0]; - } - return a; + let first = a[0] + while (a.length > 0 && first.toString() === '0') { + a = a.slice(1) + first = a[0] + } + return a } // Given a 64-byte signature [r,s] we need to figure out the v value // and attah the full signature to the end of the transaction payload const buildEthRawTx = (tx, sig, address) => { - // RLP-encode the data we sent to the lattice - const hash = Buffer.from( - Hash.keccak256(get_rlp_encoded_preimage(tx.rawTx, tx.type)), - ); - const newSig = addRecoveryParam(hash, sig, address, tx); - // Use the signature to generate a new raw transaction payload - // Strip the last 3 items and replace them with signature components - const newRawTx = tx.useEIP155 ? tx.rawTx.slice(0, -3) : tx.rawTx; - newRawTx.push(newSig.v); - // Per `ethereumjs-tx`, RLP encoding should include signature components w/ stripped zeros - // See: https://github.com/ethereumjs/ethereumjs-tx/blob/master/src/transaction.ts#L187 - newRawTx.push(stripZeros(newSig.r)); - newRawTx.push(stripZeros(newSig.s)); - const rlpEncoded = Buffer.from(RLP.encode(newRawTx)); - const rlpEncodedWithSig = tx.type - ? Buffer.concat([Buffer.from([tx.type]), rlpEncoded]) - : rlpEncoded; - - if ( - tx.type === TRANSACTION_TYPE.EIP7702_AUTH || - tx.type === TRANSACTION_TYPE.EIP7702_AUTH_LIST - ) { - // For EIP-7702 transactions, we return just the hex string - return rlpEncodedWithSig.toString('hex'); - } - - return { rawTx: rlpEncodedWithSig.toString('hex'), sigWithV: newSig }; -}; + // RLP-encode the data we sent to the lattice + const hash = Buffer.from(Hash.keccak256(get_rlp_encoded_preimage(tx.rawTx, tx.type))) + const newSig = addRecoveryParam(hash, sig, address, tx) + // Use the signature to generate a new raw transaction payload + // Strip the last 3 items and replace them with signature components + const newRawTx = tx.useEIP155 ? tx.rawTx.slice(0, -3) : tx.rawTx + newRawTx.push(newSig.v) + // Per `ethereumjs-tx`, RLP encoding should include signature components w/ stripped zeros + // See: https://github.com/ethereumjs/ethereumjs-tx/blob/master/src/transaction.ts#L187 + newRawTx.push(stripZeros(newSig.r)) + newRawTx.push(stripZeros(newSig.s)) + const rlpEncoded = Buffer.from(RLP.encode(newRawTx)) + const rlpEncodedWithSig = tx.type ? Buffer.concat([Buffer.from([tx.type]), rlpEncoded]) : rlpEncoded + + if (tx.type === TRANSACTION_TYPE.EIP7702_AUTH || tx.type === TRANSACTION_TYPE.EIP7702_AUTH_LIST) { + // For EIP-7702 transactions, we return just the hex string + return rlpEncodedWithSig.toString('hex') + } + + return { rawTx: rlpEncodedWithSig.toString('hex'), sigWithV: newSig } +} // Attach a recovery parameter to a signature by brute-forcing ECRecover export function addRecoveryParam(hashBuf, sig, address, txData = {}) { - try { - // Rebuild the keccak256 hash here so we can `ecrecover` - const hash = new Uint8Array(hashBuf); - const expectedAddrBuf = Buffer.isBuffer(address) - ? address - : ensureHexBuffer(address, false); - if (expectedAddrBuf.length !== 20) - throw new Error('Invalid signer address provided.'); - let v = 0; - // Fix signature componenet lengths to 32 bytes each - const r = fixLen(sig.r, 32); - sig.r = r; - const s = fixLen(sig.s, 32); - sig.s = s; - // Calculate the recovery param - const rs = new Uint8Array(Buffer.concat([r, s])); - let pubkey = ecdsaRecover(rs, v, hash, false).slice(1); - const expectedAddrHex = expectedAddrBuf.toString('hex'); - const recoveredAddrs: string[] = []; - // If the first `v` value is a match, return the sig! - let recovered = pubToAddrStr(pubkey); - recoveredAddrs.push(recovered); - if (recovered === expectedAddrHex) { - sig.v = getRecoveryParam(v, txData); - return sig; - } - // Otherwise, try the other `v` value - v = 1; - pubkey = ecdsaRecover(rs, v, hash, false).slice(1); - recovered = pubToAddrStr(pubkey); - recoveredAddrs.push(recovered); - if (recovered === expectedAddrHex) { - sig.v = getRecoveryParam(v, txData); - return sig; - } else { - // If neither is a match, we should return an error - throw new Error( - `Invalid Ethereum signature returned. expected=${expectedAddrHex}, recovered=${recoveredAddrs.join(',')}`, - ); - } - } catch (err) { - if (err instanceof Error) throw err; - throw new Error(String(err)); - } + try { + // Rebuild the keccak256 hash here so we can `ecrecover` + const hash = new Uint8Array(hashBuf) + const expectedAddrBuf = Buffer.isBuffer(address) ? address : ensureHexBuffer(address, false) + if (expectedAddrBuf.length !== 20) throw new Error('Invalid signer address provided.') + let v = 0 + // Fix signature componenet lengths to 32 bytes each + const r = fixLen(sig.r, 32) + sig.r = r + const s = fixLen(sig.s, 32) + sig.s = s + // Calculate the recovery param + const rs = new Uint8Array(Buffer.concat([r, s])) + let pubkey = ecdsaRecover(rs, v, hash, false).slice(1) + const expectedAddrHex = expectedAddrBuf.toString('hex') + const recoveredAddrs: string[] = [] + // If the first `v` value is a match, return the sig! + let recovered = pubToAddrStr(pubkey) + recoveredAddrs.push(recovered) + if (recovered === expectedAddrHex) { + sig.v = getRecoveryParam(v, txData) + return sig + } + // Otherwise, try the other `v` value + v = 1 + pubkey = ecdsaRecover(rs, v, hash, false).slice(1) + recovered = pubToAddrStr(pubkey) + recoveredAddrs.push(recovered) + if (recovered === expectedAddrHex) { + sig.v = getRecoveryParam(v, txData) + return sig + } else { + // If neither is a match, we should return an error + throw new Error(`Invalid Ethereum signature returned. expected=${expectedAddrHex}, recovered=${recoveredAddrs.join(',')}`) + } + } catch (err) { + if (err instanceof Error) throw err + throw new Error(String(err)) + } } /** * Normalize Lattice signature components to viem format. * Handles Buffer v value conversion and yParity vs v for different transaction types. */ -export function normalizeLatticeSignature( - latticeResult: any, - originalTx: TransactionSerializable, -) { - // Convert Buffer v value to number - let vValue: number; - if (Buffer.isBuffer(latticeResult.sig.v)) { - // Read entire buffer as big-endian integer - // Single byte: = 0x26 = 38 - // Multi-byte: = 0x0135 = 309 (Polygon chainId 137 with EIP-155) - const bufferLength = latticeResult.sig.v.length; - if (bufferLength === 1) { - vValue = latticeResult.sig.v.readUInt8(0); - } else if (bufferLength === 2) { - vValue = latticeResult.sig.v.readUInt16BE(0); - } else if (bufferLength <= 4) { - vValue = latticeResult.sig.v.readUInt32BE(Math.max(0, 4 - bufferLength)); - } else { - // For very large buffers, read as hex and convert - vValue = Number.parseInt(latticeResult.sig.v.toString('hex'), 16); - } - } else if (typeof latticeResult.sig.v === 'number') { - vValue = latticeResult.sig.v; - } else if (typeof latticeResult.sig.v === 'string') { - vValue = hexToNumber(latticeResult.sig.v as Hex); - } else { - vValue = Number(latticeResult.sig.v); - } - - // For typed transactions (non-legacy), viem expects yParity instead of v - if (originalTx.type !== 'legacy') { - // Convert v to yParity (v is either 27/28 or 0/1) - const yParity = vValue >= 27 ? vValue - 27 : vValue; - return { - ...originalTx, - r: latticeResult.sig.r as Hex, - s: latticeResult.sig.s as Hex, - yParity, - }; - } else { - // Legacy transactions use v directly as BigInt - const result = { - ...originalTx, - r: latticeResult.sig.r as Hex, - s: latticeResult.sig.s as Hex, - v: BigInt(vValue), - }; - - // For legacy transactions, remove the type field to ensure Viem treats it as legacy - result.type = undefined; - - // Also remove any typed transaction fields that might confuse viem - result.maxFeePerGas = undefined; - result.maxPriorityFeePerGas = undefined; - result.accessList = undefined; - result.authorizationList = undefined; - - return result; - } +export function normalizeLatticeSignature(latticeResult: any, originalTx: TransactionSerializable) { + // Convert Buffer v value to number + let vValue: number + if (Buffer.isBuffer(latticeResult.sig.v)) { + // Read entire buffer as big-endian integer + // Single byte: = 0x26 = 38 + // Multi-byte: = 0x0135 = 309 (Polygon chainId 137 with EIP-155) + const bufferLength = latticeResult.sig.v.length + if (bufferLength === 1) { + vValue = latticeResult.sig.v.readUInt8(0) + } else if (bufferLength === 2) { + vValue = latticeResult.sig.v.readUInt16BE(0) + } else if (bufferLength <= 4) { + vValue = latticeResult.sig.v.readUInt32BE(Math.max(0, 4 - bufferLength)) + } else { + // For very large buffers, read as hex and convert + vValue = Number.parseInt(latticeResult.sig.v.toString('hex'), 16) + } + } else if (typeof latticeResult.sig.v === 'number') { + vValue = latticeResult.sig.v + } else if (typeof latticeResult.sig.v === 'string') { + vValue = hexToNumber(latticeResult.sig.v as Hex) + } else { + vValue = Number(latticeResult.sig.v) + } + + // For typed transactions (non-legacy), viem expects yParity instead of v + if (originalTx.type !== 'legacy') { + // Convert v to yParity (v is either 27/28 or 0/1) + const yParity = vValue >= 27 ? vValue - 27 : vValue + return { + ...originalTx, + r: latticeResult.sig.r as Hex, + s: latticeResult.sig.s as Hex, + yParity, + } + } else { + // Legacy transactions use v directly as BigInt + const result = { + ...originalTx, + r: latticeResult.sig.r as Hex, + s: latticeResult.sig.s as Hex, + v: BigInt(vValue), + } + + // For legacy transactions, remove the type field to ensure Viem treats it as legacy + result.type = undefined + + // Also remove any typed transaction fields that might confuse viem + result.maxFeePerGas = undefined + result.maxPriorityFeePerGas = undefined + result.accessList = undefined + result.authorizationList = undefined + + return result + } } // Convert an RLP-serialized transaction (plus signature) into a transaction hash -const hashTransaction = (serializedTx) => - Hash.keccak256(Buffer.from(serializedTx, 'hex')); +const hashTransaction = (serializedTx) => Hash.keccak256(Buffer.from(serializedTx, 'hex')) // Returns address string given public key buffer function pubToAddrStr(pub) { - return Buffer.from(Hash.keccak256(pub)).slice(-20).toString('hex'); + return Buffer.from(Hash.keccak256(pub)).slice(-20).toString('hex') } // Convert a 0/1 `v` into a recovery param: @@ -708,466 +590,372 @@ function pubToAddrStr(pub) { // * For EIP155 transactions, return `(CHAIN_ID*2) + 35 + v` // Uses the consolidated convertRecoveryToV function from util.ts function getRecoveryParam(v, txData: any = {}) { - const result = convertRecoveryToV(v, txData); - - // convertRecoveryToV returns Buffer for typed transactions, BN for legacy - // Always return Buffer to maintain compatibility with existing code - if (Buffer.isBuffer(result)) { - return result; - } else { - // Convert BN result to hex buffer - return ensureHexBuffer(`0x${result.toString(16)}`); - } + const result = convertRecoveryToV(v, txData) + + // convertRecoveryToV returns Buffer for typed transactions, BN for legacy + // Always return Buffer to maintain compatibility with existing code + if (Buffer.isBuffer(result)) { + return result + } else { + // Convert BN result to hex buffer + return ensureHexBuffer(`0x${result.toString(16)}`) + } } const chainIds = { - mainnet: 1, - roptsten: 3, - rinkeby: 4, - kovan: 42, - goerli: 5, -}; + mainnet: 1, + roptsten: 3, + rinkeby: 4, + kovan: 42, + goerli: 5, +} // Get a buffer containing the chainId value. // Returns a 1, 2, 4, or 8 byte buffer with the chainId encoded in big endian function getChainIdBuf(chainId) { - let b; - // If our chainID is a hex string, we can convert it to a hex - // buffer directly - if (true === isValidChainIdHexNumStr(chainId)) b = ensureHexBuffer(chainId); - // If our chainID is a base-10 number, parse with bignumber.js and convert to hex buffer - else b = ensureHexBuffer(`0x${new BN(chainId).toString(16)}`); - // Make sure the buffer is an allowed size - if (b.length > 8) throw new Error('ChainID provided is too large.'); - // If this matches a u16, u32, or u64 size, return it now - if (b.length <= 2 || b.length === 4 || b.length === 8) return b; - // For other size buffers, we need to pack into u32 or u64 before returning; - let buf; - if (b.length === 3) { - buf = Buffer.alloc(4); - buf.writeUInt32BE(chainId); - } else if (b.length <= 8) { - buf = Buffer.alloc(8); - b.copy(buf, 8 - b.length); - } - return buf; + let b: Buffer + // If our chainID is a hex string, we can convert it to a hex + // buffer directly + if (true === isValidChainIdHexNumStr(chainId)) b = ensureHexBuffer(chainId) + // If our chainID is a base-10 number, parse with bignumber.js and convert to hex buffer + else b = ensureHexBuffer(`0x${new BN(chainId).toString(16)}`) + // Make sure the buffer is an allowed size + if (b.length > 8) throw new Error('ChainID provided is too large.') + // If this matches a u16, u32, or u64 size, return it now + if (b.length <= 2 || b.length === 4 || b.length === 8) return b + // For other size buffers, we need to pack into u32 or u64 before returning; + let buf: Buffer + if (b.length === 3) { + buf = Buffer.alloc(4) + buf.writeUInt32BE(chainId) + } else if (b.length <= 8) { + buf = Buffer.alloc(8) + b.copy(buf, 8 - b.length) + } + return buf } // Determine if the chain uses EIP155 by default, based on the chainID function chainUsesEIP155(chainID) { - switch (chainID) { - case 3: // ropsten - case 4: // rinkeby - return false; - default: - // all others should use eip155 - return true; - } + switch (chainID) { + case 3: // ropsten + case 4: // rinkeby + return false + default: + // all others should use eip155 + return true + } } // Determine if a valid number was passed in as a hex string function isValidChainIdHexNumStr(s) { - if (typeof s !== 'string') return false; - if (s.slice(0, 2) !== '0x') return false; - try { - const b = new BN(s, 16); - return b.isNaN() === false; - } catch (err) { - console.error('Invalid chain ID hex string:', err); - return false; - } + if (typeof s !== 'string') return false + if (s.slice(0, 2) !== '0x') return false + try { + const b = new BN(s, 16) + return b.isNaN() === false + } catch (err) { + console.error('Invalid chain ID hex string:', err) + return false + } } // If this is a nubmer that fits in one byte, we don't need to add it // to the `data` buffer of the main transaction. // Note the one edge case: we still need to use the `data` field for chainID=255. function useChainIdBuffer(id) { - const buf = getChainIdBuf(id); - if (buf.length === 1) return buf.readUInt8(0) === 255; - return true; + const buf = getChainIdBuf(id) + if (buf.length === 1) return buf.readUInt8(0) === 255 + return true } function buildPersonalSignRequest(req, input) { - const MAX_BASE_MSG_SZ = input.fwConstants.ethMaxMsgSz; - const VAR_PATH_SZ = input.fwConstants.varAddrPathSzAllowed; - const L = 24 + MAX_BASE_MSG_SZ + 4; - let off = 0; - req.payload = Buffer.alloc(L); - req.payload.writeUInt8(ethMsgProtocol.SIGN_PERSONAL, 0); - off += 1; - // Write the signer path into the buffer - const signerPathBuf = buildSignerPathBuf(input.signerPath, VAR_PATH_SZ); - signerPathBuf.copy(req.payload, off); - off += signerPathBuf.length; - // Write the payload buffer. The payload can come in either as a buffer or as a string - let payload = input.payload; - // Determine if this is a hex string - let displayHex = false; - if (typeof input.payload === 'string') { - if (input.payload.slice(0, 2) === '0x') { - payload = ensureHexBuffer(input.payload); - displayHex = - false === - ASCII_REGEX.test(Buffer.from(input.payload.slice(2), 'hex').toString()); - } else { - if (false === isAsciiStr(input.payload)) - throw new Error( - 'Currently, the Lattice can only display ASCII strings.', - ); - payload = Buffer.from(input.payload); - } - } else if (typeof input.displayHex === 'boolean') { - // If this is a buffer and the user has specified whether or not this - // is a hex buffer with the optional argument, write that - displayHex = input.displayHex; - } else { - // Otherwise, determine if this buffer is an ASCII string. If it is, set `displayHex` accordingly. - // NOTE: THIS MEANS THAT NON-ASCII STRINGS WILL DISPLAY AS HEX SINCE WE CANNOT KNOW IF THE REQUESTER - // EXPECTED NON-ASCII CHARACTERS TO DISPLAY IN A STRING - // TODO: Develop a more elegant solution for this - if (!input.payload.toString) throw new Error('Unsupported input data type'); - displayHex = false === ASCII_REGEX.test(input.payload.toString()); - } - const fwConst = input.fwConstants; - let maxSzAllowed = - MAX_BASE_MSG_SZ + fwConst.extraDataMaxFrames * fwConst.extraDataFrameSz; - if (fwConst.personalSignHeaderSz) { - // Account for the personal_sign header string - maxSzAllowed -= fwConst.personalSignHeaderSz; - } - if (fwConst.ethMsgPreHashAllowed && payload.length > maxSzAllowed) { - // If this message will not fit and pre-hashing is allowed, do that - req.payload.writeUInt8(displayHex, off); - off += 1; - req.payload.writeUInt16LE(payload.length, off); - off += 2; - const prehash = Buffer.from( - Hash.keccak256( - Buffer.concat([get_personal_sign_prefix(payload.length), payload]), - ), - ); - prehash.copy(req.payload, off); - req.prehash = prehash; - } else { - // Otherwise we can fit the payload. - // Flow data into extraData requests, which will follow-up transaction requests, if supported/applicable - const extraDataPayloads = getExtraData(payload, input); - // Write the payload and metadata into our buffer - req.extraDataPayloads = extraDataPayloads; - req.msg = payload; - req.payload.writeUInt8(displayHex, off); - off += 1; - req.payload.writeUInt16LE(payload.length, off); - off += 2; - payload.copy(req.payload, off); - } - return req; + const MAX_BASE_MSG_SZ = input.fwConstants.ethMaxMsgSz + const VAR_PATH_SZ = input.fwConstants.varAddrPathSzAllowed + const L = 24 + MAX_BASE_MSG_SZ + 4 + let off = 0 + req.payload = Buffer.alloc(L) + req.payload.writeUInt8(ethMsgProtocol.SIGN_PERSONAL, 0) + off += 1 + // Write the signer path into the buffer + const signerPathBuf = buildSignerPathBuf(input.signerPath, VAR_PATH_SZ) + signerPathBuf.copy(req.payload, off) + off += signerPathBuf.length + // Write the payload buffer. The payload can come in either as a buffer or as a string + let payload = input.payload + // Determine if this is a hex string + let displayHex = false + if (typeof input.payload === 'string') { + if (input.payload.slice(0, 2) === '0x') { + payload = ensureHexBuffer(input.payload) + displayHex = false === ASCII_REGEX.test(Buffer.from(input.payload.slice(2), 'hex').toString()) + } else { + if (false === isAsciiStr(input.payload)) throw new Error('Currently, the Lattice can only display ASCII strings.') + payload = Buffer.from(input.payload) + } + } else if (typeof input.displayHex === 'boolean') { + // If this is a buffer and the user has specified whether or not this + // is a hex buffer with the optional argument, write that + displayHex = input.displayHex + } else { + // Otherwise, determine if this buffer is an ASCII string. If it is, set `displayHex` accordingly. + // NOTE: THIS MEANS THAT NON-ASCII STRINGS WILL DISPLAY AS HEX SINCE WE CANNOT KNOW IF THE REQUESTER + // EXPECTED NON-ASCII CHARACTERS TO DISPLAY IN A STRING + // TODO: Develop a more elegant solution for this + if (!input.payload.toString) throw new Error('Unsupported input data type') + displayHex = false === ASCII_REGEX.test(input.payload.toString()) + } + const fwConst = input.fwConstants + let maxSzAllowed = MAX_BASE_MSG_SZ + fwConst.extraDataMaxFrames * fwConst.extraDataFrameSz + if (fwConst.personalSignHeaderSz) { + // Account for the personal_sign header string + maxSzAllowed -= fwConst.personalSignHeaderSz + } + if (fwConst.ethMsgPreHashAllowed && payload.length > maxSzAllowed) { + // If this message will not fit and pre-hashing is allowed, do that + req.payload.writeUInt8(displayHex, off) + off += 1 + req.payload.writeUInt16LE(payload.length, off) + off += 2 + const prehash = Buffer.from(Hash.keccak256(Buffer.concat([get_personal_sign_prefix(payload.length), payload]))) + prehash.copy(req.payload, off) + req.prehash = prehash + } else { + // Otherwise we can fit the payload. + // Flow data into extraData requests, which will follow-up transaction requests, if supported/applicable + const extraDataPayloads = getExtraData(payload, input) + // Write the payload and metadata into our buffer + req.extraDataPayloads = extraDataPayloads + req.msg = payload + req.payload.writeUInt8(displayHex, off) + off += 1 + req.payload.writeUInt16LE(payload.length, off) + off += 2 + payload.copy(req.payload, off) + } + return req } function buildEIP712Request(req, input) { - const { ethMaxMsgSz, varAddrPathSzAllowed, eip712MaxTypeParams } = - input.fwConstants; - const { TYPED_DATA } = ethMsgProtocol; - const L = 24 + ethMaxMsgSz + 4; - let off = 0; - req.payload = Buffer.alloc(L); - req.payload.writeUInt8(TYPED_DATA.enumIdx, 0); - off += 1; - // Write the signer path - const signerPathBuf = buildSignerPathBuf( - input.signerPath, - varAddrPathSzAllowed, - ); - signerPathBuf.copy(req.payload, off); - off += signerPathBuf.length; - // Parse/clean the EIP712 payload, serialize with CBOR, and write to the payload - const data = cloneTypedDataPayload(input.payload); - if (!data.primaryType || !data.types[data.primaryType]) - throw new Error( - 'primaryType must be specified and the type must be included.', - ); - if (!data.message || !data.domain) - throw new Error('message and domain must be specified.'); - if (0 > Object.keys(data.types).indexOf('EIP712Domain')) - throw new Error('EIP712Domain type must be defined.'); - // Parse the payload to ensure we have valid EIP712 data types and that - // they are encoded such that Lattice firmware can parse them. - // We need two different encodings: one to send to the Lattice in a format that plays - // nicely with our firmware CBOR decoder. The other is formatted to be consumable by - // our EIP712 validation module. - // IMPORTANT: Create a new object for the validation payload instead of modifying input.payload - // in place, so that validation uses the correctly formatted data - const validationPayload = cloneTypedDataPayload(data); - validationPayload.message = parseEIP712Msg( - cloneTypedDataPayload(data.message), - cloneTypedDataPayload(data.primaryType), - cloneTypedDataPayload(data.types), - true, - ); - validationPayload.domain = parseEIP712Msg( - cloneTypedDataPayload(data.domain), - 'EIP712Domain', - cloneTypedDataPayload(data.types), - true, - ); - // Store the validation payload separately without modifying input.payload - req.validationPayload = validationPayload; - - data.domain = parseEIP712Msg(data.domain, 'EIP712Domain', data.types, false); - data.message = parseEIP712Msg( - data.message, - data.primaryType, - data.types, - false, - ); - // Now build the message to be sent to the Lattice - const payload = Buffer.from(cbor.encode(data)); - const fwConst = input.fwConstants; - const maxSzAllowed = - ethMaxMsgSz + fwConst.extraDataMaxFrames * fwConst.extraDataFrameSz; - // Determine if we need to prehash - let shouldPrehash = payload.length > maxSzAllowed; - Object.keys(data.types).forEach((k) => { - if (data.types[k].length > eip712MaxTypeParams) { - shouldPrehash = true; - } - }); - if (fwConst.ethMsgPreHashAllowed && shouldPrehash) { - // If this payload is too large to send, but the Lattice allows a prehashed message, do that - req.payload.writeUInt16LE(payload.length, off); - off += 2; - const prehash = TypedDataUtils.eip712Hash( - req.validationPayload, - SignTypedDataVersion.V4, - ); - const prehashBuf = Buffer.from(prehash); - prehashBuf.copy(req.payload, off); - req.prehash = prehash; - } else { - const extraDataPayloads = getExtraData(payload, input); - req.extraDataPayloads = extraDataPayloads; - req.payload.writeUInt16LE(payload.length, off); - off += 2; - payload.copy(req.payload, off); - off += payload.length; - // Slice out the part of the buffer that we didn't use. - req.payload = req.payload.slice(0, off); - } - return req; + const { ethMaxMsgSz, varAddrPathSzAllowed, eip712MaxTypeParams } = input.fwConstants + const { TYPED_DATA } = ethMsgProtocol + const L = 24 + ethMaxMsgSz + 4 + let off = 0 + req.payload = Buffer.alloc(L) + req.payload.writeUInt8(TYPED_DATA.enumIdx, 0) + off += 1 + // Write the signer path + const signerPathBuf = buildSignerPathBuf(input.signerPath, varAddrPathSzAllowed) + signerPathBuf.copy(req.payload, off) + off += signerPathBuf.length + // Parse/clean the EIP712 payload, serialize with CBOR, and write to the payload + const data = cloneTypedDataPayload(input.payload) + if (!data.primaryType || !data.types[data.primaryType]) throw new Error('primaryType must be specified and the type must be included.') + if (!data.message || !data.domain) throw new Error('message and domain must be specified.') + if (0 > Object.keys(data.types).indexOf('EIP712Domain')) throw new Error('EIP712Domain type must be defined.') + // Parse the payload to ensure we have valid EIP712 data types and that + // they are encoded such that Lattice firmware can parse them. + // We need two different encodings: one to send to the Lattice in a format that plays + // nicely with our firmware CBOR decoder. The other is formatted to be consumable by + // our EIP712 validation module. + // IMPORTANT: Create a new object for the validation payload instead of modifying input.payload + // in place, so that validation uses the correctly formatted data + const validationPayload = cloneTypedDataPayload(data) + validationPayload.message = parseEIP712Msg(cloneTypedDataPayload(data.message), cloneTypedDataPayload(data.primaryType), cloneTypedDataPayload(data.types), true) + validationPayload.domain = parseEIP712Msg(cloneTypedDataPayload(data.domain), 'EIP712Domain', cloneTypedDataPayload(data.types), true) + // Store the validation payload separately without modifying input.payload + req.validationPayload = validationPayload + + data.domain = parseEIP712Msg(data.domain, 'EIP712Domain', data.types, false) + data.message = parseEIP712Msg(data.message, data.primaryType, data.types, false) + // Now build the message to be sent to the Lattice + const payload = Buffer.from(cbor.encode(data)) + const fwConst = input.fwConstants + const maxSzAllowed = ethMaxMsgSz + fwConst.extraDataMaxFrames * fwConst.extraDataFrameSz + // Determine if we need to prehash + let shouldPrehash = payload.length > maxSzAllowed + Object.keys(data.types).forEach((k) => { + if (data.types[k].length > eip712MaxTypeParams) { + shouldPrehash = true + } + }) + if (fwConst.ethMsgPreHashAllowed && shouldPrehash) { + // If this payload is too large to send, but the Lattice allows a prehashed message, do that + req.payload.writeUInt16LE(payload.length, off) + off += 2 + const prehash = TypedDataUtils.eip712Hash(req.validationPayload, SignTypedDataVersion.V4) + const prehashBuf = Buffer.from(prehash) + prehashBuf.copy(req.payload, off) + req.prehash = prehash + } else { + const extraDataPayloads = getExtraData(payload, input) + req.extraDataPayloads = extraDataPayloads + req.payload.writeUInt16LE(payload.length, off) + off += 2 + payload.copy(req.payload, off) + off += payload.length + // Slice out the part of the buffer that we didn't use. + req.payload = req.payload.slice(0, off) + } + return req } function getExtraData(payload, input) { - const { ethMaxMsgSz, extraDataFrameSz, extraDataMaxFrames } = - input.fwConstants; - const MAX_BASE_MSG_SZ = ethMaxMsgSz; - const EXTRA_DATA_ALLOWED = extraDataFrameSz > 0 && extraDataMaxFrames > 0; - const extraDataPayloads = []; - if (payload.length > MAX_BASE_MSG_SZ) { - // Determine sizes and run through sanity checks - const maxSzAllowed = - MAX_BASE_MSG_SZ + extraDataMaxFrames * extraDataFrameSz; - if (!EXTRA_DATA_ALLOWED) - throw new Error( - `Your message is ${payload.length} bytes, but can only be a maximum of ${MAX_BASE_MSG_SZ}`, - ); - else if (EXTRA_DATA_ALLOWED && payload.length > maxSzAllowed) - throw new Error( - `Your message is ${payload.length} bytes, but can only be a maximum of ${maxSzAllowed}`, - ); - // Split overflow data into extraData frames - const frames = splitFrames( - payload.slice(MAX_BASE_MSG_SZ), - extraDataFrameSz, - ); - frames.forEach((frame) => { - const szLE = Buffer.alloc(4); - szLE.writeUInt32LE(frame.length, 0); - extraDataPayloads.push(Buffer.concat([szLE, frame])); - }); - } - return extraDataPayloads; + const { ethMaxMsgSz, extraDataFrameSz, extraDataMaxFrames } = input.fwConstants + const MAX_BASE_MSG_SZ = ethMaxMsgSz + const EXTRA_DATA_ALLOWED = extraDataFrameSz > 0 && extraDataMaxFrames > 0 + const extraDataPayloads = [] + if (payload.length > MAX_BASE_MSG_SZ) { + // Determine sizes and run through sanity checks + const maxSzAllowed = MAX_BASE_MSG_SZ + extraDataMaxFrames * extraDataFrameSz + if (!EXTRA_DATA_ALLOWED) throw new Error(`Your message is ${payload.length} bytes, but can only be a maximum of ${MAX_BASE_MSG_SZ}`) + else if (EXTRA_DATA_ALLOWED && payload.length > maxSzAllowed) throw new Error(`Your message is ${payload.length} bytes, but can only be a maximum of ${maxSzAllowed}`) + // Split overflow data into extraData frames + const frames = splitFrames(payload.slice(MAX_BASE_MSG_SZ), extraDataFrameSz) + frames.forEach((frame) => { + const szLE = Buffer.alloc(4) + szLE.writeUInt32LE(frame.length, 0) + extraDataPayloads.push(Buffer.concat([szLE, frame])) + }) + } + return extraDataPayloads } function parseEIP712Msg(msg, typeName, types, forJSParser = false) { - const type = types[typeName]; - type.forEach((item) => { - const isArrayType = item.type.indexOf('[') > -1; - const singularType = isArrayType - ? item.type.slice(0, item.type.indexOf('[')) - : item.type; - const isCustomType = Object.keys(types).indexOf(singularType) > -1; - if (isCustomType && Array.isArray(msg)) { - // For custom types we need to jump into the `msg` using the key (name of type) and - // parse that entire sub-struct as if it were a message. - // We will recurse into sub-structs until we reach a level where every item is an - // elementary (i.e. non-custom) type. - // For arrays, we need to loop through each message item. - for (let i = 0; i < msg.length; i++) { - msg[i][item.name] = parseEIP712Msg( - msg[i][item.name], - singularType, - types, - forJSParser, - ); - } - } else if (isCustomType) { - // Not an array means we can jump directly into the sub-struct to convert - msg[item.name] = parseEIP712Msg( - msg[item.name], - singularType, - types, - forJSParser, - ); - } else if (Array.isArray(msg)) { - // If we have an array for this particular type and the type we are parsing - // is *not* a custom type, loop through the array elements and convert the types. - for (let i = 0; i < msg.length; i++) { - if (isArrayType) { - // If this type is itself an array, loop through those elements and parse individually. - // This code is not reachable for custom types so we assume these are arrays of - // elementary types. - for (let j = 0; j < msg[i][item.name].length; j++) { - msg[i][item.name][j] = parseEIP712Item( - msg[i][item.name][j], - singularType, - forJSParser, - ); - } - } else { - // Non-arrays parse + replace one value for the elementary type - msg[i][item.name] = parseEIP712Item( - msg[i][item.name], - singularType, - forJSParser, - ); - } - } - } else if (isArrayType) { - // If we have an elementary array type and a non-array message level, - //loop through the array and parse + replace each item individually. - for (let i = 0; i < msg[item.name].length; i++) { - msg[item.name][i] = parseEIP712Item( - msg[item.name][i], - singularType, - forJSParser, - ); - } - } else { - // If this is a singular elementary type, simply parse + replace. - msg[item.name] = parseEIP712Item( - msg[item.name], - singularType, - forJSParser, - ); - } - }); - - return msg; + const type = types[typeName] + type.forEach((item) => { + const isArrayType = item.type.indexOf('[') > -1 + const singularType = isArrayType ? item.type.slice(0, item.type.indexOf('[')) : item.type + const isCustomType = Object.keys(types).indexOf(singularType) > -1 + if (isCustomType && Array.isArray(msg)) { + // For custom types we need to jump into the `msg` using the key (name of type) and + // parse that entire sub-struct as if it were a message. + // We will recurse into sub-structs until we reach a level where every item is an + // elementary (i.e. non-custom) type. + // For arrays, we need to loop through each message item. + for (let i = 0; i < msg.length; i++) { + msg[i][item.name] = parseEIP712Msg(msg[i][item.name], singularType, types, forJSParser) + } + } else if (isCustomType) { + // Not an array means we can jump directly into the sub-struct to convert + msg[item.name] = parseEIP712Msg(msg[item.name], singularType, types, forJSParser) + } else if (Array.isArray(msg)) { + // If we have an array for this particular type and the type we are parsing + // is *not* a custom type, loop through the array elements and convert the types. + for (let i = 0; i < msg.length; i++) { + if (isArrayType) { + // If this type is itself an array, loop through those elements and parse individually. + // This code is not reachable for custom types so we assume these are arrays of + // elementary types. + for (let j = 0; j < msg[i][item.name].length; j++) { + msg[i][item.name][j] = parseEIP712Item(msg[i][item.name][j], singularType, forJSParser) + } + } else { + // Non-arrays parse + replace one value for the elementary type + msg[i][item.name] = parseEIP712Item(msg[i][item.name], singularType, forJSParser) + } + } + } else if (isArrayType) { + // If we have an elementary array type and a non-array message level, + //loop through the array and parse + replace each item individually. + for (let i = 0; i < msg[item.name].length; i++) { + msg[item.name][i] = parseEIP712Item(msg[item.name][i], singularType, forJSParser) + } + } else { + // If this is a singular elementary type, simply parse + replace. + msg[item.name] = parseEIP712Item(msg[item.name], singularType, forJSParser) + } + }) + + return msg } function parseEIP712Item(data, type, forJSParser = false) { - if (type === 'bytes') { - // Variable sized bytes need to be buffer type - data = ensureHexBuffer(data); - if (forJSParser) { - // For EIP712 encoding module it's easier to encode hex strings - data = `0x${data.toString('hex')}`; - } - } else if (type.slice(0, 5) === 'bytes') { - // Fixed sizes bytes need to be buffer type. We also add some sanity checks. - const nBytes = Number.parseInt(type.slice(5)); - data = ensureHexBuffer(data); - // Edge case to handle empty bytesN values - if (data.length === 0) { - data = Buffer.alloc(nBytes); - } - if (data.length !== nBytes) - throw new Error(`Expected ${type} type, but got ${data.length} bytes`); - if (forJSParser) { - // For EIP712 encoding module it's easier to encode hex strings - data = `0x${data.toString('hex')}`; - } - } else if (type === 'address') { - // Address must be a 20 byte buffer - data = ensureHexBuffer(data); - // Edge case to handle the 0-address - if (data.length === 0) { - data = Buffer.alloc(20); - } - if (data.length !== 20) - throw new Error( - `Address type must be 20 bytes, but got ${data.length} bytes`, - ); - // For EIP712 encoding module it's easier to encode hex strings - if (forJSParser) { - data = `0x${data.toString('hex')}`; - } - } else if ( - ethMsgProtocol.TYPED_DATA.typeCodes[type] && - type.indexOf('uint') === -1 && - type.indexOf('int') > -1 - ) { - // Handle signed integers using bignumber.js directly - if (forJSParser) { - // For EIP712 encoding in this module we need hex strings for signed ints too - const bn = new BN(data); - // Preserve full precision by returning the decimal string representation - data = bn.toFixed(); - } else { - // `bignumber.js` is needed for `cbor` encoding, which gets sent to the Lattice and plays - // nicely with its firmware cbor lib. - // NOTE: If we instantiate a `bignumber.js` object, it will not match what `borc` creates - // when run inside of the browser (i.e. MetaMask). Thus we introduce this hack to make sure - // we are creating a compatible type. - // TODO: Find another cbor lib that is compataible with the firmware's lib in a browser - // context. This is surprisingly difficult - I tried several libs and only cbor/borc have - // worked (borc is a supposedly "browser compatible" version of cbor) - data = new BN(data); - } - } else if ( - ethMsgProtocol.TYPED_DATA.typeCodes[type] && - (type.indexOf('uint') > -1 || type.indexOf('int') > -1) - ) { - // For uints, convert to a buffer and do some sanity checking. - // Note that we could probably just use bignumber.js directly as we do with - // signed ints, but this code is battle tested and we don't want to change it. - let b = ensureHexBuffer(data); - // Edge case to handle 0-value bignums - if (b.length === 0) { - b = Buffer.from('00', 'hex'); - } - // Uint256s should be encoded as bignums. - if (forJSParser) { - // For EIP712 encoding in this module we need hex strings to represent the numbers - data = `0x${b.toString('hex')}`; - } else { - // Load into bignumber.js used by cbor lib - data = new BN(b.toString('hex'), 16); - } - } else if (type === 'bool') { - // Booleans need to be cast to a u8 - data = data === true ? 1 : 0; - } - // Other types don't need to be modified - return data; + if (type === 'bytes') { + // Variable sized bytes need to be buffer type + data = ensureHexBuffer(data) + if (forJSParser) { + // For EIP712 encoding module it's easier to encode hex strings + data = `0x${data.toString('hex')}` + } + } else if (type.slice(0, 5) === 'bytes') { + // Fixed sizes bytes need to be buffer type. We also add some sanity checks. + const nBytes = Number.parseInt(type.slice(5)) + data = ensureHexBuffer(data) + // Edge case to handle empty bytesN values + if (data.length === 0) { + data = Buffer.alloc(nBytes) + } + if (data.length !== nBytes) throw new Error(`Expected ${type} type, but got ${data.length} bytes`) + if (forJSParser) { + // For EIP712 encoding module it's easier to encode hex strings + data = `0x${data.toString('hex')}` + } + } else if (type === 'address') { + // Address must be a 20 byte buffer + data = ensureHexBuffer(data) + // Edge case to handle the 0-address + if (data.length === 0) { + data = Buffer.alloc(20) + } + if (data.length !== 20) throw new Error(`Address type must be 20 bytes, but got ${data.length} bytes`) + // For EIP712 encoding module it's easier to encode hex strings + if (forJSParser) { + data = `0x${data.toString('hex')}` + } + } else if (ethMsgProtocol.TYPED_DATA.typeCodes[type] && type.indexOf('uint') === -1 && type.indexOf('int') > -1) { + // Handle signed integers using bignumber.js directly + if (forJSParser) { + // For EIP712 encoding in this module we need hex strings for signed ints too + const bn = new BN(data) + // Preserve full precision by returning the decimal string representation + data = bn.toFixed() + } else { + // `bignumber.js` is needed for `cbor` encoding, which gets sent to the Lattice and plays + // nicely with its firmware cbor lib. + // NOTE: If we instantiate a `bignumber.js` object, it will not match what `borc` creates + // when run inside of the browser (i.e. MetaMask). Thus we introduce this hack to make sure + // we are creating a compatible type. + // TODO: Find another cbor lib that is compataible with the firmware's lib in a browser + // context. This is surprisingly difficult - I tried several libs and only cbor/borc have + // worked (borc is a supposedly "browser compatible" version of cbor) + data = new BN(data) + } + } else if (ethMsgProtocol.TYPED_DATA.typeCodes[type] && (type.indexOf('uint') > -1 || type.indexOf('int') > -1)) { + // For uints, convert to a buffer and do some sanity checking. + // Note that we could probably just use bignumber.js directly as we do with + // signed ints, but this code is battle tested and we don't want to change it. + let b = ensureHexBuffer(data) + // Edge case to handle 0-value bignums + if (b.length === 0) { + b = Buffer.from('00', 'hex') + } + // Uint256s should be encoded as bignums. + if (forJSParser) { + // For EIP712 encoding in this module we need hex strings to represent the numbers + data = `0x${b.toString('hex')}` + } else { + // Load into bignumber.js used by cbor lib + data = new BN(b.toString('hex'), 16) + } + } else if (type === 'bool') { + // Booleans need to be cast to a u8 + data = data === true ? 1 : 0 + } + // Other types don't need to be modified + return data } function get_personal_sign_prefix(L) { - return Buffer.from( - `\u0019Ethereum Signed Message:\n${L.toString()}`, - 'utf-8', - ); + return Buffer.from(`\u0019Ethereum Signed Message:\n${L.toString()}`, 'utf-8') } function get_rlp_encoded_preimage(rawTx, txType) { - if (txType) { - return Buffer.concat([ - Buffer.from([txType]), - Buffer.from(RLP.encode(rawTx)), - ]); - } else { - return Buffer.from(RLP.encode(rawTx)); - } + if (txType) { + return Buffer.concat([Buffer.from([txType]), Buffer.from(RLP.encode(rawTx))]) + } else { + return Buffer.from(RLP.encode(rawTx)) + } } /** @@ -1182,72 +970,62 @@ function get_rlp_encoded_preimage(rawTx, txType) { * hex strings, numbers, bigints). * @returns A `viem`-compatible `TransactionSerializable` object. */ -export const normalizeToViemTransaction = ( - tx: unknown, -): TransactionSerializable => { - const parsed = TransactionSchema.parse(tx); - - return { - ...parsed, - to: parsed.to as Hex, - data: parsed.data as Hex, - gas: parsed.gas, - value: parsed.value, - nonce: parsed.nonce, - chainId: parsed.chainId, - gasPrice: 'gasPrice' in parsed ? parsed.gasPrice : undefined, - maxFeePerGas: 'maxFeePerGas' in parsed ? parsed.maxFeePerGas : undefined, - maxPriorityFeePerGas: - 'maxPriorityFeePerGas' in parsed - ? parsed.maxPriorityFeePerGas - : undefined, - accessList: 'accessList' in parsed ? parsed.accessList : undefined, - authorizationList: - 'authorizationList' in parsed ? parsed.authorizationList : undefined, - }; -}; +export const normalizeToViemTransaction = (tx: unknown): TransactionSerializable => { + const parsed = TransactionSchema.parse(tx) + + return { + ...parsed, + to: parsed.to as Hex, + data: parsed.data as Hex, + gas: parsed.gas, + value: parsed.value, + nonce: parsed.nonce, + chainId: parsed.chainId, + gasPrice: 'gasPrice' in parsed ? parsed.gasPrice : undefined, + maxFeePerGas: 'maxFeePerGas' in parsed ? parsed.maxFeePerGas : undefined, + maxPriorityFeePerGas: 'maxPriorityFeePerGas' in parsed ? parsed.maxPriorityFeePerGas : undefined, + accessList: 'accessList' in parsed ? parsed.accessList : undefined, + authorizationList: 'authorizationList' in parsed ? parsed.authorizationList : undefined, + } +} /** * Convert Ethereum transaction to serialized bytes for generic signing. * Bridge function for firmware v0.15.0+ which removed legacy ETH signing paths. */ -const convertEthereumTransactionToGenericRequest = ( - req: FlexibleTransaction, -) => { - // Use the unified normalization and serialization pipeline. - // 1. Normalize the potentially varied input to a standard viem format. - const viemTx = normalizeToViemTransaction(req); - // 2. Serialize the transaction to RLP-encoded bytes. - const serializedTx = serializeTransaction(viemTx); - return Buffer.from(serializedTx.slice(2), 'hex'); -}; +const convertEthereumTransactionToGenericRequest = (req: FlexibleTransaction) => { + // Use the unified normalization and serialization pipeline. + // 1. Normalize the potentially varied input to a standard viem format. + const viemTx = normalizeToViemTransaction(req) + // 2. Serialize the transaction to RLP-encoded bytes. + const serializedTx = serializeTransaction(viemTx) + return Buffer.from(serializedTx.slice(2), 'hex') +} // Type for Ethereum generic signing request type EthereumGenericSigningRequestParams = FlexibleTransaction & { - fwConstants: FirmwareConstants; - signerPath: SigningPath; -}; + fwConstants: FirmwareConstants + signerPath: SigningPath +} /** * Build complete generic signing request for Ethereum transactions. * One-step function combining transaction conversion and generic signing setup. */ -export const buildEthereumGenericSigningRequest = ( - req: EthereumGenericSigningRequestParams, -) => { - const { fwConstants, signerPath, ...txData } = req; - - const payload = convertEthereumTransactionToGenericRequest(txData); - - return buildGenericSigningMsgRequest({ - fwConstants, - encodingType: EXTERNAL.SIGNING.ENCODINGS.EVM, - curveType: EXTERNAL.SIGNING.CURVES.SECP256K1, - hashType: EXTERNAL.SIGNING.HASHES.KECCAK256, - signerPath, - payload, - }); -}; +export const buildEthereumGenericSigningRequest = (req: EthereumGenericSigningRequestParams) => { + const { fwConstants, signerPath, ...txData } = req + + const payload = convertEthereumTransactionToGenericRequest(txData) + + return buildGenericSigningMsgRequest({ + fwConstants, + encodingType: EXTERNAL.SIGNING.ENCODINGS.EVM, + curveType: EXTERNAL.SIGNING.CURVES.SECP256K1, + hashType: EXTERNAL.SIGNING.HASHES.KECCAK256, + signerPath, + payload, + }) +} /** * Serializes an EIP7702 transaction using Viem. @@ -1256,165 +1034,113 @@ export const buildEthereumGenericSigningRequest = ( * @returns The serialized transaction as a hex string */ export function serializeEIP7702Transaction(tx: TransactionRequest): Hex { - if ( - tx.type !== TRANSACTION_TYPE.EIP7702_AUTH_LIST && - tx.type !== TRANSACTION_TYPE.EIP7702_AUTH - ) { - throw new Error( - `Only EIP-7702 auth transactions (type ${TRANSACTION_TYPE.EIP7702_AUTH}) and auth-list transactions (type ${TRANSACTION_TYPE.EIP7702_AUTH_LIST}) are supported`, - ); - } - - // Type guard to ensure we have an EIP7702 transaction with appropriate authorization data - const hasAuthList = 'authorizationList' in tx; - const hasSingleAuth = 'authorization' in tx; - - if (!hasAuthList && !hasSingleAuth) { - throw new Error( - 'Transaction does not have authorization or authorizationList property', - ); - } - - // For type 4 transactions, convert single authorization to array format - let authorizationList: any[]; - if (tx.type === TRANSACTION_TYPE.EIP7702_AUTH) { - if (!hasSingleAuth) { - throw new Error( - 'EIP-7702 auth transaction (type 4) must contain authorization property', - ); - } - authorizationList = [(tx as any).authorization]; - } else { - // Type 5 transaction - only handle authorizationList field - if (hasAuthList) { - authorizationList = (tx as any).authorizationList; - } else { - throw new Error( - 'EIP-7702 auth list transaction (type 5) must contain authorizationList property', - ); - } - } - - // Validate that all required fields exist - if ( - !authorizationList || - !Array.isArray(authorizationList) || - authorizationList.length === 0 - ) { - throw new Error( - 'EIP-7702 transaction must contain at least one authorization', - ); - } - - // Validate each authorization - authorizationList.forEach((auth, index) => { - if (!auth.address) { - throw new Error( - `Authorization at index ${index} is missing a contract address`, - ); - } - }); - - // Validate required transaction fields - if (!tx.to) { - throw new Error('EIP-7702 transaction must include a valid "to" address'); - } - - // Convert to Viem's expected format - const viemTx = { - type: 'eip7702' as const, - chainId: tx.chainId, - nonce: tx.nonce, - maxPriorityFeePerGas: - typeof tx.maxPriorityFeePerGas === 'string' - ? BigInt(tx.maxPriorityFeePerGas) - : tx.maxPriorityFeePerGas, - maxFeePerGas: - typeof tx.maxFeePerGas === 'string' - ? BigInt(tx.maxFeePerGas) - : tx.maxFeePerGas, - gas: - typeof (tx as any).gas === 'string' - ? BigInt((tx as any).gas) - : (tx as any).gas || - (typeof (tx as any).gasLimit === 'string' - ? BigInt((tx as any).gasLimit) - : (tx as any).gasLimit), - to: tx.to as `0x${string}`, - value: typeof tx.value === 'string' ? BigInt(tx.value) : tx.value, - data: tx.data || '0x', - authorizationList: authorizationList.map((auth, idx) => { - // Create the Viem-formatted authorization - // Ensure proper address handling with 0x prefix - const address = auth.address || ''; - const addressStr = - typeof address === 'string' - ? address.startsWith('0x') - ? address - : `0x${address}` - : '0x'; - - if (!addressStr || addressStr === '0x') { - throw new Error( - `Authorization at index ${idx} is missing a valid address`, - ); - } - - // Handle viem's SignedAuthorization format - if ('signature' in auth && auth.signature) { - // Viem format with nested signature - return { - chainId: auth.chainId, - address: addressStr as `0x${string}`, - nonce: BigInt(auth.nonce || 0), - signature: auth.signature, - }; - } else { - // Direct signature properties (r, s, yParity/v) - return { - chainId: auth.chainId, - address: addressStr as `0x${string}`, - nonce: BigInt(auth.nonce || 0), - signature: { - yParity: - typeof auth.yParity === 'number' - ? auth.yParity - : typeof auth.yParity === 'string' - ? auth.yParity === '0x01' || - auth.yParity === '0x1' || - auth.yParity === '1' - ? 1 - : 0 - : 0, - r: auth.r || '0x0', - s: auth.s || '0x0', - }, - }; - } - }), - }; - - return serializeTransaction(viemTx as any); + if (tx.type !== TRANSACTION_TYPE.EIP7702_AUTH_LIST && tx.type !== TRANSACTION_TYPE.EIP7702_AUTH) { + throw new Error(`Only EIP-7702 auth transactions (type ${TRANSACTION_TYPE.EIP7702_AUTH}) and auth-list transactions (type ${TRANSACTION_TYPE.EIP7702_AUTH_LIST}) are supported`) + } + + // Type guard to ensure we have an EIP7702 transaction with appropriate authorization data + const hasAuthList = 'authorizationList' in tx + const hasSingleAuth = 'authorization' in tx + + if (!hasAuthList && !hasSingleAuth) { + throw new Error('Transaction does not have authorization or authorizationList property') + } + + // For type 4 transactions, convert single authorization to array format + let authorizationList: any[] + if (tx.type === TRANSACTION_TYPE.EIP7702_AUTH) { + if (!hasSingleAuth) { + throw new Error('EIP-7702 auth transaction (type 4) must contain authorization property') + } + authorizationList = [(tx as any).authorization] + } else { + // Type 5 transaction - only handle authorizationList field + if (hasAuthList) { + authorizationList = (tx as any).authorizationList + } else { + throw new Error('EIP-7702 auth list transaction (type 5) must contain authorizationList property') + } + } + + // Validate that all required fields exist + if (!authorizationList || !Array.isArray(authorizationList) || authorizationList.length === 0) { + throw new Error('EIP-7702 transaction must contain at least one authorization') + } + + // Validate each authorization + authorizationList.forEach((auth, index) => { + if (!auth.address) { + throw new Error(`Authorization at index ${index} is missing a contract address`) + } + }) + + // Validate required transaction fields + if (!tx.to) { + throw new Error('EIP-7702 transaction must include a valid "to" address') + } + + // Convert to Viem's expected format + const viemTx = { + type: 'eip7702' as const, + chainId: tx.chainId, + nonce: tx.nonce, + maxPriorityFeePerGas: typeof tx.maxPriorityFeePerGas === 'string' ? BigInt(tx.maxPriorityFeePerGas) : tx.maxPriorityFeePerGas, + maxFeePerGas: typeof tx.maxFeePerGas === 'string' ? BigInt(tx.maxFeePerGas) : tx.maxFeePerGas, + gas: typeof (tx as any).gas === 'string' ? BigInt((tx as any).gas) : (tx as any).gas || (typeof (tx as any).gasLimit === 'string' ? BigInt((tx as any).gasLimit) : (tx as any).gasLimit), + to: tx.to as `0x${string}`, + value: typeof tx.value === 'string' ? BigInt(tx.value) : tx.value, + data: tx.data || '0x', + authorizationList: authorizationList.map((auth, idx) => { + // Create the Viem-formatted authorization + // Ensure proper address handling with 0x prefix + const address = auth.address || '' + const addressStr = typeof address === 'string' ? (address.startsWith('0x') ? address : `0x${address}`) : '0x' + + if (!addressStr || addressStr === '0x') { + throw new Error(`Authorization at index ${idx} is missing a valid address`) + } + + // Handle viem's SignedAuthorization format + if ('signature' in auth && auth.signature) { + // Viem format with nested signature + return { + chainId: auth.chainId, + address: addressStr as `0x${string}`, + nonce: BigInt(auth.nonce || 0), + signature: auth.signature, + } + } else { + // Direct signature properties (r, s, yParity/v) + return { + chainId: auth.chainId, + address: addressStr as `0x${string}`, + nonce: BigInt(auth.nonce || 0), + signature: { + yParity: typeof auth.yParity === 'number' ? auth.yParity : typeof auth.yParity === 'string' ? (auth.yParity === '0x01' || auth.yParity === '0x1' || auth.yParity === '1' ? 1 : 0) : 0, + r: auth.r || '0x0', + s: auth.s || '0x0', + }, + } + } + }), + } + + return serializeTransaction(viemTx as any) } export const isEip7702Transaction = (tx: TransactionRequest): boolean => { - return ( - typeof tx === 'object' && - 'type' in tx && - (tx.type === TRANSACTION_TYPE.EIP7702_AUTH_LIST || - tx.type === TRANSACTION_TYPE.EIP7702_AUTH) - ); -}; + return typeof tx === 'object' && 'type' in tx && (tx.type === TRANSACTION_TYPE.EIP7702_AUTH_LIST || tx.type === TRANSACTION_TYPE.EIP7702_AUTH) +} export default { - buildEthereumMsgRequest, - validateEthereumMsgResponse, - buildEthereumTxRequest, - buildEthRawTx, - hashTransaction, - chainIds, - ensureHexBuffer, - normalizeToViemTransaction, - convertEthereumTransactionToGenericRequest, - buildEthereumGenericSigningRequest, -}; + buildEthereumMsgRequest, + validateEthereumMsgResponse, + buildEthereumTxRequest, + buildEthRawTx, + hashTransaction, + chainIds, + ensureHexBuffer, + normalizeToViemTransaction, + convertEthereumTransactionToGenericRequest, + buildEthereumGenericSigningRequest, +} diff --git a/packages/sdk/src/functions/addKvRecords.ts b/packages/sdk/src/functions/addKvRecords.ts index 0ce54783..045d241a 100644 --- a/packages/sdk/src/functions/addKvRecords.ts +++ b/packages/sdk/src/functions/addKvRecords.ts @@ -1,17 +1,6 @@ -import { - LatticeSecureEncryptedRequestType, - encryptedSecureRequest, -} from '../protocol'; -import { - validateConnectedClient, - validateKvRecord, - validateKvRecords, -} from '../shared/validators'; -import type { - AddKvRecordsRequestFunctionParams, - FirmwareConstants, - KVRecords, -} from '../types'; +import { LatticeSecureEncryptedRequestType, encryptedSecureRequest } from '../protocol' +import { validateConnectedClient, validateKvRecord, validateKvRecords } from '../shared/validators' +import type { AddKvRecordsRequestFunctionParams, FirmwareConstants, KVRecords } from '../types' /** * `addKvRecords` takes in a set of key-value records and sends a request to add them to the @@ -19,83 +8,74 @@ import type { * @category Lattice * @returns A callback with an error or null. */ -export async function addKvRecords({ - client, - records, - type, - caseSensitive, -}: AddKvRecordsRequestFunctionParams): Promise { - const { url, sharedSecret, ephemeralPub, fwConstants } = - validateConnectedClient(client); - validateAddKvRequest({ records, fwConstants }); +export async function addKvRecords({ client, records, type, caseSensitive }: AddKvRecordsRequestFunctionParams): Promise { + const { url, sharedSecret, ephemeralPub, fwConstants } = validateConnectedClient(client) + validateAddKvRequest({ records, fwConstants }) - // Build the data for this request - const data = encodeAddKvRecordsRequest({ - records, - type, - caseSensitive, - fwConstants, - }); + // Build the data for this request + const data = encodeAddKvRecordsRequest({ + records, + type, + caseSensitive, + fwConstants, + }) - const { decryptedData, newEphemeralPub } = await encryptedSecureRequest({ - data, - requestType: LatticeSecureEncryptedRequestType.addKvRecords, - sharedSecret, - ephemeralPub, - url, - }); + const { decryptedData, newEphemeralPub } = await encryptedSecureRequest({ + data, + requestType: LatticeSecureEncryptedRequestType.addKvRecords, + sharedSecret, + ephemeralPub, + url, + }) - client.mutate({ - ephemeralPub: newEphemeralPub, - }); + client.mutate({ + ephemeralPub: newEphemeralPub, + }) - return decryptedData; + return decryptedData } export const validateAddKvRequest = ({ - records, - fwConstants, + records, + fwConstants, }: { - records: KVRecords; - fwConstants: FirmwareConstants; + records: KVRecords + fwConstants: FirmwareConstants }) => { - validateKvRecords(records, fwConstants); -}; + validateKvRecords(records, fwConstants) +} export const encodeAddKvRecordsRequest = ({ - records, - type, - caseSensitive, - fwConstants, + records, + type, + caseSensitive, + fwConstants, }: { - records: KVRecords; - type: number; - caseSensitive: boolean; - fwConstants: FirmwareConstants; + records: KVRecords + type: number + caseSensitive: boolean + fwConstants: FirmwareConstants }) => { - const payload = Buffer.alloc(1 + 139 * fwConstants.kvActionMaxNum); - payload.writeUInt8(Object.keys(records).length, 0); - let off = 1; - Object.entries(records).forEach(([_key, _val]) => { - const { key, val } = validateKvRecord( - { key: _key, val: _val }, - fwConstants, - ); - // Skip the ID portion. This will get added by firmware. - payload.writeUInt32LE(0, off); - off += 4; - payload.writeUInt32LE(type, off); - off += 4; - payload.writeUInt8(caseSensitive ? 1 : 0, off); - off += 1; - payload.writeUInt8(String(key).length + 1, off); - off += 1; - Buffer.from(String(key)).copy(payload, off); - off += fwConstants.kvKeyMaxStrSz + 1; - payload.writeUInt8(String(val).length + 1, off); - off += 1; - Buffer.from(String(val)).copy(payload, off); - off += fwConstants.kvValMaxStrSz + 1; - }); - return payload; -}; + const payload = Buffer.alloc(1 + 139 * fwConstants.kvActionMaxNum) + payload.writeUInt8(Object.keys(records).length, 0) + let off = 1 + Object.entries(records).forEach(([_key, _val]) => { + const { key, val } = validateKvRecord({ key: _key, val: _val }, fwConstants) + // Skip the ID portion. This will get added by firmware. + payload.writeUInt32LE(0, off) + off += 4 + payload.writeUInt32LE(type, off) + off += 4 + payload.writeUInt8(caseSensitive ? 1 : 0, off) + off += 1 + payload.writeUInt8(String(key).length + 1, off) + off += 1 + Buffer.from(String(key)).copy(payload, off) + off += fwConstants.kvKeyMaxStrSz + 1 + payload.writeUInt8(String(val).length + 1, off) + off += 1 + Buffer.from(String(val)).copy(payload, off) + off += fwConstants.kvValMaxStrSz + 1 + }) + return payload +} diff --git a/packages/sdk/src/functions/connect.ts b/packages/sdk/src/functions/connect.ts index bd03b931..5449150f 100644 --- a/packages/sdk/src/functions/connect.ts +++ b/packages/sdk/src/functions/connect.ts @@ -1,88 +1,76 @@ -import { ProtocolConstants, connectSecureRequest } from '../protocol'; -import { doesFetchWalletsOnLoad } from '../shared/predicates'; -import { getSharedSecret, parseWallets } from '../shared/utilities'; -import { - validateBaseUrl, - validateDeviceId, - validateKey, -} from '../shared/validators'; -import type { - ActiveWallets, - ConnectRequestFunctionParams, - KeyPair, -} from '../types'; -import { aes256_decrypt, getP256KeyPairFromPub } from '../util'; +import { ProtocolConstants, connectSecureRequest } from '../protocol' +import { doesFetchWalletsOnLoad } from '../shared/predicates' +import { getSharedSecret, parseWallets } from '../shared/utilities' +import { validateBaseUrl, validateDeviceId, validateKey } from '../shared/validators' +import type { ActiveWallets, ConnectRequestFunctionParams, KeyPair } from '../types' +import { aes256_decrypt, getP256KeyPairFromPub } from '../util' -export async function connect({ - client, - id, -}: ConnectRequestFunctionParams): Promise { - const { deviceId, key, baseUrl } = validateConnectRequest({ - deviceId: id, - // @ts-expect-error - private access - key: client.key, - baseUrl: client.baseUrl, - }); +export async function connect({ client, id }: ConnectRequestFunctionParams): Promise { + const { deviceId, key, baseUrl } = validateConnectRequest({ + deviceId: id, + // @ts-expect-error - private access + key: client.key, + baseUrl: client.baseUrl, + }) - const url = `${baseUrl}/${deviceId}`; + const url = `${baseUrl}/${deviceId}` - const respPayloadData = await connectSecureRequest({ - url, - pubkey: client.publicKey, - }); + const respPayloadData = await connectSecureRequest({ + url, + pubkey: client.publicKey, + }) - // Decode response data params. - // Response payload data is *not* encrypted. - const { isPaired, fwVersion, activeWallets, ephemeralPub } = - await decodeConnectResponse(respPayloadData, key); + // Decode response data params. + // Response payload data is *not* encrypted. + const { isPaired, fwVersion, activeWallets, ephemeralPub } = await decodeConnectResponse(respPayloadData, key) - // Update client state with response data + // Update client state with response data - client.mutate({ - deviceId, - ephemeralPub, - url, - isPaired, - fwVersion, - activeWallets, - }); + client.mutate({ + deviceId, + ephemeralPub, + url, + isPaired, + fwVersion, + activeWallets, + }) - // If we are paired and are on older firmware (<0.14.1), we need a - // follow up request to sync wallet state. - if (isPaired && !doesFetchWalletsOnLoad(client.getFwVersion())) { - await client.fetchActiveWallet(); - } + // If we are paired and are on older firmware (<0.14.1), we need a + // follow up request to sync wallet state. + if (isPaired && !doesFetchWalletsOnLoad(client.getFwVersion())) { + await client.fetchActiveWallet() + } - // Return flag indicating whether we are paired or not. - // If we are *not* already paired, the Lattice is now in - // pairing mode and expects a `finalizePairing` encrypted - // request as a follow up. - return isPaired; + // Return flag indicating whether we are paired or not. + // If we are *not* already paired, the Lattice is now in + // pairing mode and expects a `finalizePairing` encrypted + // request as a follow up. + return isPaired } export const validateConnectRequest = ({ - deviceId, - key, - baseUrl, + deviceId, + key, + baseUrl, }: { - deviceId?: string; - key?: KeyPair; - baseUrl?: string; + deviceId?: string + key?: KeyPair + baseUrl?: string }): { - deviceId: string; - key: KeyPair; - baseUrl: string; + deviceId: string + key: KeyPair + baseUrl: string } => { - const validDeviceId = validateDeviceId(deviceId); - const validKey = validateKey(key); - const validBaseUrl = validateBaseUrl(baseUrl); + const validDeviceId = validateDeviceId(deviceId) + const validKey = validateKey(key) + const validBaseUrl = validateBaseUrl(baseUrl) - return { - deviceId: validDeviceId, - key: validKey, - baseUrl: validBaseUrl, - }; -}; + return { + deviceId: validDeviceId, + key: validKey, + baseUrl: validBaseUrl, + } +} /** * `decodeConnectResponse` will call `StartPairingMode` on the device, which gives the user 60 seconds to @@ -95,47 +83,43 @@ export const validateConnectRequest = ({ * @returns true if we are paired to the device already */ export const decodeConnectResponse = ( - response: Buffer, - key: KeyPair, + response: Buffer, + key: KeyPair, ): { - isPaired: boolean; - fwVersion: Buffer; - activeWallets: ActiveWallets | undefined; - ephemeralPub: KeyPair; + isPaired: boolean + fwVersion: Buffer + activeWallets: ActiveWallets | undefined + ephemeralPub: KeyPair } => { - let off = 0; - const isPaired = - response.readUInt8(off) === ProtocolConstants.pairingStatus.paired; - off++; - // If we are already paired, we get the next ephemeral key - const pub = response.slice(off, off + 65).toString('hex'); - off += 65; // Set the public key - const ephemeralPub = getP256KeyPairFromPub(pub); - // Grab the firmware version (will be 0-length for older fw versions) It is of format - // |fix|minor|major|reserved| - const fwVersion = response.slice(off, off + 4); - off += 4; + let off = 0 + const isPaired = response.readUInt8(off) === ProtocolConstants.pairingStatus.paired + off++ + // If we are already paired, we get the next ephemeral key + const pub = response.slice(off, off + 65).toString('hex') + off += 65 // Set the public key + const ephemeralPub = getP256KeyPairFromPub(pub) + // Grab the firmware version (will be 0-length for older fw versions) It is of format + // |fix|minor|major|reserved| + const fwVersion = response.slice(off, off + 4) + off += 4 - // If we are already paired, the response will include some encrypted data about the current - // wallets This data was added in Lattice firmware v0.14.1 - if (isPaired) { - //TODO && this._fwVersionGTE(0, 14, 1)) { - // Later versions of firmware added wallet info - const encWalletData = response.slice(off, off + 160); - off += 160; - const sharedSecret = getSharedSecret(key, ephemeralPub); - const decWalletData = aes256_decrypt(encWalletData, sharedSecret); - // Sanity check to make sure the last part of the decrypted data is empty. The last 2 bytes - // are AES padding - if ( - decWalletData[decWalletData.length - 2] !== 0 || - decWalletData[decWalletData.length - 1] !== 0 - ) { - throw new Error('Failed to connect to Lattice.'); - } - const activeWallets = parseWallets(decWalletData); - return { isPaired, fwVersion, activeWallets, ephemeralPub }; - } - // return the state of our pairing - return { isPaired, fwVersion, activeWallets: undefined, ephemeralPub }; -}; + // If we are already paired, the response will include some encrypted data about the current + // wallets This data was added in Lattice firmware v0.14.1 + if (isPaired) { + //TODO && this._fwVersionGTE(0, 14, 1)) { + // Later versions of firmware added wallet info + const encWalletData = response.slice(off, off + 160) + off += 160 + const sharedSecret = getSharedSecret(key, ephemeralPub) + const decWalletData = aes256_decrypt(encWalletData, sharedSecret) + // Sanity check to make sure the last part of the decrypted data is empty. The last 2 bytes + // are AES padding + if (decWalletData[decWalletData.length - 2] !== 0 || decWalletData[decWalletData.length - 1] !== 0) { + throw new Error('Failed to connect to Lattice.') + } + const activeWallets = parseWallets(decWalletData) + return { isPaired, fwVersion, activeWallets, ephemeralPub } + } + // return the state of our pairing + return { isPaired, fwVersion, activeWallets: undefined, ephemeralPub } +} diff --git a/packages/sdk/src/functions/fetchActiveWallet.ts b/packages/sdk/src/functions/fetchActiveWallet.ts index c426bbfc..17c0b6df 100644 --- a/packages/sdk/src/functions/fetchActiveWallet.ts +++ b/packages/sdk/src/functions/fetchActiveWallet.ts @@ -1,16 +1,7 @@ -import { EMPTY_WALLET_UID } from '../constants'; -import { - LatticeSecureEncryptedRequestType, - encryptedSecureRequest, -} from '../protocol'; -import { - validateActiveWallets, - validateConnectedClient, -} from '../shared/validators'; -import type { - ActiveWallets, - FetchActiveWalletRequestFunctionParams, -} from '../types'; +import { EMPTY_WALLET_UID } from '../constants' +import { LatticeSecureEncryptedRequestType, encryptedSecureRequest } from '../protocol' +import { validateActiveWallets, validateConnectedClient } from '../shared/validators' +import type { ActiveWallets, FetchActiveWalletRequestFunctionParams } from '../types' /** * Fetch the active wallet in the device. @@ -19,60 +10,58 @@ import type { * unlocked, the external interface is considered "active" and this will return its {@link Wallet} * data. Otherwise it will return the info for the internal Lattice wallet. */ -export async function fetchActiveWallet({ - client, -}: FetchActiveWalletRequestFunctionParams): Promise { - const { url, sharedSecret, ephemeralPub } = validateConnectedClient(client); +export async function fetchActiveWallet({ client }: FetchActiveWalletRequestFunctionParams): Promise { + const { url, sharedSecret, ephemeralPub } = validateConnectedClient(client) - const { decryptedData, newEphemeralPub } = await encryptedSecureRequest({ - data: Buffer.alloc(0), - requestType: LatticeSecureEncryptedRequestType.getWallets, - sharedSecret, - ephemeralPub, - url, - }); + const { decryptedData, newEphemeralPub } = await encryptedSecureRequest({ + data: Buffer.alloc(0), + requestType: LatticeSecureEncryptedRequestType.getWallets, + sharedSecret, + ephemeralPub, + url, + }) - const activeWallets = decodeFetchActiveWalletResponse(decryptedData); - const validActiveWallets = validateActiveWallets(activeWallets); + const activeWallets = decodeFetchActiveWalletResponse(decryptedData) + const validActiveWallets = validateActiveWallets(activeWallets) - client.mutate({ - ephemeralPub: newEphemeralPub, - activeWallets: validActiveWallets, - }); + client.mutate({ + ephemeralPub: newEphemeralPub, + activeWallets: validActiveWallets, + }) - return validActiveWallets; + return validActiveWallets } export const decodeFetchActiveWalletResponse = (data: Buffer) => { - // Read the external wallet data first. If it is non-null, the external wallet will be the - // active wallet of the device and we should save it. If the external wallet is blank, it means - // there is no card present and we should save and use the interal wallet. If both wallets are - // empty, it means the device still needs to be set up. - const walletDescriptorLen = 71; - // Internal first - const activeWallets: ActiveWallets = { - internal: { - uid: EMPTY_WALLET_UID, - external: false, - name: Buffer.alloc(0), - capabilities: 0, - }, - external: { - uid: EMPTY_WALLET_UID, - external: true, - name: Buffer.alloc(0), - capabilities: 0, - }, - }; - let off = 0; - activeWallets.internal.uid = data.slice(off, off + 32); - activeWallets.internal.capabilities = data.readUInt32BE(off + 32); - activeWallets.internal.name = data.slice(off + 36, off + walletDescriptorLen); - // Offset the first item - off += walletDescriptorLen; - // External - activeWallets.external.uid = data.slice(off, off + 32); - activeWallets.external.capabilities = data.readUInt32BE(off + 32); - activeWallets.external.name = data.slice(off + 36, off + walletDescriptorLen); - return activeWallets; -}; + // Read the external wallet data first. If it is non-null, the external wallet will be the + // active wallet of the device and we should save it. If the external wallet is blank, it means + // there is no card present and we should save and use the interal wallet. If both wallets are + // empty, it means the device still needs to be set up. + const walletDescriptorLen = 71 + // Internal first + const activeWallets: ActiveWallets = { + internal: { + uid: EMPTY_WALLET_UID, + external: false, + name: Buffer.alloc(0), + capabilities: 0, + }, + external: { + uid: EMPTY_WALLET_UID, + external: true, + name: Buffer.alloc(0), + capabilities: 0, + }, + } + let off = 0 + activeWallets.internal.uid = data.slice(off, off + 32) + activeWallets.internal.capabilities = data.readUInt32BE(off + 32) + activeWallets.internal.name = data.slice(off + 36, off + walletDescriptorLen) + // Offset the first item + off += walletDescriptorLen + // External + activeWallets.external.uid = data.slice(off, off + 32) + activeWallets.external.capabilities = data.readUInt32BE(off + 32) + activeWallets.external.name = data.slice(off + 36, off + walletDescriptorLen) + return activeWallets +} diff --git a/packages/sdk/src/functions/fetchDecoder.ts b/packages/sdk/src/functions/fetchDecoder.ts index 70b4367c..a5005338 100644 --- a/packages/sdk/src/functions/fetchDecoder.ts +++ b/packages/sdk/src/functions/fetchDecoder.ts @@ -1,37 +1,27 @@ -import { validateConnectedClient } from '../shared/validators'; +import { validateConnectedClient } from '../shared/validators' -import { getClient } from '../api'; -import type { TransactionRequest } from '../types'; -import { fetchCalldataDecoder } from '../util'; +import { getClient } from '../api' +import type { TransactionRequest } from '../types' +import { fetchCalldataDecoder } from '../util' /** * `fetchDecoder` fetches the ABI for a given contract address and chain ID. * @category Lattice * @returns An object containing the ABI and encoded definition of the contract. */ -export async function fetchDecoder({ - data, - to, - chainId, -}: TransactionRequest): Promise { - try { - const client = await getClient(); - validateConnectedClient(client); +export async function fetchDecoder({ data, to, chainId }: TransactionRequest): Promise { + try { + const client = await getClient() + validateConnectedClient(client) - const fwVersion = client.getFwVersion(); - const supportsDecoderRecursion = - fwVersion.major > 0 || fwVersion.minor >= 16; + const fwVersion = client.getFwVersion() + const supportsDecoderRecursion = fwVersion.major > 0 || fwVersion.minor >= 16 - const { def } = await fetchCalldataDecoder( - data, - to, - chainId, - supportsDecoderRecursion, - ); + const { def } = await fetchCalldataDecoder(data, to, chainId, supportsDecoderRecursion) - return def; - } catch (error) { - console.warn('Failed to fetch ABI:', error); - return undefined; - } + return def + } catch (error) { + console.warn('Failed to fetch ABI:', error) + return undefined + } } diff --git a/packages/sdk/src/functions/fetchEncData.ts b/packages/sdk/src/functions/fetchEncData.ts index 3b71139f..87bc8b02 100644 --- a/packages/sdk/src/functions/fetchEncData.ts +++ b/packages/sdk/src/functions/fetchEncData.ts @@ -2,215 +2,189 @@ * Export encrypted data from the Lattice. Data must conform * to known schema, e.g. EIP2335 derived privkey export. */ -import { v4 as uuidV4 } from 'uuid'; -import { EXTERNAL } from '../constants'; -import { - LatticeSecureEncryptedRequestType, - encryptedSecureRequest, -} from '../protocol'; -import { getPathStr } from '../shared/utilities'; -import { - validateConnectedClient, - validateStartPath, - validateWallet, -} from '../shared/validators'; -import type { - EIP2335KeyExportData, - EIP2335KeyExportReq, - FetchEncDataRequestFunctionParams, - FirmwareVersion, - Wallet, -} from '../types'; +import { v4 as uuidV4 } from 'uuid' +import { EXTERNAL } from '../constants' +import { LatticeSecureEncryptedRequestType, encryptedSecureRequest } from '../protocol' +import { getPathStr } from '../shared/utilities' +import { validateConnectedClient, validateStartPath, validateWallet } from '../shared/validators' +import type { EIP2335KeyExportData, EIP2335KeyExportReq, FetchEncDataRequestFunctionParams, FirmwareVersion, Wallet } from '../types' -const { ENC_DATA } = EXTERNAL; -const ENC_DATA_ERR_STR = - 'Unknown encrypted data export type requested. Exiting.'; -const ENC_DATA_REQ_DATA_SZ = 1025; +const { ENC_DATA } = EXTERNAL +const ENC_DATA_ERR_STR = 'Unknown encrypted data export type requested. Exiting.' +const ENC_DATA_REQ_DATA_SZ = 1025 const ENC_DATA_RESP_SZ = { - EIP2335: { - CIPHERTEXT: 32, - SALT: 32, - CHECKSUM: 32, - IV: 16, - PUBKEY: 48, - }, -} as const; + EIP2335: { + CIPHERTEXT: 32, + SALT: 32, + CHECKSUM: 32, + IV: 16, + PUBKEY: 48, + }, +} as const -export async function fetchEncData({ - client, - schema, - params, -}: FetchEncDataRequestFunctionParams): Promise { - const { url, sharedSecret, ephemeralPub, fwVersion } = - validateConnectedClient(client); - const activeWallet = validateWallet(client.getActiveWallet()); - validateFetchEncDataRequest({ params }); +export async function fetchEncData({ client, schema, params }: FetchEncDataRequestFunctionParams): Promise { + const { url, sharedSecret, ephemeralPub, fwVersion } = validateConnectedClient(client) + const activeWallet = validateWallet(client.getActiveWallet()) + validateFetchEncDataRequest({ params }) - const data = encodeFetchEncDataRequest({ - schema, - params, - fwVersion, - activeWallet, - }); + const data = encodeFetchEncDataRequest({ + schema, + params, + fwVersion, + activeWallet, + }) - const { decryptedData, newEphemeralPub } = await encryptedSecureRequest({ - data, - requestType: LatticeSecureEncryptedRequestType.fetchEncryptedData, - sharedSecret, - ephemeralPub, - url, - }); + const { decryptedData, newEphemeralPub } = await encryptedSecureRequest({ + data, + requestType: LatticeSecureEncryptedRequestType.fetchEncryptedData, + sharedSecret, + ephemeralPub, + url, + }) - client.mutate({ - ephemeralPub: newEphemeralPub, - }); + client.mutate({ + ephemeralPub: newEphemeralPub, + }) - return decodeFetchEncData({ data: decryptedData, schema, params }); + return decodeFetchEncData({ data: decryptedData, schema, params }) } export const validateFetchEncDataRequest = ({ - params, + params, }: { - params: EIP2335KeyExportReq; + params: EIP2335KeyExportReq }) => { - // Validate derivation path - validateStartPath(params.path); -}; + // Validate derivation path + validateStartPath(params.path) +} export const encodeFetchEncDataRequest = ({ - schema, - params, - fwVersion, - activeWallet, + schema, + params, + fwVersion, + activeWallet, }: { - schema: number; - params: EIP2335KeyExportReq; - fwVersion: FirmwareVersion; - activeWallet: Wallet; + schema: number + params: EIP2335KeyExportReq + fwVersion: FirmwareVersion + activeWallet: Wallet }) => { - // Check firmware version - if (fwVersion.major < 1 && fwVersion.minor < 17) { - throw new Error( - 'Firmware version >=v0.17.0 is required for encrypted data export.', - ); - } - // Update params depending on what type of data is being exported - if (schema === ENC_DATA.SCHEMAS.BLS_KEYSTORE_EIP2335_PBKDF_V4) { - // Set the wallet UID to the client's current active wallet - params.walletUID = activeWallet.uid; - } else { - throw new Error(ENC_DATA_ERR_STR); - } - // Build the payload data - const payload = Buffer.alloc(ENC_DATA_REQ_DATA_SZ); - let off = 0; - payload.writeUInt8(schema, off); - off += 1; - if (schema === ENC_DATA.SCHEMAS.BLS_KEYSTORE_EIP2335_PBKDF_V4) { - params.walletUID.copy(payload, off); - off += params.walletUID.length; - payload.writeUInt8(params.path.length, off); - off += 1; - for (let i = 0; i < 5; i++) { - if (i <= params.path.length) { - payload.writeUInt32LE(params.path[i], off); - } - off += 4; - } - if (params.c) { - payload.writeUInt32LE(params.c, off); - } - off += 4; - return payload; - } else { - throw new Error(ENC_DATA_ERR_STR); - } -}; + // Check firmware version + if (fwVersion.major < 1 && fwVersion.minor < 17) { + throw new Error('Firmware version >=v0.17.0 is required for encrypted data export.') + } + // Update params depending on what type of data is being exported + if (schema === ENC_DATA.SCHEMAS.BLS_KEYSTORE_EIP2335_PBKDF_V4) { + // Set the wallet UID to the client's current active wallet + params.walletUID = activeWallet.uid + } else { + throw new Error(ENC_DATA_ERR_STR) + } + // Build the payload data + const payload = Buffer.alloc(ENC_DATA_REQ_DATA_SZ) + let off = 0 + payload.writeUInt8(schema, off) + off += 1 + if (schema === ENC_DATA.SCHEMAS.BLS_KEYSTORE_EIP2335_PBKDF_V4) { + params.walletUID.copy(payload, off) + off += params.walletUID.length + payload.writeUInt8(params.path.length, off) + off += 1 + for (let i = 0; i < 5; i++) { + if (i <= params.path.length) { + payload.writeUInt32LE(params.path[i], off) + } + off += 4 + } + if (params.c) { + payload.writeUInt32LE(params.c, off) + } + off += 4 + return payload + } else { + throw new Error(ENC_DATA_ERR_STR) + } +} export const decodeFetchEncData = ({ - data, - schema, - params, + data, + schema, + params, }: { - schema: number; - params: EIP2335KeyExportReq; - data: Buffer; + schema: number + params: EIP2335KeyExportReq + data: Buffer }): Buffer => { - let off = 0; - if (schema === ENC_DATA.SCHEMAS.BLS_KEYSTORE_EIP2335_PBKDF_V4) { - const respData = {} as EIP2335KeyExportData; - const { CIPHERTEXT, SALT, CHECKSUM, IV, PUBKEY } = ENC_DATA_RESP_SZ.EIP2335; - const expectedSz = - 4 + // iterations = u32 - CIPHERTEXT + - SALT + - CHECKSUM + - IV + - PUBKEY; - const dataSz = data.readUInt32LE(off); - off += 4; - if (dataSz !== expectedSz) { - throw new Error( - 'Invalid data returned from Lattice. Expected EIP2335 data.', - ); - } - respData.iterations = data.readUInt32LE(off); - off += 4; - respData.cipherText = data.slice(off, off + CIPHERTEXT); - off += CIPHERTEXT; - respData.salt = data.slice(off, off + SALT); - off += SALT; - respData.checksum = data.slice(off, off + CHECKSUM); - off += CHECKSUM; - respData.iv = data.slice(off, off + IV); - off += IV; - respData.pubkey = data.slice(off, off + PUBKEY); - off += PUBKEY; - return formatEIP2335ExportData(respData, params.path); - } else { - throw new Error(ENC_DATA_ERR_STR); - } -}; + let off = 0 + if (schema === ENC_DATA.SCHEMAS.BLS_KEYSTORE_EIP2335_PBKDF_V4) { + const respData = {} as EIP2335KeyExportData + const { CIPHERTEXT, SALT, CHECKSUM, IV, PUBKEY } = ENC_DATA_RESP_SZ.EIP2335 + const expectedSz = + 4 + // iterations = u32 + CIPHERTEXT + + SALT + + CHECKSUM + + IV + + PUBKEY + const dataSz = data.readUInt32LE(off) + off += 4 + if (dataSz !== expectedSz) { + throw new Error('Invalid data returned from Lattice. Expected EIP2335 data.') + } + respData.iterations = data.readUInt32LE(off) + off += 4 + respData.cipherText = data.slice(off, off + CIPHERTEXT) + off += CIPHERTEXT + respData.salt = data.slice(off, off + SALT) + off += SALT + respData.checksum = data.slice(off, off + CHECKSUM) + off += CHECKSUM + respData.iv = data.slice(off, off + IV) + off += IV + respData.pubkey = data.slice(off, off + PUBKEY) + off += PUBKEY + return formatEIP2335ExportData(respData, params.path) + } else { + throw new Error(ENC_DATA_ERR_STR) + } +} -const formatEIP2335ExportData = ( - resp: EIP2335KeyExportData, - path: number[], -): Buffer => { - try { - const { iterations, salt, checksum, iv, cipherText, pubkey } = resp; - return Buffer.from( - JSON.stringify({ - version: 4, - uuid: uuidV4(), - path: getPathStr(path), - pubkey: pubkey.toString('hex'), - crypto: { - kdf: { - function: 'pbkdf2', - params: { - dklen: 32, - c: iterations, - prf: 'hmac-sha256', - salt: salt.toString('hex'), - }, - message: '', - }, - checksum: { - function: 'sha256', - params: {}, - message: checksum.toString('hex'), - }, - cipher: { - function: 'aes-128-ctr', - params: { - iv: iv.toString('hex'), - }, - message: cipherText.toString('hex'), - }, - }, - }), - ); - } catch (err) { - throw Error(`Failed to format EIP2335 return data: ${err.toString()}`); - } -}; +const formatEIP2335ExportData = (resp: EIP2335KeyExportData, path: number[]): Buffer => { + try { + const { iterations, salt, checksum, iv, cipherText, pubkey } = resp + return Buffer.from( + JSON.stringify({ + version: 4, + uuid: uuidV4(), + path: getPathStr(path), + pubkey: pubkey.toString('hex'), + crypto: { + kdf: { + function: 'pbkdf2', + params: { + dklen: 32, + c: iterations, + prf: 'hmac-sha256', + salt: salt.toString('hex'), + }, + message: '', + }, + checksum: { + function: 'sha256', + params: {}, + message: checksum.toString('hex'), + }, + cipher: { + function: 'aes-128-ctr', + params: { + iv: iv.toString('hex'), + }, + message: cipherText.toString('hex'), + }, + }, + }), + ) + } catch (err) { + throw Error(`Failed to format EIP2335 return data: ${err.toString()}`) + } +} diff --git a/packages/sdk/src/functions/getAddresses.ts b/packages/sdk/src/functions/getAddresses.ts index 5813132d..f081e7b7 100644 --- a/packages/sdk/src/functions/getAddresses.ts +++ b/packages/sdk/src/functions/getAddresses.ts @@ -1,22 +1,7 @@ -import { - LatticeGetAddressesFlag, - LatticeSecureEncryptedRequestType, - ProtocolConstants, - encryptedSecureRequest, -} from '../protocol'; -import { - validateConnectedClient, - validateIsUInt4, - validateNAddresses, - validateStartPath, - validateWallet, -} from '../shared/validators'; -import type { - FirmwareConstants, - GetAddressesRequestFunctionParams, - Wallet, -} from '../types'; -import { isValidAssetPath } from '../util'; +import { LatticeGetAddressesFlag, LatticeSecureEncryptedRequestType, ProtocolConstants, encryptedSecureRequest } from '../protocol' +import { validateConnectedClient, validateIsUInt4, validateNAddresses, validateStartPath, validateWallet } from '../shared/validators' +import type { FirmwareConstants, GetAddressesRequestFunctionParams, Wallet } from '../types' +import { isValidAssetPath } from '../util' /** * `getAddresses` takes a starting path and a number to get the addresses or public keys associated @@ -24,192 +9,168 @@ import { isValidAssetPath } from '../util'; * @category Lattice * @returns An array of addresses or public keys. */ -export async function getAddresses({ - client, - startPath: _startPath, - n: _n, - flag: _flag, - iterIdx, -}: GetAddressesRequestFunctionParams): Promise { - const { url, sharedSecret, ephemeralPub, fwConstants } = - validateConnectedClient(client); - const activeWallet = validateWallet(client.getActiveWallet()); - - const { startPath, n, flag } = validateGetAddressesRequest({ - startPath: _startPath, - n: _n, - flag: _flag, - }); - - const data = encodeGetAddressesRequest({ - startPath, - n, - flag, - fwConstants, - wallet: activeWallet, - iterIdx, - }); - - const { decryptedData, newEphemeralPub } = await encryptedSecureRequest({ - data, - requestType: LatticeSecureEncryptedRequestType.getAddresses, - sharedSecret, - ephemeralPub, - url, - }); - - client.mutate({ - ephemeralPub: newEphemeralPub, - }); - - return decodeGetAddressesResponse(decryptedData, flag); +export async function getAddresses({ client, startPath: _startPath, n: _n, flag: _flag, iterIdx }: GetAddressesRequestFunctionParams): Promise { + const { url, sharedSecret, ephemeralPub, fwConstants } = validateConnectedClient(client) + const activeWallet = validateWallet(client.getActiveWallet()) + + const { startPath, n, flag } = validateGetAddressesRequest({ + startPath: _startPath, + n: _n, + flag: _flag, + }) + + const data = encodeGetAddressesRequest({ + startPath, + n, + flag, + fwConstants, + wallet: activeWallet, + iterIdx, + }) + + const { decryptedData, newEphemeralPub } = await encryptedSecureRequest({ + data, + requestType: LatticeSecureEncryptedRequestType.getAddresses, + sharedSecret, + ephemeralPub, + url, + }) + + client.mutate({ + ephemeralPub: newEphemeralPub, + }) + + return decodeGetAddressesResponse(decryptedData, flag) } export const validateGetAddressesRequest = ({ - startPath, - n, - flag, + startPath, + n, + flag, }: { - startPath?: number[]; - n?: number; - flag?: number; + startPath?: number[] + n?: number + flag?: number }) => { - return { - startPath: validateStartPath(startPath), - n: validateNAddresses(n), - flag: validateIsUInt4(flag), - }; -}; + return { + startPath: validateStartPath(startPath), + n: validateNAddresses(n), + flag: validateIsUInt4(flag), + } +} export const encodeGetAddressesRequest = ({ - startPath, - n, - flag, - fwConstants, - wallet, - iterIdx, + startPath, + n, + flag, + fwConstants, + wallet, + iterIdx, }: { - startPath: number[]; - n: number; - flag: number; - fwConstants: FirmwareConstants; - wallet: Wallet; - iterIdx?: number; + startPath: number[] + n: number + flag: number + fwConstants: FirmwareConstants + wallet: Wallet + iterIdx?: number }) => { - const flags = fwConstants.getAddressFlags || ([] as any[]); - const isPubkeyOnly = - flags.indexOf(flag) > -1 && - (flag === LatticeGetAddressesFlag.ed25519Pubkey || - flag === LatticeGetAddressesFlag.secp256k1Pubkey || - flag === LatticeGetAddressesFlag.bls12_381Pubkey); - const isXpub = flag === LatticeGetAddressesFlag.secp256k1Xpub; - if (!isPubkeyOnly && !isXpub && !isValidAssetPath(startPath, fwConstants)) { - throw new Error( - 'Derivation path or flag is not supported. Try updating Lattice firmware.', - ); - } - - // Ensure path depth is valid (2-5 indices) - if (startPath.length < 2 || startPath.length > 5) { - throw new Error('Derivation path must include 2-5 indices.'); - } - - // Validate iterIdx (0-5) - if (iterIdx < 0 || iterIdx > 5) { - throw new Error('Iteration index must be between 0 and 5.'); - } - - // Ensure iterIdx is not greater than path depth - if (iterIdx > startPath.length) { - throw new Error('Iteration index cannot be greater than path depth.'); - } - - const sz = 32 + 1 + 20 + 1; // walletUID + pathDepth_IterIdx + 5 u32 indices + count/flag - const payload = Buffer.alloc(sz); - let off = 0; - - // walletUID - wallet.uid.copy(payload, off); - off += 32; - - // pathDepth_IterIdx - const pathDepth_IterIdx = ((iterIdx & 0x0f) << 4) | (startPath.length & 0x0f); - payload.writeUInt8(pathDepth_IterIdx, off); - off += 1; - - // Build the start path (5x u32 indices) - for (let i = 0; i < 5; i++) { - const val = i < startPath.length ? startPath[i] : 0; - payload.writeUInt32BE(val, off); - off += 4; - } - - // Combine count and flag into a single byte - const countVal = n & 0x0f; - const flagVal = (flag & 0x0f) << 4; - payload.writeUInt8(countVal | flagVal, off); - - return payload; -}; + const flags = fwConstants.getAddressFlags || ([] as any[]) + const isPubkeyOnly = flags.indexOf(flag) > -1 && (flag === LatticeGetAddressesFlag.ed25519Pubkey || flag === LatticeGetAddressesFlag.secp256k1Pubkey || flag === LatticeGetAddressesFlag.bls12_381Pubkey) + const isXpub = flag === LatticeGetAddressesFlag.secp256k1Xpub + if (!isPubkeyOnly && !isXpub && !isValidAssetPath(startPath, fwConstants)) { + throw new Error('Derivation path or flag is not supported. Try updating Lattice firmware.') + } + + // Ensure path depth is valid (2-5 indices) + if (startPath.length < 2 || startPath.length > 5) { + throw new Error('Derivation path must include 2-5 indices.') + } + + // Validate iterIdx (0-5) + if (iterIdx < 0 || iterIdx > 5) { + throw new Error('Iteration index must be between 0 and 5.') + } + + // Ensure iterIdx is not greater than path depth + if (iterIdx > startPath.length) { + throw new Error('Iteration index cannot be greater than path depth.') + } + + const sz = 32 + 1 + 20 + 1 // walletUID + pathDepth_IterIdx + 5 u32 indices + count/flag + const payload = Buffer.alloc(sz) + let off = 0 + + // walletUID + wallet.uid.copy(payload, off) + off += 32 + + // pathDepth_IterIdx + const pathDepth_IterIdx = ((iterIdx & 0x0f) << 4) | (startPath.length & 0x0f) + payload.writeUInt8(pathDepth_IterIdx, off) + off += 1 + + // Build the start path (5x u32 indices) + for (let i = 0; i < 5; i++) { + const val = i < startPath.length ? startPath[i] : 0 + payload.writeUInt32BE(val, off) + off += 4 + } + + // Combine count and flag into a single byte + const countVal = n & 0x0f + const flagVal = (flag & 0x0f) << 4 + payload.writeUInt8(countVal | flagVal, off) + + return payload +} /** * @internal * @return an array of address strings or pubkey buffers */ -export const decodeGetAddressesResponse = ( - data: Buffer, - flag: number, -): Buffer[] => { - let off = 0; - const addressOffset = - flag === LatticeGetAddressesFlag.ed25519Pubkey ? 113 : 65; - // Look for addresses until we reach the end (a 4 byte checksum) - const addrs: any[] = []; - // Pubkeys are formatted differently in the response - const arePubkeys = - flag === LatticeGetAddressesFlag.secp256k1Pubkey || - flag === LatticeGetAddressesFlag.ed25519Pubkey || - flag === LatticeGetAddressesFlag.bls12_381Pubkey; - if (arePubkeys) { - off += 1; // skip uint8 representing pubkey type - } - const respDataLength = - ProtocolConstants.msgSizes.secure.data.response.encrypted[ - LatticeSecureEncryptedRequestType.getAddresses - ]; - while (off < respDataLength) { - if (arePubkeys) { - // Pubkeys are shorter and are returned as buffers - const pubBytes = data.slice(off, off + addressOffset); - const isEmpty = pubBytes.every((byte: number) => byte === 0x00); - if (!isEmpty && flag === LatticeGetAddressesFlag.ed25519Pubkey) { - // ED25519 pubkeys are 32 bytes - addrs.push(pubBytes.slice(0, 32)); - } else if (!isEmpty && flag === LatticeGetAddressesFlag.bls12_381Pubkey) { - // BLS12_381_G1 keys are 48 bytes - addrs.push(pubBytes.slice(0, 48)); - } else if (!isEmpty) { - // Only other returned pubkeys are ECC, or 65 bytes Note that we return full - // (uncompressed) ECC pubkeys - addrs.push(pubBytes); - } - off += addressOffset; - } else { - // Otherwise we are dealing with address strings or XPUB strings - const addrBytes = data.slice(off, off + ProtocolConstants.addrStrLen); - off += ProtocolConstants.addrStrLen; - // Return the UTF-8 representation - const len = addrBytes.indexOf(0); // First 0 is the null terminator - if (len > 0) { - // Clean control characters from the string before adding to array - const cleanStr = addrBytes - .slice(0, len) - .toString() - // eslint-disable-next-line no-control-regex - .replace(/[\x00-\x1F\x7F-\x9F]/g, ''); - addrs.push(cleanStr); - } - } - } - - return addrs; -}; +export const decodeGetAddressesResponse = (data: Buffer, flag: number): Buffer[] => { + let off = 0 + const addressOffset = flag === LatticeGetAddressesFlag.ed25519Pubkey ? 113 : 65 + // Look for addresses until we reach the end (a 4 byte checksum) + const addrs: any[] = [] + // Pubkeys are formatted differently in the response + const arePubkeys = flag === LatticeGetAddressesFlag.secp256k1Pubkey || flag === LatticeGetAddressesFlag.ed25519Pubkey || flag === LatticeGetAddressesFlag.bls12_381Pubkey + if (arePubkeys) { + off += 1 // skip uint8 representing pubkey type + } + const respDataLength = ProtocolConstants.msgSizes.secure.data.response.encrypted[LatticeSecureEncryptedRequestType.getAddresses] + while (off < respDataLength) { + if (arePubkeys) { + // Pubkeys are shorter and are returned as buffers + const pubBytes = data.slice(off, off + addressOffset) + const isEmpty = pubBytes.every((byte: number) => byte === 0x00) + if (!isEmpty && flag === LatticeGetAddressesFlag.ed25519Pubkey) { + // ED25519 pubkeys are 32 bytes + addrs.push(pubBytes.slice(0, 32)) + } else if (!isEmpty && flag === LatticeGetAddressesFlag.bls12_381Pubkey) { + // BLS12_381_G1 keys are 48 bytes + addrs.push(pubBytes.slice(0, 48)) + } else if (!isEmpty) { + // Only other returned pubkeys are ECC, or 65 bytes Note that we return full + // (uncompressed) ECC pubkeys + addrs.push(pubBytes) + } + off += addressOffset + } else { + // Otherwise we are dealing with address strings or XPUB strings + const addrBytes = data.slice(off, off + ProtocolConstants.addrStrLen) + off += ProtocolConstants.addrStrLen + // Return the UTF-8 representation + const len = addrBytes.indexOf(0) // First 0 is the null terminator + if (len > 0) { + const cleanStr = addrBytes + .slice(0, len) + .toString() + // biome-ignore lint/suspicious/noControlCharactersInRegex: Intentional - stripping control characters + .replace(/[\u0000-\u001F\u007F-\u009F]/g, '') + addrs.push(cleanStr) + } + } + } + + return addrs +} diff --git a/packages/sdk/src/functions/getKvRecords.ts b/packages/sdk/src/functions/getKvRecords.ts index 4c788592..818d2846 100644 --- a/packages/sdk/src/functions/getKvRecords.ts +++ b/packages/sdk/src/functions/getKvRecords.ts @@ -1,132 +1,109 @@ -import { - LatticeSecureEncryptedRequestType, - encryptedSecureRequest, -} from '../protocol'; -import { validateConnectedClient } from '../shared/validators'; -import type { - FirmwareConstants, - GetKvRecordsData, - GetKvRecordsRequestFunctionParams, -} from '../types'; +import { LatticeSecureEncryptedRequestType, encryptedSecureRequest } from '../protocol' +import { validateConnectedClient } from '../shared/validators' +import type { FirmwareConstants, GetKvRecordsData, GetKvRecordsRequestFunctionParams } from '../types' -export async function getKvRecords({ - client, - type: _type, - n: _n, - start: _start, -}: GetKvRecordsRequestFunctionParams): Promise { - const { url, sharedSecret, ephemeralPub, fwConstants } = - validateConnectedClient(client); +export async function getKvRecords({ client, type: _type, n: _n, start: _start }: GetKvRecordsRequestFunctionParams): Promise { + const { url, sharedSecret, ephemeralPub, fwConstants } = validateConnectedClient(client) - const { type, n, start } = validateGetKvRequest({ - type: _type, - n: _n, - start: _start, - fwConstants, - }); + const { type, n, start } = validateGetKvRequest({ + type: _type, + n: _n, + start: _start, + fwConstants, + }) - const data = encodeGetKvRecordsRequest({ type, n, start }); + const data = encodeGetKvRecordsRequest({ type, n, start }) - const { decryptedData, newEphemeralPub } = await encryptedSecureRequest({ - data, - requestType: LatticeSecureEncryptedRequestType.getKvRecords, - sharedSecret, - ephemeralPub, - url, - }); + const { decryptedData, newEphemeralPub } = await encryptedSecureRequest({ + data, + requestType: LatticeSecureEncryptedRequestType.getKvRecords, + sharedSecret, + ephemeralPub, + url, + }) - client.mutate({ - ephemeralPub: newEphemeralPub, - }); + client.mutate({ + ephemeralPub: newEphemeralPub, + }) - return decodeGetKvRecordsResponse(decryptedData, fwConstants); + return decodeGetKvRecordsResponse(decryptedData, fwConstants) } export const validateGetKvRequest = ({ - fwConstants, - n, - type, - start, + fwConstants, + n, + type, + start, }: { - fwConstants: FirmwareConstants; - n?: number; - type?: number; - start?: number; + fwConstants: FirmwareConstants + n?: number + type?: number + start?: number }) => { - if (!fwConstants.kvActionsAllowed) { - throw new Error('Unsupported. Please update firmware.'); - } - if (!n || n < 1) { - throw new Error('You must request at least one record.'); - } - if (n > fwConstants.kvActionMaxNum) { - throw new Error( - `You may only request up to ${fwConstants.kvActionMaxNum} records at once.`, - ); - } - if (type !== 0 && !type) { - throw new Error('You must specify a type.'); - } - if (start !== 0 && !start) { - throw new Error('You must specify a type.'); - } + if (!fwConstants.kvActionsAllowed) { + throw new Error('Unsupported. Please update firmware.') + } + if (!n || n < 1) { + throw new Error('You must request at least one record.') + } + if (n > fwConstants.kvActionMaxNum) { + throw new Error(`You may only request up to ${fwConstants.kvActionMaxNum} records at once.`) + } + if (type !== 0 && !type) { + throw new Error('You must specify a type.') + } + if (start !== 0 && !start) { + throw new Error('You must specify a type.') + } - return { fwConstants, n, type, start }; -}; + return { fwConstants, n, type, start } +} export const encodeGetKvRecordsRequest = ({ - type, - n, - start, + type, + n, + start, }: { - type: number; - n: number; - start: number; + type: number + n: number + start: number }) => { - const payload = Buffer.alloc(9); - payload.writeUInt32LE(type, 0); - payload.writeUInt8(n, 4); - payload.writeUInt32LE(start, 5); - return payload; -}; + const payload = Buffer.alloc(9) + payload.writeUInt32LE(type, 0) + payload.writeUInt8(n, 4) + payload.writeUInt32LE(start, 5) + return payload +} -export const decodeGetKvRecordsResponse = ( - data: Buffer, - fwConstants: FirmwareConstants, -) => { - let off = 0; - const nTotal = data.readUInt32BE(off); - off += 4; - const nFetched = Number.parseInt( - data.slice(off, off + 1).toString('hex'), - 16, - ); - off += 1; - if (nFetched > fwConstants.kvActionMaxNum) - throw new Error('Too many records fetched. Firmware error.'); - const records: any = []; - for (let i = 0; i < nFetched; i++) { - const r: any = {}; - r.id = data.readUInt32BE(off); - off += 4; - r.type = data.readUInt32BE(off); - off += 4; - r.caseSensitive = - Number.parseInt(data.slice(off, off + 1).toString('hex'), 16) === 1; - off += 1; - const keySz = Number.parseInt(data.slice(off, off + 1).toString('hex'), 16); - off += 1; - r.key = data.slice(off, off + keySz - 1).toString(); - off += fwConstants.kvKeyMaxStrSz + 1; - const valSz = Number.parseInt(data.slice(off, off + 1).toString('hex'), 16); - off += 1; - r.val = data.slice(off, off + valSz - 1).toString(); - off += fwConstants.kvValMaxStrSz + 1; - records.push(r); - } - return { - records, - total: nTotal, - fetched: nFetched, - }; -}; +export const decodeGetKvRecordsResponse = (data: Buffer, fwConstants: FirmwareConstants) => { + let off = 0 + const nTotal = data.readUInt32BE(off) + off += 4 + const nFetched = Number.parseInt(data.slice(off, off + 1).toString('hex'), 16) + off += 1 + if (nFetched > fwConstants.kvActionMaxNum) throw new Error('Too many records fetched. Firmware error.') + const records: any = [] + for (let i = 0; i < nFetched; i++) { + const r: any = {} + r.id = data.readUInt32BE(off) + off += 4 + r.type = data.readUInt32BE(off) + off += 4 + r.caseSensitive = Number.parseInt(data.slice(off, off + 1).toString('hex'), 16) === 1 + off += 1 + const keySz = Number.parseInt(data.slice(off, off + 1).toString('hex'), 16) + off += 1 + r.key = data.slice(off, off + keySz - 1).toString() + off += fwConstants.kvKeyMaxStrSz + 1 + const valSz = Number.parseInt(data.slice(off, off + 1).toString('hex'), 16) + off += 1 + r.val = data.slice(off, off + valSz - 1).toString() + off += fwConstants.kvValMaxStrSz + 1 + records.push(r) + } + return { + records, + total: nTotal, + fetched: nFetched, + } +} diff --git a/packages/sdk/src/functions/index.ts b/packages/sdk/src/functions/index.ts index dea08a11..9e4bc417 100644 --- a/packages/sdk/src/functions/index.ts +++ b/packages/sdk/src/functions/index.ts @@ -1,9 +1,9 @@ -export * from './addKvRecords'; -export * from './connect'; -export * from './fetchEncData'; -export * from './fetchActiveWallet'; -export * from './getAddresses'; -export * from './getKvRecords'; -export * from './pair'; -export * from './removeKvRecords'; -export * from './sign'; +export * from './addKvRecords' +export * from './connect' +export * from './fetchEncData' +export * from './fetchActiveWallet' +export * from './getAddresses' +export * from './getKvRecords' +export * from './pair' +export * from './removeKvRecords' +export * from './sign' diff --git a/packages/sdk/src/functions/pair.ts b/packages/sdk/src/functions/pair.ts index aa119d0a..307ad5b7 100644 --- a/packages/sdk/src/functions/pair.ts +++ b/packages/sdk/src/functions/pair.ts @@ -1,11 +1,8 @@ -import { - LatticeSecureEncryptedRequestType, - encryptedSecureRequest, -} from '../protocol'; -import { getPubKeyBytes } from '../shared/utilities'; -import { validateConnectedClient } from '../shared/validators'; -import type { KeyPair, PairRequestParams } from '../types'; -import { generateAppSecret, toPaddedDER } from '../util'; +import { LatticeSecureEncryptedRequestType, encryptedSecureRequest } from '../protocol' +import { getPubKeyBytes } from '../shared/utilities' +import { validateConnectedClient } from '../shared/validators' +import type { KeyPair, PairRequestParams } from '../types' +import { generateAppSecret, toPaddedDER } from '../util' /** * If a pairing secret is provided, `pair` uses it to sign a hash of the public key, name, and @@ -14,57 +11,49 @@ import { generateAppSecret, toPaddedDER } from '../util'; * @category Lattice * @returns The active wallet object. */ -export async function pair({ - client, - pairingSecret, -}: PairRequestParams): Promise { - const { url, sharedSecret, ephemeralPub, appName, key } = - validateConnectedClient(client); - const data = encodePairRequest({ pairingSecret, key, appName }); +export async function pair({ client, pairingSecret }: PairRequestParams): Promise { + const { url, sharedSecret, ephemeralPub, appName, key } = validateConnectedClient(client) + const data = encodePairRequest({ pairingSecret, key, appName }) - const { newEphemeralPub } = await encryptedSecureRequest({ - data, - requestType: LatticeSecureEncryptedRequestType.finalizePairing, - sharedSecret, - ephemeralPub, - url, - }); + const { newEphemeralPub } = await encryptedSecureRequest({ + data, + requestType: LatticeSecureEncryptedRequestType.finalizePairing, + sharedSecret, + ephemeralPub, + url, + }) - client.mutate({ - ephemeralPub: newEphemeralPub, - isPaired: true, - }); + client.mutate({ + ephemeralPub: newEphemeralPub, + isPaired: true, + }) - await client.fetchActiveWallet(); - return client.hasActiveWallet(); + await client.fetchActiveWallet() + return client.hasActiveWallet() } export const encodePairRequest = ({ - key, - pairingSecret, - appName, + key, + pairingSecret, + appName, }: { - key: KeyPair; - pairingSecret: string; - appName: string; + key: KeyPair + pairingSecret: string + appName: string }) => { - // Build the payload data - const pubKeyBytes = getPubKeyBytes(key); - const nameBuf = Buffer.alloc(25); - if (pairingSecret.length > 0) { - // If a pairing secret of zero length is passed in, it usually indicates we want to cancel - // the pairing attempt. In this case we pass a zero-length name buffer so the firmware can - // know not to draw the error screen. Note that we still expect an error to come back - // (RESP_ERR_PAIR_FAIL) - nameBuf.write(appName); - } - const hash = generateAppSecret( - pubKeyBytes, - nameBuf, - Buffer.from(pairingSecret), - ); - const sig = key.sign(hash); - const derSig = toPaddedDER(sig); - const payload = Buffer.concat([nameBuf, derSig]); - return payload; -}; + // Build the payload data + const pubKeyBytes = getPubKeyBytes(key) + const nameBuf = Buffer.alloc(25) + if (pairingSecret.length > 0) { + // If a pairing secret of zero length is passed in, it usually indicates we want to cancel + // the pairing attempt. In this case we pass a zero-length name buffer so the firmware can + // know not to draw the error screen. Note that we still expect an error to come back + // (RESP_ERR_PAIR_FAIL) + nameBuf.write(appName) + } + const hash = generateAppSecret(pubKeyBytes, nameBuf, Buffer.from(pairingSecret)) + const sig = key.sign(hash) + const derSig = toPaddedDER(sig) + const payload = Buffer.concat([nameBuf, derSig]) + return payload +} diff --git a/packages/sdk/src/functions/removeKvRecords.ts b/packages/sdk/src/functions/removeKvRecords.ts index b78637eb..b426ea74 100644 --- a/packages/sdk/src/functions/removeKvRecords.ts +++ b/packages/sdk/src/functions/removeKvRecords.ts @@ -1,94 +1,81 @@ -import { - LatticeSecureEncryptedRequestType, - encryptedSecureRequest, -} from '../protocol'; -import { validateConnectedClient } from '../shared/validators'; -import type { - FirmwareConstants, - RemoveKvRecordsRequestFunctionParams, -} from '../types'; +import { LatticeSecureEncryptedRequestType, encryptedSecureRequest } from '../protocol' +import { validateConnectedClient } from '../shared/validators' +import type { FirmwareConstants, RemoveKvRecordsRequestFunctionParams } from '../types' /** * `removeKvRecords` takes in an array of ids and sends a request to remove them from the Lattice. * @category Lattice * @returns A callback with an error or null. */ -export async function removeKvRecords({ - client, - type: _type, - ids: _ids, -}: RemoveKvRecordsRequestFunctionParams): Promise { - const { url, sharedSecret, ephemeralPub, fwConstants } = - validateConnectedClient(client); +export async function removeKvRecords({ client, type: _type, ids: _ids }: RemoveKvRecordsRequestFunctionParams): Promise { + const { url, sharedSecret, ephemeralPub, fwConstants } = validateConnectedClient(client) - const { type, ids } = validateRemoveKvRequest({ - fwConstants, - type: _type, - ids: _ids, - }); + const { type, ids } = validateRemoveKvRequest({ + fwConstants, + type: _type, + ids: _ids, + }) - const data = encodeRemoveKvRecordsRequest({ - type, - ids, - fwConstants, - }); + const data = encodeRemoveKvRecordsRequest({ + type, + ids, + fwConstants, + }) - const { decryptedData, newEphemeralPub } = await encryptedSecureRequest({ - data, - requestType: LatticeSecureEncryptedRequestType.removeKvRecords, - sharedSecret, - ephemeralPub, - url, - }); + const { decryptedData, newEphemeralPub } = await encryptedSecureRequest({ + data, + requestType: LatticeSecureEncryptedRequestType.removeKvRecords, + sharedSecret, + ephemeralPub, + url, + }) - client.mutate({ - ephemeralPub: newEphemeralPub, - }); + client.mutate({ + ephemeralPub: newEphemeralPub, + }) - return decryptedData; + return decryptedData } export const validateRemoveKvRequest = ({ - fwConstants, - type, - ids, + fwConstants, + type, + ids, }: { - fwConstants: FirmwareConstants; - type?: number; - ids?: string[]; + fwConstants: FirmwareConstants + type?: number + ids?: string[] }) => { - if (!fwConstants.kvActionsAllowed) { - throw new Error('Unsupported. Please update firmware.'); - } - if (!Array.isArray(ids) || ids.length < 1) { - throw new Error('You must include one or more `ids` to removed.'); - } - if (ids.length > fwConstants.kvRemoveMaxNum) { - throw new Error( - `Only up to ${fwConstants.kvRemoveMaxNum} records may be removed at once.`, - ); - } - if (type !== 0 && !type) { - throw new Error('You must specify a type.'); - } - return { type, ids }; -}; + if (!fwConstants.kvActionsAllowed) { + throw new Error('Unsupported. Please update firmware.') + } + if (!Array.isArray(ids) || ids.length < 1) { + throw new Error('You must include one or more `ids` to removed.') + } + if (ids.length > fwConstants.kvRemoveMaxNum) { + throw new Error(`Only up to ${fwConstants.kvRemoveMaxNum} records may be removed at once.`) + } + if (type !== 0 && !type) { + throw new Error('You must specify a type.') + } + return { type, ids } +} export const encodeRemoveKvRecordsRequest = ({ - fwConstants, - type, - ids, + fwConstants, + type, + ids, }: { - fwConstants: FirmwareConstants; - type: number; - ids: string[]; + fwConstants: FirmwareConstants + type: number + ids: string[] }) => { - const payload = Buffer.alloc(5 + 4 * fwConstants.kvRemoveMaxNum); - payload.writeUInt32LE(type, 0); - payload.writeUInt8(ids.length, 4); - for (let i = 0; i < ids.length; i++) { - const id = Number.parseInt(ids[i] as string); - payload.writeUInt32LE(id, 5 + 4 * i); - } - return payload; -}; + const payload = Buffer.alloc(5 + 4 * fwConstants.kvRemoveMaxNum) + payload.writeUInt32LE(type, 0) + payload.writeUInt8(ids.length, 4) + for (let i = 0; i < ids.length; i++) { + const id = Number.parseInt(ids[i] as string) + payload.writeUInt32LE(id, 5 + 4 * i) + } + return payload +} diff --git a/packages/sdk/src/functions/sign.ts b/packages/sdk/src/functions/sign.ts index fafbbb3f..1a58ae6d 100644 --- a/packages/sdk/src/functions/sign.ts +++ b/packages/sdk/src/functions/sign.ts @@ -1,310 +1,264 @@ -import { Hash } from 'ox'; -import type { Address, Hex } from 'viem'; -import bitcoin from '../bitcoin'; -import { CURRENCIES } from '../constants'; -import ethereum from '../ethereum'; -import { parseGenericSigningResponse } from '../genericSigning'; -import { - LatticeSecureEncryptedRequestType, - LatticeSignSchema, - encryptedSecureRequest, -} from '../protocol'; -import { buildTransaction } from '../shared/functions'; -import { validateConnectedClient, validateWallet } from '../shared/validators'; -import type { - BitcoinSignRequest, - DecodeSignResponseParams, - EncodeSignRequestParams, - SignData, - SignRequest, - SignRequestFunctionParams, - SigningRequestResponse, -} from '../types'; -import { parseDER } from '../util'; +import { Hash } from 'ox' +import type { Address, Hex } from 'viem' +import bitcoin from '../bitcoin' +import { CURRENCIES } from '../constants' +import ethereum from '../ethereum' +import { parseGenericSigningResponse } from '../genericSigning' +import { LatticeSecureEncryptedRequestType, LatticeSignSchema, encryptedSecureRequest } from '../protocol' +import { buildTransaction } from '../shared/functions' +import { validateConnectedClient, validateWallet } from '../shared/validators' +import type { BitcoinSignRequest, DecodeSignResponseParams, EncodeSignRequestParams, SignData, SignRequest, SignRequestFunctionParams, SigningRequestResponse } from '../types' +import { parseDER } from '../util' /** * `sign` builds and sends a request for signing to the device. * @category Lattice * @returns The response from the device. */ -export async function sign({ - client, - data, - currency, - cachedData, - nextCode, -}: SignRequestFunctionParams): Promise { - try { - const { url, sharedSecret, ephemeralPub, fwConstants } = - validateConnectedClient(client); - const wallet = validateWallet(client.getActiveWallet()); +export async function sign({ client, data, currency, cachedData, nextCode }: SignRequestFunctionParams): Promise { + try { + const { url, sharedSecret, ephemeralPub, fwConstants } = validateConnectedClient(client) + const wallet = validateWallet(client.getActiveWallet()) - const { requestData, isGeneric } = buildTransaction({ - data, - currency, - fwConstants, - }); + const { requestData, isGeneric } = buildTransaction({ + data, + currency, + fwConstants, + }) - const { payload, hasExtraPayloads } = encodeSignRequest({ - fwConstants, - wallet, - requestData, - cachedData, - nextCode, - }); + const { payload, hasExtraPayloads } = encodeSignRequest({ + fwConstants, + wallet, + requestData, + cachedData, + nextCode, + }) - const { decryptedData, newEphemeralPub } = await encryptedSecureRequest({ - data: payload, - requestType: LatticeSecureEncryptedRequestType.sign, - sharedSecret, - ephemeralPub, - url, - }); + const { decryptedData, newEphemeralPub } = await encryptedSecureRequest({ + data: payload, + requestType: LatticeSecureEncryptedRequestType.sign, + sharedSecret, + ephemeralPub, + url, + }) - client.mutate({ - ephemeralPub: newEphemeralPub, - }); + client.mutate({ + ephemeralPub: newEphemeralPub, + }) - // If this request has multiple payloads, we need to recurse - // so that we can make the next request. - // It is chained to the first request using `nextCode` - if (hasExtraPayloads) { - return client.sign({ - data, - currency, - cachedData: requestData, - nextCode: decryptedData.slice(0, 8), - }); - } - // If this is the only (or final) request, - // decode response data and return - const decodedResponse = decodeSignResponse({ - data: decryptedData, - request: requestData as SignRequest, - isGeneric, - currency, - }); + // If this request has multiple payloads, we need to recurse + // so that we can make the next request. + // It is chained to the first request using `nextCode` + if (hasExtraPayloads) { + return client.sign({ + data, + currency, + cachedData: requestData, + nextCode: decryptedData.slice(0, 8), + }) + } + // If this is the only (or final) request, + // decode response data and return + const decodedResponse = decodeSignResponse({ + data: decryptedData, + request: requestData as SignRequest, + isGeneric, + currency, + }) - return decodedResponse; - } catch (err) { - console.error('Error signing transaction:', { - message: err.message, - payload: JSON.stringify( - { - data, - currency, - }, - (_key, value) => (typeof value === 'bigint' ? value.toString() : value), - ), - }); - throw err; - } + return decodedResponse + } catch (err) { + console.error('Error signing transaction:', { + message: err.message, + payload: JSON.stringify( + { + data, + currency, + }, + (_key, value) => (typeof value === 'bigint' ? value.toString() : value), + ), + }) + throw err + } } -export const encodeSignRequest = ({ - fwConstants, - wallet, - requestData, - cachedData, - nextCode, -}: EncodeSignRequestParams) => { - let reqPayload: Buffer; - let schema: number; - let hasExtraPayloads = 0; - const typedRequestData = requestData as SignRequest & { - extraDataPayloads?: Buffer[]; - }; +export const encodeSignRequest = ({ fwConstants, wallet, requestData, cachedData, nextCode }: EncodeSignRequestParams) => { + let reqPayload: Buffer + let schema: number + let hasExtraPayloads = 0 + const typedRequestData = requestData as SignRequest & { + extraDataPayloads?: Buffer[] + } - if (cachedData && nextCode) { - const typedCachedData = cachedData as SignRequest & { - extraDataPayloads: Buffer[]; - }; - const nextExtraPayload = typedCachedData.extraDataPayloads.shift(); - if (!nextExtraPayload) { - throw new Error( - 'No cached extra payload available for multipart sign request.', - ); - } - if (typedRequestData.extraDataPayloads) { - typedRequestData.extraDataPayloads = typedCachedData.extraDataPayloads; - } - reqPayload = Buffer.concat([nextCode, nextExtraPayload]); - schema = LatticeSignSchema.extraData; - hasExtraPayloads = Number( - (typedCachedData.extraDataPayloads?.length ?? 0) > 0, - ); - } else { - reqPayload = typedRequestData.payload; - schema = typedRequestData.schema; - hasExtraPayloads = Number( - (typedRequestData.extraDataPayloads?.length ?? 0) > 0, - ); - } + if (cachedData && nextCode) { + const typedCachedData = cachedData as SignRequest & { + extraDataPayloads: Buffer[] + } + const nextExtraPayload = typedCachedData.extraDataPayloads.shift() + if (!nextExtraPayload) { + throw new Error('No cached extra payload available for multipart sign request.') + } + if (typedRequestData.extraDataPayloads) { + typedRequestData.extraDataPayloads = typedCachedData.extraDataPayloads + } + reqPayload = Buffer.concat([nextCode, nextExtraPayload]) + schema = LatticeSignSchema.extraData + hasExtraPayloads = Number((typedCachedData.extraDataPayloads?.length ?? 0) > 0) + } else { + reqPayload = typedRequestData.payload + schema = typedRequestData.schema + hasExtraPayloads = Number((typedRequestData.extraDataPayloads?.length ?? 0) > 0) + } - const payload = Buffer.alloc(2 + fwConstants.reqMaxDataSz); - let off = 0; + const payload = Buffer.alloc(2 + fwConstants.reqMaxDataSz) + let off = 0 - payload.writeUInt8(hasExtraPayloads, off); - off += 1; - // Copy request schema (e.g. ETH or BTC transfer) - payload.writeUInt8(schema, off); - off += 1; - // Copy the wallet UID - wallet.uid?.copy(payload, off); - off += wallet.uid?.length ?? 0; - // Build data based on the type of request - reqPayload.copy(payload, off); - return { payload, hasExtraPayloads }; -}; + payload.writeUInt8(hasExtraPayloads, off) + off += 1 + // Copy request schema (e.g. ETH or BTC transfer) + payload.writeUInt8(schema, off) + off += 1 + // Copy the wallet UID + wallet.uid?.copy(payload, off) + off += wallet.uid?.length ?? 0 + // Build data based on the type of request + reqPayload.copy(payload, off) + return { payload, hasExtraPayloads } +} -export const decodeSignResponse = ({ - data, - request, - isGeneric, - currency, -}: DecodeSignResponseParams): SignData => { - let off = 0; - const derSigLen = 74; // DER signatures are 74 bytes - if (currency === CURRENCIES.BTC) { - const btcRequest = request as BitcoinSignRequest; - const pkhLen = 20; // Pubkeyhashes are 20 bytes - const sigsLen = 760; // Up to 10x DER signatures - const changeVersion = bitcoin.getAddressFormat( - btcRequest.origData.changePath, - ); - const changePubKeyHash = data.slice(off, off + pkhLen); - off += pkhLen; - const changeRecipient = bitcoin.getBitcoinAddress( - changePubKeyHash, - changeVersion, - ); - const compressedPubLength = 33; // Size of compressed public key - const pubkeys = [] as any[]; - const sigs = [] as any[]; - let n = 0; - // Parse the signature for each output -- they are returned in the serialized payload in form - // [pubkey, sig] There is one signature per output - while (off < data.length) { - // Exit out if we have seen all the returned sigs and pubkeys - if (data[off] !== 0x30) break; - // Otherwise grab another set Note that all DER sigs returned fill the maximum 74 byte - // buffer, but also contain a length at off+1, which we use to parse the non-zero data. - // First get the signature from its slot - const sigStart = off; - const sigEnd = off + 2 + data[off + 1]; - sigs.push(data.slice(sigStart, sigEnd)); - off += derSigLen; - // Next, shift by the full set of signatures to hit the respective pubkey NOTE: The data - // returned is: [, , ... ][, , ... ] - const pubStart = n * compressedPubLength + sigsLen; - const pubEnd = (n + 1) * compressedPubLength + sigsLen; - pubkeys.push(data.slice(pubStart, pubEnd)); - // Update offset to hit the next signature slot - n += 1; - } - // Build the transaction data to be serialized - const preSerializedData: any = { - inputs: [], - outputs: [], - }; +export const decodeSignResponse = ({ data, request, isGeneric, currency }: DecodeSignResponseParams): SignData => { + let off = 0 + const derSigLen = 74 // DER signatures are 74 bytes + if (currency === CURRENCIES.BTC) { + const btcRequest = request as BitcoinSignRequest + const pkhLen = 20 // Pubkeyhashes are 20 bytes + const sigsLen = 760 // Up to 10x DER signatures + const changeVersion = bitcoin.getAddressFormat(btcRequest.origData.changePath) + const changePubKeyHash = data.slice(off, off + pkhLen) + off += pkhLen + const changeRecipient = bitcoin.getBitcoinAddress(changePubKeyHash, changeVersion) + const compressedPubLength = 33 // Size of compressed public key + const pubkeys = [] as any[] + const sigs = [] as any[] + let n = 0 + // Parse the signature for each output -- they are returned in the serialized payload in form + // [pubkey, sig] There is one signature per output + while (off < data.length) { + // Exit out if we have seen all the returned sigs and pubkeys + if (data[off] !== 0x30) break + // Otherwise grab another set Note that all DER sigs returned fill the maximum 74 byte + // buffer, but also contain a length at off+1, which we use to parse the non-zero data. + // First get the signature from its slot + const sigStart = off + const sigEnd = off + 2 + data[off + 1] + sigs.push(data.slice(sigStart, sigEnd)) + off += derSigLen + // Next, shift by the full set of signatures to hit the respective pubkey NOTE: The data + // returned is: [, , ... ][, , ... ] + const pubStart = n * compressedPubLength + sigsLen + const pubEnd = (n + 1) * compressedPubLength + sigsLen + pubkeys.push(data.slice(pubStart, pubEnd)) + // Update offset to hit the next signature slot + n += 1 + } + // Build the transaction data to be serialized + const preSerializedData: any = { + inputs: [], + outputs: [], + } - // First output comes from request dta - preSerializedData.outputs.push({ - value: btcRequest.origData.value, - recipient: btcRequest.origData.recipient, - }); - if (btcRequest.changeData?.value && btcRequest.changeData.value > 0) { - // Second output comes from change data - preSerializedData.outputs.push({ - value: btcRequest.changeData.value, - recipient: changeRecipient, - }); - } + // First output comes from request dta + preSerializedData.outputs.push({ + value: btcRequest.origData.value, + recipient: btcRequest.origData.recipient, + }) + if (btcRequest.changeData?.value && btcRequest.changeData.value > 0) { + // Second output comes from change data + preSerializedData.outputs.push({ + value: btcRequest.changeData.value, + recipient: changeRecipient, + }) + } - // Add the inputs - for (let i = 0; i < sigs.length; i++) { - preSerializedData.inputs.push({ - hash: btcRequest.origData.prevOuts[i].txHash, - index: btcRequest.origData.prevOuts[i].index, - sig: sigs[i], - pubkey: pubkeys[i], - signerPath: btcRequest.origData.prevOuts[i].signerPath, - }); - } + // Add the inputs + for (let i = 0; i < sigs.length; i++) { + preSerializedData.inputs.push({ + hash: btcRequest.origData.prevOuts[i].txHash, + index: btcRequest.origData.prevOuts[i].index, + sig: sigs[i], + pubkey: pubkeys[i], + signerPath: btcRequest.origData.prevOuts[i].signerPath, + }) + } - // Finally, serialize the transaction - const serializedTx = bitcoin.serializeTx(preSerializedData); - // Generate the transaction hash so the user can look this transaction up later - const preImageTxHash = serializedTx; - const txHashPre: Buffer = Buffer.from( - Hash.sha256(Buffer.from(preImageTxHash, 'hex')), - ); - // Add extra data for debugging/lookup purposes - return { - tx: serializedTx, - txHash: `0x${Buffer.from(Hash.sha256(txHashPre)).toString('hex')}` as Hex, - changeRecipient, - sigs, - }; - } else if (currency === CURRENCIES.ETH && !isGeneric) { - const sig = parseDER(data.slice(off, off + 2 + data[off + 1])); - off += derSigLen; - const ethAddr = data.slice(off, off + 20); - // Determine the `v` param and add it to the sig before returning - const result = ethereum.buildEthRawTx(request, sig, ethAddr); + // Finally, serialize the transaction + const serializedTx = bitcoin.serializeTx(preSerializedData) + // Generate the transaction hash so the user can look this transaction up later + const preImageTxHash = serializedTx + const txHashPre: Buffer = Buffer.from(Hash.sha256(Buffer.from(preImageTxHash, 'hex'))) + // Add extra data for debugging/lookup purposes + return { + tx: serializedTx, + txHash: `0x${Buffer.from(Hash.sha256(txHashPre)).toString('hex')}` as Hex, + changeRecipient, + sigs, + } + } else if (currency === CURRENCIES.ETH && !isGeneric) { + const sig = parseDER(data.slice(off, off + 2 + data[off + 1])) + off += derSigLen + const ethAddr = data.slice(off, off + 20) + // Determine the `v` param and add it to the sig before returning + const result = ethereum.buildEthRawTx(request, sig, ethAddr) - // Handle both object and string returns from buildEthRawTx - if (typeof result === 'string') { - // EIP-7702 transactions return only the hex string - // Per EIP-7702: "The [EIP-2718] `ReceiptPayload` for this transaction is - // `rlp([status, cumulative_transaction_gas_used, logs_bloom, logs])`." - return { - tx: `0x${result}`, - txHash: `0x${ethereum.hashTransaction(result)}` as Hex, - sig: { - v: 0n, - r: `0x${''}` as Hex, - s: `0x${''}` as Hex, - }, - signer: `0x${ethAddr.toString('hex')}` as Address, - }; - } else { - // Normal transactions return object with rawTx and sigWithV - const response: SignData = { - tx: `0x${result.rawTx}`, - txHash: `0x${ethereum.hashTransaction(result.rawTx)}` as Hex, - sig: { - v: BigInt(`0x${result.sigWithV.v.toString('hex')}`), - r: `0x${result.sigWithV.r.toString('hex')}` as Hex, - s: `0x${result.sigWithV.s.toString('hex')}` as Hex, - }, - signer: `0x${ethAddr.toString('hex')}` as Address, - }; + // Handle both object and string returns from buildEthRawTx + if (typeof result === 'string') { + // EIP-7702 transactions return only the hex string + // Per EIP-7702: "The [EIP-2718] `ReceiptPayload` for this transaction is + // `rlp([status, cumulative_transaction_gas_used, logs_bloom, logs])`." + return { + tx: `0x${result}`, + txHash: `0x${ethereum.hashTransaction(result)}` as Hex, + sig: { + v: 0n, + r: `0x${''}` as Hex, + s: `0x${''}` as Hex, + }, + signer: `0x${ethAddr.toString('hex')}` as Address, + } + } else { + // Normal transactions return object with rawTx and sigWithV + const response: SignData = { + tx: `0x${result.rawTx}`, + txHash: `0x${ethereum.hashTransaction(result.rawTx)}` as Hex, + sig: { + v: BigInt(`0x${result.sigWithV.v.toString('hex')}`), + r: `0x${result.sigWithV.r.toString('hex')}` as Hex, + s: `0x${result.sigWithV.s.toString('hex')}` as Hex, + }, + signer: `0x${ethAddr.toString('hex')}` as Address, + } - // Add normalized viem-compatible signed transaction if original transaction data is available - // Note: For now, skip viem transaction normalization due to interface compatibility - // This can be added back when proper transaction data is available + // Add normalized viem-compatible signed transaction if original transaction data is available + // Note: For now, skip viem transaction normalization due to interface compatibility + // This can be added back when proper transaction data is available - return response; - } - } else if (currency === CURRENCIES.ETH_MSG) { - const sig = parseDER(data.slice(off, off + 2 + data[off + 1])); - off += derSigLen; - const signer = data.slice(off, off + 20); - const validatedSig = ethereum.validateEthereumMsgResponse( - { signer, sig }, - request, - ); - return { - sig: { - v: BigInt(`0x${validatedSig.v.toString('hex')}`), - r: `0x${validatedSig.r.toString('hex')}` as Hex, - s: `0x${validatedSig.s.toString('hex')}` as Hex, - }, - signer: `0x${signer.toString('hex')}` as Address, - }; - } else { - // Generic signing request - return parseGenericSigningResponse(data, off, request); - } -}; + return response + } + } else if (currency === CURRENCIES.ETH_MSG) { + const sig = parseDER(data.slice(off, off + 2 + data[off + 1])) + off += derSigLen + const signer = data.slice(off, off + 20) + const validatedSig = ethereum.validateEthereumMsgResponse({ signer, sig }, request) + return { + sig: { + v: BigInt(`0x${validatedSig.v.toString('hex')}`), + r: `0x${validatedSig.r.toString('hex')}` as Hex, + s: `0x${validatedSig.s.toString('hex')}` as Hex, + }, + signer: `0x${signer.toString('hex')}` as Address, + } + } else { + // Generic signing request + return parseGenericSigningResponse(data, off, request) + } +} diff --git a/packages/sdk/src/genericSigning.ts b/packages/sdk/src/genericSigning.ts index 7c66c522..1491cf4d 100644 --- a/packages/sdk/src/genericSigning.ts +++ b/packages/sdk/src/genericSigning.ts @@ -1,4 +1,4 @@ -import { RLP } from '@ethereumjs/rlp'; +import { RLP } from '@ethereumjs/rlp' /** Generic signing module. Any payload can be sent to the Lattice and will be displayed in full (note that \n and \t characters will be @@ -9,455 +9,383 @@ This payload should be coupled with: * Curve on which to derive the signing key * Hash function to use on the message */ -import { Hash } from 'ox'; -import { - type Hex, - type TransactionSerializable, - parseTransaction, - serializeTransaction, -} from 'viem'; +import { Hash } from 'ox' +import { type Hex, type TransactionSerializable, parseTransaction, serializeTransaction } from 'viem' // keccak256 now imported from ox via Hash module -import { HARDENED_OFFSET } from './constants'; -import { Constants } from './index'; -import { LatticeSignSchema } from './protocol'; -import { - buildSignerPathBuf, - existsIn, - fixLen, - getV, - getYParity, - parseDER, - splitFrames, -} from './util'; +import { HARDENED_OFFSET } from './constants' +import { Constants } from './index' +import { LatticeSignSchema } from './protocol' +import { buildSignerPathBuf, existsIn, fixLen, getV, getYParity, parseDER, splitFrames } from './util' export const buildGenericSigningMsgRequest = (req) => { - const { - signerPath, - curveType, - hashType, - encodingType = null, - decoder = null, - omitPubkey = false, - fwConstants, - blsDst = Constants.SIGNING.BLS_DST.BLS_DST_NUL, - } = req; - const { - extraDataFrameSz, - extraDataMaxFrames, - prehashAllowed, - genericSigning, - varAddrPathSzAllowed, - } = fwConstants; - const { - curveTypes, - encodingTypes, - hashTypes, - baseDataSz, - baseReqSz, - calldataDecoding, - } = genericSigning; - const encodedPayload = getEncodedPayload( - req.payload, - encodingType, - encodingTypes, - ); - const { encoding } = encodedPayload; - let { payloadBuf } = encodedPayload; - const origPayloadBuf = payloadBuf; - let payloadDataSz = payloadBuf.length; - // Size of data payload that can be included in the first/base request - const maxExpandedSz = baseDataSz + extraDataMaxFrames * extraDataFrameSz; - // Sanity checks - if (!payloadDataSz) { - throw new Error('Payload could not be handled.'); - } else if ( - !genericSigning || - !extraDataFrameSz || - !extraDataMaxFrames || - !prehashAllowed - ) { - throw new Error('Unsupported. Please update your Lattice firmware.'); - } else if (!existsIn(curveType, curveTypes)) { - throw new Error('Unsupported curve type.'); - } else if (!existsIn(hashType, hashTypes)) { - throw new Error('Unsupported hash type.'); - } + const { signerPath, curveType, hashType, encodingType = null, decoder = null, omitPubkey = false, fwConstants, blsDst = Constants.SIGNING.BLS_DST.BLS_DST_NUL } = req + const { extraDataFrameSz, extraDataMaxFrames, prehashAllowed, genericSigning, varAddrPathSzAllowed } = fwConstants + const { curveTypes, encodingTypes, hashTypes, baseDataSz, baseReqSz, calldataDecoding } = genericSigning + const encodedPayload = getEncodedPayload(req.payload, encodingType, encodingTypes) + const { encoding } = encodedPayload + let { payloadBuf } = encodedPayload + const origPayloadBuf = payloadBuf + let payloadDataSz = payloadBuf.length + // Size of data payload that can be included in the first/base request + const maxExpandedSz = baseDataSz + extraDataMaxFrames * extraDataFrameSz + // Sanity checks + if (!payloadDataSz) { + throw new Error('Payload could not be handled.') + } else if (!genericSigning || !extraDataFrameSz || !extraDataMaxFrames || !prehashAllowed) { + throw new Error('Unsupported. Please update your Lattice firmware.') + } else if (!existsIn(curveType, curveTypes)) { + throw new Error('Unsupported curve type.') + } else if (!existsIn(hashType, hashTypes)) { + throw new Error('Unsupported hash type.') + } - // If there is a decoder attached to our payload, add it to - // the data field of the request. - const hasDecoder = - decoder && calldataDecoding && decoder.length <= calldataDecoding.maxSz; - // Make sure the payload AND decoder data fits in the firmware buffer. - // If it doesn't, we can't include the decoder because the payload will likely - // be pre-hashed and the decoder data isn't part of the message to sign. - const decoderFits = - hasDecoder && payloadBuf.length + decoder.length <= maxExpandedSz; - if (hasDecoder && decoderFits) { - const decoderBuf = Buffer.alloc(8 + decoder.length); - // First write th reserved word - decoderBuf.writeUInt32LE(calldataDecoding.reserved, 0); - // Then write size, then the data - decoderBuf.writeUInt32LE(decoder.length, 4); - Buffer.from(decoder).copy(decoderBuf, 8); - payloadBuf = Buffer.concat([payloadBuf, decoderBuf]); - } + // If there is a decoder attached to our payload, add it to + // the data field of the request. + const hasDecoder = decoder && calldataDecoding && decoder.length <= calldataDecoding.maxSz + // Make sure the payload AND decoder data fits in the firmware buffer. + // If it doesn't, we can't include the decoder because the payload will likely + // be pre-hashed and the decoder data isn't part of the message to sign. + const decoderFits = hasDecoder && payloadBuf.length + decoder.length <= maxExpandedSz + if (hasDecoder && decoderFits) { + const decoderBuf = Buffer.alloc(8 + decoder.length) + // First write th reserved word + decoderBuf.writeUInt32LE(calldataDecoding.reserved, 0) + // Then write size, then the data + decoderBuf.writeUInt32LE(decoder.length, 4) + Buffer.from(decoder).copy(decoderBuf, 8) + payloadBuf = Buffer.concat([payloadBuf, decoderBuf]) + } - // Ed25519 specific sanity checks - if (curveType === curveTypes.ED25519) { - if (hashType !== hashTypes.NONE) { - throw new Error('Signing on ed25519 requires unhashed message'); - } - signerPath.forEach((idx) => { - if (idx < HARDENED_OFFSET) { - throw new Error( - 'Signing on ed25519 requires all signer path indices be hardened.', - ); - } - }); - } - // BLS12_381 specific processing - else if (curveType === curveTypes.BLS12_381_G2) { - // For BLS signing we need to prefix 4 bytes to represent the - // domain separator (DST). If none is provided, we use the default - // value of DST_NUL. - const blsDstBuf = Buffer.alloc(4); - blsDstBuf.writeUInt32LE(blsDst); - payloadBuf = Buffer.concat([blsDstBuf, payloadBuf]); - payloadDataSz += blsDstBuf.length; - } + // Ed25519 specific sanity checks + if (curveType === curveTypes.ED25519) { + if (hashType !== hashTypes.NONE) { + throw new Error('Signing on ed25519 requires unhashed message') + } + signerPath.forEach((idx) => { + if (idx < HARDENED_OFFSET) { + throw new Error('Signing on ed25519 requires all signer path indices be hardened.') + } + }) + } + // BLS12_381 specific processing + else if (curveType === curveTypes.BLS12_381_G2) { + // For BLS signing we need to prefix 4 bytes to represent the + // domain separator (DST). If none is provided, we use the default + // value of DST_NUL. + const blsDstBuf = Buffer.alloc(4) + blsDstBuf.writeUInt32LE(blsDst) + payloadBuf = Buffer.concat([blsDstBuf, payloadBuf]) + payloadDataSz += blsDstBuf.length + } - // Build the request buffer with metadata and then the payload to sign. - const buf = Buffer.alloc(baseReqSz); - let off = 0; - buf.writeUInt32LE(encoding, off); - off += 4; - buf.writeUInt8(hashType, off); - off += 1; - buf.writeUInt8(curveType, off); - off += 1; - const signerPathBuf = buildSignerPathBuf(signerPath, varAddrPathSzAllowed); - signerPathBuf.copy(buf, off); - off += signerPathBuf.length; - buf.writeUInt8(omitPubkey ? 1 : 0, off); - off += 1; + // Build the request buffer with metadata and then the payload to sign. + const buf = Buffer.alloc(baseReqSz) + let off = 0 + buf.writeUInt32LE(encoding, off) + off += 4 + buf.writeUInt8(hashType, off) + off += 1 + buf.writeUInt8(curveType, off) + off += 1 + const signerPathBuf = buildSignerPathBuf(signerPath, varAddrPathSzAllowed) + signerPathBuf.copy(buf, off) + off += signerPathBuf.length + buf.writeUInt8(omitPubkey ? 1 : 0, off) + off += 1 - // Flow data into extraData requests if applicable - const extraDataPayloads = []; - let prehash = null; + // Flow data into extraData requests if applicable + const extraDataPayloads = [] + let prehash = null - let didPrehash = false; - if (payloadBuf.length > baseDataSz) { - if (prehashAllowed && payloadBuf.length > maxExpandedSz) { - // If we prehash, we need to provide the full payload size - buf.writeUInt16LE(payloadBuf.length, off); - off += 2; - didPrehash = true; - // If we have to prehash, only hash the actual payload data, i.e. exclude - // any optional calldata decoder data. - const payloadData = payloadBuf.slice(0, payloadDataSz); - // If this payload is too large to send, but the Lattice allows a prehashed message, do that - if (hashType === hashTypes.NONE) { - // This cannot be done for ED25519 signing, which must sign the full message - throw new Error( - 'Message too large to send and could not be prehashed (hashType=NONE).', - ); - } else if (hashType === hashTypes.KECCAK256) { - prehash = Buffer.from(Hash.keccak256(payloadData)); - } else if (hashType === hashTypes.SHA256) { - prehash = Buffer.from(Hash.sha256(payloadData)); - } else { - throw new Error('Unsupported hash type.'); - } - } else { - // Split overflow data into extraData frames - const frames = splitFrames( - payloadBuf.slice(baseDataSz), - extraDataFrameSz, - ); - frames.forEach((frame) => { - const szLE = Buffer.alloc(4); - szLE.writeUInt32LE(frame.length, 0); - extraDataPayloads.push(Buffer.concat([szLE, frame])); - }); - } - } + let didPrehash = false + if (payloadBuf.length > baseDataSz) { + if (prehashAllowed && payloadBuf.length > maxExpandedSz) { + // If we prehash, we need to provide the full payload size + buf.writeUInt16LE(payloadBuf.length, off) + off += 2 + didPrehash = true + // If we have to prehash, only hash the actual payload data, i.e. exclude + // any optional calldata decoder data. + const payloadData = payloadBuf.slice(0, payloadDataSz) + // If this payload is too large to send, but the Lattice allows a prehashed message, do that + if (hashType === hashTypes.NONE) { + // This cannot be done for ED25519 signing, which must sign the full message + throw new Error('Message too large to send and could not be prehashed (hashType=NONE).') + } else if (hashType === hashTypes.KECCAK256) { + prehash = Buffer.from(Hash.keccak256(payloadData)) + } else if (hashType === hashTypes.SHA256) { + prehash = Buffer.from(Hash.sha256(payloadData)) + } else { + throw new Error('Unsupported hash type.') + } + } else { + // Split overflow data into extraData frames + const frames = splitFrames(payloadBuf.slice(baseDataSz), extraDataFrameSz) + frames.forEach((frame) => { + const szLE = Buffer.alloc(4) + szLE.writeUInt32LE(frame.length, 0) + extraDataPayloads.push(Buffer.concat([szLE, frame])) + }) + } + } - // If we didn't prehash, we know the full request (including calldata info) fits. - // Set the payload size to only include message data. This will inform firmware - // where to slice off calldata info. - if (!didPrehash) { - buf.writeUInt16LE(payloadDataSz, off); - off += 2; - } + // If we didn't prehash, we know the full request (including calldata info) fits. + // Set the payload size to only include message data. This will inform firmware + // where to slice off calldata info. + if (!didPrehash) { + buf.writeUInt16LE(payloadDataSz, off) + off += 2 + } - // If the message had to be prehashed, we will only copy the hash data into the request. - // Otherwise copy as many payload bytes into the request as possible. Follow up data - // from `frames` will come in follow up requests. - const toCopy = prehash ? prehash : payloadBuf; - toCopy.copy(buf, off); + // If the message had to be prehashed, we will only copy the hash data into the request. + // Otherwise copy as many payload bytes into the request as possible. Follow up data + // from `frames` will come in follow up requests. + const toCopy = prehash ? prehash : payloadBuf + toCopy.copy(buf, off) - // Return all the necessary data - return { - payload: buf, - extraDataPayloads, - schema: LatticeSignSchema.generic, - curveType, - encodingType, - hashType, - omitPubkey, - origPayloadBuf, - }; -}; + // Return all the necessary data + return { + payload: buf, + extraDataPayloads, + schema: LatticeSignSchema.generic, + curveType, + encodingType, + hashType, + omitPubkey, + origPayloadBuf, + } +} export const parseGenericSigningResponse = (res, off, req) => { - const parsed = { - pubkey: null, - sig: null, - }; - let digestFromResponse: Buffer | undefined; - // Parse BIP44 path - // Parse pubkey and then sig - if (req.curveType === Constants.SIGNING.CURVES.SECP256K1) { - // Handle `GpEccPubkey256_t` - if (!req.omitPubkey) { - const compression = res.readUInt8(off); - off += 1; - if (compression === 0x02 || compression === 0x03) { - // Compressed key - only copy x - parsed.pubkey = Buffer.alloc(33); - parsed.pubkey.writeUInt8(compression, 0); - res.slice(off, off + 32).copy(parsed.pubkey, 1); - } else if (compression === 0x04) { - // Uncompressed key - parsed.pubkey = Buffer.alloc(65); - parsed.pubkey.writeUInt8(compression, 0); - res.slice(off).copy(parsed.pubkey, 1); - } else { - throw new Error('Bad compression byte in signing response.'); - } - off += 64; - } else { - // Skip pubkey section - off += 65; - } - // Handle `GpECDSASig_t` - const sigLength = 2 + res[off + 1]; - const derSlice = res.slice(off, off + sigLength); - const derSig = parseDER(derSlice); - // Remove any leading zeros in signature components to ensure - // the result is a 64 byte sig - const rBuf = fixLen(derSig.r, 32); - const sBuf = fixLen(derSig.s, 32); + const parsed = { + pubkey: null, + sig: null, + } + let digestFromResponse: Buffer | undefined + // Parse BIP44 path + // Parse pubkey and then sig + if (req.curveType === Constants.SIGNING.CURVES.SECP256K1) { + // Handle `GpEccPubkey256_t` + if (!req.omitPubkey) { + const compression = res.readUInt8(off) + off += 1 + if (compression === 0x02 || compression === 0x03) { + // Compressed key - only copy x + parsed.pubkey = Buffer.alloc(33) + parsed.pubkey.writeUInt8(compression, 0) + res.slice(off, off + 32).copy(parsed.pubkey, 1) + } else if (compression === 0x04) { + // Uncompressed key + parsed.pubkey = Buffer.alloc(65) + parsed.pubkey.writeUInt8(compression, 0) + res.slice(off).copy(parsed.pubkey, 1) + } else { + throw new Error('Bad compression byte in signing response.') + } + off += 64 + } else { + // Skip pubkey section + off += 65 + } + // Handle `GpECDSASig_t` + const sigLength = 2 + res[off + 1] + const derSlice = res.slice(off, off + sigLength) + const derSig = parseDER(derSlice) + // Remove any leading zeros in signature components to ensure + // the result is a 64 byte sig + const rBuf = fixLen(derSig.r, 32) + const sBuf = fixLen(derSig.s, 32) - parsed.sig = { - r: `0x${rBuf.toString('hex')}`, - s: `0x${sBuf.toString('hex')}`, - }; - off += sigLength; - if (res.length >= off + 32) { - digestFromResponse = Buffer.from(res.slice(off, off + 32)); - off += 32; - } + parsed.sig = { + r: `0x${rBuf.toString('hex')}`, + s: `0x${sBuf.toString('hex')}`, + } + off += sigLength + if (res.length >= off + 32) { + digestFromResponse = Buffer.from(res.slice(off, off + 32)) + off += 32 + } - if (req.encodingType === Constants.SIGNING.ENCODINGS.EVM) { - // Full EVM transaction - use getV for proper chainId/EIP-155 handling - const vBn = getV(req.origPayloadBuf, parsed); - parsed.sig.v = BigInt(vBn.toString()); - populateViemSignedTx(parsed.sig.v, req, parsed); - } else if ( - req.hashType === Constants.SIGNING.HASHES.KECCAK256 && - req.encodingType !== Constants.SIGNING.ENCODINGS.EVM - ) { - // Generic Keccak256 message - determine if it looks like a transaction - let isTransaction = false; + if (req.encodingType === Constants.SIGNING.ENCODINGS.EVM) { + // Full EVM transaction - use getV for proper chainId/EIP-155 handling + const vBn = getV(req.origPayloadBuf, parsed) + parsed.sig.v = BigInt(vBn.toString()) + populateViemSignedTx(parsed.sig.v, req, parsed) + } else if (req.hashType === Constants.SIGNING.HASHES.KECCAK256 && req.encodingType !== Constants.SIGNING.ENCODINGS.EVM) { + // Generic Keccak256 message - determine if it looks like a transaction + let isTransaction = false - try { - let bufferToDecode = req.origPayloadBuf; + try { + let bufferToDecode = req.origPayloadBuf - // Try to skip EIP-2718 type byte if present - if (bufferToDecode[0] <= 0x7f) { - bufferToDecode = bufferToDecode.slice(1); - } + // Try to skip EIP-2718 type byte if present + if (bufferToDecode[0] <= 0x7f) { + bufferToDecode = bufferToDecode.slice(1) + } - const decoded = RLP.decode(bufferToDecode); - // A legacy transaction has 9 fields (or 6 if pre-EIP155) - isTransaction = Array.isArray(decoded) && decoded.length >= 6; - } catch { - isTransaction = false; - } + const decoded = RLP.decode(bufferToDecode) + // A legacy transaction has 9 fields (or 6 if pre-EIP155) + isTransaction = Array.isArray(decoded) && decoded.length >= 6 + } catch { + isTransaction = false + } - if (isTransaction) { - try { - // If it looks like a transaction, use the robust getV - const vBn = getV(req.origPayloadBuf, parsed); - parsed.sig.v = BigInt(vBn.toString()); - populateViemSignedTx(parsed.sig.v, req, parsed); - } catch (err) { - console.error( - 'Failed to get V from transaction, using fallback:', - err, - ); - // Fall back to simple recovery if getV fails (e.g., malformed RLP) - // Use the correct hash type specified in the request - const msgHash = computeMessageHash(req, digestFromResponse); - const yParity = getYParity({ - messageHash: msgHash, - signature: parsed.sig, - publicKey: parsed.pubkey, - }); - parsed.sig.v = BigInt(27 + yParity); - } - } else { - // Generic message - use simple recovery (v = 27 + recoveryId) - // Use the correct hash type specified in the request - const msgHash = computeMessageHash(req, digestFromResponse); - const yParity = getYParity({ - messageHash: msgHash, - signature: parsed.sig, - publicKey: parsed.pubkey, - }); - parsed.sig.v = BigInt(27 + yParity); - } - } - } else if (req.curveType === Constants.SIGNING.CURVES.ED25519) { - if (!req.omitPubkey) { - // Handle `GpEdDSAPubkey_t` - parsed.pubkey = Buffer.alloc(32); - res.slice(off, off + 32).copy(parsed.pubkey); - } - off += 32; - // Handle `GpEdDSASig_t` - parsed.sig = { - r: `0x${res.slice(off, off + 32).toString('hex')}`, - s: `0x${res.slice(off + 32, off + 64).toString('hex')}`, - }; - off += 64; - } else if (req.curveType === Constants.SIGNING.CURVES.BLS12_381_G2) { - if (!req.omitPubkey) { - // Handle `GpBLS12_381_G1Pub_t` - parsed.pubkey = Buffer.alloc(48); - res.slice(off, off + 48).copy(parsed.pubkey); - } - off += 48; - // Handle `GpBLS12_381_G2Sig_t` - parsed.sig = Buffer.alloc(96); - res.slice(off, off + 96).copy(parsed.sig); - off += 96; - } else { - throw new Error('Unsupported curve.'); - } - return parsed; -}; + if (isTransaction) { + try { + // If it looks like a transaction, use the robust getV + const vBn = getV(req.origPayloadBuf, parsed) + parsed.sig.v = BigInt(vBn.toString()) + populateViemSignedTx(parsed.sig.v, req, parsed) + } catch (err) { + console.error('Failed to get V from transaction, using fallback:', err) + // Fall back to simple recovery if getV fails (e.g., malformed RLP) + // Use the correct hash type specified in the request + const msgHash = computeMessageHash(req, digestFromResponse) + const yParity = getYParity({ + messageHash: msgHash, + signature: parsed.sig, + publicKey: parsed.pubkey, + }) + parsed.sig.v = BigInt(27 + yParity) + } + } else { + // Generic message - use simple recovery (v = 27 + recoveryId) + // Use the correct hash type specified in the request + const msgHash = computeMessageHash(req, digestFromResponse) + const yParity = getYParity({ + messageHash: msgHash, + signature: parsed.sig, + publicKey: parsed.pubkey, + }) + parsed.sig.v = BigInt(27 + yParity) + } + } + } else if (req.curveType === Constants.SIGNING.CURVES.ED25519) { + if (!req.omitPubkey) { + // Handle `GpEdDSAPubkey_t` + parsed.pubkey = Buffer.alloc(32) + res.slice(off, off + 32).copy(parsed.pubkey) + } + off += 32 + // Handle `GpEdDSASig_t` + parsed.sig = { + r: `0x${res.slice(off, off + 32).toString('hex')}`, + s: `0x${res.slice(off + 32, off + 64).toString('hex')}`, + } + off += 64 + } else if (req.curveType === Constants.SIGNING.CURVES.BLS12_381_G2) { + if (!req.omitPubkey) { + // Handle `GpBLS12_381_G1Pub_t` + parsed.pubkey = Buffer.alloc(48) + res.slice(off, off + 48).copy(parsed.pubkey) + } + off += 48 + // Handle `GpBLS12_381_G2Sig_t` + parsed.sig = Buffer.alloc(96) + res.slice(off, off + 96).copy(parsed.sig) + off += 96 + } else { + throw new Error('Unsupported curve.') + } + return parsed +} function computeMessageHash( - req: { - hashType: number; - origPayloadBuf: Buffer; - }, - digestFromResponse?: Buffer, + req: { + hashType: number + origPayloadBuf: Buffer + }, + digestFromResponse?: Buffer, ): Buffer { - if ( - digestFromResponse && - digestFromResponse.length === 32 && - digestFromResponse.some((byte) => byte !== 0) - ) { - return digestFromResponse; - } - if (req.hashType === Constants.SIGNING.HASHES.SHA256) { - return Buffer.from(Hash.sha256(req.origPayloadBuf)); - } - if (req.hashType === Constants.SIGNING.HASHES.KECCAK256) { - return Buffer.from(Hash.keccak256(req.origPayloadBuf)); - } - throw new Error('Unsupported hash type for message hash computation.'); + if (digestFromResponse && digestFromResponse.length === 32 && digestFromResponse.some((byte) => byte !== 0)) { + return digestFromResponse + } + if (req.hashType === Constants.SIGNING.HASHES.SHA256) { + return Buffer.from(Hash.sha256(req.origPayloadBuf)) + } + if (req.hashType === Constants.SIGNING.HASHES.KECCAK256) { + return Buffer.from(Hash.keccak256(req.origPayloadBuf)) + } + throw new Error('Unsupported hash type for message hash computation.') } // Reconstruct a viem-compatible signed transaction string from the raw payload and // recovered signature so consumers can compare or broadcast without extra parsing. -function populateViemSignedTx( - sigV: bigint, - req: any, - parsed: { sig: { r: string; s: string; v?: bigint }; viemTx?: string }, -) { - if (req.encodingType !== Constants.SIGNING.ENCODINGS.EVM) return; +function populateViemSignedTx(sigV: bigint, req: any, parsed: { sig: { r: string; s: string; v?: bigint }; viemTx?: string }) { + if (req.encodingType !== Constants.SIGNING.ENCODINGS.EVM) return - try { - const rawTxHex = `0x${req.origPayloadBuf.toString('hex')}` as Hex; - const parsedTx: any = parseTransaction(rawTxHex); + try { + const rawTxHex = `0x${req.origPayloadBuf.toString('hex')}` as Hex + const parsedTx: any = parseTransaction(rawTxHex) - const baseTx: any = { - chainId: parsedTx.chainId, - to: parsedTx.to ?? undefined, - value: parsedTx.value ?? 0n, - data: (parsedTx.data ?? '0x') as Hex, - nonce: parsedTx.nonce ?? 0n, - gas: parsedTx.gas ?? parsedTx.gasLimit ?? 0n, - }; + const baseTx: any = { + chainId: parsedTx.chainId, + to: parsedTx.to ?? undefined, + value: parsedTx.value ?? 0n, + data: (parsedTx.data ?? '0x') as Hex, + nonce: parsedTx.nonce ?? 0n, + gas: parsedTx.gas ?? parsedTx.gasLimit ?? 0n, + } - if (parsedTx.maxFeePerGas !== undefined) { - baseTx.maxFeePerGas = parsedTx.maxFeePerGas; - } - if (parsedTx.maxPriorityFeePerGas !== undefined) { - baseTx.maxPriorityFeePerGas = parsedTx.maxPriorityFeePerGas; - } - if (parsedTx.gasPrice !== undefined) { - baseTx.gasPrice = parsedTx.gasPrice; - } - if (parsedTx.accessList !== undefined) { - baseTx.accessList = parsedTx.accessList; - } - if (parsedTx.authorizationList !== undefined) { - baseTx.authorizationList = parsedTx.authorizationList; - } + if (parsedTx.maxFeePerGas !== undefined) { + baseTx.maxFeePerGas = parsedTx.maxFeePerGas + } + if (parsedTx.maxPriorityFeePerGas !== undefined) { + baseTx.maxPriorityFeePerGas = parsedTx.maxPriorityFeePerGas + } + if (parsedTx.gasPrice !== undefined) { + baseTx.gasPrice = parsedTx.gasPrice + } + if (parsedTx.accessList !== undefined) { + baseTx.accessList = parsedTx.accessList + } + if (parsedTx.authorizationList !== undefined) { + baseTx.authorizationList = parsedTx.authorizationList + } - if (parsedTx.type !== undefined && parsedTx.type !== null) { - baseTx.type = parsedTx.type; - } + if (parsedTx.type !== undefined && parsedTx.type !== null) { + baseTx.type = parsedTx.type + } - const signature = - parsedTx.type === 'legacy' || parsedTx.type === undefined - ? { - v: sigV, - r: parsed.sig.r as Hex, - s: parsed.sig.s as Hex, - } - : { - yParity: Number(sigV), - r: parsed.sig.r as Hex, - s: parsed.sig.s as Hex, - }; + const signature = + parsedTx.type === 'legacy' || parsedTx.type === undefined + ? { + v: sigV, + r: parsed.sig.r as Hex, + s: parsed.sig.s as Hex, + } + : { + yParity: Number(sigV), + r: parsed.sig.r as Hex, + s: parsed.sig.s as Hex, + } - parsed.viemTx = serializeTransaction( - baseTx as TransactionSerializable, - signature as any, - ); - } catch (_err) { - console.debug('Failed to build viemTx from response', _err); - } + parsed.viemTx = serializeTransaction(baseTx as TransactionSerializable, signature as any) + } catch (_err) { + console.debug('Failed to build viemTx from response', _err) + } } export const getEncodedPayload = (payload, encoding, allowedEncodings) => { - if (!encoding) { - encoding = Constants.SIGNING.ENCODINGS.NONE; - } - // Make sure the encoding type specified is supported by firmware - if (!existsIn(encoding, allowedEncodings)) { - throw new Error( - 'Encoding not supported by Lattice firmware. You may want to update.', - ); - } - let payloadBuf; - if (!payload) { - throw new Error('No payload included'); - } - if (typeof payload === 'string' && payload.slice(0, 2) === '0x') { - payloadBuf = Buffer.from(payload.slice(2), 'hex'); - } else { - payloadBuf = Buffer.from(payload); - } - // Build the request with the specified encoding type - return { - payloadBuf, - encoding, - }; -}; + if (!encoding) { + encoding = Constants.SIGNING.ENCODINGS.NONE + } + // Make sure the encoding type specified is supported by firmware + if (!existsIn(encoding, allowedEncodings)) { + throw new Error('Encoding not supported by Lattice firmware. You may want to update.') + } + let payloadBuf: Buffer + if (!payload) { + throw new Error('No payload included') + } + if (typeof payload === 'string' && payload.slice(0, 2) === '0x') { + payloadBuf = Buffer.from(payload.slice(2), 'hex') + } else { + payloadBuf = Buffer.from(payload) + } + // Build the request with the specified encoding type + return { + payloadBuf, + encoding, + } +} diff --git a/packages/sdk/src/index.ts b/packages/sdk/src/index.ts index 8dfc3e0d..a3095dc3 100644 --- a/packages/sdk/src/index.ts +++ b/packages/sdk/src/index.ts @@ -1,5 +1,5 @@ -export { CALLDATA as Calldata } from './calldata/index'; -export { Client } from './client'; -export { EXTERNAL as Constants } from './constants'; -export { EXTERNAL as Utils } from './util'; -export * from './api'; +export { CALLDATA as Calldata } from './calldata/index' +export { Client } from './client' +export { EXTERNAL as Constants } from './constants' +export { EXTERNAL as Utils } from './util' +export * from './api' diff --git a/packages/sdk/src/protocol/index.ts b/packages/sdk/src/protocol/index.ts index 71228be3..3109ca44 100644 --- a/packages/sdk/src/protocol/index.ts +++ b/packages/sdk/src/protocol/index.ts @@ -1,2 +1,2 @@ -export * from './latticeConstants'; -export * from './secureMessages'; +export * from './latticeConstants' +export * from './secureMessages' diff --git a/packages/sdk/src/protocol/latticeConstants.ts b/packages/sdk/src/protocol/latticeConstants.ts index 041564aa..7e08710d 100644 --- a/packages/sdk/src/protocol/latticeConstants.ts +++ b/packages/sdk/src/protocol/latticeConstants.ts @@ -1,204 +1,200 @@ export enum LatticeResponseCode { - success = 0x00, - invalidMsg = 0x80, - unsupportedVersion = 0x81, - deviceBusy = 0x82, - userTimeout = 0x83, - userDeclined = 0x84, - pairFailed = 0x85, - pairDisabled = 0x86, - permissionDisabled = 0x87, - internalError = 0x88, - gceTimeout = 0x89, - wrongWallet = 0x8a, - deviceLocked = 0x8b, - disabled = 0x8c, - already = 0x8d, - invalidEphemId = 0x8e, + success = 0x00, + invalidMsg = 0x80, + unsupportedVersion = 0x81, + deviceBusy = 0x82, + userTimeout = 0x83, + userDeclined = 0x84, + pairFailed = 0x85, + pairDisabled = 0x86, + permissionDisabled = 0x87, + internalError = 0x88, + gceTimeout = 0x89, + wrongWallet = 0x8a, + deviceLocked = 0x8b, + disabled = 0x8c, + already = 0x8d, + invalidEphemId = 0x8e, } export enum LatticeSecureMsgType { - connect = 0x01, - encrypted = 0x02, + connect = 0x01, + encrypted = 0x02, } export enum LatticeProtocolVersion { - v1 = 0x01, + v1 = 0x01, } export enum LatticeMsgType { - response = 0x00, - secure = 0x02, + response = 0x00, + secure = 0x02, } export enum LatticeSecureEncryptedRequestType { - finalizePairing = 0, - getAddresses = 1, - sign = 3, - getWallets = 4, - getKvRecords = 7, - addKvRecords = 8, - removeKvRecords = 9, - fetchEncryptedData = 12, - test = 13, + finalizePairing = 0, + getAddresses = 1, + sign = 3, + getWallets = 4, + getKvRecords = 7, + addKvRecords = 8, + removeKvRecords = 9, + fetchEncryptedData = 12, + test = 13, } export enum LatticeGetAddressesFlag { - none = 0, // For formatted addresses - secp256k1Pubkey = 3, - ed25519Pubkey = 4, - bls12_381Pubkey = 5, - secp256k1Xpub = 6, // For Bitcoin XPUB + none = 0, // For formatted addresses + secp256k1Pubkey = 3, + ed25519Pubkey = 4, + bls12_381Pubkey = 5, + secp256k1Xpub = 6, // For Bitcoin XPUB } export enum LatticeSignSchema { - bitcoin = 0, - ethereum = 1, // Deprecated - ethereumMsg = 3, - extraData = 4, - generic = 5, + bitcoin = 0, + ethereum = 1, // Deprecated + ethereumMsg = 3, + extraData = 4, + generic = 5, } export enum LatticeSignHash { - none = 0, - keccak256 = 1, - sha256 = 2, + none = 0, + keccak256 = 1, + sha256 = 2, } export enum LatticeSignCurve { - secp256k1 = 0, - ed25519 = 1, - bls12_381 = 2, + secp256k1 = 0, + ed25519 = 1, + bls12_381 = 2, } export enum LatticeSignEncoding { - none = 1, - solana = 2, - evm = 4, - eth_deposit = 5, - eip7702_auth = 6, - eip7702_auth_list = 7, + none = 1, + solana = 2, + evm = 4, + eth_deposit = 5, + eip7702_auth = 6, + eip7702_auth_list = 7, } export enum LatticeSignBlsDst { - NUL = 1, - POP = 2, + NUL = 1, + POP = 2, } export enum LatticeEncDataSchema { - eip2335 = 0, + eip2335 = 0, } export const ProtocolConstants = { - // Lattice firmware uses a static initialization vector for - // message encryption/decryption. This is generally considered - // fine because each encryption/decryption uses a unique encryption - // secret (derived from the per-message ephemeral key pair). - aesIv: [ - 0x6d, 0x79, 0x73, 0x65, 0x63, 0x72, 0x65, 0x74, 0x70, 0x61, 0x73, 0x73, - 0x77, 0x6f, 0x72, 0x64, - ], - // Constant size of address buffers from the Lattice. - // Note that this size also captures public keys returned - // by the Lattice (addresses = strings, pubkeys = buffers) - addrStrLen: 129, - // Status of the client's pairing with the target Lattice - pairingStatus: { - notPaired: 0x00, - paired: 0x01, - }, - // Response types, codes, and error messages - responseMsg: { - [LatticeResponseCode.success]: '', - [LatticeResponseCode.invalidMsg]: 'Invalid Request', - [LatticeResponseCode.unsupportedVersion]: 'Unsupported Version', - [LatticeResponseCode.deviceBusy]: 'Device Busy', - [LatticeResponseCode.userTimeout]: 'Timeout waiting for user', - [LatticeResponseCode.userDeclined]: 'Request declined by user', - [LatticeResponseCode.pairFailed]: 'Pairing failed', - [LatticeResponseCode.pairDisabled]: 'Pairing is currently disabled', - [LatticeResponseCode.permissionDisabled]: - 'Automated signing is currently disabled', - [LatticeResponseCode.internalError]: 'Device Error', - [LatticeResponseCode.gceTimeout]: 'Device Timeout', - [LatticeResponseCode.wrongWallet]: 'Active wallet does not match request', - [LatticeResponseCode.deviceLocked]: 'Device Locked', - [LatticeResponseCode.disabled]: 'Feature Disabled', - [LatticeResponseCode.already]: 'Record already exists on device', - [LatticeResponseCode.invalidEphemId]: 'Request failed - needs resync', - }, - msgSizes: { - // General message header size. Valid for all Lattice messages - header: 8, - // Checksum must be appended to each message - checksum: 4, - // Lattice secure message constants. All requests from this SDK - // are secure messages. - secure: { - // Sizes of full payloads for secure messages - payload: { - request: { - // [ requestType (1 byte) | pubkey (65 bytes) ] - connect: 66, - // [ requestType (1 byte) | ephemeralId (4 bytes) | encryptedData (1728 bytes) ] - encrypted: 1733, - }, - // Note that the response payload always has status code as the - // first byte. This byte is removed as part of `request`, inside - // `parseLattice1Response`. These constants include the status - // code byte. - response: { - connect: 215, - // Encrypted responses are as follows: - // encryptedData (1728) | empty (1728) - // The latter half is empty due to an invalid type definition - // in Lattice firmware. (Someone made a C `struct` instead of - // a `union`, oops). - encrypted: 3457, - }, - }, - // Sizes for data inside secure message payloads - data: { - // All requests also have a `requestCode`, which is omitted - // from these constants. - request: { - connect: 65, - encrypted: { - // All encrypted requests are encrypted into a 1728 byte buffer - encryptedData: 1728, - // Individual request types have different data sizes. - [LatticeSecureEncryptedRequestType.finalizePairing]: 99, - [LatticeSecureEncryptedRequestType.getAddresses]: 54, - [LatticeSecureEncryptedRequestType.sign]: 1680, - [LatticeSecureEncryptedRequestType.getWallets]: 0, - [LatticeSecureEncryptedRequestType.getKvRecords]: 9, - [LatticeSecureEncryptedRequestType.addKvRecords]: 1391, - [LatticeSecureEncryptedRequestType.removeKvRecords]: 405, - [LatticeSecureEncryptedRequestType.fetchEncryptedData]: 1025, - [LatticeSecureEncryptedRequestType.test]: 506, - }, - }, - // All responses also have a `responseCode`, which is omitted - // from these constants. - response: { - encrypted: { - encryptedData: 1728, - // Once decrypted, the data size of the response - // payload will be determined by the request type. - // NOTE: All requests also have ephemeralPublicKey (65 bytes) and - // checksum (4 bytes), which are excluded from these sizes. - [LatticeSecureEncryptedRequestType.finalizePairing]: 0, - [LatticeSecureEncryptedRequestType.getAddresses]: 1290, - [LatticeSecureEncryptedRequestType.sign]: 1090, - [LatticeSecureEncryptedRequestType.getWallets]: 142, - [LatticeSecureEncryptedRequestType.getKvRecords]: 1395, - [LatticeSecureEncryptedRequestType.addKvRecords]: 0, - [LatticeSecureEncryptedRequestType.removeKvRecords]: 0, - [LatticeSecureEncryptedRequestType.fetchEncryptedData]: 1608, - [LatticeSecureEncryptedRequestType.test]: 1646, - }, - }, - }, - }, - }, -} as const; + // Lattice firmware uses a static initialization vector for + // message encryption/decryption. This is generally considered + // fine because each encryption/decryption uses a unique encryption + // secret (derived from the per-message ephemeral key pair). + aesIv: [0x6d, 0x79, 0x73, 0x65, 0x63, 0x72, 0x65, 0x74, 0x70, 0x61, 0x73, 0x73, 0x77, 0x6f, 0x72, 0x64], + // Constant size of address buffers from the Lattice. + // Note that this size also captures public keys returned + // by the Lattice (addresses = strings, pubkeys = buffers) + addrStrLen: 129, + // Status of the client's pairing with the target Lattice + pairingStatus: { + notPaired: 0x00, + paired: 0x01, + }, + // Response types, codes, and error messages + responseMsg: { + [LatticeResponseCode.success]: '', + [LatticeResponseCode.invalidMsg]: 'Invalid Request', + [LatticeResponseCode.unsupportedVersion]: 'Unsupported Version', + [LatticeResponseCode.deviceBusy]: 'Device Busy', + [LatticeResponseCode.userTimeout]: 'Timeout waiting for user', + [LatticeResponseCode.userDeclined]: 'Request declined by user', + [LatticeResponseCode.pairFailed]: 'Pairing failed', + [LatticeResponseCode.pairDisabled]: 'Pairing is currently disabled', + [LatticeResponseCode.permissionDisabled]: 'Automated signing is currently disabled', + [LatticeResponseCode.internalError]: 'Device Error', + [LatticeResponseCode.gceTimeout]: 'Device Timeout', + [LatticeResponseCode.wrongWallet]: 'Active wallet does not match request', + [LatticeResponseCode.deviceLocked]: 'Device Locked', + [LatticeResponseCode.disabled]: 'Feature Disabled', + [LatticeResponseCode.already]: 'Record already exists on device', + [LatticeResponseCode.invalidEphemId]: 'Request failed - needs resync', + }, + msgSizes: { + // General message header size. Valid for all Lattice messages + header: 8, + // Checksum must be appended to each message + checksum: 4, + // Lattice secure message constants. All requests from this SDK + // are secure messages. + secure: { + // Sizes of full payloads for secure messages + payload: { + request: { + // [ requestType (1 byte) | pubkey (65 bytes) ] + connect: 66, + // [ requestType (1 byte) | ephemeralId (4 bytes) | encryptedData (1728 bytes) ] + encrypted: 1733, + }, + // Note that the response payload always has status code as the + // first byte. This byte is removed as part of `request`, inside + // `parseLattice1Response`. These constants include the status + // code byte. + response: { + connect: 215, + // Encrypted responses are as follows: + // encryptedData (1728) | empty (1728) + // The latter half is empty due to an invalid type definition + // in Lattice firmware. (Someone made a C `struct` instead of + // a `union`, oops). + encrypted: 3457, + }, + }, + // Sizes for data inside secure message payloads + data: { + // All requests also have a `requestCode`, which is omitted + // from these constants. + request: { + connect: 65, + encrypted: { + // All encrypted requests are encrypted into a 1728 byte buffer + encryptedData: 1728, + // Individual request types have different data sizes. + [LatticeSecureEncryptedRequestType.finalizePairing]: 99, + [LatticeSecureEncryptedRequestType.getAddresses]: 54, + [LatticeSecureEncryptedRequestType.sign]: 1680, + [LatticeSecureEncryptedRequestType.getWallets]: 0, + [LatticeSecureEncryptedRequestType.getKvRecords]: 9, + [LatticeSecureEncryptedRequestType.addKvRecords]: 1391, + [LatticeSecureEncryptedRequestType.removeKvRecords]: 405, + [LatticeSecureEncryptedRequestType.fetchEncryptedData]: 1025, + [LatticeSecureEncryptedRequestType.test]: 506, + }, + }, + // All responses also have a `responseCode`, which is omitted + // from these constants. + response: { + encrypted: { + encryptedData: 1728, + // Once decrypted, the data size of the response + // payload will be determined by the request type. + // NOTE: All requests also have ephemeralPublicKey (65 bytes) and + // checksum (4 bytes), which are excluded from these sizes. + [LatticeSecureEncryptedRequestType.finalizePairing]: 0, + [LatticeSecureEncryptedRequestType.getAddresses]: 1290, + [LatticeSecureEncryptedRequestType.sign]: 1090, + [LatticeSecureEncryptedRequestType.getWallets]: 142, + [LatticeSecureEncryptedRequestType.getKvRecords]: 1395, + [LatticeSecureEncryptedRequestType.addKvRecords]: 0, + [LatticeSecureEncryptedRequestType.removeKvRecords]: 0, + [LatticeSecureEncryptedRequestType.fetchEncryptedData]: 1608, + [LatticeSecureEncryptedRequestType.test]: 1646, + }, + }, + }, + }, + }, +} as const diff --git a/packages/sdk/src/protocol/secureMessages.ts b/packages/sdk/src/protocol/secureMessages.ts index 700e7233..dff64ba6 100644 --- a/packages/sdk/src/protocol/secureMessages.ts +++ b/packages/sdk/src/protocol/secureMessages.ts @@ -1,21 +1,7 @@ -import { getEphemeralId, request } from '../shared/functions'; -import { validateEphemeralPub } from '../shared/validators'; -import type { - DecryptedResponse, - KeyPair, - LatticeMessageHeader, - LatticeSecureConnectRequestPayloadData, - LatticeSecureDecryptedResponse, - LatticeSecureRequest, - LatticeSecureRequestPayload, -} from '../types'; -import { - aes256_decrypt, - aes256_encrypt, - checksum, - getP256KeyPairFromPub, - randomBytes, -} from '../util'; +import { getEphemeralId, request } from '../shared/functions' +import { validateEphemeralPub } from '../shared/validators' +import type { DecryptedResponse, KeyPair, LatticeMessageHeader, LatticeSecureConnectRequestPayloadData, LatticeSecureDecryptedResponse, LatticeSecureRequest, LatticeSecureRequestPayload } from '../types' +import { aes256_decrypt, aes256_encrypt, checksum, getP256KeyPairFromPub, randomBytes } from '../util' /** * All messages sent to the Lattice from this SDK will be * "secure messages", of which there are two types: @@ -36,16 +22,10 @@ import { * The response to this request will contain a new ephemral public * key, which you will need for the next encrypted request. */ -import { - ProtocolConstants as Constants, - LatticeMsgType, - LatticeProtocolVersion, - type LatticeSecureEncryptedRequestType, - LatticeSecureMsgType, -} from './latticeConstants'; +import { ProtocolConstants as Constants, LatticeMsgType, LatticeProtocolVersion, type LatticeSecureEncryptedRequestType, LatticeSecureMsgType } from './latticeConstants' -const { msgSizes } = Constants; -const { secure: szs } = msgSizes; +const { msgSizes } = Constants +const { secure: szs } = msgSizes /** * Build and make a request to connect to a specific Lattice @@ -56,29 +36,25 @@ const { secure: szs } = msgSizes; * information about the connected Lattice. */ export async function connectSecureRequest({ - url, - pubkey, + url, + pubkey, }: { - url: string; - pubkey: Buffer; + url: string + pubkey: Buffer }): Promise { - // Build the secure request message - const payloadData = serializeSecureRequestConnectPayloadData({ - pubkey: pubkey, - }); - const msgId = randomBytes(4); - const msg = serializeSecureRequestMsg( - msgId, - LatticeSecureMsgType.connect, - payloadData, - ); - // Send request to the Lattice - const resp = await request({ url, payload: msg }); - if (resp.length !== szs.payload.response.connect - 1) { - throw new Error('Wrong Lattice response message size.'); - } + // Build the secure request message + const payloadData = serializeSecureRequestConnectPayloadData({ + pubkey: pubkey, + }) + const msgId = randomBytes(4) + const msg = serializeSecureRequestMsg(msgId, LatticeSecureMsgType.connect, payloadData) + // Send request to the Lattice + const resp = await request({ url, payload: msg }) + if (resp.length !== szs.payload.response.connect - 1) { + throw new Error('Wrong Lattice response message size.') + } - return resp; + return resp } /** @@ -93,61 +69,54 @@ export async function connectSecureRequest({ * @return {Buffer} Decrypted response data (excluding metadata) */ export async function encryptedSecureRequest({ - data, - requestType, - sharedSecret, - ephemeralPub, - url, + data, + requestType, + sharedSecret, + ephemeralPub, + url, }: { - data: Buffer; - requestType: LatticeSecureEncryptedRequestType; - sharedSecret: Buffer; - ephemeralPub: KeyPair; - url: string; + data: Buffer + requestType: LatticeSecureEncryptedRequestType + sharedSecret: Buffer + ephemeralPub: KeyPair + url: string }): Promise { - // Generate a random message id for internal tracking - // of this specific request (internal on both sides). - const msgId = randomBytes(4); + // Generate a random message id for internal tracking + // of this specific request (internal on both sides). + const msgId = randomBytes(4) - // Serialize the request data into encrypted request - // payload data. - const payloadData = serializeSecureRequestEncryptedPayloadData({ - data, - requestType, - ephemeralPub, - sharedSecret, - }); + // Serialize the request data into encrypted request + // payload data. + const payloadData = serializeSecureRequestEncryptedPayloadData({ + data, + requestType, + ephemeralPub, + sharedSecret, + }) - // Serialize the payload data into an encrypted secure - // request message. - const msg = serializeSecureRequestMsg( - msgId, - LatticeSecureMsgType.encrypted, - payloadData, - ); + // Serialize the payload data into an encrypted secure + // request message. + const msg = serializeSecureRequestMsg(msgId, LatticeSecureMsgType.encrypted, payloadData) - // Send request to Lattice - const resp = await request({ - url, - payload: msg, - }); + // Send request to Lattice + const resp = await request({ + url, + payload: msg, + }) - // Deserialize the response payload data - if (resp.length !== szs.payload.response.encrypted - 1) { - throw new Error('Wrong Lattice response message size.'); - } + // Deserialize the response payload data + if (resp.length !== szs.payload.response.encrypted - 1) { + throw new Error('Wrong Lattice response message size.') + } - const encPayloadData = resp.slice( - 0, - szs.data.response.encrypted.encryptedData, - ); + const encPayloadData = resp.slice(0, szs.data.response.encrypted.encryptedData) - // Return decrypted response payload data - return decryptEncryptedLatticeResponseData({ - encPayloadData, - requestType, - sharedSecret, - }); + // Return decrypted response payload data + return decryptEncryptedLatticeResponseData({ + encPayloadData, + requestType, + sharedSecret, + }) } /** @@ -159,87 +128,76 @@ export async function encryptedSecureRequest({ * @param payloadData - Request data * @return {Buffer} Serialized message to be sent to Lattice */ -function serializeSecureRequestMsg( - msgId: Buffer, - secureRequestType: LatticeSecureMsgType, - payloadData: Buffer, -): Buffer { - // Sanity check request data - if (msgId.length !== 4) { - throw new Error('msgId must be four bytes'); - } - if ( - secureRequestType !== LatticeSecureMsgType.connect && - secureRequestType !== LatticeSecureMsgType.encrypted - ) { - throw new Error('Invalid Lattice secure request type'); - } +function serializeSecureRequestMsg(msgId: Buffer, secureRequestType: LatticeSecureMsgType, payloadData: Buffer): Buffer { + // Sanity check request data + if (msgId.length !== 4) { + throw new Error('msgId must be four bytes') + } + if (secureRequestType !== LatticeSecureMsgType.connect && secureRequestType !== LatticeSecureMsgType.encrypted) { + throw new Error('Invalid Lattice secure request type') + } - // Validate the incoming payload data size. Note that the payload - // data is prepended with a secure request type byte, so the - // payload data size is one less than the expected size. - const isValidConnectPayloadDataSz = - secureRequestType === LatticeSecureMsgType.connect && - payloadData.length === szs.payload.request.connect - 1; - const isValidEncryptedPayloadDataSz = - secureRequestType === LatticeSecureMsgType.encrypted && - payloadData.length === szs.payload.request.encrypted - 1; + // Validate the incoming payload data size. Note that the payload + // data is prepended with a secure request type byte, so the + // payload data size is one less than the expected size. + const isValidConnectPayloadDataSz = secureRequestType === LatticeSecureMsgType.connect && payloadData.length === szs.payload.request.connect - 1 + const isValidEncryptedPayloadDataSz = secureRequestType === LatticeSecureMsgType.encrypted && payloadData.length === szs.payload.request.encrypted - 1 - // Build payload and size - let msgSz = msgSizes.header + msgSizes.checksum; - let payloadLen; - const payload: LatticeSecureRequestPayload = { - requestType: secureRequestType, - data: payloadData, - }; - if (isValidConnectPayloadDataSz) { - payloadLen = szs.payload.request.connect; - } else if (isValidEncryptedPayloadDataSz) { - payloadLen = szs.payload.request.encrypted; - } else { - throw new Error('Invalid Lattice secure request payload size'); - } - msgSz += payloadLen; + // Build payload and size + let msgSz = msgSizes.header + msgSizes.checksum + let payloadLen: number + const payload: LatticeSecureRequestPayload = { + requestType: secureRequestType, + data: payloadData, + } + if (isValidConnectPayloadDataSz) { + payloadLen = szs.payload.request.connect + } else if (isValidEncryptedPayloadDataSz) { + payloadLen = szs.payload.request.encrypted + } else { + throw new Error('Invalid Lattice secure request payload size') + } + msgSz += payloadLen - // Construct the request in object form - const header: LatticeMessageHeader = { - version: LatticeProtocolVersion.v1, - type: LatticeMsgType.secure, - id: msgId, - len: payloadLen, - }; - const req: LatticeSecureRequest = { - header, - payload, - }; + // Construct the request in object form + const header: LatticeMessageHeader = { + version: LatticeProtocolVersion.v1, + type: LatticeMsgType.secure, + id: msgId, + len: payloadLen, + } + const req: LatticeSecureRequest = { + header, + payload, + } - // Now serialize the whole message - // Header | requestType | payloadData | checksum - const msg = Buffer.alloc(msgSz); - let off = 0; - // Header - msg.writeUInt8(req.header.version, off); - off += 1; - msg.writeUInt8(req.header.type, off); - off += 1; - req.header.id.copy(msg, off); - off += req.header.id.length; - msg.writeUInt16BE(req.header.len, off); - off += 2; - // Payload - msg.writeUInt8(req.payload.requestType, off); - off += 1; - req.payload.data.copy(msg, off); - off += req.payload.data.length; - // Checksum - msg.writeUInt32BE(checksum(msg.slice(0, off)), off); - off += 4; - if (off !== msgSz) { - throw new Error('Failed to build request message'); - } + // Now serialize the whole message + // Header | requestType | payloadData | checksum + const msg = Buffer.alloc(msgSz) + let off = 0 + // Header + msg.writeUInt8(req.header.version, off) + off += 1 + msg.writeUInt8(req.header.type, off) + off += 1 + req.header.id.copy(msg, off) + off += req.header.id.length + msg.writeUInt16BE(req.header.len, off) + off += 2 + // Payload + msg.writeUInt8(req.payload.requestType, off) + off += 1 + req.payload.data.copy(msg, off) + off += req.payload.data.length + // Checksum + msg.writeUInt32BE(checksum(msg.slice(0, off)), off) + off += 4 + if (off !== msgSz) { + throw new Error('Failed to build request message') + } - // We have our serialized secure message! - return msg; + // We have our serialized secure message! + return msg } /** @@ -247,12 +205,10 @@ function serializeSecureRequestMsg( * Serialize payload data for a Lattice secure request: connect * @return {Buffer} - 1700 bytes, of which only 65 are used */ -function serializeSecureRequestConnectPayloadData( - payloadData: LatticeSecureConnectRequestPayloadData, -): Buffer { - const serPayloadData = Buffer.alloc(szs.data.request.connect); - payloadData.pubkey.copy(serPayloadData, 0); - return serPayloadData; +function serializeSecureRequestConnectPayloadData(payloadData: LatticeSecureConnectRequestPayloadData): Buffer { + const serPayloadData = Buffer.alloc(szs.data.request.connect) + payloadData.pubkey.copy(serPayloadData, 0) + return serPayloadData } /** @@ -262,57 +218,52 @@ function serializeSecureRequestConnectPayloadData( * @return {Buffer} - 1700 bytes, all of which should be used */ function serializeSecureRequestEncryptedPayloadData({ - data, - requestType, - ephemeralPub, - sharedSecret, + data, + requestType, + ephemeralPub, + sharedSecret, }: { - data: Buffer; - requestType: LatticeSecureEncryptedRequestType; - ephemeralPub: KeyPair; - sharedSecret: Buffer; + data: Buffer + requestType: LatticeSecureEncryptedRequestType + ephemeralPub: KeyPair + sharedSecret: Buffer }): Buffer { - // Sanity checks request size - if (data.length > szs.data.request.encrypted.encryptedData) { - throw new Error('Encrypted request data too large'); - } - // Make sure we have a shared secret. An error will be thrown - // if there is no ephemeral pub, indicating we need to reconnect. - validateEphemeralPub(ephemeralPub); + // Sanity checks request size + if (data.length > szs.data.request.encrypted.encryptedData) { + throw new Error('Encrypted request data too large') + } + // Make sure we have a shared secret. An error will be thrown + // if there is no ephemeral pub, indicating we need to reconnect. + validateEphemeralPub(ephemeralPub) - // Validate the request data size matches the desired request - const requestDataSize = szs.data.request.encrypted[requestType]; - if (data.length !== requestDataSize) { - throw new Error( - `Invalid request datasize (wanted ${requestDataSize}, got ${data.length})`, - ); - } + // Validate the request data size matches the desired request + const requestDataSize = szs.data.request.encrypted[requestType] + if (data.length !== requestDataSize) { + throw new Error(`Invalid request datasize (wanted ${requestDataSize}, got ${data.length})`) + } - // Build the pre-encrypted data payload, which variable sized and of form: - // encryptedRequestType | data | checksum - const preEncryptedData = Buffer.alloc(1 + requestDataSize); - preEncryptedData[0] = requestType; - data.copy(preEncryptedData, 1); - const preEncryptedDataChecksum = checksum(preEncryptedData); + // Build the pre-encrypted data payload, which variable sized and of form: + // encryptedRequestType | data | checksum + const preEncryptedData = Buffer.alloc(1 + requestDataSize) + preEncryptedData[0] = requestType + data.copy(preEncryptedData, 1) + const preEncryptedDataChecksum = checksum(preEncryptedData) - // Encrypt the data into a fixed size buffer. The buffer size should - // equal to the full message request less the 4-byte ephemeral id. - const _encryptedData = Buffer.alloc(szs.data.request.encrypted.encryptedData); - preEncryptedData.copy(_encryptedData, 0); - _encryptedData.writeUInt32LE( - preEncryptedDataChecksum, - preEncryptedData.length, - ); - const encryptedData = aes256_encrypt(_encryptedData, sharedSecret); + // Encrypt the data into a fixed size buffer. The buffer size should + // equal to the full message request less the 4-byte ephemeral id. + const _encryptedData = Buffer.alloc(szs.data.request.encrypted.encryptedData) + preEncryptedData.copy(_encryptedData, 0) + _encryptedData.writeUInt32LE(preEncryptedDataChecksum, preEncryptedData.length) + const encryptedData = aes256_encrypt(_encryptedData, sharedSecret) - // Calculate ephemeral ID - const ephemeralId = getEphemeralId(sharedSecret); + // Calculate ephemeral ID + const ephemeralId = getEphemeralId(sharedSecret) - // Now we will serialize the payload data. - const serPayloadData = Buffer.alloc(szs.payload.request.encrypted - 1); - serPayloadData.writeUInt32LE(ephemeralId); - encryptedData.copy(serPayloadData, 4); - return serPayloadData; + // Now we will serialize the payload data. + const serPayloadData = Buffer.alloc(szs.payload.request.encrypted - 1) + serPayloadData.writeUInt32LE(ephemeralId) + encryptedData.copy(serPayloadData, 4) + return serPayloadData } /** @@ -322,41 +273,40 @@ function serializeSecureRequestEncryptedPayloadData({ * @return {Buffer} Decrypted response data (excluding metadata) */ function decryptEncryptedLatticeResponseData({ - encPayloadData, - requestType, - sharedSecret, + encPayloadData, + requestType, + sharedSecret, }: { - encPayloadData: Buffer; - requestType: LatticeSecureEncryptedRequestType; - sharedSecret: Buffer; + encPayloadData: Buffer + requestType: LatticeSecureEncryptedRequestType + sharedSecret: Buffer }) { - // Decrypt data using the *current* shared secret - const decData = aes256_decrypt(encPayloadData, sharedSecret); + // Decrypt data using the *current* shared secret + const decData = aes256_decrypt(encPayloadData, sharedSecret) - // Bulid the object - const ephemeralPubSz = 65; // secp256r1 pubkey - const checksumOffset = - ephemeralPubSz + szs.data.response.encrypted[requestType]; - const respData: LatticeSecureDecryptedResponse = { - ephemeralPub: decData.slice(0, ephemeralPubSz), - data: decData.slice(ephemeralPubSz, checksumOffset), - checksum: decData.readUInt32BE(checksumOffset), - }; + // Bulid the object + const ephemeralPubSz = 65 // secp256r1 pubkey + const checksumOffset = ephemeralPubSz + szs.data.response.encrypted[requestType] + const respData: LatticeSecureDecryptedResponse = { + ephemeralPub: decData.slice(0, ephemeralPubSz), + data: decData.slice(ephemeralPubSz, checksumOffset), + checksum: decData.readUInt32BE(checksumOffset), + } - // Validate the checksum - const validChecksum = checksum(decData.slice(0, checksumOffset)); - if (respData.checksum !== validChecksum) { - throw new Error('Checksum mismatch in decrypted Lattice data'); - } + // Validate the checksum + const validChecksum = checksum(decData.slice(0, checksumOffset)) + if (respData.checksum !== validChecksum) { + throw new Error('Checksum mismatch in decrypted Lattice data') + } - // Validate the response data size - const validSz = szs.data.response.encrypted[requestType]; - if (respData.data.length !== validSz) { - throw new Error('Incorrect response data returned from Lattice'); - } + // Validate the response data size + const validSz = szs.data.response.encrypted[requestType] + if (respData.data.length !== validSz) { + throw new Error('Incorrect response data returned from Lattice') + } - const newEphemeralPub = getP256KeyPairFromPub(respData.ephemeralPub); + const newEphemeralPub = getP256KeyPairFromPub(respData.ephemeralPub) - // Returned the decrypted data - return { decryptedData: respData.data, newEphemeralPub }; + // Returned the decrypted data + return { decryptedData: respData.data, newEphemeralPub } } diff --git a/packages/sdk/src/schemas/index.ts b/packages/sdk/src/schemas/index.ts index e8f45da2..e97aa5c3 100644 --- a/packages/sdk/src/schemas/index.ts +++ b/packages/sdk/src/schemas/index.ts @@ -1 +1 @@ -export * from './transaction'; +export * from './transaction' diff --git a/packages/sdk/src/schemas/transaction.ts b/packages/sdk/src/schemas/transaction.ts index 63f06eed..f3be013d 100644 --- a/packages/sdk/src/schemas/transaction.ts +++ b/packages/sdk/src/schemas/transaction.ts @@ -1,171 +1,136 @@ -import { type Hex, getAddress, hexToBigInt, isAddress, isHex } from 'viem'; -import { z } from 'zod'; -import { TRANSACTION_TYPE } from '../types'; +import { type Hex, getAddress, hexToBigInt, isAddress, isHex } from 'viem' +import { z } from 'zod' +import { TRANSACTION_TYPE } from '../types' // Helper to handle various numeric inputs and convert them to BigInt. // It also validates that the value is not negative. -const toPositiveBigInt = z - .union([ - z.string().regex(/^(0x[0-9a-fA-F]+|[0-9]+)$/, 'Invalid number format'), - z.number(), - z.bigint(), - ]) - .transform((val, ctx) => { - try { - const b = - typeof val === 'string' && isHex(val) ? hexToBigInt(val) : BigInt(val); - if (b < 0n) { - ctx.addIssue({ - code: z.ZodIssueCode.custom, - message: 'Value must be non-negative', - }); - return z.NEVER; - } - return b; - } catch { - ctx.addIssue({ - code: z.ZodIssueCode.custom, - message: 'Invalid numeric value', - }); - return z.NEVER; - } - }); +const toPositiveBigInt = z.union([z.string().regex(/^(0x[0-9a-fA-F]+|[0-9]+)$/, 'Invalid number format'), z.number(), z.bigint()]).transform((val, ctx) => { + try { + const b = typeof val === 'string' && isHex(val) ? hexToBigInt(val) : BigInt(val) + if (b < 0n) { + ctx.addIssue({ + code: z.ZodIssueCode.custom, + message: 'Value must be non-negative', + }) + return z.NEVER + } + return b + } catch { + ctx.addIssue({ + code: z.ZodIssueCode.custom, + message: 'Invalid numeric value', + }) + return z.NEVER + } +}) // Schema for gas-related fields, ensuring they are non-negative BigInts. const GasValueSchema = toPositiveBigInt.refine((val) => val >= 0n, { - message: 'Gas values must be non-negative', -}); + message: 'Gas values must be non-negative', +}) // Schema for chainId, ensuring it's a positive integer. const ChainIdSchema = z - .union([z.string(), z.number()]) - .transform((val) => - typeof val === 'string' && isHex(val) - ? Number(hexToBigInt(val as Hex)) - : Number(val), - ) - .refine((val) => Number.isInteger(val) && val > 0, { - message: 'Chain ID must be a positive integer', - }); + .union([z.string(), z.number()]) + .transform((val) => (typeof val === 'string' && isHex(val) ? Number(hexToBigInt(val as Hex)) : Number(val))) + .refine((val) => Number.isInteger(val) && val > 0, { + message: 'Chain ID must be a positive integer', + }) // Schema for an Ethereum address, which validates and checksums it. const AddressSchema = z - .string() - .refine(isAddress, 'Invalid address') - .transform((addr) => getAddress(addr)); + .string() + .refine(isAddress, 'Invalid address') + .transform((addr) => getAddress(addr)) // Schema for hex data, ensuring it's a valid hex string. -const DataSchema = z - .string() - .refine(isHex, 'Data must be a valid hex string') - .default('0x'); - -const NonceSchema = z - .union([ - z.string().regex(/^(0x[0-9a-fA-F]+|[0-9]+)$/, 'Invalid nonce format'), - z.number().int().nonnegative(), - z.bigint(), - ]) - .transform((val, ctx) => { - try { - const bigVal = - typeof val === 'string' - ? isHex(val as Hex) - ? hexToBigInt(val as Hex) - : BigInt(val) - : typeof val === 'number' - ? BigInt(val) - : val; - - if (bigVal < 0n) { - ctx.addIssue({ - code: z.ZodIssueCode.custom, - message: 'Nonce must be non-negative', - }); - return z.NEVER; - } - - const maxSafe = BigInt(Number.MAX_SAFE_INTEGER); - if (bigVal > maxSafe) { - ctx.addIssue({ - code: z.ZodIssueCode.custom, - message: 'Nonce exceeds JavaScript safe integer range', - }); - return z.NEVER; - } - - return Number(bigVal); - } catch { - ctx.addIssue({ - code: z.ZodIssueCode.custom, - message: 'Invalid nonce value', - }); - return z.NEVER; - } - }); +const DataSchema = z.string().refine(isHex, 'Data must be a valid hex string').default('0x') + +const NonceSchema = z.union([z.string().regex(/^(0x[0-9a-fA-F]+|[0-9]+)$/, 'Invalid nonce format'), z.number().int().nonnegative(), z.bigint()]).transform((val, ctx) => { + try { + const bigVal = typeof val === 'string' ? (isHex(val as Hex) ? hexToBigInt(val as Hex) : BigInt(val)) : typeof val === 'number' ? BigInt(val) : val + + if (bigVal < 0n) { + ctx.addIssue({ + code: z.ZodIssueCode.custom, + message: 'Nonce must be non-negative', + }) + return z.NEVER + } + + const maxSafe = BigInt(Number.MAX_SAFE_INTEGER) + if (bigVal > maxSafe) { + ctx.addIssue({ + code: z.ZodIssueCode.custom, + message: 'Nonce exceeds JavaScript safe integer range', + }) + return z.NEVER + } + + return Number(bigVal) + } catch { + ctx.addIssue({ + code: z.ZodIssueCode.custom, + message: 'Invalid nonce value', + }) + return z.NEVER + } +}) // Schema for access list entries. const AccessListEntrySchema = z.object({ - address: AddressSchema, - storageKeys: z.array( - z.string().refine(isHex, 'Storage key must be a hex string'), - ), -}); + address: AddressSchema, + storageKeys: z.array(z.string().refine(isHex, 'Storage key must be a hex string')), +}) // Schema for EIP-7702 authorization entries. const AuthorizationSchema = z.object({ - chainId: z.number().int().positive(), - address: AddressSchema, - nonce: z.number().int().nonnegative(), - yParity: z.number().optional().default(0), - r: z.string().refine(isHex).optional(), - s: z.string().refine(isHex).optional(), -}); + chainId: z.number().int().positive(), + address: AddressSchema, + nonce: z.number().int().nonnegative(), + yParity: z.number().optional().default(0), + r: z.string().refine(isHex).optional(), + s: z.string().refine(isHex).optional(), +}) // Base schema for all transaction types. const BaseTxSchema = z.object({ - to: AddressSchema.optional(), - value: toPositiveBigInt.optional(), - data: DataSchema, - nonce: NonceSchema.optional(), - gas: GasValueSchema.optional(), - gasLimit: GasValueSchema.optional(), - chainId: ChainIdSchema.optional().default(1), - accessList: z.array(AccessListEntrySchema).optional(), -}); + to: AddressSchema.optional(), + value: toPositiveBigInt.optional(), + data: DataSchema, + nonce: NonceSchema.optional(), + gas: GasValueSchema.optional(), + gasLimit: GasValueSchema.optional(), + chainId: ChainIdSchema.optional().default(1), + accessList: z.array(AccessListEntrySchema).optional(), +}) // Schema for Legacy (Type 0) transactions. const LegacyTxSchema = BaseTxSchema.extend({ - type: z - .union([z.literal('legacy'), z.literal(TRANSACTION_TYPE.LEGACY)]) - .optional(), - gasPrice: GasValueSchema, -}); + type: z.union([z.literal('legacy'), z.literal(TRANSACTION_TYPE.LEGACY)]).optional(), + gasPrice: GasValueSchema, +}) // Schema for EIP-2930 (Type 1) transactions. const EIP2930TxSchema = BaseTxSchema.extend({ - type: z.union([z.literal('eip2930'), z.literal(TRANSACTION_TYPE.EIP2930)]), - gasPrice: GasValueSchema, -}); + type: z.union([z.literal('eip2930'), z.literal(TRANSACTION_TYPE.EIP2930)]), + gasPrice: GasValueSchema, +}) // Schema for EIP-1559 (Type 2) transactions. const EIP1559TxSchema = BaseTxSchema.extend({ - type: z.union([z.literal('eip1559'), z.literal(TRANSACTION_TYPE.EIP1559)]), - maxFeePerGas: GasValueSchema, - maxPriorityFeePerGas: GasValueSchema, -}); + type: z.union([z.literal('eip1559'), z.literal(TRANSACTION_TYPE.EIP1559)]), + maxFeePerGas: GasValueSchema, + maxPriorityFeePerGas: GasValueSchema, +}) // Schema for EIP-7702 (Type 4/5) transactions. const EIP7702TxSchema = BaseTxSchema.extend({ - type: z.union([ - z.literal('eip7702'), - z.literal(TRANSACTION_TYPE.EIP7702_AUTH), - z.literal(TRANSACTION_TYPE.EIP7702_AUTH_LIST), - ]), - maxFeePerGas: GasValueSchema, - maxPriorityFeePerGas: GasValueSchema, - authorizationList: z.array(AuthorizationSchema).min(1), -}); + type: z.union([z.literal('eip7702'), z.literal(TRANSACTION_TYPE.EIP7702_AUTH), z.literal(TRANSACTION_TYPE.EIP7702_AUTH_LIST)]), + maxFeePerGas: GasValueSchema, + maxPriorityFeePerGas: GasValueSchema, + authorizationList: z.array(AuthorizationSchema).min(1), +}) /** * A comprehensive zod schema that validates and normalizes a flexible transaction input. @@ -176,87 +141,80 @@ const EIP7702TxSchema = BaseTxSchema.extend({ * - Merging of `gas` and `gasLimit` fields. */ export const TransactionSchema = z - .any() - // Pre-process to check for circular references before zod touches it - .refine( - (val) => { - try { - JSON.stringify(val, (_, value) => - typeof value === 'bigint' ? value.toString() : value, - ); - return true; - } catch { - return false; - } - }, - { message: 'Circular reference detected in transaction object' }, - ) - .transform((tx) => { - // Prioritize gasLimit over gas - if (tx.gasLimit) { - tx.gas = tx.gasLimit; - } - - if (tx.data === null || tx.data === undefined || tx.data === '') { - tx.data = '0x'; - } - - // Normalize EIP-7702 `authorization` to `authorizationList` - if (tx.authorization) { - tx.authorizationList = [tx.authorization]; - } - return tx; - }) - .transform((tx: any) => { - // Type inference and validation logic - const hasAuthList = !!tx.authorizationList; - const hasMaxFee = !!tx.maxFeePerGas || !!tx.maxPriorityFeePerGas; - const hasAccessList = !!tx.accessList; - const hasGasPrice = !!tx.gasPrice; - - let type: 'eip7702' | 'eip1559' | 'eip2930' | 'legacy' = 'legacy'; - let schema: z.ZodTypeAny = LegacyTxSchema; - - if ( - tx.type === 'eip7702' || - tx.type === 4 || - tx.type === 5 || - hasAuthList - ) { - type = 'eip7702'; - schema = EIP7702TxSchema; - } else if (tx.type === 'eip1559' || tx.type === 2 || hasMaxFee) { - type = 'eip1559'; - schema = EIP1559TxSchema; - } else if (tx.type === 'eip2930' || tx.type === 1 || hasAccessList) { - type = 'eip2930'; - schema = EIP2930TxSchema; - } - - // For legacy, if gasPrice is missing, it's an invalid tx - if (type === 'legacy' && !hasGasPrice) { - throw new Error('Legacy transactions require a `gasPrice` field.'); - } - - const result = schema.parse(tx); - - // Post-process the successfully parsed data - const data: any = result; - data.type = type; - if (type === 'legacy' && data.gas === undefined) { - data.gas = 21000n; // Default gas for legacy transfers - } - - // Remove fields that are not part of the final type - if (type !== 'legacy' && type !== 'eip2930') data.gasPrice = undefined; - if (type !== 'eip1559' && type !== 'eip7702') { - data.maxFeePerGas = undefined; - data.maxPriorityFeePerGas = undefined; - } - data.gasLimit = undefined; - if (type !== 'eip7702') data.authorizationList = undefined; - - return data; - }); - -export type FlexibleTransaction = z.infer; + .any() + // Pre-process to check for circular references before zod touches it + .refine( + (val) => { + try { + JSON.stringify(val, (_, value) => (typeof value === 'bigint' ? value.toString() : value)) + return true + } catch { + return false + } + }, + { message: 'Circular reference detected in transaction object' }, + ) + .transform((tx) => { + // Prioritize gasLimit over gas + if (tx.gasLimit) { + tx.gas = tx.gasLimit + } + + if (tx.data === null || tx.data === undefined || tx.data === '') { + tx.data = '0x' + } + + // Normalize EIP-7702 `authorization` to `authorizationList` + if (tx.authorization) { + tx.authorizationList = [tx.authorization] + } + return tx + }) + .transform((tx: any) => { + // Type inference and validation logic + const hasAuthList = !!tx.authorizationList + const hasMaxFee = !!tx.maxFeePerGas || !!tx.maxPriorityFeePerGas + const hasAccessList = !!tx.accessList + const hasGasPrice = !!tx.gasPrice + + let type: 'eip7702' | 'eip1559' | 'eip2930' | 'legacy' = 'legacy' + let schema: z.ZodTypeAny = LegacyTxSchema + + if (tx.type === 'eip7702' || tx.type === 4 || tx.type === 5 || hasAuthList) { + type = 'eip7702' + schema = EIP7702TxSchema + } else if (tx.type === 'eip1559' || tx.type === 2 || hasMaxFee) { + type = 'eip1559' + schema = EIP1559TxSchema + } else if (tx.type === 'eip2930' || tx.type === 1 || hasAccessList) { + type = 'eip2930' + schema = EIP2930TxSchema + } + + // For legacy, if gasPrice is missing, it's an invalid tx + if (type === 'legacy' && !hasGasPrice) { + throw new Error('Legacy transactions require a `gasPrice` field.') + } + + const result = schema.parse(tx) + + // Post-process the successfully parsed data + const data: any = result + data.type = type + if (type === 'legacy' && data.gas === undefined) { + data.gas = 21000n // Default gas for legacy transfers + } + + // Remove fields that are not part of the final type + if (type !== 'legacy' && type !== 'eip2930') data.gasPrice = undefined + if (type !== 'eip1559' && type !== 'eip7702') { + data.maxFeePerGas = undefined + data.maxPriorityFeePerGas = undefined + } + data.gasLimit = undefined + if (type !== 'eip7702') data.authorizationList = undefined + + return data + }) + +export type FlexibleTransaction = z.infer diff --git a/packages/sdk/src/shared/errors.ts b/packages/sdk/src/shared/errors.ts index c3e828db..6b3159f3 100644 --- a/packages/sdk/src/shared/errors.ts +++ b/packages/sdk/src/shared/errors.ts @@ -1,35 +1,35 @@ -import { type LatticeResponseCode, ProtocolConstants } from '../protocol'; +import { type LatticeResponseCode, ProtocolConstants } from '../protocol' const buildLatticeResponseErrorMessage = ({ - responseCode, - errorMessage, + responseCode, + errorMessage, }: { - responseCode?: LatticeResponseCode; - errorMessage?: string; + responseCode?: LatticeResponseCode + errorMessage?: string }) => { - const msg: string[] = []; - if (responseCode) { - msg.push(`${ProtocolConstants.responseMsg[responseCode]}`); - } - if (errorMessage) { - msg.push('Error Message: '); - msg.push(errorMessage); - } - return msg.join('\n'); -}; + const msg: string[] = [] + if (responseCode) { + msg.push(`${ProtocolConstants.responseMsg[responseCode]}`) + } + if (errorMessage) { + msg.push('Error Message: ') + msg.push(errorMessage) + } + return msg.join('\n') +} export class LatticeResponseError extends Error { - constructor( - public responseCode?: LatticeResponseCode, - public errorMessage?: string, - ) { - const message = buildLatticeResponseErrorMessage({ - responseCode, - errorMessage, - }); - super(message); - this.name = 'LatticeResponseError'; - this.responseCode = responseCode; - this.errorMessage = errorMessage; - } + constructor( + public responseCode?: LatticeResponseCode, + public errorMessage?: string, + ) { + const message = buildLatticeResponseErrorMessage({ + responseCode, + errorMessage, + }) + super(message) + this.name = 'LatticeResponseError' + this.responseCode = responseCode + this.errorMessage = errorMessage + } } diff --git a/packages/sdk/src/shared/functions.ts b/packages/sdk/src/shared/functions.ts index c1cf3ee4..518e057f 100644 --- a/packages/sdk/src/shared/functions.ts +++ b/packages/sdk/src/shared/functions.ts @@ -1,129 +1,112 @@ -import { Hash } from 'ox'; -import type { Client } from '..'; -import bitcoin from '../bitcoin'; -import { EXTERNAL } from '../constants'; -import ethereum from '../ethereum'; -import { buildGenericSigningMsgRequest } from '../genericSigning'; -import type { Currency, FirmwareConstants, RequestParams } from '../types'; -import { fetchWithTimeout, parseLattice1Response } from '../util'; -import { LatticeResponseError } from './errors'; -import { - isDeviceBusy, - isInvalidEphemeralId, - isWrongWallet, - shouldUseEVMLegacyConverter, -} from './predicates'; -import { validateRequestError } from './validators'; +import { Hash } from 'ox' +import type { Client } from '..' +import bitcoin from '../bitcoin' +import { EXTERNAL } from '../constants' +import ethereum from '../ethereum' +import { buildGenericSigningMsgRequest } from '../genericSigning' +import type { Currency, FirmwareConstants, RequestParams } from '../types' +import { fetchWithTimeout, parseLattice1Response } from '../util' +import { LatticeResponseError } from './errors' +import { isDeviceBusy, isInvalidEphemeralId, isWrongWallet, shouldUseEVMLegacyConverter } from './predicates' +import { validateRequestError } from './validators' export const buildTransaction = ({ - data, - currency, - fwConstants, + data, + currency, + fwConstants, }: { - data: any; - currency?: Currency; - fwConstants: FirmwareConstants; + data: any + currency?: Currency + fwConstants: FirmwareConstants }) => { - // All transaction requests must be put into the same sized buffer. This comes from - // sizeof(GpTransactionRequest_t), but note we remove the 2-byte schemaId since it is not - // returned from our resolver. Note that different firmware versions may have different data - // sizes. + // All transaction requests must be put into the same sized buffer. This comes from + // sizeof(GpTransactionRequest_t), but note we remove the 2-byte schemaId since it is not + // returned from our resolver. Note that different firmware versions may have different data + // sizes. - // TEMPORARY BRIDGE -- DEPRECATE ME In v0.15.0 Lattice firmware removed the legacy ETH - // signing path, so we need to convert such requests to general signing requests using the - // EVM decoder. NOTE: Not every request can be converted, so users should switch to using - // general signing requests for newer firmware versions. EIP1559 and EIP155 legacy - // requests will convert, but others may not. - if (currency === 'ETH' && shouldUseEVMLegacyConverter(fwConstants)) { - console.log( - 'Using the legacy ETH signing path. This will soon be deprecated. ' + - 'Please switch to general signing request.', - ); - let payload; - try { - payload = ethereum.convertEthereumTransactionToGenericRequest(data); - } catch (err) { - console.error('Failed to convert legacy Ethereum transaction:', err); - throw new Error( - 'Could not convert legacy request. Please switch to a general signing ' + - 'request. See gridplus-sdk docs for more information.', - ); - } - data = { - fwConstants, - encodingType: EXTERNAL.SIGNING.ENCODINGS.EVM, - curveType: EXTERNAL.SIGNING.CURVES.SECP256K1, - hashType: EXTERNAL.SIGNING.HASHES.KECCAK256, - signerPath: data.signerPath, - payload, - }; - return { - requestData: buildGenericSigningMsgRequest({ ...data, fwConstants }), - isGeneric: true, - }; - } else if (currency === 'ETH') { - // Legacy signing pathway -- should deprecate in the future - return { - requestData: ethereum.buildEthereumTxRequest({ ...data, fwConstants }), - isGeneric: false, - }; - } else if (currency === 'ETH_MSG') { - return { - requestData: ethereum.buildEthereumMsgRequest({ ...data, fwConstants }), - isGeneric: false, - }; - } else if (currency === 'BTC') { - return { - requestData: bitcoin.buildBitcoinTxRequest({ ...data, fwConstants }), - isGeneric: false, - }; - } - return { - requestData: buildGenericSigningMsgRequest({ ...data, fwConstants }), - isGeneric: true, - }; -}; + // TEMPORARY BRIDGE -- DEPRECATE ME In v0.15.0 Lattice firmware removed the legacy ETH + // signing path, so we need to convert such requests to general signing requests using the + // EVM decoder. NOTE: Not every request can be converted, so users should switch to using + // general signing requests for newer firmware versions. EIP1559 and EIP155 legacy + // requests will convert, but others may not. + if (currency === 'ETH' && shouldUseEVMLegacyConverter(fwConstants)) { + console.log('Using the legacy ETH signing path. This will soon be deprecated. ' + 'Please switch to general signing request.') + let payload: Buffer | undefined + try { + payload = ethereum.convertEthereumTransactionToGenericRequest(data) + } catch (err) { + console.error('Failed to convert legacy Ethereum transaction:', err) + throw new Error('Could not convert legacy request. Please switch to a general signing ' + 'request. See gridplus-sdk docs for more information.') + } + data = { + fwConstants, + encodingType: EXTERNAL.SIGNING.ENCODINGS.EVM, + curveType: EXTERNAL.SIGNING.CURVES.SECP256K1, + hashType: EXTERNAL.SIGNING.HASHES.KECCAK256, + signerPath: data.signerPath, + payload, + } + return { + requestData: buildGenericSigningMsgRequest({ ...data, fwConstants }), + isGeneric: true, + } + } else if (currency === 'ETH') { + // Legacy signing pathway -- should deprecate in the future + return { + requestData: ethereum.buildEthereumTxRequest({ ...data, fwConstants }), + isGeneric: false, + } + } else if (currency === 'ETH_MSG') { + return { + requestData: ethereum.buildEthereumMsgRequest({ ...data, fwConstants }), + isGeneric: false, + } + } else if (currency === 'BTC') { + return { + requestData: bitcoin.buildBitcoinTxRequest({ ...data, fwConstants }), + isGeneric: false, + } + } + return { + requestData: buildGenericSigningMsgRequest({ ...data, fwConstants }), + isGeneric: true, + } +} -export const request = async ({ - url, - payload, - timeout = 60000, -}: RequestParams) => { - return fetchWithTimeout(url, { - method: 'POST', - body: JSON.stringify({ data: payload }), - headers: { - 'Content-Type': 'application/json', - }, - timeout, - }) - .catch(validateRequestError) - .then((res) => res.json()) - .then((body) => { - // Handle formatting or generic HTTP errors - if (!body || !body.message) { - throw new Error('Invalid response'); - } else if (body.status !== 200) { - throw new Error(`Error code ${body.status}: ${body.message}`); - } +export const request = async ({ url, payload, timeout = 60000 }: RequestParams) => { + return fetchWithTimeout(url, { + method: 'POST', + body: JSON.stringify({ data: payload }), + headers: { + 'Content-Type': 'application/json', + }, + timeout, + }) + .catch(validateRequestError) + .then((res) => res.json()) + .then((body) => { + // Handle formatting or generic HTTP errors + if (!body || !body.message) { + throw new Error('Invalid response') + } else if (body.status !== 200) { + throw new Error(`Error code ${body.status}: ${body.message}`) + } - const { data, errorMessage, responseCode } = parseLattice1Response( - body.message, - ); + const { data, errorMessage, responseCode } = parseLattice1Response(body.message) - if (errorMessage || responseCode) { - throw new LatticeResponseError(responseCode, errorMessage); - } + if (errorMessage || responseCode) { + throw new LatticeResponseError(responseCode, errorMessage) + } - return data; - }); -}; + return data + }) +} /** * `sleep()` returns a Promise that resolves after a given number of milliseconds. */ function sleep(ms: number): Promise { - return new Promise((resolve) => setTimeout(resolve, ms)); + return new Promise((resolve) => setTimeout(resolve, ms)) } /** @@ -135,14 +118,14 @@ function sleep(ms: number): Promise { * @returns a {@link retryWrapper} function for handing retry logic */ export const buildRetryWrapper = (client: Client, retries: number) => { - return (fn, params?) => - retryWrapper({ - fn, - params: { ...params, client }, - retries, - client, - }); -}; + return (fn, params?) => + retryWrapper({ + fn, + params: { ...params, client }, + retries, + client, + }) +} /** * Retries a function call if the error message or response code is present and the number of @@ -154,48 +137,45 @@ export const buildRetryWrapper = (client: Client, retries: number) => { * @param client - The {@link Client} to use for side-effects */ export const retryWrapper = async ({ - fn, - params, - retries, - client, + fn, + params, + retries, + client, }: { - fn: (...args: any[]) => Promise; - params: any; - retries: number; - client: any; + fn: (...args: any[]) => Promise + params: any + retries: number + client: any }) => { - return fn({ ...params }).catch(async (err: Error) => { - if (err instanceof LatticeResponseError) { - /** `string` returned from the Lattice if there's an error */ - const errorMessage = err.errorMessage; - /** `number` returned from the Lattice if there's an error */ - const responseCode = err.responseCode; + return fn({ ...params }).catch(async (err: Error) => { + if (err instanceof LatticeResponseError) { + /** `string` returned from the Lattice if there's an error */ + const errorMessage = err.errorMessage + /** `number` returned from the Lattice if there's an error */ + const responseCode = err.responseCode - if ((errorMessage || responseCode) && retries) { - if (isDeviceBusy(responseCode)) { - await sleep(3000); - } else if ( - isWrongWallet(responseCode) && - !client.skipRetryOnWrongWallet - ) { - await client.fetchActiveWallet(); - } else if (isInvalidEphemeralId(responseCode)) { - await client.connect(client.deviceId); - } else { - throw err; - } + if ((errorMessage || responseCode) && retries) { + if (isDeviceBusy(responseCode)) { + await sleep(3000) + } else if (isWrongWallet(responseCode) && !client.skipRetryOnWrongWallet) { + await client.fetchActiveWallet() + } else if (isInvalidEphemeralId(responseCode)) { + await client.connect(client.deviceId) + } else { + throw err + } - return retryWrapper({ - fn, - params, - retries: retries - 1, - client, - }); - } - } - throw err; - }); -}; + return retryWrapper({ + fn, + params, + retries: retries - 1, + client, + }) + } + } + throw err + }) +} /** * Get the ephemeral id, which is the first 4 bytes of the shared secret generated from the local @@ -204,7 +184,7 @@ export const retryWrapper = async ({ * @returns Buffer */ export const getEphemeralId = (sharedSecret: Buffer) => { - // EphemId is the first 4 bytes of the hash of the shared secret - const hash = Buffer.from(Hash.sha256(sharedSecret)); - return Number.parseInt(hash.slice(0, 4).toString('hex'), 16); -}; + // EphemId is the first 4 bytes of the hash of the shared secret + const hash = Buffer.from(Hash.sha256(sharedSecret)) + return Number.parseInt(hash.slice(0, 4).toString('hex'), 16) +} diff --git a/packages/sdk/src/shared/predicates.ts b/packages/sdk/src/shared/predicates.ts index 25477720..1558d9e7 100644 --- a/packages/sdk/src/shared/predicates.ts +++ b/packages/sdk/src/shared/predicates.ts @@ -1,19 +1,13 @@ -import { LatticeResponseCode } from '../protocol'; -import type { FirmwareConstants, FirmwareVersion } from '../types'; -import { isFWSupported } from './utilities'; +import { LatticeResponseCode } from '../protocol' +import type { FirmwareConstants, FirmwareVersion } from '../types' +import { isFWSupported } from './utilities' -export const isDeviceBusy = (responseCode: number) => - responseCode === LatticeResponseCode.deviceBusy || - responseCode === LatticeResponseCode.gceTimeout; +export const isDeviceBusy = (responseCode: number) => responseCode === LatticeResponseCode.deviceBusy || responseCode === LatticeResponseCode.gceTimeout -export const isWrongWallet = (responseCode: number) => - responseCode === LatticeResponseCode.wrongWallet; +export const isWrongWallet = (responseCode: number) => responseCode === LatticeResponseCode.wrongWallet -export const isInvalidEphemeralId = (responseCode: number) => - responseCode === LatticeResponseCode.invalidEphemId; +export const isInvalidEphemeralId = (responseCode: number) => responseCode === LatticeResponseCode.invalidEphemId -export const doesFetchWalletsOnLoad = (fwVersion: FirmwareVersion) => - isFWSupported(fwVersion, { major: 0, minor: 14, fix: 1 }); +export const doesFetchWalletsOnLoad = (fwVersion: FirmwareVersion) => isFWSupported(fwVersion, { major: 0, minor: 14, fix: 1 }) -export const shouldUseEVMLegacyConverter = (fwConstants: FirmwareConstants) => - fwConstants.genericSigning?.encodingTypes?.EVM; +export const shouldUseEVMLegacyConverter = (fwConstants: FirmwareConstants) => fwConstants.genericSigning?.encodingTypes?.EVM diff --git a/packages/sdk/src/shared/utilities.ts b/packages/sdk/src/shared/utilities.ts index 08367194..fb01e938 100644 --- a/packages/sdk/src/shared/utilities.ts +++ b/packages/sdk/src/shared/utilities.ts @@ -1,5 +1,5 @@ -import { HARDENED_OFFSET } from '../constants'; -import type { ActiveWallets, FirmwareVersion, KeyPair } from '../types'; +import { HARDENED_OFFSET } from '../constants' +import type { ActiveWallets, FirmwareVersion, KeyPair } from '../types' /** * Get 64 bytes representing the public key This is the uncompressed key without the leading 04 @@ -9,19 +9,19 @@ import type { ActiveWallets, FirmwareVersion, KeyPair } from '../types'; * @returns A Buffer containing the public key. */ export const getPubKeyBytes = (key: KeyPair, LE = false) => { - const k = key.getPublic(); - const p = k.encode('hex', false); - const pb = Buffer.from(p, 'hex'); - if (LE === true) { - // Need to flip X and Y components to little endian - const x = pb.slice(1, 33).reverse(); - const y = pb.slice(33, 65).reverse(); - // @ts-expect-error - TODO: Find out why Buffer won't accept pb[0] - return Buffer.concat([pb[0], x, y]); - } else { - return pb; - } -}; + const k = key.getPublic() + const p = k.encode('hex', false) + const pb = Buffer.from(p, 'hex') + if (LE === true) { + // Need to flip X and Y components to little endian + const x = pb.slice(1, 33).reverse() + const y = pb.slice(33, 65).reverse() + // @ts-expect-error - TODO: Find out why Buffer won't accept pb[0] + return Buffer.concat([pb[0], x, y]) + } else { + return pb + } +} /** * Get the shared secret, derived via ECDH from the local private key and the ephemeral public key @@ -29,84 +29,77 @@ export const getPubKeyBytes = (key: KeyPair, LE = false) => { * @returns Buffer */ export const getSharedSecret = (key: KeyPair, ephemeralPub: KeyPair) => { - // Once every ~256 attempts, we will get a key that starts with a `00` byte, which can lead to - // problems initializing AES if we don't force a 32 byte BE buffer. - return Buffer.from(key.derive(ephemeralPub.getPublic()).toArray('be', 32)); -}; + // Once every ~256 attempts, we will get a key that starts with a `00` byte, which can lead to + // problems initializing AES if we don't force a 32 byte BE buffer. + return Buffer.from(key.derive(ephemeralPub.getPublic()).toArray('be', 32)) +} // Given a set of wallet data, which contains two wallet descriptors, parse the data and save it // to memory export const parseWallets = (walletData: any): ActiveWallets => { - // Read the external wallet data first. If it is non-null, the external wallet will be the - // active wallet of the device and we should save it. If the external wallet is blank, it means - // there is no card present and we should save and use the interal wallet. If both wallets are - // empty, it means the device still needs to be set up. - const walletDescriptorLen = 71; - // Internal first - let off = 0; - const activeWallets: ActiveWallets = { - internal: { - uid: undefined, - capabilities: undefined, - name: undefined, - external: false, - }, - external: { - uid: undefined, - capabilities: undefined, - name: undefined, - external: true, - }, - }; - activeWallets.internal.uid = walletData.slice(off, off + 32); - // NOTE: `capabilities` and `name` were deprecated in Lattice firmware. - // They never provided any real information, but have been archived here - // since the response size has been preserved and we may bring them back - // in a different form. - // activeWallets.internal.capabilities = walletData.readUInt32BE(off + 32); - // activeWallets.internal.name = walletData.slice( - // off + 36, - // off + walletDescriptorLen, - // ); - // Offset the first item - off += walletDescriptorLen; - // External - activeWallets.external.uid = walletData.slice(off, off + 32); - // activeWallets.external.capabilities = walletData.readUInt32BE(off + 32); - // activeWallets.external.name = walletData.slice( - // off + 36, - // off + walletDescriptorLen, - // ); + // Read the external wallet data first. If it is non-null, the external wallet will be the + // active wallet of the device and we should save it. If the external wallet is blank, it means + // there is no card present and we should save and use the interal wallet. If both wallets are + // empty, it means the device still needs to be set up. + const walletDescriptorLen = 71 + // Internal first + let off = 0 + const activeWallets: ActiveWallets = { + internal: { + uid: undefined, + capabilities: undefined, + name: undefined, + external: false, + }, + external: { + uid: undefined, + capabilities: undefined, + name: undefined, + external: true, + }, + } + activeWallets.internal.uid = walletData.slice(off, off + 32) + // NOTE: `capabilities` and `name` were deprecated in Lattice firmware. + // They never provided any real information, but have been archived here + // since the response size has been preserved and we may bring them back + // in a different form. + // activeWallets.internal.capabilities = walletData.readUInt32BE(off + 32); + // activeWallets.internal.name = walletData.slice( + // off + 36, + // off + walletDescriptorLen, + // ); + // Offset the first item + off += walletDescriptorLen + // External + activeWallets.external.uid = walletData.slice(off, off + 32) + // activeWallets.external.capabilities = walletData.readUInt32BE(off + 32); + // activeWallets.external.name = walletData.slice( + // off + 36, + // off + walletDescriptorLen, + // ); - return activeWallets; -}; + return activeWallets +} // Determine if a provided firmware version matches or exceeds the current firmware version -export const isFWSupported = ( - fwVersion: FirmwareVersion, - versionSupported: FirmwareVersion, -): boolean => { - const { major, minor, fix } = fwVersion; - const { major: _major, minor: _minor, fix: _fix } = versionSupported; - return ( - major > _major || - (major >= _major && minor > _minor) || - (major >= _major && minor >= _minor && fix >= _fix) - ); -}; +export const isFWSupported = (fwVersion: FirmwareVersion, versionSupported: FirmwareVersion): boolean => { + const { major, minor, fix } = fwVersion + const { major: _major, minor: _minor, fix: _fix } = versionSupported + return major > _major || (major >= _major && minor > _minor) || (major >= _major && minor >= _minor && fix >= _fix) +} /** * Convert a set of BIP39 path indices to a string * @param path - Set of indices */ export const getPathStr = (path) => { - let pathStr = 'm'; - path.forEach((idx) => { - if (idx >= HARDENED_OFFSET) { - pathStr += `/${idx - HARDENED_OFFSET}'`; - } else { - pathStr += `/${idx}`; - } - }); - return pathStr; -}; + let pathStr = 'm' + path.forEach((idx) => { + if (idx >= HARDENED_OFFSET) { + pathStr += `/${idx - HARDENED_OFFSET}'` + } else { + pathStr += `/${idx}` + } + }) + return pathStr +} diff --git a/packages/sdk/src/shared/validators.ts b/packages/sdk/src/shared/validators.ts index 5b5a9da9..541ecbf5 100644 --- a/packages/sdk/src/shared/validators.ts +++ b/packages/sdk/src/shared/validators.ts @@ -1,244 +1,199 @@ -import type { UInt4 } from 'bitwise/types'; -import isEmpty from 'lodash/isEmpty.js'; -import type { Client } from '../client'; -import { ASCII_REGEX, EMPTY_WALLET_UID, MAX_ADDR } from '../constants'; -import type { - ActiveWallets, - FirmwareConstants, - FirmwareVersion, - KVRecords, - KeyPair, - LatticeError, - Wallet, -} from '../types'; -import { isUInt4 } from '../util'; +import type { UInt4 } from 'bitwise/types' +import isEmpty from 'lodash/isEmpty.js' +import type { Client } from '../client' +import { ASCII_REGEX, EMPTY_WALLET_UID, MAX_ADDR } from '../constants' +import type { ActiveWallets, FirmwareConstants, FirmwareVersion, KVRecords, KeyPair, LatticeError, Wallet } from '../types' +import { isUInt4 } from '../util' export const validateIsUInt4 = (n?: number) => { - if (typeof n !== 'number' || !isUInt4(n)) { - throw new Error('Must be an integer between 0 and 15 inclusive'); - } - return n as UInt4; -}; + if (typeof n !== 'number' || !isUInt4(n)) { + throw new Error('Must be an integer between 0 and 15 inclusive') + } + return n as UInt4 +} export const validateNAddresses = (n?: number) => { - if (!n) { - throw new Error('The number of addresses is required.'); - } - if (n > MAX_ADDR) { - throw new Error(`You may only request ${MAX_ADDR} addresses at once.`); - } - return n; -}; + if (!n) { + throw new Error('The number of addresses is required.') + } + if (n > MAX_ADDR) { + throw new Error(`You may only request ${MAX_ADDR} addresses at once.`) + } + return n +} export const validateStartPath = (startPath?: number[]) => { - if (!startPath) { - throw new Error('Start path is required'); - } - if (startPath.length < 1 || startPath.length > 5) - throw new Error('Path must include between 1 and 5 indices'); + if (!startPath) { + throw new Error('Start path is required') + } + if (startPath.length < 1 || startPath.length > 5) throw new Error('Path must include between 1 and 5 indices') - return startPath; -}; + return startPath +} export const validateDeviceId = (deviceId?: string) => { - if (!deviceId) { - throw new Error( - 'No device ID has been stored. Please connect with your device ID first.', - ); - } - return deviceId; -}; + if (!deviceId) { + throw new Error('No device ID has been stored. Please connect with your device ID first.') + } + return deviceId +} export const validateAppName = (name?: string) => { - if (!name) { - throw new Error('Name is required.'); - } - if (name.length < 5 || name.length > 24) { - throw new Error( - 'Invalid length for name provided. Must be 5-24 characters.', - ); - } - return name; -}; + if (!name) { + throw new Error('Name is required.') + } + if (name.length < 5 || name.length > 24) { + throw new Error('Invalid length for name provided. Must be 5-24 characters.') + } + return name +} export const validateUrl = (url?: string) => { - if (!url) { - throw new Error('URL does not exist. Please reconnect.'); - } - try { - new URL(url); - } catch (err) { - console.error('Invalid URL format:', err); - throw new Error('Invalid URL provided. Please use a valid URL.'); - } - return url; -}; + if (!url) { + throw new Error('URL does not exist. Please reconnect.') + } + try { + new URL(url) + } catch (err) { + console.error('Invalid URL format:', err) + throw new Error('Invalid URL provided. Please use a valid URL.') + } + return url +} export const validateBaseUrl = (baseUrl?: string) => { - if (!baseUrl) { - throw new Error('Base URL is required.'); - } - try { - new URL(baseUrl); - } catch (err) { - console.error('Invalid Base URL format:', err); - throw new Error('Invalid Base URL provided. Please use a valid URL.'); - } - return baseUrl; -}; + if (!baseUrl) { + throw new Error('Base URL is required.') + } + try { + new URL(baseUrl) + } catch (err) { + console.error('Invalid Base URL format:', err) + throw new Error('Invalid Base URL provided. Please use a valid URL.') + } + return baseUrl +} export const validateFwConstants = (fwConstants?: FirmwareConstants) => { - if (!fwConstants) { - throw new Error('Firmware constants do not exist. Please reconnect.'); - } - return fwConstants; -}; + if (!fwConstants) { + throw new Error('Firmware constants do not exist. Please reconnect.') + } + return fwConstants +} export const validateFwVersion = (fwVersion?: FirmwareVersion) => { - if (!fwVersion) { - throw new Error('Firmware version does not exist. Please reconnect.'); - } - if ( - typeof fwVersion.fix !== 'number' || - typeof fwVersion.minor !== 'number' || - typeof fwVersion.major !== 'number' - ) { - throw new Error('Firmware version improperly formatted. Please reconnect.'); - } - return fwVersion; -}; + if (!fwVersion) { + throw new Error('Firmware version does not exist. Please reconnect.') + } + if (typeof fwVersion.fix !== 'number' || typeof fwVersion.minor !== 'number' || typeof fwVersion.major !== 'number') { + throw new Error('Firmware version improperly formatted. Please reconnect.') + } + return fwVersion +} export const validateRequestError = (err: LatticeError) => { - const isTimeout = err.code === 'ECONNABORTED' && err.errno === 'ETIME'; - if (isTimeout) { - throw new Error( - 'Timeout waiting for device. Please ensure it is connected to the internet and try again in a minute.', - ); - } - throw new Error(`Failed to make request to device:\n${err.message}`); -}; + const isTimeout = err.code === 'ECONNABORTED' && err.errno === 'ETIME' + if (isTimeout) { + throw new Error('Timeout waiting for device. Please ensure it is connected to the internet and try again in a minute.') + } + throw new Error(`Failed to make request to device:\n${err.message}`) +} export const validateWallet = (wallet?: Wallet) => { - if (!wallet || wallet === null) { - throw new Error('No active wallet.'); - } - return wallet; -}; + if (!wallet || wallet === null) { + throw new Error('No active wallet.') + } + return wallet +} export const validateConnectedClient = (client: Client) => { - const appName = validateAppName(client.getAppName()); - const ephemeralPub = validateEphemeralPub(client.ephemeralPub); - const sharedSecret = validateSharedSecret(client.sharedSecret); - const url = validateUrl(client.url); - const fwConstants = validateFwConstants(client.getFwConstants()); - const fwVersion = validateFwVersion(client.getFwVersion()); - // @ts-expect-error - Key is private - const key = validateKey(client.key); - - return { - appName, - ephemeralPub, - sharedSecret, - url, - fwConstants, - fwVersion, - key, - }; -}; + const appName = validateAppName(client.getAppName()) + const ephemeralPub = validateEphemeralPub(client.ephemeralPub) + const sharedSecret = validateSharedSecret(client.sharedSecret) + const url = validateUrl(client.url) + const fwConstants = validateFwConstants(client.getFwConstants()) + const fwVersion = validateFwVersion(client.getFwVersion()) + // @ts-expect-error - Key is private + const key = validateKey(client.key) + + return { + appName, + ephemeralPub, + sharedSecret, + url, + fwConstants, + fwVersion, + key, + } +} export const validateEphemeralPub = (ephemeralPub?: KeyPair) => { - if (!ephemeralPub) { - throw new Error( - '`ephemeralPub` (ephemeral public key) is required. Please reconnect.', - ); - } - return ephemeralPub; -}; + if (!ephemeralPub) { + throw new Error('`ephemeralPub` (ephemeral public key) is required. Please reconnect.') + } + return ephemeralPub +} export const validateSharedSecret = (sharedSecret?: Buffer) => { - if (!sharedSecret) { - throw new Error('Shared secret required. Please reconnect.'); - } - return sharedSecret; -}; + if (!sharedSecret) { + throw new Error('Shared secret required. Please reconnect.') + } + return sharedSecret +} export const validateKey = (key?: KeyPair) => { - if (!key) { - throw new Error('Key is required. Please reconnect.'); - } - return key; -}; + if (!key) { + throw new Error('Key is required. Please reconnect.') + } + return key +} export const validateActiveWallets = (activeWallets?: ActiveWallets) => { - if ( - !activeWallets || - (activeWallets?.internal?.uid?.equals(EMPTY_WALLET_UID) && - activeWallets?.external?.uid?.equals(EMPTY_WALLET_UID)) - ) { - throw new Error('No active wallet.'); - } - return activeWallets; -}; - -export const validateKvRecords = ( - records?: KVRecords, - fwConstants?: FirmwareConstants, -) => { - if (!fwConstants || !fwConstants.kvActionsAllowed) { - throw new Error('Unsupported. Please update firmware.'); - } else if (typeof records !== 'object' || Object.keys(records).length < 1) { - throw new Error( - 'One or more key-value mapping must be provided in `records` param.', - ); - } else if (Object.keys(records).length > fwConstants.kvActionMaxNum) { - throw new Error( - `Too many keys provided. Please only provide up to ${fwConstants.kvActionMaxNum}.`, - ); - } - return records; -}; - -export const validateKvRecord = ( - { key, val }: KVRecords, - fwConstants: FirmwareConstants, -) => { - if ( - typeof key !== 'string' || - String(key).length > fwConstants.kvKeyMaxStrSz - ) { - throw new Error( - `Key ${key} too large. Must be <=${fwConstants.kvKeyMaxStrSz} characters.`, - ); - } else if ( - typeof val !== 'string' || - String(val).length > fwConstants.kvValMaxStrSz - ) { - throw new Error( - `Value ${val} too large. Must be <=${fwConstants.kvValMaxStrSz} characters.`, - ); - } else if (String(key).length === 0 || String(val).length === 0) { - throw new Error('Keys and values must be >0 characters.'); - } else if (!ASCII_REGEX.test(key) || !ASCII_REGEX.test(val)) { - throw new Error('Unicode characters are not supported.'); - } - return { key, val }; -}; + if (!activeWallets || (activeWallets?.internal?.uid?.equals(EMPTY_WALLET_UID) && activeWallets?.external?.uid?.equals(EMPTY_WALLET_UID))) { + throw new Error('No active wallet.') + } + return activeWallets +} + +export const validateKvRecords = (records?: KVRecords, fwConstants?: FirmwareConstants) => { + if (!fwConstants || !fwConstants.kvActionsAllowed) { + throw new Error('Unsupported. Please update firmware.') + } else if (typeof records !== 'object' || Object.keys(records).length < 1) { + throw new Error('One or more key-value mapping must be provided in `records` param.') + } else if (Object.keys(records).length > fwConstants.kvActionMaxNum) { + throw new Error(`Too many keys provided. Please only provide up to ${fwConstants.kvActionMaxNum}.`) + } + return records +} + +export const validateKvRecord = ({ key, val }: KVRecords, fwConstants: FirmwareConstants) => { + if (typeof key !== 'string' || String(key).length > fwConstants.kvKeyMaxStrSz) { + throw new Error(`Key ${key} too large. Must be <=${fwConstants.kvKeyMaxStrSz} characters.`) + } else if (typeof val !== 'string' || String(val).length > fwConstants.kvValMaxStrSz) { + throw new Error(`Value ${val} too large. Must be <=${fwConstants.kvValMaxStrSz} characters.`) + } else if (String(key).length === 0 || String(val).length === 0) { + throw new Error('Keys and values must be >0 characters.') + } else if (!ASCII_REGEX.test(key) || !ASCII_REGEX.test(val)) { + throw new Error('Unicode characters are not supported.') + } + return { key, val } +} export const isValidBlockExplorerResponse = (data: any) => { - try { - const result = JSON.parse(data.result); - return !isEmpty(result); - } catch (err) { - console.error('Invalid block explorer response:', err); - return false; - } -}; + try { + const result = JSON.parse(data.result) + return !isEmpty(result) + } catch (err) { + console.error('Invalid block explorer response:', err) + return false + } +} export const isValid4ByteResponse = (data: any) => { - try { - return !isEmpty(data.results); - } catch (err) { - console.error('Invalid 4byte response:', err); - return false; - } -}; + try { + return !isEmpty(data.results) + } catch (err) { + console.error('Invalid 4byte response:', err) + return false + } +} diff --git a/packages/sdk/src/types/addKvRecords.ts b/packages/sdk/src/types/addKvRecords.ts index 892f0de9..7abf275c 100644 --- a/packages/sdk/src/types/addKvRecords.ts +++ b/packages/sdk/src/types/addKvRecords.ts @@ -1,13 +1,12 @@ -import type { Client } from '../client'; -import type { KVRecords } from './shared'; +import type { Client } from '../client' +import type { KVRecords } from './shared' export interface AddKvRecordsRequestParams { - records: KVRecords; - type?: number; - caseSensitive?: boolean; + records: KVRecords + type?: number + caseSensitive?: boolean } -export interface AddKvRecordsRequestFunctionParams - extends AddKvRecordsRequestParams { - client: Client; +export interface AddKvRecordsRequestFunctionParams extends AddKvRecordsRequestParams { + client: Client } diff --git a/packages/sdk/src/types/client.ts b/packages/sdk/src/types/client.ts index 61554783..5601e361 100644 --- a/packages/sdk/src/types/client.ts +++ b/packages/sdk/src/types/client.ts @@ -1,33 +1,33 @@ -import type { Address, Hash } from 'viem'; -import type { CURRENCIES } from '../constants'; -import type { KeyPair } from './shared'; +import type { Address, Hash } from 'viem' +import type { CURRENCIES } from '../constants' +import type { KeyPair } from './shared' -export type Currency = keyof typeof CURRENCIES; +export type Currency = keyof typeof CURRENCIES -export type SigningPath = number[]; +export type SigningPath = number[] /** * Signature components as returned by the Lattice device. * Values can be Buffer (raw) or string (hex) depending on context. */ export interface LatticeSignature { - r: Buffer | string; - s: Buffer | string; - v?: Buffer | string | number | bigint; + r: Buffer | string + s: Buffer | string + v?: Buffer | string | number | bigint } export interface SignData { - tx?: string; - txHash?: Hash; - changeRecipient?: string; - sig?: LatticeSignature; - sigs?: Buffer[]; - signer?: Address; - pubkey?: Buffer; - err?: string; + tx?: string + txHash?: Hash + changeRecipient?: string + sig?: LatticeSignature + sigs?: Buffer[] + signer?: Address + pubkey?: Buffer + err?: string } -export type SigningRequestResponse = SignData | { pubkey: null; sig: null }; +export type SigningRequestResponse = SignData | { pubkey: null; sig: null } /** * @deprecated This type uses legacy field names and number types instead of viem-compatible bigint. @@ -35,49 +35,49 @@ export type SigningRequestResponse = SignData | { pubkey: null; sig: null }; * This will be removed in a future version. */ export interface TransactionPayload { - type: number; - gasPrice: number; - nonce: number; - gasLimit: number; - to: string; - value: number; - data: string; - maxFeePerGas: number; - maxPriorityFeePerGas: number; + type: number + gasPrice: number + nonce: number + gasLimit: number + to: string + value: number + data: string + maxFeePerGas: number + maxPriorityFeePerGas: number } export interface Wallet { - /** 32 byte id */ - uid: Buffer; - /** 20 char (max) string */ - name: Buffer | null; - /** 4 byte flag */ - capabilities: number; - /** External or internal wallet */ - external: boolean; + /** 32 byte id */ + uid: Buffer + /** 20 char (max) string */ + name: Buffer | null + /** 4 byte flag */ + capabilities: number + /** External or internal wallet */ + external: boolean } export interface ActiveWallets { - internal: Wallet; - external: Wallet; + internal: Wallet + external: Wallet } export interface RequestParams { - url: string; - payload: any; //TODO Fix this any - timeout?: number; - retries?: number; + url: string + payload: any //TODO Fix this any + timeout?: number + retries?: number } export interface ClientStateData { - activeWallets: ActiveWallets; - ephemeralPub: KeyPair; - fwVersion: Buffer; - deviceId: string; - name: string; - baseUrl: string; - privKey: Buffer; - key: Buffer; - retryCount: number; - timeout: number; + activeWallets: ActiveWallets + ephemeralPub: KeyPair + fwVersion: Buffer + deviceId: string + name: string + baseUrl: string + privKey: Buffer + key: Buffer + retryCount: number + timeout: number } diff --git a/packages/sdk/src/types/connect.ts b/packages/sdk/src/types/connect.ts index c07ad41e..7032b3d5 100644 --- a/packages/sdk/src/types/connect.ts +++ b/packages/sdk/src/types/connect.ts @@ -1,9 +1,9 @@ -import type { Client } from '../client'; +import type { Client } from '../client' export interface ConnectRequestParams { - id: string; + id: string } export interface ConnectRequestFunctionParams extends ConnectRequestParams { - client: Client; + client: Client } diff --git a/packages/sdk/src/types/declarations.d.ts b/packages/sdk/src/types/declarations.d.ts index cb9cc0aa..b18a9a9e 100644 --- a/packages/sdk/src/types/declarations.d.ts +++ b/packages/sdk/src/types/declarations.d.ts @@ -1,25 +1,25 @@ -declare module 'aes-js'; -declare module 'hash.js/lib/hash/sha'; +declare module 'aes-js' +declare module 'hash.js/lib/hash/sha' declare module 'hash.js/lib/hash/ripemd.js' { - export function ripemd160(): { - update: (data: any) => any; - digest: (encoding?: any) => any; - }; + export function ripemd160(): { + update: (data: any) => any + digest: (encoding?: any) => any + } } declare module 'lodash/inRange.js' { - const fn: (number: any, start?: any, end?: any) => boolean; - export default fn; + const fn: (number: any, start?: any, end?: any) => boolean + export default fn } declare module 'lodash/isInteger.js' { - const fn: (value: any) => boolean; - export default fn; + const fn: (value: any) => boolean + export default fn } declare module 'lodash/isEmpty.js' { - const fn: (value: any) => boolean; - export default fn; + const fn: (value: any) => boolean + export default fn } // Add more flexible typing to reduce strict type checking for complex modules declare global { - type NodeRequire = (id: string) => any; + type NodeRequire = (id: string) => any } diff --git a/packages/sdk/src/types/fetchActiveWallet.ts b/packages/sdk/src/types/fetchActiveWallet.ts index da6c7dae..142bad44 100644 --- a/packages/sdk/src/types/fetchActiveWallet.ts +++ b/packages/sdk/src/types/fetchActiveWallet.ts @@ -1,9 +1,9 @@ -import type { Client } from '../client'; +import type { Client } from '../client' export interface FetchActiveWalletRequestFunctionParams { - client: Client; + client: Client } export interface ValidatedFetchActiveWalletRequest { - sharedSecret: Buffer; + sharedSecret: Buffer } diff --git a/packages/sdk/src/types/fetchEncData.ts b/packages/sdk/src/types/fetchEncData.ts index f6d1d76b..785ede80 100644 --- a/packages/sdk/src/types/fetchEncData.ts +++ b/packages/sdk/src/types/fetchEncData.ts @@ -1,26 +1,26 @@ -import type { Client } from '../client'; +import type { Client } from '../client' export interface EIP2335KeyExportReq { - path: number[]; - c?: number; - kdf?: number; - walletUID?: Buffer; + path: number[] + c?: number + kdf?: number + walletUID?: Buffer } export interface FetchEncDataRequest { - schema: number; - params: EIP2335KeyExportReq; // NOTE: This is a union, but only one type of request exists currently + schema: number + params: EIP2335KeyExportReq // NOTE: This is a union, but only one type of request exists currently } export interface FetchEncDataRequestFunctionParams extends FetchEncDataRequest { - client: Client; + client: Client } export interface EIP2335KeyExportData { - iterations: number; - cipherText: Buffer; - salt: Buffer; - checksum: Buffer; - iv: Buffer; - pubkey: Buffer; + iterations: number + cipherText: Buffer + salt: Buffer + checksum: Buffer + iv: Buffer + pubkey: Buffer } diff --git a/packages/sdk/src/types/firmware.ts b/packages/sdk/src/types/firmware.ts index d3b6bab3..0a6e4734 100644 --- a/packages/sdk/src/types/firmware.ts +++ b/packages/sdk/src/types/firmware.ts @@ -1,65 +1,62 @@ -import type { EXTERNAL } from '../constants'; +import type { EXTERNAL } from '../constants' -export type FirmwareArr = [number, number, number]; +export type FirmwareArr = [number, number, number] export interface FirmwareVersion { - major: number; - minor: number; - fix: number; + major: number + minor: number + fix: number } export interface GenericSigningData { - calldataDecoding: { - reserved: number; - maxSz: number; - }; - baseReqSz: number; - // See `GENERIC_SIGNING_BASE_MSG_SZ` in firmware - baseDataSz: number; - hashTypes: typeof EXTERNAL.SIGNING.HASHES; - curveTypes: typeof EXTERNAL.SIGNING.CURVES; - encodingTypes: { - NONE: typeof EXTERNAL.SIGNING.ENCODINGS.NONE; - SOLANA: typeof EXTERNAL.SIGNING.ENCODINGS.SOLANA; - EVM?: typeof EXTERNAL.SIGNING.ENCODINGS.EVM; - }; + calldataDecoding: { + reserved: number + maxSz: number + } + baseReqSz: number + // See `GENERIC_SIGNING_BASE_MSG_SZ` in firmware + baseDataSz: number + hashTypes: typeof EXTERNAL.SIGNING.HASHES + curveTypes: typeof EXTERNAL.SIGNING.CURVES + encodingTypes: { + NONE: typeof EXTERNAL.SIGNING.ENCODINGS.NONE + SOLANA: typeof EXTERNAL.SIGNING.ENCODINGS.SOLANA + EVM?: typeof EXTERNAL.SIGNING.ENCODINGS.EVM + } } export interface FirmwareConstants { - abiCategorySz: number; - abiMaxRmv: number; - addrFlagsAllowed: boolean; - allowBtcLegacyAndSegwitAddrs: boolean; - allowedEthTxTypes: number[]; - contractDeployKey: string; - eip712MaxTypeParams: number; - eip712Supported: boolean; - ethMaxDataSz: number; - ethMaxGasPrice: number; - ethMaxMsgSz: number; - ethMsgPreHashAllowed: boolean; - extraDataFrameSz: number; - extraDataMaxFrames: number; - genericSigning: GenericSigningData; - getAddressFlags: [ - typeof EXTERNAL.GET_ADDR_FLAGS.ED25519_PUB, - typeof EXTERNAL.GET_ADDR_FLAGS.SECP256K1_PUB, - ]; - kvActionMaxNum: number; - kvActionsAllowed: boolean; - kvKeyMaxStrSz: number; - kvRemoveMaxNum: number; - kvValMaxStrSz: number; - maxDecoderBufSz: number; - personalSignHeaderSz: number; - prehashAllowed: boolean; - reqMaxDataSz: number; - varAddrPathSzAllowed: boolean; - flexibleAddrPaths?: boolean; + abiCategorySz: number + abiMaxRmv: number + addrFlagsAllowed: boolean + allowBtcLegacyAndSegwitAddrs: boolean + allowedEthTxTypes: number[] + contractDeployKey: string + eip712MaxTypeParams: number + eip712Supported: boolean + ethMaxDataSz: number + ethMaxGasPrice: number + ethMaxMsgSz: number + ethMsgPreHashAllowed: boolean + extraDataFrameSz: number + extraDataMaxFrames: number + genericSigning: GenericSigningData + getAddressFlags: [typeof EXTERNAL.GET_ADDR_FLAGS.ED25519_PUB, typeof EXTERNAL.GET_ADDR_FLAGS.SECP256K1_PUB] + kvActionMaxNum: number + kvActionsAllowed: boolean + kvKeyMaxStrSz: number + kvRemoveMaxNum: number + kvValMaxStrSz: number + maxDecoderBufSz: number + personalSignHeaderSz: number + prehashAllowed: boolean + reqMaxDataSz: number + varAddrPathSzAllowed: boolean + flexibleAddrPaths?: boolean } export interface LatticeError { - code: string; - errno: string; - message: string; + code: string + errno: string + message: string } diff --git a/packages/sdk/src/types/getAddresses.ts b/packages/sdk/src/types/getAddresses.ts index 7ec1bff5..03465753 100644 --- a/packages/sdk/src/types/getAddresses.ts +++ b/packages/sdk/src/types/getAddresses.ts @@ -1,13 +1,12 @@ -import type { Client } from '../client'; +import type { Client } from '../client' export interface GetAddressesRequestParams { - startPath: number[]; - n: number; - flag?: number; - iterIdx?: number; + startPath: number[] + n: number + flag?: number + iterIdx?: number } -export interface GetAddressesRequestFunctionParams - extends GetAddressesRequestParams { - client: Client; +export interface GetAddressesRequestFunctionParams extends GetAddressesRequestParams { + client: Client } diff --git a/packages/sdk/src/types/getKvRecords.ts b/packages/sdk/src/types/getKvRecords.ts index f4c1d0e0..512b8f28 100644 --- a/packages/sdk/src/types/getKvRecords.ts +++ b/packages/sdk/src/types/getKvRecords.ts @@ -1,26 +1,25 @@ -import type { Client } from '../client'; +import type { Client } from '../client' export interface GetKvRecordsRequestParams { - type?: number; - n?: number; - start?: number; + type?: number + n?: number + start?: number } -export interface GetKvRecordsRequestFunctionParams - extends GetKvRecordsRequestParams { - client: Client; +export interface GetKvRecordsRequestFunctionParams extends GetKvRecordsRequestParams { + client: Client } export type AddressTag = { - caseSensitive: boolean; - id: number; - key: string; - type: number; - val: string; -}; + caseSensitive: boolean + id: number + key: string + type: number + val: string +} export interface GetKvRecordsData { - records: AddressTag[]; - fetched: number; - total: number; + records: AddressTag[] + fetched: number + total: number } diff --git a/packages/sdk/src/types/index.ts b/packages/sdk/src/types/index.ts index b111da12..3260b316 100644 --- a/packages/sdk/src/types/index.ts +++ b/packages/sdk/src/types/index.ts @@ -1,99 +1,99 @@ // Re-export everything from client.ts -export * from './client'; +export * from './client' // Re-export everything from addKvRecords.ts -export * from './addKvRecords'; +export * from './addKvRecords' // Re-export everything from connect.ts -export * from './connect'; +export * from './connect' // Re-export everything from fetchActiveWallet.ts -export * from './fetchActiveWallet'; +export * from './fetchActiveWallet' // Re-export everything from fetchEncData.ts -export * from './fetchEncData'; +export * from './fetchEncData' // Re-export everything from firmware.ts -export * from './firmware'; +export * from './firmware' // Re-export everything from getAddresses.ts -export * from './getAddresses'; +export * from './getAddresses' // Re-export everything from getKvRecords.ts -export * from './getKvRecords'; +export * from './getKvRecords' // Re-export everything from messages.ts -export * from './messages'; +export * from './messages' // Re-export everything from pair.ts -export * from './pair'; +export * from './pair' // Re-export everything from removeKvRecords.ts -export * from './removeKvRecords'; +export * from './removeKvRecords' // Re-export everything from secureMessages.ts -export * from './secureMessages'; +export * from './secureMessages' // Re-export everything from shared.ts -export * from './shared'; +export * from './shared' // Re-export everything from sign.ts -export * from './sign'; +export * from './sign' // Re-export everything from utils.ts -export * from './utils'; +export * from './utils' // We don't need to export from vitest.d.ts as it's a declaration file for Vitest // Exports from client.ts export type { - Currency, - SigningPath, - SignData, - SigningRequestResponse, - TransactionPayload, - Wallet, - ActiveWallets, - RequestParams, - ClientStateData, -} from './client'; + Currency, + SigningPath, + SignData, + SigningRequestResponse, + TransactionPayload, + Wallet, + ActiveWallets, + RequestParams, + ClientStateData, +} from './client' // Exports from addKvRecords.ts export type { - AddKvRecordsRequestParams, - AddKvRecordsRequestFunctionParams, -} from './addKvRecords'; + AddKvRecordsRequestParams, + AddKvRecordsRequestFunctionParams, +} from './addKvRecords' // Exports from fetchEncData.ts export type { - EIP2335KeyExportReq, - FetchEncDataRequest, - FetchEncDataRequestFunctionParams, - EIP2335KeyExportData, -} from './fetchEncData'; + EIP2335KeyExportReq, + FetchEncDataRequest, + FetchEncDataRequestFunctionParams, + EIP2335KeyExportData, +} from './fetchEncData' // Exports from getKvRecords.ts export type { - GetKvRecordsRequestParams, - GetKvRecordsRequestFunctionParams, - AddressTag, - GetKvRecordsData, -} from './getKvRecords'; + GetKvRecordsRequestParams, + GetKvRecordsRequestFunctionParams, + AddressTag, + GetKvRecordsData, +} from './getKvRecords' // Exports from removeKvRecords.ts export type { - RemoveKvRecordsRequestParams, - RemoveKvRecordsRequestFunctionParams, -} from './removeKvRecords'; + RemoveKvRecordsRequestParams, + RemoveKvRecordsRequestFunctionParams, +} from './removeKvRecords' // Exports from shared.ts export type { - KVRecords, - EncrypterParams, - KeyPair, - WalletPath, - DecryptedResponse, -} from './shared'; + KVRecords, + EncrypterParams, + KeyPair, + WalletPath, + DecryptedResponse, +} from './shared' // Note: We don't export from vitest.d.ts as it's a declaration file for Vitest diff --git a/packages/sdk/src/types/messages.ts b/packages/sdk/src/types/messages.ts index 78a77821..4371d507 100644 --- a/packages/sdk/src/types/messages.ts +++ b/packages/sdk/src/types/messages.ts @@ -1,19 +1,19 @@ -import type { LatticeMsgType } from '../protocol'; +import type { LatticeMsgType } from '../protocol' export interface LatticeMessageHeader { - // Protocol version. Should always be 0x01 - // [uint8] - version: number; - // Protocol request type. Should always be 0x02 - // for "secure" message type. - // [uint8] - type: LatticeMsgType; - // Random message ID for internal tracking in firmware - // [4 bytes] - id: Buffer; - // Length of payload data being used - // For an encrypted request, this indicates the - // size of the non-zero decrypted data. - // [uint16] - len: number; + // Protocol version. Should always be 0x01 + // [uint8] + version: number + // Protocol request type. Should always be 0x02 + // for "secure" message type. + // [uint8] + type: LatticeMsgType + // Random message ID for internal tracking in firmware + // [4 bytes] + id: Buffer + // Length of payload data being used + // For an encrypted request, this indicates the + // size of the non-zero decrypted data. + // [uint16] + len: number } diff --git a/packages/sdk/src/types/pair.ts b/packages/sdk/src/types/pair.ts index 6fb54b83..d72b9391 100644 --- a/packages/sdk/src/types/pair.ts +++ b/packages/sdk/src/types/pair.ts @@ -1,6 +1,6 @@ -import type { Client } from '../client'; +import type { Client } from '../client' export interface PairRequestParams { - pairingSecret: string; - client: Client; + pairingSecret: string + client: Client } diff --git a/packages/sdk/src/types/removeKvRecords.ts b/packages/sdk/src/types/removeKvRecords.ts index a8cfc747..27aef49e 100644 --- a/packages/sdk/src/types/removeKvRecords.ts +++ b/packages/sdk/src/types/removeKvRecords.ts @@ -1,11 +1,10 @@ -import type { Client } from '../client'; +import type { Client } from '../client' export interface RemoveKvRecordsRequestParams { - type?: number; - ids?: string[]; + type?: number + ids?: string[] } -export interface RemoveKvRecordsRequestFunctionParams - extends RemoveKvRecordsRequestParams { - client: Client; +export interface RemoveKvRecordsRequestFunctionParams extends RemoveKvRecordsRequestParams { + client: Client } diff --git a/packages/sdk/src/types/secureMessages.ts b/packages/sdk/src/types/secureMessages.ts index ebbb9127..adc83e0e 100644 --- a/packages/sdk/src/types/secureMessages.ts +++ b/packages/sdk/src/types/secureMessages.ts @@ -1,59 +1,59 @@ -import type { LatticeResponseCode, LatticeSecureMsgType } from '../protocol'; -import type { LatticeMessageHeader } from './messages'; +import type { LatticeResponseCode, LatticeSecureMsgType } from '../protocol' +import type { LatticeMessageHeader } from './messages' export interface LatticeSecureRequest { - // Message header - header: LatticeMessageHeader; - // Request data - payload: LatticeSecureRequestPayload; + // Message header + header: LatticeMessageHeader + // Request data + payload: LatticeSecureRequestPayload } export interface LatticeSecureRequestPayload { - // Indicates whether this is a connect (0x01) or - // encrypted (0x02) secure request - // [uint8] - requestType: LatticeSecureMsgType; - // Request data - // [connect = 65 bytes, encrypted = 1732] - data: Buffer; + // Indicates whether this is a connect (0x01) or + // encrypted (0x02) secure request + // [uint8] + requestType: LatticeSecureMsgType + // Request data + // [connect = 65 bytes, encrypted = 1732] + data: Buffer } export interface LatticeSecureConnectResponsePayload { - // [214 bytes] - data: Buffer; + // [214 bytes] + data: Buffer } export interface LatticeSecureEncryptedResponsePayload { - // Error code - responseCode: LatticeResponseCode; - // Response data - // [3392 bytes] - data: Buffer; + // Error code + responseCode: LatticeResponseCode + // Response data + // [3392 bytes] + data: Buffer } export interface LatticeSecureConnectRequestPayloadData { - // Public key corresponding to the static Client keypair - // [65 bytes] - pubkey: Buffer; + // Public key corresponding to the static Client keypair + // [65 bytes] + pubkey: Buffer } export interface LatticeSecureEncryptedRequestPayloadData { - // SHA256(sharedSecret).slice(0, 4) - // [uint32] - ephemeralId: number; - // Encrypted data envelope - // [1728 bytes] - encryptedData: Buffer; + // SHA256(sharedSecret).slice(0, 4) + // [uint32] + ephemeralId: number + // Encrypted data envelope + // [1728 bytes] + encryptedData: Buffer } export interface LatticeSecureDecryptedResponse { - // ECDSA public key that should replace the client's ephemeral key - // [65 bytes] - ephemeralPub: Buffer; - // Decrypted response data - // [Variable size] - data: Buffer; - // Checksum on response data (ephemeralKey | data) - // [uint32] - checksum: number; + // ECDSA public key that should replace the client's ephemeral key + // [65 bytes] + ephemeralPub: Buffer + // Decrypted response data + // [Variable size] + data: Buffer + // Checksum on response data (ephemeralKey | data) + // [uint32] + checksum: number } diff --git a/packages/sdk/src/types/shared.ts b/packages/sdk/src/types/shared.ts index 9592cb4c..bc0ab9ce 100644 --- a/packages/sdk/src/types/shared.ts +++ b/packages/sdk/src/types/shared.ts @@ -1,19 +1,19 @@ -import type { ec } from 'elliptic'; +import type { ec } from 'elliptic' export interface KVRecords { - [key: string]: string; + [key: string]: string } export interface EncrypterParams { - payload: Buffer; - sharedSecret: Buffer; + payload: Buffer + sharedSecret: Buffer } -export type KeyPair = ec.KeyPair; +export type KeyPair = ec.KeyPair -export type WalletPath = [number, number, number, number, number]; +export type WalletPath = [number, number, number, number, number] export interface DecryptedResponse { - decryptedData: Buffer; - newEphemeralPub: KeyPair; + decryptedData: Buffer + newEphemeralPub: KeyPair } diff --git a/packages/sdk/src/types/sign.ts b/packages/sdk/src/types/sign.ts index f9ed2121..df5897d9 100644 --- a/packages/sdk/src/types/sign.ts +++ b/packages/sdk/src/types/sign.ts @@ -1,193 +1,161 @@ -import type { - AccessList, - Address, - Hex, - SignedAuthorization, - SignedAuthorizationList, - TypedData, - TypedDataDefinition, -} from 'viem'; -import type { Client } from '../client'; -import type { Currency, SigningPath, Wallet } from './client'; -import type { FirmwareConstants } from './firmware'; - -export type ETH_MESSAGE_PROTOCOLS = 'eip712' | 'signPersonal'; +import type { AccessList, Address, Hex, SignedAuthorization, SignedAuthorizationList, TypedData, TypedDataDefinition } from 'viem' +import type { Client } from '../client' +import type { Currency, SigningPath, Wallet } from './client' +import type { FirmwareConstants } from './firmware' + +export type ETH_MESSAGE_PROTOCOLS = 'eip712' | 'signPersonal' export const TRANSACTION_TYPE = { - LEGACY: 0, - EIP2930: 1, - EIP1559: 2, - EIP7702_AUTH: 4, - EIP7702_AUTH_LIST: 5, -} as const; + LEGACY: 0, + EIP2930: 1, + EIP1559: 2, + EIP7702_AUTH: 4, + EIP7702_AUTH_LIST: 5, +} as const // Base transaction request with common fields type BaseTransactionRequest = { - from?: Address; - to: Address; - value?: Hex | bigint; - data?: Hex; - chainId: number; - nonce: number; - gasLimit?: Hex | bigint; -}; + from?: Address + to: Address + value?: Hex | bigint + data?: Hex + chainId: number + nonce: number + gasLimit?: Hex | bigint +} // Legacy transaction request type LegacyTransactionRequest = BaseTransactionRequest & { - type: typeof TRANSACTION_TYPE.LEGACY; - gasPrice: Hex | bigint; -}; + type: typeof TRANSACTION_TYPE.LEGACY + gasPrice: Hex | bigint +} // EIP-2930 transaction request type EIP2930TransactionRequest = BaseTransactionRequest & { - type: typeof TRANSACTION_TYPE.EIP2930; - gasPrice: Hex | bigint; - accessList?: AccessList; -}; + type: typeof TRANSACTION_TYPE.EIP2930 + gasPrice: Hex | bigint + accessList?: AccessList +} // EIP-1559 transaction request type EIP1559TransactionRequest = BaseTransactionRequest & { - type: typeof TRANSACTION_TYPE.EIP1559; - maxFeePerGas: Hex | bigint; - maxPriorityFeePerGas: Hex | bigint; - accessList?: AccessList; -}; + type: typeof TRANSACTION_TYPE.EIP1559 + maxFeePerGas: Hex | bigint + maxPriorityFeePerGas: Hex | bigint + accessList?: AccessList +} // EIP-7702 single authorization transaction request (type 4) export type EIP7702AuthTransactionRequest = BaseTransactionRequest & { - type: typeof TRANSACTION_TYPE.EIP7702_AUTH; - maxFeePerGas: Hex | bigint; - maxPriorityFeePerGas: Hex | bigint; - accessList?: AccessList; - authorization: SignedAuthorization; -}; + type: typeof TRANSACTION_TYPE.EIP7702_AUTH + maxFeePerGas: Hex | bigint + maxPriorityFeePerGas: Hex | bigint + accessList?: AccessList + authorization: SignedAuthorization +} // EIP-7702 authorization list transaction request (type 5) export type EIP7702AuthListTransactionRequest = BaseTransactionRequest & { - type: typeof TRANSACTION_TYPE.EIP7702_AUTH_LIST; - maxFeePerGas: Hex | bigint; - maxPriorityFeePerGas: Hex | bigint; - accessList?: AccessList; - authorizationList: SignedAuthorizationList; -}; + type: typeof TRANSACTION_TYPE.EIP7702_AUTH_LIST + maxFeePerGas: Hex | bigint + maxPriorityFeePerGas: Hex | bigint + accessList?: AccessList + authorizationList: SignedAuthorizationList +} // Main discriminated union for transaction requests -export type TransactionRequest = - | LegacyTransactionRequest - | EIP2930TransactionRequest - | EIP1559TransactionRequest - | EIP7702AuthTransactionRequest - | EIP7702AuthListTransactionRequest; - -export interface SigningPayload< - TTypedData extends TypedData | Record = TypedData, -> { - signerPath: SigningPath; - payload: - | Uint8Array - | Uint8Array[] - | Buffer - | Buffer[] - | Hex - | EIP712MessagePayload; - curveType: number; - hashType: number; - encodingType?: number; - protocol?: ETH_MESSAGE_PROTOCOLS; - decoder?: Buffer; -} - -export interface SignRequestParams< - TTypedData extends TypedData | Record = TypedData, -> { - data: SigningPayload | BitcoinSignPayload; - currency?: Currency; - cachedData?: unknown; - nextCode?: Buffer; -} - -export interface SignRequestFunctionParams< - TTypedData extends TypedData | Record = TypedData, -> extends SignRequestParams { - client: Client; +export type TransactionRequest = LegacyTransactionRequest | EIP2930TransactionRequest | EIP1559TransactionRequest | EIP7702AuthTransactionRequest | EIP7702AuthListTransactionRequest + +export interface SigningPayload = TypedData> { + signerPath: SigningPath + payload: Uint8Array | Uint8Array[] | Buffer | Buffer[] | Hex | EIP712MessagePayload + curveType: number + hashType: number + encodingType?: number + protocol?: ETH_MESSAGE_PROTOCOLS + decoder?: Buffer +} + +export interface SignRequestParams = TypedData> { + data: SigningPayload | BitcoinSignPayload + currency?: Currency + cachedData?: unknown + nextCode?: Buffer +} + +export interface SignRequestFunctionParams = TypedData> extends SignRequestParams { + client: Client } export interface EncodeSignRequestParams { - fwConstants: FirmwareConstants; - wallet: Wallet; - requestData: unknown; - cachedData?: unknown; - nextCode?: Buffer; + fwConstants: FirmwareConstants + wallet: Wallet + requestData: unknown + cachedData?: unknown + nextCode?: Buffer } export interface SignRequest { - payload: Buffer; - schema: number; + payload: Buffer + schema: number } export interface EthSignRequest extends SignRequest { - curveType: number; - encodingType: number; - hashType: number; - omitPubkey: boolean; - origPayloadBuf: Buffer; - extraDataPayloads: Buffer[]; + curveType: number + encodingType: number + hashType: number + omitPubkey: boolean + origPayloadBuf: Buffer + extraDataPayloads: Buffer[] } export interface EthMsgSignRequest extends SignRequest { - input: { - signerPath: SigningPath; - payload: Buffer; - protocol: string; - fwConstants: FirmwareConstants; - }; + input: { + signerPath: SigningPath + payload: Buffer + protocol: string + fwConstants: FirmwareConstants + } } export interface BitcoinSignRequest extends SignRequest { - origData: { - prevOuts: PreviousOutput[]; - recipient: string; - value: number; - fee: number; - changePath: number[]; - fwConstants: FirmwareConstants; - }; - changeData?: { value: number }; + origData: { + prevOuts: PreviousOutput[] + recipient: string + value: number + fee: number + changePath: number[] + fwConstants: FirmwareConstants + } + changeData?: { value: number } } export type PreviousOutput = { - txHash: string; - value: number; - index: number; - signerPath: number[]; -}; + txHash: string + value: number + index: number + signerPath: number[] +} export type BitcoinSignPayload = { - prevOuts: PreviousOutput[]; - recipient: string; - value: number; - fee: number; - changePath: number[]; -}; + prevOuts: PreviousOutput[] + recipient: string + value: number + fee: number + changePath: number[] +} export interface DecodeSignResponseParams { - data: Buffer; - request: SignRequest; - isGeneric: boolean; - currency?: Currency; + data: Buffer + request: SignRequest + isGeneric: boolean + currency?: Currency } // Align EIP712MessagePayload with Viem's TypedDataDefinition -export interface EIP712MessagePayload< - TTypedData extends TypedData | Record = TypedData, - TPrimaryType extends keyof TTypedData | 'EIP712Domain' = keyof TTypedData, -> { - types: TTypedData; - domain: TTypedData extends TypedData - ? TypedDataDefinition['domain'] - : Record; - primaryType: TPrimaryType; - message: TTypedData extends TypedData - ? TypedDataDefinition['message'] - : Record; +export interface EIP712MessagePayload = TypedData, TPrimaryType extends keyof TTypedData | 'EIP712Domain' = keyof TTypedData> { + types: TTypedData + domain: TTypedData extends TypedData ? TypedDataDefinition['domain'] : Record + primaryType: TPrimaryType + message: TTypedData extends TypedData ? TypedDataDefinition['message'] : Record } diff --git a/packages/sdk/src/types/tiny-secp256k1.d.ts b/packages/sdk/src/types/tiny-secp256k1.d.ts index 6199738e..6e83e4b0 100644 --- a/packages/sdk/src/types/tiny-secp256k1.d.ts +++ b/packages/sdk/src/types/tiny-secp256k1.d.ts @@ -1 +1 @@ -declare module 'tiny-secp256k1'; +declare module 'tiny-secp256k1' diff --git a/packages/sdk/src/types/utils.ts b/packages/sdk/src/types/utils.ts index c82b10cc..bc5ad835 100644 --- a/packages/sdk/src/types/utils.ts +++ b/packages/sdk/src/types/utils.ts @@ -1,34 +1,34 @@ -import type { Client } from '../client'; +import type { Client } from '../client' export interface TestRequestPayload { - payload: Buffer; - testID: number; - client: Client; + payload: Buffer + testID: number + client: Client } export interface EthDepositInfo { - networkName: string; - forkVersion: Buffer; - validatorsRoot: Buffer; + networkName: string + forkVersion: Buffer + validatorsRoot: Buffer } export interface EthDepositDataReq { - // (optional) BLS withdrawal key or ETH1 withdrawal address - withdrawalKey?: Buffer | string; - // Amount to be deposited in GWei (10**9 wei) - amountGwei: number; - // Info about the chain we are using. - // You probably shouldn't change this unless you know what you're doing. - info: EthDepositInfo; - // In order to be compatible with Ethereum's online launchpad, you need - // to set the CLI version. Obviously we are not using the CLI here but - // we are following the protocol outlined in v2.3.0. - depositCliVersion: string; + // (optional) BLS withdrawal key or ETH1 withdrawal address + withdrawalKey?: Buffer | string + // Amount to be deposited in GWei (10**9 wei) + amountGwei: number + // Info about the chain we are using. + // You probably shouldn't change this unless you know what you're doing. + info: EthDepositInfo + // In order to be compatible with Ethereum's online launchpad, you need + // to set the CLI version. Obviously we are not using the CLI here but + // we are following the protocol outlined in v2.3.0. + depositCliVersion: string } export interface EthDepositDataResp { - // Validator's pubkey as a hex string - pubkey: string; - // JSON encoded deposit data - depositData: string; + // Validator's pubkey as a hex string + pubkey: string + // JSON encoded deposit data + depositData: string } diff --git a/packages/sdk/src/types/vitest.d.ts b/packages/sdk/src/types/vitest.d.ts index 2e25ea81..508f2564 100644 --- a/packages/sdk/src/types/vitest.d.ts +++ b/packages/sdk/src/types/vitest.d.ts @@ -1,8 +1,8 @@ -export {}; +export {} declare global { - namespace Vi { - interface JestAssertion { - toEqualElseLog(a: any, msg: string): any; - } - } + namespace Vi { + interface JestAssertion { + toEqualElseLog(a: any, msg: string): any + } + } } diff --git a/packages/sdk/src/util.ts b/packages/sdk/src/util.ts index e225560b..c59eda74 100644 --- a/packages/sdk/src/util.ts +++ b/packages/sdk/src/util.ts @@ -1,36 +1,27 @@ -import { Buffer } from 'node:buffer'; +import { Buffer } from 'node:buffer' // Static utility functions -import { RLP } from '@ethereumjs/rlp'; -import aes from 'aes-js'; -import BigNum from 'bignumber.js'; -import { BN } from 'bn.js'; -import crc32 from 'crc-32'; -import elliptic from 'elliptic'; -import inRange from 'lodash/inRange.js'; -import isInteger from 'lodash/isInteger.js'; -import { Hash } from 'ox'; -import secp256k1 from 'secp256k1'; -import { type Hex, parseTransaction } from 'viem'; - -const EC = elliptic.ec; -const { ecdsaRecover } = secp256k1; -import { Calldata } from '.'; -import { - BIP_CONSTANTS, - EXTERNAL_NETWORKS_BY_CHAIN_ID_URL, - HARDENED_OFFSET, - NETWORKS_BY_CHAIN_ID, - VERSION_BYTE, -} from './constants'; -import { LatticeResponseCode, ProtocolConstants } from './protocol'; -import { - isValid4ByteResponse, - isValidBlockExplorerResponse, -} from './shared/validators'; -import type { FirmwareConstants } from './types'; - -const { COINS, PURPOSES } = BIP_CONSTANTS; -let ec: any; +import { RLP } from '@ethereumjs/rlp' +import aes from 'aes-js' +import BigNum from 'bignumber.js' +import { BN } from 'bn.js' +import crc32 from 'crc-32' +import elliptic from 'elliptic' +import inRange from 'lodash/inRange.js' +import isInteger from 'lodash/isInteger.js' +import { Hash } from 'ox' +import secp256k1 from 'secp256k1' +import { type Hex, parseTransaction } from 'viem' + +const EC = elliptic.ec +const { ecdsaRecover } = secp256k1 +import { Calldata } from '.' +import { BIP_CONSTANTS, EXTERNAL_NETWORKS_BY_CHAIN_ID_URL, HARDENED_OFFSET, NETWORKS_BY_CHAIN_ID, VERSION_BYTE } from './constants' +import { LatticeResponseCode, ProtocolConstants } from './protocol' +import { isValid4ByteResponse, isValidBlockExplorerResponse } from './shared/validators' +import type { FirmwareConstants } from './types' + +const { COINS, PURPOSES } = BIP_CONSTANTS +let ec: any //-------------------------------------------------- // LATTICE UTILS @@ -38,346 +29,304 @@ let ec: any; /** @internal Parse a response from the Lattice1 */ export const parseLattice1Response = ( - r: string, + r: string, ): { - errorMessage?: string; - responseCode?: number; - data?: Buffer; + errorMessage?: string + responseCode?: number + data?: Buffer } => { - const parsed: { - errorMessage: string | null; - data: Buffer | null; - responseCode?: number; - } = { - errorMessage: null, - data: null, - }; - const b = Buffer.from(r, 'hex'); - let off = 0; - - // Get protocol version - const protoVer = b.readUInt8(off); - off++; - if (protoVer !== VERSION_BYTE) { - parsed.errorMessage = 'Incorrect protocol version. Please update your SDK'; - return parsed; - } - - // Get the type of response - // Should always be 0x00 - const msgType = b.readUInt8(off); - off++; - if (msgType !== 0x00) { - parsed.errorMessage = 'Incorrect response from Lattice1'; - return parsed; - } - - // Get the payload - b.readUInt32BE(off); - off += 4; // First 4 bytes is the id, but we don't need that anymore - const len = b.readUInt16BE(off); - off += 2; - const payload = b.slice(off, off + len); - off += len; - - // Get response code - const responseCode = payload.readUInt8(0); - if (responseCode !== LatticeResponseCode.success) { - const errMsg = ProtocolConstants.responseMsg[responseCode]; - parsed.errorMessage = `[Lattice] ${errMsg ? errMsg : 'Unknown Error'}`; - parsed.responseCode = responseCode; - return parsed; - } else { - parsed.data = payload.slice(1, payload.length); - } - - // Verify checksum - const cs = b.readUInt32BE(off); - const expectedCs = checksum(b.slice(0, b.length - 4)); - if (cs !== expectedCs) { - parsed.errorMessage = 'Invalid checksum from device response'; - parsed.data = null; - return parsed; - } - - return parsed; -}; + const parsed: { + errorMessage: string | null + data: Buffer | null + responseCode?: number + } = { + errorMessage: null, + data: null, + } + const b = Buffer.from(r, 'hex') + let off = 0 + + // Get protocol version + const protoVer = b.readUInt8(off) + off++ + if (protoVer !== VERSION_BYTE) { + parsed.errorMessage = 'Incorrect protocol version. Please update your SDK' + return parsed + } + + // Get the type of response + // Should always be 0x00 + const msgType = b.readUInt8(off) + off++ + if (msgType !== 0x00) { + parsed.errorMessage = 'Incorrect response from Lattice1' + return parsed + } + + // Get the payload + b.readUInt32BE(off) + off += 4 // First 4 bytes is the id, but we don't need that anymore + const len = b.readUInt16BE(off) + off += 2 + const payload = b.slice(off, off + len) + off += len + + // Get response code + const responseCode = payload.readUInt8(0) + if (responseCode !== LatticeResponseCode.success) { + const errMsg = ProtocolConstants.responseMsg[responseCode] + parsed.errorMessage = `[Lattice] ${errMsg ? errMsg : 'Unknown Error'}` + parsed.responseCode = responseCode + return parsed + } else { + parsed.data = payload.slice(1, payload.length) + } + + // Verify checksum + const cs = b.readUInt32BE(off) + const expectedCs = checksum(b.slice(0, b.length - 4)) + if (cs !== expectedCs) { + parsed.errorMessage = 'Invalid checksum from device response' + parsed.data = null + return parsed + } + + return parsed +} /** @internal */ export const checksum = (x: Buffer): number => { - // crc32 returns a signed integer - need to cast it to unsigned - // Note that this uses the default 0xedb88320 polynomial - return crc32.buf(x) >>> 0; // Need this to be a uint, hence the bit shift -}; + // crc32 returns a signed integer - need to cast it to unsigned + // Note that this uses the default 0xedb88320 polynomial + return crc32.buf(x) >>> 0 // Need this to be a uint, hence the bit shift +} // Get a 74-byte padded DER-encoded signature buffer // `sig` must be the signature output from elliptic.js /** @internal */ export const toPaddedDER = (sig: any): Buffer => { - // We use 74 as the maximum length of a DER signature. All sigs must - // be right-padded with zeros so that this can be a fixed size field - const b = Buffer.alloc(74); - const ds = Buffer.from(sig.toDER()); - ds.copy(b); - return b; -}; + // We use 74 as the maximum length of a DER signature. All sigs must + // be right-padded with zeros so that this can be a fixed size field + const b = Buffer.alloc(74) + const ds = Buffer.from(sig.toDER()) + ds.copy(b) + return b +} //-------------------------------------------------- // TRANSACTION UTILS //-------------------------------------------------- /** @internal */ -export const isValidAssetPath = ( - path: number[], - fwConstants: FirmwareConstants, -): boolean => { - const allowedPurposes = [ - PURPOSES.ETH, - PURPOSES.BTC_LEGACY, - PURPOSES.BTC_WRAPPED_SEGWIT, - PURPOSES.BTC_SEGWIT, - ]; - const allowedCoins = [COINS.ETH, COINS.BTC, COINS.BTC_TESTNET]; - // These coin types were given to us by MyCrypto. They should be allowed, but we expect - // an Ethereum-type address with these coin types. - // These all use SLIP44: https://github.com/satoshilabs/slips/blob/master/slip-0044.md - const allowedMyCryptoCoins = [ - 60, 61, 966, 700, 9006, 9000, 1007, 553, 178, 137, 37310, 108, 40, 889, - 1987, 820, 6060, 1620, 1313114, 76, 246529, 246785, 1001, 227, 916, 464, - 2221, 344, 73799, 246, - ]; - // Make sure firmware supports this Bitcoin path - const isBitcoin = path[1] === COINS.BTC || path[1] === COINS.BTC_TESTNET; - const isBitcoinNonWrappedSegwit = - isBitcoin && path[0] !== PURPOSES.BTC_WRAPPED_SEGWIT; - if (isBitcoinNonWrappedSegwit && !fwConstants.allowBtcLegacyAndSegwitAddrs) - return false; - // Make sure this path is otherwise valid - return ( - allowedPurposes.indexOf(path[0]) >= 0 && - (allowedCoins.indexOf(path[1]) >= 0 || - allowedMyCryptoCoins.indexOf(path[1] - HARDENED_OFFSET) > 0) - ); -}; +export const isValidAssetPath = (path: number[], fwConstants: FirmwareConstants): boolean => { + const allowedPurposes = [PURPOSES.ETH, PURPOSES.BTC_LEGACY, PURPOSES.BTC_WRAPPED_SEGWIT, PURPOSES.BTC_SEGWIT] + const allowedCoins = [COINS.ETH, COINS.BTC, COINS.BTC_TESTNET] + // These coin types were given to us by MyCrypto. They should be allowed, but we expect + // an Ethereum-type address with these coin types. + // These all use SLIP44: https://github.com/satoshilabs/slips/blob/master/slip-0044.md + const allowedMyCryptoCoins = [60, 61, 966, 700, 9006, 9000, 1007, 553, 178, 137, 37310, 108, 40, 889, 1987, 820, 6060, 1620, 1313114, 76, 246529, 246785, 1001, 227, 916, 464, 2221, 344, 73799, 246] + // Make sure firmware supports this Bitcoin path + const isBitcoin = path[1] === COINS.BTC || path[1] === COINS.BTC_TESTNET + const isBitcoinNonWrappedSegwit = isBitcoin && path[0] !== PURPOSES.BTC_WRAPPED_SEGWIT + if (isBitcoinNonWrappedSegwit && !fwConstants.allowBtcLegacyAndSegwitAddrs) return false + // Make sure this path is otherwise valid + return allowedPurposes.indexOf(path[0]) >= 0 && (allowedCoins.indexOf(path[1]) >= 0 || allowedMyCryptoCoins.indexOf(path[1] - HARDENED_OFFSET) > 0) +} /** @internal */ export const splitFrames = (data: Buffer, frameSz: number): Buffer[] => { - const frames = []; - const n = Math.ceil(data.length / frameSz); - let off = 0; - for (let i = 0; i < n; i++) { - frames.push(data.slice(off, off + frameSz)); - off += frameSz; - } - return frames; -}; + const frames = [] + const n = Math.ceil(data.length / frameSz) + let off = 0 + for (let i = 0; i < n; i++) { + frames.push(data.slice(off, off + frameSz)) + off += frameSz + } + return frames +} /** @internal */ function isBase10NumStr(x: string): boolean { - const bn = new BigNum(x).toFixed().split('.').join(''); - const s = new String(x); - // Note that the JS native `String()` loses precision for large numbers, but we only - // want to validate the base of the number so we don't care about far out precision. - return bn.slice(0, 8) === s.slice(0, 8); + const bn = new BigNum(x).toFixed().split('.').join('') + const s = new String(x) + // Note that the JS native `String()` loses precision for large numbers, but we only + // want to validate the base of the number so we don't care about far out precision. + return bn.slice(0, 8) === s.slice(0, 8) } /** @internal Ensure a param is represented by a buffer */ -export const ensureHexBuffer = ( - x: string | number | bigint | Buffer, - zeroIsNull = true, -): Buffer => { - try { - const isZeroNumber = typeof x === 'number' && x === 0; - const isZeroBigInt = typeof x === 'bigint' && x === 0n; - if (x === null || ((isZeroNumber || isZeroBigInt) && zeroIsNull === true)) - return Buffer.alloc(0); - const isDecimalInput = - typeof x === 'number' || - typeof x === 'bigint' || - (typeof x === 'string' && isBase10NumStr(x)); - let hexString: string; - if (isDecimalInput) { - const formatted = - typeof x === 'bigint' ? x.toString(10) : (x as string | number); - hexString = new BigNum(formatted).toString(16); - } else if (typeof x === 'string' && x.slice(0, 2) === '0x') { - hexString = x.slice(2); - } else if (Buffer.isBuffer(x)) { - return x; - } else { - hexString = x.toString(); - } - if (hexString.length % 2 > 0) hexString = `0${hexString}`; - if (hexString === '00' && !isDecimalInput) return Buffer.alloc(0); - return Buffer.from(hexString, 'hex'); - } catch (_err) { - throw new Error( - `Cannot convert ${x.toString()} to hex buffer (${(_err as Error).message})`, - ); - } -}; +export const ensureHexBuffer = (x: string | number | bigint | Buffer, zeroIsNull = true): Buffer => { + try { + const isZeroNumber = typeof x === 'number' && x === 0 + const isZeroBigInt = typeof x === 'bigint' && x === 0n + if (x === null || ((isZeroNumber || isZeroBigInt) && zeroIsNull === true)) return Buffer.alloc(0) + const isDecimalInput = typeof x === 'number' || typeof x === 'bigint' || (typeof x === 'string' && isBase10NumStr(x)) + let hexString: string + if (isDecimalInput) { + const formatted = typeof x === 'bigint' ? x.toString(10) : (x as string | number) + hexString = new BigNum(formatted).toString(16) + } else if (typeof x === 'string' && x.slice(0, 2) === '0x') { + hexString = x.slice(2) + } else if (Buffer.isBuffer(x)) { + return x + } else { + hexString = x.toString() + } + if (hexString.length % 2 > 0) hexString = `0${hexString}` + if (hexString === '00' && !isDecimalInput) return Buffer.alloc(0) + return Buffer.from(hexString, 'hex') + } catch (_err) { + throw new Error(`Cannot convert ${x.toString()} to hex buffer (${(_err as Error).message})`) + } +} /** @internal */ export const fixLen = (msg: Buffer, length: number): Buffer => { - const buf = Buffer.alloc(length); - if (msg.length < length) { - msg.copy(buf, length - msg.length); - return buf; - } - return msg.slice(-length); -}; + const buf = Buffer.alloc(length) + if (msg.length < length) { + msg.copy(buf, length - msg.length) + return buf + } + return msg.slice(-length) +} //-------------------------------------------------- // CRYPTO UTILS //-------------------------------------------------- /** @internal */ export const aes256_encrypt = (data: Buffer, key: Buffer): Buffer => { - const iv = Buffer.from(ProtocolConstants.aesIv); - const aesCbc = new aes.ModeOfOperation.cbc(key, iv); - const paddedData = - data.length % 16 === 0 ? data : aes.padding.pkcs7.pad(data); - return Buffer.from(aesCbc.encrypt(paddedData)); -}; + const iv = Buffer.from(ProtocolConstants.aesIv) + const aesCbc = new aes.ModeOfOperation.cbc(key, iv) + const paddedData = data.length % 16 === 0 ? data : aes.padding.pkcs7.pad(data) + return Buffer.from(aesCbc.encrypt(paddedData)) +} /** @internal */ export const aes256_decrypt = (data: Buffer, key: Buffer): Buffer => { - const iv = Buffer.from(ProtocolConstants.aesIv); - const aesCbc = new aes.ModeOfOperation.cbc(key, iv); - return Buffer.from(aesCbc.decrypt(data)); -}; + const iv = Buffer.from(ProtocolConstants.aesIv) + const aesCbc = new aes.ModeOfOperation.cbc(key, iv) + return Buffer.from(aesCbc.decrypt(data)) +} // Decode a DER signature. Returns signature object {r, s } or null if there is an error /** @internal */ export const parseDER = (sigBuf: Buffer) => { - if (sigBuf[0] !== 0x30 || sigBuf[2] !== 0x02) - throw new Error('Failed to decode DER signature'); - let off = 3; - const rLen = sigBuf[off]; - off++; - const r = sigBuf.slice(off, off + rLen); - off += rLen; - if (sigBuf[off] !== 0x02) throw new Error('Failed to decode DER signature'); - off++; - const sLen = sigBuf[off]; - off++; - const s = sigBuf.slice(off, off + sLen); - return { r, s }; -}; + if (sigBuf[0] !== 0x30 || sigBuf[2] !== 0x02) throw new Error('Failed to decode DER signature') + let off = 3 + const rLen = sigBuf[off] + off++ + const r = sigBuf.slice(off, off + rLen) + off += rLen + if (sigBuf[off] !== 0x02) throw new Error('Failed to decode DER signature') + off++ + const sLen = sigBuf[off] + off++ + const s = sigBuf.slice(off, off + sLen) + return { r, s } +} /** @internal */ export const getP256KeyPair = (priv: Buffer | string): any => { - if (ec === undefined) ec = new EC('p256'); - return ec.keyFromPrivate(priv, 'hex'); -}; + if (ec === undefined) ec = new EC('p256') + return ec.keyFromPrivate(priv, 'hex') +} /** @internal */ export const getP256KeyPairFromPub = (pub: Buffer | string): any => { - if (ec === undefined) ec = new EC('p256'); - // Convert Buffer to hex string if needed - const pubHex = Buffer.isBuffer(pub) ? pub.toString('hex') : pub; - return ec.keyFromPublic(pubHex, 'hex'); -}; + if (ec === undefined) ec = new EC('p256') + // Convert Buffer to hex string if needed + const pubHex = Buffer.isBuffer(pub) ? pub.toString('hex') : pub + return ec.keyFromPublic(pubHex, 'hex') +} /** @internal */ -export const buildSignerPathBuf = ( - signerPath: number[], - varAddrPathSzAllowed: boolean, -): Buffer => { - const buf = Buffer.alloc(24); - let off = 0; - if (varAddrPathSzAllowed && signerPath.length > 5) - throw new Error('Signer path must be <=5 indices.'); - if (!varAddrPathSzAllowed && signerPath.length !== 5) - throw new Error( - 'Your Lattice firmware only supports 5-index derivation paths. Please upgrade.', - ); - buf.writeUInt32LE(signerPath.length, off); - off += 4; - for (let i = 0; i < 5; i++) { - if (i < signerPath.length) buf.writeUInt32LE(signerPath[i], off); - else buf.writeUInt32LE(0, off); - off += 4; - } - return buf; -}; +export const buildSignerPathBuf = (signerPath: number[], varAddrPathSzAllowed: boolean): Buffer => { + const buf = Buffer.alloc(24) + let off = 0 + if (varAddrPathSzAllowed && signerPath.length > 5) throw new Error('Signer path must be <=5 indices.') + if (!varAddrPathSzAllowed && signerPath.length !== 5) throw new Error('Your Lattice firmware only supports 5-index derivation paths. Please upgrade.') + buf.writeUInt32LE(signerPath.length, off) + off += 4 + for (let i = 0; i < 5; i++) { + if (i < signerPath.length) buf.writeUInt32LE(signerPath[i], off) + else buf.writeUInt32LE(0, off) + off += 4 + } + return buf +} //-------------------------------------------------- // OTHER UTILS //-------------------------------------------------- /** @internal */ export const isAsciiStr = (str: string, allowFormatChars = false): boolean => { - if (typeof str !== 'string') { - return false; - } - const extraChars = allowFormatChars - ? [ - 0x0020, // Space - 0x000a, // New line - ] - : []; - for (let i = 0; i < str.length; i++) { - const c = str.charCodeAt(i); - if (extraChars.indexOf(c) < 0 && (c < 0x0020 || c > 0x007f)) { - return false; - } - } - return true; -}; + if (typeof str !== 'string') { + return false + } + const extraChars = allowFormatChars + ? [ + 0x0020, // Space + 0x000a, // New line + ] + : [] + for (let i = 0; i < str.length; i++) { + const c = str.charCodeAt(i) + if (extraChars.indexOf(c) < 0 && (c < 0x0020 || c > 0x007f)) { + return false + } + } + return true +} /** @internal Check if a value exists in an object. Only checks first level of keys. */ -export const existsIn = (val: T, obj: { [key: string]: T }): boolean => - Object.keys(obj).some((key) => obj[key] === val); +export const existsIn = (val: T, obj: { [key: string]: T }): boolean => Object.keys(obj).some((key) => obj[key] === val) /** @internal Create a buffer of size `n` and fill it with random data */ export const randomBytes = (n: number): Buffer => { - const buf = Buffer.alloc(n); - for (let i = 0; i < n; i++) { - buf[i] = Math.round(Math.random() * 255); - } - return buf; -}; + const buf = Buffer.alloc(n) + for (let i = 0; i < n; i++) { + buf[i] = Math.round(Math.random() * 255) + } + return buf +} /** @internal `isUInt4` accepts a number and returns true if it is a UInt4 */ -export const isUInt4 = (n: number) => isInteger(n) && inRange(n, 0, 16); +export const isUInt4 = (n: number) => isInteger(n) && inRange(n, 0, 16) /** * Fetches an external JSON file containing networks indexed by chain id from a GridPlus repo, and * returns the parsed JSON. */ -async function fetchExternalNetworkForChainId( - chainId: number | string, -): Promise<{ - [key: string]: { - name: string; - baseUrl: string; - apiRoute: string; - }; +async function fetchExternalNetworkForChainId(chainId: number | string): Promise<{ + [key: string]: { + name: string + baseUrl: string + apiRoute: string + } }> { - try { - const body = await fetch(EXTERNAL_NETWORKS_BY_CHAIN_ID_URL).then((res) => - res.json(), - ); - if (body) { - return body[chainId]; - } else { - return undefined; - } - } catch (_err) { - console.warn('Fetching external networks failed.\n', _err); - } + try { + const body = await fetch(EXTERNAL_NETWORKS_BY_CHAIN_ID_URL).then((res) => res.json()) + if (body) { + return body[chainId] + } else { + return undefined + } + } catch (_err) { + console.warn('Fetching external networks failed.\n', _err) + } } /** * Builds a URL for fetching calldata from block explorers for any supported chains * */ function buildUrlForSupportedChainAndAddress({ supportedChain, address }) { - const baseUrl = supportedChain.baseUrl; - const apiRoute = supportedChain.apiRoute; - const urlWithRoute = `${baseUrl}/${apiRoute}&address=${address}`; + const baseUrl = supportedChain.baseUrl + const apiRoute = supportedChain.apiRoute + const urlWithRoute = `${baseUrl}/${apiRoute}&address=${address}` - const apiKey = process.env.ETHERSCAN_KEY; - const apiKeyParam = apiKey ? `&apiKey=${process.env.ETHERSCAN_KEY}` : ''; + const apiKey = process.env.ETHERSCAN_KEY + const apiKeyParam = apiKey ? `&apiKey=${process.env.ETHERSCAN_KEY}` : '' - return urlWithRoute + apiKeyParam; + return urlWithRoute + apiKeyParam } /** @@ -385,127 +334,110 @@ function buildUrlForSupportedChainAndAddress({ supportedChain, address }) { * matches the selector. */ export function selectDefFrom4byteABI(abiData: any[], selector: string) { - if (abiData.length > 1) { - console.warn('WARNING: There are multiple results. Using the first one.'); - } - let def; - abiData - .sort((a, b) => { - const aTime = new Date(a.created_at).getTime(); - const bTime = new Date(b.created_at).getTime(); - return aTime - bTime; - }) - .find((result) => { - try { - def = Calldata.EVM.parsers.parseCanonicalName( - selector, - result.text_signature, - ); - return !!def; - } catch (_err) { - console.error('Failed to parse canonical name:', _err); - return false; - } - }); - if (def) { - return def; - } else { - throw new Error('Could not find definition for selector'); - } -} - -export async function fetchWithTimeout( - url: string, - options: RequestInit & { timeout?: number }, -): Promise { - const { timeout = 8000 } = options; - const controller = new AbortController(); - const timeoutId = setTimeout(() => controller.abort(), timeout); - const response = await fetch(url, { - ...options, - signal: controller.signal, - }); - clearTimeout(timeoutId); - return response; -} - -async function fetchAndCache( - url: string, - opts?: RequestInit, -): Promise { - try { - if (globalThis.caches && globalThis.Request) { - const cache = await caches.open('gp-calldata'); - const request = new Request(url, opts); - const match = await cache.match(request); - if (match) { - return match; - } else { - const response = await fetch(request, opts); - const responseClone = response.clone(); - const data = await response.json(); - if ( - response.ok && - (isValidBlockExplorerResponse(data) || isValid4ByteResponse(data)) - ) { - await cache.put(request, responseClone); - return cache.match(request); - } - return response; - } - } else { - return fetch(url, opts); - } - } catch (err) { - console.error(err); - throw err; - } -} - -async function fetchSupportedChainData( - address: string, - supportedChain: number, -) { - const url = buildUrlForSupportedChainAndAddress({ address, supportedChain }); - return fetchAndCache(url) - .then((res) => res.json()) - .then((body) => { - if (body?.result) { - try { - return JSON.parse(body.result); - } catch { - throw new Error( - `Invalid JSON in response: ${body.result.substring(0, 50)}`, - ); - } - } else { - throw new Error('Server response was malformed'); - } - }) - .catch((error) => { - console.log(error); - throw new Error('Fetching data from external network failed'); - }); + if (abiData.length > 1) { + console.warn('WARNING: There are multiple results. Using the first one.') + } + let def: unknown[] | undefined + abiData + .sort((a, b) => { + const aTime = new Date(a.created_at).getTime() + const bTime = new Date(b.created_at).getTime() + return aTime - bTime + }) + .find((result) => { + try { + def = Calldata.EVM.parsers.parseCanonicalName(selector, result.text_signature) + return !!def + } catch (_err) { + console.error('Failed to parse canonical name:', _err) + return false + } + }) + if (def) { + return def + } else { + throw new Error('Could not find definition for selector') + } +} + +export async function fetchWithTimeout(url: string, options: RequestInit & { timeout?: number }): Promise { + const { timeout = 8000 } = options + const controller = new AbortController() + const timeoutId = setTimeout(() => controller.abort(), timeout) + const response = await fetch(url, { + ...options, + signal: controller.signal, + }) + clearTimeout(timeoutId) + return response +} + +async function fetchAndCache(url: string, opts?: RequestInit): Promise { + try { + if (globalThis.caches && globalThis.Request) { + const cache = await caches.open('gp-calldata') + const request = new Request(url, opts) + const match = await cache.match(request) + if (match) { + return match + } else { + const response = await fetch(request, opts) + const responseClone = response.clone() + const data = await response.json() + if (response.ok && (isValidBlockExplorerResponse(data) || isValid4ByteResponse(data))) { + await cache.put(request, responseClone) + return cache.match(request) + } + return response + } + } else { + return fetch(url, opts) + } + } catch (err) { + console.error(err) + throw err + } +} + +async function fetchSupportedChainData(address: string, supportedChain: number) { + const url = buildUrlForSupportedChainAndAddress({ address, supportedChain }) + return fetchAndCache(url) + .then((res) => res.json()) + .then((body) => { + if (body?.result) { + try { + return JSON.parse(body.result) + } catch { + throw new Error(`Invalid JSON in response: ${body.result.substring(0, 50)}`) + } + } else { + throw new Error('Server response was malformed') + } + }) + .catch((error) => { + console.log(error) + throw new Error('Fetching data from external network failed') + }) } async function fetch4byteData(selector: string): Promise { - const url = `https://www.4byte.directory/api/v1/signatures/?hex_signature=0x${selector}`; - return await fetch(url) - .then((res) => res.json()) - .then((body) => { - if (body?.results) { - return body.results; - } else { - throw new Error('No results found'); - } - }) - .catch((err) => { - throw new Error(`Fetching data from 4byte failed: ${err.message}`); - }); + const url = `https://www.4byte.directory/api/v1/signatures/?hex_signature=0x${selector}` + return await fetch(url) + .then((res) => res.json()) + .then((body) => { + if (body?.results) { + return body.results + } else { + throw new Error('No results found') + } + }) + .catch((err) => { + throw new Error(`Fetching data from 4byte failed: ${err.message}`) + }) } function encodeDef(def: any) { - return Buffer.from(RLP.encode(def)); + return Buffer.from(RLP.encode(def)) } /** @@ -515,41 +447,32 @@ function encodeDef(def: any) { * @return - Updated `def` */ async function postProcessDef(def, calldata) { - // Replace all nested defs if applicable. This is done by looping - // through each param in the definition and if it is of type `bytes` - // or `bytes[]`, checking the param value in `calldata`. If the param - // value (or for `bytes[]` each underlying value) is of size (4 + 32*n) - // it could be nested calldata. We should use that item's selector(s) - // to look up nested definition(s). - const nestedCalldata = Calldata.EVM.processors.getNestedCalldata( - def, - calldata, - ); - const nestedDefs = await replaceNestedDefs(nestedCalldata); - // Need to recurse before doing the full replacement - for await (const [i] of nestedDefs.entries()) { - // If this is an array of nested defs, loop through each one and - // postprocess it. The first item of a single def is the function - // name so we need to check that it isn't a string in this case. - if (Array.isArray(nestedDefs[i]) && typeof nestedDefs[i][0] !== 'string') { - for await (const [j] of nestedDefs[i].entries()) { - if (nestedDefs[i][j] !== null) { - nestedDefs[i][j] = await postProcessDef( - nestedDefs[i][j], - Buffer.from(nestedCalldata[i][j].slice(2), 'hex'), - ); - } - } - } else if (nestedDefs[i] !== null) { - nestedDefs[i] = await postProcessDef( - nestedDefs[i], - Buffer.from(nestedCalldata[i].slice(2), 'hex'), - ); - } - } - // Replace any nested defs - const newDef = Calldata.EVM.processors.replaceNestedDefs(def, nestedDefs); - return newDef; + // Replace all nested defs if applicable. This is done by looping + // through each param in the definition and if it is of type `bytes` + // or `bytes[]`, checking the param value in `calldata`. If the param + // value (or for `bytes[]` each underlying value) is of size (4 + 32*n) + // it could be nested calldata. We should use that item's selector(s) + // to look up nested definition(s). + const nestedCalldata = Calldata.EVM.processors.getNestedCalldata(def, calldata) + const nestedDefs = await replaceNestedDefs(nestedCalldata) + // Need to recurse before doing the full replacement + for await (const [i] of nestedDefs.entries()) { + // If this is an array of nested defs, loop through each one and + // postprocess it. The first item of a single def is the function + // name so we need to check that it isn't a string in this case. + if (Array.isArray(nestedDefs[i]) && typeof nestedDefs[i][0] !== 'string') { + for await (const [j] of nestedDefs[i].entries()) { + if (nestedDefs[i][j] !== null) { + nestedDefs[i][j] = await postProcessDef(nestedDefs[i][j], Buffer.from(nestedCalldata[i][j].slice(2), 'hex')) + } + } + } else if (nestedDefs[i] !== null) { + nestedDefs[i] = await postProcessDef(nestedDefs[i], Buffer.from(nestedCalldata[i].slice(2), 'hex')) + } + } + // Replace any nested defs + const newDef = Calldata.EVM.processors.replaceNestedDefs(def, nestedDefs) + return newDef } /** @@ -566,50 +489,47 @@ async function postProcessDef(def, calldata) { * */ async function replaceNestedDefs(possNestedDefs) { - // For all possible nested defs, attempt to fetch the underlying def - const nestedDefs = []; - for await (const d of possNestedDefs) { - if (d !== null) { - if (Array.isArray(d)) { - const _nestedDefs = []; - let shouldInclude = true; - for await (const _d of d) { - try { - const _nestedSelector = _d.slice(2, 10); - const _nestedAbi = await fetch4byteData(_nestedSelector); - const _nestedDef = selectDefFrom4byteABI( - _nestedAbi, - _nestedSelector, - ); - _nestedDefs.push(_nestedDef); - } catch (_err) { - console.error('Failed to fetch nested 4byte data:', _err); - shouldInclude = false; - _nestedDefs.push(null); - } - } - if (shouldInclude) { - nestedDefs.push(_nestedDefs); - } else { - nestedDefs.push(null); - } - } else { - try { - const nestedSelector = d.slice(2, 10); - const nestedAbi = await fetch4byteData(nestedSelector); - const nestedDef = selectDefFrom4byteABI(nestedAbi, nestedSelector); - nestedDefs.push(nestedDef); - } catch (_err) { - console.error('Failed to fetch nested definition:', _err); - nestedDefs.push(null); - } - } - } else { - nestedDefs.push(null); - } - } - // For all nested defs, replace the - return nestedDefs; + // For all possible nested defs, attempt to fetch the underlying def + const nestedDefs = [] + for await (const d of possNestedDefs) { + if (d !== null) { + if (Array.isArray(d)) { + const _nestedDefs = [] + let shouldInclude = true + for await (const _d of d) { + try { + const _nestedSelector = _d.slice(2, 10) + const _nestedAbi = await fetch4byteData(_nestedSelector) + const _nestedDef = selectDefFrom4byteABI(_nestedAbi, _nestedSelector) + _nestedDefs.push(_nestedDef) + } catch (_err) { + console.error('Failed to fetch nested 4byte data:', _err) + shouldInclude = false + _nestedDefs.push(null) + } + } + if (shouldInclude) { + nestedDefs.push(_nestedDefs) + } else { + nestedDefs.push(null) + } + } else { + try { + const nestedSelector = d.slice(2, 10) + const nestedAbi = await fetch4byteData(nestedSelector) + const nestedDef = selectDefFrom4byteABI(nestedAbi, nestedSelector) + nestedDefs.push(nestedDef) + } catch (_err) { + console.error('Failed to fetch nested definition:', _err) + nestedDefs.push(null) + } + } + } else { + nestedDefs.push(null) + } + } + // For all nested defs, replace the + return nestedDefs } //-------------------------------------------------- @@ -620,73 +540,61 @@ async function replaceNestedDefs(possNestedDefs) { /** * Fetches calldata from a remote scanner based on the transaction's `chainId` */ -export async function fetchCalldataDecoder( - _data: Uint8Array | string, - to: string, - _chainId: number | string, - recurse = true, -) { - try { - // Exit if there is no data. The 2 comes from the 0x prefix, but a later - // check will confirm that there are at least 4 bytes of data in the buffer. - if (!_data || _data.length < 2) { - throw new Error('Data is either undefined or less than two bytes'); - } - const isHexString = typeof _data === 'string' && _data.slice(0, 2) === '0x'; - const data = isHexString - ? Buffer.from(_data.slice(2), 'hex') - : //@ts-expect-error - Buffer doesn't recognize Uint8Array type properly - Buffer.from(_data, 'hex'); - - // For empty data (just '0x'), return early - no calldata to decode - if (data.length === 0) { - return { def: null, abi: null }; - } - - if (data.length < 4) { - throw new Error( - 'Data must contain at least 4 bytes of data to define the selector', - ); - } - const selector = Buffer.from(data.slice(0, 4)).toString('hex'); - // Convert the chainId to a number and use it to determine if we can call out to - // an etherscan-like explorer for richer data. - const chainId = Number(_chainId); - const cachedNetwork = NETWORKS_BY_CHAIN_ID[chainId]; - const supportedChain = cachedNetwork - ? cachedNetwork - : await fetchExternalNetworkForChainId(chainId); - try { - if (supportedChain) { - const abi = await fetchSupportedChainData(to, supportedChain); - const parsedAbi = Calldata.EVM.parsers.parseSolidityJSONABI( - selector, - abi, - ); - let def = parsedAbi.def; - if (recurse) { - def = await postProcessDef(def, data); - } - return { abi, def: encodeDef(def) }; - } else { - throw new Error(`Chain (id: ${chainId}) is not supported`); - } - } catch (err) { - console.warn(err.message, '\n', 'Falling back to 4byte'); - } - - // Fallback to checking 4byte - const abi = await fetch4byteData(selector); - let def = selectDefFrom4byteABI(abi, selector); - if (recurse) { - def = await postProcessDef(def, data); - } - return { abi, def: encodeDef(def) }; - } catch (err) { - console.warn(`Fetching calldata failed: ${err.message}`); - } - - return { def: null, abi: null }; +export async function fetchCalldataDecoder(_data: Uint8Array | string, to: string, _chainId: number | string, recurse = true) { + try { + // Exit if there is no data. The 2 comes from the 0x prefix, but a later + // check will confirm that there are at least 4 bytes of data in the buffer. + if (!_data || _data.length < 2) { + throw new Error('Data is either undefined or less than two bytes') + } + const isHexString = typeof _data === 'string' && _data.slice(0, 2) === '0x' + const data = isHexString + ? Buffer.from(_data.slice(2), 'hex') + : //@ts-expect-error - Buffer doesn't recognize Uint8Array type properly + Buffer.from(_data, 'hex') + + // For empty data (just '0x'), return early - no calldata to decode + if (data.length === 0) { + return { def: null, abi: null } + } + + if (data.length < 4) { + throw new Error('Data must contain at least 4 bytes of data to define the selector') + } + const selector = Buffer.from(data.slice(0, 4)).toString('hex') + // Convert the chainId to a number and use it to determine if we can call out to + // an etherscan-like explorer for richer data. + const chainId = Number(_chainId) + const cachedNetwork = NETWORKS_BY_CHAIN_ID[chainId] + const supportedChain = cachedNetwork ? cachedNetwork : await fetchExternalNetworkForChainId(chainId) + try { + if (supportedChain) { + const abi = await fetchSupportedChainData(to, supportedChain) + const parsedAbi = Calldata.EVM.parsers.parseSolidityJSONABI(selector, abi) + let def = parsedAbi.def + if (recurse) { + def = await postProcessDef(def, data) + } + return { abi, def: encodeDef(def) } + } else { + throw new Error(`Chain (id: ${chainId}) is not supported`) + } + } catch (err) { + console.warn(err.message, '\n', 'Falling back to 4byte') + } + + // Fallback to checking 4byte + const abi = await fetch4byteData(selector) + let def = selectDefFrom4byteABI(abi, selector) + if (recurse) { + def = await postProcessDef(def, data) + } + return { abi, def: encodeDef(def) } + } catch (err) { + console.warn(`Fetching calldata failed: ${err.message}`) + } + + return { def: null, abi: null } } /** @@ -697,26 +605,15 @@ export async function fetchCalldataDecoder( * @returns an application secret as a Buffer * @public */ -export const generateAppSecret = ( - deviceId: Buffer | string, - password: Buffer | string, - appName: Buffer | string, -): Buffer => { - const deviceIdBuffer = - typeof deviceId === 'string' ? Buffer.from(deviceId) : deviceId; - const passwordBuffer = - typeof password === 'string' ? Buffer.from(password) : password; - const appNameBuffer = - typeof appName === 'string' ? Buffer.from(appName) : appName; - - const preImage = Buffer.concat([ - deviceIdBuffer, - passwordBuffer, - appNameBuffer, - ]); - - return Buffer.from(Hash.sha256(preImage)); -}; +export const generateAppSecret = (deviceId: Buffer | string, password: Buffer | string, appName: Buffer | string): Buffer => { + const deviceIdBuffer = typeof deviceId === 'string' ? Buffer.from(deviceId) : deviceId + const passwordBuffer = typeof password === 'string' ? Buffer.from(password) : password + const appNameBuffer = typeof appName === 'string' ? Buffer.from(appName) : appName + + const preImage = Buffer.concat([deviceIdBuffer, passwordBuffer, appNameBuffer]) + + return Buffer.from(Hash.sha256(preImage)) +} /** * Get the `v` component of signature using viem parsing. @@ -725,142 +622,127 @@ export const generateAppSecret = ( * @returns BN object containing the `v` param */ export const getV = (tx: any, resp: any) => { - let chainId: string | undefined; - let hash: Uint8Array; - let type: string | number | undefined; - let useEIP155 = false; - - if (Buffer.isBuffer(tx) || typeof tx === 'string') { - const txHex = Buffer.isBuffer(tx) - ? (`0x${tx.toString('hex')}` as Hex) - : (tx as Hex); - const txBuf = Buffer.isBuffer(tx) ? tx : Buffer.from(tx.slice(2), 'hex'); - - hash = Buffer.from(Hash.keccak256(txBuf)); - - try { - const parsedTx = parseTransaction(txHex); - type = parsedTx.type; - - if (parsedTx.chainId !== undefined && parsedTx.chainId !== null) { - chainId = parsedTx.chainId.toString(); - if (type === 'legacy') { - useEIP155 = true; - } - } - - if (type === 'legacy' && !useEIP155) { - const legacyTxArray = RLP.decode(txBuf); - if (legacyTxArray.length >= 9) { - const vBuf = legacyTxArray[6] as Uint8Array; - if (vBuf && vBuf.length > 0) { - chainId = new BN(vBuf).toString(); - useEIP155 = true; - } - } - } - } catch (err) { - console.error('Failed to parse transaction, trying legacy format:', err); - try { - const txBufRaw = Buffer.isBuffer(tx) - ? tx - : Buffer.from(tx.slice(2), 'hex'); - const legacyTxArray = RLP.decode(txBufRaw); - - type = 'legacy'; - if (legacyTxArray.length >= 9) { - const vBuf = legacyTxArray[6] as Uint8Array; - if (vBuf && vBuf.length > 0) { - chainId = new BN(vBuf).toString(); - useEIP155 = true; - } - } - } catch { - throw new Error('Could not recover V. Bad transaction data.'); - } - } - } else { - throw new Error( - 'Unsupported transaction format. Expected Buffer or hex string.', - ); - } - - const rBuf = Buffer.isBuffer(resp.sig.r) - ? resp.sig.r - : Buffer.from(resp.sig.r.slice(2), 'hex'); - const sBuf = Buffer.isBuffer(resp.sig.s) - ? resp.sig.s - : Buffer.from(resp.sig.s.slice(2), 'hex'); - const rs = new Uint8Array(Buffer.concat([rBuf, sBuf])); - const pubkeyInput = resp.pubkey; - - if (!pubkeyInput) { - throw new Error('Response did not include a public key.'); - } - - let pubkeyBuf: Buffer; - if (Buffer.isBuffer(pubkeyInput)) { - pubkeyBuf = Buffer.from(pubkeyInput); - } else if (pubkeyInput instanceof Uint8Array) { - pubkeyBuf = Buffer.from(pubkeyInput); - } else if (typeof pubkeyInput === 'string') { - const hex = pubkeyInput.startsWith('0x') - ? pubkeyInput.slice(2) - : pubkeyInput; - pubkeyBuf = Buffer.from(hex, 'hex'); - } else { - pubkeyBuf = Buffer.from(pubkeyInput); - } - - if (pubkeyBuf.length === 64) { - pubkeyBuf = Buffer.concat([Buffer.from([0x04]), pubkeyBuf]); - } - - const isCompressedPubkey = - pubkeyBuf.length === 33 && (pubkeyBuf[0] === 0x02 || pubkeyBuf[0] === 0x03); - const isUncompressedPubkey = pubkeyBuf.length === 65 && pubkeyBuf[0] === 0x04; - - if (!isCompressedPubkey && !isUncompressedPubkey) { - throw new Error('Unsupported public key format returned by device.'); - } - - const recovery0 = Buffer.from(ecdsaRecover(rs, 0, hash, isCompressedPubkey)); - const recovery1 = Buffer.from(ecdsaRecover(rs, 1, hash, isCompressedPubkey)); - - const pubkeyStr = pubkeyBuf.toString('hex'); - const recovery0Str = recovery0.toString('hex'); - const recovery1Str = recovery1.toString('hex'); - - let recovery: number; - if (pubkeyStr === recovery0Str) { - recovery = 0; - } else if (pubkeyStr === recovery1Str) { - recovery = 1; - } else { - throw new Error( - 'Failed to recover V parameter. Bad signature or transaction data.', - ); - } - - // Use the consolidated v parameter conversion logic - const result = convertRecoveryToV(recovery, { - chainId, - useEIP155, - type, - }); - - // Always return BN for consistent interface - convertRecoveryToV returns Buffer for typed txs - if (Buffer.isBuffer(result)) { - // For typed transactions that return recovery value (0 or 1) as buffer - if (result.length === 0) { - return new BN(0); // Empty buffer means 0 - } else { - return new BN(result.toString('hex'), 16); - } - } else { - return result; // Already a BN - } -}; + let chainId: string | undefined + let hash: Uint8Array + let type: string | number | undefined + let useEIP155 = false + + if (Buffer.isBuffer(tx) || typeof tx === 'string') { + const txHex = Buffer.isBuffer(tx) ? (`0x${tx.toString('hex')}` as Hex) : (tx as Hex) + const txBuf = Buffer.isBuffer(tx) ? tx : Buffer.from(tx.slice(2), 'hex') + + hash = Buffer.from(Hash.keccak256(txBuf)) + + try { + const parsedTx = parseTransaction(txHex) + type = parsedTx.type + + if (parsedTx.chainId !== undefined && parsedTx.chainId !== null) { + chainId = parsedTx.chainId.toString() + if (type === 'legacy') { + useEIP155 = true + } + } + + if (type === 'legacy' && !useEIP155) { + const legacyTxArray = RLP.decode(txBuf) + if (legacyTxArray.length >= 9) { + const vBuf = legacyTxArray[6] as Uint8Array + if (vBuf && vBuf.length > 0) { + chainId = new BN(vBuf).toString() + useEIP155 = true + } + } + } + } catch (err) { + console.error('Failed to parse transaction, trying legacy format:', err) + try { + const txBufRaw = Buffer.isBuffer(tx) ? tx : Buffer.from(tx.slice(2), 'hex') + const legacyTxArray = RLP.decode(txBufRaw) + + type = 'legacy' + if (legacyTxArray.length >= 9) { + const vBuf = legacyTxArray[6] as Uint8Array + if (vBuf && vBuf.length > 0) { + chainId = new BN(vBuf).toString() + useEIP155 = true + } + } + } catch { + throw new Error('Could not recover V. Bad transaction data.') + } + } + } else { + throw new Error('Unsupported transaction format. Expected Buffer or hex string.') + } + + const rBuf = Buffer.isBuffer(resp.sig.r) ? resp.sig.r : Buffer.from(resp.sig.r.slice(2), 'hex') + const sBuf = Buffer.isBuffer(resp.sig.s) ? resp.sig.s : Buffer.from(resp.sig.s.slice(2), 'hex') + const rs = new Uint8Array(Buffer.concat([rBuf, sBuf])) + const pubkeyInput = resp.pubkey + + if (!pubkeyInput) { + throw new Error('Response did not include a public key.') + } + + let pubkeyBuf: Buffer + if (Buffer.isBuffer(pubkeyInput)) { + pubkeyBuf = Buffer.from(pubkeyInput) + } else if (pubkeyInput instanceof Uint8Array) { + pubkeyBuf = Buffer.from(pubkeyInput) + } else if (typeof pubkeyInput === 'string') { + const hex = pubkeyInput.startsWith('0x') ? pubkeyInput.slice(2) : pubkeyInput + pubkeyBuf = Buffer.from(hex, 'hex') + } else { + pubkeyBuf = Buffer.from(pubkeyInput) + } + + if (pubkeyBuf.length === 64) { + pubkeyBuf = Buffer.concat([Buffer.from([0x04]), pubkeyBuf]) + } + + const isCompressedPubkey = pubkeyBuf.length === 33 && (pubkeyBuf[0] === 0x02 || pubkeyBuf[0] === 0x03) + const isUncompressedPubkey = pubkeyBuf.length === 65 && pubkeyBuf[0] === 0x04 + + if (!isCompressedPubkey && !isUncompressedPubkey) { + throw new Error('Unsupported public key format returned by device.') + } + + const recovery0 = Buffer.from(ecdsaRecover(rs, 0, hash, isCompressedPubkey)) + const recovery1 = Buffer.from(ecdsaRecover(rs, 1, hash, isCompressedPubkey)) + + const pubkeyStr = pubkeyBuf.toString('hex') + const recovery0Str = recovery0.toString('hex') + const recovery1Str = recovery1.toString('hex') + + let recovery: number + if (pubkeyStr === recovery0Str) { + recovery = 0 + } else if (pubkeyStr === recovery1Str) { + recovery = 1 + } else { + throw new Error('Failed to recover V parameter. Bad signature or transaction data.') + } + + // Use the consolidated v parameter conversion logic + const result = convertRecoveryToV(recovery, { + chainId, + useEIP155, + type, + }) + + // Always return BN for consistent interface - convertRecoveryToV returns Buffer for typed txs + if (Buffer.isBuffer(result)) { + // For typed transactions that return recovery value (0 or 1) as buffer + if (result.length === 0) { + return new BN(0) // Empty buffer means 0 + } else { + return new BN(result.toString('hex'), 16) + } + } else { + return result // Already a BN + } +} /** * Convert a recovery parameter (0/1) to the proper v value format based on transaction type. @@ -870,35 +752,25 @@ export const getV = (tx: any, resp: any) => { * @param txData - Transaction data containing chainId, useEIP155, and type * @returns The properly formatted v value as Buffer or BN */ -export const convertRecoveryToV = ( - recovery: number, - txData: any = {}, -): Buffer | InstanceType => { - const { chainId, useEIP155, type } = txData; - - // For typed transactions (EIP-2930, EIP-1559, EIP-7702), we want the recoveryParam (0 or 1) - // rather than the `v` value because the `chainId` is already included in the - // transaction payload. - if ( - type === 1 || - type === 2 || - type === 4 || - type === 'eip2930' || - type === 'eip1559' || - type === 'eip7702' - ) { - return ensureHexBuffer(recovery, true); // 0 or 1, with 0 expected as an empty buffer - } else if (!useEIP155 || !chainId) { - // For ETH messages and non-EIP155 chains the set should be [27, 28] for `v` - return new BN(recovery).addn(27); - } - - // We will use EIP155 in most cases. Convert recovery to a bignum and operate on it. - // Note that the protocol calls for v = (CHAIN_ID*2) + 35/36, where 35 or 36 - // is decided on based on the ecrecover result. `recovery` is passed in as either 0 or 1 - // so we add 35 to that. - return new BN(chainId).muln(2).addn(35).addn(recovery); -}; +export const convertRecoveryToV = (recovery: number, txData: any = {}): Buffer | InstanceType => { + const { chainId, useEIP155, type } = txData + + // For typed transactions (EIP-2930, EIP-1559, EIP-7702), we want the recoveryParam (0 or 1) + // rather than the `v` value because the `chainId` is already included in the + // transaction payload. + if (type === 1 || type === 2 || type === 4 || type === 'eip2930' || type === 'eip1559' || type === 'eip7702') { + return ensureHexBuffer(recovery, true) // 0 or 1, with 0 expected as an empty buffer + } else if (!useEIP155 || !chainId) { + // For ETH messages and non-EIP155 chains the set should be [27, 28] for `v` + return new BN(recovery).addn(27) + } + + // We will use EIP155 in most cases. Convert recovery to a bignum and operate on it. + // Note that the protocol calls for v = (CHAIN_ID*2) + 35/36, where 35 or 36 + // is decided on based on the ecrecover result. `recovery` is passed in as either 0 or 1 + // so we add 35 to that. + return new BN(chainId).muln(2).addn(35).addn(recovery) +} /** * Get the y-parity value for a signature by recovering the public key. @@ -913,109 +785,85 @@ export const convertRecoveryToV = ( * @param publicKey - Expected public key * @returns 0 or 1 for the y-parity value */ -export const getYParity = ( - messageHash: - | Buffer - | Uint8Array - | string - | { messageHash: any; signature: any; publicKey: any } - | any, - signature?: { r: any; s: any } | any, - publicKey?: Buffer | Uint8Array | string, -): number => { - // Handle legacy object format for backward compatibility - if ( - typeof messageHash === 'object' && - messageHash && - 'messageHash' in messageHash - ) { - return getYParity( - messageHash.messageHash, - messageHash.signature, - messageHash.publicKey, - ); - } - - // Handle legacy transaction format for backward compatibility - if (signature?.sig && signature.pubkey && !publicKey) { - return getYParity(messageHash, signature.sig, signature.pubkey); - } - - // Validate required parameters - if (!signature || !publicKey) { - throw new Error('Response with sig and pubkey required for legacy format'); - } - - if (!signature.r || !signature.s) { - throw new Error('Response with sig and pubkey required for legacy format'); - } - - // Handle transaction objects with getMessageToSign - let hash = messageHash; - if ( - typeof messageHash === 'object' && - messageHash && - typeof messageHash.getMessageToSign === 'function' - ) { - const type = messageHash._type; - if (type !== undefined && type !== null) { - // EIP-1559 / EIP-2930 / future typed transactions - hash = messageHash.getMessageToSign(true); - } else { - // Legacy transaction objects - const preimage = RLP.encode(messageHash.getMessageToSign(false)); - hash = Buffer.from(Hash.keccak256(preimage)); - } - } else if (Buffer.isBuffer(messageHash) && messageHash.length !== 32) { - // If it's a buffer but not 32 bytes, hash it - hash = Buffer.from(Hash.keccak256(messageHash)); - } - - // Normalize inputs to Buffers - const toBuffer = (data: any): Buffer => { - if (!data) throw new Error('Invalid data'); - if (Buffer.isBuffer(data)) return data; - if (data instanceof Uint8Array) return Buffer.from(data); - if (typeof data === 'string') { - return Buffer.from(data.replace(/^0x/i, ''), 'hex'); - } - throw new Error('Invalid data type'); - }; - - const hashBuf = toBuffer(hash); - const rBuf = toBuffer(signature.r); - const sBuf = toBuffer(signature.s); - const pubkeyBuf = toBuffer(publicKey); - - // For non-32 byte hashes, hash them (legacy support) - const finalHash = - hashBuf.length === 32 ? hashBuf : Buffer.from(Hash.keccak256(hashBuf)); - - // Combine r and s - const rs = new Uint8Array(Buffer.concat([rBuf, sBuf])); - const hashBytes = new Uint8Array(finalHash); - const isCompressed = pubkeyBuf.length === 33; - - // Try both recovery values - for (let recovery = 0; recovery <= 1; recovery++) { - try { - const recovered = ecdsaRecover(rs, recovery, hashBytes, isCompressed); - if (Buffer.from(recovered).equals(pubkeyBuf)) { - return recovery; - } - } catch {} - } - - throw new Error( - 'Failed to recover Y parity. Bad signature or transaction data.', - ); -}; +export const getYParity = (messageHash: Buffer | Uint8Array | string | { messageHash: any; signature: any; publicKey: any } | any, signature?: { r: any; s: any } | any, publicKey?: Buffer | Uint8Array | string): number => { + // Handle legacy object format for backward compatibility + if (typeof messageHash === 'object' && messageHash && 'messageHash' in messageHash) { + return getYParity(messageHash.messageHash, messageHash.signature, messageHash.publicKey) + } + + // Handle legacy transaction format for backward compatibility + if (signature?.sig && signature.pubkey && !publicKey) { + return getYParity(messageHash, signature.sig, signature.pubkey) + } + + // Validate required parameters + if (!signature || !publicKey) { + throw new Error('Response with sig and pubkey required for legacy format') + } + + if (!signature.r || !signature.s) { + throw new Error('Response with sig and pubkey required for legacy format') + } + + // Handle transaction objects with getMessageToSign + let hash = messageHash + if (typeof messageHash === 'object' && messageHash && typeof messageHash.getMessageToSign === 'function') { + const type = messageHash._type + if (type !== undefined && type !== null) { + // EIP-1559 / EIP-2930 / future typed transactions + hash = messageHash.getMessageToSign(true) + } else { + // Legacy transaction objects + const preimage = RLP.encode(messageHash.getMessageToSign(false)) + hash = Buffer.from(Hash.keccak256(preimage)) + } + } else if (Buffer.isBuffer(messageHash) && messageHash.length !== 32) { + // If it's a buffer but not 32 bytes, hash it + hash = Buffer.from(Hash.keccak256(messageHash)) + } + + // Normalize inputs to Buffers + const toBuffer = (data: any): Buffer => { + if (!data) throw new Error('Invalid data') + if (Buffer.isBuffer(data)) return data + if (data instanceof Uint8Array) return Buffer.from(data) + if (typeof data === 'string') { + return Buffer.from(data.replace(/^0x/i, ''), 'hex') + } + throw new Error('Invalid data type') + } + + const hashBuf = toBuffer(hash) + const rBuf = toBuffer(signature.r) + const sBuf = toBuffer(signature.s) + const pubkeyBuf = toBuffer(publicKey) + + // For non-32 byte hashes, hash them (legacy support) + const finalHash = hashBuf.length === 32 ? hashBuf : Buffer.from(Hash.keccak256(hashBuf)) + + // Combine r and s + const rs = new Uint8Array(Buffer.concat([rBuf, sBuf])) + const hashBytes = new Uint8Array(finalHash) + const isCompressed = pubkeyBuf.length === 33 + + // Try both recovery values + for (let recovery = 0; recovery <= 1; recovery++) { + try { + const recovered = ecdsaRecover(rs, recovery, hashBytes, isCompressed) + if (Buffer.from(recovered).equals(pubkeyBuf)) { + return recovery + } + } catch {} + } + + throw new Error('Failed to recover Y parity. Bad signature or transaction data.') +} /** @internal */ export const EXTERNAL = { - fetchCalldataDecoder, - generateAppSecret, - getV, - getYParity, - convertRecoveryToV, -}; + fetchCalldataDecoder, + generateAppSecret, + getV, + getYParity, + convertRecoveryToV, +} diff --git a/packages/sdk/tsup.config.ts b/packages/sdk/tsup.config.ts index c4598578..f2a327a9 100644 --- a/packages/sdk/tsup.config.ts +++ b/packages/sdk/tsup.config.ts @@ -1,31 +1,33 @@ -import { readFileSync } from 'node:fs'; -import { dirname, resolve } from 'node:path'; -import { fileURLToPath } from 'node:url'; +import { writeFileSync } from 'node:fs'; import { defineConfig } from 'tsup'; -const __dirname = dirname(fileURLToPath(import.meta.url)); -const pkg = JSON.parse( - readFileSync(resolve(__dirname, 'package.json'), 'utf-8'), -); - -const external = Object.keys({ - ...(pkg.dependencies ?? {}), - ...(pkg.peerDependencies ?? {}), -}); +const esmWrapper = `// ESM wrapper - loads the CJS bundle +import { createRequire } from 'node:module'; +const require = createRequire(import.meta.url); +const cjs = require('./index.cjs'); +export const Calldata = cjs.Calldata; +export const Client = cjs.Client; +export const Constants = cjs.Constants; +export const Utils = cjs.Utils; +export * from './index.cjs'; +export default cjs; +`; export default defineConfig({ entry: ['src/index.ts'], outDir: './dist', - format: ['esm', 'cjs'], + format: ['cjs'], target: 'node20', + platform: 'node', sourcemap: true, clean: true, bundle: true, dts: true, silent: true, - outExtension: ({ format }) => ({ - js: format === 'esm' ? '.mjs' : '.cjs', - }), - external, + noExternal: [/.*/], tsconfig: './tsconfig.build.json', + onSuccess: async () => { + writeFileSync('./dist/index.mjs', esmWrapper); + console.log('Generated ESM wrapper: dist/index.mjs'); + }, }); diff --git a/pnpm-lock.yaml b/pnpm-lock.yaml index 33bc9781..ffec923c 100644 --- a/pnpm-lock.yaml +++ b/pnpm-lock.yaml @@ -271,7 +271,7 @@ importers: version: 7.3.1(@types/node@24.10.4)(jiti@1.21.7)(terser@5.44.1)(tsx@4.21.0) vite-plugin-dts: specifier: ^4.5.4 - version: 4.5.4(@types/node@24.10.4)(rollup@4.55.1)(typescript@5.9.3)(vite@7.3.1(@types/node@24.10.4)(jiti@1.21.7)(terser@5.44.1)(tsx@4.21.0)) + version: 4.5.4(@types/node@24.10.4)(rollup@4.55.3)(typescript@5.9.3)(vite@7.3.1(@types/node@24.10.4)(jiti@1.21.7)(terser@5.44.1)(tsx@4.21.0)) vitest: specifier: 3.2.4 version: 3.2.4(@types/debug@4.1.12)(@types/node@24.10.4)(jiti@1.21.7)(msw@2.12.7(@types/node@24.10.4)(typescript@5.9.3))(terser@5.44.1)(tsx@4.21.0) @@ -2215,128 +2215,128 @@ packages: rollup: optional: true - '@rollup/rollup-android-arm-eabi@4.55.1': - resolution: {integrity: sha512-9R0DM/ykwfGIlNu6+2U09ga0WXeZ9MRC2Ter8jnz8415VbuIykVuc6bhdrbORFZANDmTDvq26mJrEVTl8TdnDg==} + '@rollup/rollup-android-arm-eabi@4.55.3': + resolution: {integrity: sha512-qyX8+93kK/7R5BEXPC2PjUt0+fS/VO2BVHjEHyIEWiYn88rcRBHmdLgoJjktBltgAf+NY7RfCGB1SoyKS/p9kg==} cpu: [arm] os: [android] - '@rollup/rollup-android-arm64@4.55.1': - resolution: {integrity: sha512-eFZCb1YUqhTysgW3sj/55du5cG57S7UTNtdMjCW7LwVcj3dTTcowCsC8p7uBdzKsZYa8J7IDE8lhMI+HX1vQvg==} + '@rollup/rollup-android-arm64@4.55.3': + resolution: {integrity: sha512-6sHrL42bjt5dHQzJ12Q4vMKfN+kUnZ0atHHnv4V0Wd9JMTk7FDzSY35+7qbz3ypQYMBPANbpGK7JpnWNnhGt8g==} cpu: [arm64] os: [android] - '@rollup/rollup-darwin-arm64@4.55.1': - resolution: {integrity: sha512-p3grE2PHcQm2e8PSGZdzIhCKbMCw/xi9XvMPErPhwO17vxtvCN5FEA2mSLgmKlCjHGMQTP6phuQTYWUnKewwGg==} + '@rollup/rollup-darwin-arm64@4.55.3': + resolution: {integrity: sha512-1ht2SpGIjEl2igJ9AbNpPIKzb1B5goXOcmtD0RFxnwNuMxqkR6AUaaErZz+4o+FKmzxcSNBOLrzsICZVNYa1Rw==} cpu: [arm64] os: [darwin] - '@rollup/rollup-darwin-x64@4.55.1': - resolution: {integrity: sha512-rDUjG25C9qoTm+e02Esi+aqTKSBYwVTaoS1wxcN47/Luqef57Vgp96xNANwt5npq9GDxsH7kXxNkJVEsWEOEaQ==} + '@rollup/rollup-darwin-x64@4.55.3': + resolution: {integrity: sha512-FYZ4iVunXxtT+CZqQoPVwPhH7549e/Gy7PIRRtq4t5f/vt54pX6eG9ebttRH6QSH7r/zxAFA4EZGlQ0h0FvXiA==} cpu: [x64] os: [darwin] - '@rollup/rollup-freebsd-arm64@4.55.1': - resolution: {integrity: sha512-+JiU7Jbp5cdxekIgdte0jfcu5oqw4GCKr6i3PJTlXTCU5H5Fvtkpbs4XJHRmWNXF+hKmn4v7ogI5OQPaupJgOg==} + '@rollup/rollup-freebsd-arm64@4.55.3': + resolution: {integrity: sha512-M/mwDCJ4wLsIgyxv2Lj7Len+UMHd4zAXu4GQ2UaCdksStglWhP61U3uowkaYBQBhVoNpwx5Hputo8eSqM7K82Q==} cpu: [arm64] os: [freebsd] - '@rollup/rollup-freebsd-x64@4.55.1': - resolution: {integrity: sha512-V5xC1tOVWtLLmr3YUk2f6EJK4qksksOYiz/TCsFHu/R+woubcLWdC9nZQmwjOAbmExBIVKsm1/wKmEy4z4u4Bw==} + '@rollup/rollup-freebsd-x64@4.55.3': + resolution: {integrity: sha512-5jZT2c7jBCrMegKYTYTpni8mg8y3uY8gzeq2ndFOANwNuC/xJbVAoGKR9LhMDA0H3nIhvaqUoBEuJoICBudFrA==} cpu: [x64] os: [freebsd] - '@rollup/rollup-linux-arm-gnueabihf@4.55.1': - resolution: {integrity: sha512-Rn3n+FUk2J5VWx+ywrG/HGPTD9jXNbicRtTM11e/uorplArnXZYsVifnPPqNNP5BsO3roI4n8332ukpY/zN7rQ==} + '@rollup/rollup-linux-arm-gnueabihf@4.55.3': + resolution: {integrity: sha512-YeGUhkN1oA+iSPzzhEjVPS29YbViOr8s4lSsFaZKLHswgqP911xx25fPOyE9+khmN6W4VeM0aevbDp4kkEoHiA==} cpu: [arm] os: [linux] - '@rollup/rollup-linux-arm-musleabihf@4.55.1': - resolution: {integrity: sha512-grPNWydeKtc1aEdrJDWk4opD7nFtQbMmV7769hiAaYyUKCT1faPRm2av8CX1YJsZ4TLAZcg9gTR1KvEzoLjXkg==} + '@rollup/rollup-linux-arm-musleabihf@4.55.3': + resolution: {integrity: sha512-eo0iOIOvcAlWB3Z3eh8pVM8hZ0oVkK3AjEM9nSrkSug2l15qHzF3TOwT0747omI6+CJJvl7drwZepT+re6Fy/w==} cpu: [arm] os: [linux] - '@rollup/rollup-linux-arm64-gnu@4.55.1': - resolution: {integrity: sha512-a59mwd1k6x8tXKcUxSyISiquLwB5pX+fJW9TkWU46lCqD/GRDe9uDN31jrMmVP3feI3mhAdvcCClhV8V5MhJFQ==} + '@rollup/rollup-linux-arm64-gnu@4.55.3': + resolution: {integrity: sha512-DJay3ep76bKUDImmn//W5SvpjRN5LmK/ntWyeJs/dcnwiiHESd3N4uteK9FDLf0S0W8E6Y0sVRXpOCoQclQqNg==} cpu: [arm64] os: [linux] - '@rollup/rollup-linux-arm64-musl@4.55.1': - resolution: {integrity: sha512-puS1MEgWX5GsHSoiAsF0TYrpomdvkaXm0CofIMG5uVkP6IBV+ZO9xhC5YEN49nsgYo1DuuMquF9+7EDBVYu4uA==} + '@rollup/rollup-linux-arm64-musl@4.55.3': + resolution: {integrity: sha512-BKKWQkY2WgJ5MC/ayvIJTHjy0JUGb5efaHCUiG/39sSUvAYRBaO3+/EK0AZT1RF3pSj86O24GLLik9mAYu0IJg==} cpu: [arm64] os: [linux] - '@rollup/rollup-linux-loong64-gnu@4.55.1': - resolution: {integrity: sha512-r3Wv40in+lTsULSb6nnoudVbARdOwb2u5fpeoOAZjFLznp6tDU8kd+GTHmJoqZ9lt6/Sys33KdIHUaQihFcu7g==} + '@rollup/rollup-linux-loong64-gnu@4.55.3': + resolution: {integrity: sha512-Q9nVlWtKAG7ISW80OiZGxTr6rYtyDSkauHUtvkQI6TNOJjFvpj4gcH+KaJihqYInnAzEEUetPQubRwHef4exVg==} cpu: [loong64] os: [linux] - '@rollup/rollup-linux-loong64-musl@4.55.1': - resolution: {integrity: sha512-MR8c0+UxAlB22Fq4R+aQSPBayvYa3+9DrwG/i1TKQXFYEaoW3B5b/rkSRIypcZDdWjWnpcvxbNaAJDcSbJU3Lw==} + '@rollup/rollup-linux-loong64-musl@4.55.3': + resolution: {integrity: sha512-2H5LmhzrpC4fFRNwknzmmTvvyJPHwESoJgyReXeFoYYuIDfBhP29TEXOkCJE/KxHi27mj7wDUClNq78ue3QEBQ==} cpu: [loong64] os: [linux] - '@rollup/rollup-linux-ppc64-gnu@4.55.1': - resolution: {integrity: sha512-3KhoECe1BRlSYpMTeVrD4sh2Pw2xgt4jzNSZIIPLFEsnQn9gAnZagW9+VqDqAHgm1Xc77LzJOo2LdigS5qZ+gw==} + '@rollup/rollup-linux-ppc64-gnu@4.55.3': + resolution: {integrity: sha512-9S542V0ie9LCTznPYlvaeySwBeIEa7rDBgLHKZ5S9DBgcqdJYburabm8TqiqG6mrdTzfV5uttQRHcbKff9lWtA==} cpu: [ppc64] os: [linux] - '@rollup/rollup-linux-ppc64-musl@4.55.1': - resolution: {integrity: sha512-ziR1OuZx0vdYZZ30vueNZTg73alF59DicYrPViG0NEgDVN8/Jl87zkAPu4u6VjZST2llgEUjaiNl9JM6HH1Vdw==} + '@rollup/rollup-linux-ppc64-musl@4.55.3': + resolution: {integrity: sha512-ukxw+YH3XXpcezLgbJeasgxyTbdpnNAkrIlFGDl7t+pgCxZ89/6n1a+MxlY7CegU+nDgrgdqDelPRNQ/47zs0g==} cpu: [ppc64] os: [linux] - '@rollup/rollup-linux-riscv64-gnu@4.55.1': - resolution: {integrity: sha512-uW0Y12ih2XJRERZ4jAfKamTyIHVMPQnTZcQjme2HMVDAHY4amf5u414OqNYC+x+LzRdRcnIG1YodLrrtA8xsxw==} + '@rollup/rollup-linux-riscv64-gnu@4.55.3': + resolution: {integrity: sha512-Iauw9UsTTvlF++FhghFJjqYxyXdggXsOqGpFBylaRopVpcbfyIIsNvkf9oGwfgIcf57z3m8+/oSYTo6HutBFNw==} cpu: [riscv64] os: [linux] - '@rollup/rollup-linux-riscv64-musl@4.55.1': - resolution: {integrity: sha512-u9yZ0jUkOED1BFrqu3BwMQoixvGHGZ+JhJNkNKY/hyoEgOwlqKb62qu+7UjbPSHYjiVy8kKJHvXKv5coH4wDeg==} + '@rollup/rollup-linux-riscv64-musl@4.55.3': + resolution: {integrity: sha512-3OqKAHSEQXKdq9mQ4eajqUgNIK27VZPW3I26EP8miIzuKzCJ3aW3oEn2pzF+4/Hj/Moc0YDsOtBgT5bZ56/vcA==} cpu: [riscv64] os: [linux] - '@rollup/rollup-linux-s390x-gnu@4.55.1': - resolution: {integrity: sha512-/0PenBCmqM4ZUd0190j7J0UsQ/1nsi735iPRakO8iPciE7BQ495Y6msPzaOmvx0/pn+eJVVlZrNrSh4WSYLxNg==} + '@rollup/rollup-linux-s390x-gnu@4.55.3': + resolution: {integrity: sha512-0CM8dSVzVIaqMcXIFej8zZrSFLnGrAE8qlNbbHfTw1EEPnFTg1U1ekI0JdzjPyzSfUsHWtodilQQG/RA55berA==} cpu: [s390x] os: [linux] - '@rollup/rollup-linux-x64-gnu@4.55.1': - resolution: {integrity: sha512-a8G4wiQxQG2BAvo+gU6XrReRRqj+pLS2NGXKm8io19goR+K8lw269eTrPkSdDTALwMmJp4th2Uh0D8J9bEV1vg==} + '@rollup/rollup-linux-x64-gnu@4.55.3': + resolution: {integrity: sha512-+fgJE12FZMIgBaKIAGd45rxf+5ftcycANJRWk8Vz0NnMTM5rADPGuRFTYar+Mqs560xuART7XsX2lSACa1iOmQ==} cpu: [x64] os: [linux] - '@rollup/rollup-linux-x64-musl@4.55.1': - resolution: {integrity: sha512-bD+zjpFrMpP/hqkfEcnjXWHMw5BIghGisOKPj+2NaNDuVT+8Ds4mPf3XcPHuat1tz89WRL+1wbcxKY3WSbiT7w==} + '@rollup/rollup-linux-x64-musl@4.55.3': + resolution: {integrity: sha512-tMD7NnbAolWPzQlJQJjVFh/fNH3K/KnA7K8gv2dJWCwwnaK6DFCYST1QXYWfu5V0cDwarWC8Sf/cfMHniNq21A==} cpu: [x64] os: [linux] - '@rollup/rollup-openbsd-x64@4.55.1': - resolution: {integrity: sha512-eLXw0dOiqE4QmvikfQ6yjgkg/xDM+MdU9YJuP4ySTibXU0oAvnEWXt7UDJmD4UkYialMfOGFPJnIHSe/kdzPxg==} + '@rollup/rollup-openbsd-x64@4.55.3': + resolution: {integrity: sha512-u5KsqxOxjEeIbn7bUK1MPM34jrnPwjeqgyin4/N6e/KzXKfpE9Mi0nCxcQjaM9lLmPcHmn/xx1yOjgTMtu1jWQ==} cpu: [x64] os: [openbsd] - '@rollup/rollup-openharmony-arm64@4.55.1': - resolution: {integrity: sha512-xzm44KgEP11te3S2HCSyYf5zIzWmx3n8HDCc7EE59+lTcswEWNpvMLfd9uJvVX8LCg9QWG67Xt75AuHn4vgsXw==} + '@rollup/rollup-openharmony-arm64@4.55.3': + resolution: {integrity: sha512-vo54aXwjpTtsAnb3ca7Yxs9t2INZg7QdXN/7yaoG7nPGbOBXYXQY41Km+S1Ov26vzOAzLcAjmMdjyEqS1JkVhw==} cpu: [arm64] os: [openharmony] - '@rollup/rollup-win32-arm64-msvc@4.55.1': - resolution: {integrity: sha512-yR6Bl3tMC/gBok5cz/Qi0xYnVbIxGx5Fcf/ca0eB6/6JwOY+SRUcJfI0OpeTpPls7f194as62thCt/2BjxYN8g==} + '@rollup/rollup-win32-arm64-msvc@4.55.3': + resolution: {integrity: sha512-HI+PIVZ+m+9AgpnY3pt6rinUdRYrGHvmVdsNQ4odNqQ/eRF78DVpMR7mOq7nW06QxpczibwBmeQzB68wJ+4W4A==} cpu: [arm64] os: [win32] - '@rollup/rollup-win32-ia32-msvc@4.55.1': - resolution: {integrity: sha512-3fZBidchE0eY0oFZBnekYCfg+5wAB0mbpCBuofh5mZuzIU/4jIVkbESmd2dOsFNS78b53CYv3OAtwqkZZmU5nA==} + '@rollup/rollup-win32-ia32-msvc@4.55.3': + resolution: {integrity: sha512-vRByotbdMo3Wdi+8oC2nVxtc3RkkFKrGaok+a62AT8lz/YBuQjaVYAS5Zcs3tPzW43Vsf9J0wehJbUY5xRSekA==} cpu: [ia32] os: [win32] - '@rollup/rollup-win32-x64-gnu@4.55.1': - resolution: {integrity: sha512-xGGY5pXj69IxKb4yv/POoocPy/qmEGhimy/FoTpTSVju3FYXUQQMFCaZZXJVidsmGxRioZAwpThl/4zX41gRKg==} + '@rollup/rollup-win32-x64-gnu@4.55.3': + resolution: {integrity: sha512-POZHq7UeuzMJljC5NjKi8vKMFN6/5EOqcX1yGntNLp7rUTpBAXQ1hW8kWPFxYLv07QMcNM75xqVLGPWQq6TKFA==} cpu: [x64] os: [win32] - '@rollup/rollup-win32-x64-msvc@4.55.1': - resolution: {integrity: sha512-SPEpaL6DX4rmcXtnhdrQYgzQ5W2uW3SCJch88lB2zImhJRhIIK44fkUrgIV/Q8yUNfw5oyZ5vkeQsZLhCb06lw==} + '@rollup/rollup-win32-x64-msvc@4.55.3': + resolution: {integrity: sha512-aPFONczE4fUFKNXszdvnd2GqKEYQdV5oEsIbKPujJmWlCI9zEsv1Otig8RKK+X9bed9gFUN6LAeN4ZcNuu4zjg==} cpu: [x64] os: [win32] @@ -6885,8 +6885,8 @@ packages: engines: {node: '>=14.18.0', npm: '>=8.0.0'} hasBin: true - rollup@4.55.1: - resolution: {integrity: sha512-wDv/Ht1BNHB4upNbK74s9usvl7hObDnvVzknxqY/E/O3X6rW1U1rV1aENEfJ54eFZDTNo7zv1f5N4edCluH7+A==} + rollup@4.55.3: + resolution: {integrity: sha512-y9yUpfQvetAjiDLtNMf1hL9NXchIJgWt6zIKeoB+tCd3npX08Eqfzg60V9DhIGVMtQ0AlMkFw5xa+AQ37zxnAA==} engines: {node: '>=18.0.0', npm: '>=8.0.0'} hasBin: true @@ -10852,87 +10852,87 @@ snapshots: - supports-color optional: true - '@rollup/pluginutils@5.3.0(rollup@4.55.1)': + '@rollup/pluginutils@5.3.0(rollup@4.55.3)': dependencies: '@types/estree': 1.0.8 estree-walker: 2.0.2 picomatch: 4.0.3 optionalDependencies: - rollup: 4.55.1 + rollup: 4.55.3 - '@rollup/rollup-android-arm-eabi@4.55.1': + '@rollup/rollup-android-arm-eabi@4.55.3': optional: true - '@rollup/rollup-android-arm64@4.55.1': + '@rollup/rollup-android-arm64@4.55.3': optional: true - '@rollup/rollup-darwin-arm64@4.55.1': + '@rollup/rollup-darwin-arm64@4.55.3': optional: true - '@rollup/rollup-darwin-x64@4.55.1': + '@rollup/rollup-darwin-x64@4.55.3': optional: true - '@rollup/rollup-freebsd-arm64@4.55.1': + '@rollup/rollup-freebsd-arm64@4.55.3': optional: true - '@rollup/rollup-freebsd-x64@4.55.1': + '@rollup/rollup-freebsd-x64@4.55.3': optional: true - '@rollup/rollup-linux-arm-gnueabihf@4.55.1': + '@rollup/rollup-linux-arm-gnueabihf@4.55.3': optional: true - '@rollup/rollup-linux-arm-musleabihf@4.55.1': + '@rollup/rollup-linux-arm-musleabihf@4.55.3': optional: true - '@rollup/rollup-linux-arm64-gnu@4.55.1': + '@rollup/rollup-linux-arm64-gnu@4.55.3': optional: true - '@rollup/rollup-linux-arm64-musl@4.55.1': + '@rollup/rollup-linux-arm64-musl@4.55.3': optional: true - '@rollup/rollup-linux-loong64-gnu@4.55.1': + '@rollup/rollup-linux-loong64-gnu@4.55.3': optional: true - '@rollup/rollup-linux-loong64-musl@4.55.1': + '@rollup/rollup-linux-loong64-musl@4.55.3': optional: true - '@rollup/rollup-linux-ppc64-gnu@4.55.1': + '@rollup/rollup-linux-ppc64-gnu@4.55.3': optional: true - '@rollup/rollup-linux-ppc64-musl@4.55.1': + '@rollup/rollup-linux-ppc64-musl@4.55.3': optional: true - '@rollup/rollup-linux-riscv64-gnu@4.55.1': + '@rollup/rollup-linux-riscv64-gnu@4.55.3': optional: true - '@rollup/rollup-linux-riscv64-musl@4.55.1': + '@rollup/rollup-linux-riscv64-musl@4.55.3': optional: true - '@rollup/rollup-linux-s390x-gnu@4.55.1': + '@rollup/rollup-linux-s390x-gnu@4.55.3': optional: true - '@rollup/rollup-linux-x64-gnu@4.55.1': + '@rollup/rollup-linux-x64-gnu@4.55.3': optional: true - '@rollup/rollup-linux-x64-musl@4.55.1': + '@rollup/rollup-linux-x64-musl@4.55.3': optional: true - '@rollup/rollup-openbsd-x64@4.55.1': + '@rollup/rollup-openbsd-x64@4.55.3': optional: true - '@rollup/rollup-openharmony-arm64@4.55.1': + '@rollup/rollup-openharmony-arm64@4.55.3': optional: true - '@rollup/rollup-win32-arm64-msvc@4.55.1': + '@rollup/rollup-win32-arm64-msvc@4.55.3': optional: true - '@rollup/rollup-win32-ia32-msvc@4.55.1': + '@rollup/rollup-win32-ia32-msvc@4.55.3': optional: true - '@rollup/rollup-win32-x64-gnu@4.55.1': + '@rollup/rollup-win32-x64-gnu@4.55.3': optional: true - '@rollup/rollup-win32-x64-msvc@4.55.1': + '@rollup/rollup-win32-x64-msvc@4.55.3': optional: true '@rushstack/node-core-library@5.19.1(@types/node@24.10.4)': @@ -13454,7 +13454,7 @@ snapshots: dependencies: magic-string: 0.30.21 mlly: 1.8.0 - rollup: 4.55.1 + rollup: 4.55.3 flat@5.0.2: {} @@ -16634,35 +16634,35 @@ snapshots: optionalDependencies: fsevents: 2.3.3 - rollup@4.55.1: + rollup@4.55.3: dependencies: '@types/estree': 1.0.8 optionalDependencies: - '@rollup/rollup-android-arm-eabi': 4.55.1 - '@rollup/rollup-android-arm64': 4.55.1 - '@rollup/rollup-darwin-arm64': 4.55.1 - '@rollup/rollup-darwin-x64': 4.55.1 - '@rollup/rollup-freebsd-arm64': 4.55.1 - '@rollup/rollup-freebsd-x64': 4.55.1 - '@rollup/rollup-linux-arm-gnueabihf': 4.55.1 - '@rollup/rollup-linux-arm-musleabihf': 4.55.1 - '@rollup/rollup-linux-arm64-gnu': 4.55.1 - '@rollup/rollup-linux-arm64-musl': 4.55.1 - '@rollup/rollup-linux-loong64-gnu': 4.55.1 - '@rollup/rollup-linux-loong64-musl': 4.55.1 - '@rollup/rollup-linux-ppc64-gnu': 4.55.1 - '@rollup/rollup-linux-ppc64-musl': 4.55.1 - '@rollup/rollup-linux-riscv64-gnu': 4.55.1 - '@rollup/rollup-linux-riscv64-musl': 4.55.1 - '@rollup/rollup-linux-s390x-gnu': 4.55.1 - '@rollup/rollup-linux-x64-gnu': 4.55.1 - '@rollup/rollup-linux-x64-musl': 4.55.1 - '@rollup/rollup-openbsd-x64': 4.55.1 - '@rollup/rollup-openharmony-arm64': 4.55.1 - '@rollup/rollup-win32-arm64-msvc': 4.55.1 - '@rollup/rollup-win32-ia32-msvc': 4.55.1 - '@rollup/rollup-win32-x64-gnu': 4.55.1 - '@rollup/rollup-win32-x64-msvc': 4.55.1 + '@rollup/rollup-android-arm-eabi': 4.55.3 + '@rollup/rollup-android-arm64': 4.55.3 + '@rollup/rollup-darwin-arm64': 4.55.3 + '@rollup/rollup-darwin-x64': 4.55.3 + '@rollup/rollup-freebsd-arm64': 4.55.3 + '@rollup/rollup-freebsd-x64': 4.55.3 + '@rollup/rollup-linux-arm-gnueabihf': 4.55.3 + '@rollup/rollup-linux-arm-musleabihf': 4.55.3 + '@rollup/rollup-linux-arm64-gnu': 4.55.3 + '@rollup/rollup-linux-arm64-musl': 4.55.3 + '@rollup/rollup-linux-loong64-gnu': 4.55.3 + '@rollup/rollup-linux-loong64-musl': 4.55.3 + '@rollup/rollup-linux-ppc64-gnu': 4.55.3 + '@rollup/rollup-linux-ppc64-musl': 4.55.3 + '@rollup/rollup-linux-riscv64-gnu': 4.55.3 + '@rollup/rollup-linux-riscv64-musl': 4.55.3 + '@rollup/rollup-linux-s390x-gnu': 4.55.3 + '@rollup/rollup-linux-x64-gnu': 4.55.3 + '@rollup/rollup-linux-x64-musl': 4.55.3 + '@rollup/rollup-openbsd-x64': 4.55.3 + '@rollup/rollup-openharmony-arm64': 4.55.3 + '@rollup/rollup-win32-arm64-msvc': 4.55.3 + '@rollup/rollup-win32-ia32-msvc': 4.55.3 + '@rollup/rollup-win32-x64-gnu': 4.55.3 + '@rollup/rollup-win32-x64-msvc': 4.55.3 fsevents: 2.3.3 rpc-websockets@9.3.2: @@ -17296,7 +17296,7 @@ snapshots: picocolors: 1.1.1 postcss-load-config: 6.0.1(jiti@1.21.7)(postcss@8.5.6)(tsx@4.21.0) resolve-from: 5.0.0 - rollup: 4.55.1 + rollup: 4.55.3 source-map: 0.7.6 sucrase: 3.35.1 tinyexec: 0.3.2 @@ -17661,10 +17661,10 @@ snapshots: - tsx - yaml - vite-plugin-dts@4.5.4(@types/node@24.10.4)(rollup@4.55.1)(typescript@5.9.3)(vite@7.3.1(@types/node@24.10.4)(jiti@1.21.7)(terser@5.44.1)(tsx@4.21.0)): + vite-plugin-dts@4.5.4(@types/node@24.10.4)(rollup@4.55.3)(typescript@5.9.3)(vite@7.3.1(@types/node@24.10.4)(jiti@1.21.7)(terser@5.44.1)(tsx@4.21.0)): dependencies: '@microsoft/api-extractor': 7.55.2(@types/node@24.10.4) - '@rollup/pluginutils': 5.3.0(rollup@4.55.1) + '@rollup/pluginutils': 5.3.0(rollup@4.55.3) '@volar/typescript': 2.4.27 '@vue/language-core': 2.2.0(typescript@5.9.3) compare-versions: 6.1.1 @@ -17696,7 +17696,7 @@ snapshots: fdir: 6.5.0(picomatch@4.0.3) picomatch: 4.0.3 postcss: 8.5.6 - rollup: 4.55.1 + rollup: 4.55.3 tinyglobby: 0.2.15 optionalDependencies: '@types/node': 24.10.4 From 32a895d0376787c9313292a3fe17ee2a83a159b4 Mon Sep 17 00:00:00 2001 From: netbonus <151201453+netbonus@users.noreply.github.com> Date: Wed, 21 Jan 2026 13:03:35 -0500 Subject: [PATCH 03/14] fix: resolve all TypeScript errors across monorepo - docs: fix tsconfig include path and add Layout props interface - example: update imports to use workspace SDK, add component prop types - sdk: add type assertions for currency literals and getAddresses returns - sdk: add curveType/hashType to ETH_MSG signing requests in tests - sdk: use public getFwVersion() getter instead of private property --- packages/docs/src/pages/_index.tsx | 13 +- packages/docs/tsconfig.json | 7 +- packages/example/package.json | 4 +- packages/example/src/App.tsx | 16 +- packages/example/src/Button.tsx | 9 +- packages/example/src/Lattice.tsx | 45 +-- packages/sdk/src/__test__/e2e/general.test.ts | 24 +- packages/sdk/src/__test__/e2e/kv.test.ts | 9 +- .../src/__test__/e2e/non-exportable.test.ts | 17 +- .../sdk/src/__test__/e2e/signing/bls.test.ts | 15 +- .../__test__/e2e/signing/determinism.test.ts | 46 +-- .../__test__/e2e/signing/unformatted.test.ts | 8 +- packages/sdk/src/__test__/utils/builders.ts | 6 +- packages/sdk/src/api/index.ts | 2 + pnpm-lock.yaml | 376 +----------------- 15 files changed, 122 insertions(+), 475 deletions(-) diff --git a/packages/docs/src/pages/_index.tsx b/packages/docs/src/pages/_index.tsx index 878a94a2..47df9c05 100644 --- a/packages/docs/src/pages/_index.tsx +++ b/packages/docs/src/pages/_index.tsx @@ -1,9 +1,15 @@ +import type React from 'react' import Link from '@docusaurus/Link' import useDocusaurusContext from '@docusaurus/useDocusaurusContext' -import Layout from '@theme/Layout' +import Layout, { type Props as LayoutProps } from '@theme/Layout' import clsx from 'clsx' import styles from './index.module.css' +interface ExtendedLayoutProps extends LayoutProps { + title?: string + description?: string +} + function HomepageHeader() { const { siteConfig } = useDocusaurusContext() return ( @@ -23,10 +29,11 @@ function HomepageHeader() { export default function Home(): JSX.Element { const { siteConfig } = useDocusaurusContext() + const ExtendedLayout = Layout as React.ComponentType return ( - +
{/* */}
-
+ ) } diff --git a/packages/docs/tsconfig.json b/packages/docs/tsconfig.json index 203c4455..d1970d23 100644 --- a/packages/docs/tsconfig.json +++ b/packages/docs/tsconfig.json @@ -2,8 +2,9 @@ "extends": "@tsconfig/docusaurus/tsconfig.json", "compilerOptions": { "baseUrl": ".", - "skipLibCheck": true + "skipLibCheck": true, + "jsx": "react-jsx", + "types": ["node", "@docusaurus/module-type-aliases"] }, - "include": ["../src"], - "types": ["node", "jest", "vitest", "vitest/globals"] + "include": ["src"] } diff --git a/packages/example/package.json b/packages/example/package.json index eaac56ed..417bbfb3 100644 --- a/packages/example/package.json +++ b/packages/example/package.json @@ -13,7 +13,7 @@ "@ethereumjs/common": "^3.0.2", "@ethereumjs/tx": "^4.0.2", "buffer": "^6.0.3", - "gridplus-sdk": "^2.4.3", + "gridplus-sdk": "workspace:*", "react": "^18.2.0", "react-dom": "^18.2.0" }, @@ -21,7 +21,7 @@ "@types/react": "^18.0.26", "@types/react-dom": "^18.0.9", "@vitejs/plugin-react": "^3.0.0", - "typescript": "^4.9.3", + "typescript": "^5.0.0", "vite": "^4.0.0" } } diff --git a/packages/example/src/App.tsx b/packages/example/src/App.tsx index 0598ed99..efe52d4e 100644 --- a/packages/example/src/App.tsx +++ b/packages/example/src/App.tsx @@ -1,25 +1,29 @@ import { useCallback, useEffect, useState } from 'react' -import { getClient, pair, setup } from '../../src/api/index' +import { getClient, pair, setup } from 'gridplus-sdk' import './App.css' import { Lattice } from './Lattice' function App() { const [label, setLabel] = useState('No Device') - const getStoredClient = useCallback(() => window.localStorage.getItem('storedClient') || '', []) + const getStoredClient = useCallback(async () => window.localStorage.getItem('storedClient') || '', []) - const setStoredClient = useCallback((storedClient: string | null) => { + const setStoredClient = useCallback(async (storedClient: string | null) => { if (!storedClient) return window.localStorage.setItem('storedClient', storedClient) - const client = getClient() + const client = await getClient() setLabel(client?.getDeviceId() || 'No Device') }, []) useEffect(() => { - if (getStoredClient()) { - setup({ getStoredClient, setStoredClient }) + const initClient = async () => { + const storedClient = await getStoredClient() + if (storedClient) { + await setup({ getStoredClient, setStoredClient }) + } } + initClient() }, [getStoredClient, setStoredClient]) const submitInit = (e: any) => { diff --git a/packages/example/src/Button.tsx b/packages/example/src/Button.tsx index ac557717..66a0147c 100644 --- a/packages/example/src/Button.tsx +++ b/packages/example/src/Button.tsx @@ -1,6 +1,11 @@ -import { useState } from 'react' +import { type ReactNode, useState } from 'react' -export const Button = ({ onClick, children }) => { +interface ButtonProps { + onClick: () => Promise + children: ReactNode +} + +export const Button = ({ onClick, children }: ButtonProps) => { const [isLoading, setIsLoading] = useState(false) const handleOnClick = () => { diff --git a/packages/example/src/Lattice.tsx b/packages/example/src/Lattice.tsx index 66262b1f..5e7d1850 100644 --- a/packages/example/src/Lattice.tsx +++ b/packages/example/src/Lattice.tsx @@ -1,33 +1,20 @@ -import { Chain, Common, Hardfork } from '@ethereumjs/common' -import { TransactionFactory } from '@ethereumjs/tx' import { useState } from 'react' -import { addAddressTags, fetchAddressTags, fetchAddresses, fetchLedgerLiveAddresses, removeAddressTags, sign, signMessage } from '../../src/api' +import { addAddressTags, fetchAddressTags, fetchAddresses, fetchLedgerLiveAddresses, removeAddressTags, sign, signMessage, type AddressTag } from 'gridplus-sdk' import { Button } from './Button' -export const Lattice = ({ label }) => { +interface LatticeProps { + label: string +} + +export const Lattice = ({ label }: LatticeProps) => { const [addresses, setAddresses] = useState([]) - const [addressTags, setAddressTags] = useState<{ id: string }[]>([]) - const [ledgerAddresses, setLedgerAddresses] = useState([]) + const [addressTags, setAddressTags] = useState([]) + const [ledgerAddresses, setLedgerAddresses] = useState([]) - const getTxPayload = () => { - const txData = { - type: 1, - maxFeePerGas: 1200000000, - maxPriorityFeePerGas: 1200000000, - nonce: 0, - gasLimit: 50000, - to: '0xe242e54155b1abc71fc118065270cecaaf8b7768', - value: 1000000000000, - data: '0x17e914679b7e160613be4f8c2d3203d236286d74eb9192f6d6f71b9118a42bb033ccd8e8', - gasPrice: 1200000000, - } - const common = new Common({ - chain: Chain.Mainnet, - hardfork: Hardfork.London, - }) - const tx = TransactionFactory.fromTxData(txData, { common }) - const payload = tx.getMessageToSign(false) - return payload + // Example EIP-1559 transaction payload using raw hex format + const getTxPayload = (): `0x${string}` => { + // Pre-serialized EIP-1559 transaction for example purposes + return '0x02f8620180843b9aca00843b9aca0082c350940000000000000000000000000000000000000000880de0b6b3a764000080c0' } return ( @@ -41,8 +28,8 @@ export const Lattice = ({ label }) => { }} >

{label}

- - + +

Addresses

@@ -89,7 +76,7 @@ export const Lattice = ({ label }) => {

Address Tags

    - {addressTags?.map((tag: any) => ( + {addressTags?.map((tag) => (
  • {tag.key}: {tag.val}
  • @@ -100,7 +87,7 @@ export const Lattice = ({ label }) => {

    Ledger Addresses

      - {ledgerAddresses?.map((ledgerAddress: any) => ( + {ledgerAddresses?.map((ledgerAddress) => (
    • {ledgerAddress}
    • ))}
    diff --git a/packages/sdk/src/__test__/e2e/general.test.ts b/packages/sdk/src/__test__/e2e/general.test.ts index 7629fc82..c4899dfd 100644 --- a/packages/sdk/src/__test__/e2e/general.test.ts +++ b/packages/sdk/src/__test__/e2e/general.test.ts @@ -69,7 +69,7 @@ describe('General', () => { // Bitcoin addresses // NOTE: The format of address will be based on the user's Lattice settings // By default, this will be P2SH(P2WPKH), i.e. addresses that start with `3` - addrs = await client.getAddresses(addrData) + addrs = (await client.getAddresses(addrData)) as string[] expect(addrs.length).toEqual(5) expect(addrs[0]?.[0]).toEqual('3') @@ -77,7 +77,7 @@ describe('General', () => { addrData.startPath[0] = BTC_PURPOSE_P2PKH addrData.startPath[1] = ETH_COIN addrData.n = 1 - addrs = await client.getAddresses(addrData) + addrs = (await client.getAddresses(addrData)) as string[] expect(addrs.length).toEqual(1) expect(addrs[0]?.slice(0, 2)).toEqual('0x') // If firmware supports it, try shorter paths @@ -86,7 +86,7 @@ describe('General', () => { startPath: [BTC_PURPOSE_P2PKH, ETH_COIN, HARDENED_OFFSET, 0], n: 1, } - addrs = await client.getAddresses(flexData) + addrs = (await client.getAddresses(flexData)) as string[] expect(addrs.length).toEqual(1) expect(addrs[0]?.slice(0, 2)).toEqual('0x') } @@ -102,7 +102,7 @@ describe('General', () => { // Switch to BTC coin. Should work now. addrData.startPath[1] = BTC_COIN // Bech32 - addrs = await client.getAddresses(addrData) + addrs = (await client.getAddresses(addrData)) as string[] expect(addrs.length).toEqual(1) expect(addrs[0]?.slice(0, 3)).to.be.oneOf(['bc1']) addrData.startPath[0] = BTC_PURPOSE_P2SH_P2WPKH @@ -110,7 +110,7 @@ describe('General', () => { addrData.startPath[4] = 1000000 addrData.n = 3 - addrs = await client.getAddresses(addrData) + addrs = (await client.getAddresses(addrData)) as string[] expect(addrs.length).toEqual(addrData.n) addrData.startPath[4] = 0 addrData.n = 1 @@ -118,7 +118,7 @@ describe('General', () => { // Unsupported purpose (m//) addrData.startPath[0] = 0 // Purpose 0 -- undefined try { - addrs = await client.getAddresses(addrData) + addrs = (await client.getAddresses(addrData)) as string[] } catch (err: any) { expect(err.message).not.toEqual(null) } @@ -127,7 +127,7 @@ describe('General', () => { // Unsupported currency addrData.startPath[1] = HARDENED_OFFSET + 5 // 5' currency - aka unknown try { - addrs = await client.getAddresses(addrData) + addrs = (await client.getAddresses(addrData)) as string[] throw new Error(null) } catch (err: any) { expect(err.message).not.toEqual(null) @@ -136,7 +136,7 @@ describe('General', () => { // Too many addresses (n>10) addrData.n = 11 try { - addrs = await client.getAddresses(addrData) + addrs = (await client.getAddresses(addrData)) as string[] throw new Error(null) } catch (err: any) { expect(err.message).not.toEqual(null) @@ -200,7 +200,7 @@ describe('General', () => { changePath: [BTC_PURPOSE_P2SH_P2WPKH, BTC_TESTNET_COIN, HARDENED_OFFSET, 1, 0], } const req = { - currency: 'BTC', + currency: 'BTC' as const, data: txData, } @@ -226,7 +226,7 @@ describe('General', () => { changePath: [BTC_PURPOSE_P2SH_P2WPKH, BTC_TESTNET_COIN, HARDENED_OFFSET, 1, 0], } const req = { - currency: 'BTC', + currency: 'BTC' as const, data: txData, } // Sign a legit tx @@ -251,7 +251,7 @@ describe('General', () => { changePath: [BTC_PURPOSE_P2SH_P2WPKH, BTC_TESTNET_COIN, HARDENED_OFFSET, 1, 0], } const req = { - currency: 'BTC', + currency: 'BTC' as const, data: txData, } // Sign a legit tx @@ -277,7 +277,7 @@ describe('General', () => { changePath: [BTC_PURPOSE_P2WPKH, BTC_TESTNET_COIN, HARDENED_OFFSET, 1, 0], } const req = { - currency: 'BTC', + currency: 'BTC' as const, data: txData, } // Sign a legit tx diff --git a/packages/sdk/src/__test__/e2e/kv.test.ts b/packages/sdk/src/__test__/e2e/kv.test.ts index 8b81fb58..400bb11a 100644 --- a/packages/sdk/src/__test__/e2e/kv.test.ts +++ b/packages/sdk/src/__test__/e2e/kv.test.ts @@ -11,6 +11,7 @@ import { BTC_PURPOSE_P2PKH, ETH_COIN } from '../utils/helpers' import { setupClient } from '../utils/setup' import type { Client } from '../../client' +import type { SignRequestParams } from '../../types' // Random address to test the screen with. // IMPORTANT NOTE: For Ethereum addresses you should always add the lower case variety since @@ -89,7 +90,7 @@ describe('key-value', () => { }) it('Should make a request to an unknown address', async () => { - await client.sign(ETH_REQ).catch((err) => { + await client.sign(ETH_REQ as unknown as SignRequestParams).catch((err) => { expect(err.message).toContain(ProtocolConstants.responseMsg[LatticeResponseCode.userDeclined]) }) }) @@ -135,7 +136,7 @@ describe('key-value', () => { }) it('Should make a request to an address which is now known', async () => { - await client.sign(ETH_REQ) + await client.sign(ETH_REQ as unknown as SignRequestParams) }) it('Should make an EIP712 request that uses the record', async () => { @@ -168,7 +169,7 @@ describe('key-value', () => { payload: msg, }, } - await client.sign(req) + await client.sign(req as unknown as SignRequestParams) }) it('Should make a request with calldata', async () => { @@ -210,7 +211,7 @@ describe('key-value', () => { }) it('Should make another request to make sure case sensitivity is enforced', async () => { - await client.sign(ETH_REQ).catch((err) => { + await client.sign(ETH_REQ as unknown as SignRequestParams).catch((err) => { expect(err.message).toContain(ProtocolConstants.responseMsg[LatticeResponseCode.userDeclined]) }) }) diff --git a/packages/sdk/src/__test__/e2e/non-exportable.test.ts b/packages/sdk/src/__test__/e2e/non-exportable.test.ts index 82dd62be..1a700a35 100644 --- a/packages/sdk/src/__test__/e2e/non-exportable.test.ts +++ b/packages/sdk/src/__test__/e2e/non-exportable.test.ts @@ -28,6 +28,7 @@ import { DEFAULT_SIGNER } from '../utils/builders' import { getSigStr, validateSig } from '../utils/helpers' import { setupClient } from '../utils/setup' import type { Client } from '../../client' +import type { SignRequestParams } from '../../types' let runTests = true @@ -131,20 +132,22 @@ describe('Non-Exportable Seed', () => { it('Should test that ETH message sigs differ and validate on secp256k1', async () => { // Validate that signPersonal message sigs are non-uniform const msgReq = { - currency: 'ETH_MSG', + currency: 'ETH_MSG' as const, data: { signerPath: DEFAULT_SIGNER, - protocol: 'signPersonal', + protocol: 'signPersonal' as const, payload: 'test message', + curveType: Constants.SIGNING.CURVES.SECP256K1, + hashType: Constants.SIGNING.HASHES.KECCAK256, }, } // NOTE: This uses the legacy signing pathway, which validates the signature // Once we move this to generic signing, we will need to validate these. - const msg1Resp = await client.sign(msgReq) - const msg2Resp = await client.sign(msgReq) - const msg3Resp = await client.sign(msgReq) - const msg4Resp = await client.sign(msgReq) - const msg5Resp = await client.sign(msgReq) + const msg1Resp = await client.sign(msgReq as unknown as SignRequestParams) + const msg2Resp = await client.sign(msgReq as unknown as SignRequestParams) + const msg3Resp = await client.sign(msgReq as unknown as SignRequestParams) + const msg4Resp = await client.sign(msgReq as unknown as SignRequestParams) + const msg5Resp = await client.sign(msgReq as unknown as SignRequestParams) // Check sig 1 expect(getSigStr(msg1Resp)).not.toEqual(getSigStr(msg2Resp)) expect(getSigStr(msg1Resp)).not.toEqual(getSigStr(msg3Resp)) diff --git a/packages/sdk/src/__test__/e2e/signing/bls.test.ts b/packages/sdk/src/__test__/e2e/signing/bls.test.ts index 867a5fa5..7699dda2 100644 --- a/packages/sdk/src/__test__/e2e/signing/bls.test.ts +++ b/packages/sdk/src/__test__/e2e/signing/bls.test.ts @@ -50,17 +50,17 @@ describe('[BLS keys]', () => { } // Check if firmware supports BLS (requires >= 0.17.0) - const fwVersion = client.fwVersion - const versionStr = fwVersion && fwVersion.length >= 3 ? `${fwVersion[2]}.${fwVersion[1]}.${fwVersion[0]}` : 'unknown' + const fwVersion = client.getFwVersion() + const versionStr = fwVersion ? `${fwVersion.major}.${fwVersion.minor}.${fwVersion.fix}` : 'unknown' console.log(`\n[BLS Test] Firmware version: ${versionStr}`) - console.log('[BLS Test] Raw fwVersion buffer:', fwVersion) + console.log('[BLS Test] Raw fwVersion:', fwVersion) const fwConstants = client.getFwConstants() console.log('[BLS Test] getAddressFlags:', fwConstants?.getAddressFlags) console.log('[BLS Test] BLS12_381_G1_PUB constant:', Constants.GET_ADDR_FLAGS.BLS12_381_G1_PUB) - supportsBLS = fwConstants?.getAddressFlags?.includes(Constants.GET_ADDR_FLAGS.BLS12_381_G1_PUB) + supportsBLS = fwConstants?.getAddressFlags?.includes(Constants.GET_ADDR_FLAGS.BLS12_381_G1_PUB as number) console.log(`[BLS Test] supportsBLS: ${supportsBLS}\n`) @@ -127,11 +127,12 @@ describe('[BLS keys]', () => { //========================================================= // INTERNAL HELPERS //========================================================= -async function getBLSPub(startPath) { +async function getBLSPub(startPath: number[]) { const pubs = await client.getAddresses({ startPath, + n: 1, flag: Constants.GET_ADDR_FLAGS.BLS12_381_G1_PUB, - }) + } as Parameters[0]) return pubs[0] } @@ -159,7 +160,7 @@ async function testBLSDerivationAndSig(seed, signerPath) { const refSigStr = Buffer.from(refSig).toString('hex') expect(latticePub.toString('hex')).to.equal(refPubStr, 'Deposit public key mismatch') expect(latticeSig.pubkey.toString('hex')).to.equal(refPubStr, 'Lattice signature returned wrong pubkey') - expect(latticeSig.sig.toString('hex')).to.equal(refSigStr, 'Signature mismatch') + expect(Buffer.from(latticeSig.sig as unknown as Buffer).toString('hex')).to.equal(refSigStr, 'Signature mismatch') } async function validateExportedKeystore(seed, path, pw, expKeystoreBuffer) { const exportedKeystore = JSON.parse(expKeystoreBuffer.toString()) diff --git a/packages/sdk/src/__test__/e2e/signing/determinism.test.ts b/packages/sdk/src/__test__/e2e/signing/determinism.test.ts index 6ce538eb..285dd818 100644 --- a/packages/sdk/src/__test__/e2e/signing/determinism.test.ts +++ b/packages/sdk/src/__test__/e2e/signing/determinism.test.ts @@ -1,5 +1,5 @@ import { HARDENED_OFFSET } from '../../../constants' -import type { WalletPath } from '../../../types' +import type { SignRequestParams, WalletPath } from '../../../types' import { randomBytes } from '../../../util' import { DEFAULT_SIGNER, buildMsgReq, buildRandomVectors, buildTx, buildTxReq } from '../../utils/builders' import { deriveAddress, signEip712JS, signPersonalJS, testUniformSigs } from '../../utils/determinism' @@ -47,13 +47,13 @@ describe('[Determinism]', () => { n: 1, } const latAddr0 = await client.getAddresses(req) - expect(latAddr0[0].toLowerCase()).toEqualElseLog(addr0.toLowerCase(), 'Incorrect address 0 fetched. Ensure your SafeCard is loaded with the test mnemonic: "test test test test test test test test test test test junk"') + expect((latAddr0[0] as string).toLowerCase()).toEqualElseLog(addr0.toLowerCase(), 'Incorrect address 0 fetched. Ensure your SafeCard is loaded with the test mnemonic: "test test test test test test test test test test test junk"') req.startPath = path1 const latAddr1 = await client.getAddresses(req) - expect(latAddr1[0].toLowerCase()).toEqualElseLog(addr1.toLowerCase(), 'Incorrect address 1 fetched. Ensure your SafeCard is loaded with the test mnemonic: "test test test test test test test test test test test junk"') + expect((latAddr1[0] as string).toLowerCase()).toEqualElseLog(addr1.toLowerCase(), 'Incorrect address 1 fetched. Ensure your SafeCard is loaded with the test mnemonic: "test test test test test test test test test test test junk"') req.startPath = path8 const latAddr8 = await client.getAddresses(req) - expect(latAddr8[0].toLowerCase()).toEqualElseLog(addr8.toLowerCase(), 'Incorrect address 8 fetched. Ensure your SafeCard is loaded with the test mnemonic: "test test test test test test test test test test test junk"') + expect((latAddr8[0] as string).toLowerCase()).toEqualElseLog(addr8.toLowerCase(), 'Incorrect address 8 fetched. Ensure your SafeCard is loaded with the test mnemonic: "test test test test test test test test test test test junk"') }) }) @@ -105,7 +105,7 @@ describe('[Determinism]', () => { it('Should validate signature from addr0', async () => { const msgReq = buildMsgReq() msgReq.data.signerPath[2] = HARDENED_OFFSET - const res = await client.sign(msgReq) + const res = await client.sign(msgReq as unknown as SignRequestParams) const sig = getSigStr(res) const jsSig = signPersonalJS(msgReq.data.payload, msgReq.data.signerPath) expect(sig).toEqualElseLog(jsSig, 'Lattice sig does not match JS reference') @@ -114,7 +114,7 @@ describe('[Determinism]', () => { it('Should validate signature from addr1', async () => { const msgReq = buildMsgReq() msgReq.data.signerPath[2] = HARDENED_OFFSET + 1 - const res = await client.sign(msgReq) + const res = await client.sign(msgReq as unknown as SignRequestParams) const sig = getSigStr(res) const jsSig = signPersonalJS(msgReq.data.payload, msgReq.data.signerPath) expect(sig).toEqualElseLog(jsSig, 'Lattice sig does not match JS reference') @@ -123,7 +123,7 @@ describe('[Determinism]', () => { it('Should validate signature from addr8', async () => { const msgReq = buildMsgReq() msgReq.data.signerPath[2] = HARDENED_OFFSET + 8 - const res = await client.sign(msgReq) + const res = await client.sign(msgReq as unknown as SignRequestParams) const sig = getSigStr(res) const jsSig = signPersonalJS(msgReq.data.payload, msgReq.data.signerPath) expect(sig).toEqualElseLog(jsSig, 'Lattice sig does not match JS reference') @@ -134,7 +134,7 @@ describe('[Determinism]', () => { it('Should validate signature from addr0', async () => { const msgReq = buildMsgReq('hello ethereum this is another message') msgReq.data.signerPath[2] = HARDENED_OFFSET - const res = await client.sign(msgReq) + const res = await client.sign(msgReq as unknown as SignRequestParams) const sig = getSigStr(res) const jsSig = signPersonalJS(msgReq.data.payload, msgReq.data.signerPath) expect(sig).toEqualElseLog(jsSig, 'Lattice sig does not match JS reference') @@ -143,7 +143,7 @@ describe('[Determinism]', () => { it('Should validate signature from addr1', async () => { const msgReq = buildMsgReq('hello ethereum this is another message') msgReq.data.signerPath[2] = HARDENED_OFFSET + 1 - const res = await client.sign(msgReq) + const res = await client.sign(msgReq as unknown as SignRequestParams) const sig = getSigStr(res) const jsSig = signPersonalJS(msgReq.data.payload, msgReq.data.signerPath) expect(sig).toEqualElseLog(jsSig, 'Lattice sig does not match JS reference') @@ -152,7 +152,7 @@ describe('[Determinism]', () => { it('Should validate signature from addr8', async () => { const msgReq = buildMsgReq('hello ethereum this is another message') msgReq.data.signerPath[2] = HARDENED_OFFSET + 8 - const res = await client.sign(msgReq) + const res = await client.sign(msgReq as unknown as SignRequestParams) const sig = getSigStr(res) const jsSig = signPersonalJS(msgReq.data.payload, msgReq.data.signerPath) expect(sig).toEqualElseLog(jsSig, 'Lattice sig does not match JS reference') @@ -163,7 +163,7 @@ describe('[Determinism]', () => { it('Should validate signature from addr0', async () => { const msgReq = buildMsgReq('third vector yo') msgReq.data.signerPath[2] = HARDENED_OFFSET - const res = await client.sign(msgReq) + const res = await client.sign(msgReq as unknown as SignRequestParams) const sig = getSigStr(res) const jsSig = signPersonalJS(msgReq.data.payload, msgReq.data.signerPath) expect(sig).toEqualElseLog(jsSig, 'Lattice sig does not match JS reference') @@ -172,7 +172,7 @@ describe('[Determinism]', () => { it('Should validate signature from addr1', async () => { const msgReq = buildMsgReq('third vector yo') msgReq.data.signerPath[2] = HARDENED_OFFSET + 1 - const res = await client.sign(msgReq) + const res = await client.sign(msgReq as unknown as SignRequestParams) const sig = getSigStr(res) const jsSig = signPersonalJS(msgReq.data.payload, msgReq.data.signerPath) expect(sig).toEqualElseLog(jsSig, 'Lattice sig does not match JS reference') @@ -181,7 +181,7 @@ describe('[Determinism]', () => { it('Should validate signature from addr8', async () => { const msgReq = buildMsgReq('third vector yo') msgReq.data.signerPath[2] = HARDENED_OFFSET + 8 - const res = await client.sign(msgReq) + const res = await client.sign(msgReq as unknown as SignRequestParams) const sig = getSigStr(res) const jsSig = signPersonalJS(msgReq.data.payload, msgReq.data.signerPath) expect(sig).toEqualElseLog(jsSig, 'Lattice sig does not match JS reference') @@ -232,7 +232,7 @@ describe('[Determinism]', () => { it('Should validate signature from addr0', async () => { msgReq.data.signerPath[2] = HARDENED_OFFSET - const res = await client.sign(msgReq) + const res = await client.sign(msgReq as unknown as SignRequestParams) const sig = getSigStr(res) const jsSig = signEip712JS(msgReq.data.payload, msgReq.data.signerPath) expect(sig).toEqualElseLog(jsSig, 'Lattice EIP712 sig does not match JS reference') @@ -240,7 +240,7 @@ describe('[Determinism]', () => { it('Should validate signature from addr1', async () => { msgReq.data.signerPath[2] = HARDENED_OFFSET + 1 - const res = await client.sign(msgReq) + const res = await client.sign(msgReq as unknown as SignRequestParams) const sig = getSigStr(res) const jsSig = signEip712JS(msgReq.data.payload, msgReq.data.signerPath) expect(sig).toEqualElseLog(jsSig, 'Lattice EIP712 sig does not match JS reference') @@ -248,7 +248,7 @@ describe('[Determinism]', () => { it('Should validate signature from addr8', async () => { msgReq.data.signerPath[2] = HARDENED_OFFSET + 8 - const res = await client.sign(msgReq) + const res = await client.sign(msgReq as unknown as SignRequestParams) const sig = getSigStr(res) const jsSig = signEip712JS(msgReq.data.payload, msgReq.data.signerPath) expect(sig).toEqualElseLog(jsSig, 'Lattice EIP712 sig does not match JS reference') @@ -289,7 +289,7 @@ describe('[Determinism]', () => { it('Should validate signature from addr0', async () => { msgReq.data.signerPath[2] = HARDENED_OFFSET - const res = await client.sign(msgReq) + const res = await client.sign(msgReq as unknown as SignRequestParams) const sig = getSigStr(res) const jsSig = signEip712JS(msgReq.data.payload, msgReq.data.signerPath) expect(sig).toEqualElseLog(jsSig, 'Lattice EIP712 sig does not match JS reference') @@ -297,7 +297,7 @@ describe('[Determinism]', () => { it('Should validate signature from addr1', async () => { msgReq.data.signerPath[2] = HARDENED_OFFSET + 1 - const res = await client.sign(msgReq) + const res = await client.sign(msgReq as unknown as SignRequestParams) const sig = getSigStr(res) const jsSig = signEip712JS(msgReq.data.payload, msgReq.data.signerPath) expect(sig).toEqualElseLog(jsSig, 'Lattice EIP712 sig does not match JS reference') @@ -305,7 +305,7 @@ describe('[Determinism]', () => { it('Should validate signature from addr8', async () => { msgReq.data.signerPath[2] = HARDENED_OFFSET + 8 - const res = await client.sign(msgReq) + const res = await client.sign(msgReq as unknown as SignRequestParams) const sig = getSigStr(res) const jsSig = signEip712JS(msgReq.data.payload, msgReq.data.signerPath) expect(sig).toEqualElseLog(jsSig, 'Lattice EIP712 sig does not match JS reference') @@ -346,21 +346,21 @@ describe('[Determinism]', () => { it('Should validate signature from addr0', async () => { msgReq.data.signerPath[2] = HARDENED_OFFSET - const res = await client.sign(msgReq) + const res = await client.sign(msgReq as unknown as SignRequestParams) const sig = getSigStr(res) const jsSig = signEip712JS(msgReq.data.payload, msgReq.data.signerPath) expect(sig).toEqualElseLog(jsSig, 'Lattice EIP712 sig does not match JS reference') }) it('Should validate signature from addr1', async () => { msgReq.data.signerPath[2] = HARDENED_OFFSET + 1 - const res = await client.sign(msgReq) + const res = await client.sign(msgReq as unknown as SignRequestParams) const sig = getSigStr(res) const jsSig = signEip712JS(msgReq.data.payload, msgReq.data.signerPath) expect(sig).toEqualElseLog(jsSig, 'Lattice EIP712 sig does not match JS reference') }) it('Should validate signature from addr8', async () => { msgReq.data.signerPath[2] = HARDENED_OFFSET + 8 - const res = await client.sign(msgReq) + const res = await client.sign(msgReq as unknown as SignRequestParams) const sig = getSigStr(res) const jsSig = signEip712JS(msgReq.data.payload, msgReq.data.signerPath) expect(sig).toEqualElseLog(jsSig, 'Lattice EIP712 sig does not match JS reference') @@ -384,7 +384,7 @@ describe('[Determinism]', () => { } req.data.signerPath[2] = HARDENED_OFFSET + offset const jsSig = signPersonalJS(req.data.payload, req.data.signerPath) - const res = await client.sign(req) + const res = await client.sign(req as unknown as SignRequestParams) const sig = getSigStr(res) expect(sig).toEqualElseLog(jsSig, `Addr${offset} sig failed`) }) diff --git a/packages/sdk/src/__test__/e2e/signing/unformatted.test.ts b/packages/sdk/src/__test__/e2e/signing/unformatted.test.ts index 1242a3c8..af13ac4f 100644 --- a/packages/sdk/src/__test__/e2e/signing/unformatted.test.ts +++ b/packages/sdk/src/__test__/e2e/signing/unformatted.test.ts @@ -114,14 +114,16 @@ describe('[Unformatted]', () => { // Legacy request const legacyReq = { - currency: 'ETH_MSG', + currency: 'ETH_MSG' as const, data: { signerPath: req.data.signerPath, payload: msg, - protocol: 'signPersonal', + protocol: 'signPersonal' as const, + curveType: Constants.SIGNING.CURVES.SECP256K1, + hashType: Constants.SIGNING.HASHES.KECCAK256, }, } - const respLegacy = await client.sign(legacyReq) + const respLegacy = await client.sign(legacyReq as Parameters[0]) const genSigR = (respGeneric.sig?.r as Buffer)?.toString('hex') ?? '' const genSigS = (respGeneric.sig?.s as Buffer)?.toString('hex') ?? '' diff --git a/packages/sdk/src/__test__/utils/builders.ts b/packages/sdk/src/__test__/utils/builders.ts index a1a2102f..a8deabe3 100644 --- a/packages/sdk/src/__test__/utils/builders.ts +++ b/packages/sdk/src/__test__/utils/builders.ts @@ -196,12 +196,14 @@ export const buildTxReq = (tx: TypedTransaction) => ({ }, }) -export const buildMsgReq = (payload = 'hello ethereum', protocol = 'signPersonal') => ({ - currency: 'ETH_MSG', +export const buildMsgReq = (payload = 'hello ethereum', protocol: 'signPersonal' | 'eip712' = 'signPersonal') => ({ + currency: 'ETH_MSG' as const, data: { signerPath: DEFAULT_SIGNER, protocol, payload, + curveType: Constants.SIGNING.CURVES.SECP256K1, + hashType: Constants.SIGNING.HASHES.KECCAK256, }, }) diff --git a/packages/sdk/src/api/index.ts b/packages/sdk/src/api/index.ts index 9989d092..3f38d1aa 100644 --- a/packages/sdk/src/api/index.ts +++ b/packages/sdk/src/api/index.ts @@ -11,3 +11,5 @@ export { BTC_WRAPPED_SEGWIT_YPUB_PATH, BTC_SEGWIT_ZPUB_PATH, } from '../constants' + +export type { AddressTag } from '../types' diff --git a/pnpm-lock.yaml b/pnpm-lock.yaml index ffec923c..e69233a5 100644 --- a/pnpm-lock.yaml +++ b/pnpm-lock.yaml @@ -79,8 +79,8 @@ importers: specifier: ^6.0.3 version: 6.0.3 gridplus-sdk: - specifier: ^2.4.3 - version: 2.7.1 + specifier: workspace:* + version: link:../sdk react: specifier: ^18.2.0 version: 18.3.1 @@ -98,8 +98,8 @@ importers: specifier: ^3.0.0 version: 3.1.0(vite@4.5.14(@types/node@24.10.4)(terser@5.44.1)) typescript: - specifier: ^4.9.3 - version: 4.9.5 + specifier: ^5.0.0 + version: 5.9.3 vite: specifier: ^4.0.0 version: 4.5.14(@types/node@24.10.4)(terser@5.44.1) @@ -1829,9 +1829,6 @@ packages: '@ethereumjs/common@3.2.0': resolution: {integrity: sha512-pksvzI0VyLgmuEF2FA/JR/4/y6hcPq8OUail3/AvycBaW1d5VSauOZzqGvJ3RTmR4MU35lWE8KseKOsEhrFRBA==} - '@ethereumjs/common@4.3.0': - resolution: {integrity: sha512-shBNJ0ewcPNTUfZduHiczPmqkfJDn0Dh/9BR5fq7xUFTuIq7Fu1Vx00XDwQVIrpVL70oycZocOhBM6nDO+4FEQ==} - '@ethereumjs/rlp@10.1.0': resolution: {integrity: sha512-r67BJbwilammAqYI4B5okA66cNdTlFzeWxPNJOolKV52ZS/flo0tUBf4x4gxWXBgh48OgsdFV1Qp5pRoSe8IhQ==} engines: {node: '>=18'} @@ -1842,11 +1839,6 @@ packages: engines: {node: '>=14'} hasBin: true - '@ethereumjs/rlp@5.0.2': - resolution: {integrity: sha512-DziebCdg4JpGlEqEdGgXmjqcFoJi+JGulUXwEjsZGAscAQ7MyD/7LE/GVCP29vEQxKc7AAwjT3A2ywHp2xfoCA==} - engines: {node: '>=18'} - hasBin: true - '@ethereumjs/tx@10.1.0': resolution: {integrity: sha512-svG6pyzUZDpunafszf2BaolA6Izuvo8ZTIETIegpKxAXYudV1hmzPQDdSI+d8nHCFyQfEFbQ6tfUq95lNArmmg==} engines: {node: '>=18'} @@ -1855,10 +1847,6 @@ packages: resolution: {integrity: sha512-1nc6VO4jtFd172BbSnTnDQVr9IYBFl1y4xPzZdtkrkKIncBCkdbgfdRV+MiTkJYAtTxvV12GRZLqBFT1PNK6Yw==} engines: {node: '>=14'} - '@ethereumjs/tx@5.3.0': - resolution: {integrity: sha512-uv++XYuIfuqYbvymL3/o14hHuC6zX0nRQ1nI2FHsbkkorLZ2ChEIDqVeeVk7Xc9/jQNU/22sk9qZZkRlsveXxw==} - engines: {node: '>=18'} - '@ethereumjs/util@10.1.0': resolution: {integrity: sha512-GGTCkRu1kWXbz2JoUnIYtJBOoA9T5akzsYa91Bh+DZQ3Cj4qXj3hkNU0Rx6wZlbcmkmhQfrjZfVt52eJO/y2nA==} engines: {node: '>=18'} @@ -1867,64 +1855,6 @@ packages: resolution: {integrity: sha512-zQ0IqbdX8FZ9aw11vP+dZkKDkS+kgIvQPHnSAXzP9pLu+Rfu3D3XEeLbicvoXJTYnhZiPmsZUxgdzXwNKxRPbA==} engines: {node: '>=14'} - '@ethereumjs/util@9.1.0': - resolution: {integrity: sha512-XBEKsYqLGXLah9PNJbgdkigthkG7TAGvlD/sH12beMXEyHDyigfcbdvHhmLyDWgDyOJn4QwiQUaF7yeuhnjdog==} - engines: {node: '>=18'} - - '@ethersproject/abi@5.8.0': - resolution: {integrity: sha512-b9YS/43ObplgyV6SlyQsG53/vkSal0MNA1fskSC4mbnCMi8R+NkcH8K9FPYNESf6jUefBUniE4SOKms0E/KK1Q==} - - '@ethersproject/abstract-provider@5.8.0': - resolution: {integrity: sha512-wC9SFcmh4UK0oKuLJQItoQdzS/qZ51EJegK6EmAWlh+OptpQ/npECOR3QqECd8iGHC0RJb4WKbVdSfif4ammrg==} - - '@ethersproject/abstract-signer@5.8.0': - resolution: {integrity: sha512-N0XhZTswXcmIZQdYtUnd79VJzvEwXQw6PK0dTl9VoYrEBxxCPXqS0Eod7q5TNKRxe1/5WUMuR0u0nqTF/avdCA==} - - '@ethersproject/address@5.8.0': - resolution: {integrity: sha512-GhH/abcC46LJwshoN+uBNoKVFPxUuZm6dA257z0vZkKmU1+t8xTn8oK7B9qrj8W2rFRMch4gbJl6PmVxjxBEBA==} - - '@ethersproject/base64@5.8.0': - resolution: {integrity: sha512-lN0oIwfkYj9LbPx4xEkie6rAMJtySbpOAFXSDVQaBnAzYfB4X2Qr+FXJGxMoc3Bxp2Sm8OwvzMrywxyw0gLjIQ==} - - '@ethersproject/bignumber@5.8.0': - resolution: {integrity: sha512-ZyaT24bHaSeJon2tGPKIiHszWjD/54Sz8t57Toch475lCLljC6MgPmxk7Gtzz+ddNN5LuHea9qhAe0x3D+uYPA==} - - '@ethersproject/bytes@5.8.0': - resolution: {integrity: sha512-vTkeohgJVCPVHu5c25XWaWQOZ4v+DkGoC42/TS2ond+PARCxTJvgTFUNDZovyQ/uAQ4EcpqqowKydcdmRKjg7A==} - - '@ethersproject/constants@5.8.0': - resolution: {integrity: sha512-wigX4lrf5Vu+axVTIvNsuL6YrV4O5AXl5ubcURKMEME5TnWBouUh0CDTWxZ2GpnRn1kcCgE7l8O5+VbV9QTTcg==} - - '@ethersproject/hash@5.8.0': - resolution: {integrity: sha512-ac/lBcTbEWW/VGJij0CNSw/wPcw9bSRgCB0AIBz8CvED/jfvDoV9hsIIiWfvWmFEi8RcXtlNwp2jv6ozWOsooA==} - - '@ethersproject/keccak256@5.8.0': - resolution: {integrity: sha512-A1pkKLZSz8pDaQ1ftutZoaN46I6+jvuqugx5KYNeQOPqq+JZ0Txm7dlWesCHB5cndJSu5vP2VKptKf7cksERng==} - - '@ethersproject/logger@5.8.0': - resolution: {integrity: sha512-Qe6knGmY+zPPWTC+wQrpitodgBfH7XoceCGL5bJVejmH+yCS3R8jJm8iiWuvWbG76RUmyEG53oqv6GMVWqunjA==} - - '@ethersproject/networks@5.8.0': - resolution: {integrity: sha512-egPJh3aPVAzbHwq8DD7Po53J4OUSsA1MjQp8Vf/OZPav5rlmWUaFLiq8cvQiGK0Z5K6LYzm29+VA/p4RL1FzNg==} - - '@ethersproject/properties@5.8.0': - resolution: {integrity: sha512-PYuiEoQ+FMaZZNGrStmN7+lWjlsoufGIHdww7454FIaGdbe/p5rnaCXTr5MtBYl3NkeoVhHZuyzChPeGeKIpQw==} - - '@ethersproject/rlp@5.8.0': - resolution: {integrity: sha512-LqZgAznqDbiEunaUvykH2JAoXTT9NV0Atqk8rQN9nx9SEgThA/WMx5DnW8a9FOufo//6FZOCHZ+XiClzgbqV9Q==} - - '@ethersproject/signing-key@5.8.0': - resolution: {integrity: sha512-LrPW2ZxoigFi6U6aVkFN/fa9Yx/+4AtIUe4/HACTvKJdhm0eeb107EVCIQcrLZkxaSIgc/eCrX8Q1GtbH+9n3w==} - - '@ethersproject/strings@5.8.0': - resolution: {integrity: sha512-qWEAk0MAvl0LszjdfnZ2uC8xbR2wdv4cDabyHiBh3Cldq/T8dPH3V4BbBsAYJUeonwD+8afVXld274Ls+Y1xXg==} - - '@ethersproject/transactions@5.8.0': - resolution: {integrity: sha512-UglxSDjByHG0TuU17bDfCemZ3AnKO2vYrL5/2n2oXvKzvb7Cz+W9gOWXKARjp2URVwcWlQlPOEQyAviKwT4AHg==} - - '@ethersproject/web@5.8.0': - resolution: {integrity: sha512-j7+Ksi/9KfGviws6Qtf9Q7KCqRhpwrYKQPs+JBA/rKVFF/yaWLHJEH3zfVP2plVu+eys0d2DlFmhoQJayFewcw==} - '@hapi/hoek@9.3.0': resolution: {integrity: sha512-/c6rf4UJlmHlC9b5BaNvzAcFv7HZ2QHaV0D4/HNlBdvFnvQq8RI4kYdhyPCl7Xj+oWvTWQ8ujhqS53LIgAe6KQ==} @@ -2081,18 +2011,10 @@ packages: '@types/react': '>=16' react: '>=16' - '@metamask/abi-utils@2.0.4': - resolution: {integrity: sha512-StnIgUB75x7a7AgUhiaUZDpCsqGp7VkNnZh2XivXkJ6mPkE83U8ARGQj5MbRis7VJY8BC5V1AbB1fjdh0hupPQ==} - engines: {node: '>=16.0.0'} - '@metamask/abi-utils@3.0.0': resolution: {integrity: sha512-a/l0DiSIr7+CBYVpHygUa3ztSlYLFCQMsklLna+t6qmNY9+eIO5TedNxhyIyvaJ+4cN7TLy0NQFbp9FV3X2ktg==} engines: {node: ^18.18 || ^20.14 || >=22} - '@metamask/eth-sig-util@7.0.3': - resolution: {integrity: sha512-PAtGnOkYvh90k2lEZldq/FK7GTLF6WxE+2bV85PoA3pqlJnmJCAY62tuvxHSwnVngSKlc4mcNvjnUg2eYO6JGg==} - engines: {node: ^16.20 || ^18.16 || >=20} - '@metamask/eth-sig-util@8.2.0': resolution: {integrity: sha512-LZDglIh4gYGw9Myp+2aIwKrj6lIJpMC4e0m7wKJU+BxLLBFcrTgKrjdjstXGVWvuYG3kutlh9J+uNBRPJqffWQ==} engines: {node: ^18.18 || ^20.14 || >=22} @@ -2105,10 +2027,6 @@ packages: resolution: {integrity: sha512-wRnoSDD9jTWOge/+reFviJQANhS+uy8Y+OEwRanp5mQeGTjBFmK1r2cTOnei2UCZRV1crXHzeJVSFEoDDcgRbA==} engines: {node: ^18.18 || ^20.14 || >=22} - '@metamask/utils@9.3.0': - resolution: {integrity: sha512-w8CVbdkDrVXFJbfBSlDfafDR6BAkpDmv1bC1UJVCoVny5tW2RKAdn9i68Xf7asYT4TnUhl/hN4zfUiKQq9II4g==} - engines: {node: '>=16.0.0'} - '@microsoft/api-extractor-model@7.32.2': resolution: {integrity: sha512-Ussc25rAalc+4JJs9HNQE7TuO9y6jpYQX9nWD1DhqUzYPBr3Lr7O9intf+ZY8kD5HnIqeIRJX7ccCT0QyBy2Ww==} @@ -2446,10 +2364,6 @@ packages: '@solana/web3.js@1.98.4': resolution: {integrity: sha512-vv9lfnvjUsRiq//+j5pBdXig0IQdtzA0BRZ3bXEP4KaIyF1CcaydWqgyzQgfZMNIsWNWmG+AUHwPy4AHOD6gpw==} - '@sovpro/delimited-stream@1.1.0': - resolution: {integrity: sha512-kQpk267uxB19X3X2T1mvNMjyvIEonpNSHrMlK5ZaBU6aZxw7wPbpgKJOjHN3+/GPVpXgAV9soVT2oyHpLkLtyw==} - engines: {node: '>= 8'} - '@standard-schema/spec@1.1.0': resolution: {integrity: sha512-l2aFy5jALhniG5HgqrD6jXLi/rUWrKvqN/qJx6yoJsgKhblVd+iqqU4RCXavm/jPityDo5TCvKMnpjKnOriy0w==} @@ -2754,9 +2668,6 @@ packages: '@types/unist@3.0.3': resolution: {integrity: sha512-ko/gIFJRv177XgZsZcBwnqJN5x/Gien8qNOn0D5bQU/zAzVf9Zt3BlcUiLqhV9y4ARk0GbT3tnUiPNgnTXzc/Q==} - '@types/uuid@10.0.0': - resolution: {integrity: sha512-7gqG38EyHgyP1S+7+xomFtL+ZNHcKv6DwNaCZmJmo1vgMugyF3TCnXVg4t1uk89mLNwnLtnY3TpOpCOyp1/xHQ==} - '@types/uuid@11.0.0': resolution: {integrity: sha512-HVyk8nj2m+jcFRNazzqyVKiZezyhDKrGUA3jlEcg/nZ6Ms+qHwocba1Y/AaVaznJTAM9xpdFSh+ptbNrhOGvZA==} deprecated: This is a stub types definition. uuid provides its own type definitions, so you do not need this installed. @@ -3259,11 +3170,6 @@ packages: boolbase@1.0.0: resolution: {integrity: sha512-JZOSA7Mo9sNGB8+UjSgzdLtokWAky1zbztM3WRLCbZ70/3cTANmQmOdR7y2g+J0e2WXywy1yS468tY+IruqEww==} - borc@3.0.0: - resolution: {integrity: sha512-ec4JmVC46kE0+layfnwM3l15O70MlFiEbmQHY/vpqIKiUtPVntv4BY4NVnz3N4vb21edV3mY97XVckFvYHWF9g==} - engines: {node: '>=4'} - hasBin: true - borsh@0.7.0: resolution: {integrity: sha512-CLCsZGIBCFnPtkNnieW/a8wmreDmfUtjU2m9yHrzPXIlNbqVs0AQrSatSG6vdNYUqdc83tkQi2eHfF98ubzQLA==} @@ -4155,9 +4061,6 @@ packages: elkjs@0.9.3: resolution: {integrity: sha512-f/ZeWvW/BCXbhGEf1Ujp29EASo/lk1FDnETgNKwJrsVvGZhUWCZyg3xLJjAsxfOmt8KjswHmI5EwCQcPMpOYhQ==} - elliptic@6.5.6: - resolution: {integrity: sha512-mpzdtpeCLuS3BmE3pO3Cpp5bbjlOPY2Q0PgoF+Od1XZrHLYI28Xe3ossCmYCQt11FQKEYd9+PF8jymTvtWJSHQ==} - elliptic@6.6.1: resolution: {integrity: sha512-RaddvvMatK2LJHqFJ+YA4WysVN5Ita9E35botqIYspQ4TkRAlCicdzKOjlyv/1Za5RyTNn7di//eEV0uTAfe3g==} @@ -4648,9 +4551,6 @@ packages: resolution: {integrity: sha512-5v6yZd4JK3eMI3FqqCouswVqwugaA9r4dNZB1wwcmrD02QkV5H0y7XBQW8QwQqEaZY1pM9aqORSORhJRdNK44Q==} engines: {node: '>=6.0'} - gridplus-sdk@2.7.1: - resolution: {integrity: sha512-KJgNN0dnLL5/NMUJnQ2sy3vAs3AJnpYDeC5jQSo2rZVyauV6eAS9SJMTYikENbgB/BDnn5gOCbvob/CwbMMKbg==} - gzip-size@6.0.0: resolution: {integrity: sha512-ax7ZYomf6jqPTQ4+XCpUGyXKHk5WweS+e05MBO4/y3WJ5RkmPXNKvX+bx1behVILVwr6JSQvZAku021CHPXG3Q==} engines: {node: '>=10'} @@ -5075,10 +4975,6 @@ packages: isexe@2.0.0: resolution: {integrity: sha512-RHxMLp9lnKHGHRng9QFhRCMbYAcVpn69smSGcq3f36xjgVVWThj4qqLbTLlq7Ssj8B+fIQ1EuCEGI2lKsyQeIw==} - iso-url@1.2.1: - resolution: {integrity: sha512-9JPDgCN4B7QPkLtYAAOrEuAWvP9rWvR5offAr0/SeF046wIkglqH3VXgYYP6NcsKslH80UIVgmPqNe3j7tG2ng==} - engines: {node: '>=12'} - isobject@3.0.1: resolution: {integrity: sha512-WhB9zCku7EGTj/HQQRz5aUQEUeoQZH2bWcltRErOpymJ4boYE6wL9Tbr23krRPSZ+C5zqNSrSw+Cc7sZZ4b7vg==} engines: {node: '>=0.10.0'} @@ -5171,12 +5067,6 @@ packages: resolution: {integrity: sha512-34wB/Y7MW7bzjKRjUKTa46I2Z7eV62Rkhva+KkopW7Qvv/OSWBqvkSY7vusOPrNuZcUG3tApvdVgNB8POj3SPw==} engines: {node: '>=10'} - js-sha3@0.8.0: - resolution: {integrity: sha512-gF1cRrHhIzNfToc802P800N8PpXS+evLLXfsVpowqmAFR9uwbi89WvXg2QspOmXL8QL86J4T1EpFu+yUkwJY3Q==} - - js-sha3@0.9.3: - resolution: {integrity: sha512-BcJPCQeLg6WjEx3FE591wVAevlli8lxsxm9/FzV4HXkV49TmBH38Yvrpce6fjbADGMKFrBMGTqrVz3qPIZ88Gg==} - js-tokens@4.0.0: resolution: {integrity: sha512-RdJUflcE3cUzKiMqQgsCu06FPu9UdIJO0beYbPhHN4k6apgJtifcoCtT9bcxOpYBtpD2kCM6Sbzg4CausW/PKQ==} @@ -5217,10 +5107,6 @@ packages: json-stringify-safe@5.0.1: resolution: {integrity: sha512-ZClg6AaYvamvYEE82d3Iyd3vSSIjQ+odgjaTzRuO3s7toCdFKczob2i0zCh7JE8kWn17yvAWhUVxvqGwUalsRA==} - json-text-sequence@0.3.0: - resolution: {integrity: sha512-7khKIYPKwXQem4lWXfpIN/FEnhztCeRPSxH4qm3fVlqulwujrRDD54xAwDDn/qVKpFtV550+QAkcWJcufzqQuA==} - engines: {node: '>=10.18.0'} - json5@2.2.3: resolution: {integrity: sha512-XmOWe7eyHYH14cLdVPoyg+GOH3rYX++KpzrylJwSW98t3Nk+U8XOl8FWKOgwtzdb8lXGf6zYwDUzeHMWfxasyg==} engines: {node: '>=6'} @@ -6948,10 +6834,6 @@ packages: resolution: {integrity: sha512-6JfvwvjUOn8F/jUoBY2Q1v5WY5XS+rj8qSe0v8Y4ezH4InLgTEeOOPQsRll9OV429Pvo6BCHGavIyJfr3TAhsw==} engines: {node: '>=18.0.0'} - secp256k1@5.0.0: - resolution: {integrity: sha512-TKWX8xvoGHrxVdqbYeZM9w+izTF4b9z3NhSaDkdn81btvuh+ivbIMGT/zQvDtTFWhRlThpoz6LEYTr7n8A5GcA==} - engines: {node: '>=14.0.0'} - secp256k1@5.0.1: resolution: {integrity: sha512-lDFs9AAIaWP9UCdtWrotXWWF9t8PWgQDcxqgAnpM9rMqxb3Oaq2J0thzPVSxBwdJgyQtkU/sYtFtbM1RSt/iYA==} engines: {node: '>=18.0.0'} @@ -7551,11 +7433,6 @@ packages: typeforce@1.18.0: resolution: {integrity: sha512-7uc1O8h1M1g0rArakJdf0uLRSSgFcYexrVoKo+bzJd32gd4gDy2L/Z+8/FjPnU9ydY3pEnVPtr9FyscYY60K1g==} - typescript@4.9.5: - resolution: {integrity: sha512-1FXk9E2Hm+QzZQ7z+McJiHL4NW1F2EzMu9Nq9i3zAaGqibafqYwCVU6WyWAuyQRRzOlxou8xZSyXLEN8oKj24g==} - engines: {node: '>=4.2.0'} - hasBin: true - typescript@5.8.2: resolution: {integrity: sha512-aJn6wq13/afZp/jT9QZmwEjDqqvSGp1VT5GVg+f/t6/oVyrgXM6BY1h9BRh/O5p3PlUPAe+WuiEZOmb/49RqoQ==} engines: {node: '>=14.17'} @@ -7709,10 +7586,6 @@ packages: resolution: {integrity: sha512-pMZTvIkT1d+TFGvDOqodOclx0QWkkgi6Tdoa8gC8ffGAAqz9pzPTZWAybbsHHoED/ztMtkv/VoYTYyShUn81hA==} engines: {node: '>= 0.4.0'} - uuid@10.0.0: - resolution: {integrity: sha512-8XkAphELsDnEGrDxUOHB3RGvXz6TeuYSGEZBOjtTtPm2lwhGBjLgOzLHB63IUWfBpNucQjND6d3AOudO+H3RWQ==} - hasBin: true - uuid@13.0.0: resolution: {integrity: sha512-XQegIaBTVUjSHliKqcnFqYypAd4S+WCYt5NIeRs6w/UAry7z8Y9j5ZwRRL4kzq9U3sD6v+85er9FvkEaBpji2w==} hasBin: true @@ -10298,16 +10171,10 @@ snapshots: '@ethereumjs/util': 8.1.0 crc-32: 1.2.2 - '@ethereumjs/common@4.3.0': - dependencies: - '@ethereumjs/util': 9.1.0 - '@ethereumjs/rlp@10.1.0': {} '@ethereumjs/rlp@4.0.1': {} - '@ethereumjs/rlp@5.0.2': {} - '@ethereumjs/tx@10.1.0': dependencies: '@ethereumjs/common': 10.1.0 @@ -10322,13 +10189,6 @@ snapshots: '@ethereumjs/util': 8.1.0 ethereum-cryptography: 2.2.1 - '@ethereumjs/tx@5.3.0': - dependencies: - '@ethereumjs/common': 4.3.0 - '@ethereumjs/rlp': 5.0.2 - '@ethereumjs/util': 9.1.0 - ethereum-cryptography: 2.2.1 - '@ethereumjs/util@10.1.0': dependencies: '@ethereumjs/rlp': 10.1.0 @@ -10340,134 +10200,6 @@ snapshots: ethereum-cryptography: 2.2.1 micro-ftch: 0.3.1 - '@ethereumjs/util@9.1.0': - dependencies: - '@ethereumjs/rlp': 5.0.2 - ethereum-cryptography: 2.2.1 - - '@ethersproject/abi@5.8.0': - dependencies: - '@ethersproject/address': 5.8.0 - '@ethersproject/bignumber': 5.8.0 - '@ethersproject/bytes': 5.8.0 - '@ethersproject/constants': 5.8.0 - '@ethersproject/hash': 5.8.0 - '@ethersproject/keccak256': 5.8.0 - '@ethersproject/logger': 5.8.0 - '@ethersproject/properties': 5.8.0 - '@ethersproject/strings': 5.8.0 - - '@ethersproject/abstract-provider@5.8.0': - dependencies: - '@ethersproject/bignumber': 5.8.0 - '@ethersproject/bytes': 5.8.0 - '@ethersproject/logger': 5.8.0 - '@ethersproject/networks': 5.8.0 - '@ethersproject/properties': 5.8.0 - '@ethersproject/transactions': 5.8.0 - '@ethersproject/web': 5.8.0 - - '@ethersproject/abstract-signer@5.8.0': - dependencies: - '@ethersproject/abstract-provider': 5.8.0 - '@ethersproject/bignumber': 5.8.0 - '@ethersproject/bytes': 5.8.0 - '@ethersproject/logger': 5.8.0 - '@ethersproject/properties': 5.8.0 - - '@ethersproject/address@5.8.0': - dependencies: - '@ethersproject/bignumber': 5.8.0 - '@ethersproject/bytes': 5.8.0 - '@ethersproject/keccak256': 5.8.0 - '@ethersproject/logger': 5.8.0 - '@ethersproject/rlp': 5.8.0 - - '@ethersproject/base64@5.8.0': - dependencies: - '@ethersproject/bytes': 5.8.0 - - '@ethersproject/bignumber@5.8.0': - dependencies: - '@ethersproject/bytes': 5.8.0 - '@ethersproject/logger': 5.8.0 - bn.js: 5.2.2 - - '@ethersproject/bytes@5.8.0': - dependencies: - '@ethersproject/logger': 5.8.0 - - '@ethersproject/constants@5.8.0': - dependencies: - '@ethersproject/bignumber': 5.8.0 - - '@ethersproject/hash@5.8.0': - dependencies: - '@ethersproject/abstract-signer': 5.8.0 - '@ethersproject/address': 5.8.0 - '@ethersproject/base64': 5.8.0 - '@ethersproject/bignumber': 5.8.0 - '@ethersproject/bytes': 5.8.0 - '@ethersproject/keccak256': 5.8.0 - '@ethersproject/logger': 5.8.0 - '@ethersproject/properties': 5.8.0 - '@ethersproject/strings': 5.8.0 - - '@ethersproject/keccak256@5.8.0': - dependencies: - '@ethersproject/bytes': 5.8.0 - js-sha3: 0.8.0 - - '@ethersproject/logger@5.8.0': {} - - '@ethersproject/networks@5.8.0': - dependencies: - '@ethersproject/logger': 5.8.0 - - '@ethersproject/properties@5.8.0': - dependencies: - '@ethersproject/logger': 5.8.0 - - '@ethersproject/rlp@5.8.0': - dependencies: - '@ethersproject/bytes': 5.8.0 - '@ethersproject/logger': 5.8.0 - - '@ethersproject/signing-key@5.8.0': - dependencies: - '@ethersproject/bytes': 5.8.0 - '@ethersproject/logger': 5.8.0 - '@ethersproject/properties': 5.8.0 - bn.js: 5.2.2 - elliptic: 6.6.1 - hash.js: 1.1.7 - - '@ethersproject/strings@5.8.0': - dependencies: - '@ethersproject/bytes': 5.8.0 - '@ethersproject/constants': 5.8.0 - '@ethersproject/logger': 5.8.0 - - '@ethersproject/transactions@5.8.0': - dependencies: - '@ethersproject/address': 5.8.0 - '@ethersproject/bignumber': 5.8.0 - '@ethersproject/bytes': 5.8.0 - '@ethersproject/constants': 5.8.0 - '@ethersproject/keccak256': 5.8.0 - '@ethersproject/logger': 5.8.0 - '@ethersproject/properties': 5.8.0 - '@ethersproject/rlp': 5.8.0 - '@ethersproject/signing-key': 5.8.0 - - '@ethersproject/web@5.8.0': - dependencies: - '@ethersproject/base64': 5.8.0 - '@ethersproject/bytes': 5.8.0 - '@ethersproject/logger': 5.8.0 - '@ethersproject/properties': 5.8.0 - '@ethersproject/strings': 5.8.0 - '@hapi/hoek@9.3.0': {} '@hapi/topo@5.1.0': @@ -10657,13 +10389,6 @@ snapshots: '@types/react': 18.3.27 react: 18.3.1 - '@metamask/abi-utils@2.0.4': - dependencies: - '@metamask/superstruct': 3.2.1 - '@metamask/utils': 9.3.0 - transitivePeerDependencies: - - supports-color - '@metamask/abi-utils@3.0.0': dependencies: '@metamask/superstruct': 3.2.1 @@ -10671,17 +10396,6 @@ snapshots: transitivePeerDependencies: - supports-color - '@metamask/eth-sig-util@7.0.3': - dependencies: - '@ethereumjs/util': 8.1.0 - '@metamask/abi-utils': 2.0.4 - '@metamask/utils': 9.3.0 - '@scure/base': 1.1.9 - ethereum-cryptography: 2.2.1 - tweetnacl: 1.0.3 - transitivePeerDependencies: - - supports-color - '@metamask/eth-sig-util@8.2.0': dependencies: '@ethereumjs/rlp': 4.0.1 @@ -10712,20 +10426,6 @@ snapshots: transitivePeerDependencies: - supports-color - '@metamask/utils@9.3.0': - dependencies: - '@ethereumjs/tx': 4.2.0 - '@metamask/superstruct': 3.2.1 - '@noble/hashes': 1.8.0 - '@scure/base': 1.1.9 - '@types/debug': 4.1.12 - debug: 4.4.3 - pony-cause: 2.1.11 - semver: 7.7.3 - uuid: 9.0.1 - transitivePeerDependencies: - - supports-color - '@microsoft/api-extractor-model@7.32.2(@types/node@24.10.4)': dependencies: '@microsoft/tsdoc': 0.16.0 @@ -11076,8 +10776,6 @@ snapshots: - typescript - utf-8-validate - '@sovpro/delimited-stream@1.1.0': {} - '@standard-schema/spec@1.1.0': {} '@svgr/babel-plugin-add-jsx-attribute@8.0.0(@babel/core@7.28.5)': @@ -11424,8 +11122,6 @@ snapshots: '@types/unist@3.0.3': {} - '@types/uuid@10.0.0': {} - '@types/uuid@11.0.0': dependencies: uuid: 13.0.0 @@ -11999,16 +11695,6 @@ snapshots: boolbase@1.0.0: {} - borc@3.0.0: - dependencies: - bignumber.js: 9.3.1 - buffer: 6.0.3 - commander: 2.20.3 - ieee754: 1.2.1 - iso-url: 1.2.1 - json-text-sequence: 0.3.0 - readable-stream: 3.6.2 - borsh@0.7.0: dependencies: bn.js: 5.2.2 @@ -12975,16 +12661,6 @@ snapshots: elkjs@0.9.3: {} - elliptic@6.5.6: - dependencies: - bn.js: 4.12.2 - brorand: 1.1.0 - hash.js: 1.1.7 - hmac-drbg: 1.0.1 - inherits: 2.0.4 - minimalistic-assert: 1.0.1 - minimalistic-crypto-utils: 1.0.1 - elliptic@6.6.1: dependencies: bn.js: 4.12.2 @@ -13609,30 +13285,6 @@ snapshots: section-matter: 1.0.0 strip-bom-string: 1.0.0 - gridplus-sdk@2.7.1: - dependencies: - '@ethereumjs/common': 4.3.0 - '@ethereumjs/rlp': 5.0.2 - '@ethereumjs/tx': 5.3.0 - '@ethersproject/abi': 5.8.0 - '@metamask/eth-sig-util': 7.0.3 - '@types/uuid': 10.0.0 - aes-js: 3.1.2 - bech32: 2.0.0 - bignumber.js: 9.3.1 - bitwise: 2.2.1 - borc: 3.0.0 - bs58check: 4.0.0 - buffer: 6.0.3 - crc-32: 1.2.2 - elliptic: 6.5.6 - hash.js: 1.1.7 - js-sha3: 0.9.3 - secp256k1: 5.0.0 - uuid: 10.0.0 - transitivePeerDependencies: - - supports-color - gzip-size@6.0.0: dependencies: duplexer: 0.1.2 @@ -14133,8 +13785,6 @@ snapshots: isexe@2.0.0: {} - iso-url@1.2.1: {} - isobject@3.0.1: {} isomorphic-ws@4.0.1(ws@7.5.10(bufferutil@4.1.0)(utf-8-validate@5.0.10)): @@ -14279,10 +13929,6 @@ snapshots: joycon@3.1.1: {} - js-sha3@0.8.0: {} - - js-sha3@0.9.3: {} - js-tokens@4.0.0: {} js-tokens@9.0.1: {} @@ -14312,10 +13958,6 @@ snapshots: json-stringify-safe@5.0.1: {} - json-text-sequence@0.3.0: - dependencies: - '@sovpro/delimited-stream': 1.1.0 - json5@2.2.3: {} jsonc-parser@3.3.1: {} @@ -16734,12 +16376,6 @@ snapshots: node-addon-api: 5.1.0 node-gyp-build: 4.8.4 - secp256k1@5.0.0: - dependencies: - elliptic: 6.6.1 - node-addon-api: 5.1.0 - node-gyp-build: 4.8.4 - secp256k1@5.0.1: dependencies: elliptic: 6.6.1 @@ -17387,8 +17023,6 @@ snapshots: typeforce@1.18.0: {} - typescript@4.9.5: {} - typescript@5.8.2: {} typescript@5.9.3: {} @@ -17561,8 +17195,6 @@ snapshots: utils-merge@1.0.1: {} - uuid@10.0.0: {} - uuid@13.0.0: {} uuid@8.3.2: {} From 26283ee064465f946fbd8fb29262e7f54e901a53 Mon Sep 17 00:00:00 2001 From: netbonus <151201453+netbonus@users.noreply.github.com> Date: Wed, 21 Jan 2026 13:12:33 -0500 Subject: [PATCH 04/14] fix: prevent ReDoS in trailing comma regex Use unrolled loop pattern for multi-line comments to avoid exponential backtracking on malicious inputs like ',/**//**//**/' patterns. --- packages/sdk/src/__test__/utils/helpers.ts | 7 ++++++- 1 file changed, 6 insertions(+), 1 deletion(-) diff --git a/packages/sdk/src/__test__/utils/helpers.ts b/packages/sdk/src/__test__/utils/helpers.ts index 0b4f2c66..8d603671 100644 --- a/packages/sdk/src/__test__/utils/helpers.ts +++ b/packages/sdk/src/__test__/utils/helpers.ts @@ -990,7 +990,12 @@ export const compressPubKey = (pub) => { } function _stripTrailingCommas(input: string): string { - return input.replace(/,\s*(?:(?:\/\/[^\n]*\n)|\/\*[\s\S]*?\*\/|\s)*([}\]])/g, '$1') + // Use non-backtracking pattern to avoid ReDoS vulnerability + // Unrolled loop for multi-line comments: \/\*[^*]*\*+(?:[^/*][^*]*\*+)*\/ + return input.replace( + /,([\s]*(?:\/\/[^\n]*\n[\s]*|\/\*[^*]*\*+(?:[^/*][^*]*\*+)*\/[\s]*)*)([}\]])/g, + '$1$2', + ) } export const getTestVectors = () => { From e96b40a81ba5bbd83115ab216d41f5e305fb7128 Mon Sep 17 00:00:00 2001 From: netbonus <151201453+netbonus@users.noreply.github.com> Date: Wed, 21 Jan 2026 13:36:11 -0500 Subject: [PATCH 05/14] ci: use packageManager field for pnpm version Remove hardcoded pnpm version 9 from workflows to use the packageManager field from package.json (pnpm@10.6.2) instead. --- .github/workflows/build-test.yml | 2 -- .github/workflows/docs-build-deploy.yml | 2 -- .github/workflows/publish.yml | 2 -- 3 files changed, 6 deletions(-) diff --git a/.github/workflows/build-test.yml b/.github/workflows/build-test.yml index 060a3c5d..69904c17 100644 --- a/.github/workflows/build-test.yml +++ b/.github/workflows/build-test.yml @@ -33,8 +33,6 @@ jobs: - name: Install pnpm uses: pnpm/action-setup@v4 - with: - version: 9 - name: Install dependencies run: pnpm install diff --git a/.github/workflows/docs-build-deploy.yml b/.github/workflows/docs-build-deploy.yml index 267f0baa..d31c90fa 100644 --- a/.github/workflows/docs-build-deploy.yml +++ b/.github/workflows/docs-build-deploy.yml @@ -18,8 +18,6 @@ jobs: - name: Install pnpm uses: pnpm/action-setup@v4 - with: - version: 9 - name: Install dependencies run: pnpm install diff --git a/.github/workflows/publish.yml b/.github/workflows/publish.yml index 6dc568b5..19ff0f2a 100644 --- a/.github/workflows/publish.yml +++ b/.github/workflows/publish.yml @@ -20,8 +20,6 @@ jobs: - name: Install pnpm uses: pnpm/action-setup@v4 - with: - version: 9 - name: Install dependencies run: pnpm install From 86c037eb944c3e27a468dba401490f526e4a949d Mon Sep 17 00:00:00 2001 From: netbonus <151201453+netbonus@users.noreply.github.com> Date: Wed, 21 Jan 2026 15:19:06 -0500 Subject: [PATCH 06/14] style: apply Biome default line width formatting Remove custom lineWidth (240) and indentStyle from biome.json to use defaults. Reformat all files to 80-char line width. --- .github/workflows/build-test.yml | 39 +- .github/workflows/docs-build-deploy.yml | 20 +- .github/workflows/publish.yml | 20 +- biome.json | 2 - .../docs/src/components/HomepageFeatures.tsx | 17 +- packages/docs/src/pages/_index.tsx | 10 +- packages/example/src/App.tsx | 21 +- packages/example/src/Lattice.tsx | 27 +- packages/sdk/src/__test__/e2e/api.test.ts | 127 +++-- packages/sdk/src/__test__/e2e/btc.test.ts | 21 +- packages/sdk/src/__test__/e2e/eth.msg.test.ts | 104 ++-- packages/sdk/src/__test__/e2e/general.test.ts | 97 +++- packages/sdk/src/__test__/e2e/kv.test.ts | 32 +- .../src/__test__/e2e/non-exportable.test.ts | 8 +- .../sdk/src/__test__/e2e/signing/bls.test.ts | 73 ++- .../__test__/e2e/signing/determinism.test.ts | 144 +++++- .../__test__/e2e/signing/eip712-vectors.ts | 8 +- .../src/__test__/e2e/signing/evm-tx.test.ts | 8 +- .../e2e/signing/solana/__mocks__/programs.ts | 110 +++-- .../e2e/signing/solana/solana.test.ts | 39 +- .../signing/solana/solana.versioned.test.ts | 37 +- .../__test__/e2e/signing/unformatted.test.ts | 17 +- .../sdk/src/__test__/e2e/signing/vectors.ts | 95 +++- .../__test__/integration/__mocks__/4byte.ts | 9 +- .../integration/__mocks__/etherscan.ts | 12 +- .../integration/__mocks__/handlers.ts | 16 +- .../integration/fetchCalldataDecoder.test.ts | 6 +- .../__test__/unit/__mocks__/decoderData.ts | 27 +- packages/sdk/src/__test__/unit/api.test.ts | 4 +- .../unit/compareEIP7702Serialization.test.ts | 5 +- .../sdk/src/__test__/unit/decoders.test.ts | 23 +- .../sdk/src/__test__/unit/eip7702.test.ts | 5 +- .../sdk/src/__test__/unit/encoders.test.ts | 46 +- .../__test__/unit/ethereum.validate.test.ts | 7 +- .../src/__test__/unit/module.interop.test.ts | 12 +- .../unit/parseGenericSigningResponse.test.ts | 35 +- .../unit/personalSignValidation.test.ts | 40 +- .../unit/selectDefFrom4byteABI.test.ts | 6 +- .../src/__test__/unit/signatureUtils.test.ts | 91 +++- .../sdk/src/__test__/unit/validators.test.ts | 35 +- .../utils/__test__/serializers.test.ts | 5 +- packages/sdk/src/__test__/utils/builders.ts | 64 ++- .../sdk/src/__test__/utils/determinism.ts | 6 +- packages/sdk/src/__test__/utils/ethers.ts | 26 +- packages/sdk/src/__test__/utils/helpers.ts | 230 +++++++-- packages/sdk/src/__test__/utils/runners.ts | 17 +- packages/sdk/src/__test__/utils/setup.ts | 4 +- .../sdk/src/__test__/utils/testConstants.ts | 3 +- .../sdk/src/__test__/utils/testEnvironment.ts | 3 +- .../sdk/src/__test__/utils/testRequest.ts | 15 +- .../sdk/src/__test__/utils/viemComparison.ts | 27 +- packages/sdk/src/api/addressTags.ts | 13 +- packages/sdk/src/api/addresses.ts | 85 +++- packages/sdk/src/api/setup.ts | 4 +- packages/sdk/src/api/signing.ts | 126 ++++- packages/sdk/src/api/state.ts | 4 +- packages/sdk/src/api/utilities.ts | 21 +- packages/sdk/src/bitcoin.ts | 41 +- packages/sdk/src/calldata/evm.ts | 59 ++- packages/sdk/src/calldata/index.ts | 7 +- packages/sdk/src/client.ts | 93 +++- packages/sdk/src/constants.ts | 112 ++++- packages/sdk/src/ethereum.ts | 443 ++++++++++++++---- packages/sdk/src/functions/addKvRecords.ts | 27 +- packages/sdk/src/functions/connect.ts | 28 +- .../sdk/src/functions/fetchActiveWallet.ts | 19 +- packages/sdk/src/functions/fetchDecoder.ts | 16 +- packages/sdk/src/functions/fetchEncData.ts | 44 +- packages/sdk/src/functions/getAddresses.ts | 60 ++- packages/sdk/src/functions/getKvRecords.ts | 36 +- packages/sdk/src/functions/pair.ts | 19 +- packages/sdk/src/functions/removeKvRecords.ts | 23 +- packages/sdk/src/functions/sign.ts | 72 ++- packages/sdk/src/genericSigning.ts | 103 +++- packages/sdk/src/protocol/latticeConstants.ts | 8 +- packages/sdk/src/protocol/secureMessages.ts | 78 ++- packages/sdk/src/schemas/transaction.ts | 130 +++-- packages/sdk/src/shared/functions.ts | 32 +- packages/sdk/src/shared/predicates.ts | 16 +- packages/sdk/src/shared/utilities.ts | 11 +- packages/sdk/src/shared/validators.ts | 77 ++- packages/sdk/src/types/addKvRecords.ts | 3 +- packages/sdk/src/types/firmware.ts | 5 +- packages/sdk/src/types/getAddresses.ts | 3 +- packages/sdk/src/types/getKvRecords.ts | 3 +- packages/sdk/src/types/removeKvRecords.ts | 3 +- packages/sdk/src/types/sign.ts | 52 +- packages/sdk/src/util.ts | 263 ++++++++--- 88 files changed, 3134 insertions(+), 860 deletions(-) diff --git a/.github/workflows/build-test.yml b/.github/workflows/build-test.yml index 69904c17..4ec8b44b 100644 --- a/.github/workflows/build-test.yml +++ b/.github/workflows/build-test.yml @@ -16,6 +16,7 @@ jobs: build: name: Lint, Build & Unit, E2E Tests runs-on: ubuntu-latest + timeout-minutes: 60 permissions: contents: read packages: read @@ -23,36 +24,30 @@ jobs: INTERNAL_EVENT: ${{ github.event_name != 'pull_request' || github.event.pull_request.head.repo.full_name == github.repository }} steps: - - name: Checkout code - uses: actions/checkout@v5 + - uses: actions/checkout@v4 - - name: Install Node.js - uses: actions/setup-node@v3 - with: - node-version: 20.x + - uses: pnpm/action-setup@v4 - - name: Install pnpm - uses: pnpm/action-setup@v4 + - uses: actions/setup-node@v4 + with: + node-version: 22 + cache: pnpm + cache-dependency-path: pnpm-lock.yaml - name: Install dependencies - run: pnpm install + run: pnpm install --frozen-lockfile - - name: Setup Turborepo cache - uses: actions/cache@v4 - with: - path: .turbo - key: turbo-${{ runner.os }}-${{ hashFiles('**/pnpm-lock.yaml') }}-${{ github.sha }} - restore-keys: | - turbo-${{ runner.os }}-${{ hashFiles('**/pnpm-lock.yaml') }}- + - name: Lint + run: pnpm turbo run lint - - name: Run linter - run: pnpm run lint + - name: Type check + run: pnpm turbo run typecheck - - name: Run tests - run: pnpm run test + - name: Test + run: pnpm turbo run test - - name: Build project - run: pnpm run build + - name: Build + run: pnpm turbo run build - name: Release a nightly build if: env.INTERNAL_EVENT == 'true' diff --git a/.github/workflows/docs-build-deploy.yml b/.github/workflows/docs-build-deploy.yml index d31c90fa..afce6ec8 100644 --- a/.github/workflows/docs-build-deploy.yml +++ b/.github/workflows/docs-build-deploy.yml @@ -6,24 +6,24 @@ jobs: build: name: Build runs-on: ubuntu-latest + timeout-minutes: 60 steps: - - name: Checkout code - uses: actions/checkout@v5 + - uses: actions/checkout@v4 - - name: Install Node.js - uses: actions/setup-node@v3 - with: - node-version: 20.x + - uses: pnpm/action-setup@v4 - - name: Install pnpm - uses: pnpm/action-setup@v4 + - uses: actions/setup-node@v4 + with: + node-version: 22 + cache: pnpm + cache-dependency-path: pnpm-lock.yaml - name: Install dependencies - run: pnpm install + run: pnpm install --frozen-lockfile - name: Build docs - run: pnpm --filter gridplus-sdk-docs run build + run: pnpm turbo run build --filter=gridplus-sdk-docs - name: Upload production-ready build files uses: actions/upload-artifact@v4 diff --git a/.github/workflows/publish.yml b/.github/workflows/publish.yml index 19ff0f2a..784c0769 100644 --- a/.github/workflows/publish.yml +++ b/.github/workflows/publish.yml @@ -8,24 +8,24 @@ jobs: publish-npm: name: Publish runs-on: ubuntu-latest + timeout-minutes: 60 steps: - - name: Checkout code - uses: actions/checkout@v5 + - uses: actions/checkout@v4 - - name: Install Node.js - uses: actions/setup-node@v3 + - uses: pnpm/action-setup@v4 + + - uses: actions/setup-node@v4 with: - node-version: 20.x + node-version: 22 registry-url: 'https://registry.npmjs.org' - - - name: Install pnpm - uses: pnpm/action-setup@v4 + cache: pnpm + cache-dependency-path: pnpm-lock.yaml - name: Install dependencies - run: pnpm install + run: pnpm install --frozen-lockfile - name: Build project - run: pnpm run build + run: pnpm turbo run build - name: Publish to NPM run: pnpm --filter gridplus-sdk publish --no-git-checks diff --git a/biome.json b/biome.json index 5e9ef10e..db22f803 100644 --- a/biome.json +++ b/biome.json @@ -8,10 +8,8 @@ "formatter": { "enabled": true, "formatWithErrors": false, - "indentStyle": "tab", "indentWidth": 2, "lineEnding": "lf", - "lineWidth": 240, "attributePosition": "auto" }, "linter": { diff --git a/packages/docs/src/components/HomepageFeatures.tsx b/packages/docs/src/components/HomepageFeatures.tsx index ea04dfb8..603dda5b 100644 --- a/packages/docs/src/components/HomepageFeatures.tsx +++ b/packages/docs/src/components/HomepageFeatures.tsx @@ -11,21 +11,32 @@ const FeatureList: FeatureItem[] = [ { title: 'Easy to Use', image: '/img/undraw_docusaurus_mountain.svg', - description: <>Docusaurus was designed from the ground up to be easily installed and used to get your website up and running quickly., + description: ( + <> + Docusaurus was designed from the ground up to be easily installed and + used to get your website up and running quickly. + + ), }, { title: 'Focus on What Matters', image: '/img/undraw_docusaurus_tree.svg', description: ( <> - Docusaurus lets you focus on your docs, and we'll do the chores. Go ahead and move your docs into the docs directory. + Docusaurus lets you focus on your docs, and we'll do the chores. Go + ahead and move your docs into the docs directory. ), }, { title: 'Powered by React', image: '/img/undraw_docusaurus_react.svg', - description: <>Extend or customize your website layout by reusing React. Docusaurus can be extended while reusing the same header and footer., + description: ( + <> + Extend or customize your website layout by reusing React. Docusaurus can + be extended while reusing the same header and footer. + + ), }, ] diff --git a/packages/docs/src/pages/_index.tsx b/packages/docs/src/pages/_index.tsx index 47df9c05..b0f833fe 100644 --- a/packages/docs/src/pages/_index.tsx +++ b/packages/docs/src/pages/_index.tsx @@ -18,7 +18,10 @@ function HomepageHeader() {

    {siteConfig.title}

    {siteConfig.tagline}

    - + Getting Started
    @@ -31,7 +34,10 @@ export default function Home(): JSX.Element { const { siteConfig } = useDocusaurusContext() const ExtendedLayout = Layout as React.ComponentType return ( - +
    {/* */}
    diff --git a/packages/example/src/App.tsx b/packages/example/src/App.tsx index efe52d4e..afbe8e1b 100644 --- a/packages/example/src/App.tsx +++ b/packages/example/src/App.tsx @@ -6,7 +6,10 @@ import { Lattice } from './Lattice' function App() { const [label, setLabel] = useState('No Device') - const getStoredClient = useCallback(async () => window.localStorage.getItem('storedClient') || '', []) + const getStoredClient = useCallback( + async () => window.localStorage.getItem('storedClient') || '', + [], + ) const setStoredClient = useCallback(async (storedClient: string | null) => { if (!storedClient) return @@ -60,10 +63,17 @@ function App() { border: '1px solid black', }} > -
    + - +
    @@ -76,7 +86,10 @@ function App() { border: '1px solid black', }} > -
    +
    diff --git a/packages/example/src/Lattice.tsx b/packages/example/src/Lattice.tsx index 5e7d1850..140dce15 100644 --- a/packages/example/src/Lattice.tsx +++ b/packages/example/src/Lattice.tsx @@ -1,5 +1,14 @@ import { useState } from 'react' -import { addAddressTags, fetchAddressTags, fetchAddresses, fetchLedgerLiveAddresses, removeAddressTags, sign, signMessage, type AddressTag } from 'gridplus-sdk' +import { + addAddressTags, + fetchAddressTags, + fetchAddresses, + fetchLedgerLiveAddresses, + removeAddressTags, + sign, + signMessage, + type AddressTag, +} from 'gridplus-sdk' import { Button } from './Button' interface LatticeProps { @@ -28,8 +37,20 @@ export const Lattice = ({ label }: LatticeProps) => { }} >

    {label}

    - - + +

    Addresses

    diff --git a/packages/sdk/src/__test__/e2e/api.test.ts b/packages/sdk/src/__test__/e2e/api.test.ts index fbcd97bb..23662c74 100644 --- a/packages/sdk/src/__test__/e2e/api.test.ts +++ b/packages/sdk/src/__test__/e2e/api.test.ts @@ -3,7 +3,8 @@ vi.mock('../../functions/fetchDecoder.ts', () => ({ })) vi.mock('../../util', async () => { - const actual = await vi.importActual('../../util') + const actual = + await vi.importActual('../../util') return { ...actual, fetchCalldataDecoder: vi.fn().mockResolvedValue({ @@ -34,7 +35,14 @@ import { signBtcWrappedSegwitTx, signMessage, } from '../../api' -import { addAddressTags, fetchAddressTags, fetchLedgerLiveAddresses, removeAddressTags, sign, signSolanaTx } from '../../api/index' +import { + addAddressTags, + fetchAddressTags, + fetchLedgerLiveAddresses, + removeAddressTags, + sign, + signSolanaTx, +} from '../../api/index' import { HARDENED_OFFSET } from '../../constants' import { buildRandomMsg } from '../utils/builders' import { BTC_PURPOSE_P2SH_P2WPKH, BTC_TESTNET_COIN } from '../utils/helpers' @@ -52,16 +60,29 @@ describe('API', () => { const btcTxData = { prevOuts: [ { - txHash: '6e78493091f80d89a92ae3152df7fbfbdc44df09cf01a9b76c5113c02eaf2e0f', + txHash: + '6e78493091f80d89a92ae3152df7fbfbdc44df09cf01a9b76c5113c02eaf2e0f', value: 10000, index: 1, - signerPath: [BTC_PURPOSE_P2SH_P2WPKH, BTC_TESTNET_COIN, HARDENED_OFFSET, 0, 0], + signerPath: [ + BTC_PURPOSE_P2SH_P2WPKH, + BTC_TESTNET_COIN, + HARDENED_OFFSET, + 0, + 0, + ], }, ], recipient: 'mhifA1DwiMPHTjSJM8FFSL8ibrzWaBCkVT', value: 1000, fee: 1000, - changePath: [BTC_PURPOSE_P2SH_P2WPKH, BTC_TESTNET_COIN, HARDENED_OFFSET, 1, 0], + changePath: [ + BTC_PURPOSE_P2SH_P2WPKH, + BTC_TESTNET_COIN, + HARDENED_OFFSET, + 1, + 0, + ], } test('legacy', async () => { await signBtcLegacyTx(btcTxData) @@ -105,8 +126,16 @@ describe('API', () => { }) test('legacy', async () => { - const toHex = (v: bigint | number) => (typeof v === 'bigint' ? `0x${v.toString(16)}` : v) - const rawTx = RLP.encode([txData.nonce, toHex(txData.gasPrice), toHex(txData.gas), txData.to, toHex(txData.value), txData.data]) + const toHex = (v: bigint | number) => + typeof v === 'bigint' ? `0x${v.toString(16)}` : v + const rawTx = RLP.encode([ + txData.nonce, + toHex(txData.gasPrice), + toHex(txData.gas), + txData.to, + toHex(txData.value), + txData.data, + ]) await sign(rawTx) }) }) @@ -122,9 +151,20 @@ describe('API', () => { describe('address tags', () => { beforeAll(async () => { try { - await Promise.race([fetchAddressTags({ n: 1 }), new Promise((_, reject) => setTimeout(() => reject(new Error('Address tag RPC timed out')), 5000))]) + await Promise.race([ + fetchAddressTags({ n: 1 }), + new Promise((_, reject) => + setTimeout( + () => reject(new Error('Address tag RPC timed out')), + 5000, + ), + ), + ]) } catch (err) { - console.warn('Skipping address tag tests due to connectivity issue:', (err as Error).message) + console.warn( + 'Skipping address tag tests due to connectivity issue:', + (err as Error).message, + ) } }) @@ -213,9 +253,12 @@ describe('API', () => { }) test('fetch multiple addresses with wildcard', async () => { - const addresses = await fetchAddressesByDerivationPath("44'/60'/0'/0/X", { - n: 5, - }) + const addresses = await fetchAddressesByDerivationPath( + "44'/60'/0'/0/X", + { + n: 5, + }, + ) expect(addresses).toHaveLength(5) addresses.forEach((address) => { expect(address).toBeTruthy() @@ -223,10 +266,13 @@ describe('API', () => { }) test('fetch addresses with offset', async () => { - const addresses = await fetchAddressesByDerivationPath("44'/60'/0'/0/X", { - n: 3, - startPathIndex: 10, - }) + const addresses = await fetchAddressesByDerivationPath( + "44'/60'/0'/0/X", + { + n: 3, + startPathIndex: 10, + }, + ) expect(addresses).toHaveLength(3) addresses.forEach((address) => { expect(address).toBeTruthy() @@ -234,9 +280,12 @@ describe('API', () => { }) test('fetch addresses with lowercase x wildcard', async () => { - const addresses = await fetchAddressesByDerivationPath("44'/60'/0'/0/x", { - n: 2, - }) + const addresses = await fetchAddressesByDerivationPath( + "44'/60'/0'/0/x", + { + n: 2, + }, + ) expect(addresses).toHaveLength(2) addresses.forEach((address) => { expect(address).toBeTruthy() @@ -244,9 +293,12 @@ describe('API', () => { }) test('fetch addresses with wildcard in middle of path', async () => { - const addresses = await fetchAddressesByDerivationPath("44'/60'/X'/0/0", { - n: 3, - }) + const addresses = await fetchAddressesByDerivationPath( + "44'/60'/X'/0/0", + { + n: 3, + }, + ) expect(addresses).toHaveLength(3) addresses.forEach((address) => { expect(address).toBeTruthy() @@ -254,9 +306,12 @@ describe('API', () => { }) test('fetch solana addresses with wildcard in middle of path', async () => { - const addresses = await fetchAddressesByDerivationPath("44'/501'/X'/0'", { - n: 1, - }) + const addresses = await fetchAddressesByDerivationPath( + "44'/501'/X'/0'", + { + n: 1, + }, + ) expect(addresses).toHaveLength(1) addresses.forEach((address) => { expect(address).toBeTruthy() @@ -264,21 +319,29 @@ describe('API', () => { }) test('error on invalid derivation path', async () => { - await expect(fetchAddressesByDerivationPath('invalid/path')).rejects.toThrow() + await expect( + fetchAddressesByDerivationPath('invalid/path'), + ).rejects.toThrow() }) test('fetch single address when n=1 with wildcard', async () => { - const addresses = await fetchAddressesByDerivationPath("44'/60'/0'/0/X", { - n: 1, - }) + const addresses = await fetchAddressesByDerivationPath( + "44'/60'/0'/0/X", + { + n: 1, + }, + ) expect(addresses).toHaveLength(1) expect(addresses[0]).toBeTruthy() }) test('fetch no addresses when n=0', async () => { - const addresses = await fetchAddressesByDerivationPath("44'/60'/0'/0/X", { - n: 0, - }) + const addresses = await fetchAddressesByDerivationPath( + "44'/60'/0'/0/X", + { + n: 0, + }, + ) expect(addresses).toHaveLength(0) }) }) diff --git a/packages/sdk/src/__test__/e2e/btc.test.ts b/packages/sdk/src/__test__/e2e/btc.test.ts index 6c5ab067..4bedfafb 100644 --- a/packages/sdk/src/__test__/e2e/btc.test.ts +++ b/packages/sdk/src/__test__/e2e/btc.test.ts @@ -16,7 +16,13 @@ import BIP32Factory, { type BIP32Interface } from 'bip32' import * as ecc from 'tiny-secp256k1' import type { Client } from '../../client' import { getPrng, getTestnet } from '../utils/getters' -import { BTC_PURPOSE_P2PKH, BTC_PURPOSE_P2SH_P2WPKH, BTC_PURPOSE_P2WPKH, setup_btc_sig_test, stripDER } from '../utils/helpers' +import { + BTC_PURPOSE_P2PKH, + BTC_PURPOSE_P2SH_P2WPKH, + BTC_PURPOSE_P2WPKH, + setup_btc_sig_test, + stripDER, +} from '../utils/helpers' import { setupClient } from '../utils/setup' import { TEST_SEED } from '../utils/testConstants' @@ -53,11 +59,20 @@ async function testSign({ txReq, signingKeys, sigHashes, client }: any) { for (let i = 0; i < len; i++) { const sig = stripDER(tx.sigs?.[i]) const verification = signingKeys[i].verify(sigHashes[i], sig) - expect(verification).toEqualElseLog(true, `Signature validation failed for priv=${signingKeys[i].privateKey.toString('hex')}, ` + `hash=${sigHashes[i].toString('hex')}, sig=${sig.toString('hex')}`) + expect(verification).toEqualElseLog( + true, + `Signature validation failed for priv=${signingKeys[i].privateKey.toString('hex')}, ` + + `hash=${sigHashes[i].toString('hex')}, sig=${sig.toString('hex')}`, + ) } } -async function runTestSet(opts: any, wallet: BIP32Interface | null, inputsSlice: InputObj[], client) { +async function runTestSet( + opts: any, + wallet: BIP32Interface | null, + inputsSlice: InputObj[], + client, +) { expect(wallet).not.toEqualElseLog(null, 'Wallet not available') if (TEST_TESTNET) { // Testnet + change diff --git a/packages/sdk/src/__test__/e2e/eth.msg.test.ts b/packages/sdk/src/__test__/e2e/eth.msg.test.ts index aee44e66..653cd787 100644 --- a/packages/sdk/src/__test__/e2e/eth.msg.test.ts +++ b/packages/sdk/src/__test__/e2e/eth.msg.test.ts @@ -36,17 +36,30 @@ describe('ETH Messages', () => { const protocol = 'signPersonal' const msg = '⚠️' const msg2 = 'ASCII plus ⚠️' - await expect(client.sign(buildEthMsgReq(msg, protocol))).rejects.toThrow(/Lattice can only display ASCII/) - await expect(client.sign(buildEthMsgReq(msg2, protocol))).rejects.toThrow(/Lattice can only display ASCII/) + await expect(client.sign(buildEthMsgReq(msg, protocol))).rejects.toThrow( + /Lattice can only display ASCII/, + ) + await expect(client.sign(buildEthMsgReq(msg2, protocol))).rejects.toThrow( + /Lattice can only display ASCII/, + ) }) it('Should test ASCII buffers', async () => { - await runEthMsg(buildEthMsgReq(Buffer.from('i am an ascii buffer'), 'signPersonal'), client) - await runEthMsg(buildEthMsgReq(Buffer.from('{\n\ttest: foo\n}'), 'signPersonal'), client) + await runEthMsg( + buildEthMsgReq(Buffer.from('i am an ascii buffer'), 'signPersonal'), + client, + ) + await runEthMsg( + buildEthMsgReq(Buffer.from('{\n\ttest: foo\n}'), 'signPersonal'), + client, + ) }) it('Should test hex buffers', async () => { - await runEthMsg(buildEthMsgReq(Buffer.from('abcdef', 'hex'), 'signPersonal'), client) + await runEthMsg( + buildEthMsgReq(Buffer.from('abcdef', 'hex'), 'signPersonal'), + client, + ) }) it('Should test a message that needs to be prehashed', async () => { @@ -59,7 +72,10 @@ describe('ETH Messages', () => { // `personal_sign` requests have a max size smaller than other requests because a header // is displayed in the text region of the screen. The size of this is captured // by `fwConstants.personalSignHeaderSz`. - const maxMsgSz = fwConstants.ethMaxMsgSz + fwConstants.personalSignHeaderSz + fwConstants.extraDataMaxFrames * fwConstants.extraDataFrameSz + const maxMsgSz = + fwConstants.ethMaxMsgSz + + fwConstants.personalSignHeaderSz + + fwConstants.extraDataMaxFrames * fwConstants.extraDataFrameSz const maxValid = `0x${randomBytes(maxMsgSz).toString('hex')}` const minInvalid = `0x${randomBytes(maxMsgSz + 1).toString('hex')}` const zeroInvalid = '0x' @@ -72,15 +88,26 @@ describe('ETH Messages', () => { // one (which is too large) gets prehashed. const largeSignPath = [x, HARDENED_OFFSET + 60, x, x, x] as SigningPath await runEthMsg(buildEthMsgReq(maxValid, protocol, largeSignPath), client) - await runEthMsg(buildEthMsgReq(minInvalid, protocol, largeSignPath), client) + await runEthMsg( + buildEthMsgReq(minInvalid, protocol, largeSignPath), + client, + ) // Using a zero length payload should auto-reject - await expect(client.sign(buildEthMsgReq(zeroInvalid, protocol))).rejects.toThrow(/Invalid Request/) + await expect( + client.sign(buildEthMsgReq(zeroInvalid, protocol)), + ).rejects.toThrow(/Invalid Request/) }) describe(`Test ${5} random payloads`, () => { for (let i = 0; i < 5; i++) { it(`Payload: ${i}`, async () => { - await runEthMsg(buildEthMsgReq(buildRandomMsg('signPersonal', client), 'signPersonal'), client) + await runEthMsg( + buildEthMsgReq( + buildRandomMsg('signPersonal', client), + 'signPersonal', + ), + client, + ) }) } }) @@ -158,7 +185,8 @@ describe('ETH Messages', () => { side: '1', matchingPolicy: '0x00000000006411739da1c40b106f8511de5d1fac', collection: '0x7a15b36cb834aea88553de69077d3777460d73ac', - tokenId: '5280336779268220421569573059971679349075200194886069432279714075018412552192', + tokenId: + '5280336779268220421569573059971679349075200194886069432279714075018412552192', amount: '1', paymentToken: '0x0000000000000000000000000000000000000000', price: '990000000000000000', @@ -233,7 +261,8 @@ describe('ETH Messages', () => { accountID: 32494, feeTokenID: 0, maxFee: 100, - publicKey: '11413934541425201845815969801249874136651857829494005371571206042985258823663', + publicKey: + '11413934541425201845815969801249874136651857829494005371571206042985258823663', validUntil: 1631655383, nonce: 0, }, @@ -266,7 +295,8 @@ describe('ETH Messages', () => { verifyingContract: '0xf03f457a30e598d5020164a339727ef40f2b8fbc', }, message: { - sender: '0x841fe4876763357975d60da128d8a54bb045d76a64656661756c740000000000', + sender: + '0x841fe4876763357975d60da128d8a54bb045d76a64656661756c740000000000', priceX18: '28898000000000000000000', amount: '-10000000000000000', expiration: '4611687701117784255', @@ -285,17 +315,21 @@ describe('ETH Messages', () => { version: '1', }, message: { - getMakerAmount: '0xf4a215c30000000000000000000000000000000000000000000000000de0b6b3a764000000000000000000000000000000000000000000000000000018fae27693b40000', - getTakerAmount: '0x296637bf0000000000000000000000000000000000000000000000000de0b6b3a764000000000000000000000000000000000000000000000000000018fae27693b40000', + getMakerAmount: + '0xf4a215c30000000000000000000000000000000000000000000000000de0b6b3a764000000000000000000000000000000000000000000000000000018fae27693b40000', + getTakerAmount: + '0x296637bf0000000000000000000000000000000000000000000000000de0b6b3a764000000000000000000000000000000000000000000000000000018fae27693b40000', interaction: '0x', makerAsset: '0x0d500b1d8e8ef31e21c99d1db9a6444d3adf1270', - makerAssetData: '0x23b872dd0000000000000000000000003e3e2ccdd7bae6bbd4a64e8d16ca8842061335eb00000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000de0b6b3a7640000', + makerAssetData: + '0x23b872dd0000000000000000000000003e3e2ccdd7bae6bbd4a64e8d16ca8842061335eb00000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000de0b6b3a7640000', permit: '0x', predicate: '0x961d5b1e000000000000000000000000000000000000000000000000000000000000004000000000000000000000000000000000000000000000000000000000000000a00000000000000000000000000000000000000000000000000000000000000002000000000000000000000000b707d89d29c189421163515c59e42147371d6857000000000000000000000000b707d89d29c189421163515c59e42147371d68570000000000000000000000000000000000000000000000000000000000000002000000000000000000000000000000000000000000000000000000000000004000000000000000000000000000000000000000000000000000000000000000c00000000000000000000000000000000000000000000000000000000000000044cf6fc6e30000000000000000000000003e3e2ccdd7bae6bbd4a64e8d16ca8842061335eb000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000002463592c2b00000000000000000000000000000000000000000000000000000000613e28e500000000000000000000000000000000000000000000000000000000', salt: '885135864076', takerAsset: '0x8f3cf7ad23cd3cadbd9735aff958023239c6a063', - takerAssetData: '0x23b872dd00000000000000000000000000000000000000000000000000000000000000000000000000000000000000003e3e2ccdd7bae6bbd4a64e8d16ca8842061335eb00000000000000000000000000000000000000000000000018fae27693b40000', + takerAssetData: + '0x23b872dd00000000000000000000000000000000000000000000000000000000000000000000000000000000000000003e3e2ccdd7bae6bbd4a64e8d16ca8842061335eb00000000000000000000000000000000000000000000000018fae27693b40000', }, primaryType: 'Order', types: { @@ -640,11 +674,13 @@ describe('ETH Messages', () => { message: { allocations: [ { - reactorKey: '0x6f686d2d64656661756c74000000000000000000000000000000000000000000', + reactorKey: + '0x6f686d2d64656661756c74000000000000000000000000000000000000000000', amount: '1', }, { - reactorKey: '0x6f686d2d64656661756c74000000000000000000000000000000000000000000', + reactorKey: + '0x6f686d2d64656661756c74000000000000000000000000000000000000000000', amount: '2', }, ], @@ -694,11 +730,13 @@ describe('ETH Messages', () => { integer: 56, allocations: [ { - reactorKey: '0x6f686d2d64656661756c74000000000000000000000000000000000000000000', + reactorKey: + '0x6f686d2d64656661756c74000000000000000000000000000000000000000000', amount: '1', }, { - reactorKey: '0x6f686d2d64656661756c74000000000000000000000000000000000000000000', + reactorKey: + '0x6f686d2d64656661756c74000000000000000000000000000000000000000000', amount: '2', }, ], @@ -737,11 +775,13 @@ describe('ETH Messages', () => { message: { allocations: [ { - reactorKey: '0x6f686d2d64656661756c74000000000000000000000000000000000000000000', + reactorKey: + '0x6f686d2d64656661756c74000000000000000000000000000000000000000000', amount: ['1', '2'], }, { - reactorKey: '0x6f686d2d64656661756c74000000000000000000000000000000000000000000', + reactorKey: + '0x6f686d2d64656661756c74000000000000000000000000000000000000000000', amount: ['2', '3'], }, ], @@ -794,7 +834,8 @@ describe('ETH Messages', () => { test: 'hello', allocations: [ { - reactorKey: '0x6f686d2d64656661756c74000000000000000000000000000000000000000000', + reactorKey: + '0x6f686d2d64656661756c74000000000000000000000000000000000000000000', dummy: [ { foo: '0xabcd', @@ -805,7 +846,8 @@ describe('ETH Messages', () => { ], }, { - reactorKey: '0x6f686d2d64656661756c74000000000000000000000000000000000000000000', + reactorKey: + '0x6f686d2d64656661756c74000000000000000000000000000000000000000000', dummy: [ { foo: '0xdeadbeef', @@ -883,9 +925,12 @@ describe('ETH Messages', () => { BYTES16: '0x7ace034ab088fdd434f1e817f32171a0', BYTES20: '0x4ab51f2d5bfdc0f1b96f83358d5f356c98583573', BYTES21: '0x6ecdc19b30c7fa712ba334458d77377b6a586bbab5', - BYTES31: '0x06c21824a98643f96643b3220962f441210b007f4c19dfdf0dea53d097fc28', - BYTES32: '0x59cfcbf35256451756b02fa644d3d0748bd98f5904febf3433e6df19b4df7452', - BYTES: '0x0354b2c449772905b2598a93f5da69962f0444e0a6e2429e8f844f1011446f6fe81815846fb6ebe2d213968d1f8532749735f5702f565db0429b2fe596d295d9c06241389fe97fb2f3b91e1e0f2d978fb26e366737451f1193097bd0a2332e0bfc0cdb631005', + BYTES31: + '0x06c21824a98643f96643b3220962f441210b007f4c19dfdf0dea53d097fc28', + BYTES32: + '0x59cfcbf35256451756b02fa644d3d0748bd98f5904febf3433e6df19b4df7452', + BYTES: + '0x0354b2c449772905b2598a93f5da69962f0444e0a6e2429e8f844f1011446f6fe81815846fb6ebe2d213968d1f8532749735f5702f565db0429b2fe596d295d9c06241389fe97fb2f3b91e1e0f2d978fb26e366737451f1193097bd0a2332e0bfc0cdb631005', STRING: 'I am a string hello there human', BOOL: true, ADDRESS: '0x078a8d6eba928e7ea787ed48f71c5936aed4625d', @@ -1310,7 +1355,10 @@ describe('ETH Messages', () => { describe('test 5 random payloads', () => { for (let i = 0; i < 5; i++) { it(`Payload #${i}`, async () => { - await runEthMsg(buildEthMsgReq(buildRandomMsg('eip712', client), 'eip712'), client) + await runEthMsg( + buildEthMsgReq(buildRandomMsg('eip712', client), 'eip712'), + client, + ) }) } }) diff --git a/packages/sdk/src/__test__/e2e/general.test.ts b/packages/sdk/src/__test__/e2e/general.test.ts index c4899dfd..ed0473b0 100644 --- a/packages/sdk/src/__test__/e2e/general.test.ts +++ b/packages/sdk/src/__test__/e2e/general.test.ts @@ -23,7 +23,15 @@ import { LatticeResponseCode, ProtocolConstants } from '../../protocol' import { randomBytes } from '../../util' import { buildEthSignRequest } from '../utils/builders' import { getDeviceId } from '../utils/getters' -import { BTC_COIN, BTC_PURPOSE_P2PKH, BTC_PURPOSE_P2SH_P2WPKH, BTC_PURPOSE_P2WPKH, BTC_TESTNET_COIN, ETH_COIN, setupTestClient } from '../utils/helpers' +import { + BTC_COIN, + BTC_PURPOSE_P2PKH, + BTC_PURPOSE_P2SH_P2WPKH, + BTC_PURPOSE_P2WPKH, + BTC_TESTNET_COIN, + ETH_COIN, + setupTestClient, +} from '../utils/helpers' import { setupClient } from '../utils/setup' import type { Client } from '../../client' @@ -172,13 +180,18 @@ describe('General', () => { ctx.skip() return } - const { txData, req, maxDataSz, common } = await buildEthSignRequest(client) - await question('Please REJECT the next request if the warning screen displays. Press enter to continue.') + const { txData, req, maxDataSz, common } = + await buildEthSignRequest(client) + await question( + 'Please REJECT the next request if the warning screen displays. Press enter to continue.', + ) txData.data = randomBytes(maxDataSz) req.data.data = randomBytes(maxDataSz + 1) const tx = createTx(txData, { common }) req.data.payload = tx.getMessageToSign() - await expect(client.sign(req)).rejects.toThrow(`${ProtocolConstants.responseMsg[LatticeResponseCode.userDeclined]}`) + await expect(client.sign(req)).rejects.toThrow( + `${ProtocolConstants.responseMsg[LatticeResponseCode.userDeclined]}`, + ) }) }) @@ -187,17 +200,30 @@ describe('General', () => { const txData = { prevOuts: [ { - txHash: '6e78493091f80d89a92ae3152df7fbfbdc44df09cf01a9b76c5113c02eaf2e0f', + txHash: + '6e78493091f80d89a92ae3152df7fbfbdc44df09cf01a9b76c5113c02eaf2e0f', value: 10000, index: 1, - signerPath: [BTC_PURPOSE_P2SH_P2WPKH, BTC_TESTNET_COIN, HARDENED_OFFSET, 0, 0], + signerPath: [ + BTC_PURPOSE_P2SH_P2WPKH, + BTC_TESTNET_COIN, + HARDENED_OFFSET, + 0, + 0, + ], }, ], recipient: 'mhifA1DwiMPHTjSJM8FFSL8ibrzWaBCkVT', value: 1000, fee: 1000, // isSegwit: false, // old encoding - changePath: [BTC_PURPOSE_P2SH_P2WPKH, BTC_TESTNET_COIN, HARDENED_OFFSET, 1, 0], + changePath: [ + BTC_PURPOSE_P2SH_P2WPKH, + BTC_TESTNET_COIN, + HARDENED_OFFSET, + 1, + 0, + ], } const req = { currency: 'BTC' as const, @@ -214,16 +240,29 @@ describe('General', () => { const txData = { prevOuts: [ { - txHash: 'ab8288ef207f11186af98db115aa7120aa36ceb783e8792fb7b2f39c88109a99', + txHash: + 'ab8288ef207f11186af98db115aa7120aa36ceb783e8792fb7b2f39c88109a99', value: 10000, index: 1, - signerPath: [BTC_PURPOSE_P2SH_P2WPKH, BTC_TESTNET_COIN, HARDENED_OFFSET, 0, 0], + signerPath: [ + BTC_PURPOSE_P2SH_P2WPKH, + BTC_TESTNET_COIN, + HARDENED_OFFSET, + 0, + 0, + ], }, ], recipient: '2NGZrVvZG92qGYqzTLjCAewvPZ7JE8S8VxE', value: 1000, fee: 1000, - changePath: [BTC_PURPOSE_P2SH_P2WPKH, BTC_TESTNET_COIN, HARDENED_OFFSET, 1, 0], + changePath: [ + BTC_PURPOSE_P2SH_P2WPKH, + BTC_TESTNET_COIN, + HARDENED_OFFSET, + 1, + 0, + ], } const req = { currency: 'BTC' as const, @@ -239,16 +278,29 @@ describe('General', () => { const txData = { prevOuts: [ { - txHash: 'f93d0a77f58b4274d84f427d647f1f27e38b4db79fd975691e15109fde7ea06e', + txHash: + 'f93d0a77f58b4274d84f427d647f1f27e38b4db79fd975691e15109fde7ea06e', value: 1802440, index: 1, - signerPath: [BTC_PURPOSE_P2SH_P2WPKH, BTC_TESTNET_COIN, HARDENED_OFFSET, 1, 0], + signerPath: [ + BTC_PURPOSE_P2SH_P2WPKH, + BTC_TESTNET_COIN, + HARDENED_OFFSET, + 1, + 0, + ], }, ], recipient: 'tb1qym0z2a939lefrgw67ep5flhf43dvpg3h4s96tn', value: 1000, fee: 1000, - changePath: [BTC_PURPOSE_P2SH_P2WPKH, BTC_TESTNET_COIN, HARDENED_OFFSET, 1, 0], + changePath: [ + BTC_PURPOSE_P2SH_P2WPKH, + BTC_TESTNET_COIN, + HARDENED_OFFSET, + 1, + 0, + ], } const req = { currency: 'BTC' as const, @@ -264,17 +316,30 @@ describe('General', () => { const txData = { prevOuts: [ { - txHash: 'b2efdbdd3340d2bc547671ce3993a6f05d70343c07578f9d7f5626fdfc06fa35', + txHash: + 'b2efdbdd3340d2bc547671ce3993a6f05d70343c07578f9d7f5626fdfc06fa35', value: 76800, index: 0, - signerPath: [BTC_PURPOSE_P2WPKH, BTC_TESTNET_COIN, HARDENED_OFFSET, 0, 0], + signerPath: [ + BTC_PURPOSE_P2WPKH, + BTC_TESTNET_COIN, + HARDENED_OFFSET, + 0, + 0, + ], }, ], recipient: '2N4gqWT4oqWL2gz9ps92z9fm2Bg3FUkqG7Q', value: 70000, fee: 4380, isSegwit: true, - changePath: [BTC_PURPOSE_P2WPKH, BTC_TESTNET_COIN, HARDENED_OFFSET, 1, 0], + changePath: [ + BTC_PURPOSE_P2WPKH, + BTC_TESTNET_COIN, + HARDENED_OFFSET, + 1, + 0, + ], } const req = { currency: 'BTC' as const, diff --git a/packages/sdk/src/__test__/e2e/kv.test.ts b/packages/sdk/src/__test__/e2e/kv.test.ts index 400bb11a..91291e3f 100644 --- a/packages/sdk/src/__test__/e2e/kv.test.ts +++ b/packages/sdk/src/__test__/e2e/kv.test.ts @@ -46,7 +46,9 @@ describe('key-value', () => { it('Should ask if the user wants to reset state', async () => { let answer = 'Y' if (process.env.CI !== '1') { - answer = question('Do you want to clear all kv records and start anew? (Y/N) ') + answer = question( + 'Do you want to clear all kv records and start anew? (Y/N) ', + ) } else { answer = 'Y' } @@ -66,7 +68,9 @@ describe('key-value', () => { } if (lastTotal !== null && total >= lastTotal) { - console.warn('[kv.test] KV cleanup halted to avoid infinite loop (no progress detected).') + console.warn( + '[kv.test] KV cleanup halted to avoid infinite loop (no progress detected).', + ) break } @@ -91,7 +95,9 @@ describe('key-value', () => { it('Should make a request to an unknown address', async () => { await client.sign(ETH_REQ as unknown as SignRequestParams).catch((err) => { - expect(err.message).toContain(ProtocolConstants.responseMsg[LatticeResponseCode.userDeclined]) + expect(err.message).toContain( + ProtocolConstants.responseMsg[LatticeResponseCode.userDeclined], + ) }) }) @@ -111,15 +117,23 @@ describe('key-value', () => { it('Should fail to add records with unicode characters', async () => { const badKey = { '0x🔥🦍': 'Muh name' } const badVal = { UNISWAP_ADDR: 'val🔥🦍' } - await expect(client.addKvRecords({ records: badKey })).rejects.toThrow('Unicode characters are not supported.') - await expect(client.addKvRecords({ records: badVal })).rejects.toThrow('Unicode characters are not supported.') + await expect(client.addKvRecords({ records: badKey })).rejects.toThrow( + 'Unicode characters are not supported.', + ) + await expect(client.addKvRecords({ records: badVal })).rejects.toThrow( + 'Unicode characters are not supported.', + ) }) it('Should fail to add zero length keys and values', async () => { const badKey = { '': 'Muh name' } const badVal = { UNISWAP_ADDR: '' } - await expect(client.addKvRecords({ records: badKey })).rejects.toThrow('Keys and values must be >0 characters.') - await expect(client.addKvRecords({ records: badVal })).rejects.toThrow('Keys and values must be >0 characters.') + await expect(client.addKvRecords({ records: badKey })).rejects.toThrow( + 'Keys and values must be >0 characters.', + ) + await expect(client.addKvRecords({ records: badVal })).rejects.toThrow( + 'Keys and values must be >0 characters.', + ) }) it('Should fetch the newly created records', async () => { @@ -212,7 +226,9 @@ describe('key-value', () => { it('Should make another request to make sure case sensitivity is enforced', async () => { await client.sign(ETH_REQ as unknown as SignRequestParams).catch((err) => { - expect(err.message).toContain(ProtocolConstants.responseMsg[LatticeResponseCode.userDeclined]) + expect(err.message).toContain( + ProtocolConstants.responseMsg[LatticeResponseCode.userDeclined], + ) }) }) diff --git a/packages/sdk/src/__test__/e2e/non-exportable.test.ts b/packages/sdk/src/__test__/e2e/non-exportable.test.ts index 1a700a35..54478b2a 100644 --- a/packages/sdk/src/__test__/e2e/non-exportable.test.ts +++ b/packages/sdk/src/__test__/e2e/non-exportable.test.ts @@ -47,7 +47,9 @@ describe('Non-Exportable Seed', () => { return } // NOTE: non-exportable seeds were deprecated from the normal setup pathway in firmware v0.12.0 - const result = await question('Do you have a non-exportable SafeCard seed loaded and wish to continue? (Y/N) ') + const result = await question( + 'Do you have a non-exportable SafeCard seed loaded and wish to continue? (Y/N) ', + ) if (result.toLowerCase() !== 'y') { runTests = false } @@ -91,7 +93,9 @@ describe('Non-Exportable Seed', () => { } // Validate that tx sigs are non-uniform const unsignedMsg = tx.getMessageToSign() - const unsigned = Array.isArray(unsignedMsg) ? RLP.encode(unsignedMsg) : unsignedMsg + const unsigned = Array.isArray(unsignedMsg) + ? RLP.encode(unsignedMsg) + : unsignedMsg const tx1Resp = await client.sign(txReq) validateSig(tx1Resp, unsigned) const tx2Resp = await client.sign(txReq) diff --git a/packages/sdk/src/__test__/e2e/signing/bls.test.ts b/packages/sdk/src/__test__/e2e/signing/bls.test.ts index 7699dda2..b8ade177 100644 --- a/packages/sdk/src/__test__/e2e/signing/bls.test.ts +++ b/packages/sdk/src/__test__/e2e/signing/bls.test.ts @@ -15,7 +15,12 @@ * Running with a different mnemonic will cause test failures due to * incorrect key derivations. */ -import { create as createKeystore, decrypt as decryptKeystore, isValidKeystore, verifyPassword } from '@chainsafe/bls-keystore' +import { + create as createKeystore, + decrypt as decryptKeystore, + isValidKeystore, + verifyPassword, +} from '@chainsafe/bls-keystore' import { getPublicKey, sign } from '@noble/bls12-381' import { deriveSeedTree } from 'bls12-381-keygen' import { question } from 'readline-sync' @@ -51,21 +56,30 @@ describe('[BLS keys]', () => { // Check if firmware supports BLS (requires >= 0.17.0) const fwVersion = client.getFwVersion() - const versionStr = fwVersion ? `${fwVersion.major}.${fwVersion.minor}.${fwVersion.fix}` : 'unknown' + const versionStr = fwVersion + ? `${fwVersion.major}.${fwVersion.minor}.${fwVersion.fix}` + : 'unknown' console.log(`\n[BLS Test] Firmware version: ${versionStr}`) console.log('[BLS Test] Raw fwVersion:', fwVersion) const fwConstants = client.getFwConstants() console.log('[BLS Test] getAddressFlags:', fwConstants?.getAddressFlags) - console.log('[BLS Test] BLS12_381_G1_PUB constant:', Constants.GET_ADDR_FLAGS.BLS12_381_G1_PUB) + console.log( + '[BLS Test] BLS12_381_G1_PUB constant:', + Constants.GET_ADDR_FLAGS.BLS12_381_G1_PUB, + ) - supportsBLS = fwConstants?.getAddressFlags?.includes(Constants.GET_ADDR_FLAGS.BLS12_381_G1_PUB as number) + supportsBLS = fwConstants?.getAddressFlags?.includes( + Constants.GET_ADDR_FLAGS.BLS12_381_G1_PUB as number, + ) console.log(`[BLS Test] supportsBLS: ${supportsBLS}\n`) if (!supportsBLS) { - console.warn(`\nSkipping BLS tests: Firmware version ${versionStr} does not support BLS operations.\nBLS support requires firmware version >= 0.17.0\n`) + console.warn( + `\nSkipping BLS tests: Firmware version ${versionStr} does not support BLS operations.\nBLS support requires firmware version >= 0.17.0\n`, + ) } }) @@ -158,9 +172,17 @@ async function testBLSDerivationAndSig(seed, signerPath) { const refPubStr = Buffer.from(refPub).toString('hex') const refSig = await sign(msg, priv) const refSigStr = Buffer.from(refSig).toString('hex') - expect(latticePub.toString('hex')).to.equal(refPubStr, 'Deposit public key mismatch') - expect(latticeSig.pubkey.toString('hex')).to.equal(refPubStr, 'Lattice signature returned wrong pubkey') - expect(Buffer.from(latticeSig.sig as unknown as Buffer).toString('hex')).to.equal(refSigStr, 'Signature mismatch') + expect(latticePub.toString('hex')).to.equal( + refPubStr, + 'Deposit public key mismatch', + ) + expect(latticeSig.pubkey.toString('hex')).to.equal( + refPubStr, + 'Lattice signature returned wrong pubkey', + ) + expect( + Buffer.from(latticeSig.sig as unknown as Buffer).toString('hex'), + ).to.equal(refSigStr, 'Signature mismatch') } async function validateExportedKeystore(seed, path, pw, expKeystoreBuffer) { const exportedKeystore = JSON.parse(expKeystoreBuffer.toString()) @@ -168,18 +190,39 @@ async function validateExportedKeystore(seed, path, pw, expKeystoreBuffer) { const pub = getPublicKey(priv) // Validate the keystore in isolation - expect(isValidKeystore(exportedKeystore)).to.equal(true, 'Exported keystore invalid!') + expect(isValidKeystore(exportedKeystore)).to.equal( + true, + 'Exported keystore invalid!', + ) const expPwVerified = await verifyPassword(exportedKeystore, pw) - expect(expPwVerified).to.equal(true, `Password could not be verified in exported keystore. Expected "${pw}"`) + expect(expPwVerified).to.equal( + true, + `Password could not be verified in exported keystore. Expected "${pw}"`, + ) const expDec = await decryptKeystore(exportedKeystore, pw) - expect(Buffer.from(expDec).toString('hex')).to.equal(Buffer.from(priv).toString('hex'), 'Exported keystore did not properly encrypt key!') - expect(exportedKeystore.pubkey).to.equal(Buffer.from(pub).toString('hex'), 'Wrong public key exported from Lattice') + expect(Buffer.from(expDec).toString('hex')).to.equal( + Buffer.from(priv).toString('hex'), + 'Exported keystore did not properly encrypt key!', + ) + expect(exportedKeystore.pubkey).to.equal( + Buffer.from(pub).toString('hex'), + 'Wrong public key exported from Lattice', + ) // Generate an independent keystore and compare decrypted contents const genKeystore = await createKeystore(pw, priv, pub, getPathStr(path)) - expect(isValidKeystore(genKeystore)).to.equal(true, 'Generated keystore invalid?') + expect(isValidKeystore(genKeystore)).to.equal( + true, + 'Generated keystore invalid?', + ) const genPwVerified = await verifyPassword(genKeystore, pw) - expect(genPwVerified).to.equal(true, 'Password could not be verified in generated keystore?') + expect(genPwVerified).to.equal( + true, + 'Password could not be verified in generated keystore?', + ) const genDec = await decryptKeystore(genKeystore, pw) - expect(Buffer.from(expDec).toString('hex')).to.equal(Buffer.from(genDec).toString('hex'), 'Exported encrypted privkey did not match factory test example...') + expect(Buffer.from(expDec).toString('hex')).to.equal( + Buffer.from(genDec).toString('hex'), + 'Exported encrypted privkey did not match factory test example...', + ) } diff --git a/packages/sdk/src/__test__/e2e/signing/determinism.test.ts b/packages/sdk/src/__test__/e2e/signing/determinism.test.ts index 285dd818..77f88e4b 100644 --- a/packages/sdk/src/__test__/e2e/signing/determinism.test.ts +++ b/packages/sdk/src/__test__/e2e/signing/determinism.test.ts @@ -1,8 +1,19 @@ import { HARDENED_OFFSET } from '../../../constants' import type { SignRequestParams, WalletPath } from '../../../types' import { randomBytes } from '../../../util' -import { DEFAULT_SIGNER, buildMsgReq, buildRandomVectors, buildTx, buildTxReq } from '../../utils/builders' -import { deriveAddress, signEip712JS, signPersonalJS, testUniformSigs } from '../../utils/determinism' +import { + DEFAULT_SIGNER, + buildMsgReq, + buildRandomVectors, + buildTx, + buildTxReq, +} from '../../utils/builders' +import { + deriveAddress, + signEip712JS, + signPersonalJS, + testUniformSigs, +} from '../../utils/determinism' /** * REQUIRED TEST MNEMONIC: * These tests require a SafeCard loaded with the standard test mnemonic: @@ -33,11 +44,29 @@ describe('[Determinism]', () => { }) it('Should validate some Ledger addresses derived from the test seed', async () => { - const path0 = [BTC_PURPOSE_P2PKH, ETH_COIN, HARDENED_OFFSET, 0, 0] as WalletPath + const path0 = [ + BTC_PURPOSE_P2PKH, + ETH_COIN, + HARDENED_OFFSET, + 0, + 0, + ] as WalletPath const addr0 = deriveAddress(TEST_SEED, path0) - const path1 = [BTC_PURPOSE_P2PKH, ETH_COIN, HARDENED_OFFSET + 1, 0, 0] as WalletPath + const path1 = [ + BTC_PURPOSE_P2PKH, + ETH_COIN, + HARDENED_OFFSET + 1, + 0, + 0, + ] as WalletPath const addr1 = deriveAddress(TEST_SEED, path1) - const path8 = [BTC_PURPOSE_P2PKH, ETH_COIN, HARDENED_OFFSET + 8, 0, 0] as WalletPath + const path8 = [ + BTC_PURPOSE_P2PKH, + ETH_COIN, + HARDENED_OFFSET + 8, + 0, + 0, + ] as WalletPath const addr8 = deriveAddress(TEST_SEED, path8) // Fetch these addresses from the Lattice and validate @@ -47,13 +76,22 @@ describe('[Determinism]', () => { n: 1, } const latAddr0 = await client.getAddresses(req) - expect((latAddr0[0] as string).toLowerCase()).toEqualElseLog(addr0.toLowerCase(), 'Incorrect address 0 fetched. Ensure your SafeCard is loaded with the test mnemonic: "test test test test test test test test test test test junk"') + expect((latAddr0[0] as string).toLowerCase()).toEqualElseLog( + addr0.toLowerCase(), + 'Incorrect address 0 fetched. Ensure your SafeCard is loaded with the test mnemonic: "test test test test test test test test test test test junk"', + ) req.startPath = path1 const latAddr1 = await client.getAddresses(req) - expect((latAddr1[0] as string).toLowerCase()).toEqualElseLog(addr1.toLowerCase(), 'Incorrect address 1 fetched. Ensure your SafeCard is loaded with the test mnemonic: "test test test test test test test test test test test junk"') + expect((latAddr1[0] as string).toLowerCase()).toEqualElseLog( + addr1.toLowerCase(), + 'Incorrect address 1 fetched. Ensure your SafeCard is loaded with the test mnemonic: "test test test test test test test test test test test junk"', + ) req.startPath = path8 const latAddr8 = await client.getAddresses(req) - expect((latAddr8[0] as string).toLowerCase()).toEqualElseLog(addr8.toLowerCase(), 'Incorrect address 8 fetched. Ensure your SafeCard is loaded with the test mnemonic: "test test test test test test test test test test test junk"') + expect((latAddr8[0] as string).toLowerCase()).toEqualElseLog( + addr8.toLowerCase(), + 'Incorrect address 8 fetched. Ensure your SafeCard is loaded with the test mnemonic: "test test test test test test test test test test test junk"', + ) }) }) @@ -108,7 +146,10 @@ describe('[Determinism]', () => { const res = await client.sign(msgReq as unknown as SignRequestParams) const sig = getSigStr(res) const jsSig = signPersonalJS(msgReq.data.payload, msgReq.data.signerPath) - expect(sig).toEqualElseLog(jsSig, 'Lattice sig does not match JS reference') + expect(sig).toEqualElseLog( + jsSig, + 'Lattice sig does not match JS reference', + ) }) it('Should validate signature from addr1', async () => { @@ -117,7 +158,10 @@ describe('[Determinism]', () => { const res = await client.sign(msgReq as unknown as SignRequestParams) const sig = getSigStr(res) const jsSig = signPersonalJS(msgReq.data.payload, msgReq.data.signerPath) - expect(sig).toEqualElseLog(jsSig, 'Lattice sig does not match JS reference') + expect(sig).toEqualElseLog( + jsSig, + 'Lattice sig does not match JS reference', + ) }) it('Should validate signature from addr8', async () => { @@ -126,7 +170,10 @@ describe('[Determinism]', () => { const res = await client.sign(msgReq as unknown as SignRequestParams) const sig = getSigStr(res) const jsSig = signPersonalJS(msgReq.data.payload, msgReq.data.signerPath) - expect(sig).toEqualElseLog(jsSig, 'Lattice sig does not match JS reference') + expect(sig).toEqualElseLog( + jsSig, + 'Lattice sig does not match JS reference', + ) }) }) @@ -137,7 +184,10 @@ describe('[Determinism]', () => { const res = await client.sign(msgReq as unknown as SignRequestParams) const sig = getSigStr(res) const jsSig = signPersonalJS(msgReq.data.payload, msgReq.data.signerPath) - expect(sig).toEqualElseLog(jsSig, 'Lattice sig does not match JS reference') + expect(sig).toEqualElseLog( + jsSig, + 'Lattice sig does not match JS reference', + ) }) it('Should validate signature from addr1', async () => { @@ -146,7 +196,10 @@ describe('[Determinism]', () => { const res = await client.sign(msgReq as unknown as SignRequestParams) const sig = getSigStr(res) const jsSig = signPersonalJS(msgReq.data.payload, msgReq.data.signerPath) - expect(sig).toEqualElseLog(jsSig, 'Lattice sig does not match JS reference') + expect(sig).toEqualElseLog( + jsSig, + 'Lattice sig does not match JS reference', + ) }) it('Should validate signature from addr8', async () => { @@ -155,7 +208,10 @@ describe('[Determinism]', () => { const res = await client.sign(msgReq as unknown as SignRequestParams) const sig = getSigStr(res) const jsSig = signPersonalJS(msgReq.data.payload, msgReq.data.signerPath) - expect(sig).toEqualElseLog(jsSig, 'Lattice sig does not match JS reference') + expect(sig).toEqualElseLog( + jsSig, + 'Lattice sig does not match JS reference', + ) }) }) @@ -166,7 +222,10 @@ describe('[Determinism]', () => { const res = await client.sign(msgReq as unknown as SignRequestParams) const sig = getSigStr(res) const jsSig = signPersonalJS(msgReq.data.payload, msgReq.data.signerPath) - expect(sig).toEqualElseLog(jsSig, 'Lattice sig does not match JS reference') + expect(sig).toEqualElseLog( + jsSig, + 'Lattice sig does not match JS reference', + ) }) it('Should validate signature from addr1', async () => { @@ -175,7 +234,10 @@ describe('[Determinism]', () => { const res = await client.sign(msgReq as unknown as SignRequestParams) const sig = getSigStr(res) const jsSig = signPersonalJS(msgReq.data.payload, msgReq.data.signerPath) - expect(sig).toEqualElseLog(jsSig, 'Lattice sig does not match JS reference') + expect(sig).toEqualElseLog( + jsSig, + 'Lattice sig does not match JS reference', + ) }) it('Should validate signature from addr8', async () => { @@ -184,7 +246,10 @@ describe('[Determinism]', () => { const res = await client.sign(msgReq as unknown as SignRequestParams) const sig = getSigStr(res) const jsSig = signPersonalJS(msgReq.data.payload, msgReq.data.signerPath) - expect(sig).toEqualElseLog(jsSig, 'Lattice sig does not match JS reference') + expect(sig).toEqualElseLog( + jsSig, + 'Lattice sig does not match JS reference', + ) }) }) @@ -235,7 +300,10 @@ describe('[Determinism]', () => { const res = await client.sign(msgReq as unknown as SignRequestParams) const sig = getSigStr(res) const jsSig = signEip712JS(msgReq.data.payload, msgReq.data.signerPath) - expect(sig).toEqualElseLog(jsSig, 'Lattice EIP712 sig does not match JS reference') + expect(sig).toEqualElseLog( + jsSig, + 'Lattice EIP712 sig does not match JS reference', + ) }) it('Should validate signature from addr1', async () => { @@ -243,7 +311,10 @@ describe('[Determinism]', () => { const res = await client.sign(msgReq as unknown as SignRequestParams) const sig = getSigStr(res) const jsSig = signEip712JS(msgReq.data.payload, msgReq.data.signerPath) - expect(sig).toEqualElseLog(jsSig, 'Lattice EIP712 sig does not match JS reference') + expect(sig).toEqualElseLog( + jsSig, + 'Lattice EIP712 sig does not match JS reference', + ) }) it('Should validate signature from addr8', async () => { @@ -251,7 +322,10 @@ describe('[Determinism]', () => { const res = await client.sign(msgReq as unknown as SignRequestParams) const sig = getSigStr(res) const jsSig = signEip712JS(msgReq.data.payload, msgReq.data.signerPath) - expect(sig).toEqualElseLog(jsSig, 'Lattice EIP712 sig does not match JS reference') + expect(sig).toEqualElseLog( + jsSig, + 'Lattice EIP712 sig does not match JS reference', + ) }) }) @@ -292,7 +366,10 @@ describe('[Determinism]', () => { const res = await client.sign(msgReq as unknown as SignRequestParams) const sig = getSigStr(res) const jsSig = signEip712JS(msgReq.data.payload, msgReq.data.signerPath) - expect(sig).toEqualElseLog(jsSig, 'Lattice EIP712 sig does not match JS reference') + expect(sig).toEqualElseLog( + jsSig, + 'Lattice EIP712 sig does not match JS reference', + ) }) it('Should validate signature from addr1', async () => { @@ -300,7 +377,10 @@ describe('[Determinism]', () => { const res = await client.sign(msgReq as unknown as SignRequestParams) const sig = getSigStr(res) const jsSig = signEip712JS(msgReq.data.payload, msgReq.data.signerPath) - expect(sig).toEqualElseLog(jsSig, 'Lattice EIP712 sig does not match JS reference') + expect(sig).toEqualElseLog( + jsSig, + 'Lattice EIP712 sig does not match JS reference', + ) }) it('Should validate signature from addr8', async () => { @@ -308,7 +388,10 @@ describe('[Determinism]', () => { const res = await client.sign(msgReq as unknown as SignRequestParams) const sig = getSigStr(res) const jsSig = signEip712JS(msgReq.data.payload, msgReq.data.signerPath) - expect(sig).toEqualElseLog(jsSig, 'Lattice EIP712 sig does not match JS reference') + expect(sig).toEqualElseLog( + jsSig, + 'Lattice EIP712 sig does not match JS reference', + ) }) }) @@ -349,21 +432,30 @@ describe('[Determinism]', () => { const res = await client.sign(msgReq as unknown as SignRequestParams) const sig = getSigStr(res) const jsSig = signEip712JS(msgReq.data.payload, msgReq.data.signerPath) - expect(sig).toEqualElseLog(jsSig, 'Lattice EIP712 sig does not match JS reference') + expect(sig).toEqualElseLog( + jsSig, + 'Lattice EIP712 sig does not match JS reference', + ) }) it('Should validate signature from addr1', async () => { msgReq.data.signerPath[2] = HARDENED_OFFSET + 1 const res = await client.sign(msgReq as unknown as SignRequestParams) const sig = getSigStr(res) const jsSig = signEip712JS(msgReq.data.payload, msgReq.data.signerPath) - expect(sig).toEqualElseLog(jsSig, 'Lattice EIP712 sig does not match JS reference') + expect(sig).toEqualElseLog( + jsSig, + 'Lattice EIP712 sig does not match JS reference', + ) }) it('Should validate signature from addr8', async () => { msgReq.data.signerPath[2] = HARDENED_OFFSET + 8 const res = await client.sign(msgReq as unknown as SignRequestParams) const sig = getSigStr(res) const jsSig = signEip712JS(msgReq.data.payload, msgReq.data.signerPath) - expect(sig).toEqualElseLog(jsSig, 'Lattice EIP712 sig does not match JS reference') + expect(sig).toEqualElseLog( + jsSig, + 'Lattice EIP712 sig does not match JS reference', + ) }) }) diff --git a/packages/sdk/src/__test__/e2e/signing/eip712-vectors.ts b/packages/sdk/src/__test__/e2e/signing/eip712-vectors.ts index c111796b..be6b97ff 100644 --- a/packages/sdk/src/__test__/e2e/signing/eip712-vectors.ts +++ b/packages/sdk/src/__test__/e2e/signing/eip712-vectors.ts @@ -256,7 +256,9 @@ export const EIP712_MESSAGE_VECTORS: Array<{ }, primaryType: 'Data', message: { - value: BigInt('115792089237316195423570985008687907853269984665640564039457584007913129639935'), + value: BigInt( + '115792089237316195423570985008687907853269984665640564039457584007913129639935', + ), }, }, }, @@ -274,7 +276,9 @@ export const EIP712_MESSAGE_VECTORS: Array<{ }, primaryType: 'Data', message: { - value: BigInt('-57896044618658097711785492504343953926634992332820282019728792003956564819968'), + value: BigInt( + '-57896044618658097711785492504343953926634992332820282019728792003956564819968', + ), }, }, }, diff --git a/packages/sdk/src/__test__/e2e/signing/evm-tx.test.ts b/packages/sdk/src/__test__/e2e/signing/evm-tx.test.ts index e37aabbe..145e43a2 100644 --- a/packages/sdk/src/__test__/e2e/signing/evm-tx.test.ts +++ b/packages/sdk/src/__test__/e2e/signing/evm-tx.test.ts @@ -8,7 +8,13 @@ import { setupClient } from '../../utils/setup' import { signAndCompareTransaction } from '../../utils/viemComparison' -import { EDGE_CASE_TEST_VECTORS, EIP1559_TEST_VECTORS, EIP2930_TEST_VECTORS, EIP7702_TEST_VECTORS, LEGACY_VECTORS } from './vectors' +import { + EDGE_CASE_TEST_VECTORS, + EIP1559_TEST_VECTORS, + EIP2930_TEST_VECTORS, + EIP7702_TEST_VECTORS, + LEGACY_VECTORS, +} from './vectors' describe('EVM Transaction Signing - Unified Test Suite', () => { beforeAll(async () => { diff --git a/packages/sdk/src/__test__/e2e/signing/solana/__mocks__/programs.ts b/packages/sdk/src/__test__/e2e/signing/solana/__mocks__/programs.ts index 4e2d25de..e04fef69 100644 --- a/packages/sdk/src/__test__/e2e/signing/solana/__mocks__/programs.ts +++ b/packages/sdk/src/__test__/e2e/signing/solana/__mocks__/programs.ts @@ -1,33 +1,87 @@ export const raydiumProgram = Buffer.from([ - 2, 0, 10, 24, 47, 41, 101, 134, 4, 204, 35, 219, 9, 43, 55, 20, 255, 76, 29, 27, 175, 197, 102, 175, 108, 72, 231, 146, 134, 30, 219, 42, 168, 236, 17, 3, 209, 66, 76, 96, 212, 139, 188, 184, 209, 19, 177, 79, 32, 176, 155, 148, 130, 200, - 98, 110, 128, 160, 186, 226, 136, 168, 203, 105, 117, 38, 171, 161, 41, 11, 235, 184, 125, 115, 158, 2, 137, 19, 123, 58, 193, 31, 193, 215, 57, 0, 240, 162, 154, 197, 182, 102, 96, 91, 101, 29, 31, 63, 168, 145, 47, 189, 251, 138, 30, - 226, 174, 123, 162, 139, 147, 54, 101, 251, 231, 142, 230, 173, 232, 212, 153, 225, 58, 113, 24, 53, 97, 26, 89, 169, 83, 10, 48, 190, 92, 28, 22, 83, 66, 168, 232, 41, 135, 46, 21, 208, 110, 217, 210, 243, 1, 237, 247, 237, 183, 71, 1, - 193, 117, 86, 188, 217, 134, 204, 52, 147, 214, 106, 218, 145, 18, 3, 14, 152, 187, 194, 134, 97, 177, 146, 183, 102, 40, 90, 33, 217, 240, 113, 89, 142, 64, 120, 22, 21, 175, 245, 69, 184, 172, 211, 86, 215, 29, 176, 82, 209, 2, 198, - 205, 207, 75, 152, 201, 35, 86, 169, 205, 157, 142, 144, 96, 235, 228, 249, 204, 249, 212, 189, 80, 236, 218, 125, 206, 4, 54, 254, 165, 182, 236, 127, 232, 154, 117, 171, 105, 133, 186, 163, 206, 201, 159, 70, 10, 17, 138, 239, 21, 37, - 109, 139, 81, 59, 76, 57, 145, 209, 124, 73, 151, 15, 83, 117, 205, 87, 153, 166, 158, 188, 141, 157, 153, 230, 131, 244, 4, 118, 170, 60, 182, 39, 25, 110, 87, 174, 54, 186, 81, 129, 162, 212, 79, 70, 190, 180, 232, 143, 72, 102, 119, - 242, 172, 80, 2, 53, 168, 117, 184, 99, 184, 206, 230, 204, 5, 165, 141, 30, 37, 25, 159, 230, 130, 247, 252, 139, 86, 49, 132, 94, 254, 8, 198, 72, 16, 173, 202, 16, 222, 102, 65, 202, 40, 218, 190, 74, 34, 21, 163, 163, 179, 232, 137, - 88, 91, 104, 174, 112, 225, 188, 55, 104, 213, 116, 77, 143, 100, 55, 181, 95, 3, 149, 44, 132, 146, 146, 85, 62, 245, 56, 164, 243, 10, 248, 172, 153, 97, 85, 86, 177, 82, 92, 148, 248, 130, 133, 54, 160, 65, 92, 148, 142, 53, 148, 190, - 85, 136, 146, 82, 19, 186, 209, 204, 116, 25, 155, 69, 186, 207, 152, 45, 54, 94, 70, 191, 239, 106, 17, 161, 122, 157, 199, 83, 30, 23, 42, 103, 226, 74, 230, 111, 113, 173, 56, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, - 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 55, 153, 140, 203, 242, 208, 69, 139, 97, 92, 188, 198, 177, 163, 103, 196, 116, 158, 159, 239, 115, 6, 98, 46, 27, 27, 88, 145, 1, 32, 188, 154, 65, 87, 176, 88, 15, 49, 197, 252, 228, 74, 98, 88, 45, - 188, 249, 215, 142, 231, 89, 67, 160, 132, 163, 147, 179, 80, 54, 141, 34, 137, 147, 8, 75, 217, 73, 196, 54, 2, 195, 63, 32, 119, 144, 237, 22, 163, 82, 76, 161, 185, 151, 92, 241, 33, 162, 169, 12, 255, 236, 125, 248, 182, 138, 205, 95, - 183, 40, 175, 220, 220, 4, 120, 120, 238, 132, 58, 111, 235, 68, 175, 40, 205, 83, 163, 72, 40, 217, 144, 59, 90, 213, 230, 151, 25, 85, 42, 133, 15, 45, 110, 2, 164, 122, 248, 36, 208, 154, 182, 157, 196, 45, 112, 203, 40, 203, 250, 36, - 159, 183, 238, 87, 185, 210, 86, 193, 39, 98, 239, 140, 151, 37, 143, 78, 36, 137, 241, 187, 61, 16, 41, 20, 142, 13, 131, 11, 90, 19, 153, 218, 255, 16, 132, 4, 142, 123, 216, 219, 233, 248, 89, 6, 155, 136, 87, 254, 171, 129, 132, 251, - 104, 127, 99, 70, 24, 192, 53, 218, 196, 57, 220, 26, 235, 59, 85, 152, 160, 240, 0, 0, 0, 0, 1, 6, 167, 213, 23, 25, 44, 92, 81, 33, 140, 201, 76, 61, 74, 241, 127, 88, 218, 238, 8, 155, 161, 253, 68, 227, 219, 217, 138, 0, 0, 0, 0, 6, - 221, 246, 225, 215, 101, 161, 147, 217, 203, 225, 70, 206, 235, 121, 172, 28, 180, 133, 237, 95, 91, 55, 145, 58, 140, 245, 133, 126, 255, 0, 169, 238, 204, 15, 249, 117, 156, 236, 123, 109, 247, 173, 57, 139, 143, 19, 21, 15, 180, 230, - 189, 171, 230, 228, 215, 211, 229, 22, 94, 108, 135, 17, 93, 5, 14, 2, 0, 1, 52, 0, 0, 0, 0, 48, 96, 46, 0, 0, 0, 0, 0, 165, 0, 0, 0, 0, 0, 0, 0, 6, 221, 246, 225, 215, 101, 161, 147, 217, 203, 225, 70, 206, 235, 121, 172, 28, 180, 133, - 237, 95, 91, 55, 145, 58, 140, 245, 133, 126, 255, 0, 169, 23, 4, 1, 21, 0, 22, 1, 1, 20, 7, 0, 9, 0, 15, 14, 23, 22, 0, 17, 18, 23, 10, 16, 7, 6, 13, 2, 19, 12, 11, 3, 4, 8, 5, 18, 1, 9, 0, 17, 9, 64, 66, 15, 0, 0, 0, 0, 0, 89, 241, 0, - 0, 0, 0, 0, 0, 23, 3, 1, 0, 0, 1, 9, + 2, 0, 10, 24, 47, 41, 101, 134, 4, 204, 35, 219, 9, 43, 55, 20, 255, 76, 29, + 27, 175, 197, 102, 175, 108, 72, 231, 146, 134, 30, 219, 42, 168, 236, 17, 3, + 209, 66, 76, 96, 212, 139, 188, 184, 209, 19, 177, 79, 32, 176, 155, 148, 130, + 200, 98, 110, 128, 160, 186, 226, 136, 168, 203, 105, 117, 38, 171, 161, 41, + 11, 235, 184, 125, 115, 158, 2, 137, 19, 123, 58, 193, 31, 193, 215, 57, 0, + 240, 162, 154, 197, 182, 102, 96, 91, 101, 29, 31, 63, 168, 145, 47, 189, 251, + 138, 30, 226, 174, 123, 162, 139, 147, 54, 101, 251, 231, 142, 230, 173, 232, + 212, 153, 225, 58, 113, 24, 53, 97, 26, 89, 169, 83, 10, 48, 190, 92, 28, 22, + 83, 66, 168, 232, 41, 135, 46, 21, 208, 110, 217, 210, 243, 1, 237, 247, 237, + 183, 71, 1, 193, 117, 86, 188, 217, 134, 204, 52, 147, 214, 106, 218, 145, 18, + 3, 14, 152, 187, 194, 134, 97, 177, 146, 183, 102, 40, 90, 33, 217, 240, 113, + 89, 142, 64, 120, 22, 21, 175, 245, 69, 184, 172, 211, 86, 215, 29, 176, 82, + 209, 2, 198, 205, 207, 75, 152, 201, 35, 86, 169, 205, 157, 142, 144, 96, 235, + 228, 249, 204, 249, 212, 189, 80, 236, 218, 125, 206, 4, 54, 254, 165, 182, + 236, 127, 232, 154, 117, 171, 105, 133, 186, 163, 206, 201, 159, 70, 10, 17, + 138, 239, 21, 37, 109, 139, 81, 59, 76, 57, 145, 209, 124, 73, 151, 15, 83, + 117, 205, 87, 153, 166, 158, 188, 141, 157, 153, 230, 131, 244, 4, 118, 170, + 60, 182, 39, 25, 110, 87, 174, 54, 186, 81, 129, 162, 212, 79, 70, 190, 180, + 232, 143, 72, 102, 119, 242, 172, 80, 2, 53, 168, 117, 184, 99, 184, 206, 230, + 204, 5, 165, 141, 30, 37, 25, 159, 230, 130, 247, 252, 139, 86, 49, 132, 94, + 254, 8, 198, 72, 16, 173, 202, 16, 222, 102, 65, 202, 40, 218, 190, 74, 34, + 21, 163, 163, 179, 232, 137, 88, 91, 104, 174, 112, 225, 188, 55, 104, 213, + 116, 77, 143, 100, 55, 181, 95, 3, 149, 44, 132, 146, 146, 85, 62, 245, 56, + 164, 243, 10, 248, 172, 153, 97, 85, 86, 177, 82, 92, 148, 248, 130, 133, 54, + 160, 65, 92, 148, 142, 53, 148, 190, 85, 136, 146, 82, 19, 186, 209, 204, 116, + 25, 155, 69, 186, 207, 152, 45, 54, 94, 70, 191, 239, 106, 17, 161, 122, 157, + 199, 83, 30, 23, 42, 103, 226, 74, 230, 111, 113, 173, 56, 0, 0, 0, 0, 0, 0, + 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, + 55, 153, 140, 203, 242, 208, 69, 139, 97, 92, 188, 198, 177, 163, 103, 196, + 116, 158, 159, 239, 115, 6, 98, 46, 27, 27, 88, 145, 1, 32, 188, 154, 65, 87, + 176, 88, 15, 49, 197, 252, 228, 74, 98, 88, 45, 188, 249, 215, 142, 231, 89, + 67, 160, 132, 163, 147, 179, 80, 54, 141, 34, 137, 147, 8, 75, 217, 73, 196, + 54, 2, 195, 63, 32, 119, 144, 237, 22, 163, 82, 76, 161, 185, 151, 92, 241, + 33, 162, 169, 12, 255, 236, 125, 248, 182, 138, 205, 95, 183, 40, 175, 220, + 220, 4, 120, 120, 238, 132, 58, 111, 235, 68, 175, 40, 205, 83, 163, 72, 40, + 217, 144, 59, 90, 213, 230, 151, 25, 85, 42, 133, 15, 45, 110, 2, 164, 122, + 248, 36, 208, 154, 182, 157, 196, 45, 112, 203, 40, 203, 250, 36, 159, 183, + 238, 87, 185, 210, 86, 193, 39, 98, 239, 140, 151, 37, 143, 78, 36, 137, 241, + 187, 61, 16, 41, 20, 142, 13, 131, 11, 90, 19, 153, 218, 255, 16, 132, 4, 142, + 123, 216, 219, 233, 248, 89, 6, 155, 136, 87, 254, 171, 129, 132, 251, 104, + 127, 99, 70, 24, 192, 53, 218, 196, 57, 220, 26, 235, 59, 85, 152, 160, 240, + 0, 0, 0, 0, 1, 6, 167, 213, 23, 25, 44, 92, 81, 33, 140, 201, 76, 61, 74, 241, + 127, 88, 218, 238, 8, 155, 161, 253, 68, 227, 219, 217, 138, 0, 0, 0, 0, 6, + 221, 246, 225, 215, 101, 161, 147, 217, 203, 225, 70, 206, 235, 121, 172, 28, + 180, 133, 237, 95, 91, 55, 145, 58, 140, 245, 133, 126, 255, 0, 169, 238, 204, + 15, 249, 117, 156, 236, 123, 109, 247, 173, 57, 139, 143, 19, 21, 15, 180, + 230, 189, 171, 230, 228, 215, 211, 229, 22, 94, 108, 135, 17, 93, 5, 14, 2, 0, + 1, 52, 0, 0, 0, 0, 48, 96, 46, 0, 0, 0, 0, 0, 165, 0, 0, 0, 0, 0, 0, 0, 6, + 221, 246, 225, 215, 101, 161, 147, 217, 203, 225, 70, 206, 235, 121, 172, 28, + 180, 133, 237, 95, 91, 55, 145, 58, 140, 245, 133, 126, 255, 0, 169, 23, 4, 1, + 21, 0, 22, 1, 1, 20, 7, 0, 9, 0, 15, 14, 23, 22, 0, 17, 18, 23, 10, 16, 7, 6, + 13, 2, 19, 12, 11, 3, 4, 8, 5, 18, 1, 9, 0, 17, 9, 64, 66, 15, 0, 0, 0, 0, 0, + 89, 241, 0, 0, 0, 0, 0, 0, 23, 3, 1, 0, 0, 1, 9, ]) export const dexlabProgram = Buffer.from([ - 3, 0, 7, 11, 47, 41, 101, 134, 4, 204, 35, 219, 9, 43, 55, 20, 255, 76, 29, 27, 175, 197, 102, 175, 108, 72, 231, 146, 134, 30, 219, 42, 168, 236, 17, 3, 27, 22, 186, 110, 40, 115, 180, 32, 87, 1, 133, 235, 70, 100, 93, 110, 49, 176, 47, - 100, 89, 135, 44, 204, 229, 104, 63, 16, 169, 85, 62, 17, 87, 228, 120, 25, 237, 212, 48, 216, 155, 158, 74, 24, 15, 13, 148, 130, 112, 67, 62, 67, 34, 39, 96, 92, 200, 155, 110, 50, 187, 157, 163, 92, 87, 174, 54, 186, 81, 129, 162, 212, - 79, 70, 190, 180, 232, 143, 72, 102, 119, 242, 172, 80, 2, 53, 168, 117, 184, 99, 184, 206, 230, 204, 5, 165, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 55, 153, 140, 203, 242, 208, 69, - 139, 97, 92, 188, 198, 177, 163, 103, 196, 116, 158, 159, 239, 115, 6, 98, 46, 27, 27, 88, 145, 1, 32, 188, 154, 140, 151, 37, 143, 78, 36, 137, 241, 187, 61, 16, 41, 20, 142, 13, 131, 11, 90, 19, 153, 218, 255, 16, 132, 4, 142, 123, 216, - 219, 233, 248, 89, 198, 250, 122, 243, 190, 219, 173, 58, 61, 101, 243, 106, 171, 201, 116, 49, 177, 187, 228, 194, 210, 246, 224, 228, 124, 166, 2, 3, 69, 47, 93, 97, 6, 155, 136, 87, 254, 171, 129, 132, 251, 104, 127, 99, 70, 24, 192, - 53, 218, 196, 57, 220, 26, 235, 59, 85, 152, 160, 240, 0, 0, 0, 0, 1, 6, 167, 213, 23, 25, 44, 92, 81, 33, 140, 201, 76, 61, 74, 241, 127, 88, 218, 238, 8, 155, 161, 253, 68, 227, 219, 217, 138, 0, 0, 0, 0, 6, 221, 246, 225, 215, 101, - 161, 147, 217, 203, 225, 70, 206, 235, 121, 172, 28, 180, 133, 237, 95, 91, 55, 145, 58, 140, 245, 133, 126, 255, 0, 169, 2, 206, 198, 46, 159, 44, 171, 207, 167, 152, 197, 80, 224, 171, 133, 195, 162, 248, 176, 1, 213, 55, 144, 195, 89, - 153, 30, 36, 158, 74, 236, 222, 5, 4, 2, 0, 1, 52, 0, 0, 0, 0, 48, 96, 46, 0, 0, 0, 0, 0, 165, 0, 0, 0, 0, 0, 0, 0, 6, 221, 246, 225, 215, 101, 161, 147, 217, 203, 225, 70, 206, 235, 121, 172, 28, 180, 133, 237, 95, 91, 55, 145, 58, 140, - 245, 133, 126, 255, 0, 169, 10, 4, 1, 8, 0, 9, 1, 1, 6, 7, 0, 3, 0, 5, 4, 10, 9, 0, 4, 2, 0, 2, 52, 0, 0, 0, 0, 240, 29, 31, 0, 0, 0, 0, 0, 165, 0, 0, 0, 0, 0, 0, 0, 6, 221, 246, 225, 215, 101, 161, 147, 217, 203, 225, 70, 206, 235, 121, - 172, 28, 180, 133, 237, 95, 91, 55, 145, 58, 140, 245, 133, 126, 255, 0, 169, 10, 4, 2, 7, 0, 9, 1, 1, + 3, 0, 7, 11, 47, 41, 101, 134, 4, 204, 35, 219, 9, 43, 55, 20, 255, 76, 29, + 27, 175, 197, 102, 175, 108, 72, 231, 146, 134, 30, 219, 42, 168, 236, 17, 3, + 27, 22, 186, 110, 40, 115, 180, 32, 87, 1, 133, 235, 70, 100, 93, 110, 49, + 176, 47, 100, 89, 135, 44, 204, 229, 104, 63, 16, 169, 85, 62, 17, 87, 228, + 120, 25, 237, 212, 48, 216, 155, 158, 74, 24, 15, 13, 148, 130, 112, 67, 62, + 67, 34, 39, 96, 92, 200, 155, 110, 50, 187, 157, 163, 92, 87, 174, 54, 186, + 81, 129, 162, 212, 79, 70, 190, 180, 232, 143, 72, 102, 119, 242, 172, 80, 2, + 53, 168, 117, 184, 99, 184, 206, 230, 204, 5, 165, 0, 0, 0, 0, 0, 0, 0, 0, 0, + 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 55, 153, + 140, 203, 242, 208, 69, 139, 97, 92, 188, 198, 177, 163, 103, 196, 116, 158, + 159, 239, 115, 6, 98, 46, 27, 27, 88, 145, 1, 32, 188, 154, 140, 151, 37, 143, + 78, 36, 137, 241, 187, 61, 16, 41, 20, 142, 13, 131, 11, 90, 19, 153, 218, + 255, 16, 132, 4, 142, 123, 216, 219, 233, 248, 89, 198, 250, 122, 243, 190, + 219, 173, 58, 61, 101, 243, 106, 171, 201, 116, 49, 177, 187, 228, 194, 210, + 246, 224, 228, 124, 166, 2, 3, 69, 47, 93, 97, 6, 155, 136, 87, 254, 171, 129, + 132, 251, 104, 127, 99, 70, 24, 192, 53, 218, 196, 57, 220, 26, 235, 59, 85, + 152, 160, 240, 0, 0, 0, 0, 1, 6, 167, 213, 23, 25, 44, 92, 81, 33, 140, 201, + 76, 61, 74, 241, 127, 88, 218, 238, 8, 155, 161, 253, 68, 227, 219, 217, 138, + 0, 0, 0, 0, 6, 221, 246, 225, 215, 101, 161, 147, 217, 203, 225, 70, 206, 235, + 121, 172, 28, 180, 133, 237, 95, 91, 55, 145, 58, 140, 245, 133, 126, 255, 0, + 169, 2, 206, 198, 46, 159, 44, 171, 207, 167, 152, 197, 80, 224, 171, 133, + 195, 162, 248, 176, 1, 213, 55, 144, 195, 89, 153, 30, 36, 158, 74, 236, 222, + 5, 4, 2, 0, 1, 52, 0, 0, 0, 0, 48, 96, 46, 0, 0, 0, 0, 0, 165, 0, 0, 0, 0, 0, + 0, 0, 6, 221, 246, 225, 215, 101, 161, 147, 217, 203, 225, 70, 206, 235, 121, + 172, 28, 180, 133, 237, 95, 91, 55, 145, 58, 140, 245, 133, 126, 255, 0, 169, + 10, 4, 1, 8, 0, 9, 1, 1, 6, 7, 0, 3, 0, 5, 4, 10, 9, 0, 4, 2, 0, 2, 52, 0, 0, + 0, 0, 240, 29, 31, 0, 0, 0, 0, 0, 165, 0, 0, 0, 0, 0, 0, 0, 6, 221, 246, 225, + 215, 101, 161, 147, 217, 203, 225, 70, 206, 235, 121, 172, 28, 180, 133, 237, + 95, 91, 55, 145, 58, 140, 245, 133, 126, 255, 0, 169, 10, 4, 2, 7, 0, 9, 1, 1, ]) diff --git a/packages/sdk/src/__test__/e2e/signing/solana/solana.test.ts b/packages/sdk/src/__test__/e2e/signing/solana/solana.test.ts index e805579c..cc663eb8 100644 --- a/packages/sdk/src/__test__/e2e/signing/solana/solana.test.ts +++ b/packages/sdk/src/__test__/e2e/signing/solana/solana.test.ts @@ -6,7 +6,12 @@ * Running with a different mnemonic will cause test failures due to * incorrect key derivations and signature mismatches. */ -import { Keypair as SolanaKeypair, PublicKey as SolanaPublicKey, SystemProgram as SolanaSystemProgram, Transaction as SolanaTransaction } from '@solana/web3.js' +import { + Keypair as SolanaKeypair, + PublicKey as SolanaPublicKey, + SystemProgram as SolanaSystemProgram, + Transaction as SolanaTransaction, +} from '@solana/web3.js' import { Constants } from '../../../..' import { HARDENED_OFFSET } from '../../../../constants' import { ensureHexBuffer } from '../../../../util' @@ -20,7 +25,12 @@ import type { Client } from '../../../../client' //--------------------------------------- // STATE DATA //--------------------------------------- -const DEFAULT_SOLANA_SIGNER_PATH = [HARDENED_OFFSET + 44, HARDENED_OFFSET + 501, HARDENED_OFFSET, HARDENED_OFFSET] +const DEFAULT_SOLANA_SIGNER_PATH = [ + HARDENED_OFFSET + 44, + HARDENED_OFFSET + 501, + HARDENED_OFFSET, + HARDENED_OFFSET, +] const prng = getPrng() describe('[Solana]', () => { @@ -78,13 +88,22 @@ describe('[Solana]', () => { const recentBlockhash = SolanaKeypair.fromSeed(randBuf).publicKey.toBase58() // Build a transaction and sign it using Solana's JS lib - const txJs = new SolanaTransaction({ recentBlockhash }).add(transfer1, transfer2) + const txJs = new SolanaTransaction({ recentBlockhash }).add( + transfer1, + transfer2, + ) txJs.setSigners(pubA, pubB) - txJs.sign(SolanaKeypair.fromSeed(derivedA.priv), SolanaKeypair.fromSeed(derivedB.priv)) + txJs.sign( + SolanaKeypair.fromSeed(derivedA.priv), + SolanaKeypair.fromSeed(derivedB.priv), + ) const serTxJs = txJs.serialize().toString('hex') // Build a copy of the transaction and get the serialized payload for signing in firmware. - const txFw = new SolanaTransaction({ recentBlockhash }).add(transfer1, transfer2) + const txFw = new SolanaTransaction({ recentBlockhash }).add( + transfer1, + transfer2, + ) txFw.setSigners(pubA, pubB) // We want to sign the Solana message, not the full transaction const payload = txFw.compileMessage().serialize() @@ -101,7 +120,10 @@ describe('[Solana]', () => { if (!resp.sig?.r || !resp.sig?.s) { throw new Error('Missing signature components in response') } - return Buffer.concat([ensureHexBuffer(resp.sig.r as string | Buffer), ensureHexBuffer(resp.sig.s as string | Buffer)]) + return Buffer.concat([ + ensureHexBuffer(resp.sig.r as string | Buffer), + ensureHexBuffer(resp.sig.s as string | Buffer), + ]) }) const sigB = await runGeneric( @@ -114,7 +136,10 @@ describe('[Solana]', () => { if (!resp.sig?.r || !resp.sig?.s) { throw new Error('Missing signature components in response') } - return Buffer.concat([ensureHexBuffer(resp.sig.r as string | Buffer), ensureHexBuffer(resp.sig.s as string | Buffer)]) + return Buffer.concat([ + ensureHexBuffer(resp.sig.r as string | Buffer), + ensureHexBuffer(resp.sig.s as string | Buffer), + ]) }) txFw.addSignature(pubA, sigA) txFw.addSignature(pubB, sigB) diff --git a/packages/sdk/src/__test__/e2e/signing/solana/solana.versioned.test.ts b/packages/sdk/src/__test__/e2e/signing/solana/solana.versioned.test.ts index 45a9c394..d355f0c4 100644 --- a/packages/sdk/src/__test__/e2e/signing/solana/solana.versioned.test.ts +++ b/packages/sdk/src/__test__/e2e/signing/solana/solana.versioned.test.ts @@ -6,7 +6,18 @@ * Running with a different mnemonic will cause test failures due to * incorrect key derivations and signature mismatches. */ -import { AddressLookupTableProgram, Connection, Keypair, LAMPORTS_PER_SOL, PublicKey, SystemProgram, Transaction, type TransactionInstruction, TransactionMessage, VersionedTransaction } from '@solana/web3.js' +import { + AddressLookupTableProgram, + Connection, + Keypair, + LAMPORTS_PER_SOL, + PublicKey, + SystemProgram, + Transaction, + type TransactionInstruction, + TransactionMessage, + VersionedTransaction, +} from '@solana/web3.js' import { fetchSolanaAddresses, signSolanaTx } from '../../../..' import { setupClient } from '../../../utils/setup' @@ -125,9 +136,13 @@ describe('solana.versioned', () => { }) // Create a VersionedTransaction from the serialized data - const versionedTransaction = VersionedTransaction.deserialize(serializedTransaction) + const versionedTransaction = VersionedTransaction.deserialize( + serializedTransaction, + ) - const signedTx = await signSolanaTx(Buffer.from(versionedTransaction.serialize())) + const signedTx = await signSolanaTx( + Buffer.from(versionedTransaction.serialize()), + ) expect(signedTx).toBeTruthy() }) @@ -135,17 +150,21 @@ describe('solana.versioned', () => { const payer = Keypair.generate() await requestAirdrop(payer.publicKey, 1) - const [transactionInstruction, pubkey] = await AddressLookupTableProgram.createLookupTable({ - payer: payer.publicKey, - authority: payer.publicKey, - recentSlot: await SOLANA_RPC.getSlot(), - }) + const [transactionInstruction, pubkey] = + await AddressLookupTableProgram.createLookupTable({ + payer: payer.publicKey, + authority: payer.publicKey, + recentSlot: await SOLANA_RPC.getSlot(), + }) await AddressLookupTableProgram.extendLookupTable({ payer: payer.publicKey, authority: payer.publicKey, lookupTable: pubkey, - addresses: [DESTINATION_WALLET_1.publicKey, DESTINATION_WALLET_2.publicKey], + addresses: [ + DESTINATION_WALLET_1.publicKey, + DESTINATION_WALLET_2.publicKey, + ], }) const messageV0 = new TransactionMessage({ diff --git a/packages/sdk/src/__test__/e2e/signing/unformatted.test.ts b/packages/sdk/src/__test__/e2e/signing/unformatted.test.ts index af13ac4f..a5f0d0e2 100644 --- a/packages/sdk/src/__test__/e2e/signing/unformatted.test.ts +++ b/packages/sdk/src/__test__/e2e/signing/unformatted.test.ts @@ -9,7 +9,13 @@ import type { Client } from '../../../client' const prng = getPrng() const numIter = getNumIter() -const DEFAULT_SIGNER = [HARDENED_OFFSET + 44, HARDENED_OFFSET + 60, HARDENED_OFFSET, 0, 0] +const DEFAULT_SIGNER = [ + HARDENED_OFFSET + 44, + HARDENED_OFFSET + 60, + HARDENED_OFFSET, + 0, + 0, +] describe('[Unformatted]', () => { let client: Client @@ -123,7 +129,9 @@ describe('[Unformatted]', () => { hashType: Constants.SIGNING.HASHES.KECCAK256, }, } - const respLegacy = await client.sign(legacyReq as Parameters[0]) + const respLegacy = await client.sign( + legacyReq as Parameters[0], + ) const genSigR = (respGeneric.sig?.r as Buffer)?.toString('hex') ?? '' const genSigS = (respGeneric.sig?.s as Buffer)?.toString('hex') ?? '' @@ -132,7 +140,10 @@ describe('[Unformatted]', () => { const genSig = `${genSigR}${genSigS}` const legSig = `${legSigR}${legSigS}` - expect(genSig).toEqualElseLog(legSig, 'Legacy and generic requests produced different sigs.') + expect(genSig).toEqualElseLog( + legSig, + 'Legacy and generic requests produced different sigs.', + ) }) for (let i = 0; i < numIter; i++) { diff --git a/packages/sdk/src/__test__/e2e/signing/vectors.ts b/packages/sdk/src/__test__/e2e/signing/vectors.ts index 1294e416..84db84ff 100644 --- a/packages/sdk/src/__test__/e2e/signing/vectors.ts +++ b/packages/sdk/src/__test__/e2e/signing/vectors.ts @@ -229,8 +229,11 @@ export const EIP2930_TEST_VECTORS: TestVector[] = [ chainId: 1, accessList: [ { - address: '0xe242e54155b1abc71fc118065270cecaaf8b7768' as `0x${string}`, - storageKeys: ['0x0000000000000000000000000000000000000000000000000000000000000001' as `0x${string}`], + address: + '0xe242e54155b1abc71fc118065270cecaaf8b7768' as `0x${string}`, + storageKeys: [ + '0x0000000000000000000000000000000000000000000000000000000000000001' as `0x${string}`, + ], }, ], }, @@ -249,12 +252,19 @@ export const EIP2930_TEST_VECTORS: TestVector[] = [ chainId: 1, accessList: [ { - address: '0x6B175474E89094C44Da98b954EedeAC495271d0F' as `0x${string}`, // DAI contract - storageKeys: ['0x0000000000000000000000000000000000000000000000000000000000000001' as `0x${string}`, '0x0000000000000000000000000000000000000000000000000000000000000002' as `0x${string}`], + address: + '0x6B175474E89094C44Da98b954EedeAC495271d0F' as `0x${string}`, // DAI contract + storageKeys: [ + '0x0000000000000000000000000000000000000000000000000000000000000001' as `0x${string}`, + '0x0000000000000000000000000000000000000000000000000000000000000002' as `0x${string}`, + ], }, { - address: '0xe242e54155b1abc71fc118065270cecaaf8b7768' as `0x${string}`, // Recipient - storageKeys: ['0x0000000000000000000000000000000000000000000000000000000000000003' as `0x${string}`], + address: + '0xe242e54155b1abc71fc118065270cecaaf8b7768' as `0x${string}`, // Recipient + storageKeys: [ + '0x0000000000000000000000000000000000000000000000000000000000000003' as `0x${string}`, + ], }, ], }, @@ -287,16 +297,27 @@ export const EIP2930_TEST_VECTORS: TestVector[] = [ chainId: 1, accessList: [ { - address: '0x7a250d5630B4cF539739dF2C5dAcb4c659F2488D' as `0x${string}`, // Router - storageKeys: ['0x0000000000000000000000000000000000000000000000000000000000000001' as `0x${string}`, '0x0000000000000000000000000000000000000000000000000000000000000002' as `0x${string}`], + address: + '0x7a250d5630B4cF539739dF2C5dAcb4c659F2488D' as `0x${string}`, // Router + storageKeys: [ + '0x0000000000000000000000000000000000000000000000000000000000000001' as `0x${string}`, + '0x0000000000000000000000000000000000000000000000000000000000000002' as `0x${string}`, + ], }, { - address: '0x6B175474E89094C44Da98b954EedeAC495271d0F' as `0x${string}`, // DAI - storageKeys: ['0x0000000000000000000000000000000000000000000000000000000000000003' as `0x${string}`, '0x0000000000000000000000000000000000000000000000000000000000000004' as `0x${string}`], + address: + '0x6B175474E89094C44Da98b954EedeAC495271d0F' as `0x${string}`, // DAI + storageKeys: [ + '0x0000000000000000000000000000000000000000000000000000000000000003' as `0x${string}`, + '0x0000000000000000000000000000000000000000000000000000000000000004' as `0x${string}`, + ], }, { - address: '0xdAC17F958D2ee523a2206206994597C13D831ec7' as `0x${string}`, // USDT - storageKeys: ['0x0000000000000000000000000000000000000000000000000000000000000005' as `0x${string}`], + address: + '0xdAC17F958D2ee523a2206206994597C13D831ec7' as `0x${string}`, // USDT + storageKeys: [ + '0x0000000000000000000000000000000000000000000000000000000000000005' as `0x${string}`, + ], }, ], }, @@ -325,7 +346,8 @@ export const EIP7702_TEST_VECTORS: TestVector[] = [ authorizationList: [ { chainId: 1, - address: '0x000000004f43c49e93c970e84001853a70923b03' as `0x${string}`, + address: + '0x000000004f43c49e93c970e84001853a70923b03' as `0x${string}`, nonce: 1, yParity: 1, r: '0xc9f7e0af53f516744bc34827bef7236df3123c3a07a601dca75d7698416adc4a' as `0x${string}`, @@ -351,7 +373,8 @@ export const EIP7702_TEST_VECTORS: TestVector[] = [ authorizationList: [ { chainId: 1, - address: '0x000000004f43c49e93c970e84001853a70923b03' as `0x${string}`, + address: + '0x000000004f43c49e93c970e84001853a70923b03' as `0x${string}`, nonce: 1, yParity: 0, r: '0x948c69c40057e9fd4c9bb55506ef764bf80d1bbaf980fe8c09d9d9c0b67d0e49' as `0x${string}`, @@ -377,7 +400,8 @@ export const EIP7702_TEST_VECTORS: TestVector[] = [ authorizationList: [ { chainId: 1, - address: '0x163193c89de836e82bb121bd0dbcaba7e8ba67dc' as `0x${string}`, + address: + '0x163193c89de836e82bb121bd0dbcaba7e8ba67dc' as `0x${string}`, nonce: 4999, yParity: 1, r: '0x806cbbf8a3cfb25b660e03147984ff95725252b6c95aceed91d5c0bfca6ad0d1' as `0x${string}`, @@ -590,7 +614,9 @@ export const EDGE_CASE_TEST_VECTORS: TestVector[] = [ tx: { type: 'eip1559', to: '0xe242e54155b1abc71fc118065270cecaaf8b7768' as `0x${string}`, - value: BigInt('0xffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff'), // Max uint256 + value: BigInt( + '0xffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff', + ), // Max uint256 data: '0x' as `0x${string}`, // Add missing data field nonce: 0, maxFeePerGas: BigInt('20000000000'), // 20 gwei @@ -643,11 +669,15 @@ export const EDGE_CASE_TEST_VECTORS: TestVector[] = [ chainId: 1, accessList: [ { - address: '0xe242e54155b1abc71fc118065270cecaaf8b7768' as `0x${string}`, - storageKeys: ['0x7154f8b310ad6ce97ce3b15e3419d9863865dfe2d8635802f7f4a52a206255a6' as `0x${string}`], + address: + '0xe242e54155b1abc71fc118065270cecaaf8b7768' as `0x${string}`, + storageKeys: [ + '0x7154f8b310ad6ce97ce3b15e3419d9863865dfe2d8635802f7f4a52a206255a6' as `0x${string}`, + ], }, { - address: '0xe0f8ff08ef0242c461da688b8b85e438db724860' as `0x${string}`, + address: + '0xe0f8ff08ef0242c461da688b8b85e438db724860' as `0x${string}`, storageKeys: [], // Empty storage keys }, ], @@ -668,11 +698,15 @@ export const EDGE_CASE_TEST_VECTORS: TestVector[] = [ chainId: 1, accessList: [ { - address: '0xe242e54155b1abc71fc118065270cecaaf8b7768' as `0x${string}`, - storageKeys: ['0x7154f8b310ad6ce97ce3b15e3419d9863865dfe2d8635802f7f4a52a206255a6' as `0x${string}`], + address: + '0xe242e54155b1abc71fc118065270cecaaf8b7768' as `0x${string}`, + storageKeys: [ + '0x7154f8b310ad6ce97ce3b15e3419d9863865dfe2d8635802f7f4a52a206255a6' as `0x${string}`, + ], }, { - address: '0xe0f8ff08ef0242c461da688b8b85e438db724860' as `0x${string}`, + address: + '0xe0f8ff08ef0242c461da688b8b85e438db724860' as `0x${string}`, storageKeys: [], // Empty storage keys }, ], @@ -1018,7 +1052,9 @@ export const ALL_COMPREHENSIVE_VECTORS: TestVector[] = [ * Get vectors by category for targeted testing */ export function getVectorsByCategory(category: string): TestVector[] { - return ALL_COMPREHENSIVE_VECTORS.filter((vector) => vector.category === category) + return ALL_COMPREHENSIVE_VECTORS.filter( + (vector) => vector.category === category, + ) } /** @@ -1030,14 +1066,23 @@ export function getBalancedTestVectors(perType = 3): TestVector[] { const eip2930Vectors = EIP2930_TEST_VECTORS.slice(0, perType) const eip7702Vectors = EIP7702_TEST_VECTORS.slice(0, perType) - return [...legacyVectors, ...eip1559Vectors, ...eip2930Vectors, ...eip7702Vectors] + return [ + ...legacyVectors, + ...eip1559Vectors, + ...eip2930Vectors, + ...eip7702Vectors, + ] } /** * Get vectors for boundary testing specifically */ export function getBoundaryTestVectors(): TestVector[] { - return [...BOUNDARY_CONDITION_VECTORS, ...EDGE_CASE_TEST_VECTORS, ...PAYLOAD_SIZE_VECTORS] + return [ + ...BOUNDARY_CONDITION_VECTORS, + ...EDGE_CASE_TEST_VECTORS, + ...PAYLOAD_SIZE_VECTORS, + ] } /** diff --git a/packages/sdk/src/__test__/integration/__mocks__/4byte.ts b/packages/sdk/src/__test__/integration/__mocks__/4byte.ts index b1183de3..4ec088c5 100644 --- a/packages/sdk/src/__test__/integration/__mocks__/4byte.ts +++ b/packages/sdk/src/__test__/integration/__mocks__/4byte.ts @@ -38,7 +38,8 @@ export const fourbyteResponse0x38ed1739 = { created_at: '2020-08-09T08:56:14.110995Z', hex_signature: '0x38ed1739', id: 171806, - text_signature: 'swapExactTokensForTokens(uint256,uint256,address[],address,uint256)', + text_signature: + 'swapExactTokensForTokens(uint256,uint256,address[],address,uint256)', }, ], } @@ -60,7 +61,8 @@ export const fourbyteResponse0c49ccbe = { { id: 186682, created_at: '2021-05-09T03:48:17.627742Z', - text_signature: 'decreaseLiquidity((uint256,uint128,uint256,uint256,uint256))', + text_signature: + 'decreaseLiquidity((uint256,uint128,uint256,uint256,uint256))', hex_signature: '0x0c49ccbe', bytes_signature: '\\fI̾', }, @@ -84,7 +86,8 @@ export const fourbyteResponse0x6a761202 = { { id: 169422, created_at: '2020-01-28T10:40:07.614936Z', - text_signature: 'execTransaction(address,uint256,bytes,uint8,uint256,uint256,uint256,address,address,bytes)', + text_signature: + 'execTransaction(address,uint256,bytes,uint8,uint256,uint256,uint256,address,address,bytes)', hex_signature: '0x6a761202', bytes_signature: 'jv\\u0012\\u0002', }, diff --git a/packages/sdk/src/__test__/integration/__mocks__/etherscan.ts b/packages/sdk/src/__test__/integration/__mocks__/etherscan.ts index 24effa35..d7597392 100644 --- a/packages/sdk/src/__test__/integration/__mocks__/etherscan.ts +++ b/packages/sdk/src/__test__/integration/__mocks__/etherscan.ts @@ -1447,7 +1447,8 @@ export const etherscanResponse0xc36442b6 = [ type: 'uint256', }, ], - internalType: 'struct INonfungiblePositionManager.DecreaseLiquidityParams', + internalType: + 'struct INonfungiblePositionManager.DecreaseLiquidityParams', name: 'params', type: 'tuple', }, @@ -1535,7 +1536,8 @@ export const etherscanResponse0xc36442b6 = [ type: 'uint256', }, ], - internalType: 'struct INonfungiblePositionManager.IncreaseLiquidityParams', + internalType: + 'struct INonfungiblePositionManager.IncreaseLiquidityParams', name: 'params', type: 'tuple', }, @@ -2526,7 +2528,8 @@ export const etherscanResponse0x06412d7e = [ { internalType: 'uint256', name: 'amount1Min', type: 'uint256' }, { internalType: 'uint256', name: 'deadline', type: 'uint256' }, ], - internalType: 'struct INonfungiblePositionManager.DecreaseLiquidityParams', + internalType: + 'struct INonfungiblePositionManager.DecreaseLiquidityParams', name: 'params', type: 'tuple', }, @@ -2564,7 +2567,8 @@ export const etherscanResponse0x06412d7e = [ { internalType: 'uint256', name: 'amount1Min', type: 'uint256' }, { internalType: 'uint256', name: 'deadline', type: 'uint256' }, ], - internalType: 'struct INonfungiblePositionManager.IncreaseLiquidityParams', + internalType: + 'struct INonfungiblePositionManager.IncreaseLiquidityParams', name: 'params', type: 'tuple', }, diff --git a/packages/sdk/src/__test__/integration/__mocks__/handlers.ts b/packages/sdk/src/__test__/integration/__mocks__/handlers.ts index dca0c4c7..db4baa25 100644 --- a/packages/sdk/src/__test__/integration/__mocks__/handlers.ts +++ b/packages/sdk/src/__test__/integration/__mocks__/handlers.ts @@ -1,8 +1,20 @@ import { http, HttpResponse } from 'msw' -import { fourbyteResponse0c49ccbe, fourbyteResponse0x6a761202, fourbyteResponse0x38ed1739, fourbyteResponse0xa9059cbb, fourbyteResponseac9650d8, fourbyteResponsefc6f7865 } from './4byte' +import { + fourbyteResponse0c49ccbe, + fourbyteResponse0x6a761202, + fourbyteResponse0x38ed1739, + fourbyteResponse0xa9059cbb, + fourbyteResponseac9650d8, + fourbyteResponsefc6f7865, +} from './4byte' import addKvRecordsResponse from './addKvRecords.json' import connectResponse from './connect.json' -import { etherscanResponse0x06412d7e, etherscanResponse0x7a250d56, etherscanResponse0xa0b86991, etherscanResponse0xc36442b6 } from './etherscan' +import { + etherscanResponse0x06412d7e, + etherscanResponse0x7a250d56, + etherscanResponse0xa0b86991, + etherscanResponse0xc36442b6, +} from './etherscan' import fetchActiveWalletResponse from './fetchActiveWallet.json' import getAddressesResponse from './getAddresses.json' import getKvRecordsResponse from './getKvRecords.json' diff --git a/packages/sdk/src/__test__/integration/fetchCalldataDecoder.test.ts b/packages/sdk/src/__test__/integration/fetchCalldataDecoder.test.ts index 38fba123..f985ec26 100644 --- a/packages/sdk/src/__test__/integration/fetchCalldataDecoder.test.ts +++ b/packages/sdk/src/__test__/integration/fetchCalldataDecoder.test.ts @@ -33,7 +33,8 @@ describe('fetchCalldataDecoder', () => { }) test('decode proxy calldata', async () => { - const data = '0xa9059cbb0000000000000000000000004ffbf741b0a64e8bd1f9d89fc9b5584cc5227b700000000000000000000000000000000000000000000000000000003052aacdb8' + const data = + '0xa9059cbb0000000000000000000000004ffbf741b0a64e8bd1f9d89fc9b5584cc5227b700000000000000000000000000000000000000000000000000000003052aacdb8' const to = '0xa0b86991c6218b36c1d19d4a2e9eb0ce3606eb48' const decoded = await fetchCalldataDecoder(data, to, '1') expect(decoded).toMatchSnapshot() @@ -97,7 +98,8 @@ describe('fetchCalldataDecoder', () => { // TODO: add api key to fix this test test.skip('decode Celo calldata', async () => { - const data = '0xf2fde38b000000000000000000000000b538e8dcd297450bdef46222f3ceb33bb1e921b3' + const data = + '0xf2fde38b000000000000000000000000b538e8dcd297450bdef46222f3ceb33bb1e921b3' const to = '0x96d59127ccd1c0e3749e733ee04f0dfbd2f808c8' const decoded = await fetchCalldataDecoder(data, to, '42220') expect(decoded).toMatchSnapshot() diff --git a/packages/sdk/src/__test__/unit/__mocks__/decoderData.ts b/packages/sdk/src/__test__/unit/__mocks__/decoderData.ts index e4d0d4d6..7e1d0bb4 100644 --- a/packages/sdk/src/__test__/unit/__mocks__/decoderData.ts +++ b/packages/sdk/src/__test__/unit/__mocks__/decoderData.ts @@ -3,10 +3,18 @@ * For encrypted requests, the results are decrypted. * For each response, the response code and checksum are removed. */ -import { LatticeEncDataSchema, LatticeGetAddressesFlag } from '../../../protocol' +import { + LatticeEncDataSchema, + LatticeGetAddressesFlag, +} from '../../../protocol' import { getP256KeyPair } from '../../../util' -export const clientKeyPair = getP256KeyPair(Buffer.from('3fb53b677f73e4d2b8c89c303f6f6b349f0075ad88ea126cb9f6632085815dca', 'hex')) +export const clientKeyPair = getP256KeyPair( + Buffer.from( + '3fb53b677f73e4d2b8c89c303f6f6b349f0075ad88ea126cb9f6632085815dca', + 'hex', + ), +) export const decoderTestsFwConstants = JSON.parse( '{"extraDataFrameSz":1500,"extraDataMaxFrames":1,"genericSigning":{"baseReqSz":1552,"baseDataSz":1519,"hashTypes":{"NONE":0,"KECCAK256":1,"SHA256":2},"curveTypes":{"SECP256K1":0,"ED25519":1,"BLS12_381_G2":2},"encodingTypes":{"NONE":1,"SOLANA":2,"EVM":4,"ETH_DEPOSIT":5},"calldataDecoding":{"reserved":2895728,"maxSz":1024}},"reqMaxDataSz":1678,"ethMaxGasPrice":20000000000000,"addrFlagsAllowed":true,"ethMaxDataSz":1519,"ethMaxMsgSz":1540,"eip712MaxTypeParams":36,"varAddrPathSzAllowed":true,"eip712Supported":true,"prehashAllowed":true,"ethMsgPreHashAllowed":true,"allowedEthTxTypes":[1,2],"personalSignHeaderSz":72,"kvActionsAllowed":true,"kvKeyMaxStrSz":63,"kvValMaxStrSz":63,"kvActionMaxNum":10,"kvRemoveMaxNum":100,"allowBtcLegacyAndSegwitAddrs":true,"contractDeployKey":"0x08002e0fec8e6acf00835f43c9764f7364fa3f42","abiCategorySz":32,"abiMaxRmv":200,"getAddressFlags":[4,3,5],"maxDecoderBufSz":1600}', @@ -34,7 +42,10 @@ export const signGenericRequest = { encodingType: 4, hashType: 1, omitPubkey: false, - origPayloadBuf: Buffer.from('01f84b01808447868c0082c35094e242e54155b1abc71fc118065270cecaaf8b776885e8d4a51000a417e914679b7e160613be4f8c2d3203d236286d74eb9192f6d6f71b9118a42bb033ccd8e8c0', 'hex'), + origPayloadBuf: Buffer.from( + '01f84b01808447868c0082c35094e242e54155b1abc71fc118065270cecaaf8b776885e8d4a51000a417e914679b7e160613be4f8c2d3203d236286d74eb9192f6d6f71b9118a42bb033ccd8e8c0', + 'hex', + ), } export const signGenericDecoderData = Buffer.from( '04a50d7d8e5bf6353086dfaff71652a223aa13e02273a2b6bf5a145314b544be1281ac8f78d035874a06b11e3df68e45f7630b2e6ba3be0f51f916fbb6f0a6403930440220640b2c690858ab8d0b9500f9ed64c9aa6b7467b77f1199b061aa96ea780aadaa022048f830f9290dd1b3eaf1922e08a8c992873be1162bd6d5bef681cf911328abe500000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000', @@ -47,7 +58,10 @@ export const signBitcoinRequest = { origData: { prevOuts: [ { - txHash: Buffer.from('b2efdbdd3340d2bc547671ce3993a6f05d70343c07578f9d7f5626fdfc06fa35', 'hex'), + txHash: Buffer.from( + 'b2efdbdd3340d2bc547671ce3993a6f05d70343c07578f9d7f5626fdfc06fa35', + 'hex', + ), value: 76800, index: 0, signerPath: [2147483732, 2147483649, 2147483648, 0, 0], @@ -83,7 +97,10 @@ export const fetchEncryptedDataRequest = { params: { path: [12381, 3600, 0, 0], c: 999, - walletUID: Buffer.from('6ae62c0c96c1e039fc97bfeb7c2428c093fe7f0b6188a434bbac7b652c3e4012', 'hex'), + walletUID: Buffer.from( + '6ae62c0c96c1e039fc97bfeb7c2428c093fe7f0b6188a434bbac7b652c3e4012', + 'hex', + ), }, } export const fetchEncryptedDataDecoderData = Buffer.from( diff --git a/packages/sdk/src/__test__/unit/api.test.ts b/packages/sdk/src/__test__/unit/api.test.ts index f9e850ef..df59e261 100644 --- a/packages/sdk/src/__test__/unit/api.test.ts +++ b/packages/sdk/src/__test__/unit/api.test.ts @@ -52,6 +52,8 @@ describe('parseDerivationPath', () => { }) it('throws error for invalid input', () => { - expect(() => parseDerivationPath('invalid/path')).toThrow('Invalid part in derivation path: invalid') + expect(() => parseDerivationPath('invalid/path')).toThrow( + 'Invalid part in derivation path: invalid', + ) }) }) diff --git a/packages/sdk/src/__test__/unit/compareEIP7702Serialization.test.ts b/packages/sdk/src/__test__/unit/compareEIP7702Serialization.test.ts index 4e65d84e..b9cc117d 100644 --- a/packages/sdk/src/__test__/unit/compareEIP7702Serialization.test.ts +++ b/packages/sdk/src/__test__/unit/compareEIP7702Serialization.test.ts @@ -1,7 +1,10 @@ import { Hash } from 'ox' import { parseEther, serializeTransaction, toHex } from 'viem' import { serializeEIP7702Transaction } from '../../ethereum' -import type { EIP7702AuthListTransactionRequest as EIP7702AuthListTransaction, EIP7702AuthTransactionRequest as EIP7702AuthTransaction } from '../../types' +import type { + EIP7702AuthListTransactionRequest as EIP7702AuthListTransaction, + EIP7702AuthTransactionRequest as EIP7702AuthTransaction, +} from '../../types' describe('EIP7702 Transaction Serialization Comparison', () => { /** diff --git a/packages/sdk/src/__test__/unit/decoders.test.ts b/packages/sdk/src/__test__/unit/decoders.test.ts index f72ec4ab..1fc8adaa 100644 --- a/packages/sdk/src/__test__/unit/decoders.test.ts +++ b/packages/sdk/src/__test__/unit/decoders.test.ts @@ -1,4 +1,10 @@ -import { decodeConnectResponse, decodeFetchEncData, decodeGetAddressesResponse, decodeGetKvRecordsResponse, decodeSignResponse } from '../../functions' +import { + decodeConnectResponse, + decodeFetchEncData, + decodeGetAddressesResponse, + decodeGetKvRecordsResponse, + decodeSignResponse, +} from '../../functions' import type { DecodeSignResponseParams } from '../../types' import { clientKeyPair, @@ -17,11 +23,15 @@ import { describe('decoders', () => { test('connect', () => { - expect(decodeConnectResponse(connectDecoderData, clientKeyPair)).toMatchSnapshot() + expect( + decodeConnectResponse(connectDecoderData, clientKeyPair), + ).toMatchSnapshot() }) test('getAddresses', () => { - expect(decodeGetAddressesResponse(getAddressesDecoderData, getAddressesFlag)).toMatchSnapshot() + expect( + decodeGetAddressesResponse(getAddressesDecoderData, getAddressesFlag), + ).toMatchSnapshot() }) test('sign - bitcoin', () => { @@ -44,7 +54,12 @@ describe('decoders', () => { }) test('getKvRecords', () => { - expect(decodeGetKvRecordsResponse(getKvRecordsDecoderData, decoderTestsFwConstants)).toMatchSnapshot() + expect( + decodeGetKvRecordsResponse( + getKvRecordsDecoderData, + decoderTestsFwConstants, + ), + ).toMatchSnapshot() }) test('fetchEncryptedData', () => { diff --git a/packages/sdk/src/__test__/unit/eip7702.test.ts b/packages/sdk/src/__test__/unit/eip7702.test.ts index 35da05de..c5b63418 100644 --- a/packages/sdk/src/__test__/unit/eip7702.test.ts +++ b/packages/sdk/src/__test__/unit/eip7702.test.ts @@ -1,7 +1,10 @@ import { Hash } from 'ox' import { parseEther, toHex } from 'viem' import { serializeEIP7702Transaction } from '../../ethereum' -import type { EIP7702AuthListTransactionRequest, EIP7702AuthTransactionRequest } from '../../types' +import type { + EIP7702AuthListTransactionRequest, + EIP7702AuthTransactionRequest, +} from '../../types' describe('EIP-7702 Transaction Serialization', () => { /** diff --git a/packages/sdk/src/__test__/unit/encoders.test.ts b/packages/sdk/src/__test__/unit/encoders.test.ts index 3e60b600..20843f9f 100644 --- a/packages/sdk/src/__test__/unit/encoders.test.ts +++ b/packages/sdk/src/__test__/unit/encoders.test.ts @@ -1,8 +1,21 @@ import { EXTERNAL } from '../../constants' -import { encodeAddKvRecordsRequest, encodeGetAddressesRequest, encodeGetKvRecordsRequest, encodePairRequest, encodeRemoveKvRecordsRequest, encodeSignRequest } from '../../functions' +import { + encodeAddKvRecordsRequest, + encodeGetAddressesRequest, + encodeGetKvRecordsRequest, + encodePairRequest, + encodeRemoveKvRecordsRequest, + encodeSignRequest, +} from '../../functions' import { buildTransaction } from '../../shared/functions' import { getP256KeyPair } from '../../util' -import { buildFirmwareConstants, buildGetAddressesObject, buildSignObject, buildWallet, getFwVersionsList } from '../utils/builders' +import { + buildFirmwareConstants, + buildGetAddressesObject, + buildSignObject, + buildWallet, + getFwVersionsList, +} from '../utils/builders' describe('encoders', () => { let mockRandom: any @@ -57,19 +70,22 @@ describe('encoders', () => { }) describe('sign', () => { - test.each(getFwVersionsList())('should test sign encoder with firmware v%d.%d.%d', (major, minor, patch) => { - const fwVersion = Buffer.from([patch, minor, major]) - const txObj = buildSignObject(fwVersion) - const tx = buildTransaction(txObj) - const req = { - ...txObj, - ...tx, - wallet: buildWallet(), - } - const { payload } = encodeSignRequest(req) - const payloadAsString = payload.toString('hex') - expect(payloadAsString).toMatchSnapshot() - }) + test.each(getFwVersionsList())( + 'should test sign encoder with firmware v%d.%d.%d', + (major, minor, patch) => { + const fwVersion = Buffer.from([patch, minor, major]) + const txObj = buildSignObject(fwVersion) + const tx = buildTransaction(txObj) + const req = { + ...txObj, + ...tx, + wallet: buildWallet(), + } + const { payload } = encodeSignRequest(req) + const payloadAsString = payload.toString('hex') + expect(payloadAsString).toMatchSnapshot() + }, + ) }) describe('KvRecords', () => { diff --git a/packages/sdk/src/__test__/unit/ethereum.validate.test.ts b/packages/sdk/src/__test__/unit/ethereum.validate.test.ts index 62c0ad8a..78d51dd2 100644 --- a/packages/sdk/src/__test__/unit/ethereum.validate.test.ts +++ b/packages/sdk/src/__test__/unit/ethereum.validate.test.ts @@ -1,4 +1,9 @@ -import { type MessageTypes, SignTypedDataVersion, TypedDataUtils, type TypedMessage } from '@metamask/eth-sig-util' +import { + type MessageTypes, + SignTypedDataVersion, + TypedDataUtils, + type TypedMessage, +} from '@metamask/eth-sig-util' import { ecsign, privateToAddress } from 'ethereumjs-util' import { mnemonicToAccount } from 'viem/accounts' import { HARDENED_OFFSET } from '../../constants' diff --git a/packages/sdk/src/__test__/unit/module.interop.test.ts b/packages/sdk/src/__test__/unit/module.interop.test.ts index c030a559..df005282 100644 --- a/packages/sdk/src/__test__/unit/module.interop.test.ts +++ b/packages/sdk/src/__test__/unit/module.interop.test.ts @@ -1,5 +1,11 @@ import { execSync, spawnSync } from 'node:child_process' -import { existsSync, mkdirSync, mkdtempSync, rmSync, symlinkSync } from 'node:fs' +import { + existsSync, + mkdirSync, + mkdtempSync, + rmSync, + symlinkSync, +} from 'node:fs' import os from 'node:os' import path from 'node:path' import { fileURLToPath } from 'node:url' @@ -53,7 +59,9 @@ const runNodeCheck = (args: string[]) => { throw result.error } if (result.status !== 0) { - throw new Error(`Node command failed (${result.status}):\n${result.stderr || result.stdout}`) + throw new Error( + `Node command failed (${result.status}):\n${result.stderr || result.stdout}`, + ) } } diff --git a/packages/sdk/src/__test__/unit/parseGenericSigningResponse.test.ts b/packages/sdk/src/__test__/unit/parseGenericSigningResponse.test.ts index ee636370..a4a2bc87 100644 --- a/packages/sdk/src/__test__/unit/parseGenericSigningResponse.test.ts +++ b/packages/sdk/src/__test__/unit/parseGenericSigningResponse.test.ts @@ -34,12 +34,18 @@ describe('parseGenericSigningResponse', () => { const hash = Buffer.from(Hash.keccak256(payload)) // Create a fake signature - const privateKey = Buffer.from('0101010101010101010101010101010101010101010101010101010101010101', 'hex') + const privateKey = Buffer.from( + '0101010101010101010101010101010101010101010101010101010101010101', + 'hex', + ) const sigObj = secp256k1.ecdsaSign(hash, privateKey) const publicKey = secp256k1.publicKeyCreate(privateKey, false) // Create DER-encoded signature response - const derSig = createDERSignature(Buffer.from(sigObj.signature.slice(0, 32)), Buffer.from(sigObj.signature.slice(32, 64))) + const derSig = createDERSignature( + Buffer.from(sigObj.signature.slice(0, 32)), + Buffer.from(sigObj.signature.slice(32, 64)), + ) // Create mock response buffer const mockResponse = Buffer.concat([ @@ -68,16 +74,25 @@ describe('parseGenericSigningResponse', () => { it('should handle EVM transaction encoding', () => { // Simulate an unsigned legacy transaction - const unsignedTx = Buffer.from('e9808504a817c800825208943535353535353535353535353535353535353535880de0b6b3a764000080', 'hex') + const unsignedTx = Buffer.from( + 'e9808504a817c800825208943535353535353535353535353535353535353535880de0b6b3a764000080', + 'hex', + ) const hash = Buffer.from(Hash.keccak256(unsignedTx)) // Create a fake signature - const privateKey = Buffer.from('0101010101010101010101010101010101010101010101010101010101010101', 'hex') + const privateKey = Buffer.from( + '0101010101010101010101010101010101010101010101010101010101010101', + 'hex', + ) const sigObj = secp256k1.ecdsaSign(hash, privateKey) const publicKey = secp256k1.publicKeyCreate(privateKey, false) // Create DER-encoded signature response - const derSig = createDERSignature(Buffer.from(sigObj.signature.slice(0, 32)), Buffer.from(sigObj.signature.slice(32, 64))) + const derSig = createDERSignature( + Buffer.from(sigObj.signature.slice(0, 32)), + Buffer.from(sigObj.signature.slice(32, 64)), + ) // Create mock response buffer const mockResponse = Buffer.concat([ @@ -119,12 +134,18 @@ describe('parseGenericSigningResponse', () => { const hash = Buffer.from(Hash.keccak256(rlpEncoded)) // Create a fake signature - const privateKey = Buffer.from('0101010101010101010101010101010101010101010101010101010101010101', 'hex') + const privateKey = Buffer.from( + '0101010101010101010101010101010101010101010101010101010101010101', + 'hex', + ) const sigObj = secp256k1.ecdsaSign(hash, privateKey) const publicKey = secp256k1.publicKeyCreate(privateKey, false) // Create DER-encoded signature response - const derSig = createDERSignature(Buffer.from(sigObj.signature.slice(0, 32)), Buffer.from(sigObj.signature.slice(32, 64))) + const derSig = createDERSignature( + Buffer.from(sigObj.signature.slice(0, 32)), + Buffer.from(sigObj.signature.slice(32, 64)), + ) // Create mock response buffer const mockResponse = Buffer.concat([ diff --git a/packages/sdk/src/__test__/unit/personalSignValidation.test.ts b/packages/sdk/src/__test__/unit/personalSignValidation.test.ts index 0701ed20..1a0cd5c0 100644 --- a/packages/sdk/src/__test__/unit/personalSignValidation.test.ts +++ b/packages/sdk/src/__test__/unit/personalSignValidation.test.ts @@ -12,15 +12,23 @@ describe('Personal Sign Validation - Issue Fix', () => { it('should correctly validate personal message signature', () => { // Create a test private key and derive public key - const privateKey = Buffer.from('0101010101010101010101010101010101010101010101010101010101010101', 'hex') + const privateKey = Buffer.from( + '0101010101010101010101010101010101010101010101010101010101010101', + 'hex', + ) const publicKey = secp256k1.publicKeyCreate(privateKey, false) // Create a test message const message = Buffer.from('Test message', 'utf8') // Build personal sign prefix and hash - const prefix = Buffer.from(`\u0019Ethereum Signed Message:\n${message.length.toString()}`, 'utf-8') - const messageHash = Buffer.from(Hash.keccak256(Buffer.concat([prefix, message]))) + const prefix = Buffer.from( + `\u0019Ethereum Signed Message:\n${message.length.toString()}`, + 'utf-8', + ) + const messageHash = Buffer.from( + Hash.keccak256(Buffer.concat([prefix, message])), + ) // Sign the message const sigObj = secp256k1.ecdsaSign(messageHash, privateKey) @@ -34,7 +42,9 @@ describe('Personal Sign Validation - Issue Fix', () => { // Get the Ethereum address from the public key // This matches what the firmware returns const pubkeyWithoutPrefix = publicKey.slice(1) // Remove 0x04 prefix - const addressBuffer = Buffer.from(Hash.keccak256(pubkeyWithoutPrefix)).slice(-20) + const addressBuffer = Buffer.from( + Hash.keccak256(pubkeyWithoutPrefix), + ).slice(-20) // This is the function that was failing before the fix // It should now correctly add the recovery parameter @@ -45,7 +55,9 @@ describe('Personal Sign Validation - Issue Fix', () => { // Verify the signature has a valid v value (27 or 28) expect(result.v).toBeDefined() - const vValue = Buffer.isBuffer(result.v) ? result.v.readUInt8(0) : Number(result.v) + const vValue = Buffer.isBuffer(result.v) + ? result.v.readUInt8(0) + : Number(result.v) expect([27, 28]).toContain(vValue) // Verify r and s are buffers of correct length @@ -83,11 +95,19 @@ describe('Personal Sign Validation - Issue Fix', () => { const payloadBuffer = Buffer.from(testPayload.slice(2), 'hex') // Build personal sign hash - const prefix = Buffer.from(`\u0019Ethereum Signed Message:\n${payloadBuffer.length.toString()}`, 'utf-8') - const messageHash = Buffer.from(Hash.keccak256(Buffer.concat([prefix, payloadBuffer]))) + const prefix = Buffer.from( + `\u0019Ethereum Signed Message:\n${payloadBuffer.length.toString()}`, + 'utf-8', + ) + const messageHash = Buffer.from( + Hash.keccak256(Buffer.concat([prefix, payloadBuffer])), + ) // Create a valid signature for this message - const privateKey = Buffer.from('0101010101010101010101010101010101010101010101010101010101010101', 'hex') + const privateKey = Buffer.from( + '0101010101010101010101010101010101010101010101010101010101010101', + 'hex', + ) const publicKey = secp256k1.publicKeyCreate(privateKey, false) const sigObj = secp256k1.ecdsaSign(messageHash, privateKey) @@ -98,7 +118,9 @@ describe('Personal Sign Validation - Issue Fix', () => { // Get address from public key const pubkeyWithoutPrefix = publicKey.slice(1) - const addressBuffer = Buffer.from(Hash.keccak256(pubkeyWithoutPrefix)).slice(-20) + const addressBuffer = Buffer.from( + Hash.keccak256(pubkeyWithoutPrefix), + ).slice(-20) // This should NOT throw with the fix in place expect(() => { diff --git a/packages/sdk/src/__test__/unit/selectDefFrom4byteABI.test.ts b/packages/sdk/src/__test__/unit/selectDefFrom4byteABI.test.ts index 2be479f8..429fa744 100644 --- a/packages/sdk/src/__test__/unit/selectDefFrom4byteABI.test.ts +++ b/packages/sdk/src/__test__/unit/selectDefFrom4byteABI.test.ts @@ -17,14 +17,16 @@ describe('selectDefFrom4byteAbi', () => { created_at: '2020-08-09T08:56:14.110995Z', hex_signature: '0x38ed1739', id: 171801, - text_signature: 'swapExactTokensForTokens(uint256,uint256,address[],address,uint256)', + text_signature: + 'swapExactTokensForTokens(uint256,uint256,address[],address,uint256)', }, { bytes_signature: '8í9', created_at: '2020-01-09T08:56:14.110995Z', hex_signature: '0x38ed1739', id: 171806, - text_signature: 'swapExactTokensForTokens(uint256,uint256,address[],address,uint256)', + text_signature: + 'swapExactTokensForTokens(uint256,uint256,address[],address,uint256)', }, { bytes_signature: '', diff --git a/packages/sdk/src/__test__/unit/signatureUtils.test.ts b/packages/sdk/src/__test__/unit/signatureUtils.test.ts index 1881b451..c6ee0e9c 100644 --- a/packages/sdk/src/__test__/unit/signatureUtils.test.ts +++ b/packages/sdk/src/__test__/unit/signatureUtils.test.ts @@ -7,7 +7,10 @@ describe('getYParity', () => { // Helper function to create a valid signature const createValidSignature = (messageHash: Buffer) => { // Create a deterministic private key for testing - const privateKey = Buffer.from('0101010101010101010101010101010101010101010101010101010101010101', 'hex') + const privateKey = Buffer.from( + '0101010101010101010101010101010101010101010101010101010101010101', + 'hex', + ) // Sign the message const sigObj = secp256k1.ecdsaSign(messageHash, privateKey) @@ -28,7 +31,8 @@ describe('getYParity', () => { describe('Simple signature format', () => { it('should handle simple format with Buffer inputs', () => { const messageHash = randomBytes(32) - const { signature, publicKey, recovery } = createValidSignature(messageHash) + const { signature, publicKey, recovery } = + createValidSignature(messageHash) const yParity = getYParity({ messageHash, @@ -42,7 +46,8 @@ describe('getYParity', () => { it('should handle simple format with hex string inputs', () => { const messageHash = randomBytes(32) - const { signature, publicKey, recovery } = createValidSignature(messageHash) + const { signature, publicKey, recovery } = + createValidSignature(messageHash) const yParity = getYParity({ messageHash: `0x${messageHash.toString('hex')}`, @@ -58,7 +63,10 @@ describe('getYParity', () => { it('should handle simple format with compressed public key', () => { const messageHash = randomBytes(32) - const privateKey = Buffer.from('0101010101010101010101010101010101010101010101010101010101010101', 'hex') + const privateKey = Buffer.from( + '0101010101010101010101010101010101010101010101010101010101010101', + 'hex', + ) const sigObj = secp256k1.ecdsaSign(messageHash, privateKey) const compressedPubkey = secp256k1.publicKeyCreate(privateKey, true) @@ -77,7 +85,8 @@ describe('getYParity', () => { it('should handle mixed format inputs', () => { const messageHash = randomBytes(32) - const { signature, publicKey, recovery } = createValidSignature(messageHash) + const { signature, publicKey, recovery } = + createValidSignature(messageHash) const yParity = getYParity({ messageHash: messageHash.toString('hex'), // No 0x prefix @@ -129,7 +138,8 @@ describe('getYParity', () => { getMessageToSign: () => messageData, } - const { signature, publicKey, recovery } = createValidSignature(messageData) + const { signature, publicKey, recovery } = + createValidSignature(messageData) const resp = { sig: signature, @@ -149,7 +159,9 @@ describe('getYParity', () => { const messageHash = new Uint8Array(32) messageHash.fill(42) - const { signature, publicKey, recovery } = createValidSignature(Buffer.from(messageHash)) + const { signature, publicKey, recovery } = createValidSignature( + Buffer.from(messageHash), + ) const resp = { sig: { @@ -172,7 +184,9 @@ describe('getYParity', () => { for (let i = 0; i < 32; i++) { hash[i] = Math.floor(Math.random() * 256) } - const { signature, publicKey, recovery } = createValidSignature(Buffer.from(hash)) + const { signature, publicKey, recovery } = createValidSignature( + Buffer.from(hash), + ) const resp = { sig: signature, @@ -186,7 +200,8 @@ describe('getYParity', () => { it('should hash non-32-byte inputs', () => { const shortData = randomBytes(20) const expectedHash = Buffer.from(Hash.keccak256(shortData)) - const { signature, publicKey, recovery } = createValidSignature(expectedHash) + const { signature, publicKey, recovery } = + createValidSignature(expectedHash) const resp = { sig: signature, @@ -201,19 +216,25 @@ describe('getYParity', () => { describe('Error handling', () => { it('should throw error if legacy format missing response', () => { const tx = randomBytes(32) - expect(() => getYParity(tx)).toThrow('Response with sig and pubkey required for legacy format') + expect(() => getYParity(tx)).toThrow( + 'Response with sig and pubkey required for legacy format', + ) }) it('should throw error if response missing sig', () => { const tx = randomBytes(32) const resp = { pubkey: randomBytes(65) } - expect(() => getYParity(tx, resp)).toThrow('Response with sig and pubkey required for legacy format') + expect(() => getYParity(tx, resp)).toThrow( + 'Response with sig and pubkey required for legacy format', + ) }) it('should throw error if response missing pubkey', () => { const tx = randomBytes(32) const resp = { sig: { r: randomBytes(32), s: randomBytes(32) } } - expect(() => getYParity(tx, resp)).toThrow('Response with sig and pubkey required for legacy format') + expect(() => getYParity(tx, resp)).toThrow( + 'Response with sig and pubkey required for legacy format', + ) }) it('should throw error if recovery fails', () => { @@ -227,7 +248,9 @@ describe('getYParity', () => { signature, publicKey, }), - ).toThrow('Failed to recover Y parity. Bad signature or transaction data.') + ).toThrow( + 'Failed to recover Y parity. Bad signature or transaction data.', + ) }) it('should throw error with invalid signature', () => { @@ -255,10 +278,14 @@ describe('getYParity', () => { const MAGIC = Buffer.from([0x05]) // This would normally use RLP.encode but we'll create a test message - const message = Buffer.concat([MAGIC, Buffer.from('test_rlp_encoded_data', 'utf8')]) + const message = Buffer.concat([ + MAGIC, + Buffer.from('test_rlp_encoded_data', 'utf8'), + ]) const messageHash = Buffer.from(Hash.keccak256(message)) - const { signature, publicKey, recovery } = createValidSignature(messageHash) + const { signature, publicKey, recovery } = + createValidSignature(messageHash) // Test both Buffer format (as returned by device) const yParity1 = getYParity({ @@ -305,8 +332,13 @@ describe('getYParity', () => { // Try multiple messages until we get one with y-parity 1 for (let i = 0; i < 100; i++) { - const messageHash = Buffer.from(Hash.keccak256(Buffer.from(`test message ${i}`))) - const privateKey = Buffer.from('0101010101010101010101010101010101010101010101010101010101010101', 'hex') + const messageHash = Buffer.from( + Hash.keccak256(Buffer.from(`test message ${i}`)), + ) + const privateKey = Buffer.from( + '0101010101010101010101010101010101010101010101010101010101010101', + 'hex', + ) const sigObj = secp256k1.ecdsaSign(messageHash, privateKey) @@ -337,7 +369,12 @@ describe('getV function', () => { // Helper to create a valid signature const createValidSignature = (messageHash: Buffer, privateKey?: Buffer) => { // Use deterministic key if not provided - const privKey = privateKey || Buffer.from('0101010101010101010101010101010101010101010101010101010101010101', 'hex') + const privKey = + privateKey || + Buffer.from( + '0101010101010101010101010101010101010101010101010101010101010101', + 'hex', + ) const sigObj = secp256k1.ecdsaSign(messageHash, privKey) const publicKey = secp256k1.publicKeyCreate(privKey, false) @@ -354,7 +391,10 @@ describe('getV function', () => { it('should handle unsigned legacy transaction with valid signature', () => { // A simple unsigned legacy transaction - const unsignedTxRLP = Buffer.from('e9808504a817c800825208943535353535353535353535353535353535353535880de0b6b3a764000080', 'hex') + const unsignedTxRLP = Buffer.from( + 'e9808504a817c800825208943535353535353535353535353535353535353535880de0b6b3a764000080', + 'hex', + ) // Hash the transaction const hash = Buffer.from(Hash.keccak256(unsignedTxRLP)) @@ -374,8 +414,14 @@ describe('getV function', () => { const mockResp = { sig: { - r: Buffer.from('134f5038e0e6a96741e17a82c8df13e9dc10c3b0e9e956cf7dcf21e1e3b73f9f', 'hex'), - s: Buffer.from('638cf1b1f9dd5e6e8e6b9a8e6e8e6b9a8e6e8e6b9a8e6e8e6b9a8e6e8e6b9a8', 'hex'), + r: Buffer.from( + '134f5038e0e6a96741e17a82c8df13e9dc10c3b0e9e956cf7dcf21e1e3b73f9f', + 'hex', + ), + s: Buffer.from( + '638cf1b1f9dd5e6e8e6b9a8e6e8e6b9a8e6e8e6b9a8e6e8e6b9a8e6e8e6b9a8', + 'hex', + ), }, // This is a fake pubkey, so recovery will fail pubkey: Buffer.from(`04${'1'.repeat(128)}`, 'hex'), @@ -385,7 +431,8 @@ describe('getV function', () => { }) it('should throw error when signature is invalid', () => { - const txHex = '0xe9808504a817c800825208943535353535353535353535353535353535353535880de0b6b3a764000080' + const txHex = + '0xe9808504a817c800825208943535353535353535353535353535353535353535880de0b6b3a764000080' const mockResp = { sig: { diff --git a/packages/sdk/src/__test__/unit/validators.test.ts b/packages/sdk/src/__test__/unit/validators.test.ts index 11514e3a..0d2ed009 100644 --- a/packages/sdk/src/__test__/unit/validators.test.ts +++ b/packages/sdk/src/__test__/unit/validators.test.ts @@ -1,7 +1,20 @@ import { normalizeToViemTransaction } from '../../ethereum' -import { validateAddKvRequest, validateConnectRequest, validateGetAddressesRequest, validateGetKvRequest, validateRemoveKvRequest } from '../../functions' -import { isValid4ByteResponse, isValidBlockExplorerResponse } from '../../shared/validators' -import { buildGetAddressesObject, buildValidateConnectObject, buildValidateRequestObject } from '../utils/builders' +import { + validateAddKvRequest, + validateConnectRequest, + validateGetAddressesRequest, + validateGetKvRequest, + validateRemoveKvRequest, +} from '../../functions' +import { + isValid4ByteResponse, + isValidBlockExplorerResponse, +} from '../../shared/validators' +import { + buildGetAddressesObject, + buildValidateConnectObject, + buildValidateRequestObject, +} from '../utils/builders' describe('validators', () => { describe('connect', () => { @@ -29,7 +42,10 @@ describe('validators', () => { test('encodeGetAddressesRequest should throw with invalid startPath', () => { const startPath = [0x80000000 + 44, 0x80000000 + 60, 0, 0, 0, 0, 0] const fwVersion = Buffer.from([0, 0, 0]) - const testEncodingFunction = () => validateGetAddressesRequest(buildGetAddressesObject({ startPath, fwVersion })) + const testEncodingFunction = () => + validateGetAddressesRequest( + buildGetAddressesObject({ startPath, fwVersion }), + ) expect(testEncodingFunction).toThrowError() }) }) @@ -76,7 +92,9 @@ describe('validators', () => { test('should throw errors on validation failure', () => { const validateRemoveKvBundle: any = buildValidateRequestObject({}) - expect(() => validateRemoveKvRequest(validateRemoveKvBundle)).toThrowError() + expect(() => + validateRemoveKvRequest(validateRemoveKvBundle), + ).toThrowError() }) }) }) @@ -93,7 +111,8 @@ describe('validators', () => { test('should validate as false bad data', () => { const response: any = { - result: 'Max rate limit reached, please use API Key for higher rate limit', + result: + 'Max rate limit reached, please use API Key for higher rate limit', } expect(isValidBlockExplorerResponse(response)).toBe(false) }) @@ -131,7 +150,9 @@ describe('validators', () => { to: `0x${'1'.repeat(40)}`, value: '1000000000000000000', chainId: 1, - authorizationList: [{ chainId: 1, address: `0x${'2'.repeat(40)}`, nonce: 0 }], + authorizationList: [ + { chainId: 1, address: `0x${'2'.repeat(40)}`, nonce: 0 }, + ], gasPrice: '15000000000', } diff --git a/packages/sdk/src/__test__/utils/__test__/serializers.test.ts b/packages/sdk/src/__test__/utils/__test__/serializers.test.ts index efb92707..e91cc630 100644 --- a/packages/sdk/src/__test__/utils/__test__/serializers.test.ts +++ b/packages/sdk/src/__test__/utils/__test__/serializers.test.ts @@ -1,4 +1,7 @@ -import { deserializeObjectWithBuffers, serializeObjectWithBuffers } from '../serializers' +import { + deserializeObjectWithBuffers, + serializeObjectWithBuffers, +} from '../serializers' describe('serializers', () => { test('serialize obj', () => { diff --git a/packages/sdk/src/__test__/utils/builders.ts b/packages/sdk/src/__test__/utils/builders.ts index a8deabe3..1001a60c 100644 --- a/packages/sdk/src/__test__/utils/builders.ts +++ b/packages/sdk/src/__test__/utils/builders.ts @@ -10,7 +10,12 @@ import type { FirmwareConstants } from '../../types/firmware' import { randomBytes } from '../../util' import { MSG_PAYLOAD_METADATA_SZ } from './constants' import { getN, getPrng } from './getters' -import { BTC_PURPOSE_P2PKH, ETH_COIN, buildRandomEip712Object, getTestVectors } from './helpers' +import { + BTC_PURPOSE_P2PKH, + ETH_COIN, + buildRandomEip712Object, + getTestVectors, +} from './helpers' const prng = getPrng() @@ -66,7 +71,10 @@ export const buildFirmwareConstants = (...overrides: any) => { } export const buildWallet = (overrides?) => ({ - uid: Buffer.from('162b56efe561c12bc93f703dc7026b3ec3d53923270c9259e2b08015fb9defd2', 'hex'), + uid: Buffer.from( + '162b56efe561c12bc93f703dc7026b3ec3d53923270c9259e2b08015fb9defd2', + 'hex', + ), capabilities: 1, external: true, ...overrides, @@ -101,10 +109,14 @@ export const buildSignObject = (fwVersion, overrides?) => { } export const buildSharedSecret = () => { - return Buffer.from([89, 60, 130, 80, 168, 252, 34, 136, 230, 71, 230, 158, 51, 13, 239, 237, 6, 246, 71, 232, 232, 175, 193, 106, 106, 185, 38, 1, 163, 14, 225, 101]) + return Buffer.from([ + 89, 60, 130, 80, 168, 252, 34, 136, 230, 71, 230, 158, 51, 13, 239, 237, 6, + 246, 71, 232, 232, 175, 193, 106, 106, 185, 38, 1, 163, 14, 225, 101, + ]) } -export const getNumIter = (n: number | string | undefined = getN()) => (n ? Number.parseInt(`${n}`) : 5) +export const getNumIter = (n: number | string | undefined = getN()) => + n ? Number.parseInt(`${n}`) : 5 /** Generate a bunch of random test vectors using the PRNG */ export const buildRandomVectors = (n: number | string | undefined = getN()) => { @@ -118,7 +130,13 @@ export const buildRandomVectors = (n: number | string | undefined = getN()) => { return RANDOM_VEC } -export const DEFAULT_SIGNER = [BTC_PURPOSE_P2PKH, ETH_COIN, HARDENED_OFFSET, 0, 0] +export const DEFAULT_SIGNER = [ + BTC_PURPOSE_P2PKH, + ETH_COIN, + HARDENED_OFFSET, + 0, + 0, +] export const buildTx = (data: `0x${string}` = '0xdeadbeef') => { return createTx( @@ -141,7 +159,10 @@ export const buildTx = (data: `0x${string}` = '0xdeadbeef') => { ) } -export const buildEthSignRequest = async (client: Client, txDataOverrides?: any): Promise => { +export const buildEthSignRequest = async ( + client: Client, + txDataOverrides?: any, +): Promise => { if (client.getFwVersion()?.major === 0 && client.getFwVersion()?.minor < 15) { console.warn('Please update firmware. Skipping ETH signing tests.') return @@ -174,7 +195,9 @@ export const buildEthSignRequest = async (client: Client, txDataOverrides?: any) encodingType: Constants.SIGNING.ENCODINGS.EVM, }, } - const maxDataSz = fwConstants.ethMaxDataSz + fwConstants.extraDataMaxFrames * fwConstants.extraDataFrameSz + const maxDataSz = + fwConstants.ethMaxDataSz + + fwConstants.extraDataMaxFrames * fwConstants.extraDataFrameSz return { fwConstants, signerPath, @@ -196,7 +219,10 @@ export const buildTxReq = (tx: TypedTransaction) => ({ }, }) -export const buildMsgReq = (payload = 'hello ethereum', protocol: 'signPersonal' | 'eip712' = 'signPersonal') => ({ +export const buildMsgReq = ( + payload = 'hello ethereum', + protocol: 'signPersonal' | 'eip712' = 'signPersonal', +) => ({ currency: 'ETH_MSG' as const, data: { signerPath: DEFAULT_SIGNER, @@ -250,10 +276,12 @@ export const buildEncDefs = (vectors: any) => { }) // The calldata is already in hex format, we just need to ensure it has 0x prefix - const encDefsCalldata = vectors.canonicalNames.map((_: string, idx: number) => { - const calldata = `0x${idx.toString(16).padStart(8, '0')}` - return calldata - }) + const encDefsCalldata = vectors.canonicalNames.map( + (_: string, idx: number) => { + const calldata = `0x${idx.toString(16).padStart(8, '0')}` + return calldata + }, + ) return { encDefs, encDefsCalldata } } @@ -276,7 +304,17 @@ export function buildRandomMsg(type, client: Client) { } } -export function buildEthMsgReq(payload: any, protocol: 'signPersonal' | 'eip712', signerPath = [BTC_PURPOSE_P2PKH, ETH_COIN, HARDENED_OFFSET, 0, 0] as SigningPath): SignRequestParams { +export function buildEthMsgReq( + payload: any, + protocol: 'signPersonal' | 'eip712', + signerPath = [ + BTC_PURPOSE_P2PKH, + ETH_COIN, + HARDENED_OFFSET, + 0, + 0, + ] as SigningPath, +): SignRequestParams { return { currency: CURRENCIES.ETH_MSG, data: { diff --git a/packages/sdk/src/__test__/utils/determinism.ts b/packages/sdk/src/__test__/utils/determinism.ts index dac43608..8e493f02 100644 --- a/packages/sdk/src/__test__/utils/determinism.ts +++ b/packages/sdk/src/__test__/utils/determinism.ts @@ -10,7 +10,11 @@ import type { SigningPath } from '../../types' import { ethPersonalSignMsg, getSigStr } from './helpers' import { TEST_SEED } from './testConstants' -export async function testUniformSigs(payload: any, tx: TypedTransaction, client: Client) { +export async function testUniformSigs( + payload: any, + tx: TypedTransaction, + client: Client, +) { const tx1Resp = await client.sign(payload) const tx2Resp = await client.sign(payload) const tx3Resp = await client.sign(payload) diff --git a/packages/sdk/src/__test__/utils/ethers.ts b/packages/sdk/src/__test__/utils/ethers.ts index d0bdd8aa..5e4fa622 100644 --- a/packages/sdk/src/__test__/utils/ethers.ts +++ b/packages/sdk/src/__test__/utils/ethers.ts @@ -1,4 +1,13 @@ -const EVM_TYPES = [null, 'address', 'bool', 'uint', 'int', 'bytes', 'string', 'tuple'] +const EVM_TYPES = [ + null, + 'address', + 'bool', + 'uint', + 'int', + 'bytes', + 'string', + 'tuple', +] export function convertDecoderToEthers(def: unknown[]) { const converted = getConvertedDef(def) @@ -68,7 +77,9 @@ function genTupleData(tupleParam: unknown[]) { const nestedData: unknown[] = [] tupleParam.forEach((nestedParam: unknown) => { const np = nestedParam as { toString: (fmt: string) => string }[] - nestedData.push(genData(EVM_TYPES[Number.parseInt(np[1].toString('hex'), 16)] ?? '', np)) + nestedData.push( + genData(EVM_TYPES[Number.parseInt(np[1].toString('hex'), 16)] ?? '', np), + ) }) return nestedData } @@ -79,14 +90,21 @@ function genParamData(param: { toString: (fmt: string) => string }[]) { return getArrayData(param, baseData) } -function getArrayData(param: { toString: (fmt: string) => string }[], baseData: unknown) { +function getArrayData( + param: { toString: (fmt: string) => string }[], + baseData: unknown, +) { let arrayData: unknown[] | undefined let data: unknown const arrSzs = param[3] as unknown as { toString: (fmt: string) => string }[] for (let i = 0; i < arrSzs.length; i++) { // let sz = parseInt(arrSzs[i].toString('hex')); TODO: fix this const dimData: unknown[] = [] - let sz = Number.parseInt((param[3] as unknown as { toString: (fmt: string) => string }[])[i].toString('hex')) + let sz = Number.parseInt( + (param[3] as unknown as { toString: (fmt: string) => string }[])[ + i + ].toString('hex'), + ) if (Number.isNaN(sz)) { sz = 2 //1; } diff --git a/packages/sdk/src/__test__/utils/helpers.ts b/packages/sdk/src/__test__/utils/helpers.ts index 8d603671..53a92b80 100644 --- a/packages/sdk/src/__test__/utils/helpers.ts +++ b/packages/sdk/src/__test__/utils/helpers.ts @@ -5,7 +5,10 @@ import { wordlists } from 'bip39' import bitcoin, { type Payment } from 'bitcoinjs-lib' import BN from 'bn.js' import { ECPairFactory } from 'ecpair' -import { derivePath as deriveEDKey, getPublicKey as getEDPubkey } from 'ed25519-hd-key' +import { + derivePath as deriveEDKey, + getPublicKey as getEDPubkey, +} from 'ed25519-hd-key' import { ec as EC } from 'elliptic' import { privateToAddress } from 'ethereumjs-util' import { jsonc } from 'jsonc' @@ -18,7 +21,13 @@ import { Client } from '../../client' import { BIP_CONSTANTS, HARDENED_OFFSET, ethMsgProtocol } from '../../constants' import { ProtocolConstants } from '../../protocol' import { getPathStr } from '../../shared/utilities' -import { ensureHexBuffer, getV, getYParity, parseDER, randomBytes } from '../../util' +import { + ensureHexBuffer, + getV, + getYParity, + parseDER, + randomBytes, +} from '../../util' import { getEnv } from './getters' import { setStoredClient } from './setup' @@ -99,9 +108,13 @@ export const BTC_PURPOSE_P2PKH = BIP_CONSTANTS.PURPOSES.BTC_LEGACY export const BTC_COIN = BIP_CONSTANTS.COINS.BTC export const BTC_TESTNET_COIN = BIP_CONSTANTS.COINS.BTC_TESTNET export const ETH_COIN = BIP_CONSTANTS.COINS.ETH -export const REUSABLE_KEY = '3fb53b677f73e4d2b8c89c303f6f6b349f0075ad88ea126cb9f6632085815dca' +export const REUSABLE_KEY = + '3fb53b677f73e4d2b8c89c303f6f6b349f0075ad88ea126cb9f6632085815dca' -export function setupTestClient(env = getEnv() as any, stateData?: any): Client { +export function setupTestClient( + env = getEnv() as any, + stateData?: any, +): Client { if (stateData) { return new Client({ stateData }) } @@ -166,7 +179,15 @@ export function _get_btc_addr(pubkey, purpose, network) { return obj.address } -export function _start_tx_builder(wallet, recipient, value, fee, inputs, network, purpose) { +export function _start_tx_builder( + wallet, + recipient, + value, + fee, + inputs, + network, + purpose, +) { const tx = new bitcoin.Transaction() // Match serialization logic (version 2) used by device and serializer tx.version = 2 @@ -178,7 +199,9 @@ export function _start_tx_builder(wallet, recipient, value, fee, inputs, network const networkIdx = network === bitcoin.networks.testnet ? 1 : 0 const path = buildPath([purpose, harden(networkIdx), harden(0), 1, 0]) const btc_0_change = wallet.derivePath(path) - const btc_0_change_pub = ECPair.fromPublicKey(btc_0_change.publicKey).publicKey + const btc_0_change_pub = ECPair.fromPublicKey( + btc_0_change.publicKey, + ).publicKey const changeAddr = _get_btc_addr(btc_0_change_pub, purpose, network) const changeScript = bitcoin.address.toOutputScript(changeAddr, network) tx.addOutput(changeScript, changeValue) @@ -189,7 +212,8 @@ export function _start_tx_builder(wallet, recipient, value, fee, inputs, network inputs.forEach((input) => { const hashLE = Buffer.from(input.hash, 'hex').reverse() tx.addInput(hashLE, input.idx) - const coin = network === bitcoin.networks.testnet ? BTC_TESTNET_COIN : BTC_COIN + const coin = + network === bitcoin.networks.testnet ? BTC_TESTNET_COIN : BTC_COIN const path = buildPath([purpose, coin, harden(0), 0, input.signerIdx]) const keyPair = wallet.derivePath(path) const pubkeyBuf = Buffer.from(keyPair.publicKey) @@ -208,25 +232,68 @@ function _build_sighashes(txb_or_tx, purpose) { const isLegacy = purpose === BTC_PURPOSE_P2PKH if (txb.inputsMeta) { txb.inputsMeta.forEach((meta, i) => { - hashes.push(isLegacy ? txb.tx.hashForSignature(i, meta.scriptCode, SIGHASH_ALL) : txb.tx.hashForWitnessV0(i, meta.scriptCode, meta.value, SIGHASH_ALL)) + hashes.push( + isLegacy + ? txb.tx.hashForSignature(i, meta.scriptCode, SIGHASH_ALL) + : txb.tx.hashForWitnessV0( + i, + meta.scriptCode, + meta.value, + SIGHASH_ALL, + ), + ) }) } else { // Fallback for prior structure (should not be used) txb.__inputs.forEach((input, i) => { - hashes.push(isLegacy ? txb.__tx.hashForSignature(i, input.signScript, SIGHASH_ALL) : txb.__tx.hashForWitnessV0(i, input.signScript, input.value, SIGHASH_ALL)) + hashes.push( + isLegacy + ? txb.__tx.hashForSignature(i, input.signScript, SIGHASH_ALL) + : txb.__tx.hashForWitnessV0( + i, + input.signScript, + input.value, + SIGHASH_ALL, + ), + ) }) } return hashes } -function _get_reference_sighashes(wallet, recipient, value, fee, inputs, isTestnet, purpose) { - const network = isTestnet ? bitcoin.networks.testnet : bitcoin.networks.bitcoin - const built = _start_tx_builder(wallet, recipient, value, fee, inputs, network, purpose) +function _get_reference_sighashes( + wallet, + recipient, + value, + fee, + inputs, + isTestnet, + purpose, +) { + const network = isTestnet + ? bitcoin.networks.testnet + : bitcoin.networks.bitcoin + const built = _start_tx_builder( + wallet, + recipient, + value, + fee, + inputs, + network, + purpose, + ) // built has shape { tx, inputsMeta } return _build_sighashes(built, purpose) } -function _btc_tx_request_builder(inputs, recipient, value, fee, isTestnet, purpose) { +function _btc_tx_request_builder( + inputs, + recipient, + value, + fee, + isTestnet, + purpose, +) { const currencyIdx = isTestnet ? BTC_TESTNET_COIN : BTC_COIN const txData = { prevOuts: [] as any[], @@ -264,7 +331,13 @@ export function stripDER(derSig) { function _get_signing_keys(wallet, inputs, isTestnet, purpose) { const currencyIdx = isTestnet ? 1 : 0 return inputs.map((input) => { - const path = buildPath([purpose, harden(currencyIdx), harden(0), 0, input.signerIdx]) + const path = buildPath([ + purpose, + harden(currencyIdx), + harden(0), + 0, + input.signerIdx, + ]) const node = wallet.derivePath(path) const priv = Buffer.from(node.privateKey) const key = secp256k1.keyFromPrivate(priv) @@ -287,7 +360,8 @@ function _generate_btc_address(isTestnet, purpose, rand) { priv.writeUInt32BE(Math.floor(rand.quick() * 2 ** 32), j * 4) } const keyPair = ECPair.fromPrivateKey(priv) - const network = isTestnet === true ? bitcoin.networks.testnet : bitcoin.networks.bitcoin + const network = + isTestnet === true ? bitcoin.networks.testnet : bitcoin.networks.bitcoin return _get_btc_addr(keyPair.publicKey, purpose, network) } @@ -296,11 +370,32 @@ export function setup_btc_sig_test(opts, wallet, inputs, rand) { const recipient = _generate_btc_address(isTestnet, recipientPurpose, rand) const sumInputs = _getSumInputs(inputs) const fee = Math.floor(rand.quick() * 50000) - const _value = useChange === true ? Math.floor(rand.quick() * sumInputs) : sumInputs + const _value = + useChange === true ? Math.floor(rand.quick() * sumInputs) : sumInputs const value = _value - fee - const sigHashes = _get_reference_sighashes(wallet, recipient, value, fee, inputs, isTestnet, spenderPurpose) - const signingKeys = _get_signing_keys(wallet, inputs, isTestnet, spenderPurpose) - const txReq = _btc_tx_request_builder(inputs, recipient, value, fee, isTestnet, spenderPurpose) + const sigHashes = _get_reference_sighashes( + wallet, + recipient, + value, + fee, + inputs, + isTestnet, + spenderPurpose, + ) + const signingKeys = _get_signing_keys( + wallet, + inputs, + isTestnet, + spenderPurpose, + ) + const txReq = _btc_tx_request_builder( + inputs, + recipient, + value, + fee, + isTestnet, + spenderPurpose, + ) return { sigHashes, signingKeys, @@ -418,7 +513,9 @@ export const copyBuffer = (x) => { // Convert a set of indices to a human readable bip32 path export const stringifyPath = (parent) => { const convert = (parent) => { - return parent >= HARDENED_OFFSET ? `${parent - HARDENED_OFFSET}'` : `${parent}` + return parent >= HARDENED_OFFSET + ? `${parent - HARDENED_OFFSET}'` + : `${parent}` } if (parent.idx) { // BIP32 style encoding @@ -505,7 +602,8 @@ export const validateBTCAddresses = (resp, jobData, seed, useTestnet?) => { expect(resp.count).toEqual(jobData.count) const wallet = bip32.fromSeed(seed) const path = JSON.parse(JSON.stringify(jobData.path)) - const network = useTestnet === true ? bitcoin.networks.testnet : bitcoin.networks.bitcoin + const network = + useTestnet === true ? bitcoin.networks.testnet : bitcoin.networks.bitcoin for (let i = 0; i < jobData.count; i++) { path.idx[jobData.iterIdx] = jobData.path.idx[jobData.iterIdx] + i // Validate the address @@ -554,7 +652,12 @@ export const validateETHAddresses = (resp, jobData, seed) => { } } -export const validateDerivedPublicKeys = (pubKeys, firstPath, seed, flag?: number) => { +export const validateDerivedPublicKeys = ( + pubKeys, + firstPath, + seed, + flag?: number, +) => { const wallet = bip32.fromSeed(seed) // We assume the keys were derived in sequential order pubKeys.forEach((pub, i) => { @@ -563,16 +666,23 @@ export const validateDerivedPublicKeys = (pubKeys, firstPath, seed, flag?: numbe if (flag === Constants.GET_ADDR_FLAGS.ED25519_PUB) { // ED25519 requires its own derivation const key = deriveED25519Key(path, seed) - expect(pub.toString('hex')).toEqualElseLog(key.pub.toString('hex'), 'Exported ED25519 pubkey incorrect') + expect(pub.toString('hex')).toEqualElseLog( + key.pub.toString('hex'), + 'Exported ED25519 pubkey incorrect', + ) } else { // Otherwise this is a SECP256K1 pubkey const priv = wallet.derivePath(getPathStr(path)).privateKey - expect(pub.toString('hex')).toEqualElseLog(secp256k1.keyFromPrivate(priv).getPublic().encode('hex', false), 'Exported SECP256K1 pubkey incorrect') + expect(pub.toString('hex')).toEqualElseLog( + secp256k1.keyFromPrivate(priv).getPublic().encode('hex', false), + 'Exported SECP256K1 pubkey incorrect', + ) } }) } -export const ethPersonalSignMsg = (msg) => `\u0019Ethereum Signed Message:\n${String(msg.length)}${msg}` +export const ethPersonalSignMsg = (msg) => + `\u0019Ethereum Signed Message:\n${String(msg.length)}${msg}` //--------------------------------------------------- // Sign Transaction helpers @@ -647,12 +757,18 @@ export const deserializeSignTxJobResult = (res: any) => { _off += 4 o.signerPath.addr = _o.readUInt32LE(_off) _off += 4 - o.pubkey = secp256k1.keyFromPublic(_o.slice(_off, _off + 65).toString('hex'), 'hex') + o.pubkey = secp256k1.keyFromPublic( + _o.slice(_off, _off + 65).toString('hex'), + 'hex', + ) _off += PK_LEN // We get back a DER signature in 74 bytes, but not all the bytes are necessarily // used. The second byte contains the DER sig length, so we need to use that. const derLen = _o[_off + 1] - o.sig = Buffer.from(_o.slice(_off, _off + 2 + derLen).toString('hex'), 'hex') + o.sig = Buffer.from( + _o.slice(_off, _off + 2 + derLen).toString('hex'), + 'hex', + ) getTxResult.outputs.push(o) } @@ -734,11 +850,14 @@ export const buildRandomEip712Object = (randInt) => { } function getRandomName(upperCase = false, sz = 20) { const name = randStr(sz) - if (upperCase === true) return `${name.slice(0, 1).toUpperCase()}${name.slice(1)}` + if (upperCase === true) + return `${name.slice(0, 1).toUpperCase()}${name.slice(1)}` return name } function getRandomEIP712Type(customTypes: any[] = []) { - const types = Object.keys(customTypes).concat(Object.keys(ethMsgProtocol.TYPED_DATA.typeCodes)) + const types = Object.keys(customTypes).concat( + Object.keys(ethMsgProtocol.TYPED_DATA.typeCodes), + ) return { name: getRandomName(), type: types[randInt(types.length)], @@ -877,15 +996,25 @@ export const validateGenericSig = (seed, sig, payloadBuf, req, pubkey?) => { r: normalizeSigComponent(sig.r).toString('hex'), s: normalizeSigComponent(sig.s).toString('hex'), } - expect(key.verify(hash, normalizedSig)).toEqualElseLog(true, 'Signature failed verification.') + expect(key.verify(hash, normalizedSig)).toEqualElseLog( + true, + 'Signature failed verification.', + ) } else if (curveType === CURVES.ED25519) { if (hashType !== HASHES.NONE) { throw new Error('Bad params') } const { pub } = deriveED25519Key(signerPath, seed) - const signature = Buffer.concat([normalizeSigComponent(sig.r), normalizeSigComponent(sig.s)]) + const signature = Buffer.concat([ + normalizeSigComponent(sig.r), + normalizeSigComponent(sig.s), + ]) const edPublicKey = pubkey ? normalizeSigComponent(pubkey) : pub - const isValid = nacl.sign.detached.verify(new Uint8Array(payloadBuf), new Uint8Array(signature), new Uint8Array(edPublicKey)) + const isValid = nacl.sign.detached.verify( + new Uint8Array(payloadBuf), + new Uint8Array(signature), + new Uint8Array(edPublicKey), + ) expect(isValid).toEqualElseLog(true, 'Signature failed verification.') } else { throw new Error('Bad params') @@ -935,7 +1064,9 @@ export function toBuffer(data: string | number | Buffer | Uint8Array): Buffer { } if (typeof data === 'string') { const trimmed = data.trim() - const isHex = trimmed.startsWith('0x') || (/^[0-9a-fA-F]+$/.test(trimmed) && trimmed.length % 2 === 0) + const isHex = + trimmed.startsWith('0x') || + (/^[0-9a-fA-F]+$/.test(trimmed) && trimmed.length % 2 === 0) return isHex ? ensureHexBuffer(trimmed) : Buffer.from(trimmed, 'utf8') } throw new Error('Unsupported data type') @@ -945,16 +1076,22 @@ export function toUint8Array(data: Buffer): Uint8Array { return new Uint8Array(data.buffer, data.byteOffset, data.length) } -export function ensureHash32(message: string | number | Buffer | Uint8Array): Uint8Array { +export function ensureHash32( + message: string | number | Buffer | Uint8Array, +): Uint8Array { const msgBuffer = toBuffer(message) - const digest = msgBuffer.length === 32 ? msgBuffer : Buffer.from(Hash.keccak256(msgBuffer)) + const digest = + msgBuffer.length === 32 ? msgBuffer : Buffer.from(Hash.keccak256(msgBuffer)) if (digest.length !== 32) { throw new Error('Failed to derive 32-byte hash for signature validation.') } return toUint8Array(digest) } -export function validateSig(resp: any, message: string | number | Buffer | Uint8Array) { +export function validateSig( + resp: any, + message: string | number | Buffer | Uint8Array, +) { if (!resp.sig?.r || !resp.sig?.s || !resp.pubkey) { throw new Error('Missing signature components') } @@ -964,11 +1101,20 @@ export function validateSig(resp: any, message: string | number | Buffer | Uint8 const hash = ensureHash32(message) const pubkeyInput = toBuffer(resp.pubkey) - const normalizedPubkey = pubkeyInput.length === 64 ? Buffer.concat([Buffer.from([0x04]), pubkeyInput]) : pubkeyInput - const isCompressed = normalizedPubkey.length === 33 && (normalizedPubkey[0] === 0x02 || normalizedPubkey[0] === 0x03) + const normalizedPubkey = + pubkeyInput.length === 64 + ? Buffer.concat([Buffer.from([0x04]), pubkeyInput]) + : pubkeyInput + const isCompressed = + normalizedPubkey.length === 33 && + (normalizedPubkey[0] === 0x02 || normalizedPubkey[0] === 0x03) - const recoveredA = Buffer.from(ecdsaRecover(rs, 0, hash, isCompressed)).toString('hex') - const recoveredB = Buffer.from(ecdsaRecover(rs, 1, hash, isCompressed)).toString('hex') + const recoveredA = Buffer.from( + ecdsaRecover(rs, 0, hash, isCompressed), + ).toString('hex') + const recoveredB = Buffer.from( + ecdsaRecover(rs, 1, hash, isCompressed), + ).toString('hex') const expected = normalizedPubkey.toString('hex') if (expected !== recoveredA && expected !== recoveredB) { throw new Error('Signature did not validate.') @@ -999,6 +1145,8 @@ function _stripTrailingCommas(input: string): string { } export const getTestVectors = () => { - const raw = readFileSync(`${process.cwd()}/src/__test__/vectors.jsonc`).toString() + const raw = readFileSync( + `${process.cwd()}/src/__test__/vectors.jsonc`, + ).toString() return jsonc.parse(_stripTrailingCommas(raw)) } diff --git a/packages/sdk/src/__test__/utils/runners.ts b/packages/sdk/src/__test__/utils/runners.ts index 354b2036..a862f718 100644 --- a/packages/sdk/src/__test__/utils/runners.ts +++ b/packages/sdk/src/__test__/utils/runners.ts @@ -1,11 +1,18 @@ import type { Client } from '../../client' import { getEncodedPayload } from '../../genericSigning' -import type { SigningPayload, SignRequestParams, TestRequestPayload } from '../../types' +import type { + SigningPayload, + SignRequestParams, + TestRequestPayload, +} from '../../types' import { parseWalletJobResp, validateGenericSig } from './helpers' import { TEST_SEED } from './testConstants' import { testRequest } from './testRequest' -export async function runTestCase(payload: TestRequestPayload, expectedCode: number) { +export async function runTestCase( + payload: TestRequestPayload, + expectedCode: number, +) { const res = await testRequest(payload) //@ts-expect-error - Accessing private property const fwVersion = payload.client.fwVersion @@ -21,7 +28,11 @@ export async function runGeneric(request: SignRequestParams, client: Client) { // If no encoding type is specified we encode in hex or ascii const encodingType = data.encodingType || null const allowedEncodings = client.getFwConstants().genericSigning.encodingTypes - const { payloadBuf } = getEncodedPayload(data.payload, encodingType, allowedEncodings) + const { payloadBuf } = getEncodedPayload( + data.payload, + encodingType, + allowedEncodings, + ) const seed = TEST_SEED validateGenericSig(seed, response.sig, payloadBuf, data, response.pubkey) return response diff --git a/packages/sdk/src/__test__/utils/setup.ts b/packages/sdk/src/__test__/utils/setup.ts index be76ffb8..1a9537b5 100644 --- a/packages/sdk/src/__test__/utils/setup.ts +++ b/packages/sdk/src/__test__/utils/setup.ts @@ -41,7 +41,9 @@ export async function setupClient() { if (!isPaired) { if (!pairingSecret) { if (process.env.CI) { - throw new Error('Pairing secret is required. If simulator is running, set PAIRING_SECRET environment variable.') + throw new Error( + 'Pairing secret is required. If simulator is running, set PAIRING_SECRET environment variable.', + ) } pairingSecret = question('Enter pairing secret:') if (!pairingSecret) { diff --git a/packages/sdk/src/__test__/utils/testConstants.ts b/packages/sdk/src/__test__/utils/testConstants.ts index e4df92df..256a69f0 100644 --- a/packages/sdk/src/__test__/utils/testConstants.ts +++ b/packages/sdk/src/__test__/utils/testConstants.ts @@ -12,7 +12,8 @@ import { mnemonicToSeedSync } from 'bip39' * This mnemonic is used across multiple test files to ensure consistent * test behavior and deterministic results. */ -export const TEST_MNEMONIC = 'test test test test test test test test test test test junk' +export const TEST_MNEMONIC = + 'test test test test test test test test test test test junk' /** * Shared seed derived from TEST_MNEMONIC diff --git a/packages/sdk/src/__test__/utils/testEnvironment.ts b/packages/sdk/src/__test__/utils/testEnvironment.ts index 00c50522..455f1f37 100644 --- a/packages/sdk/src/__test__/utils/testEnvironment.ts +++ b/packages/sdk/src/__test__/utils/testEnvironment.ts @@ -6,7 +6,8 @@ expect.extend({ toEqualElseLog(received: unknown, expected: unknown, message?: string) { return { pass: received === expected, - message: () => message ?? `Expected ${String(received)} to equal ${String(expected)}`, + message: () => + message ?? `Expected ${String(received)} to equal ${String(expected)}`, } }, }) diff --git a/packages/sdk/src/__test__/utils/testRequest.ts b/packages/sdk/src/__test__/utils/testRequest.ts index 3a33dfdc..faa7bed9 100644 --- a/packages/sdk/src/__test__/utils/testRequest.ts +++ b/packages/sdk/src/__test__/utils/testRequest.ts @@ -1,13 +1,22 @@ -import { LatticeSecureEncryptedRequestType, encryptedSecureRequest } from '../../protocol' +import { + LatticeSecureEncryptedRequestType, + encryptedSecureRequest, +} from '../../protocol' import type { TestRequestPayload } from '../../types' /** * `test` takes a data object with a testID and a payload, and sends them to the device. * @category Lattice */ -export const testRequest = async ({ payload, testID, client }: TestRequestPayload) => { +export const testRequest = async ({ + payload, + testID, + client, +}: TestRequestPayload) => { if (!payload) { - throw new Error('First argument must contain `testID` and `payload` fields.') + throw new Error( + 'First argument must contain `testID` and `payload` fields.', + ) } const sharedSecret = client.sharedSecret const ephemeralPub = client.ephemeralPub diff --git a/packages/sdk/src/__test__/utils/viemComparison.ts b/packages/sdk/src/__test__/utils/viemComparison.ts index 98c78d71..7e96434f 100644 --- a/packages/sdk/src/__test__/utils/viemComparison.ts +++ b/packages/sdk/src/__test__/utils/viemComparison.ts @@ -1,4 +1,11 @@ -import { type Address, type Hex, type TransactionSerializable, type TypedDataDefinition, parseTransaction, serializeTransaction } from 'viem' +import { + type Address, + type Hex, + type TransactionSerializable, + type TypedDataDefinition, + parseTransaction, + serializeTransaction, +} from 'viem' import { privateKeyToAccount } from 'viem/accounts' import { sign, signMessage } from '../../api' import { normalizeLatticeSignature } from '../../ethereum' @@ -28,7 +35,10 @@ export const getFoundryAccount = () => { export type TestTransaction = TransactionSerializable // Sign transaction with both Lattice and viem, then compare -export const signAndCompareTransaction = async (tx: TestTransaction, testName: string) => { +export const signAndCompareTransaction = async ( + tx: TestTransaction, + testName: string, +) => { const foundryAccount = getFoundryAccount() try { @@ -92,14 +102,18 @@ export const signAndCompareTransaction = async (tx: TestTransaction, testName: s // Additional verification: compare signature components // Lattice returns r,s as hex strings with 0x prefix or as Buffer const normalizeSigComponent = (value: string | Buffer) => { - const hexString = typeof value === 'string' ? value : `0x${Buffer.from(value).toString('hex')}` + const hexString = + typeof value === 'string' + ? value + : `0x${Buffer.from(value).toString('hex')}` const stripped = hexString.replace(/^0x/, '').toLowerCase() return `0x${stripped.padStart(64, '0')}` } const latticeR = normalizeSigComponent(latticeResult.sig.r) const latticeS = normalizeSigComponent(latticeResult.sig.s) - if (!parsedViemTx.r || !parsedViemTx.s) throw new Error('Missing signature components') + if (!parsedViemTx.r || !parsedViemTx.s) + throw new Error('Missing signature components') const viemR = normalizeSigComponent(parsedViemTx.r) const viemS = normalizeSigComponent(parsedViemTx.s) @@ -128,7 +142,10 @@ export type EIP712TestMessage = { } // Sign EIP-712 typed data with both Lattice and viem, then compare -export const signAndCompareEIP712Message = async (eip712Message: EIP712TestMessage, testName: string) => { +export const signAndCompareEIP712Message = async ( + eip712Message: EIP712TestMessage, + testName: string, +) => { const foundryAccount = getFoundryAccount() try { diff --git a/packages/sdk/src/api/addressTags.ts b/packages/sdk/src/api/addressTags.ts index 978e672a..0df65ad9 100644 --- a/packages/sdk/src/api/addressTags.ts +++ b/packages/sdk/src/api/addressTags.ts @@ -6,7 +6,9 @@ import { queue } from './utilities' /** * Sends request to the Lattice to add Address Tags. */ -export const addAddressTags = async (tags: [{ [key: string]: string }]): Promise => { +export const addAddressTags = async ( + tags: [{ [key: string]: string }], +): Promise => { // convert an array of objects to an object const records = tags.reduce((acc, tag) => { const key = Object.keys(tag)[0] @@ -20,7 +22,10 @@ export const addAddressTags = async (tags: [{ [key: string]: string }]): Promise /** * Fetches Address Tags from the Lattice. */ -export const fetchAddressTags = async ({ n = MAX_ADDR, start = 0 }: { n?: number; start?: number } = {}) => { +export const fetchAddressTags = async ({ + n = MAX_ADDR, + start = 0, +}: { n?: number; start?: number } = {}) => { const addressTags: AddressTag[] = [] let remainingToFetch = n let fetched = start @@ -45,7 +50,9 @@ export const fetchAddressTags = async ({ n = MAX_ADDR, start = 0 }: { n?: number /** * Removes Address Tags from the Lattice. */ -export const removeAddressTags = async (tags: AddressTag[]): Promise => { +export const removeAddressTags = async ( + tags: AddressTag[], +): Promise => { const ids = tags.map((tag) => `${tag.id}`) return queue((client: Client) => client.removeKvRecords({ ids })) } diff --git a/packages/sdk/src/api/addresses.ts b/packages/sdk/src/api/addresses.ts index ea769d92..a9e51260 100644 --- a/packages/sdk/src/api/addresses.ts +++ b/packages/sdk/src/api/addresses.ts @@ -17,7 +17,12 @@ import { } from '../constants' import { LatticeGetAddressesFlag } from '../protocol/latticeConstants' import type { GetAddressesRequestParams, WalletPath } from '../types' -import { getFlagFromPath, getStartPath, parseDerivationPathComponents, queue } from './utilities' +import { + getFlagFromPath, + getStartPath, + parseDerivationPathComponents, + queue, +} from './utilities' type FetchAddressesParams = { n?: number @@ -25,7 +30,9 @@ type FetchAddressesParams = { flag?: number } -export const fetchAddresses = async (overrides?: Partial) => { +export const fetchAddresses = async ( + overrides?: Partial, +) => { let allAddresses: string[] = [] let totalFetched = 0 const totalToFetch = overrides?.n || MAX_ADDR @@ -58,9 +65,14 @@ export const fetchAddresses = async (overrides?: Partial => { +export const fetchAddress = async ( + path: number | WalletPath = 0, +): Promise => { return fetchAddresses({ - startPath: typeof path === 'number' ? getStartPath(DEFAULT_ETH_DERIVATION, path) : path, + startPath: + typeof path === 'number' + ? getStartPath(DEFAULT_ETH_DERIVATION, path) + : path, n: 1, }).then((addrs) => addrs[0]) } @@ -78,12 +90,23 @@ function createFetchBtcAddressesFunction(derivationPath: number[]) { }) } } -export const fetchBtcLegacyAddresses = createFetchBtcAddressesFunction(BTC_LEGACY_DERIVATION) -export const fetchBtcSegwitAddresses = createFetchBtcAddressesFunction(BTC_SEGWIT_DERIVATION) -export const fetchBtcWrappedSegwitAddresses = createFetchBtcAddressesFunction(BTC_WRAPPED_SEGWIT_DERIVATION) -export const fetchBtcLegacyChangeAddresses = createFetchBtcAddressesFunction(BTC_LEGACY_CHANGE_DERIVATION) -export const fetchBtcSegwitChangeAddresses = createFetchBtcAddressesFunction(BTC_SEGWIT_CHANGE_DERIVATION) -export const fetchBtcWrappedSegwitChangeAddresses = createFetchBtcAddressesFunction(BTC_WRAPPED_SEGWIT_CHANGE_DERIVATION) +export const fetchBtcLegacyAddresses = createFetchBtcAddressesFunction( + BTC_LEGACY_DERIVATION, +) +export const fetchBtcSegwitAddresses = createFetchBtcAddressesFunction( + BTC_SEGWIT_DERIVATION, +) +export const fetchBtcWrappedSegwitAddresses = createFetchBtcAddressesFunction( + BTC_WRAPPED_SEGWIT_DERIVATION, +) +export const fetchBtcLegacyChangeAddresses = createFetchBtcAddressesFunction( + BTC_LEGACY_CHANGE_DERIVATION, +) +export const fetchBtcSegwitChangeAddresses = createFetchBtcAddressesFunction( + BTC_SEGWIT_CHANGE_DERIVATION, +) +export const fetchBtcWrappedSegwitChangeAddresses = + createFetchBtcAddressesFunction(BTC_WRAPPED_SEGWIT_CHANGE_DERIVATION) export const fetchSolanaAddresses = async ( { n, startPathIndex }: FetchAddressesParams = { @@ -110,7 +133,11 @@ export const fetchLedgerLiveAddresses = async ( queue((client) => client .getAddresses({ - startPath: getStartPath(LEDGER_LIVE_DERIVATION, startPathIndex + i, 2), + startPath: getStartPath( + LEDGER_LIVE_DERIVATION, + startPathIndex + i, + 2, + ), n: 1, }) .then((addresses) => addresses.map((address) => `${address}`)), @@ -132,7 +159,11 @@ export const fetchLedgerLegacyAddresses = async ( queue((client) => client .getAddresses({ - startPath: getStartPath(LEDGER_LEGACY_DERIVATION, startPathIndex + i, 3), + startPath: getStartPath( + LEDGER_LEGACY_DERIVATION, + startPathIndex + i, + 3, + ), n: 1, }) .then((addresses) => addresses.map((address) => `${address}`)), @@ -142,12 +173,20 @@ export const fetchLedgerLegacyAddresses = async ( return Promise.all(addresses) } -export const fetchBip44ChangeAddresses = async ({ n = MAX_ADDR, startPathIndex = 0 }: FetchAddressesParams = {}) => { +export const fetchBip44ChangeAddresses = async ({ + n = MAX_ADDR, + startPathIndex = 0, +}: FetchAddressesParams = {}) => { const addresses = [] for (let i = 0; i < n; i++) { addresses.push( queue((client) => { - const startPath = [44 + HARDENED_OFFSET, 501 + HARDENED_OFFSET, startPathIndex + i + HARDENED_OFFSET, 0 + HARDENED_OFFSET] + const startPath = [ + 44 + HARDENED_OFFSET, + 501 + HARDENED_OFFSET, + startPathIndex + i + HARDENED_OFFSET, + 0 + HARDENED_OFFSET, + ] return client .getAddresses({ startPath, @@ -161,11 +200,16 @@ export const fetchBip44ChangeAddresses = async ({ n = MAX_ADDR, startPathIndex = return Promise.all(addresses) } -export async function fetchAddressesByDerivationPath(path: string, { n = 1, startPathIndex = 0, flag }: FetchAddressesParams = {}): Promise { +export async function fetchAddressesByDerivationPath( + path: string, + { n = 1, startPathIndex = 0, flag }: FetchAddressesParams = {}, +): Promise { const components = path.split('/').filter(Boolean) const parsedPath = parseDerivationPathComponents(components) const _flag = getFlagFromPath(parsedPath) - const wildcardIndex = components.findIndex((part) => part.toLowerCase().includes('x')) + const wildcardIndex = components.findIndex((part) => + part.toLowerCase().includes('x'), + ) if (wildcardIndex === -1) { return queue((client) => @@ -211,9 +255,12 @@ export async function fetchBtcXpub(): Promise { * @returns ypub string */ export async function fetchBtcYpub(): Promise { - const result = await fetchAddressesByDerivationPath(BTC_WRAPPED_SEGWIT_YPUB_PATH, { - flag: LatticeGetAddressesFlag.secp256k1Xpub, - }) + const result = await fetchAddressesByDerivationPath( + BTC_WRAPPED_SEGWIT_YPUB_PATH, + { + flag: LatticeGetAddressesFlag.secp256k1Xpub, + }, + ) return result[0] } diff --git a/packages/sdk/src/api/setup.ts b/packages/sdk/src/api/setup.ts index f07b5646..9e84b702 100644 --- a/packages/sdk/src/api/setup.ts +++ b/packages/sdk/src/api/setup.ts @@ -50,7 +50,9 @@ export const setup = async (params: SetupParameters): Promise => { setSaveClient(buildSaveClientFn(params.setStoredClient)) if ('deviceId' in params && 'password' in params && 'name' in params) { - const privKey = params.appSecret || Utils.generateAppSecret(params.deviceId, params.password, params.name) + const privKey = + params.appSecret || + Utils.generateAppSecret(params.deviceId, params.password, params.name) const client = new Client({ deviceId: params.deviceId, privKey, diff --git a/packages/sdk/src/api/signing.ts b/packages/sdk/src/api/signing.ts index 0010046e..559a44ee 100644 --- a/packages/sdk/src/api/signing.ts +++ b/packages/sdk/src/api/signing.ts @@ -1,10 +1,31 @@ import { RLP } from '@ethereumjs/rlp' import { Hash } from 'ox' -import { type Address, type Authorization, type Hex, type TransactionSerializable, type TransactionSerializableEIP7702, serializeTransaction } from 'viem' +import { + type Address, + type Authorization, + type Hex, + type TransactionSerializable, + type TransactionSerializableEIP7702, + serializeTransaction, +} from 'viem' import { Constants } from '..' -import { BTC_LEGACY_DERIVATION, BTC_SEGWIT_DERIVATION, BTC_WRAPPED_SEGWIT_DERIVATION, CURRENCIES, DEFAULT_ETH_DERIVATION, SOLANA_DERIVATION } from '../constants' +import { + BTC_LEGACY_DERIVATION, + BTC_SEGWIT_DERIVATION, + BTC_WRAPPED_SEGWIT_DERIVATION, + CURRENCIES, + DEFAULT_ETH_DERIVATION, + SOLANA_DERIVATION, +} from '../constants' import { fetchDecoder } from '../functions/fetchDecoder' -import type { BitcoinSignPayload, EIP712MessagePayload, SignData, SignRequestParams, SigningPayload, TransactionRequest } from '../types' +import type { + BitcoinSignPayload, + EIP712MessagePayload, + SignData, + SignRequestParams, + SigningPayload, + TransactionRequest, +} from '../types' import { getYParity } from '../util' import { isEIP712Payload, queue } from './utilities' @@ -19,21 +40,38 @@ type AuthorizationRequest = { */ type RawTransaction = Hex | Uint8Array | Buffer -export const sign = async (transaction: TransactionSerializable | RawTransaction, overrides?: Omit): Promise => { +export const sign = async ( + transaction: TransactionSerializable | RawTransaction, + overrides?: Omit, +): Promise => { const isRaw = isRawTransaction(transaction) - const serializedTx = isRaw ? normalizeRawTransaction(transaction) : serializeTransaction(transaction as TransactionSerializable) + const serializedTx = isRaw + ? normalizeRawTransaction(transaction) + : serializeTransaction(transaction as TransactionSerializable) // Determine the encoding type based on transaction type - let encodingType: typeof Constants.SIGNING.ENCODINGS.EVM | typeof Constants.SIGNING.ENCODINGS.EIP7702_AUTH | typeof Constants.SIGNING.ENCODINGS.EIP7702_AUTH_LIST = Constants.SIGNING.ENCODINGS.EVM + let encodingType: + | typeof Constants.SIGNING.ENCODINGS.EVM + | typeof Constants.SIGNING.ENCODINGS.EIP7702_AUTH + | typeof Constants.SIGNING.ENCODINGS.EIP7702_AUTH_LIST = + Constants.SIGNING.ENCODINGS.EVM if (!isRaw && (transaction as TransactionSerializable).type === 'eip7702') { const eip7702Tx = transaction as TransactionSerializableEIP7702 - const hasAuthList = eip7702Tx.authorizationList && eip7702Tx.authorizationList.length > 0 - encodingType = hasAuthList ? Constants.SIGNING.ENCODINGS.EIP7702_AUTH_LIST : Constants.SIGNING.ENCODINGS.EIP7702_AUTH + const hasAuthList = + eip7702Tx.authorizationList && eip7702Tx.authorizationList.length > 0 + encodingType = hasAuthList + ? Constants.SIGNING.ENCODINGS.EIP7702_AUTH_LIST + : Constants.SIGNING.ENCODINGS.EIP7702_AUTH } // Only fetch decoder if we have the required fields let decoder: Buffer | undefined - if (!isRaw && 'data' in (transaction as TransactionSerializable) && 'to' in (transaction as TransactionSerializable) && 'chainId' in (transaction as TransactionSerializable)) { + if ( + !isRaw && + 'data' in (transaction as TransactionSerializable) && + 'to' in (transaction as TransactionSerializable) && + 'chainId' in (transaction as TransactionSerializable) + ) { decoder = await fetchDecoder({ data: (transaction as TransactionSerializable).data, to: (transaction as TransactionSerializable).to, @@ -56,7 +94,15 @@ export const sign = async (transaction: TransactionSerializable | RawTransaction /** * Sign a message with support for EIP-712 typed data and const assertions */ -export function signMessage(payload: string | Uint8Array | Buffer | Buffer[] | EIP712MessagePayload>, overrides?: Omit): Promise { +export function signMessage( + payload: + | string + | Uint8Array + | Buffer + | Buffer[] + | EIP712MessagePayload>, + overrides?: Omit, +): Promise { const basePayload: SigningPayload> = { signerPath: DEFAULT_ETH_DERIVATION, curveType: Constants.SIGNING.CURVES.SECP256K1, @@ -74,8 +120,14 @@ export function signMessage(payload: string | Uint8Array | Buffer | Buffer[] | E return queue((client) => client.sign(tx)) } -function isRawTransaction(value: TransactionSerializable | RawTransaction): value is RawTransaction { - return typeof value === 'string' || value instanceof Uint8Array || Buffer.isBuffer(value) +function isRawTransaction( + value: TransactionSerializable | RawTransaction, +): value is RawTransaction { + return ( + typeof value === 'string' || + value instanceof Uint8Array || + Buffer.isBuffer(value) + ) } function normalizeRawTransaction(tx: RawTransaction): Hex | Buffer { @@ -89,15 +141,26 @@ function normalizeRawTransaction(tx: RawTransaction): Hex | Buffer { * Signs an EIP-7702 authorization to set code for an externally owned account (EOA). * Returns a Viem-compatible authorization object. */ -export const signAuthorization = async (authorization: AuthorizationRequest, overrides?: Omit): Promise => { +export const signAuthorization = async ( + authorization: AuthorizationRequest, + overrides?: Omit, +): Promise => { // EIP-7702 authorization message is: MAGIC || rlp([chain_id, address, nonce]) // MAGIC = 0x05 per EIP-7702 spec const MAGIC = Buffer.from([0x05]) // Handle the address/contractAddress alias - const address = 'address' in authorization ? authorization.address : authorization.contractAddress + const address = + 'address' in authorization + ? authorization.address + : authorization.contractAddress - const message = Buffer.concat([MAGIC, Buffer.from(RLP.encode([authorization.chainId, address, authorization.nonce]))]) + const message = Buffer.concat([ + MAGIC, + Buffer.from( + RLP.encode([authorization.chainId, address, authorization.nonce]), + ), + ]) const payload: SigningPayload = { signerPath: DEFAULT_ETH_DERIVATION, @@ -108,7 +171,9 @@ export const signAuthorization = async (authorization: AuthorizationRequest, ove } // Get the signature with all components - const response = await queue((client) => client.sign({ data: payload, ...overrides })) + const response = await queue((client) => + client.sign({ data: payload, ...overrides }), + ) // Extract signature components if they exist if (response.sig && response.pubkey) { @@ -117,8 +182,12 @@ export const signAuthorization = async (authorization: AuthorizationRequest, ove const yParity = getYParity(messageHash, response.sig, response.pubkey) // Handle both Buffer and string formats for r and s - const rValue = Buffer.isBuffer(response.sig.r) ? `0x${response.sig.r.toString('hex')}` : response.sig.r - const sValue = Buffer.isBuffer(response.sig.s) ? `0x${response.sig.s.toString('hex')}` : response.sig.s + const rValue = Buffer.isBuffer(response.sig.r) + ? `0x${response.sig.r.toString('hex')}` + : response.sig.r + const sValue = Buffer.isBuffer(response.sig.s) + ? `0x${response.sig.s.toString('hex')}` + : response.sig.s // Create a complete Authorization object with all required signature components const result: Authorization = { @@ -139,7 +208,9 @@ export const signAuthorization = async (authorization: AuthorizationRequest, ove /** * Sign an EIP-7702 transaction using Viem-compatible types */ -export const signAuthorizationList = async (tx: TransactionSerializableEIP7702): Promise => { +export const signAuthorizationList = async ( + tx: TransactionSerializableEIP7702, +): Promise => { const serializedTx = serializeTransaction(tx) const payload: SigningPayload = { @@ -156,7 +227,9 @@ export const signAuthorizationList = async (tx: TransactionSerializableEIP7702): return signedPayload } -export const signBtcLegacyTx = async (payload: BitcoinSignPayload): Promise => { +export const signBtcLegacyTx = async ( + payload: BitcoinSignPayload, +): Promise => { const tx = { data: { signerPath: BTC_LEGACY_DERIVATION, @@ -167,7 +240,9 @@ export const signBtcLegacyTx = async (payload: BitcoinSignPayload): Promise client.sign(tx)) } -export const signBtcSegwitTx = async (payload: BitcoinSignPayload): Promise => { +export const signBtcSegwitTx = async ( + payload: BitcoinSignPayload, +): Promise => { const tx = { data: { signerPath: BTC_SEGWIT_DERIVATION, @@ -178,7 +253,9 @@ export const signBtcSegwitTx = async (payload: BitcoinSignPayload): Promise client.sign(tx)) } -export const signBtcWrappedSegwitTx = async (payload: BitcoinSignPayload): Promise => { +export const signBtcWrappedSegwitTx = async ( + payload: BitcoinSignPayload, +): Promise => { const tx = { data: { signerPath: BTC_WRAPPED_SEGWIT_DERIVATION, @@ -189,7 +266,10 @@ export const signBtcWrappedSegwitTx = async (payload: BitcoinSignPayload): Promi return queue((client) => client.sign(tx)) } -export const signSolanaTx = async (payload: Buffer, overrides?: SignRequestParams): Promise => { +export const signSolanaTx = async ( + payload: Buffer, + overrides?: SignRequestParams, +): Promise => { const tx = { data: { signerPath: SOLANA_DERIVATION, diff --git a/packages/sdk/src/api/state.ts b/packages/sdk/src/api/state.ts index 52a7c68a..530b93b5 100644 --- a/packages/sdk/src/api/state.ts +++ b/packages/sdk/src/api/state.ts @@ -2,7 +2,9 @@ import type { Client } from '../client' export let saveClient: (clientData: string | null) => Promise -export const setSaveClient = (fn: (clientData: string | null) => Promise) => { +export const setSaveClient = ( + fn: (clientData: string | null) => Promise, +) => { saveClient = fn } diff --git a/packages/sdk/src/api/utilities.ts b/packages/sdk/src/api/utilities.ts index 607c92d6..362ab948 100644 --- a/packages/sdk/src/api/utilities.ts +++ b/packages/sdk/src/api/utilities.ts @@ -1,6 +1,11 @@ import { Client } from '../client' import { EXTERNAL, HARDENED_OFFSET } from '../constants' -import { getFunctionQueue, loadClient, saveClient, setFunctionQueue } from './state' +import { + getFunctionQueue, + loadClient, + saveClient, + setFunctionQueue, +} from './state' /** * `queue` is a function that wraps all functional API calls. It limits the number of concurrent @@ -49,7 +54,9 @@ const decodeClientData = (clientData: string) => { return Buffer.from(clientData, 'base64').toString() } -export const buildSaveClientFn = (setStoredClient: (clientData: string | null) => Promise) => { +export const buildSaveClientFn = ( + setStoredClient: (clientData: string | null) => Promise, +) => { return async (clientData: string | null) => { if (!clientData) return const encodedData = encodeClientData(clientData) @@ -81,7 +88,12 @@ export const getStartPath = ( return startPath } -export const isEIP712Payload = (payload: any) => typeof payload !== 'string' && 'types' in payload && 'domain' in payload && 'primaryType' in payload && 'message' in payload +export const isEIP712Payload = (payload: any) => + typeof payload !== 'string' && + 'types' in payload && + 'domain' in payload && + 'primaryType' in payload && + 'message' in payload export function parseDerivationPath(path: string): number[] { if (!path) return [] @@ -94,7 +106,8 @@ export function parseDerivationPathComponents(components: string[]): number[] { const lowerPart = part.toLowerCase() if (lowerPart === 'x') return 0 // Wildcard if (lowerPart === "x'") return HARDENED_OFFSET // Hardened wildcard - if (part.endsWith("'")) return Number.parseInt(part.slice(0, -1)) + HARDENED_OFFSET + if (part.endsWith("'")) + return Number.parseInt(part.slice(0, -1)) + HARDENED_OFFSET const val = Number.parseInt(part) if (Number.isNaN(val)) { throw new Error(`Invalid part in derivation path: ${part}`) diff --git a/packages/sdk/src/bitcoin.ts b/packages/sdk/src/bitcoin.ts index c316d425..3c64ba35 100644 --- a/packages/sdk/src/bitcoin.ts +++ b/packages/sdk/src/bitcoin.ts @@ -52,7 +52,8 @@ const BTC_SCRIPT_TYPE_P2WPKH_V0 = 0x04 const buildBitcoinTxRequest = (data) => { const { prevOuts, recipient, value, changePath, fee } = data if (!changePath) throw new Error('No changePath provided.') - if (changePath.length !== 5) throw new Error('Please provide a full change path.') + if (changePath.length !== 5) + throw new Error('Please provide a full change path.') // Serialize the request const payload = Buffer.alloc(59 + 69 * prevOuts.length) let off = 0 @@ -104,7 +105,8 @@ const buildBitcoinTxRequest = (data) => { const scriptType = getScriptType(input) payload.writeUInt8(scriptType, off) off++ - if (!Buffer.isBuffer(input.txHash)) input.txHash = Buffer.from(input.txHash, 'hex') + if (!Buffer.isBuffer(input.txHash)) + input.txHash = Buffer.from(input.txHash, 'hex') input.txHash.copy(payload, off) off += input.txHash.length }) @@ -234,7 +236,10 @@ const getBitcoinAddress = (pubkeyhash, version) => { function buildRedeemScript(pubkey) { const redeemScript = Buffer.alloc(22) const shaHash = Buffer.from(Hash.sha256(pubkey)) - const pubkeyhash = Buffer.from(ripemd160().update(shaHash).digest('hex'), 'hex') + const pubkeyhash = Buffer.from( + ripemd160().update(shaHash).digest('hex'), + 'hex', + ) redeemScript.writeUInt8(OP.ZERO, 0) redeemScript.writeUInt8(pubkeyhash.length, 1) pubkeyhash.copy(redeemScript, 2) @@ -283,7 +288,9 @@ function buildLockingScript(address) { case FMT_LEGACY_TESTNET: return buildP2pkhLockingScript(dec.pkh) default: - throw new Error(`Unknown version byte: ${dec.versionByte}. Cannot build BTC transaction.`) + throw new Error( + `Unknown version byte: ${dec.versionByte}. Cannot build BTC transaction.`, + ) } } @@ -400,7 +407,9 @@ function decodeAddress(address) { } // Make sure we decoded if (bech32Dec.words[0] !== 0) { - throw new Error(`Unsupported segwit version: must be 0, got ${bech32Dec.words[0]}`) + throw new Error( + `Unsupported segwit version: must be 0, got ${bech32Dec.words[0]}`, + ) } // Make sure address type is supported. // We currently only support P2WPKH addresses, which bech-32decode to 33 words. @@ -409,7 +418,9 @@ function decodeAddress(address) { // support them either. if (bech32Dec.words.length !== 33) { const isP2wpsh = bech32Dec.words.length === 53 - throw new Error(`Unsupported address${isP2wpsh ? ' (P2WSH not supported)' : ''}: ${address}`) + throw new Error( + `Unsupported address${isP2wpsh ? ' (P2WSH not supported)' : ''}: ${address}`, + ) } pkh = Buffer.from(bech32.fromWords(bech32Dec.words.slice(1))) @@ -432,14 +443,19 @@ function getAddressFormat(path) { return FMT_SEGWIT_NATIVE_V0_TESTNET } else if (purpose === PURPOSES.BTC_WRAPPED_SEGWIT && coin === COINS.BTC) { return FMT_SEGWIT_WRAPPED - } else if (purpose === PURPOSES.BTC_WRAPPED_SEGWIT && coin === COINS.BTC_TESTNET) { + } else if ( + purpose === PURPOSES.BTC_WRAPPED_SEGWIT && + coin === COINS.BTC_TESTNET + ) { return FMT_SEGWIT_WRAPPED_TESTNET } else if (purpose === PURPOSES.BTC_LEGACY && coin === COINS.BTC) { return FMT_LEGACY } else if (purpose === PURPOSES.BTC_LEGACY && coin === COINS.BTC_TESTNET) { return FMT_LEGACY_TESTNET } else { - throw new Error('Invalid Bitcoin path provided. Cannot determine address format.') + throw new Error( + 'Invalid Bitcoin path provided. Cannot determine address format.', + ) } } @@ -456,7 +472,9 @@ function getScriptType(input) { case PURPOSES.BTC_SEGWIT: return BTC_SCRIPT_TYPE_P2WPKH_V0 default: - throw new Error(`Unsupported path purpose (${input.signerPath[0]}): cannot determine BTC script type.`) + throw new Error( + `Unsupported path purpose (${input.signerPath[0]}): cannot determine BTC script type.`, + ) } } @@ -466,7 +484,10 @@ function getScriptType(input) { function needsWitness(inputs) { let w = false inputs.forEach((input) => { - if (input.signerPath[0] === PURPOSES.BTC_SEGWIT || input.signerPath[0] === PURPOSES.BTC_WRAPPED_SEGWIT) { + if ( + input.signerPath[0] === PURPOSES.BTC_SEGWIT || + input.signerPath[0] === PURPOSES.BTC_WRAPPED_SEGWIT + ) { w = true } }) diff --git a/packages/sdk/src/calldata/evm.ts b/packages/sdk/src/calldata/evm.ts index b1d90434..75b760be 100644 --- a/packages/sdk/src/calldata/evm.ts +++ b/packages/sdk/src/calldata/evm.ts @@ -7,7 +7,10 @@ import { decodeAbiParameters, parseAbiParameters } from 'viem' * @returns Buffer containing RLP-serialized array of calldata info to pass to signing request * @public */ -export const parseSolidityJSONABI = (sig: string, abi: any[]): { def: EVMDef } => { +export const parseSolidityJSONABI = ( + sig: string, + abi: any[], +): { def: EVMDef } => { sig = coerceSig(sig) // Find the first match in the ABI const match = abi @@ -102,11 +105,17 @@ export const getNestedCalldata = (def, calldata) => { if (Array.isArray(paramData)) { paramData.forEach((nestedParamDatum) => { // Ensure nestedParamDatum is a hex string - if (typeof nestedParamDatum !== 'string' || !nestedParamDatum.startsWith('0x')) { + if ( + typeof nestedParamDatum !== 'string' || + !nestedParamDatum.startsWith('0x') + ) { nestedDefIsPossible = false return } - const nestedParamDatumBuf = Buffer.from(nestedParamDatum.slice(2), 'hex') + const nestedParamDatumBuf = Buffer.from( + nestedParamDatum.slice(2), + 'hex', + ) if (!couldBeNestedDef(nestedParamDatumBuf)) { nestedDefIsPossible = false } @@ -116,7 +125,10 @@ export const getNestedCalldata = (def, calldata) => { } } else if (isBytesItem(defParams[i])) { // Regular `bytes` type - perform size check - if (typeof paramData !== 'string' || !(paramData as string).startsWith('0x')) { + if ( + typeof paramData !== 'string' || + !(paramData as string).startsWith('0x') + ) { nestedDefIsPossible = false } else { const data = paramData as string @@ -294,7 +306,8 @@ function parseBasicTypeStr(typeStr: string): EVMParamInfo { if (typeStr.indexOf(t) > -1 && !found) { param.typeIdx = i param.arraySzs = getArraySzs(typeStr) - const arrStart = param.arraySzs.length > 0 ? typeStr.indexOf('[') : typeStr.length + const arrStart = + param.arraySzs.length > 0 ? typeStr.indexOf('[') : typeStr.length const typeStrNum = typeStr.slice(t.length, arrStart) if (Number.parseInt(typeStrNum)) { param.szBytes = Number.parseInt(typeStrNum) / 8 @@ -316,7 +329,12 @@ function parseBasicTypeStr(typeStr: string): EVMParamInfo { * (EVMDef). This function may recurse if there are tuple types. * @internal */ -function parseDef(item, canonicalName = '', def = [], recursed = false): EVMDef { +function parseDef( + item, + canonicalName = '', + def = [], + recursed = false, +): EVMDef { // Function name. Can be an empty string. if (!recursed) { const nameStr = item.name || '' @@ -331,7 +349,12 @@ function parseDef(item, canonicalName = '', def = [], recursed = false): EVMDef const flatParam = getFlatParam(input) if (input.type.indexOf('tuple') > -1 && input.components) { // For tuples we need to recurse - const recursed = parseDef({ inputs: input.components }, canonicalName, [], true) + const recursed = parseDef( + { inputs: input.components }, + canonicalName, + [], + true, + ) canonicalName = recursed.canonicalName // Add brackets if this is a tuple array and also add a comma canonicalName += `${input.type.slice(5)},` @@ -369,7 +392,12 @@ function parseParamDef(def: any[], prefix = ''): any[] { parsedDef[parsedDef.length - 1].push(parseParamDef(param, `${i}-`)) } else { // If this is not tuple info, add the flat param info to the def - parsedDef.push([`#${prefix}${i + 1 - numTuples}`, param.typeIdx, param.szBytes, param.arraySzs]) + parsedDef.push([ + `#${prefix}${i + 1 - numTuples}`, + param.typeIdx, + param.szBytes, + param.arraySzs, + ]) } // Tuple if (param.typeIdx === EVM_TYPES.indexOf('tuple')) { @@ -482,7 +510,8 @@ function getTupleName(name, withArr = true) { } else if (name[i] === ')') { brackets -= 1 } - let canBreak = name[i + 1] === ',' || name[i + 1] === ')' || i === name.length - 1 + let canBreak = + name[i + 1] === ',' || name[i + 1] === ')' || i === name.length - 1 if (!withArr && name[i + 1] === '[') { canBreak = true } @@ -510,7 +539,17 @@ function isBytesArrItem(param) { } const BAD_CANONICAL_ERR = 'Could not parse canonical function name.' -const EVM_TYPES = [null, 'address', 'bool', 'uint', 'int', 'bytes', 'string', 'tuple', 'nestedDef'] +const EVM_TYPES = [ + null, + 'address', + 'bool', + 'uint', + 'int', + 'bytes', + 'string', + 'tuple', + 'nestedDef', +] type EVMParamInfo = { szBytes: number diff --git a/packages/sdk/src/calldata/index.ts b/packages/sdk/src/calldata/index.ts index d5f94f42..452f342f 100644 --- a/packages/sdk/src/calldata/index.ts +++ b/packages/sdk/src/calldata/index.ts @@ -3,7 +3,12 @@ * calldata decoder info is packed into the request, it is used to decode the calldata in the * request. It is optional. */ -import { getNestedCalldata, parseCanonicalName, parseSolidityJSONABI, replaceNestedDefs } from './evm' +import { + getNestedCalldata, + parseCanonicalName, + parseSolidityJSONABI, + replaceNestedDefs, +} from './evm' export const CALLDATA = { EVM: { diff --git a/packages/sdk/src/client.ts b/packages/sdk/src/client.ts index 76ea3cc9..7c93edc2 100644 --- a/packages/sdk/src/client.ts +++ b/packages/sdk/src/client.ts @@ -1,10 +1,36 @@ import { buildSaveClientFn } from './api/utilities' -import { BASE_URL, DEFAULT_ACTIVE_WALLETS, EMPTY_WALLET_UID, getFwVersionConst } from './constants' -import { addKvRecords, connect, fetchActiveWallet, fetchEncData, getAddresses, getKvRecords, pair, removeKvRecords, sign } from './functions/index' +import { + BASE_URL, + DEFAULT_ACTIVE_WALLETS, + EMPTY_WALLET_UID, + getFwVersionConst, +} from './constants' +import { + addKvRecords, + connect, + fetchActiveWallet, + fetchEncData, + getAddresses, + getKvRecords, + pair, + removeKvRecords, + sign, +} from './functions/index' import { buildRetryWrapper } from './shared/functions' import { getPubKeyBytes } from './shared/utilities' import { validateEphemeralPub } from './shared/validators' -import type { ActiveWallets, AddKvRecordsRequestParams, FetchEncDataRequest, GetAddressesRequestParams, GetKvRecordsData, GetKvRecordsRequestParams, KeyPair, RemoveKvRecordsRequestParams, SignData, SignRequestParams } from './types' +import type { + ActiveWallets, + AddKvRecordsRequestParams, + FetchEncDataRequest, + GetAddressesRequestParams, + GetKvRecordsData, + GetKvRecordsRequestParams, + KeyPair, + RemoveKvRecordsRequestParams, + SignData, + SignRequestParams, +} from './types' import { getP256KeyPair, getP256KeyPairFromPub, randomBytes } from './util' /** @@ -90,7 +116,9 @@ export class Client { this.privKey = privKey || randomBytes(32) this.key = getP256KeyPair(this.privKey) this.retryWrapper = buildRetryWrapper(this, this.retryCount) - this.setStoredClient = setStoredClient ? buildSaveClientFn(setStoredClient) : undefined + this.setStoredClient = setStoredClient + ? buildSaveClientFn(setStoredClient) + : undefined /** The user may pass in state data to rehydrate a session that was previously cached */ if (stateData) { @@ -130,7 +158,9 @@ export class Client { public get sharedSecret() { // Once every ~256 attempts, we will get a key that starts with a `00` byte, which can lead to // problems initializing AES if we don't force a 32 byte BE buffer. - return Buffer.from(this.key.derive(this.ephemeralPub.getPublic()).toArray('be', 32)) + return Buffer.from( + this.key.derive(this.ephemeralPub.getPublic()).toArray('be', 32), + ) } /** @internal */ @@ -167,7 +197,12 @@ export class Client { * Takes a starting path and a number to get the addresses associated with the active wallet. * @category Lattice */ - public async getAddresses({ startPath, n = 1, flag = 0, iterIdx = 0 }: GetAddressesRequestParams): Promise { + public async getAddresses({ + startPath, + n = 1, + flag = 0, + iterIdx = 0, + }: GetAddressesRequestParams): Promise { return this.retryWrapper(getAddresses, { startPath, n, flag, iterIdx }) } @@ -175,7 +210,12 @@ export class Client { * Builds and sends a request for signing to the Lattice. * @category Lattice */ - public async sign({ data, currency, cachedData, nextCode }: SignRequestParams): Promise { + public async sign({ + data, + currency, + cachedData, + nextCode, + }: SignRequestParams): Promise { return this.retryWrapper(sign, { data, currency, cachedData, nextCode }) } @@ -190,7 +230,11 @@ export class Client { * Takes in a set of key-value records and sends a request to add them to the Lattice. * @category Lattice */ - async addKvRecords({ type = 0, records, caseSensitive = false }: AddKvRecordsRequestParams): Promise { + async addKvRecords({ + type = 0, + records, + caseSensitive = false, + }: AddKvRecordsRequestParams): Promise { return this.retryWrapper(addKvRecords, { type, records, caseSensitive }) } @@ -198,7 +242,11 @@ export class Client { * Fetches a list of key-value records from the Lattice. * @category Lattice */ - public async getKvRecords({ type = 0, n = 1, start = 0 }: GetKvRecordsRequestParams): Promise { + public async getKvRecords({ + type = 0, + n = 1, + start = 0, + }: GetKvRecordsRequestParams): Promise { return this.retryWrapper(getKvRecords, { type, n, start }) } @@ -206,7 +254,10 @@ export class Client { * Takes in an array of ids and sends a request to remove them from the Lattice. * @category Lattice */ - public async removeKvRecords({ type = 0, ids = [] }: RemoveKvRecordsRequestParams): Promise { + public async removeKvRecords({ + type = 0, + ids = [], + }: RemoveKvRecordsRequestParams): Promise { return this.retryWrapper(removeKvRecords, { type, ids }) } @@ -216,15 +267,23 @@ export class Client { * data formatted according to the specified type. * @category Lattice */ - public async fetchEncryptedData(params: FetchEncDataRequest): Promise { + public async fetchEncryptedData( + params: FetchEncDataRequest, + ): Promise { return this.retryWrapper(fetchEncData, params) } /** Get the active wallet */ public getActiveWallet() { - if (this.activeWallets.external.uid && !EMPTY_WALLET_UID.equals(this.activeWallets.external.uid)) { + if ( + this.activeWallets.external.uid && + !EMPTY_WALLET_UID.equals(this.activeWallets.external.uid) + ) { return this.activeWallets.external - } else if (this.activeWallets.internal.uid && !EMPTY_WALLET_UID.equals(this.activeWallets.internal.uid)) { + } else if ( + this.activeWallets.internal.uid && + !EMPTY_WALLET_UID.equals(this.activeWallets.internal.uid) + ) { return this.activeWallets.internal } else { return undefined @@ -358,13 +417,17 @@ export class Client { // Attempt to parse the data const internalWallet = { uid: Buffer.from(unpacked.activeWallets.internal.uid, 'hex'), - name: unpacked.activeWallets.internal.name ? Buffer.from(unpacked.activeWallets.internal.name) : null, + name: unpacked.activeWallets.internal.name + ? Buffer.from(unpacked.activeWallets.internal.name) + : null, capabilities: unpacked.activeWallets.internal.capabilities, external: false, } const externalWallet = { uid: Buffer.from(unpacked.activeWallets.external.uid, 'hex'), - name: unpacked.activeWallets.external.name ? Buffer.from(unpacked.activeWallets.external.name) : null, + name: unpacked.activeWallets.external.name + ? Buffer.from(unpacked.activeWallets.external.name) + : null, capabilities: unpacked.activeWallets.external.capabilities, external: true, } diff --git a/packages/sdk/src/constants.ts b/packages/sdk/src/constants.ts index 214ad99c..cb681e51 100644 --- a/packages/sdk/src/constants.ts +++ b/packages/sdk/src/constants.ts @@ -1,5 +1,17 @@ -import { LatticeEncDataSchema, LatticeGetAddressesFlag, LatticeSignBlsDst, LatticeSignCurve, LatticeSignEncoding, LatticeSignHash } from './protocol/latticeConstants' -import type { ActiveWallets, FirmwareArr, FirmwareConstants, WalletPath } from './types/index.js' +import { + LatticeEncDataSchema, + LatticeGetAddressesFlag, + LatticeSignBlsDst, + LatticeSignCurve, + LatticeSignEncoding, + LatticeSignHash, +} from './protocol/latticeConstants' +import type { + ActiveWallets, + FirmwareArr, + FirmwareConstants, + WalletPath, +} from './types/index.js' /** * Externally exported constants used for building requests @@ -270,7 +282,12 @@ function getFwVersionConst(v: Buffer): FirmwareConstants { } function gte(v: Buffer, exp: FirmwareArr): boolean { // Note that `v` fields come in as [fix|minor|major] - return v[2] > exp[0] || (v[2] === exp[0] && v[1] > exp[1]) || (v[2] === exp[0] && v[1] === exp[1] && v[0] > exp[2]) || (v[2] === exp[0] && v[1] === exp[1] && v[0] === exp[2]) + return ( + v[2] > exp[0] || + (v[2] === exp[0] && v[1] > exp[1]) || + (v[2] === exp[0] && v[1] === exp[1] && v[0] > exp[2]) || + (v[2] === exp[0] && v[1] === exp[1] && v[0] === exp[2]) + ) } // Very old legacy versions do not give a version number const legacy = v.length === 0 @@ -395,7 +412,10 @@ function getFwVersionConst(v: Buffer): FirmwareConstants { SOLANA: EXTERNAL.SIGNING.ENCODINGS.SOLANA, } // Supported flags for `getAddresses` - c.getAddressFlags = [EXTERNAL.GET_ADDR_FLAGS.ED25519_PUB, EXTERNAL.GET_ADDR_FLAGS.SECP256K1_PUB] + c.getAddressFlags = [ + EXTERNAL.GET_ADDR_FLAGS.ED25519_PUB, + EXTERNAL.GET_ADDR_FLAGS.SECP256K1_PUB, + ] // We updated the max number of params in EIP712 types c.eip712MaxTypeParams = 36 } @@ -426,7 +446,8 @@ function getFwVersionConst(v: Buffer): FirmwareConstants { // V0.17.0 added support for BLS12-381-G1 pubkeys and G2 sigs if (!legacy && gte(v, [0, 17, 0])) { c.getAddressFlags.push(EXTERNAL.GET_ADDR_FLAGS.BLS12_381_G1_PUB) - c.genericSigning.encodingTypes.ETH_DEPOSIT = EXTERNAL.SIGNING.ENCODINGS.ETH_DEPOSIT + c.genericSigning.encodingTypes.ETH_DEPOSIT = + EXTERNAL.SIGNING.ENCODINGS.ETH_DEPOSIT } // --- V0.18.X --- @@ -448,7 +469,8 @@ function getFwVersionConst(v: Buffer): FirmwareConstants { const ASCII_REGEX = /^[\u0000-\u007F]+$/ /** @internal */ -const EXTERNAL_NETWORKS_BY_CHAIN_ID_URL = 'https://gridplus.github.io/chains/chains.json' +const EXTERNAL_NETWORKS_BY_CHAIN_ID_URL = + 'https://gridplus.github.io/chains/chains.json' /** @internal - Max number of addresses to fetch */ const MAX_ADDR = 10 @@ -502,25 +524,67 @@ export const DEFAULT_ACTIVE_WALLETS: ActiveWallets = { } /** @internal */ -export const DEFAULT_ETH_DERIVATION: WalletPath = [HARDENED_OFFSET + 44, HARDENED_OFFSET + 60, HARDENED_OFFSET, 0, 0] +export const DEFAULT_ETH_DERIVATION: WalletPath = [ + HARDENED_OFFSET + 44, + HARDENED_OFFSET + 60, + HARDENED_OFFSET, + 0, + 0, +] /** @internal */ -export const BTC_LEGACY_DERIVATION = [HARDENED_OFFSET + 44, HARDENED_OFFSET + 0, HARDENED_OFFSET, 0, 0] +export const BTC_LEGACY_DERIVATION = [ + HARDENED_OFFSET + 44, + HARDENED_OFFSET + 0, + HARDENED_OFFSET, + 0, + 0, +] /** @internal */ -export const BTC_LEGACY_CHANGE_DERIVATION = [HARDENED_OFFSET + 44, HARDENED_OFFSET + 0, HARDENED_OFFSET, 0, 0] +export const BTC_LEGACY_CHANGE_DERIVATION = [ + HARDENED_OFFSET + 44, + HARDENED_OFFSET + 0, + HARDENED_OFFSET, + 0, + 0, +] /** @internal */ -export const BTC_SEGWIT_DERIVATION = [HARDENED_OFFSET + 84, HARDENED_OFFSET, HARDENED_OFFSET, 0, 0] +export const BTC_SEGWIT_DERIVATION = [ + HARDENED_OFFSET + 84, + HARDENED_OFFSET, + HARDENED_OFFSET, + 0, + 0, +] /** @internal */ -export const BTC_SEGWIT_CHANGE_DERIVATION = [HARDENED_OFFSET + 84, HARDENED_OFFSET, HARDENED_OFFSET, 1, 0] +export const BTC_SEGWIT_CHANGE_DERIVATION = [ + HARDENED_OFFSET + 84, + HARDENED_OFFSET, + HARDENED_OFFSET, + 1, + 0, +] /** @internal */ -export const BTC_WRAPPED_SEGWIT_DERIVATION = [HARDENED_OFFSET + 49, HARDENED_OFFSET, HARDENED_OFFSET, 0, 0] +export const BTC_WRAPPED_SEGWIT_DERIVATION = [ + HARDENED_OFFSET + 49, + HARDENED_OFFSET, + HARDENED_OFFSET, + 0, + 0, +] /** @internal */ -export const BTC_WRAPPED_SEGWIT_CHANGE_DERIVATION = [HARDENED_OFFSET + 49, HARDENED_OFFSET, HARDENED_OFFSET, 0, 0] +export const BTC_WRAPPED_SEGWIT_CHANGE_DERIVATION = [ + HARDENED_OFFSET + 49, + HARDENED_OFFSET, + HARDENED_OFFSET, + 0, + 0, +] /** * Derivation path for Bitcoin legacy xpub (BIP44). @@ -553,13 +617,29 @@ export const BTC_WRAPPED_SEGWIT_YPUB_PATH = "49'/0'/0'" export const BTC_SEGWIT_ZPUB_PATH = "84'/0'/0'" /** @internal */ -export const SOLANA_DERIVATION = [HARDENED_OFFSET + 44, HARDENED_OFFSET + 501, HARDENED_OFFSET, HARDENED_OFFSET] +export const SOLANA_DERIVATION = [ + HARDENED_OFFSET + 44, + HARDENED_OFFSET + 501, + HARDENED_OFFSET, + HARDENED_OFFSET, +] /** @internal */ -export const LEDGER_LIVE_DERIVATION = [HARDENED_OFFSET + 49, HARDENED_OFFSET + 60, HARDENED_OFFSET, 0, 0] +export const LEDGER_LIVE_DERIVATION = [ + HARDENED_OFFSET + 49, + HARDENED_OFFSET + 60, + HARDENED_OFFSET, + 0, + 0, +] /** @internal */ -export const LEDGER_LEGACY_DERIVATION = [HARDENED_OFFSET + 49, HARDENED_OFFSET + 60, HARDENED_OFFSET, 0] +export const LEDGER_LEGACY_DERIVATION = [ + HARDENED_OFFSET + 49, + HARDENED_OFFSET + 60, + HARDENED_OFFSET, + 0, +] export { ASCII_REGEX, diff --git a/packages/sdk/src/ethereum.ts b/packages/sdk/src/ethereum.ts index 2443db08..41b224f0 100644 --- a/packages/sdk/src/ethereum.ts +++ b/packages/sdk/src/ethereum.ts @@ -7,21 +7,48 @@ import cbor from 'cbor' import bdec from 'cbor-bigdecimal' import { Hash } from 'ox' import secp256k1 from 'secp256k1' -import { type Hex, type TransactionSerializable, hexToNumber, serializeTransaction } from 'viem' -import { ASCII_REGEX, EXTERNAL, HANDLE_LARGER_CHAIN_ID, MAX_CHAIN_ID_BYTES, ethMsgProtocol } from './constants' +import { + type Hex, + type TransactionSerializable, + hexToNumber, + serializeTransaction, +} from 'viem' +import { + ASCII_REGEX, + EXTERNAL, + HANDLE_LARGER_CHAIN_ID, + MAX_CHAIN_ID_BYTES, + ethMsgProtocol, +} from './constants' import { buildGenericSigningMsgRequest } from './genericSigning' import { LatticeSignSchema } from './protocol' import { type FlexibleTransaction, TransactionSchema } from './schemas' -import { type FirmwareConstants, type SigningPath, TRANSACTION_TYPE, type TransactionRequest } from './types' -import { buildSignerPathBuf, convertRecoveryToV, ensureHexBuffer, fixLen, isAsciiStr, splitFrames } from './util' +import { + type FirmwareConstants, + type SigningPath, + TRANSACTION_TYPE, + type TransactionRequest, +} from './types' +import { + buildSignerPathBuf, + convertRecoveryToV, + ensureHexBuffer, + fixLen, + isAsciiStr, + splitFrames, +} from './util' const { ecdsaRecover } = secp256k1 bdec(cbor) const buildEthereumMsgRequest = (input) => { - if (!input.payload || !input.protocol || !input.signerPath) throw new Error('You must provide `payload`, `signerPath`, and `protocol` arguments in the messsage request') - if (input.signerPath.length > 5 || input.signerPath.length < 2) throw new Error('Please provide a signer path with 2-5 indices') + if (!input.payload || !input.protocol || !input.signerPath) + throw new Error( + 'You must provide `payload`, `signerPath`, and `protocol` arguments in the messsage request', + ) + if (input.signerPath.length > 5 || input.signerPath.length < 2) + throw new Error('Please provide a signer path with 2-5 indices') const req = { schema: LatticeSignSchema.ethereumMsg, payload: null, @@ -32,7 +59,10 @@ const buildEthereumMsgRequest = (input) => { case 'signPersonal': return buildPersonalSignRequest(req, input) case 'eip712': - if (!input.fwConstants.eip712Supported) throw new Error('EIP712 is not supported by your Lattice firmware version. Please upgrade.') + if (!input.fwConstants.eip712Supported) + throw new Error( + 'EIP712 is not supported by your Lattice firmware version. Please upgrade.', + ) return buildEIP712Request(req, input) default: throw new Error('Unsupported protocol') @@ -45,7 +75,13 @@ const validateEthereumMsgResponse = (res, req) => { if (input.protocol === 'signPersonal') { // NOTE: We are currently hardcoding networkID=1 and useEIP155=false but these // may be configurable in future versions - const hash = prehash ? prehash : Buffer.from(Hash.keccak256(Buffer.concat([get_personal_sign_prefix(msg.length), msg]))) + const hash = prehash + ? prehash + : Buffer.from( + Hash.keccak256( + Buffer.concat([get_personal_sign_prefix(msg.length), msg]), + ), + ) // Get recovery param with a `v` value of [27,28] by setting `useEIP155=false` return addRecoveryParam(hash, sig, signer, { chainId: 1, @@ -56,13 +92,21 @@ const validateEthereumMsgResponse = (res, req) => { // This payload has been parsed with forJSParser=true, converting all numbers // to the format that TypedDataUtils.eip712Hash expects const rawPayloadForHashing = req.validationPayload || req.input.payload - const payloadForHashing = req.validationPayload ? cloneTypedDataPayload(req.validationPayload) : normalizeTypedDataForHashing(rawPayloadForHashing) - const encoded = TypedDataUtils.eip712Hash(payloadForHashing, SignTypedDataVersion.V4) + const payloadForHashing = req.validationPayload + ? cloneTypedDataPayload(req.validationPayload) + : normalizeTypedDataForHashing(rawPayloadForHashing) + const encoded = TypedDataUtils.eip712Hash( + payloadForHashing, + SignTypedDataVersion.V4, + ) const digest = prehash ? prehash : encoded // Parse chainId - it could be a number, hex string, decimal string, or bigint - let chainId = input.payload.domain?.chainId || payloadForHashing.domain?.chainId + let chainId = + input.payload.domain?.chainId || payloadForHashing.domain?.chainId if (typeof chainId === 'string') { - chainId = chainId.startsWith('0x') ? Number.parseInt(chainId, 16) : Number.parseInt(chainId, 10) + chainId = chainId.startsWith('0x') + ? Number.parseInt(chainId, 16) + : Number.parseInt(chainId, 10) } else if (typeof chainId === 'bigint') { chainId = Number(chainId) } @@ -83,7 +127,10 @@ function normalizeTypedDataForHashing(value: any): any { if (/^0x[0-9a-fA-F]+$/.test(trimmed)) { try { const asBigInt = BigInt(trimmed) - if (asBigInt <= BigInt(Number.MAX_SAFE_INTEGER) && asBigInt >= BigInt(Number.MIN_SAFE_INTEGER)) { + if ( + asBigInt <= BigInt(Number.MAX_SAFE_INTEGER) && + asBigInt >= BigInt(Number.MIN_SAFE_INTEGER) + ) { return Number(asBigInt) } return asBigInt.toString(10) @@ -104,7 +151,14 @@ function normalizeTypedDataForHashing(value: any): any { return Number.isSafeInteger(asNumber) ? asNumber : value.toString(10) } - if (value && typeof value === 'object' && typeof value.toString === 'function' && value.constructor && value.constructor.name === 'BN' && typeof value.toArray === 'function') { + if ( + value && + typeof value === 'object' && + typeof value.toString === 'function' && + value.constructor && + value.constructor.name === 'BN' && + typeof value.toArray === 'function' + ) { const str = value.toString(10) const asNumber = Number(str) return Number.isSafeInteger(asNumber) ? asNumber : str @@ -153,7 +207,12 @@ function basicTypedDataClone(value: T): T { if (BN.isBigNumber(value)) { return new BN(value) as T } - if (value && typeof value === 'object' && (value as { constructor?: { name?: string } }).constructor?.name === 'BN' && typeof (value as { clone?: () => unknown }).clone === 'function') { + if ( + value && + typeof value === 'object' && + (value as { constructor?: { name?: string } }).constructor?.name === 'BN' && + typeof (value as { clone?: () => unknown }).clone === 'function' + ) { return (value as unknown as { clone: () => unknown }).clone() as T } if (value instanceof Date) { @@ -168,20 +227,32 @@ function basicTypedDataClone(value: T): T { type StructuredCloneFn = (value: T, transfer?: unknown) => T const structuredCloneFn: StructuredCloneFn | null = - typeof globalThis !== 'undefined' && typeof (globalThis as { structuredClone?: unknown }).structuredClone === 'function' ? (globalThis as { structuredClone: StructuredCloneFn }).structuredClone : null + typeof globalThis !== 'undefined' && + typeof (globalThis as { structuredClone?: unknown }).structuredClone === + 'function' + ? (globalThis as { structuredClone: StructuredCloneFn }).structuredClone + : null const buildEthereumTxRequest = (data) => { try { let { chainId = 1 } = data const { signerPath, eip155 = null, fwConstants, type = null } = data - const { contractDeployKey, extraDataFrameSz, extraDataMaxFrames, prehashAllowed } = fwConstants + const { + contractDeployKey, + extraDataFrameSz, + extraDataMaxFrames, + prehashAllowed, + } = fwConstants const EXTRA_DATA_ALLOWED = extraDataFrameSz > 0 && extraDataMaxFrames > 0 const MAX_BASE_DATA_SZ = fwConstants.ethMaxDataSz const VAR_PATH_SZ = fwConstants.varAddrPathSzAllowed // Sanity checks: // There are a handful of named chains we allow the user to reference (`chainIds`) // Custom chainIDs should be either numerical or hex strings - if (typeof chainId !== 'number' && isValidChainIdHexNumStr(chainId) === false) { + if ( + typeof chainId !== 'number' && + isValidChainIdHexNumStr(chainId) === false + ) { chainId = chainIds[chainId] } // If this was not a custom chainID and we cannot find the name of it, exit @@ -191,15 +262,22 @@ const buildEthereumTxRequest = (data) => { // Is this a contract deployment? if (data.to === null && !contractDeployKey) { - throw new Error('Contract deployment not supported. Please update your Lattice firmware.') + throw new Error( + 'Contract deployment not supported. Please update your Lattice firmware.', + ) } const isDeployment = data.to === null && contractDeployKey // We support eip1559 and eip2930 types (as well as legacy) - const eip1559IsAllowed = fwConstants.allowedEthTxTypes && fwConstants.allowedEthTxTypes.indexOf(2) > -1 - const eip2930IsAllowed = fwConstants.allowedEthTxTypes && fwConstants.allowedEthTxTypes.indexOf(1) > -1 + const eip1559IsAllowed = + fwConstants.allowedEthTxTypes && + fwConstants.allowedEthTxTypes.indexOf(2) > -1 + const eip2930IsAllowed = + fwConstants.allowedEthTxTypes && + fwConstants.allowedEthTxTypes.indexOf(1) > -1 const isEip1559 = eip1559IsAllowed && (type === 2 || type === 'eip1559') const isEip2930 = eip2930IsAllowed && (type === 1 || type === 'eip2930') - if (type !== null && !isEip1559 && !isEip2930) throw new Error('Unsupported Ethereum transaction type') + if (type !== null && !isEip1559 && !isEip2930) + throw new Error('Unsupported Ethereum transaction type') // Determine if we should use EIP155 given the chainID. // If we are explicitly told to use eip155, we will use it. Otherwise, // we will look up if the specified chainId is associated with a chain @@ -248,7 +326,10 @@ const buildEthereumTxRequest = (data) => { let maxPriorityFeePerGasBytes: Buffer let maxFeePerGasBytes: Buffer if (isEip1559) { - if (!data.maxPriorityFeePerGas) throw new Error('EIP1559 transactions must include `maxPriorityFeePerGas`') + if (!data.maxPriorityFeePerGas) + throw new Error( + 'EIP1559 transactions must include `maxPriorityFeePerGas`', + ) maxPriorityFeePerGasBytes = ensureHexBuffer(data.maxPriorityFeePerGas) rawTx.push(maxPriorityFeePerGasBytes) maxFeePerGasBytes = ensureHexBuffer(data.maxFeePerGas) @@ -305,7 +386,8 @@ const buildEthereumTxRequest = (data) => { if (useChainIdBuffer(chainId) === true) { chainIdBuf = getChainIdBuf(chainId) chainIdBufSz = chainIdBuf.length - if (chainIdBufSz > MAX_CHAIN_ID_BYTES) throw new Error('ChainID provided is too large.') + if (chainIdBufSz > MAX_CHAIN_ID_BYTES) + throw new Error('ChainID provided is too large.') // Signal to Lattice firmware that it needs to read the chainId from the tx.data buffer txReqPayload.writeUInt8(HANDLE_LARGER_CHAIN_ID, off) off++ @@ -353,8 +435,12 @@ const buildEthereumTxRequest = (data) => { if (isEip1559) { txReqPayload.writeUInt8(2, off) off += 1 // Eip1559 type enum value - if (maxPriorityFeePerGasBytes.length > 8) throw new Error('maxPriorityFeePerGasBytes too large') - maxPriorityFeePerGasBytes.copy(txReqPayload, off + (8 - maxPriorityFeePerGasBytes.length)) + if (maxPriorityFeePerGasBytes.length > 8) + throw new Error('maxPriorityFeePerGasBytes too large') + maxPriorityFeePerGasBytes.copy( + txReqPayload, + off + (8 - maxPriorityFeePerGasBytes.length), + ) off += 8 // Skip EIP1559 params } else if (isEip2930) { txReqPayload.writeUInt8(1, off) @@ -382,15 +468,27 @@ const buildEthereumTxRequest = (data) => { if (dataSz > MAX_BASE_DATA_SZ) { // Determine sizes and run through sanity checks const totalSz = dataSz + chainIdExtraSz - const maxSzAllowed = MAX_BASE_DATA_SZ + extraDataMaxFrames * extraDataFrameSz + const maxSzAllowed = + MAX_BASE_DATA_SZ + extraDataMaxFrames * extraDataFrameSz if (prehashAllowed && totalSz > maxSzAllowed) { // If this payload is too large to send, but the Lattice allows a prehashed message, do that - prehash = Buffer.from(Hash.keccak256(get_rlp_encoded_preimage(rawTx, type))) + prehash = Buffer.from( + Hash.keccak256(get_rlp_encoded_preimage(rawTx, type)), + ) } else { - if (!EXTRA_DATA_ALLOWED || (EXTRA_DATA_ALLOWED && totalSz > maxSzAllowed)) throw new Error(`Data field too large (got ${dataBytes.length}; must be <=${maxSzAllowed - chainIdExtraSz} bytes)`) + if ( + !EXTRA_DATA_ALLOWED || + (EXTRA_DATA_ALLOWED && totalSz > maxSzAllowed) + ) + throw new Error( + `Data field too large (got ${dataBytes.length}; must be <=${maxSzAllowed - chainIdExtraSz} bytes)`, + ) // Split overflow data into extraData frames - const frames = splitFrames(dataToCopy.slice(MAX_BASE_DATA_SZ), extraDataFrameSz) + const frames = splitFrames( + dataToCopy.slice(MAX_BASE_DATA_SZ), + extraDataFrameSz, + ) frames.forEach((frame) => { const szLE = Buffer.alloc(4) szLE.writeUInt32LE(frame.length, 0) @@ -400,7 +498,9 @@ const buildEthereumTxRequest = (data) => { } else if (PREHASH_UNSUPPORTED) { // If something is unsupported in firmware but we want to allow such transactions, // we prehash the message here. - prehash = Buffer.from(Hash.keccak256(get_rlp_encoded_preimage(rawTx, type))) + prehash = Buffer.from( + Hash.keccak256(get_rlp_encoded_preimage(rawTx, type)), + ) } // Write the data size (does *NOT* include the chainId buffer, if that exists) @@ -451,7 +551,9 @@ function stripZeros(a) { // and attah the full signature to the end of the transaction payload const buildEthRawTx = (tx, sig, address) => { // RLP-encode the data we sent to the lattice - const hash = Buffer.from(Hash.keccak256(get_rlp_encoded_preimage(tx.rawTx, tx.type))) + const hash = Buffer.from( + Hash.keccak256(get_rlp_encoded_preimage(tx.rawTx, tx.type)), + ) const newSig = addRecoveryParam(hash, sig, address, tx) // Use the signature to generate a new raw transaction payload // Strip the last 3 items and replace them with signature components @@ -462,9 +564,14 @@ const buildEthRawTx = (tx, sig, address) => { newRawTx.push(stripZeros(newSig.r)) newRawTx.push(stripZeros(newSig.s)) const rlpEncoded = Buffer.from(RLP.encode(newRawTx)) - const rlpEncodedWithSig = tx.type ? Buffer.concat([Buffer.from([tx.type]), rlpEncoded]) : rlpEncoded - - if (tx.type === TRANSACTION_TYPE.EIP7702_AUTH || tx.type === TRANSACTION_TYPE.EIP7702_AUTH_LIST) { + const rlpEncodedWithSig = tx.type + ? Buffer.concat([Buffer.from([tx.type]), rlpEncoded]) + : rlpEncoded + + if ( + tx.type === TRANSACTION_TYPE.EIP7702_AUTH || + tx.type === TRANSACTION_TYPE.EIP7702_AUTH_LIST + ) { // For EIP-7702 transactions, we return just the hex string return rlpEncodedWithSig.toString('hex') } @@ -477,8 +584,11 @@ export function addRecoveryParam(hashBuf, sig, address, txData = {}) { try { // Rebuild the keccak256 hash here so we can `ecrecover` const hash = new Uint8Array(hashBuf) - const expectedAddrBuf = Buffer.isBuffer(address) ? address : ensureHexBuffer(address, false) - if (expectedAddrBuf.length !== 20) throw new Error('Invalid signer address provided.') + const expectedAddrBuf = Buffer.isBuffer(address) + ? address + : ensureHexBuffer(address, false) + if (expectedAddrBuf.length !== 20) + throw new Error('Invalid signer address provided.') let v = 0 // Fix signature componenet lengths to 32 bytes each const r = fixLen(sig.r, 32) @@ -507,7 +617,9 @@ export function addRecoveryParam(hashBuf, sig, address, txData = {}) { return sig } else { // If neither is a match, we should return an error - throw new Error(`Invalid Ethereum signature returned. expected=${expectedAddrHex}, recovered=${recoveredAddrs.join(',')}`) + throw new Error( + `Invalid Ethereum signature returned. expected=${expectedAddrHex}, recovered=${recoveredAddrs.join(',')}`, + ) } } catch (err) { if (err instanceof Error) throw err @@ -519,7 +631,10 @@ export function addRecoveryParam(hashBuf, sig, address, txData = {}) { * Normalize Lattice signature components to viem format. * Handles Buffer v value conversion and yParity vs v for different transaction types. */ -export function normalizeLatticeSignature(latticeResult: any, originalTx: TransactionSerializable) { +export function normalizeLatticeSignature( + latticeResult: any, + originalTx: TransactionSerializable, +) { // Convert Buffer v value to number let vValue: number if (Buffer.isBuffer(latticeResult.sig.v)) { @@ -578,7 +693,8 @@ export function normalizeLatticeSignature(latticeResult: any, originalTx: Transa } // Convert an RLP-serialized transaction (plus signature) into a transaction hash -const hashTransaction = (serializedTx) => Hash.keccak256(Buffer.from(serializedTx, 'hex')) +const hashTransaction = (serializedTx) => + Hash.keccak256(Buffer.from(serializedTx, 'hex')) // Returns address string given public key buffer function pubToAddrStr(pub) { @@ -688,9 +804,14 @@ function buildPersonalSignRequest(req, input) { if (typeof input.payload === 'string') { if (input.payload.slice(0, 2) === '0x') { payload = ensureHexBuffer(input.payload) - displayHex = false === ASCII_REGEX.test(Buffer.from(input.payload.slice(2), 'hex').toString()) + displayHex = + false === + ASCII_REGEX.test(Buffer.from(input.payload.slice(2), 'hex').toString()) } else { - if (false === isAsciiStr(input.payload)) throw new Error('Currently, the Lattice can only display ASCII strings.') + if (false === isAsciiStr(input.payload)) + throw new Error( + 'Currently, the Lattice can only display ASCII strings.', + ) payload = Buffer.from(input.payload) } } else if (typeof input.displayHex === 'boolean') { @@ -706,7 +827,8 @@ function buildPersonalSignRequest(req, input) { displayHex = false === ASCII_REGEX.test(input.payload.toString()) } const fwConst = input.fwConstants - let maxSzAllowed = MAX_BASE_MSG_SZ + fwConst.extraDataMaxFrames * fwConst.extraDataFrameSz + let maxSzAllowed = + MAX_BASE_MSG_SZ + fwConst.extraDataMaxFrames * fwConst.extraDataFrameSz if (fwConst.personalSignHeaderSz) { // Account for the personal_sign header string maxSzAllowed -= fwConst.personalSignHeaderSz @@ -717,7 +839,11 @@ function buildPersonalSignRequest(req, input) { off += 1 req.payload.writeUInt16LE(payload.length, off) off += 2 - const prehash = Buffer.from(Hash.keccak256(Buffer.concat([get_personal_sign_prefix(payload.length), payload]))) + const prehash = Buffer.from( + Hash.keccak256( + Buffer.concat([get_personal_sign_prefix(payload.length), payload]), + ), + ) prehash.copy(req.payload, off) req.prehash = prehash } else { @@ -737,7 +863,8 @@ function buildPersonalSignRequest(req, input) { } function buildEIP712Request(req, input) { - const { ethMaxMsgSz, varAddrPathSzAllowed, eip712MaxTypeParams } = input.fwConstants + const { ethMaxMsgSz, varAddrPathSzAllowed, eip712MaxTypeParams } = + input.fwConstants const { TYPED_DATA } = ethMsgProtocol const L = 24 + ethMaxMsgSz + 4 let off = 0 @@ -745,14 +872,22 @@ function buildEIP712Request(req, input) { req.payload.writeUInt8(TYPED_DATA.enumIdx, 0) off += 1 // Write the signer path - const signerPathBuf = buildSignerPathBuf(input.signerPath, varAddrPathSzAllowed) + const signerPathBuf = buildSignerPathBuf( + input.signerPath, + varAddrPathSzAllowed, + ) signerPathBuf.copy(req.payload, off) off += signerPathBuf.length // Parse/clean the EIP712 payload, serialize with CBOR, and write to the payload const data = cloneTypedDataPayload(input.payload) - if (!data.primaryType || !data.types[data.primaryType]) throw new Error('primaryType must be specified and the type must be included.') - if (!data.message || !data.domain) throw new Error('message and domain must be specified.') - if (0 > Object.keys(data.types).indexOf('EIP712Domain')) throw new Error('EIP712Domain type must be defined.') + if (!data.primaryType || !data.types[data.primaryType]) + throw new Error( + 'primaryType must be specified and the type must be included.', + ) + if (!data.message || !data.domain) + throw new Error('message and domain must be specified.') + if (0 > Object.keys(data.types).indexOf('EIP712Domain')) + throw new Error('EIP712Domain type must be defined.') // Parse the payload to ensure we have valid EIP712 data types and that // they are encoded such that Lattice firmware can parse them. // We need two different encodings: one to send to the Lattice in a format that plays @@ -761,17 +896,33 @@ function buildEIP712Request(req, input) { // IMPORTANT: Create a new object for the validation payload instead of modifying input.payload // in place, so that validation uses the correctly formatted data const validationPayload = cloneTypedDataPayload(data) - validationPayload.message = parseEIP712Msg(cloneTypedDataPayload(data.message), cloneTypedDataPayload(data.primaryType), cloneTypedDataPayload(data.types), true) - validationPayload.domain = parseEIP712Msg(cloneTypedDataPayload(data.domain), 'EIP712Domain', cloneTypedDataPayload(data.types), true) + validationPayload.message = parseEIP712Msg( + cloneTypedDataPayload(data.message), + cloneTypedDataPayload(data.primaryType), + cloneTypedDataPayload(data.types), + true, + ) + validationPayload.domain = parseEIP712Msg( + cloneTypedDataPayload(data.domain), + 'EIP712Domain', + cloneTypedDataPayload(data.types), + true, + ) // Store the validation payload separately without modifying input.payload req.validationPayload = validationPayload data.domain = parseEIP712Msg(data.domain, 'EIP712Domain', data.types, false) - data.message = parseEIP712Msg(data.message, data.primaryType, data.types, false) + data.message = parseEIP712Msg( + data.message, + data.primaryType, + data.types, + false, + ) // Now build the message to be sent to the Lattice const payload = Buffer.from(cbor.encode(data)) const fwConst = input.fwConstants - const maxSzAllowed = ethMaxMsgSz + fwConst.extraDataMaxFrames * fwConst.extraDataFrameSz + const maxSzAllowed = + ethMaxMsgSz + fwConst.extraDataMaxFrames * fwConst.extraDataFrameSz // Determine if we need to prehash let shouldPrehash = payload.length > maxSzAllowed Object.keys(data.types).forEach((k) => { @@ -783,7 +934,10 @@ function buildEIP712Request(req, input) { // If this payload is too large to send, but the Lattice allows a prehashed message, do that req.payload.writeUInt16LE(payload.length, off) off += 2 - const prehash = TypedDataUtils.eip712Hash(req.validationPayload, SignTypedDataVersion.V4) + const prehash = TypedDataUtils.eip712Hash( + req.validationPayload, + SignTypedDataVersion.V4, + ) const prehashBuf = Buffer.from(prehash) prehashBuf.copy(req.payload, off) req.prehash = prehash @@ -801,15 +955,22 @@ function buildEIP712Request(req, input) { } function getExtraData(payload, input) { - const { ethMaxMsgSz, extraDataFrameSz, extraDataMaxFrames } = input.fwConstants + const { ethMaxMsgSz, extraDataFrameSz, extraDataMaxFrames } = + input.fwConstants const MAX_BASE_MSG_SZ = ethMaxMsgSz const EXTRA_DATA_ALLOWED = extraDataFrameSz > 0 && extraDataMaxFrames > 0 const extraDataPayloads = [] if (payload.length > MAX_BASE_MSG_SZ) { // Determine sizes and run through sanity checks const maxSzAllowed = MAX_BASE_MSG_SZ + extraDataMaxFrames * extraDataFrameSz - if (!EXTRA_DATA_ALLOWED) throw new Error(`Your message is ${payload.length} bytes, but can only be a maximum of ${MAX_BASE_MSG_SZ}`) - else if (EXTRA_DATA_ALLOWED && payload.length > maxSzAllowed) throw new Error(`Your message is ${payload.length} bytes, but can only be a maximum of ${maxSzAllowed}`) + if (!EXTRA_DATA_ALLOWED) + throw new Error( + `Your message is ${payload.length} bytes, but can only be a maximum of ${MAX_BASE_MSG_SZ}`, + ) + else if (EXTRA_DATA_ALLOWED && payload.length > maxSzAllowed) + throw new Error( + `Your message is ${payload.length} bytes, but can only be a maximum of ${maxSzAllowed}`, + ) // Split overflow data into extraData frames const frames = splitFrames(payload.slice(MAX_BASE_MSG_SZ), extraDataFrameSz) frames.forEach((frame) => { @@ -825,7 +986,9 @@ function parseEIP712Msg(msg, typeName, types, forJSParser = false) { const type = types[typeName] type.forEach((item) => { const isArrayType = item.type.indexOf('[') > -1 - const singularType = isArrayType ? item.type.slice(0, item.type.indexOf('[')) : item.type + const singularType = isArrayType + ? item.type.slice(0, item.type.indexOf('[')) + : item.type const isCustomType = Object.keys(types).indexOf(singularType) > -1 if (isCustomType && Array.isArray(msg)) { // For custom types we need to jump into the `msg` using the key (name of type) and @@ -834,11 +997,21 @@ function parseEIP712Msg(msg, typeName, types, forJSParser = false) { // elementary (i.e. non-custom) type. // For arrays, we need to loop through each message item. for (let i = 0; i < msg.length; i++) { - msg[i][item.name] = parseEIP712Msg(msg[i][item.name], singularType, types, forJSParser) + msg[i][item.name] = parseEIP712Msg( + msg[i][item.name], + singularType, + types, + forJSParser, + ) } } else if (isCustomType) { // Not an array means we can jump directly into the sub-struct to convert - msg[item.name] = parseEIP712Msg(msg[item.name], singularType, types, forJSParser) + msg[item.name] = parseEIP712Msg( + msg[item.name], + singularType, + types, + forJSParser, + ) } else if (Array.isArray(msg)) { // If we have an array for this particular type and the type we are parsing // is *not* a custom type, loop through the array elements and convert the types. @@ -848,22 +1021,38 @@ function parseEIP712Msg(msg, typeName, types, forJSParser = false) { // This code is not reachable for custom types so we assume these are arrays of // elementary types. for (let j = 0; j < msg[i][item.name].length; j++) { - msg[i][item.name][j] = parseEIP712Item(msg[i][item.name][j], singularType, forJSParser) + msg[i][item.name][j] = parseEIP712Item( + msg[i][item.name][j], + singularType, + forJSParser, + ) } } else { // Non-arrays parse + replace one value for the elementary type - msg[i][item.name] = parseEIP712Item(msg[i][item.name], singularType, forJSParser) + msg[i][item.name] = parseEIP712Item( + msg[i][item.name], + singularType, + forJSParser, + ) } } } else if (isArrayType) { // If we have an elementary array type and a non-array message level, //loop through the array and parse + replace each item individually. for (let i = 0; i < msg[item.name].length; i++) { - msg[item.name][i] = parseEIP712Item(msg[item.name][i], singularType, forJSParser) + msg[item.name][i] = parseEIP712Item( + msg[item.name][i], + singularType, + forJSParser, + ) } } else { // If this is a singular elementary type, simply parse + replace. - msg[item.name] = parseEIP712Item(msg[item.name], singularType, forJSParser) + msg[item.name] = parseEIP712Item( + msg[item.name], + singularType, + forJSParser, + ) } }) @@ -886,7 +1075,8 @@ function parseEIP712Item(data, type, forJSParser = false) { if (data.length === 0) { data = Buffer.alloc(nBytes) } - if (data.length !== nBytes) throw new Error(`Expected ${type} type, but got ${data.length} bytes`) + if (data.length !== nBytes) + throw new Error(`Expected ${type} type, but got ${data.length} bytes`) if (forJSParser) { // For EIP712 encoding module it's easier to encode hex strings data = `0x${data.toString('hex')}` @@ -898,12 +1088,19 @@ function parseEIP712Item(data, type, forJSParser = false) { if (data.length === 0) { data = Buffer.alloc(20) } - if (data.length !== 20) throw new Error(`Address type must be 20 bytes, but got ${data.length} bytes`) + if (data.length !== 20) + throw new Error( + `Address type must be 20 bytes, but got ${data.length} bytes`, + ) // For EIP712 encoding module it's easier to encode hex strings if (forJSParser) { data = `0x${data.toString('hex')}` } - } else if (ethMsgProtocol.TYPED_DATA.typeCodes[type] && type.indexOf('uint') === -1 && type.indexOf('int') > -1) { + } else if ( + ethMsgProtocol.TYPED_DATA.typeCodes[type] && + type.indexOf('uint') === -1 && + type.indexOf('int') > -1 + ) { // Handle signed integers using bignumber.js directly if (forJSParser) { // For EIP712 encoding in this module we need hex strings for signed ints too @@ -921,7 +1118,10 @@ function parseEIP712Item(data, type, forJSParser = false) { // worked (borc is a supposedly "browser compatible" version of cbor) data = new BN(data) } - } else if (ethMsgProtocol.TYPED_DATA.typeCodes[type] && (type.indexOf('uint') > -1 || type.indexOf('int') > -1)) { + } else if ( + ethMsgProtocol.TYPED_DATA.typeCodes[type] && + (type.indexOf('uint') > -1 || type.indexOf('int') > -1) + ) { // For uints, convert to a buffer and do some sanity checking. // Note that we could probably just use bignumber.js directly as we do with // signed ints, but this code is battle tested and we don't want to change it. @@ -952,7 +1152,10 @@ function get_personal_sign_prefix(L) { function get_rlp_encoded_preimage(rawTx, txType) { if (txType) { - return Buffer.concat([Buffer.from([txType]), Buffer.from(RLP.encode(rawTx))]) + return Buffer.concat([ + Buffer.from([txType]), + Buffer.from(RLP.encode(rawTx)), + ]) } else { return Buffer.from(RLP.encode(rawTx)) } @@ -970,7 +1173,9 @@ function get_rlp_encoded_preimage(rawTx, txType) { * hex strings, numbers, bigints). * @returns A `viem`-compatible `TransactionSerializable` object. */ -export const normalizeToViemTransaction = (tx: unknown): TransactionSerializable => { +export const normalizeToViemTransaction = ( + tx: unknown, +): TransactionSerializable => { const parsed = TransactionSchema.parse(tx) return { @@ -983,9 +1188,13 @@ export const normalizeToViemTransaction = (tx: unknown): TransactionSerializable chainId: parsed.chainId, gasPrice: 'gasPrice' in parsed ? parsed.gasPrice : undefined, maxFeePerGas: 'maxFeePerGas' in parsed ? parsed.maxFeePerGas : undefined, - maxPriorityFeePerGas: 'maxPriorityFeePerGas' in parsed ? parsed.maxPriorityFeePerGas : undefined, + maxPriorityFeePerGas: + 'maxPriorityFeePerGas' in parsed + ? parsed.maxPriorityFeePerGas + : undefined, accessList: 'accessList' in parsed ? parsed.accessList : undefined, - authorizationList: 'authorizationList' in parsed ? parsed.authorizationList : undefined, + authorizationList: + 'authorizationList' in parsed ? parsed.authorizationList : undefined, } } @@ -993,7 +1202,9 @@ export const normalizeToViemTransaction = (tx: unknown): TransactionSerializable * Convert Ethereum transaction to serialized bytes for generic signing. * Bridge function for firmware v0.15.0+ which removed legacy ETH signing paths. */ -const convertEthereumTransactionToGenericRequest = (req: FlexibleTransaction) => { +const convertEthereumTransactionToGenericRequest = ( + req: FlexibleTransaction, +) => { // Use the unified normalization and serialization pipeline. // 1. Normalize the potentially varied input to a standard viem format. const viemTx = normalizeToViemTransaction(req) @@ -1012,7 +1223,9 @@ type EthereumGenericSigningRequestParams = FlexibleTransaction & { * Build complete generic signing request for Ethereum transactions. * One-step function combining transaction conversion and generic signing setup. */ -export const buildEthereumGenericSigningRequest = (req: EthereumGenericSigningRequestParams) => { +export const buildEthereumGenericSigningRequest = ( + req: EthereumGenericSigningRequestParams, +) => { const { fwConstants, signerPath, ...txData } = req const payload = convertEthereumTransactionToGenericRequest(txData) @@ -1034,8 +1247,13 @@ export const buildEthereumGenericSigningRequest = (req: EthereumGenericSigningRe * @returns The serialized transaction as a hex string */ export function serializeEIP7702Transaction(tx: TransactionRequest): Hex { - if (tx.type !== TRANSACTION_TYPE.EIP7702_AUTH_LIST && tx.type !== TRANSACTION_TYPE.EIP7702_AUTH) { - throw new Error(`Only EIP-7702 auth transactions (type ${TRANSACTION_TYPE.EIP7702_AUTH}) and auth-list transactions (type ${TRANSACTION_TYPE.EIP7702_AUTH_LIST}) are supported`) + if ( + tx.type !== TRANSACTION_TYPE.EIP7702_AUTH_LIST && + tx.type !== TRANSACTION_TYPE.EIP7702_AUTH + ) { + throw new Error( + `Only EIP-7702 auth transactions (type ${TRANSACTION_TYPE.EIP7702_AUTH}) and auth-list transactions (type ${TRANSACTION_TYPE.EIP7702_AUTH_LIST}) are supported`, + ) } // Type guard to ensure we have an EIP7702 transaction with appropriate authorization data @@ -1043,14 +1261,18 @@ export function serializeEIP7702Transaction(tx: TransactionRequest): Hex { const hasSingleAuth = 'authorization' in tx if (!hasAuthList && !hasSingleAuth) { - throw new Error('Transaction does not have authorization or authorizationList property') + throw new Error( + 'Transaction does not have authorization or authorizationList property', + ) } // For type 4 transactions, convert single authorization to array format let authorizationList: any[] if (tx.type === TRANSACTION_TYPE.EIP7702_AUTH) { if (!hasSingleAuth) { - throw new Error('EIP-7702 auth transaction (type 4) must contain authorization property') + throw new Error( + 'EIP-7702 auth transaction (type 4) must contain authorization property', + ) } authorizationList = [(tx as any).authorization] } else { @@ -1058,19 +1280,29 @@ export function serializeEIP7702Transaction(tx: TransactionRequest): Hex { if (hasAuthList) { authorizationList = (tx as any).authorizationList } else { - throw new Error('EIP-7702 auth list transaction (type 5) must contain authorizationList property') + throw new Error( + 'EIP-7702 auth list transaction (type 5) must contain authorizationList property', + ) } } // Validate that all required fields exist - if (!authorizationList || !Array.isArray(authorizationList) || authorizationList.length === 0) { - throw new Error('EIP-7702 transaction must contain at least one authorization') + if ( + !authorizationList || + !Array.isArray(authorizationList) || + authorizationList.length === 0 + ) { + throw new Error( + 'EIP-7702 transaction must contain at least one authorization', + ) } // Validate each authorization authorizationList.forEach((auth, index) => { if (!auth.address) { - throw new Error(`Authorization at index ${index} is missing a contract address`) + throw new Error( + `Authorization at index ${index} is missing a contract address`, + ) } }) @@ -1084,9 +1316,21 @@ export function serializeEIP7702Transaction(tx: TransactionRequest): Hex { type: 'eip7702' as const, chainId: tx.chainId, nonce: tx.nonce, - maxPriorityFeePerGas: typeof tx.maxPriorityFeePerGas === 'string' ? BigInt(tx.maxPriorityFeePerGas) : tx.maxPriorityFeePerGas, - maxFeePerGas: typeof tx.maxFeePerGas === 'string' ? BigInt(tx.maxFeePerGas) : tx.maxFeePerGas, - gas: typeof (tx as any).gas === 'string' ? BigInt((tx as any).gas) : (tx as any).gas || (typeof (tx as any).gasLimit === 'string' ? BigInt((tx as any).gasLimit) : (tx as any).gasLimit), + maxPriorityFeePerGas: + typeof tx.maxPriorityFeePerGas === 'string' + ? BigInt(tx.maxPriorityFeePerGas) + : tx.maxPriorityFeePerGas, + maxFeePerGas: + typeof tx.maxFeePerGas === 'string' + ? BigInt(tx.maxFeePerGas) + : tx.maxFeePerGas, + gas: + typeof (tx as any).gas === 'string' + ? BigInt((tx as any).gas) + : (tx as any).gas || + (typeof (tx as any).gasLimit === 'string' + ? BigInt((tx as any).gasLimit) + : (tx as any).gasLimit), to: tx.to as `0x${string}`, value: typeof tx.value === 'string' ? BigInt(tx.value) : tx.value, data: tx.data || '0x', @@ -1094,10 +1338,17 @@ export function serializeEIP7702Transaction(tx: TransactionRequest): Hex { // Create the Viem-formatted authorization // Ensure proper address handling with 0x prefix const address = auth.address || '' - const addressStr = typeof address === 'string' ? (address.startsWith('0x') ? address : `0x${address}`) : '0x' + const addressStr = + typeof address === 'string' + ? address.startsWith('0x') + ? address + : `0x${address}` + : '0x' if (!addressStr || addressStr === '0x') { - throw new Error(`Authorization at index ${idx} is missing a valid address`) + throw new Error( + `Authorization at index ${idx} is missing a valid address`, + ) } // Handle viem's SignedAuthorization format @@ -1116,7 +1367,16 @@ export function serializeEIP7702Transaction(tx: TransactionRequest): Hex { address: addressStr as `0x${string}`, nonce: BigInt(auth.nonce || 0), signature: { - yParity: typeof auth.yParity === 'number' ? auth.yParity : typeof auth.yParity === 'string' ? (auth.yParity === '0x01' || auth.yParity === '0x1' || auth.yParity === '1' ? 1 : 0) : 0, + yParity: + typeof auth.yParity === 'number' + ? auth.yParity + : typeof auth.yParity === 'string' + ? auth.yParity === '0x01' || + auth.yParity === '0x1' || + auth.yParity === '1' + ? 1 + : 0 + : 0, r: auth.r || '0x0', s: auth.s || '0x0', }, @@ -1129,7 +1389,12 @@ export function serializeEIP7702Transaction(tx: TransactionRequest): Hex { } export const isEip7702Transaction = (tx: TransactionRequest): boolean => { - return typeof tx === 'object' && 'type' in tx && (tx.type === TRANSACTION_TYPE.EIP7702_AUTH_LIST || tx.type === TRANSACTION_TYPE.EIP7702_AUTH) + return ( + typeof tx === 'object' && + 'type' in tx && + (tx.type === TRANSACTION_TYPE.EIP7702_AUTH_LIST || + tx.type === TRANSACTION_TYPE.EIP7702_AUTH) + ) } export default { diff --git a/packages/sdk/src/functions/addKvRecords.ts b/packages/sdk/src/functions/addKvRecords.ts index 045d241a..bc13c511 100644 --- a/packages/sdk/src/functions/addKvRecords.ts +++ b/packages/sdk/src/functions/addKvRecords.ts @@ -1,6 +1,17 @@ -import { LatticeSecureEncryptedRequestType, encryptedSecureRequest } from '../protocol' -import { validateConnectedClient, validateKvRecord, validateKvRecords } from '../shared/validators' -import type { AddKvRecordsRequestFunctionParams, FirmwareConstants, KVRecords } from '../types' +import { + LatticeSecureEncryptedRequestType, + encryptedSecureRequest, +} from '../protocol' +import { + validateConnectedClient, + validateKvRecord, + validateKvRecords, +} from '../shared/validators' +import type { + AddKvRecordsRequestFunctionParams, + FirmwareConstants, + KVRecords, +} from '../types' /** * `addKvRecords` takes in a set of key-value records and sends a request to add them to the @@ -8,8 +19,14 @@ import type { AddKvRecordsRequestFunctionParams, FirmwareConstants, KVRecords } * @category Lattice * @returns A callback with an error or null. */ -export async function addKvRecords({ client, records, type, caseSensitive }: AddKvRecordsRequestFunctionParams): Promise { - const { url, sharedSecret, ephemeralPub, fwConstants } = validateConnectedClient(client) +export async function addKvRecords({ + client, + records, + type, + caseSensitive, +}: AddKvRecordsRequestFunctionParams): Promise { + const { url, sharedSecret, ephemeralPub, fwConstants } = + validateConnectedClient(client) validateAddKvRequest({ records, fwConstants }) // Build the data for this request diff --git a/packages/sdk/src/functions/connect.ts b/packages/sdk/src/functions/connect.ts index 5449150f..f4b1d2fc 100644 --- a/packages/sdk/src/functions/connect.ts +++ b/packages/sdk/src/functions/connect.ts @@ -1,11 +1,22 @@ import { ProtocolConstants, connectSecureRequest } from '../protocol' import { doesFetchWalletsOnLoad } from '../shared/predicates' import { getSharedSecret, parseWallets } from '../shared/utilities' -import { validateBaseUrl, validateDeviceId, validateKey } from '../shared/validators' -import type { ActiveWallets, ConnectRequestFunctionParams, KeyPair } from '../types' +import { + validateBaseUrl, + validateDeviceId, + validateKey, +} from '../shared/validators' +import type { + ActiveWallets, + ConnectRequestFunctionParams, + KeyPair, +} from '../types' import { aes256_decrypt, getP256KeyPairFromPub } from '../util' -export async function connect({ client, id }: ConnectRequestFunctionParams): Promise { +export async function connect({ + client, + id, +}: ConnectRequestFunctionParams): Promise { const { deviceId, key, baseUrl } = validateConnectRequest({ deviceId: id, // @ts-expect-error - private access @@ -22,7 +33,8 @@ export async function connect({ client, id }: ConnectRequestFunctionParams): Pro // Decode response data params. // Response payload data is *not* encrypted. - const { isPaired, fwVersion, activeWallets, ephemeralPub } = await decodeConnectResponse(respPayloadData, key) + const { isPaired, fwVersion, activeWallets, ephemeralPub } = + await decodeConnectResponse(respPayloadData, key) // Update client state with response data @@ -92,7 +104,8 @@ export const decodeConnectResponse = ( ephemeralPub: KeyPair } => { let off = 0 - const isPaired = response.readUInt8(off) === ProtocolConstants.pairingStatus.paired + const isPaired = + response.readUInt8(off) === ProtocolConstants.pairingStatus.paired off++ // If we are already paired, we get the next ephemeral key const pub = response.slice(off, off + 65).toString('hex') @@ -114,7 +127,10 @@ export const decodeConnectResponse = ( const decWalletData = aes256_decrypt(encWalletData, sharedSecret) // Sanity check to make sure the last part of the decrypted data is empty. The last 2 bytes // are AES padding - if (decWalletData[decWalletData.length - 2] !== 0 || decWalletData[decWalletData.length - 1] !== 0) { + if ( + decWalletData[decWalletData.length - 2] !== 0 || + decWalletData[decWalletData.length - 1] !== 0 + ) { throw new Error('Failed to connect to Lattice.') } const activeWallets = parseWallets(decWalletData) diff --git a/packages/sdk/src/functions/fetchActiveWallet.ts b/packages/sdk/src/functions/fetchActiveWallet.ts index 17c0b6df..582118cf 100644 --- a/packages/sdk/src/functions/fetchActiveWallet.ts +++ b/packages/sdk/src/functions/fetchActiveWallet.ts @@ -1,7 +1,16 @@ import { EMPTY_WALLET_UID } from '../constants' -import { LatticeSecureEncryptedRequestType, encryptedSecureRequest } from '../protocol' -import { validateActiveWallets, validateConnectedClient } from '../shared/validators' -import type { ActiveWallets, FetchActiveWalletRequestFunctionParams } from '../types' +import { + LatticeSecureEncryptedRequestType, + encryptedSecureRequest, +} from '../protocol' +import { + validateActiveWallets, + validateConnectedClient, +} from '../shared/validators' +import type { + ActiveWallets, + FetchActiveWalletRequestFunctionParams, +} from '../types' /** * Fetch the active wallet in the device. @@ -10,7 +19,9 @@ import type { ActiveWallets, FetchActiveWalletRequestFunctionParams } from '../t * unlocked, the external interface is considered "active" and this will return its {@link Wallet} * data. Otherwise it will return the info for the internal Lattice wallet. */ -export async function fetchActiveWallet({ client }: FetchActiveWalletRequestFunctionParams): Promise { +export async function fetchActiveWallet({ + client, +}: FetchActiveWalletRequestFunctionParams): Promise { const { url, sharedSecret, ephemeralPub } = validateConnectedClient(client) const { decryptedData, newEphemeralPub } = await encryptedSecureRequest({ diff --git a/packages/sdk/src/functions/fetchDecoder.ts b/packages/sdk/src/functions/fetchDecoder.ts index a5005338..c9e7a65f 100644 --- a/packages/sdk/src/functions/fetchDecoder.ts +++ b/packages/sdk/src/functions/fetchDecoder.ts @@ -9,15 +9,25 @@ import { fetchCalldataDecoder } from '../util' * @category Lattice * @returns An object containing the ABI and encoded definition of the contract. */ -export async function fetchDecoder({ data, to, chainId }: TransactionRequest): Promise { +export async function fetchDecoder({ + data, + to, + chainId, +}: TransactionRequest): Promise { try { const client = await getClient() validateConnectedClient(client) const fwVersion = client.getFwVersion() - const supportsDecoderRecursion = fwVersion.major > 0 || fwVersion.minor >= 16 + const supportsDecoderRecursion = + fwVersion.major > 0 || fwVersion.minor >= 16 - const { def } = await fetchCalldataDecoder(data, to, chainId, supportsDecoderRecursion) + const { def } = await fetchCalldataDecoder( + data, + to, + chainId, + supportsDecoderRecursion, + ) return def } catch (error) { diff --git a/packages/sdk/src/functions/fetchEncData.ts b/packages/sdk/src/functions/fetchEncData.ts index 87bc8b02..e5675b45 100644 --- a/packages/sdk/src/functions/fetchEncData.ts +++ b/packages/sdk/src/functions/fetchEncData.ts @@ -4,13 +4,27 @@ */ import { v4 as uuidV4 } from 'uuid' import { EXTERNAL } from '../constants' -import { LatticeSecureEncryptedRequestType, encryptedSecureRequest } from '../protocol' +import { + LatticeSecureEncryptedRequestType, + encryptedSecureRequest, +} from '../protocol' import { getPathStr } from '../shared/utilities' -import { validateConnectedClient, validateStartPath, validateWallet } from '../shared/validators' -import type { EIP2335KeyExportData, EIP2335KeyExportReq, FetchEncDataRequestFunctionParams, FirmwareVersion, Wallet } from '../types' +import { + validateConnectedClient, + validateStartPath, + validateWallet, +} from '../shared/validators' +import type { + EIP2335KeyExportData, + EIP2335KeyExportReq, + FetchEncDataRequestFunctionParams, + FirmwareVersion, + Wallet, +} from '../types' const { ENC_DATA } = EXTERNAL -const ENC_DATA_ERR_STR = 'Unknown encrypted data export type requested. Exiting.' +const ENC_DATA_ERR_STR = + 'Unknown encrypted data export type requested. Exiting.' const ENC_DATA_REQ_DATA_SZ = 1025 const ENC_DATA_RESP_SZ = { EIP2335: { @@ -22,8 +36,13 @@ const ENC_DATA_RESP_SZ = { }, } as const -export async function fetchEncData({ client, schema, params }: FetchEncDataRequestFunctionParams): Promise { - const { url, sharedSecret, ephemeralPub, fwVersion } = validateConnectedClient(client) +export async function fetchEncData({ + client, + schema, + params, +}: FetchEncDataRequestFunctionParams): Promise { + const { url, sharedSecret, ephemeralPub, fwVersion } = + validateConnectedClient(client) const activeWallet = validateWallet(client.getActiveWallet()) validateFetchEncDataRequest({ params }) @@ -71,7 +90,9 @@ export const encodeFetchEncDataRequest = ({ }) => { // Check firmware version if (fwVersion.major < 1 && fwVersion.minor < 17) { - throw new Error('Firmware version >=v0.17.0 is required for encrypted data export.') + throw new Error( + 'Firmware version >=v0.17.0 is required for encrypted data export.', + ) } // Update params depending on what type of data is being exported if (schema === ENC_DATA.SCHEMAS.BLS_KEYSTORE_EIP2335_PBKDF_V4) { @@ -129,7 +150,9 @@ export const decodeFetchEncData = ({ const dataSz = data.readUInt32LE(off) off += 4 if (dataSz !== expectedSz) { - throw new Error('Invalid data returned from Lattice. Expected EIP2335 data.') + throw new Error( + 'Invalid data returned from Lattice. Expected EIP2335 data.', + ) } respData.iterations = data.readUInt32LE(off) off += 4 @@ -149,7 +172,10 @@ export const decodeFetchEncData = ({ } } -const formatEIP2335ExportData = (resp: EIP2335KeyExportData, path: number[]): Buffer => { +const formatEIP2335ExportData = ( + resp: EIP2335KeyExportData, + path: number[], +): Buffer => { try { const { iterations, salt, checksum, iv, cipherText, pubkey } = resp return Buffer.from( diff --git a/packages/sdk/src/functions/getAddresses.ts b/packages/sdk/src/functions/getAddresses.ts index f081e7b7..08d244e2 100644 --- a/packages/sdk/src/functions/getAddresses.ts +++ b/packages/sdk/src/functions/getAddresses.ts @@ -1,6 +1,21 @@ -import { LatticeGetAddressesFlag, LatticeSecureEncryptedRequestType, ProtocolConstants, encryptedSecureRequest } from '../protocol' -import { validateConnectedClient, validateIsUInt4, validateNAddresses, validateStartPath, validateWallet } from '../shared/validators' -import type { FirmwareConstants, GetAddressesRequestFunctionParams, Wallet } from '../types' +import { + LatticeGetAddressesFlag, + LatticeSecureEncryptedRequestType, + ProtocolConstants, + encryptedSecureRequest, +} from '../protocol' +import { + validateConnectedClient, + validateIsUInt4, + validateNAddresses, + validateStartPath, + validateWallet, +} from '../shared/validators' +import type { + FirmwareConstants, + GetAddressesRequestFunctionParams, + Wallet, +} from '../types' import { isValidAssetPath } from '../util' /** @@ -9,8 +24,15 @@ import { isValidAssetPath } from '../util' * @category Lattice * @returns An array of addresses or public keys. */ -export async function getAddresses({ client, startPath: _startPath, n: _n, flag: _flag, iterIdx }: GetAddressesRequestFunctionParams): Promise { - const { url, sharedSecret, ephemeralPub, fwConstants } = validateConnectedClient(client) +export async function getAddresses({ + client, + startPath: _startPath, + n: _n, + flag: _flag, + iterIdx, +}: GetAddressesRequestFunctionParams): Promise { + const { url, sharedSecret, ephemeralPub, fwConstants } = + validateConnectedClient(client) const activeWallet = validateWallet(client.getActiveWallet()) const { startPath, n, flag } = validateGetAddressesRequest({ @@ -75,10 +97,16 @@ export const encodeGetAddressesRequest = ({ iterIdx?: number }) => { const flags = fwConstants.getAddressFlags || ([] as any[]) - const isPubkeyOnly = flags.indexOf(flag) > -1 && (flag === LatticeGetAddressesFlag.ed25519Pubkey || flag === LatticeGetAddressesFlag.secp256k1Pubkey || flag === LatticeGetAddressesFlag.bls12_381Pubkey) + const isPubkeyOnly = + flags.indexOf(flag) > -1 && + (flag === LatticeGetAddressesFlag.ed25519Pubkey || + flag === LatticeGetAddressesFlag.secp256k1Pubkey || + flag === LatticeGetAddressesFlag.bls12_381Pubkey) const isXpub = flag === LatticeGetAddressesFlag.secp256k1Xpub if (!isPubkeyOnly && !isXpub && !isValidAssetPath(startPath, fwConstants)) { - throw new Error('Derivation path or flag is not supported. Try updating Lattice firmware.') + throw new Error( + 'Derivation path or flag is not supported. Try updating Lattice firmware.', + ) } // Ensure path depth is valid (2-5 indices) @@ -127,17 +155,27 @@ export const encodeGetAddressesRequest = ({ * @internal * @return an array of address strings or pubkey buffers */ -export const decodeGetAddressesResponse = (data: Buffer, flag: number): Buffer[] => { +export const decodeGetAddressesResponse = ( + data: Buffer, + flag: number, +): Buffer[] => { let off = 0 - const addressOffset = flag === LatticeGetAddressesFlag.ed25519Pubkey ? 113 : 65 + const addressOffset = + flag === LatticeGetAddressesFlag.ed25519Pubkey ? 113 : 65 // Look for addresses until we reach the end (a 4 byte checksum) const addrs: any[] = [] // Pubkeys are formatted differently in the response - const arePubkeys = flag === LatticeGetAddressesFlag.secp256k1Pubkey || flag === LatticeGetAddressesFlag.ed25519Pubkey || flag === LatticeGetAddressesFlag.bls12_381Pubkey + const arePubkeys = + flag === LatticeGetAddressesFlag.secp256k1Pubkey || + flag === LatticeGetAddressesFlag.ed25519Pubkey || + flag === LatticeGetAddressesFlag.bls12_381Pubkey if (arePubkeys) { off += 1 // skip uint8 representing pubkey type } - const respDataLength = ProtocolConstants.msgSizes.secure.data.response.encrypted[LatticeSecureEncryptedRequestType.getAddresses] + const respDataLength = + ProtocolConstants.msgSizes.secure.data.response.encrypted[ + LatticeSecureEncryptedRequestType.getAddresses + ] while (off < respDataLength) { if (arePubkeys) { // Pubkeys are shorter and are returned as buffers diff --git a/packages/sdk/src/functions/getKvRecords.ts b/packages/sdk/src/functions/getKvRecords.ts index 818d2846..5ad94a9e 100644 --- a/packages/sdk/src/functions/getKvRecords.ts +++ b/packages/sdk/src/functions/getKvRecords.ts @@ -1,9 +1,22 @@ -import { LatticeSecureEncryptedRequestType, encryptedSecureRequest } from '../protocol' +import { + LatticeSecureEncryptedRequestType, + encryptedSecureRequest, +} from '../protocol' import { validateConnectedClient } from '../shared/validators' -import type { FirmwareConstants, GetKvRecordsData, GetKvRecordsRequestFunctionParams } from '../types' +import type { + FirmwareConstants, + GetKvRecordsData, + GetKvRecordsRequestFunctionParams, +} from '../types' -export async function getKvRecords({ client, type: _type, n: _n, start: _start }: GetKvRecordsRequestFunctionParams): Promise { - const { url, sharedSecret, ephemeralPub, fwConstants } = validateConnectedClient(client) +export async function getKvRecords({ + client, + type: _type, + n: _n, + start: _start, +}: GetKvRecordsRequestFunctionParams): Promise { + const { url, sharedSecret, ephemeralPub, fwConstants } = + validateConnectedClient(client) const { type, n, start } = validateGetKvRequest({ type: _type, @@ -47,7 +60,9 @@ export const validateGetKvRequest = ({ throw new Error('You must request at least one record.') } if (n > fwConstants.kvActionMaxNum) { - throw new Error(`You may only request up to ${fwConstants.kvActionMaxNum} records at once.`) + throw new Error( + `You may only request up to ${fwConstants.kvActionMaxNum} records at once.`, + ) } if (type !== 0 && !type) { throw new Error('You must specify a type.') @@ -75,13 +90,17 @@ export const encodeGetKvRecordsRequest = ({ return payload } -export const decodeGetKvRecordsResponse = (data: Buffer, fwConstants: FirmwareConstants) => { +export const decodeGetKvRecordsResponse = ( + data: Buffer, + fwConstants: FirmwareConstants, +) => { let off = 0 const nTotal = data.readUInt32BE(off) off += 4 const nFetched = Number.parseInt(data.slice(off, off + 1).toString('hex'), 16) off += 1 - if (nFetched > fwConstants.kvActionMaxNum) throw new Error('Too many records fetched. Firmware error.') + if (nFetched > fwConstants.kvActionMaxNum) + throw new Error('Too many records fetched. Firmware error.') const records: any = [] for (let i = 0; i < nFetched; i++) { const r: any = {} @@ -89,7 +108,8 @@ export const decodeGetKvRecordsResponse = (data: Buffer, fwConstants: FirmwareCo off += 4 r.type = data.readUInt32BE(off) off += 4 - r.caseSensitive = Number.parseInt(data.slice(off, off + 1).toString('hex'), 16) === 1 + r.caseSensitive = + Number.parseInt(data.slice(off, off + 1).toString('hex'), 16) === 1 off += 1 const keySz = Number.parseInt(data.slice(off, off + 1).toString('hex'), 16) off += 1 diff --git a/packages/sdk/src/functions/pair.ts b/packages/sdk/src/functions/pair.ts index 307ad5b7..3c3894fe 100644 --- a/packages/sdk/src/functions/pair.ts +++ b/packages/sdk/src/functions/pair.ts @@ -1,4 +1,7 @@ -import { LatticeSecureEncryptedRequestType, encryptedSecureRequest } from '../protocol' +import { + LatticeSecureEncryptedRequestType, + encryptedSecureRequest, +} from '../protocol' import { getPubKeyBytes } from '../shared/utilities' import { validateConnectedClient } from '../shared/validators' import type { KeyPair, PairRequestParams } from '../types' @@ -11,8 +14,12 @@ import { generateAppSecret, toPaddedDER } from '../util' * @category Lattice * @returns The active wallet object. */ -export async function pair({ client, pairingSecret }: PairRequestParams): Promise { - const { url, sharedSecret, ephemeralPub, appName, key } = validateConnectedClient(client) +export async function pair({ + client, + pairingSecret, +}: PairRequestParams): Promise { + const { url, sharedSecret, ephemeralPub, appName, key } = + validateConnectedClient(client) const data = encodePairRequest({ pairingSecret, key, appName }) const { newEphemeralPub } = await encryptedSecureRequest({ @@ -51,7 +58,11 @@ export const encodePairRequest = ({ // (RESP_ERR_PAIR_FAIL) nameBuf.write(appName) } - const hash = generateAppSecret(pubKeyBytes, nameBuf, Buffer.from(pairingSecret)) + const hash = generateAppSecret( + pubKeyBytes, + nameBuf, + Buffer.from(pairingSecret), + ) const sig = key.sign(hash) const derSig = toPaddedDER(sig) const payload = Buffer.concat([nameBuf, derSig]) diff --git a/packages/sdk/src/functions/removeKvRecords.ts b/packages/sdk/src/functions/removeKvRecords.ts index b426ea74..1e95f034 100644 --- a/packages/sdk/src/functions/removeKvRecords.ts +++ b/packages/sdk/src/functions/removeKvRecords.ts @@ -1,14 +1,25 @@ -import { LatticeSecureEncryptedRequestType, encryptedSecureRequest } from '../protocol' +import { + LatticeSecureEncryptedRequestType, + encryptedSecureRequest, +} from '../protocol' import { validateConnectedClient } from '../shared/validators' -import type { FirmwareConstants, RemoveKvRecordsRequestFunctionParams } from '../types' +import type { + FirmwareConstants, + RemoveKvRecordsRequestFunctionParams, +} from '../types' /** * `removeKvRecords` takes in an array of ids and sends a request to remove them from the Lattice. * @category Lattice * @returns A callback with an error or null. */ -export async function removeKvRecords({ client, type: _type, ids: _ids }: RemoveKvRecordsRequestFunctionParams): Promise { - const { url, sharedSecret, ephemeralPub, fwConstants } = validateConnectedClient(client) +export async function removeKvRecords({ + client, + type: _type, + ids: _ids, +}: RemoveKvRecordsRequestFunctionParams): Promise { + const { url, sharedSecret, ephemeralPub, fwConstants } = + validateConnectedClient(client) const { type, ids } = validateRemoveKvRequest({ fwConstants, @@ -53,7 +64,9 @@ export const validateRemoveKvRequest = ({ throw new Error('You must include one or more `ids` to removed.') } if (ids.length > fwConstants.kvRemoveMaxNum) { - throw new Error(`Only up to ${fwConstants.kvRemoveMaxNum} records may be removed at once.`) + throw new Error( + `Only up to ${fwConstants.kvRemoveMaxNum} records may be removed at once.`, + ) } if (type !== 0 && !type) { throw new Error('You must specify a type.') diff --git a/packages/sdk/src/functions/sign.ts b/packages/sdk/src/functions/sign.ts index 1a58ae6d..f049421f 100644 --- a/packages/sdk/src/functions/sign.ts +++ b/packages/sdk/src/functions/sign.ts @@ -4,10 +4,22 @@ import bitcoin from '../bitcoin' import { CURRENCIES } from '../constants' import ethereum from '../ethereum' import { parseGenericSigningResponse } from '../genericSigning' -import { LatticeSecureEncryptedRequestType, LatticeSignSchema, encryptedSecureRequest } from '../protocol' +import { + LatticeSecureEncryptedRequestType, + LatticeSignSchema, + encryptedSecureRequest, +} from '../protocol' import { buildTransaction } from '../shared/functions' import { validateConnectedClient, validateWallet } from '../shared/validators' -import type { BitcoinSignRequest, DecodeSignResponseParams, EncodeSignRequestParams, SignData, SignRequest, SignRequestFunctionParams, SigningRequestResponse } from '../types' +import type { + BitcoinSignRequest, + DecodeSignResponseParams, + EncodeSignRequestParams, + SignData, + SignRequest, + SignRequestFunctionParams, + SigningRequestResponse, +} from '../types' import { parseDER } from '../util' /** @@ -15,9 +27,16 @@ import { parseDER } from '../util' * @category Lattice * @returns The response from the device. */ -export async function sign({ client, data, currency, cachedData, nextCode }: SignRequestFunctionParams): Promise { +export async function sign({ + client, + data, + currency, + cachedData, + nextCode, +}: SignRequestFunctionParams): Promise { try { - const { url, sharedSecret, ephemeralPub, fwConstants } = validateConnectedClient(client) + const { url, sharedSecret, ephemeralPub, fwConstants } = + validateConnectedClient(client) const wallet = validateWallet(client.getActiveWallet()) const { requestData, isGeneric } = buildTransaction({ @@ -82,7 +101,13 @@ export async function sign({ client, data, currency, cachedData, nextCode }: Sig } } -export const encodeSignRequest = ({ fwConstants, wallet, requestData, cachedData, nextCode }: EncodeSignRequestParams) => { +export const encodeSignRequest = ({ + fwConstants, + wallet, + requestData, + cachedData, + nextCode, +}: EncodeSignRequestParams) => { let reqPayload: Buffer let schema: number let hasExtraPayloads = 0 @@ -96,18 +121,24 @@ export const encodeSignRequest = ({ fwConstants, wallet, requestData, cachedData } const nextExtraPayload = typedCachedData.extraDataPayloads.shift() if (!nextExtraPayload) { - throw new Error('No cached extra payload available for multipart sign request.') + throw new Error( + 'No cached extra payload available for multipart sign request.', + ) } if (typedRequestData.extraDataPayloads) { typedRequestData.extraDataPayloads = typedCachedData.extraDataPayloads } reqPayload = Buffer.concat([nextCode, nextExtraPayload]) schema = LatticeSignSchema.extraData - hasExtraPayloads = Number((typedCachedData.extraDataPayloads?.length ?? 0) > 0) + hasExtraPayloads = Number( + (typedCachedData.extraDataPayloads?.length ?? 0) > 0, + ) } else { reqPayload = typedRequestData.payload schema = typedRequestData.schema - hasExtraPayloads = Number((typedRequestData.extraDataPayloads?.length ?? 0) > 0) + hasExtraPayloads = Number( + (typedRequestData.extraDataPayloads?.length ?? 0) > 0, + ) } const payload = Buffer.alloc(2 + fwConstants.reqMaxDataSz) @@ -126,17 +157,27 @@ export const encodeSignRequest = ({ fwConstants, wallet, requestData, cachedData return { payload, hasExtraPayloads } } -export const decodeSignResponse = ({ data, request, isGeneric, currency }: DecodeSignResponseParams): SignData => { +export const decodeSignResponse = ({ + data, + request, + isGeneric, + currency, +}: DecodeSignResponseParams): SignData => { let off = 0 const derSigLen = 74 // DER signatures are 74 bytes if (currency === CURRENCIES.BTC) { const btcRequest = request as BitcoinSignRequest const pkhLen = 20 // Pubkeyhashes are 20 bytes const sigsLen = 760 // Up to 10x DER signatures - const changeVersion = bitcoin.getAddressFormat(btcRequest.origData.changePath) + const changeVersion = bitcoin.getAddressFormat( + btcRequest.origData.changePath, + ) const changePubKeyHash = data.slice(off, off + pkhLen) off += pkhLen - const changeRecipient = bitcoin.getBitcoinAddress(changePubKeyHash, changeVersion) + const changeRecipient = bitcoin.getBitcoinAddress( + changePubKeyHash, + changeVersion, + ) const compressedPubLength = 33 // Size of compressed public key const pubkeys = [] as any[] const sigs = [] as any[] @@ -195,7 +236,9 @@ export const decodeSignResponse = ({ data, request, isGeneric, currency }: Decod const serializedTx = bitcoin.serializeTx(preSerializedData) // Generate the transaction hash so the user can look this transaction up later const preImageTxHash = serializedTx - const txHashPre: Buffer = Buffer.from(Hash.sha256(Buffer.from(preImageTxHash, 'hex'))) + const txHashPre: Buffer = Buffer.from( + Hash.sha256(Buffer.from(preImageTxHash, 'hex')), + ) // Add extra data for debugging/lookup purposes return { tx: serializedTx, @@ -248,7 +291,10 @@ export const decodeSignResponse = ({ data, request, isGeneric, currency }: Decod const sig = parseDER(data.slice(off, off + 2 + data[off + 1])) off += derSigLen const signer = data.slice(off, off + 20) - const validatedSig = ethereum.validateEthereumMsgResponse({ signer, sig }, request) + const validatedSig = ethereum.validateEthereumMsgResponse( + { signer, sig }, + request, + ) return { sig: { v: BigInt(`0x${validatedSig.v.toString('hex')}`), diff --git a/packages/sdk/src/genericSigning.ts b/packages/sdk/src/genericSigning.ts index 1491cf4d..b5184d55 100644 --- a/packages/sdk/src/genericSigning.ts +++ b/packages/sdk/src/genericSigning.ts @@ -10,18 +10,57 @@ This payload should be coupled with: * Hash function to use on the message */ import { Hash } from 'ox' -import { type Hex, type TransactionSerializable, parseTransaction, serializeTransaction } from 'viem' +import { + type Hex, + type TransactionSerializable, + parseTransaction, + serializeTransaction, +} from 'viem' // keccak256 now imported from ox via Hash module import { HARDENED_OFFSET } from './constants' import { Constants } from './index' import { LatticeSignSchema } from './protocol' -import { buildSignerPathBuf, existsIn, fixLen, getV, getYParity, parseDER, splitFrames } from './util' +import { + buildSignerPathBuf, + existsIn, + fixLen, + getV, + getYParity, + parseDER, + splitFrames, +} from './util' export const buildGenericSigningMsgRequest = (req) => { - const { signerPath, curveType, hashType, encodingType = null, decoder = null, omitPubkey = false, fwConstants, blsDst = Constants.SIGNING.BLS_DST.BLS_DST_NUL } = req - const { extraDataFrameSz, extraDataMaxFrames, prehashAllowed, genericSigning, varAddrPathSzAllowed } = fwConstants - const { curveTypes, encodingTypes, hashTypes, baseDataSz, baseReqSz, calldataDecoding } = genericSigning - const encodedPayload = getEncodedPayload(req.payload, encodingType, encodingTypes) + const { + signerPath, + curveType, + hashType, + encodingType = null, + decoder = null, + omitPubkey = false, + fwConstants, + blsDst = Constants.SIGNING.BLS_DST.BLS_DST_NUL, + } = req + const { + extraDataFrameSz, + extraDataMaxFrames, + prehashAllowed, + genericSigning, + varAddrPathSzAllowed, + } = fwConstants + const { + curveTypes, + encodingTypes, + hashTypes, + baseDataSz, + baseReqSz, + calldataDecoding, + } = genericSigning + const encodedPayload = getEncodedPayload( + req.payload, + encodingType, + encodingTypes, + ) const { encoding } = encodedPayload let { payloadBuf } = encodedPayload const origPayloadBuf = payloadBuf @@ -31,7 +70,12 @@ export const buildGenericSigningMsgRequest = (req) => { // Sanity checks if (!payloadDataSz) { throw new Error('Payload could not be handled.') - } else if (!genericSigning || !extraDataFrameSz || !extraDataMaxFrames || !prehashAllowed) { + } else if ( + !genericSigning || + !extraDataFrameSz || + !extraDataMaxFrames || + !prehashAllowed + ) { throw new Error('Unsupported. Please update your Lattice firmware.') } else if (!existsIn(curveType, curveTypes)) { throw new Error('Unsupported curve type.') @@ -41,11 +85,13 @@ export const buildGenericSigningMsgRequest = (req) => { // If there is a decoder attached to our payload, add it to // the data field of the request. - const hasDecoder = decoder && calldataDecoding && decoder.length <= calldataDecoding.maxSz + const hasDecoder = + decoder && calldataDecoding && decoder.length <= calldataDecoding.maxSz // Make sure the payload AND decoder data fits in the firmware buffer. // If it doesn't, we can't include the decoder because the payload will likely // be pre-hashed and the decoder data isn't part of the message to sign. - const decoderFits = hasDecoder && payloadBuf.length + decoder.length <= maxExpandedSz + const decoderFits = + hasDecoder && payloadBuf.length + decoder.length <= maxExpandedSz if (hasDecoder && decoderFits) { const decoderBuf = Buffer.alloc(8 + decoder.length) // First write th reserved word @@ -63,7 +109,9 @@ export const buildGenericSigningMsgRequest = (req) => { } signerPath.forEach((idx) => { if (idx < HARDENED_OFFSET) { - throw new Error('Signing on ed25519 requires all signer path indices be hardened.') + throw new Error( + 'Signing on ed25519 requires all signer path indices be hardened.', + ) } }) } @@ -110,7 +158,9 @@ export const buildGenericSigningMsgRequest = (req) => { // If this payload is too large to send, but the Lattice allows a prehashed message, do that if (hashType === hashTypes.NONE) { // This cannot be done for ED25519 signing, which must sign the full message - throw new Error('Message too large to send and could not be prehashed (hashType=NONE).') + throw new Error( + 'Message too large to send and could not be prehashed (hashType=NONE).', + ) } else if (hashType === hashTypes.KECCAK256) { prehash = Buffer.from(Hash.keccak256(payloadData)) } else if (hashType === hashTypes.SHA256) { @@ -211,7 +261,10 @@ export const parseGenericSigningResponse = (res, off, req) => { const vBn = getV(req.origPayloadBuf, parsed) parsed.sig.v = BigInt(vBn.toString()) populateViemSignedTx(parsed.sig.v, req, parsed) - } else if (req.hashType === Constants.SIGNING.HASHES.KECCAK256 && req.encodingType !== Constants.SIGNING.ENCODINGS.EVM) { + } else if ( + req.hashType === Constants.SIGNING.HASHES.KECCAK256 && + req.encodingType !== Constants.SIGNING.ENCODINGS.EVM + ) { // Generic Keccak256 message - determine if it looks like a transaction let isTransaction = false @@ -237,7 +290,10 @@ export const parseGenericSigningResponse = (res, off, req) => { parsed.sig.v = BigInt(vBn.toString()) populateViemSignedTx(parsed.sig.v, req, parsed) } catch (err) { - console.error('Failed to get V from transaction, using fallback:', err) + console.error( + 'Failed to get V from transaction, using fallback:', + err, + ) // Fall back to simple recovery if getV fails (e.g., malformed RLP) // Use the correct hash type specified in the request const msgHash = computeMessageHash(req, digestFromResponse) @@ -297,7 +353,11 @@ function computeMessageHash( }, digestFromResponse?: Buffer, ): Buffer { - if (digestFromResponse && digestFromResponse.length === 32 && digestFromResponse.some((byte) => byte !== 0)) { + if ( + digestFromResponse && + digestFromResponse.length === 32 && + digestFromResponse.some((byte) => byte !== 0) + ) { return digestFromResponse } if (req.hashType === Constants.SIGNING.HASHES.SHA256) { @@ -311,7 +371,11 @@ function computeMessageHash( // Reconstruct a viem-compatible signed transaction string from the raw payload and // recovered signature so consumers can compare or broadcast without extra parsing. -function populateViemSignedTx(sigV: bigint, req: any, parsed: { sig: { r: string; s: string; v?: bigint }; viemTx?: string }) { +function populateViemSignedTx( + sigV: bigint, + req: any, + parsed: { sig: { r: string; s: string; v?: bigint }; viemTx?: string }, +) { if (req.encodingType !== Constants.SIGNING.ENCODINGS.EVM) return try { @@ -360,7 +424,10 @@ function populateViemSignedTx(sigV: bigint, req: any, parsed: { sig: { r: string s: parsed.sig.s as Hex, } - parsed.viemTx = serializeTransaction(baseTx as TransactionSerializable, signature as any) + parsed.viemTx = serializeTransaction( + baseTx as TransactionSerializable, + signature as any, + ) } catch (_err) { console.debug('Failed to build viemTx from response', _err) } @@ -372,7 +439,9 @@ export const getEncodedPayload = (payload, encoding, allowedEncodings) => { } // Make sure the encoding type specified is supported by firmware if (!existsIn(encoding, allowedEncodings)) { - throw new Error('Encoding not supported by Lattice firmware. You may want to update.') + throw new Error( + 'Encoding not supported by Lattice firmware. You may want to update.', + ) } let payloadBuf: Buffer if (!payload) { diff --git a/packages/sdk/src/protocol/latticeConstants.ts b/packages/sdk/src/protocol/latticeConstants.ts index 7e08710d..7304a34d 100644 --- a/packages/sdk/src/protocol/latticeConstants.ts +++ b/packages/sdk/src/protocol/latticeConstants.ts @@ -94,7 +94,10 @@ export const ProtocolConstants = { // message encryption/decryption. This is generally considered // fine because each encryption/decryption uses a unique encryption // secret (derived from the per-message ephemeral key pair). - aesIv: [0x6d, 0x79, 0x73, 0x65, 0x63, 0x72, 0x65, 0x74, 0x70, 0x61, 0x73, 0x73, 0x77, 0x6f, 0x72, 0x64], + aesIv: [ + 0x6d, 0x79, 0x73, 0x65, 0x63, 0x72, 0x65, 0x74, 0x70, 0x61, 0x73, 0x73, + 0x77, 0x6f, 0x72, 0x64, + ], // Constant size of address buffers from the Lattice. // Note that this size also captures public keys returned // by the Lattice (addresses = strings, pubkeys = buffers) @@ -114,7 +117,8 @@ export const ProtocolConstants = { [LatticeResponseCode.userDeclined]: 'Request declined by user', [LatticeResponseCode.pairFailed]: 'Pairing failed', [LatticeResponseCode.pairDisabled]: 'Pairing is currently disabled', - [LatticeResponseCode.permissionDisabled]: 'Automated signing is currently disabled', + [LatticeResponseCode.permissionDisabled]: + 'Automated signing is currently disabled', [LatticeResponseCode.internalError]: 'Device Error', [LatticeResponseCode.gceTimeout]: 'Device Timeout', [LatticeResponseCode.wrongWallet]: 'Active wallet does not match request', diff --git a/packages/sdk/src/protocol/secureMessages.ts b/packages/sdk/src/protocol/secureMessages.ts index dff64ba6..58af96ba 100644 --- a/packages/sdk/src/protocol/secureMessages.ts +++ b/packages/sdk/src/protocol/secureMessages.ts @@ -1,7 +1,21 @@ import { getEphemeralId, request } from '../shared/functions' import { validateEphemeralPub } from '../shared/validators' -import type { DecryptedResponse, KeyPair, LatticeMessageHeader, LatticeSecureConnectRequestPayloadData, LatticeSecureDecryptedResponse, LatticeSecureRequest, LatticeSecureRequestPayload } from '../types' -import { aes256_decrypt, aes256_encrypt, checksum, getP256KeyPairFromPub, randomBytes } from '../util' +import type { + DecryptedResponse, + KeyPair, + LatticeMessageHeader, + LatticeSecureConnectRequestPayloadData, + LatticeSecureDecryptedResponse, + LatticeSecureRequest, + LatticeSecureRequestPayload, +} from '../types' +import { + aes256_decrypt, + aes256_encrypt, + checksum, + getP256KeyPairFromPub, + randomBytes, +} from '../util' /** * All messages sent to the Lattice from this SDK will be * "secure messages", of which there are two types: @@ -22,7 +36,13 @@ import { aes256_decrypt, aes256_encrypt, checksum, getP256KeyPairFromPub, random * The response to this request will contain a new ephemral public * key, which you will need for the next encrypted request. */ -import { ProtocolConstants as Constants, LatticeMsgType, LatticeProtocolVersion, type LatticeSecureEncryptedRequestType, LatticeSecureMsgType } from './latticeConstants' +import { + ProtocolConstants as Constants, + LatticeMsgType, + LatticeProtocolVersion, + type LatticeSecureEncryptedRequestType, + LatticeSecureMsgType, +} from './latticeConstants' const { msgSizes } = Constants const { secure: szs } = msgSizes @@ -47,7 +67,11 @@ export async function connectSecureRequest({ pubkey: pubkey, }) const msgId = randomBytes(4) - const msg = serializeSecureRequestMsg(msgId, LatticeSecureMsgType.connect, payloadData) + const msg = serializeSecureRequestMsg( + msgId, + LatticeSecureMsgType.connect, + payloadData, + ) // Send request to the Lattice const resp = await request({ url, payload: msg }) if (resp.length !== szs.payload.response.connect - 1) { @@ -96,7 +120,11 @@ export async function encryptedSecureRequest({ // Serialize the payload data into an encrypted secure // request message. - const msg = serializeSecureRequestMsg(msgId, LatticeSecureMsgType.encrypted, payloadData) + const msg = serializeSecureRequestMsg( + msgId, + LatticeSecureMsgType.encrypted, + payloadData, + ) // Send request to Lattice const resp = await request({ @@ -109,7 +137,10 @@ export async function encryptedSecureRequest({ throw new Error('Wrong Lattice response message size.') } - const encPayloadData = resp.slice(0, szs.data.response.encrypted.encryptedData) + const encPayloadData = resp.slice( + 0, + szs.data.response.encrypted.encryptedData, + ) // Return decrypted response payload data return decryptEncryptedLatticeResponseData({ @@ -128,20 +159,31 @@ export async function encryptedSecureRequest({ * @param payloadData - Request data * @return {Buffer} Serialized message to be sent to Lattice */ -function serializeSecureRequestMsg(msgId: Buffer, secureRequestType: LatticeSecureMsgType, payloadData: Buffer): Buffer { +function serializeSecureRequestMsg( + msgId: Buffer, + secureRequestType: LatticeSecureMsgType, + payloadData: Buffer, +): Buffer { // Sanity check request data if (msgId.length !== 4) { throw new Error('msgId must be four bytes') } - if (secureRequestType !== LatticeSecureMsgType.connect && secureRequestType !== LatticeSecureMsgType.encrypted) { + if ( + secureRequestType !== LatticeSecureMsgType.connect && + secureRequestType !== LatticeSecureMsgType.encrypted + ) { throw new Error('Invalid Lattice secure request type') } // Validate the incoming payload data size. Note that the payload // data is prepended with a secure request type byte, so the // payload data size is one less than the expected size. - const isValidConnectPayloadDataSz = secureRequestType === LatticeSecureMsgType.connect && payloadData.length === szs.payload.request.connect - 1 - const isValidEncryptedPayloadDataSz = secureRequestType === LatticeSecureMsgType.encrypted && payloadData.length === szs.payload.request.encrypted - 1 + const isValidConnectPayloadDataSz = + secureRequestType === LatticeSecureMsgType.connect && + payloadData.length === szs.payload.request.connect - 1 + const isValidEncryptedPayloadDataSz = + secureRequestType === LatticeSecureMsgType.encrypted && + payloadData.length === szs.payload.request.encrypted - 1 // Build payload and size let msgSz = msgSizes.header + msgSizes.checksum @@ -205,7 +247,9 @@ function serializeSecureRequestMsg(msgId: Buffer, secureRequestType: LatticeSecu * Serialize payload data for a Lattice secure request: connect * @return {Buffer} - 1700 bytes, of which only 65 are used */ -function serializeSecureRequestConnectPayloadData(payloadData: LatticeSecureConnectRequestPayloadData): Buffer { +function serializeSecureRequestConnectPayloadData( + payloadData: LatticeSecureConnectRequestPayloadData, +): Buffer { const serPayloadData = Buffer.alloc(szs.data.request.connect) payloadData.pubkey.copy(serPayloadData, 0) return serPayloadData @@ -239,7 +283,9 @@ function serializeSecureRequestEncryptedPayloadData({ // Validate the request data size matches the desired request const requestDataSize = szs.data.request.encrypted[requestType] if (data.length !== requestDataSize) { - throw new Error(`Invalid request datasize (wanted ${requestDataSize}, got ${data.length})`) + throw new Error( + `Invalid request datasize (wanted ${requestDataSize}, got ${data.length})`, + ) } // Build the pre-encrypted data payload, which variable sized and of form: @@ -253,7 +299,10 @@ function serializeSecureRequestEncryptedPayloadData({ // equal to the full message request less the 4-byte ephemeral id. const _encryptedData = Buffer.alloc(szs.data.request.encrypted.encryptedData) preEncryptedData.copy(_encryptedData, 0) - _encryptedData.writeUInt32LE(preEncryptedDataChecksum, preEncryptedData.length) + _encryptedData.writeUInt32LE( + preEncryptedDataChecksum, + preEncryptedData.length, + ) const encryptedData = aes256_encrypt(_encryptedData, sharedSecret) // Calculate ephemeral ID @@ -286,7 +335,8 @@ function decryptEncryptedLatticeResponseData({ // Bulid the object const ephemeralPubSz = 65 // secp256r1 pubkey - const checksumOffset = ephemeralPubSz + szs.data.response.encrypted[requestType] + const checksumOffset = + ephemeralPubSz + szs.data.response.encrypted[requestType] const respData: LatticeSecureDecryptedResponse = { ephemeralPub: decData.slice(0, ephemeralPubSz), data: decData.slice(ephemeralPubSz, checksumOffset), diff --git a/packages/sdk/src/schemas/transaction.ts b/packages/sdk/src/schemas/transaction.ts index f3be013d..80772c99 100644 --- a/packages/sdk/src/schemas/transaction.ts +++ b/packages/sdk/src/schemas/transaction.ts @@ -4,25 +4,32 @@ import { TRANSACTION_TYPE } from '../types' // Helper to handle various numeric inputs and convert them to BigInt. // It also validates that the value is not negative. -const toPositiveBigInt = z.union([z.string().regex(/^(0x[0-9a-fA-F]+|[0-9]+)$/, 'Invalid number format'), z.number(), z.bigint()]).transform((val, ctx) => { - try { - const b = typeof val === 'string' && isHex(val) ? hexToBigInt(val) : BigInt(val) - if (b < 0n) { +const toPositiveBigInt = z + .union([ + z.string().regex(/^(0x[0-9a-fA-F]+|[0-9]+)$/, 'Invalid number format'), + z.number(), + z.bigint(), + ]) + .transform((val, ctx) => { + try { + const b = + typeof val === 'string' && isHex(val) ? hexToBigInt(val) : BigInt(val) + if (b < 0n) { + ctx.addIssue({ + code: z.ZodIssueCode.custom, + message: 'Value must be non-negative', + }) + return z.NEVER + } + return b + } catch { ctx.addIssue({ code: z.ZodIssueCode.custom, - message: 'Value must be non-negative', + message: 'Invalid numeric value', }) return z.NEVER } - return b - } catch { - ctx.addIssue({ - code: z.ZodIssueCode.custom, - message: 'Invalid numeric value', - }) - return z.NEVER - } -}) + }) // Schema for gas-related fields, ensuring they are non-negative BigInts. const GasValueSchema = toPositiveBigInt.refine((val) => val >= 0n, { @@ -32,7 +39,11 @@ const GasValueSchema = toPositiveBigInt.refine((val) => val >= 0n, { // Schema for chainId, ensuring it's a positive integer. const ChainIdSchema = z .union([z.string(), z.number()]) - .transform((val) => (typeof val === 'string' && isHex(val) ? Number(hexToBigInt(val as Hex)) : Number(val))) + .transform((val) => + typeof val === 'string' && isHex(val) + ? Number(hexToBigInt(val as Hex)) + : Number(val), + ) .refine((val) => Number.isInteger(val) && val > 0, { message: 'Chain ID must be a positive integer', }) @@ -44,43 +55,61 @@ const AddressSchema = z .transform((addr) => getAddress(addr)) // Schema for hex data, ensuring it's a valid hex string. -const DataSchema = z.string().refine(isHex, 'Data must be a valid hex string').default('0x') +const DataSchema = z + .string() + .refine(isHex, 'Data must be a valid hex string') + .default('0x') -const NonceSchema = z.union([z.string().regex(/^(0x[0-9a-fA-F]+|[0-9]+)$/, 'Invalid nonce format'), z.number().int().nonnegative(), z.bigint()]).transform((val, ctx) => { - try { - const bigVal = typeof val === 'string' ? (isHex(val as Hex) ? hexToBigInt(val as Hex) : BigInt(val)) : typeof val === 'number' ? BigInt(val) : val +const NonceSchema = z + .union([ + z.string().regex(/^(0x[0-9a-fA-F]+|[0-9]+)$/, 'Invalid nonce format'), + z.number().int().nonnegative(), + z.bigint(), + ]) + .transform((val, ctx) => { + try { + const bigVal = + typeof val === 'string' + ? isHex(val as Hex) + ? hexToBigInt(val as Hex) + : BigInt(val) + : typeof val === 'number' + ? BigInt(val) + : val - if (bigVal < 0n) { - ctx.addIssue({ - code: z.ZodIssueCode.custom, - message: 'Nonce must be non-negative', - }) - return z.NEVER - } + if (bigVal < 0n) { + ctx.addIssue({ + code: z.ZodIssueCode.custom, + message: 'Nonce must be non-negative', + }) + return z.NEVER + } + + const maxSafe = BigInt(Number.MAX_SAFE_INTEGER) + if (bigVal > maxSafe) { + ctx.addIssue({ + code: z.ZodIssueCode.custom, + message: 'Nonce exceeds JavaScript safe integer range', + }) + return z.NEVER + } - const maxSafe = BigInt(Number.MAX_SAFE_INTEGER) - if (bigVal > maxSafe) { + return Number(bigVal) + } catch { ctx.addIssue({ code: z.ZodIssueCode.custom, - message: 'Nonce exceeds JavaScript safe integer range', + message: 'Invalid nonce value', }) return z.NEVER } - - return Number(bigVal) - } catch { - ctx.addIssue({ - code: z.ZodIssueCode.custom, - message: 'Invalid nonce value', - }) - return z.NEVER - } -}) + }) // Schema for access list entries. const AccessListEntrySchema = z.object({ address: AddressSchema, - storageKeys: z.array(z.string().refine(isHex, 'Storage key must be a hex string')), + storageKeys: z.array( + z.string().refine(isHex, 'Storage key must be a hex string'), + ), }) // Schema for EIP-7702 authorization entries. @@ -107,7 +136,9 @@ const BaseTxSchema = z.object({ // Schema for Legacy (Type 0) transactions. const LegacyTxSchema = BaseTxSchema.extend({ - type: z.union([z.literal('legacy'), z.literal(TRANSACTION_TYPE.LEGACY)]).optional(), + type: z + .union([z.literal('legacy'), z.literal(TRANSACTION_TYPE.LEGACY)]) + .optional(), gasPrice: GasValueSchema, }) @@ -126,7 +157,11 @@ const EIP1559TxSchema = BaseTxSchema.extend({ // Schema for EIP-7702 (Type 4/5) transactions. const EIP7702TxSchema = BaseTxSchema.extend({ - type: z.union([z.literal('eip7702'), z.literal(TRANSACTION_TYPE.EIP7702_AUTH), z.literal(TRANSACTION_TYPE.EIP7702_AUTH_LIST)]), + type: z.union([ + z.literal('eip7702'), + z.literal(TRANSACTION_TYPE.EIP7702_AUTH), + z.literal(TRANSACTION_TYPE.EIP7702_AUTH_LIST), + ]), maxFeePerGas: GasValueSchema, maxPriorityFeePerGas: GasValueSchema, authorizationList: z.array(AuthorizationSchema).min(1), @@ -146,7 +181,9 @@ export const TransactionSchema = z .refine( (val) => { try { - JSON.stringify(val, (_, value) => (typeof value === 'bigint' ? value.toString() : value)) + JSON.stringify(val, (_, value) => + typeof value === 'bigint' ? value.toString() : value, + ) return true } catch { return false @@ -180,7 +217,12 @@ export const TransactionSchema = z let type: 'eip7702' | 'eip1559' | 'eip2930' | 'legacy' = 'legacy' let schema: z.ZodTypeAny = LegacyTxSchema - if (tx.type === 'eip7702' || tx.type === 4 || tx.type === 5 || hasAuthList) { + if ( + tx.type === 'eip7702' || + tx.type === 4 || + tx.type === 5 || + hasAuthList + ) { type = 'eip7702' schema = EIP7702TxSchema } else if (tx.type === 'eip1559' || tx.type === 2 || hasMaxFee) { diff --git a/packages/sdk/src/shared/functions.ts b/packages/sdk/src/shared/functions.ts index 518e057f..4c6120b2 100644 --- a/packages/sdk/src/shared/functions.ts +++ b/packages/sdk/src/shared/functions.ts @@ -7,7 +7,12 @@ import { buildGenericSigningMsgRequest } from '../genericSigning' import type { Currency, FirmwareConstants, RequestParams } from '../types' import { fetchWithTimeout, parseLattice1Response } from '../util' import { LatticeResponseError } from './errors' -import { isDeviceBusy, isInvalidEphemeralId, isWrongWallet, shouldUseEVMLegacyConverter } from './predicates' +import { + isDeviceBusy, + isInvalidEphemeralId, + isWrongWallet, + shouldUseEVMLegacyConverter, +} from './predicates' import { validateRequestError } from './validators' export const buildTransaction = ({ @@ -30,13 +35,19 @@ export const buildTransaction = ({ // general signing requests for newer firmware versions. EIP1559 and EIP155 legacy // requests will convert, but others may not. if (currency === 'ETH' && shouldUseEVMLegacyConverter(fwConstants)) { - console.log('Using the legacy ETH signing path. This will soon be deprecated. ' + 'Please switch to general signing request.') + console.log( + 'Using the legacy ETH signing path. This will soon be deprecated. ' + + 'Please switch to general signing request.', + ) let payload: Buffer | undefined try { payload = ethereum.convertEthereumTransactionToGenericRequest(data) } catch (err) { console.error('Failed to convert legacy Ethereum transaction:', err) - throw new Error('Could not convert legacy request. Please switch to a general signing ' + 'request. See gridplus-sdk docs for more information.') + throw new Error( + 'Could not convert legacy request. Please switch to a general signing ' + + 'request. See gridplus-sdk docs for more information.', + ) } data = { fwConstants, @@ -73,7 +84,11 @@ export const buildTransaction = ({ } } -export const request = async ({ url, payload, timeout = 60000 }: RequestParams) => { +export const request = async ({ + url, + payload, + timeout = 60000, +}: RequestParams) => { return fetchWithTimeout(url, { method: 'POST', body: JSON.stringify({ data: payload }), @@ -92,7 +107,9 @@ export const request = async ({ url, payload, timeout = 60000 }: RequestParams) throw new Error(`Error code ${body.status}: ${body.message}`) } - const { data, errorMessage, responseCode } = parseLattice1Response(body.message) + const { data, errorMessage, responseCode } = parseLattice1Response( + body.message, + ) if (errorMessage || responseCode) { throw new LatticeResponseError(responseCode, errorMessage) @@ -157,7 +174,10 @@ export const retryWrapper = async ({ if ((errorMessage || responseCode) && retries) { if (isDeviceBusy(responseCode)) { await sleep(3000) - } else if (isWrongWallet(responseCode) && !client.skipRetryOnWrongWallet) { + } else if ( + isWrongWallet(responseCode) && + !client.skipRetryOnWrongWallet + ) { await client.fetchActiveWallet() } else if (isInvalidEphemeralId(responseCode)) { await client.connect(client.deviceId) diff --git a/packages/sdk/src/shared/predicates.ts b/packages/sdk/src/shared/predicates.ts index 1558d9e7..265ec762 100644 --- a/packages/sdk/src/shared/predicates.ts +++ b/packages/sdk/src/shared/predicates.ts @@ -2,12 +2,18 @@ import { LatticeResponseCode } from '../protocol' import type { FirmwareConstants, FirmwareVersion } from '../types' import { isFWSupported } from './utilities' -export const isDeviceBusy = (responseCode: number) => responseCode === LatticeResponseCode.deviceBusy || responseCode === LatticeResponseCode.gceTimeout +export const isDeviceBusy = (responseCode: number) => + responseCode === LatticeResponseCode.deviceBusy || + responseCode === LatticeResponseCode.gceTimeout -export const isWrongWallet = (responseCode: number) => responseCode === LatticeResponseCode.wrongWallet +export const isWrongWallet = (responseCode: number) => + responseCode === LatticeResponseCode.wrongWallet -export const isInvalidEphemeralId = (responseCode: number) => responseCode === LatticeResponseCode.invalidEphemId +export const isInvalidEphemeralId = (responseCode: number) => + responseCode === LatticeResponseCode.invalidEphemId -export const doesFetchWalletsOnLoad = (fwVersion: FirmwareVersion) => isFWSupported(fwVersion, { major: 0, minor: 14, fix: 1 }) +export const doesFetchWalletsOnLoad = (fwVersion: FirmwareVersion) => + isFWSupported(fwVersion, { major: 0, minor: 14, fix: 1 }) -export const shouldUseEVMLegacyConverter = (fwConstants: FirmwareConstants) => fwConstants.genericSigning?.encodingTypes?.EVM +export const shouldUseEVMLegacyConverter = (fwConstants: FirmwareConstants) => + fwConstants.genericSigning?.encodingTypes?.EVM diff --git a/packages/sdk/src/shared/utilities.ts b/packages/sdk/src/shared/utilities.ts index fb01e938..66632b86 100644 --- a/packages/sdk/src/shared/utilities.ts +++ b/packages/sdk/src/shared/utilities.ts @@ -82,10 +82,17 @@ export const parseWallets = (walletData: any): ActiveWallets => { } // Determine if a provided firmware version matches or exceeds the current firmware version -export const isFWSupported = (fwVersion: FirmwareVersion, versionSupported: FirmwareVersion): boolean => { +export const isFWSupported = ( + fwVersion: FirmwareVersion, + versionSupported: FirmwareVersion, +): boolean => { const { major, minor, fix } = fwVersion const { major: _major, minor: _minor, fix: _fix } = versionSupported - return major > _major || (major >= _major && minor > _minor) || (major >= _major && minor >= _minor && fix >= _fix) + return ( + major > _major || + (major >= _major && minor > _minor) || + (major >= _major && minor >= _minor && fix >= _fix) + ) } /** diff --git a/packages/sdk/src/shared/validators.ts b/packages/sdk/src/shared/validators.ts index 541ecbf5..59fc0dbb 100644 --- a/packages/sdk/src/shared/validators.ts +++ b/packages/sdk/src/shared/validators.ts @@ -2,7 +2,15 @@ import type { UInt4 } from 'bitwise/types' import isEmpty from 'lodash/isEmpty.js' import type { Client } from '../client' import { ASCII_REGEX, EMPTY_WALLET_UID, MAX_ADDR } from '../constants' -import type { ActiveWallets, FirmwareConstants, FirmwareVersion, KVRecords, KeyPair, LatticeError, Wallet } from '../types' +import type { + ActiveWallets, + FirmwareConstants, + FirmwareVersion, + KVRecords, + KeyPair, + LatticeError, + Wallet, +} from '../types' import { isUInt4 } from '../util' export const validateIsUInt4 = (n?: number) => { @@ -26,14 +34,17 @@ export const validateStartPath = (startPath?: number[]) => { if (!startPath) { throw new Error('Start path is required') } - if (startPath.length < 1 || startPath.length > 5) throw new Error('Path must include between 1 and 5 indices') + if (startPath.length < 1 || startPath.length > 5) + throw new Error('Path must include between 1 and 5 indices') return startPath } export const validateDeviceId = (deviceId?: string) => { if (!deviceId) { - throw new Error('No device ID has been stored. Please connect with your device ID first.') + throw new Error( + 'No device ID has been stored. Please connect with your device ID first.', + ) } return deviceId } @@ -43,7 +54,9 @@ export const validateAppName = (name?: string) => { throw new Error('Name is required.') } if (name.length < 5 || name.length > 24) { - throw new Error('Invalid length for name provided. Must be 5-24 characters.') + throw new Error( + 'Invalid length for name provided. Must be 5-24 characters.', + ) } return name } @@ -85,7 +98,11 @@ export const validateFwVersion = (fwVersion?: FirmwareVersion) => { if (!fwVersion) { throw new Error('Firmware version does not exist. Please reconnect.') } - if (typeof fwVersion.fix !== 'number' || typeof fwVersion.minor !== 'number' || typeof fwVersion.major !== 'number') { + if ( + typeof fwVersion.fix !== 'number' || + typeof fwVersion.minor !== 'number' || + typeof fwVersion.major !== 'number' + ) { throw new Error('Firmware version improperly formatted. Please reconnect.') } return fwVersion @@ -94,7 +111,9 @@ export const validateFwVersion = (fwVersion?: FirmwareVersion) => { export const validateRequestError = (err: LatticeError) => { const isTimeout = err.code === 'ECONNABORTED' && err.errno === 'ETIME' if (isTimeout) { - throw new Error('Timeout waiting for device. Please ensure it is connected to the internet and try again in a minute.') + throw new Error( + 'Timeout waiting for device. Please ensure it is connected to the internet and try again in a minute.', + ) } throw new Error(`Failed to make request to device:\n${err.message}`) } @@ -129,7 +148,9 @@ export const validateConnectedClient = (client: Client) => { export const validateEphemeralPub = (ephemeralPub?: KeyPair) => { if (!ephemeralPub) { - throw new Error('`ephemeralPub` (ephemeral public key) is required. Please reconnect.') + throw new Error( + '`ephemeralPub` (ephemeral public key) is required. Please reconnect.', + ) } return ephemeralPub } @@ -149,28 +170,52 @@ export const validateKey = (key?: KeyPair) => { } export const validateActiveWallets = (activeWallets?: ActiveWallets) => { - if (!activeWallets || (activeWallets?.internal?.uid?.equals(EMPTY_WALLET_UID) && activeWallets?.external?.uid?.equals(EMPTY_WALLET_UID))) { + if ( + !activeWallets || + (activeWallets?.internal?.uid?.equals(EMPTY_WALLET_UID) && + activeWallets?.external?.uid?.equals(EMPTY_WALLET_UID)) + ) { throw new Error('No active wallet.') } return activeWallets } -export const validateKvRecords = (records?: KVRecords, fwConstants?: FirmwareConstants) => { +export const validateKvRecords = ( + records?: KVRecords, + fwConstants?: FirmwareConstants, +) => { if (!fwConstants || !fwConstants.kvActionsAllowed) { throw new Error('Unsupported. Please update firmware.') } else if (typeof records !== 'object' || Object.keys(records).length < 1) { - throw new Error('One or more key-value mapping must be provided in `records` param.') + throw new Error( + 'One or more key-value mapping must be provided in `records` param.', + ) } else if (Object.keys(records).length > fwConstants.kvActionMaxNum) { - throw new Error(`Too many keys provided. Please only provide up to ${fwConstants.kvActionMaxNum}.`) + throw new Error( + `Too many keys provided. Please only provide up to ${fwConstants.kvActionMaxNum}.`, + ) } return records } -export const validateKvRecord = ({ key, val }: KVRecords, fwConstants: FirmwareConstants) => { - if (typeof key !== 'string' || String(key).length > fwConstants.kvKeyMaxStrSz) { - throw new Error(`Key ${key} too large. Must be <=${fwConstants.kvKeyMaxStrSz} characters.`) - } else if (typeof val !== 'string' || String(val).length > fwConstants.kvValMaxStrSz) { - throw new Error(`Value ${val} too large. Must be <=${fwConstants.kvValMaxStrSz} characters.`) +export const validateKvRecord = ( + { key, val }: KVRecords, + fwConstants: FirmwareConstants, +) => { + if ( + typeof key !== 'string' || + String(key).length > fwConstants.kvKeyMaxStrSz + ) { + throw new Error( + `Key ${key} too large. Must be <=${fwConstants.kvKeyMaxStrSz} characters.`, + ) + } else if ( + typeof val !== 'string' || + String(val).length > fwConstants.kvValMaxStrSz + ) { + throw new Error( + `Value ${val} too large. Must be <=${fwConstants.kvValMaxStrSz} characters.`, + ) } else if (String(key).length === 0 || String(val).length === 0) { throw new Error('Keys and values must be >0 characters.') } else if (!ASCII_REGEX.test(key) || !ASCII_REGEX.test(val)) { diff --git a/packages/sdk/src/types/addKvRecords.ts b/packages/sdk/src/types/addKvRecords.ts index 7abf275c..5492d6b1 100644 --- a/packages/sdk/src/types/addKvRecords.ts +++ b/packages/sdk/src/types/addKvRecords.ts @@ -7,6 +7,7 @@ export interface AddKvRecordsRequestParams { caseSensitive?: boolean } -export interface AddKvRecordsRequestFunctionParams extends AddKvRecordsRequestParams { +export interface AddKvRecordsRequestFunctionParams + extends AddKvRecordsRequestParams { client: Client } diff --git a/packages/sdk/src/types/firmware.ts b/packages/sdk/src/types/firmware.ts index 0a6e4734..7ace3ff7 100644 --- a/packages/sdk/src/types/firmware.ts +++ b/packages/sdk/src/types/firmware.ts @@ -41,7 +41,10 @@ export interface FirmwareConstants { extraDataFrameSz: number extraDataMaxFrames: number genericSigning: GenericSigningData - getAddressFlags: [typeof EXTERNAL.GET_ADDR_FLAGS.ED25519_PUB, typeof EXTERNAL.GET_ADDR_FLAGS.SECP256K1_PUB] + getAddressFlags: [ + typeof EXTERNAL.GET_ADDR_FLAGS.ED25519_PUB, + typeof EXTERNAL.GET_ADDR_FLAGS.SECP256K1_PUB, + ] kvActionMaxNum: number kvActionsAllowed: boolean kvKeyMaxStrSz: number diff --git a/packages/sdk/src/types/getAddresses.ts b/packages/sdk/src/types/getAddresses.ts index 03465753..91d1704a 100644 --- a/packages/sdk/src/types/getAddresses.ts +++ b/packages/sdk/src/types/getAddresses.ts @@ -7,6 +7,7 @@ export interface GetAddressesRequestParams { iterIdx?: number } -export interface GetAddressesRequestFunctionParams extends GetAddressesRequestParams { +export interface GetAddressesRequestFunctionParams + extends GetAddressesRequestParams { client: Client } diff --git a/packages/sdk/src/types/getKvRecords.ts b/packages/sdk/src/types/getKvRecords.ts index 512b8f28..73041dd4 100644 --- a/packages/sdk/src/types/getKvRecords.ts +++ b/packages/sdk/src/types/getKvRecords.ts @@ -6,7 +6,8 @@ export interface GetKvRecordsRequestParams { start?: number } -export interface GetKvRecordsRequestFunctionParams extends GetKvRecordsRequestParams { +export interface GetKvRecordsRequestFunctionParams + extends GetKvRecordsRequestParams { client: Client } diff --git a/packages/sdk/src/types/removeKvRecords.ts b/packages/sdk/src/types/removeKvRecords.ts index 27aef49e..25d9e9c6 100644 --- a/packages/sdk/src/types/removeKvRecords.ts +++ b/packages/sdk/src/types/removeKvRecords.ts @@ -5,6 +5,7 @@ export interface RemoveKvRecordsRequestParams { ids?: string[] } -export interface RemoveKvRecordsRequestFunctionParams extends RemoveKvRecordsRequestParams { +export interface RemoveKvRecordsRequestFunctionParams + extends RemoveKvRecordsRequestParams { client: Client } diff --git a/packages/sdk/src/types/sign.ts b/packages/sdk/src/types/sign.ts index df5897d9..d5994e88 100644 --- a/packages/sdk/src/types/sign.ts +++ b/packages/sdk/src/types/sign.ts @@ -1,4 +1,12 @@ -import type { AccessList, Address, Hex, SignedAuthorization, SignedAuthorizationList, TypedData, TypedDataDefinition } from 'viem' +import type { + AccessList, + Address, + Hex, + SignedAuthorization, + SignedAuthorizationList, + TypedData, + TypedDataDefinition, +} from 'viem' import type { Client } from '../client' import type { Currency, SigningPath, Wallet } from './client' import type { FirmwareConstants } from './firmware' @@ -64,11 +72,24 @@ export type EIP7702AuthListTransactionRequest = BaseTransactionRequest & { } // Main discriminated union for transaction requests -export type TransactionRequest = LegacyTransactionRequest | EIP2930TransactionRequest | EIP1559TransactionRequest | EIP7702AuthTransactionRequest | EIP7702AuthListTransactionRequest - -export interface SigningPayload = TypedData> { +export type TransactionRequest = + | LegacyTransactionRequest + | EIP2930TransactionRequest + | EIP1559TransactionRequest + | EIP7702AuthTransactionRequest + | EIP7702AuthListTransactionRequest + +export interface SigningPayload< + TTypedData extends TypedData | Record = TypedData, +> { signerPath: SigningPath - payload: Uint8Array | Uint8Array[] | Buffer | Buffer[] | Hex | EIP712MessagePayload + payload: + | Uint8Array + | Uint8Array[] + | Buffer + | Buffer[] + | Hex + | EIP712MessagePayload curveType: number hashType: number encodingType?: number @@ -76,14 +97,18 @@ export interface SigningPayload = TypedData> { +export interface SignRequestParams< + TTypedData extends TypedData | Record = TypedData, +> { data: SigningPayload | BitcoinSignPayload currency?: Currency cachedData?: unknown nextCode?: Buffer } -export interface SignRequestFunctionParams = TypedData> extends SignRequestParams { +export interface SignRequestFunctionParams< + TTypedData extends TypedData | Record = TypedData, +> extends SignRequestParams { client: Client } @@ -153,9 +178,16 @@ export interface DecodeSignResponseParams { } // Align EIP712MessagePayload with Viem's TypedDataDefinition -export interface EIP712MessagePayload = TypedData, TPrimaryType extends keyof TTypedData | 'EIP712Domain' = keyof TTypedData> { +export interface EIP712MessagePayload< + TTypedData extends TypedData | Record = TypedData, + TPrimaryType extends keyof TTypedData | 'EIP712Domain' = keyof TTypedData, +> { types: TTypedData - domain: TTypedData extends TypedData ? TypedDataDefinition['domain'] : Record + domain: TTypedData extends TypedData + ? TypedDataDefinition['domain'] + : Record primaryType: TPrimaryType - message: TTypedData extends TypedData ? TypedDataDefinition['message'] : Record + message: TTypedData extends TypedData + ? TypedDataDefinition['message'] + : Record } diff --git a/packages/sdk/src/util.ts b/packages/sdk/src/util.ts index c59eda74..ba0433c3 100644 --- a/packages/sdk/src/util.ts +++ b/packages/sdk/src/util.ts @@ -15,9 +15,18 @@ import { type Hex, parseTransaction } from 'viem' const EC = elliptic.ec const { ecdsaRecover } = secp256k1 import { Calldata } from '.' -import { BIP_CONSTANTS, EXTERNAL_NETWORKS_BY_CHAIN_ID_URL, HARDENED_OFFSET, NETWORKS_BY_CHAIN_ID, VERSION_BYTE } from './constants' +import { + BIP_CONSTANTS, + EXTERNAL_NETWORKS_BY_CHAIN_ID_URL, + HARDENED_OFFSET, + NETWORKS_BY_CHAIN_ID, + VERSION_BYTE, +} from './constants' import { LatticeResponseCode, ProtocolConstants } from './protocol' -import { isValid4ByteResponse, isValidBlockExplorerResponse } from './shared/validators' +import { + isValid4ByteResponse, + isValidBlockExplorerResponse, +} from './shared/validators' import type { FirmwareConstants } from './types' const { COINS, PURPOSES } = BIP_CONSTANTS @@ -117,19 +126,37 @@ export const toPaddedDER = (sig: any): Buffer => { // TRANSACTION UTILS //-------------------------------------------------- /** @internal */ -export const isValidAssetPath = (path: number[], fwConstants: FirmwareConstants): boolean => { - const allowedPurposes = [PURPOSES.ETH, PURPOSES.BTC_LEGACY, PURPOSES.BTC_WRAPPED_SEGWIT, PURPOSES.BTC_SEGWIT] +export const isValidAssetPath = ( + path: number[], + fwConstants: FirmwareConstants, +): boolean => { + const allowedPurposes = [ + PURPOSES.ETH, + PURPOSES.BTC_LEGACY, + PURPOSES.BTC_WRAPPED_SEGWIT, + PURPOSES.BTC_SEGWIT, + ] const allowedCoins = [COINS.ETH, COINS.BTC, COINS.BTC_TESTNET] // These coin types were given to us by MyCrypto. They should be allowed, but we expect // an Ethereum-type address with these coin types. // These all use SLIP44: https://github.com/satoshilabs/slips/blob/master/slip-0044.md - const allowedMyCryptoCoins = [60, 61, 966, 700, 9006, 9000, 1007, 553, 178, 137, 37310, 108, 40, 889, 1987, 820, 6060, 1620, 1313114, 76, 246529, 246785, 1001, 227, 916, 464, 2221, 344, 73799, 246] + const allowedMyCryptoCoins = [ + 60, 61, 966, 700, 9006, 9000, 1007, 553, 178, 137, 37310, 108, 40, 889, + 1987, 820, 6060, 1620, 1313114, 76, 246529, 246785, 1001, 227, 916, 464, + 2221, 344, 73799, 246, + ] // Make sure firmware supports this Bitcoin path const isBitcoin = path[1] === COINS.BTC || path[1] === COINS.BTC_TESTNET - const isBitcoinNonWrappedSegwit = isBitcoin && path[0] !== PURPOSES.BTC_WRAPPED_SEGWIT - if (isBitcoinNonWrappedSegwit && !fwConstants.allowBtcLegacyAndSegwitAddrs) return false + const isBitcoinNonWrappedSegwit = + isBitcoin && path[0] !== PURPOSES.BTC_WRAPPED_SEGWIT + if (isBitcoinNonWrappedSegwit && !fwConstants.allowBtcLegacyAndSegwitAddrs) + return false // Make sure this path is otherwise valid - return allowedPurposes.indexOf(path[0]) >= 0 && (allowedCoins.indexOf(path[1]) >= 0 || allowedMyCryptoCoins.indexOf(path[1] - HARDENED_OFFSET) > 0) + return ( + allowedPurposes.indexOf(path[0]) >= 0 && + (allowedCoins.indexOf(path[1]) >= 0 || + allowedMyCryptoCoins.indexOf(path[1] - HARDENED_OFFSET) > 0) + ) } /** @internal */ @@ -154,15 +181,23 @@ function isBase10NumStr(x: string): boolean { } /** @internal Ensure a param is represented by a buffer */ -export const ensureHexBuffer = (x: string | number | bigint | Buffer, zeroIsNull = true): Buffer => { +export const ensureHexBuffer = ( + x: string | number | bigint | Buffer, + zeroIsNull = true, +): Buffer => { try { const isZeroNumber = typeof x === 'number' && x === 0 const isZeroBigInt = typeof x === 'bigint' && x === 0n - if (x === null || ((isZeroNumber || isZeroBigInt) && zeroIsNull === true)) return Buffer.alloc(0) - const isDecimalInput = typeof x === 'number' || typeof x === 'bigint' || (typeof x === 'string' && isBase10NumStr(x)) + if (x === null || ((isZeroNumber || isZeroBigInt) && zeroIsNull === true)) + return Buffer.alloc(0) + const isDecimalInput = + typeof x === 'number' || + typeof x === 'bigint' || + (typeof x === 'string' && isBase10NumStr(x)) let hexString: string if (isDecimalInput) { - const formatted = typeof x === 'bigint' ? x.toString(10) : (x as string | number) + const formatted = + typeof x === 'bigint' ? x.toString(10) : (x as string | number) hexString = new BigNum(formatted).toString(16) } else if (typeof x === 'string' && x.slice(0, 2) === '0x') { hexString = x.slice(2) @@ -175,7 +210,9 @@ export const ensureHexBuffer = (x: string | number | bigint | Buffer, zeroIsNull if (hexString === '00' && !isDecimalInput) return Buffer.alloc(0) return Buffer.from(hexString, 'hex') } catch (_err) { - throw new Error(`Cannot convert ${x.toString()} to hex buffer (${(_err as Error).message})`) + throw new Error( + `Cannot convert ${x.toString()} to hex buffer (${(_err as Error).message})`, + ) } } @@ -210,7 +247,8 @@ export const aes256_decrypt = (data: Buffer, key: Buffer): Buffer => { // Decode a DER signature. Returns signature object {r, s } or null if there is an error /** @internal */ export const parseDER = (sigBuf: Buffer) => { - if (sigBuf[0] !== 0x30 || sigBuf[2] !== 0x02) throw new Error('Failed to decode DER signature') + if (sigBuf[0] !== 0x30 || sigBuf[2] !== 0x02) + throw new Error('Failed to decode DER signature') let off = 3 const rLen = sigBuf[off] off++ @@ -239,11 +277,18 @@ export const getP256KeyPairFromPub = (pub: Buffer | string): any => { } /** @internal */ -export const buildSignerPathBuf = (signerPath: number[], varAddrPathSzAllowed: boolean): Buffer => { +export const buildSignerPathBuf = ( + signerPath: number[], + varAddrPathSzAllowed: boolean, +): Buffer => { const buf = Buffer.alloc(24) let off = 0 - if (varAddrPathSzAllowed && signerPath.length > 5) throw new Error('Signer path must be <=5 indices.') - if (!varAddrPathSzAllowed && signerPath.length !== 5) throw new Error('Your Lattice firmware only supports 5-index derivation paths. Please upgrade.') + if (varAddrPathSzAllowed && signerPath.length > 5) + throw new Error('Signer path must be <=5 indices.') + if (!varAddrPathSzAllowed && signerPath.length !== 5) + throw new Error( + 'Your Lattice firmware only supports 5-index derivation paths. Please upgrade.', + ) buf.writeUInt32LE(signerPath.length, off) off += 4 for (let i = 0; i < 5; i++) { @@ -278,7 +323,8 @@ export const isAsciiStr = (str: string, allowFormatChars = false): boolean => { } /** @internal Check if a value exists in an object. Only checks first level of keys. */ -export const existsIn = (val: T, obj: { [key: string]: T }): boolean => Object.keys(obj).some((key) => obj[key] === val) +export const existsIn = (val: T, obj: { [key: string]: T }): boolean => + Object.keys(obj).some((key) => obj[key] === val) /** @internal Create a buffer of size `n` and fill it with random data */ export const randomBytes = (n: number): Buffer => { @@ -296,7 +342,9 @@ export const isUInt4 = (n: number) => isInteger(n) && inRange(n, 0, 16) * Fetches an external JSON file containing networks indexed by chain id from a GridPlus repo, and * returns the parsed JSON. */ -async function fetchExternalNetworkForChainId(chainId: number | string): Promise<{ +async function fetchExternalNetworkForChainId( + chainId: number | string, +): Promise<{ [key: string]: { name: string baseUrl: string @@ -304,7 +352,9 @@ async function fetchExternalNetworkForChainId(chainId: number | string): Promise } }> { try { - const body = await fetch(EXTERNAL_NETWORKS_BY_CHAIN_ID_URL).then((res) => res.json()) + const body = await fetch(EXTERNAL_NETWORKS_BY_CHAIN_ID_URL).then((res) => + res.json(), + ) if (body) { return body[chainId] } else { @@ -346,7 +396,10 @@ export function selectDefFrom4byteABI(abiData: any[], selector: string) { }) .find((result) => { try { - def = Calldata.EVM.parsers.parseCanonicalName(selector, result.text_signature) + def = Calldata.EVM.parsers.parseCanonicalName( + selector, + result.text_signature, + ) return !!def } catch (_err) { console.error('Failed to parse canonical name:', _err) @@ -360,7 +413,10 @@ export function selectDefFrom4byteABI(abiData: any[], selector: string) { } } -export async function fetchWithTimeout(url: string, options: RequestInit & { timeout?: number }): Promise { +export async function fetchWithTimeout( + url: string, + options: RequestInit & { timeout?: number }, +): Promise { const { timeout = 8000 } = options const controller = new AbortController() const timeoutId = setTimeout(() => controller.abort(), timeout) @@ -372,7 +428,10 @@ export async function fetchWithTimeout(url: string, options: RequestInit & { tim return response } -async function fetchAndCache(url: string, opts?: RequestInit): Promise { +async function fetchAndCache( + url: string, + opts?: RequestInit, +): Promise { try { if (globalThis.caches && globalThis.Request) { const cache = await caches.open('gp-calldata') @@ -384,7 +443,10 @@ async function fetchAndCache(url: string, opts?: RequestInit): Promise const response = await fetch(request, opts) const responseClone = response.clone() const data = await response.json() - if (response.ok && (isValidBlockExplorerResponse(data) || isValid4ByteResponse(data))) { + if ( + response.ok && + (isValidBlockExplorerResponse(data) || isValid4ByteResponse(data)) + ) { await cache.put(request, responseClone) return cache.match(request) } @@ -399,7 +461,10 @@ async function fetchAndCache(url: string, opts?: RequestInit): Promise } } -async function fetchSupportedChainData(address: string, supportedChain: number) { +async function fetchSupportedChainData( + address: string, + supportedChain: number, +) { const url = buildUrlForSupportedChainAndAddress({ address, supportedChain }) return fetchAndCache(url) .then((res) => res.json()) @@ -408,7 +473,9 @@ async function fetchSupportedChainData(address: string, supportedChain: number) try { return JSON.parse(body.result) } catch { - throw new Error(`Invalid JSON in response: ${body.result.substring(0, 50)}`) + throw new Error( + `Invalid JSON in response: ${body.result.substring(0, 50)}`, + ) } } else { throw new Error('Server response was malformed') @@ -453,7 +520,10 @@ async function postProcessDef(def, calldata) { // value (or for `bytes[]` each underlying value) is of size (4 + 32*n) // it could be nested calldata. We should use that item's selector(s) // to look up nested definition(s). - const nestedCalldata = Calldata.EVM.processors.getNestedCalldata(def, calldata) + const nestedCalldata = Calldata.EVM.processors.getNestedCalldata( + def, + calldata, + ) const nestedDefs = await replaceNestedDefs(nestedCalldata) // Need to recurse before doing the full replacement for await (const [i] of nestedDefs.entries()) { @@ -463,11 +533,17 @@ async function postProcessDef(def, calldata) { if (Array.isArray(nestedDefs[i]) && typeof nestedDefs[i][0] !== 'string') { for await (const [j] of nestedDefs[i].entries()) { if (nestedDefs[i][j] !== null) { - nestedDefs[i][j] = await postProcessDef(nestedDefs[i][j], Buffer.from(nestedCalldata[i][j].slice(2), 'hex')) + nestedDefs[i][j] = await postProcessDef( + nestedDefs[i][j], + Buffer.from(nestedCalldata[i][j].slice(2), 'hex'), + ) } } } else if (nestedDefs[i] !== null) { - nestedDefs[i] = await postProcessDef(nestedDefs[i], Buffer.from(nestedCalldata[i].slice(2), 'hex')) + nestedDefs[i] = await postProcessDef( + nestedDefs[i], + Buffer.from(nestedCalldata[i].slice(2), 'hex'), + ) } } // Replace any nested defs @@ -500,7 +576,10 @@ async function replaceNestedDefs(possNestedDefs) { try { const _nestedSelector = _d.slice(2, 10) const _nestedAbi = await fetch4byteData(_nestedSelector) - const _nestedDef = selectDefFrom4byteABI(_nestedAbi, _nestedSelector) + const _nestedDef = selectDefFrom4byteABI( + _nestedAbi, + _nestedSelector, + ) _nestedDefs.push(_nestedDef) } catch (_err) { console.error('Failed to fetch nested 4byte data:', _err) @@ -540,7 +619,12 @@ async function replaceNestedDefs(possNestedDefs) { /** * Fetches calldata from a remote scanner based on the transaction's `chainId` */ -export async function fetchCalldataDecoder(_data: Uint8Array | string, to: string, _chainId: number | string, recurse = true) { +export async function fetchCalldataDecoder( + _data: Uint8Array | string, + to: string, + _chainId: number | string, + recurse = true, +) { try { // Exit if there is no data. The 2 comes from the 0x prefix, but a later // check will confirm that there are at least 4 bytes of data in the buffer. @@ -559,18 +643,25 @@ export async function fetchCalldataDecoder(_data: Uint8Array | string, to: strin } if (data.length < 4) { - throw new Error('Data must contain at least 4 bytes of data to define the selector') + throw new Error( + 'Data must contain at least 4 bytes of data to define the selector', + ) } const selector = Buffer.from(data.slice(0, 4)).toString('hex') // Convert the chainId to a number and use it to determine if we can call out to // an etherscan-like explorer for richer data. const chainId = Number(_chainId) const cachedNetwork = NETWORKS_BY_CHAIN_ID[chainId] - const supportedChain = cachedNetwork ? cachedNetwork : await fetchExternalNetworkForChainId(chainId) + const supportedChain = cachedNetwork + ? cachedNetwork + : await fetchExternalNetworkForChainId(chainId) try { if (supportedChain) { const abi = await fetchSupportedChainData(to, supportedChain) - const parsedAbi = Calldata.EVM.parsers.parseSolidityJSONABI(selector, abi) + const parsedAbi = Calldata.EVM.parsers.parseSolidityJSONABI( + selector, + abi, + ) let def = parsedAbi.def if (recurse) { def = await postProcessDef(def, data) @@ -605,12 +696,23 @@ export async function fetchCalldataDecoder(_data: Uint8Array | string, to: strin * @returns an application secret as a Buffer * @public */ -export const generateAppSecret = (deviceId: Buffer | string, password: Buffer | string, appName: Buffer | string): Buffer => { - const deviceIdBuffer = typeof deviceId === 'string' ? Buffer.from(deviceId) : deviceId - const passwordBuffer = typeof password === 'string' ? Buffer.from(password) : password - const appNameBuffer = typeof appName === 'string' ? Buffer.from(appName) : appName - - const preImage = Buffer.concat([deviceIdBuffer, passwordBuffer, appNameBuffer]) +export const generateAppSecret = ( + deviceId: Buffer | string, + password: Buffer | string, + appName: Buffer | string, +): Buffer => { + const deviceIdBuffer = + typeof deviceId === 'string' ? Buffer.from(deviceId) : deviceId + const passwordBuffer = + typeof password === 'string' ? Buffer.from(password) : password + const appNameBuffer = + typeof appName === 'string' ? Buffer.from(appName) : appName + + const preImage = Buffer.concat([ + deviceIdBuffer, + passwordBuffer, + appNameBuffer, + ]) return Buffer.from(Hash.sha256(preImage)) } @@ -628,7 +730,9 @@ export const getV = (tx: any, resp: any) => { let useEIP155 = false if (Buffer.isBuffer(tx) || typeof tx === 'string') { - const txHex = Buffer.isBuffer(tx) ? (`0x${tx.toString('hex')}` as Hex) : (tx as Hex) + const txHex = Buffer.isBuffer(tx) + ? (`0x${tx.toString('hex')}` as Hex) + : (tx as Hex) const txBuf = Buffer.isBuffer(tx) ? tx : Buffer.from(tx.slice(2), 'hex') hash = Buffer.from(Hash.keccak256(txBuf)) @@ -657,7 +761,9 @@ export const getV = (tx: any, resp: any) => { } catch (err) { console.error('Failed to parse transaction, trying legacy format:', err) try { - const txBufRaw = Buffer.isBuffer(tx) ? tx : Buffer.from(tx.slice(2), 'hex') + const txBufRaw = Buffer.isBuffer(tx) + ? tx + : Buffer.from(tx.slice(2), 'hex') const legacyTxArray = RLP.decode(txBufRaw) type = 'legacy' @@ -673,11 +779,17 @@ export const getV = (tx: any, resp: any) => { } } } else { - throw new Error('Unsupported transaction format. Expected Buffer or hex string.') + throw new Error( + 'Unsupported transaction format. Expected Buffer or hex string.', + ) } - const rBuf = Buffer.isBuffer(resp.sig.r) ? resp.sig.r : Buffer.from(resp.sig.r.slice(2), 'hex') - const sBuf = Buffer.isBuffer(resp.sig.s) ? resp.sig.s : Buffer.from(resp.sig.s.slice(2), 'hex') + const rBuf = Buffer.isBuffer(resp.sig.r) + ? resp.sig.r + : Buffer.from(resp.sig.r.slice(2), 'hex') + const sBuf = Buffer.isBuffer(resp.sig.s) + ? resp.sig.s + : Buffer.from(resp.sig.s.slice(2), 'hex') const rs = new Uint8Array(Buffer.concat([rBuf, sBuf])) const pubkeyInput = resp.pubkey @@ -691,7 +803,9 @@ export const getV = (tx: any, resp: any) => { } else if (pubkeyInput instanceof Uint8Array) { pubkeyBuf = Buffer.from(pubkeyInput) } else if (typeof pubkeyInput === 'string') { - const hex = pubkeyInput.startsWith('0x') ? pubkeyInput.slice(2) : pubkeyInput + const hex = pubkeyInput.startsWith('0x') + ? pubkeyInput.slice(2) + : pubkeyInput pubkeyBuf = Buffer.from(hex, 'hex') } else { pubkeyBuf = Buffer.from(pubkeyInput) @@ -701,7 +815,8 @@ export const getV = (tx: any, resp: any) => { pubkeyBuf = Buffer.concat([Buffer.from([0x04]), pubkeyBuf]) } - const isCompressedPubkey = pubkeyBuf.length === 33 && (pubkeyBuf[0] === 0x02 || pubkeyBuf[0] === 0x03) + const isCompressedPubkey = + pubkeyBuf.length === 33 && (pubkeyBuf[0] === 0x02 || pubkeyBuf[0] === 0x03) const isUncompressedPubkey = pubkeyBuf.length === 65 && pubkeyBuf[0] === 0x04 if (!isCompressedPubkey && !isUncompressedPubkey) { @@ -721,7 +836,9 @@ export const getV = (tx: any, resp: any) => { } else if (pubkeyStr === recovery1Str) { recovery = 1 } else { - throw new Error('Failed to recover V parameter. Bad signature or transaction data.') + throw new Error( + 'Failed to recover V parameter. Bad signature or transaction data.', + ) } // Use the consolidated v parameter conversion logic @@ -752,13 +869,23 @@ export const getV = (tx: any, resp: any) => { * @param txData - Transaction data containing chainId, useEIP155, and type * @returns The properly formatted v value as Buffer or BN */ -export const convertRecoveryToV = (recovery: number, txData: any = {}): Buffer | InstanceType => { +export const convertRecoveryToV = ( + recovery: number, + txData: any = {}, +): Buffer | InstanceType => { const { chainId, useEIP155, type } = txData // For typed transactions (EIP-2930, EIP-1559, EIP-7702), we want the recoveryParam (0 or 1) // rather than the `v` value because the `chainId` is already included in the // transaction payload. - if (type === 1 || type === 2 || type === 4 || type === 'eip2930' || type === 'eip1559' || type === 'eip7702') { + if ( + type === 1 || + type === 2 || + type === 4 || + type === 'eip2930' || + type === 'eip1559' || + type === 'eip7702' + ) { return ensureHexBuffer(recovery, true) // 0 or 1, with 0 expected as an empty buffer } else if (!useEIP155 || !chainId) { // For ETH messages and non-EIP155 chains the set should be [27, 28] for `v` @@ -785,10 +912,27 @@ export const convertRecoveryToV = (recovery: number, txData: any = {}): Buffer | * @param publicKey - Expected public key * @returns 0 or 1 for the y-parity value */ -export const getYParity = (messageHash: Buffer | Uint8Array | string | { messageHash: any; signature: any; publicKey: any } | any, signature?: { r: any; s: any } | any, publicKey?: Buffer | Uint8Array | string): number => { +export const getYParity = ( + messageHash: + | Buffer + | Uint8Array + | string + | { messageHash: any; signature: any; publicKey: any } + | any, + signature?: { r: any; s: any } | any, + publicKey?: Buffer | Uint8Array | string, +): number => { // Handle legacy object format for backward compatibility - if (typeof messageHash === 'object' && messageHash && 'messageHash' in messageHash) { - return getYParity(messageHash.messageHash, messageHash.signature, messageHash.publicKey) + if ( + typeof messageHash === 'object' && + messageHash && + 'messageHash' in messageHash + ) { + return getYParity( + messageHash.messageHash, + messageHash.signature, + messageHash.publicKey, + ) } // Handle legacy transaction format for backward compatibility @@ -807,7 +951,11 @@ export const getYParity = (messageHash: Buffer | Uint8Array | string | { message // Handle transaction objects with getMessageToSign let hash = messageHash - if (typeof messageHash === 'object' && messageHash && typeof messageHash.getMessageToSign === 'function') { + if ( + typeof messageHash === 'object' && + messageHash && + typeof messageHash.getMessageToSign === 'function' + ) { const type = messageHash._type if (type !== undefined && type !== null) { // EIP-1559 / EIP-2930 / future typed transactions @@ -839,7 +987,8 @@ export const getYParity = (messageHash: Buffer | Uint8Array | string | { message const pubkeyBuf = toBuffer(publicKey) // For non-32 byte hashes, hash them (legacy support) - const finalHash = hashBuf.length === 32 ? hashBuf : Buffer.from(Hash.keccak256(hashBuf)) + const finalHash = + hashBuf.length === 32 ? hashBuf : Buffer.from(Hash.keccak256(hashBuf)) // Combine r and s const rs = new Uint8Array(Buffer.concat([rBuf, sBuf])) @@ -856,7 +1005,9 @@ export const getYParity = (messageHash: Buffer | Uint8Array | string | { message } catch {} } - throw new Error('Failed to recover Y parity. Bad signature or transaction data.') + throw new Error( + 'Failed to recover Y parity. Bad signature or transaction data.', + ) } /** @internal */ From be921f0fc40a58c2857517c44b08ac1dee047e84 Mon Sep 17 00:00:00 2001 From: netbonus <151201453+netbonus@users.noreply.github.com> Date: Wed, 21 Jan 2026 15:20:21 -0500 Subject: [PATCH 07/14] style: switch from tabs to spaces (2-space indent) Convert all indentation from tabs to 2-space indent for consistency. --- biome.json | 153 +- .../components/HomepageFeatures.module.css | 12 +- .../docs/src/components/HomepageFeatures.tsx | 110 +- packages/docs/src/css/custom.css | 26 +- packages/docs/src/pages/_index.tsx | 60 +- packages/docs/src/pages/index.module.css | 20 +- packages/example/src/App.tsx | 174 +- packages/example/src/Button.tsx | 24 +- packages/example/src/Lattice.tsx | 224 +- packages/example/src/index.css | 88 +- packages/example/src/main.tsx | 6 +- packages/sdk/biome.json | 12 +- packages/sdk/src/__test__/e2e/api.test.ts | 686 +- packages/sdk/src/__test__/e2e/btc.test.ts | 306 +- packages/sdk/src/__test__/e2e/eth.msg.test.ts | 2618 ++++---- .../__test__/e2e/ethereum/addresses.test.ts | 24 +- packages/sdk/src/__test__/e2e/general.test.ts | 602 +- packages/sdk/src/__test__/e2e/kv.test.ts | 426 +- .../src/__test__/e2e/non-exportable.test.ts | 284 +- .../sdk/src/__test__/e2e/signing/bls.test.ts | 344 +- .../__test__/e2e/signing/determinism.test.ts | 926 +-- .../__test__/e2e/signing/eip712-msg.test.ts | 16 +- .../__test__/e2e/signing/eip712-vectors.ts | 612 +- .../src/__test__/e2e/signing/evm-abi.test.ts | 30 +- .../src/__test__/e2e/signing/evm-tx.test.ts | 86 +- .../e2e/signing/solana/__mocks__/programs.ts | 164 +- .../signing/solana/solana.programs.test.ts | 60 +- .../e2e/signing/solana/solana.test.ts | 222 +- .../signing/solana/solana.versioned.test.ts | 358 +- .../__test__/e2e/signing/unformatted.test.ts | 312 +- .../sdk/src/__test__/e2e/signing/vectors.ts | 1916 +++--- .../src/__test__/e2e/solana/addresses.test.ts | 66 +- packages/sdk/src/__test__/e2e/xpub.test.ts | 42 +- .../__test__/integration/__mocks__/4byte.ts | 156 +- .../integration/__mocks__/addKvRecords.json | 4 +- .../integration/__mocks__/connect.json | 4 +- .../integration/__mocks__/etherscan.ts | 5726 ++++++++--------- .../__mocks__/fetchActiveWallet.json | 4 +- .../integration/__mocks__/getAddresses.json | 4 +- .../integration/__mocks__/getKvRecords.json | 4 +- .../integration/__mocks__/handlers.ts | 166 +- .../__mocks__/removeKvRecords.json | 4 +- .../__test__/integration/__mocks__/setup.ts | 6 +- .../__test__/integration/__mocks__/sign.json | 4 +- .../integration/client.interop.test.ts | 22 +- .../src/__test__/integration/connect.test.ts | 108 +- .../integration/fetchCalldataDecoder.test.ts | 160 +- .../__test__/unit/__mocks__/decoderData.ts | 136 +- packages/sdk/src/__test__/unit/api.test.ts | 110 +- .../unit/compareEIP7702Serialization.test.ts | 790 +-- .../sdk/src/__test__/unit/decoders.test.ts | 128 +- .../sdk/src/__test__/unit/eip7702.test.ts | 328 +- .../sdk/src/__test__/unit/encoders.test.ts | 210 +- .../__test__/unit/ethereum.validate.test.ts | 138 +- .../src/__test__/unit/module.interop.test.ts | 116 +- .../unit/parseGenericSigningResponse.test.ts | 324 +- .../unit/personalSignValidation.test.ts | 262 +- .../unit/selectDefFrom4byteABI.test.ts | 144 +- .../src/__test__/unit/signatureUtils.test.ts | 874 +-- .../sdk/src/__test__/unit/validators.test.ts | 588 +- .../__test__/utils/__test__/builders.test.ts | 30 +- .../utils/__test__/serializers.test.ts | 64 +- packages/sdk/src/__test__/utils/builders.ts | 538 +- .../sdk/src/__test__/utils/determinism.ts | 106 +- packages/sdk/src/__test__/utils/ethers.ts | 254 +- packages/sdk/src/__test__/utils/getters.ts | 6 +- packages/sdk/src/__test__/utils/helpers.ts | 1800 +++--- packages/sdk/src/__test__/utils/runners.ts | 54 +- .../sdk/src/__test__/utils/serializers.ts | 40 +- packages/sdk/src/__test__/utils/setup.ts | 80 +- .../sdk/src/__test__/utils/testConstants.ts | 2 +- .../sdk/src/__test__/utils/testEnvironment.ts | 14 +- .../sdk/src/__test__/utils/testRequest.ts | 52 +- .../sdk/src/__test__/utils/viemComparison.ts | 408 +- packages/sdk/src/__test__/utils/vitest.d.ts | 10 +- packages/sdk/src/__test__/vectors.jsonc | 464 +- .../sdk/src/__test__/vectors/abi-vectors.ts | 270 +- packages/sdk/src/api/addressTags.ts | 62 +- packages/sdk/src/api/addresses.ts | 384 +- packages/sdk/src/api/index.ts | 6 +- packages/sdk/src/api/setup.ts | 88 +- packages/sdk/src/api/signing.ts | 392 +- packages/sdk/src/api/state.ts | 8 +- packages/sdk/src/api/utilities.ts | 154 +- packages/sdk/src/api/wallets.ts | 2 +- packages/sdk/src/bitcoin.ts | 734 +-- packages/sdk/src/calldata/evm.ts | 782 +-- packages/sdk/src/calldata/index.ts | 30 +- packages/sdk/src/client.ts | 806 +-- packages/sdk/src/constants.ts | 1008 +-- packages/sdk/src/ethereum.ts | 2474 +++---- packages/sdk/src/functions/addKvRecords.ts | 136 +- packages/sdk/src/functions/connect.ts | 198 +- .../sdk/src/functions/fetchActiveWallet.ts | 106 +- packages/sdk/src/functions/fetchDecoder.ts | 40 +- packages/sdk/src/functions/fetchEncData.ts | 340 +- packages/sdk/src/functions/getAddresses.ts | 360 +- packages/sdk/src/functions/getKvRecords.ts | 200 +- packages/sdk/src/functions/pair.ts | 90 +- packages/sdk/src/functions/removeKvRecords.ts | 130 +- packages/sdk/src/functions/sign.ts | 530 +- packages/sdk/src/genericSigning.ts | 802 +-- packages/sdk/src/protocol/latticeConstants.ts | 332 +- packages/sdk/src/protocol/secureMessages.ts | 472 +- packages/sdk/src/schemas/transaction.ts | 376 +- packages/sdk/src/shared/errors.ts | 52 +- packages/sdk/src/shared/functions.ts | 286 +- packages/sdk/src/shared/predicates.ts | 12 +- packages/sdk/src/shared/utilities.ts | 148 +- packages/sdk/src/shared/validators.ts | 348 +- packages/sdk/src/types/addKvRecords.ts | 10 +- packages/sdk/src/types/client.ts | 88 +- packages/sdk/src/types/connect.ts | 4 +- packages/sdk/src/types/declarations.d.ts | 22 +- packages/sdk/src/types/fetchActiveWallet.ts | 4 +- packages/sdk/src/types/fetchEncData.ts | 26 +- packages/sdk/src/types/firmware.ts | 100 +- packages/sdk/src/types/getAddresses.ts | 12 +- packages/sdk/src/types/getKvRecords.ts | 26 +- packages/sdk/src/types/index.ts | 52 +- packages/sdk/src/types/messages.ts | 30 +- packages/sdk/src/types/pair.ts | 4 +- packages/sdk/src/types/removeKvRecords.ts | 8 +- packages/sdk/src/types/secureMessages.ts | 72 +- packages/sdk/src/types/shared.ts | 10 +- packages/sdk/src/types/sign.ts | 230 +- packages/sdk/src/types/utils.ts | 42 +- packages/sdk/src/types/vitest.d.ts | 10 +- packages/sdk/src/util.ts | 1536 ++--- 129 files changed, 19948 insertions(+), 19947 deletions(-) diff --git a/biome.json b/biome.json index db22f803..7ce59fb0 100644 --- a/biome.json +++ b/biome.json @@ -1,78 +1,79 @@ { - "$schema": "https://biomejs.dev/schemas/2.2.4/schema.json", - "vcs": { - "enabled": true, - "clientKind": "git", - "useIgnoreFile": true - }, - "formatter": { - "enabled": true, - "formatWithErrors": false, - "indentWidth": 2, - "lineEnding": "lf", - "attributePosition": "auto" - }, - "linter": { - "enabled": true, - "rules": { - "recommended": true, - "complexity": { - "noForEach": "warn" - }, - "correctness": { - "noUnusedImports": { - "level": "error" - }, - "noUnusedVariables": { - "level": "warn", - "fix": "none" - }, - "noUnusedFunctionParameters": { - "level": "warn", - "fix": "none" - } - }, - "style": { - "noParameterAssign": "warn", - "noNonNullAssertion": "error", - "noUselessElse": "warn", - "noUnusedTemplateLiteral": "error", - "useAsConstAssertion": "error", - "useDefaultParameterLast": "error", - "useEnumInitializers": "error", - "useSingleVarDeclarator": "error", - "useNumberNamespace": "error", - "noInferrableTypes": "error" - }, - "performance": { - "noAccumulatingSpread": "warn" - }, - "suspicious": { - "noArrayIndexKey": "warn", - "noAssignInExpressions": "warn", - "noExplicitAny": "warn" - } - } - }, - "organizeImports": { - "enabled": false - }, - "files": { - "ignoreUnknown": true, - "include": ["packages/*/src/**"], - "ignore": ["**/dist/**", "**/node_modules/**", "**/coverage/**"] - }, - "javascript": { - "formatter": { - "jsxQuoteStyle": "double", - "quoteProperties": "asNeeded", - "trailingCommas": "all", - "semicolons": "asNeeded", - "arrowParentheses": "always", - "bracketSpacing": true, - "bracketSameLine": false, - "quoteStyle": "single", - "attributePosition": "auto" - } - } + "$schema": "https://biomejs.dev/schemas/2.2.4/schema.json", + "vcs": { + "enabled": true, + "clientKind": "git", + "useIgnoreFile": true + }, + "formatter": { + "enabled": true, + "formatWithErrors": false, + "indentStyle": "space", + "indentWidth": 2, + "lineEnding": "lf", + "attributePosition": "auto" + }, + "linter": { + "enabled": true, + "rules": { + "recommended": true, + "complexity": { + "noForEach": "warn" + }, + "correctness": { + "noUnusedImports": { + "level": "error" + }, + "noUnusedVariables": { + "level": "warn", + "fix": "none" + }, + "noUnusedFunctionParameters": { + "level": "warn", + "fix": "none" + } + }, + "style": { + "noParameterAssign": "warn", + "noNonNullAssertion": "error", + "noUselessElse": "warn", + "noUnusedTemplateLiteral": "error", + "useAsConstAssertion": "error", + "useDefaultParameterLast": "error", + "useEnumInitializers": "error", + "useSingleVarDeclarator": "error", + "useNumberNamespace": "error", + "noInferrableTypes": "error" + }, + "performance": { + "noAccumulatingSpread": "warn" + }, + "suspicious": { + "noArrayIndexKey": "warn", + "noAssignInExpressions": "warn", + "noExplicitAny": "warn" + } + } + }, + "organizeImports": { + "enabled": false + }, + "files": { + "ignoreUnknown": true, + "include": ["packages/*/src/**"], + "ignore": ["**/dist/**", "**/node_modules/**", "**/coverage/**"] + }, + "javascript": { + "formatter": { + "jsxQuoteStyle": "double", + "quoteProperties": "asNeeded", + "trailingCommas": "all", + "semicolons": "asNeeded", + "arrowParentheses": "always", + "bracketSpacing": true, + "bracketSameLine": false, + "quoteStyle": "single", + "attributePosition": "auto" + } + } } diff --git a/packages/docs/src/components/HomepageFeatures.module.css b/packages/docs/src/components/HomepageFeatures.module.css index 774404de..b248eb2e 100644 --- a/packages/docs/src/components/HomepageFeatures.module.css +++ b/packages/docs/src/components/HomepageFeatures.module.css @@ -1,11 +1,11 @@ .features { - display: flex; - align-items: center; - padding: 2rem 0; - width: 100%; + display: flex; + align-items: center; + padding: 2rem 0; + width: 100%; } .featureSvg { - height: 200px; - width: 200px; + height: 200px; + width: 200px; } diff --git a/packages/docs/src/components/HomepageFeatures.tsx b/packages/docs/src/components/HomepageFeatures.tsx index 603dda5b..8ca21f72 100644 --- a/packages/docs/src/components/HomepageFeatures.tsx +++ b/packages/docs/src/components/HomepageFeatures.tsx @@ -2,68 +2,68 @@ import clsx from 'clsx' import styles from './HomepageFeatures.module.css' type FeatureItem = { - title: string - image: string - description: JSX.Element + title: string + image: string + description: JSX.Element } const FeatureList: FeatureItem[] = [ - { - title: 'Easy to Use', - image: '/img/undraw_docusaurus_mountain.svg', - description: ( - <> - Docusaurus was designed from the ground up to be easily installed and - used to get your website up and running quickly. - - ), - }, - { - title: 'Focus on What Matters', - image: '/img/undraw_docusaurus_tree.svg', - description: ( - <> - Docusaurus lets you focus on your docs, and we'll do the chores. Go - ahead and move your docs into the docs directory. - - ), - }, - { - title: 'Powered by React', - image: '/img/undraw_docusaurus_react.svg', - description: ( - <> - Extend or customize your website layout by reusing React. Docusaurus can - be extended while reusing the same header and footer. - - ), - }, + { + title: 'Easy to Use', + image: '/img/undraw_docusaurus_mountain.svg', + description: ( + <> + Docusaurus was designed from the ground up to be easily installed and + used to get your website up and running quickly. + + ), + }, + { + title: 'Focus on What Matters', + image: '/img/undraw_docusaurus_tree.svg', + description: ( + <> + Docusaurus lets you focus on your docs, and we'll do the chores. Go + ahead and move your docs into the docs directory. + + ), + }, + { + title: 'Powered by React', + image: '/img/undraw_docusaurus_react.svg', + description: ( + <> + Extend or customize your website layout by reusing React. Docusaurus can + be extended while reusing the same header and footer. + + ), + }, ] function Feature({ title, image, description }: FeatureItem) { - return ( -
    -
    - {title} -
    -
    -

    {title}

    -

    {description}

    -
    -
    - ) + return ( +
    +
    + {title} +
    +
    +

    {title}

    +

    {description}

    +
    +
    + ) } export default function HomepageFeatures(): JSX.Element { - return ( -
    -
    -
    - {FeatureList.map((props, idx) => ( - - ))} -
    -
    -
    - ) + return ( +
    +
    +
    + {FeatureList.map((props, idx) => ( + + ))} +
    +
    +
    + ) } diff --git a/packages/docs/src/css/custom.css b/packages/docs/src/css/custom.css index 1e29b327..efa9f15e 100644 --- a/packages/docs/src/css/custom.css +++ b/packages/docs/src/css/custom.css @@ -6,23 +6,23 @@ /* You can override the default Infima variables here. */ :root { - --ifm-color-primary: #53b7e8; - --ifm-color-primary-dark: rgb(33, 175, 144); - --ifm-color-primary-darker: rgb(31, 165, 136); - --ifm-color-primary-darkest: rgb(26, 136, 112); - --ifm-color-primary-light: rgb(70, 203, 174); - --ifm-color-primary-lighter: rgb(102, 212, 189); - --ifm-color-primary-lightest: rgb(146, 224, 208); - --ifm-code-font-size: 95%; + --ifm-color-primary: #53b7e8; + --ifm-color-primary-dark: rgb(33, 175, 144); + --ifm-color-primary-darker: rgb(31, 165, 136); + --ifm-color-primary-darkest: rgb(26, 136, 112); + --ifm-color-primary-light: rgb(70, 203, 174); + --ifm-color-primary-lighter: rgb(102, 212, 189); + --ifm-color-primary-lightest: rgb(146, 224, 208); + --ifm-code-font-size: 95%; } .docusaurus-highlight-code-line { - background-color: rgba(0, 0, 0, 0.1); - display: block; - margin: 0 calc(-1 * var(--ifm-pre-padding)); - padding: 0 var(--ifm-pre-padding); + background-color: rgba(0, 0, 0, 0.1); + display: block; + margin: 0 calc(-1 * var(--ifm-pre-padding)); + padding: 0 var(--ifm-pre-padding); } html[data-theme="dark"] .docusaurus-highlight-code-line { - background-color: rgba(0, 0, 0, 0.3); + background-color: rgba(0, 0, 0, 0.3); } diff --git a/packages/docs/src/pages/_index.tsx b/packages/docs/src/pages/_index.tsx index b0f833fe..e89503ce 100644 --- a/packages/docs/src/pages/_index.tsx +++ b/packages/docs/src/pages/_index.tsx @@ -6,40 +6,40 @@ import clsx from 'clsx' import styles from './index.module.css' interface ExtendedLayoutProps extends LayoutProps { - title?: string - description?: string + title?: string + description?: string } function HomepageHeader() { - const { siteConfig } = useDocusaurusContext() - return ( -
    -
    -

    {siteConfig.title}

    -

    {siteConfig.tagline}

    -
    - - Getting Started - -
    -
    -
    - ) + const { siteConfig } = useDocusaurusContext() + return ( +
    +
    +

    {siteConfig.title}

    +

    {siteConfig.tagline}

    +
    + + Getting Started + +
    +
    +
    + ) } export default function Home(): JSX.Element { - const { siteConfig } = useDocusaurusContext() - const ExtendedLayout = Layout as React.ComponentType - return ( - - -
    {/* */}
    -
    - ) + const { siteConfig } = useDocusaurusContext() + const ExtendedLayout = Layout as React.ComponentType + return ( + + +
    {/* */}
    +
    + ) } diff --git a/packages/docs/src/pages/index.module.css b/packages/docs/src/pages/index.module.css index df02ba51..666feb6a 100644 --- a/packages/docs/src/pages/index.module.css +++ b/packages/docs/src/pages/index.module.css @@ -4,20 +4,20 @@ */ .heroBanner { - padding: 4rem 0; - text-align: center; - position: relative; - overflow: hidden; + padding: 4rem 0; + text-align: center; + position: relative; + overflow: hidden; } @media screen and (max-width: 966px) { - .heroBanner { - padding: 2rem; - } + .heroBanner { + padding: 2rem; + } } .buttons { - display: flex; - align-items: center; - justify-content: center; + display: flex; + align-items: center; + justify-content: center; } diff --git a/packages/example/src/App.tsx b/packages/example/src/App.tsx index afbe8e1b..f5b59ed2 100644 --- a/packages/example/src/App.tsx +++ b/packages/example/src/App.tsx @@ -4,100 +4,100 @@ import './App.css' import { Lattice } from './Lattice' function App() { - const [label, setLabel] = useState('No Device') + const [label, setLabel] = useState('No Device') - const getStoredClient = useCallback( - async () => window.localStorage.getItem('storedClient') || '', - [], - ) + const getStoredClient = useCallback( + async () => window.localStorage.getItem('storedClient') || '', + [], + ) - const setStoredClient = useCallback(async (storedClient: string | null) => { - if (!storedClient) return - window.localStorage.setItem('storedClient', storedClient) + const setStoredClient = useCallback(async (storedClient: string | null) => { + if (!storedClient) return + window.localStorage.setItem('storedClient', storedClient) - const client = await getClient() - setLabel(client?.getDeviceId() || 'No Device') - }, []) + const client = await getClient() + setLabel(client?.getDeviceId() || 'No Device') + }, []) - useEffect(() => { - const initClient = async () => { - const storedClient = await getStoredClient() - if (storedClient) { - await setup({ getStoredClient, setStoredClient }) - } - } - initClient() - }, [getStoredClient, setStoredClient]) + useEffect(() => { + const initClient = async () => { + const storedClient = await getStoredClient() + if (storedClient) { + await setup({ getStoredClient, setStoredClient }) + } + } + initClient() + }, [getStoredClient, setStoredClient]) - const submitInit = (e: any) => { - e.preventDefault() - const deviceId = e.currentTarget[0].value - const password = e.currentTarget[1].value - const name = e.currentTarget[2].value - setup({ - deviceId, - password, - name, - getStoredClient, - setStoredClient, - }) - } + const submitInit = (e: any) => { + e.preventDefault() + const deviceId = e.currentTarget[0].value + const password = e.currentTarget[1].value + const name = e.currentTarget[2].value + setup({ + deviceId, + password, + name, + getStoredClient, + setStoredClient, + }) + } - const submitPair = (e: React.FormEvent) => { - e.preventDefault() - // @ts-expect-error - bad html types - const pairingCode = e.currentTarget[0].value.toUpperCase() - pair(pairingCode) - } + const submitPair = (e: React.FormEvent) => { + e.preventDefault() + // @ts-expect-error - bad html types + const pairingCode = e.currentTarget[0].value.toUpperCase() + pair(pairingCode) + } - return ( -
    -

    EXAMPLE APP

    -
    -
    -
    - - - - -
    -
    -
    -
    - - -
    -
    - -
    -
    - ) + return ( +
    +

    EXAMPLE APP

    +
    +
    +
    + + + + +
    +
    +
    +
    + + +
    +
    + +
    +
    + ) } export default App diff --git a/packages/example/src/Button.tsx b/packages/example/src/Button.tsx index 66a0147c..30f2a500 100644 --- a/packages/example/src/Button.tsx +++ b/packages/example/src/Button.tsx @@ -1,20 +1,20 @@ import { type ReactNode, useState } from 'react' interface ButtonProps { - onClick: () => Promise - children: ReactNode + onClick: () => Promise + children: ReactNode } export const Button = ({ onClick, children }: ButtonProps) => { - const [isLoading, setIsLoading] = useState(false) + const [isLoading, setIsLoading] = useState(false) - const handleOnClick = () => { - setIsLoading(true) - onClick().finally(() => setIsLoading(false)) - } - return ( - - ) + const handleOnClick = () => { + setIsLoading(true) + onClick().finally(() => setIsLoading(false)) + } + return ( + + ) } diff --git a/packages/example/src/Lattice.tsx b/packages/example/src/Lattice.tsx index 140dce15..7f6c9665 100644 --- a/packages/example/src/Lattice.tsx +++ b/packages/example/src/Lattice.tsx @@ -1,126 +1,126 @@ import { useState } from 'react' import { - addAddressTags, - fetchAddressTags, - fetchAddresses, - fetchLedgerLiveAddresses, - removeAddressTags, - sign, - signMessage, - type AddressTag, + addAddressTags, + fetchAddressTags, + fetchAddresses, + fetchLedgerLiveAddresses, + removeAddressTags, + sign, + signMessage, + type AddressTag, } from 'gridplus-sdk' import { Button } from './Button' interface LatticeProps { - label: string + label: string } export const Lattice = ({ label }: LatticeProps) => { - const [addresses, setAddresses] = useState([]) - const [addressTags, setAddressTags] = useState([]) - const [ledgerAddresses, setLedgerAddresses] = useState([]) + const [addresses, setAddresses] = useState([]) + const [addressTags, setAddressTags] = useState([]) + const [ledgerAddresses, setLedgerAddresses] = useState([]) - // Example EIP-1559 transaction payload using raw hex format - const getTxPayload = (): `0x${string}` => { - // Pre-serialized EIP-1559 transaction for example purposes - return '0x02f8620180843b9aca00843b9aca0082c350940000000000000000000000000000000000000000880de0b6b3a764000080c0' - } + // Example EIP-1559 transaction payload using raw hex format + const getTxPayload = (): `0x${string}` => { + // Pre-serialized EIP-1559 transaction for example purposes + return '0x02f8620180843b9aca00843b9aca0082c350940000000000000000000000000000000000000000880de0b6b3a764000080c0' + } - return ( -
    -

    {label}

    - - + return ( +
    +

    {label}

    + + -
    -

    Addresses

    -
      - {addresses?.map((address) => ( -
    • {address}
    • - ))} -
    -
    - - - - -
    -

    Address Tags

    -
      - {addressTags?.map((tag) => ( -
    • - {tag.key}: {tag.val} -
    • - ))} -
    -
    +
    +

    Addresses

    +
      + {addresses?.map((address) => ( +
    • {address}
    • + ))} +
    +
    + + + + +
    +

    Address Tags

    +
      + {addressTags?.map((tag) => ( +
    • + {tag.key}: {tag.val} +
    • + ))} +
    +
    -
    -

    Ledger Addresses

    -
      - {ledgerAddresses?.map((ledgerAddress) => ( -
    • {ledgerAddress}
    • - ))} -
    -
    - -
    - ) +
    +

    Ledger Addresses

    +
      + {ledgerAddresses?.map((ledgerAddress) => ( +
    • {ledgerAddress}
    • + ))} +
    +
    + +
    + ) } diff --git a/packages/example/src/index.css b/packages/example/src/index.css index 4bd3f8e8..c6ec5363 100644 --- a/packages/example/src/index.css +++ b/packages/example/src/index.css @@ -1,73 +1,73 @@ :root { - font-family: Inter, Avenir, Helvetica, Arial, sans-serif; - font-size: 16px; - line-height: 24px; - font-weight: 400; + font-family: Inter, Avenir, Helvetica, Arial, sans-serif; + font-size: 16px; + line-height: 24px; + font-weight: 400; - color-scheme: light dark; - color: rgba(255, 255, 255, 0.87); - background-color: #242424; + color-scheme: light dark; + color: rgba(255, 255, 255, 0.87); + background-color: #242424; - font-synthesis: none; - text-rendering: optimizeLegibility; - -webkit-font-smoothing: antialiased; - -moz-osx-font-smoothing: grayscale; - -webkit-text-size-adjust: 100%; + font-synthesis: none; + text-rendering: optimizeLegibility; + -webkit-font-smoothing: antialiased; + -moz-osx-font-smoothing: grayscale; + -webkit-text-size-adjust: 100%; } #root { - max-width: 1280px; - margin: 0 auto; - padding: 2rem; - text-align: center; + max-width: 1280px; + margin: 0 auto; + padding: 2rem; + text-align: center; } a { - font-weight: 500; - color: #646cff; - text-decoration: inherit; + font-weight: 500; + color: #646cff; + text-decoration: inherit; } a:hover { - color: #535bf2; + color: #535bf2; } body { - margin: 0; - display: flex; - place-items: center; - min-width: 320px; - min-height: 100vh; + margin: 0; + display: flex; + place-items: center; + min-width: 320px; + min-height: 100vh; } h1 { - font-size: 3.2em; - line-height: 1.1; + font-size: 3.2em; + line-height: 1.1; } button { - border-radius: 8px; - border: 1px solid transparent; - padding: 0.6em 1.2em; - font-size: 1em; - font-weight: 500; - font-family: inherit; - background-color: #1a1a1a; - cursor: pointer; - transition: border-color 0.25s; - margin-top: 10px; + border-radius: 8px; + border: 1px solid transparent; + padding: 0.6em 1.2em; + font-size: 1em; + font-weight: 500; + font-family: inherit; + background-color: #1a1a1a; + cursor: pointer; + transition: border-color 0.25s; + margin-top: 10px; } button:hover { - border-color: #646cff; + border-color: #646cff; } button:focus, button:focus-visible { - outline: 4px auto -webkit-focus-ring-color; + outline: 4px auto -webkit-focus-ring-color; } input { - border-radius: 8px; - border: 1px solid transparent; - margin-top: 15px; - font-size: 16px; - padding: 0.6em; + border-radius: 8px; + border: 1px solid transparent; + margin-top: 15px; + font-size: 16px; + padding: 0.6em; } diff --git a/packages/example/src/main.tsx b/packages/example/src/main.tsx index 57939945..791f139e 100644 --- a/packages/example/src/main.tsx +++ b/packages/example/src/main.tsx @@ -4,7 +4,7 @@ import App from './App' import './index.css' ReactDOM.createRoot(document.getElementById('root') as HTMLElement).render( - - - , + + + , ) diff --git a/packages/sdk/biome.json b/packages/sdk/biome.json index 0c057ff6..5fb64252 100644 --- a/packages/sdk/biome.json +++ b/packages/sdk/biome.json @@ -1,8 +1,8 @@ { - "$schema": "https://biomejs.dev/schemas/2.2.4/schema.json", - "extends": ["../../biome.json"], - "files": { - "include": ["src/**/*.ts"], - "ignore": ["**/dist/**", "**/node_modules/**", "**/coverage/**"] - } + "$schema": "https://biomejs.dev/schemas/2.2.4/schema.json", + "extends": ["../../biome.json"], + "files": { + "include": ["src/**/*.ts"], + "ignore": ["**/dist/**", "**/node_modules/**", "**/coverage/**"] + } } diff --git a/packages/sdk/src/__test__/e2e/api.test.ts b/packages/sdk/src/__test__/e2e/api.test.ts index 23662c74..9a26569c 100644 --- a/packages/sdk/src/__test__/e2e/api.test.ts +++ b/packages/sdk/src/__test__/e2e/api.test.ts @@ -1,47 +1,47 @@ vi.mock('../../functions/fetchDecoder.ts', () => ({ - fetchDecoder: vi.fn().mockResolvedValue(undefined), + fetchDecoder: vi.fn().mockResolvedValue(undefined), })) vi.mock('../../util', async () => { - const actual = - await vi.importActual('../../util') - return { - ...actual, - fetchCalldataDecoder: vi.fn().mockResolvedValue({ - def: Buffer.alloc(0), - abi: [ - { - name: 'mockFunction', - type: 'function', - inputs: [], - }, - ], - }), - } + const actual = + await vi.importActual('../../util') + return { + ...actual, + fetchCalldataDecoder: vi.fn().mockResolvedValue({ + def: Buffer.alloc(0), + abi: [ + { + name: 'mockFunction', + type: 'function', + inputs: [], + }, + ], + }), + } }) import { RLP } from '@ethereumjs/rlp' import { - fetchActiveWallets, - fetchAddress, - fetchAddresses, - fetchAddressesByDerivationPath, - fetchBip44ChangeAddresses, - fetchBtcLegacyAddresses, - fetchBtcSegwitAddresses, - fetchSolanaAddresses, - signBtcLegacyTx, - signBtcSegwitTx, - signBtcWrappedSegwitTx, - signMessage, + fetchActiveWallets, + fetchAddress, + fetchAddresses, + fetchAddressesByDerivationPath, + fetchBip44ChangeAddresses, + fetchBtcLegacyAddresses, + fetchBtcSegwitAddresses, + fetchSolanaAddresses, + signBtcLegacyTx, + signBtcSegwitTx, + signBtcWrappedSegwitTx, + signMessage, } from '../../api' import { - addAddressTags, - fetchAddressTags, - fetchLedgerLiveAddresses, - removeAddressTags, - sign, - signSolanaTx, + addAddressTags, + fetchAddressTags, + fetchLedgerLiveAddresses, + removeAddressTags, + sign, + signSolanaTx, } from '../../api/index' import { HARDENED_OFFSET } from '../../constants' import { buildRandomMsg } from '../utils/builders' @@ -51,313 +51,313 @@ import { getClient } from './../../api/utilities' import { dexlabProgram } from './signing/solana/__mocks__/programs' describe('API', () => { - beforeAll(async () => { - await setupClient() - }) - - describe('signing', () => { - describe('bitcoin', () => { - const btcTxData = { - prevOuts: [ - { - txHash: - '6e78493091f80d89a92ae3152df7fbfbdc44df09cf01a9b76c5113c02eaf2e0f', - value: 10000, - index: 1, - signerPath: [ - BTC_PURPOSE_P2SH_P2WPKH, - BTC_TESTNET_COIN, - HARDENED_OFFSET, - 0, - 0, - ], - }, - ], - recipient: 'mhifA1DwiMPHTjSJM8FFSL8ibrzWaBCkVT', - value: 1000, - fee: 1000, - changePath: [ - BTC_PURPOSE_P2SH_P2WPKH, - BTC_TESTNET_COIN, - HARDENED_OFFSET, - 1, - 0, - ], - } - test('legacy', async () => { - await signBtcLegacyTx(btcTxData) - }) - - test('segwit', async () => { - await signBtcSegwitTx(btcTxData) - }) - - test('wrapped segwit', async () => { - await signBtcWrappedSegwitTx(btcTxData) - }) - }) - - describe('ethereum', () => { - describe('messages', () => { - test('signPersonal', async () => { - await signMessage('test message') - }) - - test('eip712', async () => { - const client = await getClient() - await signMessage(buildRandomMsg('eip712', client)) - }) - }) - - describe('transactions', () => { - const txData = { - type: 'eip2930', - chainId: 1, - nonce: 0, - gas: 50000n, - to: '0x7a250d5630b4cf539739df2c5dacb4c659f2488d', - value: 1000000000000n, - data: '0x38ed17390000000000000000000000000000000000000000000c1c173c5b782a5b154ab900000000000000000000000000000000000000000000000f380d77022fe8c32600000000000000000000000000000000000000000000000000000000000000a00000000000000000000000007ae7684581f0298241c3d6a6567a48d56b42b15c00000000000000000000000000000000000000000000000000000000622f8d27000000000000000000000000000000000000000000000000000000000000000300000000000000000000000095ad61b0a150d79219dcf64e1e6cc01f0b64c4ce000000000000000000000000c02aaa39b223fe8d0a0e5c4f27ead9083c756cc200000000000000000000000050522c769e01eb06c02bd299066509d8f97a69ae', - gasPrice: 1200000000n, - } as const - - test('generic', async () => { - await sign(txData) - }) - - test('legacy', async () => { - const toHex = (v: bigint | number) => - typeof v === 'bigint' ? `0x${v.toString(16)}` : v - const rawTx = RLP.encode([ - txData.nonce, - toHex(txData.gasPrice), - toHex(txData.gas), - txData.to, - toHex(txData.value), - txData.data, - ]) - await sign(rawTx) - }) - }) - }) - - describe('solana', () => { - test('sign solana', async () => { - await signSolanaTx(dexlabProgram) - }) - }) - }) - - describe('address tags', () => { - beforeAll(async () => { - try { - await Promise.race([ - fetchAddressTags({ n: 1 }), - new Promise((_, reject) => - setTimeout( - () => reject(new Error('Address tag RPC timed out')), - 5000, - ), - ), - ]) - } catch (err) { - console.warn( - 'Skipping address tag tests due to connectivity issue:', - (err as Error).message, - ) - } - }) - - it('addAddressTags', async () => { - const key = `tag-${Date.now()}` - await addAddressTags([{ [key]: 'test' }]) - const addressTags = await fetchAddressTags() - expect(addressTags.some((tag) => tag.key === key)).toBeTruthy() - const tagsToRemove = addressTags.filter((tag) => tag.key === key) - if (tagsToRemove.length) { - await removeAddressTags(tagsToRemove) - } - }) - - it('fetchAddressTags', async () => { - const key = `fetch-tag-${Date.now()}` - await addAddressTags([{ [key]: 'value' }]) - const addressTags = await fetchAddressTags() - expect(addressTags.some((tag) => tag.key === key)).toBeTruthy() - const tagsToRemove = addressTags.filter((tag) => tag.key === key) - if (tagsToRemove.length) { - await removeAddressTags(tagsToRemove) - } - }) - - it('removeAddressTags', async () => { - const key = `remove-tag-${Date.now()}` - await addAddressTags([{ [key]: 'value' }]) - const addressTags = await fetchAddressTags() - const tagsToRemove = addressTags.filter((tag) => tag.key === key) - expect(tagsToRemove).not.toHaveLength(0) - await removeAddressTags(tagsToRemove) - const remainingTags = await fetchAddressTags() - expect(remainingTags.some((tag) => tag.key === key)).toBeFalsy() - }) - }) - - describe('addresses', () => { - describe('fetchAddresses', () => { - test('fetchAddresses', async () => { - const addresses = await fetchAddresses() - expect(addresses).toHaveLength(10) - }) - - test('fetchAddresses[1]', async () => { - const addresses = await fetchAddresses({ n: 1 }) - expect(addresses).toHaveLength(1) - }) - - test('fetchAddresses[12]', async () => { - const addresses = await fetchAddresses({ n: 12 }) - expect(addresses).toHaveLength(12) - }) - - test('fetchBtcLegacyAddresses', async () => { - const addresses = await fetchBtcLegacyAddresses() - expect(addresses).toHaveLength(10) - }) - - test('fetchBtcSegwitAddresses[12]', async () => { - const addresses = await fetchBtcSegwitAddresses({ n: 12 }) - expect(addresses).toHaveLength(12) - }) - - test('fetchLedgerLiveAddresses', async () => { - const addresses = await fetchLedgerLiveAddresses() - expect(addresses).toHaveLength(10) - }) - - test('fetchSolanaAddresses', async () => { - const addresses = await fetchSolanaAddresses() - expect(addresses).toHaveLength(10) - }) - - test('fetchBip44ChangeAddresses', async () => { - const addresses = await fetchBip44ChangeAddresses() - expect(addresses).toHaveLength(10) - }) - }) - - describe('fetchAddressesByDerivationPath', () => { - test('fetch single specific address', async () => { - const addresses = await fetchAddressesByDerivationPath("44'/60'/0'/0/0") - expect(addresses).toHaveLength(1) - expect(addresses[0]).toBeTruthy() - }) - - test('fetch multiple addresses with wildcard', async () => { - const addresses = await fetchAddressesByDerivationPath( - "44'/60'/0'/0/X", - { - n: 5, - }, - ) - expect(addresses).toHaveLength(5) - addresses.forEach((address) => { - expect(address).toBeTruthy() - }) - }) - - test('fetch addresses with offset', async () => { - const addresses = await fetchAddressesByDerivationPath( - "44'/60'/0'/0/X", - { - n: 3, - startPathIndex: 10, - }, - ) - expect(addresses).toHaveLength(3) - addresses.forEach((address) => { - expect(address).toBeTruthy() - }) - }) - - test('fetch addresses with lowercase x wildcard', async () => { - const addresses = await fetchAddressesByDerivationPath( - "44'/60'/0'/0/x", - { - n: 2, - }, - ) - expect(addresses).toHaveLength(2) - addresses.forEach((address) => { - expect(address).toBeTruthy() - }) - }) - - test('fetch addresses with wildcard in middle of path', async () => { - const addresses = await fetchAddressesByDerivationPath( - "44'/60'/X'/0/0", - { - n: 3, - }, - ) - expect(addresses).toHaveLength(3) - addresses.forEach((address) => { - expect(address).toBeTruthy() - }) - }) - - test('fetch solana addresses with wildcard in middle of path', async () => { - const addresses = await fetchAddressesByDerivationPath( - "44'/501'/X'/0'", - { - n: 1, - }, - ) - expect(addresses).toHaveLength(1) - addresses.forEach((address) => { - expect(address).toBeTruthy() - }) - }) - - test('error on invalid derivation path', async () => { - await expect( - fetchAddressesByDerivationPath('invalid/path'), - ).rejects.toThrow() - }) - - test('fetch single address when n=1 with wildcard', async () => { - const addresses = await fetchAddressesByDerivationPath( - "44'/60'/0'/0/X", - { - n: 1, - }, - ) - expect(addresses).toHaveLength(1) - expect(addresses[0]).toBeTruthy() - }) - - test('fetch no addresses when n=0', async () => { - const addresses = await fetchAddressesByDerivationPath( - "44'/60'/0'/0/X", - { - n: 0, - }, - ) - expect(addresses).toHaveLength(0) - }) - }) - - describe('fetchAddress', () => { - test('fetchAddress', async () => { - const address = await fetchAddress() - expect(address).toBeTruthy() - }) - }) - }) - - describe('fetchActiveWallets', () => { - test('fetchActiveWallets', async () => { - const wallet = await fetchActiveWallets() - expect(wallet).toBeTruthy() - }) - }) + beforeAll(async () => { + await setupClient() + }) + + describe('signing', () => { + describe('bitcoin', () => { + const btcTxData = { + prevOuts: [ + { + txHash: + '6e78493091f80d89a92ae3152df7fbfbdc44df09cf01a9b76c5113c02eaf2e0f', + value: 10000, + index: 1, + signerPath: [ + BTC_PURPOSE_P2SH_P2WPKH, + BTC_TESTNET_COIN, + HARDENED_OFFSET, + 0, + 0, + ], + }, + ], + recipient: 'mhifA1DwiMPHTjSJM8FFSL8ibrzWaBCkVT', + value: 1000, + fee: 1000, + changePath: [ + BTC_PURPOSE_P2SH_P2WPKH, + BTC_TESTNET_COIN, + HARDENED_OFFSET, + 1, + 0, + ], + } + test('legacy', async () => { + await signBtcLegacyTx(btcTxData) + }) + + test('segwit', async () => { + await signBtcSegwitTx(btcTxData) + }) + + test('wrapped segwit', async () => { + await signBtcWrappedSegwitTx(btcTxData) + }) + }) + + describe('ethereum', () => { + describe('messages', () => { + test('signPersonal', async () => { + await signMessage('test message') + }) + + test('eip712', async () => { + const client = await getClient() + await signMessage(buildRandomMsg('eip712', client)) + }) + }) + + describe('transactions', () => { + const txData = { + type: 'eip2930', + chainId: 1, + nonce: 0, + gas: 50000n, + to: '0x7a250d5630b4cf539739df2c5dacb4c659f2488d', + value: 1000000000000n, + data: '0x38ed17390000000000000000000000000000000000000000000c1c173c5b782a5b154ab900000000000000000000000000000000000000000000000f380d77022fe8c32600000000000000000000000000000000000000000000000000000000000000a00000000000000000000000007ae7684581f0298241c3d6a6567a48d56b42b15c00000000000000000000000000000000000000000000000000000000622f8d27000000000000000000000000000000000000000000000000000000000000000300000000000000000000000095ad61b0a150d79219dcf64e1e6cc01f0b64c4ce000000000000000000000000c02aaa39b223fe8d0a0e5c4f27ead9083c756cc200000000000000000000000050522c769e01eb06c02bd299066509d8f97a69ae', + gasPrice: 1200000000n, + } as const + + test('generic', async () => { + await sign(txData) + }) + + test('legacy', async () => { + const toHex = (v: bigint | number) => + typeof v === 'bigint' ? `0x${v.toString(16)}` : v + const rawTx = RLP.encode([ + txData.nonce, + toHex(txData.gasPrice), + toHex(txData.gas), + txData.to, + toHex(txData.value), + txData.data, + ]) + await sign(rawTx) + }) + }) + }) + + describe('solana', () => { + test('sign solana', async () => { + await signSolanaTx(dexlabProgram) + }) + }) + }) + + describe('address tags', () => { + beforeAll(async () => { + try { + await Promise.race([ + fetchAddressTags({ n: 1 }), + new Promise((_, reject) => + setTimeout( + () => reject(new Error('Address tag RPC timed out')), + 5000, + ), + ), + ]) + } catch (err) { + console.warn( + 'Skipping address tag tests due to connectivity issue:', + (err as Error).message, + ) + } + }) + + it('addAddressTags', async () => { + const key = `tag-${Date.now()}` + await addAddressTags([{ [key]: 'test' }]) + const addressTags = await fetchAddressTags() + expect(addressTags.some((tag) => tag.key === key)).toBeTruthy() + const tagsToRemove = addressTags.filter((tag) => tag.key === key) + if (tagsToRemove.length) { + await removeAddressTags(tagsToRemove) + } + }) + + it('fetchAddressTags', async () => { + const key = `fetch-tag-${Date.now()}` + await addAddressTags([{ [key]: 'value' }]) + const addressTags = await fetchAddressTags() + expect(addressTags.some((tag) => tag.key === key)).toBeTruthy() + const tagsToRemove = addressTags.filter((tag) => tag.key === key) + if (tagsToRemove.length) { + await removeAddressTags(tagsToRemove) + } + }) + + it('removeAddressTags', async () => { + const key = `remove-tag-${Date.now()}` + await addAddressTags([{ [key]: 'value' }]) + const addressTags = await fetchAddressTags() + const tagsToRemove = addressTags.filter((tag) => tag.key === key) + expect(tagsToRemove).not.toHaveLength(0) + await removeAddressTags(tagsToRemove) + const remainingTags = await fetchAddressTags() + expect(remainingTags.some((tag) => tag.key === key)).toBeFalsy() + }) + }) + + describe('addresses', () => { + describe('fetchAddresses', () => { + test('fetchAddresses', async () => { + const addresses = await fetchAddresses() + expect(addresses).toHaveLength(10) + }) + + test('fetchAddresses[1]', async () => { + const addresses = await fetchAddresses({ n: 1 }) + expect(addresses).toHaveLength(1) + }) + + test('fetchAddresses[12]', async () => { + const addresses = await fetchAddresses({ n: 12 }) + expect(addresses).toHaveLength(12) + }) + + test('fetchBtcLegacyAddresses', async () => { + const addresses = await fetchBtcLegacyAddresses() + expect(addresses).toHaveLength(10) + }) + + test('fetchBtcSegwitAddresses[12]', async () => { + const addresses = await fetchBtcSegwitAddresses({ n: 12 }) + expect(addresses).toHaveLength(12) + }) + + test('fetchLedgerLiveAddresses', async () => { + const addresses = await fetchLedgerLiveAddresses() + expect(addresses).toHaveLength(10) + }) + + test('fetchSolanaAddresses', async () => { + const addresses = await fetchSolanaAddresses() + expect(addresses).toHaveLength(10) + }) + + test('fetchBip44ChangeAddresses', async () => { + const addresses = await fetchBip44ChangeAddresses() + expect(addresses).toHaveLength(10) + }) + }) + + describe('fetchAddressesByDerivationPath', () => { + test('fetch single specific address', async () => { + const addresses = await fetchAddressesByDerivationPath("44'/60'/0'/0/0") + expect(addresses).toHaveLength(1) + expect(addresses[0]).toBeTruthy() + }) + + test('fetch multiple addresses with wildcard', async () => { + const addresses = await fetchAddressesByDerivationPath( + "44'/60'/0'/0/X", + { + n: 5, + }, + ) + expect(addresses).toHaveLength(5) + addresses.forEach((address) => { + expect(address).toBeTruthy() + }) + }) + + test('fetch addresses with offset', async () => { + const addresses = await fetchAddressesByDerivationPath( + "44'/60'/0'/0/X", + { + n: 3, + startPathIndex: 10, + }, + ) + expect(addresses).toHaveLength(3) + addresses.forEach((address) => { + expect(address).toBeTruthy() + }) + }) + + test('fetch addresses with lowercase x wildcard', async () => { + const addresses = await fetchAddressesByDerivationPath( + "44'/60'/0'/0/x", + { + n: 2, + }, + ) + expect(addresses).toHaveLength(2) + addresses.forEach((address) => { + expect(address).toBeTruthy() + }) + }) + + test('fetch addresses with wildcard in middle of path', async () => { + const addresses = await fetchAddressesByDerivationPath( + "44'/60'/X'/0/0", + { + n: 3, + }, + ) + expect(addresses).toHaveLength(3) + addresses.forEach((address) => { + expect(address).toBeTruthy() + }) + }) + + test('fetch solana addresses with wildcard in middle of path', async () => { + const addresses = await fetchAddressesByDerivationPath( + "44'/501'/X'/0'", + { + n: 1, + }, + ) + expect(addresses).toHaveLength(1) + addresses.forEach((address) => { + expect(address).toBeTruthy() + }) + }) + + test('error on invalid derivation path', async () => { + await expect( + fetchAddressesByDerivationPath('invalid/path'), + ).rejects.toThrow() + }) + + test('fetch single address when n=1 with wildcard', async () => { + const addresses = await fetchAddressesByDerivationPath( + "44'/60'/0'/0/X", + { + n: 1, + }, + ) + expect(addresses).toHaveLength(1) + expect(addresses[0]).toBeTruthy() + }) + + test('fetch no addresses when n=0', async () => { + const addresses = await fetchAddressesByDerivationPath( + "44'/60'/0'/0/X", + { + n: 0, + }, + ) + expect(addresses).toHaveLength(0) + }) + }) + + describe('fetchAddress', () => { + test('fetchAddress', async () => { + const address = await fetchAddress() + expect(address).toBeTruthy() + }) + }) + }) + + describe('fetchActiveWallets', () => { + test('fetchActiveWallets', async () => { + const wallet = await fetchActiveWallets() + expect(wallet).toBeTruthy() + }) + }) }) diff --git a/packages/sdk/src/__test__/e2e/btc.test.ts b/packages/sdk/src/__test__/e2e/btc.test.ts index 4bedfafb..86e285ca 100644 --- a/packages/sdk/src/__test__/e2e/btc.test.ts +++ b/packages/sdk/src/__test__/e2e/btc.test.ts @@ -17,11 +17,11 @@ import * as ecc from 'tiny-secp256k1' import type { Client } from '../../client' import { getPrng, getTestnet } from '../utils/getters' import { - BTC_PURPOSE_P2PKH, - BTC_PURPOSE_P2SH_P2WPKH, - BTC_PURPOSE_P2WPKH, - setup_btc_sig_test, - stripDER, + BTC_PURPOSE_P2PKH, + BTC_PURPOSE_P2SH_P2WPKH, + BTC_PURPOSE_P2WPKH, + setup_btc_sig_test, + stripDER, } from '../utils/helpers' import { setupClient } from '../utils/setup' import { TEST_SEED } from '../utils/testConstants' @@ -35,166 +35,166 @@ type InputObj = { hash: string; value: number; signerIdx: number; idx: number } // Build the inputs. By default we will build 10. Note that there are `n` tests for // *each category*, where `n` is the number of inputs. function rand32Bit() { - return Math.floor(prng.quick() * 2 ** 32) + return Math.floor(prng.quick() * 2 ** 32) } const inputs: InputObj[] = [] const count = 10 for (let i = 0; i < count; i++) { - const hash = Buffer.alloc(32) - for (let j = 0; j < 8; j++) { - // 32 bits of randomness per call - hash.writeUInt32BE(rand32Bit(), j * 4) - } - const value = Math.floor(rand32Bit()) - const signerIdx = Math.floor(prng.quick() * 19) // Random signer (keep it inside initial cache of 20) - const idx = Math.floor(prng.quick() * 25) // Random previous output index (keep it small) - inputs.push({ hash: hash.toString('hex'), value, signerIdx, idx }) + const hash = Buffer.alloc(32) + for (let j = 0; j < 8; j++) { + // 32 bits of randomness per call + hash.writeUInt32BE(rand32Bit(), j * 4) + } + const value = Math.floor(rand32Bit()) + const signerIdx = Math.floor(prng.quick() * 19) // Random signer (keep it inside initial cache of 20) + const idx = Math.floor(prng.quick() * 25) // Random previous output index (keep it small) + inputs.push({ hash: hash.toString('hex'), value, signerIdx, idx }) } async function testSign({ txReq, signingKeys, sigHashes, client }: any) { - const tx = await client.sign(txReq) - const len = tx?.sigs?.length ?? 0 - expect(len).toEqual(signingKeys.length) - expect(len).toEqual(sigHashes.length) - for (let i = 0; i < len; i++) { - const sig = stripDER(tx.sigs?.[i]) - const verification = signingKeys[i].verify(sigHashes[i], sig) - expect(verification).toEqualElseLog( - true, - `Signature validation failed for priv=${signingKeys[i].privateKey.toString('hex')}, ` + - `hash=${sigHashes[i].toString('hex')}, sig=${sig.toString('hex')}`, - ) - } + const tx = await client.sign(txReq) + const len = tx?.sigs?.length ?? 0 + expect(len).toEqual(signingKeys.length) + expect(len).toEqual(sigHashes.length) + for (let i = 0; i < len; i++) { + const sig = stripDER(tx.sigs?.[i]) + const verification = signingKeys[i].verify(sigHashes[i], sig) + expect(verification).toEqualElseLog( + true, + `Signature validation failed for priv=${signingKeys[i].privateKey.toString('hex')}, ` + + `hash=${sigHashes[i].toString('hex')}, sig=${sig.toString('hex')}`, + ) + } } async function runTestSet( - opts: any, - wallet: BIP32Interface | null, - inputsSlice: InputObj[], - client, + opts: any, + wallet: BIP32Interface | null, + inputsSlice: InputObj[], + client, ) { - expect(wallet).not.toEqualElseLog(null, 'Wallet not available') - if (TEST_TESTNET) { - // Testnet + change - opts.isTestnet = true - opts.useChange = true - await testSign({ - ...setup_btc_sig_test(opts, wallet, inputsSlice, prng), - client, - }) - // Testnet + no change - opts.isTestnet = true - opts.useChange = false - await testSign({ - ...setup_btc_sig_test(opts, wallet, inputsSlice, prng), - client, - }) - } - // Mainnet + change - opts.isTestnet = false - opts.useChange = true - await testSign({ - ...setup_btc_sig_test(opts, wallet, inputsSlice, prng), - client, - }) - // Mainnet + no change - opts.isTestnet = false - opts.useChange = false - await testSign({ - ...setup_btc_sig_test(opts, wallet, inputsSlice, prng), - client, - }) + expect(wallet).not.toEqualElseLog(null, 'Wallet not available') + if (TEST_TESTNET) { + // Testnet + change + opts.isTestnet = true + opts.useChange = true + await testSign({ + ...setup_btc_sig_test(opts, wallet, inputsSlice, prng), + client, + }) + // Testnet + no change + opts.isTestnet = true + opts.useChange = false + await testSign({ + ...setup_btc_sig_test(opts, wallet, inputsSlice, prng), + client, + }) + } + // Mainnet + change + opts.isTestnet = false + opts.useChange = true + await testSign({ + ...setup_btc_sig_test(opts, wallet, inputsSlice, prng), + client, + }) + // Mainnet + no change + opts.isTestnet = false + opts.useChange = false + await testSign({ + ...setup_btc_sig_test(opts, wallet, inputsSlice, prng), + client, + }) } describe('Bitcoin', () => { - let client: Client - - beforeAll(async () => { - client = await setupClient() - wallet = bip32.fromSeed(TEST_SEED) - }) - - for (let i = 0; i < inputs.length; i++) { - const inputsSlice = inputs.slice(0, i + 1) - - describe(`Input Set ${i}`, () => { - describe('segwit spender (p2wpkh)', () => { - it('p2wpkh->p2pkh', async () => { - const opts = { - spenderPurpose: BTC_PURPOSE_P2WPKH, - recipientPurpose: BTC_PURPOSE_P2PKH, - } - await runTestSet(opts, wallet, inputsSlice, client) - }) - - it('p2wpkh->p2sh-p2wpkh', async () => { - const opts = { - spenderPurpose: BTC_PURPOSE_P2WPKH, - recipientPurpose: BTC_PURPOSE_P2SH_P2WPKH, - } - await runTestSet(opts, wallet, inputsSlice, client) - }) - - it('p2wpkh->p2wpkh', async () => { - const opts = { - spenderPurpose: BTC_PURPOSE_P2WPKH, - recipientPurpose: BTC_PURPOSE_P2WPKH, - } - await runTestSet(opts, wallet, inputsSlice, client) - }) - }) - - describe('wrapped segwit spender (p2sh-p2wpkh)', () => { - it('p2sh-p2wpkh->p2pkh', async () => { - const opts = { - spenderPurpose: BTC_PURPOSE_P2SH_P2WPKH, - recipientPurpose: BTC_PURPOSE_P2PKH, - } - await runTestSet(opts, wallet, inputsSlice, client) - }) - - it('p2sh-p2wpkh->p2sh-p2wpkh', async () => { - const opts = { - spenderPurpose: BTC_PURPOSE_P2SH_P2WPKH, - recipientPurpose: BTC_PURPOSE_P2SH_P2WPKH, - } - await runTestSet(opts, wallet, inputsSlice, client) - }) - - it('p2sh-p2wpkh->p2wpkh', async () => { - const opts = { - spenderPurpose: BTC_PURPOSE_P2SH_P2WPKH, - recipientPurpose: BTC_PURPOSE_P2WPKH, - } - await runTestSet(opts, wallet, inputsSlice, client) - }) - }) - - describe('legacy spender (p2pkh)', () => { - it('p2pkh->p2pkh', async () => { - const opts = { - spenderPurpose: BTC_PURPOSE_P2PKH, - recipientPurpose: BTC_PURPOSE_P2PKH, - } - await runTestSet(opts, wallet, inputsSlice, client) - }) - - it('p2pkh->p2sh-p2wpkh', async () => { - const opts = { - spenderPurpose: BTC_PURPOSE_P2PKH, - recipientPurpose: BTC_PURPOSE_P2SH_P2WPKH, - } - await runTestSet(opts, wallet, inputsSlice, client) - }) - - it('p2pkh->p2wpkh', async () => { - const opts = { - spenderPurpose: BTC_PURPOSE_P2PKH, - recipientPurpose: BTC_PURPOSE_P2WPKH, - } - await runTestSet(opts, wallet, inputsSlice, client) - }) - }) - }) - } + let client: Client + + beforeAll(async () => { + client = await setupClient() + wallet = bip32.fromSeed(TEST_SEED) + }) + + for (let i = 0; i < inputs.length; i++) { + const inputsSlice = inputs.slice(0, i + 1) + + describe(`Input Set ${i}`, () => { + describe('segwit spender (p2wpkh)', () => { + it('p2wpkh->p2pkh', async () => { + const opts = { + spenderPurpose: BTC_PURPOSE_P2WPKH, + recipientPurpose: BTC_PURPOSE_P2PKH, + } + await runTestSet(opts, wallet, inputsSlice, client) + }) + + it('p2wpkh->p2sh-p2wpkh', async () => { + const opts = { + spenderPurpose: BTC_PURPOSE_P2WPKH, + recipientPurpose: BTC_PURPOSE_P2SH_P2WPKH, + } + await runTestSet(opts, wallet, inputsSlice, client) + }) + + it('p2wpkh->p2wpkh', async () => { + const opts = { + spenderPurpose: BTC_PURPOSE_P2WPKH, + recipientPurpose: BTC_PURPOSE_P2WPKH, + } + await runTestSet(opts, wallet, inputsSlice, client) + }) + }) + + describe('wrapped segwit spender (p2sh-p2wpkh)', () => { + it('p2sh-p2wpkh->p2pkh', async () => { + const opts = { + spenderPurpose: BTC_PURPOSE_P2SH_P2WPKH, + recipientPurpose: BTC_PURPOSE_P2PKH, + } + await runTestSet(opts, wallet, inputsSlice, client) + }) + + it('p2sh-p2wpkh->p2sh-p2wpkh', async () => { + const opts = { + spenderPurpose: BTC_PURPOSE_P2SH_P2WPKH, + recipientPurpose: BTC_PURPOSE_P2SH_P2WPKH, + } + await runTestSet(opts, wallet, inputsSlice, client) + }) + + it('p2sh-p2wpkh->p2wpkh', async () => { + const opts = { + spenderPurpose: BTC_PURPOSE_P2SH_P2WPKH, + recipientPurpose: BTC_PURPOSE_P2WPKH, + } + await runTestSet(opts, wallet, inputsSlice, client) + }) + }) + + describe('legacy spender (p2pkh)', () => { + it('p2pkh->p2pkh', async () => { + const opts = { + spenderPurpose: BTC_PURPOSE_P2PKH, + recipientPurpose: BTC_PURPOSE_P2PKH, + } + await runTestSet(opts, wallet, inputsSlice, client) + }) + + it('p2pkh->p2sh-p2wpkh', async () => { + const opts = { + spenderPurpose: BTC_PURPOSE_P2PKH, + recipientPurpose: BTC_PURPOSE_P2SH_P2WPKH, + } + await runTestSet(opts, wallet, inputsSlice, client) + }) + + it('p2pkh->p2wpkh', async () => { + const opts = { + spenderPurpose: BTC_PURPOSE_P2PKH, + recipientPurpose: BTC_PURPOSE_P2WPKH, + } + await runTestSet(opts, wallet, inputsSlice, client) + }) + }) + }) + } }) diff --git a/packages/sdk/src/__test__/e2e/eth.msg.test.ts b/packages/sdk/src/__test__/e2e/eth.msg.test.ts index 653cd787..944ae95f 100644 --- a/packages/sdk/src/__test__/e2e/eth.msg.test.ts +++ b/packages/sdk/src/__test__/e2e/eth.msg.test.ts @@ -25,1342 +25,1342 @@ import { setupClient } from '../utils/setup' import type { Client } from '../../client' describe('ETH Messages', () => { - let client: Client + let client: Client - beforeAll(async () => { - client = await setupClient() - }) + beforeAll(async () => { + client = await setupClient() + }) - describe('Test ETH personalSign', () => { - it('Should throw error when message contains non-ASCII characters', async () => { - const protocol = 'signPersonal' - const msg = '⚠️' - const msg2 = 'ASCII plus ⚠️' - await expect(client.sign(buildEthMsgReq(msg, protocol))).rejects.toThrow( - /Lattice can only display ASCII/, - ) - await expect(client.sign(buildEthMsgReq(msg2, protocol))).rejects.toThrow( - /Lattice can only display ASCII/, - ) - }) + describe('Test ETH personalSign', () => { + it('Should throw error when message contains non-ASCII characters', async () => { + const protocol = 'signPersonal' + const msg = '⚠️' + const msg2 = 'ASCII plus ⚠️' + await expect(client.sign(buildEthMsgReq(msg, protocol))).rejects.toThrow( + /Lattice can only display ASCII/, + ) + await expect(client.sign(buildEthMsgReq(msg2, protocol))).rejects.toThrow( + /Lattice can only display ASCII/, + ) + }) - it('Should test ASCII buffers', async () => { - await runEthMsg( - buildEthMsgReq(Buffer.from('i am an ascii buffer'), 'signPersonal'), - client, - ) - await runEthMsg( - buildEthMsgReq(Buffer.from('{\n\ttest: foo\n}'), 'signPersonal'), - client, - ) - }) + it('Should test ASCII buffers', async () => { + await runEthMsg( + buildEthMsgReq(Buffer.from('i am an ascii buffer'), 'signPersonal'), + client, + ) + await runEthMsg( + buildEthMsgReq(Buffer.from('{\n\ttest: foo\n}'), 'signPersonal'), + client, + ) + }) - it('Should test hex buffers', async () => { - await runEthMsg( - buildEthMsgReq(Buffer.from('abcdef', 'hex'), 'signPersonal'), - client, - ) - }) + it('Should test hex buffers', async () => { + await runEthMsg( + buildEthMsgReq(Buffer.from('abcdef', 'hex'), 'signPersonal'), + client, + ) + }) - it('Should test a message that needs to be prehashed', async () => { - await runEthMsg(buildEthMsgReq(randomBytes(4000), 'signPersonal'), client) - }) + it('Should test a message that needs to be prehashed', async () => { + await runEthMsg(buildEthMsgReq(randomBytes(4000), 'signPersonal'), client) + }) - it('Msg: sign_personal boundary conditions and auto-rejected requests', async () => { - const protocol = 'signPersonal' - const fwConstants = client.getFwConstants() - // `personal_sign` requests have a max size smaller than other requests because a header - // is displayed in the text region of the screen. The size of this is captured - // by `fwConstants.personalSignHeaderSz`. - const maxMsgSz = - fwConstants.ethMaxMsgSz + - fwConstants.personalSignHeaderSz + - fwConstants.extraDataMaxFrames * fwConstants.extraDataFrameSz - const maxValid = `0x${randomBytes(maxMsgSz).toString('hex')}` - const minInvalid = `0x${randomBytes(maxMsgSz + 1).toString('hex')}` - const zeroInvalid = '0x' - // The largest non-hardened index which will take the most chars to print - const x = HARDENED_OFFSET - 1 - // Okay sooo this is a bit awkward. We have to use a known coin_type here (e.g. ETH) - // or else firmware will return an error, but the maxSz is based on the max length - // of a path, which is larger than we can actually print. - // I guess all this tests is that the first one is shown in plaintext while the second - // one (which is too large) gets prehashed. - const largeSignPath = [x, HARDENED_OFFSET + 60, x, x, x] as SigningPath - await runEthMsg(buildEthMsgReq(maxValid, protocol, largeSignPath), client) - await runEthMsg( - buildEthMsgReq(minInvalid, protocol, largeSignPath), - client, - ) - // Using a zero length payload should auto-reject - await expect( - client.sign(buildEthMsgReq(zeroInvalid, protocol)), - ).rejects.toThrow(/Invalid Request/) - }) + it('Msg: sign_personal boundary conditions and auto-rejected requests', async () => { + const protocol = 'signPersonal' + const fwConstants = client.getFwConstants() + // `personal_sign` requests have a max size smaller than other requests because a header + // is displayed in the text region of the screen. The size of this is captured + // by `fwConstants.personalSignHeaderSz`. + const maxMsgSz = + fwConstants.ethMaxMsgSz + + fwConstants.personalSignHeaderSz + + fwConstants.extraDataMaxFrames * fwConstants.extraDataFrameSz + const maxValid = `0x${randomBytes(maxMsgSz).toString('hex')}` + const minInvalid = `0x${randomBytes(maxMsgSz + 1).toString('hex')}` + const zeroInvalid = '0x' + // The largest non-hardened index which will take the most chars to print + const x = HARDENED_OFFSET - 1 + // Okay sooo this is a bit awkward. We have to use a known coin_type here (e.g. ETH) + // or else firmware will return an error, but the maxSz is based on the max length + // of a path, which is larger than we can actually print. + // I guess all this tests is that the first one is shown in plaintext while the second + // one (which is too large) gets prehashed. + const largeSignPath = [x, HARDENED_OFFSET + 60, x, x, x] as SigningPath + await runEthMsg(buildEthMsgReq(maxValid, protocol, largeSignPath), client) + await runEthMsg( + buildEthMsgReq(minInvalid, protocol, largeSignPath), + client, + ) + // Using a zero length payload should auto-reject + await expect( + client.sign(buildEthMsgReq(zeroInvalid, protocol)), + ).rejects.toThrow(/Invalid Request/) + }) - describe(`Test ${5} random payloads`, () => { - for (let i = 0; i < 5; i++) { - it(`Payload: ${i}`, async () => { - await runEthMsg( - buildEthMsgReq( - buildRandomMsg('signPersonal', client), - 'signPersonal', - ), - client, - ) - }) - } - }) - }) + describe(`Test ${5} random payloads`, () => { + for (let i = 0; i < 5; i++) { + it(`Payload: ${i}`, async () => { + await runEthMsg( + buildEthMsgReq( + buildRandomMsg('signPersonal', client), + 'signPersonal', + ), + client, + ) + }) + } + }) + }) - describe('Test ETH EIP712', () => { - it('Should test a message that needs to be prehashed', async () => { - const msg = { - types: { - EIP712Domain: [ - { name: 'name', type: 'string' }, - { name: 'version', type: 'string' }, - { name: 'chainId', type: 'uint256' }, - ], - dYdX: [ - { type: 'string', name: 'action' }, - { type: 'string', name: 'onlySignOn' }, - ], - }, - domain: { - name: 'dYdX', - version: '1.0', - chainId: '1', - }, - primaryType: 'dYdX', - message: { - action: 'dYdX STARK Key', - onlySignOn: randomBytes(4000).toString('hex'), - }, - } - await runEthMsg(buildEthMsgReq(msg, 'eip712'), client) - }) + describe('Test ETH EIP712', () => { + it('Should test a message that needs to be prehashed', async () => { + const msg = { + types: { + EIP712Domain: [ + { name: 'name', type: 'string' }, + { name: 'version', type: 'string' }, + { name: 'chainId', type: 'uint256' }, + ], + dYdX: [ + { type: 'string', name: 'action' }, + { type: 'string', name: 'onlySignOn' }, + ], + }, + domain: { + name: 'dYdX', + version: '1.0', + chainId: '1', + }, + primaryType: 'dYdX', + message: { + action: 'dYdX STARK Key', + onlySignOn: randomBytes(4000).toString('hex'), + }, + } + await runEthMsg(buildEthMsgReq(msg, 'eip712'), client) + }) - it('Should test an example from Blur NFT w/ 0 fees', async () => { - const msg = { - types: { - EIP712Domain: [ - { name: 'name', type: 'string' }, - { name: 'host', type: 'string' }, - { name: 'version', type: 'string' }, - { name: 'chainId', type: 'uint256' }, - { name: 'verifyingContract', type: 'address' }, - ], - Fee: [ - { name: 'rate', type: 'uint256' }, - { name: 'recipient', type: 'address' }, - ], - Message: [ - { name: 'trader', type: 'address' }, - { name: 'side', type: 'uint256' }, - { name: 'matchingPolicy', type: 'address' }, - { name: 'collection', type: 'address' }, - { name: 'tokenId', type: 'uint256' }, - { name: 'amount', type: 'uint256' }, - { name: 'paymentToken', type: 'address' }, - { name: 'price', type: 'uint256' }, - { name: 'listingTime', type: 'uint256' }, - { name: 'expirationTime', type: 'uint256' }, - { name: 'salt', type: 'uint256' }, - { name: 'extraParams', type: 'bytes' }, - { name: 'nonce', type: 'uint256' }, - { name: 'fees', type: 'Fee[]' }, - ], - }, - domain: { - name: 'Blur', - verifyingContract: '0x0', - version: '1', - chainId: '', - host: '', - }, - primaryType: 'Message', - message: { - trader: '0xfc92dff6d9519c79782e0d345915c441cf5ac41f', - side: '1', - matchingPolicy: '0x00000000006411739da1c40b106f8511de5d1fac', - collection: '0x7a15b36cb834aea88553de69077d3777460d73ac', - tokenId: - '5280336779268220421569573059971679349075200194886069432279714075018412552192', - amount: '1', - paymentToken: '0x0000000000000000000000000000000000000000', - price: '990000000000000000', - listingTime: '1666370346', - expirationTime: '1666975146', - salt: '64535264870076277194623607183653108264', - extraParams: '0x', - nonce: '0', - fees: [ - // { rate: 1, recipient: '0x00000000006411739da1c40b106f8511de5d1fac'} - ], - }, - } - await runEthMsg(buildEthMsgReq(msg, 'eip712'), client) - }) + it('Should test an example from Blur NFT w/ 0 fees', async () => { + const msg = { + types: { + EIP712Domain: [ + { name: 'name', type: 'string' }, + { name: 'host', type: 'string' }, + { name: 'version', type: 'string' }, + { name: 'chainId', type: 'uint256' }, + { name: 'verifyingContract', type: 'address' }, + ], + Fee: [ + { name: 'rate', type: 'uint256' }, + { name: 'recipient', type: 'address' }, + ], + Message: [ + { name: 'trader', type: 'address' }, + { name: 'side', type: 'uint256' }, + { name: 'matchingPolicy', type: 'address' }, + { name: 'collection', type: 'address' }, + { name: 'tokenId', type: 'uint256' }, + { name: 'amount', type: 'uint256' }, + { name: 'paymentToken', type: 'address' }, + { name: 'price', type: 'uint256' }, + { name: 'listingTime', type: 'uint256' }, + { name: 'expirationTime', type: 'uint256' }, + { name: 'salt', type: 'uint256' }, + { name: 'extraParams', type: 'bytes' }, + { name: 'nonce', type: 'uint256' }, + { name: 'fees', type: 'Fee[]' }, + ], + }, + domain: { + name: 'Blur', + verifyingContract: '0x0', + version: '1', + chainId: '', + host: '', + }, + primaryType: 'Message', + message: { + trader: '0xfc92dff6d9519c79782e0d345915c441cf5ac41f', + side: '1', + matchingPolicy: '0x00000000006411739da1c40b106f8511de5d1fac', + collection: '0x7a15b36cb834aea88553de69077d3777460d73ac', + tokenId: + '5280336779268220421569573059971679349075200194886069432279714075018412552192', + amount: '1', + paymentToken: '0x0000000000000000000000000000000000000000', + price: '990000000000000000', + listingTime: '1666370346', + expirationTime: '1666975146', + salt: '64535264870076277194623607183653108264', + extraParams: '0x', + nonce: '0', + fees: [ + // { rate: 1, recipient: '0x00000000006411739da1c40b106f8511de5d1fac'} + ], + }, + } + await runEthMsg(buildEthMsgReq(msg, 'eip712'), client) + }) - it('Should test simple dydx example', async () => { - const msg = { - types: { - EIP712Domain: [ - { name: 'name', type: 'string' }, - { name: 'version', type: 'string' }, - { name: 'chainId', type: 'uint256' }, - ], - dYdX: [ - { type: 'string', name: 'action' }, - { type: 'string', name: 'onlySignOn' }, - ], - }, - domain: { - name: 'dYdX', - version: '1.0', - chainId: '1', - }, - primaryType: 'dYdX', - message: { - action: 'dYdX STARK Key', - onlySignOn: 'https://trade.dydx.exchange', - }, - } - await runEthMsg(buildEthMsgReq(msg, 'eip712'), client) - }) + it('Should test simple dydx example', async () => { + const msg = { + types: { + EIP712Domain: [ + { name: 'name', type: 'string' }, + { name: 'version', type: 'string' }, + { name: 'chainId', type: 'uint256' }, + ], + dYdX: [ + { type: 'string', name: 'action' }, + { type: 'string', name: 'onlySignOn' }, + ], + }, + domain: { + name: 'dYdX', + version: '1.0', + chainId: '1', + }, + primaryType: 'dYdX', + message: { + action: 'dYdX STARK Key', + onlySignOn: 'https://trade.dydx.exchange', + }, + } + await runEthMsg(buildEthMsgReq(msg, 'eip712'), client) + }) - it('Should test a Loopring message with non-standard numerical type', async () => { - const msg = { - types: { - EIP712Domain: [ - { name: 'name', type: 'string' }, - { name: 'version', type: 'string' }, - { name: 'chainId', type: 'uint256' }, - { name: 'verifyingContract', type: 'address' }, - ], - AccountUpdate: [ - { name: 'owner', type: 'address' }, - { name: 'accountID', type: 'uint32' }, - { name: 'feeTokenID', type: 'uint16' }, - { name: 'maxFee', type: 'uint96' }, - { name: 'publicKey', type: 'uint256' }, - { name: 'validUntil', type: 'uint32' }, - { name: 'nonce', type: 'uint32' }, - ], - }, - primaryType: 'AccountUpdate', - domain: { - name: 'Loopring Protocol', - version: '3.6.0', - chainId: 1, - verifyingContract: '0x0BABA1Ad5bE3a5C0a66E7ac838a129Bf948f1eA4', - }, - message: { - owner: '0x8c3b776bdac9a7a4facc3cc20cdb40832bff9005', - accountID: 32494, - feeTokenID: 0, - maxFee: 100, - publicKey: - '11413934541425201845815969801249874136651857829494005371571206042985258823663', - validUntil: 1631655383, - nonce: 0, - }, - } - await runEthMsg(buildEthMsgReq(msg, 'eip712'), client) - }) + it('Should test a Loopring message with non-standard numerical type', async () => { + const msg = { + types: { + EIP712Domain: [ + { name: 'name', type: 'string' }, + { name: 'version', type: 'string' }, + { name: 'chainId', type: 'uint256' }, + { name: 'verifyingContract', type: 'address' }, + ], + AccountUpdate: [ + { name: 'owner', type: 'address' }, + { name: 'accountID', type: 'uint32' }, + { name: 'feeTokenID', type: 'uint16' }, + { name: 'maxFee', type: 'uint96' }, + { name: 'publicKey', type: 'uint256' }, + { name: 'validUntil', type: 'uint32' }, + { name: 'nonce', type: 'uint32' }, + ], + }, + primaryType: 'AccountUpdate', + domain: { + name: 'Loopring Protocol', + version: '3.6.0', + chainId: 1, + verifyingContract: '0x0BABA1Ad5bE3a5C0a66E7ac838a129Bf948f1eA4', + }, + message: { + owner: '0x8c3b776bdac9a7a4facc3cc20cdb40832bff9005', + accountID: 32494, + feeTokenID: 0, + maxFee: 100, + publicKey: + '11413934541425201845815969801249874136651857829494005371571206042985258823663', + validUntil: 1631655383, + nonce: 0, + }, + } + await runEthMsg(buildEthMsgReq(msg, 'eip712'), client) + }) - it('Should test Vertex message', async () => { - const msg = { - types: { - EIP712Domain: [ - { name: 'name', type: 'string' }, - { name: 'version', type: 'string' }, - { name: 'chainId', type: 'uint256' }, - { name: 'verifyingContract', type: 'address' }, - ], - Order: [ - { name: 'sender', type: 'bytes32' }, - { name: 'priceX18', type: 'int128' }, - { name: 'amount', type: 'int128' }, - { name: 'expiration', type: 'uint64' }, - { name: 'nonce', type: 'uint64' }, - ], - }, - primaryType: 'Order', - domain: { - name: 'Vertex', - version: '0.0.1', - chainId: '42161', - verifyingContract: '0xf03f457a30e598d5020164a339727ef40f2b8fbc', - }, - message: { - sender: - '0x841fe4876763357975d60da128d8a54bb045d76a64656661756c740000000000', - priceX18: '28898000000000000000000', - amount: '-10000000000000000', - expiration: '4611687701117784255', - nonce: '1764428860167815857', - }, - } - await runEthMsg(buildEthMsgReq(msg, 'eip712'), client) - }) + it('Should test Vertex message', async () => { + const msg = { + types: { + EIP712Domain: [ + { name: 'name', type: 'string' }, + { name: 'version', type: 'string' }, + { name: 'chainId', type: 'uint256' }, + { name: 'verifyingContract', type: 'address' }, + ], + Order: [ + { name: 'sender', type: 'bytes32' }, + { name: 'priceX18', type: 'int128' }, + { name: 'amount', type: 'int128' }, + { name: 'expiration', type: 'uint64' }, + { name: 'nonce', type: 'uint64' }, + ], + }, + primaryType: 'Order', + domain: { + name: 'Vertex', + version: '0.0.1', + chainId: '42161', + verifyingContract: '0xf03f457a30e598d5020164a339727ef40f2b8fbc', + }, + message: { + sender: + '0x841fe4876763357975d60da128d8a54bb045d76a64656661756c740000000000', + priceX18: '28898000000000000000000', + amount: '-10000000000000000', + expiration: '4611687701117784255', + nonce: '1764428860167815857', + }, + } + await runEthMsg(buildEthMsgReq(msg, 'eip712'), client) + }) - it('Should test a large 1inch transaction', async () => { - const msg = { - domain: { - chainId: 137, - name: '1inch Limit Order Protocol', - verifyingContract: '0xb707d89d29c189421163515c59e42147371d6857', - version: '1', - }, - message: { - getMakerAmount: - '0xf4a215c30000000000000000000000000000000000000000000000000de0b6b3a764000000000000000000000000000000000000000000000000000018fae27693b40000', - getTakerAmount: - '0x296637bf0000000000000000000000000000000000000000000000000de0b6b3a764000000000000000000000000000000000000000000000000000018fae27693b40000', - interaction: '0x', - makerAsset: '0x0d500b1d8e8ef31e21c99d1db9a6444d3adf1270', - makerAssetData: - '0x23b872dd0000000000000000000000003e3e2ccdd7bae6bbd4a64e8d16ca8842061335eb00000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000de0b6b3a7640000', - permit: '0x', - predicate: - '0x961d5b1e000000000000000000000000000000000000000000000000000000000000004000000000000000000000000000000000000000000000000000000000000000a00000000000000000000000000000000000000000000000000000000000000002000000000000000000000000b707d89d29c189421163515c59e42147371d6857000000000000000000000000b707d89d29c189421163515c59e42147371d68570000000000000000000000000000000000000000000000000000000000000002000000000000000000000000000000000000000000000000000000000000004000000000000000000000000000000000000000000000000000000000000000c00000000000000000000000000000000000000000000000000000000000000044cf6fc6e30000000000000000000000003e3e2ccdd7bae6bbd4a64e8d16ca8842061335eb000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000002463592c2b00000000000000000000000000000000000000000000000000000000613e28e500000000000000000000000000000000000000000000000000000000', - salt: '885135864076', - takerAsset: '0x8f3cf7ad23cd3cadbd9735aff958023239c6a063', - takerAssetData: - '0x23b872dd00000000000000000000000000000000000000000000000000000000000000000000000000000000000000003e3e2ccdd7bae6bbd4a64e8d16ca8842061335eb00000000000000000000000000000000000000000000000018fae27693b40000', - }, - primaryType: 'Order', - types: { - EIP712Domain: [ - { - name: 'name', - type: 'string', - }, - { - name: 'version', - type: 'string', - }, - { - name: 'chainId', - type: 'uint256', - }, - { - name: 'verifyingContract', - type: 'address', - }, - ], - Order: [ - { - name: 'salt', - type: 'uint256', - }, - { - name: 'makerAsset', - type: 'address', - }, - { - name: 'takerAsset', - type: 'address', - }, - { - name: 'makerAssetData', - type: 'bytes', - }, - { - name: 'takerAssetData', - type: 'bytes', - }, - { - name: 'getMakerAmount', - type: 'bytes', - }, - { - name: 'getTakerAmount', - type: 'bytes', - }, - { - name: 'predicate', - type: 'bytes', - }, - { - name: 'permit', - type: 'bytes', - }, - { - name: 'interaction', - type: 'bytes', - }, - ], - }, - } - await runEthMsg(buildEthMsgReq(msg, 'eip712'), client) - }) + it('Should test a large 1inch transaction', async () => { + const msg = { + domain: { + chainId: 137, + name: '1inch Limit Order Protocol', + verifyingContract: '0xb707d89d29c189421163515c59e42147371d6857', + version: '1', + }, + message: { + getMakerAmount: + '0xf4a215c30000000000000000000000000000000000000000000000000de0b6b3a764000000000000000000000000000000000000000000000000000018fae27693b40000', + getTakerAmount: + '0x296637bf0000000000000000000000000000000000000000000000000de0b6b3a764000000000000000000000000000000000000000000000000000018fae27693b40000', + interaction: '0x', + makerAsset: '0x0d500b1d8e8ef31e21c99d1db9a6444d3adf1270', + makerAssetData: + '0x23b872dd0000000000000000000000003e3e2ccdd7bae6bbd4a64e8d16ca8842061335eb00000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000de0b6b3a7640000', + permit: '0x', + predicate: + '0x961d5b1e000000000000000000000000000000000000000000000000000000000000004000000000000000000000000000000000000000000000000000000000000000a00000000000000000000000000000000000000000000000000000000000000002000000000000000000000000b707d89d29c189421163515c59e42147371d6857000000000000000000000000b707d89d29c189421163515c59e42147371d68570000000000000000000000000000000000000000000000000000000000000002000000000000000000000000000000000000000000000000000000000000004000000000000000000000000000000000000000000000000000000000000000c00000000000000000000000000000000000000000000000000000000000000044cf6fc6e30000000000000000000000003e3e2ccdd7bae6bbd4a64e8d16ca8842061335eb000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000002463592c2b00000000000000000000000000000000000000000000000000000000613e28e500000000000000000000000000000000000000000000000000000000', + salt: '885135864076', + takerAsset: '0x8f3cf7ad23cd3cadbd9735aff958023239c6a063', + takerAssetData: + '0x23b872dd00000000000000000000000000000000000000000000000000000000000000000000000000000000000000003e3e2ccdd7bae6bbd4a64e8d16ca8842061335eb00000000000000000000000000000000000000000000000018fae27693b40000', + }, + primaryType: 'Order', + types: { + EIP712Domain: [ + { + name: 'name', + type: 'string', + }, + { + name: 'version', + type: 'string', + }, + { + name: 'chainId', + type: 'uint256', + }, + { + name: 'verifyingContract', + type: 'address', + }, + ], + Order: [ + { + name: 'salt', + type: 'uint256', + }, + { + name: 'makerAsset', + type: 'address', + }, + { + name: 'takerAsset', + type: 'address', + }, + { + name: 'makerAssetData', + type: 'bytes', + }, + { + name: 'takerAssetData', + type: 'bytes', + }, + { + name: 'getMakerAmount', + type: 'bytes', + }, + { + name: 'getTakerAmount', + type: 'bytes', + }, + { + name: 'predicate', + type: 'bytes', + }, + { + name: 'permit', + type: 'bytes', + }, + { + name: 'interaction', + type: 'bytes', + }, + ], + }, + } + await runEthMsg(buildEthMsgReq(msg, 'eip712'), client) + }) - it('Should test an example with 0 values', async () => { - const msg = { - types: { - EIP712Domain: [ - { name: 'name', type: 'string' }, - { name: 'host', type: 'string' }, - { name: 'version', type: 'string' }, - { name: 'chainId', type: 'uint256' }, - { name: 'verifyingContract', type: 'address' }, - ], - Test: [ - { name: 'owner', type: 'string' }, - { name: 'testArray', type: 'uint256[]' }, - ], - }, - domain: { - name: 'Opensea on Matic', - verifyingContract: '0x0', - version: '1', - chainId: '', - host: '', - }, - primaryType: 'Test', - message: { - owner: '0x56626bd0d646ce9da4a12403b2c1ba00fb9e1c43', - testArray: [], - }, - } - await runEthMsg(buildEthMsgReq(msg, 'eip712'), client) - }) + it('Should test an example with 0 values', async () => { + const msg = { + types: { + EIP712Domain: [ + { name: 'name', type: 'string' }, + { name: 'host', type: 'string' }, + { name: 'version', type: 'string' }, + { name: 'chainId', type: 'uint256' }, + { name: 'verifyingContract', type: 'address' }, + ], + Test: [ + { name: 'owner', type: 'string' }, + { name: 'testArray', type: 'uint256[]' }, + ], + }, + domain: { + name: 'Opensea on Matic', + verifyingContract: '0x0', + version: '1', + chainId: '', + host: '', + }, + primaryType: 'Test', + message: { + owner: '0x56626bd0d646ce9da4a12403b2c1ba00fb9e1c43', + testArray: [], + }, + } + await runEthMsg(buildEthMsgReq(msg, 'eip712'), client) + }) - it('Should test canonical EIP712 example', async () => { - const msg = { - types: { - EIP712Domain: [ - { name: 'name', type: 'string' }, - { name: 'version', type: 'string' }, - { name: 'chainId', type: 'uint256' }, - { name: 'verifyingContract', type: 'address' }, - ], - Person: [ - { name: 'name', type: 'string' }, - { name: 'wallet', type: 'address' }, - ], - Mail: [ - { name: 'from', type: 'Person' }, - { name: 'to', type: 'Person' }, - { name: 'contents', type: 'string' }, - ], - }, - primaryType: 'Mail', - domain: { - name: 'Ether Mail', - version: '1', - chainId: 12, - verifyingContract: '0xCcCCccccCCCCcCCCCCCcCcCccCcCCCcCcccccccC', - }, - message: { - from: { - name: 'Cow', - wallet: '0xCD2a3d9F938E13CD947Ec05AbC7FE734Df8DD826', - }, - to: { - name: 'Bob', - wallet: '0xbBbBBBBbbBBBbbbBbbBbbbbBBbBbbbbBbBbbBBbB', - }, - contents: 'foobar', - }, - } - await runEthMsg(buildEthMsgReq(msg, 'eip712'), client) - }) + it('Should test canonical EIP712 example', async () => { + const msg = { + types: { + EIP712Domain: [ + { name: 'name', type: 'string' }, + { name: 'version', type: 'string' }, + { name: 'chainId', type: 'uint256' }, + { name: 'verifyingContract', type: 'address' }, + ], + Person: [ + { name: 'name', type: 'string' }, + { name: 'wallet', type: 'address' }, + ], + Mail: [ + { name: 'from', type: 'Person' }, + { name: 'to', type: 'Person' }, + { name: 'contents', type: 'string' }, + ], + }, + primaryType: 'Mail', + domain: { + name: 'Ether Mail', + version: '1', + chainId: 12, + verifyingContract: '0xCcCCccccCCCCcCCCCCCcCcCccCcCCCcCcccccccC', + }, + message: { + from: { + name: 'Cow', + wallet: '0xCD2a3d9F938E13CD947Ec05AbC7FE734Df8DD826', + }, + to: { + name: 'Bob', + wallet: '0xbBbBBBBbbBBBbbbBbbBbbbbBBbBbbbbBbBbbBBbB', + }, + contents: 'foobar', + }, + } + await runEthMsg(buildEthMsgReq(msg, 'eip712'), client) + }) - it('Should test canonical EIP712 example with 2nd level nesting', async () => { - const msg = { - types: { - EIP712Domain: [ - { name: 'name', type: 'string' }, - { name: 'version', type: 'string' }, - { name: 'chainId', type: 'uint256' }, - { name: 'verifyingContract', type: 'address' }, - ], - Wallet: [ - { name: 'address', type: 'address' }, - { name: 'balance', type: 'uint256' }, - ], - Person: [ - { name: 'name', type: 'string' }, - { name: 'wallet', type: 'Wallet' }, - ], - Mail: [ - { name: 'from', type: 'Person' }, - { name: 'to', type: 'Person' }, - { name: 'contents', type: 'string' }, - ], - }, - primaryType: 'Mail', - domain: { - name: 'Ether Mail', - version: '1', - chainId: 12, - verifyingContract: '0xCcCCccccCCCCcCCCCCCcCcCccCcCCCcCcccccccC', - }, - message: { - from: { - name: 'Cow', - wallet: { - address: '0xCD2a3d9F938E13CD947Ec05AbC7FE734Df8DD826', - balance: '0x12345678', - }, - }, - to: { - name: 'Bob', - wallet: { - address: '0xbBbBBBBbbBBBbbbBbbBbbbbBBbBbbbbBbBbbBBbB', - balance: '0xabcdef12', - }, - }, - contents: 'foobar', - }, - } - await runEthMsg(buildEthMsgReq(msg, 'eip712'), client) - }) + it('Should test canonical EIP712 example with 2nd level nesting', async () => { + const msg = { + types: { + EIP712Domain: [ + { name: 'name', type: 'string' }, + { name: 'version', type: 'string' }, + { name: 'chainId', type: 'uint256' }, + { name: 'verifyingContract', type: 'address' }, + ], + Wallet: [ + { name: 'address', type: 'address' }, + { name: 'balance', type: 'uint256' }, + ], + Person: [ + { name: 'name', type: 'string' }, + { name: 'wallet', type: 'Wallet' }, + ], + Mail: [ + { name: 'from', type: 'Person' }, + { name: 'to', type: 'Person' }, + { name: 'contents', type: 'string' }, + ], + }, + primaryType: 'Mail', + domain: { + name: 'Ether Mail', + version: '1', + chainId: 12, + verifyingContract: '0xCcCCccccCCCCcCCCCCCcCcCccCcCCCcCcccccccC', + }, + message: { + from: { + name: 'Cow', + wallet: { + address: '0xCD2a3d9F938E13CD947Ec05AbC7FE734Df8DD826', + balance: '0x12345678', + }, + }, + to: { + name: 'Bob', + wallet: { + address: '0xbBbBBBBbbBBBbbbBbbBbbbbBBbBbbbbBbBbbBBbB', + balance: '0xabcdef12', + }, + }, + contents: 'foobar', + }, + } + await runEthMsg(buildEthMsgReq(msg, 'eip712'), client) + }) - it('Should test canonical EIP712 example with 3rd level nesting', async () => { - const msg = { - types: { - EIP712Domain: [ - { name: 'name', type: 'string' }, - { name: 'version', type: 'string' }, - { name: 'chainId', type: 'uint256' }, - { name: 'verifyingContract', type: 'address' }, - ], - Wallet: [ - { name: 'address', type: 'address' }, - { name: 'balance', type: 'Balance' }, - ], - Person: [ - { name: 'name', type: 'string' }, - { name: 'wallet', type: 'Wallet' }, - ], - Mail: [ - { name: 'from', type: 'Person' }, - { name: 'to', type: 'Person' }, - { name: 'contents', type: 'string' }, - ], - Balance: [ - { name: 'value', type: 'uint256' }, - { name: 'currency', type: 'string' }, - ], - }, - primaryType: 'Mail', - domain: { - name: 'Ether Mail', - version: '1', - chainId: 12, - verifyingContract: '0xCcCCccccCCCCcCCCCCCcCcCccCcCCCcCcccccccC', - }, - message: { - from: { - name: 'Cow', - wallet: { - address: '0xCD2a3d9F938E13CD947Ec05AbC7FE734Df8DD826', - balance: { - value: '0x12345678', - currency: 'ETH', - }, - }, - }, - to: { - name: 'Bob', - wallet: { - address: '0xbBbBBBBbbBBBbbbBbbBbbbbBBbBbbbbBbBbbBBbB', - balance: { - value: '0xabcdef12', - currency: 'UNI', - }, - }, - }, - contents: 'foobar', - }, - } - await runEthMsg(buildEthMsgReq(msg, 'eip712'), client) - }) + it('Should test canonical EIP712 example with 3rd level nesting', async () => { + const msg = { + types: { + EIP712Domain: [ + { name: 'name', type: 'string' }, + { name: 'version', type: 'string' }, + { name: 'chainId', type: 'uint256' }, + { name: 'verifyingContract', type: 'address' }, + ], + Wallet: [ + { name: 'address', type: 'address' }, + { name: 'balance', type: 'Balance' }, + ], + Person: [ + { name: 'name', type: 'string' }, + { name: 'wallet', type: 'Wallet' }, + ], + Mail: [ + { name: 'from', type: 'Person' }, + { name: 'to', type: 'Person' }, + { name: 'contents', type: 'string' }, + ], + Balance: [ + { name: 'value', type: 'uint256' }, + { name: 'currency', type: 'string' }, + ], + }, + primaryType: 'Mail', + domain: { + name: 'Ether Mail', + version: '1', + chainId: 12, + verifyingContract: '0xCcCCccccCCCCcCCCCCCcCcCccCcCCCcCcccccccC', + }, + message: { + from: { + name: 'Cow', + wallet: { + address: '0xCD2a3d9F938E13CD947Ec05AbC7FE734Df8DD826', + balance: { + value: '0x12345678', + currency: 'ETH', + }, + }, + }, + to: { + name: 'Bob', + wallet: { + address: '0xbBbBBBBbbBBBbbbBbbBbbbbBBbBbbbbBbBbbBBbB', + balance: { + value: '0xabcdef12', + currency: 'UNI', + }, + }, + }, + contents: 'foobar', + }, + } + await runEthMsg(buildEthMsgReq(msg, 'eip712'), client) + }) - it('Should test canonical EIP712 example with 3rd level nesting and params in a different order', async () => { - const msg = { - types: { - Balance: [ - { name: 'value', type: 'uint256' }, - { name: 'currency', type: 'string' }, - ], - EIP712Domain: [ - { name: 'name', type: 'string' }, - { name: 'version', type: 'string' }, - { name: 'chainId', type: 'uint256' }, - { name: 'verifyingContract', type: 'address' }, - ], - Person: [ - { name: 'name', type: 'string' }, - { name: 'wallet', type: 'Wallet' }, - ], - Wallet: [ - { name: 'address', type: 'address' }, - { name: 'balance', type: 'Balance' }, - ], - Mail: [ - { name: 'from', type: 'Person' }, - { name: 'to', type: 'Person' }, - { name: 'contents', type: 'string' }, - ], - }, - primaryType: 'Mail', - domain: { - name: 'Ether Mail', - version: '1', - chainId: 12, - verifyingContract: '0xCcCCccccCCCCcCCCCCCcCcCccCcCCCcCcccccccC', - }, - message: { - contents: 'foobar', - from: { - name: 'Cow', - wallet: { - address: '0xCD2a3d9F938E13CD947Ec05AbC7FE734Df8DD826', - balance: { - value: '0x12345678', - currency: 'ETH', - }, - }, - }, - to: { - name: 'Bob', - wallet: { - address: '0xbBbBBBBbbBBBbbbBbbBbbbbBBbBbbbbBbBbbBBbB', - balance: { - value: '0xabcdef12', - currency: 'UNI', - }, - }, - }, - }, - } - await runEthMsg(buildEthMsgReq(msg, 'eip712'), client) - }) + it('Should test canonical EIP712 example with 3rd level nesting and params in a different order', async () => { + const msg = { + types: { + Balance: [ + { name: 'value', type: 'uint256' }, + { name: 'currency', type: 'string' }, + ], + EIP712Domain: [ + { name: 'name', type: 'string' }, + { name: 'version', type: 'string' }, + { name: 'chainId', type: 'uint256' }, + { name: 'verifyingContract', type: 'address' }, + ], + Person: [ + { name: 'name', type: 'string' }, + { name: 'wallet', type: 'Wallet' }, + ], + Wallet: [ + { name: 'address', type: 'address' }, + { name: 'balance', type: 'Balance' }, + ], + Mail: [ + { name: 'from', type: 'Person' }, + { name: 'to', type: 'Person' }, + { name: 'contents', type: 'string' }, + ], + }, + primaryType: 'Mail', + domain: { + name: 'Ether Mail', + version: '1', + chainId: 12, + verifyingContract: '0xCcCCccccCCCCcCCCCCCcCcCccCcCCCcCcccccccC', + }, + message: { + contents: 'foobar', + from: { + name: 'Cow', + wallet: { + address: '0xCD2a3d9F938E13CD947Ec05AbC7FE734Df8DD826', + balance: { + value: '0x12345678', + currency: 'ETH', + }, + }, + }, + to: { + name: 'Bob', + wallet: { + address: '0xbBbBBBBbbBBBbbbBbbBbbbbBBbBbbbbBbBbbBBbB', + balance: { + value: '0xabcdef12', + currency: 'UNI', + }, + }, + }, + }, + } + await runEthMsg(buildEthMsgReq(msg, 'eip712'), client) + }) - it('Should test a payload with an array type', async () => { - const msg = { - types: { - EIP712Domain: [{ name: 'name', type: 'string' }], - UserVotePayload: [ - { - name: 'allocations', - type: 'UserVoteAllocationItem[]', - }, - ], - UserVoteAllocationItem: [ - { - name: 'reactorKey', - type: 'bytes32', - }, - { - name: 'amount', - type: 'uint256', - }, - ], - }, - primaryType: 'UserVotePayload', - domain: { - name: 'Tokemak Voting', - version: '1', - chainId: 1, - verifyingContract: '0x4495982ea5ed9c1b7cec37434cbf930b9472e823', - }, - message: { - allocations: [ - { - reactorKey: - '0x6f686d2d64656661756c74000000000000000000000000000000000000000000', - amount: '1', - }, - { - reactorKey: - '0x6f686d2d64656661756c74000000000000000000000000000000000000000000', - amount: '2', - }, - ], - }, - } - await runEthMsg(buildEthMsgReq(msg, 'eip712'), client) - }) + it('Should test a payload with an array type', async () => { + const msg = { + types: { + EIP712Domain: [{ name: 'name', type: 'string' }], + UserVotePayload: [ + { + name: 'allocations', + type: 'UserVoteAllocationItem[]', + }, + ], + UserVoteAllocationItem: [ + { + name: 'reactorKey', + type: 'bytes32', + }, + { + name: 'amount', + type: 'uint256', + }, + ], + }, + primaryType: 'UserVotePayload', + domain: { + name: 'Tokemak Voting', + version: '1', + chainId: 1, + verifyingContract: '0x4495982ea5ed9c1b7cec37434cbf930b9472e823', + }, + message: { + allocations: [ + { + reactorKey: + '0x6f686d2d64656661756c74000000000000000000000000000000000000000000', + amount: '1', + }, + { + reactorKey: + '0x6f686d2d64656661756c74000000000000000000000000000000000000000000', + amount: '2', + }, + ], + }, + } + await runEthMsg(buildEthMsgReq(msg, 'eip712'), client) + }) - it('Should test multiple array types', async () => { - const msg = { - types: { - EIP712Domain: [{ name: 'name', type: 'string' }], - UserVotePayload: [ - { - name: 'integer', - type: 'uint256', - }, - { - name: 'allocations', - type: 'UserVoteAllocationItem[]', - }, - { - name: 'dummy', - type: 'uint256', - }, - { - name: 'integerArray', - type: 'uint256[]', - }, - ], - UserVoteAllocationItem: [ - { - name: 'reactorKey', - type: 'bytes32', - }, - { - name: 'amount', - type: 'uint256', - }, - ], - }, - primaryType: 'UserVotePayload', - domain: { - name: 'Tokemak Voting', - }, - message: { - integer: 56, - allocations: [ - { - reactorKey: - '0x6f686d2d64656661756c74000000000000000000000000000000000000000000', - amount: '1', - }, - { - reactorKey: - '0x6f686d2d64656661756c74000000000000000000000000000000000000000000', - amount: '2', - }, - ], - dummy: 52, - integerArray: [1, 2, 3], - }, - } - await runEthMsg(buildEthMsgReq(msg, 'eip712'), client) - }) + it('Should test multiple array types', async () => { + const msg = { + types: { + EIP712Domain: [{ name: 'name', type: 'string' }], + UserVotePayload: [ + { + name: 'integer', + type: 'uint256', + }, + { + name: 'allocations', + type: 'UserVoteAllocationItem[]', + }, + { + name: 'dummy', + type: 'uint256', + }, + { + name: 'integerArray', + type: 'uint256[]', + }, + ], + UserVoteAllocationItem: [ + { + name: 'reactorKey', + type: 'bytes32', + }, + { + name: 'amount', + type: 'uint256', + }, + ], + }, + primaryType: 'UserVotePayload', + domain: { + name: 'Tokemak Voting', + }, + message: { + integer: 56, + allocations: [ + { + reactorKey: + '0x6f686d2d64656661756c74000000000000000000000000000000000000000000', + amount: '1', + }, + { + reactorKey: + '0x6f686d2d64656661756c74000000000000000000000000000000000000000000', + amount: '2', + }, + ], + dummy: 52, + integerArray: [1, 2, 3], + }, + } + await runEthMsg(buildEthMsgReq(msg, 'eip712'), client) + }) - it('Should test a nested array', async () => { - const msg = { - types: { - EIP712Domain: [{ name: 'name', type: 'string' }], - UserVotePayload: [ - { - name: 'allocations', - type: 'UserVoteAllocationItem[]', - }, - ], - UserVoteAllocationItem: [ - { - name: 'reactorKey', - type: 'bytes32', - }, - { - name: 'amount', - type: 'uint256[]', - }, - ], - }, - primaryType: 'UserVotePayload', - domain: { - name: 'Tokemak Voting', - }, - message: { - allocations: [ - { - reactorKey: - '0x6f686d2d64656661756c74000000000000000000000000000000000000000000', - amount: ['1', '2'], - }, - { - reactorKey: - '0x6f686d2d64656661756c74000000000000000000000000000000000000000000', - amount: ['2', '3'], - }, - ], - }, - } - await runEthMsg(buildEthMsgReq(msg, 'eip712'), client) - }) + it('Should test a nested array', async () => { + const msg = { + types: { + EIP712Domain: [{ name: 'name', type: 'string' }], + UserVotePayload: [ + { + name: 'allocations', + type: 'UserVoteAllocationItem[]', + }, + ], + UserVoteAllocationItem: [ + { + name: 'reactorKey', + type: 'bytes32', + }, + { + name: 'amount', + type: 'uint256[]', + }, + ], + }, + primaryType: 'UserVotePayload', + domain: { + name: 'Tokemak Voting', + }, + message: { + allocations: [ + { + reactorKey: + '0x6f686d2d64656661756c74000000000000000000000000000000000000000000', + amount: ['1', '2'], + }, + { + reactorKey: + '0x6f686d2d64656661756c74000000000000000000000000000000000000000000', + amount: ['2', '3'], + }, + ], + }, + } + await runEthMsg(buildEthMsgReq(msg, 'eip712'), client) + }) - it('Should test a nested array of custom type', async () => { - const msg = { - types: { - EIP712Domain: [{ name: 'name', type: 'string' }], - DummyThing: [ - { - name: 'foo', - type: 'bytes', - }, - ], - UserVotePayload: [ - { - name: 'test', - type: 'string', - }, - { - name: 'athing', - type: 'uint32', - }, - { - name: 'allocations', - type: 'UserVoteAllocationItem[]', - }, - ], - UserVoteAllocationItem: [ - { - name: 'reactorKey', - type: 'bytes32', - }, - { - name: 'dummy', - type: 'DummyThing[]', - }, - ], - }, - primaryType: 'UserVotePayload', - domain: { - name: 'Tokemak Voting', - }, - message: { - athing: 5, - test: 'hello', - allocations: [ - { - reactorKey: - '0x6f686d2d64656661756c74000000000000000000000000000000000000000000', - dummy: [ - { - foo: '0xabcd', - }, - { - foo: '0x123456', - }, - ], - }, - { - reactorKey: - '0x6f686d2d64656661756c74000000000000000000000000000000000000000000', - dummy: [ - { - foo: '0xdeadbeef', - }, - { - foo: '0x', - }, - ], - }, - ], - }, - } - await runEthMsg(buildEthMsgReq(msg, 'eip712'), client) - }) + it('Should test a nested array of custom type', async () => { + const msg = { + types: { + EIP712Domain: [{ name: 'name', type: 'string' }], + DummyThing: [ + { + name: 'foo', + type: 'bytes', + }, + ], + UserVotePayload: [ + { + name: 'test', + type: 'string', + }, + { + name: 'athing', + type: 'uint32', + }, + { + name: 'allocations', + type: 'UserVoteAllocationItem[]', + }, + ], + UserVoteAllocationItem: [ + { + name: 'reactorKey', + type: 'bytes32', + }, + { + name: 'dummy', + type: 'DummyThing[]', + }, + ], + }, + primaryType: 'UserVotePayload', + domain: { + name: 'Tokemak Voting', + }, + message: { + athing: 5, + test: 'hello', + allocations: [ + { + reactorKey: + '0x6f686d2d64656661756c74000000000000000000000000000000000000000000', + dummy: [ + { + foo: '0xabcd', + }, + { + foo: '0x123456', + }, + ], + }, + { + reactorKey: + '0x6f686d2d64656661756c74000000000000000000000000000000000000000000', + dummy: [ + { + foo: '0xdeadbeef', + }, + { + foo: '0x', + }, + ], + }, + ], + }, + } + await runEthMsg(buildEthMsgReq(msg, 'eip712'), client) + }) - it('Should test a bunch of EIP712 data types', async () => { - const msg = { - types: { - EIP712Domain: [ - { - name: 'name', - type: 'string', - }, - { - name: 'version', - type: 'string', - }, - { - name: 'chainId', - type: 'uint256', - }, - { - name: 'verifyingContract', - type: 'address', - }, - ], - PrimaryStuff: [ - { name: 'UINT8', type: 'uint8' }, - { name: 'UINT16', type: 'uint16' }, - { name: 'UINT32', type: 'uint32' }, - { name: 'UINT64', type: 'uint64' }, - { name: 'UINT256', type: 'uint256' }, - { name: 'BYTES1', type: 'bytes1' }, - { name: 'BYTES5', type: 'bytes5' }, - { name: 'BYTES7', type: 'bytes7' }, - { name: 'BYTES12', type: 'bytes12' }, - { name: 'BYTES16', type: 'bytes16' }, - { name: 'BYTES20', type: 'bytes20' }, - { name: 'BYTES21', type: 'bytes21' }, - { name: 'BYTES31', type: 'bytes31' }, - { name: 'BYTES32', type: 'bytes32' }, - { name: 'BYTES', type: 'bytes' }, - { name: 'STRING', type: 'string' }, - { name: 'BOOL', type: 'bool' }, - { name: 'ADDRESS', type: 'address' }, - ], - }, - primaryType: 'PrimaryStuff', - domain: { - name: 'Muh Domainz', - version: '1', - chainId: 270, - verifyingContract: '0xcc9c93cef8c70a7b46e32b3635d1a746ee0ec5b4', - }, - message: { - UINT8: '0xab', - UINT16: '0xb1d7', - UINT32: '0x80bb335b', - UINT64: '0x259528d5bc', - UINT256: '0xad2693f24ba507750d1763ebae3661c07504', - BYTES1: '0x2f', - BYTES5: '0x9485269fa5', - BYTES7: '0xc4e8d65ce8c3cf', - BYTES12: '0x358eb7b28e8e1643e7c4737f', - BYTES16: '0x7ace034ab088fdd434f1e817f32171a0', - BYTES20: '0x4ab51f2d5bfdc0f1b96f83358d5f356c98583573', - BYTES21: '0x6ecdc19b30c7fa712ba334458d77377b6a586bbab5', - BYTES31: - '0x06c21824a98643f96643b3220962f441210b007f4c19dfdf0dea53d097fc28', - BYTES32: - '0x59cfcbf35256451756b02fa644d3d0748bd98f5904febf3433e6df19b4df7452', - BYTES: - '0x0354b2c449772905b2598a93f5da69962f0444e0a6e2429e8f844f1011446f6fe81815846fb6ebe2d213968d1f8532749735f5702f565db0429b2fe596d295d9c06241389fe97fb2f3b91e1e0f2d978fb26e366737451f1193097bd0a2332e0bfc0cdb631005', - STRING: 'I am a string hello there human', - BOOL: true, - ADDRESS: '0x078a8d6eba928e7ea787ed48f71c5936aed4625d', - }, - } - await runEthMsg(buildEthMsgReq(msg, 'eip712'), client) - }) + it('Should test a bunch of EIP712 data types', async () => { + const msg = { + types: { + EIP712Domain: [ + { + name: 'name', + type: 'string', + }, + { + name: 'version', + type: 'string', + }, + { + name: 'chainId', + type: 'uint256', + }, + { + name: 'verifyingContract', + type: 'address', + }, + ], + PrimaryStuff: [ + { name: 'UINT8', type: 'uint8' }, + { name: 'UINT16', type: 'uint16' }, + { name: 'UINT32', type: 'uint32' }, + { name: 'UINT64', type: 'uint64' }, + { name: 'UINT256', type: 'uint256' }, + { name: 'BYTES1', type: 'bytes1' }, + { name: 'BYTES5', type: 'bytes5' }, + { name: 'BYTES7', type: 'bytes7' }, + { name: 'BYTES12', type: 'bytes12' }, + { name: 'BYTES16', type: 'bytes16' }, + { name: 'BYTES20', type: 'bytes20' }, + { name: 'BYTES21', type: 'bytes21' }, + { name: 'BYTES31', type: 'bytes31' }, + { name: 'BYTES32', type: 'bytes32' }, + { name: 'BYTES', type: 'bytes' }, + { name: 'STRING', type: 'string' }, + { name: 'BOOL', type: 'bool' }, + { name: 'ADDRESS', type: 'address' }, + ], + }, + primaryType: 'PrimaryStuff', + domain: { + name: 'Muh Domainz', + version: '1', + chainId: 270, + verifyingContract: '0xcc9c93cef8c70a7b46e32b3635d1a746ee0ec5b4', + }, + message: { + UINT8: '0xab', + UINT16: '0xb1d7', + UINT32: '0x80bb335b', + UINT64: '0x259528d5bc', + UINT256: '0xad2693f24ba507750d1763ebae3661c07504', + BYTES1: '0x2f', + BYTES5: '0x9485269fa5', + BYTES7: '0xc4e8d65ce8c3cf', + BYTES12: '0x358eb7b28e8e1643e7c4737f', + BYTES16: '0x7ace034ab088fdd434f1e817f32171a0', + BYTES20: '0x4ab51f2d5bfdc0f1b96f83358d5f356c98583573', + BYTES21: '0x6ecdc19b30c7fa712ba334458d77377b6a586bbab5', + BYTES31: + '0x06c21824a98643f96643b3220962f441210b007f4c19dfdf0dea53d097fc28', + BYTES32: + '0x59cfcbf35256451756b02fa644d3d0748bd98f5904febf3433e6df19b4df7452', + BYTES: + '0x0354b2c449772905b2598a93f5da69962f0444e0a6e2429e8f844f1011446f6fe81815846fb6ebe2d213968d1f8532749735f5702f565db0429b2fe596d295d9c06241389fe97fb2f3b91e1e0f2d978fb26e366737451f1193097bd0a2332e0bfc0cdb631005', + STRING: 'I am a string hello there human', + BOOL: true, + ADDRESS: '0x078a8d6eba928e7ea787ed48f71c5936aed4625d', + }, + } + await runEthMsg(buildEthMsgReq(msg, 'eip712'), client) + }) - it('Should test a payload with a nested type in multiple nesting levels', async () => { - const msg = { - types: { - EIP712Domain: [ - { - name: 'name', - type: 'string', - }, - ], - PrimaryType: [ - { - name: 'one', - type: 'Type1', - }, - { - name: 'zero', - type: 'Type0', - }, - ], - Type1: [ - { - name: '1s', - type: 'string', - }, - ], - Type0: [ - { - name: 'one', - type: 'Type1', - }, - ], - }, - primaryType: 'PrimaryType', - domain: { - name: 'Domain', - }, - message: { - one: { - '1s': 'nestedOne', - }, - zero: { - one: { - '1s': 'nestedTwo', - }, - }, - }, - } - await runEthMsg(buildEthMsgReq(msg, 'eip712'), client) - }) + it('Should test a payload with a nested type in multiple nesting levels', async () => { + const msg = { + types: { + EIP712Domain: [ + { + name: 'name', + type: 'string', + }, + ], + PrimaryType: [ + { + name: 'one', + type: 'Type1', + }, + { + name: 'zero', + type: 'Type0', + }, + ], + Type1: [ + { + name: '1s', + type: 'string', + }, + ], + Type0: [ + { + name: 'one', + type: 'Type1', + }, + ], + }, + primaryType: 'PrimaryType', + domain: { + name: 'Domain', + }, + message: { + one: { + '1s': 'nestedOne', + }, + zero: { + one: { + '1s': 'nestedTwo', + }, + }, + }, + } + await runEthMsg(buildEthMsgReq(msg, 'eip712'), client) + }) - it('Should test a payload that requires use of extraData frames', async () => { - const msg = { - types: { - EIP712Domain: [ - { name: 'name', type: 'string' }, - { name: 'version', type: 'string' }, - { name: 'chainId', type: 'uint256' }, - { name: 'verifyingContract', type: 'address' }, - ], - Wallet: [ - { name: 'address', type: 'address' }, - { name: 'balance', type: 'Balance' }, - ], - Person: [ - { name: 'name', type: 'string' }, - { name: 'wallet', type: 'Wallet' }, - ], - Mail: [ - { name: 'from', type: 'Person' }, - { name: 'to', type: 'Person' }, - { name: 'contents', type: 'string' }, - ], - Balance: [ - { name: 'value', type: 'uint256' }, - { name: 'currency', type: 'string' }, - ], - }, - primaryType: 'Mail', - domain: { - name: 'Ether Mail', - version: '1', - chainId: 12, - verifyingContract: '0xCcCCccccCCCCcCCCCCCcCcCccCcCCCcCcccccccC', - }, - message: { - from: { - name: 'Cow', - wallet: { - address: '0xCD2a3d9F938E13CD947Ec05AbC7FE734Df8DD826', - balance: { - value: '0x12345678', - currency: 'ETH', - }, - }, - }, - to: { - name: 'Bob', - wallet: { - address: '0xbBbBBBBbbBBBbbbBbbBbbbbBBbBbbbbBbBbbBBbB', - balance: { - value: '0xabcdef12', - currency: 'UNI', - }, - }, - }, - contents: - 'stupidlylongstringthatshouldstretchintomultiplepageswhencopiedmanytimesstupidlylongstringthatshouldstretchintomultiplepageswhencopiedmanytimesstupidlylongstringthatshouldstretchintomultiplepageswhencopiedmanytimesstupidlylongstringthatshouldstretchintomultiplepageswhencopiedmanytimesstupidlylongstringthatshouldstretchintomultiplepageswhencopiedmanytimesstupidlylongstringthatshouldstretchintomultiplepageswhencopiedmanytimesstupidlylongstringthatshouldstretchintomultiplepageswhencopiedmanytimesstupidlylongstringthatshouldstretchintomultiplepageswhencopiedmanytimesstupidlylongstringthatshouldstretchintomultiplepageswhencopiedmanytimesstupidlylongstringthatshouldstretchintomultiplepageswhencopiedmanytimesstupidlylongstringthatshouldstretchintomultiplepageswhencopiedmanytimesstupidlylongstringthatshouldstretchintomultiplepageswhencopiedmanytimesstupidlylongstringthatshouldstretchintomultiplepageswhencopiedmanytimesstupidlylongstringthatshouldstretchintomultiplepageswhencopiedmanytimesstupidlylongstringthatshouldstretchintomultiplepageswhencopiedmanytimesstupidlylongstringthatshouldstretchintomultiplepageswhencopiedmanytimesstupidlylongstringthatshouldstretchintomultiplepageswhencopiedmanytimesstupidlylongstringthatshouldstretchintomultiplepageswhencopiedmanytimesstupidlylongstringthatshouldstretchintomultiplepageswhencopiedmanytimesstupidlylongstringthatshouldstretchintomultiplepageswhencopiedmanytimesstupidlylongstringthatshouldstretchintomultiplepageswhencopiedmanytimesstupidlylongstringthatshouldstretchintomultiplepageswhencopiedmanytimesstupidlylongstringthatshouldstretchintomultiplepageswhencopiedmanytimesstupidlylongstringthatshouldstretchintomultiplepageswhencopiedmanytimesstupidlylongstringthatshouldstretchintomultiplepageswhencopiedmanytimesstupidlylongstringthatshouldstretchintomultiplepageswhencopiedmanytimesstupidlylongstringthatshouldstretchintomultiplepageswhencopiedmanytimesstupidlylongstringthatshouldstretchintomultiplepageswhencopiedmanytimesstupidlylongstringthatshouldstretchintomultiplepageswhencopiedmanytimesstupidlylongstringthatshouldstretchintomultiplepageswhencopiedmanytimesstupidlylongstringthatshouldstretchintomultiplepageswhencopiedmanytimesstupidlylongstringthatshouldstretchintomultiplepageswhencopiedmanytimesstupidlylongstringthatshouldstretchintomultiplepageswhencopiedmanytimes', - }, - } - await runEthMsg(buildEthMsgReq(msg, 'eip712'), client) - }) + it('Should test a payload that requires use of extraData frames', async () => { + const msg = { + types: { + EIP712Domain: [ + { name: 'name', type: 'string' }, + { name: 'version', type: 'string' }, + { name: 'chainId', type: 'uint256' }, + { name: 'verifyingContract', type: 'address' }, + ], + Wallet: [ + { name: 'address', type: 'address' }, + { name: 'balance', type: 'Balance' }, + ], + Person: [ + { name: 'name', type: 'string' }, + { name: 'wallet', type: 'Wallet' }, + ], + Mail: [ + { name: 'from', type: 'Person' }, + { name: 'to', type: 'Person' }, + { name: 'contents', type: 'string' }, + ], + Balance: [ + { name: 'value', type: 'uint256' }, + { name: 'currency', type: 'string' }, + ], + }, + primaryType: 'Mail', + domain: { + name: 'Ether Mail', + version: '1', + chainId: 12, + verifyingContract: '0xCcCCccccCCCCcCCCCCCcCcCccCcCCCcCcccccccC', + }, + message: { + from: { + name: 'Cow', + wallet: { + address: '0xCD2a3d9F938E13CD947Ec05AbC7FE734Df8DD826', + balance: { + value: '0x12345678', + currency: 'ETH', + }, + }, + }, + to: { + name: 'Bob', + wallet: { + address: '0xbBbBBBBbbBBBbbbBbbBbbbbBBbBbbbbBbBbbBBbB', + balance: { + value: '0xabcdef12', + currency: 'UNI', + }, + }, + }, + contents: + 'stupidlylongstringthatshouldstretchintomultiplepageswhencopiedmanytimesstupidlylongstringthatshouldstretchintomultiplepageswhencopiedmanytimesstupidlylongstringthatshouldstretchintomultiplepageswhencopiedmanytimesstupidlylongstringthatshouldstretchintomultiplepageswhencopiedmanytimesstupidlylongstringthatshouldstretchintomultiplepageswhencopiedmanytimesstupidlylongstringthatshouldstretchintomultiplepageswhencopiedmanytimesstupidlylongstringthatshouldstretchintomultiplepageswhencopiedmanytimesstupidlylongstringthatshouldstretchintomultiplepageswhencopiedmanytimesstupidlylongstringthatshouldstretchintomultiplepageswhencopiedmanytimesstupidlylongstringthatshouldstretchintomultiplepageswhencopiedmanytimesstupidlylongstringthatshouldstretchintomultiplepageswhencopiedmanytimesstupidlylongstringthatshouldstretchintomultiplepageswhencopiedmanytimesstupidlylongstringthatshouldstretchintomultiplepageswhencopiedmanytimesstupidlylongstringthatshouldstretchintomultiplepageswhencopiedmanytimesstupidlylongstringthatshouldstretchintomultiplepageswhencopiedmanytimesstupidlylongstringthatshouldstretchintomultiplepageswhencopiedmanytimesstupidlylongstringthatshouldstretchintomultiplepageswhencopiedmanytimesstupidlylongstringthatshouldstretchintomultiplepageswhencopiedmanytimesstupidlylongstringthatshouldstretchintomultiplepageswhencopiedmanytimesstupidlylongstringthatshouldstretchintomultiplepageswhencopiedmanytimesstupidlylongstringthatshouldstretchintomultiplepageswhencopiedmanytimesstupidlylongstringthatshouldstretchintomultiplepageswhencopiedmanytimesstupidlylongstringthatshouldstretchintomultiplepageswhencopiedmanytimesstupidlylongstringthatshouldstretchintomultiplepageswhencopiedmanytimesstupidlylongstringthatshouldstretchintomultiplepageswhencopiedmanytimesstupidlylongstringthatshouldstretchintomultiplepageswhencopiedmanytimesstupidlylongstringthatshouldstretchintomultiplepageswhencopiedmanytimesstupidlylongstringthatshouldstretchintomultiplepageswhencopiedmanytimesstupidlylongstringthatshouldstretchintomultiplepageswhencopiedmanytimesstupidlylongstringthatshouldstretchintomultiplepageswhencopiedmanytimesstupidlylongstringthatshouldstretchintomultiplepageswhencopiedmanytimesstupidlylongstringthatshouldstretchintomultiplepageswhencopiedmanytimesstupidlylongstringthatshouldstretchintomultiplepageswhencopiedmanytimes', + }, + } + await runEthMsg(buildEthMsgReq(msg, 'eip712'), client) + }) - it('Should test a message with very large types', async () => { - const msg = { - types: { - EIP712Domain: [ - { - name: 'name', - type: 'string', - }, - { - name: 'version', - type: 'string', - }, - { - name: 'chainId', - type: 'uint256', - }, - { - name: 'verifyingContract', - type: 'address', - }, - ], - Order: [ - { - name: 'exchange', - type: 'address', - }, - { - name: 'maker', - type: 'address', - }, - { - name: 'taker', - type: 'address', - }, - { - name: 'makerRelayerFee', - type: 'uint256', - }, - { - name: 'takerRelayerFee', - type: 'uint256', - }, - { - name: 'makerProtocolFee', - type: 'uint256', - }, - { - name: 'takerProtocolFee', - type: 'uint256', - }, - { - name: 'feeRecipient', - type: 'address', - }, - { - name: 'feeMethod', - type: 'uint8', - }, - { - name: 'side', - type: 'uint8', - }, - { - name: 'saleKind', - type: 'uint8', - }, - { - name: 'target', - type: 'address', - }, - { - name: 'howToCall', - type: 'uint8', - }, - { - name: 'calldata', - type: 'bytes', - }, - { - name: 'replacementPattern', - type: 'bytes', - }, - { - name: 'staticTarget', - type: 'address', - }, - { - name: 'staticExtradata', - type: 'bytes', - }, - { - name: 'paymentToken', - type: 'address', - }, - { - name: 'basePrice', - type: 'uint256', - }, - { - name: 'extra', - type: 'uint256', - }, - { - name: 'listingTime', - type: 'uint256', - }, - { - name: 'expirationTime', - type: 'uint256', - }, - { - name: 'salt', - type: 'uint256', - }, - { - name: 'nonce', - type: 'uint256', - }, - ], - }, - domain: { - name: 'Wyvern Exchange Contract', - version: '2.3', - chainId: 1, - verifyingContract: '0x7f268357a8c2552623316e2562d90e642bb538e5', - }, - primaryType: 'Order', - message: { - maker: '0x44fa5d521a02db7ce5a88842a6842496f84009bc', - exchange: '0x7f268357a8c2552623316e2562d90e642bb538e5', - taker: '0x0000000000000000000000000000000000000000', - makerRelayerFee: '750', - takerRelayerFee: '0', - makerProtocolFee: '0', - takerProtocolFee: '0', - feeRecipient: '0x5b3256965e7c3cf26e11fcaf296dfc8807c01073', - feeMethod: 1, - side: 1, - saleKind: 0, - target: '0xbaf2127b49fc93cbca6269fade0f7f31df4c88a7', - howToCall: 1, - calldata: - '0xfb16a59500000000000000000000000044fa5d521a02db7ce5a88842a6842496f84009bc000000000000000000000000000000000000000000000000000000000000000000000000000000000000000001a9f037d4cd7da318ab097a47acd4dea3abc083000000000000000000000000000000000000000000000000000000000000028a000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000c00000000000000000000000000000000000000000000000000000000000000000', - replacementPattern: - '0x000000000000000000000000000000000000000000000000000000000000000000000000ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff00000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000', - staticTarget: '0x0000000000000000000000000000000000000000', - staticExtradata: '0x', - paymentToken: '0x0000000000000000000000000000000000000000', - basePrice: '1000000000000000000', - extra: '0', - listingTime: '1645233344', - expirationTime: '1645838240', - salt: '35033335384310326785897317545538185126505283328747281434561962939625063440824', - nonce: 0, - }, - } - await runEthMsg(buildEthMsgReq(msg, 'eip712'), client) - }) + it('Should test a message with very large types', async () => { + const msg = { + types: { + EIP712Domain: [ + { + name: 'name', + type: 'string', + }, + { + name: 'version', + type: 'string', + }, + { + name: 'chainId', + type: 'uint256', + }, + { + name: 'verifyingContract', + type: 'address', + }, + ], + Order: [ + { + name: 'exchange', + type: 'address', + }, + { + name: 'maker', + type: 'address', + }, + { + name: 'taker', + type: 'address', + }, + { + name: 'makerRelayerFee', + type: 'uint256', + }, + { + name: 'takerRelayerFee', + type: 'uint256', + }, + { + name: 'makerProtocolFee', + type: 'uint256', + }, + { + name: 'takerProtocolFee', + type: 'uint256', + }, + { + name: 'feeRecipient', + type: 'address', + }, + { + name: 'feeMethod', + type: 'uint8', + }, + { + name: 'side', + type: 'uint8', + }, + { + name: 'saleKind', + type: 'uint8', + }, + { + name: 'target', + type: 'address', + }, + { + name: 'howToCall', + type: 'uint8', + }, + { + name: 'calldata', + type: 'bytes', + }, + { + name: 'replacementPattern', + type: 'bytes', + }, + { + name: 'staticTarget', + type: 'address', + }, + { + name: 'staticExtradata', + type: 'bytes', + }, + { + name: 'paymentToken', + type: 'address', + }, + { + name: 'basePrice', + type: 'uint256', + }, + { + name: 'extra', + type: 'uint256', + }, + { + name: 'listingTime', + type: 'uint256', + }, + { + name: 'expirationTime', + type: 'uint256', + }, + { + name: 'salt', + type: 'uint256', + }, + { + name: 'nonce', + type: 'uint256', + }, + ], + }, + domain: { + name: 'Wyvern Exchange Contract', + version: '2.3', + chainId: 1, + verifyingContract: '0x7f268357a8c2552623316e2562d90e642bb538e5', + }, + primaryType: 'Order', + message: { + maker: '0x44fa5d521a02db7ce5a88842a6842496f84009bc', + exchange: '0x7f268357a8c2552623316e2562d90e642bb538e5', + taker: '0x0000000000000000000000000000000000000000', + makerRelayerFee: '750', + takerRelayerFee: '0', + makerProtocolFee: '0', + takerProtocolFee: '0', + feeRecipient: '0x5b3256965e7c3cf26e11fcaf296dfc8807c01073', + feeMethod: 1, + side: 1, + saleKind: 0, + target: '0xbaf2127b49fc93cbca6269fade0f7f31df4c88a7', + howToCall: 1, + calldata: + '0xfb16a59500000000000000000000000044fa5d521a02db7ce5a88842a6842496f84009bc000000000000000000000000000000000000000000000000000000000000000000000000000000000000000001a9f037d4cd7da318ab097a47acd4dea3abc083000000000000000000000000000000000000000000000000000000000000028a000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000c00000000000000000000000000000000000000000000000000000000000000000', + replacementPattern: + '0x000000000000000000000000000000000000000000000000000000000000000000000000ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff00000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000', + staticTarget: '0x0000000000000000000000000000000000000000', + staticExtradata: '0x', + paymentToken: '0x0000000000000000000000000000000000000000', + basePrice: '1000000000000000000', + extra: '0', + listingTime: '1645233344', + expirationTime: '1645838240', + salt: '35033335384310326785897317545538185126505283328747281434561962939625063440824', + nonce: 0, + }, + } + await runEthMsg(buildEthMsgReq(msg, 'eip712'), client) + }) - it('Should test random edge case #1', async () => { - // This was a randomly generated payload which caused an edge case. - // It has been slimmed down but definition structure is preserved. - const msg = { - types: { - EIP712Domain: [ - { - name: 'name', - type: 'string', - }, - { - name: 'version', - type: 'string', - }, - { - name: 'chainId', - type: 'uint256', - }, - { - name: 'verifyingContract', - type: 'address', - }, - ], - Primary_Click: [ - { - name: 'utility', - type: 'Expose', - }, - { - name: 'aisle', - type: 'Cancel', - }, - { - name: 'gym', - type: 'Razor', - }, - { - name: 'drift_patch_cable_bi', - type: 'bytes1', - }, - ], - Expose: [ - { - name: 'favorite', - type: 'bytes21', - }, - ], - Cancel: [ - { - name: 'clever', - type: 'uint200', - }, - ], - Razor: [ - { - name: 'private', - type: 'bytes2', - }, - ], - }, - primaryType: 'Primary_Click', - domain: { - name: 'Domain_Avocado_luggage_twel', - version: '1', - chainId: '0x324e', - verifyingContract: '0x69f758a7911448c2f7aa6df15ca27d69ffa1c6b7', - }, - message: { - utility: { - favorite: '0x891b56dc6ab87ab73cf69761183d499283f1925871', - }, - aisle: { - clever: '0x0102', - }, - gym: { - private: '0xbb42', - }, - drift_patch_cable_bi: '0xb4', - }, - } - await runEthMsg(buildEthMsgReq(msg, 'eip712'), client) - }) + it('Should test random edge case #1', async () => { + // This was a randomly generated payload which caused an edge case. + // It has been slimmed down but definition structure is preserved. + const msg = { + types: { + EIP712Domain: [ + { + name: 'name', + type: 'string', + }, + { + name: 'version', + type: 'string', + }, + { + name: 'chainId', + type: 'uint256', + }, + { + name: 'verifyingContract', + type: 'address', + }, + ], + Primary_Click: [ + { + name: 'utility', + type: 'Expose', + }, + { + name: 'aisle', + type: 'Cancel', + }, + { + name: 'gym', + type: 'Razor', + }, + { + name: 'drift_patch_cable_bi', + type: 'bytes1', + }, + ], + Expose: [ + { + name: 'favorite', + type: 'bytes21', + }, + ], + Cancel: [ + { + name: 'clever', + type: 'uint200', + }, + ], + Razor: [ + { + name: 'private', + type: 'bytes2', + }, + ], + }, + primaryType: 'Primary_Click', + domain: { + name: 'Domain_Avocado_luggage_twel', + version: '1', + chainId: '0x324e', + verifyingContract: '0x69f758a7911448c2f7aa6df15ca27d69ffa1c6b7', + }, + message: { + utility: { + favorite: '0x891b56dc6ab87ab73cf69761183d499283f1925871', + }, + aisle: { + clever: '0x0102', + }, + gym: { + private: '0xbb42', + }, + drift_patch_cable_bi: '0xb4', + }, + } + await runEthMsg(buildEthMsgReq(msg, 'eip712'), client) + }) - it('Signs long primary types', async () => { - const msg = { - types: { - EIP712Domain: [ - { - name: 'name', - type: 'string', - }, - { - name: 'version', - type: 'string', - }, - { - name: 'chainId', - type: 'uint256', - }, - { - name: 'verifyingContract', - type: 'address', - }, - ], - 'HyperliquidTransaction:ApproveAgent': [ - { - name: 'hyperliquidChain', - type: 'string', - }, - { - name: 'agentAddress', - type: 'address', - }, - { - name: 'agentName', - type: 'string', - }, - { - name: 'nonce', - type: 'uint64', - }, - ], - }, - primaryType: 'HyperliquidTransaction:ApproveAgent', - domain: { - name: 'HyperliquidSignTransaction', - version: '1', - chainId: 1, - verifyingContract: '0x0000000000000000000000000000000000000000', - }, - message: { - hyperliquidChain: 'Mainnet', - signatureChainId: '0x1', - agentAddress: '0x343ab48c498a5b71e93a0c4c6e7f783ee8950436', - agentName: '', - nonce: 1718376161247, - type: 'approveAgent', - }, - } - await runEthMsg(buildEthMsgReq(msg, 'eip712'), client) - }) + it('Signs long primary types', async () => { + const msg = { + types: { + EIP712Domain: [ + { + name: 'name', + type: 'string', + }, + { + name: 'version', + type: 'string', + }, + { + name: 'chainId', + type: 'uint256', + }, + { + name: 'verifyingContract', + type: 'address', + }, + ], + 'HyperliquidTransaction:ApproveAgent': [ + { + name: 'hyperliquidChain', + type: 'string', + }, + { + name: 'agentAddress', + type: 'address', + }, + { + name: 'agentName', + type: 'string', + }, + { + name: 'nonce', + type: 'uint64', + }, + ], + }, + primaryType: 'HyperliquidTransaction:ApproveAgent', + domain: { + name: 'HyperliquidSignTransaction', + version: '1', + chainId: 1, + verifyingContract: '0x0000000000000000000000000000000000000000', + }, + message: { + hyperliquidChain: 'Mainnet', + signatureChainId: '0x1', + agentAddress: '0x343ab48c498a5b71e93a0c4c6e7f783ee8950436', + agentName: '', + nonce: 1718376161247, + type: 'approveAgent', + }, + } + await runEthMsg(buildEthMsgReq(msg, 'eip712'), client) + }) - describe('test 5 random payloads', () => { - for (let i = 0; i < 5; i++) { - it(`Payload #${i}`, async () => { - await runEthMsg( - buildEthMsgReq(buildRandomMsg('eip712', client), 'eip712'), - client, - ) - }) - } - }) - }) + describe('test 5 random payloads', () => { + for (let i = 0; i < 5; i++) { + it(`Payload #${i}`, async () => { + await runEthMsg( + buildEthMsgReq(buildRandomMsg('eip712', client), 'eip712'), + client, + ) + }) + } + }) + }) }) diff --git a/packages/sdk/src/__test__/e2e/ethereum/addresses.test.ts b/packages/sdk/src/__test__/e2e/ethereum/addresses.test.ts index 1c3342a4..a24d40f6 100644 --- a/packages/sdk/src/__test__/e2e/ethereum/addresses.test.ts +++ b/packages/sdk/src/__test__/e2e/ethereum/addresses.test.ts @@ -4,17 +4,17 @@ import { fetchAddresses } from '../../../api/addresses' import { setupClient } from '../../utils/setup' describe('Ethereum Addresses', () => { - test('pair', async () => { - const isPaired = await setupClient() - if (!isPaired) { - const secret = question('Please enter the pairing secret: ') - await pair(secret.toUpperCase()) - } - }) + test('pair', async () => { + const isPaired = await setupClient() + if (!isPaired) { + const secret = question('Please enter the pairing secret: ') + await pair(secret.toUpperCase()) + } + }) - test('Should fetch addressess', async () => { - const addresses = await fetchAddresses() - expect(addresses.length).toBe(10) - expect(addresses.every((addr) => !addr.startsWith('11111'))).toBe(true) - }) + test('Should fetch addressess', async () => { + const addresses = await fetchAddresses() + expect(addresses.length).toBe(10) + expect(addresses.every((addr) => !addr.startsWith('11111'))).toBe(true) + }) }) diff --git a/packages/sdk/src/__test__/e2e/general.test.ts b/packages/sdk/src/__test__/e2e/general.test.ts index ed0473b0..f02f4b73 100644 --- a/packages/sdk/src/__test__/e2e/general.test.ts +++ b/packages/sdk/src/__test__/e2e/general.test.ts @@ -24,13 +24,13 @@ import { randomBytes } from '../../util' import { buildEthSignRequest } from '../utils/builders' import { getDeviceId } from '../utils/getters' import { - BTC_COIN, - BTC_PURPOSE_P2PKH, - BTC_PURPOSE_P2SH_P2WPKH, - BTC_PURPOSE_P2WPKH, - BTC_TESTNET_COIN, - ETH_COIN, - setupTestClient, + BTC_COIN, + BTC_PURPOSE_P2PKH, + BTC_PURPOSE_P2SH_P2WPKH, + BTC_PURPOSE_P2WPKH, + BTC_TESTNET_COIN, + ETH_COIN, + setupTestClient, } from '../utils/helpers' import { setupClient } from '../utils/setup' @@ -39,317 +39,317 @@ import type { Client } from '../../client' const id = getDeviceId() describe('General', () => { - let client: Client + let client: Client - beforeAll(async () => { - client = await setupClient() - }) + beforeAll(async () => { + client = await setupClient() + }) - it('Should test SDK dehydration/rehydration', async () => { - const addrData = { - startPath: [BTC_PURPOSE_P2SH_P2WPKH, BTC_COIN, HARDENED_OFFSET, 0, 0], - n: 1, - } + it('Should test SDK dehydration/rehydration', async () => { + const addrData = { + startPath: [BTC_PURPOSE_P2SH_P2WPKH, BTC_COIN, HARDENED_OFFSET, 0, 0], + n: 1, + } - const client1 = setupTestClient() - await client1.connect(id) - expect(client1.isPaired).toBeTruthy() - const addrs1 = await client1.getAddresses(addrData) + const client1 = setupTestClient() + await client1.connect(id) + expect(client1.isPaired).toBeTruthy() + const addrs1 = await client1.getAddresses(addrData) - const stateData = client1.getStateData() + const stateData = client1.getStateData() - const client2 = setupTestClient(null, stateData) - await client2.connect(id) - expect(client2.isPaired).toBeTruthy() - const addrs2 = await client2.getAddresses(addrData) + const client2 = setupTestClient(null, stateData) + await client2.connect(id) + expect(client2.isPaired).toBeTruthy() + const addrs2 = await client2.getAddresses(addrData) - expect(addrs1).toEqual(addrs2) - }) + expect(addrs1).toEqual(addrs2) + }) - it('Should get addresses', async () => { - await client.connect(id) - const fwConstants = client.getFwConstants() - const addrData = { - startPath: [BTC_PURPOSE_P2SH_P2WPKH, BTC_COIN, HARDENED_OFFSET, 0, 0], - n: 5, - } - let addrs: string[] | undefined - // Bitcoin addresses - // NOTE: The format of address will be based on the user's Lattice settings - // By default, this will be P2SH(P2WPKH), i.e. addresses that start with `3` - addrs = (await client.getAddresses(addrData)) as string[] - expect(addrs.length).toEqual(5) - expect(addrs[0]?.[0]).toEqual('3') + it('Should get addresses', async () => { + await client.connect(id) + const fwConstants = client.getFwConstants() + const addrData = { + startPath: [BTC_PURPOSE_P2SH_P2WPKH, BTC_COIN, HARDENED_OFFSET, 0, 0], + n: 5, + } + let addrs: string[] | undefined + // Bitcoin addresses + // NOTE: The format of address will be based on the user's Lattice settings + // By default, this will be P2SH(P2WPKH), i.e. addresses that start with `3` + addrs = (await client.getAddresses(addrData)) as string[] + expect(addrs.length).toEqual(5) + expect(addrs[0]?.[0]).toEqual('3') - // Ethereum addresses - addrData.startPath[0] = BTC_PURPOSE_P2PKH - addrData.startPath[1] = ETH_COIN - addrData.n = 1 - addrs = (await client.getAddresses(addrData)) as string[] - expect(addrs.length).toEqual(1) - expect(addrs[0]?.slice(0, 2)).toEqual('0x') - // If firmware supports it, try shorter paths - if (fwConstants.flexibleAddrPaths) { - const flexData = { - startPath: [BTC_PURPOSE_P2PKH, ETH_COIN, HARDENED_OFFSET, 0], - n: 1, - } - addrs = (await client.getAddresses(flexData)) as string[] - expect(addrs.length).toEqual(1) - expect(addrs[0]?.slice(0, 2)).toEqual('0x') - } - // Should fail for non-EVM purpose and non-matching coin_type - addrData.n = 1 - try { - addrData.startPath[0] = BTC_PURPOSE_P2WPKH - await client.getAddresses(addrData) - throw new Error(null) - } catch (err: any) { - expect(err.message).not.toEqual(null) - } - // Switch to BTC coin. Should work now. - addrData.startPath[1] = BTC_COIN - // Bech32 - addrs = (await client.getAddresses(addrData)) as string[] - expect(addrs.length).toEqual(1) - expect(addrs[0]?.slice(0, 3)).to.be.oneOf(['bc1']) - addrData.startPath[0] = BTC_PURPOSE_P2SH_P2WPKH - addrData.n = 5 + // Ethereum addresses + addrData.startPath[0] = BTC_PURPOSE_P2PKH + addrData.startPath[1] = ETH_COIN + addrData.n = 1 + addrs = (await client.getAddresses(addrData)) as string[] + expect(addrs.length).toEqual(1) + expect(addrs[0]?.slice(0, 2)).toEqual('0x') + // If firmware supports it, try shorter paths + if (fwConstants.flexibleAddrPaths) { + const flexData = { + startPath: [BTC_PURPOSE_P2PKH, ETH_COIN, HARDENED_OFFSET, 0], + n: 1, + } + addrs = (await client.getAddresses(flexData)) as string[] + expect(addrs.length).toEqual(1) + expect(addrs[0]?.slice(0, 2)).toEqual('0x') + } + // Should fail for non-EVM purpose and non-matching coin_type + addrData.n = 1 + try { + addrData.startPath[0] = BTC_PURPOSE_P2WPKH + await client.getAddresses(addrData) + throw new Error(null) + } catch (err: any) { + expect(err.message).not.toEqual(null) + } + // Switch to BTC coin. Should work now. + addrData.startPath[1] = BTC_COIN + // Bech32 + addrs = (await client.getAddresses(addrData)) as string[] + expect(addrs.length).toEqual(1) + expect(addrs[0]?.slice(0, 3)).to.be.oneOf(['bc1']) + addrData.startPath[0] = BTC_PURPOSE_P2SH_P2WPKH + addrData.n = 5 - addrData.startPath[4] = 1000000 - addrData.n = 3 - addrs = (await client.getAddresses(addrData)) as string[] - expect(addrs.length).toEqual(addrData.n) - addrData.startPath[4] = 0 - addrData.n = 1 + addrData.startPath[4] = 1000000 + addrData.n = 3 + addrs = (await client.getAddresses(addrData)) as string[] + expect(addrs.length).toEqual(addrData.n) + addrData.startPath[4] = 0 + addrData.n = 1 - // Unsupported purpose (m//) - addrData.startPath[0] = 0 // Purpose 0 -- undefined - try { - addrs = (await client.getAddresses(addrData)) as string[] - } catch (err: any) { - expect(err.message).not.toEqual(null) - } - addrData.startPath[0] = BTC_PURPOSE_P2SH_P2WPKH + // Unsupported purpose (m//) + addrData.startPath[0] = 0 // Purpose 0 -- undefined + try { + addrs = (await client.getAddresses(addrData)) as string[] + } catch (err: any) { + expect(err.message).not.toEqual(null) + } + addrData.startPath[0] = BTC_PURPOSE_P2SH_P2WPKH - // Unsupported currency - addrData.startPath[1] = HARDENED_OFFSET + 5 // 5' currency - aka unknown - try { - addrs = (await client.getAddresses(addrData)) as string[] - throw new Error(null) - } catch (err: any) { - expect(err.message).not.toEqual(null) - } - addrData.startPath[1] = BTC_COIN - // Too many addresses (n>10) - addrData.n = 11 - try { - addrs = (await client.getAddresses(addrData)) as string[] - throw new Error(null) - } catch (err: any) { - expect(err.message).not.toEqual(null) - } - }) + // Unsupported currency + addrData.startPath[1] = HARDENED_OFFSET + 5 // 5' currency - aka unknown + try { + addrs = (await client.getAddresses(addrData)) as string[] + throw new Error(null) + } catch (err: any) { + expect(err.message).not.toEqual(null) + } + addrData.startPath[1] = BTC_COIN + // Too many addresses (n>10) + addrData.n = 11 + try { + addrs = (await client.getAddresses(addrData)) as string[] + throw new Error(null) + } catch (err: any) { + expect(err.message).not.toEqual(null) + } + }) - describe('Should sign Ethereum transactions', () => { - it('should sign Legacy transactions', async () => { - const { req } = await buildEthSignRequest(client) - await client.sign(req) - }) + describe('Should sign Ethereum transactions', () => { + it('should sign Legacy transactions', async () => { + const { req } = await buildEthSignRequest(client) + await client.sign(req) + }) - it('should sign newer transactions', async () => { - const { txData, req, common } = await buildEthSignRequest(client, { - type: 1, - gasPrice: 1200000000, - nonce: 0, - gasLimit: 50000, - to: '0xe242e54155b1abc71fc118065270cecaaf8b7768', - value: 1000000000000, - data: '0x17e914679b7e160613be4f8c2d3203d236286d74eb9192f6d6f71b9118a42bb033ccd8e8', - }) - // NOTE: This will display a prehashed payload for bridged general signing - // requests because `ethMaxDataSz` represents the `data` field for legacy - // requests, but it represents the entire payload for general signing requests. - const tx = createTx(txData, { common }) - req.data.payload = tx.getMessageToSign() - await client.sign(req) - }) + it('should sign newer transactions', async () => { + const { txData, req, common } = await buildEthSignRequest(client, { + type: 1, + gasPrice: 1200000000, + nonce: 0, + gasLimit: 50000, + to: '0xe242e54155b1abc71fc118065270cecaaf8b7768', + value: 1000000000000, + data: '0x17e914679b7e160613be4f8c2d3203d236286d74eb9192f6d6f71b9118a42bb033ccd8e8', + }) + // NOTE: This will display a prehashed payload for bridged general signing + // requests because `ethMaxDataSz` represents the `data` field for legacy + // requests, but it represents the entire payload for general signing requests. + const tx = createTx(txData, { common }) + req.data.payload = tx.getMessageToSign() + await client.sign(req) + }) - it('should sign bad transactions', async (ctx: any) => { - if (process.env.CI === '1') { - ctx.skip() - return - } - const { txData, req, maxDataSz, common } = - await buildEthSignRequest(client) - await question( - 'Please REJECT the next request if the warning screen displays. Press enter to continue.', - ) - txData.data = randomBytes(maxDataSz) - req.data.data = randomBytes(maxDataSz + 1) - const tx = createTx(txData, { common }) - req.data.payload = tx.getMessageToSign() - await expect(client.sign(req)).rejects.toThrow( - `${ProtocolConstants.responseMsg[LatticeResponseCode.userDeclined]}`, - ) - }) - }) + it('should sign bad transactions', async (ctx: any) => { + if (process.env.CI === '1') { + ctx.skip() + return + } + const { txData, req, maxDataSz, common } = + await buildEthSignRequest(client) + await question( + 'Please REJECT the next request if the warning screen displays. Press enter to continue.', + ) + txData.data = randomBytes(maxDataSz) + req.data.data = randomBytes(maxDataSz + 1) + const tx = createTx(txData, { common }) + req.data.payload = tx.getMessageToSign() + await expect(client.sign(req)).rejects.toThrow( + `${ProtocolConstants.responseMsg[LatticeResponseCode.userDeclined]}`, + ) + }) + }) - describe('Should sign Bitcoin transactions', () => { - it('Should sign legacy Bitcoin inputs', async () => { - const txData = { - prevOuts: [ - { - txHash: - '6e78493091f80d89a92ae3152df7fbfbdc44df09cf01a9b76c5113c02eaf2e0f', - value: 10000, - index: 1, - signerPath: [ - BTC_PURPOSE_P2SH_P2WPKH, - BTC_TESTNET_COIN, - HARDENED_OFFSET, - 0, - 0, - ], - }, - ], - recipient: 'mhifA1DwiMPHTjSJM8FFSL8ibrzWaBCkVT', - value: 1000, - fee: 1000, - // isSegwit: false, // old encoding - changePath: [ - BTC_PURPOSE_P2SH_P2WPKH, - BTC_TESTNET_COIN, - HARDENED_OFFSET, - 1, - 0, - ], - } - const req = { - currency: 'BTC' as const, - data: txData, - } + describe('Should sign Bitcoin transactions', () => { + it('Should sign legacy Bitcoin inputs', async () => { + const txData = { + prevOuts: [ + { + txHash: + '6e78493091f80d89a92ae3152df7fbfbdc44df09cf01a9b76c5113c02eaf2e0f', + value: 10000, + index: 1, + signerPath: [ + BTC_PURPOSE_P2SH_P2WPKH, + BTC_TESTNET_COIN, + HARDENED_OFFSET, + 0, + 0, + ], + }, + ], + recipient: 'mhifA1DwiMPHTjSJM8FFSL8ibrzWaBCkVT', + value: 1000, + fee: 1000, + // isSegwit: false, // old encoding + changePath: [ + BTC_PURPOSE_P2SH_P2WPKH, + BTC_TESTNET_COIN, + HARDENED_OFFSET, + 1, + 0, + ], + } + const req = { + currency: 'BTC' as const, + data: txData, + } - // Sign a legit tx - const sigResp = await client.sign(req) - expect(sigResp.tx).not.toEqual(null) - expect(sigResp.txHash).not.toEqual(null) - }) + // Sign a legit tx + const sigResp = await client.sign(req) + expect(sigResp.tx).not.toEqual(null) + expect(sigResp.txHash).not.toEqual(null) + }) - it('Should sign wrapped segwit Bitcoin inputs', async () => { - const txData = { - prevOuts: [ - { - txHash: - 'ab8288ef207f11186af98db115aa7120aa36ceb783e8792fb7b2f39c88109a99', - value: 10000, - index: 1, - signerPath: [ - BTC_PURPOSE_P2SH_P2WPKH, - BTC_TESTNET_COIN, - HARDENED_OFFSET, - 0, - 0, - ], - }, - ], - recipient: '2NGZrVvZG92qGYqzTLjCAewvPZ7JE8S8VxE', - value: 1000, - fee: 1000, - changePath: [ - BTC_PURPOSE_P2SH_P2WPKH, - BTC_TESTNET_COIN, - HARDENED_OFFSET, - 1, - 0, - ], - } - const req = { - currency: 'BTC' as const, - data: txData, - } - // Sign a legit tx - const sigResp = await client.sign(req) - expect(sigResp.tx).not.toEqual(null) - expect(sigResp.txHash).not.toEqual(null) - }) + it('Should sign wrapped segwit Bitcoin inputs', async () => { + const txData = { + prevOuts: [ + { + txHash: + 'ab8288ef207f11186af98db115aa7120aa36ceb783e8792fb7b2f39c88109a99', + value: 10000, + index: 1, + signerPath: [ + BTC_PURPOSE_P2SH_P2WPKH, + BTC_TESTNET_COIN, + HARDENED_OFFSET, + 0, + 0, + ], + }, + ], + recipient: '2NGZrVvZG92qGYqzTLjCAewvPZ7JE8S8VxE', + value: 1000, + fee: 1000, + changePath: [ + BTC_PURPOSE_P2SH_P2WPKH, + BTC_TESTNET_COIN, + HARDENED_OFFSET, + 1, + 0, + ], + } + const req = { + currency: 'BTC' as const, + data: txData, + } + // Sign a legit tx + const sigResp = await client.sign(req) + expect(sigResp.tx).not.toEqual(null) + expect(sigResp.txHash).not.toEqual(null) + }) - it('Should sign wrapped segwit Bitcoin inputs to a bech32 address', async () => { - const txData = { - prevOuts: [ - { - txHash: - 'f93d0a77f58b4274d84f427d647f1f27e38b4db79fd975691e15109fde7ea06e', - value: 1802440, - index: 1, - signerPath: [ - BTC_PURPOSE_P2SH_P2WPKH, - BTC_TESTNET_COIN, - HARDENED_OFFSET, - 1, - 0, - ], - }, - ], - recipient: 'tb1qym0z2a939lefrgw67ep5flhf43dvpg3h4s96tn', - value: 1000, - fee: 1000, - changePath: [ - BTC_PURPOSE_P2SH_P2WPKH, - BTC_TESTNET_COIN, - HARDENED_OFFSET, - 1, - 0, - ], - } - const req = { - currency: 'BTC' as const, - data: txData, - } - // Sign a legit tx - const sigResp = await client.sign(req) - expect(sigResp.tx).not.toEqual(null) - expect(sigResp.txHash).not.toEqual(null) - }) + it('Should sign wrapped segwit Bitcoin inputs to a bech32 address', async () => { + const txData = { + prevOuts: [ + { + txHash: + 'f93d0a77f58b4274d84f427d647f1f27e38b4db79fd975691e15109fde7ea06e', + value: 1802440, + index: 1, + signerPath: [ + BTC_PURPOSE_P2SH_P2WPKH, + BTC_TESTNET_COIN, + HARDENED_OFFSET, + 1, + 0, + ], + }, + ], + recipient: 'tb1qym0z2a939lefrgw67ep5flhf43dvpg3h4s96tn', + value: 1000, + fee: 1000, + changePath: [ + BTC_PURPOSE_P2SH_P2WPKH, + BTC_TESTNET_COIN, + HARDENED_OFFSET, + 1, + 0, + ], + } + const req = { + currency: 'BTC' as const, + data: txData, + } + // Sign a legit tx + const sigResp = await client.sign(req) + expect(sigResp.tx).not.toEqual(null) + expect(sigResp.txHash).not.toEqual(null) + }) - it('Should sign an input from a native segwit account', async () => { - const txData = { - prevOuts: [ - { - txHash: - 'b2efdbdd3340d2bc547671ce3993a6f05d70343c07578f9d7f5626fdfc06fa35', - value: 76800, - index: 0, - signerPath: [ - BTC_PURPOSE_P2WPKH, - BTC_TESTNET_COIN, - HARDENED_OFFSET, - 0, - 0, - ], - }, - ], - recipient: '2N4gqWT4oqWL2gz9ps92z9fm2Bg3FUkqG7Q', - value: 70000, - fee: 4380, - isSegwit: true, - changePath: [ - BTC_PURPOSE_P2WPKH, - BTC_TESTNET_COIN, - HARDENED_OFFSET, - 1, - 0, - ], - } - const req = { - currency: 'BTC' as const, - data: txData, - } - // Sign a legit tx - const sigResp = await client.sign(req) - expect(sigResp.tx).not.toEqual(null) - expect(sigResp.txHash).not.toEqual(null) - expect(sigResp.changeRecipient?.slice(0, 2)).toEqual('tb') - }) - }) + it('Should sign an input from a native segwit account', async () => { + const txData = { + prevOuts: [ + { + txHash: + 'b2efdbdd3340d2bc547671ce3993a6f05d70343c07578f9d7f5626fdfc06fa35', + value: 76800, + index: 0, + signerPath: [ + BTC_PURPOSE_P2WPKH, + BTC_TESTNET_COIN, + HARDENED_OFFSET, + 0, + 0, + ], + }, + ], + recipient: '2N4gqWT4oqWL2gz9ps92z9fm2Bg3FUkqG7Q', + value: 70000, + fee: 4380, + isSegwit: true, + changePath: [ + BTC_PURPOSE_P2WPKH, + BTC_TESTNET_COIN, + HARDENED_OFFSET, + 1, + 0, + ], + } + const req = { + currency: 'BTC' as const, + data: txData, + } + // Sign a legit tx + const sigResp = await client.sign(req) + expect(sigResp.tx).not.toEqual(null) + expect(sigResp.txHash).not.toEqual(null) + expect(sigResp.changeRecipient?.slice(0, 2)).toEqual('tb') + }) + }) }) diff --git a/packages/sdk/src/__test__/e2e/kv.test.ts b/packages/sdk/src/__test__/e2e/kv.test.ts index 91291e3f..022e6e5f 100644 --- a/packages/sdk/src/__test__/e2e/kv.test.ts +++ b/packages/sdk/src/__test__/e2e/kv.test.ts @@ -23,245 +23,245 @@ const RANDOM_TAG = 'Test Address Name' let _numStartingRecords = 0 let _fetchedRecords: unknown[] = [] const ETH_REQ = { - currency: 'ETH', - data: { - signerPath: DEFAULT_SIGNER, - nonce: '0x02', - gasPrice: '0x1fe5d61a00', - gasLimit: '0x034e97', - to: RANDOM_ADDR, - value: '0x01cba1761f7ab9870c', - data: null, - chainId: 4, - }, + currency: 'ETH', + data: { + signerPath: DEFAULT_SIGNER, + nonce: '0x02', + gasPrice: '0x1fe5d61a00', + gasLimit: '0x034e97', + to: RANDOM_ADDR, + value: '0x01cba1761f7ab9870c', + data: null, + chainId: 4, + }, } describe('key-value', () => { - let client: Client + let client: Client - beforeAll(async () => { - client = await setupClient() - }) + beforeAll(async () => { + client = await setupClient() + }) - it('Should ask if the user wants to reset state', async () => { - let answer = 'Y' - if (process.env.CI !== '1') { - answer = question( - 'Do you want to clear all kv records and start anew? (Y/N) ', - ) - } else { - answer = 'Y' - } - if (answer.toUpperCase() === 'Y') { - const batchSize = 10 - let lastTotal: number | null = null - let iterations = 0 + it('Should ask if the user wants to reset state', async () => { + let answer = 'Y' + if (process.env.CI !== '1') { + answer = question( + 'Do you want to clear all kv records and start anew? (Y/N) ', + ) + } else { + answer = 'Y' + } + if (answer.toUpperCase() === 'Y') { + const batchSize = 10 + let lastTotal: number | null = null + let iterations = 0 - while (iterations < 100) { - iterations += 1 - const data = await client.getKvRecords({ n: batchSize, start: 0 }) - console.log('[getKvRecords] data:', JSON.stringify(data, null, 2)) + while (iterations < 100) { + iterations += 1 + const data = await client.getKvRecords({ n: batchSize, start: 0 }) + console.log('[getKvRecords] data:', JSON.stringify(data, null, 2)) - const { records = [], total = 0 } = data - if (!records.length || total === 0) { - break - } + const { records = [], total = 0 } = data + if (!records.length || total === 0) { + break + } - if (lastTotal !== null && total >= lastTotal) { - console.warn( - '[kv.test] KV cleanup halted to avoid infinite loop (no progress detected).', - ) - break - } + if (lastTotal !== null && total >= lastTotal) { + console.warn( + '[kv.test] KV cleanup halted to avoid infinite loop (no progress detected).', + ) + break + } - const ids = records - .slice(0, batchSize) - .map((record: any) => (record?.id ?? '').toString()) - .filter((id: string) => id.length > 0) + const ids = records + .slice(0, batchSize) + .map((record: any) => (record?.id ?? '').toString()) + .filter((id: string) => id.length > 0) - if (!ids.length) { - break - } + if (!ids.length) { + break + } - await client.removeKvRecords({ type: 0, ids }) - if (total <= ids.length) { - break - } + await client.removeKvRecords({ type: 0, ids }) + if (total <= ids.length) { + break + } - lastTotal = total - } - } - }) + lastTotal = total + } + } + }) - it('Should make a request to an unknown address', async () => { - await client.sign(ETH_REQ as unknown as SignRequestParams).catch((err) => { - expect(err.message).toContain( - ProtocolConstants.responseMsg[LatticeResponseCode.userDeclined], - ) - }) - }) + it('Should make a request to an unknown address', async () => { + await client.sign(ETH_REQ as unknown as SignRequestParams).catch((err) => { + expect(err.message).toContain( + ProtocolConstants.responseMsg[LatticeResponseCode.userDeclined], + ) + }) + }) - it('Should get the initial set of records', async () => { - const resp = await client.getKvRecords({ n: 2, start: 0 }) - _numStartingRecords = resp.total - }) + it('Should get the initial set of records', async () => { + const resp = await client.getKvRecords({ n: 2, start: 0 }) + _numStartingRecords = resp.total + }) - it('Should add some key value records', async () => { - const records = { - [UNISWAP_ADDR]: UNISWAP_TAG, - [RANDOM_ADDR]: RANDOM_TAG, - } - await client.addKvRecords({ records, caseSensitive: false, type: 0 }) - }) + it('Should add some key value records', async () => { + const records = { + [UNISWAP_ADDR]: UNISWAP_TAG, + [RANDOM_ADDR]: RANDOM_TAG, + } + await client.addKvRecords({ records, caseSensitive: false, type: 0 }) + }) - it('Should fail to add records with unicode characters', async () => { - const badKey = { '0x🔥🦍': 'Muh name' } - const badVal = { UNISWAP_ADDR: 'val🔥🦍' } - await expect(client.addKvRecords({ records: badKey })).rejects.toThrow( - 'Unicode characters are not supported.', - ) - await expect(client.addKvRecords({ records: badVal })).rejects.toThrow( - 'Unicode characters are not supported.', - ) - }) + it('Should fail to add records with unicode characters', async () => { + const badKey = { '0x🔥🦍': 'Muh name' } + const badVal = { UNISWAP_ADDR: 'val🔥🦍' } + await expect(client.addKvRecords({ records: badKey })).rejects.toThrow( + 'Unicode characters are not supported.', + ) + await expect(client.addKvRecords({ records: badVal })).rejects.toThrow( + 'Unicode characters are not supported.', + ) + }) - it('Should fail to add zero length keys and values', async () => { - const badKey = { '': 'Muh name' } - const badVal = { UNISWAP_ADDR: '' } - await expect(client.addKvRecords({ records: badKey })).rejects.toThrow( - 'Keys and values must be >0 characters.', - ) - await expect(client.addKvRecords({ records: badVal })).rejects.toThrow( - 'Keys and values must be >0 characters.', - ) - }) + it('Should fail to add zero length keys and values', async () => { + const badKey = { '': 'Muh name' } + const badVal = { UNISWAP_ADDR: '' } + await expect(client.addKvRecords({ records: badKey })).rejects.toThrow( + 'Keys and values must be >0 characters.', + ) + await expect(client.addKvRecords({ records: badVal })).rejects.toThrow( + 'Keys and values must be >0 characters.', + ) + }) - it('Should fetch the newly created records', async () => { - const opts = { - n: 2, - start: _numStartingRecords, - } - const resp = await client.getKvRecords(opts) - const { records, total, fetched } = resp - _fetchedRecords = records - expect(total).toEqual(fetched + _numStartingRecords) - expect(records.length).toEqual(fetched) - expect(records.length).toEqual(2) - }) + it('Should fetch the newly created records', async () => { + const opts = { + n: 2, + start: _numStartingRecords, + } + const resp = await client.getKvRecords(opts) + const { records, total, fetched } = resp + _fetchedRecords = records + expect(total).toEqual(fetched + _numStartingRecords) + expect(records.length).toEqual(fetched) + expect(records.length).toEqual(2) + }) - it('Should make a request to an address which is now known', async () => { - await client.sign(ETH_REQ as unknown as SignRequestParams) - }) + it('Should make a request to an address which is now known', async () => { + await client.sign(ETH_REQ as unknown as SignRequestParams) + }) - it('Should make an EIP712 request that uses the record', async () => { - const msg = { - types: { - EIP712Domain: [ - { name: 'name', type: 'string' }, - { name: 'verifyingContract', type: 'address' }, - ], - Test: [ - { name: 'owner', type: 'string' }, - { name: 'ownerAddr', type: 'address' }, - ], - }, - domain: { - name: 'A Message', - verifyingContract: RANDOM_ADDR, - }, - primaryType: 'Test', - message: { - owner: RANDOM_ADDR, - ownerAddr: RANDOM_ADDR, - }, - } - const req = { - currency: 'ETH_MSG', - data: { - signerPath: [BTC_PURPOSE_P2PKH, ETH_COIN, HARDENED_OFFSET, 0, 0], - protocol: 'eip712', - payload: msg, - }, - } - await client.sign(req as unknown as SignRequestParams) - }) + it('Should make an EIP712 request that uses the record', async () => { + const msg = { + types: { + EIP712Domain: [ + { name: 'name', type: 'string' }, + { name: 'verifyingContract', type: 'address' }, + ], + Test: [ + { name: 'owner', type: 'string' }, + { name: 'ownerAddr', type: 'address' }, + ], + }, + domain: { + name: 'A Message', + verifyingContract: RANDOM_ADDR, + }, + primaryType: 'Test', + message: { + owner: RANDOM_ADDR, + ownerAddr: RANDOM_ADDR, + }, + } + const req = { + currency: 'ETH_MSG', + data: { + signerPath: [BTC_PURPOSE_P2PKH, ETH_COIN, HARDENED_OFFSET, 0, 0], + protocol: 'eip712', + payload: msg, + }, + } + await client.sign(req as unknown as SignRequestParams) + }) - it('Should make a request with calldata', async () => { - // TODO: Add decoder data - const req = JSON.parse(JSON.stringify(ETH_REQ)) - req.data.data = `0x23b872dd00000000000000000000000057974eb88e50cc61049b44e43e90d3bc40fa61c0000000000000000000000000${RANDOM_ADDR.slice(2)}000000000000000000000000000000000000000000000000000000000000270f` - await client.sign(req) - }) + it('Should make a request with calldata', async () => { + // TODO: Add decoder data + const req = JSON.parse(JSON.stringify(ETH_REQ)) + req.data.data = `0x23b872dd00000000000000000000000057974eb88e50cc61049b44e43e90d3bc40fa61c0000000000000000000000000${RANDOM_ADDR.slice(2)}000000000000000000000000000000000000000000000000000000000000270f` + await client.sign(req) + }) - it('Should remove key value records', async () => { - const idsToRemove: any[] = [] - _fetchedRecords.forEach((r: any) => { - idsToRemove.push(r.id) - }) - await client.removeKvRecords({ ids: idsToRemove }) - }) + it('Should remove key value records', async () => { + const idsToRemove: any[] = [] + _fetchedRecords.forEach((r: any) => { + idsToRemove.push(r.id) + }) + await client.removeKvRecords({ ids: idsToRemove }) + }) - it('Should confirm the records we recently added are removed', async () => { - const opts = { - n: 1, - start: _numStartingRecords, - } - const resp = await client.getKvRecords(opts) - const { records, total, fetched } = resp - expect(total).toEqual(_numStartingRecords) - expect(fetched).toEqual(0) - expect(records.length).toEqual(0) - }) + it('Should confirm the records we recently added are removed', async () => { + const opts = { + n: 1, + start: _numStartingRecords, + } + const resp = await client.getKvRecords(opts) + const { records, total, fetched } = resp + expect(total).toEqual(_numStartingRecords) + expect(fetched).toEqual(0) + expect(records.length).toEqual(0) + }) - it('Should add the same record with case sensitivity', async () => { - const records = { - [RANDOM_ADDR]: 'Test Address Name', - } - await client.addKvRecords({ - records, - caseSensitive: true, - type: 0, - }) - }) + it('Should add the same record with case sensitivity', async () => { + const records = { + [RANDOM_ADDR]: 'Test Address Name', + } + await client.addKvRecords({ + records, + caseSensitive: true, + type: 0, + }) + }) - it('Should make another request to make sure case sensitivity is enforced', async () => { - await client.sign(ETH_REQ as unknown as SignRequestParams).catch((err) => { - expect(err.message).toContain( - ProtocolConstants.responseMsg[LatticeResponseCode.userDeclined], - ) - }) - }) + it('Should make another request to make sure case sensitivity is enforced', async () => { + await client.sign(ETH_REQ as unknown as SignRequestParams).catch((err) => { + expect(err.message).toContain( + ProtocolConstants.responseMsg[LatticeResponseCode.userDeclined], + ) + }) + }) - it('Should get the id of the newly added record', async () => { - const opts = { - n: 1, - start: _numStartingRecords, - } - const resp: any = await client.getKvRecords(opts) - const { records, total, fetched } = resp - expect(total).toEqual(_numStartingRecords + 1) - expect(fetched).toEqual(1) - expect(records.length).toEqual(1) - _fetchedRecords = records - }) + it('Should get the id of the newly added record', async () => { + const opts = { + n: 1, + start: _numStartingRecords, + } + const resp: any = await client.getKvRecords(opts) + const { records, total, fetched } = resp + expect(total).toEqual(_numStartingRecords + 1) + expect(fetched).toEqual(1) + expect(records.length).toEqual(1) + _fetchedRecords = records + }) - it('Should remove the new record', async () => { - const idsToRemove: any = [] - _fetchedRecords.forEach((r: any) => { - idsToRemove.push(r.id) - }) - await client.removeKvRecords({ ids: idsToRemove }) - }) + it('Should remove the new record', async () => { + const idsToRemove: any = [] + _fetchedRecords.forEach((r: any) => { + idsToRemove.push(r.id) + }) + await client.removeKvRecords({ ids: idsToRemove }) + }) - it('Should confirm there are no new records', async () => { - const opts = { - n: 1, - start: _numStartingRecords, - } - const resp: any = await client.getKvRecords(opts) - const { records, total, fetched } = resp - expect(total).toEqual(_numStartingRecords) - expect(fetched).toEqual(0) - expect(records.length).toEqual(0) - }) + it('Should confirm there are no new records', async () => { + const opts = { + n: 1, + start: _numStartingRecords, + } + const resp: any = await client.getKvRecords(opts) + const { records, total, fetched } = resp + expect(total).toEqual(_numStartingRecords) + expect(fetched).toEqual(0) + expect(records.length).toEqual(0) + }) }) diff --git a/packages/sdk/src/__test__/e2e/non-exportable.test.ts b/packages/sdk/src/__test__/e2e/non-exportable.test.ts index 54478b2a..9ed9e6fd 100644 --- a/packages/sdk/src/__test__/e2e/non-exportable.test.ts +++ b/packages/sdk/src/__test__/e2e/non-exportable.test.ts @@ -33,150 +33,150 @@ import type { SignRequestParams } from '../../types' let runTests = true describe('Non-Exportable Seed', () => { - let client: Client + let client: Client - beforeAll(async () => { - client = await setupClient() - }) + beforeAll(async () => { + client = await setupClient() + }) - describe('Setup', () => { - it('Should ask if the user wants to test a card with a non-exportable seed', async (ctx: any) => { - if (process.env.CI === '1') { - runTests = false - ctx.skip() - return - } - // NOTE: non-exportable seeds were deprecated from the normal setup pathway in firmware v0.12.0 - const result = await question( - 'Do you have a non-exportable SafeCard seed loaded and wish to continue? (Y/N) ', - ) - if (result.toLowerCase() !== 'y') { - runTests = false - } - }) - }) + describe('Setup', () => { + it('Should ask if the user wants to test a card with a non-exportable seed', async (ctx: any) => { + if (process.env.CI === '1') { + runTests = false + ctx.skip() + return + } + // NOTE: non-exportable seeds were deprecated from the normal setup pathway in firmware v0.12.0 + const result = await question( + 'Do you have a non-exportable SafeCard seed loaded and wish to continue? (Y/N) ', + ) + if (result.toLowerCase() !== 'y') { + runTests = false + } + }) + }) - describe('Test non-exportable seed on SafeCard', () => { - beforeEach((ctx: any) => { - if (!runTests) { - ctx.skip('Skipping tests due to lack of non-exportable seed SafeCard.') - } - }) - it('Should test that ETH transaction sigs differ and validate on secp256k1', async () => { - // Test ETH transactions - const tx = createTx( - { - type: 2, - maxFeePerGas: 1200000000, - maxPriorityFeePerGas: 1200000000, - nonce: 0, - gasLimit: 50000, - to: '0xe242e54155b1abc71fc118065270cecaaf8b7768', - value: 100, - data: '0xdeadbeef', - }, - { - common: new Common({ - chain: Mainnet, - hardfork: Hardfork.London, - }), - }, - ) - const txReq = { - data: { - signerPath: DEFAULT_SIGNER, - payload: tx.getMessageToSign(), - curveType: Constants.SIGNING.CURVES.SECP256K1, - hashType: Constants.SIGNING.HASHES.KECCAK256, - encodingType: Constants.SIGNING.ENCODINGS.EVM, - }, - } - // Validate that tx sigs are non-uniform - const unsignedMsg = tx.getMessageToSign() - const unsigned = Array.isArray(unsignedMsg) - ? RLP.encode(unsignedMsg) - : unsignedMsg - const tx1Resp = await client.sign(txReq) - validateSig(tx1Resp, unsigned) - const tx2Resp = await client.sign(txReq) - validateSig(tx2Resp, unsigned) - const tx3Resp = await client.sign(txReq) - validateSig(tx3Resp, unsigned) - const tx4Resp = await client.sign(txReq) - validateSig(tx4Resp, unsigned) - const tx5Resp = await client.sign(txReq) - validateSig(tx5Resp, unsigned) - // Check sig 1 - expect(getSigStr(tx1Resp, tx)).not.toEqual(getSigStr(tx2Resp, tx)) - expect(getSigStr(tx1Resp, tx)).not.toEqual(getSigStr(tx3Resp, tx)) - expect(getSigStr(tx1Resp, tx)).not.toEqual(getSigStr(tx4Resp, tx)) - expect(getSigStr(tx1Resp, tx)).not.toEqual(getSigStr(tx5Resp, tx)) - // Check sig 2 - expect(getSigStr(tx2Resp, tx)).not.toEqual(getSigStr(tx1Resp, tx)) - expect(getSigStr(tx2Resp, tx)).not.toEqual(getSigStr(tx3Resp, tx)) - expect(getSigStr(tx2Resp, tx)).not.toEqual(getSigStr(tx4Resp, tx)) - expect(getSigStr(tx2Resp, tx)).not.toEqual(getSigStr(tx5Resp, tx)) - // Check sig 3 - expect(getSigStr(tx3Resp, tx)).not.toEqual(getSigStr(tx1Resp, tx)) - expect(getSigStr(tx3Resp, tx)).not.toEqual(getSigStr(tx2Resp, tx)) - expect(getSigStr(tx3Resp, tx)).not.toEqual(getSigStr(tx4Resp, tx)) - expect(getSigStr(tx3Resp, tx)).not.toEqual(getSigStr(tx5Resp, tx)) - // Check sig 4 - expect(getSigStr(tx4Resp, tx)).not.toEqual(getSigStr(tx1Resp, tx)) - expect(getSigStr(tx4Resp, tx)).not.toEqual(getSigStr(tx2Resp, tx)) - expect(getSigStr(tx4Resp, tx)).not.toEqual(getSigStr(tx3Resp, tx)) - expect(getSigStr(tx4Resp, tx)).not.toEqual(getSigStr(tx5Resp, tx)) - // Check sig 5 - expect(getSigStr(tx5Resp, tx)).not.toEqual(getSigStr(tx1Resp, tx)) - expect(getSigStr(tx5Resp, tx)).not.toEqual(getSigStr(tx2Resp, tx)) - expect(getSigStr(tx5Resp, tx)).not.toEqual(getSigStr(tx3Resp, tx)) - expect(getSigStr(tx5Resp, tx)).not.toEqual(getSigStr(tx4Resp, tx)) - }) + describe('Test non-exportable seed on SafeCard', () => { + beforeEach((ctx: any) => { + if (!runTests) { + ctx.skip('Skipping tests due to lack of non-exportable seed SafeCard.') + } + }) + it('Should test that ETH transaction sigs differ and validate on secp256k1', async () => { + // Test ETH transactions + const tx = createTx( + { + type: 2, + maxFeePerGas: 1200000000, + maxPriorityFeePerGas: 1200000000, + nonce: 0, + gasLimit: 50000, + to: '0xe242e54155b1abc71fc118065270cecaaf8b7768', + value: 100, + data: '0xdeadbeef', + }, + { + common: new Common({ + chain: Mainnet, + hardfork: Hardfork.London, + }), + }, + ) + const txReq = { + data: { + signerPath: DEFAULT_SIGNER, + payload: tx.getMessageToSign(), + curveType: Constants.SIGNING.CURVES.SECP256K1, + hashType: Constants.SIGNING.HASHES.KECCAK256, + encodingType: Constants.SIGNING.ENCODINGS.EVM, + }, + } + // Validate that tx sigs are non-uniform + const unsignedMsg = tx.getMessageToSign() + const unsigned = Array.isArray(unsignedMsg) + ? RLP.encode(unsignedMsg) + : unsignedMsg + const tx1Resp = await client.sign(txReq) + validateSig(tx1Resp, unsigned) + const tx2Resp = await client.sign(txReq) + validateSig(tx2Resp, unsigned) + const tx3Resp = await client.sign(txReq) + validateSig(tx3Resp, unsigned) + const tx4Resp = await client.sign(txReq) + validateSig(tx4Resp, unsigned) + const tx5Resp = await client.sign(txReq) + validateSig(tx5Resp, unsigned) + // Check sig 1 + expect(getSigStr(tx1Resp, tx)).not.toEqual(getSigStr(tx2Resp, tx)) + expect(getSigStr(tx1Resp, tx)).not.toEqual(getSigStr(tx3Resp, tx)) + expect(getSigStr(tx1Resp, tx)).not.toEqual(getSigStr(tx4Resp, tx)) + expect(getSigStr(tx1Resp, tx)).not.toEqual(getSigStr(tx5Resp, tx)) + // Check sig 2 + expect(getSigStr(tx2Resp, tx)).not.toEqual(getSigStr(tx1Resp, tx)) + expect(getSigStr(tx2Resp, tx)).not.toEqual(getSigStr(tx3Resp, tx)) + expect(getSigStr(tx2Resp, tx)).not.toEqual(getSigStr(tx4Resp, tx)) + expect(getSigStr(tx2Resp, tx)).not.toEqual(getSigStr(tx5Resp, tx)) + // Check sig 3 + expect(getSigStr(tx3Resp, tx)).not.toEqual(getSigStr(tx1Resp, tx)) + expect(getSigStr(tx3Resp, tx)).not.toEqual(getSigStr(tx2Resp, tx)) + expect(getSigStr(tx3Resp, tx)).not.toEqual(getSigStr(tx4Resp, tx)) + expect(getSigStr(tx3Resp, tx)).not.toEqual(getSigStr(tx5Resp, tx)) + // Check sig 4 + expect(getSigStr(tx4Resp, tx)).not.toEqual(getSigStr(tx1Resp, tx)) + expect(getSigStr(tx4Resp, tx)).not.toEqual(getSigStr(tx2Resp, tx)) + expect(getSigStr(tx4Resp, tx)).not.toEqual(getSigStr(tx3Resp, tx)) + expect(getSigStr(tx4Resp, tx)).not.toEqual(getSigStr(tx5Resp, tx)) + // Check sig 5 + expect(getSigStr(tx5Resp, tx)).not.toEqual(getSigStr(tx1Resp, tx)) + expect(getSigStr(tx5Resp, tx)).not.toEqual(getSigStr(tx2Resp, tx)) + expect(getSigStr(tx5Resp, tx)).not.toEqual(getSigStr(tx3Resp, tx)) + expect(getSigStr(tx5Resp, tx)).not.toEqual(getSigStr(tx4Resp, tx)) + }) - it('Should test that ETH message sigs differ and validate on secp256k1', async () => { - // Validate that signPersonal message sigs are non-uniform - const msgReq = { - currency: 'ETH_MSG' as const, - data: { - signerPath: DEFAULT_SIGNER, - protocol: 'signPersonal' as const, - payload: 'test message', - curveType: Constants.SIGNING.CURVES.SECP256K1, - hashType: Constants.SIGNING.HASHES.KECCAK256, - }, - } - // NOTE: This uses the legacy signing pathway, which validates the signature - // Once we move this to generic signing, we will need to validate these. - const msg1Resp = await client.sign(msgReq as unknown as SignRequestParams) - const msg2Resp = await client.sign(msgReq as unknown as SignRequestParams) - const msg3Resp = await client.sign(msgReq as unknown as SignRequestParams) - const msg4Resp = await client.sign(msgReq as unknown as SignRequestParams) - const msg5Resp = await client.sign(msgReq as unknown as SignRequestParams) - // Check sig 1 - expect(getSigStr(msg1Resp)).not.toEqual(getSigStr(msg2Resp)) - expect(getSigStr(msg1Resp)).not.toEqual(getSigStr(msg3Resp)) - expect(getSigStr(msg1Resp)).not.toEqual(getSigStr(msg4Resp)) - expect(getSigStr(msg1Resp)).not.toEqual(getSigStr(msg5Resp)) - // Check sig 2 - expect(getSigStr(msg2Resp)).not.toEqual(getSigStr(msg1Resp)) - expect(getSigStr(msg2Resp)).not.toEqual(getSigStr(msg3Resp)) - expect(getSigStr(msg2Resp)).not.toEqual(getSigStr(msg4Resp)) - expect(getSigStr(msg2Resp)).not.toEqual(getSigStr(msg5Resp)) - // Check sig 3 - expect(getSigStr(msg3Resp)).not.toEqual(getSigStr(msg1Resp)) - expect(getSigStr(msg3Resp)).not.toEqual(getSigStr(msg2Resp)) - expect(getSigStr(msg3Resp)).not.toEqual(getSigStr(msg4Resp)) - expect(getSigStr(msg3Resp)).not.toEqual(getSigStr(msg5Resp)) - // Check sig 4 - expect(getSigStr(msg4Resp)).not.toEqual(getSigStr(msg1Resp)) - expect(getSigStr(msg4Resp)).not.toEqual(getSigStr(msg2Resp)) - expect(getSigStr(msg4Resp)).not.toEqual(getSigStr(msg3Resp)) - expect(getSigStr(msg4Resp)).not.toEqual(getSigStr(msg5Resp)) - // Check sig 5 - expect(getSigStr(msg5Resp)).not.toEqual(getSigStr(msg1Resp)) - expect(getSigStr(msg5Resp)).not.toEqual(getSigStr(msg2Resp)) - expect(getSigStr(msg5Resp)).not.toEqual(getSigStr(msg3Resp)) - expect(getSigStr(msg5Resp)).not.toEqual(getSigStr(msg4Resp)) - }) - }) + it('Should test that ETH message sigs differ and validate on secp256k1', async () => { + // Validate that signPersonal message sigs are non-uniform + const msgReq = { + currency: 'ETH_MSG' as const, + data: { + signerPath: DEFAULT_SIGNER, + protocol: 'signPersonal' as const, + payload: 'test message', + curveType: Constants.SIGNING.CURVES.SECP256K1, + hashType: Constants.SIGNING.HASHES.KECCAK256, + }, + } + // NOTE: This uses the legacy signing pathway, which validates the signature + // Once we move this to generic signing, we will need to validate these. + const msg1Resp = await client.sign(msgReq as unknown as SignRequestParams) + const msg2Resp = await client.sign(msgReq as unknown as SignRequestParams) + const msg3Resp = await client.sign(msgReq as unknown as SignRequestParams) + const msg4Resp = await client.sign(msgReq as unknown as SignRequestParams) + const msg5Resp = await client.sign(msgReq as unknown as SignRequestParams) + // Check sig 1 + expect(getSigStr(msg1Resp)).not.toEqual(getSigStr(msg2Resp)) + expect(getSigStr(msg1Resp)).not.toEqual(getSigStr(msg3Resp)) + expect(getSigStr(msg1Resp)).not.toEqual(getSigStr(msg4Resp)) + expect(getSigStr(msg1Resp)).not.toEqual(getSigStr(msg5Resp)) + // Check sig 2 + expect(getSigStr(msg2Resp)).not.toEqual(getSigStr(msg1Resp)) + expect(getSigStr(msg2Resp)).not.toEqual(getSigStr(msg3Resp)) + expect(getSigStr(msg2Resp)).not.toEqual(getSigStr(msg4Resp)) + expect(getSigStr(msg2Resp)).not.toEqual(getSigStr(msg5Resp)) + // Check sig 3 + expect(getSigStr(msg3Resp)).not.toEqual(getSigStr(msg1Resp)) + expect(getSigStr(msg3Resp)).not.toEqual(getSigStr(msg2Resp)) + expect(getSigStr(msg3Resp)).not.toEqual(getSigStr(msg4Resp)) + expect(getSigStr(msg3Resp)).not.toEqual(getSigStr(msg5Resp)) + // Check sig 4 + expect(getSigStr(msg4Resp)).not.toEqual(getSigStr(msg1Resp)) + expect(getSigStr(msg4Resp)).not.toEqual(getSigStr(msg2Resp)) + expect(getSigStr(msg4Resp)).not.toEqual(getSigStr(msg3Resp)) + expect(getSigStr(msg4Resp)).not.toEqual(getSigStr(msg5Resp)) + // Check sig 5 + expect(getSigStr(msg5Resp)).not.toEqual(getSigStr(msg1Resp)) + expect(getSigStr(msg5Resp)).not.toEqual(getSigStr(msg2Resp)) + expect(getSigStr(msg5Resp)).not.toEqual(getSigStr(msg3Resp)) + expect(getSigStr(msg5Resp)).not.toEqual(getSigStr(msg4Resp)) + }) + }) }) diff --git a/packages/sdk/src/__test__/e2e/signing/bls.test.ts b/packages/sdk/src/__test__/e2e/signing/bls.test.ts index b8ade177..f66ffdd8 100644 --- a/packages/sdk/src/__test__/e2e/signing/bls.test.ts +++ b/packages/sdk/src/__test__/e2e/signing/bls.test.ts @@ -16,10 +16,10 @@ * incorrect key derivations. */ import { - create as createKeystore, - decrypt as decryptKeystore, - isValidKeystore, - verifyPassword, + create as createKeystore, + decrypt as decryptKeystore, + isValidKeystore, + verifyPassword, } from '@chainsafe/bls-keystore' import { getPublicKey, sign } from '@noble/bls12-381' import { deriveSeedTree } from 'bls12-381-keygen' @@ -43,186 +43,186 @@ const N_TEST_SIGS = 5 const KNOWN_SEED = TEST_SEED describe('[BLS keys]', () => { - beforeAll(async () => { - client = await setupClient() - if (process.env.CI) { - encPw = process.env.ENC_PW - } else { - encPw = getEncPw() - if (!encPw) { - encPw = await question('Enter your Lattice encryption password: ') - } - } - - // Check if firmware supports BLS (requires >= 0.17.0) - const fwVersion = client.getFwVersion() - const versionStr = fwVersion - ? `${fwVersion.major}.${fwVersion.minor}.${fwVersion.fix}` - : 'unknown' - - console.log(`\n[BLS Test] Firmware version: ${versionStr}`) - console.log('[BLS Test] Raw fwVersion:', fwVersion) - - const fwConstants = client.getFwConstants() - console.log('[BLS Test] getAddressFlags:', fwConstants?.getAddressFlags) - console.log( - '[BLS Test] BLS12_381_G1_PUB constant:', - Constants.GET_ADDR_FLAGS.BLS12_381_G1_PUB, - ) - - supportsBLS = fwConstants?.getAddressFlags?.includes( - Constants.GET_ADDR_FLAGS.BLS12_381_G1_PUB as number, - ) - - console.log(`[BLS Test] supportsBLS: ${supportsBLS}\n`) - - if (!supportsBLS) { - console.warn( - `\nSkipping BLS tests: Firmware version ${versionStr} does not support BLS operations.\nBLS support requires firmware version >= 0.17.0\n`, - ) - } - }) - - it('Should validate exported EIP2335 keystores', async (ctx) => { - if (!supportsBLS) { - ctx.skip() - return - } - const req = { - schema: Constants.ENC_DATA.SCHEMAS.BLS_KEYSTORE_EIP2335_PBKDF_V4, - params: { - path: WITHDRAWAL_PATH, - c: 999, // if this is not specified, the default value will be used - }, - } - let encData: unknown - // Test custom iteration count (c) - encData = await client.fetchEncryptedData(req) - await validateExportedKeystore(KNOWN_SEED, req.params.path, encPw, encData) - // Test different paths - req.params.path = DEPOSIT_PATH - encData = await client.fetchEncryptedData(req) - await validateExportedKeystore(KNOWN_SEED, req.params.path, encPw, encData) - req.params.path[4] = 1847 - encData = await client.fetchEncryptedData(req) - await validateExportedKeystore(KNOWN_SEED, req.params.path, encPw, encData) - // Test default values - req.params.path = DEPOSIT_PATH - req.params.c = undefined - encData = await client.fetchEncryptedData(req) - await validateExportedKeystore(KNOWN_SEED, req.params.path, encPw, encData) - }) - - for (let i = 0; i < N_TEST_SIGS; i++) { - describe(`[Validate Derived Signature #${i + 1}/${N_TEST_SIGS}]`, () => { - it(`Should validate derivation and signing at deposit index #${i + 1}`, async (ctx) => { - if (!supportsBLS) { - ctx.skip() - return - } - const depositPath = JSON.parse(JSON.stringify(DEPOSIT_PATH)) - depositPath[2] = i - await testBLSDerivationAndSig(KNOWN_SEED, depositPath) - }) - - it(`Should validate derivation and signing at withdrawal index #${i + 1}`, async (ctx) => { - if (!supportsBLS) { - ctx.skip() - return - } - const withdrawalPath = JSON.parse(JSON.stringify(WITHDRAWAL_PATH)) - withdrawalPath[2] = i - await testBLSDerivationAndSig(KNOWN_SEED, withdrawalPath) - }) - }) - } + beforeAll(async () => { + client = await setupClient() + if (process.env.CI) { + encPw = process.env.ENC_PW + } else { + encPw = getEncPw() + if (!encPw) { + encPw = await question('Enter your Lattice encryption password: ') + } + } + + // Check if firmware supports BLS (requires >= 0.17.0) + const fwVersion = client.getFwVersion() + const versionStr = fwVersion + ? `${fwVersion.major}.${fwVersion.minor}.${fwVersion.fix}` + : 'unknown' + + console.log(`\n[BLS Test] Firmware version: ${versionStr}`) + console.log('[BLS Test] Raw fwVersion:', fwVersion) + + const fwConstants = client.getFwConstants() + console.log('[BLS Test] getAddressFlags:', fwConstants?.getAddressFlags) + console.log( + '[BLS Test] BLS12_381_G1_PUB constant:', + Constants.GET_ADDR_FLAGS.BLS12_381_G1_PUB, + ) + + supportsBLS = fwConstants?.getAddressFlags?.includes( + Constants.GET_ADDR_FLAGS.BLS12_381_G1_PUB as number, + ) + + console.log(`[BLS Test] supportsBLS: ${supportsBLS}\n`) + + if (!supportsBLS) { + console.warn( + `\nSkipping BLS tests: Firmware version ${versionStr} does not support BLS operations.\nBLS support requires firmware version >= 0.17.0\n`, + ) + } + }) + + it('Should validate exported EIP2335 keystores', async (ctx) => { + if (!supportsBLS) { + ctx.skip() + return + } + const req = { + schema: Constants.ENC_DATA.SCHEMAS.BLS_KEYSTORE_EIP2335_PBKDF_V4, + params: { + path: WITHDRAWAL_PATH, + c: 999, // if this is not specified, the default value will be used + }, + } + let encData: unknown + // Test custom iteration count (c) + encData = await client.fetchEncryptedData(req) + await validateExportedKeystore(KNOWN_SEED, req.params.path, encPw, encData) + // Test different paths + req.params.path = DEPOSIT_PATH + encData = await client.fetchEncryptedData(req) + await validateExportedKeystore(KNOWN_SEED, req.params.path, encPw, encData) + req.params.path[4] = 1847 + encData = await client.fetchEncryptedData(req) + await validateExportedKeystore(KNOWN_SEED, req.params.path, encPw, encData) + // Test default values + req.params.path = DEPOSIT_PATH + req.params.c = undefined + encData = await client.fetchEncryptedData(req) + await validateExportedKeystore(KNOWN_SEED, req.params.path, encPw, encData) + }) + + for (let i = 0; i < N_TEST_SIGS; i++) { + describe(`[Validate Derived Signature #${i + 1}/${N_TEST_SIGS}]`, () => { + it(`Should validate derivation and signing at deposit index #${i + 1}`, async (ctx) => { + if (!supportsBLS) { + ctx.skip() + return + } + const depositPath = JSON.parse(JSON.stringify(DEPOSIT_PATH)) + depositPath[2] = i + await testBLSDerivationAndSig(KNOWN_SEED, depositPath) + }) + + it(`Should validate derivation and signing at withdrawal index #${i + 1}`, async (ctx) => { + if (!supportsBLS) { + ctx.skip() + return + } + const withdrawalPath = JSON.parse(JSON.stringify(WITHDRAWAL_PATH)) + withdrawalPath[2] = i + await testBLSDerivationAndSig(KNOWN_SEED, withdrawalPath) + }) + }) + } }) //========================================================= // INTERNAL HELPERS //========================================================= async function getBLSPub(startPath: number[]) { - const pubs = await client.getAddresses({ - startPath, - n: 1, - flag: Constants.GET_ADDR_FLAGS.BLS12_381_G1_PUB, - } as Parameters[0]) - return pubs[0] + const pubs = await client.getAddresses({ + startPath, + n: 1, + flag: Constants.GET_ADDR_FLAGS.BLS12_381_G1_PUB, + } as Parameters[0]) + return pubs[0] } async function signBLS(signerPath, message) { - const signReq = { - data: { - signerPath, - curveType: Constants.SIGNING.CURVES.BLS12_381_G2, - hashType: Constants.SIGNING.HASHES.NONE, - encodingType: Constants.SIGNING.ENCODINGS.NONE, - payload: message, - }, - } - return await client.sign(signReq) + const signReq = { + data: { + signerPath, + curveType: Constants.SIGNING.CURVES.BLS12_381_G2, + hashType: Constants.SIGNING.HASHES.NONE, + encodingType: Constants.SIGNING.ENCODINGS.NONE, + payload: message, + }, + } + return await client.sign(signReq) } async function testBLSDerivationAndSig(seed, signerPath) { - const msg = Buffer.from('64726e3da8', 'hex') - const priv = deriveSeedTree(seed, buildPath(signerPath)) - const latticePub = await getBLSPub(signerPath) - const latticeSig = await signBLS(signerPath, msg) - const refPub = getPublicKey(priv) - const refPubStr = Buffer.from(refPub).toString('hex') - const refSig = await sign(msg, priv) - const refSigStr = Buffer.from(refSig).toString('hex') - expect(latticePub.toString('hex')).to.equal( - refPubStr, - 'Deposit public key mismatch', - ) - expect(latticeSig.pubkey.toString('hex')).to.equal( - refPubStr, - 'Lattice signature returned wrong pubkey', - ) - expect( - Buffer.from(latticeSig.sig as unknown as Buffer).toString('hex'), - ).to.equal(refSigStr, 'Signature mismatch') + const msg = Buffer.from('64726e3da8', 'hex') + const priv = deriveSeedTree(seed, buildPath(signerPath)) + const latticePub = await getBLSPub(signerPath) + const latticeSig = await signBLS(signerPath, msg) + const refPub = getPublicKey(priv) + const refPubStr = Buffer.from(refPub).toString('hex') + const refSig = await sign(msg, priv) + const refSigStr = Buffer.from(refSig).toString('hex') + expect(latticePub.toString('hex')).to.equal( + refPubStr, + 'Deposit public key mismatch', + ) + expect(latticeSig.pubkey.toString('hex')).to.equal( + refPubStr, + 'Lattice signature returned wrong pubkey', + ) + expect( + Buffer.from(latticeSig.sig as unknown as Buffer).toString('hex'), + ).to.equal(refSigStr, 'Signature mismatch') } async function validateExportedKeystore(seed, path, pw, expKeystoreBuffer) { - const exportedKeystore = JSON.parse(expKeystoreBuffer.toString()) - const priv = deriveSeedTree(seed, buildPath(path)) - const pub = getPublicKey(priv) - - // Validate the keystore in isolation - expect(isValidKeystore(exportedKeystore)).to.equal( - true, - 'Exported keystore invalid!', - ) - const expPwVerified = await verifyPassword(exportedKeystore, pw) - expect(expPwVerified).to.equal( - true, - `Password could not be verified in exported keystore. Expected "${pw}"`, - ) - const expDec = await decryptKeystore(exportedKeystore, pw) - expect(Buffer.from(expDec).toString('hex')).to.equal( - Buffer.from(priv).toString('hex'), - 'Exported keystore did not properly encrypt key!', - ) - expect(exportedKeystore.pubkey).to.equal( - Buffer.from(pub).toString('hex'), - 'Wrong public key exported from Lattice', - ) - - // Generate an independent keystore and compare decrypted contents - const genKeystore = await createKeystore(pw, priv, pub, getPathStr(path)) - expect(isValidKeystore(genKeystore)).to.equal( - true, - 'Generated keystore invalid?', - ) - const genPwVerified = await verifyPassword(genKeystore, pw) - expect(genPwVerified).to.equal( - true, - 'Password could not be verified in generated keystore?', - ) - const genDec = await decryptKeystore(genKeystore, pw) - expect(Buffer.from(expDec).toString('hex')).to.equal( - Buffer.from(genDec).toString('hex'), - 'Exported encrypted privkey did not match factory test example...', - ) + const exportedKeystore = JSON.parse(expKeystoreBuffer.toString()) + const priv = deriveSeedTree(seed, buildPath(path)) + const pub = getPublicKey(priv) + + // Validate the keystore in isolation + expect(isValidKeystore(exportedKeystore)).to.equal( + true, + 'Exported keystore invalid!', + ) + const expPwVerified = await verifyPassword(exportedKeystore, pw) + expect(expPwVerified).to.equal( + true, + `Password could not be verified in exported keystore. Expected "${pw}"`, + ) + const expDec = await decryptKeystore(exportedKeystore, pw) + expect(Buffer.from(expDec).toString('hex')).to.equal( + Buffer.from(priv).toString('hex'), + 'Exported keystore did not properly encrypt key!', + ) + expect(exportedKeystore.pubkey).to.equal( + Buffer.from(pub).toString('hex'), + 'Wrong public key exported from Lattice', + ) + + // Generate an independent keystore and compare decrypted contents + const genKeystore = await createKeystore(pw, priv, pub, getPathStr(path)) + expect(isValidKeystore(genKeystore)).to.equal( + true, + 'Generated keystore invalid?', + ) + const genPwVerified = await verifyPassword(genKeystore, pw) + expect(genPwVerified).to.equal( + true, + 'Password could not be verified in generated keystore?', + ) + const genDec = await decryptKeystore(genKeystore, pw) + expect(Buffer.from(expDec).toString('hex')).to.equal( + Buffer.from(genDec).toString('hex'), + 'Exported encrypted privkey did not match factory test example...', + ) } diff --git a/packages/sdk/src/__test__/e2e/signing/determinism.test.ts b/packages/sdk/src/__test__/e2e/signing/determinism.test.ts index 77f88e4b..8f327e9a 100644 --- a/packages/sdk/src/__test__/e2e/signing/determinism.test.ts +++ b/packages/sdk/src/__test__/e2e/signing/determinism.test.ts @@ -2,17 +2,17 @@ import { HARDENED_OFFSET } from '../../../constants' import type { SignRequestParams, WalletPath } from '../../../types' import { randomBytes } from '../../../util' import { - DEFAULT_SIGNER, - buildMsgReq, - buildRandomVectors, - buildTx, - buildTxReq, + DEFAULT_SIGNER, + buildMsgReq, + buildRandomVectors, + buildTx, + buildTxReq, } from '../../utils/builders' import { - deriveAddress, - signEip712JS, - signPersonalJS, - testUniformSigs, + deriveAddress, + signEip712JS, + signPersonalJS, + testUniformSigs, } from '../../utils/determinism' /** * REQUIRED TEST MNEMONIC: @@ -29,458 +29,458 @@ import { TEST_SEED } from '../../utils/testConstants' import type { Client } from '../../../client' describe('[Determinism]', () => { - let client: Client - - beforeAll(async () => { - client = await setupClient() - }) - - describe('Setup and validate seed', () => { - it('Should re-connect to the Lattice and update the walletUID.', async () => { - expect(getDeviceId()).to.not.equal(null) - await client.connect(getDeviceId()) - expect(client.isPaired).toEqual(true) - expect(!!client.getActiveWallet()).toEqual(true) - }) - - it('Should validate some Ledger addresses derived from the test seed', async () => { - const path0 = [ - BTC_PURPOSE_P2PKH, - ETH_COIN, - HARDENED_OFFSET, - 0, - 0, - ] as WalletPath - const addr0 = deriveAddress(TEST_SEED, path0) - const path1 = [ - BTC_PURPOSE_P2PKH, - ETH_COIN, - HARDENED_OFFSET + 1, - 0, - 0, - ] as WalletPath - const addr1 = deriveAddress(TEST_SEED, path1) - const path8 = [ - BTC_PURPOSE_P2PKH, - ETH_COIN, - HARDENED_OFFSET + 8, - 0, - 0, - ] as WalletPath - const addr8 = deriveAddress(TEST_SEED, path8) - // Fetch these addresses from the Lattice and validate - - const req = { - currency: 'ETH', - startPath: path0, - n: 1, - } - const latAddr0 = await client.getAddresses(req) - expect((latAddr0[0] as string).toLowerCase()).toEqualElseLog( - addr0.toLowerCase(), - 'Incorrect address 0 fetched. Ensure your SafeCard is loaded with the test mnemonic: "test test test test test test test test test test test junk"', - ) - req.startPath = path1 - const latAddr1 = await client.getAddresses(req) - expect((latAddr1[0] as string).toLowerCase()).toEqualElseLog( - addr1.toLowerCase(), - 'Incorrect address 1 fetched. Ensure your SafeCard is loaded with the test mnemonic: "test test test test test test test test test test test junk"', - ) - req.startPath = path8 - const latAddr8 = await client.getAddresses(req) - expect((latAddr8[0] as string).toLowerCase()).toEqualElseLog( - addr8.toLowerCase(), - 'Incorrect address 8 fetched. Ensure your SafeCard is loaded with the test mnemonic: "test test test test test test test test test test test junk"', - ) - }) - }) - - describe('Test uniformity of Ethereum transaction sigs', () => { - it("Should validate uniformity sigs on m/44'/60'/0'/0/0", async () => { - const tx = buildTx() - const txReq = buildTxReq(tx) - txReq.data.signerPath[2] = HARDENED_OFFSET - await testUniformSigs(txReq, tx, client) - }) - - it("Should validate uniformity sigs on m/44'/60'/1'/0/0", async () => { - const tx = buildTx() - const txReq = buildTxReq(tx) - txReq.data.signerPath[2] = HARDENED_OFFSET + 1 - await testUniformSigs(txReq, tx, client) - }) - - it("Should validate uniformity sigs on m/44'/60'/8'/0/0", async () => { - const tx = buildTx() - const txReq = buildTxReq(tx) - txReq.data.signerPath[2] = HARDENED_OFFSET + 8 - await testUniformSigs(txReq, tx, client) - }) - - it("Should validate uniformity sigs on m/44'/60'/0'/0/0", async () => { - const tx = buildTx(`0x${randomBytes(4000).toString('hex')}`) - const txReq = buildTxReq(tx) - txReq.data.signerPath[2] = HARDENED_OFFSET - await testUniformSigs(txReq, tx, client) - }) - - it("Should validate uniformity sigs on m/44'/60'/1'/0/0", async () => { - const tx = buildTx(`0x${randomBytes(4000).toString('hex')}`) - const txReq = buildTxReq(tx) - txReq.data.signerPath[2] = HARDENED_OFFSET + 1 - await testUniformSigs(txReq, tx, client) - }) - - it("Should validate uniformity sigs on m/44'/60'/8'/0/0", async () => { - const tx = buildTx(`0x${randomBytes(4000).toString('hex')}`) - const txReq = buildTxReq(tx) - txReq.data.signerPath[2] = HARDENED_OFFSET + 8 - await testUniformSigs(txReq, tx, client) - }) - }) - - describe('Compare personal_sign signatures vs Ledger vectors (1)', () => { - it('Should validate signature from addr0', async () => { - const msgReq = buildMsgReq() - msgReq.data.signerPath[2] = HARDENED_OFFSET - const res = await client.sign(msgReq as unknown as SignRequestParams) - const sig = getSigStr(res) - const jsSig = signPersonalJS(msgReq.data.payload, msgReq.data.signerPath) - expect(sig).toEqualElseLog( - jsSig, - 'Lattice sig does not match JS reference', - ) - }) - - it('Should validate signature from addr1', async () => { - const msgReq = buildMsgReq() - msgReq.data.signerPath[2] = HARDENED_OFFSET + 1 - const res = await client.sign(msgReq as unknown as SignRequestParams) - const sig = getSigStr(res) - const jsSig = signPersonalJS(msgReq.data.payload, msgReq.data.signerPath) - expect(sig).toEqualElseLog( - jsSig, - 'Lattice sig does not match JS reference', - ) - }) - - it('Should validate signature from addr8', async () => { - const msgReq = buildMsgReq() - msgReq.data.signerPath[2] = HARDENED_OFFSET + 8 - const res = await client.sign(msgReq as unknown as SignRequestParams) - const sig = getSigStr(res) - const jsSig = signPersonalJS(msgReq.data.payload, msgReq.data.signerPath) - expect(sig).toEqualElseLog( - jsSig, - 'Lattice sig does not match JS reference', - ) - }) - }) - - describe('Compare personal_sign signatures vs Ledger vectors (2)', () => { - it('Should validate signature from addr0', async () => { - const msgReq = buildMsgReq('hello ethereum this is another message') - msgReq.data.signerPath[2] = HARDENED_OFFSET - const res = await client.sign(msgReq as unknown as SignRequestParams) - const sig = getSigStr(res) - const jsSig = signPersonalJS(msgReq.data.payload, msgReq.data.signerPath) - expect(sig).toEqualElseLog( - jsSig, - 'Lattice sig does not match JS reference', - ) - }) - - it('Should validate signature from addr1', async () => { - const msgReq = buildMsgReq('hello ethereum this is another message') - msgReq.data.signerPath[2] = HARDENED_OFFSET + 1 - const res = await client.sign(msgReq as unknown as SignRequestParams) - const sig = getSigStr(res) - const jsSig = signPersonalJS(msgReq.data.payload, msgReq.data.signerPath) - expect(sig).toEqualElseLog( - jsSig, - 'Lattice sig does not match JS reference', - ) - }) - - it('Should validate signature from addr8', async () => { - const msgReq = buildMsgReq('hello ethereum this is another message') - msgReq.data.signerPath[2] = HARDENED_OFFSET + 8 - const res = await client.sign(msgReq as unknown as SignRequestParams) - const sig = getSigStr(res) - const jsSig = signPersonalJS(msgReq.data.payload, msgReq.data.signerPath) - expect(sig).toEqualElseLog( - jsSig, - 'Lattice sig does not match JS reference', - ) - }) - }) - - describe('Compare personal_sign signatures vs Ledger vectors (3)', () => { - it('Should validate signature from addr0', async () => { - const msgReq = buildMsgReq('third vector yo') - msgReq.data.signerPath[2] = HARDENED_OFFSET - const res = await client.sign(msgReq as unknown as SignRequestParams) - const sig = getSigStr(res) - const jsSig = signPersonalJS(msgReq.data.payload, msgReq.data.signerPath) - expect(sig).toEqualElseLog( - jsSig, - 'Lattice sig does not match JS reference', - ) - }) - - it('Should validate signature from addr1', async () => { - const msgReq = buildMsgReq('third vector yo') - msgReq.data.signerPath[2] = HARDENED_OFFSET + 1 - const res = await client.sign(msgReq as unknown as SignRequestParams) - const sig = getSigStr(res) - const jsSig = signPersonalJS(msgReq.data.payload, msgReq.data.signerPath) - expect(sig).toEqualElseLog( - jsSig, - 'Lattice sig does not match JS reference', - ) - }) - - it('Should validate signature from addr8', async () => { - const msgReq = buildMsgReq('third vector yo') - msgReq.data.signerPath[2] = HARDENED_OFFSET + 8 - const res = await client.sign(msgReq as unknown as SignRequestParams) - const sig = getSigStr(res) - const jsSig = signPersonalJS(msgReq.data.payload, msgReq.data.signerPath) - expect(sig).toEqualElseLog( - jsSig, - 'Lattice sig does not match JS reference', - ) - }) - }) - - describe('Compare EIP712 signatures vs Ledger vectors (1)', () => { - const msgReq = { - currency: 'ETH_MSG', - data: { - signerPath: DEFAULT_SIGNER, - protocol: 'eip712', - payload: { - types: { - Greeting: [ - { - name: 'salutation', - type: 'string', - }, - { - name: 'target', - type: 'string', - }, - { - name: 'born', - type: 'int32', - }, - ], - EIP712Domain: [ - { - name: 'chainId', - type: 'uint256', - }, - ], - }, - domain: { - chainId: 1, - }, - primaryType: 'Greeting', - message: { - salutation: 'Hello', - target: 'Ethereum', - born: '2015', - }, - }, - }, - } - - it('Should validate signature from addr0', async () => { - msgReq.data.signerPath[2] = HARDENED_OFFSET - const res = await client.sign(msgReq as unknown as SignRequestParams) - const sig = getSigStr(res) - const jsSig = signEip712JS(msgReq.data.payload, msgReq.data.signerPath) - expect(sig).toEqualElseLog( - jsSig, - 'Lattice EIP712 sig does not match JS reference', - ) - }) - - it('Should validate signature from addr1', async () => { - msgReq.data.signerPath[2] = HARDENED_OFFSET + 1 - const res = await client.sign(msgReq as unknown as SignRequestParams) - const sig = getSigStr(res) - const jsSig = signEip712JS(msgReq.data.payload, msgReq.data.signerPath) - expect(sig).toEqualElseLog( - jsSig, - 'Lattice EIP712 sig does not match JS reference', - ) - }) - - it('Should validate signature from addr8', async () => { - msgReq.data.signerPath[2] = HARDENED_OFFSET + 8 - const res = await client.sign(msgReq as unknown as SignRequestParams) - const sig = getSigStr(res) - const jsSig = signEip712JS(msgReq.data.payload, msgReq.data.signerPath) - expect(sig).toEqualElseLog( - jsSig, - 'Lattice EIP712 sig does not match JS reference', - ) - }) - }) - - describe('Compare EIP712 signatures vs Ledger vectors (2)', () => { - const msgReq = { - currency: 'ETH_MSG', - data: { - signerPath: DEFAULT_SIGNER, - protocol: 'eip712', - payload: { - types: { - MuhType: [ - { - name: 'thing', - type: 'string', - }, - ], - EIP712Domain: [ - { - name: 'chainId', - type: 'uint256', - }, - ], - }, - domain: { - chainId: 1, - }, - primaryType: 'MuhType', - message: { - thing: 'I am a string', - }, - }, - }, - } - - it('Should validate signature from addr0', async () => { - msgReq.data.signerPath[2] = HARDENED_OFFSET - const res = await client.sign(msgReq as unknown as SignRequestParams) - const sig = getSigStr(res) - const jsSig = signEip712JS(msgReq.data.payload, msgReq.data.signerPath) - expect(sig).toEqualElseLog( - jsSig, - 'Lattice EIP712 sig does not match JS reference', - ) - }) - - it('Should validate signature from addr1', async () => { - msgReq.data.signerPath[2] = HARDENED_OFFSET + 1 - const res = await client.sign(msgReq as unknown as SignRequestParams) - const sig = getSigStr(res) - const jsSig = signEip712JS(msgReq.data.payload, msgReq.data.signerPath) - expect(sig).toEqualElseLog( - jsSig, - 'Lattice EIP712 sig does not match JS reference', - ) - }) - - it('Should validate signature from addr8', async () => { - msgReq.data.signerPath[2] = HARDENED_OFFSET + 8 - const res = await client.sign(msgReq as unknown as SignRequestParams) - const sig = getSigStr(res) - const jsSig = signEip712JS(msgReq.data.payload, msgReq.data.signerPath) - expect(sig).toEqualElseLog( - jsSig, - 'Lattice EIP712 sig does not match JS reference', - ) - }) - }) - - describe('Compare EIP712 signatures vs Ledger vectors (3)', () => { - const msgReq = { - currency: 'ETH_MSG', - data: { - signerPath: DEFAULT_SIGNER, - protocol: 'eip712', - payload: { - types: { - MuhType: [ - { - name: 'numbawang', - type: 'uint32', - }, - ], - EIP712Domain: [ - { - name: 'chainId', - type: 'uint256', - }, - ], - }, - domain: { - chainId: 1, - }, - primaryType: 'MuhType', - message: { - numbawang: 999, - }, - }, - }, - } - - it('Should validate signature from addr0', async () => { - msgReq.data.signerPath[2] = HARDENED_OFFSET - const res = await client.sign(msgReq as unknown as SignRequestParams) - const sig = getSigStr(res) - const jsSig = signEip712JS(msgReq.data.payload, msgReq.data.signerPath) - expect(sig).toEqualElseLog( - jsSig, - 'Lattice EIP712 sig does not match JS reference', - ) - }) - it('Should validate signature from addr1', async () => { - msgReq.data.signerPath[2] = HARDENED_OFFSET + 1 - const res = await client.sign(msgReq as unknown as SignRequestParams) - const sig = getSigStr(res) - const jsSig = signEip712JS(msgReq.data.payload, msgReq.data.signerPath) - expect(sig).toEqualElseLog( - jsSig, - 'Lattice EIP712 sig does not match JS reference', - ) - }) - it('Should validate signature from addr8', async () => { - msgReq.data.signerPath[2] = HARDENED_OFFSET + 8 - const res = await client.sign(msgReq as unknown as SignRequestParams) - const sig = getSigStr(res) - const jsSig = signEip712JS(msgReq.data.payload, msgReq.data.signerPath) - expect(sig).toEqualElseLog( - jsSig, - 'Lattice EIP712 sig does not match JS reference', - ) - }) - }) - - describe('Test random personal_sign messages against JS signatures', () => { - const randomVectors = buildRandomVectors() - const signerPathOffsets = [0, 1, 8] - - randomVectors.forEach((payload, i) => { - signerPathOffsets.forEach(async (offset) => { - it(`Should test random vector: ${i} with offset ${offset}`, async () => { - const req = { - currency: 'ETH_MSG', - data: { - signerPath: DEFAULT_SIGNER, - protocol: 'signPersonal', - payload, - }, - } - req.data.signerPath[2] = HARDENED_OFFSET + offset - const jsSig = signPersonalJS(req.data.payload, req.data.signerPath) - const res = await client.sign(req as unknown as SignRequestParams) - const sig = getSigStr(res) - expect(sig).toEqualElseLog(jsSig, `Addr${offset} sig failed`) - }) - }) - }) - }) + let client: Client + + beforeAll(async () => { + client = await setupClient() + }) + + describe('Setup and validate seed', () => { + it('Should re-connect to the Lattice and update the walletUID.', async () => { + expect(getDeviceId()).to.not.equal(null) + await client.connect(getDeviceId()) + expect(client.isPaired).toEqual(true) + expect(!!client.getActiveWallet()).toEqual(true) + }) + + it('Should validate some Ledger addresses derived from the test seed', async () => { + const path0 = [ + BTC_PURPOSE_P2PKH, + ETH_COIN, + HARDENED_OFFSET, + 0, + 0, + ] as WalletPath + const addr0 = deriveAddress(TEST_SEED, path0) + const path1 = [ + BTC_PURPOSE_P2PKH, + ETH_COIN, + HARDENED_OFFSET + 1, + 0, + 0, + ] as WalletPath + const addr1 = deriveAddress(TEST_SEED, path1) + const path8 = [ + BTC_PURPOSE_P2PKH, + ETH_COIN, + HARDENED_OFFSET + 8, + 0, + 0, + ] as WalletPath + const addr8 = deriveAddress(TEST_SEED, path8) + // Fetch these addresses from the Lattice and validate + + const req = { + currency: 'ETH', + startPath: path0, + n: 1, + } + const latAddr0 = await client.getAddresses(req) + expect((latAddr0[0] as string).toLowerCase()).toEqualElseLog( + addr0.toLowerCase(), + 'Incorrect address 0 fetched. Ensure your SafeCard is loaded with the test mnemonic: "test test test test test test test test test test test junk"', + ) + req.startPath = path1 + const latAddr1 = await client.getAddresses(req) + expect((latAddr1[0] as string).toLowerCase()).toEqualElseLog( + addr1.toLowerCase(), + 'Incorrect address 1 fetched. Ensure your SafeCard is loaded with the test mnemonic: "test test test test test test test test test test test junk"', + ) + req.startPath = path8 + const latAddr8 = await client.getAddresses(req) + expect((latAddr8[0] as string).toLowerCase()).toEqualElseLog( + addr8.toLowerCase(), + 'Incorrect address 8 fetched. Ensure your SafeCard is loaded with the test mnemonic: "test test test test test test test test test test test junk"', + ) + }) + }) + + describe('Test uniformity of Ethereum transaction sigs', () => { + it("Should validate uniformity sigs on m/44'/60'/0'/0/0", async () => { + const tx = buildTx() + const txReq = buildTxReq(tx) + txReq.data.signerPath[2] = HARDENED_OFFSET + await testUniformSigs(txReq, tx, client) + }) + + it("Should validate uniformity sigs on m/44'/60'/1'/0/0", async () => { + const tx = buildTx() + const txReq = buildTxReq(tx) + txReq.data.signerPath[2] = HARDENED_OFFSET + 1 + await testUniformSigs(txReq, tx, client) + }) + + it("Should validate uniformity sigs on m/44'/60'/8'/0/0", async () => { + const tx = buildTx() + const txReq = buildTxReq(tx) + txReq.data.signerPath[2] = HARDENED_OFFSET + 8 + await testUniformSigs(txReq, tx, client) + }) + + it("Should validate uniformity sigs on m/44'/60'/0'/0/0", async () => { + const tx = buildTx(`0x${randomBytes(4000).toString('hex')}`) + const txReq = buildTxReq(tx) + txReq.data.signerPath[2] = HARDENED_OFFSET + await testUniformSigs(txReq, tx, client) + }) + + it("Should validate uniformity sigs on m/44'/60'/1'/0/0", async () => { + const tx = buildTx(`0x${randomBytes(4000).toString('hex')}`) + const txReq = buildTxReq(tx) + txReq.data.signerPath[2] = HARDENED_OFFSET + 1 + await testUniformSigs(txReq, tx, client) + }) + + it("Should validate uniformity sigs on m/44'/60'/8'/0/0", async () => { + const tx = buildTx(`0x${randomBytes(4000).toString('hex')}`) + const txReq = buildTxReq(tx) + txReq.data.signerPath[2] = HARDENED_OFFSET + 8 + await testUniformSigs(txReq, tx, client) + }) + }) + + describe('Compare personal_sign signatures vs Ledger vectors (1)', () => { + it('Should validate signature from addr0', async () => { + const msgReq = buildMsgReq() + msgReq.data.signerPath[2] = HARDENED_OFFSET + const res = await client.sign(msgReq as unknown as SignRequestParams) + const sig = getSigStr(res) + const jsSig = signPersonalJS(msgReq.data.payload, msgReq.data.signerPath) + expect(sig).toEqualElseLog( + jsSig, + 'Lattice sig does not match JS reference', + ) + }) + + it('Should validate signature from addr1', async () => { + const msgReq = buildMsgReq() + msgReq.data.signerPath[2] = HARDENED_OFFSET + 1 + const res = await client.sign(msgReq as unknown as SignRequestParams) + const sig = getSigStr(res) + const jsSig = signPersonalJS(msgReq.data.payload, msgReq.data.signerPath) + expect(sig).toEqualElseLog( + jsSig, + 'Lattice sig does not match JS reference', + ) + }) + + it('Should validate signature from addr8', async () => { + const msgReq = buildMsgReq() + msgReq.data.signerPath[2] = HARDENED_OFFSET + 8 + const res = await client.sign(msgReq as unknown as SignRequestParams) + const sig = getSigStr(res) + const jsSig = signPersonalJS(msgReq.data.payload, msgReq.data.signerPath) + expect(sig).toEqualElseLog( + jsSig, + 'Lattice sig does not match JS reference', + ) + }) + }) + + describe('Compare personal_sign signatures vs Ledger vectors (2)', () => { + it('Should validate signature from addr0', async () => { + const msgReq = buildMsgReq('hello ethereum this is another message') + msgReq.data.signerPath[2] = HARDENED_OFFSET + const res = await client.sign(msgReq as unknown as SignRequestParams) + const sig = getSigStr(res) + const jsSig = signPersonalJS(msgReq.data.payload, msgReq.data.signerPath) + expect(sig).toEqualElseLog( + jsSig, + 'Lattice sig does not match JS reference', + ) + }) + + it('Should validate signature from addr1', async () => { + const msgReq = buildMsgReq('hello ethereum this is another message') + msgReq.data.signerPath[2] = HARDENED_OFFSET + 1 + const res = await client.sign(msgReq as unknown as SignRequestParams) + const sig = getSigStr(res) + const jsSig = signPersonalJS(msgReq.data.payload, msgReq.data.signerPath) + expect(sig).toEqualElseLog( + jsSig, + 'Lattice sig does not match JS reference', + ) + }) + + it('Should validate signature from addr8', async () => { + const msgReq = buildMsgReq('hello ethereum this is another message') + msgReq.data.signerPath[2] = HARDENED_OFFSET + 8 + const res = await client.sign(msgReq as unknown as SignRequestParams) + const sig = getSigStr(res) + const jsSig = signPersonalJS(msgReq.data.payload, msgReq.data.signerPath) + expect(sig).toEqualElseLog( + jsSig, + 'Lattice sig does not match JS reference', + ) + }) + }) + + describe('Compare personal_sign signatures vs Ledger vectors (3)', () => { + it('Should validate signature from addr0', async () => { + const msgReq = buildMsgReq('third vector yo') + msgReq.data.signerPath[2] = HARDENED_OFFSET + const res = await client.sign(msgReq as unknown as SignRequestParams) + const sig = getSigStr(res) + const jsSig = signPersonalJS(msgReq.data.payload, msgReq.data.signerPath) + expect(sig).toEqualElseLog( + jsSig, + 'Lattice sig does not match JS reference', + ) + }) + + it('Should validate signature from addr1', async () => { + const msgReq = buildMsgReq('third vector yo') + msgReq.data.signerPath[2] = HARDENED_OFFSET + 1 + const res = await client.sign(msgReq as unknown as SignRequestParams) + const sig = getSigStr(res) + const jsSig = signPersonalJS(msgReq.data.payload, msgReq.data.signerPath) + expect(sig).toEqualElseLog( + jsSig, + 'Lattice sig does not match JS reference', + ) + }) + + it('Should validate signature from addr8', async () => { + const msgReq = buildMsgReq('third vector yo') + msgReq.data.signerPath[2] = HARDENED_OFFSET + 8 + const res = await client.sign(msgReq as unknown as SignRequestParams) + const sig = getSigStr(res) + const jsSig = signPersonalJS(msgReq.data.payload, msgReq.data.signerPath) + expect(sig).toEqualElseLog( + jsSig, + 'Lattice sig does not match JS reference', + ) + }) + }) + + describe('Compare EIP712 signatures vs Ledger vectors (1)', () => { + const msgReq = { + currency: 'ETH_MSG', + data: { + signerPath: DEFAULT_SIGNER, + protocol: 'eip712', + payload: { + types: { + Greeting: [ + { + name: 'salutation', + type: 'string', + }, + { + name: 'target', + type: 'string', + }, + { + name: 'born', + type: 'int32', + }, + ], + EIP712Domain: [ + { + name: 'chainId', + type: 'uint256', + }, + ], + }, + domain: { + chainId: 1, + }, + primaryType: 'Greeting', + message: { + salutation: 'Hello', + target: 'Ethereum', + born: '2015', + }, + }, + }, + } + + it('Should validate signature from addr0', async () => { + msgReq.data.signerPath[2] = HARDENED_OFFSET + const res = await client.sign(msgReq as unknown as SignRequestParams) + const sig = getSigStr(res) + const jsSig = signEip712JS(msgReq.data.payload, msgReq.data.signerPath) + expect(sig).toEqualElseLog( + jsSig, + 'Lattice EIP712 sig does not match JS reference', + ) + }) + + it('Should validate signature from addr1', async () => { + msgReq.data.signerPath[2] = HARDENED_OFFSET + 1 + const res = await client.sign(msgReq as unknown as SignRequestParams) + const sig = getSigStr(res) + const jsSig = signEip712JS(msgReq.data.payload, msgReq.data.signerPath) + expect(sig).toEqualElseLog( + jsSig, + 'Lattice EIP712 sig does not match JS reference', + ) + }) + + it('Should validate signature from addr8', async () => { + msgReq.data.signerPath[2] = HARDENED_OFFSET + 8 + const res = await client.sign(msgReq as unknown as SignRequestParams) + const sig = getSigStr(res) + const jsSig = signEip712JS(msgReq.data.payload, msgReq.data.signerPath) + expect(sig).toEqualElseLog( + jsSig, + 'Lattice EIP712 sig does not match JS reference', + ) + }) + }) + + describe('Compare EIP712 signatures vs Ledger vectors (2)', () => { + const msgReq = { + currency: 'ETH_MSG', + data: { + signerPath: DEFAULT_SIGNER, + protocol: 'eip712', + payload: { + types: { + MuhType: [ + { + name: 'thing', + type: 'string', + }, + ], + EIP712Domain: [ + { + name: 'chainId', + type: 'uint256', + }, + ], + }, + domain: { + chainId: 1, + }, + primaryType: 'MuhType', + message: { + thing: 'I am a string', + }, + }, + }, + } + + it('Should validate signature from addr0', async () => { + msgReq.data.signerPath[2] = HARDENED_OFFSET + const res = await client.sign(msgReq as unknown as SignRequestParams) + const sig = getSigStr(res) + const jsSig = signEip712JS(msgReq.data.payload, msgReq.data.signerPath) + expect(sig).toEqualElseLog( + jsSig, + 'Lattice EIP712 sig does not match JS reference', + ) + }) + + it('Should validate signature from addr1', async () => { + msgReq.data.signerPath[2] = HARDENED_OFFSET + 1 + const res = await client.sign(msgReq as unknown as SignRequestParams) + const sig = getSigStr(res) + const jsSig = signEip712JS(msgReq.data.payload, msgReq.data.signerPath) + expect(sig).toEqualElseLog( + jsSig, + 'Lattice EIP712 sig does not match JS reference', + ) + }) + + it('Should validate signature from addr8', async () => { + msgReq.data.signerPath[2] = HARDENED_OFFSET + 8 + const res = await client.sign(msgReq as unknown as SignRequestParams) + const sig = getSigStr(res) + const jsSig = signEip712JS(msgReq.data.payload, msgReq.data.signerPath) + expect(sig).toEqualElseLog( + jsSig, + 'Lattice EIP712 sig does not match JS reference', + ) + }) + }) + + describe('Compare EIP712 signatures vs Ledger vectors (3)', () => { + const msgReq = { + currency: 'ETH_MSG', + data: { + signerPath: DEFAULT_SIGNER, + protocol: 'eip712', + payload: { + types: { + MuhType: [ + { + name: 'numbawang', + type: 'uint32', + }, + ], + EIP712Domain: [ + { + name: 'chainId', + type: 'uint256', + }, + ], + }, + domain: { + chainId: 1, + }, + primaryType: 'MuhType', + message: { + numbawang: 999, + }, + }, + }, + } + + it('Should validate signature from addr0', async () => { + msgReq.data.signerPath[2] = HARDENED_OFFSET + const res = await client.sign(msgReq as unknown as SignRequestParams) + const sig = getSigStr(res) + const jsSig = signEip712JS(msgReq.data.payload, msgReq.data.signerPath) + expect(sig).toEqualElseLog( + jsSig, + 'Lattice EIP712 sig does not match JS reference', + ) + }) + it('Should validate signature from addr1', async () => { + msgReq.data.signerPath[2] = HARDENED_OFFSET + 1 + const res = await client.sign(msgReq as unknown as SignRequestParams) + const sig = getSigStr(res) + const jsSig = signEip712JS(msgReq.data.payload, msgReq.data.signerPath) + expect(sig).toEqualElseLog( + jsSig, + 'Lattice EIP712 sig does not match JS reference', + ) + }) + it('Should validate signature from addr8', async () => { + msgReq.data.signerPath[2] = HARDENED_OFFSET + 8 + const res = await client.sign(msgReq as unknown as SignRequestParams) + const sig = getSigStr(res) + const jsSig = signEip712JS(msgReq.data.payload, msgReq.data.signerPath) + expect(sig).toEqualElseLog( + jsSig, + 'Lattice EIP712 sig does not match JS reference', + ) + }) + }) + + describe('Test random personal_sign messages against JS signatures', () => { + const randomVectors = buildRandomVectors() + const signerPathOffsets = [0, 1, 8] + + randomVectors.forEach((payload, i) => { + signerPathOffsets.forEach(async (offset) => { + it(`Should test random vector: ${i} with offset ${offset}`, async () => { + const req = { + currency: 'ETH_MSG', + data: { + signerPath: DEFAULT_SIGNER, + protocol: 'signPersonal', + payload, + }, + } + req.data.signerPath[2] = HARDENED_OFFSET + offset + const jsSig = signPersonalJS(req.data.payload, req.data.signerPath) + const res = await client.sign(req as unknown as SignRequestParams) + const sig = getSigStr(res) + expect(sig).toEqualElseLog(jsSig, `Addr${offset} sig failed`) + }) + }) + }) + }) }) diff --git a/packages/sdk/src/__test__/e2e/signing/eip712-msg.test.ts b/packages/sdk/src/__test__/e2e/signing/eip712-msg.test.ts index 9ae54d2f..27be0dd3 100644 --- a/packages/sdk/src/__test__/e2e/signing/eip712-msg.test.ts +++ b/packages/sdk/src/__test__/e2e/signing/eip712-msg.test.ts @@ -9,13 +9,13 @@ import { signAndCompareEIP712Message } from '../../utils/viemComparison' import { EIP712_MESSAGE_VECTORS } from './eip712-vectors' describe('EIP-712 Message Signing - Viem Compatibility', () => { - beforeAll(async () => { - await setupClient() - }) + beforeAll(async () => { + await setupClient() + }) - EIP712_MESSAGE_VECTORS.forEach((vector, index) => { - it(`${vector.name} (${index + 1}/${EIP712_MESSAGE_VECTORS.length})`, async () => { - await signAndCompareEIP712Message(vector.message, vector.name) - }) - }) + EIP712_MESSAGE_VECTORS.forEach((vector, index) => { + it(`${vector.name} (${index + 1}/${EIP712_MESSAGE_VECTORS.length})`, async () => { + await signAndCompareEIP712Message(vector.message, vector.name) + }) + }) }) diff --git a/packages/sdk/src/__test__/e2e/signing/eip712-vectors.ts b/packages/sdk/src/__test__/e2e/signing/eip712-vectors.ts index be6b97ff..ae89e2ba 100644 --- a/packages/sdk/src/__test__/e2e/signing/eip712-vectors.ts +++ b/packages/sdk/src/__test__/e2e/signing/eip712-vectors.ts @@ -11,311 +11,311 @@ import type { EIP712TestMessage } from '../../utils/viemComparison' const MOCK_CONTRACT_ADDRESS = '0x1234567890123456789012345678901234567890' export const EIP712_MESSAGE_VECTORS: Array<{ - name: string - message: EIP712TestMessage + name: string + message: EIP712TestMessage }> = [ - { - name: 'Negative Amount - Basic negative integer', - message: { - domain: { - name: 'NegativeAmountHandler', - version: '1', - chainId: 1, - verifyingContract: MOCK_CONTRACT_ADDRESS, - }, - types: { - Data: [ - { name: 'amount', type: 'int256' }, - { name: 'message', type: 'string' }, - ], - }, - primaryType: 'Data', - message: { - amount: -100, - message: 'Negative payment test', - }, - }, - }, - { - name: 'Negative Amount - Large negative value', - message: { - domain: { - name: 'NegativeAmountHandler', - version: '1', - chainId: 1, - verifyingContract: MOCK_CONTRACT_ADDRESS, - }, - types: { - Data: [ - { name: 'amount', type: 'int256' }, - { name: 'message', type: 'string' }, - ], - }, - primaryType: 'Data', - message: { - amount: -1000000000000, - message: 'Large negative amount', - }, - }, - }, - { - name: 'Negative Amount - Zero value', - message: { - domain: { - name: 'NegativeAmountHandler', - version: '1', - chainId: 1, - verifyingContract: MOCK_CONTRACT_ADDRESS, - }, - types: { - Data: [ - { name: 'amount', type: 'int256' }, - { name: 'message', type: 'string' }, - ], - }, - primaryType: 'Data', - message: { - amount: 0, - message: 'Zero amount test', - }, - }, - }, - { - name: 'Negative Amount - Positive value', - message: { - domain: { - name: 'NegativeAmountHandler', - version: '1', - chainId: 1, - verifyingContract: MOCK_CONTRACT_ADDRESS, - }, - types: { - Data: [ - { name: 'amount', type: 'int256' }, - { name: 'message', type: 'string' }, - ], - }, - primaryType: 'Data', - message: { - amount: 500, - message: 'Positive amount test', - }, - }, - }, - { - name: 'Mail - Basic email structure', - message: { - domain: { - name: 'Ether Mail', - version: '1', - chainId: 1, - verifyingContract: MOCK_CONTRACT_ADDRESS, - }, - types: { - Person: [ - { name: 'name', type: 'string' }, - { name: 'wallet', type: 'address' }, - ], - Mail: [ - { name: 'from', type: 'Person' }, - { name: 'to', type: 'Person' }, - { name: 'contents', type: 'string' }, - ], - }, - primaryType: 'Mail', - message: { - from: { - name: 'Alice', - wallet: '0xCD2a3d9F938E13CD947Ec05AbC7FE734Df8DD826', - }, - to: { - name: 'Bob', - wallet: '0xbBbBBBBbbBBBbbbBbbBbbbbBBbBbbbbBbBbbBBbB', - }, - contents: 'Hello, Bob!', - }, - }, - }, - { - name: 'Permit - ERC20 approval', - message: { - domain: { - name: 'USD Coin', - version: '2', - chainId: 1, - verifyingContract: MOCK_CONTRACT_ADDRESS, - }, - types: { - Permit: [ - { name: 'owner', type: 'address' }, - { name: 'spender', type: 'address' }, - { name: 'value', type: 'uint256' }, - { name: 'nonce', type: 'uint256' }, - { name: 'deadline', type: 'uint256' }, - ], - }, - primaryType: 'Permit', - message: { - owner: '0xCD2a3d9F938E13CD947Ec05AbC7FE734Df8DD826', - spender: '0x1234567890123456789012345678901234567890', - value: 1000000, - nonce: 0, - deadline: 9999999999, - }, - }, - }, - { - name: 'Vote - Governance voting', - message: { - domain: { - name: 'Governance', - version: '1', - chainId: 1, - verifyingContract: MOCK_CONTRACT_ADDRESS, - }, - types: { - Vote: [ - { name: 'proposalId', type: 'uint256' }, - { name: 'support', type: 'bool' }, - { name: 'voter', type: 'address' }, - ], - }, - primaryType: 'Vote', - message: { - proposalId: 42, - support: true, - voter: '0xCD2a3d9F938E13CD947Ec05AbC7FE734Df8DD826', - }, - }, - }, - { - name: 'Complex Types - Nested structures', - message: { - domain: { - name: 'Complex Protocol', - version: '1', - chainId: 1, - verifyingContract: MOCK_CONTRACT_ADDRESS, - }, - types: { - Asset: [ - { name: 'token', type: 'address' }, - { name: 'amount', type: 'uint256' }, - ], - Order: [ - { name: 'trader', type: 'address' }, - { name: 'baseAsset', type: 'Asset' }, - { name: 'quoteAsset', type: 'Asset' }, - { name: 'deadline', type: 'uint256' }, - ], - }, - primaryType: 'Order', - message: { - trader: '0xCD2a3d9F938E13CD947Ec05AbC7FE734Df8DD826', - baseAsset: { - token: '0x1234567890123456789012345678901234567890', - amount: 1000, - }, - quoteAsset: { - token: '0xabcdabcdabcdabcdabcdabcdabcdabcdabcdabcd', - amount: 2000, - }, - deadline: 9999999999, - }, - }, - }, - { - name: 'Edge Case - Empty string', - message: { - domain: { - name: 'Test', - version: '1', - chainId: 1, - verifyingContract: MOCK_CONTRACT_ADDRESS, - }, - types: { - Data: [{ name: 'value', type: 'string' }], - }, - primaryType: 'Data', - message: { - value: '', - }, - }, - }, - { - name: 'Edge Case - Maximum uint256', - message: { - domain: { - name: 'Test', - version: '1', - chainId: 1, - verifyingContract: MOCK_CONTRACT_ADDRESS, - }, - types: { - Data: [{ name: 'value', type: 'uint256' }], - }, - primaryType: 'Data', - message: { - value: BigInt( - '115792089237316195423570985008687907853269984665640564039457584007913129639935', - ), - }, - }, - }, - { - name: 'Edge Case - Minimum int256', - message: { - domain: { - name: 'Test', - version: '1', - chainId: 1, - verifyingContract: MOCK_CONTRACT_ADDRESS, - }, - types: { - Data: [{ name: 'value', type: 'int256' }], - }, - primaryType: 'Data', - message: { - value: BigInt( - '-57896044618658097711785492504343953926634992332820282019728792003956564819968', - ), - }, - }, - }, - { - name: 'Different Chain - Polygon', - message: { - domain: { - name: 'Test', - version: '1', - chainId: 137, - verifyingContract: MOCK_CONTRACT_ADDRESS, - }, - types: { - Data: [{ name: 'message', type: 'string' }], - }, - primaryType: 'Data', - message: { - message: 'Polygon test', - }, - }, - }, - { - name: 'Different Chain - BSC', - message: { - domain: { - name: 'Test', - version: '1', - chainId: 56, - verifyingContract: MOCK_CONTRACT_ADDRESS, - }, - types: { - Data: [{ name: 'message', type: 'string' }], - }, - primaryType: 'Data', - message: { - message: 'BSC test', - }, - }, - }, + { + name: 'Negative Amount - Basic negative integer', + message: { + domain: { + name: 'NegativeAmountHandler', + version: '1', + chainId: 1, + verifyingContract: MOCK_CONTRACT_ADDRESS, + }, + types: { + Data: [ + { name: 'amount', type: 'int256' }, + { name: 'message', type: 'string' }, + ], + }, + primaryType: 'Data', + message: { + amount: -100, + message: 'Negative payment test', + }, + }, + }, + { + name: 'Negative Amount - Large negative value', + message: { + domain: { + name: 'NegativeAmountHandler', + version: '1', + chainId: 1, + verifyingContract: MOCK_CONTRACT_ADDRESS, + }, + types: { + Data: [ + { name: 'amount', type: 'int256' }, + { name: 'message', type: 'string' }, + ], + }, + primaryType: 'Data', + message: { + amount: -1000000000000, + message: 'Large negative amount', + }, + }, + }, + { + name: 'Negative Amount - Zero value', + message: { + domain: { + name: 'NegativeAmountHandler', + version: '1', + chainId: 1, + verifyingContract: MOCK_CONTRACT_ADDRESS, + }, + types: { + Data: [ + { name: 'amount', type: 'int256' }, + { name: 'message', type: 'string' }, + ], + }, + primaryType: 'Data', + message: { + amount: 0, + message: 'Zero amount test', + }, + }, + }, + { + name: 'Negative Amount - Positive value', + message: { + domain: { + name: 'NegativeAmountHandler', + version: '1', + chainId: 1, + verifyingContract: MOCK_CONTRACT_ADDRESS, + }, + types: { + Data: [ + { name: 'amount', type: 'int256' }, + { name: 'message', type: 'string' }, + ], + }, + primaryType: 'Data', + message: { + amount: 500, + message: 'Positive amount test', + }, + }, + }, + { + name: 'Mail - Basic email structure', + message: { + domain: { + name: 'Ether Mail', + version: '1', + chainId: 1, + verifyingContract: MOCK_CONTRACT_ADDRESS, + }, + types: { + Person: [ + { name: 'name', type: 'string' }, + { name: 'wallet', type: 'address' }, + ], + Mail: [ + { name: 'from', type: 'Person' }, + { name: 'to', type: 'Person' }, + { name: 'contents', type: 'string' }, + ], + }, + primaryType: 'Mail', + message: { + from: { + name: 'Alice', + wallet: '0xCD2a3d9F938E13CD947Ec05AbC7FE734Df8DD826', + }, + to: { + name: 'Bob', + wallet: '0xbBbBBBBbbBBBbbbBbbBbbbbBBbBbbbbBbBbbBBbB', + }, + contents: 'Hello, Bob!', + }, + }, + }, + { + name: 'Permit - ERC20 approval', + message: { + domain: { + name: 'USD Coin', + version: '2', + chainId: 1, + verifyingContract: MOCK_CONTRACT_ADDRESS, + }, + types: { + Permit: [ + { name: 'owner', type: 'address' }, + { name: 'spender', type: 'address' }, + { name: 'value', type: 'uint256' }, + { name: 'nonce', type: 'uint256' }, + { name: 'deadline', type: 'uint256' }, + ], + }, + primaryType: 'Permit', + message: { + owner: '0xCD2a3d9F938E13CD947Ec05AbC7FE734Df8DD826', + spender: '0x1234567890123456789012345678901234567890', + value: 1000000, + nonce: 0, + deadline: 9999999999, + }, + }, + }, + { + name: 'Vote - Governance voting', + message: { + domain: { + name: 'Governance', + version: '1', + chainId: 1, + verifyingContract: MOCK_CONTRACT_ADDRESS, + }, + types: { + Vote: [ + { name: 'proposalId', type: 'uint256' }, + { name: 'support', type: 'bool' }, + { name: 'voter', type: 'address' }, + ], + }, + primaryType: 'Vote', + message: { + proposalId: 42, + support: true, + voter: '0xCD2a3d9F938E13CD947Ec05AbC7FE734Df8DD826', + }, + }, + }, + { + name: 'Complex Types - Nested structures', + message: { + domain: { + name: 'Complex Protocol', + version: '1', + chainId: 1, + verifyingContract: MOCK_CONTRACT_ADDRESS, + }, + types: { + Asset: [ + { name: 'token', type: 'address' }, + { name: 'amount', type: 'uint256' }, + ], + Order: [ + { name: 'trader', type: 'address' }, + { name: 'baseAsset', type: 'Asset' }, + { name: 'quoteAsset', type: 'Asset' }, + { name: 'deadline', type: 'uint256' }, + ], + }, + primaryType: 'Order', + message: { + trader: '0xCD2a3d9F938E13CD947Ec05AbC7FE734Df8DD826', + baseAsset: { + token: '0x1234567890123456789012345678901234567890', + amount: 1000, + }, + quoteAsset: { + token: '0xabcdabcdabcdabcdabcdabcdabcdabcdabcdabcd', + amount: 2000, + }, + deadline: 9999999999, + }, + }, + }, + { + name: 'Edge Case - Empty string', + message: { + domain: { + name: 'Test', + version: '1', + chainId: 1, + verifyingContract: MOCK_CONTRACT_ADDRESS, + }, + types: { + Data: [{ name: 'value', type: 'string' }], + }, + primaryType: 'Data', + message: { + value: '', + }, + }, + }, + { + name: 'Edge Case - Maximum uint256', + message: { + domain: { + name: 'Test', + version: '1', + chainId: 1, + verifyingContract: MOCK_CONTRACT_ADDRESS, + }, + types: { + Data: [{ name: 'value', type: 'uint256' }], + }, + primaryType: 'Data', + message: { + value: BigInt( + '115792089237316195423570985008687907853269984665640564039457584007913129639935', + ), + }, + }, + }, + { + name: 'Edge Case - Minimum int256', + message: { + domain: { + name: 'Test', + version: '1', + chainId: 1, + verifyingContract: MOCK_CONTRACT_ADDRESS, + }, + types: { + Data: [{ name: 'value', type: 'int256' }], + }, + primaryType: 'Data', + message: { + value: BigInt( + '-57896044618658097711785492504343953926634992332820282019728792003956564819968', + ), + }, + }, + }, + { + name: 'Different Chain - Polygon', + message: { + domain: { + name: 'Test', + version: '1', + chainId: 137, + verifyingContract: MOCK_CONTRACT_ADDRESS, + }, + types: { + Data: [{ name: 'message', type: 'string' }], + }, + primaryType: 'Data', + message: { + message: 'Polygon test', + }, + }, + }, + { + name: 'Different Chain - BSC', + message: { + domain: { + name: 'Test', + version: '1', + chainId: 56, + verifyingContract: MOCK_CONTRACT_ADDRESS, + }, + types: { + Data: [{ name: 'message', type: 'string' }], + }, + primaryType: 'Data', + message: { + message: 'BSC test', + }, + }, + }, ] diff --git a/packages/sdk/src/__test__/e2e/signing/evm-abi.test.ts b/packages/sdk/src/__test__/e2e/signing/evm-abi.test.ts index f5496db5..ed09cd5e 100644 --- a/packages/sdk/src/__test__/e2e/signing/evm-abi.test.ts +++ b/packages/sdk/src/__test__/e2e/signing/evm-abi.test.ts @@ -8,20 +8,20 @@ import { setupClient } from '../../utils/setup' import { ABI_TEST_VECTORS } from '../../vectors/abi-vectors' describe('[EVM ABI] ABI Decoding Tests', () => { - beforeAll(async () => { - await setupClient() - }) + beforeAll(async () => { + await setupClient() + }) - describe('ABI Patterns with Complex Structures', () => { - ABI_TEST_VECTORS.forEach((testCase, index) => { - it(`Should test ${testCase.name} (${index + 1}/${ABI_TEST_VECTORS.length})`, async () => { - const result = await sign(testCase.tx) - expect(result).toBeDefined() - expect(result.sig).toBeDefined() - expect(result.sig.r).toBeDefined() - expect(result.sig.s).toBeDefined() - expect(result.sig.v).toBeDefined() - }) - }) - }) + describe('ABI Patterns with Complex Structures', () => { + ABI_TEST_VECTORS.forEach((testCase, index) => { + it(`Should test ${testCase.name} (${index + 1}/${ABI_TEST_VECTORS.length})`, async () => { + const result = await sign(testCase.tx) + expect(result).toBeDefined() + expect(result.sig).toBeDefined() + expect(result.sig.r).toBeDefined() + expect(result.sig.s).toBeDefined() + expect(result.sig.v).toBeDefined() + }) + }) + }) }) diff --git a/packages/sdk/src/__test__/e2e/signing/evm-tx.test.ts b/packages/sdk/src/__test__/e2e/signing/evm-tx.test.ts index 145e43a2..dfc0d4ca 100644 --- a/packages/sdk/src/__test__/e2e/signing/evm-tx.test.ts +++ b/packages/sdk/src/__test__/e2e/signing/evm-tx.test.ts @@ -9,55 +9,55 @@ import { setupClient } from '../../utils/setup' import { signAndCompareTransaction } from '../../utils/viemComparison' import { - EDGE_CASE_TEST_VECTORS, - EIP1559_TEST_VECTORS, - EIP2930_TEST_VECTORS, - EIP7702_TEST_VECTORS, - LEGACY_VECTORS, + EDGE_CASE_TEST_VECTORS, + EIP1559_TEST_VECTORS, + EIP2930_TEST_VECTORS, + EIP7702_TEST_VECTORS, + LEGACY_VECTORS, } from './vectors' describe('EVM Transaction Signing - Unified Test Suite', () => { - beforeAll(async () => { - await setupClient() - }) + beforeAll(async () => { + await setupClient() + }) - describe('Legacy Transactions', () => { - LEGACY_VECTORS.forEach((vector, index) => { - it(`${vector.name} (${index + 1}/${LEGACY_VECTORS.length})`, async () => { - await signAndCompareTransaction(vector.tx, vector.name) - }) - }) - }) + describe('Legacy Transactions', () => { + LEGACY_VECTORS.forEach((vector, index) => { + it(`${vector.name} (${index + 1}/${LEGACY_VECTORS.length})`, async () => { + await signAndCompareTransaction(vector.tx, vector.name) + }) + }) + }) - describe('EIP-1559 Transactions (Fee Market)', () => { - EIP1559_TEST_VECTORS.forEach((vector, index) => { - it(`${vector.name} (${index + 1}/${EIP1559_TEST_VECTORS.length})`, async () => { - await signAndCompareTransaction(vector.tx, vector.name) - }) - }) - }) + describe('EIP-1559 Transactions (Fee Market)', () => { + EIP1559_TEST_VECTORS.forEach((vector, index) => { + it(`${vector.name} (${index + 1}/${EIP1559_TEST_VECTORS.length})`, async () => { + await signAndCompareTransaction(vector.tx, vector.name) + }) + }) + }) - describe('EIP-2930 Transactions (Access Lists)', () => { - EIP2930_TEST_VECTORS.forEach((vector, index) => { - it(`${vector.name} (${index + 1}/${EIP2930_TEST_VECTORS.length})`, async () => { - await signAndCompareTransaction(vector.tx, vector.name) - }) - }) - }) + describe('EIP-2930 Transactions (Access Lists)', () => { + EIP2930_TEST_VECTORS.forEach((vector, index) => { + it(`${vector.name} (${index + 1}/${EIP2930_TEST_VECTORS.length})`, async () => { + await signAndCompareTransaction(vector.tx, vector.name) + }) + }) + }) - describe('EIP-7702 Transactions (Account Abstraction)', () => { - EIP7702_TEST_VECTORS.forEach((vector, index) => { - it(`${vector.name} (${index + 1}/${EIP7702_TEST_VECTORS.length})`, async () => { - await signAndCompareTransaction(vector.tx, vector.name) - }) - }) - }) + describe('EIP-7702 Transactions (Account Abstraction)', () => { + EIP7702_TEST_VECTORS.forEach((vector, index) => { + it(`${vector.name} (${index + 1}/${EIP7702_TEST_VECTORS.length})`, async () => { + await signAndCompareTransaction(vector.tx, vector.name) + }) + }) + }) - describe('Edge Cases & Boundary Conditions', () => { - EDGE_CASE_TEST_VECTORS.forEach((vector, index) => { - it(`${vector.name} (${index + 1}/${EDGE_CASE_TEST_VECTORS.length})`, async () => { - await signAndCompareTransaction(vector.tx, vector.name) - }) - }) - }) + describe('Edge Cases & Boundary Conditions', () => { + EDGE_CASE_TEST_VECTORS.forEach((vector, index) => { + it(`${vector.name} (${index + 1}/${EDGE_CASE_TEST_VECTORS.length})`, async () => { + await signAndCompareTransaction(vector.tx, vector.name) + }) + }) + }) }) diff --git a/packages/sdk/src/__test__/e2e/signing/solana/__mocks__/programs.ts b/packages/sdk/src/__test__/e2e/signing/solana/__mocks__/programs.ts index e04fef69..13f702a2 100644 --- a/packages/sdk/src/__test__/e2e/signing/solana/__mocks__/programs.ts +++ b/packages/sdk/src/__test__/e2e/signing/solana/__mocks__/programs.ts @@ -1,87 +1,87 @@ export const raydiumProgram = Buffer.from([ - 2, 0, 10, 24, 47, 41, 101, 134, 4, 204, 35, 219, 9, 43, 55, 20, 255, 76, 29, - 27, 175, 197, 102, 175, 108, 72, 231, 146, 134, 30, 219, 42, 168, 236, 17, 3, - 209, 66, 76, 96, 212, 139, 188, 184, 209, 19, 177, 79, 32, 176, 155, 148, 130, - 200, 98, 110, 128, 160, 186, 226, 136, 168, 203, 105, 117, 38, 171, 161, 41, - 11, 235, 184, 125, 115, 158, 2, 137, 19, 123, 58, 193, 31, 193, 215, 57, 0, - 240, 162, 154, 197, 182, 102, 96, 91, 101, 29, 31, 63, 168, 145, 47, 189, 251, - 138, 30, 226, 174, 123, 162, 139, 147, 54, 101, 251, 231, 142, 230, 173, 232, - 212, 153, 225, 58, 113, 24, 53, 97, 26, 89, 169, 83, 10, 48, 190, 92, 28, 22, - 83, 66, 168, 232, 41, 135, 46, 21, 208, 110, 217, 210, 243, 1, 237, 247, 237, - 183, 71, 1, 193, 117, 86, 188, 217, 134, 204, 52, 147, 214, 106, 218, 145, 18, - 3, 14, 152, 187, 194, 134, 97, 177, 146, 183, 102, 40, 90, 33, 217, 240, 113, - 89, 142, 64, 120, 22, 21, 175, 245, 69, 184, 172, 211, 86, 215, 29, 176, 82, - 209, 2, 198, 205, 207, 75, 152, 201, 35, 86, 169, 205, 157, 142, 144, 96, 235, - 228, 249, 204, 249, 212, 189, 80, 236, 218, 125, 206, 4, 54, 254, 165, 182, - 236, 127, 232, 154, 117, 171, 105, 133, 186, 163, 206, 201, 159, 70, 10, 17, - 138, 239, 21, 37, 109, 139, 81, 59, 76, 57, 145, 209, 124, 73, 151, 15, 83, - 117, 205, 87, 153, 166, 158, 188, 141, 157, 153, 230, 131, 244, 4, 118, 170, - 60, 182, 39, 25, 110, 87, 174, 54, 186, 81, 129, 162, 212, 79, 70, 190, 180, - 232, 143, 72, 102, 119, 242, 172, 80, 2, 53, 168, 117, 184, 99, 184, 206, 230, - 204, 5, 165, 141, 30, 37, 25, 159, 230, 130, 247, 252, 139, 86, 49, 132, 94, - 254, 8, 198, 72, 16, 173, 202, 16, 222, 102, 65, 202, 40, 218, 190, 74, 34, - 21, 163, 163, 179, 232, 137, 88, 91, 104, 174, 112, 225, 188, 55, 104, 213, - 116, 77, 143, 100, 55, 181, 95, 3, 149, 44, 132, 146, 146, 85, 62, 245, 56, - 164, 243, 10, 248, 172, 153, 97, 85, 86, 177, 82, 92, 148, 248, 130, 133, 54, - 160, 65, 92, 148, 142, 53, 148, 190, 85, 136, 146, 82, 19, 186, 209, 204, 116, - 25, 155, 69, 186, 207, 152, 45, 54, 94, 70, 191, 239, 106, 17, 161, 122, 157, - 199, 83, 30, 23, 42, 103, 226, 74, 230, 111, 113, 173, 56, 0, 0, 0, 0, 0, 0, - 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, - 55, 153, 140, 203, 242, 208, 69, 139, 97, 92, 188, 198, 177, 163, 103, 196, - 116, 158, 159, 239, 115, 6, 98, 46, 27, 27, 88, 145, 1, 32, 188, 154, 65, 87, - 176, 88, 15, 49, 197, 252, 228, 74, 98, 88, 45, 188, 249, 215, 142, 231, 89, - 67, 160, 132, 163, 147, 179, 80, 54, 141, 34, 137, 147, 8, 75, 217, 73, 196, - 54, 2, 195, 63, 32, 119, 144, 237, 22, 163, 82, 76, 161, 185, 151, 92, 241, - 33, 162, 169, 12, 255, 236, 125, 248, 182, 138, 205, 95, 183, 40, 175, 220, - 220, 4, 120, 120, 238, 132, 58, 111, 235, 68, 175, 40, 205, 83, 163, 72, 40, - 217, 144, 59, 90, 213, 230, 151, 25, 85, 42, 133, 15, 45, 110, 2, 164, 122, - 248, 36, 208, 154, 182, 157, 196, 45, 112, 203, 40, 203, 250, 36, 159, 183, - 238, 87, 185, 210, 86, 193, 39, 98, 239, 140, 151, 37, 143, 78, 36, 137, 241, - 187, 61, 16, 41, 20, 142, 13, 131, 11, 90, 19, 153, 218, 255, 16, 132, 4, 142, - 123, 216, 219, 233, 248, 89, 6, 155, 136, 87, 254, 171, 129, 132, 251, 104, - 127, 99, 70, 24, 192, 53, 218, 196, 57, 220, 26, 235, 59, 85, 152, 160, 240, - 0, 0, 0, 0, 1, 6, 167, 213, 23, 25, 44, 92, 81, 33, 140, 201, 76, 61, 74, 241, - 127, 88, 218, 238, 8, 155, 161, 253, 68, 227, 219, 217, 138, 0, 0, 0, 0, 6, - 221, 246, 225, 215, 101, 161, 147, 217, 203, 225, 70, 206, 235, 121, 172, 28, - 180, 133, 237, 95, 91, 55, 145, 58, 140, 245, 133, 126, 255, 0, 169, 238, 204, - 15, 249, 117, 156, 236, 123, 109, 247, 173, 57, 139, 143, 19, 21, 15, 180, - 230, 189, 171, 230, 228, 215, 211, 229, 22, 94, 108, 135, 17, 93, 5, 14, 2, 0, - 1, 52, 0, 0, 0, 0, 48, 96, 46, 0, 0, 0, 0, 0, 165, 0, 0, 0, 0, 0, 0, 0, 6, - 221, 246, 225, 215, 101, 161, 147, 217, 203, 225, 70, 206, 235, 121, 172, 28, - 180, 133, 237, 95, 91, 55, 145, 58, 140, 245, 133, 126, 255, 0, 169, 23, 4, 1, - 21, 0, 22, 1, 1, 20, 7, 0, 9, 0, 15, 14, 23, 22, 0, 17, 18, 23, 10, 16, 7, 6, - 13, 2, 19, 12, 11, 3, 4, 8, 5, 18, 1, 9, 0, 17, 9, 64, 66, 15, 0, 0, 0, 0, 0, - 89, 241, 0, 0, 0, 0, 0, 0, 23, 3, 1, 0, 0, 1, 9, + 2, 0, 10, 24, 47, 41, 101, 134, 4, 204, 35, 219, 9, 43, 55, 20, 255, 76, 29, + 27, 175, 197, 102, 175, 108, 72, 231, 146, 134, 30, 219, 42, 168, 236, 17, 3, + 209, 66, 76, 96, 212, 139, 188, 184, 209, 19, 177, 79, 32, 176, 155, 148, 130, + 200, 98, 110, 128, 160, 186, 226, 136, 168, 203, 105, 117, 38, 171, 161, 41, + 11, 235, 184, 125, 115, 158, 2, 137, 19, 123, 58, 193, 31, 193, 215, 57, 0, + 240, 162, 154, 197, 182, 102, 96, 91, 101, 29, 31, 63, 168, 145, 47, 189, 251, + 138, 30, 226, 174, 123, 162, 139, 147, 54, 101, 251, 231, 142, 230, 173, 232, + 212, 153, 225, 58, 113, 24, 53, 97, 26, 89, 169, 83, 10, 48, 190, 92, 28, 22, + 83, 66, 168, 232, 41, 135, 46, 21, 208, 110, 217, 210, 243, 1, 237, 247, 237, + 183, 71, 1, 193, 117, 86, 188, 217, 134, 204, 52, 147, 214, 106, 218, 145, 18, + 3, 14, 152, 187, 194, 134, 97, 177, 146, 183, 102, 40, 90, 33, 217, 240, 113, + 89, 142, 64, 120, 22, 21, 175, 245, 69, 184, 172, 211, 86, 215, 29, 176, 82, + 209, 2, 198, 205, 207, 75, 152, 201, 35, 86, 169, 205, 157, 142, 144, 96, 235, + 228, 249, 204, 249, 212, 189, 80, 236, 218, 125, 206, 4, 54, 254, 165, 182, + 236, 127, 232, 154, 117, 171, 105, 133, 186, 163, 206, 201, 159, 70, 10, 17, + 138, 239, 21, 37, 109, 139, 81, 59, 76, 57, 145, 209, 124, 73, 151, 15, 83, + 117, 205, 87, 153, 166, 158, 188, 141, 157, 153, 230, 131, 244, 4, 118, 170, + 60, 182, 39, 25, 110, 87, 174, 54, 186, 81, 129, 162, 212, 79, 70, 190, 180, + 232, 143, 72, 102, 119, 242, 172, 80, 2, 53, 168, 117, 184, 99, 184, 206, 230, + 204, 5, 165, 141, 30, 37, 25, 159, 230, 130, 247, 252, 139, 86, 49, 132, 94, + 254, 8, 198, 72, 16, 173, 202, 16, 222, 102, 65, 202, 40, 218, 190, 74, 34, + 21, 163, 163, 179, 232, 137, 88, 91, 104, 174, 112, 225, 188, 55, 104, 213, + 116, 77, 143, 100, 55, 181, 95, 3, 149, 44, 132, 146, 146, 85, 62, 245, 56, + 164, 243, 10, 248, 172, 153, 97, 85, 86, 177, 82, 92, 148, 248, 130, 133, 54, + 160, 65, 92, 148, 142, 53, 148, 190, 85, 136, 146, 82, 19, 186, 209, 204, 116, + 25, 155, 69, 186, 207, 152, 45, 54, 94, 70, 191, 239, 106, 17, 161, 122, 157, + 199, 83, 30, 23, 42, 103, 226, 74, 230, 111, 113, 173, 56, 0, 0, 0, 0, 0, 0, + 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, + 55, 153, 140, 203, 242, 208, 69, 139, 97, 92, 188, 198, 177, 163, 103, 196, + 116, 158, 159, 239, 115, 6, 98, 46, 27, 27, 88, 145, 1, 32, 188, 154, 65, 87, + 176, 88, 15, 49, 197, 252, 228, 74, 98, 88, 45, 188, 249, 215, 142, 231, 89, + 67, 160, 132, 163, 147, 179, 80, 54, 141, 34, 137, 147, 8, 75, 217, 73, 196, + 54, 2, 195, 63, 32, 119, 144, 237, 22, 163, 82, 76, 161, 185, 151, 92, 241, + 33, 162, 169, 12, 255, 236, 125, 248, 182, 138, 205, 95, 183, 40, 175, 220, + 220, 4, 120, 120, 238, 132, 58, 111, 235, 68, 175, 40, 205, 83, 163, 72, 40, + 217, 144, 59, 90, 213, 230, 151, 25, 85, 42, 133, 15, 45, 110, 2, 164, 122, + 248, 36, 208, 154, 182, 157, 196, 45, 112, 203, 40, 203, 250, 36, 159, 183, + 238, 87, 185, 210, 86, 193, 39, 98, 239, 140, 151, 37, 143, 78, 36, 137, 241, + 187, 61, 16, 41, 20, 142, 13, 131, 11, 90, 19, 153, 218, 255, 16, 132, 4, 142, + 123, 216, 219, 233, 248, 89, 6, 155, 136, 87, 254, 171, 129, 132, 251, 104, + 127, 99, 70, 24, 192, 53, 218, 196, 57, 220, 26, 235, 59, 85, 152, 160, 240, + 0, 0, 0, 0, 1, 6, 167, 213, 23, 25, 44, 92, 81, 33, 140, 201, 76, 61, 74, 241, + 127, 88, 218, 238, 8, 155, 161, 253, 68, 227, 219, 217, 138, 0, 0, 0, 0, 6, + 221, 246, 225, 215, 101, 161, 147, 217, 203, 225, 70, 206, 235, 121, 172, 28, + 180, 133, 237, 95, 91, 55, 145, 58, 140, 245, 133, 126, 255, 0, 169, 238, 204, + 15, 249, 117, 156, 236, 123, 109, 247, 173, 57, 139, 143, 19, 21, 15, 180, + 230, 189, 171, 230, 228, 215, 211, 229, 22, 94, 108, 135, 17, 93, 5, 14, 2, 0, + 1, 52, 0, 0, 0, 0, 48, 96, 46, 0, 0, 0, 0, 0, 165, 0, 0, 0, 0, 0, 0, 0, 6, + 221, 246, 225, 215, 101, 161, 147, 217, 203, 225, 70, 206, 235, 121, 172, 28, + 180, 133, 237, 95, 91, 55, 145, 58, 140, 245, 133, 126, 255, 0, 169, 23, 4, 1, + 21, 0, 22, 1, 1, 20, 7, 0, 9, 0, 15, 14, 23, 22, 0, 17, 18, 23, 10, 16, 7, 6, + 13, 2, 19, 12, 11, 3, 4, 8, 5, 18, 1, 9, 0, 17, 9, 64, 66, 15, 0, 0, 0, 0, 0, + 89, 241, 0, 0, 0, 0, 0, 0, 23, 3, 1, 0, 0, 1, 9, ]) export const dexlabProgram = Buffer.from([ - 3, 0, 7, 11, 47, 41, 101, 134, 4, 204, 35, 219, 9, 43, 55, 20, 255, 76, 29, - 27, 175, 197, 102, 175, 108, 72, 231, 146, 134, 30, 219, 42, 168, 236, 17, 3, - 27, 22, 186, 110, 40, 115, 180, 32, 87, 1, 133, 235, 70, 100, 93, 110, 49, - 176, 47, 100, 89, 135, 44, 204, 229, 104, 63, 16, 169, 85, 62, 17, 87, 228, - 120, 25, 237, 212, 48, 216, 155, 158, 74, 24, 15, 13, 148, 130, 112, 67, 62, - 67, 34, 39, 96, 92, 200, 155, 110, 50, 187, 157, 163, 92, 87, 174, 54, 186, - 81, 129, 162, 212, 79, 70, 190, 180, 232, 143, 72, 102, 119, 242, 172, 80, 2, - 53, 168, 117, 184, 99, 184, 206, 230, 204, 5, 165, 0, 0, 0, 0, 0, 0, 0, 0, 0, - 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 55, 153, - 140, 203, 242, 208, 69, 139, 97, 92, 188, 198, 177, 163, 103, 196, 116, 158, - 159, 239, 115, 6, 98, 46, 27, 27, 88, 145, 1, 32, 188, 154, 140, 151, 37, 143, - 78, 36, 137, 241, 187, 61, 16, 41, 20, 142, 13, 131, 11, 90, 19, 153, 218, - 255, 16, 132, 4, 142, 123, 216, 219, 233, 248, 89, 198, 250, 122, 243, 190, - 219, 173, 58, 61, 101, 243, 106, 171, 201, 116, 49, 177, 187, 228, 194, 210, - 246, 224, 228, 124, 166, 2, 3, 69, 47, 93, 97, 6, 155, 136, 87, 254, 171, 129, - 132, 251, 104, 127, 99, 70, 24, 192, 53, 218, 196, 57, 220, 26, 235, 59, 85, - 152, 160, 240, 0, 0, 0, 0, 1, 6, 167, 213, 23, 25, 44, 92, 81, 33, 140, 201, - 76, 61, 74, 241, 127, 88, 218, 238, 8, 155, 161, 253, 68, 227, 219, 217, 138, - 0, 0, 0, 0, 6, 221, 246, 225, 215, 101, 161, 147, 217, 203, 225, 70, 206, 235, - 121, 172, 28, 180, 133, 237, 95, 91, 55, 145, 58, 140, 245, 133, 126, 255, 0, - 169, 2, 206, 198, 46, 159, 44, 171, 207, 167, 152, 197, 80, 224, 171, 133, - 195, 162, 248, 176, 1, 213, 55, 144, 195, 89, 153, 30, 36, 158, 74, 236, 222, - 5, 4, 2, 0, 1, 52, 0, 0, 0, 0, 48, 96, 46, 0, 0, 0, 0, 0, 165, 0, 0, 0, 0, 0, - 0, 0, 6, 221, 246, 225, 215, 101, 161, 147, 217, 203, 225, 70, 206, 235, 121, - 172, 28, 180, 133, 237, 95, 91, 55, 145, 58, 140, 245, 133, 126, 255, 0, 169, - 10, 4, 1, 8, 0, 9, 1, 1, 6, 7, 0, 3, 0, 5, 4, 10, 9, 0, 4, 2, 0, 2, 52, 0, 0, - 0, 0, 240, 29, 31, 0, 0, 0, 0, 0, 165, 0, 0, 0, 0, 0, 0, 0, 6, 221, 246, 225, - 215, 101, 161, 147, 217, 203, 225, 70, 206, 235, 121, 172, 28, 180, 133, 237, - 95, 91, 55, 145, 58, 140, 245, 133, 126, 255, 0, 169, 10, 4, 2, 7, 0, 9, 1, 1, + 3, 0, 7, 11, 47, 41, 101, 134, 4, 204, 35, 219, 9, 43, 55, 20, 255, 76, 29, + 27, 175, 197, 102, 175, 108, 72, 231, 146, 134, 30, 219, 42, 168, 236, 17, 3, + 27, 22, 186, 110, 40, 115, 180, 32, 87, 1, 133, 235, 70, 100, 93, 110, 49, + 176, 47, 100, 89, 135, 44, 204, 229, 104, 63, 16, 169, 85, 62, 17, 87, 228, + 120, 25, 237, 212, 48, 216, 155, 158, 74, 24, 15, 13, 148, 130, 112, 67, 62, + 67, 34, 39, 96, 92, 200, 155, 110, 50, 187, 157, 163, 92, 87, 174, 54, 186, + 81, 129, 162, 212, 79, 70, 190, 180, 232, 143, 72, 102, 119, 242, 172, 80, 2, + 53, 168, 117, 184, 99, 184, 206, 230, 204, 5, 165, 0, 0, 0, 0, 0, 0, 0, 0, 0, + 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 55, 153, + 140, 203, 242, 208, 69, 139, 97, 92, 188, 198, 177, 163, 103, 196, 116, 158, + 159, 239, 115, 6, 98, 46, 27, 27, 88, 145, 1, 32, 188, 154, 140, 151, 37, 143, + 78, 36, 137, 241, 187, 61, 16, 41, 20, 142, 13, 131, 11, 90, 19, 153, 218, + 255, 16, 132, 4, 142, 123, 216, 219, 233, 248, 89, 198, 250, 122, 243, 190, + 219, 173, 58, 61, 101, 243, 106, 171, 201, 116, 49, 177, 187, 228, 194, 210, + 246, 224, 228, 124, 166, 2, 3, 69, 47, 93, 97, 6, 155, 136, 87, 254, 171, 129, + 132, 251, 104, 127, 99, 70, 24, 192, 53, 218, 196, 57, 220, 26, 235, 59, 85, + 152, 160, 240, 0, 0, 0, 0, 1, 6, 167, 213, 23, 25, 44, 92, 81, 33, 140, 201, + 76, 61, 74, 241, 127, 88, 218, 238, 8, 155, 161, 253, 68, 227, 219, 217, 138, + 0, 0, 0, 0, 6, 221, 246, 225, 215, 101, 161, 147, 217, 203, 225, 70, 206, 235, + 121, 172, 28, 180, 133, 237, 95, 91, 55, 145, 58, 140, 245, 133, 126, 255, 0, + 169, 2, 206, 198, 46, 159, 44, 171, 207, 167, 152, 197, 80, 224, 171, 133, + 195, 162, 248, 176, 1, 213, 55, 144, 195, 89, 153, 30, 36, 158, 74, 236, 222, + 5, 4, 2, 0, 1, 52, 0, 0, 0, 0, 48, 96, 46, 0, 0, 0, 0, 0, 165, 0, 0, 0, 0, 0, + 0, 0, 6, 221, 246, 225, 215, 101, 161, 147, 217, 203, 225, 70, 206, 235, 121, + 172, 28, 180, 133, 237, 95, 91, 55, 145, 58, 140, 245, 133, 126, 255, 0, 169, + 10, 4, 1, 8, 0, 9, 1, 1, 6, 7, 0, 3, 0, 5, 4, 10, 9, 0, 4, 2, 0, 2, 52, 0, 0, + 0, 0, 240, 29, 31, 0, 0, 0, 0, 0, 165, 0, 0, 0, 0, 0, 0, 0, 6, 221, 246, 225, + 215, 101, 161, 147, 217, 203, 225, 70, 206, 235, 121, 172, 28, 180, 133, 237, + 95, 91, 55, 145, 58, 140, 245, 133, 126, 255, 0, 169, 10, 4, 2, 7, 0, 9, 1, 1, ]) diff --git a/packages/sdk/src/__test__/e2e/signing/solana/solana.programs.test.ts b/packages/sdk/src/__test__/e2e/signing/solana/solana.programs.test.ts index a130df91..58944657 100644 --- a/packages/sdk/src/__test__/e2e/signing/solana/solana.programs.test.ts +++ b/packages/sdk/src/__test__/e2e/signing/solana/solana.programs.test.ts @@ -12,37 +12,37 @@ import { setupClient } from '../../../utils/setup' import { dexlabProgram, raydiumProgram } from './__mocks__/programs' describe('Solana Programs', () => { - let client: Client + let client: Client - beforeAll(async () => { - client = await setupClient() - }) + beforeAll(async () => { + client = await setupClient() + }) - it('should sign Dexlab program', async () => { - const payload = dexlabProgram - const signedMessage = await client.sign({ - data: { - signerPath: [0x80000000 + 44, 0x80000000 + 501, 0x80000000], - curveType: Constants.SIGNING.CURVES.ED25519, - hashType: Constants.SIGNING.HASHES.NONE, - encodingType: Constants.SIGNING.ENCODINGS.SOLANA, - payload, - }, - }) - expect(signedMessage).toBeTruthy() - }) + it('should sign Dexlab program', async () => { + const payload = dexlabProgram + const signedMessage = await client.sign({ + data: { + signerPath: [0x80000000 + 44, 0x80000000 + 501, 0x80000000], + curveType: Constants.SIGNING.CURVES.ED25519, + hashType: Constants.SIGNING.HASHES.NONE, + encodingType: Constants.SIGNING.ENCODINGS.SOLANA, + payload, + }, + }) + expect(signedMessage).toBeTruthy() + }) - it('should sign Raydium program', async () => { - const payload = raydiumProgram - const signedMessage = await client.sign({ - data: { - signerPath: [0x80000000 + 44, 0x80000000 + 501, 0x80000000], - curveType: Constants.SIGNING.CURVES.ED25519, - hashType: Constants.SIGNING.HASHES.NONE, - encodingType: Constants.SIGNING.ENCODINGS.SOLANA, - payload, - }, - }) - expect(signedMessage).toBeTruthy() - }) + it('should sign Raydium program', async () => { + const payload = raydiumProgram + const signedMessage = await client.sign({ + data: { + signerPath: [0x80000000 + 44, 0x80000000 + 501, 0x80000000], + curveType: Constants.SIGNING.CURVES.ED25519, + hashType: Constants.SIGNING.HASHES.NONE, + encodingType: Constants.SIGNING.ENCODINGS.SOLANA, + payload, + }, + }) + expect(signedMessage).toBeTruthy() + }) }) diff --git a/packages/sdk/src/__test__/e2e/signing/solana/solana.test.ts b/packages/sdk/src/__test__/e2e/signing/solana/solana.test.ts index cc663eb8..99bc116f 100644 --- a/packages/sdk/src/__test__/e2e/signing/solana/solana.test.ts +++ b/packages/sdk/src/__test__/e2e/signing/solana/solana.test.ts @@ -7,10 +7,10 @@ * incorrect key derivations and signature mismatches. */ import { - Keypair as SolanaKeypair, - PublicKey as SolanaPublicKey, - SystemProgram as SolanaSystemProgram, - Transaction as SolanaTransaction, + Keypair as SolanaKeypair, + PublicKey as SolanaPublicKey, + SystemProgram as SolanaSystemProgram, + Transaction as SolanaTransaction, } from '@solana/web3.js' import { Constants } from '../../../..' import { HARDENED_OFFSET } from '../../../../constants' @@ -26,126 +26,126 @@ import type { Client } from '../../../../client' // STATE DATA //--------------------------------------- const DEFAULT_SOLANA_SIGNER_PATH = [ - HARDENED_OFFSET + 44, - HARDENED_OFFSET + 501, - HARDENED_OFFSET, - HARDENED_OFFSET, + HARDENED_OFFSET + 44, + HARDENED_OFFSET + 501, + HARDENED_OFFSET, + HARDENED_OFFSET, ] const prng = getPrng() describe('[Solana]', () => { - let client: Client + let client: Client - beforeAll(async () => { - client = await setupClient() - }) + beforeAll(async () => { + client = await setupClient() + }) - const getReq = (overrides: any) => ({ - data: { - curveType: Constants.SIGNING.CURVES.ED25519, - hashType: Constants.SIGNING.HASHES.NONE, - encodingType: Constants.SIGNING.ENCODINGS.SOLANA, - payload: null, - ...overrides, - }, - }) + const getReq = (overrides: any) => ({ + data: { + curveType: Constants.SIGNING.CURVES.ED25519, + hashType: Constants.SIGNING.HASHES.NONE, + encodingType: Constants.SIGNING.ENCODINGS.SOLANA, + payload: null, + ...overrides, + }, + }) - it('Should validate Solana transaction encoding', async () => { - // Build a Solana transaction with two signers, each derived from the Lattice's seed. - // This will require two separate general signing requests, one per signer. + it('Should validate Solana transaction encoding', async () => { + // Build a Solana transaction with two signers, each derived from the Lattice's seed. + // This will require two separate general signing requests, one per signer. - // Get the full set of Solana addresses and keys - // NOTE: Solana addresses are just base58 encoded public keys. We do not - // currently support exporting of Solana addresses in firmware but we can - // derive them here using the exported seed. - const seed = TEST_SEED - const derivedAPath = [...DEFAULT_SOLANA_SIGNER_PATH] - const derivedBPath = [...DEFAULT_SOLANA_SIGNER_PATH] - derivedBPath[3] += 1 - const derivedCPath = [...DEFAULT_SOLANA_SIGNER_PATH] - derivedCPath[3] += 2 - const derivedA = deriveED25519Key(derivedAPath, seed) - const derivedB = deriveED25519Key(derivedBPath, seed) - const derivedC = deriveED25519Key(derivedCPath, seed) - const pubA = new SolanaPublicKey(derivedA.pub) - const pubB = new SolanaPublicKey(derivedB.pub) - const pubC = new SolanaPublicKey(derivedC.pub) + // Get the full set of Solana addresses and keys + // NOTE: Solana addresses are just base58 encoded public keys. We do not + // currently support exporting of Solana addresses in firmware but we can + // derive them here using the exported seed. + const seed = TEST_SEED + const derivedAPath = [...DEFAULT_SOLANA_SIGNER_PATH] + const derivedBPath = [...DEFAULT_SOLANA_SIGNER_PATH] + derivedBPath[3] += 1 + const derivedCPath = [...DEFAULT_SOLANA_SIGNER_PATH] + derivedCPath[3] += 2 + const derivedA = deriveED25519Key(derivedAPath, seed) + const derivedB = deriveED25519Key(derivedBPath, seed) + const derivedC = deriveED25519Key(derivedCPath, seed) + const pubA = new SolanaPublicKey(derivedA.pub) + const pubB = new SolanaPublicKey(derivedB.pub) + const pubC = new SolanaPublicKey(derivedC.pub) - // Define transaction instructions - const transfer1 = SolanaSystemProgram.transfer({ - fromPubkey: pubA, - toPubkey: pubC, - lamports: 111, - }) - const transfer2 = SolanaSystemProgram.transfer({ - fromPubkey: pubB, - toPubkey: pubC, - lamports: 222, - }) + // Define transaction instructions + const transfer1 = SolanaSystemProgram.transfer({ + fromPubkey: pubA, + toPubkey: pubC, + lamports: 111, + }) + const transfer2 = SolanaSystemProgram.transfer({ + fromPubkey: pubB, + toPubkey: pubC, + lamports: 222, + }) - // Generate a pseudorandom blockhash, which is just a public key appearently. - const randBuf = prandomBuf(prng, 32, true) - const recentBlockhash = SolanaKeypair.fromSeed(randBuf).publicKey.toBase58() + // Generate a pseudorandom blockhash, which is just a public key appearently. + const randBuf = prandomBuf(prng, 32, true) + const recentBlockhash = SolanaKeypair.fromSeed(randBuf).publicKey.toBase58() - // Build a transaction and sign it using Solana's JS lib - const txJs = new SolanaTransaction({ recentBlockhash }).add( - transfer1, - transfer2, - ) - txJs.setSigners(pubA, pubB) - txJs.sign( - SolanaKeypair.fromSeed(derivedA.priv), - SolanaKeypair.fromSeed(derivedB.priv), - ) - const serTxJs = txJs.serialize().toString('hex') + // Build a transaction and sign it using Solana's JS lib + const txJs = new SolanaTransaction({ recentBlockhash }).add( + transfer1, + transfer2, + ) + txJs.setSigners(pubA, pubB) + txJs.sign( + SolanaKeypair.fromSeed(derivedA.priv), + SolanaKeypair.fromSeed(derivedB.priv), + ) + const serTxJs = txJs.serialize().toString('hex') - // Build a copy of the transaction and get the serialized payload for signing in firmware. - const txFw = new SolanaTransaction({ recentBlockhash }).add( - transfer1, - transfer2, - ) - txFw.setSigners(pubA, pubB) - // We want to sign the Solana message, not the full transaction - const payload = txFw.compileMessage().serialize() - const payloadHex = `0x${payload.toString('hex')}` + // Build a copy of the transaction and get the serialized payload for signing in firmware. + const txFw = new SolanaTransaction({ recentBlockhash }).add( + transfer1, + transfer2, + ) + txFw.setSigners(pubA, pubB) + // We want to sign the Solana message, not the full transaction + const payload = txFw.compileMessage().serialize() + const payloadHex = `0x${payload.toString('hex')}` - // Sign payload from Lattice and add signatures to tx object - const sigA = await runGeneric( - getReq({ - signerPath: derivedAPath, - payload: payloadHex, - }), - client, - ).then((resp) => { - if (!resp.sig?.r || !resp.sig?.s) { - throw new Error('Missing signature components in response') - } - return Buffer.concat([ - ensureHexBuffer(resp.sig.r as string | Buffer), - ensureHexBuffer(resp.sig.s as string | Buffer), - ]) - }) + // Sign payload from Lattice and add signatures to tx object + const sigA = await runGeneric( + getReq({ + signerPath: derivedAPath, + payload: payloadHex, + }), + client, + ).then((resp) => { + if (!resp.sig?.r || !resp.sig?.s) { + throw new Error('Missing signature components in response') + } + return Buffer.concat([ + ensureHexBuffer(resp.sig.r as string | Buffer), + ensureHexBuffer(resp.sig.s as string | Buffer), + ]) + }) - const sigB = await runGeneric( - getReq({ - signerPath: derivedBPath, - payload: payloadHex, - }), - client, - ).then((resp) => { - if (!resp.sig?.r || !resp.sig?.s) { - throw new Error('Missing signature components in response') - } - return Buffer.concat([ - ensureHexBuffer(resp.sig.r as string | Buffer), - ensureHexBuffer(resp.sig.s as string | Buffer), - ]) - }) - txFw.addSignature(pubA, sigA) - txFw.addSignature(pubB, sigB) + const sigB = await runGeneric( + getReq({ + signerPath: derivedBPath, + payload: payloadHex, + }), + client, + ).then((resp) => { + if (!resp.sig?.r || !resp.sig?.s) { + throw new Error('Missing signature components in response') + } + return Buffer.concat([ + ensureHexBuffer(resp.sig.r as string | Buffer), + ensureHexBuffer(resp.sig.s as string | Buffer), + ]) + }) + txFw.addSignature(pubA, sigA) + txFw.addSignature(pubB, sigB) - // Validate the signatures from the Lattice match those of the Solana library - const serTxFw = txFw.serialize().toString('hex') - expect(serTxFw).toEqual(serTxJs) - }) + // Validate the signatures from the Lattice match those of the Solana library + const serTxFw = txFw.serialize().toString('hex') + expect(serTxFw).toEqual(serTxJs) + }) }) diff --git a/packages/sdk/src/__test__/e2e/signing/solana/solana.versioned.test.ts b/packages/sdk/src/__test__/e2e/signing/solana/solana.versioned.test.ts index d355f0c4..65ee878f 100644 --- a/packages/sdk/src/__test__/e2e/signing/solana/solana.versioned.test.ts +++ b/packages/sdk/src/__test__/e2e/signing/solana/solana.versioned.test.ts @@ -7,16 +7,16 @@ * incorrect key derivations and signature mismatches. */ import { - AddressLookupTableProgram, - Connection, - Keypair, - LAMPORTS_PER_SOL, - PublicKey, - SystemProgram, - Transaction, - type TransactionInstruction, - TransactionMessage, - VersionedTransaction, + AddressLookupTableProgram, + Connection, + Keypair, + LAMPORTS_PER_SOL, + PublicKey, + SystemProgram, + Transaction, + type TransactionInstruction, + TransactionMessage, + VersionedTransaction, } from '@solana/web3.js' import { fetchSolanaAddresses, signSolanaTx } from '../../../..' import { setupClient } from '../../../utils/setup' @@ -24,179 +24,179 @@ import { setupClient } from '../../../utils/setup' const SOLANA_RPC = new Connection('https://api.devnet.solana.com', 'confirmed') const fetchSigningWallet = async () => { - const addresses = await fetchSolanaAddresses({ n: 1 }) - const solanaAddr = addresses[0] - const pubkey = new PublicKey(solanaAddr) - expect(PublicKey.isOnCurve(pubkey)).toBeTruthy() - return pubkey + const addresses = await fetchSolanaAddresses({ n: 1 }) + const solanaAddr = addresses[0] + const pubkey = new PublicKey(solanaAddr) + expect(PublicKey.isOnCurve(pubkey)).toBeTruthy() + return pubkey } const requestAirdrop = async (wallet: PublicKey, lamports: number) => { - try { - await SOLANA_RPC.requestAirdrop(wallet, lamports * LAMPORTS_PER_SOL) - await new Promise((resolve) => setTimeout(resolve, 20000)) - } catch (error) { - /** - * The faucet is flakey, so you might need to request funds manually from the faucet at https://faucet.solana.com/ - * Also, for Solana to work, your address must have interacted with the network previously. - */ - console.log(error) - } + try { + await SOLANA_RPC.requestAirdrop(wallet, lamports * LAMPORTS_PER_SOL) + await new Promise((resolve) => setTimeout(resolve, 20000)) + } catch (error) { + /** + * The faucet is flakey, so you might need to request funds manually from the faucet at https://faucet.solana.com/ + * Also, for Solana to work, your address must have interacted with the network previously. + */ + console.log(error) + } } describe('solana.versioned', () => { - let SIGNER_WALLET: PublicKey - let DESTINATION_WALLET_1: Keypair - let DESTINATION_WALLET_2: Keypair - let latestBlockhash: { blockhash: string; lastValidBlockHeight: number } - - beforeAll(async () => { - await setupClient() - - SIGNER_WALLET = await fetchSigningWallet() - DESTINATION_WALLET_1 = Keypair.generate() - DESTINATION_WALLET_2 = Keypair.generate() - latestBlockhash = await SOLANA_RPC.getLatestBlockhash('confirmed') - }) - - test('sign solana', async () => { - SIGNER_WALLET = await fetchSigningWallet() - const txInstructions: TransactionInstruction[] = [ - SystemProgram.transfer({ - fromPubkey: SIGNER_WALLET, - toPubkey: DESTINATION_WALLET_1.publicKey, - lamports: 0.01 * LAMPORTS_PER_SOL, - }), - ] - const messageV0 = new TransactionMessage({ - payerKey: SIGNER_WALLET, - recentBlockhash: latestBlockhash.blockhash, - instructions: txInstructions, - }).compileToV0Message() - - const signedTx = await signSolanaTx(Buffer.from(messageV0.serialize())) - expect(signedTx).toBeTruthy() - }) - - test('sign solana multiple instructions', async () => { - const txInstructions = [ - SystemProgram.transfer({ - fromPubkey: SIGNER_WALLET, - toPubkey: DESTINATION_WALLET_1.publicKey, - lamports: 0.005 * LAMPORTS_PER_SOL, - }), - SystemProgram.transfer({ - fromPubkey: SIGNER_WALLET, - toPubkey: DESTINATION_WALLET_2.publicKey, - lamports: 0.005 * LAMPORTS_PER_SOL, - }), - ] - const message = new TransactionMessage({ - payerKey: SIGNER_WALLET, - recentBlockhash: latestBlockhash.blockhash, - instructions: txInstructions, - }).compileToV0Message() - - const signedTx = await signSolanaTx(Buffer.from(message.serialize())) - expect(signedTx).toBeTruthy() - }) - - test('sign solana zero lamport transfer', async () => { - const txInstruction = SystemProgram.transfer({ - fromPubkey: SIGNER_WALLET, - toPubkey: DESTINATION_WALLET_1.publicKey, - lamports: 0, - }) - const message = new TransactionMessage({ - payerKey: SIGNER_WALLET, - recentBlockhash: latestBlockhash.blockhash, - instructions: [txInstruction], - }).compileToV0Message() - - const signedTx = await signSolanaTx(Buffer.from(message.serialize())) - expect(signedTx).toBeTruthy() - }) - - // Skipping this test because VersionedTransaction are getting rejected by the device (LatticeResponseCode.userDeclined) - test.skip('simulate versioned solana transaction', async () => { - const txInstruction = SystemProgram.transfer({ - fromPubkey: SIGNER_WALLET, - toPubkey: DESTINATION_WALLET_1.publicKey, - lamports: 0.001 * LAMPORTS_PER_SOL, - }) - - const transaction = new Transaction() - transaction.add(txInstruction) - transaction.recentBlockhash = latestBlockhash.blockhash - transaction.feePayer = SIGNER_WALLET - - // Serialize the transaction to get the wire format - const serializedTransaction = transaction.serialize({ - requireAllSignatures: false, - }) - - // Create a VersionedTransaction from the serialized data - const versionedTransaction = VersionedTransaction.deserialize( - serializedTransaction, - ) - - const signedTx = await signSolanaTx( - Buffer.from(versionedTransaction.serialize()), - ) - expect(signedTx).toBeTruthy() - }) - - test('simulate versioned solana transaction with multiple instructions', async () => { - const payer = Keypair.generate() - await requestAirdrop(payer.publicKey, 1) - - const [transactionInstruction, pubkey] = - await AddressLookupTableProgram.createLookupTable({ - payer: payer.publicKey, - authority: payer.publicKey, - recentSlot: await SOLANA_RPC.getSlot(), - }) - - await AddressLookupTableProgram.extendLookupTable({ - payer: payer.publicKey, - authority: payer.publicKey, - lookupTable: pubkey, - addresses: [ - DESTINATION_WALLET_1.publicKey, - DESTINATION_WALLET_2.publicKey, - ], - }) - - const messageV0 = new TransactionMessage({ - payerKey: SIGNER_WALLET, - recentBlockhash: latestBlockhash.blockhash, - instructions: [transactionInstruction], - }).compileToV0Message() - - const signedTx = await signSolanaTx(Buffer.from(messageV0.serialize())) - - expect(signedTx).toBeDefined() - }) - - // Skipping this test because the messages are getting rejected by the device (LatticeResponseCode.userDeclined) - test.skip('simulate versioned solana transactions from nufi', async () => { - // sign transaction - const signedTx = await signSolanaTx( - Buffer.from( - '800100131b7154e1c10d16949038dd040363bca1f9d6501c694f6e0325ad817166fb2e91ee0c503f9578fb1f7621e5a0c2c384547f22cd4116be06e523219f332b61d41644801401dbe3c1c1f14a4eca5605ce5071e3301e79a79d95ecc50c73b977286be44ffdb307c4cce0aa81999d925e13f482e6deb72c8cc1d31ebf6b95478bd11f4f0cd8b42c34cd1d82beaec835fd525d1e90c248cf6849b277e78015a46e48789107a241e063d943cba135e306d4f8059f2ecf7a69744b993531a9d767674c85c7f6d2eabe47377602308731d0ac6ee6483c3a0f34b6aff13d37e53e00719032e928a52bdc5c44449874879e44c88ed5eb62b77a2b5e672ab9f50010479abc43e10306466fe5211732ffecadba72c39be7bc8ce5bbc5f7126b2c439b3a4000000006b5efb175d780346c18fa61f588190e5981c4668d651d67ad73c36e31180fdf9aca3abb5a18a0fe31a5031e7765dc6264a19fa84b3925e256dc2b4e53974b3c06b43c95041249ed7dedfc195aec0bc3ff2760a53409c79a7607b29fa3a12ad61393a26044ecc1adf84705420dd79ceeeca7be0dc63cd55a3994d9aba59b0e4303da8d1d21a468a26ba2183fd61de731ca4d5d61fd81296f97d916d81fd074ba18a633ac3c601aff49b0ffb943b9cdc1d89793f880bd8765868fd1ab42aa3b670286c0be1afb0442e17ee761789fe71c6e6914dd5771f7993092c9ca8bb05f3ae77546322de214d7944e9846236fe15e4f47731268ee9d6f60dcd058c96ea60a930890e9e6be0f4b35f0bf2f4c373f074ecc0404d221729981f11bb0dd5586fa55ac0b630d53e1fbf40c31f6163a1fed819daad6c34ec6eebbd2c9094ab8f0868ad7baf66ee465a9f41bc1fb7e3f72b663d4bfd34fe585e91fbd1f2753437cbad2569d176621a5d281ef6683261d8cc0da378f1139f1c01996edda54eb109ac4348ea0872c378762af7b1423c9c5b5ad1dd48584fcdbdb10e4922e01573228b8cd362cd18826752b72a9bf8c3d1fd7e3faeec4d44bd0f8988a902511c22b6ab46ba8557b567c6cbcd3e23efe17a9df8269dddf11d65a8f703314b84688b0bb9954004e7014187f35fb8aa37fc7e7cfe9c21e1e3def76baa18ab0668b61cf8bb66c55c082dfa8853bec08eeb7402096300a48b0b7259205ca703a1eaa032a21f6dcf2b4502abaead6d2b5495f0ea97222e2cd76aefb60c8d59d435e7a51cab236333b989b9593098437cfe28786fd22445b730c237a17d1110c8fef327d5f058e0308000502882502000800090333000000000000000922010a021b1c1d1e09030000040909040b0506070c0d0e0f101112131415161718191a7166063d1201daebea164609000000000016460900b9b0b7828d68598fa5ddf8fd4e8ba1315b7afae1785fb6ce5632d8699dd56fb15e4372ff949cf950d5dd678d5622184bc37b32d3a396f9d41ab609e65b1496b3040000000017262704000000010000008a02585c0a0000000000016400017b6eea3282722d20e3d0b0ce1eef6cf588aa519c82103084255fc33deb476d72000416170015', - 'hex', - ), - ) - - expect(signedTx).toBeDefined() - - // signMessage - const signedMessage = await signSolanaTx( - Buffer.from( - '6d616769636564656e2e696f2077616e747320796f7520746f207369676e20696e207769746820796f757220536f6c616e61206163636f756e743a0a386451393746414e6368337a75346348426942793556365a716478555365547858465873624b62436e6451710a0a57656c636f6d6520746f204d61676963204564656e2e205369676e696e6720697320746865206f6e6c79207761792077652063616e207472756c79206b6e6f77207468617420796f752061726520746865206f776e6572206f66207468652077616c6c657420796f752061726520636f6e6e656374696e672e205369676e696e67206973206120736166652c206761732d6c657373207472616e73616374696f6e207468617420646f6573206e6f7420696e20616e79207761792067697665204d61676963204564656e207065726d697373696f6e20746f20706572666f726d20616e79207472616e73616374696f6e73207769746820796f75722077616c6c65742e0a0a5552493a2068747470733a2f2f6d616769636564656e2e696f2f706f70756c61722d636f6c6c656374696f6e730a56657273696f6e3a20310a436861696e2049443a206d61696e6e65740a4e6f6e63653a2038373066313166646234373734353962393433353730316463366532353035350a4973737565642041743a20323032342d30372d30325431353a32383a34382e3736305a0a526571756573742049443a2063313331346235622d656365382d346234662d613837392d333839346464613336346534', - 'hex', - ), - ) - expect(signedMessage).toBeDefined() - }) + let SIGNER_WALLET: PublicKey + let DESTINATION_WALLET_1: Keypair + let DESTINATION_WALLET_2: Keypair + let latestBlockhash: { blockhash: string; lastValidBlockHeight: number } + + beforeAll(async () => { + await setupClient() + + SIGNER_WALLET = await fetchSigningWallet() + DESTINATION_WALLET_1 = Keypair.generate() + DESTINATION_WALLET_2 = Keypair.generate() + latestBlockhash = await SOLANA_RPC.getLatestBlockhash('confirmed') + }) + + test('sign solana', async () => { + SIGNER_WALLET = await fetchSigningWallet() + const txInstructions: TransactionInstruction[] = [ + SystemProgram.transfer({ + fromPubkey: SIGNER_WALLET, + toPubkey: DESTINATION_WALLET_1.publicKey, + lamports: 0.01 * LAMPORTS_PER_SOL, + }), + ] + const messageV0 = new TransactionMessage({ + payerKey: SIGNER_WALLET, + recentBlockhash: latestBlockhash.blockhash, + instructions: txInstructions, + }).compileToV0Message() + + const signedTx = await signSolanaTx(Buffer.from(messageV0.serialize())) + expect(signedTx).toBeTruthy() + }) + + test('sign solana multiple instructions', async () => { + const txInstructions = [ + SystemProgram.transfer({ + fromPubkey: SIGNER_WALLET, + toPubkey: DESTINATION_WALLET_1.publicKey, + lamports: 0.005 * LAMPORTS_PER_SOL, + }), + SystemProgram.transfer({ + fromPubkey: SIGNER_WALLET, + toPubkey: DESTINATION_WALLET_2.publicKey, + lamports: 0.005 * LAMPORTS_PER_SOL, + }), + ] + const message = new TransactionMessage({ + payerKey: SIGNER_WALLET, + recentBlockhash: latestBlockhash.blockhash, + instructions: txInstructions, + }).compileToV0Message() + + const signedTx = await signSolanaTx(Buffer.from(message.serialize())) + expect(signedTx).toBeTruthy() + }) + + test('sign solana zero lamport transfer', async () => { + const txInstruction = SystemProgram.transfer({ + fromPubkey: SIGNER_WALLET, + toPubkey: DESTINATION_WALLET_1.publicKey, + lamports: 0, + }) + const message = new TransactionMessage({ + payerKey: SIGNER_WALLET, + recentBlockhash: latestBlockhash.blockhash, + instructions: [txInstruction], + }).compileToV0Message() + + const signedTx = await signSolanaTx(Buffer.from(message.serialize())) + expect(signedTx).toBeTruthy() + }) + + // Skipping this test because VersionedTransaction are getting rejected by the device (LatticeResponseCode.userDeclined) + test.skip('simulate versioned solana transaction', async () => { + const txInstruction = SystemProgram.transfer({ + fromPubkey: SIGNER_WALLET, + toPubkey: DESTINATION_WALLET_1.publicKey, + lamports: 0.001 * LAMPORTS_PER_SOL, + }) + + const transaction = new Transaction() + transaction.add(txInstruction) + transaction.recentBlockhash = latestBlockhash.blockhash + transaction.feePayer = SIGNER_WALLET + + // Serialize the transaction to get the wire format + const serializedTransaction = transaction.serialize({ + requireAllSignatures: false, + }) + + // Create a VersionedTransaction from the serialized data + const versionedTransaction = VersionedTransaction.deserialize( + serializedTransaction, + ) + + const signedTx = await signSolanaTx( + Buffer.from(versionedTransaction.serialize()), + ) + expect(signedTx).toBeTruthy() + }) + + test('simulate versioned solana transaction with multiple instructions', async () => { + const payer = Keypair.generate() + await requestAirdrop(payer.publicKey, 1) + + const [transactionInstruction, pubkey] = + await AddressLookupTableProgram.createLookupTable({ + payer: payer.publicKey, + authority: payer.publicKey, + recentSlot: await SOLANA_RPC.getSlot(), + }) + + await AddressLookupTableProgram.extendLookupTable({ + payer: payer.publicKey, + authority: payer.publicKey, + lookupTable: pubkey, + addresses: [ + DESTINATION_WALLET_1.publicKey, + DESTINATION_WALLET_2.publicKey, + ], + }) + + const messageV0 = new TransactionMessage({ + payerKey: SIGNER_WALLET, + recentBlockhash: latestBlockhash.blockhash, + instructions: [transactionInstruction], + }).compileToV0Message() + + const signedTx = await signSolanaTx(Buffer.from(messageV0.serialize())) + + expect(signedTx).toBeDefined() + }) + + // Skipping this test because the messages are getting rejected by the device (LatticeResponseCode.userDeclined) + test.skip('simulate versioned solana transactions from nufi', async () => { + // sign transaction + const signedTx = await signSolanaTx( + Buffer.from( + '800100131b7154e1c10d16949038dd040363bca1f9d6501c694f6e0325ad817166fb2e91ee0c503f9578fb1f7621e5a0c2c384547f22cd4116be06e523219f332b61d41644801401dbe3c1c1f14a4eca5605ce5071e3301e79a79d95ecc50c73b977286be44ffdb307c4cce0aa81999d925e13f482e6deb72c8cc1d31ebf6b95478bd11f4f0cd8b42c34cd1d82beaec835fd525d1e90c248cf6849b277e78015a46e48789107a241e063d943cba135e306d4f8059f2ecf7a69744b993531a9d767674c85c7f6d2eabe47377602308731d0ac6ee6483c3a0f34b6aff13d37e53e00719032e928a52bdc5c44449874879e44c88ed5eb62b77a2b5e672ab9f50010479abc43e10306466fe5211732ffecadba72c39be7bc8ce5bbc5f7126b2c439b3a4000000006b5efb175d780346c18fa61f588190e5981c4668d651d67ad73c36e31180fdf9aca3abb5a18a0fe31a5031e7765dc6264a19fa84b3925e256dc2b4e53974b3c06b43c95041249ed7dedfc195aec0bc3ff2760a53409c79a7607b29fa3a12ad61393a26044ecc1adf84705420dd79ceeeca7be0dc63cd55a3994d9aba59b0e4303da8d1d21a468a26ba2183fd61de731ca4d5d61fd81296f97d916d81fd074ba18a633ac3c601aff49b0ffb943b9cdc1d89793f880bd8765868fd1ab42aa3b670286c0be1afb0442e17ee761789fe71c6e6914dd5771f7993092c9ca8bb05f3ae77546322de214d7944e9846236fe15e4f47731268ee9d6f60dcd058c96ea60a930890e9e6be0f4b35f0bf2f4c373f074ecc0404d221729981f11bb0dd5586fa55ac0b630d53e1fbf40c31f6163a1fed819daad6c34ec6eebbd2c9094ab8f0868ad7baf66ee465a9f41bc1fb7e3f72b663d4bfd34fe585e91fbd1f2753437cbad2569d176621a5d281ef6683261d8cc0da378f1139f1c01996edda54eb109ac4348ea0872c378762af7b1423c9c5b5ad1dd48584fcdbdb10e4922e01573228b8cd362cd18826752b72a9bf8c3d1fd7e3faeec4d44bd0f8988a902511c22b6ab46ba8557b567c6cbcd3e23efe17a9df8269dddf11d65a8f703314b84688b0bb9954004e7014187f35fb8aa37fc7e7cfe9c21e1e3def76baa18ab0668b61cf8bb66c55c082dfa8853bec08eeb7402096300a48b0b7259205ca703a1eaa032a21f6dcf2b4502abaead6d2b5495f0ea97222e2cd76aefb60c8d59d435e7a51cab236333b989b9593098437cfe28786fd22445b730c237a17d1110c8fef327d5f058e0308000502882502000800090333000000000000000922010a021b1c1d1e09030000040909040b0506070c0d0e0f101112131415161718191a7166063d1201daebea164609000000000016460900b9b0b7828d68598fa5ddf8fd4e8ba1315b7afae1785fb6ce5632d8699dd56fb15e4372ff949cf950d5dd678d5622184bc37b32d3a396f9d41ab609e65b1496b3040000000017262704000000010000008a02585c0a0000000000016400017b6eea3282722d20e3d0b0ce1eef6cf588aa519c82103084255fc33deb476d72000416170015', + 'hex', + ), + ) + + expect(signedTx).toBeDefined() + + // signMessage + const signedMessage = await signSolanaTx( + Buffer.from( + '6d616769636564656e2e696f2077616e747320796f7520746f207369676e20696e207769746820796f757220536f6c616e61206163636f756e743a0a386451393746414e6368337a75346348426942793556365a716478555365547858465873624b62436e6451710a0a57656c636f6d6520746f204d61676963204564656e2e205369676e696e6720697320746865206f6e6c79207761792077652063616e207472756c79206b6e6f77207468617420796f752061726520746865206f776e6572206f66207468652077616c6c657420796f752061726520636f6e6e656374696e672e205369676e696e67206973206120736166652c206761732d6c657373207472616e73616374696f6e207468617420646f6573206e6f7420696e20616e79207761792067697665204d61676963204564656e207065726d697373696f6e20746f20706572666f726d20616e79207472616e73616374696f6e73207769746820796f75722077616c6c65742e0a0a5552493a2068747470733a2f2f6d616769636564656e2e696f2f706f70756c61722d636f6c6c656374696f6e730a56657273696f6e3a20310a436861696e2049443a206d61696e6e65740a4e6f6e63653a2038373066313166646234373734353962393433353730316463366532353035350a4973737565642041743a20323032342d30372d30325431353a32383a34382e3736305a0a526571756573742049443a2063313331346235622d656365382d346234662d613837392d333839346464613336346534', + 'hex', + ), + ) + expect(signedMessage).toBeDefined() + }) }) diff --git a/packages/sdk/src/__test__/e2e/signing/unformatted.test.ts b/packages/sdk/src/__test__/e2e/signing/unformatted.test.ts index a5f0d0e2..4894ccad 100644 --- a/packages/sdk/src/__test__/e2e/signing/unformatted.test.ts +++ b/packages/sdk/src/__test__/e2e/signing/unformatted.test.ts @@ -10,163 +10,163 @@ import type { Client } from '../../../client' const prng = getPrng() const numIter = getNumIter() const DEFAULT_SIGNER = [ - HARDENED_OFFSET + 44, - HARDENED_OFFSET + 60, - HARDENED_OFFSET, - 0, - 0, + HARDENED_OFFSET + 44, + HARDENED_OFFSET + 60, + HARDENED_OFFSET, + 0, + 0, ] describe('[Unformatted]', () => { - let client: Client - - beforeAll(async () => { - client = await setupClient() - }) - - const getReq = (overrides: any) => ({ - data: { - signerPath: DEFAULT_SIGNER, - curveType: Constants.SIGNING.CURVES.SECP256K1, - hashType: Constants.SIGNING.HASHES.KECCAK256, - payload: null, - ...overrides, - }, - }) - - it('Should test pre-hashed messages', async () => { - const fwConstants = client.getFwConstants() - const { extraDataFrameSz, extraDataMaxFrames, genericSigning } = fwConstants - const { baseDataSz } = genericSigning - // Max size that won't be prehashed - const maxSz = baseDataSz + extraDataMaxFrames * extraDataFrameSz - - // Use extraData frames - await runGeneric( - getReq({ - payload: `0x${prandomBuf(prng, maxSz, true).toString('hex')}`, - }), - client, - ) - - // Prehash (keccak256) - await runGeneric( - getReq({ - payload: `0x${prandomBuf(prng, maxSz + 1, true).toString('hex')}`, - }), - client, - ) - - // Prehash (sha256) - await runGeneric( - getReq({ - payload: `0x${prandomBuf(prng, maxSz + 1, true).toString('hex')}`, - hashType: Constants.SIGNING.HASHES.SHA256, - }), - client, - ) - }) - - it('Should test ASCII text formatting', async () => { - // Build a payload that uses spaces and newlines - await runGeneric( - getReq({ - payload: JSON.stringify( - { - testPayload: 'json with spaces', - anotherThing: -1, - }, - null, - 2, - ), - }), - client, - ) - }) - - it('Should validate SECP256K1/KECCAK signature against derived key', async () => { - // ASCII message encoding - await runGeneric(getReq({ payload: 'test' }), client) - - // Hex message encoding - const req = getReq({ payload: '0x123456' }) - await runGeneric(req, client) - }) - - it('Should validate ED25519/NULL signature against derived key', async () => { - const req = getReq({ - payload: '0x123456', - curveType: Constants.SIGNING.CURVES.ED25519, - /* Not doing anything. It is commented out. */ - hashType: Constants.SIGNING.HASHES.NONE, - // ED25519 derivation requires hardened indices - signerPath: DEFAULT_SIGNER.slice(0, 3), - }) - // Make generic signing request - await runGeneric(req, client) - }) - - it('Should validate SECP256K1/KECCAK signature against ETH_MSG request (legacy)', async () => { - // Generic request - const msg = 'Testing personal_sign' - const psMsg = ethPersonalSignMsg(msg) - // NOTE: The request contains some non ASCII characters so it will get - // encoded as hex automatically. - const req = getReq({ - payload: psMsg, - }) - - const respGeneric = await runGeneric(req, client) - - // Legacy request - const legacyReq = { - currency: 'ETH_MSG' as const, - data: { - signerPath: req.data.signerPath, - payload: msg, - protocol: 'signPersonal' as const, - curveType: Constants.SIGNING.CURVES.SECP256K1, - hashType: Constants.SIGNING.HASHES.KECCAK256, - }, - } - const respLegacy = await client.sign( - legacyReq as Parameters[0], - ) - - const genSigR = (respGeneric.sig?.r as Buffer)?.toString('hex') ?? '' - const genSigS = (respGeneric.sig?.s as Buffer)?.toString('hex') ?? '' - const legSigR = (respLegacy.sig?.r as Buffer)?.toString('hex') ?? '' - const legSigS = (respLegacy.sig?.s as Buffer)?.toString('hex') ?? '' - - const genSig = `${genSigR}${genSigS}` - const legSig = `${legSigR}${legSigS}` - expect(genSig).toEqualElseLog( - legSig, - 'Legacy and generic requests produced different sigs.', - ) - }) - - for (let i = 0; i < numIter; i++) { - it(`Should test random payloads - #${i}`, async () => { - const fwConstants = client.getFwConstants() - const req = getReq({ - hashType: Constants.SIGNING.HASHES.KECCAK256, - curveType: Constants.SIGNING.CURVES.SECP256K1, - payload: prandomBuf(prng, fwConstants.genericSigning.baseDataSz), - }) - - // 1. Secp256k1/keccak256 - await runGeneric(req, client) - - // 2. Secp256k1/sha256 - req.data.hashType = Constants.SIGNING.HASHES.SHA256 - await runGeneric(req, client) - - // 3. Ed25519 - req.data.curveType = Constants.SIGNING.CURVES.ED25519 - req.data.hashType = Constants.SIGNING.HASHES.NONE - req.data.signerPath = DEFAULT_SIGNER.slice(0, 3) - await runGeneric(req, client) - }) - } + let client: Client + + beforeAll(async () => { + client = await setupClient() + }) + + const getReq = (overrides: any) => ({ + data: { + signerPath: DEFAULT_SIGNER, + curveType: Constants.SIGNING.CURVES.SECP256K1, + hashType: Constants.SIGNING.HASHES.KECCAK256, + payload: null, + ...overrides, + }, + }) + + it('Should test pre-hashed messages', async () => { + const fwConstants = client.getFwConstants() + const { extraDataFrameSz, extraDataMaxFrames, genericSigning } = fwConstants + const { baseDataSz } = genericSigning + // Max size that won't be prehashed + const maxSz = baseDataSz + extraDataMaxFrames * extraDataFrameSz + + // Use extraData frames + await runGeneric( + getReq({ + payload: `0x${prandomBuf(prng, maxSz, true).toString('hex')}`, + }), + client, + ) + + // Prehash (keccak256) + await runGeneric( + getReq({ + payload: `0x${prandomBuf(prng, maxSz + 1, true).toString('hex')}`, + }), + client, + ) + + // Prehash (sha256) + await runGeneric( + getReq({ + payload: `0x${prandomBuf(prng, maxSz + 1, true).toString('hex')}`, + hashType: Constants.SIGNING.HASHES.SHA256, + }), + client, + ) + }) + + it('Should test ASCII text formatting', async () => { + // Build a payload that uses spaces and newlines + await runGeneric( + getReq({ + payload: JSON.stringify( + { + testPayload: 'json with spaces', + anotherThing: -1, + }, + null, + 2, + ), + }), + client, + ) + }) + + it('Should validate SECP256K1/KECCAK signature against derived key', async () => { + // ASCII message encoding + await runGeneric(getReq({ payload: 'test' }), client) + + // Hex message encoding + const req = getReq({ payload: '0x123456' }) + await runGeneric(req, client) + }) + + it('Should validate ED25519/NULL signature against derived key', async () => { + const req = getReq({ + payload: '0x123456', + curveType: Constants.SIGNING.CURVES.ED25519, + /* Not doing anything. It is commented out. */ + hashType: Constants.SIGNING.HASHES.NONE, + // ED25519 derivation requires hardened indices + signerPath: DEFAULT_SIGNER.slice(0, 3), + }) + // Make generic signing request + await runGeneric(req, client) + }) + + it('Should validate SECP256K1/KECCAK signature against ETH_MSG request (legacy)', async () => { + // Generic request + const msg = 'Testing personal_sign' + const psMsg = ethPersonalSignMsg(msg) + // NOTE: The request contains some non ASCII characters so it will get + // encoded as hex automatically. + const req = getReq({ + payload: psMsg, + }) + + const respGeneric = await runGeneric(req, client) + + // Legacy request + const legacyReq = { + currency: 'ETH_MSG' as const, + data: { + signerPath: req.data.signerPath, + payload: msg, + protocol: 'signPersonal' as const, + curveType: Constants.SIGNING.CURVES.SECP256K1, + hashType: Constants.SIGNING.HASHES.KECCAK256, + }, + } + const respLegacy = await client.sign( + legacyReq as Parameters[0], + ) + + const genSigR = (respGeneric.sig?.r as Buffer)?.toString('hex') ?? '' + const genSigS = (respGeneric.sig?.s as Buffer)?.toString('hex') ?? '' + const legSigR = (respLegacy.sig?.r as Buffer)?.toString('hex') ?? '' + const legSigS = (respLegacy.sig?.s as Buffer)?.toString('hex') ?? '' + + const genSig = `${genSigR}${genSigS}` + const legSig = `${legSigR}${legSigS}` + expect(genSig).toEqualElseLog( + legSig, + 'Legacy and generic requests produced different sigs.', + ) + }) + + for (let i = 0; i < numIter; i++) { + it(`Should test random payloads - #${i}`, async () => { + const fwConstants = client.getFwConstants() + const req = getReq({ + hashType: Constants.SIGNING.HASHES.KECCAK256, + curveType: Constants.SIGNING.CURVES.SECP256K1, + payload: prandomBuf(prng, fwConstants.genericSigning.baseDataSz), + }) + + // 1. Secp256k1/keccak256 + await runGeneric(req, client) + + // 2. Secp256k1/sha256 + req.data.hashType = Constants.SIGNING.HASHES.SHA256 + await runGeneric(req, client) + + // 3. Ed25519 + req.data.curveType = Constants.SIGNING.CURVES.ED25519 + req.data.hashType = Constants.SIGNING.HASHES.NONE + req.data.signerPath = DEFAULT_SIGNER.slice(0, 3) + await runGeneric(req, client) + }) + } }) diff --git a/packages/sdk/src/__test__/e2e/signing/vectors.ts b/packages/sdk/src/__test__/e2e/signing/vectors.ts index 84db84ff..d80b6dbd 100644 --- a/packages/sdk/src/__test__/e2e/signing/vectors.ts +++ b/packages/sdk/src/__test__/e2e/signing/vectors.ts @@ -36,9 +36,9 @@ import type { TransactionSerializable } from 'viem' export interface TestVector { - name: string - tx: TransactionSerializable - category?: string + name: string + tx: TransactionSerializable + category?: string } // ============================================================================= @@ -46,76 +46,76 @@ export interface TestVector { // ============================================================================= export const LEGACY_VECTORS: TestVector[] = [ - { - name: 'Simple ETH transfer - Legacy', - tx: { - type: 'legacy', - to: '0xe242e54155b1abc71fc118065270cecaaf8b7768' as `0x${string}`, - value: BigInt('1000000000000000000'), // 1 ETH - data: '0x' as `0x${string}`, // Add missing data field - nonce: 0, - gasPrice: BigInt('20000000000'), // 20 gwei - gas: BigInt('21000'), - chainId: 1, - }, - category: 'basic-transfer', - }, - { - name: 'Contract interaction - Legacy', - tx: { - type: 'legacy', - to: '0x6B175474E89094C44Da98b954EedeAC495271d0F' as `0x${string}`, // DAI - value: BigInt(0), - data: '0xa9059cbb000000000000000000000000742d35cc6b2d9e9a3b7b7b4a7b0b2b7b0b2b7b0b0000000000000000000000000000000000000000000000001bc16d674ec80000' as `0x${string}`, - nonce: 1, - gasPrice: BigInt('25000000000'), // 25 gwei - gas: BigInt('100000'), - chainId: 1, - }, - category: 'contract-call', - }, - { - name: 'Zero value transaction - Legacy', - tx: { - type: 'legacy', - to: '0xe242e54155b1abc71fc118065270cecaaf8b7768' as `0x${string}`, - value: BigInt(0), - data: '0x' as `0x${string}`, // Add missing data field - nonce: 5, - gasPrice: BigInt('10000000000'), // 10 gwei - gas: BigInt('21000'), - chainId: 1, - }, - category: 'zero-value', - }, - { - name: 'High nonce transaction - Legacy', - tx: { - type: 'legacy', - to: '0xe242e54155b1abc71fc118065270cecaaf8b7768' as `0x${string}`, - value: BigInt('500000000000000000'), // 0.5 ETH - data: '0x' as `0x${string}`, // Add missing data field - nonce: 999, - gasPrice: BigInt('50000000000'), // 50 gwei - gas: BigInt('21000'), - chainId: 1, - }, - category: 'high-nonce', - }, - { - name: 'Polygon Legacy transaction', - tx: { - type: 'legacy', - to: '0xe242e54155b1abc71fc118065270cecaaf8b7768' as `0x${string}`, - value: BigInt('1000000000000000000'), // 1 MATIC - data: '0x' as `0x${string}`, // Add missing data field - nonce: 0, - gasPrice: BigInt('30000000000'), // 30 gwei - gas: BigInt('21000'), - chainId: 137, - }, - category: 'polygon', - }, + { + name: 'Simple ETH transfer - Legacy', + tx: { + type: 'legacy', + to: '0xe242e54155b1abc71fc118065270cecaaf8b7768' as `0x${string}`, + value: BigInt('1000000000000000000'), // 1 ETH + data: '0x' as `0x${string}`, // Add missing data field + nonce: 0, + gasPrice: BigInt('20000000000'), // 20 gwei + gas: BigInt('21000'), + chainId: 1, + }, + category: 'basic-transfer', + }, + { + name: 'Contract interaction - Legacy', + tx: { + type: 'legacy', + to: '0x6B175474E89094C44Da98b954EedeAC495271d0F' as `0x${string}`, // DAI + value: BigInt(0), + data: '0xa9059cbb000000000000000000000000742d35cc6b2d9e9a3b7b7b4a7b0b2b7b0b2b7b0b0000000000000000000000000000000000000000000000001bc16d674ec80000' as `0x${string}`, + nonce: 1, + gasPrice: BigInt('25000000000'), // 25 gwei + gas: BigInt('100000'), + chainId: 1, + }, + category: 'contract-call', + }, + { + name: 'Zero value transaction - Legacy', + tx: { + type: 'legacy', + to: '0xe242e54155b1abc71fc118065270cecaaf8b7768' as `0x${string}`, + value: BigInt(0), + data: '0x' as `0x${string}`, // Add missing data field + nonce: 5, + gasPrice: BigInt('10000000000'), // 10 gwei + gas: BigInt('21000'), + chainId: 1, + }, + category: 'zero-value', + }, + { + name: 'High nonce transaction - Legacy', + tx: { + type: 'legacy', + to: '0xe242e54155b1abc71fc118065270cecaaf8b7768' as `0x${string}`, + value: BigInt('500000000000000000'), // 0.5 ETH + data: '0x' as `0x${string}`, // Add missing data field + nonce: 999, + gasPrice: BigInt('50000000000'), // 50 gwei + gas: BigInt('21000'), + chainId: 1, + }, + category: 'high-nonce', + }, + { + name: 'Polygon Legacy transaction', + tx: { + type: 'legacy', + to: '0xe242e54155b1abc71fc118065270cecaaf8b7768' as `0x${string}`, + value: BigInt('1000000000000000000'), // 1 MATIC + data: '0x' as `0x${string}`, // Add missing data field + nonce: 0, + gasPrice: BigInt('30000000000'), // 30 gwei + gas: BigInt('21000'), + chainId: 137, + }, + category: 'polygon', + }, ] // ============================================================================= @@ -123,93 +123,93 @@ export const LEGACY_VECTORS: TestVector[] = [ // ============================================================================= export const EIP1559_TEST_VECTORS: TestVector[] = [ - { - name: 'Simple ETH transfer - EIP-1559', - tx: { - type: 'eip1559', - to: '0xe242e54155b1abc71fc118065270cecaaf8b7768' as `0x${string}`, - value: BigInt('1000000000000000000'), // 1 ETH - nonce: 0, - maxFeePerGas: BigInt('30000000000'), // 30 gwei - maxPriorityFeePerGas: BigInt('2000000000'), // 2 gwei - gas: BigInt('21000'), - chainId: 1, - }, - category: 'basic-transfer', - }, - { - name: 'DAI transfer - EIP-1559', - tx: { - type: 'eip1559', - to: '0x6B175474E89094C44Da98b954EedeAC495271d0F' as `0x${string}`, // DAI - value: BigInt(0), - data: '0xa9059cbb000000000000000000000000742d35cc6b2d9e9a3b7b7b4a7b0b2b7b0b2b7b0b0000000000000000000000000000000000000000000000001bc16d674ec80000' as `0x${string}`, - nonce: 1, - maxFeePerGas: BigInt('40000000000'), // 40 gwei - maxPriorityFeePerGas: BigInt('3000000000'), // 3 gwei - gas: BigInt('100000'), - chainId: 1, - }, - category: 'erc20-transfer', - }, - { - name: 'Uniswap V2 swap - EIP-1559', - tx: { - type: 'eip1559', - to: '0x7a250d5630B4cF539739dF2C5dAcb4c659F2488D' as `0x${string}`, // Uniswap V2 Router - value: BigInt(0), - data: '0x38ed17390000000000000000000000000000000000000000000000000de0b6b3a76400000000000000000000000000000000000000000000000000000c7d713b49da000000000000000000000000000000000000000000000000000000000000000000a0000000000000000000000000742d35cc6b2d9e9a3b7b7b4a7b0b2b7b0b2b7b0b000000000000000000000000000000000000000000000000000000006553f10000000000000000000000000000000000000000000000000000000000000000020000000000000000000000006b175474e89094c44da98b954eedeac495271d0f000000000000000000000000dac17f958d2ee523a2206206994597c13d831ec7' as `0x${string}`, - nonce: 2, - maxFeePerGas: BigInt('50000000000'), // 50 gwei - maxPriorityFeePerGas: BigInt('5000000000'), // 5 gwei - gas: BigInt('200000'), - chainId: 1, - }, - category: 'defi-swap', - }, - { - name: 'WETH deposit - EIP-1559', - tx: { - type: 'eip1559', - to: '0xC02aaA39b223FE8D0A0e5C4F27eAD9083C756Cc2' as `0x${string}`, // WETH - value: BigInt('1000000000000000000'), // 1 ETH - data: '0xd0e30db0' as `0x${string}`, // deposit() - nonce: 3, - maxFeePerGas: BigInt('25000000000'), // 25 gwei - maxPriorityFeePerGas: BigInt('2000000000'), // 2 gwei - gas: BigInt('100000'), - chainId: 1, - }, - category: 'payable-contract', - }, - { - name: 'High priority fee - EIP-1559', - tx: { - type: 'eip1559', - to: '0xe242e54155b1abc71fc118065270cecaaf8b7768' as `0x${string}`, - value: BigInt('500000000000000000'), // 0.5 ETH - nonce: 10, - maxFeePerGas: BigInt('100000000000'), // 100 gwei - maxPriorityFeePerGas: BigInt('50000000000'), // 50 gwei - gas: BigInt('21000'), - chainId: 1, - }, - category: 'high-priority', - }, - { - name: 'Polygon EIP-1559 transaction', - tx: { - type: 'eip1559', - to: '0xe242e54155b1abc71fc118065270cecaaf8b7768' as `0x${string}`, - value: BigInt('1000000000000000000'), // 1 MATIC - nonce: 0, - maxFeePerGas: BigInt('30000000000'), // 30 gwei - maxPriorityFeePerGas: BigInt('30000000000'), // 30 gwei (Polygon often has high priority fees) - gas: BigInt('21000'), - chainId: 137, - }, - category: 'polygon', - }, + { + name: 'Simple ETH transfer - EIP-1559', + tx: { + type: 'eip1559', + to: '0xe242e54155b1abc71fc118065270cecaaf8b7768' as `0x${string}`, + value: BigInt('1000000000000000000'), // 1 ETH + nonce: 0, + maxFeePerGas: BigInt('30000000000'), // 30 gwei + maxPriorityFeePerGas: BigInt('2000000000'), // 2 gwei + gas: BigInt('21000'), + chainId: 1, + }, + category: 'basic-transfer', + }, + { + name: 'DAI transfer - EIP-1559', + tx: { + type: 'eip1559', + to: '0x6B175474E89094C44Da98b954EedeAC495271d0F' as `0x${string}`, // DAI + value: BigInt(0), + data: '0xa9059cbb000000000000000000000000742d35cc6b2d9e9a3b7b7b4a7b0b2b7b0b2b7b0b0000000000000000000000000000000000000000000000001bc16d674ec80000' as `0x${string}`, + nonce: 1, + maxFeePerGas: BigInt('40000000000'), // 40 gwei + maxPriorityFeePerGas: BigInt('3000000000'), // 3 gwei + gas: BigInt('100000'), + chainId: 1, + }, + category: 'erc20-transfer', + }, + { + name: 'Uniswap V2 swap - EIP-1559', + tx: { + type: 'eip1559', + to: '0x7a250d5630B4cF539739dF2C5dAcb4c659F2488D' as `0x${string}`, // Uniswap V2 Router + value: BigInt(0), + data: '0x38ed17390000000000000000000000000000000000000000000000000de0b6b3a76400000000000000000000000000000000000000000000000000000c7d713b49da000000000000000000000000000000000000000000000000000000000000000000a0000000000000000000000000742d35cc6b2d9e9a3b7b7b4a7b0b2b7b0b2b7b0b000000000000000000000000000000000000000000000000000000006553f10000000000000000000000000000000000000000000000000000000000000000020000000000000000000000006b175474e89094c44da98b954eedeac495271d0f000000000000000000000000dac17f958d2ee523a2206206994597c13d831ec7' as `0x${string}`, + nonce: 2, + maxFeePerGas: BigInt('50000000000'), // 50 gwei + maxPriorityFeePerGas: BigInt('5000000000'), // 5 gwei + gas: BigInt('200000'), + chainId: 1, + }, + category: 'defi-swap', + }, + { + name: 'WETH deposit - EIP-1559', + tx: { + type: 'eip1559', + to: '0xC02aaA39b223FE8D0A0e5C4F27eAD9083C756Cc2' as `0x${string}`, // WETH + value: BigInt('1000000000000000000'), // 1 ETH + data: '0xd0e30db0' as `0x${string}`, // deposit() + nonce: 3, + maxFeePerGas: BigInt('25000000000'), // 25 gwei + maxPriorityFeePerGas: BigInt('2000000000'), // 2 gwei + gas: BigInt('100000'), + chainId: 1, + }, + category: 'payable-contract', + }, + { + name: 'High priority fee - EIP-1559', + tx: { + type: 'eip1559', + to: '0xe242e54155b1abc71fc118065270cecaaf8b7768' as `0x${string}`, + value: BigInt('500000000000000000'), // 0.5 ETH + nonce: 10, + maxFeePerGas: BigInt('100000000000'), // 100 gwei + maxPriorityFeePerGas: BigInt('50000000000'), // 50 gwei + gas: BigInt('21000'), + chainId: 1, + }, + category: 'high-priority', + }, + { + name: 'Polygon EIP-1559 transaction', + tx: { + type: 'eip1559', + to: '0xe242e54155b1abc71fc118065270cecaaf8b7768' as `0x${string}`, + value: BigInt('1000000000000000000'), // 1 MATIC + nonce: 0, + maxFeePerGas: BigInt('30000000000'), // 30 gwei + maxPriorityFeePerGas: BigInt('30000000000'), // 30 gwei (Polygon often has high priority fees) + gas: BigInt('21000'), + chainId: 137, + }, + category: 'polygon', + }, ] // ============================================================================= @@ -217,112 +217,112 @@ export const EIP1559_TEST_VECTORS: TestVector[] = [ // ============================================================================= export const EIP2930_TEST_VECTORS: TestVector[] = [ - { - name: 'Simple transfer with access list - EIP-2930', - tx: { - type: 'eip2930', - to: '0xe242e54155b1abc71fc118065270cecaaf8b7768' as `0x${string}`, - value: BigInt('1000000000000000000'), // 1 ETH - nonce: 0, - gasPrice: BigInt('20000000000'), // 20 gwei - gas: BigInt('21000'), - chainId: 1, - accessList: [ - { - address: - '0xe242e54155b1abc71fc118065270cecaaf8b7768' as `0x${string}`, - storageKeys: [ - '0x0000000000000000000000000000000000000000000000000000000000000001' as `0x${string}`, - ], - }, - ], - }, - category: 'simple-access-list', - }, - { - name: 'Contract interaction with multiple access entries - EIP-2930', - tx: { - type: 'eip2930', - to: '0x6B175474E89094C44Da98b954EedeAC495271d0F' as `0x${string}`, // DAI - value: BigInt(0), - data: '0xa9059cbb000000000000000000000000742d35cc6b2d9e9a3b7b7b4a7b0b2b7b0b2b7b0b0000000000000000000000000000000000000000000000001bc16d674ec80000' as `0x${string}`, - nonce: 1, - gasPrice: BigInt('25000000000'), // 25 gwei - gas: BigInt('100000'), - chainId: 1, - accessList: [ - { - address: - '0x6B175474E89094C44Da98b954EedeAC495271d0F' as `0x${string}`, // DAI contract - storageKeys: [ - '0x0000000000000000000000000000000000000000000000000000000000000001' as `0x${string}`, - '0x0000000000000000000000000000000000000000000000000000000000000002' as `0x${string}`, - ], - }, - { - address: - '0xe242e54155b1abc71fc118065270cecaaf8b7768' as `0x${string}`, // Recipient - storageKeys: [ - '0x0000000000000000000000000000000000000000000000000000000000000003' as `0x${string}`, - ], - }, - ], - }, - category: 'multi-access-list', - }, - { - name: 'Empty access list - EIP-2930', - tx: { - type: 'eip2930', - to: '0xe242e54155b1abc71fc118065270cecaaf8b7768' as `0x${string}`, - value: BigInt('500000000000000000'), // 0.5 ETH - nonce: 5, - gasPrice: BigInt('15000000000'), // 15 gwei - gas: BigInt('21000'), - chainId: 1, - accessList: [], - }, - category: 'empty-access-list', - }, - { - name: 'Complex DeFi interaction - EIP-2930', - tx: { - type: 'eip2930', - to: '0x7a250d5630B4cF539739dF2C5dAcb4c659F2488D' as `0x${string}`, // Uniswap V2 Router - value: BigInt(0), - data: '0x38ed17390000000000000000000000000000000000000000000000000de0b6b3a76400000000000000000000000000000000000000000000000000000c7d713b49da000000000000000000000000000000000000000000000000000000000000000000a0000000000000000000000000742d35cc6b2d9e9a3b7b7b4a7b0b2b7b0b2b7b0b000000000000000000000000000000000000000000000000000000006553f10000000000000000000000000000000000000000000000000000000000000000020000000000000000000000006b175474e89094c44da98b954eedeac495271d0f000000000000000000000000dac17f958d2ee523a2206206994597c13d831ec7' as `0x${string}`, - nonce: 10, - gasPrice: BigInt('30000000000'), // 30 gwei - gas: BigInt('200000'), - chainId: 1, - accessList: [ - { - address: - '0x7a250d5630B4cF539739dF2C5dAcb4c659F2488D' as `0x${string}`, // Router - storageKeys: [ - '0x0000000000000000000000000000000000000000000000000000000000000001' as `0x${string}`, - '0x0000000000000000000000000000000000000000000000000000000000000002' as `0x${string}`, - ], - }, - { - address: - '0x6B175474E89094C44Da98b954EedeAC495271d0F' as `0x${string}`, // DAI - storageKeys: [ - '0x0000000000000000000000000000000000000000000000000000000000000003' as `0x${string}`, - '0x0000000000000000000000000000000000000000000000000000000000000004' as `0x${string}`, - ], - }, - { - address: - '0xdAC17F958D2ee523a2206206994597C13D831ec7' as `0x${string}`, // USDT - storageKeys: [ - '0x0000000000000000000000000000000000000000000000000000000000000005' as `0x${string}`, - ], - }, - ], - }, - category: 'defi-complex', - }, + { + name: 'Simple transfer with access list - EIP-2930', + tx: { + type: 'eip2930', + to: '0xe242e54155b1abc71fc118065270cecaaf8b7768' as `0x${string}`, + value: BigInt('1000000000000000000'), // 1 ETH + nonce: 0, + gasPrice: BigInt('20000000000'), // 20 gwei + gas: BigInt('21000'), + chainId: 1, + accessList: [ + { + address: + '0xe242e54155b1abc71fc118065270cecaaf8b7768' as `0x${string}`, + storageKeys: [ + '0x0000000000000000000000000000000000000000000000000000000000000001' as `0x${string}`, + ], + }, + ], + }, + category: 'simple-access-list', + }, + { + name: 'Contract interaction with multiple access entries - EIP-2930', + tx: { + type: 'eip2930', + to: '0x6B175474E89094C44Da98b954EedeAC495271d0F' as `0x${string}`, // DAI + value: BigInt(0), + data: '0xa9059cbb000000000000000000000000742d35cc6b2d9e9a3b7b7b4a7b0b2b7b0b2b7b0b0000000000000000000000000000000000000000000000001bc16d674ec80000' as `0x${string}`, + nonce: 1, + gasPrice: BigInt('25000000000'), // 25 gwei + gas: BigInt('100000'), + chainId: 1, + accessList: [ + { + address: + '0x6B175474E89094C44Da98b954EedeAC495271d0F' as `0x${string}`, // DAI contract + storageKeys: [ + '0x0000000000000000000000000000000000000000000000000000000000000001' as `0x${string}`, + '0x0000000000000000000000000000000000000000000000000000000000000002' as `0x${string}`, + ], + }, + { + address: + '0xe242e54155b1abc71fc118065270cecaaf8b7768' as `0x${string}`, // Recipient + storageKeys: [ + '0x0000000000000000000000000000000000000000000000000000000000000003' as `0x${string}`, + ], + }, + ], + }, + category: 'multi-access-list', + }, + { + name: 'Empty access list - EIP-2930', + tx: { + type: 'eip2930', + to: '0xe242e54155b1abc71fc118065270cecaaf8b7768' as `0x${string}`, + value: BigInt('500000000000000000'), // 0.5 ETH + nonce: 5, + gasPrice: BigInt('15000000000'), // 15 gwei + gas: BigInt('21000'), + chainId: 1, + accessList: [], + }, + category: 'empty-access-list', + }, + { + name: 'Complex DeFi interaction - EIP-2930', + tx: { + type: 'eip2930', + to: '0x7a250d5630B4cF539739dF2C5dAcb4c659F2488D' as `0x${string}`, // Uniswap V2 Router + value: BigInt(0), + data: '0x38ed17390000000000000000000000000000000000000000000000000de0b6b3a76400000000000000000000000000000000000000000000000000000c7d713b49da000000000000000000000000000000000000000000000000000000000000000000a0000000000000000000000000742d35cc6b2d9e9a3b7b7b4a7b0b2b7b0b2b7b0b000000000000000000000000000000000000000000000000000000006553f10000000000000000000000000000000000000000000000000000000000000000020000000000000000000000006b175474e89094c44da98b954eedeac495271d0f000000000000000000000000dac17f958d2ee523a2206206994597c13d831ec7' as `0x${string}`, + nonce: 10, + gasPrice: BigInt('30000000000'), // 30 gwei + gas: BigInt('200000'), + chainId: 1, + accessList: [ + { + address: + '0x7a250d5630B4cF539739dF2C5dAcb4c659F2488D' as `0x${string}`, // Router + storageKeys: [ + '0x0000000000000000000000000000000000000000000000000000000000000001' as `0x${string}`, + '0x0000000000000000000000000000000000000000000000000000000000000002' as `0x${string}`, + ], + }, + { + address: + '0x6B175474E89094C44Da98b954EedeAC495271d0F' as `0x${string}`, // DAI + storageKeys: [ + '0x0000000000000000000000000000000000000000000000000000000000000003' as `0x${string}`, + '0x0000000000000000000000000000000000000000000000000000000000000004' as `0x${string}`, + ], + }, + { + address: + '0xdAC17F958D2ee523a2206206994597C13D831ec7' as `0x${string}`, // USDT + storageKeys: [ + '0x0000000000000000000000000000000000000000000000000000000000000005' as `0x${string}`, + ], + }, + ], + }, + category: 'defi-complex', + }, ] // ============================================================================= @@ -330,87 +330,87 @@ export const EIP2930_TEST_VECTORS: TestVector[] = [ // ============================================================================= export const EIP7702_TEST_VECTORS: TestVector[] = [ - { - name: 'Real mainnet EIP-7702 transaction - Simple authorization', - tx: { - type: 'eip7702', - to: '0x0000000071727De22E5E9d8BAf0edAc6f37da032' as `0x${string}`, - value: BigInt(0), - data: '0x765e827f00000000000000000000000000000000000000000000000000000000000000400000000000000000000000004337001fff419768e088ce247456c1b89288808400000000000000000000000000000000000000000000000000000000000000010000000000000000000000000000000000000000000000000000000000000020000000000000000000000000e02cb371a3ad18a14b40a7ef2d5cf03cc0d2b08f45f7e80000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000012000000000000000000000000000000000000000000000000000000000000001400000000000000000000000000001a1a00000000000000000000000000000c4040000000000000000000000000000000000000000000000000000000000016b530000000000000000000000000321162000000000000000000000000242ef2e9500000000000000000000000000000000000000000000000000000000000002600000000000000000000000000000000000000000000000000000000000000280000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000e4e9ae5c53000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000400000000000000000000000000000000000000000000000000000000000000078d0ec028a3d21533fdd200838f39c85b03679285d0000000000000000000000000000000000000000000000000000000000000000a9059cbb000000000000000000000000d7bd3ba35431d1cf8ad71300794d8958e34dcf850000000000000000000000000000000000000000000000020f5b1eaad8d80000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000415355c70dde835c7dabee4bc7a36be2f62f432da40b08b30ec733be5802dba7d647992b9b57035f27ddc43151285d1ead3c9b6df9485cbdeb37fea884e5d7e1c61b00000000000000000000000000000000000000000000000000000000000000' as `0x${string}`, - nonce: 23978, - maxFeePerGas: BigInt('7918212158'), - maxPriorityFeePerGas: BigInt('7918212158'), - gas: BigInt('260220'), - chainId: 1, - accessList: [], - authorizationList: [ - { - chainId: 1, - address: - '0x000000004f43c49e93c970e84001853a70923b03' as `0x${string}`, - nonce: 1, - yParity: 1, - r: '0xc9f7e0af53f516744bc34827bef7236df3123c3a07a601dca75d7698416adc4a' as `0x${string}`, - s: '0x5e8ec8137222d3b97016889093745707d0871cba3a5e8e32aad129dfd2a45727' as `0x${string}`, - }, - ], - }, - category: 'simple-auth', - }, - { - name: 'Real mainnet EIP-7702 transaction - Different authorization', - tx: { - type: 'eip7702', - to: '0x0000000071727De22E5E9d8BAf0edAc6f37da032' as `0x${string}`, - value: BigInt(0), - data: '0x765e827f00000000000000000000000000000000000000000000000000000000000000400000000000000000000000004337002c5702ce424cb62a56ca038e31e1d4a93d00000000000000000000000000000000000000000000000000000000000000010000000000000000000000000000000000000000000000000000000000000020000000000000000000000000f60d2b5657bde93983552c6509deb6201c9ef0dc4601700000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000012000000000000000000000000000000000000000000000000000000000000001400000000000000000000000000001a1a00000000000000000000000000000c4040000000000000000000000000000000000000000000000000000000000016b600000000000000000000000000321162000000000000000000000000242ef2e9500000000000000000000000000000000000000000000000000000000000002600000000000000000000000000000000000000000000000000000000000000280000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000e4e9ae5c53000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000400000000000000000000000000000000000000000000000000000000000000078d0ec028a3d21533fdd200838f39c85b03679285d0000000000000000000000000000000000000000000000000000000000000000a9059cbb0000000000000000000000008bf6fbea0be049e1eeb1f3287054c058c73c9f1b000000000000000000000000000000000000000000000002a802f8630a24000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000041453a20e7cc10dd3fff382ca4d15f0f418e3016ec9d58c8d6ab984bb8e81025e833f4d778d87d8268e24d438f49b383a0e23d24a43b8d8111bef58caa7707c20d1b00000000000000000000000000000000000000000000000000000000000000' as `0x${string}`, - nonce: 23736, - maxFeePerGas: BigInt('8358605855'), - maxPriorityFeePerGas: BigInt('8358605855'), - gas: BigInt('260220'), - chainId: 1, - accessList: [], - authorizationList: [ - { - chainId: 1, - address: - '0x000000004f43c49e93c970e84001853a70923b03' as `0x${string}`, - nonce: 1, - yParity: 0, - r: '0x948c69c40057e9fd4c9bb55506ef764bf80d1bbaf980fe8c09d9d9c0b67d0e49' as `0x${string}`, - s: '0x6554554e4b8dd345eb6c73cf88cb212d8e428fc4dff73a54b9b329c793ee2382' as `0x${string}`, - }, - ], - }, - category: 'different-auth', - }, - { - name: 'Real mainnet EIP-7702 transaction - High value authorization', - tx: { - type: 'eip7702', - to: '0xA935433DE1E70538269c417a01543b3Da8478A48' as `0x${string}`, - value: BigInt(0), - data: '0x2c7bddf4' as `0x${string}`, - nonce: 1185, - maxFeePerGas: BigInt('30555080604'), - maxPriorityFeePerGas: BigInt('30555080604'), - gas: BigInt('100000'), - chainId: 1, - accessList: [], - authorizationList: [ - { - chainId: 1, - address: - '0x163193c89de836e82bb121bd0dbcaba7e8ba67dc' as `0x${string}`, - nonce: 4999, - yParity: 1, - r: '0x806cbbf8a3cfb25b660e03147984ff95725252b6c95aceed91d5c0bfca6ad0d1' as `0x${string}`, - s: '0x2ac5998bdd42f8d89aaac417bc17b08f10a063c71eeaee1c95c6036ce399d759' as `0x${string}`, - }, - ], - }, - category: 'high-value-auth', - }, + { + name: 'Real mainnet EIP-7702 transaction - Simple authorization', + tx: { + type: 'eip7702', + to: '0x0000000071727De22E5E9d8BAf0edAc6f37da032' as `0x${string}`, + value: BigInt(0), + data: '0x765e827f00000000000000000000000000000000000000000000000000000000000000400000000000000000000000004337001fff419768e088ce247456c1b89288808400000000000000000000000000000000000000000000000000000000000000010000000000000000000000000000000000000000000000000000000000000020000000000000000000000000e02cb371a3ad18a14b40a7ef2d5cf03cc0d2b08f45f7e80000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000012000000000000000000000000000000000000000000000000000000000000001400000000000000000000000000001a1a00000000000000000000000000000c4040000000000000000000000000000000000000000000000000000000000016b530000000000000000000000000321162000000000000000000000000242ef2e9500000000000000000000000000000000000000000000000000000000000002600000000000000000000000000000000000000000000000000000000000000280000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000e4e9ae5c53000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000400000000000000000000000000000000000000000000000000000000000000078d0ec028a3d21533fdd200838f39c85b03679285d0000000000000000000000000000000000000000000000000000000000000000a9059cbb000000000000000000000000d7bd3ba35431d1cf8ad71300794d8958e34dcf850000000000000000000000000000000000000000000000020f5b1eaad8d80000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000415355c70dde835c7dabee4bc7a36be2f62f432da40b08b30ec733be5802dba7d647992b9b57035f27ddc43151285d1ead3c9b6df9485cbdeb37fea884e5d7e1c61b00000000000000000000000000000000000000000000000000000000000000' as `0x${string}`, + nonce: 23978, + maxFeePerGas: BigInt('7918212158'), + maxPriorityFeePerGas: BigInt('7918212158'), + gas: BigInt('260220'), + chainId: 1, + accessList: [], + authorizationList: [ + { + chainId: 1, + address: + '0x000000004f43c49e93c970e84001853a70923b03' as `0x${string}`, + nonce: 1, + yParity: 1, + r: '0xc9f7e0af53f516744bc34827bef7236df3123c3a07a601dca75d7698416adc4a' as `0x${string}`, + s: '0x5e8ec8137222d3b97016889093745707d0871cba3a5e8e32aad129dfd2a45727' as `0x${string}`, + }, + ], + }, + category: 'simple-auth', + }, + { + name: 'Real mainnet EIP-7702 transaction - Different authorization', + tx: { + type: 'eip7702', + to: '0x0000000071727De22E5E9d8BAf0edAc6f37da032' as `0x${string}`, + value: BigInt(0), + data: '0x765e827f00000000000000000000000000000000000000000000000000000000000000400000000000000000000000004337002c5702ce424cb62a56ca038e31e1d4a93d00000000000000000000000000000000000000000000000000000000000000010000000000000000000000000000000000000000000000000000000000000020000000000000000000000000f60d2b5657bde93983552c6509deb6201c9ef0dc4601700000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000012000000000000000000000000000000000000000000000000000000000000001400000000000000000000000000001a1a00000000000000000000000000000c4040000000000000000000000000000000000000000000000000000000000016b600000000000000000000000000321162000000000000000000000000242ef2e9500000000000000000000000000000000000000000000000000000000000002600000000000000000000000000000000000000000000000000000000000000280000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000e4e9ae5c53000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000400000000000000000000000000000000000000000000000000000000000000078d0ec028a3d21533fdd200838f39c85b03679285d0000000000000000000000000000000000000000000000000000000000000000a9059cbb0000000000000000000000008bf6fbea0be049e1eeb1f3287054c058c73c9f1b000000000000000000000000000000000000000000000002a802f8630a24000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000041453a20e7cc10dd3fff382ca4d15f0f418e3016ec9d58c8d6ab984bb8e81025e833f4d778d87d8268e24d438f49b383a0e23d24a43b8d8111bef58caa7707c20d1b00000000000000000000000000000000000000000000000000000000000000' as `0x${string}`, + nonce: 23736, + maxFeePerGas: BigInt('8358605855'), + maxPriorityFeePerGas: BigInt('8358605855'), + gas: BigInt('260220'), + chainId: 1, + accessList: [], + authorizationList: [ + { + chainId: 1, + address: + '0x000000004f43c49e93c970e84001853a70923b03' as `0x${string}`, + nonce: 1, + yParity: 0, + r: '0x948c69c40057e9fd4c9bb55506ef764bf80d1bbaf980fe8c09d9d9c0b67d0e49' as `0x${string}`, + s: '0x6554554e4b8dd345eb6c73cf88cb212d8e428fc4dff73a54b9b329c793ee2382' as `0x${string}`, + }, + ], + }, + category: 'different-auth', + }, + { + name: 'Real mainnet EIP-7702 transaction - High value authorization', + tx: { + type: 'eip7702', + to: '0xA935433DE1E70538269c417a01543b3Da8478A48' as `0x${string}`, + value: BigInt(0), + data: '0x2c7bddf4' as `0x${string}`, + nonce: 1185, + maxFeePerGas: BigInt('30555080604'), + maxPriorityFeePerGas: BigInt('30555080604'), + gas: BigInt('100000'), + chainId: 1, + accessList: [], + authorizationList: [ + { + chainId: 1, + address: + '0x163193c89de836e82bb121bd0dbcaba7e8ba67dc' as `0x${string}`, + nonce: 4999, + yParity: 1, + r: '0x806cbbf8a3cfb25b660e03147984ff95725252b6c95aceed91d5c0bfca6ad0d1' as `0x${string}`, + s: '0x2ac5998bdd42f8d89aaac417bc17b08f10a063c71eeaee1c95c6036ce399d759' as `0x${string}`, + }, + ], + }, + category: 'high-value-auth', + }, ] // ============================================================================= @@ -418,301 +418,301 @@ export const EIP7702_TEST_VECTORS: TestVector[] = [ // ============================================================================= export const EDGE_CASE_TEST_VECTORS: TestVector[] = [ - { - name: 'Maximum nonce value - Legacy', - tx: { - type: 'legacy', - to: '0xe242e54155b1abc71fc118065270cecaaf8b7768' as `0x${string}`, - value: BigInt('1000000000000000'), // 0.001 ETH - data: '0x' as `0x${string}`, // Add missing data field - nonce: 2 ** 32 - 1, // Maximum safe 32-bit integer - gasPrice: BigInt('20000000000'), // 20 gwei - gas: BigInt('21000'), - chainId: 1, - }, - category: 'max-nonce', - }, - { - name: 'Maximum gas price - Legacy', - tx: { - type: 'legacy', - to: '0xe242e54155b1abc71fc118065270cecaaf8b7768' as `0x${string}`, - value: BigInt('1000000000000000'), // 0.001 ETH - data: '0x' as `0x${string}`, // Add missing data field - nonce: 0, - gasPrice: BigInt('1000000000000'), // 1000 gwei (very high) - gas: BigInt('21000'), - chainId: 1, - }, - category: 'max-gas-price', - }, - { - name: 'Minimal gas limit - EIP-1559', - tx: { - type: 'eip1559', - to: '0xe242e54155b1abc71fc118065270cecaaf8b7768' as `0x${string}`, - value: BigInt('1000000000000000'), // 0.001 ETH - data: '0x' as `0x${string}`, // Add missing data field - nonce: 0, - maxFeePerGas: BigInt('20000000000'), // 20 gwei - maxPriorityFeePerGas: BigInt('1000000000'), // 1 gwei - gas: BigInt('21000'), // Minimum for ETH transfer - chainId: 1, - }, - category: 'min-gas', - }, - { - name: 'Large data payload - EIP-1559', - tx: { - type: 'eip1559', - to: '0xe242e54155b1abc71fc118065270cecaaf8b7768' as `0x${string}`, - value: BigInt(0), - data: `0x${'a'.repeat(1000)}` as `0x${string}`, // Large data payload - nonce: 0, - maxFeePerGas: BigInt('30000000000'), // 30 gwei - maxPriorityFeePerGas: BigInt('2000000000'), // 2 gwei - gas: BigInt('500000'), // High gas for large data - chainId: 1, - }, - category: 'large-data', - }, - { - name: 'Contract creation - Legacy', - tx: { - type: 'legacy', - to: undefined, // Contract creation - value: BigInt(0), - data: '0x608060405234801561001057600080fd5b506040518060400160405280600681526020017f48656c6c6f210000000000000000000000000000000000000000000000000000815250600090805190602001906100609291906100c7565b5034801561006d57600080fd5b5061016c565b828054600181600116156101000203166002900490600052602060002090601f016020900481019282601f106100a657805160ff19168380011785556100d4565b828001600101855582156100d4579182015b828111156100d35782518255916020019190600101906100b8565b5b5090506100e191906100e5565b5090565b61010791905b808211156101035760008160009055506001016100eb565b5090565b90565b610455806101186000396000f3fe' as `0x${string}`, - nonce: 0, - gasPrice: BigInt('20000000000'), // 20 gwei - gas: BigInt('2000000'), // High gas for contract creation - chainId: 1, - }, - category: 'contract-creation', - }, - { - name: 'Zero gas price - Legacy (pre-EIP-155)', - tx: { - type: 'legacy', - to: '0xe242e54155b1abc71fc118065270cecaaf8b7768' as `0x${string}`, - value: BigInt('1000000000000000'), // 0.001 ETH - data: '0x' as `0x${string}`, // Add missing data field - nonce: 0, - gasPrice: BigInt(0), // Zero gas price - gas: BigInt('21000'), - // No chainId for pre-EIP-155 - }, - category: 'zero-gas-price', - }, - { - name: 'Alternative chain IDs - Polygon', - tx: { - type: 'eip1559', - to: '0xe242e54155b1abc71fc118065270cecaaf8b7768' as `0x${string}`, - value: BigInt('1000000000000000000'), // 1 MATIC - data: '0x' as `0x${string}`, // Add missing data field - nonce: 0, - maxFeePerGas: BigInt('30000000000'), // 30 gwei - maxPriorityFeePerGas: BigInt('30000000000'), // 30 gwei - gas: BigInt('21000'), - chainId: 137, - }, - category: 'alt-chains', - }, - { - name: 'Alternative chain IDs - BSC', - tx: { - type: 'eip1559', - to: '0xe242e54155b1abc71fc118065270cecaaf8b7768' as `0x${string}`, - value: BigInt('1000000000000000000'), // 1 BNB - data: '0x' as `0x${string}`, // Add missing data field - nonce: 0, - maxFeePerGas: BigInt('5000000000'), // 5 gwei - maxPriorityFeePerGas: BigInt('1000000000'), // 1 gwei - gas: BigInt('21000'), - chainId: 56, - }, - category: 'alt-chains', - }, - { - name: 'Alternative chain IDs - Avalanche', - tx: { - type: 'eip1559', - to: '0xe242e54155b1abc71fc118065270cecaaf8b7768' as `0x${string}`, - value: BigInt('1000000000000000000'), // 1 AVAX - data: '0x' as `0x${string}`, // Add missing data field - nonce: 0, - maxFeePerGas: BigInt('25000000000'), // 25 gwei - maxPriorityFeePerGas: BigInt('1500000000'), // 1.5 gwei - gas: BigInt('21000'), - chainId: 43114, - }, - category: 'alt-chains', - }, - { - name: 'Large chain ID - Palm Network', - tx: { - type: 'eip1559', - to: '0xe242e54155b1abc71fc118065270cecaaf8b7768' as `0x${string}`, - value: BigInt('1000000000000000000'), // 1 PALM - data: '0x' as `0x${string}`, // Add missing data field - nonce: 0, - maxFeePerGas: BigInt('20000000000'), // 20 gwei - maxPriorityFeePerGas: BigInt('2000000000'), // 2 gwei - gas: BigInt('21000'), - chainId: 11297108109, - }, - category: 'large-chain-id', - }, - { - name: 'Unknown chain ID', - tx: { - type: 'eip1559', - to: '0xe242e54155b1abc71fc118065270cecaaf8b7768' as `0x${string}`, - value: BigInt('1000000000000000000'), // 1 ETH - data: '0x' as `0x${string}`, // Add missing data field - nonce: 0, - maxFeePerGas: BigInt('20000000000'), // 20 gwei - maxPriorityFeePerGas: BigInt('2000000000'), // 2 gwei - gas: BigInt('21000'), - chainId: 9999, - }, - category: 'unknown-chain', - }, - { - name: 'Minimal value - 1 wei', - tx: { - type: 'eip1559', - to: '0xe242e54155b1abc71fc118065270cecaaf8b7768' as `0x${string}`, - value: BigInt(1), // 1 wei - data: '0x' as `0x${string}`, // Add missing data field - nonce: 0, - maxFeePerGas: BigInt('20000000000'), // 20 gwei - maxPriorityFeePerGas: BigInt('2000000000'), // 2 gwei - gas: BigInt('21000'), - chainId: 1, - }, - category: 'min-value', - }, - { - name: 'Scientific notation value - 1e8 wei', - tx: { - type: 'eip1559', - to: '0xe242e54155b1abc71fc118065270cecaaf8b7768' as `0x${string}`, - value: BigInt('100000000000'), // 1e8 wei = 0.1 gwei - data: '0x' as `0x${string}`, // Add missing data field - nonce: 0, - maxFeePerGas: BigInt('20000000000'), // 20 gwei - maxPriorityFeePerGas: BigInt('2000000000'), // 2 gwei - gas: BigInt('21000'), - chainId: 1, - }, - category: 'scientific-value', - }, - { - name: 'Maximum safe value', - tx: { - type: 'eip1559', - to: '0xe242e54155b1abc71fc118065270cecaaf8b7768' as `0x${string}`, - value: BigInt( - '0xffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff', - ), // Max uint256 - data: '0x' as `0x${string}`, // Add missing data field - nonce: 0, - maxFeePerGas: BigInt('20000000000'), // 20 gwei - maxPriorityFeePerGas: BigInt('2000000000'), // 2 gwei - gas: BigInt('21000'), - chainId: 1, - }, - category: 'max-value', - }, - { - name: 'Contract creation - EIP-1559', - tx: { - type: 'eip1559', - to: undefined, // Contract creation - value: BigInt(0), - data: `0x${'60'.repeat(96)}` as `0x${string}`, // Simple contract bytecode - nonce: 0, - maxFeePerGas: BigInt('30000000000'), // 30 gwei - maxPriorityFeePerGas: BigInt('2000000000'), // 2 gwei - gas: BigInt('2000000'), // High gas for contract creation - chainId: 1, - }, - category: 'contract-creation', - }, - { - name: 'Contract creation - EIP-2930', - tx: { - type: 'eip2930', - to: undefined, // Contract creation - value: BigInt(0), - data: `0x${'60'.repeat(96)}` as `0x${string}`, // Simple contract bytecode - nonce: 0, - gasPrice: BigInt('25000000000'), // 25 gwei - gas: BigInt('2000000'), // High gas for contract creation - chainId: 1, - accessList: [], - }, - category: 'contract-creation', - }, - { - name: 'EIP-2930 with mixed storage keys', - tx: { - type: 'eip2930', - to: '0xe242e54155b1abc71fc118065270cecaaf8b7768' as `0x${string}`, - value: BigInt('1000000000000000000'), // 1 ETH - data: '0x' as `0x${string}`, // Add missing data field - nonce: 0, - gasPrice: BigInt('20000000000'), // 20 gwei - gas: BigInt('100000'), - chainId: 1, - accessList: [ - { - address: - '0xe242e54155b1abc71fc118065270cecaaf8b7768' as `0x${string}`, - storageKeys: [ - '0x7154f8b310ad6ce97ce3b15e3419d9863865dfe2d8635802f7f4a52a206255a6' as `0x${string}`, - ], - }, - { - address: - '0xe0f8ff08ef0242c461da688b8b85e438db724860' as `0x${string}`, - storageKeys: [], // Empty storage keys - }, - ], - }, - category: 'mixed-access-list', - }, - { - name: 'EIP-1559 with access list', - tx: { - type: 'eip1559', - to: '0xe242e54155b1abc71fc118065270cecaaf8b7768' as `0x${string}`, - value: BigInt('1000000000000000000'), // 1 ETH - data: '0x' as `0x${string}`, // Add missing data field - nonce: 0, - maxFeePerGas: BigInt('30000000000'), // 30 gwei - maxPriorityFeePerGas: BigInt('2000000000'), // 2 gwei - gas: BigInt('100000'), - chainId: 1, - accessList: [ - { - address: - '0xe242e54155b1abc71fc118065270cecaaf8b7768' as `0x${string}`, - storageKeys: [ - '0x7154f8b310ad6ce97ce3b15e3419d9863865dfe2d8635802f7f4a52a206255a6' as `0x${string}`, - ], - }, - { - address: - '0xe0f8ff08ef0242c461da688b8b85e438db724860' as `0x${string}`, - storageKeys: [], // Empty storage keys - }, - ], - }, - category: 'eip1559-with-access-list', - }, + { + name: 'Maximum nonce value - Legacy', + tx: { + type: 'legacy', + to: '0xe242e54155b1abc71fc118065270cecaaf8b7768' as `0x${string}`, + value: BigInt('1000000000000000'), // 0.001 ETH + data: '0x' as `0x${string}`, // Add missing data field + nonce: 2 ** 32 - 1, // Maximum safe 32-bit integer + gasPrice: BigInt('20000000000'), // 20 gwei + gas: BigInt('21000'), + chainId: 1, + }, + category: 'max-nonce', + }, + { + name: 'Maximum gas price - Legacy', + tx: { + type: 'legacy', + to: '0xe242e54155b1abc71fc118065270cecaaf8b7768' as `0x${string}`, + value: BigInt('1000000000000000'), // 0.001 ETH + data: '0x' as `0x${string}`, // Add missing data field + nonce: 0, + gasPrice: BigInt('1000000000000'), // 1000 gwei (very high) + gas: BigInt('21000'), + chainId: 1, + }, + category: 'max-gas-price', + }, + { + name: 'Minimal gas limit - EIP-1559', + tx: { + type: 'eip1559', + to: '0xe242e54155b1abc71fc118065270cecaaf8b7768' as `0x${string}`, + value: BigInt('1000000000000000'), // 0.001 ETH + data: '0x' as `0x${string}`, // Add missing data field + nonce: 0, + maxFeePerGas: BigInt('20000000000'), // 20 gwei + maxPriorityFeePerGas: BigInt('1000000000'), // 1 gwei + gas: BigInt('21000'), // Minimum for ETH transfer + chainId: 1, + }, + category: 'min-gas', + }, + { + name: 'Large data payload - EIP-1559', + tx: { + type: 'eip1559', + to: '0xe242e54155b1abc71fc118065270cecaaf8b7768' as `0x${string}`, + value: BigInt(0), + data: `0x${'a'.repeat(1000)}` as `0x${string}`, // Large data payload + nonce: 0, + maxFeePerGas: BigInt('30000000000'), // 30 gwei + maxPriorityFeePerGas: BigInt('2000000000'), // 2 gwei + gas: BigInt('500000'), // High gas for large data + chainId: 1, + }, + category: 'large-data', + }, + { + name: 'Contract creation - Legacy', + tx: { + type: 'legacy', + to: undefined, // Contract creation + value: BigInt(0), + data: '0x608060405234801561001057600080fd5b506040518060400160405280600681526020017f48656c6c6f210000000000000000000000000000000000000000000000000000815250600090805190602001906100609291906100c7565b5034801561006d57600080fd5b5061016c565b828054600181600116156101000203166002900490600052602060002090601f016020900481019282601f106100a657805160ff19168380011785556100d4565b828001600101855582156100d4579182015b828111156100d35782518255916020019190600101906100b8565b5b5090506100e191906100e5565b5090565b61010791905b808211156101035760008160009055506001016100eb565b5090565b90565b610455806101186000396000f3fe' as `0x${string}`, + nonce: 0, + gasPrice: BigInt('20000000000'), // 20 gwei + gas: BigInt('2000000'), // High gas for contract creation + chainId: 1, + }, + category: 'contract-creation', + }, + { + name: 'Zero gas price - Legacy (pre-EIP-155)', + tx: { + type: 'legacy', + to: '0xe242e54155b1abc71fc118065270cecaaf8b7768' as `0x${string}`, + value: BigInt('1000000000000000'), // 0.001 ETH + data: '0x' as `0x${string}`, // Add missing data field + nonce: 0, + gasPrice: BigInt(0), // Zero gas price + gas: BigInt('21000'), + // No chainId for pre-EIP-155 + }, + category: 'zero-gas-price', + }, + { + name: 'Alternative chain IDs - Polygon', + tx: { + type: 'eip1559', + to: '0xe242e54155b1abc71fc118065270cecaaf8b7768' as `0x${string}`, + value: BigInt('1000000000000000000'), // 1 MATIC + data: '0x' as `0x${string}`, // Add missing data field + nonce: 0, + maxFeePerGas: BigInt('30000000000'), // 30 gwei + maxPriorityFeePerGas: BigInt('30000000000'), // 30 gwei + gas: BigInt('21000'), + chainId: 137, + }, + category: 'alt-chains', + }, + { + name: 'Alternative chain IDs - BSC', + tx: { + type: 'eip1559', + to: '0xe242e54155b1abc71fc118065270cecaaf8b7768' as `0x${string}`, + value: BigInt('1000000000000000000'), // 1 BNB + data: '0x' as `0x${string}`, // Add missing data field + nonce: 0, + maxFeePerGas: BigInt('5000000000'), // 5 gwei + maxPriorityFeePerGas: BigInt('1000000000'), // 1 gwei + gas: BigInt('21000'), + chainId: 56, + }, + category: 'alt-chains', + }, + { + name: 'Alternative chain IDs - Avalanche', + tx: { + type: 'eip1559', + to: '0xe242e54155b1abc71fc118065270cecaaf8b7768' as `0x${string}`, + value: BigInt('1000000000000000000'), // 1 AVAX + data: '0x' as `0x${string}`, // Add missing data field + nonce: 0, + maxFeePerGas: BigInt('25000000000'), // 25 gwei + maxPriorityFeePerGas: BigInt('1500000000'), // 1.5 gwei + gas: BigInt('21000'), + chainId: 43114, + }, + category: 'alt-chains', + }, + { + name: 'Large chain ID - Palm Network', + tx: { + type: 'eip1559', + to: '0xe242e54155b1abc71fc118065270cecaaf8b7768' as `0x${string}`, + value: BigInt('1000000000000000000'), // 1 PALM + data: '0x' as `0x${string}`, // Add missing data field + nonce: 0, + maxFeePerGas: BigInt('20000000000'), // 20 gwei + maxPriorityFeePerGas: BigInt('2000000000'), // 2 gwei + gas: BigInt('21000'), + chainId: 11297108109, + }, + category: 'large-chain-id', + }, + { + name: 'Unknown chain ID', + tx: { + type: 'eip1559', + to: '0xe242e54155b1abc71fc118065270cecaaf8b7768' as `0x${string}`, + value: BigInt('1000000000000000000'), // 1 ETH + data: '0x' as `0x${string}`, // Add missing data field + nonce: 0, + maxFeePerGas: BigInt('20000000000'), // 20 gwei + maxPriorityFeePerGas: BigInt('2000000000'), // 2 gwei + gas: BigInt('21000'), + chainId: 9999, + }, + category: 'unknown-chain', + }, + { + name: 'Minimal value - 1 wei', + tx: { + type: 'eip1559', + to: '0xe242e54155b1abc71fc118065270cecaaf8b7768' as `0x${string}`, + value: BigInt(1), // 1 wei + data: '0x' as `0x${string}`, // Add missing data field + nonce: 0, + maxFeePerGas: BigInt('20000000000'), // 20 gwei + maxPriorityFeePerGas: BigInt('2000000000'), // 2 gwei + gas: BigInt('21000'), + chainId: 1, + }, + category: 'min-value', + }, + { + name: 'Scientific notation value - 1e8 wei', + tx: { + type: 'eip1559', + to: '0xe242e54155b1abc71fc118065270cecaaf8b7768' as `0x${string}`, + value: BigInt('100000000000'), // 1e8 wei = 0.1 gwei + data: '0x' as `0x${string}`, // Add missing data field + nonce: 0, + maxFeePerGas: BigInt('20000000000'), // 20 gwei + maxPriorityFeePerGas: BigInt('2000000000'), // 2 gwei + gas: BigInt('21000'), + chainId: 1, + }, + category: 'scientific-value', + }, + { + name: 'Maximum safe value', + tx: { + type: 'eip1559', + to: '0xe242e54155b1abc71fc118065270cecaaf8b7768' as `0x${string}`, + value: BigInt( + '0xffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff', + ), // Max uint256 + data: '0x' as `0x${string}`, // Add missing data field + nonce: 0, + maxFeePerGas: BigInt('20000000000'), // 20 gwei + maxPriorityFeePerGas: BigInt('2000000000'), // 2 gwei + gas: BigInt('21000'), + chainId: 1, + }, + category: 'max-value', + }, + { + name: 'Contract creation - EIP-1559', + tx: { + type: 'eip1559', + to: undefined, // Contract creation + value: BigInt(0), + data: `0x${'60'.repeat(96)}` as `0x${string}`, // Simple contract bytecode + nonce: 0, + maxFeePerGas: BigInt('30000000000'), // 30 gwei + maxPriorityFeePerGas: BigInt('2000000000'), // 2 gwei + gas: BigInt('2000000'), // High gas for contract creation + chainId: 1, + }, + category: 'contract-creation', + }, + { + name: 'Contract creation - EIP-2930', + tx: { + type: 'eip2930', + to: undefined, // Contract creation + value: BigInt(0), + data: `0x${'60'.repeat(96)}` as `0x${string}`, // Simple contract bytecode + nonce: 0, + gasPrice: BigInt('25000000000'), // 25 gwei + gas: BigInt('2000000'), // High gas for contract creation + chainId: 1, + accessList: [], + }, + category: 'contract-creation', + }, + { + name: 'EIP-2930 with mixed storage keys', + tx: { + type: 'eip2930', + to: '0xe242e54155b1abc71fc118065270cecaaf8b7768' as `0x${string}`, + value: BigInt('1000000000000000000'), // 1 ETH + data: '0x' as `0x${string}`, // Add missing data field + nonce: 0, + gasPrice: BigInt('20000000000'), // 20 gwei + gas: BigInt('100000'), + chainId: 1, + accessList: [ + { + address: + '0xe242e54155b1abc71fc118065270cecaaf8b7768' as `0x${string}`, + storageKeys: [ + '0x7154f8b310ad6ce97ce3b15e3419d9863865dfe2d8635802f7f4a52a206255a6' as `0x${string}`, + ], + }, + { + address: + '0xe0f8ff08ef0242c461da688b8b85e438db724860' as `0x${string}`, + storageKeys: [], // Empty storage keys + }, + ], + }, + category: 'mixed-access-list', + }, + { + name: 'EIP-1559 with access list', + tx: { + type: 'eip1559', + to: '0xe242e54155b1abc71fc118065270cecaaf8b7768' as `0x${string}`, + value: BigInt('1000000000000000000'), // 1 ETH + data: '0x' as `0x${string}`, // Add missing data field + nonce: 0, + maxFeePerGas: BigInt('30000000000'), // 30 gwei + maxPriorityFeePerGas: BigInt('2000000000'), // 2 gwei + gas: BigInt('100000'), + chainId: 1, + accessList: [ + { + address: + '0xe242e54155b1abc71fc118065270cecaaf8b7768' as `0x${string}`, + storageKeys: [ + '0x7154f8b310ad6ce97ce3b15e3419d9863865dfe2d8635802f7f4a52a206255a6' as `0x${string}`, + ], + }, + { + address: + '0xe0f8ff08ef0242c461da688b8b85e438db724860' as `0x${string}`, + storageKeys: [], // Empty storage keys + }, + ], + }, + category: 'eip1559-with-access-list', + }, ] // ============================================================================= @@ -723,375 +723,375 @@ export const EDGE_CASE_TEST_VECTORS: TestVector[] = [ * Test various derivation path lengths to ensure wallet compatibility */ export const DERIVATION_PATH_VECTORS: TestVector[] = [ - { - name: 'Derivation path - Full BIP44 path (5 levels)', - tx: { - type: 'eip1559', - to: '0xe242e54155b1abc71fc118065270cecaaf8b7768' as `0x${string}`, - value: BigInt('1000000000000000000'), // 1 ETH - data: '0x' as `0x${string}`, // Add missing data field - nonce: 0, - maxFeePerGas: BigInt('20000000000'), // 20 gwei - maxPriorityFeePerGas: BigInt('2000000000'), // 2 gwei - gas: BigInt('21000'), - chainId: 1, - }, - category: 'derivation-path-5', - }, - { - name: 'Derivation path - 3 levels', - tx: { - type: 'legacy', - to: '0xe242e54155b1abc71fc118065270cecaaf8b7768' as `0x${string}`, - value: BigInt('500000000000000000'), // 0.5 ETH - data: '0x' as `0x${string}`, // Add missing data field - nonce: 1, - gasPrice: BigInt('15000000000'), // 15 gwei - gas: BigInt('21000'), - chainId: 1, - }, - category: 'derivation-path-3', - }, - { - name: 'Derivation path - 2 levels', - tx: { - type: 'eip2930', - to: '0xe242e54155b1abc71fc118065270cecaaf8b7768' as `0x${string}`, - value: BigInt('250000000000000000'), // 0.25 ETH - data: '0x' as `0x${string}`, // Add missing data field - nonce: 2, - gasPrice: BigInt('10000000000'), // 10 gwei - gas: BigInt('21000'), - chainId: 1, - accessList: [], - }, - category: 'derivation-path-2', - }, - { - name: 'Derivation path - 1 level (root)', - tx: { - type: 'eip1559', - to: '0xe242e54155b1abc71fc118065270cecaaf8b7768' as `0x${string}`, - value: BigInt('100000000000000000'), // 0.1 ETH - data: '0x' as `0x${string}`, // Add missing data field - nonce: 3, - maxFeePerGas: BigInt('25000000000'), // 25 gwei - maxPriorityFeePerGas: BigInt('2500000000'), // 2.5 gwei - gas: BigInt('21000'), - chainId: 1, - }, - category: 'derivation-path-1', - }, + { + name: 'Derivation path - Full BIP44 path (5 levels)', + tx: { + type: 'eip1559', + to: '0xe242e54155b1abc71fc118065270cecaaf8b7768' as `0x${string}`, + value: BigInt('1000000000000000000'), // 1 ETH + data: '0x' as `0x${string}`, // Add missing data field + nonce: 0, + maxFeePerGas: BigInt('20000000000'), // 20 gwei + maxPriorityFeePerGas: BigInt('2000000000'), // 2 gwei + gas: BigInt('21000'), + chainId: 1, + }, + category: 'derivation-path-5', + }, + { + name: 'Derivation path - 3 levels', + tx: { + type: 'legacy', + to: '0xe242e54155b1abc71fc118065270cecaaf8b7768' as `0x${string}`, + value: BigInt('500000000000000000'), // 0.5 ETH + data: '0x' as `0x${string}`, // Add missing data field + nonce: 1, + gasPrice: BigInt('15000000000'), // 15 gwei + gas: BigInt('21000'), + chainId: 1, + }, + category: 'derivation-path-3', + }, + { + name: 'Derivation path - 2 levels', + tx: { + type: 'eip2930', + to: '0xe242e54155b1abc71fc118065270cecaaf8b7768' as `0x${string}`, + value: BigInt('250000000000000000'), // 0.25 ETH + data: '0x' as `0x${string}`, // Add missing data field + nonce: 2, + gasPrice: BigInt('10000000000'), // 10 gwei + gas: BigInt('21000'), + chainId: 1, + accessList: [], + }, + category: 'derivation-path-2', + }, + { + name: 'Derivation path - 1 level (root)', + tx: { + type: 'eip1559', + to: '0xe242e54155b1abc71fc118065270cecaaf8b7768' as `0x${string}`, + value: BigInt('100000000000000000'), // 0.1 ETH + data: '0x' as `0x${string}`, // Add missing data field + nonce: 3, + maxFeePerGas: BigInt('25000000000'), // 25 gwei + maxPriorityFeePerGas: BigInt('2500000000'), // 2.5 gwei + gas: BigInt('21000'), + chainId: 1, + }, + category: 'derivation-path-1', + }, ] /** * Test specific network configurations that are commonly used */ export const NETWORK_SPECIFIC_VECTORS: TestVector[] = [ - { - name: 'Rinkeby testnet (historical)', - tx: { - type: 'eip1559', - to: '0xe242e54155b1abc71fc118065270cecaaf8b7768' as `0x${string}`, - value: BigInt('1000000000000000000'), // 1 ETH - data: '0x' as `0x${string}`, // Add missing data field - nonce: 0, - maxFeePerGas: BigInt('20000000000'), // 20 gwei - maxPriorityFeePerGas: BigInt('2000000000'), // 2 gwei - gas: BigInt('21000'), - chainId: 4, // Rinkeby - }, - category: 'rinkeby', - }, - { - name: 'Goerli testnet', - tx: { - type: 'eip1559', - to: '0xe242e54155b1abc71fc118065270cecaaf8b7768' as `0x${string}`, - value: BigInt('1000000000000000000'), // 1 ETH - data: '0x' as `0x${string}`, // Add missing data field - nonce: 0, - maxFeePerGas: BigInt('20000000000'), // 20 gwei - maxPriorityFeePerGas: BigInt('2000000000'), // 2 gwei - gas: BigInt('21000'), - chainId: 5, // Goerli - }, - category: 'goerli', - }, - { - name: 'Sepolia testnet', - tx: { - type: 'eip1559', - to: '0xe242e54155b1abc71fc118065270cecaaf8b7768' as `0x${string}`, - value: BigInt('1000000000000000000'), // 1 ETH - data: '0x' as `0x${string}`, // Add missing data field - nonce: 0, - maxFeePerGas: BigInt('20000000000'), // 20 gwei - maxPriorityFeePerGas: BigInt('2000000000'), // 2 gwei - gas: BigInt('21000'), - chainId: 11155111, // Sepolia - }, - category: 'sepolia', - }, + { + name: 'Rinkeby testnet (historical)', + tx: { + type: 'eip1559', + to: '0xe242e54155b1abc71fc118065270cecaaf8b7768' as `0x${string}`, + value: BigInt('1000000000000000000'), // 1 ETH + data: '0x' as `0x${string}`, // Add missing data field + nonce: 0, + maxFeePerGas: BigInt('20000000000'), // 20 gwei + maxPriorityFeePerGas: BigInt('2000000000'), // 2 gwei + gas: BigInt('21000'), + chainId: 4, // Rinkeby + }, + category: 'rinkeby', + }, + { + name: 'Goerli testnet', + tx: { + type: 'eip1559', + to: '0xe242e54155b1abc71fc118065270cecaaf8b7768' as `0x${string}`, + value: BigInt('1000000000000000000'), // 1 ETH + data: '0x' as `0x${string}`, // Add missing data field + nonce: 0, + maxFeePerGas: BigInt('20000000000'), // 20 gwei + maxPriorityFeePerGas: BigInt('2000000000'), // 2 gwei + gas: BigInt('21000'), + chainId: 5, // Goerli + }, + category: 'goerli', + }, + { + name: 'Sepolia testnet', + tx: { + type: 'eip1559', + to: '0xe242e54155b1abc71fc118065270cecaaf8b7768' as `0x${string}`, + value: BigInt('1000000000000000000'), // 1 ETH + data: '0x' as `0x${string}`, // Add missing data field + nonce: 0, + maxFeePerGas: BigInt('20000000000'), // 20 gwei + maxPriorityFeePerGas: BigInt('2000000000'), // 2 gwei + gas: BigInt('21000'), + chainId: 11155111, // Sepolia + }, + category: 'sepolia', + }, ] /** * Test payload size boundaries to ensure proper handling of large transactions */ export const PAYLOAD_SIZE_VECTORS: TestVector[] = [ - { - name: 'No data payload', - tx: { - type: 'eip1559', - to: '0xe242e54155b1abc71fc118065270cecaaf8b7768' as `0x${string}`, - value: BigInt('1000000000000000000'), // 1 ETH - data: '0x' as `0x${string}`, // Fix undefined data field - nonce: 0, - maxFeePerGas: BigInt('20000000000'), // 20 gwei - maxPriorityFeePerGas: BigInt('2000000000'), // 2 gwei - gas: BigInt('21000'), - chainId: 1, - }, - category: 'no-data', - }, - { - name: 'Small data payload (32 bytes)', - tx: { - type: 'eip1559', - to: '0xe242e54155b1abc71fc118065270cecaaf8b7768' as `0x${string}`, - value: BigInt('1000000000000000000'), // 1 ETH - nonce: 0, - maxFeePerGas: BigInt('30000000000'), // 30 gwei - maxPriorityFeePerGas: BigInt('2000000000'), // 2 gwei - gas: BigInt('50000'), - chainId: 1, - data: `0x${'00'.repeat(32)}` as `0x${string}`, - }, - category: 'small-data', - }, - { - name: 'Medium data payload (256 bytes)', - tx: { - type: 'eip1559', - to: '0xe242e54155b1abc71fc118065270cecaaf8b7768' as `0x${string}`, - value: BigInt('1000000000000000000'), // 1 ETH - nonce: 0, - maxFeePerGas: BigInt('30000000000'), // 30 gwei - maxPriorityFeePerGas: BigInt('2000000000'), // 2 gwei - gas: BigInt('100000'), - chainId: 1, - data: `0x${'ab'.repeat(256)}` as `0x${string}`, - }, - category: 'medium-data', - }, - { - name: 'Large data payload (1024 bytes)', - tx: { - type: 'eip1559', - to: '0xe242e54155b1abc71fc118065270cecaaf8b7768' as `0x${string}`, - value: BigInt('1000000000000000000'), // 1 ETH - nonce: 0, - maxFeePerGas: BigInt('50000000000'), // 50 gwei - maxPriorityFeePerGas: BigInt('3000000000'), // 3 gwei - gas: BigInt('500000'), - chainId: 1, - data: `0x${'cd'.repeat(1024)}` as `0x${string}`, - }, - category: 'large-data', - }, - { - name: 'Very large data payload (2000 bytes)', - tx: { - type: 'eip1559', - to: '0xe242e54155b1abc71fc118065270cecaaf8b7768' as `0x${string}`, - value: BigInt('1000000000000000000'), // 1 ETH - nonce: 0, - maxFeePerGas: BigInt('50000000000'), // 50 gwei - maxPriorityFeePerGas: BigInt('3000000000'), // 3 gwei - gas: BigInt('1000000'), - chainId: 1, - data: `0x${'ef'.repeat(2000)}` as `0x${string}`, - }, - category: 'very-large-data', - }, + { + name: 'No data payload', + tx: { + type: 'eip1559', + to: '0xe242e54155b1abc71fc118065270cecaaf8b7768' as `0x${string}`, + value: BigInt('1000000000000000000'), // 1 ETH + data: '0x' as `0x${string}`, // Fix undefined data field + nonce: 0, + maxFeePerGas: BigInt('20000000000'), // 20 gwei + maxPriorityFeePerGas: BigInt('2000000000'), // 2 gwei + gas: BigInt('21000'), + chainId: 1, + }, + category: 'no-data', + }, + { + name: 'Small data payload (32 bytes)', + tx: { + type: 'eip1559', + to: '0xe242e54155b1abc71fc118065270cecaaf8b7768' as `0x${string}`, + value: BigInt('1000000000000000000'), // 1 ETH + nonce: 0, + maxFeePerGas: BigInt('30000000000'), // 30 gwei + maxPriorityFeePerGas: BigInt('2000000000'), // 2 gwei + gas: BigInt('50000'), + chainId: 1, + data: `0x${'00'.repeat(32)}` as `0x${string}`, + }, + category: 'small-data', + }, + { + name: 'Medium data payload (256 bytes)', + tx: { + type: 'eip1559', + to: '0xe242e54155b1abc71fc118065270cecaaf8b7768' as `0x${string}`, + value: BigInt('1000000000000000000'), // 1 ETH + nonce: 0, + maxFeePerGas: BigInt('30000000000'), // 30 gwei + maxPriorityFeePerGas: BigInt('2000000000'), // 2 gwei + gas: BigInt('100000'), + chainId: 1, + data: `0x${'ab'.repeat(256)}` as `0x${string}`, + }, + category: 'medium-data', + }, + { + name: 'Large data payload (1024 bytes)', + tx: { + type: 'eip1559', + to: '0xe242e54155b1abc71fc118065270cecaaf8b7768' as `0x${string}`, + value: BigInt('1000000000000000000'), // 1 ETH + nonce: 0, + maxFeePerGas: BigInt('50000000000'), // 50 gwei + maxPriorityFeePerGas: BigInt('3000000000'), // 3 gwei + gas: BigInt('500000'), + chainId: 1, + data: `0x${'cd'.repeat(1024)}` as `0x${string}`, + }, + category: 'large-data', + }, + { + name: 'Very large data payload (2000 bytes)', + tx: { + type: 'eip1559', + to: '0xe242e54155b1abc71fc118065270cecaaf8b7768' as `0x${string}`, + value: BigInt('1000000000000000000'), // 1 ETH + nonce: 0, + maxFeePerGas: BigInt('50000000000'), // 50 gwei + maxPriorityFeePerGas: BigInt('3000000000'), // 3 gwei + gas: BigInt('1000000'), + chainId: 1, + data: `0x${'ef'.repeat(2000)}` as `0x${string}`, + }, + category: 'very-large-data', + }, ] /** * Comprehensive boundary condition test vectors */ export const BOUNDARY_CONDITION_VECTORS: TestVector[] = [ - { - name: 'Maximum nonce (2^32-1)', - tx: { - type: 'legacy', - to: '0xe242e54155b1abc71fc118065270cecaaf8b7768' as `0x${string}`, - value: BigInt('1000000000000000'), // 1 ETH - data: '0x' as `0x${string}`, // Add missing data field - nonce: 2 ** 32 - 1, - gasPrice: BigInt('20000000000'), - gas: BigInt('21000'), - chainId: 1, - }, - category: 'max-nonce', - }, - { - name: 'Maximum safe chain ID', - tx: { - type: 'eip1559', - to: '0xe242e54155b1abc71fc118065270cecaaf8b7768' as `0x${string}`, - value: BigInt('1000000000000000000'), - data: '0x' as `0x${string}`, // Add missing data field - nonce: 0, - maxFeePerGas: BigInt('20000000000'), - maxPriorityFeePerGas: BigInt('2000000000'), - gas: BigInt('21000'), - chainId: Number.MAX_SAFE_INTEGER, - }, - category: 'max-chain-id', - }, - { - name: 'Minimum gas limit (21000)', - tx: { - type: 'eip1559', - to: '0xe242e54155b1abc71fc118065270cecaaf8b7768' as `0x${string}`, - value: BigInt(1), - data: '0x' as `0x${string}`, // Add missing data field - nonce: 0, - maxFeePerGas: BigInt('1'), // 1 wei - maxPriorityFeePerGas: BigInt('1'), - gas: BigInt('21000'), // Minimum for ETH transfer - chainId: 1, - }, - category: 'min-gas', - }, - { - name: 'Maximum gas limit', - tx: { - type: 'eip1559', - to: '0xe242e54155b1abc71fc118065270cecaaf8b7768' as `0x${string}`, - value: BigInt('1000000000000000000'), - data: '0x' as `0x${string}`, // Add missing data field - nonce: 0, - maxFeePerGas: BigInt('100000000000'), // 100 gwei - maxPriorityFeePerGas: BigInt('5000000000'), // 5 gwei - gas: BigInt('30000000'), // Block gas limit - chainId: 1, - }, - category: 'max-gas', - }, + { + name: 'Maximum nonce (2^32-1)', + tx: { + type: 'legacy', + to: '0xe242e54155b1abc71fc118065270cecaaf8b7768' as `0x${string}`, + value: BigInt('1000000000000000'), // 1 ETH + data: '0x' as `0x${string}`, // Add missing data field + nonce: 2 ** 32 - 1, + gasPrice: BigInt('20000000000'), + gas: BigInt('21000'), + chainId: 1, + }, + category: 'max-nonce', + }, + { + name: 'Maximum safe chain ID', + tx: { + type: 'eip1559', + to: '0xe242e54155b1abc71fc118065270cecaaf8b7768' as `0x${string}`, + value: BigInt('1000000000000000000'), + data: '0x' as `0x${string}`, // Add missing data field + nonce: 0, + maxFeePerGas: BigInt('20000000000'), + maxPriorityFeePerGas: BigInt('2000000000'), + gas: BigInt('21000'), + chainId: Number.MAX_SAFE_INTEGER, + }, + category: 'max-chain-id', + }, + { + name: 'Minimum gas limit (21000)', + tx: { + type: 'eip1559', + to: '0xe242e54155b1abc71fc118065270cecaaf8b7768' as `0x${string}`, + value: BigInt(1), + data: '0x' as `0x${string}`, // Add missing data field + nonce: 0, + maxFeePerGas: BigInt('1'), // 1 wei + maxPriorityFeePerGas: BigInt('1'), + gas: BigInt('21000'), // Minimum for ETH transfer + chainId: 1, + }, + category: 'min-gas', + }, + { + name: 'Maximum gas limit', + tx: { + type: 'eip1559', + to: '0xe242e54155b1abc71fc118065270cecaaf8b7768' as `0x${string}`, + value: BigInt('1000000000000000000'), + data: '0x' as `0x${string}`, // Add missing data field + nonce: 0, + maxFeePerGas: BigInt('100000000000'), // 100 gwei + maxPriorityFeePerGas: BigInt('5000000000'), // 5 gwei + gas: BigInt('30000000'), // Block gas limit + chainId: 1, + }, + category: 'max-gas', + }, ] /** * Test specific transaction patterns from real-world usage */ export const REAL_WORLD_PATTERN_VECTORS: TestVector[] = [ - { - name: 'DeFi approval transaction', - tx: { - type: 'eip1559', - to: '0xA0b86a33E6417c14f8c9C4E5659dF7a08D2C65c3' as `0x${string}`, // Example DeFi contract - value: BigInt(0), - data: '0x095ea7b3000000000000000000000000742d35cc6b2d9e9a3b7b7b4a7b0b2b7b0b2b7b0bffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff' as `0x${string}`, // approve(spender, amount) - nonce: 5, - maxFeePerGas: BigInt('25000000000'), // 25 gwei - maxPriorityFeePerGas: BigInt('2000000000'), // 2 gwei - gas: BigInt('46000'), // Typical for ERC20 approval - chainId: 1, - }, - category: 'defi-approval', - }, - { - name: 'NFT minting transaction', - tx: { - type: 'eip1559', - to: '0xe242e54155b1abc71fc118065270cecaaf8b7768' as `0x${string}`, - value: BigInt('50000000000000000'), // 0.05 ETH mint fee - data: '0x40c10f19000000000000000000000000742d35cc6b2d9e9a3b7b7b4a7b0b2b7b0b2b7b0b0000000000000000000000000000000000000000000000000000000000000001' as `0x${string}`, // mint(to, tokenId) - nonce: 10, - maxFeePerGas: BigInt('30000000000'), // 30 gwei - maxPriorityFeePerGas: BigInt('3000000000'), // 3 gwei - gas: BigInt('200000'), // Higher gas for NFT mint - chainId: 1, - }, - category: 'nft-mint', - }, - { - name: 'Multi-send transaction', - tx: { - type: 'eip1559', - to: '0xe242e54155b1abc71fc118065270cecaaf8b7768' as `0x${string}`, - value: BigInt(0), - data: `0x8d80ff0a${'00'.repeat(500)}` as `0x${string}`, // multiSend with batch data - nonce: 15, - maxFeePerGas: BigInt('40000000000'), // 40 gwei - maxPriorityFeePerGas: BigInt('4000000000'), // 4 gwei - gas: BigInt('800000'), // High gas for multi-send - chainId: 1, - }, - category: 'multi-send', - }, + { + name: 'DeFi approval transaction', + tx: { + type: 'eip1559', + to: '0xA0b86a33E6417c14f8c9C4E5659dF7a08D2C65c3' as `0x${string}`, // Example DeFi contract + value: BigInt(0), + data: '0x095ea7b3000000000000000000000000742d35cc6b2d9e9a3b7b7b4a7b0b2b7b0b2b7b0bffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff' as `0x${string}`, // approve(spender, amount) + nonce: 5, + maxFeePerGas: BigInt('25000000000'), // 25 gwei + maxPriorityFeePerGas: BigInt('2000000000'), // 2 gwei + gas: BigInt('46000'), // Typical for ERC20 approval + chainId: 1, + }, + category: 'defi-approval', + }, + { + name: 'NFT minting transaction', + tx: { + type: 'eip1559', + to: '0xe242e54155b1abc71fc118065270cecaaf8b7768' as `0x${string}`, + value: BigInt('50000000000000000'), // 0.05 ETH mint fee + data: '0x40c10f19000000000000000000000000742d35cc6b2d9e9a3b7b7b4a7b0b2b7b0b2b7b0b0000000000000000000000000000000000000000000000000000000000000001' as `0x${string}`, // mint(to, tokenId) + nonce: 10, + maxFeePerGas: BigInt('30000000000'), // 30 gwei + maxPriorityFeePerGas: BigInt('3000000000'), // 3 gwei + gas: BigInt('200000'), // Higher gas for NFT mint + chainId: 1, + }, + category: 'nft-mint', + }, + { + name: 'Multi-send transaction', + tx: { + type: 'eip1559', + to: '0xe242e54155b1abc71fc118065270cecaaf8b7768' as `0x${string}`, + value: BigInt(0), + data: `0x8d80ff0a${'00'.repeat(500)}` as `0x${string}`, // multiSend with batch data + nonce: 15, + maxFeePerGas: BigInt('40000000000'), // 40 gwei + maxPriorityFeePerGas: BigInt('4000000000'), // 4 gwei + gas: BigInt('800000'), // High gas for multi-send + chainId: 1, + }, + category: 'multi-send', + }, ] /** * All comprehensive test vectors combined for easy access */ export const ALL_COMPREHENSIVE_VECTORS: TestVector[] = [ - ...LEGACY_VECTORS, - ...EIP1559_TEST_VECTORS, - ...EIP2930_TEST_VECTORS, - ...EIP7702_TEST_VECTORS, - ...EDGE_CASE_TEST_VECTORS, - ...DERIVATION_PATH_VECTORS, - ...NETWORK_SPECIFIC_VECTORS, - ...PAYLOAD_SIZE_VECTORS, - ...BOUNDARY_CONDITION_VECTORS, - ...REAL_WORLD_PATTERN_VECTORS, + ...LEGACY_VECTORS, + ...EIP1559_TEST_VECTORS, + ...EIP2930_TEST_VECTORS, + ...EIP7702_TEST_VECTORS, + ...EDGE_CASE_TEST_VECTORS, + ...DERIVATION_PATH_VECTORS, + ...NETWORK_SPECIFIC_VECTORS, + ...PAYLOAD_SIZE_VECTORS, + ...BOUNDARY_CONDITION_VECTORS, + ...REAL_WORLD_PATTERN_VECTORS, ] /** * Get vectors by category for targeted testing */ export function getVectorsByCategory(category: string): TestVector[] { - return ALL_COMPREHENSIVE_VECTORS.filter( - (vector) => vector.category === category, - ) + return ALL_COMPREHENSIVE_VECTORS.filter( + (vector) => vector.category === category, + ) } /** * Get a specific number of vectors from each transaction type for balanced testing */ export function getBalancedTestVectors(perType = 3): TestVector[] { - const legacyVectors = LEGACY_VECTORS.slice(0, perType) - const eip1559Vectors = EIP1559_TEST_VECTORS.slice(0, perType) - const eip2930Vectors = EIP2930_TEST_VECTORS.slice(0, perType) - const eip7702Vectors = EIP7702_TEST_VECTORS.slice(0, perType) + const legacyVectors = LEGACY_VECTORS.slice(0, perType) + const eip1559Vectors = EIP1559_TEST_VECTORS.slice(0, perType) + const eip2930Vectors = EIP2930_TEST_VECTORS.slice(0, perType) + const eip7702Vectors = EIP7702_TEST_VECTORS.slice(0, perType) - return [ - ...legacyVectors, - ...eip1559Vectors, - ...eip2930Vectors, - ...eip7702Vectors, - ] + return [ + ...legacyVectors, + ...eip1559Vectors, + ...eip2930Vectors, + ...eip7702Vectors, + ] } /** * Get vectors for boundary testing specifically */ export function getBoundaryTestVectors(): TestVector[] { - return [ - ...BOUNDARY_CONDITION_VECTORS, - ...EDGE_CASE_TEST_VECTORS, - ...PAYLOAD_SIZE_VECTORS, - ] + return [ + ...BOUNDARY_CONDITION_VECTORS, + ...EDGE_CASE_TEST_VECTORS, + ...PAYLOAD_SIZE_VECTORS, + ] } /** * Get vectors for network compatibility testing */ export function getNetworkTestVectors(): TestVector[] { - return [ - ...NETWORK_SPECIFIC_VECTORS, - // Add some edge cases with different networks - ...EDGE_CASE_TEST_VECTORS.filter((v) => v.category?.includes('chain')), - ] + return [ + ...NETWORK_SPECIFIC_VECTORS, + // Add some edge cases with different networks + ...EDGE_CASE_TEST_VECTORS.filter((v) => v.category?.includes('chain')), + ] } diff --git a/packages/sdk/src/__test__/e2e/solana/addresses.test.ts b/packages/sdk/src/__test__/e2e/solana/addresses.test.ts index bbb2423b..5652ee5a 100644 --- a/packages/sdk/src/__test__/e2e/solana/addresses.test.ts +++ b/packages/sdk/src/__test__/e2e/solana/addresses.test.ts @@ -5,40 +5,40 @@ import { fetchSolanaAddresses } from '../../../api/addresses' import { setupClient } from '../../utils/setup' describe('Solana Addresses', () => { - test('pair', async () => { - const isPaired = await setupClient() - if (!isPaired) { - const secret = question('Please enter the pairing secret: ') - await pair(secret.toUpperCase()) - } - }) + test('pair', async () => { + const isPaired = await setupClient() + if (!isPaired) { + const secret = question('Please enter the pairing secret: ') + await pair(secret.toUpperCase()) + } + }) - test('Should fetch a single Solana Ed25519 public key using fetchSolanaAddresses', async () => { - const addresses = await fetchSolanaAddresses({ - n: 10, - }) + test('Should fetch a single Solana Ed25519 public key using fetchSolanaAddresses', async () => { + const addresses = await fetchSolanaAddresses({ + n: 10, + }) - const addrs = addresses - .filter((addr) => { - try { - // Check if the key is a valid Ed25519 public key - const pk = new PublicKey(addr) - const isOnCurve = PublicKey.isOnCurve(pk.toBytes()) - expect(isOnCurve).toBe(true) - return true - } catch (e) { - console.error('Invalid Solana public key:', e) - return false - } - }) - .map((addr) => { - const pk = new PublicKey(addr) - return pk.toBase58() - }) + const addrs = addresses + .filter((addr) => { + try { + // Check if the key is a valid Ed25519 public key + const pk = new PublicKey(addr) + const isOnCurve = PublicKey.isOnCurve(pk.toBytes()) + expect(isOnCurve).toBe(true) + return true + } catch (e) { + console.error('Invalid Solana public key:', e) + return false + } + }) + .map((addr) => { + const pk = new PublicKey(addr) + return pk.toBase58() + }) - // Ensure we got at least one valid address - expect(addrs.length).toBeGreaterThan(0) - // Ensure none of the addresses start with '11111' - expect(addrs.every((addr) => !addr.startsWith('11111'))).toBe(true) - }) + // Ensure we got at least one valid address + expect(addrs.length).toBeGreaterThan(0) + // Ensure none of the addresses start with '11111' + expect(addrs.every((addr) => !addr.startsWith('11111'))).toBe(true) + }) }) diff --git a/packages/sdk/src/__test__/e2e/xpub.test.ts b/packages/sdk/src/__test__/e2e/xpub.test.ts index 9f69272b..d4e8b5d4 100644 --- a/packages/sdk/src/__test__/e2e/xpub.test.ts +++ b/packages/sdk/src/__test__/e2e/xpub.test.ts @@ -2,28 +2,28 @@ import { fetchBtcXpub, fetchBtcYpub, fetchBtcZpub } from '../../api' import { setupClient } from '../utils/setup' describe('XPUB', () => { - beforeAll(async () => { - await setupClient() - }) + beforeAll(async () => { + await setupClient() + }) - test('fetchBtcXpub returns xpub', async () => { - const xpub = await fetchBtcXpub() - expect(xpub).toBeTruthy() - expect(xpub.startsWith('xpub')).toBe(true) - expect(xpub.length).toBeGreaterThan(100) - }) + test('fetchBtcXpub returns xpub', async () => { + const xpub = await fetchBtcXpub() + expect(xpub).toBeTruthy() + expect(xpub.startsWith('xpub')).toBe(true) + expect(xpub.length).toBeGreaterThan(100) + }) - test('fetchBtcYpub returns ypub', async () => { - const ypub = await fetchBtcYpub() - expect(ypub).toBeTruthy() - expect(ypub.startsWith('ypub')).toBe(true) - expect(ypub.length).toBeGreaterThan(100) - }) + test('fetchBtcYpub returns ypub', async () => { + const ypub = await fetchBtcYpub() + expect(ypub).toBeTruthy() + expect(ypub.startsWith('ypub')).toBe(true) + expect(ypub.length).toBeGreaterThan(100) + }) - test('fetchBtcZpub returns zpub', async () => { - const zpub = await fetchBtcZpub() - expect(zpub).toBeTruthy() - expect(zpub.startsWith('zpub')).toBe(true) - expect(zpub.length).toBeGreaterThan(100) - }) + test('fetchBtcZpub returns zpub', async () => { + const zpub = await fetchBtcZpub() + expect(zpub).toBeTruthy() + expect(zpub.startsWith('zpub')).toBe(true) + expect(zpub.length).toBeGreaterThan(100) + }) }) diff --git a/packages/sdk/src/__test__/integration/__mocks__/4byte.ts b/packages/sdk/src/__test__/integration/__mocks__/4byte.ts index 4ec088c5..ebb80cee 100644 --- a/packages/sdk/src/__test__/integration/__mocks__/4byte.ts +++ b/packages/sdk/src/__test__/integration/__mocks__/4byte.ts @@ -1,95 +1,95 @@ export const fourbyteResponse0xa9059cbb = { - results: [ - { - bytes_signature: '©œ»', - created_at: '2016-07-09T03:58:28.234977Z', - hex_signature: '0xa9059cbb', - id: 145, - text_signature: 'transfer(address,uint256)', - }, - { - bytes_signature: '©œ»', - created_at: '2018-05-11T08:39:29.708250Z', - hex_signature: '0xa9059cbb', - id: 31780, - text_signature: 'many_msg_babbage(bytes1)', - }, - { - bytes_signature: '©œ»', - created_at: '2019-03-22T19:13:17.314877Z', - hex_signature: '0xa9059cbb', - id: 161159, - text_signature: 'transfer(bytes4[9],bytes5[6],int48[11])', - }, - { - bytes_signature: '©œ»', - created_at: '2021-10-20T05:29:13.555535Z', - hex_signature: '0xa9059cbb', - id: 313067, - text_signature: 'func_2093253501(bytes)', - }, - ], + results: [ + { + bytes_signature: '©œ»', + created_at: '2016-07-09T03:58:28.234977Z', + hex_signature: '0xa9059cbb', + id: 145, + text_signature: 'transfer(address,uint256)', + }, + { + bytes_signature: '©œ»', + created_at: '2018-05-11T08:39:29.708250Z', + hex_signature: '0xa9059cbb', + id: 31780, + text_signature: 'many_msg_babbage(bytes1)', + }, + { + bytes_signature: '©œ»', + created_at: '2019-03-22T19:13:17.314877Z', + hex_signature: '0xa9059cbb', + id: 161159, + text_signature: 'transfer(bytes4[9],bytes5[6],int48[11])', + }, + { + bytes_signature: '©œ»', + created_at: '2021-10-20T05:29:13.555535Z', + hex_signature: '0xa9059cbb', + id: 313067, + text_signature: 'func_2093253501(bytes)', + }, + ], } export const fourbyteResponse0x38ed1739 = { - results: [ - { - bytes_signature: '8í9', - created_at: '2020-08-09T08:56:14.110995Z', - hex_signature: '0x38ed1739', - id: 171806, - text_signature: - 'swapExactTokensForTokens(uint256,uint256,address[],address,uint256)', - }, - ], + results: [ + { + bytes_signature: '8í9', + created_at: '2020-08-09T08:56:14.110995Z', + hex_signature: '0x38ed1739', + id: 171806, + text_signature: + 'swapExactTokensForTokens(uint256,uint256,address[],address,uint256)', + }, + ], } export const fourbyteResponseac9650d8 = { - results: [ - { - id: 134730, - created_at: '2018-10-13T08:40:29.456228Z', - text_signature: 'multicall(bytes[])', - hex_signature: '0xac9650d8', - bytes_signature: '¬\x96PØ', - }, - ], + results: [ + { + id: 134730, + created_at: '2018-10-13T08:40:29.456228Z', + text_signature: 'multicall(bytes[])', + hex_signature: '0xac9650d8', + bytes_signature: '¬\x96PØ', + }, + ], } export const fourbyteResponse0c49ccbe = { - results: [ - { - id: 186682, - created_at: '2021-05-09T03:48:17.627742Z', - text_signature: - 'decreaseLiquidity((uint256,uint128,uint256,uint256,uint256))', - hex_signature: '0x0c49ccbe', - bytes_signature: '\\fI̾', - }, - ], + results: [ + { + id: 186682, + created_at: '2021-05-09T03:48:17.627742Z', + text_signature: + 'decreaseLiquidity((uint256,uint128,uint256,uint256,uint256))', + hex_signature: '0x0c49ccbe', + bytes_signature: '\\fI̾', + }, + ], } export const fourbyteResponsefc6f7865 = { - results: [ - { - id: 186681, - created_at: '2021-05-09T03:48:17.621683Z', - text_signature: 'collect((uint256,address,uint128,uint128))', - hex_signature: '0xfc6f7865', - bytes_signature: 'üoxe', - }, - ], + results: [ + { + id: 186681, + created_at: '2021-05-09T03:48:17.621683Z', + text_signature: 'collect((uint256,address,uint128,uint128))', + hex_signature: '0xfc6f7865', + bytes_signature: 'üoxe', + }, + ], } export const fourbyteResponse0x6a761202 = { - results: [ - { - id: 169422, - created_at: '2020-01-28T10:40:07.614936Z', - text_signature: - 'execTransaction(address,uint256,bytes,uint8,uint256,uint256,uint256,address,address,bytes)', - hex_signature: '0x6a761202', - bytes_signature: 'jv\\u0012\\u0002', - }, - ], + results: [ + { + id: 169422, + created_at: '2020-01-28T10:40:07.614936Z', + text_signature: + 'execTransaction(address,uint256,bytes,uint8,uint256,uint256,uint256,address,address,bytes)', + hex_signature: '0x6a761202', + bytes_signature: 'jv\\u0012\\u0002', + }, + ], } diff --git a/packages/sdk/src/__test__/integration/__mocks__/addKvRecords.json b/packages/sdk/src/__test__/integration/__mocks__/addKvRecords.json index 011fd8a6..e19abf7c 100644 --- a/packages/sdk/src/__test__/integration/__mocks__/addKvRecords.json +++ b/packages/sdk/src/__test__/integration/__mocks__/addKvRecords.json @@ -1,4 +1,4 @@ { - "status": 200, - "message": "0100d04ed4310d810055eb0c33ba04d9631dde8d79d0d92ee01c9bf716e06af3f44b77c2082b4862d02135a928631e1f54844a0f90fee7c79cde79e72eddb484b83777e60b450af882384cdbb1d173e1d01b9c405d7617d0da2932f66d90b80e3acfd78d290775cfcad12160b4b9cf586271475d047b033af9d03a6a69f52ce7cb02152c9b3f0f19aae2b30362352c07aa7fc23d464765cf7dafc40fb9d5657f8bdfcf0e23f7d8219327556ab684d3c6857ba9c4c7946d48e3977f05dab5aaaaec16e6011bf9ce98f8b0016de91da0ff89f6877a2c7f93f9830588fbede08635da18345e5a7a7bd0e85cfbf751f28269a1d47b90ad3e673eb9ba1b8676ae3f12aac74cbf20c32a11f812e10e59a612c5af770a9116f6a673fb62286c84c923610dd103d92ad713225912bd65c797243a9a70987b51729643761b7b3f8401ab062f72efc1d3d6a25708c8805538a8af0c43de7541c3ad384c09555e4015c7c008904a26ce05b38bff4cc7736e47bfec550ec66a64d3b0cb0f025f32aa8cc9f5d25c7fd0dbee81ec90136ae5a45fdcef180a779f03025fad97d035d02f2c2fa7f235f917992db038528f28b994e04af5b049ec275142d57b176346e3a97074fd94a8c1726ed86fe9f44e3db4b608948ea830d845f05100fa70e507af5b0b6a810548b23bf744ed140295525e667cf1a529645add1d02795f58b9b3ca6c47e40ec99ada4725d0094cc2fdd76448cf45f4644845f3c86f5d50643679c94aba63e6c78d877cc868701176cccb5930265c84858e7d5ea20a5f4ca35bfd4b28687bf6b67afea7d6c3aa7d73d586b074644d3469aeec086d9f91a56c40d3f5eff4466ef0c866f279b4c3d0d6d0cf7d35d052fd2e8626b9f2a966cd90efd49c8c9894778f06582a5f11994cb3055779dc1b09c401b622af9ff9797a254568425c93a06b9dac8f97482ad282e2d4f53497076952f7c4ebb39946a4bd360ede9472d6401811f2189418f5513cdeb898dffc463d0071a68a91a40740967a39386eb643519603ce2ba94725c61367ad31ee1e180d74148d81f52f6fca491d83af84d124459be161e259ea2088c4f5f0c157b54aaa0e84295acf0bcf42d079c7397c0712356c451964bb4f52c0b2881d7bae8f021e69f474bb92cc2e473ef564cac4570afdc57c6eb45ce4dcb18f4f8fec4c0ad29b8dc93044fc2c7c46495d0d9be6ff25b5531917b324b60311c982fc2de3149685d3ba4eecc567f2018cf90b7436bd0d3b3d29abd8babffd26df94abb39278e92b7f62d2c7bdfea7de442acdbdd578ba233768e512081bd148445c87704a44353e1dfd770d4aa1f0ef2194f085ecdbfd1cfe3acd46d8457f45e52d2e5e27607702f2261a51a91fb23ce0890ad0290166993ed4642cb040e6952b1f8c4fa50685e98264e41c8cc747dcc9f149d229b5462a5643fd240b01c59a1453c39795b619fcdf0a9bf16cb280fb27ab7fd9b2fc85d330211f4b4c224e59c1f69698bf38613f8ab6ecf868748fe57d7787b15f45871f1e9e893a57ecc29331147712c1be855526e5d036745ef75e154149d441a6c7ace5a3e787a97577b09993d7423ed08ea0eb9a654cbcefc1afc62abc88028241e77625b1489f7cede86e905a807a6833f1fa223f6a7a02cc3e927cfcf7d230b188717788190ddc716a6586688ed6d562890c6e5af8142f272709c8c3bb2221030ae6d1c8937972c32782d32c9a871723107c7e6b5c5cf610f4a77d8fa477b27f60beb67cf9361acd46bb7d7934e9705753eee8a7f02e9769e72785094c283a9c85a5002d05c2d57670194cffb0be4409608560bcb8fb8fe8eb1d3b1975279f9b76c36653276498210a388aa7fe8e312ffaf97d71ba0c190471b0efa79dea111150fd71bce2386811eecc4537d2942a24a2f83ab4e3e764da9864c5d1fb51ae342d9b289e1616fad4c6e23575b88004fc254beed53e474d8400b81cf13e208475fae3292cd7cf60dec5346b4bb4cba008d3ff52ac8d8f748331243821e86cc8addd96da026268e780f170189d4a57098ac7f474eda8d94cad1fb0967010e37ccb28603a21bed6efed3f9295cb6cc7406758a9ca0350535523d07c50b1197dbcea31db87c03f5ef3c9a5580c598483124d76ae7f38cc76fb94c07bbd236c7c7294fd9b80f280ee02102399eb8e4e0c66dc12cfd4f61137f9e7def0af4db1f88b40b1888a09f18b60ac17f2469f47e7d6eeb98e25638f89594b20a87217ab15ea9c9ac7530ce10af64d53601d15438f330afd80b55f717bc2f5d86d3811902fc4eb6ee8d4b1aa2d86ff2788d1b1833f6581bc5f1748579c2999aaa51e56364a3e5aad2172e7d5de74953e6ab1b5de74077c8267f92dec39abe2f1f31163cab9efbe1b5852b2ad891fe289ec68919bf422586dfc7328afcd5dc6a27f3d9f87e3f3e247d5857963f0083e2deea20600000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000066e17acd" + "status": 200, + "message": "0100d04ed4310d810055eb0c33ba04d9631dde8d79d0d92ee01c9bf716e06af3f44b77c2082b4862d02135a928631e1f54844a0f90fee7c79cde79e72eddb484b83777e60b450af882384cdbb1d173e1d01b9c405d7617d0da2932f66d90b80e3acfd78d290775cfcad12160b4b9cf586271475d047b033af9d03a6a69f52ce7cb02152c9b3f0f19aae2b30362352c07aa7fc23d464765cf7dafc40fb9d5657f8bdfcf0e23f7d8219327556ab684d3c6857ba9c4c7946d48e3977f05dab5aaaaec16e6011bf9ce98f8b0016de91da0ff89f6877a2c7f93f9830588fbede08635da18345e5a7a7bd0e85cfbf751f28269a1d47b90ad3e673eb9ba1b8676ae3f12aac74cbf20c32a11f812e10e59a612c5af770a9116f6a673fb62286c84c923610dd103d92ad713225912bd65c797243a9a70987b51729643761b7b3f8401ab062f72efc1d3d6a25708c8805538a8af0c43de7541c3ad384c09555e4015c7c008904a26ce05b38bff4cc7736e47bfec550ec66a64d3b0cb0f025f32aa8cc9f5d25c7fd0dbee81ec90136ae5a45fdcef180a779f03025fad97d035d02f2c2fa7f235f917992db038528f28b994e04af5b049ec275142d57b176346e3a97074fd94a8c1726ed86fe9f44e3db4b608948ea830d845f05100fa70e507af5b0b6a810548b23bf744ed140295525e667cf1a529645add1d02795f58b9b3ca6c47e40ec99ada4725d0094cc2fdd76448cf45f4644845f3c86f5d50643679c94aba63e6c78d877cc868701176cccb5930265c84858e7d5ea20a5f4ca35bfd4b28687bf6b67afea7d6c3aa7d73d586b074644d3469aeec086d9f91a56c40d3f5eff4466ef0c866f279b4c3d0d6d0cf7d35d052fd2e8626b9f2a966cd90efd49c8c9894778f06582a5f11994cb3055779dc1b09c401b622af9ff9797a254568425c93a06b9dac8f97482ad282e2d4f53497076952f7c4ebb39946a4bd360ede9472d6401811f2189418f5513cdeb898dffc463d0071a68a91a40740967a39386eb643519603ce2ba94725c61367ad31ee1e180d74148d81f52f6fca491d83af84d124459be161e259ea2088c4f5f0c157b54aaa0e84295acf0bcf42d079c7397c0712356c451964bb4f52c0b2881d7bae8f021e69f474bb92cc2e473ef564cac4570afdc57c6eb45ce4dcb18f4f8fec4c0ad29b8dc93044fc2c7c46495d0d9be6ff25b5531917b324b60311c982fc2de3149685d3ba4eecc567f2018cf90b7436bd0d3b3d29abd8babffd26df94abb39278e92b7f62d2c7bdfea7de442acdbdd578ba233768e512081bd148445c87704a44353e1dfd770d4aa1f0ef2194f085ecdbfd1cfe3acd46d8457f45e52d2e5e27607702f2261a51a91fb23ce0890ad0290166993ed4642cb040e6952b1f8c4fa50685e98264e41c8cc747dcc9f149d229b5462a5643fd240b01c59a1453c39795b619fcdf0a9bf16cb280fb27ab7fd9b2fc85d330211f4b4c224e59c1f69698bf38613f8ab6ecf868748fe57d7787b15f45871f1e9e893a57ecc29331147712c1be855526e5d036745ef75e154149d441a6c7ace5a3e787a97577b09993d7423ed08ea0eb9a654cbcefc1afc62abc88028241e77625b1489f7cede86e905a807a6833f1fa223f6a7a02cc3e927cfcf7d230b188717788190ddc716a6586688ed6d562890c6e5af8142f272709c8c3bb2221030ae6d1c8937972c32782d32c9a871723107c7e6b5c5cf610f4a77d8fa477b27f60beb67cf9361acd46bb7d7934e9705753eee8a7f02e9769e72785094c283a9c85a5002d05c2d57670194cffb0be4409608560bcb8fb8fe8eb1d3b1975279f9b76c36653276498210a388aa7fe8e312ffaf97d71ba0c190471b0efa79dea111150fd71bce2386811eecc4537d2942a24a2f83ab4e3e764da9864c5d1fb51ae342d9b289e1616fad4c6e23575b88004fc254beed53e474d8400b81cf13e208475fae3292cd7cf60dec5346b4bb4cba008d3ff52ac8d8f748331243821e86cc8addd96da026268e780f170189d4a57098ac7f474eda8d94cad1fb0967010e37ccb28603a21bed6efed3f9295cb6cc7406758a9ca0350535523d07c50b1197dbcea31db87c03f5ef3c9a5580c598483124d76ae7f38cc76fb94c07bbd236c7c7294fd9b80f280ee02102399eb8e4e0c66dc12cfd4f61137f9e7def0af4db1f88b40b1888a09f18b60ac17f2469f47e7d6eeb98e25638f89594b20a87217ab15ea9c9ac7530ce10af64d53601d15438f330afd80b55f717bc2f5d86d3811902fc4eb6ee8d4b1aa2d86ff2788d1b1833f6581bc5f1748579c2999aaa51e56364a3e5aad2172e7d5de74953e6ab1b5de74077c8267f92dec39abe2f1f31163cab9efbe1b5852b2ad891fe289ec68919bf422586dfc7328afcd5dc6a27f3d9f87e3f3e247d5857963f0083e2deea20600000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000066e17acd" } diff --git a/packages/sdk/src/__test__/integration/__mocks__/connect.json b/packages/sdk/src/__test__/integration/__mocks__/connect.json index ed4fdf2b..d4b37d57 100644 --- a/packages/sdk/src/__test__/integration/__mocks__/connect.json +++ b/packages/sdk/src/__test__/integration/__mocks__/connect.json @@ -1,4 +1,4 @@ { - "status": 200, - "message": "0100c7d11ffc00d70001048f065cf1beafadb8d15d31adc8d1f029e25b24dc9cda017cfd054a2a86e2b53eaabf9437cf69fa68f20fff3b80b2b5806e8fc6651890bdae7ffad2c4a8a43151000f00009d00f3d077fabeac89f0f0b2fba9c642630e3a1c1aa845f9e3c9e2c0234d6d05634b0d7bbf4c0de40a69336e5126392aa86976d2378cefd835659f22b1b7dc47c9f7731696ae1ba92c9d3011ad9c9cb12973b0e788a4d87d72f4a757e1a7e4fb41616d0790e90fcd0fe138b4e15170b77f76e2974e47bd275010fc70f03ed71692da4780422fba345bb787abaa91f8e9b7075819" + "status": 200, + "message": "0100c7d11ffc00d70001048f065cf1beafadb8d15d31adc8d1f029e25b24dc9cda017cfd054a2a86e2b53eaabf9437cf69fa68f20fff3b80b2b5806e8fc6651890bdae7ffad2c4a8a43151000f00009d00f3d077fabeac89f0f0b2fba9c642630e3a1c1aa845f9e3c9e2c0234d6d05634b0d7bbf4c0de40a69336e5126392aa86976d2378cefd835659f22b1b7dc47c9f7731696ae1ba92c9d3011ad9c9cb12973b0e788a4d87d72f4a757e1a7e4fb41616d0790e90fcd0fe138b4e15170b77f76e2974e47bd275010fc70f03ed71692da4780422fba345bb787abaa91f8e9b7075819" } diff --git a/packages/sdk/src/__test__/integration/__mocks__/etherscan.ts b/packages/sdk/src/__test__/integration/__mocks__/etherscan.ts index d7597392..b206043c 100644 --- a/packages/sdk/src/__test__/integration/__mocks__/etherscan.ts +++ b/packages/sdk/src/__test__/integration/__mocks__/etherscan.ts @@ -1,2874 +1,2874 @@ export const etherscanResponse0xa0b86991 = [ - { - constant: false, - inputs: [{ name: 'newImplementation', type: 'address' }], - name: 'upgradeTo', - outputs: [], - payable: false, - stateMutability: 'nonpayable', - type: 'function', - }, - { - constant: false, - inputs: [ - { name: 'newImplementation', type: 'address' }, - { name: 'data', type: 'bytes' }, - ], - name: 'upgradeToAndCall', - outputs: [], - payable: true, - stateMutability: 'payable', - type: 'function', - }, - { - constant: true, - inputs: [], - name: 'implementation', - outputs: [{ name: '', type: 'address' }], - payable: false, - stateMutability: 'view', - type: 'function', - }, - { - constant: false, - inputs: [{ name: 'newAdmin', type: 'address' }], - name: 'changeAdmin', - outputs: [], - payable: false, - stateMutability: 'nonpayable', - type: 'function', - }, - { - constant: true, - inputs: [], - name: 'admin', - outputs: [{ name: '', type: 'address' }], - payable: false, - stateMutability: 'view', - type: 'function', - }, - { - inputs: [{ name: '_implementation', type: 'address' }], - payable: false, - stateMutability: 'nonpayable', - type: 'constructor', - }, - { payable: true, stateMutability: 'payable', type: 'fallback' }, - { - anonymous: false, - inputs: [ - { indexed: false, name: 'previousAdmin', type: 'address' }, - { indexed: false, name: 'newAdmin', type: 'address' }, - ], - name: 'AdminChanged', - type: 'event', - }, - { - anonymous: false, - inputs: [{ indexed: false, name: 'implementation', type: 'address' }], - name: 'Upgraded', - type: 'event', - }, + { + constant: false, + inputs: [{ name: 'newImplementation', type: 'address' }], + name: 'upgradeTo', + outputs: [], + payable: false, + stateMutability: 'nonpayable', + type: 'function', + }, + { + constant: false, + inputs: [ + { name: 'newImplementation', type: 'address' }, + { name: 'data', type: 'bytes' }, + ], + name: 'upgradeToAndCall', + outputs: [], + payable: true, + stateMutability: 'payable', + type: 'function', + }, + { + constant: true, + inputs: [], + name: 'implementation', + outputs: [{ name: '', type: 'address' }], + payable: false, + stateMutability: 'view', + type: 'function', + }, + { + constant: false, + inputs: [{ name: 'newAdmin', type: 'address' }], + name: 'changeAdmin', + outputs: [], + payable: false, + stateMutability: 'nonpayable', + type: 'function', + }, + { + constant: true, + inputs: [], + name: 'admin', + outputs: [{ name: '', type: 'address' }], + payable: false, + stateMutability: 'view', + type: 'function', + }, + { + inputs: [{ name: '_implementation', type: 'address' }], + payable: false, + stateMutability: 'nonpayable', + type: 'constructor', + }, + { payable: true, stateMutability: 'payable', type: 'fallback' }, + { + anonymous: false, + inputs: [ + { indexed: false, name: 'previousAdmin', type: 'address' }, + { indexed: false, name: 'newAdmin', type: 'address' }, + ], + name: 'AdminChanged', + type: 'event', + }, + { + anonymous: false, + inputs: [{ indexed: false, name: 'implementation', type: 'address' }], + name: 'Upgraded', + type: 'event', + }, ] export const etherscanResponse0x7a250d56 = [ - { - inputs: [ - { - internalType: 'address', - name: '_factory', - type: 'address', - }, - { - internalType: 'address', - name: '_WETH', - type: 'address', - }, - ], - stateMutability: 'nonpayable', - type: 'constructor', - }, - { - inputs: [], - name: 'WETH', - outputs: [ - { - internalType: 'address', - name: '', - type: 'address', - }, - ], - stateMutability: 'view', - type: 'function', - }, - { - inputs: [ - { - internalType: 'address', - name: 'tokenA', - type: 'address', - }, - { - internalType: 'address', - name: 'tokenB', - type: 'address', - }, - { - internalType: 'uint256', - name: 'amountADesired', - type: 'uint256', - }, - { - internalType: 'uint256', - name: 'amountBDesired', - type: 'uint256', - }, - { - internalType: 'uint256', - name: 'amountAMin', - type: 'uint256', - }, - { - internalType: 'uint256', - name: 'amountBMin', - type: 'uint256', - }, - { - internalType: 'address', - name: 'to', - type: 'address', - }, - { - internalType: 'uint256', - name: 'deadline', - type: 'uint256', - }, - ], - name: 'addLiquidity', - outputs: [ - { - internalType: 'uint256', - name: 'amountA', - type: 'uint256', - }, - { - internalType: 'uint256', - name: 'amountB', - type: 'uint256', - }, - { - internalType: 'uint256', - name: 'liquidity', - type: 'uint256', - }, - ], - stateMutability: 'nonpayable', - type: 'function', - }, - { - inputs: [ - { - internalType: 'address', - name: 'token', - type: 'address', - }, - { - internalType: 'uint256', - name: 'amountTokenDesired', - type: 'uint256', - }, - { - internalType: 'uint256', - name: 'amountTokenMin', - type: 'uint256', - }, - { - internalType: 'uint256', - name: 'amountETHMin', - type: 'uint256', - }, - { - internalType: 'address', - name: 'to', - type: 'address', - }, - { - internalType: 'uint256', - name: 'deadline', - type: 'uint256', - }, - ], - name: 'addLiquidityETH', - outputs: [ - { - internalType: 'uint256', - name: 'amountToken', - type: 'uint256', - }, - { - internalType: 'uint256', - name: 'amountETH', - type: 'uint256', - }, - { - internalType: 'uint256', - name: 'liquidity', - type: 'uint256', - }, - ], - stateMutability: 'payable', - type: 'function', - }, - { - inputs: [], - name: 'factory', - outputs: [ - { - internalType: 'address', - name: '', - type: 'address', - }, - ], - stateMutability: 'view', - type: 'function', - }, - { - inputs: [ - { - internalType: 'uint256', - name: 'amountOut', - type: 'uint256', - }, - { - internalType: 'uint256', - name: 'reserveIn', - type: 'uint256', - }, - { - internalType: 'uint256', - name: 'reserveOut', - type: 'uint256', - }, - ], - name: 'getAmountIn', - outputs: [ - { - internalType: 'uint256', - name: 'amountIn', - type: 'uint256', - }, - ], - stateMutability: 'pure', - type: 'function', - }, - { - inputs: [ - { - internalType: 'uint256', - name: 'amountIn', - type: 'uint256', - }, - { - internalType: 'uint256', - name: 'reserveIn', - type: 'uint256', - }, - { - internalType: 'uint256', - name: 'reserveOut', - type: 'uint256', - }, - ], - name: 'getAmountOut', - outputs: [ - { - internalType: 'uint256', - name: 'amountOut', - type: 'uint256', - }, - ], - stateMutability: 'pure', - type: 'function', - }, - { - inputs: [ - { - internalType: 'uint256', - name: 'amountOut', - type: 'uint256', - }, - { - internalType: 'address[]', - name: 'path', - type: 'address[]', - }, - ], - name: 'getAmountsIn', - outputs: [ - { - internalType: 'uint256[]', - name: 'amounts', - type: 'uint256[]', - }, - ], - stateMutability: 'view', - type: 'function', - }, - { - inputs: [ - { - internalType: 'uint256', - name: 'amountIn', - type: 'uint256', - }, - { - internalType: 'address[]', - name: 'path', - type: 'address[]', - }, - ], - name: 'getAmountsOut', - outputs: [ - { - internalType: 'uint256[]', - name: 'amounts', - type: 'uint256[]', - }, - ], - stateMutability: 'view', - type: 'function', - }, - { - inputs: [ - { - internalType: 'uint256', - name: 'amountA', - type: 'uint256', - }, - { - internalType: 'uint256', - name: 'reserveA', - type: 'uint256', - }, - { - internalType: 'uint256', - name: 'reserveB', - type: 'uint256', - }, - ], - name: 'quote', - outputs: [ - { - internalType: 'uint256', - name: 'amountB', - type: 'uint256', - }, - ], - stateMutability: 'pure', - type: 'function', - }, - { - inputs: [ - { - internalType: 'address', - name: 'tokenA', - type: 'address', - }, - { - internalType: 'address', - name: 'tokenB', - type: 'address', - }, - { - internalType: 'uint256', - name: 'liquidity', - type: 'uint256', - }, - { - internalType: 'uint256', - name: 'amountAMin', - type: 'uint256', - }, - { - internalType: 'uint256', - name: 'amountBMin', - type: 'uint256', - }, - { - internalType: 'address', - name: 'to', - type: 'address', - }, - { - internalType: 'uint256', - name: 'deadline', - type: 'uint256', - }, - ], - name: 'removeLiquidity', - outputs: [ - { - internalType: 'uint256', - name: 'amountA', - type: 'uint256', - }, - { - internalType: 'uint256', - name: 'amountB', - type: 'uint256', - }, - ], - stateMutability: 'nonpayable', - type: 'function', - }, - { - inputs: [ - { - internalType: 'address', - name: 'token', - type: 'address', - }, - { - internalType: 'uint256', - name: 'liquidity', - type: 'uint256', - }, - { - internalType: 'uint256', - name: 'amountTokenMin', - type: 'uint256', - }, - { - internalType: 'uint256', - name: 'amountETHMin', - type: 'uint256', - }, - { - internalType: 'address', - name: 'to', - type: 'address', - }, - { - internalType: 'uint256', - name: 'deadline', - type: 'uint256', - }, - ], - name: 'removeLiquidityETH', - outputs: [ - { - internalType: 'uint256', - name: 'amountToken', - type: 'uint256', - }, - { - internalType: 'uint256', - name: 'amountETH', - type: 'uint256', - }, - ], - stateMutability: 'nonpayable', - type: 'function', - }, - { - inputs: [ - { - internalType: 'address', - name: 'token', - type: 'address', - }, - { - internalType: 'uint256', - name: 'liquidity', - type: 'uint256', - }, - { - internalType: 'uint256', - name: 'amountTokenMin', - type: 'uint256', - }, - { - internalType: 'uint256', - name: 'amountETHMin', - type: 'uint256', - }, - { - internalType: 'address', - name: 'to', - type: 'address', - }, - { - internalType: 'uint256', - name: 'deadline', - type: 'uint256', - }, - ], - name: 'removeLiquidityETHSupportingFeeOnTransferTokens', - outputs: [ - { - internalType: 'uint256', - name: 'amountETH', - type: 'uint256', - }, - ], - stateMutability: 'nonpayable', - type: 'function', - }, - { - inputs: [ - { - internalType: 'address', - name: 'token', - type: 'address', - }, - { - internalType: 'uint256', - name: 'liquidity', - type: 'uint256', - }, - { - internalType: 'uint256', - name: 'amountTokenMin', - type: 'uint256', - }, - { - internalType: 'uint256', - name: 'amountETHMin', - type: 'uint256', - }, - { - internalType: 'address', - name: 'to', - type: 'address', - }, - { - internalType: 'uint256', - name: 'deadline', - type: 'uint256', - }, - { - internalType: 'bool', - name: 'approveMax', - type: 'bool', - }, - { - internalType: 'uint8', - name: 'v', - type: 'uint8', - }, - { - internalType: 'bytes32', - name: 'r', - type: 'bytes32', - }, - { - internalType: 'bytes32', - name: 's', - type: 'bytes32', - }, - ], - name: 'removeLiquidityETHWithPermit', - outputs: [ - { - internalType: 'uint256', - name: 'amountToken', - type: 'uint256', - }, - { - internalType: 'uint256', - name: 'amountETH', - type: 'uint256', - }, - ], - stateMutability: 'nonpayable', - type: 'function', - }, - { - inputs: [ - { - internalType: 'address', - name: 'token', - type: 'address', - }, - { - internalType: 'uint256', - name: 'liquidity', - type: 'uint256', - }, - { - internalType: 'uint256', - name: 'amountTokenMin', - type: 'uint256', - }, - { - internalType: 'uint256', - name: 'amountETHMin', - type: 'uint256', - }, - { - internalType: 'address', - name: 'to', - type: 'address', - }, - { - internalType: 'uint256', - name: 'deadline', - type: 'uint256', - }, - { - internalType: 'bool', - name: 'approveMax', - type: 'bool', - }, - { - internalType: 'uint8', - name: 'v', - type: 'uint8', - }, - { - internalType: 'bytes32', - name: 'r', - type: 'bytes32', - }, - { - internalType: 'bytes32', - name: 's', - type: 'bytes32', - }, - ], - name: 'removeLiquidityETHWithPermitSupportingFeeOnTransferTokens', - outputs: [ - { - internalType: 'uint256', - name: 'amountETH', - type: 'uint256', - }, - ], - stateMutability: 'nonpayable', - type: 'function', - }, - { - inputs: [ - { - internalType: 'address', - name: 'tokenA', - type: 'address', - }, - { - internalType: 'address', - name: 'tokenB', - type: 'address', - }, - { - internalType: 'uint256', - name: 'liquidity', - type: 'uint256', - }, - { - internalType: 'uint256', - name: 'amountAMin', - type: 'uint256', - }, - { - internalType: 'uint256', - name: 'amountBMin', - type: 'uint256', - }, - { - internalType: 'address', - name: 'to', - type: 'address', - }, - { - internalType: 'uint256', - name: 'deadline', - type: 'uint256', - }, - { - internalType: 'bool', - name: 'approveMax', - type: 'bool', - }, - { - internalType: 'uint8', - name: 'v', - type: 'uint8', - }, - { - internalType: 'bytes32', - name: 'r', - type: 'bytes32', - }, - { - internalType: 'bytes32', - name: 's', - type: 'bytes32', - }, - ], - name: 'removeLiquidityWithPermit', - outputs: [ - { - internalType: 'uint256', - name: 'amountA', - type: 'uint256', - }, - { - internalType: 'uint256', - name: 'amountB', - type: 'uint256', - }, - ], - stateMutability: 'nonpayable', - type: 'function', - }, - { - inputs: [ - { - internalType: 'uint256', - name: 'amountOut', - type: 'uint256', - }, - { - internalType: 'address[]', - name: 'path', - type: 'address[]', - }, - { - internalType: 'address', - name: 'to', - type: 'address', - }, - { - internalType: 'uint256', - name: 'deadline', - type: 'uint256', - }, - ], - name: 'swapETHForExactTokens', - outputs: [ - { - internalType: 'uint256[]', - name: 'amounts', - type: 'uint256[]', - }, - ], - stateMutability: 'payable', - type: 'function', - }, - { - inputs: [ - { - internalType: 'uint256', - name: 'amountOutMin', - type: 'uint256', - }, - { - internalType: 'address[]', - name: 'path', - type: 'address[]', - }, - { - internalType: 'address', - name: 'to', - type: 'address', - }, - { - internalType: 'uint256', - name: 'deadline', - type: 'uint256', - }, - ], - name: 'swapExactETHForTokens', - outputs: [ - { - internalType: 'uint256[]', - name: 'amounts', - type: 'uint256[]', - }, - ], - stateMutability: 'payable', - type: 'function', - }, - { - inputs: [ - { - internalType: 'uint256', - name: 'amountOutMin', - type: 'uint256', - }, - { - internalType: 'address[]', - name: 'path', - type: 'address[]', - }, - { - internalType: 'address', - name: 'to', - type: 'address', - }, - { - internalType: 'uint256', - name: 'deadline', - type: 'uint256', - }, - ], - name: 'swapExactETHForTokensSupportingFeeOnTransferTokens', - outputs: [], - stateMutability: 'payable', - type: 'function', - }, - { - inputs: [ - { - internalType: 'uint256', - name: 'amountIn', - type: 'uint256', - }, - { - internalType: 'uint256', - name: 'amountOutMin', - type: 'uint256', - }, - { - internalType: 'address[]', - name: 'path', - type: 'address[]', - }, - { - internalType: 'address', - name: 'to', - type: 'address', - }, - { - internalType: 'uint256', - name: 'deadline', - type: 'uint256', - }, - ], - name: 'swapExactTokensForETH', - outputs: [ - { - internalType: 'uint256[]', - name: 'amounts', - type: 'uint256[]', - }, - ], - stateMutability: 'nonpayable', - type: 'function', - }, - { - inputs: [ - { - internalType: 'uint256', - name: 'amountIn', - type: 'uint256', - }, - { - internalType: 'uint256', - name: 'amountOutMin', - type: 'uint256', - }, - { - internalType: 'address[]', - name: 'path', - type: 'address[]', - }, - { - internalType: 'address', - name: 'to', - type: 'address', - }, - { - internalType: 'uint256', - name: 'deadline', - type: 'uint256', - }, - ], - name: 'swapExactTokensForETHSupportingFeeOnTransferTokens', - outputs: [], - stateMutability: 'nonpayable', - type: 'function', - }, - { - inputs: [ - { - internalType: 'uint256', - name: 'amountIn', - type: 'uint256', - }, - { - internalType: 'uint256', - name: 'amountOutMin', - type: 'uint256', - }, - { - internalType: 'address[]', - name: 'path', - type: 'address[]', - }, - { - internalType: 'address', - name: 'to', - type: 'address', - }, - { - internalType: 'uint256', - name: 'deadline', - type: 'uint256', - }, - ], - name: 'swapExactTokensForTokens', - outputs: [ - { - internalType: 'uint256[]', - name: 'amounts', - type: 'uint256[]', - }, - ], - stateMutability: 'nonpayable', - type: 'function', - }, - { - inputs: [ - { - internalType: 'uint256', - name: 'amountIn', - type: 'uint256', - }, - { - internalType: 'uint256', - name: 'amountOutMin', - type: 'uint256', - }, - { - internalType: 'address[]', - name: 'path', - type: 'address[]', - }, - { - internalType: 'address', - name: 'to', - type: 'address', - }, - { - internalType: 'uint256', - name: 'deadline', - type: 'uint256', - }, - ], - name: 'swapExactTokensForTokensSupportingFeeOnTransferTokens', - outputs: [], - stateMutability: 'nonpayable', - type: 'function', - }, - { - inputs: [ - { - internalType: 'uint256', - name: 'amountOut', - type: 'uint256', - }, - { - internalType: 'uint256', - name: 'amountInMax', - type: 'uint256', - }, - { - internalType: 'address[]', - name: 'path', - type: 'address[]', - }, - { - internalType: 'address', - name: 'to', - type: 'address', - }, - { - internalType: 'uint256', - name: 'deadline', - type: 'uint256', - }, - ], - name: 'swapTokensForExactETH', - outputs: [ - { - internalType: 'uint256[]', - name: 'amounts', - type: 'uint256[]', - }, - ], - stateMutability: 'nonpayable', - type: 'function', - }, - { - inputs: [ - { - internalType: 'uint256', - name: 'amountOut', - type: 'uint256', - }, - { - internalType: 'uint256', - name: 'amountInMax', - type: 'uint256', - }, - { - internalType: 'address[]', - name: 'path', - type: 'address[]', - }, - { - internalType: 'address', - name: 'to', - type: 'address', - }, - { - internalType: 'uint256', - name: 'deadline', - type: 'uint256', - }, - ], - name: 'swapTokensForExactTokens', - outputs: [ - { - internalType: 'uint256[]', - name: 'amounts', - type: 'uint256[]', - }, - ], - stateMutability: 'nonpayable', - type: 'function', - }, - { - stateMutability: 'payable', - type: 'receive', - }, + { + inputs: [ + { + internalType: 'address', + name: '_factory', + type: 'address', + }, + { + internalType: 'address', + name: '_WETH', + type: 'address', + }, + ], + stateMutability: 'nonpayable', + type: 'constructor', + }, + { + inputs: [], + name: 'WETH', + outputs: [ + { + internalType: 'address', + name: '', + type: 'address', + }, + ], + stateMutability: 'view', + type: 'function', + }, + { + inputs: [ + { + internalType: 'address', + name: 'tokenA', + type: 'address', + }, + { + internalType: 'address', + name: 'tokenB', + type: 'address', + }, + { + internalType: 'uint256', + name: 'amountADesired', + type: 'uint256', + }, + { + internalType: 'uint256', + name: 'amountBDesired', + type: 'uint256', + }, + { + internalType: 'uint256', + name: 'amountAMin', + type: 'uint256', + }, + { + internalType: 'uint256', + name: 'amountBMin', + type: 'uint256', + }, + { + internalType: 'address', + name: 'to', + type: 'address', + }, + { + internalType: 'uint256', + name: 'deadline', + type: 'uint256', + }, + ], + name: 'addLiquidity', + outputs: [ + { + internalType: 'uint256', + name: 'amountA', + type: 'uint256', + }, + { + internalType: 'uint256', + name: 'amountB', + type: 'uint256', + }, + { + internalType: 'uint256', + name: 'liquidity', + type: 'uint256', + }, + ], + stateMutability: 'nonpayable', + type: 'function', + }, + { + inputs: [ + { + internalType: 'address', + name: 'token', + type: 'address', + }, + { + internalType: 'uint256', + name: 'amountTokenDesired', + type: 'uint256', + }, + { + internalType: 'uint256', + name: 'amountTokenMin', + type: 'uint256', + }, + { + internalType: 'uint256', + name: 'amountETHMin', + type: 'uint256', + }, + { + internalType: 'address', + name: 'to', + type: 'address', + }, + { + internalType: 'uint256', + name: 'deadline', + type: 'uint256', + }, + ], + name: 'addLiquidityETH', + outputs: [ + { + internalType: 'uint256', + name: 'amountToken', + type: 'uint256', + }, + { + internalType: 'uint256', + name: 'amountETH', + type: 'uint256', + }, + { + internalType: 'uint256', + name: 'liquidity', + type: 'uint256', + }, + ], + stateMutability: 'payable', + type: 'function', + }, + { + inputs: [], + name: 'factory', + outputs: [ + { + internalType: 'address', + name: '', + type: 'address', + }, + ], + stateMutability: 'view', + type: 'function', + }, + { + inputs: [ + { + internalType: 'uint256', + name: 'amountOut', + type: 'uint256', + }, + { + internalType: 'uint256', + name: 'reserveIn', + type: 'uint256', + }, + { + internalType: 'uint256', + name: 'reserveOut', + type: 'uint256', + }, + ], + name: 'getAmountIn', + outputs: [ + { + internalType: 'uint256', + name: 'amountIn', + type: 'uint256', + }, + ], + stateMutability: 'pure', + type: 'function', + }, + { + inputs: [ + { + internalType: 'uint256', + name: 'amountIn', + type: 'uint256', + }, + { + internalType: 'uint256', + name: 'reserveIn', + type: 'uint256', + }, + { + internalType: 'uint256', + name: 'reserveOut', + type: 'uint256', + }, + ], + name: 'getAmountOut', + outputs: [ + { + internalType: 'uint256', + name: 'amountOut', + type: 'uint256', + }, + ], + stateMutability: 'pure', + type: 'function', + }, + { + inputs: [ + { + internalType: 'uint256', + name: 'amountOut', + type: 'uint256', + }, + { + internalType: 'address[]', + name: 'path', + type: 'address[]', + }, + ], + name: 'getAmountsIn', + outputs: [ + { + internalType: 'uint256[]', + name: 'amounts', + type: 'uint256[]', + }, + ], + stateMutability: 'view', + type: 'function', + }, + { + inputs: [ + { + internalType: 'uint256', + name: 'amountIn', + type: 'uint256', + }, + { + internalType: 'address[]', + name: 'path', + type: 'address[]', + }, + ], + name: 'getAmountsOut', + outputs: [ + { + internalType: 'uint256[]', + name: 'amounts', + type: 'uint256[]', + }, + ], + stateMutability: 'view', + type: 'function', + }, + { + inputs: [ + { + internalType: 'uint256', + name: 'amountA', + type: 'uint256', + }, + { + internalType: 'uint256', + name: 'reserveA', + type: 'uint256', + }, + { + internalType: 'uint256', + name: 'reserveB', + type: 'uint256', + }, + ], + name: 'quote', + outputs: [ + { + internalType: 'uint256', + name: 'amountB', + type: 'uint256', + }, + ], + stateMutability: 'pure', + type: 'function', + }, + { + inputs: [ + { + internalType: 'address', + name: 'tokenA', + type: 'address', + }, + { + internalType: 'address', + name: 'tokenB', + type: 'address', + }, + { + internalType: 'uint256', + name: 'liquidity', + type: 'uint256', + }, + { + internalType: 'uint256', + name: 'amountAMin', + type: 'uint256', + }, + { + internalType: 'uint256', + name: 'amountBMin', + type: 'uint256', + }, + { + internalType: 'address', + name: 'to', + type: 'address', + }, + { + internalType: 'uint256', + name: 'deadline', + type: 'uint256', + }, + ], + name: 'removeLiquidity', + outputs: [ + { + internalType: 'uint256', + name: 'amountA', + type: 'uint256', + }, + { + internalType: 'uint256', + name: 'amountB', + type: 'uint256', + }, + ], + stateMutability: 'nonpayable', + type: 'function', + }, + { + inputs: [ + { + internalType: 'address', + name: 'token', + type: 'address', + }, + { + internalType: 'uint256', + name: 'liquidity', + type: 'uint256', + }, + { + internalType: 'uint256', + name: 'amountTokenMin', + type: 'uint256', + }, + { + internalType: 'uint256', + name: 'amountETHMin', + type: 'uint256', + }, + { + internalType: 'address', + name: 'to', + type: 'address', + }, + { + internalType: 'uint256', + name: 'deadline', + type: 'uint256', + }, + ], + name: 'removeLiquidityETH', + outputs: [ + { + internalType: 'uint256', + name: 'amountToken', + type: 'uint256', + }, + { + internalType: 'uint256', + name: 'amountETH', + type: 'uint256', + }, + ], + stateMutability: 'nonpayable', + type: 'function', + }, + { + inputs: [ + { + internalType: 'address', + name: 'token', + type: 'address', + }, + { + internalType: 'uint256', + name: 'liquidity', + type: 'uint256', + }, + { + internalType: 'uint256', + name: 'amountTokenMin', + type: 'uint256', + }, + { + internalType: 'uint256', + name: 'amountETHMin', + type: 'uint256', + }, + { + internalType: 'address', + name: 'to', + type: 'address', + }, + { + internalType: 'uint256', + name: 'deadline', + type: 'uint256', + }, + ], + name: 'removeLiquidityETHSupportingFeeOnTransferTokens', + outputs: [ + { + internalType: 'uint256', + name: 'amountETH', + type: 'uint256', + }, + ], + stateMutability: 'nonpayable', + type: 'function', + }, + { + inputs: [ + { + internalType: 'address', + name: 'token', + type: 'address', + }, + { + internalType: 'uint256', + name: 'liquidity', + type: 'uint256', + }, + { + internalType: 'uint256', + name: 'amountTokenMin', + type: 'uint256', + }, + { + internalType: 'uint256', + name: 'amountETHMin', + type: 'uint256', + }, + { + internalType: 'address', + name: 'to', + type: 'address', + }, + { + internalType: 'uint256', + name: 'deadline', + type: 'uint256', + }, + { + internalType: 'bool', + name: 'approveMax', + type: 'bool', + }, + { + internalType: 'uint8', + name: 'v', + type: 'uint8', + }, + { + internalType: 'bytes32', + name: 'r', + type: 'bytes32', + }, + { + internalType: 'bytes32', + name: 's', + type: 'bytes32', + }, + ], + name: 'removeLiquidityETHWithPermit', + outputs: [ + { + internalType: 'uint256', + name: 'amountToken', + type: 'uint256', + }, + { + internalType: 'uint256', + name: 'amountETH', + type: 'uint256', + }, + ], + stateMutability: 'nonpayable', + type: 'function', + }, + { + inputs: [ + { + internalType: 'address', + name: 'token', + type: 'address', + }, + { + internalType: 'uint256', + name: 'liquidity', + type: 'uint256', + }, + { + internalType: 'uint256', + name: 'amountTokenMin', + type: 'uint256', + }, + { + internalType: 'uint256', + name: 'amountETHMin', + type: 'uint256', + }, + { + internalType: 'address', + name: 'to', + type: 'address', + }, + { + internalType: 'uint256', + name: 'deadline', + type: 'uint256', + }, + { + internalType: 'bool', + name: 'approveMax', + type: 'bool', + }, + { + internalType: 'uint8', + name: 'v', + type: 'uint8', + }, + { + internalType: 'bytes32', + name: 'r', + type: 'bytes32', + }, + { + internalType: 'bytes32', + name: 's', + type: 'bytes32', + }, + ], + name: 'removeLiquidityETHWithPermitSupportingFeeOnTransferTokens', + outputs: [ + { + internalType: 'uint256', + name: 'amountETH', + type: 'uint256', + }, + ], + stateMutability: 'nonpayable', + type: 'function', + }, + { + inputs: [ + { + internalType: 'address', + name: 'tokenA', + type: 'address', + }, + { + internalType: 'address', + name: 'tokenB', + type: 'address', + }, + { + internalType: 'uint256', + name: 'liquidity', + type: 'uint256', + }, + { + internalType: 'uint256', + name: 'amountAMin', + type: 'uint256', + }, + { + internalType: 'uint256', + name: 'amountBMin', + type: 'uint256', + }, + { + internalType: 'address', + name: 'to', + type: 'address', + }, + { + internalType: 'uint256', + name: 'deadline', + type: 'uint256', + }, + { + internalType: 'bool', + name: 'approveMax', + type: 'bool', + }, + { + internalType: 'uint8', + name: 'v', + type: 'uint8', + }, + { + internalType: 'bytes32', + name: 'r', + type: 'bytes32', + }, + { + internalType: 'bytes32', + name: 's', + type: 'bytes32', + }, + ], + name: 'removeLiquidityWithPermit', + outputs: [ + { + internalType: 'uint256', + name: 'amountA', + type: 'uint256', + }, + { + internalType: 'uint256', + name: 'amountB', + type: 'uint256', + }, + ], + stateMutability: 'nonpayable', + type: 'function', + }, + { + inputs: [ + { + internalType: 'uint256', + name: 'amountOut', + type: 'uint256', + }, + { + internalType: 'address[]', + name: 'path', + type: 'address[]', + }, + { + internalType: 'address', + name: 'to', + type: 'address', + }, + { + internalType: 'uint256', + name: 'deadline', + type: 'uint256', + }, + ], + name: 'swapETHForExactTokens', + outputs: [ + { + internalType: 'uint256[]', + name: 'amounts', + type: 'uint256[]', + }, + ], + stateMutability: 'payable', + type: 'function', + }, + { + inputs: [ + { + internalType: 'uint256', + name: 'amountOutMin', + type: 'uint256', + }, + { + internalType: 'address[]', + name: 'path', + type: 'address[]', + }, + { + internalType: 'address', + name: 'to', + type: 'address', + }, + { + internalType: 'uint256', + name: 'deadline', + type: 'uint256', + }, + ], + name: 'swapExactETHForTokens', + outputs: [ + { + internalType: 'uint256[]', + name: 'amounts', + type: 'uint256[]', + }, + ], + stateMutability: 'payable', + type: 'function', + }, + { + inputs: [ + { + internalType: 'uint256', + name: 'amountOutMin', + type: 'uint256', + }, + { + internalType: 'address[]', + name: 'path', + type: 'address[]', + }, + { + internalType: 'address', + name: 'to', + type: 'address', + }, + { + internalType: 'uint256', + name: 'deadline', + type: 'uint256', + }, + ], + name: 'swapExactETHForTokensSupportingFeeOnTransferTokens', + outputs: [], + stateMutability: 'payable', + type: 'function', + }, + { + inputs: [ + { + internalType: 'uint256', + name: 'amountIn', + type: 'uint256', + }, + { + internalType: 'uint256', + name: 'amountOutMin', + type: 'uint256', + }, + { + internalType: 'address[]', + name: 'path', + type: 'address[]', + }, + { + internalType: 'address', + name: 'to', + type: 'address', + }, + { + internalType: 'uint256', + name: 'deadline', + type: 'uint256', + }, + ], + name: 'swapExactTokensForETH', + outputs: [ + { + internalType: 'uint256[]', + name: 'amounts', + type: 'uint256[]', + }, + ], + stateMutability: 'nonpayable', + type: 'function', + }, + { + inputs: [ + { + internalType: 'uint256', + name: 'amountIn', + type: 'uint256', + }, + { + internalType: 'uint256', + name: 'amountOutMin', + type: 'uint256', + }, + { + internalType: 'address[]', + name: 'path', + type: 'address[]', + }, + { + internalType: 'address', + name: 'to', + type: 'address', + }, + { + internalType: 'uint256', + name: 'deadline', + type: 'uint256', + }, + ], + name: 'swapExactTokensForETHSupportingFeeOnTransferTokens', + outputs: [], + stateMutability: 'nonpayable', + type: 'function', + }, + { + inputs: [ + { + internalType: 'uint256', + name: 'amountIn', + type: 'uint256', + }, + { + internalType: 'uint256', + name: 'amountOutMin', + type: 'uint256', + }, + { + internalType: 'address[]', + name: 'path', + type: 'address[]', + }, + { + internalType: 'address', + name: 'to', + type: 'address', + }, + { + internalType: 'uint256', + name: 'deadline', + type: 'uint256', + }, + ], + name: 'swapExactTokensForTokens', + outputs: [ + { + internalType: 'uint256[]', + name: 'amounts', + type: 'uint256[]', + }, + ], + stateMutability: 'nonpayable', + type: 'function', + }, + { + inputs: [ + { + internalType: 'uint256', + name: 'amountIn', + type: 'uint256', + }, + { + internalType: 'uint256', + name: 'amountOutMin', + type: 'uint256', + }, + { + internalType: 'address[]', + name: 'path', + type: 'address[]', + }, + { + internalType: 'address', + name: 'to', + type: 'address', + }, + { + internalType: 'uint256', + name: 'deadline', + type: 'uint256', + }, + ], + name: 'swapExactTokensForTokensSupportingFeeOnTransferTokens', + outputs: [], + stateMutability: 'nonpayable', + type: 'function', + }, + { + inputs: [ + { + internalType: 'uint256', + name: 'amountOut', + type: 'uint256', + }, + { + internalType: 'uint256', + name: 'amountInMax', + type: 'uint256', + }, + { + internalType: 'address[]', + name: 'path', + type: 'address[]', + }, + { + internalType: 'address', + name: 'to', + type: 'address', + }, + { + internalType: 'uint256', + name: 'deadline', + type: 'uint256', + }, + ], + name: 'swapTokensForExactETH', + outputs: [ + { + internalType: 'uint256[]', + name: 'amounts', + type: 'uint256[]', + }, + ], + stateMutability: 'nonpayable', + type: 'function', + }, + { + inputs: [ + { + internalType: 'uint256', + name: 'amountOut', + type: 'uint256', + }, + { + internalType: 'uint256', + name: 'amountInMax', + type: 'uint256', + }, + { + internalType: 'address[]', + name: 'path', + type: 'address[]', + }, + { + internalType: 'address', + name: 'to', + type: 'address', + }, + { + internalType: 'uint256', + name: 'deadline', + type: 'uint256', + }, + ], + name: 'swapTokensForExactTokens', + outputs: [ + { + internalType: 'uint256[]', + name: 'amounts', + type: 'uint256[]', + }, + ], + stateMutability: 'nonpayable', + type: 'function', + }, + { + stateMutability: 'payable', + type: 'receive', + }, ] export const etherscanResponse0xc36442b6 = [ - { - inputs: [ - { - internalType: 'address', - name: '_factory', - type: 'address', - }, - { - internalType: 'address', - name: '_WETH9', - type: 'address', - }, - { - internalType: 'address', - name: '_tokenDescriptor_', - type: 'address', - }, - ], - stateMutability: 'nonpayable', - type: 'constructor', - }, - { - anonymous: false, - inputs: [ - { - indexed: true, - internalType: 'address', - name: 'owner', - type: 'address', - }, - { - indexed: true, - internalType: 'address', - name: 'approved', - type: 'address', - }, - { - indexed: true, - internalType: 'uint256', - name: 'tokenId', - type: 'uint256', - }, - ], - name: 'Approval', - type: 'event', - }, - { - anonymous: false, - inputs: [ - { - indexed: true, - internalType: 'address', - name: 'owner', - type: 'address', - }, - { - indexed: true, - internalType: 'address', - name: 'operator', - type: 'address', - }, - { - indexed: false, - internalType: 'bool', - name: 'approved', - type: 'bool', - }, - ], - name: 'ApprovalForAll', - type: 'event', - }, - { - anonymous: false, - inputs: [ - { - indexed: true, - internalType: 'uint256', - name: 'tokenId', - type: 'uint256', - }, - { - indexed: false, - internalType: 'address', - name: 'recipient', - type: 'address', - }, - { - indexed: false, - internalType: 'uint256', - name: 'amount0', - type: 'uint256', - }, - { - indexed: false, - internalType: 'uint256', - name: 'amount1', - type: 'uint256', - }, - ], - name: 'Collect', - type: 'event', - }, - { - anonymous: false, - inputs: [ - { - indexed: true, - internalType: 'uint256', - name: 'tokenId', - type: 'uint256', - }, - { - indexed: false, - internalType: 'uint128', - name: 'liquidity', - type: 'uint128', - }, - { - indexed: false, - internalType: 'uint256', - name: 'amount0', - type: 'uint256', - }, - { - indexed: false, - internalType: 'uint256', - name: 'amount1', - type: 'uint256', - }, - ], - name: 'DecreaseLiquidity', - type: 'event', - }, - { - anonymous: false, - inputs: [ - { - indexed: true, - internalType: 'uint256', - name: 'tokenId', - type: 'uint256', - }, - { - indexed: false, - internalType: 'uint128', - name: 'liquidity', - type: 'uint128', - }, - { - indexed: false, - internalType: 'uint256', - name: 'amount0', - type: 'uint256', - }, - { - indexed: false, - internalType: 'uint256', - name: 'amount1', - type: 'uint256', - }, - ], - name: 'IncreaseLiquidity', - type: 'event', - }, - { - anonymous: false, - inputs: [ - { - indexed: true, - internalType: 'address', - name: 'from', - type: 'address', - }, - { - indexed: true, - internalType: 'address', - name: 'to', - type: 'address', - }, - { - indexed: true, - internalType: 'uint256', - name: 'tokenId', - type: 'uint256', - }, - ], - name: 'Transfer', - type: 'event', - }, - { - inputs: [], - name: 'DOMAIN_SEPARATOR', - outputs: [ - { - internalType: 'bytes32', - name: '', - type: 'bytes32', - }, - ], - stateMutability: 'view', - type: 'function', - }, - { - inputs: [], - name: 'PERMIT_TYPEHASH', - outputs: [ - { - internalType: 'bytes32', - name: '', - type: 'bytes32', - }, - ], - stateMutability: 'view', - type: 'function', - }, - { - inputs: [], - name: 'WETH9', - outputs: [ - { - internalType: 'address', - name: '', - type: 'address', - }, - ], - stateMutability: 'view', - type: 'function', - }, - { - inputs: [ - { - internalType: 'address', - name: 'to', - type: 'address', - }, - { - internalType: 'uint256', - name: 'tokenId', - type: 'uint256', - }, - ], - name: 'approve', - outputs: [], - stateMutability: 'nonpayable', - type: 'function', - }, - { - inputs: [ - { - internalType: 'address', - name: 'owner', - type: 'address', - }, - ], - name: 'balanceOf', - outputs: [ - { - internalType: 'uint256', - name: '', - type: 'uint256', - }, - ], - stateMutability: 'view', - type: 'function', - }, - { - inputs: [], - name: 'baseURI', - outputs: [ - { - internalType: 'string', - name: '', - type: 'string', - }, - ], - stateMutability: 'pure', - type: 'function', - }, - { - inputs: [ - { - internalType: 'uint256', - name: 'tokenId', - type: 'uint256', - }, - ], - name: 'burn', - outputs: [], - stateMutability: 'payable', - type: 'function', - }, - { - inputs: [ - { - components: [ - { - internalType: 'uint256', - name: 'tokenId', - type: 'uint256', - }, - { - internalType: 'address', - name: 'recipient', - type: 'address', - }, - { - internalType: 'uint128', - name: 'amount0Max', - type: 'uint128', - }, - { - internalType: 'uint128', - name: 'amount1Max', - type: 'uint128', - }, - ], - internalType: 'struct INonfungiblePositionManager.CollectParams', - name: 'params', - type: 'tuple', - }, - ], - name: 'collect', - outputs: [ - { - internalType: 'uint256', - name: 'amount0', - type: 'uint256', - }, - { - internalType: 'uint256', - name: 'amount1', - type: 'uint256', - }, - ], - stateMutability: 'payable', - type: 'function', - }, - { - inputs: [ - { - internalType: 'address', - name: 'token0', - type: 'address', - }, - { - internalType: 'address', - name: 'token1', - type: 'address', - }, - { - internalType: 'uint24', - name: 'fee', - type: 'uint24', - }, - { - internalType: 'uint160', - name: 'sqrtPriceX96', - type: 'uint160', - }, - ], - name: 'createAndInitializePoolIfNecessary', - outputs: [ - { - internalType: 'address', - name: 'pool', - type: 'address', - }, - ], - stateMutability: 'payable', - type: 'function', - }, - { - inputs: [ - { - components: [ - { - internalType: 'uint256', - name: 'tokenId', - type: 'uint256', - }, - { - internalType: 'uint128', - name: 'liquidity', - type: 'uint128', - }, - { - internalType: 'uint256', - name: 'amount0Min', - type: 'uint256', - }, - { - internalType: 'uint256', - name: 'amount1Min', - type: 'uint256', - }, - { - internalType: 'uint256', - name: 'deadline', - type: 'uint256', - }, - ], - internalType: - 'struct INonfungiblePositionManager.DecreaseLiquidityParams', - name: 'params', - type: 'tuple', - }, - ], - name: 'decreaseLiquidity', - outputs: [ - { - internalType: 'uint256', - name: 'amount0', - type: 'uint256', - }, - { - internalType: 'uint256', - name: 'amount1', - type: 'uint256', - }, - ], - stateMutability: 'payable', - type: 'function', - }, - { - inputs: [], - name: 'factory', - outputs: [ - { - internalType: 'address', - name: '', - type: 'address', - }, - ], - stateMutability: 'view', - type: 'function', - }, - { - inputs: [ - { - internalType: 'uint256', - name: 'tokenId', - type: 'uint256', - }, - ], - name: 'getApproved', - outputs: [ - { - internalType: 'address', - name: '', - type: 'address', - }, - ], - stateMutability: 'view', - type: 'function', - }, - { - inputs: [ - { - components: [ - { - internalType: 'uint256', - name: 'tokenId', - type: 'uint256', - }, - { - internalType: 'uint256', - name: 'amount0Desired', - type: 'uint256', - }, - { - internalType: 'uint256', - name: 'amount1Desired', - type: 'uint256', - }, - { - internalType: 'uint256', - name: 'amount0Min', - type: 'uint256', - }, - { - internalType: 'uint256', - name: 'amount1Min', - type: 'uint256', - }, - { - internalType: 'uint256', - name: 'deadline', - type: 'uint256', - }, - ], - internalType: - 'struct INonfungiblePositionManager.IncreaseLiquidityParams', - name: 'params', - type: 'tuple', - }, - ], - name: 'increaseLiquidity', - outputs: [ - { - internalType: 'uint128', - name: 'liquidity', - type: 'uint128', - }, - { - internalType: 'uint256', - name: 'amount0', - type: 'uint256', - }, - { - internalType: 'uint256', - name: 'amount1', - type: 'uint256', - }, - ], - stateMutability: 'payable', - type: 'function', - }, - { - inputs: [ - { - internalType: 'address', - name: 'owner', - type: 'address', - }, - { - internalType: 'address', - name: 'operator', - type: 'address', - }, - ], - name: 'isApprovedForAll', - outputs: [ - { - internalType: 'bool', - name: '', - type: 'bool', - }, - ], - stateMutability: 'view', - type: 'function', - }, - { - inputs: [ - { - components: [ - { - internalType: 'address', - name: 'token0', - type: 'address', - }, - { - internalType: 'address', - name: 'token1', - type: 'address', - }, - { - internalType: 'uint24', - name: 'fee', - type: 'uint24', - }, - { - internalType: 'int24', - name: 'tickLower', - type: 'int24', - }, - { - internalType: 'int24', - name: 'tickUpper', - type: 'int24', - }, - { - internalType: 'uint256', - name: 'amount0Desired', - type: 'uint256', - }, - { - internalType: 'uint256', - name: 'amount1Desired', - type: 'uint256', - }, - { - internalType: 'uint256', - name: 'amount0Min', - type: 'uint256', - }, - { - internalType: 'uint256', - name: 'amount1Min', - type: 'uint256', - }, - { - internalType: 'address', - name: 'recipient', - type: 'address', - }, - { - internalType: 'uint256', - name: 'deadline', - type: 'uint256', - }, - ], - internalType: 'struct INonfungiblePositionManager.MintParams', - name: 'params', - type: 'tuple', - }, - ], - name: 'mint', - outputs: [ - { - internalType: 'uint256', - name: 'tokenId', - type: 'uint256', - }, - { - internalType: 'uint128', - name: 'liquidity', - type: 'uint128', - }, - { - internalType: 'uint256', - name: 'amount0', - type: 'uint256', - }, - { - internalType: 'uint256', - name: 'amount1', - type: 'uint256', - }, - ], - stateMutability: 'payable', - type: 'function', - }, - { - inputs: [ - { - internalType: 'bytes[]', - name: 'data', - type: 'bytes[]', - }, - ], - name: 'multicall', - outputs: [ - { - internalType: 'bytes[]', - name: 'results', - type: 'bytes[]', - }, - ], - stateMutability: 'payable', - type: 'function', - }, - { - inputs: [], - name: 'name', - outputs: [ - { - internalType: 'string', - name: '', - type: 'string', - }, - ], - stateMutability: 'view', - type: 'function', - }, - { - inputs: [ - { - internalType: 'uint256', - name: 'tokenId', - type: 'uint256', - }, - ], - name: 'ownerOf', - outputs: [ - { - internalType: 'address', - name: '', - type: 'address', - }, - ], - stateMutability: 'view', - type: 'function', - }, - { - inputs: [ - { - internalType: 'address', - name: 'spender', - type: 'address', - }, - { - internalType: 'uint256', - name: 'tokenId', - type: 'uint256', - }, - { - internalType: 'uint256', - name: 'deadline', - type: 'uint256', - }, - { - internalType: 'uint8', - name: 'v', - type: 'uint8', - }, - { - internalType: 'bytes32', - name: 'r', - type: 'bytes32', - }, - { - internalType: 'bytes32', - name: 's', - type: 'bytes32', - }, - ], - name: 'permit', - outputs: [], - stateMutability: 'payable', - type: 'function', - }, - { - inputs: [ - { - internalType: 'uint256', - name: 'tokenId', - type: 'uint256', - }, - ], - name: 'positions', - outputs: [ - { - internalType: 'uint96', - name: 'nonce', - type: 'uint96', - }, - { - internalType: 'address', - name: 'operator', - type: 'address', - }, - { - internalType: 'address', - name: 'token0', - type: 'address', - }, - { - internalType: 'address', - name: 'token1', - type: 'address', - }, - { - internalType: 'uint24', - name: 'fee', - type: 'uint24', - }, - { - internalType: 'int24', - name: 'tickLower', - type: 'int24', - }, - { - internalType: 'int24', - name: 'tickUpper', - type: 'int24', - }, - { - internalType: 'uint128', - name: 'liquidity', - type: 'uint128', - }, - { - internalType: 'uint256', - name: 'feeGrowthInside0LastX128', - type: 'uint256', - }, - { - internalType: 'uint256', - name: 'feeGrowthInside1LastX128', - type: 'uint256', - }, - { - internalType: 'uint128', - name: 'tokensOwed0', - type: 'uint128', - }, - { - internalType: 'uint128', - name: 'tokensOwed1', - type: 'uint128', - }, - ], - stateMutability: 'view', - type: 'function', - }, - { - inputs: [], - name: 'refundETH', - outputs: [], - stateMutability: 'payable', - type: 'function', - }, - { - inputs: [ - { - internalType: 'address', - name: 'from', - type: 'address', - }, - { - internalType: 'address', - name: 'to', - type: 'address', - }, - { - internalType: 'uint256', - name: 'tokenId', - type: 'uint256', - }, - ], - name: 'safeTransferFrom', - outputs: [], - stateMutability: 'nonpayable', - type: 'function', - }, - { - inputs: [ - { - internalType: 'address', - name: 'from', - type: 'address', - }, - { - internalType: 'address', - name: 'to', - type: 'address', - }, - { - internalType: 'uint256', - name: 'tokenId', - type: 'uint256', - }, - { - internalType: 'bytes', - name: '_data', - type: 'bytes', - }, - ], - name: 'safeTransferFrom', - outputs: [], - stateMutability: 'nonpayable', - type: 'function', - }, - { - inputs: [ - { - internalType: 'address', - name: 'token', - type: 'address', - }, - { - internalType: 'uint256', - name: 'value', - type: 'uint256', - }, - { - internalType: 'uint256', - name: 'deadline', - type: 'uint256', - }, - { - internalType: 'uint8', - name: 'v', - type: 'uint8', - }, - { - internalType: 'bytes32', - name: 'r', - type: 'bytes32', - }, - { - internalType: 'bytes32', - name: 's', - type: 'bytes32', - }, - ], - name: 'selfPermit', - outputs: [], - stateMutability: 'payable', - type: 'function', - }, - { - inputs: [ - { - internalType: 'address', - name: 'token', - type: 'address', - }, - { - internalType: 'uint256', - name: 'nonce', - type: 'uint256', - }, - { - internalType: 'uint256', - name: 'expiry', - type: 'uint256', - }, - { - internalType: 'uint8', - name: 'v', - type: 'uint8', - }, - { - internalType: 'bytes32', - name: 'r', - type: 'bytes32', - }, - { - internalType: 'bytes32', - name: 's', - type: 'bytes32', - }, - ], - name: 'selfPermitAllowed', - outputs: [], - stateMutability: 'payable', - type: 'function', - }, - { - inputs: [ - { - internalType: 'address', - name: 'token', - type: 'address', - }, - { - internalType: 'uint256', - name: 'nonce', - type: 'uint256', - }, - { - internalType: 'uint256', - name: 'expiry', - type: 'uint256', - }, - { - internalType: 'uint8', - name: 'v', - type: 'uint8', - }, - { - internalType: 'bytes32', - name: 'r', - type: 'bytes32', - }, - { - internalType: 'bytes32', - name: 's', - type: 'bytes32', - }, - ], - name: 'selfPermitAllowedIfNecessary', - outputs: [], - stateMutability: 'payable', - type: 'function', - }, - { - inputs: [ - { - internalType: 'address', - name: 'token', - type: 'address', - }, - { - internalType: 'uint256', - name: 'value', - type: 'uint256', - }, - { - internalType: 'uint256', - name: 'deadline', - type: 'uint256', - }, - { - internalType: 'uint8', - name: 'v', - type: 'uint8', - }, - { - internalType: 'bytes32', - name: 'r', - type: 'bytes32', - }, - { - internalType: 'bytes32', - name: 's', - type: 'bytes32', - }, - ], - name: 'selfPermitIfNecessary', - outputs: [], - stateMutability: 'payable', - type: 'function', - }, - { - inputs: [ - { - internalType: 'address', - name: 'operator', - type: 'address', - }, - { - internalType: 'bool', - name: 'approved', - type: 'bool', - }, - ], - name: 'setApprovalForAll', - outputs: [], - stateMutability: 'nonpayable', - type: 'function', - }, - { - inputs: [ - { - internalType: 'bytes4', - name: 'interfaceId', - type: 'bytes4', - }, - ], - name: 'supportsInterface', - outputs: [ - { - internalType: 'bool', - name: '', - type: 'bool', - }, - ], - stateMutability: 'view', - type: 'function', - }, - { - inputs: [ - { - internalType: 'address', - name: 'token', - type: 'address', - }, - { - internalType: 'uint256', - name: 'amountMinimum', - type: 'uint256', - }, - { - internalType: 'address', - name: 'recipient', - type: 'address', - }, - ], - name: 'sweepToken', - outputs: [], - stateMutability: 'payable', - type: 'function', - }, - { - inputs: [], - name: 'symbol', - outputs: [ - { - internalType: 'string', - name: '', - type: 'string', - }, - ], - stateMutability: 'view', - type: 'function', - }, - { - inputs: [ - { - internalType: 'uint256', - name: 'index', - type: 'uint256', - }, - ], - name: 'tokenByIndex', - outputs: [ - { - internalType: 'uint256', - name: '', - type: 'uint256', - }, - ], - stateMutability: 'view', - type: 'function', - }, - { - inputs: [ - { - internalType: 'address', - name: 'owner', - type: 'address', - }, - { - internalType: 'uint256', - name: 'index', - type: 'uint256', - }, - ], - name: 'tokenOfOwnerByIndex', - outputs: [ - { - internalType: 'uint256', - name: '', - type: 'uint256', - }, - ], - stateMutability: 'view', - type: 'function', - }, - { - inputs: [ - { - internalType: 'uint256', - name: 'tokenId', - type: 'uint256', - }, - ], - name: 'tokenURI', - outputs: [ - { - internalType: 'string', - name: '', - type: 'string', - }, - ], - stateMutability: 'view', - type: 'function', - }, - { - inputs: [], - name: 'totalSupply', - outputs: [ - { - internalType: 'uint256', - name: '', - type: 'uint256', - }, - ], - stateMutability: 'view', - type: 'function', - }, - { - inputs: [ - { - internalType: 'address', - name: 'from', - type: 'address', - }, - { - internalType: 'address', - name: 'to', - type: 'address', - }, - { - internalType: 'uint256', - name: 'tokenId', - type: 'uint256', - }, - ], - name: 'transferFrom', - outputs: [], - stateMutability: 'nonpayable', - type: 'function', - }, - { - inputs: [ - { - internalType: 'uint256', - name: 'amount0Owed', - type: 'uint256', - }, - { - internalType: 'uint256', - name: 'amount1Owed', - type: 'uint256', - }, - { - internalType: 'bytes', - name: 'data', - type: 'bytes', - }, - ], - name: 'uniswapV3MintCallback', - outputs: [], - stateMutability: 'nonpayable', - type: 'function', - }, - { - inputs: [ - { - internalType: 'uint256', - name: 'amountMinimum', - type: 'uint256', - }, - { - internalType: 'address', - name: 'recipient', - type: 'address', - }, - ], - name: 'unwrapWETH9', - outputs: [], - stateMutability: 'payable', - type: 'function', - }, - { - stateMutability: 'payable', - type: 'receive', - }, + { + inputs: [ + { + internalType: 'address', + name: '_factory', + type: 'address', + }, + { + internalType: 'address', + name: '_WETH9', + type: 'address', + }, + { + internalType: 'address', + name: '_tokenDescriptor_', + type: 'address', + }, + ], + stateMutability: 'nonpayable', + type: 'constructor', + }, + { + anonymous: false, + inputs: [ + { + indexed: true, + internalType: 'address', + name: 'owner', + type: 'address', + }, + { + indexed: true, + internalType: 'address', + name: 'approved', + type: 'address', + }, + { + indexed: true, + internalType: 'uint256', + name: 'tokenId', + type: 'uint256', + }, + ], + name: 'Approval', + type: 'event', + }, + { + anonymous: false, + inputs: [ + { + indexed: true, + internalType: 'address', + name: 'owner', + type: 'address', + }, + { + indexed: true, + internalType: 'address', + name: 'operator', + type: 'address', + }, + { + indexed: false, + internalType: 'bool', + name: 'approved', + type: 'bool', + }, + ], + name: 'ApprovalForAll', + type: 'event', + }, + { + anonymous: false, + inputs: [ + { + indexed: true, + internalType: 'uint256', + name: 'tokenId', + type: 'uint256', + }, + { + indexed: false, + internalType: 'address', + name: 'recipient', + type: 'address', + }, + { + indexed: false, + internalType: 'uint256', + name: 'amount0', + type: 'uint256', + }, + { + indexed: false, + internalType: 'uint256', + name: 'amount1', + type: 'uint256', + }, + ], + name: 'Collect', + type: 'event', + }, + { + anonymous: false, + inputs: [ + { + indexed: true, + internalType: 'uint256', + name: 'tokenId', + type: 'uint256', + }, + { + indexed: false, + internalType: 'uint128', + name: 'liquidity', + type: 'uint128', + }, + { + indexed: false, + internalType: 'uint256', + name: 'amount0', + type: 'uint256', + }, + { + indexed: false, + internalType: 'uint256', + name: 'amount1', + type: 'uint256', + }, + ], + name: 'DecreaseLiquidity', + type: 'event', + }, + { + anonymous: false, + inputs: [ + { + indexed: true, + internalType: 'uint256', + name: 'tokenId', + type: 'uint256', + }, + { + indexed: false, + internalType: 'uint128', + name: 'liquidity', + type: 'uint128', + }, + { + indexed: false, + internalType: 'uint256', + name: 'amount0', + type: 'uint256', + }, + { + indexed: false, + internalType: 'uint256', + name: 'amount1', + type: 'uint256', + }, + ], + name: 'IncreaseLiquidity', + type: 'event', + }, + { + anonymous: false, + inputs: [ + { + indexed: true, + internalType: 'address', + name: 'from', + type: 'address', + }, + { + indexed: true, + internalType: 'address', + name: 'to', + type: 'address', + }, + { + indexed: true, + internalType: 'uint256', + name: 'tokenId', + type: 'uint256', + }, + ], + name: 'Transfer', + type: 'event', + }, + { + inputs: [], + name: 'DOMAIN_SEPARATOR', + outputs: [ + { + internalType: 'bytes32', + name: '', + type: 'bytes32', + }, + ], + stateMutability: 'view', + type: 'function', + }, + { + inputs: [], + name: 'PERMIT_TYPEHASH', + outputs: [ + { + internalType: 'bytes32', + name: '', + type: 'bytes32', + }, + ], + stateMutability: 'view', + type: 'function', + }, + { + inputs: [], + name: 'WETH9', + outputs: [ + { + internalType: 'address', + name: '', + type: 'address', + }, + ], + stateMutability: 'view', + type: 'function', + }, + { + inputs: [ + { + internalType: 'address', + name: 'to', + type: 'address', + }, + { + internalType: 'uint256', + name: 'tokenId', + type: 'uint256', + }, + ], + name: 'approve', + outputs: [], + stateMutability: 'nonpayable', + type: 'function', + }, + { + inputs: [ + { + internalType: 'address', + name: 'owner', + type: 'address', + }, + ], + name: 'balanceOf', + outputs: [ + { + internalType: 'uint256', + name: '', + type: 'uint256', + }, + ], + stateMutability: 'view', + type: 'function', + }, + { + inputs: [], + name: 'baseURI', + outputs: [ + { + internalType: 'string', + name: '', + type: 'string', + }, + ], + stateMutability: 'pure', + type: 'function', + }, + { + inputs: [ + { + internalType: 'uint256', + name: 'tokenId', + type: 'uint256', + }, + ], + name: 'burn', + outputs: [], + stateMutability: 'payable', + type: 'function', + }, + { + inputs: [ + { + components: [ + { + internalType: 'uint256', + name: 'tokenId', + type: 'uint256', + }, + { + internalType: 'address', + name: 'recipient', + type: 'address', + }, + { + internalType: 'uint128', + name: 'amount0Max', + type: 'uint128', + }, + { + internalType: 'uint128', + name: 'amount1Max', + type: 'uint128', + }, + ], + internalType: 'struct INonfungiblePositionManager.CollectParams', + name: 'params', + type: 'tuple', + }, + ], + name: 'collect', + outputs: [ + { + internalType: 'uint256', + name: 'amount0', + type: 'uint256', + }, + { + internalType: 'uint256', + name: 'amount1', + type: 'uint256', + }, + ], + stateMutability: 'payable', + type: 'function', + }, + { + inputs: [ + { + internalType: 'address', + name: 'token0', + type: 'address', + }, + { + internalType: 'address', + name: 'token1', + type: 'address', + }, + { + internalType: 'uint24', + name: 'fee', + type: 'uint24', + }, + { + internalType: 'uint160', + name: 'sqrtPriceX96', + type: 'uint160', + }, + ], + name: 'createAndInitializePoolIfNecessary', + outputs: [ + { + internalType: 'address', + name: 'pool', + type: 'address', + }, + ], + stateMutability: 'payable', + type: 'function', + }, + { + inputs: [ + { + components: [ + { + internalType: 'uint256', + name: 'tokenId', + type: 'uint256', + }, + { + internalType: 'uint128', + name: 'liquidity', + type: 'uint128', + }, + { + internalType: 'uint256', + name: 'amount0Min', + type: 'uint256', + }, + { + internalType: 'uint256', + name: 'amount1Min', + type: 'uint256', + }, + { + internalType: 'uint256', + name: 'deadline', + type: 'uint256', + }, + ], + internalType: + 'struct INonfungiblePositionManager.DecreaseLiquidityParams', + name: 'params', + type: 'tuple', + }, + ], + name: 'decreaseLiquidity', + outputs: [ + { + internalType: 'uint256', + name: 'amount0', + type: 'uint256', + }, + { + internalType: 'uint256', + name: 'amount1', + type: 'uint256', + }, + ], + stateMutability: 'payable', + type: 'function', + }, + { + inputs: [], + name: 'factory', + outputs: [ + { + internalType: 'address', + name: '', + type: 'address', + }, + ], + stateMutability: 'view', + type: 'function', + }, + { + inputs: [ + { + internalType: 'uint256', + name: 'tokenId', + type: 'uint256', + }, + ], + name: 'getApproved', + outputs: [ + { + internalType: 'address', + name: '', + type: 'address', + }, + ], + stateMutability: 'view', + type: 'function', + }, + { + inputs: [ + { + components: [ + { + internalType: 'uint256', + name: 'tokenId', + type: 'uint256', + }, + { + internalType: 'uint256', + name: 'amount0Desired', + type: 'uint256', + }, + { + internalType: 'uint256', + name: 'amount1Desired', + type: 'uint256', + }, + { + internalType: 'uint256', + name: 'amount0Min', + type: 'uint256', + }, + { + internalType: 'uint256', + name: 'amount1Min', + type: 'uint256', + }, + { + internalType: 'uint256', + name: 'deadline', + type: 'uint256', + }, + ], + internalType: + 'struct INonfungiblePositionManager.IncreaseLiquidityParams', + name: 'params', + type: 'tuple', + }, + ], + name: 'increaseLiquidity', + outputs: [ + { + internalType: 'uint128', + name: 'liquidity', + type: 'uint128', + }, + { + internalType: 'uint256', + name: 'amount0', + type: 'uint256', + }, + { + internalType: 'uint256', + name: 'amount1', + type: 'uint256', + }, + ], + stateMutability: 'payable', + type: 'function', + }, + { + inputs: [ + { + internalType: 'address', + name: 'owner', + type: 'address', + }, + { + internalType: 'address', + name: 'operator', + type: 'address', + }, + ], + name: 'isApprovedForAll', + outputs: [ + { + internalType: 'bool', + name: '', + type: 'bool', + }, + ], + stateMutability: 'view', + type: 'function', + }, + { + inputs: [ + { + components: [ + { + internalType: 'address', + name: 'token0', + type: 'address', + }, + { + internalType: 'address', + name: 'token1', + type: 'address', + }, + { + internalType: 'uint24', + name: 'fee', + type: 'uint24', + }, + { + internalType: 'int24', + name: 'tickLower', + type: 'int24', + }, + { + internalType: 'int24', + name: 'tickUpper', + type: 'int24', + }, + { + internalType: 'uint256', + name: 'amount0Desired', + type: 'uint256', + }, + { + internalType: 'uint256', + name: 'amount1Desired', + type: 'uint256', + }, + { + internalType: 'uint256', + name: 'amount0Min', + type: 'uint256', + }, + { + internalType: 'uint256', + name: 'amount1Min', + type: 'uint256', + }, + { + internalType: 'address', + name: 'recipient', + type: 'address', + }, + { + internalType: 'uint256', + name: 'deadline', + type: 'uint256', + }, + ], + internalType: 'struct INonfungiblePositionManager.MintParams', + name: 'params', + type: 'tuple', + }, + ], + name: 'mint', + outputs: [ + { + internalType: 'uint256', + name: 'tokenId', + type: 'uint256', + }, + { + internalType: 'uint128', + name: 'liquidity', + type: 'uint128', + }, + { + internalType: 'uint256', + name: 'amount0', + type: 'uint256', + }, + { + internalType: 'uint256', + name: 'amount1', + type: 'uint256', + }, + ], + stateMutability: 'payable', + type: 'function', + }, + { + inputs: [ + { + internalType: 'bytes[]', + name: 'data', + type: 'bytes[]', + }, + ], + name: 'multicall', + outputs: [ + { + internalType: 'bytes[]', + name: 'results', + type: 'bytes[]', + }, + ], + stateMutability: 'payable', + type: 'function', + }, + { + inputs: [], + name: 'name', + outputs: [ + { + internalType: 'string', + name: '', + type: 'string', + }, + ], + stateMutability: 'view', + type: 'function', + }, + { + inputs: [ + { + internalType: 'uint256', + name: 'tokenId', + type: 'uint256', + }, + ], + name: 'ownerOf', + outputs: [ + { + internalType: 'address', + name: '', + type: 'address', + }, + ], + stateMutability: 'view', + type: 'function', + }, + { + inputs: [ + { + internalType: 'address', + name: 'spender', + type: 'address', + }, + { + internalType: 'uint256', + name: 'tokenId', + type: 'uint256', + }, + { + internalType: 'uint256', + name: 'deadline', + type: 'uint256', + }, + { + internalType: 'uint8', + name: 'v', + type: 'uint8', + }, + { + internalType: 'bytes32', + name: 'r', + type: 'bytes32', + }, + { + internalType: 'bytes32', + name: 's', + type: 'bytes32', + }, + ], + name: 'permit', + outputs: [], + stateMutability: 'payable', + type: 'function', + }, + { + inputs: [ + { + internalType: 'uint256', + name: 'tokenId', + type: 'uint256', + }, + ], + name: 'positions', + outputs: [ + { + internalType: 'uint96', + name: 'nonce', + type: 'uint96', + }, + { + internalType: 'address', + name: 'operator', + type: 'address', + }, + { + internalType: 'address', + name: 'token0', + type: 'address', + }, + { + internalType: 'address', + name: 'token1', + type: 'address', + }, + { + internalType: 'uint24', + name: 'fee', + type: 'uint24', + }, + { + internalType: 'int24', + name: 'tickLower', + type: 'int24', + }, + { + internalType: 'int24', + name: 'tickUpper', + type: 'int24', + }, + { + internalType: 'uint128', + name: 'liquidity', + type: 'uint128', + }, + { + internalType: 'uint256', + name: 'feeGrowthInside0LastX128', + type: 'uint256', + }, + { + internalType: 'uint256', + name: 'feeGrowthInside1LastX128', + type: 'uint256', + }, + { + internalType: 'uint128', + name: 'tokensOwed0', + type: 'uint128', + }, + { + internalType: 'uint128', + name: 'tokensOwed1', + type: 'uint128', + }, + ], + stateMutability: 'view', + type: 'function', + }, + { + inputs: [], + name: 'refundETH', + outputs: [], + stateMutability: 'payable', + type: 'function', + }, + { + inputs: [ + { + internalType: 'address', + name: 'from', + type: 'address', + }, + { + internalType: 'address', + name: 'to', + type: 'address', + }, + { + internalType: 'uint256', + name: 'tokenId', + type: 'uint256', + }, + ], + name: 'safeTransferFrom', + outputs: [], + stateMutability: 'nonpayable', + type: 'function', + }, + { + inputs: [ + { + internalType: 'address', + name: 'from', + type: 'address', + }, + { + internalType: 'address', + name: 'to', + type: 'address', + }, + { + internalType: 'uint256', + name: 'tokenId', + type: 'uint256', + }, + { + internalType: 'bytes', + name: '_data', + type: 'bytes', + }, + ], + name: 'safeTransferFrom', + outputs: [], + stateMutability: 'nonpayable', + type: 'function', + }, + { + inputs: [ + { + internalType: 'address', + name: 'token', + type: 'address', + }, + { + internalType: 'uint256', + name: 'value', + type: 'uint256', + }, + { + internalType: 'uint256', + name: 'deadline', + type: 'uint256', + }, + { + internalType: 'uint8', + name: 'v', + type: 'uint8', + }, + { + internalType: 'bytes32', + name: 'r', + type: 'bytes32', + }, + { + internalType: 'bytes32', + name: 's', + type: 'bytes32', + }, + ], + name: 'selfPermit', + outputs: [], + stateMutability: 'payable', + type: 'function', + }, + { + inputs: [ + { + internalType: 'address', + name: 'token', + type: 'address', + }, + { + internalType: 'uint256', + name: 'nonce', + type: 'uint256', + }, + { + internalType: 'uint256', + name: 'expiry', + type: 'uint256', + }, + { + internalType: 'uint8', + name: 'v', + type: 'uint8', + }, + { + internalType: 'bytes32', + name: 'r', + type: 'bytes32', + }, + { + internalType: 'bytes32', + name: 's', + type: 'bytes32', + }, + ], + name: 'selfPermitAllowed', + outputs: [], + stateMutability: 'payable', + type: 'function', + }, + { + inputs: [ + { + internalType: 'address', + name: 'token', + type: 'address', + }, + { + internalType: 'uint256', + name: 'nonce', + type: 'uint256', + }, + { + internalType: 'uint256', + name: 'expiry', + type: 'uint256', + }, + { + internalType: 'uint8', + name: 'v', + type: 'uint8', + }, + { + internalType: 'bytes32', + name: 'r', + type: 'bytes32', + }, + { + internalType: 'bytes32', + name: 's', + type: 'bytes32', + }, + ], + name: 'selfPermitAllowedIfNecessary', + outputs: [], + stateMutability: 'payable', + type: 'function', + }, + { + inputs: [ + { + internalType: 'address', + name: 'token', + type: 'address', + }, + { + internalType: 'uint256', + name: 'value', + type: 'uint256', + }, + { + internalType: 'uint256', + name: 'deadline', + type: 'uint256', + }, + { + internalType: 'uint8', + name: 'v', + type: 'uint8', + }, + { + internalType: 'bytes32', + name: 'r', + type: 'bytes32', + }, + { + internalType: 'bytes32', + name: 's', + type: 'bytes32', + }, + ], + name: 'selfPermitIfNecessary', + outputs: [], + stateMutability: 'payable', + type: 'function', + }, + { + inputs: [ + { + internalType: 'address', + name: 'operator', + type: 'address', + }, + { + internalType: 'bool', + name: 'approved', + type: 'bool', + }, + ], + name: 'setApprovalForAll', + outputs: [], + stateMutability: 'nonpayable', + type: 'function', + }, + { + inputs: [ + { + internalType: 'bytes4', + name: 'interfaceId', + type: 'bytes4', + }, + ], + name: 'supportsInterface', + outputs: [ + { + internalType: 'bool', + name: '', + type: 'bool', + }, + ], + stateMutability: 'view', + type: 'function', + }, + { + inputs: [ + { + internalType: 'address', + name: 'token', + type: 'address', + }, + { + internalType: 'uint256', + name: 'amountMinimum', + type: 'uint256', + }, + { + internalType: 'address', + name: 'recipient', + type: 'address', + }, + ], + name: 'sweepToken', + outputs: [], + stateMutability: 'payable', + type: 'function', + }, + { + inputs: [], + name: 'symbol', + outputs: [ + { + internalType: 'string', + name: '', + type: 'string', + }, + ], + stateMutability: 'view', + type: 'function', + }, + { + inputs: [ + { + internalType: 'uint256', + name: 'index', + type: 'uint256', + }, + ], + name: 'tokenByIndex', + outputs: [ + { + internalType: 'uint256', + name: '', + type: 'uint256', + }, + ], + stateMutability: 'view', + type: 'function', + }, + { + inputs: [ + { + internalType: 'address', + name: 'owner', + type: 'address', + }, + { + internalType: 'uint256', + name: 'index', + type: 'uint256', + }, + ], + name: 'tokenOfOwnerByIndex', + outputs: [ + { + internalType: 'uint256', + name: '', + type: 'uint256', + }, + ], + stateMutability: 'view', + type: 'function', + }, + { + inputs: [ + { + internalType: 'uint256', + name: 'tokenId', + type: 'uint256', + }, + ], + name: 'tokenURI', + outputs: [ + { + internalType: 'string', + name: '', + type: 'string', + }, + ], + stateMutability: 'view', + type: 'function', + }, + { + inputs: [], + name: 'totalSupply', + outputs: [ + { + internalType: 'uint256', + name: '', + type: 'uint256', + }, + ], + stateMutability: 'view', + type: 'function', + }, + { + inputs: [ + { + internalType: 'address', + name: 'from', + type: 'address', + }, + { + internalType: 'address', + name: 'to', + type: 'address', + }, + { + internalType: 'uint256', + name: 'tokenId', + type: 'uint256', + }, + ], + name: 'transferFrom', + outputs: [], + stateMutability: 'nonpayable', + type: 'function', + }, + { + inputs: [ + { + internalType: 'uint256', + name: 'amount0Owed', + type: 'uint256', + }, + { + internalType: 'uint256', + name: 'amount1Owed', + type: 'uint256', + }, + { + internalType: 'bytes', + name: 'data', + type: 'bytes', + }, + ], + name: 'uniswapV3MintCallback', + outputs: [], + stateMutability: 'nonpayable', + type: 'function', + }, + { + inputs: [ + { + internalType: 'uint256', + name: 'amountMinimum', + type: 'uint256', + }, + { + internalType: 'address', + name: 'recipient', + type: 'address', + }, + ], + name: 'unwrapWETH9', + outputs: [], + stateMutability: 'payable', + type: 'function', + }, + { + stateMutability: 'payable', + type: 'receive', + }, ] export const etherscanResponse0x06412d7e = [ - { - inputs: [ - { internalType: 'address', name: '_factory', type: 'address' }, - { internalType: 'address', name: '_WETH9', type: 'address' }, - { internalType: 'address', name: '_tokenDescriptor_', type: 'address' }, - ], - stateMutability: 'nonpayable', - type: 'constructor', - }, - { - anonymous: false, - inputs: [ - { - indexed: true, - internalType: 'address', - name: 'owner', - type: 'address', - }, - { - indexed: true, - internalType: 'address', - name: 'approved', - type: 'address', - }, - { - indexed: true, - internalType: 'uint256', - name: 'tokenId', - type: 'uint256', - }, - ], - name: 'Approval', - type: 'event', - }, - { - anonymous: false, - inputs: [ - { - indexed: true, - internalType: 'address', - name: 'owner', - type: 'address', - }, - { - indexed: true, - internalType: 'address', - name: 'operator', - type: 'address', - }, - { indexed: false, internalType: 'bool', name: 'approved', type: 'bool' }, - ], - name: 'ApprovalForAll', - type: 'event', - }, - { - anonymous: false, - inputs: [ - { - indexed: true, - internalType: 'uint256', - name: 'tokenId', - type: 'uint256', - }, - { - indexed: false, - internalType: 'address', - name: 'recipient', - type: 'address', - }, - { - indexed: false, - internalType: 'uint256', - name: 'amount0', - type: 'uint256', - }, - { - indexed: false, - internalType: 'uint256', - name: 'amount1', - type: 'uint256', - }, - ], - name: 'Collect', - type: 'event', - }, - { - anonymous: false, - inputs: [ - { - indexed: true, - internalType: 'uint256', - name: 'tokenId', - type: 'uint256', - }, - { - indexed: false, - internalType: 'uint128', - name: 'liquidity', - type: 'uint128', - }, - { - indexed: false, - internalType: 'uint256', - name: 'amount0', - type: 'uint256', - }, - { - indexed: false, - internalType: 'uint256', - name: 'amount1', - type: 'uint256', - }, - ], - name: 'DecreaseLiquidity', - type: 'event', - }, - { - anonymous: false, - inputs: [ - { - indexed: true, - internalType: 'uint256', - name: 'tokenId', - type: 'uint256', - }, - { - indexed: false, - internalType: 'uint128', - name: 'liquidity', - type: 'uint128', - }, - { - indexed: false, - internalType: 'uint256', - name: 'amount0', - type: 'uint256', - }, - { - indexed: false, - internalType: 'uint256', - name: 'amount1', - type: 'uint256', - }, - ], - name: 'IncreaseLiquidity', - type: 'event', - }, - { - anonymous: false, - inputs: [ - { indexed: true, internalType: 'address', name: 'from', type: 'address' }, - { indexed: true, internalType: 'address', name: 'to', type: 'address' }, - { - indexed: true, - internalType: 'uint256', - name: 'tokenId', - type: 'uint256', - }, - ], - name: 'Transfer', - type: 'event', - }, - { - inputs: [], - name: 'DOMAIN_SEPARATOR', - outputs: [{ internalType: 'bytes32', name: '', type: 'bytes32' }], - stateMutability: 'view', - type: 'function', - }, - { - inputs: [], - name: 'PERMIT_TYPEHASH', - outputs: [{ internalType: 'bytes32', name: '', type: 'bytes32' }], - stateMutability: 'view', - type: 'function', - }, - { - inputs: [], - name: 'WETH9', - outputs: [{ internalType: 'address', name: '', type: 'address' }], - stateMutability: 'view', - type: 'function', - }, - { - inputs: [ - { internalType: 'address', name: 'to', type: 'address' }, - { internalType: 'uint256', name: 'tokenId', type: 'uint256' }, - ], - name: 'approve', - outputs: [], - stateMutability: 'nonpayable', - type: 'function', - }, - { - inputs: [{ internalType: 'address', name: 'owner', type: 'address' }], - name: 'balanceOf', - outputs: [{ internalType: 'uint256', name: '', type: 'uint256' }], - stateMutability: 'view', - type: 'function', - }, - { - inputs: [], - name: 'baseURI', - outputs: [{ internalType: 'string', name: '', type: 'string' }], - stateMutability: 'pure', - type: 'function', - }, - { - inputs: [{ internalType: 'uint256', name: 'tokenId', type: 'uint256' }], - name: 'burn', - outputs: [], - stateMutability: 'payable', - type: 'function', - }, - { - inputs: [ - { - components: [ - { internalType: 'uint256', name: 'tokenId', type: 'uint256' }, - { internalType: 'address', name: 'recipient', type: 'address' }, - { internalType: 'uint128', name: 'amount0Max', type: 'uint128' }, - { internalType: 'uint128', name: 'amount1Max', type: 'uint128' }, - ], - internalType: 'struct INonfungiblePositionManager.CollectParams', - name: 'params', - type: 'tuple', - }, - ], - name: 'collect', - outputs: [ - { internalType: 'uint256', name: 'amount0', type: 'uint256' }, - { internalType: 'uint256', name: 'amount1', type: 'uint256' }, - ], - stateMutability: 'payable', - type: 'function', - }, - { - inputs: [ - { internalType: 'address', name: 'token0', type: 'address' }, - { internalType: 'address', name: 'token1', type: 'address' }, - { internalType: 'uint24', name: 'fee', type: 'uint24' }, - { internalType: 'uint160', name: 'sqrtPriceX96', type: 'uint160' }, - ], - name: 'createAndInitializePoolIfNecessary', - outputs: [{ internalType: 'address', name: 'pool', type: 'address' }], - stateMutability: 'payable', - type: 'function', - }, - { - inputs: [ - { - components: [ - { internalType: 'uint256', name: 'tokenId', type: 'uint256' }, - { internalType: 'uint128', name: 'liquidity', type: 'uint128' }, - { internalType: 'uint256', name: 'amount0Min', type: 'uint256' }, - { internalType: 'uint256', name: 'amount1Min', type: 'uint256' }, - { internalType: 'uint256', name: 'deadline', type: 'uint256' }, - ], - internalType: - 'struct INonfungiblePositionManager.DecreaseLiquidityParams', - name: 'params', - type: 'tuple', - }, - ], - name: 'decreaseLiquidity', - outputs: [ - { internalType: 'uint256', name: 'amount0', type: 'uint256' }, - { internalType: 'uint256', name: 'amount1', type: 'uint256' }, - ], - stateMutability: 'payable', - type: 'function', - }, - { - inputs: [], - name: 'factory', - outputs: [{ internalType: 'address', name: '', type: 'address' }], - stateMutability: 'view', - type: 'function', - }, - { - inputs: [{ internalType: 'uint256', name: 'tokenId', type: 'uint256' }], - name: 'getApproved', - outputs: [{ internalType: 'address', name: '', type: 'address' }], - stateMutability: 'view', - type: 'function', - }, - { - inputs: [ - { - components: [ - { internalType: 'uint256', name: 'tokenId', type: 'uint256' }, - { internalType: 'uint256', name: 'amount0Desired', type: 'uint256' }, - { internalType: 'uint256', name: 'amount1Desired', type: 'uint256' }, - { internalType: 'uint256', name: 'amount0Min', type: 'uint256' }, - { internalType: 'uint256', name: 'amount1Min', type: 'uint256' }, - { internalType: 'uint256', name: 'deadline', type: 'uint256' }, - ], - internalType: - 'struct INonfungiblePositionManager.IncreaseLiquidityParams', - name: 'params', - type: 'tuple', - }, - ], - name: 'increaseLiquidity', - outputs: [ - { internalType: 'uint128', name: 'liquidity', type: 'uint128' }, - { internalType: 'uint256', name: 'amount0', type: 'uint256' }, - { internalType: 'uint256', name: 'amount1', type: 'uint256' }, - ], - stateMutability: 'payable', - type: 'function', - }, - { - inputs: [ - { internalType: 'address', name: 'owner', type: 'address' }, - { internalType: 'address', name: 'operator', type: 'address' }, - ], - name: 'isApprovedForAll', - outputs: [{ internalType: 'bool', name: '', type: 'bool' }], - stateMutability: 'view', - type: 'function', - }, - { - inputs: [ - { - components: [ - { internalType: 'address', name: 'token0', type: 'address' }, - { internalType: 'address', name: 'token1', type: 'address' }, - { internalType: 'uint24', name: 'fee', type: 'uint24' }, - { internalType: 'int24', name: 'tickLower', type: 'int24' }, - { internalType: 'int24', name: 'tickUpper', type: 'int24' }, - { internalType: 'uint256', name: 'amount0Desired', type: 'uint256' }, - { internalType: 'uint256', name: 'amount1Desired', type: 'uint256' }, - { internalType: 'uint256', name: 'amount0Min', type: 'uint256' }, - { internalType: 'uint256', name: 'amount1Min', type: 'uint256' }, - { internalType: 'address', name: 'recipient', type: 'address' }, - { internalType: 'uint256', name: 'deadline', type: 'uint256' }, - ], - internalType: 'struct INonfungiblePositionManager.MintParams', - name: 'params', - type: 'tuple', - }, - ], - name: 'mint', - outputs: [ - { internalType: 'uint256', name: 'tokenId', type: 'uint256' }, - { internalType: 'uint128', name: 'liquidity', type: 'uint128' }, - { internalType: 'uint256', name: 'amount0', type: 'uint256' }, - { internalType: 'uint256', name: 'amount1', type: 'uint256' }, - ], - stateMutability: 'payable', - type: 'function', - }, - { - inputs: [{ internalType: 'bytes[]', name: 'data', type: 'bytes[]' }], - name: 'multicall', - outputs: [{ internalType: 'bytes[]', name: 'results', type: 'bytes[]' }], - stateMutability: 'payable', - type: 'function', - }, - { - inputs: [], - name: 'name', - outputs: [{ internalType: 'string', name: '', type: 'string' }], - stateMutability: 'view', - type: 'function', - }, - { - inputs: [{ internalType: 'uint256', name: 'tokenId', type: 'uint256' }], - name: 'ownerOf', - outputs: [{ internalType: 'address', name: '', type: 'address' }], - stateMutability: 'view', - type: 'function', - }, - { - inputs: [ - { internalType: 'address', name: 'spender', type: 'address' }, - { internalType: 'uint256', name: 'tokenId', type: 'uint256' }, - { internalType: 'uint256', name: 'deadline', type: 'uint256' }, - { internalType: 'uint8', name: 'v', type: 'uint8' }, - { internalType: 'bytes32', name: 'r', type: 'bytes32' }, - { internalType: 'bytes32', name: 's', type: 'bytes32' }, - ], - name: 'permit', - outputs: [], - stateMutability: 'payable', - type: 'function', - }, - { - inputs: [{ internalType: 'uint256', name: 'tokenId', type: 'uint256' }], - name: 'positions', - outputs: [ - { internalType: 'uint96', name: 'nonce', type: 'uint96' }, - { internalType: 'address', name: 'operator', type: 'address' }, - { internalType: 'address', name: 'token0', type: 'address' }, - { internalType: 'address', name: 'token1', type: 'address' }, - { internalType: 'uint24', name: 'fee', type: 'uint24' }, - { internalType: 'int24', name: 'tickLower', type: 'int24' }, - { internalType: 'int24', name: 'tickUpper', type: 'int24' }, - { internalType: 'uint128', name: 'liquidity', type: 'uint128' }, - { - internalType: 'uint256', - name: 'feeGrowthInside0LastX128', - type: 'uint256', - }, - { - internalType: 'uint256', - name: 'feeGrowthInside1LastX128', - type: 'uint256', - }, - { internalType: 'uint128', name: 'tokensOwed0', type: 'uint128' }, - { internalType: 'uint128', name: 'tokensOwed1', type: 'uint128' }, - ], - stateMutability: 'view', - type: 'function', - }, - { - inputs: [], - name: 'refundETH', - outputs: [], - stateMutability: 'payable', - type: 'function', - }, - { - inputs: [ - { internalType: 'address', name: 'from', type: 'address' }, - { internalType: 'address', name: 'to', type: 'address' }, - { internalType: 'uint256', name: 'tokenId', type: 'uint256' }, - ], - name: 'safeTransferFrom', - outputs: [], - stateMutability: 'nonpayable', - type: 'function', - }, - { - inputs: [ - { internalType: 'address', name: 'from', type: 'address' }, - { internalType: 'address', name: 'to', type: 'address' }, - { internalType: 'uint256', name: 'tokenId', type: 'uint256' }, - { internalType: 'bytes', name: '_data', type: 'bytes' }, - ], - name: 'safeTransferFrom', - outputs: [], - stateMutability: 'nonpayable', - type: 'function', - }, - { - inputs: [ - { internalType: 'address', name: 'token', type: 'address' }, - { internalType: 'uint256', name: 'value', type: 'uint256' }, - { internalType: 'uint256', name: 'deadline', type: 'uint256' }, - { internalType: 'uint8', name: 'v', type: 'uint8' }, - { internalType: 'bytes32', name: 'r', type: 'bytes32' }, - { internalType: 'bytes32', name: 's', type: 'bytes32' }, - ], - name: 'selfPermit', - outputs: [], - stateMutability: 'payable', - type: 'function', - }, - { - inputs: [ - { internalType: 'address', name: 'token', type: 'address' }, - { internalType: 'uint256', name: 'nonce', type: 'uint256' }, - { internalType: 'uint256', name: 'expiry', type: 'uint256' }, - { internalType: 'uint8', name: 'v', type: 'uint8' }, - { internalType: 'bytes32', name: 'r', type: 'bytes32' }, - { internalType: 'bytes32', name: 's', type: 'bytes32' }, - ], - name: 'selfPermitAllowed', - outputs: [], - stateMutability: 'payable', - type: 'function', - }, - { - inputs: [ - { internalType: 'address', name: 'token', type: 'address' }, - { internalType: 'uint256', name: 'nonce', type: 'uint256' }, - { internalType: 'uint256', name: 'expiry', type: 'uint256' }, - { internalType: 'uint8', name: 'v', type: 'uint8' }, - { internalType: 'bytes32', name: 'r', type: 'bytes32' }, - { internalType: 'bytes32', name: 's', type: 'bytes32' }, - ], - name: 'selfPermitAllowedIfNecessary', - outputs: [], - stateMutability: 'payable', - type: 'function', - }, - { - inputs: [ - { internalType: 'address', name: 'token', type: 'address' }, - { internalType: 'uint256', name: 'value', type: 'uint256' }, - { internalType: 'uint256', name: 'deadline', type: 'uint256' }, - { internalType: 'uint8', name: 'v', type: 'uint8' }, - { internalType: 'bytes32', name: 'r', type: 'bytes32' }, - { internalType: 'bytes32', name: 's', type: 'bytes32' }, - ], - name: 'selfPermitIfNecessary', - outputs: [], - stateMutability: 'payable', - type: 'function', - }, - { - inputs: [ - { internalType: 'address', name: 'operator', type: 'address' }, - { internalType: 'bool', name: 'approved', type: 'bool' }, - ], - name: 'setApprovalForAll', - outputs: [], - stateMutability: 'nonpayable', - type: 'function', - }, - { - inputs: [{ internalType: 'bytes4', name: 'interfaceId', type: 'bytes4' }], - name: 'supportsInterface', - outputs: [{ internalType: 'bool', name: '', type: 'bool' }], - stateMutability: 'view', - type: 'function', - }, - { - inputs: [ - { internalType: 'address', name: 'token', type: 'address' }, - { internalType: 'uint256', name: 'amountMinimum', type: 'uint256' }, - { internalType: 'address', name: 'recipient', type: 'address' }, - ], - name: 'sweepToken', - outputs: [], - stateMutability: 'payable', - type: 'function', - }, - { - inputs: [], - name: 'symbol', - outputs: [{ internalType: 'string', name: '', type: 'string' }], - stateMutability: 'view', - type: 'function', - }, - { - inputs: [{ internalType: 'uint256', name: 'index', type: 'uint256' }], - name: 'tokenByIndex', - outputs: [{ internalType: 'uint256', name: '', type: 'uint256' }], - stateMutability: 'view', - type: 'function', - }, - { - inputs: [ - { internalType: 'address', name: 'owner', type: 'address' }, - { internalType: 'uint256', name: 'index', type: 'uint256' }, - ], - name: 'tokenOfOwnerByIndex', - outputs: [{ internalType: 'uint256', name: '', type: 'uint256' }], - stateMutability: 'view', - type: 'function', - }, - { - inputs: [{ internalType: 'uint256', name: 'tokenId', type: 'uint256' }], - name: 'tokenURI', - outputs: [{ internalType: 'string', name: '', type: 'string' }], - stateMutability: 'view', - type: 'function', - }, - { - inputs: [], - name: 'totalSupply', - outputs: [{ internalType: 'uint256', name: '', type: 'uint256' }], - stateMutability: 'view', - type: 'function', - }, - { - inputs: [ - { internalType: 'address', name: 'from', type: 'address' }, - { internalType: 'address', name: 'to', type: 'address' }, - { internalType: 'uint256', name: 'tokenId', type: 'uint256' }, - ], - name: 'transferFrom', - outputs: [], - stateMutability: 'nonpayable', - type: 'function', - }, - { - inputs: [ - { internalType: 'uint256', name: 'amount0Owed', type: 'uint256' }, - { internalType: 'uint256', name: 'amount1Owed', type: 'uint256' }, - { internalType: 'bytes', name: 'data', type: 'bytes' }, - ], - name: 'uniswapV3MintCallback', - outputs: [], - stateMutability: 'nonpayable', - type: 'function', - }, - { - inputs: [ - { internalType: 'uint256', name: 'amountMinimum', type: 'uint256' }, - { internalType: 'address', name: 'recipient', type: 'address' }, - ], - name: 'unwrapWETH9', - outputs: [], - stateMutability: 'payable', - type: 'function', - }, - { stateMutability: 'payable', type: 'receive' }, + { + inputs: [ + { internalType: 'address', name: '_factory', type: 'address' }, + { internalType: 'address', name: '_WETH9', type: 'address' }, + { internalType: 'address', name: '_tokenDescriptor_', type: 'address' }, + ], + stateMutability: 'nonpayable', + type: 'constructor', + }, + { + anonymous: false, + inputs: [ + { + indexed: true, + internalType: 'address', + name: 'owner', + type: 'address', + }, + { + indexed: true, + internalType: 'address', + name: 'approved', + type: 'address', + }, + { + indexed: true, + internalType: 'uint256', + name: 'tokenId', + type: 'uint256', + }, + ], + name: 'Approval', + type: 'event', + }, + { + anonymous: false, + inputs: [ + { + indexed: true, + internalType: 'address', + name: 'owner', + type: 'address', + }, + { + indexed: true, + internalType: 'address', + name: 'operator', + type: 'address', + }, + { indexed: false, internalType: 'bool', name: 'approved', type: 'bool' }, + ], + name: 'ApprovalForAll', + type: 'event', + }, + { + anonymous: false, + inputs: [ + { + indexed: true, + internalType: 'uint256', + name: 'tokenId', + type: 'uint256', + }, + { + indexed: false, + internalType: 'address', + name: 'recipient', + type: 'address', + }, + { + indexed: false, + internalType: 'uint256', + name: 'amount0', + type: 'uint256', + }, + { + indexed: false, + internalType: 'uint256', + name: 'amount1', + type: 'uint256', + }, + ], + name: 'Collect', + type: 'event', + }, + { + anonymous: false, + inputs: [ + { + indexed: true, + internalType: 'uint256', + name: 'tokenId', + type: 'uint256', + }, + { + indexed: false, + internalType: 'uint128', + name: 'liquidity', + type: 'uint128', + }, + { + indexed: false, + internalType: 'uint256', + name: 'amount0', + type: 'uint256', + }, + { + indexed: false, + internalType: 'uint256', + name: 'amount1', + type: 'uint256', + }, + ], + name: 'DecreaseLiquidity', + type: 'event', + }, + { + anonymous: false, + inputs: [ + { + indexed: true, + internalType: 'uint256', + name: 'tokenId', + type: 'uint256', + }, + { + indexed: false, + internalType: 'uint128', + name: 'liquidity', + type: 'uint128', + }, + { + indexed: false, + internalType: 'uint256', + name: 'amount0', + type: 'uint256', + }, + { + indexed: false, + internalType: 'uint256', + name: 'amount1', + type: 'uint256', + }, + ], + name: 'IncreaseLiquidity', + type: 'event', + }, + { + anonymous: false, + inputs: [ + { indexed: true, internalType: 'address', name: 'from', type: 'address' }, + { indexed: true, internalType: 'address', name: 'to', type: 'address' }, + { + indexed: true, + internalType: 'uint256', + name: 'tokenId', + type: 'uint256', + }, + ], + name: 'Transfer', + type: 'event', + }, + { + inputs: [], + name: 'DOMAIN_SEPARATOR', + outputs: [{ internalType: 'bytes32', name: '', type: 'bytes32' }], + stateMutability: 'view', + type: 'function', + }, + { + inputs: [], + name: 'PERMIT_TYPEHASH', + outputs: [{ internalType: 'bytes32', name: '', type: 'bytes32' }], + stateMutability: 'view', + type: 'function', + }, + { + inputs: [], + name: 'WETH9', + outputs: [{ internalType: 'address', name: '', type: 'address' }], + stateMutability: 'view', + type: 'function', + }, + { + inputs: [ + { internalType: 'address', name: 'to', type: 'address' }, + { internalType: 'uint256', name: 'tokenId', type: 'uint256' }, + ], + name: 'approve', + outputs: [], + stateMutability: 'nonpayable', + type: 'function', + }, + { + inputs: [{ internalType: 'address', name: 'owner', type: 'address' }], + name: 'balanceOf', + outputs: [{ internalType: 'uint256', name: '', type: 'uint256' }], + stateMutability: 'view', + type: 'function', + }, + { + inputs: [], + name: 'baseURI', + outputs: [{ internalType: 'string', name: '', type: 'string' }], + stateMutability: 'pure', + type: 'function', + }, + { + inputs: [{ internalType: 'uint256', name: 'tokenId', type: 'uint256' }], + name: 'burn', + outputs: [], + stateMutability: 'payable', + type: 'function', + }, + { + inputs: [ + { + components: [ + { internalType: 'uint256', name: 'tokenId', type: 'uint256' }, + { internalType: 'address', name: 'recipient', type: 'address' }, + { internalType: 'uint128', name: 'amount0Max', type: 'uint128' }, + { internalType: 'uint128', name: 'amount1Max', type: 'uint128' }, + ], + internalType: 'struct INonfungiblePositionManager.CollectParams', + name: 'params', + type: 'tuple', + }, + ], + name: 'collect', + outputs: [ + { internalType: 'uint256', name: 'amount0', type: 'uint256' }, + { internalType: 'uint256', name: 'amount1', type: 'uint256' }, + ], + stateMutability: 'payable', + type: 'function', + }, + { + inputs: [ + { internalType: 'address', name: 'token0', type: 'address' }, + { internalType: 'address', name: 'token1', type: 'address' }, + { internalType: 'uint24', name: 'fee', type: 'uint24' }, + { internalType: 'uint160', name: 'sqrtPriceX96', type: 'uint160' }, + ], + name: 'createAndInitializePoolIfNecessary', + outputs: [{ internalType: 'address', name: 'pool', type: 'address' }], + stateMutability: 'payable', + type: 'function', + }, + { + inputs: [ + { + components: [ + { internalType: 'uint256', name: 'tokenId', type: 'uint256' }, + { internalType: 'uint128', name: 'liquidity', type: 'uint128' }, + { internalType: 'uint256', name: 'amount0Min', type: 'uint256' }, + { internalType: 'uint256', name: 'amount1Min', type: 'uint256' }, + { internalType: 'uint256', name: 'deadline', type: 'uint256' }, + ], + internalType: + 'struct INonfungiblePositionManager.DecreaseLiquidityParams', + name: 'params', + type: 'tuple', + }, + ], + name: 'decreaseLiquidity', + outputs: [ + { internalType: 'uint256', name: 'amount0', type: 'uint256' }, + { internalType: 'uint256', name: 'amount1', type: 'uint256' }, + ], + stateMutability: 'payable', + type: 'function', + }, + { + inputs: [], + name: 'factory', + outputs: [{ internalType: 'address', name: '', type: 'address' }], + stateMutability: 'view', + type: 'function', + }, + { + inputs: [{ internalType: 'uint256', name: 'tokenId', type: 'uint256' }], + name: 'getApproved', + outputs: [{ internalType: 'address', name: '', type: 'address' }], + stateMutability: 'view', + type: 'function', + }, + { + inputs: [ + { + components: [ + { internalType: 'uint256', name: 'tokenId', type: 'uint256' }, + { internalType: 'uint256', name: 'amount0Desired', type: 'uint256' }, + { internalType: 'uint256', name: 'amount1Desired', type: 'uint256' }, + { internalType: 'uint256', name: 'amount0Min', type: 'uint256' }, + { internalType: 'uint256', name: 'amount1Min', type: 'uint256' }, + { internalType: 'uint256', name: 'deadline', type: 'uint256' }, + ], + internalType: + 'struct INonfungiblePositionManager.IncreaseLiquidityParams', + name: 'params', + type: 'tuple', + }, + ], + name: 'increaseLiquidity', + outputs: [ + { internalType: 'uint128', name: 'liquidity', type: 'uint128' }, + { internalType: 'uint256', name: 'amount0', type: 'uint256' }, + { internalType: 'uint256', name: 'amount1', type: 'uint256' }, + ], + stateMutability: 'payable', + type: 'function', + }, + { + inputs: [ + { internalType: 'address', name: 'owner', type: 'address' }, + { internalType: 'address', name: 'operator', type: 'address' }, + ], + name: 'isApprovedForAll', + outputs: [{ internalType: 'bool', name: '', type: 'bool' }], + stateMutability: 'view', + type: 'function', + }, + { + inputs: [ + { + components: [ + { internalType: 'address', name: 'token0', type: 'address' }, + { internalType: 'address', name: 'token1', type: 'address' }, + { internalType: 'uint24', name: 'fee', type: 'uint24' }, + { internalType: 'int24', name: 'tickLower', type: 'int24' }, + { internalType: 'int24', name: 'tickUpper', type: 'int24' }, + { internalType: 'uint256', name: 'amount0Desired', type: 'uint256' }, + { internalType: 'uint256', name: 'amount1Desired', type: 'uint256' }, + { internalType: 'uint256', name: 'amount0Min', type: 'uint256' }, + { internalType: 'uint256', name: 'amount1Min', type: 'uint256' }, + { internalType: 'address', name: 'recipient', type: 'address' }, + { internalType: 'uint256', name: 'deadline', type: 'uint256' }, + ], + internalType: 'struct INonfungiblePositionManager.MintParams', + name: 'params', + type: 'tuple', + }, + ], + name: 'mint', + outputs: [ + { internalType: 'uint256', name: 'tokenId', type: 'uint256' }, + { internalType: 'uint128', name: 'liquidity', type: 'uint128' }, + { internalType: 'uint256', name: 'amount0', type: 'uint256' }, + { internalType: 'uint256', name: 'amount1', type: 'uint256' }, + ], + stateMutability: 'payable', + type: 'function', + }, + { + inputs: [{ internalType: 'bytes[]', name: 'data', type: 'bytes[]' }], + name: 'multicall', + outputs: [{ internalType: 'bytes[]', name: 'results', type: 'bytes[]' }], + stateMutability: 'payable', + type: 'function', + }, + { + inputs: [], + name: 'name', + outputs: [{ internalType: 'string', name: '', type: 'string' }], + stateMutability: 'view', + type: 'function', + }, + { + inputs: [{ internalType: 'uint256', name: 'tokenId', type: 'uint256' }], + name: 'ownerOf', + outputs: [{ internalType: 'address', name: '', type: 'address' }], + stateMutability: 'view', + type: 'function', + }, + { + inputs: [ + { internalType: 'address', name: 'spender', type: 'address' }, + { internalType: 'uint256', name: 'tokenId', type: 'uint256' }, + { internalType: 'uint256', name: 'deadline', type: 'uint256' }, + { internalType: 'uint8', name: 'v', type: 'uint8' }, + { internalType: 'bytes32', name: 'r', type: 'bytes32' }, + { internalType: 'bytes32', name: 's', type: 'bytes32' }, + ], + name: 'permit', + outputs: [], + stateMutability: 'payable', + type: 'function', + }, + { + inputs: [{ internalType: 'uint256', name: 'tokenId', type: 'uint256' }], + name: 'positions', + outputs: [ + { internalType: 'uint96', name: 'nonce', type: 'uint96' }, + { internalType: 'address', name: 'operator', type: 'address' }, + { internalType: 'address', name: 'token0', type: 'address' }, + { internalType: 'address', name: 'token1', type: 'address' }, + { internalType: 'uint24', name: 'fee', type: 'uint24' }, + { internalType: 'int24', name: 'tickLower', type: 'int24' }, + { internalType: 'int24', name: 'tickUpper', type: 'int24' }, + { internalType: 'uint128', name: 'liquidity', type: 'uint128' }, + { + internalType: 'uint256', + name: 'feeGrowthInside0LastX128', + type: 'uint256', + }, + { + internalType: 'uint256', + name: 'feeGrowthInside1LastX128', + type: 'uint256', + }, + { internalType: 'uint128', name: 'tokensOwed0', type: 'uint128' }, + { internalType: 'uint128', name: 'tokensOwed1', type: 'uint128' }, + ], + stateMutability: 'view', + type: 'function', + }, + { + inputs: [], + name: 'refundETH', + outputs: [], + stateMutability: 'payable', + type: 'function', + }, + { + inputs: [ + { internalType: 'address', name: 'from', type: 'address' }, + { internalType: 'address', name: 'to', type: 'address' }, + { internalType: 'uint256', name: 'tokenId', type: 'uint256' }, + ], + name: 'safeTransferFrom', + outputs: [], + stateMutability: 'nonpayable', + type: 'function', + }, + { + inputs: [ + { internalType: 'address', name: 'from', type: 'address' }, + { internalType: 'address', name: 'to', type: 'address' }, + { internalType: 'uint256', name: 'tokenId', type: 'uint256' }, + { internalType: 'bytes', name: '_data', type: 'bytes' }, + ], + name: 'safeTransferFrom', + outputs: [], + stateMutability: 'nonpayable', + type: 'function', + }, + { + inputs: [ + { internalType: 'address', name: 'token', type: 'address' }, + { internalType: 'uint256', name: 'value', type: 'uint256' }, + { internalType: 'uint256', name: 'deadline', type: 'uint256' }, + { internalType: 'uint8', name: 'v', type: 'uint8' }, + { internalType: 'bytes32', name: 'r', type: 'bytes32' }, + { internalType: 'bytes32', name: 's', type: 'bytes32' }, + ], + name: 'selfPermit', + outputs: [], + stateMutability: 'payable', + type: 'function', + }, + { + inputs: [ + { internalType: 'address', name: 'token', type: 'address' }, + { internalType: 'uint256', name: 'nonce', type: 'uint256' }, + { internalType: 'uint256', name: 'expiry', type: 'uint256' }, + { internalType: 'uint8', name: 'v', type: 'uint8' }, + { internalType: 'bytes32', name: 'r', type: 'bytes32' }, + { internalType: 'bytes32', name: 's', type: 'bytes32' }, + ], + name: 'selfPermitAllowed', + outputs: [], + stateMutability: 'payable', + type: 'function', + }, + { + inputs: [ + { internalType: 'address', name: 'token', type: 'address' }, + { internalType: 'uint256', name: 'nonce', type: 'uint256' }, + { internalType: 'uint256', name: 'expiry', type: 'uint256' }, + { internalType: 'uint8', name: 'v', type: 'uint8' }, + { internalType: 'bytes32', name: 'r', type: 'bytes32' }, + { internalType: 'bytes32', name: 's', type: 'bytes32' }, + ], + name: 'selfPermitAllowedIfNecessary', + outputs: [], + stateMutability: 'payable', + type: 'function', + }, + { + inputs: [ + { internalType: 'address', name: 'token', type: 'address' }, + { internalType: 'uint256', name: 'value', type: 'uint256' }, + { internalType: 'uint256', name: 'deadline', type: 'uint256' }, + { internalType: 'uint8', name: 'v', type: 'uint8' }, + { internalType: 'bytes32', name: 'r', type: 'bytes32' }, + { internalType: 'bytes32', name: 's', type: 'bytes32' }, + ], + name: 'selfPermitIfNecessary', + outputs: [], + stateMutability: 'payable', + type: 'function', + }, + { + inputs: [ + { internalType: 'address', name: 'operator', type: 'address' }, + { internalType: 'bool', name: 'approved', type: 'bool' }, + ], + name: 'setApprovalForAll', + outputs: [], + stateMutability: 'nonpayable', + type: 'function', + }, + { + inputs: [{ internalType: 'bytes4', name: 'interfaceId', type: 'bytes4' }], + name: 'supportsInterface', + outputs: [{ internalType: 'bool', name: '', type: 'bool' }], + stateMutability: 'view', + type: 'function', + }, + { + inputs: [ + { internalType: 'address', name: 'token', type: 'address' }, + { internalType: 'uint256', name: 'amountMinimum', type: 'uint256' }, + { internalType: 'address', name: 'recipient', type: 'address' }, + ], + name: 'sweepToken', + outputs: [], + stateMutability: 'payable', + type: 'function', + }, + { + inputs: [], + name: 'symbol', + outputs: [{ internalType: 'string', name: '', type: 'string' }], + stateMutability: 'view', + type: 'function', + }, + { + inputs: [{ internalType: 'uint256', name: 'index', type: 'uint256' }], + name: 'tokenByIndex', + outputs: [{ internalType: 'uint256', name: '', type: 'uint256' }], + stateMutability: 'view', + type: 'function', + }, + { + inputs: [ + { internalType: 'address', name: 'owner', type: 'address' }, + { internalType: 'uint256', name: 'index', type: 'uint256' }, + ], + name: 'tokenOfOwnerByIndex', + outputs: [{ internalType: 'uint256', name: '', type: 'uint256' }], + stateMutability: 'view', + type: 'function', + }, + { + inputs: [{ internalType: 'uint256', name: 'tokenId', type: 'uint256' }], + name: 'tokenURI', + outputs: [{ internalType: 'string', name: '', type: 'string' }], + stateMutability: 'view', + type: 'function', + }, + { + inputs: [], + name: 'totalSupply', + outputs: [{ internalType: 'uint256', name: '', type: 'uint256' }], + stateMutability: 'view', + type: 'function', + }, + { + inputs: [ + { internalType: 'address', name: 'from', type: 'address' }, + { internalType: 'address', name: 'to', type: 'address' }, + { internalType: 'uint256', name: 'tokenId', type: 'uint256' }, + ], + name: 'transferFrom', + outputs: [], + stateMutability: 'nonpayable', + type: 'function', + }, + { + inputs: [ + { internalType: 'uint256', name: 'amount0Owed', type: 'uint256' }, + { internalType: 'uint256', name: 'amount1Owed', type: 'uint256' }, + { internalType: 'bytes', name: 'data', type: 'bytes' }, + ], + name: 'uniswapV3MintCallback', + outputs: [], + stateMutability: 'nonpayable', + type: 'function', + }, + { + inputs: [ + { internalType: 'uint256', name: 'amountMinimum', type: 'uint256' }, + { internalType: 'address', name: 'recipient', type: 'address' }, + ], + name: 'unwrapWETH9', + outputs: [], + stateMutability: 'payable', + type: 'function', + }, + { stateMutability: 'payable', type: 'receive' }, ] diff --git a/packages/sdk/src/__test__/integration/__mocks__/fetchActiveWallet.json b/packages/sdk/src/__test__/integration/__mocks__/fetchActiveWallet.json index fb3d60c8..e62c22b0 100644 --- a/packages/sdk/src/__test__/integration/__mocks__/fetchActiveWallet.json +++ b/packages/sdk/src/__test__/integration/__mocks__/fetchActiveWallet.json @@ -1,4 +1,4 @@ { - "status": 200, - "message": "01003bcd1aa00d81002787d6c0b1220a4b90cfa9e1a60a52898a7e93f11071d2c0aa0326334a22a757f1a60a5db6133eb0fa4e2abe2a76b2bcecc63fd04fff0faf216e09acda8fea4499773f0ab7eef76d1d622acc89bca480f214bd5f4f1f1fe15178e9cb4eddcb390b1cb58a8a4fef696366f93e898ef2513479a045534b4a8eb12f5dcc3e7fa32158b4e6c4d6117f0cf6985034bb16e4bc98c1f8618751c547b76b896c23255ef0a5c94a83a7b1cd2b6d80d9249e238cc72481d0e121e3d9eee8289ce1039a51e7549f057eb071cd987da5d930b584e9b72adb3c10a46ab7770340fb419ee7f4902203a4de8f8fb5d9e47fcf607f606ab696062eb606b55bb59c61351c34dd141184546603026825d410165f34c9fa5ddc767d19d87bbdb62ebb62d69fe28fc4382b56d86b9e6465144a39ef63dcefc249a7a774f586c949c6fa80662a6d5160ebc941a8c2175273133563ed62ede65a3f86db8dc88d8b265874b05e8cddf761889a3b0f88851d5ecb44bde41b80ab6b16da50ba2dcbab2547059bc739b3d72a65b2b73e4b1d213fe2845c3ddfe3a4b89f12b960efe3e529814e8af72d7870bb7463d94d5de6a248e07b9ab35abedc9d5ff9a56fa694f8a7c8b488b5d99d72f73695e03e9af7e168e25c8e623c5f6c2083522eaeb5c22dbdb96b86b4151db81749606b04fb5d90a36b2152c11cb969cb517eb82be5ef64080f21bcc9e0d8ab605284426e1ceed0c5bdd723a0684bff52e0aaeadd808ae34d5c9925a5d001aedf08450b3735c9163b6cc06c27cf799b9547fdc7394c823f646fc963b0157084da1f80229861c7505ea8f5b2632b466fb65dad5c14324c5bfa393eb680acc88826ee44d460f0e8a68f0327efdf4f6d6a9f3b84adc880d68b26bdbd810e4e4108c207b5ae16398a923ee99fce38ae919741d61b32b14eb7d2d93afc62c7b158838640b0c05166dfc336447db72a14486a2052654b91414e1c9770ef114b406c2da1362ef0dfad67d4596928a80203e37d55eb34140bfc08e8de3b08e6710a5d5dcaea4ba7218e9486d5b47ad21322cde46de690f28e1a9e65ee0f87ecf9f5677fca297aed8dc80d31b98052d23f3e98694aa1bdf604a07ce7e4c6f3003b8b05cd7f9d12834d46697128a938475ffdb959a5eb0fa8496063c583afc95f60adc837878c0253f48d0308a0b00f43deab2859e0f80b3371d565a26289d8fd4497959610871853a246eca4c4479c191fdd639591b5f6d30cd1bc43a361c1fe0552a5d0da0ed3bad45a315cd5bd22cec8e3d5e72cba511d648db4ccfa879693edba0c3be2dad2575e35572b8778358a7741266af08be09aadfa4fb4e1b319498177141456464f1205c9eba6ba70c1b54b62c9731e13e6b237c4a3054fa0e5e3c8c44d270313fa2bd983ecfbd39d9515ded9ab10229b88c3817325e4011ae523e7e8ef3c64155f7de97c49db23e2dc878e6a4323d33f386a7a30bad4115b6014f70045bf40a3ebf62018b983f2347d404d15b6f85efea7416cc96eb286191d6ae445000e888076019156b918b06924a11af86848559e786fcc32e8ec8ab78c768147893eac84f8c5768b934fbfa53cb51553c96dc96f55c17542501096b358d6c93991810aef3c3fea8a2acaee05460979e063d45a1c2f9df8af32d6d197b70e6baf0a1e1dd854aab30af389c0adf2e3dceb06c45be1c106991e63c2aa9d490ec14aef9bdacb4444065e42f934dab13d98232d4e3423edacfe0d45d21c275611e32216fd1d0f9b8bdff2220636436591ea1ec9aa9edbd8ce9918a05be052785233ea7a77cfcbc2cf99d78611369c1de30357f96304d641a36dd06a37648b22a0c71502952777b8dc6357378e6f7498b84cd0f3a995f427b7ba515bc747f95007181966da77989d1a12591ca642997b4ed8d5ac1e9987ece7795c3fcdb1537cc137a83dc2b3f92eabcf71062fab5954cb8209034655340420ddc738927122695d419588aaf17e474f4cf9a17b4bee303f199bbe6e8532f9676e4d661e1d723a27cb8a72ccb55b25c7a737fae013428318da1e431b45846ab17a44df40af30be3f1828a6a5fa94300b9073f9cb684b226919fe48ef8d4fd4d3a54962ab44641284e20e3f95942decba15fe4e317a44043bc5c688131af1c90878c4b3f606f7e470dbd554536bb4e782406737ebbaf895bd7c7e872cad1a69f5b8da0cbd4b42e5e3bff54dcf210cbf5a1f268346a4a06939e1c7387eb6e8feae01b29d0cec0825666479c989f5b6f87f5ecd9d5c019f6cd3fb0ff1982c4a524c8e6f27d538b3692940f2efb99aabe7de08144ad16bb2dc3fbb364d52d1d60d05358cf7e2f59fdbaf5b997913fe835effbf1559b465570ea5b2536cbd12e09c7bbc7a09a52947922c44f69d6fbeb77a138810f2296c890835bc29b7057a5e1aa01a71f6c80358106b80a596ba3383000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000dd784421" + "status": 200, + "message": "01003bcd1aa00d81002787d6c0b1220a4b90cfa9e1a60a52898a7e93f11071d2c0aa0326334a22a757f1a60a5db6133eb0fa4e2abe2a76b2bcecc63fd04fff0faf216e09acda8fea4499773f0ab7eef76d1d622acc89bca480f214bd5f4f1f1fe15178e9cb4eddcb390b1cb58a8a4fef696366f93e898ef2513479a045534b4a8eb12f5dcc3e7fa32158b4e6c4d6117f0cf6985034bb16e4bc98c1f8618751c547b76b896c23255ef0a5c94a83a7b1cd2b6d80d9249e238cc72481d0e121e3d9eee8289ce1039a51e7549f057eb071cd987da5d930b584e9b72adb3c10a46ab7770340fb419ee7f4902203a4de8f8fb5d9e47fcf607f606ab696062eb606b55bb59c61351c34dd141184546603026825d410165f34c9fa5ddc767d19d87bbdb62ebb62d69fe28fc4382b56d86b9e6465144a39ef63dcefc249a7a774f586c949c6fa80662a6d5160ebc941a8c2175273133563ed62ede65a3f86db8dc88d8b265874b05e8cddf761889a3b0f88851d5ecb44bde41b80ab6b16da50ba2dcbab2547059bc739b3d72a65b2b73e4b1d213fe2845c3ddfe3a4b89f12b960efe3e529814e8af72d7870bb7463d94d5de6a248e07b9ab35abedc9d5ff9a56fa694f8a7c8b488b5d99d72f73695e03e9af7e168e25c8e623c5f6c2083522eaeb5c22dbdb96b86b4151db81749606b04fb5d90a36b2152c11cb969cb517eb82be5ef64080f21bcc9e0d8ab605284426e1ceed0c5bdd723a0684bff52e0aaeadd808ae34d5c9925a5d001aedf08450b3735c9163b6cc06c27cf799b9547fdc7394c823f646fc963b0157084da1f80229861c7505ea8f5b2632b466fb65dad5c14324c5bfa393eb680acc88826ee44d460f0e8a68f0327efdf4f6d6a9f3b84adc880d68b26bdbd810e4e4108c207b5ae16398a923ee99fce38ae919741d61b32b14eb7d2d93afc62c7b158838640b0c05166dfc336447db72a14486a2052654b91414e1c9770ef114b406c2da1362ef0dfad67d4596928a80203e37d55eb34140bfc08e8de3b08e6710a5d5dcaea4ba7218e9486d5b47ad21322cde46de690f28e1a9e65ee0f87ecf9f5677fca297aed8dc80d31b98052d23f3e98694aa1bdf604a07ce7e4c6f3003b8b05cd7f9d12834d46697128a938475ffdb959a5eb0fa8496063c583afc95f60adc837878c0253f48d0308a0b00f43deab2859e0f80b3371d565a26289d8fd4497959610871853a246eca4c4479c191fdd639591b5f6d30cd1bc43a361c1fe0552a5d0da0ed3bad45a315cd5bd22cec8e3d5e72cba511d648db4ccfa879693edba0c3be2dad2575e35572b8778358a7741266af08be09aadfa4fb4e1b319498177141456464f1205c9eba6ba70c1b54b62c9731e13e6b237c4a3054fa0e5e3c8c44d270313fa2bd983ecfbd39d9515ded9ab10229b88c3817325e4011ae523e7e8ef3c64155f7de97c49db23e2dc878e6a4323d33f386a7a30bad4115b6014f70045bf40a3ebf62018b983f2347d404d15b6f85efea7416cc96eb286191d6ae445000e888076019156b918b06924a11af86848559e786fcc32e8ec8ab78c768147893eac84f8c5768b934fbfa53cb51553c96dc96f55c17542501096b358d6c93991810aef3c3fea8a2acaee05460979e063d45a1c2f9df8af32d6d197b70e6baf0a1e1dd854aab30af389c0adf2e3dceb06c45be1c106991e63c2aa9d490ec14aef9bdacb4444065e42f934dab13d98232d4e3423edacfe0d45d21c275611e32216fd1d0f9b8bdff2220636436591ea1ec9aa9edbd8ce9918a05be052785233ea7a77cfcbc2cf99d78611369c1de30357f96304d641a36dd06a37648b22a0c71502952777b8dc6357378e6f7498b84cd0f3a995f427b7ba515bc747f95007181966da77989d1a12591ca642997b4ed8d5ac1e9987ece7795c3fcdb1537cc137a83dc2b3f92eabcf71062fab5954cb8209034655340420ddc738927122695d419588aaf17e474f4cf9a17b4bee303f199bbe6e8532f9676e4d661e1d723a27cb8a72ccb55b25c7a737fae013428318da1e431b45846ab17a44df40af30be3f1828a6a5fa94300b9073f9cb684b226919fe48ef8d4fd4d3a54962ab44641284e20e3f95942decba15fe4e317a44043bc5c688131af1c90878c4b3f606f7e470dbd554536bb4e782406737ebbaf895bd7c7e872cad1a69f5b8da0cbd4b42e5e3bff54dcf210cbf5a1f268346a4a06939e1c7387eb6e8feae01b29d0cec0825666479c989f5b6f87f5ecd9d5c019f6cd3fb0ff1982c4a524c8e6f27d538b3692940f2efb99aabe7de08144ad16bb2dc3fbb364d52d1d60d05358cf7e2f59fdbaf5b997913fe835effbf1559b465570ea5b2536cbd12e09c7bbc7a09a52947922c44f69d6fbeb77a138810f2296c890835bc29b7057a5e1aa01a71f6c80358106b80a596ba3383000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000dd784421" } diff --git a/packages/sdk/src/__test__/integration/__mocks__/getAddresses.json b/packages/sdk/src/__test__/integration/__mocks__/getAddresses.json index 3a57f646..738c09d4 100644 --- a/packages/sdk/src/__test__/integration/__mocks__/getAddresses.json +++ b/packages/sdk/src/__test__/integration/__mocks__/getAddresses.json @@ -1,4 +1,4 @@ { - "status": 200, - "message": "01008b93b3ab0d81002985bb78f76d77d49d48acf5442bdef94b128a2f8abeb7b00afe7f695f833d0fac418b85babdd26f23c1d70408f3bbeadd777167d9f7ec258a299e3b939fbfda6ebf61e1fba91a646aa0a54a81514adf813a1406f6244ec3f9b20cd2084a1087917b3d7497aee4aca9de756f54ae193b9f21621c57f87e6fe4464297ae13e02b0d342d3087b267558a9f8c02b10ca045afa501b4581642f8c4b26e9adb665eeedb4ff86cf230a9f72c0903d33bf6617c87dacf9c02cb68687d53961f202704930a9522392c61cd906681352c355285668afa2312d20e7e87ad7ebb50f486d48c437d9b6fff67c6ebee6dccba8763f3b1fa8037a25981baedb2a671b40db7ddb6fa96605cf1f6c6484782b7f7e3bb1ea256e3a01afddb303ad325462ed745b8e2b0cc3837b7616b9ad815bebd4d40b1539d319dcc01ae068da364d5921d1a364a69e4825eff47aaa10fe26cfaa676df692295f1dba4dece0634621b8e087352f06dff391c27d70b22bd788ab5e4ec90cd89ae264e1d2391180e31f6c8c457d10b7855fa4be125c4fc3cee903f707171480d8f9f4ca974a211aa43b521ad4d86ca8883d2ddef931203fff0af3989e3a18fa47b3efc63996fffecfa6c9bcf499fcb5cdf996988bbce91b89815eca134992d191ede6eede485e94a3a199b7404c26b5058d5552bf7e61654f8733c12de0b52f23d7c031a2b86e89d1af9b5a8d7212c6ee448ff2a4f3996920bcdea914c128833862cf6f54f42781d688f6d6f5c6e789363aa811ee3f1422b93e18dca729bae933f460313f5348667cde5e61e0a3c24c65e6969f1c4d80aa94f94d28a79821a47994ac45fc36d2ff7fc11948ab880b6d8c352f13336c707bcd108994f75dbb4481c8912a883d07a9f8678ab1a2e5124607b8a18c12b2e19e22c46ed48d374fd7fcb11d8e6f58028313c8784f13fe71c91f6daa3e9a32658f9e6c8230b31f973e6790e6cd5e53035bb1039a556ab9ad9f99e317ed84caade9e5d8c973371ee56b831600085c09a7233f088ae883f0037b5c3e7ade54be865bb8526e8b8973c3c34b96954bec66183acbd094a56840401b01f151fbf47b7c73887d45257307da30fc957ae0be98161d413d7bfeea6fe743f2a0364ff8c231b97bcfc4bc6f45b48018759e27167ba60a1a66382bb1f45747c73690d40bb09921585b4d11afa9827434713f5ca707be077b9e23a41774d535c83f9adaff7ff2bab37a344bcb4b3bd8049cd3281125e4d9376cf17572fe04bf8d32fc5fe1a274dc26493a940ca11ab7327a8eb3ff8fb3fac99a5a313b0ca3320070ce9946965cbf7538e18e3cd7f1955741ea5d1c5c669cdef304c4b2622123f2fa26b559103d9cd44e405fa35ed4748e0eb5a1991cd389f1da5224629ac8e585f9cf087db470385a5bebbd792088f9c53ba7f8db516b22b9adf7390f3d65479078d7f7b4dd6c42201e520a41766f7d0b5dc5cb0b352a0f05832badd947e5643acf84f2917da9168e29f3c2de45a06c9625ea48e447f2141cf488544ac025715a649f08252b515e6b9e3fb1559759a2985a6ab4c9fbdead8838e97c048326ab0f6438dad15eb8d3bdff5fa687d422e98461a04289df0591e7e318ac71fd10c94748e36598651906157cc73b8a24f933b9d281ed5d8893cfd5670a1549af10ecb0fb85b90a10523ca5c32de3f3409d5381acf42f63ad25817de6f9e55ddead7d887d0271448738fa464ed7483a26c9f46471dbda12faf81eb2e0959699d41ef52a077203933551192e2e5daa16cc24bac966fdba50097c37e686f74c2ad29b5b4e012ac6d5f41c92be365e0d3de30ed5dda205f098ea33e3d10dfd40847237745be12910f012fc1293ae62beff4086274a4fef3f945a742df1e36aa58ccc4e32e43ee6566bacc11815a5d49c1c1f9f813248951338a0db937aa81f4a06c49e4c91e1a18b892be88adb6d56fac8f76bf4e422b273f5671a64f842734c74d8db51e1e10cdc1929488ef84d4a0934644a4c594c89cdf3796d2c1a26321a8729ddf10d9fe706ff5faa5484ec518b9ca22f119d24cb66b5c2f185c97b6092c53181903baccc46d42165c568d9b10f8f1d1192e6eda80140bc6c05a0be04a08025fdf45a56d09ff4c392adb6942d825e67efc72f21c70a61562b6496e5593ee52d71fa3436b57a42b27215bf51d20f72d563111edfabd594d566bfa1be65567b9a6f2844a18a3c4a5c9df2cc490cc5edbee414cd8dff57bfc38fab0a0a4180cd4ae9c6bcf32ca3dfd792893301bfd3d29d1daaea8a7e96f6ed129f26e3aafd0088506bd411711ff3d1351fa97bc580e960775edfff52015d382e827a0ebdb1b2bfd104c8272ddfe6a9182bf66ec23a7162cb7aee10c83b9b3aa612612183a3613bb84ef0a59edbdc6b36b33a520222b5959bb39a30cad70a72456a6aae3d291509446000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000327c4720" + "status": 200, + "message": "01008b93b3ab0d81002985bb78f76d77d49d48acf5442bdef94b128a2f8abeb7b00afe7f695f833d0fac418b85babdd26f23c1d70408f3bbeadd777167d9f7ec258a299e3b939fbfda6ebf61e1fba91a646aa0a54a81514adf813a1406f6244ec3f9b20cd2084a1087917b3d7497aee4aca9de756f54ae193b9f21621c57f87e6fe4464297ae13e02b0d342d3087b267558a9f8c02b10ca045afa501b4581642f8c4b26e9adb665eeedb4ff86cf230a9f72c0903d33bf6617c87dacf9c02cb68687d53961f202704930a9522392c61cd906681352c355285668afa2312d20e7e87ad7ebb50f486d48c437d9b6fff67c6ebee6dccba8763f3b1fa8037a25981baedb2a671b40db7ddb6fa96605cf1f6c6484782b7f7e3bb1ea256e3a01afddb303ad325462ed745b8e2b0cc3837b7616b9ad815bebd4d40b1539d319dcc01ae068da364d5921d1a364a69e4825eff47aaa10fe26cfaa676df692295f1dba4dece0634621b8e087352f06dff391c27d70b22bd788ab5e4ec90cd89ae264e1d2391180e31f6c8c457d10b7855fa4be125c4fc3cee903f707171480d8f9f4ca974a211aa43b521ad4d86ca8883d2ddef931203fff0af3989e3a18fa47b3efc63996fffecfa6c9bcf499fcb5cdf996988bbce91b89815eca134992d191ede6eede485e94a3a199b7404c26b5058d5552bf7e61654f8733c12de0b52f23d7c031a2b86e89d1af9b5a8d7212c6ee448ff2a4f3996920bcdea914c128833862cf6f54f42781d688f6d6f5c6e789363aa811ee3f1422b93e18dca729bae933f460313f5348667cde5e61e0a3c24c65e6969f1c4d80aa94f94d28a79821a47994ac45fc36d2ff7fc11948ab880b6d8c352f13336c707bcd108994f75dbb4481c8912a883d07a9f8678ab1a2e5124607b8a18c12b2e19e22c46ed48d374fd7fcb11d8e6f58028313c8784f13fe71c91f6daa3e9a32658f9e6c8230b31f973e6790e6cd5e53035bb1039a556ab9ad9f99e317ed84caade9e5d8c973371ee56b831600085c09a7233f088ae883f0037b5c3e7ade54be865bb8526e8b8973c3c34b96954bec66183acbd094a56840401b01f151fbf47b7c73887d45257307da30fc957ae0be98161d413d7bfeea6fe743f2a0364ff8c231b97bcfc4bc6f45b48018759e27167ba60a1a66382bb1f45747c73690d40bb09921585b4d11afa9827434713f5ca707be077b9e23a41774d535c83f9adaff7ff2bab37a344bcb4b3bd8049cd3281125e4d9376cf17572fe04bf8d32fc5fe1a274dc26493a940ca11ab7327a8eb3ff8fb3fac99a5a313b0ca3320070ce9946965cbf7538e18e3cd7f1955741ea5d1c5c669cdef304c4b2622123f2fa26b559103d9cd44e405fa35ed4748e0eb5a1991cd389f1da5224629ac8e585f9cf087db470385a5bebbd792088f9c53ba7f8db516b22b9adf7390f3d65479078d7f7b4dd6c42201e520a41766f7d0b5dc5cb0b352a0f05832badd947e5643acf84f2917da9168e29f3c2de45a06c9625ea48e447f2141cf488544ac025715a649f08252b515e6b9e3fb1559759a2985a6ab4c9fbdead8838e97c048326ab0f6438dad15eb8d3bdff5fa687d422e98461a04289df0591e7e318ac71fd10c94748e36598651906157cc73b8a24f933b9d281ed5d8893cfd5670a1549af10ecb0fb85b90a10523ca5c32de3f3409d5381acf42f63ad25817de6f9e55ddead7d887d0271448738fa464ed7483a26c9f46471dbda12faf81eb2e0959699d41ef52a077203933551192e2e5daa16cc24bac966fdba50097c37e686f74c2ad29b5b4e012ac6d5f41c92be365e0d3de30ed5dda205f098ea33e3d10dfd40847237745be12910f012fc1293ae62beff4086274a4fef3f945a742df1e36aa58ccc4e32e43ee6566bacc11815a5d49c1c1f9f813248951338a0db937aa81f4a06c49e4c91e1a18b892be88adb6d56fac8f76bf4e422b273f5671a64f842734c74d8db51e1e10cdc1929488ef84d4a0934644a4c594c89cdf3796d2c1a26321a8729ddf10d9fe706ff5faa5484ec518b9ca22f119d24cb66b5c2f185c97b6092c53181903baccc46d42165c568d9b10f8f1d1192e6eda80140bc6c05a0be04a08025fdf45a56d09ff4c392adb6942d825e67efc72f21c70a61562b6496e5593ee52d71fa3436b57a42b27215bf51d20f72d563111edfabd594d566bfa1be65567b9a6f2844a18a3c4a5c9df2cc490cc5edbee414cd8dff57bfc38fab0a0a4180cd4ae9c6bcf32ca3dfd792893301bfd3d29d1daaea8a7e96f6ed129f26e3aafd0088506bd411711ff3d1351fa97bc580e960775edfff52015d382e827a0ebdb1b2bfd104c8272ddfe6a9182bf66ec23a7162cb7aee10c83b9b3aa612612183a3613bb84ef0a59edbdc6b36b33a520222b5959bb39a30cad70a72456a6aae3d291509446000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000327c4720" } diff --git a/packages/sdk/src/__test__/integration/__mocks__/getKvRecords.json b/packages/sdk/src/__test__/integration/__mocks__/getKvRecords.json index f9ae698b..f7571298 100644 --- a/packages/sdk/src/__test__/integration/__mocks__/getKvRecords.json +++ b/packages/sdk/src/__test__/integration/__mocks__/getKvRecords.json @@ -1,4 +1,4 @@ { - "status": 200, - "message": "010092647efa0d8100347d04bc0f5c154535340722c1c2a7294131418e44d3010adcc9061dc46619741489df487aff8d3a998d3ce4578e959fb350d47d1afaff74fa88edf5e56592c20317ea62e0114c15c0342db2ebb684f8dfb7f16bf0229d20a043254f7aa032a5f1572811218c76b2e53c3ba12702deef12fb7d91aa8488bb7cc878faa9e7f310faac834fea46179feac05f7b63ba011485cef895c615ed3c21b42d09ba963ac83993f3ccc603c4d5a3d7c423d0b588cfdf4503cf1ac2260a2fd843d38713395ac7eecf60ef037e6191481310c10f912891bb4a82afd890f6b8a609a875d0f1c9102d05a1b2410d1fd39fb74170fe2aeeeecc3fa648fb9f9fb37c022fd68591a279f21537d33cdde63dc2408e298e0f001740e44f60c6178786ef82b0216c4a53495aa0b42b0ccd496f71e0f5bcd4994f13eac91e65e02195ea5b4adf29b02c25349f068006c73d03dfcc4f560340e5c8a35bf09052c458f7c2d62859259884659f91c3f77adf4c04c73d38c5f14e81189e21c01f83ae36653b4dbea4bf6ef52b8fa8b95bc990a2fee721556710113694ae4b70425c56371639a24cea27cbc81fb4f871da3d66ed797ad70dccf4d9f2fedfa1668dad76b6681d21ddcd1f0fa49abbefded15b5877e909df677036e38c56e680b6da88f87a43638914337eaa6c4475a17e59206c629c2c5f6baaf4aa7bfad96bb36000a9d792d0100d1b2d5d522f28e1902164a3234bf3242068d824d68cf597882d6833b39b301c8a7274e96e66350336dbffc1e84d5acfd39b289c4d901c7d2d233058dd2438d7cb03146faf7d6bb1a36f8608171e3f547a14e435778b1351de1c697ce0b45c7dd042b225559a56c26c544afe66eebddfa637f3d3ea9550df2a5b73b607b6e68bd4638ed40c2eafa1f20daefbf21fae12f69442e01fc9ddb558d90fa047e0761c59e56b2a367b827c44a134ccef8e59a43b5ce9b4ce0ae2cfbd60558eb3f76741ddde019c8b9964a5f9d9f1a5fbaf0ee2ee0434f38999ab373122eb6532cf6d4b2d1f2e9f37a53fa5348f47a2450c675e591a32b14b80b2fd930e4e33656f23839fc84cba96fa10cbd06841ee894606bdac7ba7260c4b86bfdd0bafe2d7f2bd0eff7e117b4239171745705b0fe8ca2d671f35a447dd5ab3ff0d2b2321e308c7b068abb71aea5b633129f8b2ab953c3081985ccbb095184f7523371ceb0aa94fd45b9b860de9da67da62113fe509d5b2d881ed0880d196d04d2ad44be299aa13528c09fcfa7e5da1c0489f8f3bd92a67ed11cf0555beeba875bf77b568ca978ddf61dc2b0b5807f5a161df6f408c668a4edb0d05d7d4fb4e017159722b126f635f0d93e7ca110b08dc2b1f8a527e0dca0868ecd76647d94b3c021b5ecd0d7626b940e17662f2df931dcd9e354b1d7d8bf271413bc6a4c1e89be5cfdd02af31a888314a13f51b4767705813fb6d8b98e4032473bc8ff6baefa4bdf6bf1f578134829a1ba4952a87e871ee54f36fb10f1d9bfe3279f2ef6f61e8af7b5bccfe8d86a68b0755cc8fc440e0ddcde5476df656b49a908815e20f8cb8a2ba4ae8bd447cd6535e044f7e4420b0bdd9289df6d91256d1cfae090afe25afcbd5a15fbfc146760dffd7538f70005b87d697f9c96f0e1672e4c1e8892517f4cee2c27faccf245d4f52e3874c637cbb10986a3970cc9d005c866b930972c200ae78a7fea1cb03ea33df8702542007d03f5406e9d4f93259b7ea8ce2f7d7debfeb10a71886a3d43866c98da548d2c716d104e31da89032056e2cdda4dca80267f28c6a943cbc21dc956d3887316c48c9f050a4181f7cf3b855d757fb11f9caed0adfab821afd0c512cf71cf6b2cfcc344912b937611d88581de96ab75b11121dc3be02c73d44181dd840b2a3968d79f0d7029dee4690d91ae1dccc739fdceb4612e070da018128a7556be8eef8930516c13b8e8d8d28dc5034cce953d017d02546ff7292b3aa284ab3c7894bf4f636e0a9c8b8c3473bb5b0086a29fe77fac6087ced1778f5eeb2def0d1790b802a9dee1b4683679b8c0235060e39a3ebf5a194e5583b020c2b2ecc53904e6a3dd281b072fd13fea08ce3e8b2d490b3c57402335c20bb4547e066d4166ff2728d720687818397d5dd0c30ff55f0b2b0f122a76c636c3334b26009f57b41ae33c2638f8ebb4408e95cf0bff7a6232075fd1dfc482d74e9ac8a6b0cdbd17d9e1daa75d72c3d26cd2c31046794d53000dd99ecdcf6574b9cdade232535ebc86044858deee8d5894989e9e0495702fa31a8bf0cf97f8d9a34d204818cf71996e1f07e20ec22b9c27ee74044c72733c055ffa8530527cdee616235ce4cb986bab91ff5bcaa5f5aa647a57e4e25194db9bcdf2b5392efc0ebad5db35ff8661bd9daf024619711f4a3941fdefc399c2d77b05c152ad243f314d7178da553f2fa6adb8a08d1000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000de551a6b" + "status": 200, + "message": "010092647efa0d8100347d04bc0f5c154535340722c1c2a7294131418e44d3010adcc9061dc46619741489df487aff8d3a998d3ce4578e959fb350d47d1afaff74fa88edf5e56592c20317ea62e0114c15c0342db2ebb684f8dfb7f16bf0229d20a043254f7aa032a5f1572811218c76b2e53c3ba12702deef12fb7d91aa8488bb7cc878faa9e7f310faac834fea46179feac05f7b63ba011485cef895c615ed3c21b42d09ba963ac83993f3ccc603c4d5a3d7c423d0b588cfdf4503cf1ac2260a2fd843d38713395ac7eecf60ef037e6191481310c10f912891bb4a82afd890f6b8a609a875d0f1c9102d05a1b2410d1fd39fb74170fe2aeeeecc3fa648fb9f9fb37c022fd68591a279f21537d33cdde63dc2408e298e0f001740e44f60c6178786ef82b0216c4a53495aa0b42b0ccd496f71e0f5bcd4994f13eac91e65e02195ea5b4adf29b02c25349f068006c73d03dfcc4f560340e5c8a35bf09052c458f7c2d62859259884659f91c3f77adf4c04c73d38c5f14e81189e21c01f83ae36653b4dbea4bf6ef52b8fa8b95bc990a2fee721556710113694ae4b70425c56371639a24cea27cbc81fb4f871da3d66ed797ad70dccf4d9f2fedfa1668dad76b6681d21ddcd1f0fa49abbefded15b5877e909df677036e38c56e680b6da88f87a43638914337eaa6c4475a17e59206c629c2c5f6baaf4aa7bfad96bb36000a9d792d0100d1b2d5d522f28e1902164a3234bf3242068d824d68cf597882d6833b39b301c8a7274e96e66350336dbffc1e84d5acfd39b289c4d901c7d2d233058dd2438d7cb03146faf7d6bb1a36f8608171e3f547a14e435778b1351de1c697ce0b45c7dd042b225559a56c26c544afe66eebddfa637f3d3ea9550df2a5b73b607b6e68bd4638ed40c2eafa1f20daefbf21fae12f69442e01fc9ddb558d90fa047e0761c59e56b2a367b827c44a134ccef8e59a43b5ce9b4ce0ae2cfbd60558eb3f76741ddde019c8b9964a5f9d9f1a5fbaf0ee2ee0434f38999ab373122eb6532cf6d4b2d1f2e9f37a53fa5348f47a2450c675e591a32b14b80b2fd930e4e33656f23839fc84cba96fa10cbd06841ee894606bdac7ba7260c4b86bfdd0bafe2d7f2bd0eff7e117b4239171745705b0fe8ca2d671f35a447dd5ab3ff0d2b2321e308c7b068abb71aea5b633129f8b2ab953c3081985ccbb095184f7523371ceb0aa94fd45b9b860de9da67da62113fe509d5b2d881ed0880d196d04d2ad44be299aa13528c09fcfa7e5da1c0489f8f3bd92a67ed11cf0555beeba875bf77b568ca978ddf61dc2b0b5807f5a161df6f408c668a4edb0d05d7d4fb4e017159722b126f635f0d93e7ca110b08dc2b1f8a527e0dca0868ecd76647d94b3c021b5ecd0d7626b940e17662f2df931dcd9e354b1d7d8bf271413bc6a4c1e89be5cfdd02af31a888314a13f51b4767705813fb6d8b98e4032473bc8ff6baefa4bdf6bf1f578134829a1ba4952a87e871ee54f36fb10f1d9bfe3279f2ef6f61e8af7b5bccfe8d86a68b0755cc8fc440e0ddcde5476df656b49a908815e20f8cb8a2ba4ae8bd447cd6535e044f7e4420b0bdd9289df6d91256d1cfae090afe25afcbd5a15fbfc146760dffd7538f70005b87d697f9c96f0e1672e4c1e8892517f4cee2c27faccf245d4f52e3874c637cbb10986a3970cc9d005c866b930972c200ae78a7fea1cb03ea33df8702542007d03f5406e9d4f93259b7ea8ce2f7d7debfeb10a71886a3d43866c98da548d2c716d104e31da89032056e2cdda4dca80267f28c6a943cbc21dc956d3887316c48c9f050a4181f7cf3b855d757fb11f9caed0adfab821afd0c512cf71cf6b2cfcc344912b937611d88581de96ab75b11121dc3be02c73d44181dd840b2a3968d79f0d7029dee4690d91ae1dccc739fdceb4612e070da018128a7556be8eef8930516c13b8e8d8d28dc5034cce953d017d02546ff7292b3aa284ab3c7894bf4f636e0a9c8b8c3473bb5b0086a29fe77fac6087ced1778f5eeb2def0d1790b802a9dee1b4683679b8c0235060e39a3ebf5a194e5583b020c2b2ecc53904e6a3dd281b072fd13fea08ce3e8b2d490b3c57402335c20bb4547e066d4166ff2728d720687818397d5dd0c30ff55f0b2b0f122a76c636c3334b26009f57b41ae33c2638f8ebb4408e95cf0bff7a6232075fd1dfc482d74e9ac8a6b0cdbd17d9e1daa75d72c3d26cd2c31046794d53000dd99ecdcf6574b9cdade232535ebc86044858deee8d5894989e9e0495702fa31a8bf0cf97f8d9a34d204818cf71996e1f07e20ec22b9c27ee74044c72733c055ffa8530527cdee616235ce4cb986bab91ff5bcaa5f5aa647a57e4e25194db9bcdf2b5392efc0ebad5db35ff8661bd9daf024619711f4a3941fdefc399c2d77b05c152ad243f314d7178da553f2fa6adb8a08d1000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000de551a6b" } diff --git a/packages/sdk/src/__test__/integration/__mocks__/handlers.ts b/packages/sdk/src/__test__/integration/__mocks__/handlers.ts index db4baa25..495c59d4 100644 --- a/packages/sdk/src/__test__/integration/__mocks__/handlers.ts +++ b/packages/sdk/src/__test__/integration/__mocks__/handlers.ts @@ -1,19 +1,19 @@ import { http, HttpResponse } from 'msw' import { - fourbyteResponse0c49ccbe, - fourbyteResponse0x6a761202, - fourbyteResponse0x38ed1739, - fourbyteResponse0xa9059cbb, - fourbyteResponseac9650d8, - fourbyteResponsefc6f7865, + fourbyteResponse0c49ccbe, + fourbyteResponse0x6a761202, + fourbyteResponse0x38ed1739, + fourbyteResponse0xa9059cbb, + fourbyteResponseac9650d8, + fourbyteResponsefc6f7865, } from './4byte' import addKvRecordsResponse from './addKvRecords.json' import connectResponse from './connect.json' import { - etherscanResponse0x06412d7e, - etherscanResponse0x7a250d56, - etherscanResponse0xa0b86991, - etherscanResponse0xc36442b6, + etherscanResponse0x06412d7e, + etherscanResponse0x7a250d56, + etherscanResponse0xa0b86991, + etherscanResponse0xc36442b6, } from './etherscan' import fetchActiveWalletResponse from './fetchActiveWallet.json' import getAddressesResponse from './getAddresses.json' @@ -22,78 +22,78 @@ import removeKvRecordsResponse from './removeKvRecords.json' import signResponse from './sign.json' export const handlers = [ - http.post('https://signing.gridpl.us/test/connect', () => { - return HttpResponse.json(connectResponse) - }), - http.post('https://signing.gridpl.us/test/getAddresses', () => { - return HttpResponse.json(getAddressesResponse) - }), - http.post('https://signing.gridpl.us/test/sign', () => { - return HttpResponse.json(signResponse) - }), - http.post('https://signing.gridpl.us/test/fetchActiveWallet', () => { - return HttpResponse.json(fetchActiveWalletResponse) - }), - http.post('https://signing.gridpl.us/test/addKvRecords', () => { - return HttpResponse.json(addKvRecordsResponse) - }), - http.post('https://signing.gridpl.us/test/getKvRecords', () => { - return HttpResponse.json(getKvRecordsResponse) - }), - http.post('https://signing.gridpl.us/test/removeKvRecords', () => { - return HttpResponse.json(removeKvRecordsResponse) - }), - http.get('https://api.etherscan.io/api', ({ request }) => { - const url = new URL(request.url) - const module = url.searchParams.get('module') - const action = url.searchParams.get('action') - const address = url.searchParams.get('address') + http.post('https://signing.gridpl.us/test/connect', () => { + return HttpResponse.json(connectResponse) + }), + http.post('https://signing.gridpl.us/test/getAddresses', () => { + return HttpResponse.json(getAddressesResponse) + }), + http.post('https://signing.gridpl.us/test/sign', () => { + return HttpResponse.json(signResponse) + }), + http.post('https://signing.gridpl.us/test/fetchActiveWallet', () => { + return HttpResponse.json(fetchActiveWalletResponse) + }), + http.post('https://signing.gridpl.us/test/addKvRecords', () => { + return HttpResponse.json(addKvRecordsResponse) + }), + http.post('https://signing.gridpl.us/test/getKvRecords', () => { + return HttpResponse.json(getKvRecordsResponse) + }), + http.post('https://signing.gridpl.us/test/removeKvRecords', () => { + return HttpResponse.json(removeKvRecordsResponse) + }), + http.get('https://api.etherscan.io/api', ({ request }) => { + const url = new URL(request.url) + const module = url.searchParams.get('module') + const action = url.searchParams.get('action') + const address = url.searchParams.get('address') - if (module === 'contract' && action === 'getabi') { - if (address === '0xa0b86991c6218b36c1d19d4a2e9eb0ce3606eb48') { - return HttpResponse.json({ - result: JSON.stringify(etherscanResponse0xa0b86991), - }) - } - if (address === '0x7a250d5630b4cf539739df2c5dacb4c659f2488d') { - return HttpResponse.json({ - result: JSON.stringify(etherscanResponse0x7a250d56), - }) - } - if (address === '0xc36442b4a4522e871399cd717abdd847ab11fe88') { - return HttpResponse.json({ - result: JSON.stringify(etherscanResponse0xc36442b6), - }) - } - if (address === '0x06412d7ebfbf66c25607e2ed24c1d207043be327') { - return HttpResponse.json({ - result: JSON.stringify(etherscanResponse0x06412d7e), - }) - } - } - return new HttpResponse(null, { status: 404 }) - }), - http.get('https://www.4byte.directory/api/v1/signatures', ({ request }) => { - const url = new URL(request.url) - const hexSignature = url.searchParams.get('hex_signature') - if (hexSignature === '0xa9059cbb') { - return HttpResponse.json(fourbyteResponse0xa9059cbb) - } - if (hexSignature === '0x38ed1739') { - return HttpResponse.json(fourbyteResponse0x38ed1739) - } - if (hexSignature === '0xac9650d8') { - return HttpResponse.json(fourbyteResponseac9650d8) - } - if (hexSignature === '0x0c49ccbe') { - return HttpResponse.json(fourbyteResponse0c49ccbe) - } - if (hexSignature === '0xfc6f7865') { - return HttpResponse.json(fourbyteResponsefc6f7865) - } - if (hexSignature === '0x6a761202') { - return HttpResponse.json(fourbyteResponse0x6a761202) - } - return new HttpResponse(null, { status: 404 }) - }), + if (module === 'contract' && action === 'getabi') { + if (address === '0xa0b86991c6218b36c1d19d4a2e9eb0ce3606eb48') { + return HttpResponse.json({ + result: JSON.stringify(etherscanResponse0xa0b86991), + }) + } + if (address === '0x7a250d5630b4cf539739df2c5dacb4c659f2488d') { + return HttpResponse.json({ + result: JSON.stringify(etherscanResponse0x7a250d56), + }) + } + if (address === '0xc36442b4a4522e871399cd717abdd847ab11fe88') { + return HttpResponse.json({ + result: JSON.stringify(etherscanResponse0xc36442b6), + }) + } + if (address === '0x06412d7ebfbf66c25607e2ed24c1d207043be327') { + return HttpResponse.json({ + result: JSON.stringify(etherscanResponse0x06412d7e), + }) + } + } + return new HttpResponse(null, { status: 404 }) + }), + http.get('https://www.4byte.directory/api/v1/signatures', ({ request }) => { + const url = new URL(request.url) + const hexSignature = url.searchParams.get('hex_signature') + if (hexSignature === '0xa9059cbb') { + return HttpResponse.json(fourbyteResponse0xa9059cbb) + } + if (hexSignature === '0x38ed1739') { + return HttpResponse.json(fourbyteResponse0x38ed1739) + } + if (hexSignature === '0xac9650d8') { + return HttpResponse.json(fourbyteResponseac9650d8) + } + if (hexSignature === '0x0c49ccbe') { + return HttpResponse.json(fourbyteResponse0c49ccbe) + } + if (hexSignature === '0xfc6f7865') { + return HttpResponse.json(fourbyteResponsefc6f7865) + } + if (hexSignature === '0x6a761202') { + return HttpResponse.json(fourbyteResponse0x6a761202) + } + return new HttpResponse(null, { status: 404 }) + }), ] diff --git a/packages/sdk/src/__test__/integration/__mocks__/removeKvRecords.json b/packages/sdk/src/__test__/integration/__mocks__/removeKvRecords.json index 902f2c92..daadc019 100644 --- a/packages/sdk/src/__test__/integration/__mocks__/removeKvRecords.json +++ b/packages/sdk/src/__test__/integration/__mocks__/removeKvRecords.json @@ -1,4 +1,4 @@ { - "status": 200, - "message": "0100f506dfce0d8100f5382a032169e8a60db71d0cc125b75e03839d4a886fad4dff893c0edb6c6bcf908755bd667b09416f308f313c34dff3e696e08225615e29e75af7f2c5450ed7246e1c7aebe6a74834ce7d6c797575da65718d88d3349a8b370924059db3e38022816d0f968f22c9061f000cfd7e734bc4775647701ee7b640577b62e797630c7fb91bc89faf6783ab93b3d3f66b19f6bd5bf1eeb5dc6300f4a42391a592c7df2913654a4e4963e6ddbc07a215fd815d0ede1fbf31c81225a55825171b4f0d10ab0cd8ea1fe560961cbcd8fb6732fab742afdeaf92d79d91d06cf9591eabcc470e684080c861bb94b4c8b79977c69dc39804b91c18d257192412432aaefbbf546196d09c5586cfa5a951c7a4e1dbe908b82022225739884328dc44c6b60cc23f6f71ff506ad514761e14d0b94c742220e67ed15145cf2b82bb030ee560447bf421d65d97aa52fe97f9f8bf92d11acca19b3bca7d076fb36171c431491de93e105a598426d4fdc7d7e5e214ac22ccfbd89def4a0876d440492b6cb706d5d3698b44f9d2dbb7623cd7981575ad8f42654f36656dfe5b3fb606cfe134124e07320ac6d00b7f6d000036737439404b49df0ccaaff56cf60d93c18f253c5abc1518350d5c286862dd5d6f11d01e676fe32c0bb19e658ccb829136bbff6fe36b16a74929be6df7672c0bcf1b4f11fadc38e0bec43d5fee0b4b95c6d16d9e968f142392a0da4d138798d0f98747c771cd23c7bd81910139415a6d5cc894f312406976782c8ffc18d63d7797cda33e2d73d79e2d42dab4778fb1aef80b049c010045c898ebf1cc0da9a62a10a2c3f00e8fc7b03fd240a11262bec322b8b74680c06bd21f77044992b85877a9f7fc60763007d7e9929caf0c1f2a746e99c544ed3b0550f62b1cc99b4bb76c543ffde434187ae923971af02ee800690d999ab82c40c3cb926b41b90fa06d77d16b91bddfff24b0afc6fc31acd31a98fd7992af943d6e6e462cc53deaf02725ddb64056d9bd2a381ca1537f52ffc010cdeaf35f6b34ca2dc045d7f28a0fbff646ceeaf1ebfcea8951ebf3a84058d69f923fceecd35183f92f5e4c5b70300ca50bb304c0b6a8e91afbc06d6468a276f2ee5ae524320838a280e4c334b3e6fdeba302c3a767ae9989441493b6e4ee731ccd0641847f8465808cd5be7515e9bd406866970297e6b886e76251b62be2e22dfa2eaa1c0f53cde7884a345470ea7505ad0b618c3eaa469210bfe1b79d0bbeb0e477177e75ed0e0b7b1dacd00f2dcc05f5ad940e7cfd52f1417b05cebd26470b87ad8f278ac92bcff2a5f9a5f97b3a085ee6306e57ce3bf0df76ef34aec9ad7b9b3eb307d0948ee6157d27c433c49a8815e7d7391eb9b74a3421128bdb4b9699a3d3256dc769adf975d9fb0d517e25d5c2884c2a8d448b16c24d9c012a3f2c9ebec6a7f5ea99cef6fed3be6abd6df8e3b0ad4894350da4085446ed7ac9854242b5b296e83c39e2dcb74976b6a8c8da756e9897fad7db9c37f100d3fde0683f1dad72b3712705dcc4761884f2b8e88e08056f0c15bb9aed9e8f4bb113ee35487eb14c79bad5254869ebc69b1249ce8640425f3c7e0077ad3de5b29b43b506157f5ed17cdecca39dd6694e6aba9de524f34da16231c4ce991b5420dd4d77640d2af0b45bdc679d1894de07021aceca5547998be5c306bbb1a67d3125cc71f1286afd99f62ff8dc74815ba4a1136447aaff9599dffeda139de7c1f739171c17ae54306c6cc8c9a73e1437ec8c627384a2229d3be5992895922ef434e6a1db7083aabaf3401d3025f5f943a5bb39733a265782b783d8a746fd9bf0bf40bbea7c7bd963bb3a47a3117e831920dddd1393944a0eeb8a00a209e010c4f85eb6aa5e17dcb7af55aab9ff982395b5915b55db81d42facb1044c5453c0cec5cd6207920730258c22482237c318045a27638bb00bf7e89d3a6175aa18eab54a8ab7269f8718ead7c5ea04a879fc8eca77e58626d1f801bc6986c32cdbde57b3674e21152ffdeb8a582336ec9c14351f43964858d50265727b6dd8c55d292a45bbf81d224381eafe80957913dcf893f66ff7d3b4906b3a7b03769dd937c50080a9a9adb1b63bcd8c3260310a12808415faf955af4d4c2abb3978794b0d326138b77a8260349acf09cc8c9692872317070aaa67bc59fe7259bc2e27390f72527aefa2650a94f1f0c982ce9247fd945ef28501f9202928d2601fca9b88ac7c4438c3dc95478fe5d21747cfa3ea616bd7c054aa3b9ed856fad8cc239b374a0514dc1629139a1266bf479489a632fa5e0e2880f2e6ebce99e9fd0f1057c89882b8be3e7759fa5ff320c2d3ad0b749cc9fbf73bc1a555290c6561c60e7696372608caf4cebe3e9536c5d68f7e3ba6d4ea0e53d44fe2720c0bd59beab66914eba61fa7d9d41e44e059ba22821ff59a945d1900000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000077c76262" + "status": 200, + "message": "0100f506dfce0d8100f5382a032169e8a60db71d0cc125b75e03839d4a886fad4dff893c0edb6c6bcf908755bd667b09416f308f313c34dff3e696e08225615e29e75af7f2c5450ed7246e1c7aebe6a74834ce7d6c797575da65718d88d3349a8b370924059db3e38022816d0f968f22c9061f000cfd7e734bc4775647701ee7b640577b62e797630c7fb91bc89faf6783ab93b3d3f66b19f6bd5bf1eeb5dc6300f4a42391a592c7df2913654a4e4963e6ddbc07a215fd815d0ede1fbf31c81225a55825171b4f0d10ab0cd8ea1fe560961cbcd8fb6732fab742afdeaf92d79d91d06cf9591eabcc470e684080c861bb94b4c8b79977c69dc39804b91c18d257192412432aaefbbf546196d09c5586cfa5a951c7a4e1dbe908b82022225739884328dc44c6b60cc23f6f71ff506ad514761e14d0b94c742220e67ed15145cf2b82bb030ee560447bf421d65d97aa52fe97f9f8bf92d11acca19b3bca7d076fb36171c431491de93e105a598426d4fdc7d7e5e214ac22ccfbd89def4a0876d440492b6cb706d5d3698b44f9d2dbb7623cd7981575ad8f42654f36656dfe5b3fb606cfe134124e07320ac6d00b7f6d000036737439404b49df0ccaaff56cf60d93c18f253c5abc1518350d5c286862dd5d6f11d01e676fe32c0bb19e658ccb829136bbff6fe36b16a74929be6df7672c0bcf1b4f11fadc38e0bec43d5fee0b4b95c6d16d9e968f142392a0da4d138798d0f98747c771cd23c7bd81910139415a6d5cc894f312406976782c8ffc18d63d7797cda33e2d73d79e2d42dab4778fb1aef80b049c010045c898ebf1cc0da9a62a10a2c3f00e8fc7b03fd240a11262bec322b8b74680c06bd21f77044992b85877a9f7fc60763007d7e9929caf0c1f2a746e99c544ed3b0550f62b1cc99b4bb76c543ffde434187ae923971af02ee800690d999ab82c40c3cb926b41b90fa06d77d16b91bddfff24b0afc6fc31acd31a98fd7992af943d6e6e462cc53deaf02725ddb64056d9bd2a381ca1537f52ffc010cdeaf35f6b34ca2dc045d7f28a0fbff646ceeaf1ebfcea8951ebf3a84058d69f923fceecd35183f92f5e4c5b70300ca50bb304c0b6a8e91afbc06d6468a276f2ee5ae524320838a280e4c334b3e6fdeba302c3a767ae9989441493b6e4ee731ccd0641847f8465808cd5be7515e9bd406866970297e6b886e76251b62be2e22dfa2eaa1c0f53cde7884a345470ea7505ad0b618c3eaa469210bfe1b79d0bbeb0e477177e75ed0e0b7b1dacd00f2dcc05f5ad940e7cfd52f1417b05cebd26470b87ad8f278ac92bcff2a5f9a5f97b3a085ee6306e57ce3bf0df76ef34aec9ad7b9b3eb307d0948ee6157d27c433c49a8815e7d7391eb9b74a3421128bdb4b9699a3d3256dc769adf975d9fb0d517e25d5c2884c2a8d448b16c24d9c012a3f2c9ebec6a7f5ea99cef6fed3be6abd6df8e3b0ad4894350da4085446ed7ac9854242b5b296e83c39e2dcb74976b6a8c8da756e9897fad7db9c37f100d3fde0683f1dad72b3712705dcc4761884f2b8e88e08056f0c15bb9aed9e8f4bb113ee35487eb14c79bad5254869ebc69b1249ce8640425f3c7e0077ad3de5b29b43b506157f5ed17cdecca39dd6694e6aba9de524f34da16231c4ce991b5420dd4d77640d2af0b45bdc679d1894de07021aceca5547998be5c306bbb1a67d3125cc71f1286afd99f62ff8dc74815ba4a1136447aaff9599dffeda139de7c1f739171c17ae54306c6cc8c9a73e1437ec8c627384a2229d3be5992895922ef434e6a1db7083aabaf3401d3025f5f943a5bb39733a265782b783d8a746fd9bf0bf40bbea7c7bd963bb3a47a3117e831920dddd1393944a0eeb8a00a209e010c4f85eb6aa5e17dcb7af55aab9ff982395b5915b55db81d42facb1044c5453c0cec5cd6207920730258c22482237c318045a27638bb00bf7e89d3a6175aa18eab54a8ab7269f8718ead7c5ea04a879fc8eca77e58626d1f801bc6986c32cdbde57b3674e21152ffdeb8a582336ec9c14351f43964858d50265727b6dd8c55d292a45bbf81d224381eafe80957913dcf893f66ff7d3b4906b3a7b03769dd937c50080a9a9adb1b63bcd8c3260310a12808415faf955af4d4c2abb3978794b0d326138b77a8260349acf09cc8c9692872317070aaa67bc59fe7259bc2e27390f72527aefa2650a94f1f0c982ce9247fd945ef28501f9202928d2601fca9b88ac7c4438c3dc95478fe5d21747cfa3ea616bd7c054aa3b9ed856fad8cc239b374a0514dc1629139a1266bf479489a632fa5e0e2880f2e6ebce99e9fd0f1057c89882b8be3e7759fa5ff320c2d3ad0b749cc9fbf73bc1a555290c6561c60e7696372608caf4cebe3e9536c5d68f7e3ba6d4ea0e53d44fe2720c0bd59beab66914eba61fa7d9d41e44e059ba22821ff59a945d1900000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000077c76262" } diff --git a/packages/sdk/src/__test__/integration/__mocks__/setup.ts b/packages/sdk/src/__test__/integration/__mocks__/setup.ts index cccb8c13..01d96008 100644 --- a/packages/sdk/src/__test__/integration/__mocks__/setup.ts +++ b/packages/sdk/src/__test__/integration/__mocks__/setup.ts @@ -1,7 +1,7 @@ import { server } from './server' export const setup = () => { - beforeAll(() => server.listen({ onUnhandledRequest: 'bypass' })) - afterAll(() => server.close()) - afterEach(() => server.resetHandlers()) + beforeAll(() => server.listen({ onUnhandledRequest: 'bypass' })) + afterAll(() => server.close()) + afterEach(() => server.resetHandlers()) } diff --git a/packages/sdk/src/__test__/integration/__mocks__/sign.json b/packages/sdk/src/__test__/integration/__mocks__/sign.json index 0d493670..d5086add 100644 --- a/packages/sdk/src/__test__/integration/__mocks__/sign.json +++ b/packages/sdk/src/__test__/integration/__mocks__/sign.json @@ -1,4 +1,4 @@ { - "status": 200, - "message": "01002dde2fc80d8100f4467649112807f2691aba39712f8276c532ec605d258d952d7367d6cd31e9f4851885e13d318991756b371200687537752710471422eb48e56ce485886516ec7ac01ae2981880e504560d3a34e328cf1bcf0ca2f34ff9013cb5362973dd8987e077f79305ec32ca42e57438e4029ca11cdfe1c063a5bbcdc13e4e970767ff402e7edc665f7a607df7c8e19685b21ecb0286054c854a588e45b675a10d23212ec9afc8ac25380ca893c6a2d031a4154b778e1cc87c5dfb59d04afc9eb5f5a6339a0fae0c74c1ee877e21a636a2324c97c26f6f64f8749c389b0b78d7c1f4cb67e8aaa683d1b0440f1b2d334433fd92b3a6bcbeb05d19eb10ec2c90b4360fc180478402f5994a95e5b3d7b7a2037234e0c70a4746d87597a7a720b09bbd62ed8d13ff0e4522fc27b56e773e1d44575bca9ee313195d7bba55183a85dc3347fd21ae59105020fa366d9c9ee215f452b78acaa8ccaf2389d37f35832133ea08980c1a176646a01ac66f00eccd797b61691184b29e3749fa136a4f7aaaac9a035c48fe3c69d1671529ba5fb0123014092272821b3137313fb2cd2fce9cf98b95b979f56ad1f6acc1cbc060cd931ddde70769665a94d582dfb8b29bb8ae55a5f780110dfc82e33f3cf7f808836f6761214ac776e1087bf607259bf16bd3477116fda0ed2abdd631a8b0fe538afbf8b7c2f017eba763f59bb8d7f41d5a043389804c2bbdb8fe70830606beae34095b3d1f376e0dff3d65a4b012f7c5338bc17aa2ec4e376b9d50529d577ed199fb370c5d35e0c9b1b07589f80f5c344c9f02d2221aa8aa342160882ffc52a55fb557df7615c3e225f0b6188c95c862ce4f6f852dc7cf38f246599e59359a6c503e0cd17181cd4d44895df7a8397f0da8a9b4f1f0dbe1fc97a511069e4a6529698c12d25e842b670fec89c4131dd2877bf679a31e2ec565aaeebda19c22635a97a8d8e0e64879e7976fbd48f7babce745b59f5c6c97a9436292053200d829b6d1ab111b804c580a9b42454e232c83af4f4c0782b728674bda6964bbac10332cd0811e1a598e5b77f2ca865b6857118d3a50f633e46150d038342bf00fb1ff1db3085a29f7e9176c5da9a87bc374ad7a3b8245e650bf372ea2582bdc876e89e2ee1d4788d10d2336878db914f0ac76838fc822c9d57507cc6deaf1a75fd4608c241c92bcb4e5f3c038d5411d7a4d7e2dacedcdd089712a7dbaf4473ab00a3c8fbe3a134a967dfd4661b4548845bd2430c6fc45d41e6160e777c0431567bbeda0824c383c1dfb6b34cc86c97f398dd28d1e235ccc44707724ef2f85bd899523e3f72ce7fd8ba28ab32afff69a3477f7b110f11217e30c1fa0d381b2fa164b5ab7080aabeb728a3884add4d2154be53853b19dc7ee278f8a2c3de3a5813b3222cc703775949cf2242395a57471e89d2fbb105834c24da5cd97a3a455decf7ed777c16dba6bf53ab6cf9470f10cae1e0cf3d03228478f9ebded681caf39578893d049f735b636fc2aca25555193d32f705091b85a160fbc0e1557b4e08a7a0d53a9551f457250338f2a750a288901af01c0084c8c09565ba3d63e1773ebf6af0cd0c9bedc1273ff53558e9fcc3d0e9485b12fcd80916beecdeaa78ac913e5483380c1b42329eaafd952d08aabf3441e1f88f119abe5c302984bec72790ab182ee4247852d0b721b33fefb22161ead095bd6a2bcbb58e0cfbedd03a3a3d500312426e55e911f6ae79caddb148d18bf60e311caf14677edf8bad0a477052dc2ff195f61367e492911e5a4e7cbf55296eae45c3cd0d3eedc0f061411ea8af9e59f7e6a231d7fbe4171a63c15f1055978ebaa9dd0c51aad9600df64c328fcb929b108f480804c81d59415fb7a34a5e927efd9c46b4cbefc4fc6ee4c278384f887d6f4db98d13b0e2ee76df8c2a9caa39495e9c0c72506e535d6a3a621548adceb4890570b750e15a2ee6067a81d85b26899cb376cf29c4c273b59ab920be8889e93bdd5b3fd9e1e772e207a141f667c75070ea8422f1ef183a988a54c72781b0a25ff384379fb58b6257b8937a7e5586fe25954d01fa096e8961adb1ac6ac4d3dbaa2f39ce4230c35dfe17e3f654c2d2be6af1ef2c4d754bbd706622385b2d7fe08a1a6e4ddd97ad79e8348432b2994517c92ef577c4dbb9654a4b269b74690c4c671fb4280fdfa8451d1114a8ed4de40771ac07fe5af53a4caff6e4947121a4100e86cf70ebef0f13d2ec0f464a5b62ddfced450d16a391e9b3cbd5a29c02a329f467d9126bbb9c6277c83642b843e4e66ae7404e95350ed11bdf68808909c16da254eb419d358153bd3a77e92bc089e7e0f237ece80a271689685025cc3b8b8f0abbeef2809f4124a25188af345923aca679241b0235e5cb3bd305b6aa7f4054505c1fb0dff6cd2ed54070622d7acc41817913ea351d72f087e000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000a35cd3c2" + "status": 200, + "message": "01002dde2fc80d8100f4467649112807f2691aba39712f8276c532ec605d258d952d7367d6cd31e9f4851885e13d318991756b371200687537752710471422eb48e56ce485886516ec7ac01ae2981880e504560d3a34e328cf1bcf0ca2f34ff9013cb5362973dd8987e077f79305ec32ca42e57438e4029ca11cdfe1c063a5bbcdc13e4e970767ff402e7edc665f7a607df7c8e19685b21ecb0286054c854a588e45b675a10d23212ec9afc8ac25380ca893c6a2d031a4154b778e1cc87c5dfb59d04afc9eb5f5a6339a0fae0c74c1ee877e21a636a2324c97c26f6f64f8749c389b0b78d7c1f4cb67e8aaa683d1b0440f1b2d334433fd92b3a6bcbeb05d19eb10ec2c90b4360fc180478402f5994a95e5b3d7b7a2037234e0c70a4746d87597a7a720b09bbd62ed8d13ff0e4522fc27b56e773e1d44575bca9ee313195d7bba55183a85dc3347fd21ae59105020fa366d9c9ee215f452b78acaa8ccaf2389d37f35832133ea08980c1a176646a01ac66f00eccd797b61691184b29e3749fa136a4f7aaaac9a035c48fe3c69d1671529ba5fb0123014092272821b3137313fb2cd2fce9cf98b95b979f56ad1f6acc1cbc060cd931ddde70769665a94d582dfb8b29bb8ae55a5f780110dfc82e33f3cf7f808836f6761214ac776e1087bf607259bf16bd3477116fda0ed2abdd631a8b0fe538afbf8b7c2f017eba763f59bb8d7f41d5a043389804c2bbdb8fe70830606beae34095b3d1f376e0dff3d65a4b012f7c5338bc17aa2ec4e376b9d50529d577ed199fb370c5d35e0c9b1b07589f80f5c344c9f02d2221aa8aa342160882ffc52a55fb557df7615c3e225f0b6188c95c862ce4f6f852dc7cf38f246599e59359a6c503e0cd17181cd4d44895df7a8397f0da8a9b4f1f0dbe1fc97a511069e4a6529698c12d25e842b670fec89c4131dd2877bf679a31e2ec565aaeebda19c22635a97a8d8e0e64879e7976fbd48f7babce745b59f5c6c97a9436292053200d829b6d1ab111b804c580a9b42454e232c83af4f4c0782b728674bda6964bbac10332cd0811e1a598e5b77f2ca865b6857118d3a50f633e46150d038342bf00fb1ff1db3085a29f7e9176c5da9a87bc374ad7a3b8245e650bf372ea2582bdc876e89e2ee1d4788d10d2336878db914f0ac76838fc822c9d57507cc6deaf1a75fd4608c241c92bcb4e5f3c038d5411d7a4d7e2dacedcdd089712a7dbaf4473ab00a3c8fbe3a134a967dfd4661b4548845bd2430c6fc45d41e6160e777c0431567bbeda0824c383c1dfb6b34cc86c97f398dd28d1e235ccc44707724ef2f85bd899523e3f72ce7fd8ba28ab32afff69a3477f7b110f11217e30c1fa0d381b2fa164b5ab7080aabeb728a3884add4d2154be53853b19dc7ee278f8a2c3de3a5813b3222cc703775949cf2242395a57471e89d2fbb105834c24da5cd97a3a455decf7ed777c16dba6bf53ab6cf9470f10cae1e0cf3d03228478f9ebded681caf39578893d049f735b636fc2aca25555193d32f705091b85a160fbc0e1557b4e08a7a0d53a9551f457250338f2a750a288901af01c0084c8c09565ba3d63e1773ebf6af0cd0c9bedc1273ff53558e9fcc3d0e9485b12fcd80916beecdeaa78ac913e5483380c1b42329eaafd952d08aabf3441e1f88f119abe5c302984bec72790ab182ee4247852d0b721b33fefb22161ead095bd6a2bcbb58e0cfbedd03a3a3d500312426e55e911f6ae79caddb148d18bf60e311caf14677edf8bad0a477052dc2ff195f61367e492911e5a4e7cbf55296eae45c3cd0d3eedc0f061411ea8af9e59f7e6a231d7fbe4171a63c15f1055978ebaa9dd0c51aad9600df64c328fcb929b108f480804c81d59415fb7a34a5e927efd9c46b4cbefc4fc6ee4c278384f887d6f4db98d13b0e2ee76df8c2a9caa39495e9c0c72506e535d6a3a621548adceb4890570b750e15a2ee6067a81d85b26899cb376cf29c4c273b59ab920be8889e93bdd5b3fd9e1e772e207a141f667c75070ea8422f1ef183a988a54c72781b0a25ff384379fb58b6257b8937a7e5586fe25954d01fa096e8961adb1ac6ac4d3dbaa2f39ce4230c35dfe17e3f654c2d2be6af1ef2c4d754bbd706622385b2d7fe08a1a6e4ddd97ad79e8348432b2994517c92ef577c4dbb9654a4b269b74690c4c671fb4280fdfa8451d1114a8ed4de40771ac07fe5af53a4caff6e4947121a4100e86cf70ebef0f13d2ec0f464a5b62ddfced450d16a391e9b3cbd5a29c02a329f467d9126bbb9c6277c83642b843e4e66ae7404e95350ed11bdf68808909c16da254eb419d358153bd3a77e92bc089e7e0f237ece80a271689685025cc3b8b8f0abbeef2809f4124a25188af345923aca679241b0235e5cb3bd305b6aa7f4054505c1fb0dff6cd2ed54070622d7acc41817913ea351d72f087e000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000a35cd3c2" } diff --git a/packages/sdk/src/__test__/integration/client.interop.test.ts b/packages/sdk/src/__test__/integration/client.interop.test.ts index a3cd7a26..b438e206 100644 --- a/packages/sdk/src/__test__/integration/client.interop.test.ts +++ b/packages/sdk/src/__test__/integration/client.interop.test.ts @@ -7,17 +7,17 @@ import { getStoredClient, setStoredClient } from '../utils/setup' * This test is used to test the interoperability between the Class-based API and the Functional API. */ describe.skip('client interop', () => { - it('should setup the Client, then use that client data to', async () => { - const client = setupTestClient() - const isPaired = await client.connect(getDeviceId()) - expect(isPaired).toBe(true) + it('should setup the Client, then use that client data to', async () => { + const client = setupTestClient() + const isPaired = await client.connect(getDeviceId()) + expect(isPaired).toBe(true) - await setup({ - getStoredClient, - setStoredClient, - }) + await setup({ + getStoredClient, + setStoredClient, + }) - const activeWallets = await fetchActiveWallets() - expect(activeWallets).toBeTruthy() - }) + const activeWallets = await fetchActiveWallets() + expect(activeWallets).toBeTruthy() + }) }) diff --git a/packages/sdk/src/__test__/integration/connect.test.ts b/packages/sdk/src/__test__/integration/connect.test.ts index 1d7bfe91..26f3568c 100644 --- a/packages/sdk/src/__test__/integration/connect.test.ts +++ b/packages/sdk/src/__test__/integration/connect.test.ts @@ -4,69 +4,69 @@ import { getDeviceId } from '../utils/getters' import { BTC_PURPOSE_P2PKH, ETH_COIN, setupTestClient } from '../utils/helpers' describe('connect', () => { - it('should test connect', async () => { - const client = setupTestClient() - const isPaired = await client.connect(getDeviceId()) - expect(isPaired).toMatchSnapshot() - }) + it('should test connect', async () => { + const client = setupTestClient() + const isPaired = await client.connect(getDeviceId()) + expect(isPaired).toMatchSnapshot() + }) - it('should test fetchActiveWallet', async () => { - const client = setupTestClient() - await client.connect(getDeviceId()) - await client.fetchActiveWallet() - }) + it('should test fetchActiveWallet', async () => { + const client = setupTestClient() + await client.connect(getDeviceId()) + await client.fetchActiveWallet() + }) - it('should test getAddresses', async () => { - const client = setupTestClient() - await client.connect(getDeviceId()) + it('should test getAddresses', async () => { + const client = setupTestClient() + await client.connect(getDeviceId()) - const startPath = [BTC_PURPOSE_P2PKH, ETH_COIN, HARDENED_OFFSET, 0, 0] + const startPath = [BTC_PURPOSE_P2PKH, ETH_COIN, HARDENED_OFFSET, 0, 0] - const addrs = await client.getAddresses({ startPath, n: 1 }) - expect(addrs).toMatchSnapshot() - }) + const addrs = await client.getAddresses({ startPath, n: 1 }) + expect(addrs).toMatchSnapshot() + }) - it('should test sign', async () => { - const client = setupTestClient() - await client.connect(getDeviceId()) + it('should test sign', async () => { + const client = setupTestClient() + await client.connect(getDeviceId()) - const { req } = await buildEthSignRequest(client) - const signData = await client.sign(req) - expect(signData).toMatchSnapshot() - }) + const { req } = await buildEthSignRequest(client) + const signData = await client.sign(req) + expect(signData).toMatchSnapshot() + }) - it('should test fetchActiveWallet', async () => { - const client = setupTestClient() - await client.connect(getDeviceId()) + it('should test fetchActiveWallet', async () => { + const client = setupTestClient() + await client.connect(getDeviceId()) - const activeWallet = await client.fetchActiveWallet() - expect(activeWallet).toMatchSnapshot() - }) + const activeWallet = await client.fetchActiveWallet() + expect(activeWallet).toMatchSnapshot() + }) - it('should test getKvRecords', async () => { - const client = setupTestClient() - await client.connect(getDeviceId()) + it('should test getKvRecords', async () => { + const client = setupTestClient() + await client.connect(getDeviceId()) - const activeWallet = await client.getKvRecords({ start: 0 }) - expect(activeWallet).toMatchSnapshot() - }) - it('should test addKvRecords', async () => { - const client = setupTestClient() - await client.connect(getDeviceId()) + const activeWallet = await client.getKvRecords({ start: 0 }) + expect(activeWallet).toMatchSnapshot() + }) + it('should test addKvRecords', async () => { + const client = setupTestClient() + await client.connect(getDeviceId()) - const activeWallet = await client.addKvRecords({ - records: { test2: 'test2' }, - }) - expect(activeWallet).toMatchSnapshot() - }) - it('should test removeKvRecords', async () => { - const client = setupTestClient() - await client.connect(getDeviceId()) - await client.addKvRecords({ records: { test: `${Math.random()}` } }) - const { records } = await client.getKvRecords({ start: 0 }) - const activeWallet = await client.removeKvRecords({ - ids: records.map((r) => `${r.id}`), - }) - expect(activeWallet).toMatchSnapshot() - }) + const activeWallet = await client.addKvRecords({ + records: { test2: 'test2' }, + }) + expect(activeWallet).toMatchSnapshot() + }) + it('should test removeKvRecords', async () => { + const client = setupTestClient() + await client.connect(getDeviceId()) + await client.addKvRecords({ records: { test: `${Math.random()}` } }) + const { records } = await client.getKvRecords({ start: 0 }) + const activeWallet = await client.removeKvRecords({ + ids: records.map((r) => `${r.id}`), + }) + expect(activeWallet).toMatchSnapshot() + }) }) diff --git a/packages/sdk/src/__test__/integration/fetchCalldataDecoder.test.ts b/packages/sdk/src/__test__/integration/fetchCalldataDecoder.test.ts index f985ec26..fc873fe4 100644 --- a/packages/sdk/src/__test__/integration/fetchCalldataDecoder.test.ts +++ b/packages/sdk/src/__test__/integration/fetchCalldataDecoder.test.ts @@ -2,106 +2,106 @@ import { fetchCalldataDecoder } from '../../util' import { setup as setupMockServiceWorker } from './__mocks__/setup' describe('fetchCalldataDecoder', () => { - // Mocks out responses from Etherscan and 4byte - setupMockServiceWorker() + // Mocks out responses from Etherscan and 4byte + setupMockServiceWorker() - beforeAll(() => { - // Disable this mock to restore console logs when updating tests - console.warn = vi.fn() - }) + beforeAll(() => { + // Disable this mock to restore console logs when updating tests + console.warn = vi.fn() + }) - afterAll(() => { - vi.clearAllMocks() - }) + afterAll(() => { + vi.clearAllMocks() + }) - test('decode calldata', async () => { - const data = - '0x38ed17390000000000000000000000000000000000000000000c1c173c5b782a5b154ab900000000000000000000000000000000000000000000000f380d77022fe8c32600000000000000000000000000000000000000000000000000000000000000a00000000000000000000000007ae7684581f0298241c3d6a6567a48d56b42b15c00000000000000000000000000000000000000000000000000000000622f8d27000000000000000000000000000000000000000000000000000000000000000300000000000000000000000095ad61b0a150d79219dcf64e1e6cc01f0b64c4ce000000000000000000000000c02aaa39b223fe8d0a0e5c4f27ead9083c756cc200000000000000000000000050522c769e01eb06c02bd299066509d8f97a69ae' - const to = '0x7a250d5630b4cf539739df2c5dacb4c659f2488d' - const decoded = await fetchCalldataDecoder(data, to, '1') - expect(decoded).toMatchSnapshot() - }) + test('decode calldata', async () => { + const data = + '0x38ed17390000000000000000000000000000000000000000000c1c173c5b782a5b154ab900000000000000000000000000000000000000000000000f380d77022fe8c32600000000000000000000000000000000000000000000000000000000000000a00000000000000000000000007ae7684581f0298241c3d6a6567a48d56b42b15c00000000000000000000000000000000000000000000000000000000622f8d27000000000000000000000000000000000000000000000000000000000000000300000000000000000000000095ad61b0a150d79219dcf64e1e6cc01f0b64c4ce000000000000000000000000c02aaa39b223fe8d0a0e5c4f27ead9083c756cc200000000000000000000000050522c769e01eb06c02bd299066509d8f97a69ae' + const to = '0x7a250d5630b4cf539739df2c5dacb4c659f2488d' + const decoded = await fetchCalldataDecoder(data, to, '1') + expect(decoded).toMatchSnapshot() + }) - test('decode calldata as Buffer', async () => { - const data = Buffer.from( - '38ed17390000000000000000000000000000000000000000000c1c173c5b782a5b154ab900000000000000000000000000000000000000000000000f380d77022fe8c32600000000000000000000000000000000000000000000000000000000000000a00000000000000000000000007ae7684581f0298241c3d6a6567a48d56b42b15c00000000000000000000000000000000000000000000000000000000622f8d27000000000000000000000000000000000000000000000000000000000000000300000000000000000000000095ad61b0a150d79219dcf64e1e6cc01f0b64c4ce000000000000000000000000c02aaa39b223fe8d0a0e5c4f27ead9083c756cc200000000000000000000000050522c769e01eb06c02bd299066509d8f97a69ae', - 'hex', - ) - const to = '0x7a250d5630b4cf539739df2c5dacb4c659f2488d' - const decoded = await fetchCalldataDecoder(data, to, '1') - expect(decoded).toMatchSnapshot() - }) + test('decode calldata as Buffer', async () => { + const data = Buffer.from( + '38ed17390000000000000000000000000000000000000000000c1c173c5b782a5b154ab900000000000000000000000000000000000000000000000f380d77022fe8c32600000000000000000000000000000000000000000000000000000000000000a00000000000000000000000007ae7684581f0298241c3d6a6567a48d56b42b15c00000000000000000000000000000000000000000000000000000000622f8d27000000000000000000000000000000000000000000000000000000000000000300000000000000000000000095ad61b0a150d79219dcf64e1e6cc01f0b64c4ce000000000000000000000000c02aaa39b223fe8d0a0e5c4f27ead9083c756cc200000000000000000000000050522c769e01eb06c02bd299066509d8f97a69ae', + 'hex', + ) + const to = '0x7a250d5630b4cf539739df2c5dacb4c659f2488d' + const decoded = await fetchCalldataDecoder(data, to, '1') + expect(decoded).toMatchSnapshot() + }) - test('decode proxy calldata', async () => { - const data = - '0xa9059cbb0000000000000000000000004ffbf741b0a64e8bd1f9d89fc9b5584cc5227b700000000000000000000000000000000000000000000000000000003052aacdb8' - const to = '0xa0b86991c6218b36c1d19d4a2e9eb0ce3606eb48' - const decoded = await fetchCalldataDecoder(data, to, '1') - expect(decoded).toMatchSnapshot() - }) + test('decode proxy calldata', async () => { + const data = + '0xa9059cbb0000000000000000000000004ffbf741b0a64e8bd1f9d89fc9b5584cc5227b700000000000000000000000000000000000000000000000000000003052aacdb8' + const to = '0xa0b86991c6218b36c1d19d4a2e9eb0ce3606eb48' + const decoded = await fetchCalldataDecoder(data, to, '1') + expect(decoded).toMatchSnapshot() + }) - test('fallback to 4byte', async () => { - const data = - '0x38ed17390000000000000000000000000000000000000000000c1c173c5b782a5b154ab900000000000000000000000000000000000000000000000f380d77022fe8c32600000000000000000000000000000000000000000000000000000000000000a00000000000000000000000007ae7684581f0298241c3d6a6567a48d56b42b15c00000000000000000000000000000000000000000000000000000000622f8d27000000000000000000000000000000000000000000000000000000000000000300000000000000000000000095ad61b0a150d79219dcf64e1e6cc01f0b64c4ce000000000000000000000000c02aaa39b223fe8d0a0e5c4f27ead9083c756cc200000000000000000000000050522c769e01eb06c02bd299066509d8f97a69ae' - const to = '0x7a250d5630b4cf539739df2c5dacb4c659f2488d' - const decoded = await fetchCalldataDecoder(data, to, -1) - expect(decoded).toMatchSnapshot() - }) + test('fallback to 4byte', async () => { + const data = + '0x38ed17390000000000000000000000000000000000000000000c1c173c5b782a5b154ab900000000000000000000000000000000000000000000000f380d77022fe8c32600000000000000000000000000000000000000000000000000000000000000a00000000000000000000000007ae7684581f0298241c3d6a6567a48d56b42b15c00000000000000000000000000000000000000000000000000000000622f8d27000000000000000000000000000000000000000000000000000000000000000300000000000000000000000095ad61b0a150d79219dcf64e1e6cc01f0b64c4ce000000000000000000000000c02aaa39b223fe8d0a0e5c4f27ead9083c756cc200000000000000000000000050522c769e01eb06c02bd299066509d8f97a69ae' + const to = '0x7a250d5630b4cf539739df2c5dacb4c659f2488d' + const decoded = await fetchCalldataDecoder(data, to, -1) + expect(decoded).toMatchSnapshot() + }) - test('decode calldata from external chain', async () => { - const data = - '0x38ed17390000000000000000000000000000000000000000000c1c173c5b782a5b154ab900000000000000000000000000000000000000000000000f380d77022fe8c32600000000000000000000000000000000000000000000000000000000000000a00000000000000000000000007ae7684581f0298241c3d6a6567a48d56b42b15c00000000000000000000000000000000000000000000000000000000622f8d27000000000000000000000000000000000000000000000000000000000000000300000000000000000000000095ad61b0a150d79219dcf64e1e6cc01f0b64c4ce000000000000000000000000c02aaa39b223fe8d0a0e5c4f27ead9083c756cc200000000000000000000000050522c769e01eb06c02bd299066509d8f97a69ae' - const to = '0xa0b86991c6218b36c1d19d4a2e9eb0ce3606eb48' - const decoded = await fetchCalldataDecoder(data, to, '1337') - expect(decoded).toMatchSnapshot() - }) + test('decode calldata from external chain', async () => { + const data = + '0x38ed17390000000000000000000000000000000000000000000c1c173c5b782a5b154ab900000000000000000000000000000000000000000000000f380d77022fe8c32600000000000000000000000000000000000000000000000000000000000000a00000000000000000000000007ae7684581f0298241c3d6a6567a48d56b42b15c00000000000000000000000000000000000000000000000000000000622f8d27000000000000000000000000000000000000000000000000000000000000000300000000000000000000000095ad61b0a150d79219dcf64e1e6cc01f0b64c4ce000000000000000000000000c02aaa39b223fe8d0a0e5c4f27ead9083c756cc200000000000000000000000050522c769e01eb06c02bd299066509d8f97a69ae' + const to = '0xa0b86991c6218b36c1d19d4a2e9eb0ce3606eb48' + const decoded = await fetchCalldataDecoder(data, to, '1337') + expect(decoded).toMatchSnapshot() + }) - test('decode nested calldata: multicall(bytes[])', async () => { - const data = - '0xac9650d8000000000000000000000000000000000000000000000000000000000000002000000000000000000000000000000000000000000000000000000000000000020000000000000000000000000000000000000000000000000000000000000040000000000000000000000000000000000000000000000000000000000000012000000000000000000000000000000000000000000000000000000000000000a40c49ccbe000000000000000000000000000000000000000000000000000000000002d6da00000000000000000000000000000000000000000000000000000b100a58bd63000000000000000000000000000000000000000000000000000016ac77cb53fe00000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000061ef0508000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000084fc6f7865000000000000000000000000000000000000000000000000000000000002d6da0000000000000000000000004ce6aea89f059915ae5efbf34a2a8adc544ae09e00000000000000000000000000000000ffffffffffffffffffffffffffffffff00000000000000000000000000000000ffffffffffffffffffffffffffffffff00000000000000000000000000000000000000000000000000000000' - const to = '0xc36442b4a4522e871399cd717abdd847ab11fe88' - const { def } = await fetchCalldataDecoder(data, to, '1', true) - expect({ def }).toMatchSnapshot() - }) + test('decode nested calldata: multicall(bytes[])', async () => { + const data = + '0xac9650d8000000000000000000000000000000000000000000000000000000000000002000000000000000000000000000000000000000000000000000000000000000020000000000000000000000000000000000000000000000000000000000000040000000000000000000000000000000000000000000000000000000000000012000000000000000000000000000000000000000000000000000000000000000a40c49ccbe000000000000000000000000000000000000000000000000000000000002d6da00000000000000000000000000000000000000000000000000000b100a58bd63000000000000000000000000000000000000000000000000000016ac77cb53fe00000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000061ef0508000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000084fc6f7865000000000000000000000000000000000000000000000000000000000002d6da0000000000000000000000004ce6aea89f059915ae5efbf34a2a8adc544ae09e00000000000000000000000000000000ffffffffffffffffffffffffffffffff00000000000000000000000000000000ffffffffffffffffffffffffffffffff00000000000000000000000000000000000000000000000000000000' + const to = '0xc36442b4a4522e871399cd717abdd847ab11fe88' + const { def } = await fetchCalldataDecoder(data, to, '1', true) + expect({ def }).toMatchSnapshot() + }) - test('decode nested calldata: execTransaction(address,uint256,(multicall(bytes[])),uint8,uint256,uint256,uint256,address,address,bytes)', async () => { - const data = - '0x6a761202000000000000000000000000c36442b4a4522e871399cd717abdd847ab11fe880000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000014000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000036e3f000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000003a00000000000000000000000000000000000000000000000000000000000000224ac9650d8000000000000000000000000000000000000000000000000000000000000002000000000000000000000000000000000000000000000000000000000000000020000000000000000000000000000000000000000000000000000000000000040000000000000000000000000000000000000000000000000000000000000012000000000000000000000000000000000000000000000000000000000000000a40c49ccbe0000000000000000000000000000000000000000000000000000000000042677000000000000000000000000000000000000000000000000002223dbc72b15a70000000000000000000000000000000000000000000000000000014f8d4596e400000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000062e90b1b000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000084fc6f7865000000000000000000000000000000000000000000000000000000000004267700000000000000000000000006412d7ebfbf66c25607e2ed24c1d207043be32700000000000000000000000000000000ffffffffffffffffffffffffffffffff00000000000000000000000000000000ffffffffffffffffffffffffffffffff000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000c351d12bf187b0b2cb69bcf58076f8ce9197ebc4ff5fbb97ca5f36d296cfc55ffe4e62e761ab9d1a5d27577d76dc18f1e851e6ec87a239bedcbbef8bb52bd811501bc23b15b14d797a0cc444b9ab5e0a9340b8f1341210778446c948f5120b282b105ae55d0aef0bf62af4e614d42a9c99b2724272486433a191ed16ecaaab81dab61c0000000000000000000000009789d9d99409bf01699b8988da8886647418998e0000000000000000000000000000000000000000000000000000000000000000010000000000000000000000000000000000000000000000000000000000' - const to = '0x06412d7ebfbf66c25607e2ed24c1d207043be327' - const { def } = await fetchCalldataDecoder(data, to, '1', true) - expect({ def }).toMatchSnapshot() - }) + test('decode nested calldata: execTransaction(address,uint256,(multicall(bytes[])),uint8,uint256,uint256,uint256,address,address,bytes)', async () => { + const data = + '0x6a761202000000000000000000000000c36442b4a4522e871399cd717abdd847ab11fe880000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000014000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000036e3f000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000003a00000000000000000000000000000000000000000000000000000000000000224ac9650d8000000000000000000000000000000000000000000000000000000000000002000000000000000000000000000000000000000000000000000000000000000020000000000000000000000000000000000000000000000000000000000000040000000000000000000000000000000000000000000000000000000000000012000000000000000000000000000000000000000000000000000000000000000a40c49ccbe0000000000000000000000000000000000000000000000000000000000042677000000000000000000000000000000000000000000000000002223dbc72b15a70000000000000000000000000000000000000000000000000000014f8d4596e400000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000062e90b1b000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000084fc6f7865000000000000000000000000000000000000000000000000000000000004267700000000000000000000000006412d7ebfbf66c25607e2ed24c1d207043be32700000000000000000000000000000000ffffffffffffffffffffffffffffffff00000000000000000000000000000000ffffffffffffffffffffffffffffffff000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000c351d12bf187b0b2cb69bcf58076f8ce9197ebc4ff5fbb97ca5f36d296cfc55ffe4e62e761ab9d1a5d27577d76dc18f1e851e6ec87a239bedcbbef8bb52bd811501bc23b15b14d797a0cc444b9ab5e0a9340b8f1341210778446c948f5120b282b105ae55d0aef0bf62af4e614d42a9c99b2724272486433a191ed16ecaaab81dab61c0000000000000000000000009789d9d99409bf01699b8988da8886647418998e0000000000000000000000000000000000000000000000000000000000000000010000000000000000000000000000000000000000000000000000000000' + const to = '0x06412d7ebfbf66c25607e2ed24c1d207043be327' + const { def } = await fetchCalldataDecoder(data, to, '1', true) + expect({ def }).toMatchSnapshot() + }) - test('handle too short data', async () => { - const data = '0x001' - const to = '0xa0b86991c6218b36c1d19d4a2e9eb0ce3606eb48' - const decoded = await fetchCalldataDecoder(data, to, '1') - expect(decoded).toMatchInlineSnapshot(` + test('handle too short data', async () => { + const data = '0x001' + const to = '0xa0b86991c6218b36c1d19d4a2e9eb0ce3606eb48' + const decoded = await fetchCalldataDecoder(data, to, '1') + expect(decoded).toMatchInlineSnapshot(` { "abi": null, "def": null, } `) - }) + }) - test('handle no data', async () => { - const data = '' - const to = '0xa0b86991c6218b36c1d19d4a2e9eb0ce3606eb48' - const decoded = await fetchCalldataDecoder(data, to, '1') - expect(decoded).toMatchInlineSnapshot(` + test('handle no data', async () => { + const data = '' + const to = '0xa0b86991c6218b36c1d19d4a2e9eb0ce3606eb48' + const decoded = await fetchCalldataDecoder(data, to, '1') + expect(decoded).toMatchInlineSnapshot(` { "abi": null, "def": null, } `) - }) + }) - // TODO: add api key to fix this test - test.skip('decode Celo calldata', async () => { - const data = - '0xf2fde38b000000000000000000000000b538e8dcd297450bdef46222f3ceb33bb1e921b3' - const to = '0x96d59127ccd1c0e3749e733ee04f0dfbd2f808c8' - const decoded = await fetchCalldataDecoder(data, to, '42220') - expect(decoded).toMatchSnapshot() - }) + // TODO: add api key to fix this test + test.skip('decode Celo calldata', async () => { + const data = + '0xf2fde38b000000000000000000000000b538e8dcd297450bdef46222f3ceb33bb1e921b3' + const to = '0x96d59127ccd1c0e3749e733ee04f0dfbd2f808c8' + const decoded = await fetchCalldataDecoder(data, to, '42220') + expect(decoded).toMatchSnapshot() + }) }) diff --git a/packages/sdk/src/__test__/unit/__mocks__/decoderData.ts b/packages/sdk/src/__test__/unit/__mocks__/decoderData.ts index 7e1d0bb4..2a761f79 100644 --- a/packages/sdk/src/__test__/unit/__mocks__/decoderData.ts +++ b/packages/sdk/src/__test__/unit/__mocks__/decoderData.ts @@ -4,106 +4,106 @@ * For each response, the response code and checksum are removed. */ import { - LatticeEncDataSchema, - LatticeGetAddressesFlag, + LatticeEncDataSchema, + LatticeGetAddressesFlag, } from '../../../protocol' import { getP256KeyPair } from '../../../util' export const clientKeyPair = getP256KeyPair( - Buffer.from( - '3fb53b677f73e4d2b8c89c303f6f6b349f0075ad88ea126cb9f6632085815dca', - 'hex', - ), + Buffer.from( + '3fb53b677f73e4d2b8c89c303f6f6b349f0075ad88ea126cb9f6632085815dca', + 'hex', + ), ) export const decoderTestsFwConstants = JSON.parse( - '{"extraDataFrameSz":1500,"extraDataMaxFrames":1,"genericSigning":{"baseReqSz":1552,"baseDataSz":1519,"hashTypes":{"NONE":0,"KECCAK256":1,"SHA256":2},"curveTypes":{"SECP256K1":0,"ED25519":1,"BLS12_381_G2":2},"encodingTypes":{"NONE":1,"SOLANA":2,"EVM":4,"ETH_DEPOSIT":5},"calldataDecoding":{"reserved":2895728,"maxSz":1024}},"reqMaxDataSz":1678,"ethMaxGasPrice":20000000000000,"addrFlagsAllowed":true,"ethMaxDataSz":1519,"ethMaxMsgSz":1540,"eip712MaxTypeParams":36,"varAddrPathSzAllowed":true,"eip712Supported":true,"prehashAllowed":true,"ethMsgPreHashAllowed":true,"allowedEthTxTypes":[1,2],"personalSignHeaderSz":72,"kvActionsAllowed":true,"kvKeyMaxStrSz":63,"kvValMaxStrSz":63,"kvActionMaxNum":10,"kvRemoveMaxNum":100,"allowBtcLegacyAndSegwitAddrs":true,"contractDeployKey":"0x08002e0fec8e6acf00835f43c9764f7364fa3f42","abiCategorySz":32,"abiMaxRmv":200,"getAddressFlags":[4,3,5],"maxDecoderBufSz":1600}', + '{"extraDataFrameSz":1500,"extraDataMaxFrames":1,"genericSigning":{"baseReqSz":1552,"baseDataSz":1519,"hashTypes":{"NONE":0,"KECCAK256":1,"SHA256":2},"curveTypes":{"SECP256K1":0,"ED25519":1,"BLS12_381_G2":2},"encodingTypes":{"NONE":1,"SOLANA":2,"EVM":4,"ETH_DEPOSIT":5},"calldataDecoding":{"reserved":2895728,"maxSz":1024}},"reqMaxDataSz":1678,"ethMaxGasPrice":20000000000000,"addrFlagsAllowed":true,"ethMaxDataSz":1519,"ethMaxMsgSz":1540,"eip712MaxTypeParams":36,"varAddrPathSzAllowed":true,"eip712Supported":true,"prehashAllowed":true,"ethMsgPreHashAllowed":true,"allowedEthTxTypes":[1,2],"personalSignHeaderSz":72,"kvActionsAllowed":true,"kvKeyMaxStrSz":63,"kvValMaxStrSz":63,"kvActionMaxNum":10,"kvRemoveMaxNum":100,"allowBtcLegacyAndSegwitAddrs":true,"contractDeployKey":"0x08002e0fec8e6acf00835f43c9764f7364fa3f42","abiCategorySz":32,"abiMaxRmv":200,"getAddressFlags":[4,3,5],"maxDecoderBufSz":1600}', ) export const connectDecoderData = Buffer.from( - '0104de558941cc182423e1fa6b0ee81b2c17c6203d0c2897929f900480a8b879261993500d7c0bb5b80f75e2ca462681fcaa20d0261775d3204c6ee461c9250ee1d60011000022cfce60cc7995770a9d2c5080351ae2068c71cecb766de54c65053f5662337809baf7d9ddaaafca95180611fb37601c8eebc1d9f967c061edee1511309a70fbe2491a266f84dc5d1c868b6e129170fa3b777ebd9af7c047fd6dff2a8a563cf6b8ea8c6157b33bc5a0614c65369ffc3c4d35537f37f197f9bae12c574b9847174e38c41b0302833ebbd2101237703794', - 'hex', + '0104de558941cc182423e1fa6b0ee81b2c17c6203d0c2897929f900480a8b879261993500d7c0bb5b80f75e2ca462681fcaa20d0261775d3204c6ee461c9250ee1d60011000022cfce60cc7995770a9d2c5080351ae2068c71cecb766de54c65053f5662337809baf7d9ddaaafca95180611fb37601c8eebc1d9f967c061edee1511309a70fbe2491a266f84dc5d1c868b6e129170fa3b777ebd9af7c047fd6dff2a8a563cf6b8ea8c6157b33bc5a0614c65369ffc3c4d35537f37f197f9bae12c574b9847174e38c41b0302833ebbd2101237703794', + 'hex', ) export const getAddressesFlag = LatticeGetAddressesFlag.none export const getAddressesDecoderData = Buffer.from( - '334d32465857534a7569584d4d79737179534d52566e4d655935335141545a664459000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000033435757456366725447394441336648376955714562506b6d504c6b5a396d416838000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000033384b734472704a3556524553516150364163785072704875554a6d5270397a646800000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000003351665444387372784a636742685958667557443555505955375146616258476733000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000033426a685344394a386b50553648346b52475071614d5a58485a4134726f447a344c0000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000', - 'hex', + '334d32465857534a7569584d4d79737179534d52566e4d655935335141545a664459000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000033435757456366725447394441336648376955714562506b6d504c6b5a396d416838000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000033384b734472704a3556524553516150364163785072704875554a6d5270397a646800000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000003351665444387372784a636742685958667557443555505955375146616258476733000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000033426a685344394a386b50553648346b52475071614d5a58485a4134726f447a344c0000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000', + 'hex', ) export const signGenericRequest = { - payload: Buffer.from( - '040000000100050000002c0000803c000080000000800000000000000000004e0001f84b01808447868c0082c35094e242e54155b1abc71fc118065270cecaaf8b776885e8d4a51000a417e914679b7e160613be4f8c2d3203d236286d74eb9192f6d6f71b9118a42bb033ccd8e8c000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000', - 'hex', - ), - extraDataPayloads: [], - schema: 5, - curveType: 0, - encodingType: 4, - hashType: 1, - omitPubkey: false, - origPayloadBuf: Buffer.from( - '01f84b01808447868c0082c35094e242e54155b1abc71fc118065270cecaaf8b776885e8d4a51000a417e914679b7e160613be4f8c2d3203d236286d74eb9192f6d6f71b9118a42bb033ccd8e8c0', - 'hex', - ), + payload: Buffer.from( + '040000000100050000002c0000803c000080000000800000000000000000004e0001f84b01808447868c0082c35094e242e54155b1abc71fc118065270cecaaf8b776885e8d4a51000a417e914679b7e160613be4f8c2d3203d236286d74eb9192f6d6f71b9118a42bb033ccd8e8c000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000', + 'hex', + ), + extraDataPayloads: [], + schema: 5, + curveType: 0, + encodingType: 4, + hashType: 1, + omitPubkey: false, + origPayloadBuf: Buffer.from( + '01f84b01808447868c0082c35094e242e54155b1abc71fc118065270cecaaf8b776885e8d4a51000a417e914679b7e160613be4f8c2d3203d236286d74eb9192f6d6f71b9118a42bb033ccd8e8c0', + 'hex', + ), } export const signGenericDecoderData = Buffer.from( - '04a50d7d8e5bf6353086dfaff71652a223aa13e02273a2b6bf5a145314b544be1281ac8f78d035874a06b11e3df68e45f7630b2e6ba3be0f51f916fbb6f0a6403930440220640b2c690858ab8d0b9500f9ed64c9aa6b7467b77f1199b061aa96ea780aadaa022048f830f9290dd1b3eaf1922e08a8c992873be1162bd6d5bef681cf911328abe500000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000', - 'hex', + '04a50d7d8e5bf6353086dfaff71652a223aa13e02273a2b6bf5a145314b544be1281ac8f78d035874a06b11e3df68e45f7630b2e6ba3be0f51f916fbb6f0a6403930440220640b2c690858ab8d0b9500f9ed64c9aa6b7467b77f1199b061aa96ea780aadaa022048f830f9290dd1b3eaf1922e08a8c992873be1162bd6d5bef681cf911328abe500000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000', + 'hex', ) export const signBitcoinRequest = { - schema: 0, - // NOTE: This data was tricky to fetch because JSON stringify and buffers don't match well - origData: { - prevOuts: [ - { - txHash: Buffer.from( - 'b2efdbdd3340d2bc547671ce3993a6f05d70343c07578f9d7f5626fdfc06fa35', - 'hex', - ), - value: 76800, - index: 0, - signerPath: [2147483732, 2147483649, 2147483648, 0, 0], - }, - ], - recipient: '2N4gqWT4oqWL2gz9ps92z9fm2Bg3FUkqG7Q', - value: 70000, - fee: 4380, - isSegwit: true, - changePath: [2147483732, 2147483649, 2147483648, 1, 0], - fwConstants: decoderTestsFwConstants, - }, - changeData: { value: 2420 }, - payload: Buffer.from( - 'f00500000054000080010000800000008001000000000000001c110000c47d816ef0a39d6497963ebcf24d05242d51ada74370110100000000000105000000540000800100008000000080000000000000000000000000002c01000000000004b2efdbdd3340d2bc547671ce3993a6f05d70343c07578f9d7f5626fdfc06fa35', - 'hex', - ), + schema: 0, + // NOTE: This data was tricky to fetch because JSON stringify and buffers don't match well + origData: { + prevOuts: [ + { + txHash: Buffer.from( + 'b2efdbdd3340d2bc547671ce3993a6f05d70343c07578f9d7f5626fdfc06fa35', + 'hex', + ), + value: 76800, + index: 0, + signerPath: [2147483732, 2147483649, 2147483648, 0, 0], + }, + ], + recipient: '2N4gqWT4oqWL2gz9ps92z9fm2Bg3FUkqG7Q', + value: 70000, + fee: 4380, + isSegwit: true, + changePath: [2147483732, 2147483649, 2147483648, 1, 0], + fwConstants: decoderTestsFwConstants, + }, + changeData: { value: 2420 }, + payload: Buffer.from( + 'f00500000054000080010000800000008001000000000000001c110000c47d816ef0a39d6497963ebcf24d05242d51ada74370110100000000000105000000540000800100008000000080000000000000000000000000002c01000000000004b2efdbdd3340d2bc547671ce3993a6f05d70343c07578f9d7f5626fdfc06fa35', + 'hex', + ), } export const signBitcoinDecoderData = Buffer.from( - '6bb07ddb748b655c8478581af3e128335c16eca0304502210084e356184a7dc1e05a08808cb6da03f9e5d1c37be0f382a761eac2a266a4737f0220172d99b82cf78bf5bb4299e4e663f13e1e4578d144ffeabc474631fb1ac1a4fe00000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000002d423e4c1cc57744a0e7365954e7a632ab272f5d0167337f69227c58e6e2d113e000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000', - 'hex', + '6bb07ddb748b655c8478581af3e128335c16eca0304502210084e356184a7dc1e05a08808cb6da03f9e5d1c37be0f382a761eac2a266a4737f0220172d99b82cf78bf5bb4299e4e663f13e1e4578d144ffeabc474631fb1ac1a4fe00000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000002d423e4c1cc57744a0e7365954e7a632ab272f5d0167337f69227c58e6e2d113e000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000', + 'hex', ) // Uses `decoderTestsFwConstants` export const getKvRecordsDecoderData = Buffer.from( - '00000001013f53e5d800000000012b3078333064613364374138363543393334623338396339313963373337353130303534313131414233410000000000000000000000000000000000000000000012546573742041646472657373204e616d650000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000', - 'hex', + '00000001013f53e5d800000000012b3078333064613364374138363543393334623338396339313963373337353130303534313131414233410000000000000000000000000000000000000000000012546573742041646472657373204e616d650000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000', + 'hex', ) export const fetchEncryptedDataRequest = { - schema: LatticeEncDataSchema.eip2335, - params: { - path: [12381, 3600, 0, 0], - c: 999, - walletUID: Buffer.from( - '6ae62c0c96c1e039fc97bfeb7c2428c093fe7f0b6188a434bbac7b652c3e4012', - 'hex', - ), - }, + schema: LatticeEncDataSchema.eip2335, + params: { + path: [12381, 3600, 0, 0], + c: 999, + walletUID: Buffer.from( + '6ae62c0c96c1e039fc97bfeb7c2428c093fe7f0b6188a434bbac7b652c3e4012', + 'hex', + ), + }, } export const fetchEncryptedDataDecoderData = Buffer.from( - 'a4000000e703000077051b0f03811b1b0bdb48e44977ac70c685312aa6df31c8b3491a9de63b6266f3d76007aba10d19c51ffd031101ac78089c7325d3168d492509735d7f064bfc7f057f2a33451a665c3a8e16dc552e0b1c386f922a80dfd7208ef98afa499dcd2fc5b879b78281c8e5b699904dbbaba690a9a242b1aa0cd4458398c77497200e485f55c16b1e7a3146ac74bff42872d2f76e63689be7066f557e985ebd671114000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000', - 'hex', + 'a4000000e703000077051b0f03811b1b0bdb48e44977ac70c685312aa6df31c8b3491a9de63b6266f3d76007aba10d19c51ffd031101ac78089c7325d3168d492509735d7f064bfc7f057f2a33451a665c3a8e16dc552e0b1c386f922a80dfd7208ef98afa499dcd2fc5b879b78281c8e5b699904dbbaba690a9a242b1aa0cd4458398c77497200e485f55c16b1e7a3146ac74bff42872d2f76e63689be7066f557e985ebd671114000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000', + 'hex', ) diff --git a/packages/sdk/src/__test__/unit/api.test.ts b/packages/sdk/src/__test__/unit/api.test.ts index df59e261..33dcf6ef 100644 --- a/packages/sdk/src/__test__/unit/api.test.ts +++ b/packages/sdk/src/__test__/unit/api.test.ts @@ -1,59 +1,59 @@ import { parseDerivationPath } from '../../api/utilities' describe('parseDerivationPath', () => { - it('parses a simple derivation path correctly', () => { - const result = parseDerivationPath('44/0/0/0') - expect(result).toEqual([44, 0, 0, 0]) - }) - - it('parses a derivation path with hardened indices correctly', () => { - const result = parseDerivationPath("44'/0'/0'/0") - expect(result).toEqual([0x8000002c, 0x80000000, 0x80000000, 0]) - }) - - it('handles mixed hardened and non-hardened indices', () => { - const result = parseDerivationPath("44'/60/0'/0/0") - expect(result).toEqual([0x8000002c, 60, 0x80000000, 0, 0]) - }) - - it('interprets lowercase x as 0', () => { - const result = parseDerivationPath('44/x/0/0') - expect(result).toEqual([44, 0, 0, 0]) - }) - - it('interprets uppercase X as 0', () => { - const result = parseDerivationPath('44/X/0/0') - expect(result).toEqual([44, 0, 0, 0]) - }) - - it("interprets X' as hardened zero", () => { - const result = parseDerivationPath("44'/X'/0/0") - expect(result).toEqual([0x8000002c, 0x80000000, 0, 0]) - }) - - it("interprets x' as hardened zero", () => { - const result = parseDerivationPath("44'/x'/0/0") - expect(result).toEqual([0x8000002c, 0x80000000, 0, 0]) - }) - - it('handles a complex path with all features', () => { - const result = parseDerivationPath("44'/501'/X'/0'") - expect(result).toEqual([0x8000002c, 0x800001f5, 0x80000000, 0x80000000]) - }) - - it('returns an empty array for an empty path', () => { - const result = parseDerivationPath('') - expect(result).toEqual([]) - }) - - it('handles leading slash correctly', () => { - const result = parseDerivationPath('/44/0/0/0') - expect(result).toEqual([44, 0, 0, 0]) - }) - - it('throws error for invalid input', () => { - expect(() => parseDerivationPath('invalid/path')).toThrow( - 'Invalid part in derivation path: invalid', - ) - }) + it('parses a simple derivation path correctly', () => { + const result = parseDerivationPath('44/0/0/0') + expect(result).toEqual([44, 0, 0, 0]) + }) + + it('parses a derivation path with hardened indices correctly', () => { + const result = parseDerivationPath("44'/0'/0'/0") + expect(result).toEqual([0x8000002c, 0x80000000, 0x80000000, 0]) + }) + + it('handles mixed hardened and non-hardened indices', () => { + const result = parseDerivationPath("44'/60/0'/0/0") + expect(result).toEqual([0x8000002c, 60, 0x80000000, 0, 0]) + }) + + it('interprets lowercase x as 0', () => { + const result = parseDerivationPath('44/x/0/0') + expect(result).toEqual([44, 0, 0, 0]) + }) + + it('interprets uppercase X as 0', () => { + const result = parseDerivationPath('44/X/0/0') + expect(result).toEqual([44, 0, 0, 0]) + }) + + it("interprets X' as hardened zero", () => { + const result = parseDerivationPath("44'/X'/0/0") + expect(result).toEqual([0x8000002c, 0x80000000, 0, 0]) + }) + + it("interprets x' as hardened zero", () => { + const result = parseDerivationPath("44'/x'/0/0") + expect(result).toEqual([0x8000002c, 0x80000000, 0, 0]) + }) + + it('handles a complex path with all features', () => { + const result = parseDerivationPath("44'/501'/X'/0'") + expect(result).toEqual([0x8000002c, 0x800001f5, 0x80000000, 0x80000000]) + }) + + it('returns an empty array for an empty path', () => { + const result = parseDerivationPath('') + expect(result).toEqual([]) + }) + + it('handles leading slash correctly', () => { + const result = parseDerivationPath('/44/0/0/0') + expect(result).toEqual([44, 0, 0, 0]) + }) + + it('throws error for invalid input', () => { + expect(() => parseDerivationPath('invalid/path')).toThrow( + 'Invalid part in derivation path: invalid', + ) + }) }) diff --git a/packages/sdk/src/__test__/unit/compareEIP7702Serialization.test.ts b/packages/sdk/src/__test__/unit/compareEIP7702Serialization.test.ts index b9cc117d..5ffe859d 100644 --- a/packages/sdk/src/__test__/unit/compareEIP7702Serialization.test.ts +++ b/packages/sdk/src/__test__/unit/compareEIP7702Serialization.test.ts @@ -2,402 +2,402 @@ import { Hash } from 'ox' import { parseEther, serializeTransaction, toHex } from 'viem' import { serializeEIP7702Transaction } from '../../ethereum' import type { - EIP7702AuthListTransactionRequest as EIP7702AuthListTransaction, - EIP7702AuthTransactionRequest as EIP7702AuthTransaction, + EIP7702AuthListTransactionRequest as EIP7702AuthListTransaction, + EIP7702AuthTransactionRequest as EIP7702AuthTransaction, } from '../../types' describe('EIP7702 Transaction Serialization Comparison', () => { - /** - * Simple minimal test case to identify differences - */ - test('minimal single authorization transaction', async () => { - // Create a minimal transaction for easier comparison - const tx: EIP7702AuthTransaction = { - type: 4, - chainId: 1, - nonce: 0, - maxPriorityFeePerGas: '0x1', - maxFeePerGas: '0x2', - gasLimit: '0x3', - to: '0x1111111111111111111111111111111111111111', - value: '0x0', - data: '0x', - accessList: [], - authorization: { - chainId: 1, - address: '0x2222222222222222222222222222222222222222', - nonce: 0, - yParity: 0, - r: '0x0000000000000000000000000000000000000000000000000000000000000001', - s: '0x0000000000000000000000000000000000000000000000000000000000000002', - }, - } - - // Convert to Viem's transaction format - const viemTx = { - type: 'eip7702', - chainId: 1, - nonce: 0, - maxPriorityFeePerGas: BigInt('0x1'), - maxFeePerGas: BigInt('0x2'), - gas: BigInt('0x3'), - to: '0x1111111111111111111111111111111111111111', - value: BigInt('0x0'), - data: '0x', - accessList: [], - authorizationList: [ - { - chainId: 1, - address: '0x2222222222222222222222222222222222222222', - nonce: 0, - signature: { - yParity: 0, - r: '0x0000000000000000000000000000000000000000000000000000000000000001', - s: '0x0000000000000000000000000000000000000000000000000000000000000002', - }, - }, - ], - } - - // Serialize using our implementation - const ourSerialized = serializeEIP7702Transaction(tx) - - // Serialize using Viem - const viemSerialized = serializeTransaction(viemTx as any) - - // Output raw serialized data for debugging - console.log('Our serialized (minimal):', ourSerialized) - console.log('Viem serialized (minimal):', viemSerialized) - - // Output serialized by byte - console.log( - 'Our bytes:', - Buffer.from(ourSerialized.slice(2), 'hex') - .toString('hex') - .match(/.{1,2}/g) - ?.join(' '), - ) - console.log( - 'Viem bytes:', - Buffer.from(viemSerialized.slice(2), 'hex') - .toString('hex') - .match(/.{1,2}/g) - ?.join(' '), - ) - - // Compare the serialized transactions - expect(ourSerialized).toEqual(viemSerialized) - }) - - /** - * Test case comparing our implementation of EIP-7702 transaction serialization with Viem's implementation - * - * This ensures that our serialization matches Viem's expected format, - * providing compatibility with external libraries and tools. - */ - test('single authorization transaction serialization matches Viem', async () => { - // Create a single authorization transaction with deterministic values - const tx: EIP7702AuthTransaction = { - type: 4, // Must be exactly 4 for auth transaction - chainId: 1, // Mainnet - nonce: 0, - maxPriorityFeePerGas: toHex(parseEther('0.000000001')), // 1 gwei - maxFeePerGas: toHex(parseEther('0.00000001')), // 10 gwei - gasLimit: toHex(BigInt(21000)), - to: '0x1111111111111111111111111111111111111111', // Simple address for testing - value: toHex(parseEther('1.0')), // 1 ETH - data: '0x', - accessList: [], - authorization: { - chainId: 1, - address: '0x2222222222222222222222222222222222222222', - nonce: 0, - yParity: 0, // Known signature values for deterministic test - r: '0x1111111111111111111111111111111111111111111111111111111111111111', - s: '0x2222222222222222222222222222222222222222222222222222222222222222', - }, - } - - // Convert to Viem's transaction format - const viemTx = { - type: 'eip7702', - chainId: 1, - nonce: 0, - maxPriorityFeePerGas: BigInt(parseEther('0.000000001')), - maxFeePerGas: BigInt(parseEther('0.00000001')), - gas: BigInt(21000), - to: '0x1111111111111111111111111111111111111111', - value: BigInt(parseEther('1.0')), - data: '0x', - accessList: [], - authorizationList: [ - { - chainId: 1, - address: '0x2222222222222222222222222222222222222222', - nonce: 0, - signature: { - yParity: 0, - r: '0x1111111111111111111111111111111111111111111111111111111111111111', - s: '0x2222222222222222222222222222222222222222222222222222222222222222', - }, - }, - ], - } - - // Serialize using our implementation - const ourSerialized = serializeEIP7702Transaction(tx) - - // Serialize using Viem - const viemSerialized = serializeTransaction(viemTx as any) - - // Compute hashes for comparison - const ourHash = `0x${Buffer.from(Hash.keccak256(Buffer.from(ourSerialized.slice(2), 'hex'))).toString('hex')}` - const viemHash = `0x${Buffer.from(Hash.keccak256(Buffer.from(viemSerialized.slice(2), 'hex'))).toString('hex')}` - - // Output for debugging - console.log('Our serialized:', ourSerialized) - console.log('Viem serialized:', viemSerialized) - console.log('Our hash:', ourHash) - console.log('Viem hash:', viemHash) - - // Compare the serialized transactions - expect(ourSerialized).toEqual(viemSerialized) - }) - - /** - * Test case for serializing an EIP-7702 authorization list transaction (type 5) - * comparing our implementation with Viem's implementation. - */ - test('authorization list transaction serialization matches Viem', async () => { - const tx: EIP7702AuthListTransaction = { - type: 5, // Must be exactly 5 for auth list transaction - chainId: 1, // Mainnet - nonce: 0, - maxPriorityFeePerGas: toHex(parseEther('0.000000001')), // 1 gwei - maxFeePerGas: toHex(parseEther('0.00000001')), // 10 gwei - gasLimit: toHex(BigInt(21000)), - to: '0x1111111111111111111111111111111111111111', // Simple address for testing - value: toHex(parseEther('1.0')), // 1 ETH - data: '0x', - accessList: [], - authorizationList: [ - { - chainId: 1, - address: '0x2222222222222222222222222222222222222222', - nonce: 0, - yParity: 0, // Known signature values for deterministic test - r: '0x1111111111111111111111111111111111111111111111111111111111111111', - s: '0x2222222222222222222222222222222222222222222222222222222222222222', - }, - { - chainId: 1, - address: '0x3333333333333333333333333333333333333333', - nonce: 0, - yParity: 1, // Different signature - r: '0x3333333333333333333333333333333333333333333333333333333333333333', - s: '0x4444444444444444444444444444444444444444444444444444444444444444', - }, - ], - } - - // Convert to Viem's transaction format - const viemTx = { - type: 'eip7702', - chainId: 1, - nonce: 0, - maxPriorityFeePerGas: BigInt(parseEther('0.000000001')), - maxFeePerGas: BigInt(parseEther('0.00000001')), - gas: BigInt(21000), - to: '0x1111111111111111111111111111111111111111', - value: BigInt(parseEther('1.0')), - data: '0x', - accessList: [], - authorizationList: [ - { - chainId: 1, - address: '0x2222222222222222222222222222222222222222', - nonce: 0, - signature: { - yParity: 0, - r: '0x1111111111111111111111111111111111111111111111111111111111111111', - s: '0x2222222222222222222222222222222222222222222222222222222222222222', - }, - }, - { - chainId: 1, - address: '0x3333333333333333333333333333333333333333', - nonce: 0, - signature: { - yParity: 1, - r: '0x3333333333333333333333333333333333333333333333333333333333333333', - s: '0x4444444444444444444444444444444444444444444444444444444444444444', - }, - }, - ], - } - - // Serialize using our implementation - const ourSerialized = serializeEIP7702Transaction(tx) - - // Serialize using Viem - const viemSerialized = serializeTransaction(viemTx as any) - - // Compute hashes for comparison - const ourHash = `0x${Buffer.from(Hash.keccak256(Buffer.from(ourSerialized.slice(2), 'hex'))).toString('hex')}` - const viemHash = `0x${Buffer.from(Hash.keccak256(Buffer.from(viemSerialized.slice(2), 'hex'))).toString('hex')}` - - // Output for debugging - console.log('Our serialized (auth list):', ourSerialized) - console.log('Viem serialized (auth list):', viemSerialized) - console.log('Our hash (auth list):', ourHash) - console.log('Viem hash (auth list):', viemHash) - - // Compare the serialized transactions - expect(ourSerialized).toEqual(viemSerialized) - }) - - /** - * Test case using realistic transaction values - */ - test('realistic transaction values match between implementations', async () => { - // Reference transaction with specific values - const tx: EIP7702AuthTransaction = { - type: 4, - chainId: 1, // Mainnet - nonce: 42, - maxPriorityFeePerGas: '0x3b9aca00', // 1 gwei (1,000,000,000 wei) - maxFeePerGas: '0x77359400', // 2 gwei (2,000,000,000 wei) - gasLimit: '0x5208', // 21000 - to: '0xd8da6bf26964af9d7eed9e03e53415d37aa96045', // vitalik.eth - value: '0x2386f26fc10000', // 0.01 ETH (10,000,000,000,000,000 wei) - data: '0x68656c6c6f', // "hello" in hex - accessList: [], - authorization: { - chainId: 1, - address: '0xc02aaa39b223fe8d0a0e5c4f27ead9083c756cc2', // WETH contract - nonce: 0, - // Standard test signature values - yParity: 0, - r: '0x0000000000000000000000000000000000000000000000000000000000000001', - s: '0x0000000000000000000000000000000000000000000000000000000000000002', - }, - } - - // Convert to Viem's transaction format - const viemTx = { - type: 'eip7702', - chainId: 1, - nonce: 42, - maxPriorityFeePerGas: BigInt('0x3b9aca00'), - maxFeePerGas: BigInt('0x77359400'), - gas: BigInt('0x5208'), - to: '0xd8da6bf26964af9d7eed9e03e53415d37aa96045', - value: BigInt('0x2386f26fc10000'), - data: '0x68656c6c6f', - accessList: [], - authorizationList: [ - { - chainId: 1, - address: '0xc02aaa39b223fe8d0a0e5c4f27ead9083c756cc2', - nonce: 0, - signature: { - yParity: 0, - r: '0x0000000000000000000000000000000000000000000000000000000000000001', - s: '0x0000000000000000000000000000000000000000000000000000000000000002', - }, - }, - ], - } - - // Serialize using our implementation - const ourSerialized = serializeEIP7702Transaction(tx) - - // Serialize using Viem - const viemSerialized = serializeTransaction(viemTx as any) - - // Compute hashes for comparison - const ourHash = `0x${Buffer.from(Hash.keccak256(Buffer.from(ourSerialized.slice(2), 'hex'))).toString('hex')}` - const viemHash = `0x${Buffer.from(Hash.keccak256(Buffer.from(viemSerialized.slice(2), 'hex'))).toString('hex')}` - - // Output for debugging - console.log('Our serialized (realistic):', ourSerialized) - console.log('Viem serialized (realistic):', viemSerialized) - console.log('Our hash (realistic):', ourHash) - console.log('Viem hash (realistic):', viemHash) - - // Compare the serialized transactions - expect(ourSerialized).toEqual(viemSerialized) - }) - - /** - * Test case with contract auth (when authorization has nonce) - */ - test('contract authorization transaction serialization matches Viem', async () => { - // Create a single authorization transaction with contract auth (with nonce) - const tx: EIP7702AuthTransaction = { - type: 4, - chainId: 1, - nonce: 0, - maxPriorityFeePerGas: '0x3b9aca00', - maxFeePerGas: '0x77359400', - gasLimit: '0x5208', - to: '0x1111111111111111111111111111111111111111', - value: '0x0', - data: '0x', - accessList: [], - authorization: { - chainId: 1, - address: '0x2222222222222222222222222222222222222222', - nonce: 5, // Contract auth has non-zero nonce - yParity: 0, - r: '0x1111111111111111111111111111111111111111111111111111111111111111', - s: '0x2222222222222222222222222222222222222222222222222222222222222222', - }, - } - - // Convert to Viem's transaction format - const viemTx = { - type: 'eip7702', - chainId: 1, - nonce: 0, - maxPriorityFeePerGas: BigInt('0x3b9aca00'), - maxFeePerGas: BigInt('0x77359400'), - gas: BigInt('0x5208'), - to: '0x1111111111111111111111111111111111111111', - value: BigInt('0x0'), - data: '0x', - accessList: [], - authorizationList: [ - { - chainId: 1, - address: '0x2222222222222222222222222222222222222222', - nonce: 5, - signature: { - yParity: 0, - r: '0x1111111111111111111111111111111111111111111111111111111111111111', - s: '0x2222222222222222222222222222222222222222222222222222222222222222', - }, - }, - ], - } - - // Serialize using our implementation - const ourSerialized = serializeEIP7702Transaction(tx) - - // Serialize using Viem - const viemSerialized = serializeTransaction(viemTx as any) - - // Compute hashes for comparison - const ourHash = `0x${Buffer.from(Hash.keccak256(Buffer.from(ourSerialized.slice(2), 'hex'))).toString('hex')}` - const viemHash = `0x${Buffer.from(Hash.keccak256(Buffer.from(viemSerialized.slice(2), 'hex'))).toString('hex')}` - - // Output for debugging - console.log('Our serialized (contract auth):', ourSerialized) - console.log('Viem serialized (contract auth):', viemSerialized) - console.log('Our hash (contract auth):', ourHash) - console.log('Viem hash (contract auth):', viemHash) - - // Compare the serialized transactions - expect(ourSerialized).toEqual(viemSerialized) - }) + /** + * Simple minimal test case to identify differences + */ + test('minimal single authorization transaction', async () => { + // Create a minimal transaction for easier comparison + const tx: EIP7702AuthTransaction = { + type: 4, + chainId: 1, + nonce: 0, + maxPriorityFeePerGas: '0x1', + maxFeePerGas: '0x2', + gasLimit: '0x3', + to: '0x1111111111111111111111111111111111111111', + value: '0x0', + data: '0x', + accessList: [], + authorization: { + chainId: 1, + address: '0x2222222222222222222222222222222222222222', + nonce: 0, + yParity: 0, + r: '0x0000000000000000000000000000000000000000000000000000000000000001', + s: '0x0000000000000000000000000000000000000000000000000000000000000002', + }, + } + + // Convert to Viem's transaction format + const viemTx = { + type: 'eip7702', + chainId: 1, + nonce: 0, + maxPriorityFeePerGas: BigInt('0x1'), + maxFeePerGas: BigInt('0x2'), + gas: BigInt('0x3'), + to: '0x1111111111111111111111111111111111111111', + value: BigInt('0x0'), + data: '0x', + accessList: [], + authorizationList: [ + { + chainId: 1, + address: '0x2222222222222222222222222222222222222222', + nonce: 0, + signature: { + yParity: 0, + r: '0x0000000000000000000000000000000000000000000000000000000000000001', + s: '0x0000000000000000000000000000000000000000000000000000000000000002', + }, + }, + ], + } + + // Serialize using our implementation + const ourSerialized = serializeEIP7702Transaction(tx) + + // Serialize using Viem + const viemSerialized = serializeTransaction(viemTx as any) + + // Output raw serialized data for debugging + console.log('Our serialized (minimal):', ourSerialized) + console.log('Viem serialized (minimal):', viemSerialized) + + // Output serialized by byte + console.log( + 'Our bytes:', + Buffer.from(ourSerialized.slice(2), 'hex') + .toString('hex') + .match(/.{1,2}/g) + ?.join(' '), + ) + console.log( + 'Viem bytes:', + Buffer.from(viemSerialized.slice(2), 'hex') + .toString('hex') + .match(/.{1,2}/g) + ?.join(' '), + ) + + // Compare the serialized transactions + expect(ourSerialized).toEqual(viemSerialized) + }) + + /** + * Test case comparing our implementation of EIP-7702 transaction serialization with Viem's implementation + * + * This ensures that our serialization matches Viem's expected format, + * providing compatibility with external libraries and tools. + */ + test('single authorization transaction serialization matches Viem', async () => { + // Create a single authorization transaction with deterministic values + const tx: EIP7702AuthTransaction = { + type: 4, // Must be exactly 4 for auth transaction + chainId: 1, // Mainnet + nonce: 0, + maxPriorityFeePerGas: toHex(parseEther('0.000000001')), // 1 gwei + maxFeePerGas: toHex(parseEther('0.00000001')), // 10 gwei + gasLimit: toHex(BigInt(21000)), + to: '0x1111111111111111111111111111111111111111', // Simple address for testing + value: toHex(parseEther('1.0')), // 1 ETH + data: '0x', + accessList: [], + authorization: { + chainId: 1, + address: '0x2222222222222222222222222222222222222222', + nonce: 0, + yParity: 0, // Known signature values for deterministic test + r: '0x1111111111111111111111111111111111111111111111111111111111111111', + s: '0x2222222222222222222222222222222222222222222222222222222222222222', + }, + } + + // Convert to Viem's transaction format + const viemTx = { + type: 'eip7702', + chainId: 1, + nonce: 0, + maxPriorityFeePerGas: BigInt(parseEther('0.000000001')), + maxFeePerGas: BigInt(parseEther('0.00000001')), + gas: BigInt(21000), + to: '0x1111111111111111111111111111111111111111', + value: BigInt(parseEther('1.0')), + data: '0x', + accessList: [], + authorizationList: [ + { + chainId: 1, + address: '0x2222222222222222222222222222222222222222', + nonce: 0, + signature: { + yParity: 0, + r: '0x1111111111111111111111111111111111111111111111111111111111111111', + s: '0x2222222222222222222222222222222222222222222222222222222222222222', + }, + }, + ], + } + + // Serialize using our implementation + const ourSerialized = serializeEIP7702Transaction(tx) + + // Serialize using Viem + const viemSerialized = serializeTransaction(viemTx as any) + + // Compute hashes for comparison + const ourHash = `0x${Buffer.from(Hash.keccak256(Buffer.from(ourSerialized.slice(2), 'hex'))).toString('hex')}` + const viemHash = `0x${Buffer.from(Hash.keccak256(Buffer.from(viemSerialized.slice(2), 'hex'))).toString('hex')}` + + // Output for debugging + console.log('Our serialized:', ourSerialized) + console.log('Viem serialized:', viemSerialized) + console.log('Our hash:', ourHash) + console.log('Viem hash:', viemHash) + + // Compare the serialized transactions + expect(ourSerialized).toEqual(viemSerialized) + }) + + /** + * Test case for serializing an EIP-7702 authorization list transaction (type 5) + * comparing our implementation with Viem's implementation. + */ + test('authorization list transaction serialization matches Viem', async () => { + const tx: EIP7702AuthListTransaction = { + type: 5, // Must be exactly 5 for auth list transaction + chainId: 1, // Mainnet + nonce: 0, + maxPriorityFeePerGas: toHex(parseEther('0.000000001')), // 1 gwei + maxFeePerGas: toHex(parseEther('0.00000001')), // 10 gwei + gasLimit: toHex(BigInt(21000)), + to: '0x1111111111111111111111111111111111111111', // Simple address for testing + value: toHex(parseEther('1.0')), // 1 ETH + data: '0x', + accessList: [], + authorizationList: [ + { + chainId: 1, + address: '0x2222222222222222222222222222222222222222', + nonce: 0, + yParity: 0, // Known signature values for deterministic test + r: '0x1111111111111111111111111111111111111111111111111111111111111111', + s: '0x2222222222222222222222222222222222222222222222222222222222222222', + }, + { + chainId: 1, + address: '0x3333333333333333333333333333333333333333', + nonce: 0, + yParity: 1, // Different signature + r: '0x3333333333333333333333333333333333333333333333333333333333333333', + s: '0x4444444444444444444444444444444444444444444444444444444444444444', + }, + ], + } + + // Convert to Viem's transaction format + const viemTx = { + type: 'eip7702', + chainId: 1, + nonce: 0, + maxPriorityFeePerGas: BigInt(parseEther('0.000000001')), + maxFeePerGas: BigInt(parseEther('0.00000001')), + gas: BigInt(21000), + to: '0x1111111111111111111111111111111111111111', + value: BigInt(parseEther('1.0')), + data: '0x', + accessList: [], + authorizationList: [ + { + chainId: 1, + address: '0x2222222222222222222222222222222222222222', + nonce: 0, + signature: { + yParity: 0, + r: '0x1111111111111111111111111111111111111111111111111111111111111111', + s: '0x2222222222222222222222222222222222222222222222222222222222222222', + }, + }, + { + chainId: 1, + address: '0x3333333333333333333333333333333333333333', + nonce: 0, + signature: { + yParity: 1, + r: '0x3333333333333333333333333333333333333333333333333333333333333333', + s: '0x4444444444444444444444444444444444444444444444444444444444444444', + }, + }, + ], + } + + // Serialize using our implementation + const ourSerialized = serializeEIP7702Transaction(tx) + + // Serialize using Viem + const viemSerialized = serializeTransaction(viemTx as any) + + // Compute hashes for comparison + const ourHash = `0x${Buffer.from(Hash.keccak256(Buffer.from(ourSerialized.slice(2), 'hex'))).toString('hex')}` + const viemHash = `0x${Buffer.from(Hash.keccak256(Buffer.from(viemSerialized.slice(2), 'hex'))).toString('hex')}` + + // Output for debugging + console.log('Our serialized (auth list):', ourSerialized) + console.log('Viem serialized (auth list):', viemSerialized) + console.log('Our hash (auth list):', ourHash) + console.log('Viem hash (auth list):', viemHash) + + // Compare the serialized transactions + expect(ourSerialized).toEqual(viemSerialized) + }) + + /** + * Test case using realistic transaction values + */ + test('realistic transaction values match between implementations', async () => { + // Reference transaction with specific values + const tx: EIP7702AuthTransaction = { + type: 4, + chainId: 1, // Mainnet + nonce: 42, + maxPriorityFeePerGas: '0x3b9aca00', // 1 gwei (1,000,000,000 wei) + maxFeePerGas: '0x77359400', // 2 gwei (2,000,000,000 wei) + gasLimit: '0x5208', // 21000 + to: '0xd8da6bf26964af9d7eed9e03e53415d37aa96045', // vitalik.eth + value: '0x2386f26fc10000', // 0.01 ETH (10,000,000,000,000,000 wei) + data: '0x68656c6c6f', // "hello" in hex + accessList: [], + authorization: { + chainId: 1, + address: '0xc02aaa39b223fe8d0a0e5c4f27ead9083c756cc2', // WETH contract + nonce: 0, + // Standard test signature values + yParity: 0, + r: '0x0000000000000000000000000000000000000000000000000000000000000001', + s: '0x0000000000000000000000000000000000000000000000000000000000000002', + }, + } + + // Convert to Viem's transaction format + const viemTx = { + type: 'eip7702', + chainId: 1, + nonce: 42, + maxPriorityFeePerGas: BigInt('0x3b9aca00'), + maxFeePerGas: BigInt('0x77359400'), + gas: BigInt('0x5208'), + to: '0xd8da6bf26964af9d7eed9e03e53415d37aa96045', + value: BigInt('0x2386f26fc10000'), + data: '0x68656c6c6f', + accessList: [], + authorizationList: [ + { + chainId: 1, + address: '0xc02aaa39b223fe8d0a0e5c4f27ead9083c756cc2', + nonce: 0, + signature: { + yParity: 0, + r: '0x0000000000000000000000000000000000000000000000000000000000000001', + s: '0x0000000000000000000000000000000000000000000000000000000000000002', + }, + }, + ], + } + + // Serialize using our implementation + const ourSerialized = serializeEIP7702Transaction(tx) + + // Serialize using Viem + const viemSerialized = serializeTransaction(viemTx as any) + + // Compute hashes for comparison + const ourHash = `0x${Buffer.from(Hash.keccak256(Buffer.from(ourSerialized.slice(2), 'hex'))).toString('hex')}` + const viemHash = `0x${Buffer.from(Hash.keccak256(Buffer.from(viemSerialized.slice(2), 'hex'))).toString('hex')}` + + // Output for debugging + console.log('Our serialized (realistic):', ourSerialized) + console.log('Viem serialized (realistic):', viemSerialized) + console.log('Our hash (realistic):', ourHash) + console.log('Viem hash (realistic):', viemHash) + + // Compare the serialized transactions + expect(ourSerialized).toEqual(viemSerialized) + }) + + /** + * Test case with contract auth (when authorization has nonce) + */ + test('contract authorization transaction serialization matches Viem', async () => { + // Create a single authorization transaction with contract auth (with nonce) + const tx: EIP7702AuthTransaction = { + type: 4, + chainId: 1, + nonce: 0, + maxPriorityFeePerGas: '0x3b9aca00', + maxFeePerGas: '0x77359400', + gasLimit: '0x5208', + to: '0x1111111111111111111111111111111111111111', + value: '0x0', + data: '0x', + accessList: [], + authorization: { + chainId: 1, + address: '0x2222222222222222222222222222222222222222', + nonce: 5, // Contract auth has non-zero nonce + yParity: 0, + r: '0x1111111111111111111111111111111111111111111111111111111111111111', + s: '0x2222222222222222222222222222222222222222222222222222222222222222', + }, + } + + // Convert to Viem's transaction format + const viemTx = { + type: 'eip7702', + chainId: 1, + nonce: 0, + maxPriorityFeePerGas: BigInt('0x3b9aca00'), + maxFeePerGas: BigInt('0x77359400'), + gas: BigInt('0x5208'), + to: '0x1111111111111111111111111111111111111111', + value: BigInt('0x0'), + data: '0x', + accessList: [], + authorizationList: [ + { + chainId: 1, + address: '0x2222222222222222222222222222222222222222', + nonce: 5, + signature: { + yParity: 0, + r: '0x1111111111111111111111111111111111111111111111111111111111111111', + s: '0x2222222222222222222222222222222222222222222222222222222222222222', + }, + }, + ], + } + + // Serialize using our implementation + const ourSerialized = serializeEIP7702Transaction(tx) + + // Serialize using Viem + const viemSerialized = serializeTransaction(viemTx as any) + + // Compute hashes for comparison + const ourHash = `0x${Buffer.from(Hash.keccak256(Buffer.from(ourSerialized.slice(2), 'hex'))).toString('hex')}` + const viemHash = `0x${Buffer.from(Hash.keccak256(Buffer.from(viemSerialized.slice(2), 'hex'))).toString('hex')}` + + // Output for debugging + console.log('Our serialized (contract auth):', ourSerialized) + console.log('Viem serialized (contract auth):', viemSerialized) + console.log('Our hash (contract auth):', ourHash) + console.log('Viem hash (contract auth):', viemHash) + + // Compare the serialized transactions + expect(ourSerialized).toEqual(viemSerialized) + }) }) diff --git a/packages/sdk/src/__test__/unit/decoders.test.ts b/packages/sdk/src/__test__/unit/decoders.test.ts index 1fc8adaa..64080439 100644 --- a/packages/sdk/src/__test__/unit/decoders.test.ts +++ b/packages/sdk/src/__test__/unit/decoders.test.ts @@ -1,77 +1,77 @@ import { - decodeConnectResponse, - decodeFetchEncData, - decodeGetAddressesResponse, - decodeGetKvRecordsResponse, - decodeSignResponse, + decodeConnectResponse, + decodeFetchEncData, + decodeGetAddressesResponse, + decodeGetKvRecordsResponse, + decodeSignResponse, } from '../../functions' import type { DecodeSignResponseParams } from '../../types' import { - clientKeyPair, - connectDecoderData, - decoderTestsFwConstants, - fetchEncryptedDataDecoderData, - fetchEncryptedDataRequest, - getAddressesDecoderData, - getAddressesFlag, - getKvRecordsDecoderData, - signBitcoinDecoderData, - signBitcoinRequest, - signGenericDecoderData, - signGenericRequest, + clientKeyPair, + connectDecoderData, + decoderTestsFwConstants, + fetchEncryptedDataDecoderData, + fetchEncryptedDataRequest, + getAddressesDecoderData, + getAddressesFlag, + getKvRecordsDecoderData, + signBitcoinDecoderData, + signBitcoinRequest, + signGenericDecoderData, + signGenericRequest, } from './__mocks__/decoderData' describe('decoders', () => { - test('connect', () => { - expect( - decodeConnectResponse(connectDecoderData, clientKeyPair), - ).toMatchSnapshot() - }) + test('connect', () => { + expect( + decodeConnectResponse(connectDecoderData, clientKeyPair), + ).toMatchSnapshot() + }) - test('getAddresses', () => { - expect( - decodeGetAddressesResponse(getAddressesDecoderData, getAddressesFlag), - ).toMatchSnapshot() - }) + test('getAddresses', () => { + expect( + decodeGetAddressesResponse(getAddressesDecoderData, getAddressesFlag), + ).toMatchSnapshot() + }) - test('sign - bitcoin', () => { - const params: DecodeSignResponseParams = { - data: signBitcoinDecoderData, - request: signBitcoinRequest, - isGeneric: false, - currency: 'BTC', - } - expect(decodeSignResponse(params)).toMatchSnapshot() - }) + test('sign - bitcoin', () => { + const params: DecodeSignResponseParams = { + data: signBitcoinDecoderData, + request: signBitcoinRequest, + isGeneric: false, + currency: 'BTC', + } + expect(decodeSignResponse(params)).toMatchSnapshot() + }) - test('sign - generic', () => { - const params: DecodeSignResponseParams = { - data: signGenericDecoderData, - request: signGenericRequest, - isGeneric: true, - } - expect(decodeSignResponse(params)).toMatchSnapshot() - }) + test('sign - generic', () => { + const params: DecodeSignResponseParams = { + data: signGenericDecoderData, + request: signGenericRequest, + isGeneric: true, + } + expect(decodeSignResponse(params)).toMatchSnapshot() + }) - test('getKvRecords', () => { - expect( - decodeGetKvRecordsResponse( - getKvRecordsDecoderData, - decoderTestsFwConstants, - ), - ).toMatchSnapshot() - }) + test('getKvRecords', () => { + expect( + decodeGetKvRecordsResponse( + getKvRecordsDecoderData, + decoderTestsFwConstants, + ), + ).toMatchSnapshot() + }) - test('fetchEncryptedData', () => { - // This test is different than the others because one part of the data is - // randomly generated (UUID) before the response is returned. - // We will just zero it out for testing purposes. - const decoded = decodeFetchEncData({ - data: fetchEncryptedDataDecoderData, - ...fetchEncryptedDataRequest, - }) - const decodedDerp = JSON.parse(decoded.toString()) - decodedDerp.uuid = '00000000-0000-0000-0000-000000000000' - expect(Buffer.from(JSON.stringify(decodedDerp))).toMatchSnapshot() - }) + test('fetchEncryptedData', () => { + // This test is different than the others because one part of the data is + // randomly generated (UUID) before the response is returned. + // We will just zero it out for testing purposes. + const decoded = decodeFetchEncData({ + data: fetchEncryptedDataDecoderData, + ...fetchEncryptedDataRequest, + }) + const decodedDerp = JSON.parse(decoded.toString()) + decodedDerp.uuid = '00000000-0000-0000-0000-000000000000' + expect(Buffer.from(JSON.stringify(decodedDerp))).toMatchSnapshot() + }) }) diff --git a/packages/sdk/src/__test__/unit/eip7702.test.ts b/packages/sdk/src/__test__/unit/eip7702.test.ts index c5b63418..3ffb73b9 100644 --- a/packages/sdk/src/__test__/unit/eip7702.test.ts +++ b/packages/sdk/src/__test__/unit/eip7702.test.ts @@ -2,171 +2,171 @@ import { Hash } from 'ox' import { parseEther, toHex } from 'viem' import { serializeEIP7702Transaction } from '../../ethereum' import type { - EIP7702AuthListTransactionRequest, - EIP7702AuthTransactionRequest, + EIP7702AuthListTransactionRequest, + EIP7702AuthTransactionRequest, } from '../../types' describe('EIP-7702 Transaction Serialization', () => { - /** - * Test case for serializing an EIP-7702 authorization transaction (type 4). - * - * This test creates a deterministic transaction with known values and - * verifies that the serialized result matches the expected hash. - * - * EIP-7702 spec requires exact field ordering: - * rlp([chain_id, nonce, max_priority_fee_per_gas, max_fee_per_gas, gas_limit, - * destination, value, data, access_list, authorization_list, - * signature_y_parity, signature_r, signature_s]) - */ - test('single authorization transaction serialization', () => { - // Create a single authorization transaction with deterministic values - const tx: EIP7702AuthTransactionRequest = { - type: 4, // Must be exactly 4 for auth transaction - chainId: 1, // Mainnet - nonce: 0, - maxPriorityFeePerGas: toHex(parseEther('0.000000001')), // 1 gwei - maxFeePerGas: toHex(parseEther('0.00000001')), // 10 gwei - gasLimit: toHex(BigInt(21000)), - to: '0x1111111111111111111111111111111111111111', // Simple address for testing - value: toHex(parseEther('1.0')), // 1 ETH - data: '0x', - accessList: [], - authorization: { - chainId: 1, - address: '0x2222222222222222222222222222222222222222', - nonce: 0, - yParity: 0, // Valid ECDSA signature values - r: '0xbfa71d3b2c96bd4f573ee8e2b0da387999eb521b8c09f68499f4ed528cbeeb40', - s: '0x171bb6415a3ff1207ddf5314aa05ffc168bd82f3abd0c8a7c91ef22ff58c4698', - }, - } - - // Serialize the transaction - const serialized = serializeEIP7702Transaction(tx) - - // Compute the keccak256 hash of the serialized transaction - const txHash = `0x${Buffer.from(Hash.keccak256(Buffer.from(serialized.slice(2), 'hex'))).toString('hex')}` - - // Store the serialized value for debugging - console.log('Serialized transaction:', serialized) - console.log('Transaction hash:', txHash) - - // Store the expected serialized form (can be replaced with actual expected value) - // For now we'll assert that serialization produces consistent results - const initialRun = serializeEIP7702Transaction(tx) - expect(serialized).toEqual(initialRun) - - // Ensure the serialized transaction starts with the transaction type (0x04) - expect(serialized.startsWith('0x04')).toBe(true) - }) - - /** - * Test case for serializing an EIP-7702 authorization list transaction (type 5). - * - * This test creates a transaction with multiple authorizations and verifies - * that the serialized result is consistent and properly formatted. - */ - test('authorization list transaction serialization', () => { - const tx: EIP7702AuthListTransactionRequest = { - type: 5, // Must be exactly 5 for auth list transaction - chainId: 1, // Mainnet - nonce: 0, - maxPriorityFeePerGas: toHex(parseEther('0.000000001')), // 1 gwei - maxFeePerGas: toHex(parseEther('0.00000001')), // 10 gwei - gasLimit: toHex(BigInt(21000)), - to: '0x1111111111111111111111111111111111111111', // Simple address for testing - value: toHex(parseEther('1.0')), // 1 ETH - data: '0x', - accessList: [], - authorizationList: [ - { - chainId: 1, - address: '0x2222222222222222222222222222222222222222', - nonce: 0, - yParity: 0, // Valid ECDSA signature values - r: '0xbfa71d3b2c96bd4f573ee8e2b0da387999eb521b8c09f68499f4ed528cbeeb40', - s: '0x171bb6415a3ff1207ddf5314aa05ffc168bd82f3abd0c8a7c91ef22ff58c4698', - }, - { - chainId: 1, - address: '0x3333333333333333333333333333333333333333', - nonce: 0, - yParity: 0, // Valid ECDSA signature values - r: '0x888acc1e501f052175c59fa2167699341709bd72f9809182bdf580c1c3bf6cf', - s: '0x7e03cfbc948cf6b8c4cd946d511b3ea1c4c64e8c70e0259573183ef22d565034', - }, - ], - } - - // Serialize the transaction - const serialized = serializeEIP7702Transaction(tx) - - // Compute the keccak256 hash of the serialized transaction - const txHash = `0x${Buffer.from(Hash.keccak256(Buffer.from(serialized.slice(2), 'hex'))).toString('hex')}` - - // Store the serialized value for debugging - console.log('Serialized auth list transaction:', serialized) - console.log('Transaction hash:', txHash) - - // Store the expected serialized form (can be replaced with actual expected value) - // For now we'll assert that serialization produces consistent results - const initialRun = serializeEIP7702Transaction(tx) - expect(serialized).toEqual(initialRun) - - // Ensure the serialized transaction starts with the transaction type (0x05) - expect(serialized.startsWith('0x04')).toBe(true) - }) - - /** - * Test case for comparing serialization against a known good hash. - * - * This test uses a transaction with specific values that should produce - * a known hash when serialized correctly. - * - * Note: The expected hash used here is based on a reference implementation - * of the EIP-7702 serialization process. - */ - test('serialization matches known good hash', () => { - // Reference transaction with specific values - const tx: EIP7702AuthTransactionRequest = { - type: 4, // Must be exactly 4 for auth transaction - chainId: 1, // Mainnet - nonce: 42, - maxPriorityFeePerGas: '0x3b9aca00', // 1 gwei (1,000,000,000 wei) - maxFeePerGas: '0x77359400', // 2 gwei (2,000,000,000 wei) - gasLimit: '0x5208', // 21000 - to: '0xd8da6bf26964af9d7eed9e03e53415d37aa96045', // vitalik.eth - value: '0x2386f26fc10000', // 0.01 ETH (10,000,000,000,000,000 wei) - data: '0x68656c6c6f', // "hello" in hex - accessList: [], - authorization: { - chainId: 1, - address: '0xc02aaa39b223fe8d0a0e5c4f27ead9083c756cc2', // WETH contract - nonce: 0, - // Valid ECDSA signature values for chainId=1, address=WETH, nonce=0 - yParity: 1, - r: '0x7afecf0fa2f0c5f3cee3bf477dc4b0787afaecf5c8b0e2f7ec6c47c893bb06f0', - s: '0x2e019bd0bb7b96a5beb6f92c63bc7d72f19f6b960d50f8b1c0c4f6bc690e95f4', - }, - } - - // Serialize the transaction - const serialized = serializeEIP7702Transaction(tx) - - // Compute the keccak256 hash of the serialized transaction - const txHash = `0x${Buffer.from(Hash.keccak256(Buffer.from(serialized.slice(2), 'hex'))).toString('hex')}` - - console.log('Reference serialized transaction:', serialized) - console.log('Reference transaction hash:', txHash) - - // The expected hash would be provided by a reference implementation - // For now,. I will assert consistency across multiple serializations - const secondRun = serializeEIP7702Transaction(tx) - expect(serialized).toEqual(secondRun) - - // Store the hash for future reference - this can be replaced with a - // verified correct hash once available from a reference implementation - const knownGoodHash = txHash - expect(txHash).toEqual(knownGoodHash) - }) + /** + * Test case for serializing an EIP-7702 authorization transaction (type 4). + * + * This test creates a deterministic transaction with known values and + * verifies that the serialized result matches the expected hash. + * + * EIP-7702 spec requires exact field ordering: + * rlp([chain_id, nonce, max_priority_fee_per_gas, max_fee_per_gas, gas_limit, + * destination, value, data, access_list, authorization_list, + * signature_y_parity, signature_r, signature_s]) + */ + test('single authorization transaction serialization', () => { + // Create a single authorization transaction with deterministic values + const tx: EIP7702AuthTransactionRequest = { + type: 4, // Must be exactly 4 for auth transaction + chainId: 1, // Mainnet + nonce: 0, + maxPriorityFeePerGas: toHex(parseEther('0.000000001')), // 1 gwei + maxFeePerGas: toHex(parseEther('0.00000001')), // 10 gwei + gasLimit: toHex(BigInt(21000)), + to: '0x1111111111111111111111111111111111111111', // Simple address for testing + value: toHex(parseEther('1.0')), // 1 ETH + data: '0x', + accessList: [], + authorization: { + chainId: 1, + address: '0x2222222222222222222222222222222222222222', + nonce: 0, + yParity: 0, // Valid ECDSA signature values + r: '0xbfa71d3b2c96bd4f573ee8e2b0da387999eb521b8c09f68499f4ed528cbeeb40', + s: '0x171bb6415a3ff1207ddf5314aa05ffc168bd82f3abd0c8a7c91ef22ff58c4698', + }, + } + + // Serialize the transaction + const serialized = serializeEIP7702Transaction(tx) + + // Compute the keccak256 hash of the serialized transaction + const txHash = `0x${Buffer.from(Hash.keccak256(Buffer.from(serialized.slice(2), 'hex'))).toString('hex')}` + + // Store the serialized value for debugging + console.log('Serialized transaction:', serialized) + console.log('Transaction hash:', txHash) + + // Store the expected serialized form (can be replaced with actual expected value) + // For now we'll assert that serialization produces consistent results + const initialRun = serializeEIP7702Transaction(tx) + expect(serialized).toEqual(initialRun) + + // Ensure the serialized transaction starts with the transaction type (0x04) + expect(serialized.startsWith('0x04')).toBe(true) + }) + + /** + * Test case for serializing an EIP-7702 authorization list transaction (type 5). + * + * This test creates a transaction with multiple authorizations and verifies + * that the serialized result is consistent and properly formatted. + */ + test('authorization list transaction serialization', () => { + const tx: EIP7702AuthListTransactionRequest = { + type: 5, // Must be exactly 5 for auth list transaction + chainId: 1, // Mainnet + nonce: 0, + maxPriorityFeePerGas: toHex(parseEther('0.000000001')), // 1 gwei + maxFeePerGas: toHex(parseEther('0.00000001')), // 10 gwei + gasLimit: toHex(BigInt(21000)), + to: '0x1111111111111111111111111111111111111111', // Simple address for testing + value: toHex(parseEther('1.0')), // 1 ETH + data: '0x', + accessList: [], + authorizationList: [ + { + chainId: 1, + address: '0x2222222222222222222222222222222222222222', + nonce: 0, + yParity: 0, // Valid ECDSA signature values + r: '0xbfa71d3b2c96bd4f573ee8e2b0da387999eb521b8c09f68499f4ed528cbeeb40', + s: '0x171bb6415a3ff1207ddf5314aa05ffc168bd82f3abd0c8a7c91ef22ff58c4698', + }, + { + chainId: 1, + address: '0x3333333333333333333333333333333333333333', + nonce: 0, + yParity: 0, // Valid ECDSA signature values + r: '0x888acc1e501f052175c59fa2167699341709bd72f9809182bdf580c1c3bf6cf', + s: '0x7e03cfbc948cf6b8c4cd946d511b3ea1c4c64e8c70e0259573183ef22d565034', + }, + ], + } + + // Serialize the transaction + const serialized = serializeEIP7702Transaction(tx) + + // Compute the keccak256 hash of the serialized transaction + const txHash = `0x${Buffer.from(Hash.keccak256(Buffer.from(serialized.slice(2), 'hex'))).toString('hex')}` + + // Store the serialized value for debugging + console.log('Serialized auth list transaction:', serialized) + console.log('Transaction hash:', txHash) + + // Store the expected serialized form (can be replaced with actual expected value) + // For now we'll assert that serialization produces consistent results + const initialRun = serializeEIP7702Transaction(tx) + expect(serialized).toEqual(initialRun) + + // Ensure the serialized transaction starts with the transaction type (0x05) + expect(serialized.startsWith('0x04')).toBe(true) + }) + + /** + * Test case for comparing serialization against a known good hash. + * + * This test uses a transaction with specific values that should produce + * a known hash when serialized correctly. + * + * Note: The expected hash used here is based on a reference implementation + * of the EIP-7702 serialization process. + */ + test('serialization matches known good hash', () => { + // Reference transaction with specific values + const tx: EIP7702AuthTransactionRequest = { + type: 4, // Must be exactly 4 for auth transaction + chainId: 1, // Mainnet + nonce: 42, + maxPriorityFeePerGas: '0x3b9aca00', // 1 gwei (1,000,000,000 wei) + maxFeePerGas: '0x77359400', // 2 gwei (2,000,000,000 wei) + gasLimit: '0x5208', // 21000 + to: '0xd8da6bf26964af9d7eed9e03e53415d37aa96045', // vitalik.eth + value: '0x2386f26fc10000', // 0.01 ETH (10,000,000,000,000,000 wei) + data: '0x68656c6c6f', // "hello" in hex + accessList: [], + authorization: { + chainId: 1, + address: '0xc02aaa39b223fe8d0a0e5c4f27ead9083c756cc2', // WETH contract + nonce: 0, + // Valid ECDSA signature values for chainId=1, address=WETH, nonce=0 + yParity: 1, + r: '0x7afecf0fa2f0c5f3cee3bf477dc4b0787afaecf5c8b0e2f7ec6c47c893bb06f0', + s: '0x2e019bd0bb7b96a5beb6f92c63bc7d72f19f6b960d50f8b1c0c4f6bc690e95f4', + }, + } + + // Serialize the transaction + const serialized = serializeEIP7702Transaction(tx) + + // Compute the keccak256 hash of the serialized transaction + const txHash = `0x${Buffer.from(Hash.keccak256(Buffer.from(serialized.slice(2), 'hex'))).toString('hex')}` + + console.log('Reference serialized transaction:', serialized) + console.log('Reference transaction hash:', txHash) + + // The expected hash would be provided by a reference implementation + // For now,. I will assert consistency across multiple serializations + const secondRun = serializeEIP7702Transaction(tx) + expect(serialized).toEqual(secondRun) + + // Store the hash for future reference - this can be replaced with a + // verified correct hash once available from a reference implementation + const knownGoodHash = txHash + expect(txHash).toEqual(knownGoodHash) + }) }) diff --git a/packages/sdk/src/__test__/unit/encoders.test.ts b/packages/sdk/src/__test__/unit/encoders.test.ts index 20843f9f..abd0e623 100644 --- a/packages/sdk/src/__test__/unit/encoders.test.ts +++ b/packages/sdk/src/__test__/unit/encoders.test.ts @@ -1,125 +1,125 @@ import { EXTERNAL } from '../../constants' import { - encodeAddKvRecordsRequest, - encodeGetAddressesRequest, - encodeGetKvRecordsRequest, - encodePairRequest, - encodeRemoveKvRecordsRequest, - encodeSignRequest, + encodeAddKvRecordsRequest, + encodeGetAddressesRequest, + encodeGetKvRecordsRequest, + encodePairRequest, + encodeRemoveKvRecordsRequest, + encodeSignRequest, } from '../../functions' import { buildTransaction } from '../../shared/functions' import { getP256KeyPair } from '../../util' import { - buildFirmwareConstants, - buildGetAddressesObject, - buildSignObject, - buildWallet, - getFwVersionsList, + buildFirmwareConstants, + buildGetAddressesObject, + buildSignObject, + buildWallet, + getFwVersionsList, } from '../utils/builders' describe('encoders', () => { - let mockRandom: any + let mockRandom: any - beforeAll(() => { - mockRandom = vi.spyOn(globalThis.Math, 'random').mockReturnValue(0.1) - }) + beforeAll(() => { + mockRandom = vi.spyOn(globalThis.Math, 'random').mockReturnValue(0.1) + }) - afterAll(() => { - mockRandom.mockRestore() - }) + afterAll(() => { + mockRandom.mockRestore() + }) - describe('pair', () => { - test('pair encoder', () => { - const privKey = Buffer.alloc(32, '1') - expect(privKey.toString()).toMatchSnapshot() - const key = getP256KeyPair(privKey) - const payload = encodePairRequest({ - key, - pairingSecret: 'testtest', - appName: 'testtest', - }) - const payloadAsString = payload.toString('hex') - expect(payloadAsString).toMatchSnapshot() - }) - }) + describe('pair', () => { + test('pair encoder', () => { + const privKey = Buffer.alloc(32, '1') + expect(privKey.toString()).toMatchSnapshot() + const key = getP256KeyPair(privKey) + const payload = encodePairRequest({ + key, + pairingSecret: 'testtest', + appName: 'testtest', + }) + const payloadAsString = payload.toString('hex') + expect(payloadAsString).toMatchSnapshot() + }) + }) - describe('getAddresses', () => { - test('encodeGetAddressesRequest with default flag', () => { - const payload = encodeGetAddressesRequest(buildGetAddressesObject()) - const payloadAsString = payload.toString('hex') - expect(payloadAsString).toMatchSnapshot() - }) + describe('getAddresses', () => { + test('encodeGetAddressesRequest with default flag', () => { + const payload = encodeGetAddressesRequest(buildGetAddressesObject()) + const payloadAsString = payload.toString('hex') + expect(payloadAsString).toMatchSnapshot() + }) - test('encodeGetAddressesRequest with ED25519_PUB', () => { - const mockObject = buildGetAddressesObject({ - flag: EXTERNAL.GET_ADDR_FLAGS.ED25519_PUB, - }) - const payload = encodeGetAddressesRequest(mockObject) - const payloadAsString = payload.toString('hex') - expect(payloadAsString).toMatchSnapshot() - }) + test('encodeGetAddressesRequest with ED25519_PUB', () => { + const mockObject = buildGetAddressesObject({ + flag: EXTERNAL.GET_ADDR_FLAGS.ED25519_PUB, + }) + const payload = encodeGetAddressesRequest(mockObject) + const payloadAsString = payload.toString('hex') + expect(payloadAsString).toMatchSnapshot() + }) - test('encodeGetAddressesRequest with SECP256K1_PUB', () => { - const mockObject = buildGetAddressesObject({ - flag: EXTERNAL.GET_ADDR_FLAGS.SECP256K1_PUB, - }) - const payload = encodeGetAddressesRequest(mockObject) - const payloadAsString = payload.toString('hex') - expect(payloadAsString).toMatchSnapshot() - }) - }) + test('encodeGetAddressesRequest with SECP256K1_PUB', () => { + const mockObject = buildGetAddressesObject({ + flag: EXTERNAL.GET_ADDR_FLAGS.SECP256K1_PUB, + }) + const payload = encodeGetAddressesRequest(mockObject) + const payloadAsString = payload.toString('hex') + expect(payloadAsString).toMatchSnapshot() + }) + }) - describe('sign', () => { - test.each(getFwVersionsList())( - 'should test sign encoder with firmware v%d.%d.%d', - (major, minor, patch) => { - const fwVersion = Buffer.from([patch, minor, major]) - const txObj = buildSignObject(fwVersion) - const tx = buildTransaction(txObj) - const req = { - ...txObj, - ...tx, - wallet: buildWallet(), - } - const { payload } = encodeSignRequest(req) - const payloadAsString = payload.toString('hex') - expect(payloadAsString).toMatchSnapshot() - }, - ) - }) + describe('sign', () => { + test.each(getFwVersionsList())( + 'should test sign encoder with firmware v%d.%d.%d', + (major, minor, patch) => { + const fwVersion = Buffer.from([patch, minor, major]) + const txObj = buildSignObject(fwVersion) + const tx = buildTransaction(txObj) + const req = { + ...txObj, + ...tx, + wallet: buildWallet(), + } + const { payload } = encodeSignRequest(req) + const payloadAsString = payload.toString('hex') + expect(payloadAsString).toMatchSnapshot() + }, + ) + }) - describe('KvRecords', () => { - test('getKvRecords', () => { - const mockObject = { type: 0, n: 1, start: 0 } - const payload = encodeGetKvRecordsRequest(mockObject) - const payloadAsString = payload.toString('hex') - expect(payloadAsString).toMatchSnapshot() - }) + describe('KvRecords', () => { + test('getKvRecords', () => { + const mockObject = { type: 0, n: 1, start: 0 } + const payload = encodeGetKvRecordsRequest(mockObject) + const payloadAsString = payload.toString('hex') + expect(payloadAsString).toMatchSnapshot() + }) - test('addKvRecords', () => { - const fwConstants = buildFirmwareConstants() - const mockObject = { - type: 0, - records: { key: 'value' }, - caseSensitive: false, - fwConstants, - } - const payload = encodeAddKvRecordsRequest(mockObject) - const payloadAsString = payload.toString('hex') - expect(payloadAsString).toMatchSnapshot() - }) + test('addKvRecords', () => { + const fwConstants = buildFirmwareConstants() + const mockObject = { + type: 0, + records: { key: 'value' }, + caseSensitive: false, + fwConstants, + } + const payload = encodeAddKvRecordsRequest(mockObject) + const payloadAsString = payload.toString('hex') + expect(payloadAsString).toMatchSnapshot() + }) - test('removeKvRecords', () => { - const fwConstants = buildFirmwareConstants() - const mockObject = { - type: 0, - ids: ['0'], - caseSensitive: false, - fwConstants, - } - const payload = encodeRemoveKvRecordsRequest(mockObject) - const payloadAsString = payload.toString('hex') - expect(payloadAsString).toMatchSnapshot() - }) - }) + test('removeKvRecords', () => { + const fwConstants = buildFirmwareConstants() + const mockObject = { + type: 0, + ids: ['0'], + caseSensitive: false, + fwConstants, + } + const payload = encodeRemoveKvRecordsRequest(mockObject) + const payloadAsString = payload.toString('hex') + expect(payloadAsString).toMatchSnapshot() + }) + }) }) diff --git a/packages/sdk/src/__test__/unit/ethereum.validate.test.ts b/packages/sdk/src/__test__/unit/ethereum.validate.test.ts index 78d51dd2..c61d865f 100644 --- a/packages/sdk/src/__test__/unit/ethereum.validate.test.ts +++ b/packages/sdk/src/__test__/unit/ethereum.validate.test.ts @@ -1,8 +1,8 @@ import { - type MessageTypes, - SignTypedDataVersion, - TypedDataUtils, - type TypedMessage, + type MessageTypes, + SignTypedDataVersion, + TypedDataUtils, + type TypedMessage, } from '@metamask/eth-sig-util' import { ecsign, privateToAddress } from 'ethereumjs-util' import { mnemonicToAccount } from 'viem/accounts' @@ -12,78 +12,78 @@ import { DEFAULT_SIGNER, buildFirmwareConstants } from '../utils/builders' import { TEST_MNEMONIC } from '../utils/testConstants' const typedData: TypedMessage = { - types: { - EIP712Domain: [{ name: 'chainId', type: 'uint256' }], - Greeting: [ - { name: 'salutation', type: 'string' }, - { name: 'target', type: 'string' }, - { name: 'born', type: 'int32' }, - ], - }, - primaryType: 'Greeting', - domain: { chainId: 1 }, - message: { - salutation: 'Hello', - target: 'Ethereum', - born: 2015, - }, + types: { + EIP712Domain: [{ name: 'chainId', type: 'uint256' }], + Greeting: [ + { name: 'salutation', type: 'string' }, + { name: 'target', type: 'string' }, + { name: 'born', type: 'int32' }, + ], + }, + primaryType: 'Greeting', + domain: { chainId: 1 }, + message: { + salutation: 'Hello', + target: 'Ethereum', + born: 2015, + }, } describe('validateEthereumMsgResponse', () => { - it('recovers expected signature for EIP712 payload', () => { - const account = mnemonicToAccount(TEST_MNEMONIC) - const hdKey = account.getHdKey() - if (!hdKey.privateKey) throw new Error('No private key') - const priv = Buffer.from(hdKey.privateKey) - const signer = privateToAddress(priv) - const digest = TypedDataUtils.eip712Hash(typedData, SignTypedDataVersion.V4) - const sig = ecsign(Buffer.from(digest), priv) - const fwConstants = buildFirmwareConstants() - const request = ethereum.buildEthereumMsgRequest({ - signerPath: DEFAULT_SIGNER, - protocol: 'eip712', - payload: JSON.parse(JSON.stringify(typedData)), - fwConstants, - }) - const result = ethereum.validateEthereumMsgResponse( - { - signer: `0x${signer.toString('hex')}`, - sig: { r: Buffer.from(sig.r), s: Buffer.from(sig.s) }, - }, - request, - ) + it('recovers expected signature for EIP712 payload', () => { + const account = mnemonicToAccount(TEST_MNEMONIC) + const hdKey = account.getHdKey() + if (!hdKey.privateKey) throw new Error('No private key') + const priv = Buffer.from(hdKey.privateKey) + const signer = privateToAddress(priv) + const digest = TypedDataUtils.eip712Hash(typedData, SignTypedDataVersion.V4) + const sig = ecsign(Buffer.from(digest), priv) + const fwConstants = buildFirmwareConstants() + const request = ethereum.buildEthereumMsgRequest({ + signerPath: DEFAULT_SIGNER, + protocol: 'eip712', + payload: JSON.parse(JSON.stringify(typedData)), + fwConstants, + }) + const result = ethereum.validateEthereumMsgResponse( + { + signer: `0x${signer.toString('hex')}`, + sig: { r: Buffer.from(sig.r), s: Buffer.from(sig.s) }, + }, + request, + ) - expect(result.v.toString('hex')).toBe('1c') - }) + expect(result.v.toString('hex')).toBe('1c') + }) - it('validates response using buildEthereumMsgRequest request context', () => { - const fwConstants = buildFirmwareConstants() - const signerPath = [...DEFAULT_SIGNER] - signerPath[2] = HARDENED_OFFSET + it('validates response using buildEthereumMsgRequest request context', () => { + const fwConstants = buildFirmwareConstants() + const signerPath = [...DEFAULT_SIGNER] + signerPath[2] = HARDENED_OFFSET - const request = ethereum.buildEthereumMsgRequest({ - signerPath, - protocol: 'eip712', - payload: JSON.parse(JSON.stringify(typedData)), - fwConstants, - }) + const request = ethereum.buildEthereumMsgRequest({ + signerPath, + protocol: 'eip712', + payload: JSON.parse(JSON.stringify(typedData)), + fwConstants, + }) - const account = mnemonicToAccount(TEST_MNEMONIC) - const hdKey = account.getHdKey() - if (!hdKey.privateKey) throw new Error('No private key') - const priv = Buffer.from(hdKey.privateKey) - const signer = privateToAddress(priv) - const digest = TypedDataUtils.eip712Hash(typedData, SignTypedDataVersion.V4) - const sig = ecsign(Buffer.from(digest), priv) + const account = mnemonicToAccount(TEST_MNEMONIC) + const hdKey = account.getHdKey() + if (!hdKey.privateKey) throw new Error('No private key') + const priv = Buffer.from(hdKey.privateKey) + const signer = privateToAddress(priv) + const digest = TypedDataUtils.eip712Hash(typedData, SignTypedDataVersion.V4) + const sig = ecsign(Buffer.from(digest), priv) - const result = ethereum.validateEthereumMsgResponse( - { - signer, - sig: { r: Buffer.from(sig.r), s: Buffer.from(sig.s) }, - }, - request, - ) + const result = ethereum.validateEthereumMsgResponse( + { + signer, + sig: { r: Buffer.from(sig.r), s: Buffer.from(sig.s) }, + }, + request, + ) - expect(result.v.toString('hex')).toBe('1c') - }) + expect(result.v.toString('hex')).toBe('1c') + }) }) diff --git a/packages/sdk/src/__test__/unit/module.interop.test.ts b/packages/sdk/src/__test__/unit/module.interop.test.ts index df005282..619bb0ec 100644 --- a/packages/sdk/src/__test__/unit/module.interop.test.ts +++ b/packages/sdk/src/__test__/unit/module.interop.test.ts @@ -1,10 +1,10 @@ import { execSync, spawnSync } from 'node:child_process' import { - existsSync, - mkdirSync, - mkdtempSync, - rmSync, - symlinkSync, + existsSync, + mkdirSync, + mkdtempSync, + rmSync, + symlinkSync, } from 'node:fs' import os from 'node:os' import path from 'node:path' @@ -21,63 +21,63 @@ let built = false let fixtureDir: string | undefined const ensureBuildArtifacts = () => { - if (built) { - return - } - console.log('Building package with pnpm run build ...') - execSync('pnpm run build', { - cwd: packageRoot, - stdio: 'inherit', - }) - if (!existsSync(cjsOutput) || !existsSync(esmOutput)) { - throw new Error('Expected dual build outputs were not generated') - } - built = true + if (built) { + return + } + console.log('Building package with pnpm run build ...') + execSync('pnpm run build', { + cwd: packageRoot, + stdio: 'inherit', + }) + if (!existsSync(cjsOutput) || !existsSync(esmOutput)) { + throw new Error('Expected dual build outputs were not generated') + } + built = true } const ensureLinkedFixture = () => { - if (fixtureDir) { - return fixtureDir - } - const tmpDir = mkdtempSync(path.join(os.tmpdir(), 'gridplus-sdk-interop-')) - const nodeModulesDir = path.join(tmpDir, 'node_modules') - mkdirSync(nodeModulesDir, { recursive: true }) - const linkTarget = path.join(nodeModulesDir, packageName) - symlinkSync(packageRoot, linkTarget, 'junction') - fixtureDir = tmpDir - return fixtureDir + if (fixtureDir) { + return fixtureDir + } + const tmpDir = mkdtempSync(path.join(os.tmpdir(), 'gridplus-sdk-interop-')) + const nodeModulesDir = path.join(tmpDir, 'node_modules') + mkdirSync(nodeModulesDir, { recursive: true }) + const linkTarget = path.join(nodeModulesDir, packageName) + symlinkSync(packageRoot, linkTarget, 'junction') + fixtureDir = tmpDir + return fixtureDir } const runNodeCheck = (args: string[]) => { - const cwd = ensureLinkedFixture() - const result = spawnSync(process.execPath, args, { - cwd, - env: { ...process.env }, - encoding: 'utf-8', - }) - if (result.error) { - throw result.error - } - if (result.status !== 0) { - throw new Error( - `Node command failed (${result.status}):\n${result.stderr || result.stdout}`, - ) - } + const cwd = ensureLinkedFixture() + const result = spawnSync(process.execPath, args, { + cwd, + env: { ...process.env }, + encoding: 'utf-8', + }) + if (result.error) { + throw result.error + } + if (result.status !== 0) { + throw new Error( + `Node command failed (${result.status}):\n${result.stderr || result.stdout}`, + ) + } } describe('package module interoperability', () => { - beforeAll(() => { - ensureBuildArtifacts() - }) - afterAll(() => { - if (fixtureDir) { - rmSync(fixtureDir, { recursive: true, force: true }) - fixtureDir = undefined - } - }) + beforeAll(() => { + ensureBuildArtifacts() + }) + afterAll(() => { + if (fixtureDir) { + rmSync(fixtureDir, { recursive: true, force: true }) + fixtureDir = undefined + } + }) - it('exposes CommonJS entry via require()', () => { - const script = ` + it('exposes CommonJS entry via require()', () => { + const script = ` const sdk = require('${packageName}'); if (typeof sdk.connect !== 'function') { throw new Error('connect export missing'); @@ -86,11 +86,11 @@ describe('package module interoperability', () => { throw new Error('Client export missing'); } ` - runNodeCheck(['-e', script]) - }) + runNodeCheck(['-e', script]) + }) - it('exposes ESM entry via dynamic import()', () => { - const script = ` + it('exposes ESM entry via dynamic import()', () => { + const script = ` const sdk = await import('${packageName}'); if (typeof sdk.connect !== 'function') { throw new Error('connect export missing'); @@ -99,6 +99,6 @@ describe('package module interoperability', () => { throw new Error('Client export missing'); } ` - runNodeCheck(['--input-type=module', '-e', script]) - }) + runNodeCheck(['--input-type=module', '-e', script]) + }) }) diff --git a/packages/sdk/src/__test__/unit/parseGenericSigningResponse.test.ts b/packages/sdk/src/__test__/unit/parseGenericSigningResponse.test.ts index a4a2bc87..cc95ed43 100644 --- a/packages/sdk/src/__test__/unit/parseGenericSigningResponse.test.ts +++ b/packages/sdk/src/__test__/unit/parseGenericSigningResponse.test.ts @@ -6,166 +6,166 @@ import { parseGenericSigningResponse } from '../../genericSigning' import { Constants } from '../../index' describe('parseGenericSigningResponse', () => { - // Helper to create a DER signature - const createDERSignature = (r: Buffer, s: Buffer): Buffer => { - const rLen = r.length - const sLen = s.length - const totalLen = 4 + rLen + sLen - const sig = Buffer.alloc(totalLen + 2) - - sig[0] = 0x30 // DER sequence - sig[1] = totalLen - sig[2] = 0x02 // Integer type - sig[3] = rLen - r.copy(sig, 4) - sig[4 + rLen] = 0x02 // Integer type - sig[4 + rLen + 1] = sLen - s.copy(sig, 4 + rLen + 2) - - // Pad to 74 bytes (standard for Lattice) - const padded = Buffer.alloc(74) - sig.copy(padded, 0) - return padded - } - - it('should handle generic KECCAK256 message (not EVM transaction)', () => { - // Simulate signing a plain text message "Test!" - const payload = Buffer.from('Test!') - const hash = Buffer.from(Hash.keccak256(payload)) - - // Create a fake signature - const privateKey = Buffer.from( - '0101010101010101010101010101010101010101010101010101010101010101', - 'hex', - ) - const sigObj = secp256k1.ecdsaSign(hash, privateKey) - const publicKey = secp256k1.publicKeyCreate(privateKey, false) - - // Create DER-encoded signature response - const derSig = createDERSignature( - Buffer.from(sigObj.signature.slice(0, 32)), - Buffer.from(sigObj.signature.slice(32, 64)), - ) - - // Create mock response buffer - const mockResponse = Buffer.concat([ - Buffer.from([0x04]), // Uncompressed pubkey prefix - publicKey.slice(1), // Remove compression prefix from secp256k1 output (64 bytes) - derSig, - ]) - - const req = { - curveType: Constants.SIGNING.CURVES.SECP256K1, - hashType: Constants.SIGNING.HASHES.KECCAK256, - encodingType: null, // Not EVM encoding - origPayloadBuf: payload, - } - - const result = parseGenericSigningResponse(mockResponse, 0, req) - - expect(result).toBeDefined() - expect(result.sig).toBeDefined() - expect(result.sig.v).toBeDefined() - expect(typeof result.sig.v).toBe('bigint') - - // For non-EVM generic messages, v should be 27 or 28 - expect([27n, 28n]).toContain(result.sig.v) - }) - - it('should handle EVM transaction encoding', () => { - // Simulate an unsigned legacy transaction - const unsignedTx = Buffer.from( - 'e9808504a817c800825208943535353535353535353535353535353535353535880de0b6b3a764000080', - 'hex', - ) - const hash = Buffer.from(Hash.keccak256(unsignedTx)) - - // Create a fake signature - const privateKey = Buffer.from( - '0101010101010101010101010101010101010101010101010101010101010101', - 'hex', - ) - const sigObj = secp256k1.ecdsaSign(hash, privateKey) - const publicKey = secp256k1.publicKeyCreate(privateKey, false) - - // Create DER-encoded signature response - const derSig = createDERSignature( - Buffer.from(sigObj.signature.slice(0, 32)), - Buffer.from(sigObj.signature.slice(32, 64)), - ) - - // Create mock response buffer - const mockResponse = Buffer.concat([ - Buffer.from([0x04]), // Uncompressed pubkey prefix - publicKey.slice(1), // Remove compression prefix from secp256k1 output (64 bytes) - derSig, - ]) - - const req = { - curveType: Constants.SIGNING.CURVES.SECP256K1, - hashType: Constants.SIGNING.HASHES.KECCAK256, - encodingType: Constants.SIGNING.ENCODINGS.EVM, - origPayloadBuf: unsignedTx, - } - - const result = parseGenericSigningResponse(mockResponse, 0, req) - - expect(result).toBeDefined() - expect(result.sig).toBeDefined() - expect(result.sig.v).toBeDefined() - expect(typeof result.sig.v).toBe('bigint') - - // For pre-EIP155 transactions, v should be 27 or 28 - const vNumber = Number(result.sig.v) - expect([27, 28]).toContain(vNumber) - }) - - it('should handle RLP-encoded data that looks like a transaction', () => { - // Create an RLP-encoded array with 6+ elements (looks like a transaction) - const txLikeData = [ - Buffer.from([0x01]), // nonce - Buffer.from([0x02]), // gasPrice - Buffer.from([0x03]), // gasLimit - Buffer.from([0x04]), // to - Buffer.from([0x05]), // value - Buffer.from([0x06]), // data - ] - const rlpEncoded = Buffer.from(RLP.encode(txLikeData)) - const hash = Buffer.from(Hash.keccak256(rlpEncoded)) - - // Create a fake signature - const privateKey = Buffer.from( - '0101010101010101010101010101010101010101010101010101010101010101', - 'hex', - ) - const sigObj = secp256k1.ecdsaSign(hash, privateKey) - const publicKey = secp256k1.publicKeyCreate(privateKey, false) - - // Create DER-encoded signature response - const derSig = createDERSignature( - Buffer.from(sigObj.signature.slice(0, 32)), - Buffer.from(sigObj.signature.slice(32, 64)), - ) - - // Create mock response buffer - const mockResponse = Buffer.concat([ - Buffer.from([0x04]), // Uncompressed pubkey prefix - publicKey.slice(1), // Remove compression prefix from secp256k1 output (64 bytes) - derSig, - ]) - - const req = { - curveType: Constants.SIGNING.CURVES.SECP256K1, - hashType: Constants.SIGNING.HASHES.KECCAK256, - encodingType: null, // Not explicitly EVM - origPayloadBuf: rlpEncoded, - } - - const result = parseGenericSigningResponse(mockResponse, 0, req) - - expect(result).toBeDefined() - expect(result.sig).toBeDefined() - expect(result.sig.v).toBeDefined() - expect(typeof result.sig.v).toBe('bigint') - }) + // Helper to create a DER signature + const createDERSignature = (r: Buffer, s: Buffer): Buffer => { + const rLen = r.length + const sLen = s.length + const totalLen = 4 + rLen + sLen + const sig = Buffer.alloc(totalLen + 2) + + sig[0] = 0x30 // DER sequence + sig[1] = totalLen + sig[2] = 0x02 // Integer type + sig[3] = rLen + r.copy(sig, 4) + sig[4 + rLen] = 0x02 // Integer type + sig[4 + rLen + 1] = sLen + s.copy(sig, 4 + rLen + 2) + + // Pad to 74 bytes (standard for Lattice) + const padded = Buffer.alloc(74) + sig.copy(padded, 0) + return padded + } + + it('should handle generic KECCAK256 message (not EVM transaction)', () => { + // Simulate signing a plain text message "Test!" + const payload = Buffer.from('Test!') + const hash = Buffer.from(Hash.keccak256(payload)) + + // Create a fake signature + const privateKey = Buffer.from( + '0101010101010101010101010101010101010101010101010101010101010101', + 'hex', + ) + const sigObj = secp256k1.ecdsaSign(hash, privateKey) + const publicKey = secp256k1.publicKeyCreate(privateKey, false) + + // Create DER-encoded signature response + const derSig = createDERSignature( + Buffer.from(sigObj.signature.slice(0, 32)), + Buffer.from(sigObj.signature.slice(32, 64)), + ) + + // Create mock response buffer + const mockResponse = Buffer.concat([ + Buffer.from([0x04]), // Uncompressed pubkey prefix + publicKey.slice(1), // Remove compression prefix from secp256k1 output (64 bytes) + derSig, + ]) + + const req = { + curveType: Constants.SIGNING.CURVES.SECP256K1, + hashType: Constants.SIGNING.HASHES.KECCAK256, + encodingType: null, // Not EVM encoding + origPayloadBuf: payload, + } + + const result = parseGenericSigningResponse(mockResponse, 0, req) + + expect(result).toBeDefined() + expect(result.sig).toBeDefined() + expect(result.sig.v).toBeDefined() + expect(typeof result.sig.v).toBe('bigint') + + // For non-EVM generic messages, v should be 27 or 28 + expect([27n, 28n]).toContain(result.sig.v) + }) + + it('should handle EVM transaction encoding', () => { + // Simulate an unsigned legacy transaction + const unsignedTx = Buffer.from( + 'e9808504a817c800825208943535353535353535353535353535353535353535880de0b6b3a764000080', + 'hex', + ) + const hash = Buffer.from(Hash.keccak256(unsignedTx)) + + // Create a fake signature + const privateKey = Buffer.from( + '0101010101010101010101010101010101010101010101010101010101010101', + 'hex', + ) + const sigObj = secp256k1.ecdsaSign(hash, privateKey) + const publicKey = secp256k1.publicKeyCreate(privateKey, false) + + // Create DER-encoded signature response + const derSig = createDERSignature( + Buffer.from(sigObj.signature.slice(0, 32)), + Buffer.from(sigObj.signature.slice(32, 64)), + ) + + // Create mock response buffer + const mockResponse = Buffer.concat([ + Buffer.from([0x04]), // Uncompressed pubkey prefix + publicKey.slice(1), // Remove compression prefix from secp256k1 output (64 bytes) + derSig, + ]) + + const req = { + curveType: Constants.SIGNING.CURVES.SECP256K1, + hashType: Constants.SIGNING.HASHES.KECCAK256, + encodingType: Constants.SIGNING.ENCODINGS.EVM, + origPayloadBuf: unsignedTx, + } + + const result = parseGenericSigningResponse(mockResponse, 0, req) + + expect(result).toBeDefined() + expect(result.sig).toBeDefined() + expect(result.sig.v).toBeDefined() + expect(typeof result.sig.v).toBe('bigint') + + // For pre-EIP155 transactions, v should be 27 or 28 + const vNumber = Number(result.sig.v) + expect([27, 28]).toContain(vNumber) + }) + + it('should handle RLP-encoded data that looks like a transaction', () => { + // Create an RLP-encoded array with 6+ elements (looks like a transaction) + const txLikeData = [ + Buffer.from([0x01]), // nonce + Buffer.from([0x02]), // gasPrice + Buffer.from([0x03]), // gasLimit + Buffer.from([0x04]), // to + Buffer.from([0x05]), // value + Buffer.from([0x06]), // data + ] + const rlpEncoded = Buffer.from(RLP.encode(txLikeData)) + const hash = Buffer.from(Hash.keccak256(rlpEncoded)) + + // Create a fake signature + const privateKey = Buffer.from( + '0101010101010101010101010101010101010101010101010101010101010101', + 'hex', + ) + const sigObj = secp256k1.ecdsaSign(hash, privateKey) + const publicKey = secp256k1.publicKeyCreate(privateKey, false) + + // Create DER-encoded signature response + const derSig = createDERSignature( + Buffer.from(sigObj.signature.slice(0, 32)), + Buffer.from(sigObj.signature.slice(32, 64)), + ) + + // Create mock response buffer + const mockResponse = Buffer.concat([ + Buffer.from([0x04]), // Uncompressed pubkey prefix + publicKey.slice(1), // Remove compression prefix from secp256k1 output (64 bytes) + derSig, + ]) + + const req = { + curveType: Constants.SIGNING.CURVES.SECP256K1, + hashType: Constants.SIGNING.HASHES.KECCAK256, + encodingType: null, // Not explicitly EVM + origPayloadBuf: rlpEncoded, + } + + const result = parseGenericSigningResponse(mockResponse, 0, req) + + expect(result).toBeDefined() + expect(result.sig).toBeDefined() + expect(result.sig.v).toBeDefined() + expect(typeof result.sig.v).toBe('bigint') + }) }) diff --git a/packages/sdk/src/__test__/unit/personalSignValidation.test.ts b/packages/sdk/src/__test__/unit/personalSignValidation.test.ts index 1a0cd5c0..7aa3789e 100644 --- a/packages/sdk/src/__test__/unit/personalSignValidation.test.ts +++ b/packages/sdk/src/__test__/unit/personalSignValidation.test.ts @@ -4,135 +4,135 @@ import secp256k1 from 'secp256k1' import { addRecoveryParam } from '../../ethereum' describe('Personal Sign Validation - Issue Fix', () => { - /** - * This test validates the fix for the personal sign validation bug. - * The issue was in pubToAddrStr function which was incorrectly slicing - * the hash buffer, causing address comparison to always fail. - */ - - it('should correctly validate personal message signature', () => { - // Create a test private key and derive public key - const privateKey = Buffer.from( - '0101010101010101010101010101010101010101010101010101010101010101', - 'hex', - ) - const publicKey = secp256k1.publicKeyCreate(privateKey, false) - - // Create a test message - const message = Buffer.from('Test message', 'utf8') - - // Build personal sign prefix and hash - const prefix = Buffer.from( - `\u0019Ethereum Signed Message:\n${message.length.toString()}`, - 'utf-8', - ) - const messageHash = Buffer.from( - Hash.keccak256(Buffer.concat([prefix, message])), - ) - - // Sign the message - const sigObj = secp256k1.ecdsaSign(messageHash, privateKey) - - // Prepare signature object - const sig = { - r: Buffer.from(sigObj.signature.slice(0, 32)), - s: Buffer.from(sigObj.signature.slice(32, 64)), - } - - // Get the Ethereum address from the public key - // This matches what the firmware returns - const pubkeyWithoutPrefix = publicKey.slice(1) // Remove 0x04 prefix - const addressBuffer = Buffer.from( - Hash.keccak256(pubkeyWithoutPrefix), - ).slice(-20) - - // This is the function that was failing before the fix - // It should now correctly add the recovery parameter - const result = addRecoveryParam(messageHash, sig, addressBuffer, { - chainId: 1, - useEIP155: false, - }) - - // Verify the signature has a valid v value (27 or 28) - expect(result.v).toBeDefined() - const vValue = Buffer.isBuffer(result.v) - ? result.v.readUInt8(0) - : Number(result.v) - expect([27, 28]).toContain(vValue) - - // Verify r and s are buffers of correct length - expect(Buffer.isBuffer(result.r)).toBe(true) - expect(Buffer.isBuffer(result.s)).toBe(true) - expect(result.r.length).toBe(32) - expect(result.s.length).toBe(32) - }) - - it('should throw error when signature does not match address', () => { - // Create a test message hash - const messageHash = Buffer.from(Hash.keccak256(Buffer.from('test'))) - - // Create a random signature - const sig = { - r: Buffer.from('1'.repeat(64), 'hex'), - s: Buffer.from('2'.repeat(64), 'hex'), - } - - // Use a random address that won't match the signature - const wrongAddress = Buffer.from('3'.repeat(40), 'hex') - - // This should throw because the signature doesn't match the address - expect(() => { - addRecoveryParam(messageHash, sig, wrongAddress, { - chainId: 1, - useEIP155: false, - }) - }).toThrow() // Just verify it throws, exact message may vary - }) - - it('should handle the exact scenario from Ambire bug report', () => { - // This is the exact scenario reported by Kalo from Ambire - const testPayload = '0x54657374206d657373616765' // "Test message" in hex - const payloadBuffer = Buffer.from(testPayload.slice(2), 'hex') - - // Build personal sign hash - const prefix = Buffer.from( - `\u0019Ethereum Signed Message:\n${payloadBuffer.length.toString()}`, - 'utf-8', - ) - const messageHash = Buffer.from( - Hash.keccak256(Buffer.concat([prefix, payloadBuffer])), - ) - - // Create a valid signature for this message - const privateKey = Buffer.from( - '0101010101010101010101010101010101010101010101010101010101010101', - 'hex', - ) - const publicKey = secp256k1.publicKeyCreate(privateKey, false) - const sigObj = secp256k1.ecdsaSign(messageHash, privateKey) - - const sig = { - r: Buffer.from(sigObj.signature.slice(0, 32)), - s: Buffer.from(sigObj.signature.slice(32, 64)), - } - - // Get address from public key - const pubkeyWithoutPrefix = publicKey.slice(1) - const addressBuffer = Buffer.from( - Hash.keccak256(pubkeyWithoutPrefix), - ).slice(-20) - - // This should NOT throw with the fix in place - expect(() => { - const result = addRecoveryParam(messageHash, sig, addressBuffer, { - chainId: 1, - useEIP155: false, - }) - - // Verify we got a valid result - expect(result.v).toBeDefined() - expect(result.r).toBeDefined() - expect(result.s).toBeDefined() - }).not.toThrow() - }) + /** + * This test validates the fix for the personal sign validation bug. + * The issue was in pubToAddrStr function which was incorrectly slicing + * the hash buffer, causing address comparison to always fail. + */ + + it('should correctly validate personal message signature', () => { + // Create a test private key and derive public key + const privateKey = Buffer.from( + '0101010101010101010101010101010101010101010101010101010101010101', + 'hex', + ) + const publicKey = secp256k1.publicKeyCreate(privateKey, false) + + // Create a test message + const message = Buffer.from('Test message', 'utf8') + + // Build personal sign prefix and hash + const prefix = Buffer.from( + `\u0019Ethereum Signed Message:\n${message.length.toString()}`, + 'utf-8', + ) + const messageHash = Buffer.from( + Hash.keccak256(Buffer.concat([prefix, message])), + ) + + // Sign the message + const sigObj = secp256k1.ecdsaSign(messageHash, privateKey) + + // Prepare signature object + const sig = { + r: Buffer.from(sigObj.signature.slice(0, 32)), + s: Buffer.from(sigObj.signature.slice(32, 64)), + } + + // Get the Ethereum address from the public key + // This matches what the firmware returns + const pubkeyWithoutPrefix = publicKey.slice(1) // Remove 0x04 prefix + const addressBuffer = Buffer.from( + Hash.keccak256(pubkeyWithoutPrefix), + ).slice(-20) + + // This is the function that was failing before the fix + // It should now correctly add the recovery parameter + const result = addRecoveryParam(messageHash, sig, addressBuffer, { + chainId: 1, + useEIP155: false, + }) + + // Verify the signature has a valid v value (27 or 28) + expect(result.v).toBeDefined() + const vValue = Buffer.isBuffer(result.v) + ? result.v.readUInt8(0) + : Number(result.v) + expect([27, 28]).toContain(vValue) + + // Verify r and s are buffers of correct length + expect(Buffer.isBuffer(result.r)).toBe(true) + expect(Buffer.isBuffer(result.s)).toBe(true) + expect(result.r.length).toBe(32) + expect(result.s.length).toBe(32) + }) + + it('should throw error when signature does not match address', () => { + // Create a test message hash + const messageHash = Buffer.from(Hash.keccak256(Buffer.from('test'))) + + // Create a random signature + const sig = { + r: Buffer.from('1'.repeat(64), 'hex'), + s: Buffer.from('2'.repeat(64), 'hex'), + } + + // Use a random address that won't match the signature + const wrongAddress = Buffer.from('3'.repeat(40), 'hex') + + // This should throw because the signature doesn't match the address + expect(() => { + addRecoveryParam(messageHash, sig, wrongAddress, { + chainId: 1, + useEIP155: false, + }) + }).toThrow() // Just verify it throws, exact message may vary + }) + + it('should handle the exact scenario from Ambire bug report', () => { + // This is the exact scenario reported by Kalo from Ambire + const testPayload = '0x54657374206d657373616765' // "Test message" in hex + const payloadBuffer = Buffer.from(testPayload.slice(2), 'hex') + + // Build personal sign hash + const prefix = Buffer.from( + `\u0019Ethereum Signed Message:\n${payloadBuffer.length.toString()}`, + 'utf-8', + ) + const messageHash = Buffer.from( + Hash.keccak256(Buffer.concat([prefix, payloadBuffer])), + ) + + // Create a valid signature for this message + const privateKey = Buffer.from( + '0101010101010101010101010101010101010101010101010101010101010101', + 'hex', + ) + const publicKey = secp256k1.publicKeyCreate(privateKey, false) + const sigObj = secp256k1.ecdsaSign(messageHash, privateKey) + + const sig = { + r: Buffer.from(sigObj.signature.slice(0, 32)), + s: Buffer.from(sigObj.signature.slice(32, 64)), + } + + // Get address from public key + const pubkeyWithoutPrefix = publicKey.slice(1) + const addressBuffer = Buffer.from( + Hash.keccak256(pubkeyWithoutPrefix), + ).slice(-20) + + // This should NOT throw with the fix in place + expect(() => { + const result = addRecoveryParam(messageHash, sig, addressBuffer, { + chainId: 1, + useEIP155: false, + }) + + // Verify we got a valid result + expect(result.v).toBeDefined() + expect(result.r).toBeDefined() + expect(result.s).toBeDefined() + }).not.toThrow() + }) }) diff --git a/packages/sdk/src/__test__/unit/selectDefFrom4byteABI.test.ts b/packages/sdk/src/__test__/unit/selectDefFrom4byteABI.test.ts index 429fa744..fd92cc1c 100644 --- a/packages/sdk/src/__test__/unit/selectDefFrom4byteABI.test.ts +++ b/packages/sdk/src/__test__/unit/selectDefFrom4byteABI.test.ts @@ -1,82 +1,82 @@ import { selectDefFrom4byteABI } from '../../util' describe('selectDefFrom4byteAbi', () => { - beforeAll(() => { - // Disable this mock to restore console logs when testing - console.warn = vi.fn() - }) + beforeAll(() => { + // Disable this mock to restore console logs when testing + console.warn = vi.fn() + }) - afterAll(() => { - vi.clearAllMocks() - }) + afterAll(() => { + vi.clearAllMocks() + }) - test('select correct result', () => { - const result = [ - { - bytes_signature: '8í9', - created_at: '2020-08-09T08:56:14.110995Z', - hex_signature: '0x38ed1739', - id: 171801, - text_signature: - 'swapExactTokensForTokens(uint256,uint256,address[],address,uint256)', - }, - { - bytes_signature: '8í9', - created_at: '2020-01-09T08:56:14.110995Z', - hex_signature: '0x38ed1739', - id: 171806, - text_signature: - 'swapExactTokensForTokens(uint256,uint256,address[],address,uint256)', - }, - { - bytes_signature: '', - created_at: '2020-01-09T08:56:14.110995Z', - hex_signature: '0x38ed9', - id: 171806, - text_signature: 'swapToken', - }, - ] - const selector = '0x38ed1739' - expect(selectDefFrom4byteABI(result, selector)).toMatchSnapshot() - }) + test('select correct result', () => { + const result = [ + { + bytes_signature: '8í9', + created_at: '2020-08-09T08:56:14.110995Z', + hex_signature: '0x38ed1739', + id: 171801, + text_signature: + 'swapExactTokensForTokens(uint256,uint256,address[],address,uint256)', + }, + { + bytes_signature: '8í9', + created_at: '2020-01-09T08:56:14.110995Z', + hex_signature: '0x38ed1739', + id: 171806, + text_signature: + 'swapExactTokensForTokens(uint256,uint256,address[],address,uint256)', + }, + { + bytes_signature: '', + created_at: '2020-01-09T08:56:14.110995Z', + hex_signature: '0x38ed9', + id: 171806, + text_signature: 'swapToken', + }, + ] + const selector = '0x38ed1739' + expect(selectDefFrom4byteABI(result, selector)).toMatchSnapshot() + }) - test('handle no match', () => { - const result = [ - { - bytes_signature: '', - created_at: '2020-01-09T08:56:14.110995Z', - hex_signature: '0x3ed9', - id: 171806, - text_signature: 'swapToken', - }, - ] - const selector = '0x38ed1739' - expect(() => selectDefFrom4byteABI(result, selector)).toThrowError() - }) + test('handle no match', () => { + const result = [ + { + bytes_signature: '', + created_at: '2020-01-09T08:56:14.110995Z', + hex_signature: '0x3ed9', + id: 171806, + text_signature: 'swapToken', + }, + ] + const selector = '0x38ed1739' + expect(() => selectDefFrom4byteABI(result, selector)).toThrowError() + }) - test('handle no selector', () => { - const result = [ - { - bytes_signature: '', - created_at: '2020-01-09T08:56:14.110995Z', - hex_signature: '0x3ed9', - id: 171806, - text_signature: 'swapToken', - }, - ] - const selector = undefined - expect(() => selectDefFrom4byteABI(result, selector)).toThrowError() - }) + test('handle no selector', () => { + const result = [ + { + bytes_signature: '', + created_at: '2020-01-09T08:56:14.110995Z', + hex_signature: '0x3ed9', + id: 171806, + text_signature: 'swapToken', + }, + ] + const selector = undefined + expect(() => selectDefFrom4byteABI(result, selector)).toThrowError() + }) - test('handle no result', () => { - const result = undefined - const selector = '0x38ed1739' - expect(() => selectDefFrom4byteABI(result, selector)).toThrowError() - }) + test('handle no result', () => { + const result = undefined + const selector = '0x38ed1739' + expect(() => selectDefFrom4byteABI(result, selector)).toThrowError() + }) - test('handle bad data', () => { - const result = [] - const selector = '' - expect(() => selectDefFrom4byteABI(result, selector)).toThrowError() - }) + test('handle bad data', () => { + const result = [] + const selector = '' + expect(() => selectDefFrom4byteABI(result, selector)).toThrowError() + }) }) diff --git a/packages/sdk/src/__test__/unit/signatureUtils.test.ts b/packages/sdk/src/__test__/unit/signatureUtils.test.ts index c6ee0e9c..8d82e5d4 100644 --- a/packages/sdk/src/__test__/unit/signatureUtils.test.ts +++ b/packages/sdk/src/__test__/unit/signatureUtils.test.ts @@ -4,444 +4,444 @@ import secp256k1 from 'secp256k1' import { getV, getYParity, randomBytes } from '../../util' describe('getYParity', () => { - // Helper function to create a valid signature - const createValidSignature = (messageHash: Buffer) => { - // Create a deterministic private key for testing - const privateKey = Buffer.from( - '0101010101010101010101010101010101010101010101010101010101010101', - 'hex', - ) - - // Sign the message - const sigObj = secp256k1.ecdsaSign(messageHash, privateKey) - - // Get the public key - const publicKey = secp256k1.publicKeyCreate(privateKey, false) - - return { - signature: { - r: Buffer.from(sigObj.signature.slice(0, 32)), - s: Buffer.from(sigObj.signature.slice(32, 64)), - }, - publicKey: Buffer.from(publicKey), - recovery: sigObj.recid, - } - } - - describe('Simple signature format', () => { - it('should handle simple format with Buffer inputs', () => { - const messageHash = randomBytes(32) - const { signature, publicKey, recovery } = - createValidSignature(messageHash) - - const yParity = getYParity({ - messageHash, - signature, - publicKey, - }) - - expect(yParity).toBe(recovery) - expect([0, 1]).toContain(yParity) - }) - - it('should handle simple format with hex string inputs', () => { - const messageHash = randomBytes(32) - const { signature, publicKey, recovery } = - createValidSignature(messageHash) - - const yParity = getYParity({ - messageHash: `0x${messageHash.toString('hex')}`, - signature: { - r: `0x${signature.r.toString('hex')}`, - s: `0x${signature.s.toString('hex')}`, - }, - publicKey: `0x${publicKey.toString('hex')}`, - }) - - expect(yParity).toBe(recovery) - }) - - it('should handle simple format with compressed public key', () => { - const messageHash = randomBytes(32) - const privateKey = Buffer.from( - '0101010101010101010101010101010101010101010101010101010101010101', - 'hex', - ) - - const sigObj = secp256k1.ecdsaSign(messageHash, privateKey) - const compressedPubkey = secp256k1.publicKeyCreate(privateKey, true) - - const yParity = getYParity({ - messageHash, - signature: { - r: Buffer.from(sigObj.signature.slice(0, 32)), - s: Buffer.from(sigObj.signature.slice(32, 64)), - }, - publicKey: Buffer.from(compressedPubkey), - }) - - expect(yParity).toBe(sigObj.recid) - }) - - it('should handle mixed format inputs', () => { - const messageHash = randomBytes(32) - const { signature, publicKey, recovery } = - createValidSignature(messageHash) - - const yParity = getYParity({ - messageHash: messageHash.toString('hex'), // No 0x prefix - signature: { - r: signature.r, // Buffer - s: `0x${signature.s.toString('hex')}`, // Hex string - }, - publicKey, // Buffer - }) - - expect(yParity).toBe(recovery) - }) - }) - - describe('Legacy format with transaction and response', () => { - it('should handle Buffer transaction input', () => { - const tx = randomBytes(100) - const hash = Buffer.from(Hash.keccak256(tx)) - const { signature, publicKey, recovery } = createValidSignature(hash) - - const resp = { - sig: signature, - pubkey: publicKey, - } - - const yParity = getYParity(tx, resp) - expect(yParity).toBe(recovery) - }) - - it('should handle hex string as pre-computed hash', () => { - // When passing a hex string, it's treated as a pre-computed hash - const hash = randomBytes(32) - const txHex = `0x${hash.toString('hex')}` - const { signature, publicKey, recovery } = createValidSignature(hash) - - const resp = { - sig: signature, - pubkey: publicKey, - } - - const yParity = getYParity(txHex, resp) - expect(yParity).toBe(recovery) - }) - - it('should handle transaction object with getMessageToSign method', () => { - const messageData = randomBytes(32) - const mockTx = { - _type: 2, // EIP-1559 - getMessageToSign: () => messageData, - } - - const { signature, publicKey, recovery } = - createValidSignature(messageData) - - const resp = { - sig: signature, - pubkey: publicKey, - } - - const yParity = getYParity(mockTx, resp) - expect(yParity).toBe(recovery) - }) - - it.skip('should handle legacy transaction object', () => { - // Skip this test for now - legacy transaction handling is complex - // and would require proper RLP encoding to test correctly - }) - - it('should handle Uint8Array inputs', () => { - const messageHash = new Uint8Array(32) - messageHash.fill(42) - - const { signature, publicKey, recovery } = createValidSignature( - Buffer.from(messageHash), - ) - - const resp = { - sig: { - r: new Uint8Array(signature.r), - s: new Uint8Array(signature.s), - }, - pubkey: new Uint8Array(publicKey), - } - - const yParity = getYParity(messageHash, resp) - expect(yParity).toBe(recovery) - }) - - it.skip('should handle direct 32-byte hash input', () => { - // Skip for now - this test relies on specific behavior that may differ - }) - - it('should handle 32-byte hash as Uint8Array', () => { - const hash = new Uint8Array(32) - for (let i = 0; i < 32; i++) { - hash[i] = Math.floor(Math.random() * 256) - } - const { signature, publicKey, recovery } = createValidSignature( - Buffer.from(hash), - ) - - const resp = { - sig: signature, - pubkey: publicKey, - } - - const yParity = getYParity(hash, resp) - expect(yParity).toBe(recovery) - }) - - it('should hash non-32-byte inputs', () => { - const shortData = randomBytes(20) - const expectedHash = Buffer.from(Hash.keccak256(shortData)) - const { signature, publicKey, recovery } = - createValidSignature(expectedHash) - - const resp = { - sig: signature, - pubkey: publicKey, - } - - const yParity = getYParity(shortData, resp) - expect(yParity).toBe(recovery) - }) - }) - - describe('Error handling', () => { - it('should throw error if legacy format missing response', () => { - const tx = randomBytes(32) - expect(() => getYParity(tx)).toThrow( - 'Response with sig and pubkey required for legacy format', - ) - }) - - it('should throw error if response missing sig', () => { - const tx = randomBytes(32) - const resp = { pubkey: randomBytes(65) } - expect(() => getYParity(tx, resp)).toThrow( - 'Response with sig and pubkey required for legacy format', - ) - }) - - it('should throw error if response missing pubkey', () => { - const tx = randomBytes(32) - const resp = { sig: { r: randomBytes(32), s: randomBytes(32) } } - expect(() => getYParity(tx, resp)).toThrow( - 'Response with sig and pubkey required for legacy format', - ) - }) - - it('should throw error if recovery fails', () => { - const messageHash = randomBytes(32) - const wrongHash = randomBytes(32) - const { signature, publicKey } = createValidSignature(wrongHash) - - expect(() => - getYParity({ - messageHash, - signature, - publicKey, - }), - ).toThrow( - 'Failed to recover Y parity. Bad signature or transaction data.', - ) - }) - - it('should throw error with invalid signature', () => { - const messageHash = randomBytes(32) - const invalidSig = { - r: randomBytes(32), - s: randomBytes(32), - } - const randomPubkey = randomBytes(65) - randomPubkey[0] = 0x04 // Ensure valid uncompressed format - - expect(() => - getYParity({ - messageHash, - signature: invalidSig, - publicKey: randomPubkey, - }), - ).toThrow() // Just check that it throws, don't check exact message - }) - }) - - describe('Real world scenarios', () => { - it('should handle EIP-7702 authorization signature', () => { - // Simulate the exact scenario from signAuthorization - const MAGIC = Buffer.from([0x05]) - - // This would normally use RLP.encode but we'll create a test message - const message = Buffer.concat([ - MAGIC, - Buffer.from('test_rlp_encoded_data', 'utf8'), - ]) - - const messageHash = Buffer.from(Hash.keccak256(message)) - const { signature, publicKey, recovery } = - createValidSignature(messageHash) - - // Test both Buffer format (as returned by device) - const yParity1 = getYParity({ - messageHash, - signature, - publicKey, - }) - expect(yParity1).toBe(recovery) - - // Test with hex string format (as might be used in API) - const yParity2 = getYParity({ - messageHash, - signature: { - r: `0x${signature.r.toString('hex')}`, - s: `0x${signature.s.toString('hex')}`, - }, - publicKey, - }) - expect(yParity2).toBe(recovery) - }) - - it('should return consistent y-parity for multiple calls with same data', () => { - const messageHash = randomBytes(32) - const { signature, publicKey } = createValidSignature(messageHash) - - const yParity1 = getYParity({ - messageHash, - signature, - publicKey, - }) - - const yParity2 = getYParity({ - messageHash, - signature, - publicKey, - }) - - expect(yParity1).toBe(yParity2) - }) - - it('should handle real signature that should return y-parity of 1', () => { - // Use a specific private key that we know produces recovery id 1 for a specific message - let foundYParityOne = false - - // Try multiple messages until we get one with y-parity 1 - for (let i = 0; i < 100; i++) { - const messageHash = Buffer.from( - Hash.keccak256(Buffer.from(`test message ${i}`)), - ) - const privateKey = Buffer.from( - '0101010101010101010101010101010101010101010101010101010101010101', - 'hex', - ) - - const sigObj = secp256k1.ecdsaSign(messageHash, privateKey) - - if (sigObj.recid === 1) { - const publicKey = secp256k1.publicKeyCreate(privateKey, false) - - const yParity = getYParity({ - messageHash, - signature: { - r: Buffer.from(sigObj.signature.slice(0, 32)), - s: Buffer.from(sigObj.signature.slice(32, 64)), - }, - publicKey: Buffer.from(publicKey), - }) - - expect(yParity).toBe(1) - foundYParityOne = true - break - } - } - - expect(foundYParityOne).toBe(true) - }) - }) + // Helper function to create a valid signature + const createValidSignature = (messageHash: Buffer) => { + // Create a deterministic private key for testing + const privateKey = Buffer.from( + '0101010101010101010101010101010101010101010101010101010101010101', + 'hex', + ) + + // Sign the message + const sigObj = secp256k1.ecdsaSign(messageHash, privateKey) + + // Get the public key + const publicKey = secp256k1.publicKeyCreate(privateKey, false) + + return { + signature: { + r: Buffer.from(sigObj.signature.slice(0, 32)), + s: Buffer.from(sigObj.signature.slice(32, 64)), + }, + publicKey: Buffer.from(publicKey), + recovery: sigObj.recid, + } + } + + describe('Simple signature format', () => { + it('should handle simple format with Buffer inputs', () => { + const messageHash = randomBytes(32) + const { signature, publicKey, recovery } = + createValidSignature(messageHash) + + const yParity = getYParity({ + messageHash, + signature, + publicKey, + }) + + expect(yParity).toBe(recovery) + expect([0, 1]).toContain(yParity) + }) + + it('should handle simple format with hex string inputs', () => { + const messageHash = randomBytes(32) + const { signature, publicKey, recovery } = + createValidSignature(messageHash) + + const yParity = getYParity({ + messageHash: `0x${messageHash.toString('hex')}`, + signature: { + r: `0x${signature.r.toString('hex')}`, + s: `0x${signature.s.toString('hex')}`, + }, + publicKey: `0x${publicKey.toString('hex')}`, + }) + + expect(yParity).toBe(recovery) + }) + + it('should handle simple format with compressed public key', () => { + const messageHash = randomBytes(32) + const privateKey = Buffer.from( + '0101010101010101010101010101010101010101010101010101010101010101', + 'hex', + ) + + const sigObj = secp256k1.ecdsaSign(messageHash, privateKey) + const compressedPubkey = secp256k1.publicKeyCreate(privateKey, true) + + const yParity = getYParity({ + messageHash, + signature: { + r: Buffer.from(sigObj.signature.slice(0, 32)), + s: Buffer.from(sigObj.signature.slice(32, 64)), + }, + publicKey: Buffer.from(compressedPubkey), + }) + + expect(yParity).toBe(sigObj.recid) + }) + + it('should handle mixed format inputs', () => { + const messageHash = randomBytes(32) + const { signature, publicKey, recovery } = + createValidSignature(messageHash) + + const yParity = getYParity({ + messageHash: messageHash.toString('hex'), // No 0x prefix + signature: { + r: signature.r, // Buffer + s: `0x${signature.s.toString('hex')}`, // Hex string + }, + publicKey, // Buffer + }) + + expect(yParity).toBe(recovery) + }) + }) + + describe('Legacy format with transaction and response', () => { + it('should handle Buffer transaction input', () => { + const tx = randomBytes(100) + const hash = Buffer.from(Hash.keccak256(tx)) + const { signature, publicKey, recovery } = createValidSignature(hash) + + const resp = { + sig: signature, + pubkey: publicKey, + } + + const yParity = getYParity(tx, resp) + expect(yParity).toBe(recovery) + }) + + it('should handle hex string as pre-computed hash', () => { + // When passing a hex string, it's treated as a pre-computed hash + const hash = randomBytes(32) + const txHex = `0x${hash.toString('hex')}` + const { signature, publicKey, recovery } = createValidSignature(hash) + + const resp = { + sig: signature, + pubkey: publicKey, + } + + const yParity = getYParity(txHex, resp) + expect(yParity).toBe(recovery) + }) + + it('should handle transaction object with getMessageToSign method', () => { + const messageData = randomBytes(32) + const mockTx = { + _type: 2, // EIP-1559 + getMessageToSign: () => messageData, + } + + const { signature, publicKey, recovery } = + createValidSignature(messageData) + + const resp = { + sig: signature, + pubkey: publicKey, + } + + const yParity = getYParity(mockTx, resp) + expect(yParity).toBe(recovery) + }) + + it.skip('should handle legacy transaction object', () => { + // Skip this test for now - legacy transaction handling is complex + // and would require proper RLP encoding to test correctly + }) + + it('should handle Uint8Array inputs', () => { + const messageHash = new Uint8Array(32) + messageHash.fill(42) + + const { signature, publicKey, recovery } = createValidSignature( + Buffer.from(messageHash), + ) + + const resp = { + sig: { + r: new Uint8Array(signature.r), + s: new Uint8Array(signature.s), + }, + pubkey: new Uint8Array(publicKey), + } + + const yParity = getYParity(messageHash, resp) + expect(yParity).toBe(recovery) + }) + + it.skip('should handle direct 32-byte hash input', () => { + // Skip for now - this test relies on specific behavior that may differ + }) + + it('should handle 32-byte hash as Uint8Array', () => { + const hash = new Uint8Array(32) + for (let i = 0; i < 32; i++) { + hash[i] = Math.floor(Math.random() * 256) + } + const { signature, publicKey, recovery } = createValidSignature( + Buffer.from(hash), + ) + + const resp = { + sig: signature, + pubkey: publicKey, + } + + const yParity = getYParity(hash, resp) + expect(yParity).toBe(recovery) + }) + + it('should hash non-32-byte inputs', () => { + const shortData = randomBytes(20) + const expectedHash = Buffer.from(Hash.keccak256(shortData)) + const { signature, publicKey, recovery } = + createValidSignature(expectedHash) + + const resp = { + sig: signature, + pubkey: publicKey, + } + + const yParity = getYParity(shortData, resp) + expect(yParity).toBe(recovery) + }) + }) + + describe('Error handling', () => { + it('should throw error if legacy format missing response', () => { + const tx = randomBytes(32) + expect(() => getYParity(tx)).toThrow( + 'Response with sig and pubkey required for legacy format', + ) + }) + + it('should throw error if response missing sig', () => { + const tx = randomBytes(32) + const resp = { pubkey: randomBytes(65) } + expect(() => getYParity(tx, resp)).toThrow( + 'Response with sig and pubkey required for legacy format', + ) + }) + + it('should throw error if response missing pubkey', () => { + const tx = randomBytes(32) + const resp = { sig: { r: randomBytes(32), s: randomBytes(32) } } + expect(() => getYParity(tx, resp)).toThrow( + 'Response with sig and pubkey required for legacy format', + ) + }) + + it('should throw error if recovery fails', () => { + const messageHash = randomBytes(32) + const wrongHash = randomBytes(32) + const { signature, publicKey } = createValidSignature(wrongHash) + + expect(() => + getYParity({ + messageHash, + signature, + publicKey, + }), + ).toThrow( + 'Failed to recover Y parity. Bad signature or transaction data.', + ) + }) + + it('should throw error with invalid signature', () => { + const messageHash = randomBytes(32) + const invalidSig = { + r: randomBytes(32), + s: randomBytes(32), + } + const randomPubkey = randomBytes(65) + randomPubkey[0] = 0x04 // Ensure valid uncompressed format + + expect(() => + getYParity({ + messageHash, + signature: invalidSig, + publicKey: randomPubkey, + }), + ).toThrow() // Just check that it throws, don't check exact message + }) + }) + + describe('Real world scenarios', () => { + it('should handle EIP-7702 authorization signature', () => { + // Simulate the exact scenario from signAuthorization + const MAGIC = Buffer.from([0x05]) + + // This would normally use RLP.encode but we'll create a test message + const message = Buffer.concat([ + MAGIC, + Buffer.from('test_rlp_encoded_data', 'utf8'), + ]) + + const messageHash = Buffer.from(Hash.keccak256(message)) + const { signature, publicKey, recovery } = + createValidSignature(messageHash) + + // Test both Buffer format (as returned by device) + const yParity1 = getYParity({ + messageHash, + signature, + publicKey, + }) + expect(yParity1).toBe(recovery) + + // Test with hex string format (as might be used in API) + const yParity2 = getYParity({ + messageHash, + signature: { + r: `0x${signature.r.toString('hex')}`, + s: `0x${signature.s.toString('hex')}`, + }, + publicKey, + }) + expect(yParity2).toBe(recovery) + }) + + it('should return consistent y-parity for multiple calls with same data', () => { + const messageHash = randomBytes(32) + const { signature, publicKey } = createValidSignature(messageHash) + + const yParity1 = getYParity({ + messageHash, + signature, + publicKey, + }) + + const yParity2 = getYParity({ + messageHash, + signature, + publicKey, + }) + + expect(yParity1).toBe(yParity2) + }) + + it('should handle real signature that should return y-parity of 1', () => { + // Use a specific private key that we know produces recovery id 1 for a specific message + let foundYParityOne = false + + // Try multiple messages until we get one with y-parity 1 + for (let i = 0; i < 100; i++) { + const messageHash = Buffer.from( + Hash.keccak256(Buffer.from(`test message ${i}`)), + ) + const privateKey = Buffer.from( + '0101010101010101010101010101010101010101010101010101010101010101', + 'hex', + ) + + const sigObj = secp256k1.ecdsaSign(messageHash, privateKey) + + if (sigObj.recid === 1) { + const publicKey = secp256k1.publicKeyCreate(privateKey, false) + + const yParity = getYParity({ + messageHash, + signature: { + r: Buffer.from(sigObj.signature.slice(0, 32)), + s: Buffer.from(sigObj.signature.slice(32, 64)), + }, + publicKey: Buffer.from(publicKey), + }) + + expect(yParity).toBe(1) + foundYParityOne = true + break + } + } + + expect(foundYParityOne).toBe(true) + }) + }) }) describe('getV function', () => { - // Helper to create a valid signature - const createValidSignature = (messageHash: Buffer, privateKey?: Buffer) => { - // Use deterministic key if not provided - const privKey = - privateKey || - Buffer.from( - '0101010101010101010101010101010101010101010101010101010101010101', - 'hex', - ) - - const sigObj = secp256k1.ecdsaSign(messageHash, privKey) - const publicKey = secp256k1.publicKeyCreate(privKey, false) - - return { - sig: { - r: Buffer.from(sigObj.signature.slice(0, 32)), - s: Buffer.from(sigObj.signature.slice(32, 64)), - }, - pubkey: Buffer.from(publicKey), - recovery: sigObj.recid, - } - } - - it('should handle unsigned legacy transaction with valid signature', () => { - // A simple unsigned legacy transaction - const unsignedTxRLP = Buffer.from( - 'e9808504a817c800825208943535353535353535353535353535353535353535880de0b6b3a764000080', - 'hex', - ) - - // Hash the transaction - const hash = Buffer.from(Hash.keccak256(unsignedTxRLP)) - - // Create a valid signature for this hash - const resp = createValidSignature(hash) - - // Should return correct v value (27 or 28 for non-EIP155) - const v = getV(unsignedTxRLP, resp) - expect(v.toNumber()).toBe(27 + resp.recovery) - }) - - it('should throw error when pubkey does not match signature', () => { - // This is a signed legacy transaction - const signedTx = - '0xf86c0a8504a817c800825208943535353535353535353535353535353535353535880de0b6b3a76400008025a0134f5038e0e6a96741e17a82c8df13e9dc10c3b0e9e956cf7dcf21e1e3b73f9fa0638cf1b1f9dd5e6e8e6b9a8e6e8e6b9a8e6e8e6b9a8e6e8e6b9a8e6e8e6b9a8' - - const mockResp = { - sig: { - r: Buffer.from( - '134f5038e0e6a96741e17a82c8df13e9dc10c3b0e9e956cf7dcf21e1e3b73f9f', - 'hex', - ), - s: Buffer.from( - '638cf1b1f9dd5e6e8e6b9a8e6e8e6b9a8e6e8e6b9a8e6e8e6b9a8e6e8e6b9a8', - 'hex', - ), - }, - // This is a fake pubkey, so recovery will fail - pubkey: Buffer.from(`04${'1'.repeat(128)}`, 'hex'), - } - - expect(() => getV(signedTx, mockResp)).toThrow() - }) - - it('should throw error when signature is invalid', () => { - const txHex = - '0xe9808504a817c800825208943535353535353535353535353535353535353535880de0b6b3a764000080' - - const mockResp = { - sig: { - r: `0x${'1'.repeat(64)}`, // 32 bytes as hex string - s: `0x${'2'.repeat(64)}`, // 32 bytes as hex string - }, - pubkey: Buffer.from(`04${'1'.repeat(128)}`, 'hex'), - } - - expect(() => getV(txHex, mockResp)).toThrow() - }) + // Helper to create a valid signature + const createValidSignature = (messageHash: Buffer, privateKey?: Buffer) => { + // Use deterministic key if not provided + const privKey = + privateKey || + Buffer.from( + '0101010101010101010101010101010101010101010101010101010101010101', + 'hex', + ) + + const sigObj = secp256k1.ecdsaSign(messageHash, privKey) + const publicKey = secp256k1.publicKeyCreate(privKey, false) + + return { + sig: { + r: Buffer.from(sigObj.signature.slice(0, 32)), + s: Buffer.from(sigObj.signature.slice(32, 64)), + }, + pubkey: Buffer.from(publicKey), + recovery: sigObj.recid, + } + } + + it('should handle unsigned legacy transaction with valid signature', () => { + // A simple unsigned legacy transaction + const unsignedTxRLP = Buffer.from( + 'e9808504a817c800825208943535353535353535353535353535353535353535880de0b6b3a764000080', + 'hex', + ) + + // Hash the transaction + const hash = Buffer.from(Hash.keccak256(unsignedTxRLP)) + + // Create a valid signature for this hash + const resp = createValidSignature(hash) + + // Should return correct v value (27 or 28 for non-EIP155) + const v = getV(unsignedTxRLP, resp) + expect(v.toNumber()).toBe(27 + resp.recovery) + }) + + it('should throw error when pubkey does not match signature', () => { + // This is a signed legacy transaction + const signedTx = + '0xf86c0a8504a817c800825208943535353535353535353535353535353535353535880de0b6b3a76400008025a0134f5038e0e6a96741e17a82c8df13e9dc10c3b0e9e956cf7dcf21e1e3b73f9fa0638cf1b1f9dd5e6e8e6b9a8e6e8e6b9a8e6e8e6b9a8e6e8e6b9a8e6e8e6b9a8' + + const mockResp = { + sig: { + r: Buffer.from( + '134f5038e0e6a96741e17a82c8df13e9dc10c3b0e9e956cf7dcf21e1e3b73f9f', + 'hex', + ), + s: Buffer.from( + '638cf1b1f9dd5e6e8e6b9a8e6e8e6b9a8e6e8e6b9a8e6e8e6b9a8e6e8e6b9a8', + 'hex', + ), + }, + // This is a fake pubkey, so recovery will fail + pubkey: Buffer.from(`04${'1'.repeat(128)}`, 'hex'), + } + + expect(() => getV(signedTx, mockResp)).toThrow() + }) + + it('should throw error when signature is invalid', () => { + const txHex = + '0xe9808504a817c800825208943535353535353535353535353535353535353535880de0b6b3a764000080' + + const mockResp = { + sig: { + r: `0x${'1'.repeat(64)}`, // 32 bytes as hex string + s: `0x${'2'.repeat(64)}`, // 32 bytes as hex string + }, + pubkey: Buffer.from(`04${'1'.repeat(128)}`, 'hex'), + } + + expect(() => getV(txHex, mockResp)).toThrow() + }) }) diff --git a/packages/sdk/src/__test__/unit/validators.test.ts b/packages/sdk/src/__test__/unit/validators.test.ts index 0d2ed009..c930ca6e 100644 --- a/packages/sdk/src/__test__/unit/validators.test.ts +++ b/packages/sdk/src/__test__/unit/validators.test.ts @@ -1,304 +1,304 @@ import { normalizeToViemTransaction } from '../../ethereum' import { - validateAddKvRequest, - validateConnectRequest, - validateGetAddressesRequest, - validateGetKvRequest, - validateRemoveKvRequest, + validateAddKvRequest, + validateConnectRequest, + validateGetAddressesRequest, + validateGetKvRequest, + validateRemoveKvRequest, } from '../../functions' import { - isValid4ByteResponse, - isValidBlockExplorerResponse, + isValid4ByteResponse, + isValidBlockExplorerResponse, } from '../../shared/validators' import { - buildGetAddressesObject, - buildValidateConnectObject, - buildValidateRequestObject, + buildGetAddressesObject, + buildValidateConnectObject, + buildValidateRequestObject, } from '../utils/builders' describe('validators', () => { - describe('connect', () => { - test('should successfully validate', () => { - validateConnectRequest(buildValidateConnectObject()) - }) - - // NOTE: There aren't many possible error conditions because - // the Client constructor has lots of fallback values. However, - // we should validate that you can't set a null ephemeral pub. - test('should throw errors on validation failure', () => { - const req = buildValidateConnectObject({ name: '' }) - expect(() => { - req.client.ephemeralPub = null - }).toThrowError() - }) - }) - - describe('getAddresses', () => { - test('should successfully validate', () => { - const getAddressesBundle = buildGetAddressesObject({}) - validateGetAddressesRequest(getAddressesBundle) - }) - - test('encodeGetAddressesRequest should throw with invalid startPath', () => { - const startPath = [0x80000000 + 44, 0x80000000 + 60, 0, 0, 0, 0, 0] - const fwVersion = Buffer.from([0, 0, 0]) - const testEncodingFunction = () => - validateGetAddressesRequest( - buildGetAddressesObject({ startPath, fwVersion }), - ) - expect(testEncodingFunction).toThrowError() - }) - }) - - describe('KvRecords', () => { - describe('addKvRecords', () => { - test('should successfully validate', () => { - const validateAddKvBundle: any = buildValidateRequestObject({ - records: { key: 'value' }, - }) - validateAddKvRequest(validateAddKvBundle) - }) - - test('should throw errors on validation failure', () => { - const validateAddKvBundle: any = buildValidateRequestObject({}) - expect(() => validateAddKvRequest(validateAddKvBundle)).toThrowError() - }) - }) - - describe('getKvRecords', () => { - test('should successfully validate', () => { - const validateGetKvBundle: any = buildValidateRequestObject({ - n: 1, - type: 1, - start: 0, - }) - validateGetKvRequest(validateGetKvBundle) - }) - - test('should throw errors on validation failure', () => { - const validateGetKvBundle: any = buildValidateRequestObject({ n: 0 }) - expect(() => validateGetKvRequest(validateGetKvBundle)).toThrowError() - }) - }) - - describe('removeKvRecords', () => { - test('should successfully validate', () => { - const validateRemoveKvBundle: any = buildValidateRequestObject({ - ids: [1], - type: 1, - }) - validateRemoveKvRequest(validateRemoveKvBundle) - }) - - test('should throw errors on validation failure', () => { - const validateRemoveKvBundle: any = buildValidateRequestObject({}) - expect(() => - validateRemoveKvRequest(validateRemoveKvBundle), - ).toThrowError() - }) - }) - }) - - describe('abi data responses', () => { - describe('block explorers', () => { - test('should successfully validate etherscan data', () => { - const response: any = { - result: - '[{"inputs":[],"stateMutability":"nonpayable","type":"constructor"},{"stateMutability":"payable","type":"fallback"},{"inputs":[],"name":"implementation","outputs":[{"internalType":"address","name":"","type":"address"}],"stateMutability":"view","type":"function"},{"stateMutability":"payable","type":"receive"}]', - } - expect(isValidBlockExplorerResponse(response)).toBe(true) - }) - - test('should validate as false bad data', () => { - const response: any = { - result: - 'Max rate limit reached, please use API Key for higher rate limit', - } - expect(isValidBlockExplorerResponse(response)).toBe(false) - }) - }) - - describe('4byte', () => { - test('should successfully validate etherscan data', () => { - const response: any = { - results: [ - { - id: 447919, - created_at: '2021-12-25T13:54:33.120581Z', - text_signature: 'multicall(uint256,bytes[])', - hex_signature: '0x5ae401dc', - bytes_signature: 'test', - }, - ], - } - expect(isValid4ByteResponse(response)).toBe(true) - }) - - test('should validate as false bad data', () => { - const response: any = { - results: [], - } - expect(isValid4ByteResponse(response)).toBe(false) - }) - }) - }) - - describe('transaction validation', () => { - describe('EIP-7702 transactions', () => { - test('rejects missing fee fields', () => { - const tx = { - to: `0x${'1'.repeat(40)}`, - value: '1000000000000000000', - chainId: 1, - authorizationList: [ - { chainId: 1, address: `0x${'2'.repeat(40)}`, nonce: 0 }, - ], - gasPrice: '15000000000', - } - - expect(() => normalizeToViemTransaction(tx)).toThrow() - }) - }) - - describe('negative values', () => { - test('rejects negative value', () => { - const tx = { - to: `0x${'1'.repeat(40)}`, - value: -100, - gasPrice: '10000000000', - } - - expect(() => normalizeToViemTransaction(tx)).toThrow() - }) - - test('rejects negative nonce', () => { - const tx = { - to: `0x${'1'.repeat(40)}`, - value: '100', - gasPrice: '10000000000', - nonce: -1, - } - - expect(() => normalizeToViemTransaction(tx)).toThrow() - }) - - test('rejects negative gas price', () => { - const tx = { - to: `0x${'1'.repeat(40)}`, - value: '100', - gasPrice: -10, - } - - expect(() => normalizeToViemTransaction(tx)).toThrow() - }) - }) - - describe('invalid data types', () => { - test('rejects boolean data field', () => { - const tx = { - to: '0x1234567890123456789012345678901234567890', - value: true, - chainId: '0x1', - gasPrice: Number.NaN, - nonce: null, - data: false, - } - - expect(() => normalizeToViemTransaction(tx as any)).toThrow() - }) - }) - - describe('authorization list validation', () => { - test('rejects invalid authorization data', () => { - const tx = { - to: '0x1234567890123456789012345678901234567890', - value: '1000000000000000000', - chainId: 1, - maxFeePerGas: '20000000000', - maxPriorityFeePerGas: '2000000000', - authorizationList: [ - { - chainId: 'not-a-number', - address: '0x123', - nonce: undefined, - }, - ], - } - - expect(() => normalizeToViemTransaction(tx as any)).toThrow() - }) - }) - - describe('circular references', () => { - test('rejects circular references', () => { - const tx: any = { - to: '0x1234567890123456789012345678901234567890', - value: '1000000000000000000', - chainId: 1, - maxFeePerGas: '20000000000', - maxPriorityFeePerGas: '2000000000', - } - - tx.self = tx - tx.authorizationList = [tx] - - expect(() => normalizeToViemTransaction(tx)).toThrow() - }) - }) - - describe('gas field handling', () => { - test('gasLimit takes precedence over gas', () => { - const tx = { - to: '0x1234567890123456789012345678901234567890', - value: '1000000000000000000', - chainId: 1, - gasPrice: '15000000000', - gas: '50000', - gasLimit: '21000', - } - - const result = normalizeToViemTransaction(tx) - expect(result.gas).toBe(21000n) - }) - - test('accepts zero gas values', () => { - const tx = { - to: '0x1234567890123456789012345678901234567890', - value: '1000000000000000000', - chainId: 1, - gasPrice: '0', - gasLimit: '0', - } - - const result = normalizeToViemTransaction(tx) - expect(result.gas).toBe(0n) - expect(result.type).toBe('legacy') - expect((result as any).gasPrice).toBe(0n) - }) - }) - - describe('chainId validation', () => { - test('rejects zero chainId', () => { - const tx = { - to: '0x1234567890123456789012345678901234567890', - value: '1000000000000000000', - chainId: 0, - gasPrice: '15000000000', - } - - expect(() => normalizeToViemTransaction(tx)).toThrow() - }) - - test('rejects non-integer chainId', () => { - const tx = { - to: '0x1234567890123456789012345678901234567890', - value: '1000000000000000000', - chainId: 1.5, - gasPrice: '15000000000', - } - - expect(() => normalizeToViemTransaction(tx)).toThrow() - }) - }) - }) + describe('connect', () => { + test('should successfully validate', () => { + validateConnectRequest(buildValidateConnectObject()) + }) + + // NOTE: There aren't many possible error conditions because + // the Client constructor has lots of fallback values. However, + // we should validate that you can't set a null ephemeral pub. + test('should throw errors on validation failure', () => { + const req = buildValidateConnectObject({ name: '' }) + expect(() => { + req.client.ephemeralPub = null + }).toThrowError() + }) + }) + + describe('getAddresses', () => { + test('should successfully validate', () => { + const getAddressesBundle = buildGetAddressesObject({}) + validateGetAddressesRequest(getAddressesBundle) + }) + + test('encodeGetAddressesRequest should throw with invalid startPath', () => { + const startPath = [0x80000000 + 44, 0x80000000 + 60, 0, 0, 0, 0, 0] + const fwVersion = Buffer.from([0, 0, 0]) + const testEncodingFunction = () => + validateGetAddressesRequest( + buildGetAddressesObject({ startPath, fwVersion }), + ) + expect(testEncodingFunction).toThrowError() + }) + }) + + describe('KvRecords', () => { + describe('addKvRecords', () => { + test('should successfully validate', () => { + const validateAddKvBundle: any = buildValidateRequestObject({ + records: { key: 'value' }, + }) + validateAddKvRequest(validateAddKvBundle) + }) + + test('should throw errors on validation failure', () => { + const validateAddKvBundle: any = buildValidateRequestObject({}) + expect(() => validateAddKvRequest(validateAddKvBundle)).toThrowError() + }) + }) + + describe('getKvRecords', () => { + test('should successfully validate', () => { + const validateGetKvBundle: any = buildValidateRequestObject({ + n: 1, + type: 1, + start: 0, + }) + validateGetKvRequest(validateGetKvBundle) + }) + + test('should throw errors on validation failure', () => { + const validateGetKvBundle: any = buildValidateRequestObject({ n: 0 }) + expect(() => validateGetKvRequest(validateGetKvBundle)).toThrowError() + }) + }) + + describe('removeKvRecords', () => { + test('should successfully validate', () => { + const validateRemoveKvBundle: any = buildValidateRequestObject({ + ids: [1], + type: 1, + }) + validateRemoveKvRequest(validateRemoveKvBundle) + }) + + test('should throw errors on validation failure', () => { + const validateRemoveKvBundle: any = buildValidateRequestObject({}) + expect(() => + validateRemoveKvRequest(validateRemoveKvBundle), + ).toThrowError() + }) + }) + }) + + describe('abi data responses', () => { + describe('block explorers', () => { + test('should successfully validate etherscan data', () => { + const response: any = { + result: + '[{"inputs":[],"stateMutability":"nonpayable","type":"constructor"},{"stateMutability":"payable","type":"fallback"},{"inputs":[],"name":"implementation","outputs":[{"internalType":"address","name":"","type":"address"}],"stateMutability":"view","type":"function"},{"stateMutability":"payable","type":"receive"}]', + } + expect(isValidBlockExplorerResponse(response)).toBe(true) + }) + + test('should validate as false bad data', () => { + const response: any = { + result: + 'Max rate limit reached, please use API Key for higher rate limit', + } + expect(isValidBlockExplorerResponse(response)).toBe(false) + }) + }) + + describe('4byte', () => { + test('should successfully validate etherscan data', () => { + const response: any = { + results: [ + { + id: 447919, + created_at: '2021-12-25T13:54:33.120581Z', + text_signature: 'multicall(uint256,bytes[])', + hex_signature: '0x5ae401dc', + bytes_signature: 'test', + }, + ], + } + expect(isValid4ByteResponse(response)).toBe(true) + }) + + test('should validate as false bad data', () => { + const response: any = { + results: [], + } + expect(isValid4ByteResponse(response)).toBe(false) + }) + }) + }) + + describe('transaction validation', () => { + describe('EIP-7702 transactions', () => { + test('rejects missing fee fields', () => { + const tx = { + to: `0x${'1'.repeat(40)}`, + value: '1000000000000000000', + chainId: 1, + authorizationList: [ + { chainId: 1, address: `0x${'2'.repeat(40)}`, nonce: 0 }, + ], + gasPrice: '15000000000', + } + + expect(() => normalizeToViemTransaction(tx)).toThrow() + }) + }) + + describe('negative values', () => { + test('rejects negative value', () => { + const tx = { + to: `0x${'1'.repeat(40)}`, + value: -100, + gasPrice: '10000000000', + } + + expect(() => normalizeToViemTransaction(tx)).toThrow() + }) + + test('rejects negative nonce', () => { + const tx = { + to: `0x${'1'.repeat(40)}`, + value: '100', + gasPrice: '10000000000', + nonce: -1, + } + + expect(() => normalizeToViemTransaction(tx)).toThrow() + }) + + test('rejects negative gas price', () => { + const tx = { + to: `0x${'1'.repeat(40)}`, + value: '100', + gasPrice: -10, + } + + expect(() => normalizeToViemTransaction(tx)).toThrow() + }) + }) + + describe('invalid data types', () => { + test('rejects boolean data field', () => { + const tx = { + to: '0x1234567890123456789012345678901234567890', + value: true, + chainId: '0x1', + gasPrice: Number.NaN, + nonce: null, + data: false, + } + + expect(() => normalizeToViemTransaction(tx as any)).toThrow() + }) + }) + + describe('authorization list validation', () => { + test('rejects invalid authorization data', () => { + const tx = { + to: '0x1234567890123456789012345678901234567890', + value: '1000000000000000000', + chainId: 1, + maxFeePerGas: '20000000000', + maxPriorityFeePerGas: '2000000000', + authorizationList: [ + { + chainId: 'not-a-number', + address: '0x123', + nonce: undefined, + }, + ], + } + + expect(() => normalizeToViemTransaction(tx as any)).toThrow() + }) + }) + + describe('circular references', () => { + test('rejects circular references', () => { + const tx: any = { + to: '0x1234567890123456789012345678901234567890', + value: '1000000000000000000', + chainId: 1, + maxFeePerGas: '20000000000', + maxPriorityFeePerGas: '2000000000', + } + + tx.self = tx + tx.authorizationList = [tx] + + expect(() => normalizeToViemTransaction(tx)).toThrow() + }) + }) + + describe('gas field handling', () => { + test('gasLimit takes precedence over gas', () => { + const tx = { + to: '0x1234567890123456789012345678901234567890', + value: '1000000000000000000', + chainId: 1, + gasPrice: '15000000000', + gas: '50000', + gasLimit: '21000', + } + + const result = normalizeToViemTransaction(tx) + expect(result.gas).toBe(21000n) + }) + + test('accepts zero gas values', () => { + const tx = { + to: '0x1234567890123456789012345678901234567890', + value: '1000000000000000000', + chainId: 1, + gasPrice: '0', + gasLimit: '0', + } + + const result = normalizeToViemTransaction(tx) + expect(result.gas).toBe(0n) + expect(result.type).toBe('legacy') + expect((result as any).gasPrice).toBe(0n) + }) + }) + + describe('chainId validation', () => { + test('rejects zero chainId', () => { + const tx = { + to: '0x1234567890123456789012345678901234567890', + value: '1000000000000000000', + chainId: 0, + gasPrice: '15000000000', + } + + expect(() => normalizeToViemTransaction(tx)).toThrow() + }) + + test('rejects non-integer chainId', () => { + const tx = { + to: '0x1234567890123456789012345678901234567890', + value: '1000000000000000000', + chainId: 1.5, + gasPrice: '15000000000', + } + + expect(() => normalizeToViemTransaction(tx)).toThrow() + }) + }) + }) }) diff --git a/packages/sdk/src/__test__/utils/__test__/builders.test.ts b/packages/sdk/src/__test__/utils/__test__/builders.test.ts index 97f38f54..0e6504f9 100644 --- a/packages/sdk/src/__test__/utils/__test__/builders.test.ts +++ b/packages/sdk/src/__test__/utils/__test__/builders.test.ts @@ -1,13 +1,13 @@ import { buildEvmReq, buildRandomVectors, getFwVersionsList } from '../builders' describe('building', () => { - test('should test client', () => { - expect(getFwVersionsList()).toMatchSnapshot() - }) + test('should test client', () => { + expect(getFwVersionsList()).toMatchSnapshot() + }) - test('RANDOM_VEC', () => { - const RANDOM_VEC = buildRandomVectors(10) - expect(RANDOM_VEC).toMatchInlineSnapshot(` + test('RANDOM_VEC', () => { + const RANDOM_VEC = buildRandomVectors(10) + expect(RANDOM_VEC).toMatchInlineSnapshot(` [ "9f2c1f8", "334e3bf5", @@ -21,15 +21,15 @@ describe('building', () => { "2851e10c", ] `) - }) + }) - test('buildEvmReq', () => { - const testObj = buildEvmReq({ - common: 'test', - data: { payload: 'test' }, - txData: { data: 'test', type: undefined }, - }) - expect(testObj).toMatchInlineSnapshot(` + test('buildEvmReq', () => { + const testObj = buildEvmReq({ + common: 'test', + data: { payload: 'test' }, + txData: { data: 'test', type: undefined }, + }) + expect(testObj).toMatchInlineSnapshot(` { "common": "test", "data": { @@ -57,5 +57,5 @@ describe('building', () => { }, } `) - }) + }) }) diff --git a/packages/sdk/src/__test__/utils/__test__/serializers.test.ts b/packages/sdk/src/__test__/utils/__test__/serializers.test.ts index e91cc630..8e9c5582 100644 --- a/packages/sdk/src/__test__/utils/__test__/serializers.test.ts +++ b/packages/sdk/src/__test__/utils/__test__/serializers.test.ts @@ -1,20 +1,20 @@ import { - deserializeObjectWithBuffers, - serializeObjectWithBuffers, + deserializeObjectWithBuffers, + serializeObjectWithBuffers, } from '../serializers' describe('serializers', () => { - test('serialize obj', () => { - const obj = { - a: 1, - b: Buffer.from('test'), - c: { - d: 2, - e: Buffer.from('test'), - }, - } - const serialized = serializeObjectWithBuffers(obj) - expect(serialized).toMatchInlineSnapshot(` + test('serialize obj', () => { + const obj = { + a: 1, + b: Buffer.from('test'), + c: { + d: 2, + e: Buffer.from('test'), + }, + } + const serialized = serializeObjectWithBuffers(obj) + expect(serialized).toMatchInlineSnapshot(` { "a": 1, "b": { @@ -30,26 +30,26 @@ describe('serializers', () => { }, } `) - }) + }) - test('deserialize obj', () => { - const obj = { - a: 1, - b: { - isBuffer: true, - value: '74657374', - }, - c: { - d: 2, - e: { - isBuffer: true, - value: '74657374', - }, - }, - } + test('deserialize obj', () => { + const obj = { + a: 1, + b: { + isBuffer: true, + value: '74657374', + }, + c: { + d: 2, + e: { + isBuffer: true, + value: '74657374', + }, + }, + } - const serialized = deserializeObjectWithBuffers(obj) - expect(serialized).toMatchInlineSnapshot(` + const serialized = deserializeObjectWithBuffers(obj) + expect(serialized).toMatchInlineSnapshot(` { "a": 1, "b": { @@ -75,5 +75,5 @@ describe('serializers', () => { }, } `) - }) + }) }) diff --git a/packages/sdk/src/__test__/utils/builders.ts b/packages/sdk/src/__test__/utils/builders.ts index 1001a60c..76b2e592 100644 --- a/packages/sdk/src/__test__/utils/builders.ts +++ b/packages/sdk/src/__test__/utils/builders.ts @@ -11,346 +11,346 @@ import { randomBytes } from '../../util' import { MSG_PAYLOAD_METADATA_SZ } from './constants' import { getN, getPrng } from './getters' import { - BTC_PURPOSE_P2PKH, - ETH_COIN, - buildRandomEip712Object, - getTestVectors, + BTC_PURPOSE_P2PKH, + ETH_COIN, + buildRandomEip712Object, + getTestVectors, } from './helpers' const prng = getPrng() export const getFwVersionsList = () => { - const arr: number[][] = [] - Array.from({ length: 1 }, (x, i) => { - Array.from({ length: 10 }, (y, j) => { - Array.from({ length: 5 }, (z, k) => { - arr.push([i, j + 10, k]) - }) - }) - }) - return arr + const arr: number[][] = [] + Array.from({ length: 1 }, (x, i) => { + Array.from({ length: 10 }, (y, j) => { + Array.from({ length: 5 }, (z, k) => { + arr.push([i, j + 10, k]) + }) + }) + }) + return arr } export const buildFirmwareConstants = (...overrides: any) => { - return { - abiCategorySz: 32, - abiMaxRmv: 200, - addrFlagsAllowed: true, - allowBtcLegacyAndSegwitAddrs: true, - allowedEthTxTypes: [1, 2], - contractDeployKey: '0x08002e0fec8e6acf00835f43c9764f7364fa3f42', - eip712MaxTypeParams: 36, - eip712Supported: true, - ethMaxDataSz: 1519, - ethMaxGasPrice: 20000000000000, - ethMaxMsgSz: 1540, - ethMsgPreHashAllowed: true, - extraDataFrameSz: 1500, - extraDataMaxFrames: 1, - genericSigning: { - baseReqSz: 1552, - baseDataSz: 1519, - hashTypes: { NONE: 0, KECCAK256: 1, SHA256: 2 }, - curveTypes: { SECP256K1: 0, ED25519: 1 }, - encodingTypes: { NONE: 1, SOLANA: 2, EVM: 4 }, - calldataDecoding: { reserved: 2895728, maxSz: 1024 }, - }, - getAddressFlags: [4, 3], - kvActionMaxNum: 10, - kvActionsAllowed: true, - kvKeyMaxStrSz: 63, - kvRemoveMaxNum: 100, - kvValMaxStrSz: 63, - maxDecoderBufSz: 1600, - personalSignHeaderSz: 72, - prehashAllowed: true, - reqMaxDataSz: 1678, - varAddrPathSzAllowed: true, - ...overrides, - } as FirmwareConstants + return { + abiCategorySz: 32, + abiMaxRmv: 200, + addrFlagsAllowed: true, + allowBtcLegacyAndSegwitAddrs: true, + allowedEthTxTypes: [1, 2], + contractDeployKey: '0x08002e0fec8e6acf00835f43c9764f7364fa3f42', + eip712MaxTypeParams: 36, + eip712Supported: true, + ethMaxDataSz: 1519, + ethMaxGasPrice: 20000000000000, + ethMaxMsgSz: 1540, + ethMsgPreHashAllowed: true, + extraDataFrameSz: 1500, + extraDataMaxFrames: 1, + genericSigning: { + baseReqSz: 1552, + baseDataSz: 1519, + hashTypes: { NONE: 0, KECCAK256: 1, SHA256: 2 }, + curveTypes: { SECP256K1: 0, ED25519: 1 }, + encodingTypes: { NONE: 1, SOLANA: 2, EVM: 4 }, + calldataDecoding: { reserved: 2895728, maxSz: 1024 }, + }, + getAddressFlags: [4, 3], + kvActionMaxNum: 10, + kvActionsAllowed: true, + kvKeyMaxStrSz: 63, + kvRemoveMaxNum: 100, + kvValMaxStrSz: 63, + maxDecoderBufSz: 1600, + personalSignHeaderSz: 72, + prehashAllowed: true, + reqMaxDataSz: 1678, + varAddrPathSzAllowed: true, + ...overrides, + } as FirmwareConstants } export const buildWallet = (overrides?) => ({ - uid: Buffer.from( - '162b56efe561c12bc93f703dc7026b3ec3d53923270c9259e2b08015fb9defd2', - 'hex', - ), - capabilities: 1, - external: true, - ...overrides, + uid: Buffer.from( + '162b56efe561c12bc93f703dc7026b3ec3d53923270c9259e2b08015fb9defd2', + 'hex', + ), + capabilities: 1, + external: true, + ...overrides, }) export const buildGetAddressesObject = (overrides?) => ({ - startPath: [0x80000000 + 44, 0x80000000 + 60, 0x80000000, 0, 0], - n: 1, - flag: 1, - fwConstants: buildFirmwareConstants(), - wallet: buildWallet(), - ...overrides, + startPath: [0x80000000 + 44, 0x80000000 + 60, 0x80000000, 0, 0], + n: 1, + flag: 1, + fwConstants: buildFirmwareConstants(), + wallet: buildWallet(), + ...overrides, }) export const buildSignObject = (fwVersion, overrides?) => { - const fwConstants = getFwVersionConst(fwVersion) - return { - data: { - to: '0xc0c8f96C2fE011cc96770D2e37CfbfeAFB585F0e', - from: '0xc0c8f96C2fE011cc96770D2e37CfbfeAFB585F0e', - value: 0x80000000, - data: '0x0', - signerPath: [0x80000000 + 44, 0x80000000 + 60, 0x80000000, 0, 0], - nonce: 0x80000000, - gasLimit: 0x80000000, - gasPrice: 0x80000000, - }, - currency: CURRENCIES.ETH as Currency, - fwConstants, - ...overrides, - } + const fwConstants = getFwVersionConst(fwVersion) + return { + data: { + to: '0xc0c8f96C2fE011cc96770D2e37CfbfeAFB585F0e', + from: '0xc0c8f96C2fE011cc96770D2e37CfbfeAFB585F0e', + value: 0x80000000, + data: '0x0', + signerPath: [0x80000000 + 44, 0x80000000 + 60, 0x80000000, 0, 0], + nonce: 0x80000000, + gasLimit: 0x80000000, + gasPrice: 0x80000000, + }, + currency: CURRENCIES.ETH as Currency, + fwConstants, + ...overrides, + } } export const buildSharedSecret = () => { - return Buffer.from([ - 89, 60, 130, 80, 168, 252, 34, 136, 230, 71, 230, 158, 51, 13, 239, 237, 6, - 246, 71, 232, 232, 175, 193, 106, 106, 185, 38, 1, 163, 14, 225, 101, - ]) + return Buffer.from([ + 89, 60, 130, 80, 168, 252, 34, 136, 230, 71, 230, 158, 51, 13, 239, 237, 6, + 246, 71, 232, 232, 175, 193, 106, 106, 185, 38, 1, 163, 14, 225, 101, + ]) } export const getNumIter = (n: number | string | undefined = getN()) => - n ? Number.parseInt(`${n}`) : 5 + n ? Number.parseInt(`${n}`) : 5 /** Generate a bunch of random test vectors using the PRNG */ export const buildRandomVectors = (n: number | string | undefined = getN()) => { - const numIter = getNumIter(n) + const numIter = getNumIter(n) - // Generate a bunch of random test vectors using the PRNG - const RANDOM_VEC: any[] = [] - for (let i = 0; i < numIter; i++) { - RANDOM_VEC.push(Math.floor(1000000000 * prng.quick()).toString(16)) - } - return RANDOM_VEC + // Generate a bunch of random test vectors using the PRNG + const RANDOM_VEC: any[] = [] + for (let i = 0; i < numIter; i++) { + RANDOM_VEC.push(Math.floor(1000000000 * prng.quick()).toString(16)) + } + return RANDOM_VEC } export const DEFAULT_SIGNER = [ - BTC_PURPOSE_P2PKH, - ETH_COIN, - HARDENED_OFFSET, - 0, - 0, + BTC_PURPOSE_P2PKH, + ETH_COIN, + HARDENED_OFFSET, + 0, + 0, ] export const buildTx = (data: `0x${string}` = '0xdeadbeef') => { - return createTx( - { - type: 2, - maxFeePerGas: 1200000000, - maxPriorityFeePerGas: 1200000000, - nonce: 0, - gasLimit: 50000, - to: '0xe242e54155b1abc71fc118065270cecaaf8b7768', - value: 100, - data, - }, - { - common: new Common({ - chain: Mainnet, - hardfork: Hardfork.London, - }), - }, - ) + return createTx( + { + type: 2, + maxFeePerGas: 1200000000, + maxPriorityFeePerGas: 1200000000, + nonce: 0, + gasLimit: 50000, + to: '0xe242e54155b1abc71fc118065270cecaaf8b7768', + value: 100, + data, + }, + { + common: new Common({ + chain: Mainnet, + hardfork: Hardfork.London, + }), + }, + ) } export const buildEthSignRequest = async ( - client: Client, - txDataOverrides?: any, + client: Client, + txDataOverrides?: any, ): Promise => { - if (client.getFwVersion()?.major === 0 && client.getFwVersion()?.minor < 15) { - console.warn('Please update firmware. Skipping ETH signing tests.') - return - } + if (client.getFwVersion()?.major === 0 && client.getFwVersion()?.minor < 15) { + console.warn('Please update firmware. Skipping ETH signing tests.') + return + } - const fwConstants = client.getFwConstants() - const signerPath = [BTC_PURPOSE_P2PKH, ETH_COIN, HARDENED_OFFSET, 0, 0] - const common = new Common({ - chain: Mainnet, - hardfork: Hardfork.London, - }) - const txData = { - type: 2, - maxFeePerGas: 1200000000, - maxPriorityFeePerGas: 1200000000, - nonce: 0, - gasLimit: 50000, - to: '0xe242e54155b1abc71fc118065270cecaaf8b7768', - value: 1000000000000, - data: '0x17e914679b7e160613be4f8c2d3203d236286d74eb9192f6d6f71b9118a42bb033ccd8e8', - ...txDataOverrides, - } - const tx = createTx(txData, { common }) - const req = { - data: { - signerPath, - payload: tx.getMessageToSign(), - curveType: Constants.SIGNING.CURVES.SECP256K1, - hashType: Constants.SIGNING.HASHES.KECCAK256, - encodingType: Constants.SIGNING.ENCODINGS.EVM, - }, - } - const maxDataSz = - fwConstants.ethMaxDataSz + - fwConstants.extraDataMaxFrames * fwConstants.extraDataFrameSz - return { - fwConstants, - signerPath, - common, - txData, - tx, - req, - maxDataSz, - } + const fwConstants = client.getFwConstants() + const signerPath = [BTC_PURPOSE_P2PKH, ETH_COIN, HARDENED_OFFSET, 0, 0] + const common = new Common({ + chain: Mainnet, + hardfork: Hardfork.London, + }) + const txData = { + type: 2, + maxFeePerGas: 1200000000, + maxPriorityFeePerGas: 1200000000, + nonce: 0, + gasLimit: 50000, + to: '0xe242e54155b1abc71fc118065270cecaaf8b7768', + value: 1000000000000, + data: '0x17e914679b7e160613be4f8c2d3203d236286d74eb9192f6d6f71b9118a42bb033ccd8e8', + ...txDataOverrides, + } + const tx = createTx(txData, { common }) + const req = { + data: { + signerPath, + payload: tx.getMessageToSign(), + curveType: Constants.SIGNING.CURVES.SECP256K1, + hashType: Constants.SIGNING.HASHES.KECCAK256, + encodingType: Constants.SIGNING.ENCODINGS.EVM, + }, + } + const maxDataSz = + fwConstants.ethMaxDataSz + + fwConstants.extraDataMaxFrames * fwConstants.extraDataFrameSz + return { + fwConstants, + signerPath, + common, + txData, + tx, + req, + maxDataSz, + } } export const buildTxReq = (tx: TypedTransaction) => ({ - data: { - signerPath: DEFAULT_SIGNER, - payload: tx.getMessageToSign(), - curveType: Constants.SIGNING.CURVES.SECP256K1, - hashType: Constants.SIGNING.HASHES.KECCAK256, - encodingType: Constants.SIGNING.ENCODINGS.EVM, - }, + data: { + signerPath: DEFAULT_SIGNER, + payload: tx.getMessageToSign(), + curveType: Constants.SIGNING.CURVES.SECP256K1, + hashType: Constants.SIGNING.HASHES.KECCAK256, + encodingType: Constants.SIGNING.ENCODINGS.EVM, + }, }) export const buildMsgReq = ( - payload = 'hello ethereum', - protocol: 'signPersonal' | 'eip712' = 'signPersonal', + payload = 'hello ethereum', + protocol: 'signPersonal' | 'eip712' = 'signPersonal', ) => ({ - currency: 'ETH_MSG' as const, - data: { - signerPath: DEFAULT_SIGNER, - protocol, - payload, - curveType: Constants.SIGNING.CURVES.SECP256K1, - hashType: Constants.SIGNING.HASHES.KECCAK256, - }, + currency: 'ETH_MSG' as const, + data: { + signerPath: DEFAULT_SIGNER, + protocol, + payload, + curveType: Constants.SIGNING.CURVES.SECP256K1, + hashType: Constants.SIGNING.HASHES.KECCAK256, + }, }) export const buildEvmReq = (overrides?: { - data?: any - txData?: any - common?: any + data?: any + txData?: any + common?: any }) => { - let chainInfo = null - if (overrides?.common) { - chainInfo = overrides.common - } else { - chainInfo = new Common({ chain: Mainnet, hardfork: Hardfork.London }) - } - const req = { - data: { - signerPath: DEFAULT_SIGNER, - curveType: Constants.SIGNING.CURVES.SECP256K1, - hashType: Constants.SIGNING.HASHES.KECCAK256, - encodingType: Constants.SIGNING.ENCODINGS.EVM, - payload: null, - ...overrides?.data, - }, - txData: { - type: 2, - maxFeePerGas: 1200000000, - maxPriorityFeePerGas: 1200000000, - nonce: 0, - gasLimit: 50000, - to: '0xe242e54155b1abc71fc118065270cecaaf8b7768', - value: 100, - data: '0xdeadbeef', - ...overrides?.txData, - }, - common: chainInfo, - } - return req + let chainInfo = null + if (overrides?.common) { + chainInfo = overrides.common + } else { + chainInfo = new Common({ chain: Mainnet, hardfork: Hardfork.London }) + } + const req = { + data: { + signerPath: DEFAULT_SIGNER, + curveType: Constants.SIGNING.CURVES.SECP256K1, + hashType: Constants.SIGNING.HASHES.KECCAK256, + encodingType: Constants.SIGNING.ENCODINGS.EVM, + payload: null, + ...overrides?.data, + }, + txData: { + type: 2, + maxFeePerGas: 1200000000, + maxPriorityFeePerGas: 1200000000, + nonce: 0, + gasLimit: 50000, + to: '0xe242e54155b1abc71fc118065270cecaaf8b7768', + value: 100, + data: '0xdeadbeef', + ...overrides?.txData, + }, + common: chainInfo, + } + return req } export const buildEncDefs = (vectors: any) => { - const encDefs = vectors.canonicalNames.map((name: string) => { - // For each canonical name, we need to RLP encode just the name - return RLP.encode([name]) - }) + const encDefs = vectors.canonicalNames.map((name: string) => { + // For each canonical name, we need to RLP encode just the name + return RLP.encode([name]) + }) - // The calldata is already in hex format, we just need to ensure it has 0x prefix - const encDefsCalldata = vectors.canonicalNames.map( - (_: string, idx: number) => { - const calldata = `0x${idx.toString(16).padStart(8, '0')}` - return calldata - }, - ) + // The calldata is already in hex format, we just need to ensure it has 0x prefix + const encDefsCalldata = vectors.canonicalNames.map( + (_: string, idx: number) => { + const calldata = `0x${idx.toString(16).padStart(8, '0')}` + return calldata + }, + ) - return { encDefs, encDefsCalldata } + return { encDefs, encDefsCalldata } } export function buildRandomMsg(type, client: Client) { - function randInt(n: number) { - return Math.floor(n * prng.quick()) - } + function randInt(n: number) { + return Math.floor(n * prng.quick()) + } - if (type === 'signPersonal') { - // A random string will do - const isHexStr = randInt(2) > 0 - const fwConstants = client.getFwConstants() - const L = randInt(fwConstants.ethMaxDataSz - MSG_PAYLOAD_METADATA_SZ) - if (isHexStr) return `0x${randomBytes(L).toString('hex')}` - // Get L hex bytes (represented with a string with 2*L chars) - else return randomWords({ exactly: L, join: ' ' }).slice(0, L) // Get L ASCII characters (bytes) - } else if (type === 'eip712') { - return buildRandomEip712Object(randInt) - } + if (type === 'signPersonal') { + // A random string will do + const isHexStr = randInt(2) > 0 + const fwConstants = client.getFwConstants() + const L = randInt(fwConstants.ethMaxDataSz - MSG_PAYLOAD_METADATA_SZ) + if (isHexStr) return `0x${randomBytes(L).toString('hex')}` + // Get L hex bytes (represented with a string with 2*L chars) + else return randomWords({ exactly: L, join: ' ' }).slice(0, L) // Get L ASCII characters (bytes) + } else if (type === 'eip712') { + return buildRandomEip712Object(randInt) + } } export function buildEthMsgReq( - payload: any, - protocol: 'signPersonal' | 'eip712', - signerPath = [ - BTC_PURPOSE_P2PKH, - ETH_COIN, - HARDENED_OFFSET, - 0, - 0, - ] as SigningPath, + payload: any, + protocol: 'signPersonal' | 'eip712', + signerPath = [ + BTC_PURPOSE_P2PKH, + ETH_COIN, + HARDENED_OFFSET, + 0, + 0, + ] as SigningPath, ): SignRequestParams { - return { - currency: CURRENCIES.ETH_MSG, - data: { - signerPath, - curveType: Constants.SIGNING.CURVES.SECP256K1, - hashType: Constants.SIGNING.HASHES.KECCAK256, - payload, - protocol, - }, - } + return { + currency: CURRENCIES.ETH_MSG, + data: { + signerPath, + curveType: Constants.SIGNING.CURVES.SECP256K1, + hashType: Constants.SIGNING.HASHES.KECCAK256, + payload, + protocol, + }, + } } export const buildValidateConnectObject = (overrides?) => ({ - deviceId: 'test', - key: 'test', - baseUrl: 'https://www.test.com', - ...overrides, + deviceId: 'test', + key: 'test', + baseUrl: 'https://www.test.com', + ...overrides, }) export const buildValidateRequestObject = (overrides?) => { - const fwConstants = buildFirmwareConstants() - return { - fwConstants, - ...overrides, - } + const fwConstants = buildFirmwareConstants() + return { + fwConstants, + ...overrides, + } } // Most of the endpoint validators (for encrypted requests) // will require a connected client instance. export function buildMockConnectedClient(opts) { - const _stateData = JSON.parse(getTestVectors().dehydratedClientState) - const stateData = { - ..._stateData, - ...opts, - } - return new Client({ - stateData: JSON.stringify(stateData), - }) + const _stateData = JSON.parse(getTestVectors().dehydratedClientState) + const stateData = { + ..._stateData, + ...opts, + } + return new Client({ + stateData: JSON.stringify(stateData), + }) } diff --git a/packages/sdk/src/__test__/utils/determinism.ts b/packages/sdk/src/__test__/utils/determinism.ts index 8e493f02..762eb2ed 100644 --- a/packages/sdk/src/__test__/utils/determinism.ts +++ b/packages/sdk/src/__test__/utils/determinism.ts @@ -11,67 +11,67 @@ import { ethPersonalSignMsg, getSigStr } from './helpers' import { TEST_SEED } from './testConstants' export async function testUniformSigs( - payload: any, - tx: TypedTransaction, - client: Client, + payload: any, + tx: TypedTransaction, + client: Client, ) { - const tx1Resp = await client.sign(payload) - const tx2Resp = await client.sign(payload) - const tx3Resp = await client.sign(payload) - const tx4Resp = await client.sign(payload) - const tx5Resp = await client.sign(payload) - // Check sig 1 - expect(getSigStr(tx1Resp, tx)).toEqual(getSigStr(tx2Resp, tx)) - expect(getSigStr(tx1Resp, tx)).toEqual(getSigStr(tx3Resp, tx)) - expect(getSigStr(tx1Resp, tx)).toEqual(getSigStr(tx4Resp, tx)) - expect(getSigStr(tx1Resp, tx)).toEqual(getSigStr(tx5Resp, tx)) - // Check sig 2 - expect(getSigStr(tx2Resp, tx)).toEqual(getSigStr(tx1Resp, tx)) - expect(getSigStr(tx2Resp, tx)).toEqual(getSigStr(tx3Resp, tx)) - expect(getSigStr(tx2Resp, tx)).toEqual(getSigStr(tx4Resp, tx)) - expect(getSigStr(tx2Resp, tx)).toEqual(getSigStr(tx5Resp, tx)) - // Check sig 3 - expect(getSigStr(tx3Resp, tx)).toEqual(getSigStr(tx1Resp, tx)) - expect(getSigStr(tx3Resp, tx)).toEqual(getSigStr(tx2Resp, tx)) - expect(getSigStr(tx3Resp, tx)).toEqual(getSigStr(tx4Resp, tx)) - expect(getSigStr(tx3Resp, tx)).toEqual(getSigStr(tx5Resp, tx)) - // Check sig 4 - expect(getSigStr(tx4Resp, tx)).toEqual(getSigStr(tx1Resp, tx)) - expect(getSigStr(tx4Resp, tx)).toEqual(getSigStr(tx2Resp, tx)) - expect(getSigStr(tx4Resp, tx)).toEqual(getSigStr(tx3Resp, tx)) - expect(getSigStr(tx4Resp, tx)).toEqual(getSigStr(tx5Resp, tx)) - // Check sig 5 - expect(getSigStr(tx5Resp, tx)).toEqual(getSigStr(tx1Resp, tx)) - expect(getSigStr(tx5Resp, tx)).toEqual(getSigStr(tx2Resp, tx)) - expect(getSigStr(tx5Resp, tx)).toEqual(getSigStr(tx3Resp, tx)) - expect(getSigStr(tx5Resp, tx)).toEqual(getSigStr(tx4Resp, tx)) + const tx1Resp = await client.sign(payload) + const tx2Resp = await client.sign(payload) + const tx3Resp = await client.sign(payload) + const tx4Resp = await client.sign(payload) + const tx5Resp = await client.sign(payload) + // Check sig 1 + expect(getSigStr(tx1Resp, tx)).toEqual(getSigStr(tx2Resp, tx)) + expect(getSigStr(tx1Resp, tx)).toEqual(getSigStr(tx3Resp, tx)) + expect(getSigStr(tx1Resp, tx)).toEqual(getSigStr(tx4Resp, tx)) + expect(getSigStr(tx1Resp, tx)).toEqual(getSigStr(tx5Resp, tx)) + // Check sig 2 + expect(getSigStr(tx2Resp, tx)).toEqual(getSigStr(tx1Resp, tx)) + expect(getSigStr(tx2Resp, tx)).toEqual(getSigStr(tx3Resp, tx)) + expect(getSigStr(tx2Resp, tx)).toEqual(getSigStr(tx4Resp, tx)) + expect(getSigStr(tx2Resp, tx)).toEqual(getSigStr(tx5Resp, tx)) + // Check sig 3 + expect(getSigStr(tx3Resp, tx)).toEqual(getSigStr(tx1Resp, tx)) + expect(getSigStr(tx3Resp, tx)).toEqual(getSigStr(tx2Resp, tx)) + expect(getSigStr(tx3Resp, tx)).toEqual(getSigStr(tx4Resp, tx)) + expect(getSigStr(tx3Resp, tx)).toEqual(getSigStr(tx5Resp, tx)) + // Check sig 4 + expect(getSigStr(tx4Resp, tx)).toEqual(getSigStr(tx1Resp, tx)) + expect(getSigStr(tx4Resp, tx)).toEqual(getSigStr(tx2Resp, tx)) + expect(getSigStr(tx4Resp, tx)).toEqual(getSigStr(tx3Resp, tx)) + expect(getSigStr(tx4Resp, tx)).toEqual(getSigStr(tx5Resp, tx)) + // Check sig 5 + expect(getSigStr(tx5Resp, tx)).toEqual(getSigStr(tx1Resp, tx)) + expect(getSigStr(tx5Resp, tx)).toEqual(getSigStr(tx2Resp, tx)) + expect(getSigStr(tx5Resp, tx)).toEqual(getSigStr(tx3Resp, tx)) + expect(getSigStr(tx5Resp, tx)).toEqual(getSigStr(tx4Resp, tx)) } export function deriveAddress(seed: Buffer, path: SigningPath) { - const bip32 = BIP32Factory(ecc) - const wallet = bip32.fromSeed(seed) - const priv = wallet.derivePath(getPathStr(path)).privateKey - return `0x${privateToAddress(priv).toString('hex')}` + const bip32 = BIP32Factory(ecc) + const wallet = bip32.fromSeed(seed) + const priv = wallet.derivePath(getPathStr(path)).privateKey + return `0x${privateToAddress(priv).toString('hex')}` } export function signPersonalJS(_msg: string, path: SigningPath) { - const bip32 = BIP32Factory(ecc) - const wallet = bip32.fromSeed(TEST_SEED) - const priv = wallet.derivePath(getPathStr(path)).privateKey - const msg = ethPersonalSignMsg(_msg) - const hash = Buffer.from(Hash.keccak256(Buffer.from(msg))) - const sig = ecsign(hash, priv) - const v = (sig.v - 27).toString(16).padStart(2, '0') - return `${sig.r.toString('hex')}${sig.s.toString('hex')}${v}` + const bip32 = BIP32Factory(ecc) + const wallet = bip32.fromSeed(TEST_SEED) + const priv = wallet.derivePath(getPathStr(path)).privateKey + const msg = ethPersonalSignMsg(_msg) + const hash = Buffer.from(Hash.keccak256(Buffer.from(msg))) + const sig = ecsign(hash, priv) + const v = (sig.v - 27).toString(16).padStart(2, '0') + return `${sig.r.toString('hex')}${sig.s.toString('hex')}${v}` } export function signEip712JS(payload: any, path: SigningPath) { - const bip32 = BIP32Factory(ecc) - const wallet = bip32.fromSeed(TEST_SEED) - const priv = wallet.derivePath(getPathStr(path)).privateKey - // Calculate the EIP712 hash using the same method as the SDK validation - const hash = TypedDataUtils.eip712Hash(payload, SignTypedDataVersion.V4) - const sig = ecsign(Buffer.from(hash), priv) - const v = (sig.v - 27).toString(16).padStart(2, '0') - return `${sig.r.toString('hex')}${sig.s.toString('hex')}${v}` + const bip32 = BIP32Factory(ecc) + const wallet = bip32.fromSeed(TEST_SEED) + const priv = wallet.derivePath(getPathStr(path)).privateKey + // Calculate the EIP712 hash using the same method as the SDK validation + const hash = TypedDataUtils.eip712Hash(payload, SignTypedDataVersion.V4) + const sig = ecsign(Buffer.from(hash), priv) + const v = (sig.v - 27).toString(16).padStart(2, '0') + return `${sig.r.toString('hex')}${sig.s.toString('hex')}${v}` } diff --git a/packages/sdk/src/__test__/utils/ethers.ts b/packages/sdk/src/__test__/utils/ethers.ts index 5e4fa622..c8a14bb9 100644 --- a/packages/sdk/src/__test__/utils/ethers.ts +++ b/packages/sdk/src/__test__/utils/ethers.ts @@ -1,23 +1,23 @@ const EVM_TYPES = [ - null, - 'address', - 'bool', - 'uint', - 'int', - 'bytes', - 'string', - 'tuple', + null, + 'address', + 'bool', + 'uint', + 'int', + 'bytes', + 'string', + 'tuple', ] export function convertDecoderToEthers(def: unknown[]) { - const converted = getConvertedDef(def) - const types: any[] = [] - const data: any[] = [] - converted.forEach((i: any) => { - types.push(i.type) - data.push(i.data) - }) - return { types, data } + const converted = getConvertedDef(def) + const types: any[] = [] + const data: any[] = [] + converted.forEach((i: any) => { + types.push(i.type) + data.push(i.data) + }) + return { types, data } } // Convert an encoded def into a combination of ethers-compatable @@ -25,128 +25,128 @@ export function convertDecoderToEthers(def: unknown[]) { // doesn't matter much for these tests, which mainly just test // structure of the definitions function getConvertedDef(def: unknown[]) { - const converted: { type: string | null; data: unknown }[] = [] - def.forEach((param: unknown) => { - const p = param as { toString: (fmt: string) => string }[] - const arrSzs = p[3] as { toString: (fmt: string) => string }[] - const evmType = EVM_TYPES[Number.parseInt(p[1].toString('hex'), 16)] - let type = evmType - const numBytes = Number.parseInt(p[2].toString('hex'), 16) - if (numBytes > 0) { - type = `${type}${numBytes * 8}` - } - // Handle tuples by recursively generating data - let tupleData: unknown[] | undefined - if (evmType === 'tuple') { - tupleData = [] - type = `${type}(` - const tupleDef = getConvertedDef(p[4] as unknown[]) - tupleDef.forEach((tupleParam) => { - type = `${type}${tupleParam.type}, ` - tupleData?.push(tupleParam.data) - }) - type = type.slice(0, type.length - 2) - type = `${type})` - } - // Get the data of a single function (i.e. excluding arrays) - const funcData = tupleData ? tupleData : genParamData(p) - // Apply the data to arrays - for (let i = 0; i < arrSzs.length; i++) { - const sz = Number.parseInt(arrSzs[i].toString('hex')) - if (Number.isNaN(sz)) { - // This is a 0 size, which means we need to - // define a size to generate data - type = `${type}[]` - } else { - type = `${type}[${sz}]` - } - } - // If this param is a tuple we need to copy base data - // across all dimensions. The individual params are already - // arraified this way, but not the tuple type - if (tupleData) { - converted.push({ type, data: getArrayData(p, funcData) }) - } else { - converted.push({ type, data: funcData }) - } - }) - return converted + const converted: { type: string | null; data: unknown }[] = [] + def.forEach((param: unknown) => { + const p = param as { toString: (fmt: string) => string }[] + const arrSzs = p[3] as { toString: (fmt: string) => string }[] + const evmType = EVM_TYPES[Number.parseInt(p[1].toString('hex'), 16)] + let type = evmType + const numBytes = Number.parseInt(p[2].toString('hex'), 16) + if (numBytes > 0) { + type = `${type}${numBytes * 8}` + } + // Handle tuples by recursively generating data + let tupleData: unknown[] | undefined + if (evmType === 'tuple') { + tupleData = [] + type = `${type}(` + const tupleDef = getConvertedDef(p[4] as unknown[]) + tupleDef.forEach((tupleParam) => { + type = `${type}${tupleParam.type}, ` + tupleData?.push(tupleParam.data) + }) + type = type.slice(0, type.length - 2) + type = `${type})` + } + // Get the data of a single function (i.e. excluding arrays) + const funcData = tupleData ? tupleData : genParamData(p) + // Apply the data to arrays + for (let i = 0; i < arrSzs.length; i++) { + const sz = Number.parseInt(arrSzs[i].toString('hex')) + if (Number.isNaN(sz)) { + // This is a 0 size, which means we need to + // define a size to generate data + type = `${type}[]` + } else { + type = `${type}[${sz}]` + } + } + // If this param is a tuple we need to copy base data + // across all dimensions. The individual params are already + // arraified this way, but not the tuple type + if (tupleData) { + converted.push({ type, data: getArrayData(p, funcData) }) + } else { + converted.push({ type, data: funcData }) + } + }) + return converted } function genTupleData(tupleParam: unknown[]) { - const nestedData: unknown[] = [] - tupleParam.forEach((nestedParam: unknown) => { - const np = nestedParam as { toString: (fmt: string) => string }[] - nestedData.push( - genData(EVM_TYPES[Number.parseInt(np[1].toString('hex'), 16)] ?? '', np), - ) - }) - return nestedData + const nestedData: unknown[] = [] + tupleParam.forEach((nestedParam: unknown) => { + const np = nestedParam as { toString: (fmt: string) => string }[] + nestedData.push( + genData(EVM_TYPES[Number.parseInt(np[1].toString('hex'), 16)] ?? '', np), + ) + }) + return nestedData } function genParamData(param: { toString: (fmt: string) => string }[]) { - const evmType = EVM_TYPES[Number.parseInt(param[1].toString('hex'), 16)] ?? '' - const baseData = genData(evmType, param) - return getArrayData(param, baseData) + const evmType = EVM_TYPES[Number.parseInt(param[1].toString('hex'), 16)] ?? '' + const baseData = genData(evmType, param) + return getArrayData(param, baseData) } function getArrayData( - param: { toString: (fmt: string) => string }[], - baseData: unknown, + param: { toString: (fmt: string) => string }[], + baseData: unknown, ) { - let arrayData: unknown[] | undefined - let data: unknown - const arrSzs = param[3] as unknown as { toString: (fmt: string) => string }[] - for (let i = 0; i < arrSzs.length; i++) { - // let sz = parseInt(arrSzs[i].toString('hex')); TODO: fix this - const dimData: unknown[] = [] - let sz = Number.parseInt( - (param[3] as unknown as { toString: (fmt: string) => string }[])[ - i - ].toString('hex'), - ) - if (Number.isNaN(sz)) { - sz = 2 //1; - } - if (!arrayData) { - arrayData = [] - } - const lastDimData = JSON.parse(JSON.stringify(arrayData)) - for (let j = 0; j < sz; j++) { - if (i === 0) { - dimData.push(baseData) - } else { - dimData.push(lastDimData) - } - } - arrayData = dimData - } - if (!data) { - data = arrayData ? arrayData : baseData - } - return data + let arrayData: unknown[] | undefined + let data: unknown + const arrSzs = param[3] as unknown as { toString: (fmt: string) => string }[] + for (let i = 0; i < arrSzs.length; i++) { + // let sz = parseInt(arrSzs[i].toString('hex')); TODO: fix this + const dimData: unknown[] = [] + let sz = Number.parseInt( + (param[3] as unknown as { toString: (fmt: string) => string }[])[ + i + ].toString('hex'), + ) + if (Number.isNaN(sz)) { + sz = 2 //1; + } + if (!arrayData) { + arrayData = [] + } + const lastDimData = JSON.parse(JSON.stringify(arrayData)) + for (let j = 0; j < sz; j++) { + if (i === 0) { + dimData.push(baseData) + } else { + dimData.push(lastDimData) + } + } + arrayData = dimData + } + if (!data) { + data = arrayData ? arrayData : baseData + } + return data } function genData(type: string, param: { toString: (fmt: string) => string }[]) { - switch (type) { - case 'address': - return '0xdead00000000000000000000000000000000beef' - case 'bool': - return true - case 'uint': - return 9 - case 'int': - return -9 - case 'bytes': - return '0xdeadbeef' - case 'string': - return 'string' - case 'tuple': - if (!param || param.length < 4) { - throw new Error('Invalid tuple data') - } - return genTupleData(param[4] as unknown[]) - default: - throw new Error('Unrecognized type') - } + switch (type) { + case 'address': + return '0xdead00000000000000000000000000000000beef' + case 'bool': + return true + case 'uint': + return 9 + case 'int': + return -9 + case 'bytes': + return '0xdeadbeef' + case 'string': + return 'string' + case 'tuple': + if (!param || param.length < 4) { + throw new Error('Invalid tuple data') + } + return genTupleData(param[4] as unknown[]) + default: + throw new Error('Unrecognized type') + } } diff --git a/packages/sdk/src/__test__/utils/getters.ts b/packages/sdk/src/__test__/utils/getters.ts index 588c90c4..f8bdb15a 100644 --- a/packages/sdk/src/__test__/utils/getters.ts +++ b/packages/sdk/src/__test__/utils/getters.ts @@ -1,8 +1,8 @@ import seedrandom from 'seedrandom' export const getEnv = () => { - if (!process.env) throw new Error('env cannot be found') - return process.env + if (!process.env) throw new Error('env cannot be found') + return process.env } export const getDeviceId = (): string => getEnv().DEVICE_ID ?? '' export const getN = (): number => Number.parseInt(getEnv().N ?? '5') @@ -12,5 +12,5 @@ export const getEtherscanKey = (): string => getEnv().ETHERSCAN_KEY ?? '' export const getEncPw = (): string => getEnv().ENC_PW ?? null export const getPrng = (seed?: string) => { - return seedrandom(seed ? seed : getSeed()) + return seedrandom(seed ? seed : getSeed()) } diff --git a/packages/sdk/src/__test__/utils/helpers.ts b/packages/sdk/src/__test__/utils/helpers.ts index 53a92b80..af886bca 100644 --- a/packages/sdk/src/__test__/utils/helpers.ts +++ b/packages/sdk/src/__test__/utils/helpers.ts @@ -6,8 +6,8 @@ import bitcoin, { type Payment } from 'bitcoinjs-lib' import BN from 'bn.js' import { ECPairFactory } from 'ecpair' import { - derivePath as deriveEDKey, - getPublicKey as getEDPubkey, + derivePath as deriveEDKey, + getPublicKey as getEDPubkey, } from 'ed25519-hd-key' import { ec as EC } from 'elliptic' import { privateToAddress } from 'ethereumjs-util' @@ -22,11 +22,11 @@ import { BIP_CONSTANTS, HARDENED_OFFSET, ethMsgProtocol } from '../../constants' import { ProtocolConstants } from '../../protocol' import { getPathStr } from '../../shared/utilities' import { - ensureHexBuffer, - getV, - getYParity, - parseDER, - randomBytes, + ensureHexBuffer, + getV, + getYParity, + parseDER, + randomBytes, } from '../../util' import { getEnv } from './getters' import { setStoredClient } from './setup' @@ -37,35 +37,35 @@ const bip32 = BIP32Factory(ecc) const ECPair = ECPairFactory(ecc) const normalizeSigComponent = (component: any): Buffer => { - if (component === null || component === undefined) { - return Buffer.alloc(0) - } - if (Buffer.isBuffer(component)) { - return component - } - if (component instanceof Uint8Array) { - return Buffer.from(component) - } - if (typeof component === 'bigint') { - const hex = component.toString(16) - return Buffer.from(hex.padStart(hex.length + (hex.length % 2), '0'), 'hex') - } - if (typeof component === 'number') { - return ensureHexBuffer(component) - } - if (typeof component === 'string') { - return ensureHexBuffer(component) - } - if (typeof component?.toArray === 'function') { - return Buffer.from(component.toArray('be')) - } - if (typeof component?.toBuffer === 'function') { - return Buffer.from(component.toBuffer()) - } - if (typeof component?.toString === 'function') { - return ensureHexBuffer(component.toString()) - } - throw new Error('Unsupported signature component format') + if (component === null || component === undefined) { + return Buffer.alloc(0) + } + if (Buffer.isBuffer(component)) { + return component + } + if (component instanceof Uint8Array) { + return Buffer.from(component) + } + if (typeof component === 'bigint') { + const hex = component.toString(16) + return Buffer.from(hex.padStart(hex.length + (hex.length % 2), '0'), 'hex') + } + if (typeof component === 'number') { + return ensureHexBuffer(component) + } + if (typeof component === 'string') { + return ensureHexBuffer(component) + } + if (typeof component?.toArray === 'function') { + return Buffer.from(component.toArray('be')) + } + if (typeof component?.toBuffer === 'function') { + return Buffer.from(component.toBuffer()) + } + if (typeof component?.toString === 'function') { + return ensureHexBuffer(component.toString()) + } + throw new Error('Unsupported signature component format') } /** @@ -74,14 +74,14 @@ const normalizeSigComponent = (component: any): Buffer => { * For legacy transactions, returns the full V value. */ export const getSignatureVParam = (tx: any, resp: any): string => { - if (tx._type && tx._type > 0) { - // For EIP-1559 and newer transaction types, use yParity (0 or 1) - return getYParity(tx, resp).toString(16).padStart(2, '0') - } else { - // For legacy transactions, return full V value - const vBn = getV(tx, resp) - return vBn.toString(16).padStart(2, '0') - } + if (tx._type && tx._type > 0) { + // For EIP-1559 and newer transaction types, use yParity (0 or 1) + return getYParity(tx, resp).toString(16).padStart(2, '0') + } else { + // For legacy transactions, return full V value + const vBn = getV(tx, resp) + return vBn.toString(16).padStart(2, '0') + } } /** @@ -90,13 +90,13 @@ export const getSignatureVParam = (tx: any, resp: any): string => { * For legacy transactions, returns the full V value as BN. */ export const getSignatureVBN = (tx: any, resp: any): BN => { - if (tx._type && tx._type > 0) { - // For EIP-1559 and newer transaction types, get y-parity (0 or 1) - return new BN(getYParity(tx, resp)) - } else { - // For legacy transactions, use the v value from signature - return new BN(resp.sig.v) - } + if (tx._type && tx._type > 0) { + // For EIP-1559 and newer transaction types, get y-parity (0 or 1) + return new BN(getYParity(tx, resp)) + } else { + // For legacy transactions, use the v value from signature + return new BN(resp.sig.v) + } } // NOTE: We use the HARDEN(49) purpose for p2sh(p2wpkh) address derivations. @@ -109,330 +109,330 @@ export const BTC_COIN = BIP_CONSTANTS.COINS.BTC export const BTC_TESTNET_COIN = BIP_CONSTANTS.COINS.BTC_TESTNET export const ETH_COIN = BIP_CONSTANTS.COINS.ETH export const REUSABLE_KEY = - '3fb53b677f73e4d2b8c89c303f6f6b349f0075ad88ea126cb9f6632085815dca' + '3fb53b677f73e4d2b8c89c303f6f6b349f0075ad88ea126cb9f6632085815dca' export function setupTestClient( - env = getEnv() as any, - stateData?: any, + env = getEnv() as any, + stateData?: any, ): Client { - if (stateData) { - return new Client({ stateData }) - } - const setup: any = { - name: env.APP_NAME || 'SDK Test', - baseUrl: env.baseUrl || 'https://signing.gridpl.us', - timeout: 120000, - setStoredClient, - } + if (stateData) { + return new Client({ stateData }) + } + const setup: any = { + name: env.APP_NAME || 'SDK Test', + baseUrl: env.baseUrl || 'https://signing.gridpl.us', + timeout: 120000, + setStoredClient, + } - // If the user passes a deviceID in the env, we assume they have previously - // connected to the Lattice. - if (env.DEVICE_ID) { - setup.privKey = Buffer.from(REUSABLE_KEY, 'hex') - } - // Separate check -- if we are connecting for the first time but want to be able - // to reconnect quickly with the same device ID as an env var, we need to pair - // with a reusable key - if (Number.parseInt(env.REUSE_KEY) === 1) { - setup.privKey = Buffer.from(REUSABLE_KEY, 'hex') - } - // Initialize a global SDK client - const client = new Client(setup) - return client + // If the user passes a deviceID in the env, we assume they have previously + // connected to the Lattice. + if (env.DEVICE_ID) { + setup.privKey = Buffer.from(REUSABLE_KEY, 'hex') + } + // Separate check -- if we are connecting for the first time but want to be able + // to reconnect quickly with the same device ID as an env var, we need to pair + // with a reusable key + if (Number.parseInt(env.REUSE_KEY) === 1) { + setup.privKey = Buffer.from(REUSABLE_KEY, 'hex') + } + // Initialize a global SDK client + const client = new Client(setup) + return client } export const unharden = (x) => { - return x >= HARDENED_OFFSET ? x - HARDENED_OFFSET : x + return x >= HARDENED_OFFSET ? x - HARDENED_OFFSET : x } export const buildPath = (indices) => { - let path = 'm' - indices.forEach((idx) => { - path += `/${unharden(idx)}${idx >= HARDENED_OFFSET ? "'" : ''}` - }) - return path + let path = 'm' + indices.forEach((idx) => { + path += `/${unharden(idx)}${idx >= HARDENED_OFFSET ? "'" : ''}` + }) + return path } export function _getSumInputs(inputs) { - let sum = 0 - inputs.forEach((input) => { - sum += input.value - }) - return sum + let sum = 0 + inputs.forEach((input) => { + sum += input.value + }) + return sum } export function _get_btc_addr(pubkey, purpose, network) { - const pk = Buffer.isBuffer(pubkey) ? pubkey : Buffer.from(pubkey) - let obj: Payment - if (purpose === BTC_PURPOSE_P2SH_P2WPKH) { - // Wrapped segwit requires p2sh wrapping - obj = bitcoin.payments.p2sh({ - redeem: bitcoin.payments.p2wpkh({ pubkey: pk, network }), - network, - }) - } else if (purpose === BTC_PURPOSE_P2WPKH) { - obj = bitcoin.payments.p2wpkh({ pubkey: pk, network }) - } else { - // Native segwit and legacy addresses are treated teh same - obj = bitcoin.payments.p2pkh({ pubkey: pk, network }) - } - return obj.address + const pk = Buffer.isBuffer(pubkey) ? pubkey : Buffer.from(pubkey) + let obj: Payment + if (purpose === BTC_PURPOSE_P2SH_P2WPKH) { + // Wrapped segwit requires p2sh wrapping + obj = bitcoin.payments.p2sh({ + redeem: bitcoin.payments.p2wpkh({ pubkey: pk, network }), + network, + }) + } else if (purpose === BTC_PURPOSE_P2WPKH) { + obj = bitcoin.payments.p2wpkh({ pubkey: pk, network }) + } else { + // Native segwit and legacy addresses are treated teh same + obj = bitcoin.payments.p2pkh({ pubkey: pk, network }) + } + return obj.address } export function _start_tx_builder( - wallet, - recipient, - value, - fee, - inputs, - network, - purpose, + wallet, + recipient, + value, + fee, + inputs, + network, + purpose, ) { - const tx = new bitcoin.Transaction() - // Match serialization logic (version 2) used by device and serializer - tx.version = 2 - const inputSum = _getSumInputs(inputs) - const recipientScript = bitcoin.address.toOutputScript(recipient, network) - tx.addOutput(recipientScript, value) - const changeValue = inputSum - value - fee - if (changeValue > 0) { - const networkIdx = network === bitcoin.networks.testnet ? 1 : 0 - const path = buildPath([purpose, harden(networkIdx), harden(0), 1, 0]) - const btc_0_change = wallet.derivePath(path) - const btc_0_change_pub = ECPair.fromPublicKey( - btc_0_change.publicKey, - ).publicKey - const changeAddr = _get_btc_addr(btc_0_change_pub, purpose, network) - const changeScript = bitcoin.address.toOutputScript(changeAddr, network) - tx.addOutput(changeScript, changeValue) - } else if (changeValue < 0) { - throw new Error('Value + fee > sumInputs!') - } - const inputsMeta: { scriptCode: Buffer; value: number }[] = [] - inputs.forEach((input) => { - const hashLE = Buffer.from(input.hash, 'hex').reverse() - tx.addInput(hashLE, input.idx) - const coin = - network === bitcoin.networks.testnet ? BTC_TESTNET_COIN : BTC_COIN - const path = buildPath([purpose, coin, harden(0), 0, input.signerIdx]) - const keyPair = wallet.derivePath(path) - const pubkeyBuf = Buffer.from(keyPair.publicKey) - const p2pkh = bitcoin.payments.p2pkh({ pubkey: pubkeyBuf, network }) - // For P2WPKH and P2SH-P2WPKH the BIP143 scriptCode is the standard P2PKH script - if (!p2pkh.output) throw new Error('No P2PKH output') - const scriptCode = p2pkh.output - inputsMeta.push({ scriptCode, value: input.value }) - }) - return { tx, inputsMeta } + const tx = new bitcoin.Transaction() + // Match serialization logic (version 2) used by device and serializer + tx.version = 2 + const inputSum = _getSumInputs(inputs) + const recipientScript = bitcoin.address.toOutputScript(recipient, network) + tx.addOutput(recipientScript, value) + const changeValue = inputSum - value - fee + if (changeValue > 0) { + const networkIdx = network === bitcoin.networks.testnet ? 1 : 0 + const path = buildPath([purpose, harden(networkIdx), harden(0), 1, 0]) + const btc_0_change = wallet.derivePath(path) + const btc_0_change_pub = ECPair.fromPublicKey( + btc_0_change.publicKey, + ).publicKey + const changeAddr = _get_btc_addr(btc_0_change_pub, purpose, network) + const changeScript = bitcoin.address.toOutputScript(changeAddr, network) + tx.addOutput(changeScript, changeValue) + } else if (changeValue < 0) { + throw new Error('Value + fee > sumInputs!') + } + const inputsMeta: { scriptCode: Buffer; value: number }[] = [] + inputs.forEach((input) => { + const hashLE = Buffer.from(input.hash, 'hex').reverse() + tx.addInput(hashLE, input.idx) + const coin = + network === bitcoin.networks.testnet ? BTC_TESTNET_COIN : BTC_COIN + const path = buildPath([purpose, coin, harden(0), 0, input.signerIdx]) + const keyPair = wallet.derivePath(path) + const pubkeyBuf = Buffer.from(keyPair.publicKey) + const p2pkh = bitcoin.payments.p2pkh({ pubkey: pubkeyBuf, network }) + // For P2WPKH and P2SH-P2WPKH the BIP143 scriptCode is the standard P2PKH script + if (!p2pkh.output) throw new Error('No P2PKH output') + const scriptCode = p2pkh.output + inputsMeta.push({ scriptCode, value: input.value }) + }) + return { tx, inputsMeta } } function _build_sighashes(txb_or_tx, purpose) { - const hashes: any = [] - const txb = txb_or_tx as any - const isLegacy = purpose === BTC_PURPOSE_P2PKH - if (txb.inputsMeta) { - txb.inputsMeta.forEach((meta, i) => { - hashes.push( - isLegacy - ? txb.tx.hashForSignature(i, meta.scriptCode, SIGHASH_ALL) - : txb.tx.hashForWitnessV0( - i, - meta.scriptCode, - meta.value, - SIGHASH_ALL, - ), - ) - }) - } else { - // Fallback for prior structure (should not be used) - txb.__inputs.forEach((input, i) => { - hashes.push( - isLegacy - ? txb.__tx.hashForSignature(i, input.signScript, SIGHASH_ALL) - : txb.__tx.hashForWitnessV0( - i, - input.signScript, - input.value, - SIGHASH_ALL, - ), - ) - }) - } - return hashes + const hashes: any = [] + const txb = txb_or_tx as any + const isLegacy = purpose === BTC_PURPOSE_P2PKH + if (txb.inputsMeta) { + txb.inputsMeta.forEach((meta, i) => { + hashes.push( + isLegacy + ? txb.tx.hashForSignature(i, meta.scriptCode, SIGHASH_ALL) + : txb.tx.hashForWitnessV0( + i, + meta.scriptCode, + meta.value, + SIGHASH_ALL, + ), + ) + }) + } else { + // Fallback for prior structure (should not be used) + txb.__inputs.forEach((input, i) => { + hashes.push( + isLegacy + ? txb.__tx.hashForSignature(i, input.signScript, SIGHASH_ALL) + : txb.__tx.hashForWitnessV0( + i, + input.signScript, + input.value, + SIGHASH_ALL, + ), + ) + }) + } + return hashes } function _get_reference_sighashes( - wallet, - recipient, - value, - fee, - inputs, - isTestnet, - purpose, + wallet, + recipient, + value, + fee, + inputs, + isTestnet, + purpose, ) { - const network = isTestnet - ? bitcoin.networks.testnet - : bitcoin.networks.bitcoin - const built = _start_tx_builder( - wallet, - recipient, - value, - fee, - inputs, - network, - purpose, - ) - // built has shape { tx, inputsMeta } - return _build_sighashes(built, purpose) + const network = isTestnet + ? bitcoin.networks.testnet + : bitcoin.networks.bitcoin + const built = _start_tx_builder( + wallet, + recipient, + value, + fee, + inputs, + network, + purpose, + ) + // built has shape { tx, inputsMeta } + return _build_sighashes(built, purpose) } function _btc_tx_request_builder( - inputs, - recipient, - value, - fee, - isTestnet, - purpose, + inputs, + recipient, + value, + fee, + isTestnet, + purpose, ) { - const currencyIdx = isTestnet ? BTC_TESTNET_COIN : BTC_COIN - const txData = { - prevOuts: [] as any[], - recipient, - value, - fee, - changePath: [purpose, currencyIdx, HARDENED_OFFSET, 1, 0], - } - inputs.forEach((input) => { - txData.prevOuts.push({ - txHash: input.hash, - value: input.value, - index: input.idx, - signerPath: [purpose, currencyIdx, HARDENED_OFFSET, 0, input.signerIdx], - }) - }) - return { - currency: 'BTC', - data: txData, - } + const currencyIdx = isTestnet ? BTC_TESTNET_COIN : BTC_COIN + const txData = { + prevOuts: [] as any[], + recipient, + value, + fee, + changePath: [purpose, currencyIdx, HARDENED_OFFSET, 1, 0], + } + inputs.forEach((input) => { + txData.prevOuts.push({ + txHash: input.hash, + value: input.value, + index: input.idx, + signerPath: [purpose, currencyIdx, HARDENED_OFFSET, 0, input.signerIdx], + }) + }) + return { + currency: 'BTC', + data: txData, + } } // Convert DER signature to buffer of form `${r}${s}` export function stripDER(derSig) { - const parsed = parseDER(derSig) - // Left-pad r and s to 32 bytes and concatenate (no extra normalization) - const r = Buffer.from(parsed.r.slice(-32)) - const s = Buffer.from(parsed.s.slice(-32)) - const sig = Buffer.alloc(64) - r.copy(sig, 32 - r.length) - s.copy(sig, 64 - s.length) - return sig + const parsed = parseDER(derSig) + // Left-pad r and s to 32 bytes and concatenate (no extra normalization) + const r = Buffer.from(parsed.r.slice(-32)) + const s = Buffer.from(parsed.s.slice(-32)) + const sig = Buffer.alloc(64) + r.copy(sig, 32 - r.length) + s.copy(sig, 64 - s.length) + return sig } function _get_signing_keys(wallet, inputs, isTestnet, purpose) { - const currencyIdx = isTestnet ? 1 : 0 - return inputs.map((input) => { - const path = buildPath([ - purpose, - harden(currencyIdx), - harden(0), - 0, - input.signerIdx, - ]) - const node = wallet.derivePath(path) - const priv = Buffer.from(node.privateKey) - const key = secp256k1.keyFromPrivate(priv) - return { - privateKey: priv, - verify(hash: Buffer, sig: Buffer) { - return key.verify(hash, { - r: sig.slice(0, 32).toString('hex'), - s: sig.slice(32).toString('hex'), - }) - }, - } - }) + const currencyIdx = isTestnet ? 1 : 0 + return inputs.map((input) => { + const path = buildPath([ + purpose, + harden(currencyIdx), + harden(0), + 0, + input.signerIdx, + ]) + const node = wallet.derivePath(path) + const priv = Buffer.from(node.privateKey) + const key = secp256k1.keyFromPrivate(priv) + return { + privateKey: priv, + verify(hash: Buffer, sig: Buffer) { + return key.verify(hash, { + r: sig.slice(0, 32).toString('hex'), + s: sig.slice(32).toString('hex'), + }) + }, + } + }) } function _generate_btc_address(isTestnet, purpose, rand) { - const priv = Buffer.alloc(32) - for (let j = 0; j < 8; j++) { - // 32 bits of randomness per call - priv.writeUInt32BE(Math.floor(rand.quick() * 2 ** 32), j * 4) - } - const keyPair = ECPair.fromPrivateKey(priv) - const network = - isTestnet === true ? bitcoin.networks.testnet : bitcoin.networks.bitcoin - return _get_btc_addr(keyPair.publicKey, purpose, network) + const priv = Buffer.alloc(32) + for (let j = 0; j < 8; j++) { + // 32 bits of randomness per call + priv.writeUInt32BE(Math.floor(rand.quick() * 2 ** 32), j * 4) + } + const keyPair = ECPair.fromPrivateKey(priv) + const network = + isTestnet === true ? bitcoin.networks.testnet : bitcoin.networks.bitcoin + return _get_btc_addr(keyPair.publicKey, purpose, network) } export function setup_btc_sig_test(opts, wallet, inputs, rand) { - const { isTestnet, useChange, spenderPurpose, recipientPurpose } = opts - const recipient = _generate_btc_address(isTestnet, recipientPurpose, rand) - const sumInputs = _getSumInputs(inputs) - const fee = Math.floor(rand.quick() * 50000) - const _value = - useChange === true ? Math.floor(rand.quick() * sumInputs) : sumInputs - const value = _value - fee - const sigHashes = _get_reference_sighashes( - wallet, - recipient, - value, - fee, - inputs, - isTestnet, - spenderPurpose, - ) - const signingKeys = _get_signing_keys( - wallet, - inputs, - isTestnet, - spenderPurpose, - ) - const txReq = _btc_tx_request_builder( - inputs, - recipient, - value, - fee, - isTestnet, - spenderPurpose, - ) - return { - sigHashes, - signingKeys, - txReq, - } + const { isTestnet, useChange, spenderPurpose, recipientPurpose } = opts + const recipient = _generate_btc_address(isTestnet, recipientPurpose, rand) + const sumInputs = _getSumInputs(inputs) + const fee = Math.floor(rand.quick() * 50000) + const _value = + useChange === true ? Math.floor(rand.quick() * sumInputs) : sumInputs + const value = _value - fee + const sigHashes = _get_reference_sighashes( + wallet, + recipient, + value, + fee, + inputs, + isTestnet, + spenderPurpose, + ) + const signingKeys = _get_signing_keys( + wallet, + inputs, + isTestnet, + spenderPurpose, + ) + const txReq = _btc_tx_request_builder( + inputs, + recipient, + value, + fee, + isTestnet, + spenderPurpose, + ) + return { + sigHashes, + signingKeys, + txReq, + } } export const harden = (x) => { - return x + HARDENED_OFFSET + return x + HARDENED_OFFSET } export const prandomBuf = (prng, maxSz, forceSize = false) => { - // Build a random payload that can fit in the base request - const sz = forceSize ? maxSz : Math.floor(maxSz * prng.quick()) - const buf = Buffer.alloc(sz) - for (let i = 0; i < sz; i++) { - buf[i] = Math.floor(0xff * prng.quick()) - } - return buf + // Build a random payload that can fit in the base request + const sz = forceSize ? maxSz : Math.floor(maxSz * prng.quick()) + const buf = Buffer.alloc(sz) + for (let i = 0; i < sz; i++) { + buf[i] = Math.floor(0xff * prng.quick()) + } + return buf } export const deriveED25519Key = (path, seed) => { - const { key } = deriveEDKey(getPathStr(path), seed) - const pub = getEDPubkey(key, false) // `false` removes the leading zero byte - return { - priv: key, - pub, - } + const { key } = deriveEDKey(getPathStr(path), seed) + const pub = getEDPubkey(key, false) // `false` removes the leading zero byte + return { + priv: key, + pub, + } } export const deriveSECP256K1Key = (path, seed) => { - const wallet = bip32.fromSeed(seed) - const key = wallet.derivePath(getPathStr(path)) - return { - priv: key.privateKey, - pub: key.publicKey, - } + const wallet = bip32.fromSeed(seed) + const key = wallet.derivePath(getPathStr(path)) + return { + priv: key.privateKey, + pub: key.publicKey, + } } //============================================================ @@ -444,61 +444,61 @@ export const deriveSECP256K1Key = (path, seed) => { // Relevant test harness constants //--------------------------------------------------- export const jobTypes = { - WALLET_JOB_GET_ADDRESSES: 1, - WALLET_JOB_SIGN_TX: 2, - WALLET_JOB_LOAD_SEED: 3, - WALLET_JOB_EXPORT_SEED: 4, - WALLET_JOB_DELETE_SEED: 5, + WALLET_JOB_GET_ADDRESSES: 1, + WALLET_JOB_SIGN_TX: 2, + WALLET_JOB_LOAD_SEED: 3, + WALLET_JOB_EXPORT_SEED: 4, + WALLET_JOB_DELETE_SEED: 5, } export const gpErrors = { - GP_SUCCESS: 0x00, - GP_EINVAL: 0xffffffff + 1 - 22, // (4294967061) - GP_ENODATA: 0xffffffff + 1 - 61, // (4294967100) - GP_EOVERFLOW: 0xffffffff + 1 - 84, // (4294967123) - GP_EALREADY: 0xffffffff + 1 - 114, // (4294967153) - GP_ENODEV: 0xffffffff + 1 - 19, // (4294967058) - GP_EAGAIN: 0xffffffff + 1 - 11, // (4294967050) - GP_FAILURE: 0xffffffff + 1 - 128, // (4294967168) - GP_EWALLET: 0xffffffff + 1 - 113, // (4294967183) + GP_SUCCESS: 0x00, + GP_EINVAL: 0xffffffff + 1 - 22, // (4294967061) + GP_ENODATA: 0xffffffff + 1 - 61, // (4294967100) + GP_EOVERFLOW: 0xffffffff + 1 - 84, // (4294967123) + GP_EALREADY: 0xffffffff + 1 - 114, // (4294967153) + GP_ENODEV: 0xffffffff + 1 - 19, // (4294967058) + GP_EAGAIN: 0xffffffff + 1 - 11, // (4294967050) + GP_FAILURE: 0xffffffff + 1 - 128, // (4294967168) + GP_EWALLET: 0xffffffff + 1 - 113, // (4294967183) } //--------------------------------------------------- // General helpers //--------------------------------------------------- export const getCodeMsg = (code, expected) => { - if (code !== expected) { - let codeTxt = code - let expectedTxt = expected - Object.keys(gpErrors).forEach((key) => { - if (code === gpErrors[key]) { - codeTxt = key - } - if (expected === gpErrors[key]) { - expectedTxt = key - } - }) - return `Incorrect response code. Got ${codeTxt}. Expected ${expectedTxt}` - } - return '' + if (code !== expected) { + let codeTxt = code + let expectedTxt = expected + Object.keys(gpErrors).forEach((key) => { + if (code === gpErrors[key]) { + codeTxt = key + } + if (expected === gpErrors[key]) { + expectedTxt = key + } + }) + return `Incorrect response code. Got ${codeTxt}. Expected ${expectedTxt}` + } + return '' } export const parseWalletJobResp = (res, v) => { - const jobRes = { - resultStatus: null, - result: null, - } - jobRes.resultStatus = res.readUInt32LE(0) - const dataLen = res.readUInt16LE(4) - if (v.length === 0 || (v[1] < 10 && v[2] === 0)) { - // Legacy fw versions ( res.result.readUInt32LE(0) // Have to do this weird copy because `Buffer`s from the client are not real buffers // which is a vestige of requiring support on react native export const copyBuffer = (x) => { - return Buffer.from(x.toString('hex'), 'hex') + return Buffer.from(x.toString('hex'), 'hex') } // Convert a set of indices to a human readable bip32 path export const stringifyPath = (parent) => { - const convert = (parent) => { - return parent >= HARDENED_OFFSET - ? `${parent - HARDENED_OFFSET}'` - : `${parent}` - } - if (parent.idx) { - // BIP32 style encoding - let s = 'm' - for (let i = 0; i < parent.pathDepth; i++) { - s += `/${convert(parent.idx[i])}` - } - return s - } + const convert = (parent) => { + return parent >= HARDENED_OFFSET + ? `${parent - HARDENED_OFFSET}'` + : `${parent}` + } + if (parent.idx) { + // BIP32 style encoding + let s = 'm' + for (let i = 0; i < parent.pathDepth; i++) { + s += `/${convert(parent.idx[i])}` + } + return s + } - let d = parent.pathDepth - let s = 'm' - if (d <= 0) return s - if (parent.purpose !== undefined) { - s += `/${convert(parent.purpose)}` - d-- - if (d <= 0) return s - } - if (parent.coin !== undefined) { - s += `/${convert(parent.coin)}` - d-- - if (d <= 0) return s - } - if (parent.account !== undefined) { - s += `/${convert(parent.account)}` - d-- - if (d <= 0) return s - } - if (parent.change !== undefined) { - s += `/${convert(parent.change)}` - d-- - if (d <= 0) return s - } - if (parent.addr !== undefined) s += `/${convert(parent.addr)}` - d-- - return s + let d = parent.pathDepth + let s = 'm' + if (d <= 0) return s + if (parent.purpose !== undefined) { + s += `/${convert(parent.purpose)}` + d-- + if (d <= 0) return s + } + if (parent.coin !== undefined) { + s += `/${convert(parent.coin)}` + d-- + if (d <= 0) return s + } + if (parent.account !== undefined) { + s += `/${convert(parent.account)}` + d-- + if (d <= 0) return s + } + if (parent.change !== undefined) { + s += `/${convert(parent.change)}` + d-- + if (d <= 0) return s + } + if (parent.addr !== undefined) s += `/${convert(parent.addr)}` + d-- + return s } //--------------------------------------------------- // Get Addresses helpers //--------------------------------------------------- export const serializeGetAddressesJobData = (data) => { - const req = Buffer.alloc(33) - let off = 0 - req.writeUInt32LE(data.path.pathDepth, off) - off += 4 - for (let i = 0; i < 5; i++) { - req.writeUInt32LE(i < data.path.pathDepth ? data.path.idx[i] : 0, off) - off += 4 - } - req.writeUInt32LE(data.iterIdx, off) - off += 4 - req.writeUInt32LE(data.count, off) - off += 4 - // Deprecated skipCache flag. It isn't used by firmware anymore. - req.writeUInt8(data.flag || 0, off) - return req + const req = Buffer.alloc(33) + let off = 0 + req.writeUInt32LE(data.path.pathDepth, off) + off += 4 + for (let i = 0; i < 5; i++) { + req.writeUInt32LE(i < data.path.pathDepth ? data.path.idx[i] : 0, off) + off += 4 + } + req.writeUInt32LE(data.iterIdx, off) + off += 4 + req.writeUInt32LE(data.count, off) + off += 4 + // Deprecated skipCache flag. It isn't used by firmware anymore. + req.writeUInt8(data.flag || 0, off) + return req } export const deserializeGetAddressesJobResult = (res) => { - let off = 0 - const getAddrResult = { - count: 0, - addresses: [] as any[], - pubOnly: undefined, - } - getAddrResult.pubOnly = res.readUInt8(off) - off += 1 - getAddrResult.count = res.readUInt8(off) - off += 3 // Skip a 2-byte empty shim value (for backwards compatibility) - for (let i = 0; i < getAddrResult.count; i++) { - const _addr = res.slice(off, off + ProtocolConstants.addrStrLen) - off += ProtocolConstants.addrStrLen - for (let j = 0; j < _addr.length; j++) - if (_addr[j] === 0x00) { - getAddrResult.addresses.push(_addr.slice(0, j).toString('utf8')) - break - } - } - return getAddrResult + let off = 0 + const getAddrResult = { + count: 0, + addresses: [] as any[], + pubOnly: undefined, + } + getAddrResult.pubOnly = res.readUInt8(off) + off += 1 + getAddrResult.count = res.readUInt8(off) + off += 3 // Skip a 2-byte empty shim value (for backwards compatibility) + for (let i = 0; i < getAddrResult.count; i++) { + const _addr = res.slice(off, off + ProtocolConstants.addrStrLen) + off += ProtocolConstants.addrStrLen + for (let j = 0; j < _addr.length; j++) + if (_addr[j] === 0x00) { + getAddrResult.addresses.push(_addr.slice(0, j).toString('utf8')) + break + } + } + return getAddrResult } export const validateBTCAddresses = (resp, jobData, seed, useTestnet?) => { - expect(resp.count).toEqual(jobData.count) - const wallet = bip32.fromSeed(seed) - const path = JSON.parse(JSON.stringify(jobData.path)) - const network = - useTestnet === true ? bitcoin.networks.testnet : bitcoin.networks.bitcoin - for (let i = 0; i < jobData.count; i++) { - path.idx[jobData.iterIdx] = jobData.path.idx[jobData.iterIdx] + i - // Validate the address - const purpose = jobData.path.idx[0] - const pubkey = wallet.derivePath(stringifyPath(path)).publicKey - let address: string - if (purpose === BTC_PURPOSE_P2WPKH) { - // Bech32 - address = bitcoin.payments.p2wpkh({ - pubkey: Buffer.from(pubkey), - network, - }).address - } else if (purpose === BTC_PURPOSE_P2SH_P2WPKH) { - // Wrapped segwit - address = bitcoin.payments.p2sh({ - redeem: bitcoin.payments.p2wpkh({ - pubkey: Buffer.from(pubkey), - network, - }), - }).address - } else { - // Legacy - // This is the default and any unrecognized purpose will yield a legacy address. - address = bitcoin.payments.p2pkh({ - pubkey: Buffer.from(pubkey), - network, - }).address - } - expect(address).toEqual(resp.addresses[i]) - } + expect(resp.count).toEqual(jobData.count) + const wallet = bip32.fromSeed(seed) + const path = JSON.parse(JSON.stringify(jobData.path)) + const network = + useTestnet === true ? bitcoin.networks.testnet : bitcoin.networks.bitcoin + for (let i = 0; i < jobData.count; i++) { + path.idx[jobData.iterIdx] = jobData.path.idx[jobData.iterIdx] + i + // Validate the address + const purpose = jobData.path.idx[0] + const pubkey = wallet.derivePath(stringifyPath(path)).publicKey + let address: string + if (purpose === BTC_PURPOSE_P2WPKH) { + // Bech32 + address = bitcoin.payments.p2wpkh({ + pubkey: Buffer.from(pubkey), + network, + }).address + } else if (purpose === BTC_PURPOSE_P2SH_P2WPKH) { + // Wrapped segwit + address = bitcoin.payments.p2sh({ + redeem: bitcoin.payments.p2wpkh({ + pubkey: Buffer.from(pubkey), + network, + }), + }).address + } else { + // Legacy + // This is the default and any unrecognized purpose will yield a legacy address. + address = bitcoin.payments.p2pkh({ + pubkey: Buffer.from(pubkey), + network, + }).address + } + expect(address).toEqual(resp.addresses[i]) + } } export const validateETHAddresses = (resp, jobData, seed) => { - expect(resp.count).toEqual(jobData.count) - // Confirm it is an Ethereum address - expect(resp.addresses[0].slice(0, 2)).toEqual('0x') - expect(resp.addresses[0].length).toEqual(42) - // Confirm we can derive the same address from the previously exported seed - const wallet = bip32.fromSeed(seed) - const path = JSON.parse(JSON.stringify(jobData.path)) - for (let i = 0; i < jobData.count; i++) { - path.idx[jobData.iterIdx] = jobData.path.idx[jobData.iterIdx] + i - const priv = wallet.derivePath(stringifyPath(path)).privateKey - const addr = `0x${privateToAddress(priv).toString('hex')}` - expect(addr).toEqual(resp.addresses[i]) - } + expect(resp.count).toEqual(jobData.count) + // Confirm it is an Ethereum address + expect(resp.addresses[0].slice(0, 2)).toEqual('0x') + expect(resp.addresses[0].length).toEqual(42) + // Confirm we can derive the same address from the previously exported seed + const wallet = bip32.fromSeed(seed) + const path = JSON.parse(JSON.stringify(jobData.path)) + for (let i = 0; i < jobData.count; i++) { + path.idx[jobData.iterIdx] = jobData.path.idx[jobData.iterIdx] + i + const priv = wallet.derivePath(stringifyPath(path)).privateKey + const addr = `0x${privateToAddress(priv).toString('hex')}` + expect(addr).toEqual(resp.addresses[i]) + } } export const validateDerivedPublicKeys = ( - pubKeys, - firstPath, - seed, - flag?: number, + pubKeys, + firstPath, + seed, + flag?: number, ) => { - const wallet = bip32.fromSeed(seed) - // We assume the keys were derived in sequential order - pubKeys.forEach((pub, i) => { - const path = JSON.parse(JSON.stringify(firstPath)) - path[path.length - 1] += i - if (flag === Constants.GET_ADDR_FLAGS.ED25519_PUB) { - // ED25519 requires its own derivation - const key = deriveED25519Key(path, seed) - expect(pub.toString('hex')).toEqualElseLog( - key.pub.toString('hex'), - 'Exported ED25519 pubkey incorrect', - ) - } else { - // Otherwise this is a SECP256K1 pubkey - const priv = wallet.derivePath(getPathStr(path)).privateKey - expect(pub.toString('hex')).toEqualElseLog( - secp256k1.keyFromPrivate(priv).getPublic().encode('hex', false), - 'Exported SECP256K1 pubkey incorrect', - ) - } - }) + const wallet = bip32.fromSeed(seed) + // We assume the keys were derived in sequential order + pubKeys.forEach((pub, i) => { + const path = JSON.parse(JSON.stringify(firstPath)) + path[path.length - 1] += i + if (flag === Constants.GET_ADDR_FLAGS.ED25519_PUB) { + // ED25519 requires its own derivation + const key = deriveED25519Key(path, seed) + expect(pub.toString('hex')).toEqualElseLog( + key.pub.toString('hex'), + 'Exported ED25519 pubkey incorrect', + ) + } else { + // Otherwise this is a SECP256K1 pubkey + const priv = wallet.derivePath(getPathStr(path)).privateKey + expect(pub.toString('hex')).toEqualElseLog( + secp256k1.keyFromPrivate(priv).getPublic().encode('hex', false), + 'Exported SECP256K1 pubkey incorrect', + ) + } + }) } export const ethPersonalSignMsg = (msg) => - `\u0019Ethereum Signed Message:\n${String(msg.length)}${msg}` + `\u0019Ethereum Signed Message:\n${String(msg.length)}${msg}` //--------------------------------------------------- // Sign Transaction helpers //--------------------------------------------------- export const serializeSignTxJobDataLegacy = (data) => { - // Serialize a signTX request using the legacy option - // (see `WalletJobData_SignTx_t` and `SignatureRequest_t`) - // in firmware for more info on legacy vs generic (new) - // wallet job requests - const n = data.sigReq.length - const req = Buffer.alloc(4 + 56 * n) - let off = 0 - req.writeUInt32LE(data.numRequests, 0) - off += 4 - for (let i = 0; i < n; i++) { - const r = data.sigReq[i] - r.data.copy(req, off) - off += r.data.length - req.writeUInt32LE(r.signerPath.pathDepth, off) - off += 4 - req.writeUInt32LE(r.signerPath.purpose, off) - off += 4 - req.writeUInt32LE(r.signerPath.coin, off) - off += 4 - req.writeUInt32LE(r.signerPath.account, off) - off += 4 - req.writeUInt32LE(r.signerPath.change, off) - off += 4 - req.writeUInt32LE(r.signerPath.addr, off) - off += 4 - } - return req + // Serialize a signTX request using the legacy option + // (see `WalletJobData_SignTx_t` and `SignatureRequest_t`) + // in firmware for more info on legacy vs generic (new) + // wallet job requests + const n = data.sigReq.length + const req = Buffer.alloc(4 + 56 * n) + let off = 0 + req.writeUInt32LE(data.numRequests, 0) + off += 4 + for (let i = 0; i < n; i++) { + const r = data.sigReq[i] + r.data.copy(req, off) + off += r.data.length + req.writeUInt32LE(r.signerPath.pathDepth, off) + off += 4 + req.writeUInt32LE(r.signerPath.purpose, off) + off += 4 + req.writeUInt32LE(r.signerPath.coin, off) + off += 4 + req.writeUInt32LE(r.signerPath.account, off) + off += 4 + req.writeUInt32LE(r.signerPath.change, off) + off += 4 + req.writeUInt32LE(r.signerPath.addr, off) + off += 4 + } + return req } export const deserializeSignTxJobResult = (res: any) => { - let off = 0 - const getTxResult: any = { - numOutputs: null, - outputs: [], - } - getTxResult.numOutputs = res.readUInt32LE(off) - off += 4 - const PK_LEN = 65 // uncompressed pubkey - const SIG_LEN = 74 // DER sig - const outputSz = 6 * 4 + PK_LEN + SIG_LEN - let _off = 0 - for (let i = 0; i < getTxResult.numOutputs; i++) { - const o = { - signerPath: { - pathDepth: null, - purpose: null, - coin: null, - account: null, - change: null, - addr: null, - }, - pubkey: null as any, - sig: null as any, - } - const _o = res.slice(off, off + outputSz) - off += outputSz - _off = 0 - o.signerPath.pathDepth = _o.readUInt32LE(_off) - _off += 4 - o.signerPath.purpose = _o.readUInt32LE(_off) - _off += 4 - o.signerPath.coin = _o.readUInt32LE(_off) - _off += 4 - o.signerPath.account = _o.readUInt32LE(_off) - _off += 4 - o.signerPath.change = _o.readUInt32LE(_off) - _off += 4 - o.signerPath.addr = _o.readUInt32LE(_off) - _off += 4 - o.pubkey = secp256k1.keyFromPublic( - _o.slice(_off, _off + 65).toString('hex'), - 'hex', - ) - _off += PK_LEN - // We get back a DER signature in 74 bytes, but not all the bytes are necessarily - // used. The second byte contains the DER sig length, so we need to use that. - const derLen = _o[_off + 1] - o.sig = Buffer.from( - _o.slice(_off, _off + 2 + derLen).toString('hex'), - 'hex', - ) - getTxResult.outputs.push(o) - } + let off = 0 + const getTxResult: any = { + numOutputs: null, + outputs: [], + } + getTxResult.numOutputs = res.readUInt32LE(off) + off += 4 + const PK_LEN = 65 // uncompressed pubkey + const SIG_LEN = 74 // DER sig + const outputSz = 6 * 4 + PK_LEN + SIG_LEN + let _off = 0 + for (let i = 0; i < getTxResult.numOutputs; i++) { + const o = { + signerPath: { + pathDepth: null, + purpose: null, + coin: null, + account: null, + change: null, + addr: null, + }, + pubkey: null as any, + sig: null as any, + } + const _o = res.slice(off, off + outputSz) + off += outputSz + _off = 0 + o.signerPath.pathDepth = _o.readUInt32LE(_off) + _off += 4 + o.signerPath.purpose = _o.readUInt32LE(_off) + _off += 4 + o.signerPath.coin = _o.readUInt32LE(_off) + _off += 4 + o.signerPath.account = _o.readUInt32LE(_off) + _off += 4 + o.signerPath.change = _o.readUInt32LE(_off) + _off += 4 + o.signerPath.addr = _o.readUInt32LE(_off) + _off += 4 + o.pubkey = secp256k1.keyFromPublic( + _o.slice(_off, _off + 65).toString('hex'), + 'hex', + ) + _off += PK_LEN + // We get back a DER signature in 74 bytes, but not all the bytes are necessarily + // used. The second byte contains the DER sig length, so we need to use that. + const derLen = _o[_off + 1] + o.sig = Buffer.from( + _o.slice(_off, _off + 2 + derLen).toString('hex'), + 'hex', + ) + getTxResult.outputs.push(o) + } - return getTxResult + return getTxResult } //--------------------------------------------------- @@ -781,244 +781,244 @@ export const deserializeSignTxJobResult = (res: any) => { export const serializeExportSeedJobData = () => Buffer.alloc(0) export const deserializeExportSeedJobResult = (res) => { - let off = 0 - const seed = res.slice(off, 64) - off += 64 - const words = [] - for (let i = 0; i < 24; i++) { - const wordIdx = res.slice(off, off + 4).readUInt32LE(0) - words.push(wordlists.english[wordIdx]) - off += 4 - } - const numWords = res.slice(off, off + 4).readUInt32LE(0) - off += 4 - return { - seed, - mnemonic: words.slice(0, numWords).join(' '), - } + let off = 0 + const seed = res.slice(off, 64) + off += 64 + const words = [] + for (let i = 0; i < 24; i++) { + const wordIdx = res.slice(off, off + 4).readUInt32LE(0) + words.push(wordlists.english[wordIdx]) + off += 4 + } + const numWords = res.slice(off, off + 4).readUInt32LE(0) + off += 4 + return { + seed, + mnemonic: words.slice(0, numWords).join(' '), + } } //--------------------------------------------------- // Delete Seed helpers //--------------------------------------------------- export const serializeDeleteSeedJobData = (data) => { - const req = Buffer.alloc(1) - req.writeUInt8(data.iface, 0) - return req + const req = Buffer.alloc(1) + req.writeUInt8(data.iface, 0) + return req } //--------------------------------------------------- // Load Seed helpers //--------------------------------------------------- export const serializeLoadSeedJobData = (data) => { - const req = Buffer.alloc(217) - let off = 0 - req.writeUInt8(data.iface, off) - off += 1 - data.seed.copy(req, off) - off += data.seed.length - req.writeUInt8(data.exportability, off) - off += 1 - if (data.mnemonic) { - // Serialize the mnemonic - const mWords = data.mnemonic.split(' ') - for (let i = 0; i < mWords.length; i++) { - req.writeUint32LE(wordlists.english.indexOf(mWords[i]), off + i * 4) - } - // Strangely the struct is written with the length of - // words after the words themselves lol (24 words * 4 bytes per word = 96) - // (Preserved for fear of any unintended consequences to chaning `BIP39Phrase_t` in fw) - req.writeUInt32LE(mWords.length, off + 96) - // Ignore the passphrase since we only use this wallet job - // helper to test loading a mnemonic onto the card's extraData - // buffer, which does not include the passphrase. - } - return req + const req = Buffer.alloc(217) + let off = 0 + req.writeUInt8(data.iface, off) + off += 1 + data.seed.copy(req, off) + off += data.seed.length + req.writeUInt8(data.exportability, off) + off += 1 + if (data.mnemonic) { + // Serialize the mnemonic + const mWords = data.mnemonic.split(' ') + for (let i = 0; i < mWords.length; i++) { + req.writeUint32LE(wordlists.english.indexOf(mWords[i]), off + i * 4) + } + // Strangely the struct is written with the length of + // words after the words themselves lol (24 words * 4 bytes per word = 96) + // (Preserved for fear of any unintended consequences to chaning `BIP39Phrase_t` in fw) + req.writeUInt32LE(mWords.length, off + 96) + // Ignore the passphrase since we only use this wallet job + // helper to test loading a mnemonic onto the card's extraData + // buffer, which does not include the passphrase. + } + return req } //--------------------------------------------------- // Struct builders //--------------------------------------------------- export const buildRandomEip712Object = (randInt) => { - function randStr(n) { - const words = wordlists.english - let s = '' - while (s.length < n) { - s += `${words?.[randInt(words?.length)]}_` - } - return s.slice(0, n) - } - function getRandomName(upperCase = false, sz = 20) { - const name = randStr(sz) - if (upperCase === true) - return `${name.slice(0, 1).toUpperCase()}${name.slice(1)}` - return name - } - function getRandomEIP712Type(customTypes: any[] = []) { - const types = Object.keys(customTypes).concat( - Object.keys(ethMsgProtocol.TYPED_DATA.typeCodes), - ) - return { - name: getRandomName(), - type: types[randInt(types.length)], - } - } - function getRandomEIP712Val(type) { - if (type !== 'bytes' && type.slice(0, 5) === 'bytes') { - return `0x${randomBytes(Number.parseInt(type.slice(5))).toString('hex')}` - } + function randStr(n) { + const words = wordlists.english + let s = '' + while (s.length < n) { + s += `${words?.[randInt(words?.length)]}_` + } + return s.slice(0, n) + } + function getRandomName(upperCase = false, sz = 20) { + const name = randStr(sz) + if (upperCase === true) + return `${name.slice(0, 1).toUpperCase()}${name.slice(1)}` + return name + } + function getRandomEIP712Type(customTypes: any[] = []) { + const types = Object.keys(customTypes).concat( + Object.keys(ethMsgProtocol.TYPED_DATA.typeCodes), + ) + return { + name: getRandomName(), + type: types[randInt(types.length)], + } + } + function getRandomEIP712Val(type) { + if (type !== 'bytes' && type.slice(0, 5) === 'bytes') { + return `0x${randomBytes(Number.parseInt(type.slice(5))).toString('hex')}` + } - if (type === 'uint' || type.indexOf('uint') === 0) { - const bits = Number.parseInt(type.slice(4) || '256', 10) - const byteLength = Math.max(1, Math.ceil(bits / 8)) - return `0x${randomBytes(byteLength).toString('hex')}` - } + if (type === 'uint' || type.indexOf('uint') === 0) { + const bits = Number.parseInt(type.slice(4) || '256', 10) + const byteLength = Math.max(1, Math.ceil(bits / 8)) + return `0x${randomBytes(byteLength).toString('hex')}` + } - if (type === 'int' || type.indexOf('int') === 0) { - const bits = Number.parseInt(type.slice(3) || '256', 10) - const byteLength = Math.max(1, Math.ceil(bits / 8)) - const raw = randomBytes(byteLength).toString('hex') - const modulus = 1n << BigInt(bits) - const halfModulus = modulus >> 1n - let value = BigInt(`0x${raw}`) - value = ((value % modulus) + modulus) % modulus - if (value >= halfModulus) { - value -= modulus - } - return value.toString() - } - switch (type) { - case 'bytes': - return `0x${randomBytes(1 + randInt(50)).toString('hex')}` - case 'string': - return randStr(100) - case 'bool': - return randInt(1) > 0 - case 'address': - return `0x${randomBytes(20).toString('hex')}` - default: - throw new Error('unsupported eip712 type') - } - } - function buildCustomTypeVal(typeName, msg) { - const val = {} - const subTypes = msg.types[typeName] - subTypes.forEach((subType) => { - if (Object.keys(msg.types).indexOf(subType.type) > -1) { - // If this is a custom type we need to recurse - val[subType.name] = buildCustomTypeVal(subType.type, msg) - } else { - val[subType.name] = getRandomEIP712Val(subType.type) - } - }) - return val - } + if (type === 'int' || type.indexOf('int') === 0) { + const bits = Number.parseInt(type.slice(3) || '256', 10) + const byteLength = Math.max(1, Math.ceil(bits / 8)) + const raw = randomBytes(byteLength).toString('hex') + const modulus = 1n << BigInt(bits) + const halfModulus = modulus >> 1n + let value = BigInt(`0x${raw}`) + value = ((value % modulus) + modulus) % modulus + if (value >= halfModulus) { + value -= modulus + } + return value.toString() + } + switch (type) { + case 'bytes': + return `0x${randomBytes(1 + randInt(50)).toString('hex')}` + case 'string': + return randStr(100) + case 'bool': + return randInt(1) > 0 + case 'address': + return `0x${randomBytes(20).toString('hex')}` + default: + throw new Error('unsupported eip712 type') + } + } + function buildCustomTypeVal(typeName, msg) { + const val = {} + const subTypes = msg.types[typeName] + subTypes.forEach((subType) => { + if (Object.keys(msg.types).indexOf(subType.type) > -1) { + // If this is a custom type we need to recurse + val[subType.name] = buildCustomTypeVal(subType.type, msg) + } else { + val[subType.name] = getRandomEIP712Val(subType.type) + } + }) + return val + } - const msg = { - types: { - EIP712Domain: [ - { name: 'name', type: 'string' }, - { name: 'version', type: 'string' }, - { name: 'chainId', type: 'uint256' }, - { name: 'verifyingContract', type: 'address' }, - ], - }, - primaryType: `Primary_${getRandomName(true)}`, - domain: { - name: `Domain_${getRandomName(true)}`, - version: '1', - chainId: `0x${(1 + randInt(15000)).toString(16)}`, - verifyingContract: `0x${randomBytes(20).toString('hex')}`, - }, - message: {}, - } - msg.types[msg.primaryType] = [] + const msg = { + types: { + EIP712Domain: [ + { name: 'name', type: 'string' }, + { name: 'version', type: 'string' }, + { name: 'chainId', type: 'uint256' }, + { name: 'verifyingContract', type: 'address' }, + ], + }, + primaryType: `Primary_${getRandomName(true)}`, + domain: { + name: `Domain_${getRandomName(true)}`, + version: '1', + chainId: `0x${(1 + randInt(15000)).toString(16)}`, + verifyingContract: `0x${randomBytes(20).toString('hex')}`, + }, + message: {}, + } + msg.types[msg.primaryType] = [] - // Create custom types and add them to the types definitions - const numCustomTypes = 1 + randInt(3) - const numDefaulTypes = 1 + randInt(3) - const customTypesMap: any = {} - for (let i = 0; i < numCustomTypes; i++) { - const subTypes: any[] = [] - for (let j = 0; j < 1 + randInt(3); j++) { - subTypes.push(getRandomEIP712Type(customTypesMap)) - } - // Capitalize custom type names to distinguish them - let typeName = getRandomName(true) - typeName = `${typeName.slice(0, 1).toUpperCase()}${typeName.slice(1)}` - customTypesMap[typeName] = subTypes - // Record the type - msg.types[typeName] = subTypes - // Add a record in the primary type. We will need to create a value later. - msg.types[msg.primaryType].push({ - name: getRandomName(), - type: typeName, - }) - } - // Generate default (i.e. "atomic") types to mix into the message - for (let i = 0; i < numDefaulTypes; i++) { - const t = getRandomEIP712Type() - // Add to the primary type definition - msg.types[msg.primaryType].push(t) - } - // Generate random values - msg.types[msg.primaryType].forEach((typeDef) => { - if (Object.keys(msg.types).indexOf(typeDef.type) === -1) { - // Normal EIP712 atomic type - msg.message[typeDef.name] = getRandomEIP712Val(typeDef.type) - } else { - // Custom type - msg.message[typeDef.name] = buildCustomTypeVal(typeDef.type, msg) - } - }) - return msg + // Create custom types and add them to the types definitions + const numCustomTypes = 1 + randInt(3) + const numDefaulTypes = 1 + randInt(3) + const customTypesMap: any = {} + for (let i = 0; i < numCustomTypes; i++) { + const subTypes: any[] = [] + for (let j = 0; j < 1 + randInt(3); j++) { + subTypes.push(getRandomEIP712Type(customTypesMap)) + } + // Capitalize custom type names to distinguish them + let typeName = getRandomName(true) + typeName = `${typeName.slice(0, 1).toUpperCase()}${typeName.slice(1)}` + customTypesMap[typeName] = subTypes + // Record the type + msg.types[typeName] = subTypes + // Add a record in the primary type. We will need to create a value later. + msg.types[msg.primaryType].push({ + name: getRandomName(), + type: typeName, + }) + } + // Generate default (i.e. "atomic") types to mix into the message + for (let i = 0; i < numDefaulTypes; i++) { + const t = getRandomEIP712Type() + // Add to the primary type definition + msg.types[msg.primaryType].push(t) + } + // Generate random values + msg.types[msg.primaryType].forEach((typeDef) => { + if (Object.keys(msg.types).indexOf(typeDef.type) === -1) { + // Normal EIP712 atomic type + msg.message[typeDef.name] = getRandomEIP712Val(typeDef.type) + } else { + // Custom type + msg.message[typeDef.name] = buildCustomTypeVal(typeDef.type, msg) + } + }) + return msg } //--------------------------------------------------- // Generic signing //--------------------------------------------------- export const validateGenericSig = (seed, sig, payloadBuf, req, pubkey?) => { - const { signerPath, hashType, curveType } = req - const HASHES = Constants.SIGNING.HASHES - const CURVES = Constants.SIGNING.CURVES - let hash: Buffer - if (curveType === CURVES.SECP256K1) { - if (hashType === HASHES.SHA256) { - hash = Buffer.from(Hash.sha256(payloadBuf)) - } else if (hashType === HASHES.KECCAK256) { - hash = Buffer.from(Hash.keccak256(payloadBuf)) - } else { - throw new Error('Bad params') - } - const { priv } = deriveSECP256K1Key(signerPath, seed) - const key = secp256k1.keyFromPrivate(priv) - const normalizedSig = { - r: normalizeSigComponent(sig.r).toString('hex'), - s: normalizeSigComponent(sig.s).toString('hex'), - } - expect(key.verify(hash, normalizedSig)).toEqualElseLog( - true, - 'Signature failed verification.', - ) - } else if (curveType === CURVES.ED25519) { - if (hashType !== HASHES.NONE) { - throw new Error('Bad params') - } - const { pub } = deriveED25519Key(signerPath, seed) - const signature = Buffer.concat([ - normalizeSigComponent(sig.r), - normalizeSigComponent(sig.s), - ]) - const edPublicKey = pubkey ? normalizeSigComponent(pubkey) : pub - const isValid = nacl.sign.detached.verify( - new Uint8Array(payloadBuf), - new Uint8Array(signature), - new Uint8Array(edPublicKey), - ) - expect(isValid).toEqualElseLog(true, 'Signature failed verification.') - } else { - throw new Error('Bad params') - } + const { signerPath, hashType, curveType } = req + const HASHES = Constants.SIGNING.HASHES + const CURVES = Constants.SIGNING.CURVES + let hash: Buffer + if (curveType === CURVES.SECP256K1) { + if (hashType === HASHES.SHA256) { + hash = Buffer.from(Hash.sha256(payloadBuf)) + } else if (hashType === HASHES.KECCAK256) { + hash = Buffer.from(Hash.keccak256(payloadBuf)) + } else { + throw new Error('Bad params') + } + const { priv } = deriveSECP256K1Key(signerPath, seed) + const key = secp256k1.keyFromPrivate(priv) + const normalizedSig = { + r: normalizeSigComponent(sig.r).toString('hex'), + s: normalizeSigComponent(sig.s).toString('hex'), + } + expect(key.verify(hash, normalizedSig)).toEqualElseLog( + true, + 'Signature failed verification.', + ) + } else if (curveType === CURVES.ED25519) { + if (hashType !== HASHES.NONE) { + throw new Error('Bad params') + } + const { pub } = deriveED25519Key(signerPath, seed) + const signature = Buffer.concat([ + normalizeSigComponent(sig.r), + normalizeSigComponent(sig.s), + ]) + const edPublicKey = pubkey ? normalizeSigComponent(pubkey) : pub + const isValid = nacl.sign.detached.verify( + new Uint8Array(payloadBuf), + new Uint8Array(signature), + new Uint8Array(edPublicKey), + ) + expect(isValid).toEqualElseLog(true, 'Signature failed verification.') + } else { + throw new Error('Bad params') + } } /** @@ -1027,126 +1027,126 @@ export const validateGenericSig = (seed, sig, payloadBuf, req, pubkey?) => { * @param tx - optional, an @ethereumjs/tx Transaction object */ export const getSigStr = (resp: any, tx?: TypedTransaction) => { - let v: string - if (resp.sig.v !== undefined) { - const vBuf = normalizeSigComponent(resp.sig.v) - const vHex = vBuf.toString('hex') - let vInt = vHex ? Number.parseInt(vHex, 16) : 0 - if (!Number.isFinite(vInt)) { - vInt = 0 - } - if (vInt >= 27) { - vInt -= 27 - } - v = vInt.toString(16).padStart(2, '0') - } else if (tx) { - v = getSignatureVParam(tx, resp) - } else { - throw new Error('Could not build sig string') - } - const rHex = normalizeSigComponent(resp.sig.r).toString('hex') - const sHex = normalizeSigComponent(resp.sig.s).toString('hex') - return `${rHex}${sHex}${v}` + let v: string + if (resp.sig.v !== undefined) { + const vBuf = normalizeSigComponent(resp.sig.v) + const vHex = vBuf.toString('hex') + let vInt = vHex ? Number.parseInt(vHex, 16) : 0 + if (!Number.isFinite(vInt)) { + vInt = 0 + } + if (vInt >= 27) { + vInt -= 27 + } + v = vInt.toString(16).padStart(2, '0') + } else if (tx) { + v = getSignatureVParam(tx, resp) + } else { + throw new Error('Could not build sig string') + } + const rHex = normalizeSigComponent(resp.sig.r).toString('hex') + const sHex = normalizeSigComponent(resp.sig.s).toString('hex') + return `${rHex}${sHex}${v}` } export function toBuffer(data: string | number | Buffer | Uint8Array): Buffer { - if (data === null || data === undefined) { - throw new Error('Invalid data') - } - if (Buffer.isBuffer(data)) { - return data - } - if (data instanceof Uint8Array) { - return Buffer.from(data.buffer, data.byteOffset, data.length) - } - if (typeof data === 'number') { - return ensureHexBuffer(data) - } - if (typeof data === 'string') { - const trimmed = data.trim() - const isHex = - trimmed.startsWith('0x') || - (/^[0-9a-fA-F]+$/.test(trimmed) && trimmed.length % 2 === 0) - return isHex ? ensureHexBuffer(trimmed) : Buffer.from(trimmed, 'utf8') - } - throw new Error('Unsupported data type') + if (data === null || data === undefined) { + throw new Error('Invalid data') + } + if (Buffer.isBuffer(data)) { + return data + } + if (data instanceof Uint8Array) { + return Buffer.from(data.buffer, data.byteOffset, data.length) + } + if (typeof data === 'number') { + return ensureHexBuffer(data) + } + if (typeof data === 'string') { + const trimmed = data.trim() + const isHex = + trimmed.startsWith('0x') || + (/^[0-9a-fA-F]+$/.test(trimmed) && trimmed.length % 2 === 0) + return isHex ? ensureHexBuffer(trimmed) : Buffer.from(trimmed, 'utf8') + } + throw new Error('Unsupported data type') } export function toUint8Array(data: Buffer): Uint8Array { - return new Uint8Array(data.buffer, data.byteOffset, data.length) + return new Uint8Array(data.buffer, data.byteOffset, data.length) } export function ensureHash32( - message: string | number | Buffer | Uint8Array, + message: string | number | Buffer | Uint8Array, ): Uint8Array { - const msgBuffer = toBuffer(message) - const digest = - msgBuffer.length === 32 ? msgBuffer : Buffer.from(Hash.keccak256(msgBuffer)) - if (digest.length !== 32) { - throw new Error('Failed to derive 32-byte hash for signature validation.') - } - return toUint8Array(digest) + const msgBuffer = toBuffer(message) + const digest = + msgBuffer.length === 32 ? msgBuffer : Buffer.from(Hash.keccak256(msgBuffer)) + if (digest.length !== 32) { + throw new Error('Failed to derive 32-byte hash for signature validation.') + } + return toUint8Array(digest) } export function validateSig( - resp: any, - message: string | number | Buffer | Uint8Array, + resp: any, + message: string | number | Buffer | Uint8Array, ) { - if (!resp.sig?.r || !resp.sig?.s || !resp.pubkey) { - throw new Error('Missing signature components') - } - const rBuf = normalizeSigComponent(resp.sig.r) - const sBuf = normalizeSigComponent(resp.sig.s) - const rs = new Uint8Array(Buffer.concat([rBuf, sBuf])) - const hash = ensureHash32(message) + if (!resp.sig?.r || !resp.sig?.s || !resp.pubkey) { + throw new Error('Missing signature components') + } + const rBuf = normalizeSigComponent(resp.sig.r) + const sBuf = normalizeSigComponent(resp.sig.s) + const rs = new Uint8Array(Buffer.concat([rBuf, sBuf])) + const hash = ensureHash32(message) - const pubkeyInput = toBuffer(resp.pubkey) - const normalizedPubkey = - pubkeyInput.length === 64 - ? Buffer.concat([Buffer.from([0x04]), pubkeyInput]) - : pubkeyInput - const isCompressed = - normalizedPubkey.length === 33 && - (normalizedPubkey[0] === 0x02 || normalizedPubkey[0] === 0x03) + const pubkeyInput = toBuffer(resp.pubkey) + const normalizedPubkey = + pubkeyInput.length === 64 + ? Buffer.concat([Buffer.from([0x04]), pubkeyInput]) + : pubkeyInput + const isCompressed = + normalizedPubkey.length === 33 && + (normalizedPubkey[0] === 0x02 || normalizedPubkey[0] === 0x03) - const recoveredA = Buffer.from( - ecdsaRecover(rs, 0, hash, isCompressed), - ).toString('hex') - const recoveredB = Buffer.from( - ecdsaRecover(rs, 1, hash, isCompressed), - ).toString('hex') - const expected = normalizedPubkey.toString('hex') - if (expected !== recoveredA && expected !== recoveredB) { - throw new Error('Signature did not validate.') - } + const recoveredA = Buffer.from( + ecdsaRecover(rs, 0, hash, isCompressed), + ).toString('hex') + const recoveredB = Buffer.from( + ecdsaRecover(rs, 1, hash, isCompressed), + ).toString('hex') + const expected = normalizedPubkey.toString('hex') + if (expected !== recoveredA && expected !== recoveredB) { + throw new Error('Signature did not validate.') + } } export const compressPubKey = (pub) => { - if (pub.length !== 65) { - return pub - } - const compressed = Buffer.alloc(33) - pub.slice(1, 33).copy(compressed, 1) - if (pub[64] % 2) { - compressed[0] = 0x03 - } else { - compressed[0] = 0x02 - } - return compressed + if (pub.length !== 65) { + return pub + } + const compressed = Buffer.alloc(33) + pub.slice(1, 33).copy(compressed, 1) + if (pub[64] % 2) { + compressed[0] = 0x03 + } else { + compressed[0] = 0x02 + } + return compressed } function _stripTrailingCommas(input: string): string { - // Use non-backtracking pattern to avoid ReDoS vulnerability - // Unrolled loop for multi-line comments: \/\*[^*]*\*+(?:[^/*][^*]*\*+)*\/ - return input.replace( - /,([\s]*(?:\/\/[^\n]*\n[\s]*|\/\*[^*]*\*+(?:[^/*][^*]*\*+)*\/[\s]*)*)([}\]])/g, - '$1$2', - ) + // Use non-backtracking pattern to avoid ReDoS vulnerability + // Unrolled loop for multi-line comments: \/\*[^*]*\*+(?:[^/*][^*]*\*+)*\/ + return input.replace( + /,([\s]*(?:\/\/[^\n]*\n[\s]*|\/\*[^*]*\*+(?:[^/*][^*]*\*+)*\/[\s]*)*)([}\]])/g, + '$1$2', + ) } export const getTestVectors = () => { - const raw = readFileSync( - `${process.cwd()}/src/__test__/vectors.jsonc`, - ).toString() - return jsonc.parse(_stripTrailingCommas(raw)) + const raw = readFileSync( + `${process.cwd()}/src/__test__/vectors.jsonc`, + ).toString() + return jsonc.parse(_stripTrailingCommas(raw)) } diff --git a/packages/sdk/src/__test__/utils/runners.ts b/packages/sdk/src/__test__/utils/runners.ts index a862f718..b408cd9e 100644 --- a/packages/sdk/src/__test__/utils/runners.ts +++ b/packages/sdk/src/__test__/utils/runners.ts @@ -1,44 +1,44 @@ import type { Client } from '../../client' import { getEncodedPayload } from '../../genericSigning' import type { - SigningPayload, - SignRequestParams, - TestRequestPayload, + SigningPayload, + SignRequestParams, + TestRequestPayload, } from '../../types' import { parseWalletJobResp, validateGenericSig } from './helpers' import { TEST_SEED } from './testConstants' import { testRequest } from './testRequest' export async function runTestCase( - payload: TestRequestPayload, - expectedCode: number, + payload: TestRequestPayload, + expectedCode: number, ) { - const res = await testRequest(payload) - //@ts-expect-error - Accessing private property - const fwVersion = payload.client.fwVersion - const parsedRes = parseWalletJobResp(res, fwVersion) - expect(parsedRes.resultStatus).toEqual(expectedCode) - return parsedRes + const res = await testRequest(payload) + //@ts-expect-error - Accessing private property + const fwVersion = payload.client.fwVersion + const parsedRes = parseWalletJobResp(res, fwVersion) + expect(parsedRes.resultStatus).toEqual(expectedCode) + return parsedRes } export async function runGeneric(request: SignRequestParams, client: Client) { - const response = await client.sign(request) - // runGeneric is only used for generic signing, not Bitcoin - const data = request.data as SigningPayload - // If no encoding type is specified we encode in hex or ascii - const encodingType = data.encodingType || null - const allowedEncodings = client.getFwConstants().genericSigning.encodingTypes - const { payloadBuf } = getEncodedPayload( - data.payload, - encodingType, - allowedEncodings, - ) - const seed = TEST_SEED - validateGenericSig(seed, response.sig, payloadBuf, data, response.pubkey) - return response + const response = await client.sign(request) + // runGeneric is only used for generic signing, not Bitcoin + const data = request.data as SigningPayload + // If no encoding type is specified we encode in hex or ascii + const encodingType = data.encodingType || null + const allowedEncodings = client.getFwConstants().genericSigning.encodingTypes + const { payloadBuf } = getEncodedPayload( + data.payload, + encodingType, + allowedEncodings, + ) + const seed = TEST_SEED + validateGenericSig(seed, response.sig, payloadBuf, data, response.pubkey) + return response } export const runEthMsg = async (req: SignRequestParams, client: Client) => { - const sig = await client.sign(req) - expect(sig.sig).not.toEqual(null) + const sig = await client.sign(req) + expect(sig.sig).not.toEqual(null) } diff --git a/packages/sdk/src/__test__/utils/serializers.ts b/packages/sdk/src/__test__/utils/serializers.ts index 68e12fdb..fd85c5e7 100644 --- a/packages/sdk/src/__test__/utils/serializers.ts +++ b/packages/sdk/src/__test__/utils/serializers.ts @@ -1,25 +1,25 @@ export const serializeObjectWithBuffers = (obj: any) => { - return Object.entries(obj).reduce((acc: any, [key, value]) => { - if (value instanceof Buffer) { - acc[key] = { isBuffer: true, value: value.toString('hex') } - } else if (typeof value === 'object') { - acc[key] = serializeObjectWithBuffers(value) - } else { - acc[key] = value - } - return acc - }, {}) + return Object.entries(obj).reduce((acc: any, [key, value]) => { + if (value instanceof Buffer) { + acc[key] = { isBuffer: true, value: value.toString('hex') } + } else if (typeof value === 'object') { + acc[key] = serializeObjectWithBuffers(value) + } else { + acc[key] = value + } + return acc + }, {}) } export const deserializeObjectWithBuffers = (obj: any) => { - return Object.entries(obj).reduce((acc: any, [key, value]: any) => { - if (value?.isBuffer) { - acc[key] = Buffer.from(value.value, 'hex') - } else if (typeof value === 'object') { - acc[key] = deserializeObjectWithBuffers(value) - } else { - acc[key] = value - } - return acc - }, {}) + return Object.entries(obj).reduce((acc: any, [key, value]: any) => { + if (value?.isBuffer) { + acc[key] = Buffer.from(value.value, 'hex') + } else if (typeof value === 'object') { + acc[key] = deserializeObjectWithBuffers(value) + } else { + acc[key] = value + } + return acc + }, {}) } diff --git a/packages/sdk/src/__test__/utils/setup.ts b/packages/sdk/src/__test__/utils/setup.ts index 1a9537b5..75d3bdf2 100644 --- a/packages/sdk/src/__test__/utils/setup.ts +++ b/packages/sdk/src/__test__/utils/setup.ts @@ -7,50 +7,50 @@ const question = readlineSync.question const TEMP_CLIENT_FILE = './client.temp' export async function setStoredClient(data: string) { - try { - fs.writeFileSync(TEMP_CLIENT_FILE, data) - } catch (err) { - console.error('Failed to store client data:', err) - return - } + try { + fs.writeFileSync(TEMP_CLIENT_FILE, data) + } catch (err) { + console.error('Failed to store client data:', err) + return + } } export async function getStoredClient() { - try { - return fs.readFileSync(TEMP_CLIENT_FILE, 'utf8') - } catch (err) { - console.error('Failed to read stored client data:', err) - return '' - } + try { + return fs.readFileSync(TEMP_CLIENT_FILE, 'utf8') + } catch (err) { + console.error('Failed to read stored client data:', err) + return '' + } } export async function setupClient() { - const deviceId = process.env.DEVICE_ID - const baseUrl = process.env.baseUrl || 'https://signing.gridpl.us' - const password = process.env.PASSWORD || 'password' - const name = process.env.APP_NAME || 'SDK Test' - let pairingSecret = process.env.PAIRING_SECRET - const isPaired = await setup({ - deviceId, - password, - name, - baseUrl, - getStoredClient, - setStoredClient, - }) - if (!isPaired) { - if (!pairingSecret) { - if (process.env.CI) { - throw new Error( - 'Pairing secret is required. If simulator is running, set PAIRING_SECRET environment variable.', - ) - } - pairingSecret = question('Enter pairing secret:') - if (!pairingSecret) { - throw new Error('Pairing secret is required.') - } - } - await pair(pairingSecret.toUpperCase()) - } - return getClient() + const deviceId = process.env.DEVICE_ID + const baseUrl = process.env.baseUrl || 'https://signing.gridpl.us' + const password = process.env.PASSWORD || 'password' + const name = process.env.APP_NAME || 'SDK Test' + let pairingSecret = process.env.PAIRING_SECRET + const isPaired = await setup({ + deviceId, + password, + name, + baseUrl, + getStoredClient, + setStoredClient, + }) + if (!isPaired) { + if (!pairingSecret) { + if (process.env.CI) { + throw new Error( + 'Pairing secret is required. If simulator is running, set PAIRING_SECRET environment variable.', + ) + } + pairingSecret = question('Enter pairing secret:') + if (!pairingSecret) { + throw new Error('Pairing secret is required.') + } + } + await pair(pairingSecret.toUpperCase()) + } + return getClient() } diff --git a/packages/sdk/src/__test__/utils/testConstants.ts b/packages/sdk/src/__test__/utils/testConstants.ts index 256a69f0..186c5c0c 100644 --- a/packages/sdk/src/__test__/utils/testConstants.ts +++ b/packages/sdk/src/__test__/utils/testConstants.ts @@ -13,7 +13,7 @@ import { mnemonicToSeedSync } from 'bip39' * test behavior and deterministic results. */ export const TEST_MNEMONIC = - 'test test test test test test test test test test test junk' + 'test test test test test test test test test test test junk' /** * Shared seed derived from TEST_MNEMONIC diff --git a/packages/sdk/src/__test__/utils/testEnvironment.ts b/packages/sdk/src/__test__/utils/testEnvironment.ts index 455f1f37..17937018 100644 --- a/packages/sdk/src/__test__/utils/testEnvironment.ts +++ b/packages/sdk/src/__test__/utils/testEnvironment.ts @@ -3,11 +3,11 @@ import * as dotenv from 'dotenv' dotenv.config() expect.extend({ - toEqualElseLog(received: unknown, expected: unknown, message?: string) { - return { - pass: received === expected, - message: () => - message ?? `Expected ${String(received)} to equal ${String(expected)}`, - } - }, + toEqualElseLog(received: unknown, expected: unknown, message?: string) { + return { + pass: received === expected, + message: () => + message ?? `Expected ${String(received)} to equal ${String(expected)}`, + } + }, }) diff --git a/packages/sdk/src/__test__/utils/testRequest.ts b/packages/sdk/src/__test__/utils/testRequest.ts index faa7bed9..8d50b70a 100644 --- a/packages/sdk/src/__test__/utils/testRequest.ts +++ b/packages/sdk/src/__test__/utils/testRequest.ts @@ -1,6 +1,6 @@ import { - LatticeSecureEncryptedRequestType, - encryptedSecureRequest, + LatticeSecureEncryptedRequestType, + encryptedSecureRequest, } from '../../protocol' import type { TestRequestPayload } from '../../types' @@ -9,31 +9,31 @@ import type { TestRequestPayload } from '../../types' * @category Lattice */ export const testRequest = async ({ - payload, - testID, - client, + payload, + testID, + client, }: TestRequestPayload) => { - if (!payload) { - throw new Error( - 'First argument must contain `testID` and `payload` fields.', - ) - } - const sharedSecret = client.sharedSecret - const ephemeralPub = client.ephemeralPub - const url = client.url + if (!payload) { + throw new Error( + 'First argument must contain `testID` and `payload` fields.', + ) + } + const sharedSecret = client.sharedSecret + const ephemeralPub = client.ephemeralPub + const url = client.url - const TEST_DATA_SZ = 500 - const data = Buffer.alloc(TEST_DATA_SZ + 6) - data.writeUInt32BE(testID, 0) - data.writeUInt16BE(payload.length, 4) - payload.copy(data, 6) + const TEST_DATA_SZ = 500 + const data = Buffer.alloc(TEST_DATA_SZ + 6) + data.writeUInt32BE(testID, 0) + data.writeUInt16BE(payload.length, 4) + payload.copy(data, 6) - const { decryptedData } = await encryptedSecureRequest({ - data, - requestType: LatticeSecureEncryptedRequestType.test, - sharedSecret, - ephemeralPub, - url, - }) - return decryptedData + const { decryptedData } = await encryptedSecureRequest({ + data, + requestType: LatticeSecureEncryptedRequestType.test, + sharedSecret, + ephemeralPub, + url, + }) + return decryptedData } diff --git a/packages/sdk/src/__test__/utils/viemComparison.ts b/packages/sdk/src/__test__/utils/viemComparison.ts index 7e96434f..466d6aa6 100644 --- a/packages/sdk/src/__test__/utils/viemComparison.ts +++ b/packages/sdk/src/__test__/utils/viemComparison.ts @@ -1,10 +1,10 @@ import { - type Address, - type Hex, - type TransactionSerializable, - type TypedDataDefinition, - parseTransaction, - serializeTransaction, + type Address, + type Hex, + type TransactionSerializable, + type TypedDataDefinition, + parseTransaction, + serializeTransaction, } from 'viem' import { privateKeyToAccount } from 'viem/accounts' import { sign, signMessage } from '../../api' @@ -15,20 +15,20 @@ import { TEST_SEED } from './testConstants' // Utility function to create foundry account address for comparison export const getFoundryAddress = (): Address => { - // Use first account derivation path: m/44'/60'/0'/0/0 - const foundryPath = [44 + 0x80000000, 60 + 0x80000000, 0x80000000, 0, 0] - return deriveAddress(TEST_SEED, foundryPath as any) as Address + // Use first account derivation path: m/44'/60'/0'/0/0 + const foundryPath = [44 + 0x80000000, 60 + 0x80000000, 0x80000000, 0, 0] + return deriveAddress(TEST_SEED, foundryPath as any) as Address } // Get Foundry private key for signing export const getFoundryPrivateKey = (): Hex => { - return '0xac0974bec39a17e36ba4a6b4d238ff944bacb478cbed5efcae784d7bf4f2ff80' // Foundry test account #0 private key + return '0xac0974bec39a17e36ba4a6b4d238ff944bacb478cbed5efcae784d7bf4f2ff80' // Foundry test account #0 private key } // Create foundry account for actual signing export const getFoundryAccount = () => { - const privateKey = getFoundryPrivateKey() - return privateKeyToAccount(privateKey) + const privateKey = getFoundryPrivateKey() + return privateKeyToAccount(privateKey) } // Transaction type for our test vectors - use viem's TransactionSerializable @@ -36,206 +36,206 @@ export type TestTransaction = TransactionSerializable // Sign transaction with both Lattice and viem, then compare export const signAndCompareTransaction = async ( - tx: TestTransaction, - testName: string, + tx: TestTransaction, + testName: string, ) => { - const foundryAccount = getFoundryAccount() - - try { - // Sign with Lattice using the new sign API that accepts TransactionSerializable directly - const latticeResult = await sign(tx).catch((err) => { - if (err.responseCode === 128) { - err.message = `NOTE: You must have \`FEATURE_TEST_RUNNER=1\` enabled in firmware to run these tests.\n${err.message}` - } - if (err.responseCode === 132) { - err.message = `NOTE: Please approve the transaction on your Lattice device.\n${err.message}` - } - throw err - }) - - // Sign with viem wallet (use original transaction) - const viemSignedTx = await foundryAccount.signTransaction(tx) - - // Parse the viem signed transaction to extract signature components - const parsedViemTx = parseTransaction(viemSignedTx as `0x${string}`) - - // Verify Lattice signature structure - expect(latticeResult.sig).toBeDefined() - expect(latticeResult.sig.r).toBeDefined() - expect(latticeResult.sig.s).toBeDefined() - - // For legacy transactions, expect v; for modern transactions, v might be undefined - if (tx.type === 'legacy') { - expect(latticeResult.sig.v).toBeDefined() - } - - // Verify viem signature components exist - expect(parsedViemTx.r).toBeDefined() - expect(parsedViemTx.s).toBeDefined() - - // For typed transactions (EIP-1559, EIP-2930, EIP-7702), check yParity - // For legacy transactions, check v - if (tx.type !== 'legacy') { - expect(parsedViemTx.yParity).toBeDefined() - } else { - expect(parsedViemTx.v).toBeDefined() - } - - // Get the signed transaction from Lattice - let latticeSignedTx: string - - // Check if the new viemTx field is available (automatic normalization) - if ((latticeResult as any).viemTx) { - latticeSignedTx = (latticeResult as any).viemTx - } else if (latticeResult.tx) { - // Use the provided signed transaction - latticeSignedTx = latticeResult.tx - } else { - // Fallback to manual normalization for backward compatibility - const normalizedSignedTx = normalizeLatticeSignature(latticeResult, tx) - latticeSignedTx = serializeTransaction(normalizedSignedTx) - } - - // The most important comparison: verify both produce the same serialized transaction - expect(latticeSignedTx).toBe(viemSignedTx) - - // Additional verification: compare signature components - // Lattice returns r,s as hex strings with 0x prefix or as Buffer - const normalizeSigComponent = (value: string | Buffer) => { - const hexString = - typeof value === 'string' - ? value - : `0x${Buffer.from(value).toString('hex')}` - const stripped = hexString.replace(/^0x/, '').toLowerCase() - return `0x${stripped.padStart(64, '0')}` - } - - const latticeR = normalizeSigComponent(latticeResult.sig.r) - const latticeS = normalizeSigComponent(latticeResult.sig.s) - if (!parsedViemTx.r || !parsedViemTx.s) - throw new Error('Missing signature components') - const viemR = normalizeSigComponent(parsedViemTx.r) - const viemS = normalizeSigComponent(parsedViemTx.s) - - // Verify r and s components match exactly - expect(latticeR).toBe(viemR) - expect(latticeS).toBe(viemS) - - return { - lattice: latticeResult, - viem: viemSignedTx, - success: true, - } - } catch (error) { - console.error(`❌ Test failed for ${testName}:`, error.message) - - throw error - } + const foundryAccount = getFoundryAccount() + + try { + // Sign with Lattice using the new sign API that accepts TransactionSerializable directly + const latticeResult = await sign(tx).catch((err) => { + if (err.responseCode === 128) { + err.message = `NOTE: You must have \`FEATURE_TEST_RUNNER=1\` enabled in firmware to run these tests.\n${err.message}` + } + if (err.responseCode === 132) { + err.message = `NOTE: Please approve the transaction on your Lattice device.\n${err.message}` + } + throw err + }) + + // Sign with viem wallet (use original transaction) + const viemSignedTx = await foundryAccount.signTransaction(tx) + + // Parse the viem signed transaction to extract signature components + const parsedViemTx = parseTransaction(viemSignedTx as `0x${string}`) + + // Verify Lattice signature structure + expect(latticeResult.sig).toBeDefined() + expect(latticeResult.sig.r).toBeDefined() + expect(latticeResult.sig.s).toBeDefined() + + // For legacy transactions, expect v; for modern transactions, v might be undefined + if (tx.type === 'legacy') { + expect(latticeResult.sig.v).toBeDefined() + } + + // Verify viem signature components exist + expect(parsedViemTx.r).toBeDefined() + expect(parsedViemTx.s).toBeDefined() + + // For typed transactions (EIP-1559, EIP-2930, EIP-7702), check yParity + // For legacy transactions, check v + if (tx.type !== 'legacy') { + expect(parsedViemTx.yParity).toBeDefined() + } else { + expect(parsedViemTx.v).toBeDefined() + } + + // Get the signed transaction from Lattice + let latticeSignedTx: string + + // Check if the new viemTx field is available (automatic normalization) + if ((latticeResult as any).viemTx) { + latticeSignedTx = (latticeResult as any).viemTx + } else if (latticeResult.tx) { + // Use the provided signed transaction + latticeSignedTx = latticeResult.tx + } else { + // Fallback to manual normalization for backward compatibility + const normalizedSignedTx = normalizeLatticeSignature(latticeResult, tx) + latticeSignedTx = serializeTransaction(normalizedSignedTx) + } + + // The most important comparison: verify both produce the same serialized transaction + expect(latticeSignedTx).toBe(viemSignedTx) + + // Additional verification: compare signature components + // Lattice returns r,s as hex strings with 0x prefix or as Buffer + const normalizeSigComponent = (value: string | Buffer) => { + const hexString = + typeof value === 'string' + ? value + : `0x${Buffer.from(value).toString('hex')}` + const stripped = hexString.replace(/^0x/, '').toLowerCase() + return `0x${stripped.padStart(64, '0')}` + } + + const latticeR = normalizeSigComponent(latticeResult.sig.r) + const latticeS = normalizeSigComponent(latticeResult.sig.s) + if (!parsedViemTx.r || !parsedViemTx.s) + throw new Error('Missing signature components') + const viemR = normalizeSigComponent(parsedViemTx.r) + const viemS = normalizeSigComponent(parsedViemTx.s) + + // Verify r and s components match exactly + expect(latticeR).toBe(viemR) + expect(latticeS).toBe(viemS) + + return { + lattice: latticeResult, + viem: viemSignedTx, + success: true, + } + } catch (error) { + console.error(`❌ Test failed for ${testName}:`, error.message) + + throw error + } } // EIP-712 message type for test vectors export type EIP712TestMessage = { - domain: TypedDataDefinition['domain'] - types: TypedDataDefinition['types'] - primaryType: string - message: Record + domain: TypedDataDefinition['domain'] + types: TypedDataDefinition['types'] + primaryType: string + message: Record } // Sign EIP-712 typed data with both Lattice and viem, then compare export const signAndCompareEIP712Message = async ( - eip712Message: EIP712TestMessage, - testName: string, + eip712Message: EIP712TestMessage, + testName: string, ) => { - const foundryAccount = getFoundryAccount() - - try { - // Sign with viem wallet - const viemSignature = await foundryAccount.signTypedData({ - domain: eip712Message.domain, - types: eip712Message.types, - primaryType: eip712Message.primaryType, - message: eip712Message.message, - }) - - // Sign with Lattice - need to add EIP712Domain to types - const latticeTypes = { - ...eip712Message.types, - EIP712Domain: [ - { name: 'name', type: 'string' }, - { name: 'version', type: 'string' }, - { name: 'chainId', type: 'uint256' }, - { name: 'verifyingContract', type: 'address' }, - ], - } - - const latticePayload = { - types: latticeTypes, - domain: eip712Message.domain, - primaryType: eip712Message.primaryType, - message: eip712Message.message, - } - - const latticeResult = await signMessage(latticePayload).catch((err) => { - if (err.responseCode === 128) { - err.message = `NOTE: You must have \`FEATURE_TEST_RUNNER=1\` enabled in firmware to run these tests.\n${err.message}` - } - if (err.responseCode === 132) { - err.message = `NOTE: Please approve the message signature on your Lattice device.\n${err.message}` - } - throw err - }) - - // Normalize signature components - const normalizeHex = (value: any): string => { - if (value === null || value === undefined) { - return '' - } - if (typeof value === 'bigint') { - let hex = value.toString(16) - if (hex.length % 2 !== 0) hex = `0${hex}` - return hex - } - if (typeof value === 'number') { - return value.toString(16) - } - if (typeof value === 'string') { - return value.startsWith('0x') ? value.slice(2) : value - } - if (Buffer.isBuffer(value) || value instanceof Uint8Array) { - return Buffer.from(value).toString('hex') - } - if (typeof value?.toString === 'function') { - const str = value.toString() - if (/^0x[0-9a-f]+$/i.test(str)) { - return str.slice(2) - } - if (/^[0-9a-f]+$/i.test(str)) { - return str - } - } - return ensureHexBuffer(value as string | number | Buffer).toString('hex') - } - - const rHex = normalizeHex(latticeResult.sig.r) - const sHex = normalizeHex(latticeResult.sig.s) - let vHex = normalizeHex(latticeResult.sig.v) - if (!vHex) { - vHex = '00' - } - vHex = vHex.padStart(2, '0') - - const latticeSignature = `0x${rHex}${sHex}${vHex}` - - // Compare signatures - expect(latticeSignature).toBe(viemSignature) - - return { - lattice: latticeResult, - viem: viemSignature, - success: true, - } - } catch (error) { - console.error(`❌ Test failed for ${testName}:`, error.message) - throw error - } + const foundryAccount = getFoundryAccount() + + try { + // Sign with viem wallet + const viemSignature = await foundryAccount.signTypedData({ + domain: eip712Message.domain, + types: eip712Message.types, + primaryType: eip712Message.primaryType, + message: eip712Message.message, + }) + + // Sign with Lattice - need to add EIP712Domain to types + const latticeTypes = { + ...eip712Message.types, + EIP712Domain: [ + { name: 'name', type: 'string' }, + { name: 'version', type: 'string' }, + { name: 'chainId', type: 'uint256' }, + { name: 'verifyingContract', type: 'address' }, + ], + } + + const latticePayload = { + types: latticeTypes, + domain: eip712Message.domain, + primaryType: eip712Message.primaryType, + message: eip712Message.message, + } + + const latticeResult = await signMessage(latticePayload).catch((err) => { + if (err.responseCode === 128) { + err.message = `NOTE: You must have \`FEATURE_TEST_RUNNER=1\` enabled in firmware to run these tests.\n${err.message}` + } + if (err.responseCode === 132) { + err.message = `NOTE: Please approve the message signature on your Lattice device.\n${err.message}` + } + throw err + }) + + // Normalize signature components + const normalizeHex = (value: any): string => { + if (value === null || value === undefined) { + return '' + } + if (typeof value === 'bigint') { + let hex = value.toString(16) + if (hex.length % 2 !== 0) hex = `0${hex}` + return hex + } + if (typeof value === 'number') { + return value.toString(16) + } + if (typeof value === 'string') { + return value.startsWith('0x') ? value.slice(2) : value + } + if (Buffer.isBuffer(value) || value instanceof Uint8Array) { + return Buffer.from(value).toString('hex') + } + if (typeof value?.toString === 'function') { + const str = value.toString() + if (/^0x[0-9a-f]+$/i.test(str)) { + return str.slice(2) + } + if (/^[0-9a-f]+$/i.test(str)) { + return str + } + } + return ensureHexBuffer(value as string | number | Buffer).toString('hex') + } + + const rHex = normalizeHex(latticeResult.sig.r) + const sHex = normalizeHex(latticeResult.sig.s) + let vHex = normalizeHex(latticeResult.sig.v) + if (!vHex) { + vHex = '00' + } + vHex = vHex.padStart(2, '0') + + const latticeSignature = `0x${rHex}${sHex}${vHex}` + + // Compare signatures + expect(latticeSignature).toBe(viemSignature) + + return { + lattice: latticeResult, + viem: viemSignature, + success: true, + } + } catch (error) { + console.error(`❌ Test failed for ${testName}:`, error.message) + throw error + } } diff --git a/packages/sdk/src/__test__/utils/vitest.d.ts b/packages/sdk/src/__test__/utils/vitest.d.ts index fa093948..d21d99a3 100644 --- a/packages/sdk/src/__test__/utils/vitest.d.ts +++ b/packages/sdk/src/__test__/utils/vitest.d.ts @@ -1,12 +1,12 @@ /// interface CustomMatchers { - toEqualElseLog(expected: unknown, message?: string): R + toEqualElseLog(expected: unknown, message?: string): R } declare module 'vitest' { - // eslint-disable-next-line @typescript-eslint/no-empty-object-type - interface Assertion extends CustomMatchers {} - // eslint-disable-next-line @typescript-eslint/no-empty-object-type - interface AsymmetricMatchersContaining extends CustomMatchers {} + // eslint-disable-next-line @typescript-eslint/no-empty-object-type + interface Assertion extends CustomMatchers {} + // eslint-disable-next-line @typescript-eslint/no-empty-object-type + interface AsymmetricMatchersContaining extends CustomMatchers {} } diff --git a/packages/sdk/src/__test__/vectors.jsonc b/packages/sdk/src/__test__/vectors.jsonc index 5bf28c97..7287f8d3 100644 --- a/packages/sdk/src/__test__/vectors.jsonc +++ b/packages/sdk/src/__test__/vectors.jsonc @@ -1,235 +1,235 @@ // Constant test vectors for various things { - "evm": { - // We use these vectors to test just-in-type calldata decoding for EVM transactions. - // The relevant protocol is Ethereum ABI serdes: https://docs.soliditylang.org/en/develop/abi-spec.html - "calldata": { - // Public transactions interacting with contracts. We use these - // to simulate a production environment of fetching ABI data and adding - // decoding info to the request. - "publicTxHashes": [ - { - "chainID": 137, - "hash": "0xd19a9bf70da20c10faf4d4355940cca8a5db91fa6cc1d258d6660d475d36616d", - // If you look at this example on Polygonscan, you'll notice the function being - // called is not actually available in the source code of the contract... - // So we needed to add a param to skip that test. - "skipBlockExplorerReq": true - }, - // swapExactTokensForTokens(uint256 amountIn, uint256 amountOutMin, address[] path, - // address to, uint256 deadline) - { - "chainID": 1, - "hash": "0xeee0752109c6d31038bab6c2b0a3e3857e8bffb9c229de71f0196fda6fb28a5e" - }, - // remove_liquidity_one_coin(uint256 _token_amount, int128 i, uint256 _min_amount) - { - "chainID": 1, - "hash": "0xa6173b4890303e12ca1b195ea4a04d8891b0d83768b734d2ecdb9c6dd9d828c4" - }, - // atomicMatch_(address[14],uint256[18],uint8[8],bytes,bytes,bytes,bytes,bytes,bytes, - // uint8[2],bytes32[5]) - // this one is too large for 1 frame - // "0x92c82aad37a925e3aabe3d603109a5e65993aa2615c4b2278c3d355d9d433dff", - // exactInput((bytes,address,uint256,uint256,uint256)) - { - "chainID": 1, - "hash": "0xee9710119c13dba6fe2de240ef1e24a2489e98d9c7dd5881e2901056764ee234" - }, - // exactInputSingle((address,address,uint24,address,uint256,uint256,uint256,uint160)) - { - "chainID": 1, - "hash": "0xc33308ca105630b99a0c24ddde4f4506aa4115ec6f1a25f1138f3bd8cfa32b49" - }, - // proveAndClaimWithResolver(bytes,(bytes,bytes)[],bytes,address,address) - { - "chainID": 1, - "hash": "0xe15e6205c6696d444fc426468defdf08e89a0f5b3b8a17c68428f7aeefd53ca1" - }, - // bulkTransfer(((uint8,address,uint256,uint256)[],address,bool)[],bytes32) - { - "chainID": 1, - "hash": "0x6f0ba3cb3c08e0ee443e4b7a78e1397413762e63287e348829c18f287c5a457f" - }, - // RECURSIVE DEFS - // multicall(bytes[]) - { - "chainID": 1, - "hash": "0xf4c48f0300acb2982fe8861ffd9291634115a33dc4107a66b4f9f43efb66896b" - }, - // execTransaction(address,uint256,(disperseTokenSimple(address,address[],uint256[])),uint8,uint256,uint256,uint256,address,address,bytes) - { - "chainID": 1, - "hash": "0xb6349347a1dec402c59cd94c5715513af7ecf3e532376f2a5a47c99ee224de2a" - }, - // execTransaction(address,uint256,(multicall(bytes[])),uint8,uint256,uint256,uint256,address,address,bytes) - { - "chainID": 1, - "hash": "0x2af244a02066c0a8e3998d247e071a03cd38ecbec60f93ddf63da0dce3932f86" - } - ], - // These are canonical ABI definitions that we use to test more unusual function types. - // The names are parsed and dummy data is filled in (see helpers in ./evm.ts). - // The accompanying tests also have some helpful print lines which are commented out - // by default but are useful for debugging. - // The purpose of these is to ensure all (most) ABI structures can be decoded. - // A few of these towards the end are not supported but they are such edge cases that - // it's not worth worrying about them for now. - "canonicalNames": [ - // --- - // Test a few vectors we alredy checked with Etherscan - // --- - "swapExactTokensForTokens(uint256,uint256,address[],address,uint256)", - "proveAndClaimWithResolver((bytes,bytes)[])", - "proveAndClaimWithResolver(bytes,(bytes,bytes)[])", - "proveAndClaimWithResolver(bytes,(bytes,bytes)[],bytes,address,address)", - "remove_liquidity_one_coin(uint256,int128,uint256)", - // --- - // Made up vectors that should all decode - // --- - "multidimArr(uint256[][],bool)", - "multidimArr(uint256[2][])", - "multidimArr(uint256[][2])", - "multidimArr(uint256[2],bool)", - "multidimArr(uint256[2][2],bool)", - "multidimArr(uint256[2][2][2],bool)", - "multidimArr(uint256[2][2],bool)", - "multidimArr(uint256[][][],bool)", - "multidimArr(bool,uint256[],bool)", - "multidimArr(bytes,uint256[][],bool)", - "singleTup((uint256,uint256))", - "singleTup((uint256[]))", - "singleTup((uint256[],bool))", - "singleTup((bool,bool,uint[],uint256))", - "singleTup((uint256[1],uint256[]))", - "singleTup((bytes,uint256))", - "singleTup((bytes,uint256)[])", - "singleTup((uint256,uint256)[])", - "singleTup((uint256,uint256)[2])", - "singleTup((uint256[2],uint256))", - "singleTup((uint256,uint256)[2])", - "singleTup((uint256,uint256)[],bytes)", - "singleTup((uint256,uint256)[][],bytes)", - "singleTup((uint256,uint256)[2][],bytes)", - "singleTup((uint256,uint256)[][2],bytes)", - "singleTup((uint256,uint256)[2],bytes)", - "singleTup(bytes[],(uint256,uint256)[2],bytes)", - "singleTup(bytes[2],(uint256,uint256)[2],bytes)", - "singleTup((uint256,uint256)[2],bytes)", - "singleTup((uint256,uint256)[2][2],bytes)", - "singleTup((uint256,uint256)[2][2][2],bytes)", - "singleTup((uint256),bool)", - "singleTup(bytes,(bytes),bool)", - "singleTup((uint256,bool),bool)", - "singleTup((uint256,bool),bytes)", - "singleTup(bytes,(uint256),bool)", - "singleTup(bytes,(bytes),bool)", - "singleTup(bool,(uint256,bytes),bytes)", - "singleTup((uint256,uint256)[],bool)", - "singleTup((bytes)[],bool)", - "singleTup((bytes)[1],bool)", - "multiTup((uint256,uint256)[],(bool))", - "multiTup((uint256,uint256)[2],(bool))", - "multiTup((uint256)[2],(bool)[])", - "multiTup((uint256)[2],(bool)[2])", - "multiTup((uint256)[],(bool))", - "multiTup((uint256)[],(bool)[2])", - "multiTup((uint256,uint256)[],(bool)[])", - "multiTup((uint256,uint256)[2],(bool)[])", - "multiTup((uint256)[2][2],(bool)[2][2])", - "multiTup((uint256)[2][],(bool)[2][])", - "multiTup((uint256)[2][],(bool)[][])", - "multiTup((uint256)[][],(bool)[2][])", - "multiTup((uint256)[][],(bool)[][])", - "multiTup((uint256)[][],(bool)[2][2])", - "multiTup((uint256)[2][2],(bool)[][])", - "nestedTup(uint256,(bytes)[1],bool)", - "nestedTup((uint256,(bool,address),bool))", - "nestedTup((uint256,(bool,bytes),bool))", - "nestedTup(bool,(uint256,(bool,bytes),bool))", - "nestedTup(bytes,(uint256,(bool,bytes),bool))", - "nestedTup(bytes,(uint256,(bool,bytes)[],bool))", - "nestedTup(bool,(uint256,(bool,bytes),bool)[])", - "nestedTup(bytes,(uint256,(bool,bytes)[][],bool)[])", - "nestedTup(bytes,(uint256,(bool)[2],bool))", - "nestedTup((uint256,(bytes[])[2],uint256))", - "nestedTup((uint256,(bytes[2])[2],uint256))", - "nestedTup((uint256,(bytes[2])[],uint256))", - "nestedTup((uint256,(bytes[])[],uint256))", - "nestedTup((uint256,(bytes)[2],bool))", - "nestedTup((bytes,(bytes)[2],bool))", - "nestedTup(bytes,(uint256,(bool)[2],bool))", - "nestedTup(bytes,(uint256,(bytes)[2],bool))" - // --- - // Extreme edge cases that do not currently decode - // --- - // "nestedTup(((bytes)[1],bool))", // does not decode - // "nestedTup(((bytes)[2],bool))", // does not decode - // "nestedTup(bytes,(uint256,(bool,bytes)[2],bool))", // does not decode - // "nestedTup(bytes,(uint256,(bool,bytes)[2],bool))", // does not decode - // "nestedTup(bytes,(uint256,(bool,bytes)[2],bool)[])", // does not decode - // "nestedTup(bytes,(uint256,(bool,bytes)[2],bool)[2])", // does not decode - // "nestedTup(bytes,(uint256,(bool,bytes)[][],bool)[2][])", // too large - ] - } - }, - "ethDeposit": { - "mnemonic": "winner much erosion weird rubber onion diagram mandate assist fluid slush theory", - "depositData": [ - { - "depositPath": [12381, 3600, 0, 0, 0], - "eth1WithdrawalKey": "0x95222290dd7278aa3ddd389cc1e1d165cc4bafe5", - "blsWithdrawalRef": { - "pubkey": "b77d6918d5edb4073a4ea4408073b698f00df478ff2726cdb8190e3be1fe5496f22b089f6ae4cf7cafaccb74683be5e4", - "withdrawal_credentials": "001cdf4c156742da500ed24475b8afd9a8b5a06f3a7ce8521bd3a23d6982ad7a", - "amount": 32000000000, - "signature": "8313af52b8822e9d8e66718e37eb8f14ca4cf25b7ab727dd9858c55667dd2a7fdbec9ed16c50911fbfee50eaeda76e1a026ebbf141b050ea94b203ff6b45d175cc33be26d65795e8cff02373ae68a2c3c94c261a0a7b45a897c5f4cead8cbf6e", - "deposit_message_root": "011f25b8a533635297ca36c97320aeb2644d7784f16b6dc668dc5acf3f17751c", - "deposit_data_root": "aa95cc7b31056cbc524fb82bc5641f60a0f3592bf960bdc875d24b332cd730ca", - "fork_version": "00000000", - "network_name": "mainnet", - "deposit_cli_version": "2.3.0" - }, - "eth1WithdrawalRef": { - "pubkey": "b77d6918d5edb4073a4ea4408073b698f00df478ff2726cdb8190e3be1fe5496f22b089f6ae4cf7cafaccb74683be5e4", - "withdrawal_credentials": "01000000000000000000000095222290dd7278aa3ddd389cc1e1d165cc4bafe5", - "amount": 32000000000, - "signature": "88dc6055732e97a2cd4524b658ef62f67daf12f03b0334fd72de4feeb7c44c681fb39ec6a27aee8d3a458a63642ef75711966581ec9dac56572973511c993560c52855d261e47476f970ffd76868c56e9a4d0f37bf23f05268ff73ddb686efeb", - "deposit_message_root": "e2b6b2dbc2f1a9cdd4a111f396c009490c67dfa08582c7c4ea18379adde6148f", - "deposit_data_root": "603160dc42fe11f2e2c057c85179cc008dd64c9d11ae20b8ffc9a5ceecece847", - "fork_version": "00000000", - "network_name": "mainnet", - "deposit_cli_version": "2.3.0" - } - }, - { - "depositPath": [12381, 3600, 1, 0, 0], - "eth1WithdrawalKey": "0x95222290dd7278aa3ddd389cc1e1d165cc4bafe5", - "blsWithdrawalRef": { - "pubkey": "a0aa536f68981cb705e506036e40b4373f67e10ff66f64835d63ffb3ca5e5eeea5396b2569315b060f0b90a2862a740c", - "withdrawal_credentials": "00783f5f1c25faa1282bbd1be67c0da48e5fbb9a21cf7bb95819b37a9a545429", - "amount": 32000000000, - "signature": "a54d6e949f359aaa9c5ec7ea078bff59a8a747ff8c1518963434d0090ee53dd4c87540d9cd7456a370b3db9d29491abe0413eda2384affa87c7b377b995ad617f5dc4c420593066a8b83345956b44ca0235344d1478e8500f9731dfae7128f4c", - "deposit_message_root": "3bd64fca376ba66a9b7428872f143e6e222e411af05401eba760722683a2cd47", - "deposit_data_root": "2dd6d49609584c73ec12194cb9a5f39841b6854e04e0df043bdb9eaa5b65bb86", - "fork_version": "00000000", - "network_name": "mainnet", - "deposit_cli_version": "2.3.0" - }, - "eth1WithdrawalRef": { - "pubkey": "a0aa536f68981cb705e506036e40b4373f67e10ff66f64835d63ffb3ca5e5eeea5396b2569315b060f0b90a2862a740c", - "withdrawal_credentials": "01000000000000000000000095222290dd7278aa3ddd389cc1e1d165cc4bafe5", - "amount": 32000000000, - "signature": "b1e98e44f1a8a550afeeaac839f93e74d07651c1fa1d12cf857787e20939d346f44a41997682b93a7d51267f77bd979406a7e42c4531abd23ee0d65e1ccc5432313714c1abd92fbebbbde798a9e6b875b4f59cb7a76d7d07094b66e102918248", - "deposit_message_root": "f4b94882da58e71f4557620c35b2c16c975b50093ae9a8041aaa67e840aba9aa", - "deposit_data_root": "1cc71b9a34ef5730154985c36ac2f9e4f9017959c144e9083d0e8f79311eed59", - "fork_version": "00000000", - "network_name": "mainnet", - "deposit_cli_version": "2.3.0" - } - } - ] - }, - // Dehydrated state for unit/integration tests - "dehydratedClientState": "{\"activeWallets\":{\"internal\":{\"uid\":\"162b56efe561c12bc93f703dc7026b3ec3d53923270c9259e2b08015fb9defd2\"},\"external\":{\"uid\":\"0000000000000000000000000000000000000000000000000000000000000000\"}},\"ephemeralPub\":\"04627c74680bee7907c07fdea2bde0ab1ac17c95213f379ccc1dce87f3586babe8ba0ed02688fd5539a54ea1b7b8ab0860d1853006f55f22a2e3ea4e190a17ab30\",\"fwVersion\":\"00110000\",\"deviceId\":\"Cd3dtg\",\"name\":\"SDK Test\",\"baseUrl\":\"https: //signing.gridpl.us\",\"privKey\":\"3fb53b677f73e4d2b8c89c303f6f6b349f0075ad88ea126cb9f6632085815dca\",\"retryCount\":3,\"timeout\":120000}" + "evm": { + // We use these vectors to test just-in-type calldata decoding for EVM transactions. + // The relevant protocol is Ethereum ABI serdes: https://docs.soliditylang.org/en/develop/abi-spec.html + "calldata": { + // Public transactions interacting with contracts. We use these + // to simulate a production environment of fetching ABI data and adding + // decoding info to the request. + "publicTxHashes": [ + { + "chainID": 137, + "hash": "0xd19a9bf70da20c10faf4d4355940cca8a5db91fa6cc1d258d6660d475d36616d", + // If you look at this example on Polygonscan, you'll notice the function being + // called is not actually available in the source code of the contract... + // So we needed to add a param to skip that test. + "skipBlockExplorerReq": true + }, + // swapExactTokensForTokens(uint256 amountIn, uint256 amountOutMin, address[] path, + // address to, uint256 deadline) + { + "chainID": 1, + "hash": "0xeee0752109c6d31038bab6c2b0a3e3857e8bffb9c229de71f0196fda6fb28a5e" + }, + // remove_liquidity_one_coin(uint256 _token_amount, int128 i, uint256 _min_amount) + { + "chainID": 1, + "hash": "0xa6173b4890303e12ca1b195ea4a04d8891b0d83768b734d2ecdb9c6dd9d828c4" + }, + // atomicMatch_(address[14],uint256[18],uint8[8],bytes,bytes,bytes,bytes,bytes,bytes, + // uint8[2],bytes32[5]) + // this one is too large for 1 frame + // "0x92c82aad37a925e3aabe3d603109a5e65993aa2615c4b2278c3d355d9d433dff", + // exactInput((bytes,address,uint256,uint256,uint256)) + { + "chainID": 1, + "hash": "0xee9710119c13dba6fe2de240ef1e24a2489e98d9c7dd5881e2901056764ee234" + }, + // exactInputSingle((address,address,uint24,address,uint256,uint256,uint256,uint160)) + { + "chainID": 1, + "hash": "0xc33308ca105630b99a0c24ddde4f4506aa4115ec6f1a25f1138f3bd8cfa32b49" + }, + // proveAndClaimWithResolver(bytes,(bytes,bytes)[],bytes,address,address) + { + "chainID": 1, + "hash": "0xe15e6205c6696d444fc426468defdf08e89a0f5b3b8a17c68428f7aeefd53ca1" + }, + // bulkTransfer(((uint8,address,uint256,uint256)[],address,bool)[],bytes32) + { + "chainID": 1, + "hash": "0x6f0ba3cb3c08e0ee443e4b7a78e1397413762e63287e348829c18f287c5a457f" + }, + // RECURSIVE DEFS + // multicall(bytes[]) + { + "chainID": 1, + "hash": "0xf4c48f0300acb2982fe8861ffd9291634115a33dc4107a66b4f9f43efb66896b" + }, + // execTransaction(address,uint256,(disperseTokenSimple(address,address[],uint256[])),uint8,uint256,uint256,uint256,address,address,bytes) + { + "chainID": 1, + "hash": "0xb6349347a1dec402c59cd94c5715513af7ecf3e532376f2a5a47c99ee224de2a" + }, + // execTransaction(address,uint256,(multicall(bytes[])),uint8,uint256,uint256,uint256,address,address,bytes) + { + "chainID": 1, + "hash": "0x2af244a02066c0a8e3998d247e071a03cd38ecbec60f93ddf63da0dce3932f86" + } + ], + // These are canonical ABI definitions that we use to test more unusual function types. + // The names are parsed and dummy data is filled in (see helpers in ./evm.ts). + // The accompanying tests also have some helpful print lines which are commented out + // by default but are useful for debugging. + // The purpose of these is to ensure all (most) ABI structures can be decoded. + // A few of these towards the end are not supported but they are such edge cases that + // it's not worth worrying about them for now. + "canonicalNames": [ + // --- + // Test a few vectors we alredy checked with Etherscan + // --- + "swapExactTokensForTokens(uint256,uint256,address[],address,uint256)", + "proveAndClaimWithResolver((bytes,bytes)[])", + "proveAndClaimWithResolver(bytes,(bytes,bytes)[])", + "proveAndClaimWithResolver(bytes,(bytes,bytes)[],bytes,address,address)", + "remove_liquidity_one_coin(uint256,int128,uint256)", + // --- + // Made up vectors that should all decode + // --- + "multidimArr(uint256[][],bool)", + "multidimArr(uint256[2][])", + "multidimArr(uint256[][2])", + "multidimArr(uint256[2],bool)", + "multidimArr(uint256[2][2],bool)", + "multidimArr(uint256[2][2][2],bool)", + "multidimArr(uint256[2][2],bool)", + "multidimArr(uint256[][][],bool)", + "multidimArr(bool,uint256[],bool)", + "multidimArr(bytes,uint256[][],bool)", + "singleTup((uint256,uint256))", + "singleTup((uint256[]))", + "singleTup((uint256[],bool))", + "singleTup((bool,bool,uint[],uint256))", + "singleTup((uint256[1],uint256[]))", + "singleTup((bytes,uint256))", + "singleTup((bytes,uint256)[])", + "singleTup((uint256,uint256)[])", + "singleTup((uint256,uint256)[2])", + "singleTup((uint256[2],uint256))", + "singleTup((uint256,uint256)[2])", + "singleTup((uint256,uint256)[],bytes)", + "singleTup((uint256,uint256)[][],bytes)", + "singleTup((uint256,uint256)[2][],bytes)", + "singleTup((uint256,uint256)[][2],bytes)", + "singleTup((uint256,uint256)[2],bytes)", + "singleTup(bytes[],(uint256,uint256)[2],bytes)", + "singleTup(bytes[2],(uint256,uint256)[2],bytes)", + "singleTup((uint256,uint256)[2],bytes)", + "singleTup((uint256,uint256)[2][2],bytes)", + "singleTup((uint256,uint256)[2][2][2],bytes)", + "singleTup((uint256),bool)", + "singleTup(bytes,(bytes),bool)", + "singleTup((uint256,bool),bool)", + "singleTup((uint256,bool),bytes)", + "singleTup(bytes,(uint256),bool)", + "singleTup(bytes,(bytes),bool)", + "singleTup(bool,(uint256,bytes),bytes)", + "singleTup((uint256,uint256)[],bool)", + "singleTup((bytes)[],bool)", + "singleTup((bytes)[1],bool)", + "multiTup((uint256,uint256)[],(bool))", + "multiTup((uint256,uint256)[2],(bool))", + "multiTup((uint256)[2],(bool)[])", + "multiTup((uint256)[2],(bool)[2])", + "multiTup((uint256)[],(bool))", + "multiTup((uint256)[],(bool)[2])", + "multiTup((uint256,uint256)[],(bool)[])", + "multiTup((uint256,uint256)[2],(bool)[])", + "multiTup((uint256)[2][2],(bool)[2][2])", + "multiTup((uint256)[2][],(bool)[2][])", + "multiTup((uint256)[2][],(bool)[][])", + "multiTup((uint256)[][],(bool)[2][])", + "multiTup((uint256)[][],(bool)[][])", + "multiTup((uint256)[][],(bool)[2][2])", + "multiTup((uint256)[2][2],(bool)[][])", + "nestedTup(uint256,(bytes)[1],bool)", + "nestedTup((uint256,(bool,address),bool))", + "nestedTup((uint256,(bool,bytes),bool))", + "nestedTup(bool,(uint256,(bool,bytes),bool))", + "nestedTup(bytes,(uint256,(bool,bytes),bool))", + "nestedTup(bytes,(uint256,(bool,bytes)[],bool))", + "nestedTup(bool,(uint256,(bool,bytes),bool)[])", + "nestedTup(bytes,(uint256,(bool,bytes)[][],bool)[])", + "nestedTup(bytes,(uint256,(bool)[2],bool))", + "nestedTup((uint256,(bytes[])[2],uint256))", + "nestedTup((uint256,(bytes[2])[2],uint256))", + "nestedTup((uint256,(bytes[2])[],uint256))", + "nestedTup((uint256,(bytes[])[],uint256))", + "nestedTup((uint256,(bytes)[2],bool))", + "nestedTup((bytes,(bytes)[2],bool))", + "nestedTup(bytes,(uint256,(bool)[2],bool))", + "nestedTup(bytes,(uint256,(bytes)[2],bool))" + // --- + // Extreme edge cases that do not currently decode + // --- + // "nestedTup(((bytes)[1],bool))", // does not decode + // "nestedTup(((bytes)[2],bool))", // does not decode + // "nestedTup(bytes,(uint256,(bool,bytes)[2],bool))", // does not decode + // "nestedTup(bytes,(uint256,(bool,bytes)[2],bool))", // does not decode + // "nestedTup(bytes,(uint256,(bool,bytes)[2],bool)[])", // does not decode + // "nestedTup(bytes,(uint256,(bool,bytes)[2],bool)[2])", // does not decode + // "nestedTup(bytes,(uint256,(bool,bytes)[][],bool)[2][])", // too large + ] + } + }, + "ethDeposit": { + "mnemonic": "winner much erosion weird rubber onion diagram mandate assist fluid slush theory", + "depositData": [ + { + "depositPath": [12381, 3600, 0, 0, 0], + "eth1WithdrawalKey": "0x95222290dd7278aa3ddd389cc1e1d165cc4bafe5", + "blsWithdrawalRef": { + "pubkey": "b77d6918d5edb4073a4ea4408073b698f00df478ff2726cdb8190e3be1fe5496f22b089f6ae4cf7cafaccb74683be5e4", + "withdrawal_credentials": "001cdf4c156742da500ed24475b8afd9a8b5a06f3a7ce8521bd3a23d6982ad7a", + "amount": 32000000000, + "signature": "8313af52b8822e9d8e66718e37eb8f14ca4cf25b7ab727dd9858c55667dd2a7fdbec9ed16c50911fbfee50eaeda76e1a026ebbf141b050ea94b203ff6b45d175cc33be26d65795e8cff02373ae68a2c3c94c261a0a7b45a897c5f4cead8cbf6e", + "deposit_message_root": "011f25b8a533635297ca36c97320aeb2644d7784f16b6dc668dc5acf3f17751c", + "deposit_data_root": "aa95cc7b31056cbc524fb82bc5641f60a0f3592bf960bdc875d24b332cd730ca", + "fork_version": "00000000", + "network_name": "mainnet", + "deposit_cli_version": "2.3.0" + }, + "eth1WithdrawalRef": { + "pubkey": "b77d6918d5edb4073a4ea4408073b698f00df478ff2726cdb8190e3be1fe5496f22b089f6ae4cf7cafaccb74683be5e4", + "withdrawal_credentials": "01000000000000000000000095222290dd7278aa3ddd389cc1e1d165cc4bafe5", + "amount": 32000000000, + "signature": "88dc6055732e97a2cd4524b658ef62f67daf12f03b0334fd72de4feeb7c44c681fb39ec6a27aee8d3a458a63642ef75711966581ec9dac56572973511c993560c52855d261e47476f970ffd76868c56e9a4d0f37bf23f05268ff73ddb686efeb", + "deposit_message_root": "e2b6b2dbc2f1a9cdd4a111f396c009490c67dfa08582c7c4ea18379adde6148f", + "deposit_data_root": "603160dc42fe11f2e2c057c85179cc008dd64c9d11ae20b8ffc9a5ceecece847", + "fork_version": "00000000", + "network_name": "mainnet", + "deposit_cli_version": "2.3.0" + } + }, + { + "depositPath": [12381, 3600, 1, 0, 0], + "eth1WithdrawalKey": "0x95222290dd7278aa3ddd389cc1e1d165cc4bafe5", + "blsWithdrawalRef": { + "pubkey": "a0aa536f68981cb705e506036e40b4373f67e10ff66f64835d63ffb3ca5e5eeea5396b2569315b060f0b90a2862a740c", + "withdrawal_credentials": "00783f5f1c25faa1282bbd1be67c0da48e5fbb9a21cf7bb95819b37a9a545429", + "amount": 32000000000, + "signature": "a54d6e949f359aaa9c5ec7ea078bff59a8a747ff8c1518963434d0090ee53dd4c87540d9cd7456a370b3db9d29491abe0413eda2384affa87c7b377b995ad617f5dc4c420593066a8b83345956b44ca0235344d1478e8500f9731dfae7128f4c", + "deposit_message_root": "3bd64fca376ba66a9b7428872f143e6e222e411af05401eba760722683a2cd47", + "deposit_data_root": "2dd6d49609584c73ec12194cb9a5f39841b6854e04e0df043bdb9eaa5b65bb86", + "fork_version": "00000000", + "network_name": "mainnet", + "deposit_cli_version": "2.3.0" + }, + "eth1WithdrawalRef": { + "pubkey": "a0aa536f68981cb705e506036e40b4373f67e10ff66f64835d63ffb3ca5e5eeea5396b2569315b060f0b90a2862a740c", + "withdrawal_credentials": "01000000000000000000000095222290dd7278aa3ddd389cc1e1d165cc4bafe5", + "amount": 32000000000, + "signature": "b1e98e44f1a8a550afeeaac839f93e74d07651c1fa1d12cf857787e20939d346f44a41997682b93a7d51267f77bd979406a7e42c4531abd23ee0d65e1ccc5432313714c1abd92fbebbbde798a9e6b875b4f59cb7a76d7d07094b66e102918248", + "deposit_message_root": "f4b94882da58e71f4557620c35b2c16c975b50093ae9a8041aaa67e840aba9aa", + "deposit_data_root": "1cc71b9a34ef5730154985c36ac2f9e4f9017959c144e9083d0e8f79311eed59", + "fork_version": "00000000", + "network_name": "mainnet", + "deposit_cli_version": "2.3.0" + } + } + ] + }, + // Dehydrated state for unit/integration tests + "dehydratedClientState": "{\"activeWallets\":{\"internal\":{\"uid\":\"162b56efe561c12bc93f703dc7026b3ec3d53923270c9259e2b08015fb9defd2\"},\"external\":{\"uid\":\"0000000000000000000000000000000000000000000000000000000000000000\"}},\"ephemeralPub\":\"04627c74680bee7907c07fdea2bde0ab1ac17c95213f379ccc1dce87f3586babe8ba0ed02688fd5539a54ea1b7b8ab0860d1853006f55f22a2e3ea4e190a17ab30\",\"fwVersion\":\"00110000\",\"deviceId\":\"Cd3dtg\",\"name\":\"SDK Test\",\"baseUrl\":\"https: //signing.gridpl.us\",\"privKey\":\"3fb53b677f73e4d2b8c89c303f6f6b349f0075ad88ea126cb9f6632085815dca\",\"retryCount\":3,\"timeout\":120000}" } diff --git a/packages/sdk/src/__test__/vectors/abi-vectors.ts b/packages/sdk/src/__test__/vectors/abi-vectors.ts index 9561f83a..581eee4a 100644 --- a/packages/sdk/src/__test__/vectors/abi-vectors.ts +++ b/packages/sdk/src/__test__/vectors/abi-vectors.ts @@ -1,137 +1,137 @@ export const ABI_TEST_VECTORS = [ - { - name: 'DAI totalSupply() - Zero parameters', - tx: { - type: 'eip1559' as const, - to: '0x6B175474E89094C44Da98b954EedeAC495271d0F' as `0x${string}`, - value: BigInt(0), - data: '0x18160ddd' as `0x${string}`, - nonce: 0, - maxFeePerGas: BigInt('20000000000'), - maxPriorityFeePerGas: BigInt('2000000000'), - gas: BigInt('100000'), - chainId: 1, - }, - category: 'empty-params', - }, - { - name: 'DAI transfer() - Basic types (address,uint256)', - tx: { - type: 'eip1559' as const, - to: '0x6B175474E89094C44Da98b954EedeAC495271d0F' as `0x${string}`, - value: BigInt(0), - data: '0xa9059cbb000000000000000000000000742d35cc6b2d9e9a3b7b7b4a7b0b2b7b0b2b7b0b0000000000000000000000000000000000000000000000001bc16d674ec80000' as `0x${string}`, - nonce: 0, - maxFeePerGas: BigInt('20000000000'), - maxPriorityFeePerGas: BigInt('2000000000'), - gas: BigInt('100000'), - chainId: 1, - }, - category: 'basic-types', - }, - { - name: 'DAI approve() - Basic types (address,uint256)', - tx: { - type: 'eip1559' as const, - to: '0x6B175474E89094C44Da98b954EedeAC495271d0F' as `0x${string}`, - value: BigInt(0), - data: '0x095ea7b3000000000000000000000000742d35cc6b2d9e9a3b7b7b4a7b0b2b7b0b2b7b0bffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff' as `0x${string}`, - nonce: 0, - maxFeePerGas: BigInt('20000000000'), - maxPriorityFeePerGas: BigInt('2000000000'), - gas: BigInt('100000'), - chainId: 1, - }, - category: 'basic-types-2', - }, - { - name: 'Uniswap V2 swapExactTokensForTokens() - Arrays and multiple params', - tx: { - type: 'eip1559' as const, - to: '0x7a250d5630B4cF539739dF2C5dAcb4c659F2488D' as `0x${string}`, - value: BigInt(0), - data: '0x38ed17390000000000000000000000000000000000000000000000000de0b6b3a76400000000000000000000000000000000000000000000000000000c7d713b49da000000000000000000000000000000000000000000000000000000000000000000a0000000000000000000000000742d35cc6b2d9e9a3b7b7b4a7b0b2b7b0b2b7b0b000000000000000000000000000000000000000000000000000000006553f10000000000000000000000000000000000000000000000000000000000000000020000000000000000000000006b175474e89094c44da98b954eedeac495271d0f000000000000000000000000dac17f958d2ee523a2206206994597c13d831ec7' as `0x${string}`, - nonce: 0, - maxFeePerGas: BigInt('20000000000'), - maxPriorityFeePerGas: BigInt('2000000000'), - gas: BigInt('200000'), - chainId: 1, - }, - category: 'defi-swap', - }, - { - name: 'USDC transferFrom() - Three parameters (address,address,uint256)', - tx: { - type: 'eip1559' as const, - to: '0xA0b86991c6218b36c1d19D4a2e9Eb0cE3606eB48' as `0x${string}`, - value: BigInt(0), - data: '0x23b872dd000000000000000000000000742d35cc6b2d9e9a3b7b7b4a7b0b2b7b0b2b7b0b000000000000000000000000a1b2c3d4e5f6a1b2c3d4e5f6a1b2c3d4e5f6a1b20000000000000000000000000000000000000000000000000000000005f5e100' as `0x${string}`, - nonce: 0, - maxFeePerGas: BigInt('20000000000'), - maxPriorityFeePerGas: BigInt('2000000000'), - gas: BigInt('300000'), - chainId: 1, - }, - category: 'three-params', - }, - { - name: 'WETH deposit() - Payable function with value', - tx: { - type: 'eip1559' as const, - to: '0xC02aaA39b223FE8D0A0e5C4F27eAD9083C756Cc2' as `0x${string}`, - value: BigInt('1000000000000000000'), - data: '0xd0e30db0' as `0x${string}`, - nonce: 0, - maxFeePerGas: BigInt('20000000000'), - maxPriorityFeePerGas: BigInt('2000000000'), - gas: BigInt('400000'), - chainId: 1, - }, - category: 'payable-function', - }, - { - name: 'WETH withdraw() - Single uint256 parameter', - tx: { - type: 'eip1559' as const, - to: '0xC02aaA39b223FE8D0A0e5C4F27eAD9083C756Cc2' as `0x${string}`, - value: BigInt(0), - data: '0x2e1a7d4d0000000000000000000000000000000000000000000000000de0b6b3a7640000' as `0x${string}`, - nonce: 0, - maxFeePerGas: BigInt('20000000000'), - maxPriorityFeePerGas: BigInt('2000000000'), - gas: BigInt('300000'), - chainId: 1, - }, - category: 'single-param', - }, - { - name: 'Uniswap V3 exactInputSingle() - Complex struct parameter', - tx: { - type: 'eip1559' as const, - to: '0x68b3465833fb72A70ecDF485E0e4C7bD8665Fc45' as `0x${string}`, - value: BigInt(0), - data: '0x414bf3890000000000000000000000006b175474e89094c44da98b954eedeac495271d0f000000000000000000000000a0b86991c6218b36c1d19d4a2e9eb0ce3606eb480000000000000000000000000000000000000000000000000000000000000bb8000000000000000000000000742d35cc6b2d9e9a3b7b7b4a7b0b2b7b0b2b7b0b0000000000000000000000000000000000000000000000000de0b6b3a76400000000000000000000000000000000000000000000000000000000000000989680000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000' as `0x${string}`, - nonce: 0, - maxFeePerGas: BigInt('20000000000'), - maxPriorityFeePerGas: BigInt('2000000000'), - gas: BigInt('500000'), - chainId: 1, - }, - category: 'complex-struct', - }, - { - name: 'Curve remove_liquidity_one_coin() - Negative int and complex params', - tx: { - type: 'eip1559' as const, - to: '0xbEbc44782C7dB0a1A60Cb6fe97d0b483032FF1C7' as `0x${string}`, - value: BigInt(0), - data: '0x1a4d01d20000000000000000000000000000000000000000000000000de0b6b3a764000000000000000000000000000000000000000000000000000000000000000000010000000000000000000000000000000000000000000000000c7d713b49da0000' as `0x${string}`, - nonce: 0, - maxFeePerGas: BigInt('20000000000'), - maxPriorityFeePerGas: BigInt('2000000000'), - gas: BigInt('600000'), - chainId: 1, - }, - category: 'complex-params', - }, + { + name: 'DAI totalSupply() - Zero parameters', + tx: { + type: 'eip1559' as const, + to: '0x6B175474E89094C44Da98b954EedeAC495271d0F' as `0x${string}`, + value: BigInt(0), + data: '0x18160ddd' as `0x${string}`, + nonce: 0, + maxFeePerGas: BigInt('20000000000'), + maxPriorityFeePerGas: BigInt('2000000000'), + gas: BigInt('100000'), + chainId: 1, + }, + category: 'empty-params', + }, + { + name: 'DAI transfer() - Basic types (address,uint256)', + tx: { + type: 'eip1559' as const, + to: '0x6B175474E89094C44Da98b954EedeAC495271d0F' as `0x${string}`, + value: BigInt(0), + data: '0xa9059cbb000000000000000000000000742d35cc6b2d9e9a3b7b7b4a7b0b2b7b0b2b7b0b0000000000000000000000000000000000000000000000001bc16d674ec80000' as `0x${string}`, + nonce: 0, + maxFeePerGas: BigInt('20000000000'), + maxPriorityFeePerGas: BigInt('2000000000'), + gas: BigInt('100000'), + chainId: 1, + }, + category: 'basic-types', + }, + { + name: 'DAI approve() - Basic types (address,uint256)', + tx: { + type: 'eip1559' as const, + to: '0x6B175474E89094C44Da98b954EedeAC495271d0F' as `0x${string}`, + value: BigInt(0), + data: '0x095ea7b3000000000000000000000000742d35cc6b2d9e9a3b7b7b4a7b0b2b7b0b2b7b0bffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff' as `0x${string}`, + nonce: 0, + maxFeePerGas: BigInt('20000000000'), + maxPriorityFeePerGas: BigInt('2000000000'), + gas: BigInt('100000'), + chainId: 1, + }, + category: 'basic-types-2', + }, + { + name: 'Uniswap V2 swapExactTokensForTokens() - Arrays and multiple params', + tx: { + type: 'eip1559' as const, + to: '0x7a250d5630B4cF539739dF2C5dAcb4c659F2488D' as `0x${string}`, + value: BigInt(0), + data: '0x38ed17390000000000000000000000000000000000000000000000000de0b6b3a76400000000000000000000000000000000000000000000000000000c7d713b49da000000000000000000000000000000000000000000000000000000000000000000a0000000000000000000000000742d35cc6b2d9e9a3b7b7b4a7b0b2b7b0b2b7b0b000000000000000000000000000000000000000000000000000000006553f10000000000000000000000000000000000000000000000000000000000000000020000000000000000000000006b175474e89094c44da98b954eedeac495271d0f000000000000000000000000dac17f958d2ee523a2206206994597c13d831ec7' as `0x${string}`, + nonce: 0, + maxFeePerGas: BigInt('20000000000'), + maxPriorityFeePerGas: BigInt('2000000000'), + gas: BigInt('200000'), + chainId: 1, + }, + category: 'defi-swap', + }, + { + name: 'USDC transferFrom() - Three parameters (address,address,uint256)', + tx: { + type: 'eip1559' as const, + to: '0xA0b86991c6218b36c1d19D4a2e9Eb0cE3606eB48' as `0x${string}`, + value: BigInt(0), + data: '0x23b872dd000000000000000000000000742d35cc6b2d9e9a3b7b7b4a7b0b2b7b0b2b7b0b000000000000000000000000a1b2c3d4e5f6a1b2c3d4e5f6a1b2c3d4e5f6a1b20000000000000000000000000000000000000000000000000000000005f5e100' as `0x${string}`, + nonce: 0, + maxFeePerGas: BigInt('20000000000'), + maxPriorityFeePerGas: BigInt('2000000000'), + gas: BigInt('300000'), + chainId: 1, + }, + category: 'three-params', + }, + { + name: 'WETH deposit() - Payable function with value', + tx: { + type: 'eip1559' as const, + to: '0xC02aaA39b223FE8D0A0e5C4F27eAD9083C756Cc2' as `0x${string}`, + value: BigInt('1000000000000000000'), + data: '0xd0e30db0' as `0x${string}`, + nonce: 0, + maxFeePerGas: BigInt('20000000000'), + maxPriorityFeePerGas: BigInt('2000000000'), + gas: BigInt('400000'), + chainId: 1, + }, + category: 'payable-function', + }, + { + name: 'WETH withdraw() - Single uint256 parameter', + tx: { + type: 'eip1559' as const, + to: '0xC02aaA39b223FE8D0A0e5C4F27eAD9083C756Cc2' as `0x${string}`, + value: BigInt(0), + data: '0x2e1a7d4d0000000000000000000000000000000000000000000000000de0b6b3a7640000' as `0x${string}`, + nonce: 0, + maxFeePerGas: BigInt('20000000000'), + maxPriorityFeePerGas: BigInt('2000000000'), + gas: BigInt('300000'), + chainId: 1, + }, + category: 'single-param', + }, + { + name: 'Uniswap V3 exactInputSingle() - Complex struct parameter', + tx: { + type: 'eip1559' as const, + to: '0x68b3465833fb72A70ecDF485E0e4C7bD8665Fc45' as `0x${string}`, + value: BigInt(0), + data: '0x414bf3890000000000000000000000006b175474e89094c44da98b954eedeac495271d0f000000000000000000000000a0b86991c6218b36c1d19d4a2e9eb0ce3606eb480000000000000000000000000000000000000000000000000000000000000bb8000000000000000000000000742d35cc6b2d9e9a3b7b7b4a7b0b2b7b0b2b7b0b0000000000000000000000000000000000000000000000000de0b6b3a76400000000000000000000000000000000000000000000000000000000000000989680000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000' as `0x${string}`, + nonce: 0, + maxFeePerGas: BigInt('20000000000'), + maxPriorityFeePerGas: BigInt('2000000000'), + gas: BigInt('500000'), + chainId: 1, + }, + category: 'complex-struct', + }, + { + name: 'Curve remove_liquidity_one_coin() - Negative int and complex params', + tx: { + type: 'eip1559' as const, + to: '0xbEbc44782C7dB0a1A60Cb6fe97d0b483032FF1C7' as `0x${string}`, + value: BigInt(0), + data: '0x1a4d01d20000000000000000000000000000000000000000000000000de0b6b3a764000000000000000000000000000000000000000000000000000000000000000000010000000000000000000000000000000000000000000000000c7d713b49da0000' as `0x${string}`, + nonce: 0, + maxFeePerGas: BigInt('20000000000'), + maxPriorityFeePerGas: BigInt('2000000000'), + gas: BigInt('600000'), + chainId: 1, + }, + category: 'complex-params', + }, ] diff --git a/packages/sdk/src/api/addressTags.ts b/packages/sdk/src/api/addressTags.ts index 0df65ad9..70c675f1 100644 --- a/packages/sdk/src/api/addressTags.ts +++ b/packages/sdk/src/api/addressTags.ts @@ -7,52 +7,52 @@ import { queue } from './utilities' * Sends request to the Lattice to add Address Tags. */ export const addAddressTags = async ( - tags: [{ [key: string]: string }], + tags: [{ [key: string]: string }], ): Promise => { - // convert an array of objects to an object - const records = tags.reduce((acc, tag) => { - const key = Object.keys(tag)[0] - acc[key] = tag[key] - return acc - }, {}) + // convert an array of objects to an object + const records = tags.reduce((acc, tag) => { + const key = Object.keys(tag)[0] + acc[key] = tag[key] + return acc + }, {}) - return queue((client) => client.addKvRecords({ records })) + return queue((client) => client.addKvRecords({ records })) } /** * Fetches Address Tags from the Lattice. */ export const fetchAddressTags = async ({ - n = MAX_ADDR, - start = 0, + n = MAX_ADDR, + start = 0, }: { n?: number; start?: number } = {}) => { - const addressTags: AddressTag[] = [] - let remainingToFetch = n - let fetched = start + const addressTags: AddressTag[] = [] + let remainingToFetch = n + let fetched = start - while (remainingToFetch > 0) { - await queue((client) => - client - .getKvRecords({ - start: fetched, - n: remainingToFetch > MAX_ADDR ? MAX_ADDR : remainingToFetch, - }) - .then(async (res) => { - addressTags.push(...res.records) - fetched = res.fetched + fetched - remainingToFetch = res.total - fetched - }), - ) - } - return addressTags + while (remainingToFetch > 0) { + await queue((client) => + client + .getKvRecords({ + start: fetched, + n: remainingToFetch > MAX_ADDR ? MAX_ADDR : remainingToFetch, + }) + .then(async (res) => { + addressTags.push(...res.records) + fetched = res.fetched + fetched + remainingToFetch = res.total - fetched + }), + ) + } + return addressTags } /** * Removes Address Tags from the Lattice. */ export const removeAddressTags = async ( - tags: AddressTag[], + tags: AddressTag[], ): Promise => { - const ids = tags.map((tag) => `${tag.id}`) - return queue((client: Client) => client.removeKvRecords({ ids })) + const ids = tags.map((tag) => `${tag.id}`) + return queue((client: Client) => client.removeKvRecords({ ids })) } diff --git a/packages/sdk/src/api/addresses.ts b/packages/sdk/src/api/addresses.ts index a9e51260..5e713037 100644 --- a/packages/sdk/src/api/addresses.ts +++ b/packages/sdk/src/api/addresses.ts @@ -1,62 +1,62 @@ import { - BTC_LEGACY_CHANGE_DERIVATION, - BTC_LEGACY_DERIVATION, - BTC_LEGACY_XPUB_PATH, - BTC_SEGWIT_CHANGE_DERIVATION, - BTC_SEGWIT_DERIVATION, - BTC_SEGWIT_ZPUB_PATH, - BTC_WRAPPED_SEGWIT_CHANGE_DERIVATION, - BTC_WRAPPED_SEGWIT_DERIVATION, - BTC_WRAPPED_SEGWIT_YPUB_PATH, - DEFAULT_ETH_DERIVATION, - HARDENED_OFFSET, - LEDGER_LEGACY_DERIVATION, - LEDGER_LIVE_DERIVATION, - MAX_ADDR, - SOLANA_DERIVATION, + BTC_LEGACY_CHANGE_DERIVATION, + BTC_LEGACY_DERIVATION, + BTC_LEGACY_XPUB_PATH, + BTC_SEGWIT_CHANGE_DERIVATION, + BTC_SEGWIT_DERIVATION, + BTC_SEGWIT_ZPUB_PATH, + BTC_WRAPPED_SEGWIT_CHANGE_DERIVATION, + BTC_WRAPPED_SEGWIT_DERIVATION, + BTC_WRAPPED_SEGWIT_YPUB_PATH, + DEFAULT_ETH_DERIVATION, + HARDENED_OFFSET, + LEDGER_LEGACY_DERIVATION, + LEDGER_LIVE_DERIVATION, + MAX_ADDR, + SOLANA_DERIVATION, } from '../constants' import { LatticeGetAddressesFlag } from '../protocol/latticeConstants' import type { GetAddressesRequestParams, WalletPath } from '../types' import { - getFlagFromPath, - getStartPath, - parseDerivationPathComponents, - queue, + getFlagFromPath, + getStartPath, + parseDerivationPathComponents, + queue, } from './utilities' type FetchAddressesParams = { - n?: number - startPathIndex?: number - flag?: number + n?: number + startPathIndex?: number + flag?: number } export const fetchAddresses = async ( - overrides?: Partial, + overrides?: Partial, ) => { - let allAddresses: string[] = [] - let totalFetched = 0 - const totalToFetch = overrides?.n || MAX_ADDR + let allAddresses: string[] = [] + let totalFetched = 0 + const totalToFetch = overrides?.n || MAX_ADDR - while (totalFetched < totalToFetch) { - const batchSize = Math.min(MAX_ADDR, totalToFetch - totalFetched) - const startPath = getStartPath(DEFAULT_ETH_DERIVATION, totalFetched) - await queue((client) => - client - .getAddresses({ - startPath, - ...overrides, - n: batchSize, - }) - .then((addresses: string[]) => { - if (addresses.length > 0) { - allAddresses = [...allAddresses, ...addresses] - totalFetched += addresses.length - } - }), - ) - } + while (totalFetched < totalToFetch) { + const batchSize = Math.min(MAX_ADDR, totalToFetch - totalFetched) + const startPath = getStartPath(DEFAULT_ETH_DERIVATION, totalFetched) + await queue((client) => + client + .getAddresses({ + startPath, + ...overrides, + n: batchSize, + }) + .then((addresses: string[]) => { + if (addresses.length > 0) { + allAddresses = [...allAddresses, ...addresses] + totalFetched += addresses.length + } + }), + ) + } - return allAddresses + return allAddresses } /** @@ -66,177 +66,177 @@ export const fetchAddresses = async ( * @param path - either the index of ETH signing path or the derivation path to fetch */ export const fetchAddress = async ( - path: number | WalletPath = 0, + path: number | WalletPath = 0, ): Promise => { - return fetchAddresses({ - startPath: - typeof path === 'number' - ? getStartPath(DEFAULT_ETH_DERIVATION, path) - : path, - n: 1, - }).then((addrs) => addrs[0]) + return fetchAddresses({ + startPath: + typeof path === 'number' + ? getStartPath(DEFAULT_ETH_DERIVATION, path) + : path, + n: 1, + }).then((addrs) => addrs[0]) } function createFetchBtcAddressesFunction(derivationPath: number[]) { - return async ( - { n, startPathIndex }: FetchAddressesParams = { - n: MAX_ADDR, - startPathIndex: 0, - }, - ) => { - return fetchAddresses({ - startPath: getStartPath(derivationPath, startPathIndex), - n, - }) - } + return async ( + { n, startPathIndex }: FetchAddressesParams = { + n: MAX_ADDR, + startPathIndex: 0, + }, + ) => { + return fetchAddresses({ + startPath: getStartPath(derivationPath, startPathIndex), + n, + }) + } } export const fetchBtcLegacyAddresses = createFetchBtcAddressesFunction( - BTC_LEGACY_DERIVATION, + BTC_LEGACY_DERIVATION, ) export const fetchBtcSegwitAddresses = createFetchBtcAddressesFunction( - BTC_SEGWIT_DERIVATION, + BTC_SEGWIT_DERIVATION, ) export const fetchBtcWrappedSegwitAddresses = createFetchBtcAddressesFunction( - BTC_WRAPPED_SEGWIT_DERIVATION, + BTC_WRAPPED_SEGWIT_DERIVATION, ) export const fetchBtcLegacyChangeAddresses = createFetchBtcAddressesFunction( - BTC_LEGACY_CHANGE_DERIVATION, + BTC_LEGACY_CHANGE_DERIVATION, ) export const fetchBtcSegwitChangeAddresses = createFetchBtcAddressesFunction( - BTC_SEGWIT_CHANGE_DERIVATION, + BTC_SEGWIT_CHANGE_DERIVATION, ) export const fetchBtcWrappedSegwitChangeAddresses = - createFetchBtcAddressesFunction(BTC_WRAPPED_SEGWIT_CHANGE_DERIVATION) + createFetchBtcAddressesFunction(BTC_WRAPPED_SEGWIT_CHANGE_DERIVATION) export const fetchSolanaAddresses = async ( - { n, startPathIndex }: FetchAddressesParams = { - n: MAX_ADDR, - startPathIndex: 0, - }, + { n, startPathIndex }: FetchAddressesParams = { + n: MAX_ADDR, + startPathIndex: 0, + }, ) => { - return fetchAddresses({ - startPath: getStartPath(SOLANA_DERIVATION, startPathIndex, 2), - n, - flag: 4, - }) + return fetchAddresses({ + startPath: getStartPath(SOLANA_DERIVATION, startPathIndex, 2), + n, + flag: 4, + }) } export const fetchLedgerLiveAddresses = async ( - { n, startPathIndex }: FetchAddressesParams = { - n: MAX_ADDR, - startPathIndex: 0, - }, + { n, startPathIndex }: FetchAddressesParams = { + n: MAX_ADDR, + startPathIndex: 0, + }, ) => { - const addresses = [] - for (let i = 0; i < n; i++) { - addresses.push( - queue((client) => - client - .getAddresses({ - startPath: getStartPath( - LEDGER_LIVE_DERIVATION, - startPathIndex + i, - 2, - ), - n: 1, - }) - .then((addresses) => addresses.map((address) => `${address}`)), - ), - ) - } - return Promise.all(addresses) + const addresses = [] + for (let i = 0; i < n; i++) { + addresses.push( + queue((client) => + client + .getAddresses({ + startPath: getStartPath( + LEDGER_LIVE_DERIVATION, + startPathIndex + i, + 2, + ), + n: 1, + }) + .then((addresses) => addresses.map((address) => `${address}`)), + ), + ) + } + return Promise.all(addresses) } export const fetchLedgerLegacyAddresses = async ( - { n, startPathIndex }: FetchAddressesParams = { - n: MAX_ADDR, - startPathIndex: 0, - }, + { n, startPathIndex }: FetchAddressesParams = { + n: MAX_ADDR, + startPathIndex: 0, + }, ) => { - const addresses = [] - for (let i = 0; i < n; i++) { - addresses.push( - queue((client) => - client - .getAddresses({ - startPath: getStartPath( - LEDGER_LEGACY_DERIVATION, - startPathIndex + i, - 3, - ), - n: 1, - }) - .then((addresses) => addresses.map((address) => `${address}`)), - ), - ) - } - return Promise.all(addresses) + const addresses = [] + for (let i = 0; i < n; i++) { + addresses.push( + queue((client) => + client + .getAddresses({ + startPath: getStartPath( + LEDGER_LEGACY_DERIVATION, + startPathIndex + i, + 3, + ), + n: 1, + }) + .then((addresses) => addresses.map((address) => `${address}`)), + ), + ) + } + return Promise.all(addresses) } export const fetchBip44ChangeAddresses = async ({ - n = MAX_ADDR, - startPathIndex = 0, + n = MAX_ADDR, + startPathIndex = 0, }: FetchAddressesParams = {}) => { - const addresses = [] - for (let i = 0; i < n; i++) { - addresses.push( - queue((client) => { - const startPath = [ - 44 + HARDENED_OFFSET, - 501 + HARDENED_OFFSET, - startPathIndex + i + HARDENED_OFFSET, - 0 + HARDENED_OFFSET, - ] - return client - .getAddresses({ - startPath, - n: 1, - flag: 4, - }) - .then((addresses) => addresses.map((address) => `${address}`)) - }), - ) - } - return Promise.all(addresses) + const addresses = [] + for (let i = 0; i < n; i++) { + addresses.push( + queue((client) => { + const startPath = [ + 44 + HARDENED_OFFSET, + 501 + HARDENED_OFFSET, + startPathIndex + i + HARDENED_OFFSET, + 0 + HARDENED_OFFSET, + ] + return client + .getAddresses({ + startPath, + n: 1, + flag: 4, + }) + .then((addresses) => addresses.map((address) => `${address}`)) + }), + ) + } + return Promise.all(addresses) } export async function fetchAddressesByDerivationPath( - path: string, - { n = 1, startPathIndex = 0, flag }: FetchAddressesParams = {}, + path: string, + { n = 1, startPathIndex = 0, flag }: FetchAddressesParams = {}, ): Promise { - const components = path.split('/').filter(Boolean) - const parsedPath = parseDerivationPathComponents(components) - const _flag = getFlagFromPath(parsedPath) - const wildcardIndex = components.findIndex((part) => - part.toLowerCase().includes('x'), - ) + const components = path.split('/').filter(Boolean) + const parsedPath = parseDerivationPathComponents(components) + const _flag = getFlagFromPath(parsedPath) + const wildcardIndex = components.findIndex((part) => + part.toLowerCase().includes('x'), + ) - if (wildcardIndex === -1) { - return queue((client) => - client.getAddresses({ - startPath: parsedPath, - flag: flag || _flag, - n, - }), - ) - } + if (wildcardIndex === -1) { + return queue((client) => + client.getAddresses({ + startPath: parsedPath, + flag: flag || _flag, + n, + }), + ) + } - const addresses: string[] = [] - for (let i = 0; i < n; i++) { - const currentPath = [...parsedPath] - currentPath[wildcardIndex] = currentPath[wildcardIndex] + startPathIndex + i + const addresses: string[] = [] + for (let i = 0; i < n; i++) { + const currentPath = [...parsedPath] + currentPath[wildcardIndex] = currentPath[wildcardIndex] + startPathIndex + i - const result = await queue((client) => - client.getAddresses({ - startPath: currentPath, - flag: flag || _flag, - n: 1, - }), - ) - addresses.push(...result) - } + const result = await queue((client) => + client.getAddresses({ + startPath: currentPath, + flag: flag || _flag, + n: 1, + }), + ) + addresses.push(...result) + } - return addresses + return addresses } /** @@ -244,10 +244,10 @@ export async function fetchAddressesByDerivationPath( * @returns xpub string */ export async function fetchBtcXpub(): Promise { - const result = await fetchAddressesByDerivationPath(BTC_LEGACY_XPUB_PATH, { - flag: LatticeGetAddressesFlag.secp256k1Xpub, - }) - return result[0] + const result = await fetchAddressesByDerivationPath(BTC_LEGACY_XPUB_PATH, { + flag: LatticeGetAddressesFlag.secp256k1Xpub, + }) + return result[0] } /** @@ -255,13 +255,13 @@ export async function fetchBtcXpub(): Promise { * @returns ypub string */ export async function fetchBtcYpub(): Promise { - const result = await fetchAddressesByDerivationPath( - BTC_WRAPPED_SEGWIT_YPUB_PATH, - { - flag: LatticeGetAddressesFlag.secp256k1Xpub, - }, - ) - return result[0] + const result = await fetchAddressesByDerivationPath( + BTC_WRAPPED_SEGWIT_YPUB_PATH, + { + flag: LatticeGetAddressesFlag.secp256k1Xpub, + }, + ) + return result[0] } /** @@ -269,8 +269,8 @@ export async function fetchBtcYpub(): Promise { * @returns zpub string */ export async function fetchBtcZpub(): Promise { - const result = await fetchAddressesByDerivationPath(BTC_SEGWIT_ZPUB_PATH, { - flag: LatticeGetAddressesFlag.secp256k1Xpub, - }) - return result[0] + const result = await fetchAddressesByDerivationPath(BTC_SEGWIT_ZPUB_PATH, { + flag: LatticeGetAddressesFlag.secp256k1Xpub, + }) + return result[0] } diff --git a/packages/sdk/src/api/index.ts b/packages/sdk/src/api/index.ts index 3f38d1aa..655eb91b 100644 --- a/packages/sdk/src/api/index.ts +++ b/packages/sdk/src/api/index.ts @@ -7,9 +7,9 @@ export * from './wallets' export * from './setup' export { - BTC_LEGACY_XPUB_PATH, - BTC_WRAPPED_SEGWIT_YPUB_PATH, - BTC_SEGWIT_ZPUB_PATH, + BTC_LEGACY_XPUB_PATH, + BTC_WRAPPED_SEGWIT_YPUB_PATH, + BTC_SEGWIT_ZPUB_PATH, } from '../constants' export type { AddressTag } from '../types' diff --git a/packages/sdk/src/api/setup.ts b/packages/sdk/src/api/setup.ts index 9e84b702..1bee97ed 100644 --- a/packages/sdk/src/api/setup.ts +++ b/packages/sdk/src/api/setup.ts @@ -13,19 +13,19 @@ import { buildLoadClientFn, buildSaveClientFn, queue } from './utilities' * @prop {Function} SetupParameters.setStoredClient - a function that stores the client data */ type SetupParameters = - | { - deviceId: string - password: string - name: string - appSecret?: string - getStoredClient: () => Promise - setStoredClient: (clientData: string | null) => Promise - baseUrl?: string - } - | { - getStoredClient: () => Promise - setStoredClient: (clientData: string | null) => Promise - } + | { + deviceId: string + password: string + name: string + appSecret?: string + getStoredClient: () => Promise + setStoredClient: (clientData: string | null) => Promise + baseUrl?: string + } + | { + getStoredClient: () => Promise + setStoredClient: (clientData: string | null) => Promise + } /** * `setup` initializes the Client and executes `connect()` if necessary. It returns a promise that @@ -43,43 +43,43 @@ type SetupParameters = * */ export const setup = async (params: SetupParameters): Promise => { - if (!params.getStoredClient) throw new Error('Client data getter required') - setLoadClient(buildLoadClientFn(params.getStoredClient)) + if (!params.getStoredClient) throw new Error('Client data getter required') + setLoadClient(buildLoadClientFn(params.getStoredClient)) - if (!params.setStoredClient) throw new Error('Client data setter required') - setSaveClient(buildSaveClientFn(params.setStoredClient)) + if (!params.setStoredClient) throw new Error('Client data setter required') + setSaveClient(buildSaveClientFn(params.setStoredClient)) - if ('deviceId' in params && 'password' in params && 'name' in params) { - const privKey = - params.appSecret || - Utils.generateAppSecret(params.deviceId, params.password, params.name) - const client = new Client({ - deviceId: params.deviceId, - privKey, - name: params.name, - baseUrl: params.baseUrl, - }) - return client.connect(params.deviceId).then(async (isPaired) => { - await saveClient(client.getStateData()) - return isPaired - }) - } else { - const client = await loadClient() - if (!client) throw new Error('Client not initialized') - const deviceId = client.getDeviceId() - if (!client.ephemeralPub && deviceId) { - return connect(deviceId) - } else { - await saveClient(client.getStateData()) - return Promise.resolve(true) - } - } + if ('deviceId' in params && 'password' in params && 'name' in params) { + const privKey = + params.appSecret || + Utils.generateAppSecret(params.deviceId, params.password, params.name) + const client = new Client({ + deviceId: params.deviceId, + privKey, + name: params.name, + baseUrl: params.baseUrl, + }) + return client.connect(params.deviceId).then(async (isPaired) => { + await saveClient(client.getStateData()) + return isPaired + }) + } else { + const client = await loadClient() + if (!client) throw new Error('Client not initialized') + const deviceId = client.getDeviceId() + if (!client.ephemeralPub && deviceId) { + return connect(deviceId) + } else { + await saveClient(client.getStateData()) + return Promise.resolve(true) + } + } } export const connect = async (deviceId: string): Promise => { - return queue((client) => client.connect(deviceId)) + return queue((client) => client.connect(deviceId)) } export const pair = async (pairingCode: string): Promise => { - return queue((client) => client.pair(pairingCode)) + return queue((client) => client.pair(pairingCode)) } diff --git a/packages/sdk/src/api/signing.ts b/packages/sdk/src/api/signing.ts index 559a44ee..fe3b785e 100644 --- a/packages/sdk/src/api/signing.ts +++ b/packages/sdk/src/api/signing.ts @@ -1,38 +1,38 @@ import { RLP } from '@ethereumjs/rlp' import { Hash } from 'ox' import { - type Address, - type Authorization, - type Hex, - type TransactionSerializable, - type TransactionSerializableEIP7702, - serializeTransaction, + type Address, + type Authorization, + type Hex, + type TransactionSerializable, + type TransactionSerializableEIP7702, + serializeTransaction, } from 'viem' import { Constants } from '..' import { - BTC_LEGACY_DERIVATION, - BTC_SEGWIT_DERIVATION, - BTC_WRAPPED_SEGWIT_DERIVATION, - CURRENCIES, - DEFAULT_ETH_DERIVATION, - SOLANA_DERIVATION, + BTC_LEGACY_DERIVATION, + BTC_SEGWIT_DERIVATION, + BTC_WRAPPED_SEGWIT_DERIVATION, + CURRENCIES, + DEFAULT_ETH_DERIVATION, + SOLANA_DERIVATION, } from '../constants' import { fetchDecoder } from '../functions/fetchDecoder' import type { - BitcoinSignPayload, - EIP712MessagePayload, - SignData, - SignRequestParams, - SigningPayload, - TransactionRequest, + BitcoinSignPayload, + EIP712MessagePayload, + SignData, + SignRequestParams, + SigningPayload, + TransactionRequest, } from '../types' import { getYParity } from '../util' import { isEIP712Payload, queue } from './utilities' // Define the authorization request type based on Viem's structure type AuthorizationRequest = { - chainId: number - nonce: number + chainId: number + nonce: number } & ({ address: Address } | { contractAddress: Address }) /** @@ -41,100 +41,100 @@ type AuthorizationRequest = { type RawTransaction = Hex | Uint8Array | Buffer export const sign = async ( - transaction: TransactionSerializable | RawTransaction, - overrides?: Omit, + transaction: TransactionSerializable | RawTransaction, + overrides?: Omit, ): Promise => { - const isRaw = isRawTransaction(transaction) - const serializedTx = isRaw - ? normalizeRawTransaction(transaction) - : serializeTransaction(transaction as TransactionSerializable) + const isRaw = isRawTransaction(transaction) + const serializedTx = isRaw + ? normalizeRawTransaction(transaction) + : serializeTransaction(transaction as TransactionSerializable) - // Determine the encoding type based on transaction type - let encodingType: - | typeof Constants.SIGNING.ENCODINGS.EVM - | typeof Constants.SIGNING.ENCODINGS.EIP7702_AUTH - | typeof Constants.SIGNING.ENCODINGS.EIP7702_AUTH_LIST = - Constants.SIGNING.ENCODINGS.EVM - if (!isRaw && (transaction as TransactionSerializable).type === 'eip7702') { - const eip7702Tx = transaction as TransactionSerializableEIP7702 - const hasAuthList = - eip7702Tx.authorizationList && eip7702Tx.authorizationList.length > 0 - encodingType = hasAuthList - ? Constants.SIGNING.ENCODINGS.EIP7702_AUTH_LIST - : Constants.SIGNING.ENCODINGS.EIP7702_AUTH - } + // Determine the encoding type based on transaction type + let encodingType: + | typeof Constants.SIGNING.ENCODINGS.EVM + | typeof Constants.SIGNING.ENCODINGS.EIP7702_AUTH + | typeof Constants.SIGNING.ENCODINGS.EIP7702_AUTH_LIST = + Constants.SIGNING.ENCODINGS.EVM + if (!isRaw && (transaction as TransactionSerializable).type === 'eip7702') { + const eip7702Tx = transaction as TransactionSerializableEIP7702 + const hasAuthList = + eip7702Tx.authorizationList && eip7702Tx.authorizationList.length > 0 + encodingType = hasAuthList + ? Constants.SIGNING.ENCODINGS.EIP7702_AUTH_LIST + : Constants.SIGNING.ENCODINGS.EIP7702_AUTH + } - // Only fetch decoder if we have the required fields - let decoder: Buffer | undefined - if ( - !isRaw && - 'data' in (transaction as TransactionSerializable) && - 'to' in (transaction as TransactionSerializable) && - 'chainId' in (transaction as TransactionSerializable) - ) { - decoder = await fetchDecoder({ - data: (transaction as TransactionSerializable).data, - to: (transaction as TransactionSerializable).to, - chainId: (transaction as TransactionSerializable).chainId, - } as TransactionRequest) - } + // Only fetch decoder if we have the required fields + let decoder: Buffer | undefined + if ( + !isRaw && + 'data' in (transaction as TransactionSerializable) && + 'to' in (transaction as TransactionSerializable) && + 'chainId' in (transaction as TransactionSerializable) + ) { + decoder = await fetchDecoder({ + data: (transaction as TransactionSerializable).data, + to: (transaction as TransactionSerializable).to, + chainId: (transaction as TransactionSerializable).chainId, + } as TransactionRequest) + } - const payload: SigningPayload = { - signerPath: DEFAULT_ETH_DERIVATION, - curveType: Constants.SIGNING.CURVES.SECP256K1, - hashType: Constants.SIGNING.HASHES.KECCAK256, - encodingType, - payload: serializedTx, - decoder, - } + const payload: SigningPayload = { + signerPath: DEFAULT_ETH_DERIVATION, + curveType: Constants.SIGNING.CURVES.SECP256K1, + hashType: Constants.SIGNING.HASHES.KECCAK256, + encodingType, + payload: serializedTx, + decoder, + } - return queue((client) => client.sign({ data: payload, ...overrides })) + return queue((client) => client.sign({ data: payload, ...overrides })) } /** * Sign a message with support for EIP-712 typed data and const assertions */ export function signMessage( - payload: - | string - | Uint8Array - | Buffer - | Buffer[] - | EIP712MessagePayload>, - overrides?: Omit, + payload: + | string + | Uint8Array + | Buffer + | Buffer[] + | EIP712MessagePayload>, + overrides?: Omit, ): Promise { - const basePayload: SigningPayload> = { - signerPath: DEFAULT_ETH_DERIVATION, - curveType: Constants.SIGNING.CURVES.SECP256K1, - hashType: Constants.SIGNING.HASHES.KECCAK256, - protocol: isEIP712Payload(payload) ? 'eip712' : 'signPersonal', - payload: payload as SigningPayload>['payload'], - } + const basePayload: SigningPayload> = { + signerPath: DEFAULT_ETH_DERIVATION, + curveType: Constants.SIGNING.CURVES.SECP256K1, + hashType: Constants.SIGNING.HASHES.KECCAK256, + protocol: isEIP712Payload(payload) ? 'eip712' : 'signPersonal', + payload: payload as SigningPayload>['payload'], + } - const tx: SignRequestParams = { - data: basePayload as SignRequestParams['data'], - currency: overrides?.currency ?? CURRENCIES.ETH_MSG, - ...(overrides ?? {}), - } + const tx: SignRequestParams = { + data: basePayload as SignRequestParams['data'], + currency: overrides?.currency ?? CURRENCIES.ETH_MSG, + ...(overrides ?? {}), + } - return queue((client) => client.sign(tx)) + return queue((client) => client.sign(tx)) } function isRawTransaction( - value: TransactionSerializable | RawTransaction, + value: TransactionSerializable | RawTransaction, ): value is RawTransaction { - return ( - typeof value === 'string' || - value instanceof Uint8Array || - Buffer.isBuffer(value) - ) + return ( + typeof value === 'string' || + value instanceof Uint8Array || + Buffer.isBuffer(value) + ) } function normalizeRawTransaction(tx: RawTransaction): Hex | Buffer { - if (typeof tx === 'string') { - return tx.startsWith('0x') ? (tx as Hex) : (`0x${tx}` as Hex) - } - return Buffer.from(tx) + if (typeof tx === 'string') { + return tx.startsWith('0x') ? (tx as Hex) : (`0x${tx}` as Hex) + } + return Buffer.from(tx) } /** @@ -142,143 +142,143 @@ function normalizeRawTransaction(tx: RawTransaction): Hex | Buffer { * Returns a Viem-compatible authorization object. */ export const signAuthorization = async ( - authorization: AuthorizationRequest, - overrides?: Omit, + authorization: AuthorizationRequest, + overrides?: Omit, ): Promise => { - // EIP-7702 authorization message is: MAGIC || rlp([chain_id, address, nonce]) - // MAGIC = 0x05 per EIP-7702 spec - const MAGIC = Buffer.from([0x05]) + // EIP-7702 authorization message is: MAGIC || rlp([chain_id, address, nonce]) + // MAGIC = 0x05 per EIP-7702 spec + const MAGIC = Buffer.from([0x05]) - // Handle the address/contractAddress alias - const address = - 'address' in authorization - ? authorization.address - : authorization.contractAddress + // Handle the address/contractAddress alias + const address = + 'address' in authorization + ? authorization.address + : authorization.contractAddress - const message = Buffer.concat([ - MAGIC, - Buffer.from( - RLP.encode([authorization.chainId, address, authorization.nonce]), - ), - ]) + const message = Buffer.concat([ + MAGIC, + Buffer.from( + RLP.encode([authorization.chainId, address, authorization.nonce]), + ), + ]) - const payload: SigningPayload = { - signerPath: DEFAULT_ETH_DERIVATION, - curveType: Constants.SIGNING.CURVES.SECP256K1, - hashType: Constants.SIGNING.HASHES.KECCAK256, - encodingType: Constants.SIGNING.ENCODINGS.EIP7702_AUTH, - payload: message, - } + const payload: SigningPayload = { + signerPath: DEFAULT_ETH_DERIVATION, + curveType: Constants.SIGNING.CURVES.SECP256K1, + hashType: Constants.SIGNING.HASHES.KECCAK256, + encodingType: Constants.SIGNING.ENCODINGS.EIP7702_AUTH, + payload: message, + } - // Get the signature with all components - const response = await queue((client) => - client.sign({ data: payload, ...overrides }), - ) + // Get the signature with all components + const response = await queue((client) => + client.sign({ data: payload, ...overrides }), + ) - // Extract signature components if they exist - if (response.sig && response.pubkey) { - // Calculate the correct y-parity value - const messageHash = Buffer.from(Hash.keccak256(message)) - const yParity = getYParity(messageHash, response.sig, response.pubkey) + // Extract signature components if they exist + if (response.sig && response.pubkey) { + // Calculate the correct y-parity value + const messageHash = Buffer.from(Hash.keccak256(message)) + const yParity = getYParity(messageHash, response.sig, response.pubkey) - // Handle both Buffer and string formats for r and s - const rValue = Buffer.isBuffer(response.sig.r) - ? `0x${response.sig.r.toString('hex')}` - : response.sig.r - const sValue = Buffer.isBuffer(response.sig.s) - ? `0x${response.sig.s.toString('hex')}` - : response.sig.s + // Handle both Buffer and string formats for r and s + const rValue = Buffer.isBuffer(response.sig.r) + ? `0x${response.sig.r.toString('hex')}` + : response.sig.r + const sValue = Buffer.isBuffer(response.sig.s) + ? `0x${response.sig.s.toString('hex')}` + : response.sig.s - // Create a complete Authorization object with all required signature components - const result: Authorization = { - address, // Viem compatibility - chainId: authorization.chainId, - nonce: authorization.nonce, - yParity, - r: rValue as Hex, - s: sValue as Hex, - } + // Create a complete Authorization object with all required signature components + const result: Authorization = { + address, // Viem compatibility + chainId: authorization.chainId, + nonce: authorization.nonce, + yParity, + r: rValue as Hex, + s: sValue as Hex, + } - return result - } + return result + } - throw new Error('Failed to get signature from device') + throw new Error('Failed to get signature from device') } /** * Sign an EIP-7702 transaction using Viem-compatible types */ export const signAuthorizationList = async ( - tx: TransactionSerializableEIP7702, + tx: TransactionSerializableEIP7702, ): Promise => { - const serializedTx = serializeTransaction(tx) + const serializedTx = serializeTransaction(tx) - const payload: SigningPayload = { - signerPath: DEFAULT_ETH_DERIVATION, - curveType: Constants.SIGNING.CURVES.SECP256K1, - hashType: Constants.SIGNING.HASHES.KECCAK256, - encodingType: Constants.SIGNING.ENCODINGS.EIP7702_AUTH_LIST, - payload: serializedTx, - } + const payload: SigningPayload = { + signerPath: DEFAULT_ETH_DERIVATION, + curveType: Constants.SIGNING.CURVES.SECP256K1, + hashType: Constants.SIGNING.HASHES.KECCAK256, + encodingType: Constants.SIGNING.ENCODINGS.EIP7702_AUTH_LIST, + payload: serializedTx, + } - const signedPayload = await queue((client) => client.sign({ data: payload })) + const signedPayload = await queue((client) => client.sign({ data: payload })) - // Return the SignData structure from Lattice, not the converted signature - return signedPayload + // Return the SignData structure from Lattice, not the converted signature + return signedPayload } export const signBtcLegacyTx = async ( - payload: BitcoinSignPayload, + payload: BitcoinSignPayload, ): Promise => { - const tx = { - data: { - signerPath: BTC_LEGACY_DERIVATION, - ...payload, - }, - currency: CURRENCIES.BTC, - } - return queue((client) => client.sign(tx)) + const tx = { + data: { + signerPath: BTC_LEGACY_DERIVATION, + ...payload, + }, + currency: CURRENCIES.BTC, + } + return queue((client) => client.sign(tx)) } export const signBtcSegwitTx = async ( - payload: BitcoinSignPayload, + payload: BitcoinSignPayload, ): Promise => { - const tx = { - data: { - signerPath: BTC_SEGWIT_DERIVATION, - ...payload, - }, - currency: CURRENCIES.BTC, - } - return queue((client) => client.sign(tx)) + const tx = { + data: { + signerPath: BTC_SEGWIT_DERIVATION, + ...payload, + }, + currency: CURRENCIES.BTC, + } + return queue((client) => client.sign(tx)) } export const signBtcWrappedSegwitTx = async ( - payload: BitcoinSignPayload, + payload: BitcoinSignPayload, ): Promise => { - const tx = { - data: { - signerPath: BTC_WRAPPED_SEGWIT_DERIVATION, - ...payload, - }, - currency: CURRENCIES.BTC, - } - return queue((client) => client.sign(tx)) + const tx = { + data: { + signerPath: BTC_WRAPPED_SEGWIT_DERIVATION, + ...payload, + }, + currency: CURRENCIES.BTC, + } + return queue((client) => client.sign(tx)) } export const signSolanaTx = async ( - payload: Buffer, - overrides?: SignRequestParams, + payload: Buffer, + overrides?: SignRequestParams, ): Promise => { - const tx = { - data: { - signerPath: SOLANA_DERIVATION, - curveType: Constants.SIGNING.CURVES.ED25519, - hashType: Constants.SIGNING.HASHES.NONE, - encodingType: Constants.SIGNING.ENCODINGS.SOLANA, - payload, - ...overrides, - }, - } - return queue((client) => client.sign(tx)) + const tx = { + data: { + signerPath: SOLANA_DERIVATION, + curveType: Constants.SIGNING.CURVES.ED25519, + hashType: Constants.SIGNING.HASHES.NONE, + encodingType: Constants.SIGNING.ENCODINGS.SOLANA, + payload, + ...overrides, + }, + } + return queue((client) => client.sign(tx)) } diff --git a/packages/sdk/src/api/state.ts b/packages/sdk/src/api/state.ts index 530b93b5..75771e36 100644 --- a/packages/sdk/src/api/state.ts +++ b/packages/sdk/src/api/state.ts @@ -3,15 +3,15 @@ import type { Client } from '../client' export let saveClient: (clientData: string | null) => Promise export const setSaveClient = ( - fn: (clientData: string | null) => Promise, + fn: (clientData: string | null) => Promise, ) => { - saveClient = fn + saveClient = fn } export let loadClient: () => Promise export const setLoadClient = (fn: () => Promise) => { - loadClient = fn + loadClient = fn } let functionQueue: Promise @@ -19,5 +19,5 @@ let functionQueue: Promise export const getFunctionQueue = () => functionQueue export const setFunctionQueue = (queue: Promise) => { - functionQueue = queue + functionQueue = queue } diff --git a/packages/sdk/src/api/utilities.ts b/packages/sdk/src/api/utilities.ts index 362ab948..b737639e 100644 --- a/packages/sdk/src/api/utilities.ts +++ b/packages/sdk/src/api/utilities.ts @@ -1,10 +1,10 @@ import { Client } from '../client' import { EXTERNAL, HARDENED_OFFSET } from '../constants' import { - getFunctionQueue, - loadClient, - saveClient, - setFunctionQueue, + getFunctionQueue, + loadClient, + saveClient, + setFunctionQueue, } from './state' /** @@ -17,108 +17,108 @@ import { * @internal */ export const queue = async (fn: (client: Client) => Promise) => { - const client = await loadClient() - if (!client) throw new Error('Client not initialized') - if (!getFunctionQueue()) { - setFunctionQueue(Promise.resolve()) - } - setFunctionQueue( - getFunctionQueue().then( - async () => - await fn(client) - .catch((err) => { - // Empty the queue if any function call fails - setFunctionQueue(Promise.resolve()) - throw err - }) - .then((returnValue) => { - saveClient(client.getStateData()) - return returnValue - }), - ), - ) - return getFunctionQueue() + const client = await loadClient() + if (!client) throw new Error('Client not initialized') + if (!getFunctionQueue()) { + setFunctionQueue(Promise.resolve()) + } + setFunctionQueue( + getFunctionQueue().then( + async () => + await fn(client) + .catch((err) => { + // Empty the queue if any function call fails + setFunctionQueue(Promise.resolve()) + throw err + }) + .then((returnValue) => { + saveClient(client.getStateData()) + return returnValue + }), + ), + ) + return getFunctionQueue() } export const getClient = async (): Promise => { - const client = loadClient ? await loadClient() : undefined - if (!client) throw new Error('Client not initialized') - return client + const client = loadClient ? await loadClient() : undefined + if (!client) throw new Error('Client not initialized') + return client } const encodeClientData = (clientData: string) => { - return Buffer.from(clientData).toString('base64') + return Buffer.from(clientData).toString('base64') } const decodeClientData = (clientData: string) => { - return Buffer.from(clientData, 'base64').toString() + return Buffer.from(clientData, 'base64').toString() } export const buildSaveClientFn = ( - setStoredClient: (clientData: string | null) => Promise, + setStoredClient: (clientData: string | null) => Promise, ) => { - return async (clientData: string | null) => { - if (!clientData) return - const encodedData = encodeClientData(clientData) - await setStoredClient(encodedData) - } + return async (clientData: string | null) => { + if (!clientData) return + const encodedData = encodeClientData(clientData) + await setStoredClient(encodedData) + } } export const buildLoadClientFn = (getStoredClient: () => Promise) => { - return async () => { - const clientData = await getStoredClient() - if (!clientData) return undefined - const stateData = decodeClientData(clientData) - if (!stateData) return undefined - const client = new Client({ stateData }) - if (!client) throw new Error('Client not initialized') - return client - } + return async () => { + const clientData = await getStoredClient() + if (!clientData) return undefined + const stateData = decodeClientData(clientData) + if (!stateData) return undefined + const client = new Client({ stateData }) + if (!client) throw new Error('Client not initialized') + return client + } } export const getStartPath = ( - defaultStartPath: number[], - addressIndex = 0, // The value to increment `defaultStartPath` - pathIndex = 4, // Which index in `defaultStartPath` array to increment + defaultStartPath: number[], + addressIndex = 0, // The value to increment `defaultStartPath` + pathIndex = 4, // Which index in `defaultStartPath` array to increment ): number[] => { - const startPath = [...defaultStartPath] - if (addressIndex > 0) { - startPath[pathIndex] = defaultStartPath[pathIndex] + addressIndex - } - return startPath + const startPath = [...defaultStartPath] + if (addressIndex > 0) { + startPath[pathIndex] = defaultStartPath[pathIndex] + addressIndex + } + return startPath } export const isEIP712Payload = (payload: any) => - typeof payload !== 'string' && - 'types' in payload && - 'domain' in payload && - 'primaryType' in payload && - 'message' in payload + typeof payload !== 'string' && + 'types' in payload && + 'domain' in payload && + 'primaryType' in payload && + 'message' in payload export function parseDerivationPath(path: string): number[] { - if (!path) return [] - const components = path.split('/').filter(Boolean) - return parseDerivationPathComponents(components) + if (!path) return [] + const components = path.split('/').filter(Boolean) + return parseDerivationPathComponents(components) } export function parseDerivationPathComponents(components: string[]): number[] { - return components.map((part) => { - const lowerPart = part.toLowerCase() - if (lowerPart === 'x') return 0 // Wildcard - if (lowerPart === "x'") return HARDENED_OFFSET // Hardened wildcard - if (part.endsWith("'")) - return Number.parseInt(part.slice(0, -1)) + HARDENED_OFFSET - const val = Number.parseInt(part) - if (Number.isNaN(val)) { - throw new Error(`Invalid part in derivation path: ${part}`) - } - return val - }) + return components.map((part) => { + const lowerPart = part.toLowerCase() + if (lowerPart === 'x') return 0 // Wildcard + if (lowerPart === "x'") return HARDENED_OFFSET // Hardened wildcard + if (part.endsWith("'")) + return Number.parseInt(part.slice(0, -1)) + HARDENED_OFFSET + const val = Number.parseInt(part) + if (Number.isNaN(val)) { + throw new Error(`Invalid part in derivation path: ${part}`) + } + return val + }) } export function getFlagFromPath(path: number[]): number | undefined { - if (path.length >= 2 && path[1] === 501 + HARDENED_OFFSET) { - return EXTERNAL.GET_ADDR_FLAGS.ED25519_PUB // SOLANA - } - return undefined + if (path.length >= 2 && path[1] === 501 + HARDENED_OFFSET) { + return EXTERNAL.GET_ADDR_FLAGS.ED25519_PUB // SOLANA + } + return undefined } diff --git a/packages/sdk/src/api/wallets.ts b/packages/sdk/src/api/wallets.ts index 0f7197d8..e716c9fc 100644 --- a/packages/sdk/src/api/wallets.ts +++ b/packages/sdk/src/api/wallets.ts @@ -5,5 +5,5 @@ import { queue } from './utilities' * Fetches the active wallets */ export const fetchActiveWallets = async (): Promise => { - return queue((client) => client.fetchActiveWallet()) + return queue((client) => client.fetchActiveWallet()) } diff --git a/packages/sdk/src/bitcoin.ts b/packages/sdk/src/bitcoin.ts index 3c64ba35..fee75215 100644 --- a/packages/sdk/src/bitcoin.ts +++ b/packages/sdk/src/bitcoin.ts @@ -9,12 +9,12 @@ const DEFAULT_SEQUENCE = 0xffffffff const DEFAULT_SIGHASH_BUFFER = Buffer.from('01', 'hex') // SIGHASH_ALL = 0x01 const { PURPOSES, COINS } = BIP_CONSTANTS const OP = { - ZERO: 0x00, - HASH160: 0xa9, - DUP: 0x76, - EQUAL: 0x87, - EQUALVERIFY: 0x88, - CHECKSIG: 0xac, + ZERO: 0x00, + HASH160: 0xa9, + DUP: 0x76, + EQUAL: 0x87, + EQUALVERIFY: 0x88, + CHECKSIG: 0xac, } const SEGWIT_V0 = 0x00 const SEGWIT_NATIVE_V0_PREFIX = 'bc' @@ -50,76 +50,76 @@ const BTC_SCRIPT_TYPE_P2WPKH_V0 = 0x04 // `version`: Transaction version of the inputs. All inputs must be of the same version! // `isSegwit`: a boolean which determines how we serialize the data and parameterize txb const buildBitcoinTxRequest = (data) => { - const { prevOuts, recipient, value, changePath, fee } = data - if (!changePath) throw new Error('No changePath provided.') - if (changePath.length !== 5) - throw new Error('Please provide a full change path.') - // Serialize the request - const payload = Buffer.alloc(59 + 69 * prevOuts.length) - let off = 0 - // Change version byte (a.k.a. address format byte) - const changeFmt = getAddressFormat(changePath) - payload.writeUInt8(changeFmt, 0) - off++ + const { prevOuts, recipient, value, changePath, fee } = data + if (!changePath) throw new Error('No changePath provided.') + if (changePath.length !== 5) + throw new Error('Please provide a full change path.') + // Serialize the request + const payload = Buffer.alloc(59 + 69 * prevOuts.length) + let off = 0 + // Change version byte (a.k.a. address format byte) + const changeFmt = getAddressFormat(changePath) + payload.writeUInt8(changeFmt, 0) + off++ - // Build the change data - payload.writeUInt32LE(changePath.length, off) - off += 4 - for (let i = 0; i < changePath.length; i++) { - payload.writeUInt32LE(changePath[i], off) - off += 4 - } + // Build the change data + payload.writeUInt32LE(changePath.length, off) + off += 4 + for (let i = 0; i < changePath.length; i++) { + payload.writeUInt32LE(changePath[i], off) + off += 4 + } - // Fee is a param - payload.writeUInt32LE(fee, off) - off += 4 - const dec = decodeAddress(recipient) - // Parameterize the recipient output - payload.writeUInt8(dec.versionByte, off) - off++ - dec.pkh.copy(payload, off) - off += dec.pkh.length - writeUInt64LE(value, payload, off) - off += 8 + // Fee is a param + payload.writeUInt32LE(fee, off) + off += 4 + const dec = decodeAddress(recipient) + // Parameterize the recipient output + payload.writeUInt8(dec.versionByte, off) + off++ + dec.pkh.copy(payload, off) + off += dec.pkh.length + writeUInt64LE(value, payload, off) + off += 8 - // Build the inputs from the previous outputs - payload.writeUInt8(prevOuts.length, off) - off++ - let inputSum = 0 + // Build the inputs from the previous outputs + payload.writeUInt8(prevOuts.length, off) + off++ + let inputSum = 0 - prevOuts.forEach((input) => { - if (!input.signerPath || input.signerPath.length !== 5) { - throw new Error('Full recipient path not specified ') - } - payload.writeUInt32LE(input.signerPath.length, off) - off += 4 - for (let i = 0; i < input.signerPath.length; i++) { - payload.writeUInt32LE(input.signerPath[i], off) - off += 4 - } - payload.writeUInt32LE(input.index, off) - off += 4 - writeUInt64LE(input.value, payload, off) - off += 8 - inputSum += input.value - const scriptType = getScriptType(input) - payload.writeUInt8(scriptType, off) - off++ - if (!Buffer.isBuffer(input.txHash)) - input.txHash = Buffer.from(input.txHash, 'hex') - input.txHash.copy(payload, off) - off += input.txHash.length - }) - // Send them back! - return { - payload, - schema: LatticeSignSchema.bitcoin, - origData: data, // We will need the original data for serializing the tx - changeData: { - // This data helps fill in the change output - value: inputSum - (value + fee), - }, - } + prevOuts.forEach((input) => { + if (!input.signerPath || input.signerPath.length !== 5) { + throw new Error('Full recipient path not specified ') + } + payload.writeUInt32LE(input.signerPath.length, off) + off += 4 + for (let i = 0; i < input.signerPath.length; i++) { + payload.writeUInt32LE(input.signerPath[i], off) + off += 4 + } + payload.writeUInt32LE(input.index, off) + off += 4 + writeUInt64LE(input.value, payload, off) + off += 8 + inputSum += input.value + const scriptType = getScriptType(input) + payload.writeUInt8(scriptType, off) + off++ + if (!Buffer.isBuffer(input.txHash)) + input.txHash = Buffer.from(input.txHash, 'hex') + input.txHash.copy(payload, off) + off += input.txHash.length + }) + // Send them back! + return { + payload, + schema: LatticeSignSchema.bitcoin, + origData: data, // We will need the original data for serializing the tx + changeData: { + // This data helps fill in the change output + value: inputSum - (value + fee), + }, + } } // Serialize a transaction consisting of inputs, outputs, and some @@ -130,333 +130,333 @@ const buildBitcoinTxRequest = (data) => { // (NOTE: either ALL are being spent, or none are) // -- lockTime = Will probably always be 0 const serializeTx = (data) => { - const { inputs, outputs, lockTime = 0 } = data - let payload = Buffer.alloc(4) - let off = 0 - // Always use version 2 - const version = 2 - const useWitness = needsWitness(inputs) - payload.writeUInt32LE(version, off) - off += 4 - if (useWitness) { - payload = concat(payload, Buffer.from('00', 'hex')) // marker = 0x00 - payload = concat(payload, Buffer.from('01', 'hex')) // flag = 0x01 - } - // Serialize signed inputs - const numInputs = getVarInt(inputs.length) - payload = concat(payload, numInputs) - off += numInputs.length - inputs.forEach((input) => { - payload = concat(payload, input.hash.reverse()) - off += input.hash.length - const index = getU32LE(input.index) - payload = concat(payload, index) - off += index.length - const scriptType = getScriptType(input) - // Build the sigScript. Note that p2wpkh does not have a scriptSig. - if (scriptType === BTC_SCRIPT_TYPE_P2SH_P2WPKH) { - // Build a vector (varSlice of varSlice) containing the redeemScript - const redeemScript = buildRedeemScript(input.pubkey) - const redeemScriptLen = getVarInt(redeemScript.length) - const slice = Buffer.concat([redeemScriptLen, redeemScript]) - const sliceLen = getVarInt(slice.length) - payload = concat(payload, sliceLen) - off += sliceLen.length - payload = concat(payload, slice) - off += slice.length - } else if (scriptType === BTC_SCRIPT_TYPE_P2PKH) { - // Build the signature + pubkey script to spend this input - const slice = buildSig(input.sig, input.pubkey) - payload = concat(payload, slice) - off += slice.length - } else if (scriptType === BTC_SCRIPT_TYPE_P2WPKH_V0) { - const emptyScript = Buffer.from('00', 'hex') - payload = concat(payload, emptyScript) - off += 1 - } - // Use the default sequence for all transactions - const sequence = getU32LE(DEFAULT_SEQUENCE) - payload = concat(payload, sequence) - off += sequence.length - }) - // Serialize outputs - const numOutputs = getVarInt(outputs.length) - payload = concat(payload, numOutputs) - off += numOutputs.length - outputs.forEach((output) => { - const value = getU64LE(output.value) - payload = concat(payload, value) - off += value.length - // Build the output locking script and write it as a var slice - const script = buildLockingScript(output.recipient) - const scriptLen = getVarInt(script.length) - payload = concat(payload, scriptLen) - off += scriptLen.length - payload = concat(payload, script) - off += script.length - }) - // Add witness data if needed - if (useWitness) { - const sigs = [] - const pubkeys = [] - for (let i = 0; i < inputs.length; i++) { - sigs.push(inputs[i].sig) - pubkeys.push(inputs[i].pubkey) - } - const witnessSlice = buildWitness(sigs, pubkeys) - payload = concat(payload, witnessSlice) - off += witnessSlice.length - } - // Finish with locktime - return Buffer.concat([payload, getU32LE(lockTime)]).toString('hex') + const { inputs, outputs, lockTime = 0 } = data + let payload = Buffer.alloc(4) + let off = 0 + // Always use version 2 + const version = 2 + const useWitness = needsWitness(inputs) + payload.writeUInt32LE(version, off) + off += 4 + if (useWitness) { + payload = concat(payload, Buffer.from('00', 'hex')) // marker = 0x00 + payload = concat(payload, Buffer.from('01', 'hex')) // flag = 0x01 + } + // Serialize signed inputs + const numInputs = getVarInt(inputs.length) + payload = concat(payload, numInputs) + off += numInputs.length + inputs.forEach((input) => { + payload = concat(payload, input.hash.reverse()) + off += input.hash.length + const index = getU32LE(input.index) + payload = concat(payload, index) + off += index.length + const scriptType = getScriptType(input) + // Build the sigScript. Note that p2wpkh does not have a scriptSig. + if (scriptType === BTC_SCRIPT_TYPE_P2SH_P2WPKH) { + // Build a vector (varSlice of varSlice) containing the redeemScript + const redeemScript = buildRedeemScript(input.pubkey) + const redeemScriptLen = getVarInt(redeemScript.length) + const slice = Buffer.concat([redeemScriptLen, redeemScript]) + const sliceLen = getVarInt(slice.length) + payload = concat(payload, sliceLen) + off += sliceLen.length + payload = concat(payload, slice) + off += slice.length + } else if (scriptType === BTC_SCRIPT_TYPE_P2PKH) { + // Build the signature + pubkey script to spend this input + const slice = buildSig(input.sig, input.pubkey) + payload = concat(payload, slice) + off += slice.length + } else if (scriptType === BTC_SCRIPT_TYPE_P2WPKH_V0) { + const emptyScript = Buffer.from('00', 'hex') + payload = concat(payload, emptyScript) + off += 1 + } + // Use the default sequence for all transactions + const sequence = getU32LE(DEFAULT_SEQUENCE) + payload = concat(payload, sequence) + off += sequence.length + }) + // Serialize outputs + const numOutputs = getVarInt(outputs.length) + payload = concat(payload, numOutputs) + off += numOutputs.length + outputs.forEach((output) => { + const value = getU64LE(output.value) + payload = concat(payload, value) + off += value.length + // Build the output locking script and write it as a var slice + const script = buildLockingScript(output.recipient) + const scriptLen = getVarInt(script.length) + payload = concat(payload, scriptLen) + off += scriptLen.length + payload = concat(payload, script) + off += script.length + }) + // Add witness data if needed + if (useWitness) { + const sigs = [] + const pubkeys = [] + for (let i = 0; i < inputs.length; i++) { + sigs.push(inputs[i].sig) + pubkeys.push(inputs[i].pubkey) + } + const witnessSlice = buildWitness(sigs, pubkeys) + payload = concat(payload, witnessSlice) + off += witnessSlice.length + } + // Finish with locktime + return Buffer.concat([payload, getU32LE(lockTime)]).toString('hex') } // Convert a pubkeyhash to a bitcoin base58check address with a version byte const getBitcoinAddress = (pubkeyhash, version) => { - let bech32Prefix = null - let bech32Version = null - if (version === FMT_SEGWIT_NATIVE_V0) { - bech32Prefix = SEGWIT_NATIVE_V0_PREFIX - bech32Version = SEGWIT_V0 - } else if (version === FMT_SEGWIT_NATIVE_V0_TESTNET) { - bech32Prefix = SEGWIT_NATIVE_V0_TESTNET_PREFIX - bech32Version = SEGWIT_V0 - } - if (bech32Prefix !== null && bech32Version !== null) { - const words = bech32.toWords(pubkeyhash) - words.unshift(bech32Version) - return bech32.encode(bech32Prefix, words) - } else { - return bs58check.encode(Buffer.concat([Buffer.from([version]), pubkeyhash])) - } + let bech32Prefix = null + let bech32Version = null + if (version === FMT_SEGWIT_NATIVE_V0) { + bech32Prefix = SEGWIT_NATIVE_V0_PREFIX + bech32Version = SEGWIT_V0 + } else if (version === FMT_SEGWIT_NATIVE_V0_TESTNET) { + bech32Prefix = SEGWIT_NATIVE_V0_TESTNET_PREFIX + bech32Version = SEGWIT_V0 + } + if (bech32Prefix !== null && bech32Version !== null) { + const words = bech32.toWords(pubkeyhash) + words.unshift(bech32Version) + return bech32.encode(bech32Prefix, words) + } else { + return bs58check.encode(Buffer.concat([Buffer.from([version]), pubkeyhash])) + } } // Builder utils //----------------------- function buildRedeemScript(pubkey) { - const redeemScript = Buffer.alloc(22) - const shaHash = Buffer.from(Hash.sha256(pubkey)) - const pubkeyhash = Buffer.from( - ripemd160().update(shaHash).digest('hex'), - 'hex', - ) - redeemScript.writeUInt8(OP.ZERO, 0) - redeemScript.writeUInt8(pubkeyhash.length, 1) - pubkeyhash.copy(redeemScript, 2) - return redeemScript + const redeemScript = Buffer.alloc(22) + const shaHash = Buffer.from(Hash.sha256(pubkey)) + const pubkeyhash = Buffer.from( + ripemd160().update(shaHash).digest('hex'), + 'hex', + ) + redeemScript.writeUInt8(OP.ZERO, 0) + redeemScript.writeUInt8(pubkeyhash.length, 1) + pubkeyhash.copy(redeemScript, 2) + return redeemScript } // Var slice of signature + var slice of pubkey function buildSig(sig, pubkey) { - sig = Buffer.concat([sig, DEFAULT_SIGHASH_BUFFER]) - const sigLen = getVarInt(sig.length) - const pubkeyLen = getVarInt(pubkey.length) - const slice = Buffer.concat([sigLen, sig, pubkeyLen, pubkey]) - const len = getVarInt(slice.length) - return Buffer.concat([len, slice]) + sig = Buffer.concat([sig, DEFAULT_SIGHASH_BUFFER]) + const sigLen = getVarInt(sig.length) + const pubkeyLen = getVarInt(pubkey.length) + const slice = Buffer.concat([sigLen, sig, pubkeyLen, pubkey]) + const len = getVarInt(slice.length) + return Buffer.concat([len, slice]) } // Witness is written as a "vector", which is a list of varSlices // prefixed by the number of items function buildWitness(sigs, pubkeys) { - let witness = Buffer.alloc(0) - // Two items in each vector (sig, pubkey) - const len = Buffer.alloc(1) - len.writeUInt8(2, 0) - for (let i = 0; i < sigs.length; i++) { - const sig = Buffer.concat([sigs[i], DEFAULT_SIGHASH_BUFFER]) - const sigLen = getVarInt(sig.length) - const pubkey = pubkeys[i] - const pubkeyLen = getVarInt(pubkey.length) - witness = Buffer.concat([witness, len, sigLen, sig, pubkeyLen, pubkey]) - } - return witness + let witness = Buffer.alloc(0) + // Two items in each vector (sig, pubkey) + const len = Buffer.alloc(1) + len.writeUInt8(2, 0) + for (let i = 0; i < sigs.length; i++) { + const sig = Buffer.concat([sigs[i], DEFAULT_SIGHASH_BUFFER]) + const sigLen = getVarInt(sig.length) + const pubkey = pubkeys[i] + const pubkeyLen = getVarInt(pubkey.length) + witness = Buffer.concat([witness, len, sigLen, sig, pubkeyLen, pubkey]) + } + return witness } // Locking script buiders //----------------------- function buildLockingScript(address) { - const dec = decodeAddress(address) - switch (dec.versionByte) { - case FMT_SEGWIT_NATIVE_V0: - case FMT_SEGWIT_NATIVE_V0_TESTNET: - return buildP2wpkhLockingScript(dec.pkh) - case FMT_SEGWIT_WRAPPED: - case FMT_SEGWIT_WRAPPED_TESTNET: - return buildP2shLockingScript(dec.pkh) - case FMT_LEGACY: - case FMT_LEGACY_TESTNET: - return buildP2pkhLockingScript(dec.pkh) - default: - throw new Error( - `Unknown version byte: ${dec.versionByte}. Cannot build BTC transaction.`, - ) - } + const dec = decodeAddress(address) + switch (dec.versionByte) { + case FMT_SEGWIT_NATIVE_V0: + case FMT_SEGWIT_NATIVE_V0_TESTNET: + return buildP2wpkhLockingScript(dec.pkh) + case FMT_SEGWIT_WRAPPED: + case FMT_SEGWIT_WRAPPED_TESTNET: + return buildP2shLockingScript(dec.pkh) + case FMT_LEGACY: + case FMT_LEGACY_TESTNET: + return buildP2pkhLockingScript(dec.pkh) + default: + throw new Error( + `Unknown version byte: ${dec.versionByte}. Cannot build BTC transaction.`, + ) + } } function buildP2pkhLockingScript(pubkeyhash) { - const out = Buffer.alloc(5 + pubkeyhash.length) - let off = 0 - out.writeUInt8(OP.DUP, off) - off++ - out.writeUInt8(OP.HASH160, off) - off++ - out.writeUInt8(pubkeyhash.length, off) - off++ - pubkeyhash.copy(out, off) - off += pubkeyhash.length - out.writeUInt8(OP.EQUALVERIFY, off) - off++ - out.writeUInt8(OP.CHECKSIG, off) - off++ - return out + const out = Buffer.alloc(5 + pubkeyhash.length) + let off = 0 + out.writeUInt8(OP.DUP, off) + off++ + out.writeUInt8(OP.HASH160, off) + off++ + out.writeUInt8(pubkeyhash.length, off) + off++ + pubkeyhash.copy(out, off) + off += pubkeyhash.length + out.writeUInt8(OP.EQUALVERIFY, off) + off++ + out.writeUInt8(OP.CHECKSIG, off) + off++ + return out } function buildP2shLockingScript(pubkeyhash) { - const out = Buffer.alloc(3 + pubkeyhash.length) - let off = 0 - out.writeUInt8(OP.HASH160, off) - off++ - out.writeUInt8(pubkeyhash.length, off) - off++ - pubkeyhash.copy(out, off) - off += pubkeyhash.length - out.writeUInt8(OP.EQUAL, off) - off++ - return out + const out = Buffer.alloc(3 + pubkeyhash.length) + let off = 0 + out.writeUInt8(OP.HASH160, off) + off++ + out.writeUInt8(pubkeyhash.length, off) + off++ + pubkeyhash.copy(out, off) + off += pubkeyhash.length + out.writeUInt8(OP.EQUAL, off) + off++ + return out } function buildP2wpkhLockingScript(pubkeyhash) { - const out = Buffer.alloc(2 + pubkeyhash.length) - out.writeUInt8(OP.ZERO, 0) - out.writeUInt8(pubkeyhash.length, 1) - pubkeyhash.copy(out, 2) - return out + const out = Buffer.alloc(2 + pubkeyhash.length) + out.writeUInt8(OP.ZERO, 0) + out.writeUInt8(pubkeyhash.length, 1) + pubkeyhash.copy(out, 2) + return out } // Static Utils //---------------------- function concat(base, addition) { - return Buffer.concat([base, addition]) + return Buffer.concat([base, addition]) } function getU64LE(x) { - const buffer = Buffer.alloc(8) - writeUInt64LE(x, buffer, 0) - return buffer + const buffer = Buffer.alloc(8) + writeUInt64LE(x, buffer, 0) + return buffer } function getU32LE(x) { - const buffer = Buffer.alloc(4) - buffer.writeUInt32LE(x, 0) - return buffer + const buffer = Buffer.alloc(4) + buffer.writeUInt32LE(x, 0) + return buffer } function getVarInt(x) { - let buffer: Buffer - if (x < 0xfd) { - buffer = Buffer.alloc(1) - buffer.writeUInt8(x, 0) - } else if (x <= 0xffff) { - buffer = Buffer.alloc(3) - buffer.writeUInt8(0xfd, 0) - buffer.writeUInt16LE(x, 1) - } else if (x < 0xffffffff) { - buffer = Buffer.alloc(5) - buffer.writeUInt8(0xfe, 0) - buffer.writeUInt32LE(x, 1) - } else { - buffer = Buffer.alloc(9) - buffer.writeUInt8(0xff, 0) - buffer.writeUInt32LE(x >>> 0, 1) - buffer.writeUInt32LE((x / 0x100000000) | 0, 5) - } - return buffer + let buffer: Buffer + if (x < 0xfd) { + buffer = Buffer.alloc(1) + buffer.writeUInt8(x, 0) + } else if (x <= 0xffff) { + buffer = Buffer.alloc(3) + buffer.writeUInt8(0xfd, 0) + buffer.writeUInt16LE(x, 1) + } else if (x < 0xffffffff) { + buffer = Buffer.alloc(5) + buffer.writeUInt8(0xfe, 0) + buffer.writeUInt32LE(x, 1) + } else { + buffer = Buffer.alloc(9) + buffer.writeUInt8(0xff, 0) + buffer.writeUInt32LE(x >>> 0, 1) + buffer.writeUInt32LE((x / 0x100000000) | 0, 5) + } + return buffer } function writeUInt64LE(n, buf, off) { - if (typeof n === 'number') n = n.toString(16) - const preBuf = Buffer.alloc(8) - const nStr = n.length % 2 === 0 ? n.toString(16) : `0${n.toString(16)}` - const nBuf = Buffer.from(nStr, 'hex') - nBuf.reverse().copy(preBuf, 0) - preBuf.copy(buf, off) - return preBuf + if (typeof n === 'number') n = n.toString(16) + const preBuf = Buffer.alloc(8) + const nStr = n.length % 2 === 0 ? n.toString(16) : `0${n.toString(16)}` + const nBuf = Buffer.from(nStr, 'hex') + nBuf.reverse().copy(preBuf, 0) + preBuf.copy(buf, off) + return preBuf } function decodeAddress(address) { - let versionByte: number | undefined - let pkh: Buffer | undefined - try { - // Attempt to base58 decode the address. This will work for older - // P2PKH, P2SH, and P2SH-P2WPKH addresses - versionByte = bs58check.decode(address)[0] - pkh = Buffer.from(bs58check.decode(address).slice(1)) - } catch (err) { - console.error('Failed to decode base58 address, trying bech32:', err) - // If we could not base58 decode, the address must be bech32 encoded. - // If neither decoding method works, the address is invalid. - try { - const bech32Dec = bech32.decode(address) - if (bech32Dec.prefix === SEGWIT_NATIVE_V0_PREFIX) { - versionByte = FMT_SEGWIT_NATIVE_V0 - } else if (bech32Dec.prefix === SEGWIT_NATIVE_V0_TESTNET_PREFIX) { - versionByte = FMT_SEGWIT_NATIVE_V0_TESTNET - } else { - throw new Error('Unsupported prefix: must be bc or tb.') - } - // Make sure we decoded - if (bech32Dec.words[0] !== 0) { - throw new Error( - `Unsupported segwit version: must be 0, got ${bech32Dec.words[0]}`, - ) - } - // Make sure address type is supported. - // We currently only support P2WPKH addresses, which bech-32decode to 33 words. - // P2WSH addresses are 53 words, but we do not support them. - // Not sure what other address types could exist, but if they exist we don't - // support them either. - if (bech32Dec.words.length !== 33) { - const isP2wpsh = bech32Dec.words.length === 53 - throw new Error( - `Unsupported address${isP2wpsh ? ' (P2WSH not supported)' : ''}: ${address}`, - ) - } + let versionByte: number | undefined + let pkh: Buffer | undefined + try { + // Attempt to base58 decode the address. This will work for older + // P2PKH, P2SH, and P2SH-P2WPKH addresses + versionByte = bs58check.decode(address)[0] + pkh = Buffer.from(bs58check.decode(address).slice(1)) + } catch (err) { + console.error('Failed to decode base58 address, trying bech32:', err) + // If we could not base58 decode, the address must be bech32 encoded. + // If neither decoding method works, the address is invalid. + try { + const bech32Dec = bech32.decode(address) + if (bech32Dec.prefix === SEGWIT_NATIVE_V0_PREFIX) { + versionByte = FMT_SEGWIT_NATIVE_V0 + } else if (bech32Dec.prefix === SEGWIT_NATIVE_V0_TESTNET_PREFIX) { + versionByte = FMT_SEGWIT_NATIVE_V0_TESTNET + } else { + throw new Error('Unsupported prefix: must be bc or tb.') + } + // Make sure we decoded + if (bech32Dec.words[0] !== 0) { + throw new Error( + `Unsupported segwit version: must be 0, got ${bech32Dec.words[0]}`, + ) + } + // Make sure address type is supported. + // We currently only support P2WPKH addresses, which bech-32decode to 33 words. + // P2WSH addresses are 53 words, but we do not support them. + // Not sure what other address types could exist, but if they exist we don't + // support them either. + if (bech32Dec.words.length !== 33) { + const isP2wpsh = bech32Dec.words.length === 53 + throw new Error( + `Unsupported address${isP2wpsh ? ' (P2WSH not supported)' : ''}: ${address}`, + ) + } - pkh = Buffer.from(bech32.fromWords(bech32Dec.words.slice(1))) - } catch (err) { - throw new Error(`Unable to decode address: ${address}: ${err.message}`) - } - } - return { versionByte, pkh } + pkh = Buffer.from(bech32.fromWords(bech32Dec.words.slice(1))) + } catch (err) { + throw new Error(`Unable to decode address: ${address}: ${err.message}`) + } + } + return { versionByte, pkh } } // Determine the address format (a.k.a. "version") depending on the // purpose of the dervation path. function getAddressFormat(path) { - if (path.length < 2) throw new Error('Path must be >1 index') - const purpose = path[0] - const coin = path[1] - if (purpose === PURPOSES.BTC_SEGWIT && coin === COINS.BTC) { - return FMT_SEGWIT_NATIVE_V0 - } else if (purpose === PURPOSES.BTC_SEGWIT && coin === COINS.BTC_TESTNET) { - return FMT_SEGWIT_NATIVE_V0_TESTNET - } else if (purpose === PURPOSES.BTC_WRAPPED_SEGWIT && coin === COINS.BTC) { - return FMT_SEGWIT_WRAPPED - } else if ( - purpose === PURPOSES.BTC_WRAPPED_SEGWIT && - coin === COINS.BTC_TESTNET - ) { - return FMT_SEGWIT_WRAPPED_TESTNET - } else if (purpose === PURPOSES.BTC_LEGACY && coin === COINS.BTC) { - return FMT_LEGACY - } else if (purpose === PURPOSES.BTC_LEGACY && coin === COINS.BTC_TESTNET) { - return FMT_LEGACY_TESTNET - } else { - throw new Error( - 'Invalid Bitcoin path provided. Cannot determine address format.', - ) - } + if (path.length < 2) throw new Error('Path must be >1 index') + const purpose = path[0] + const coin = path[1] + if (purpose === PURPOSES.BTC_SEGWIT && coin === COINS.BTC) { + return FMT_SEGWIT_NATIVE_V0 + } else if (purpose === PURPOSES.BTC_SEGWIT && coin === COINS.BTC_TESTNET) { + return FMT_SEGWIT_NATIVE_V0_TESTNET + } else if (purpose === PURPOSES.BTC_WRAPPED_SEGWIT && coin === COINS.BTC) { + return FMT_SEGWIT_WRAPPED + } else if ( + purpose === PURPOSES.BTC_WRAPPED_SEGWIT && + coin === COINS.BTC_TESTNET + ) { + return FMT_SEGWIT_WRAPPED_TESTNET + } else if (purpose === PURPOSES.BTC_LEGACY && coin === COINS.BTC) { + return FMT_LEGACY + } else if (purpose === PURPOSES.BTC_LEGACY && coin === COINS.BTC_TESTNET) { + return FMT_LEGACY_TESTNET + } else { + throw new Error( + 'Invalid Bitcoin path provided. Cannot determine address format.', + ) + } } // Determine the script type for an input based on its owner's derivation @@ -464,39 +464,39 @@ function getAddressFormat(path) { // We do not support p2sh and only issue single-key addresses from the Lattice // so we can determine this based on path alone. function getScriptType(input) { - switch (input.signerPath[0]) { - case PURPOSES.BTC_LEGACY: - return BTC_SCRIPT_TYPE_P2PKH - case PURPOSES.BTC_WRAPPED_SEGWIT: - return BTC_SCRIPT_TYPE_P2SH_P2WPKH - case PURPOSES.BTC_SEGWIT: - return BTC_SCRIPT_TYPE_P2WPKH_V0 - default: - throw new Error( - `Unsupported path purpose (${input.signerPath[0]}): cannot determine BTC script type.`, - ) - } + switch (input.signerPath[0]) { + case PURPOSES.BTC_LEGACY: + return BTC_SCRIPT_TYPE_P2PKH + case PURPOSES.BTC_WRAPPED_SEGWIT: + return BTC_SCRIPT_TYPE_P2SH_P2WPKH + case PURPOSES.BTC_SEGWIT: + return BTC_SCRIPT_TYPE_P2WPKH_V0 + default: + throw new Error( + `Unsupported path purpose (${input.signerPath[0]}): cannot determine BTC script type.`, + ) + } } // Determine if a a transaction should have a witness portion. // This will return true if any input is p2sh(p2wpkh) or p2wpkh. // We determine the script type based on the derivation path. function needsWitness(inputs) { - let w = false - inputs.forEach((input) => { - if ( - input.signerPath[0] === PURPOSES.BTC_SEGWIT || - input.signerPath[0] === PURPOSES.BTC_WRAPPED_SEGWIT - ) { - w = true - } - }) - return w + let w = false + inputs.forEach((input) => { + if ( + input.signerPath[0] === PURPOSES.BTC_SEGWIT || + input.signerPath[0] === PURPOSES.BTC_WRAPPED_SEGWIT + ) { + w = true + } + }) + return w } export default { - buildBitcoinTxRequest, - serializeTx, - getBitcoinAddress, - getAddressFormat, + buildBitcoinTxRequest, + serializeTx, + getBitcoinAddress, + getAddressFormat, } diff --git a/packages/sdk/src/calldata/evm.ts b/packages/sdk/src/calldata/evm.ts index 75b760be..a1861c72 100644 --- a/packages/sdk/src/calldata/evm.ts +++ b/packages/sdk/src/calldata/evm.ts @@ -8,23 +8,23 @@ import { decodeAbiParameters, parseAbiParameters } from 'viem' * @public */ export const parseSolidityJSONABI = ( - sig: string, - abi: any[], + sig: string, + abi: any[], ): { def: EVMDef } => { - sig = coerceSig(sig) - // Find the first match in the ABI - const match = abi - .filter((item) => item.type === 'function') - .find((item) => { - const def = parseDef(item) - const funcSig = getFuncSig(def.canonicalName) - return funcSig === sig - }) - if (match) { - const def = parseDef(match).def - return { def } - } - throw new Error('Unable to find matching function in ABI') + sig = coerceSig(sig) + // Find the first match in the ABI + const match = abi + .filter((item) => item.type === 'function') + .find((item) => { + const def = parseDef(item) + const funcSig = getFuncSig(def.canonicalName) + return funcSig === sig + }) + if (match) { + const def = parseDef(match).def + return { def } + } + throw new Error('Unable to find matching function in ABI') } /** @@ -36,27 +36,27 @@ export const parseSolidityJSONABI = ( * @public */ export const parseCanonicalName = (sig: string, name: string) => { - sig = coerceSig(sig) - if (sig !== getFuncSig(name)) { - throw new Error('Name does not match provided sig.') - } - const def = [] - // Get the function name - const paramStart = name.indexOf('(') - if (paramStart < 0) { - throw new Error(BAD_CANONICAL_ERR) - } - def.push(name.slice(0, paramStart)) - name = name.slice(paramStart + 1) - let paramDef = [] - while (name.length > 1) { - // scan until the terminating ')' - const typeStr = popTypeStrFromCanonical(name) - paramDef = paramDef.concat(parseTypeStr(typeStr)) - name = name.slice(typeStr.length + 1) - } - const parsedParamDef = parseParamDef(paramDef) - return def.concat(parsedParamDef) + sig = coerceSig(sig) + if (sig !== getFuncSig(name)) { + throw new Error('Name does not match provided sig.') + } + const def = [] + // Get the function name + const paramStart = name.indexOf('(') + if (paramStart < 0) { + throw new Error(BAD_CANONICAL_ERR) + } + def.push(name.slice(0, paramStart)) + name = name.slice(paramStart + 1) + let paramDef = [] + while (name.length > 1) { + // scan until the terminating ')' + const typeStr = popTypeStrFromCanonical(name) + paramDef = paramDef.concat(parseTypeStr(typeStr)) + name = name.slice(typeStr.length + 1) + } + const parsedParamDef = parseParamDef(paramDef) + return def.concat(parsedParamDef) } /** @@ -71,83 +71,83 @@ export const parseCanonicalName = (sig: string, name: string) => { * checked as a possible nested def */ export const getNestedCalldata = (def, calldata) => { - const possibleNestedDefs = [] - // Skip past first item, which is the function name - const defParams = def.slice(1) - const strParams = getParamStrNames(defParams) - const hexStr = `0x${calldata.slice(4).toString('hex')}` as `0x${string}` - // Convert strParams to viem's format - const viemParams = strParams.map((type) => { - // Convert tuple format from 'tuple(uint256,uint128)' to '(uint256,uint128)' - if (type.startsWith('tuple(')) { - return type.replace('tuple', '') - } - return type - }) + const possibleNestedDefs = [] + // Skip past first item, which is the function name + const defParams = def.slice(1) + const strParams = getParamStrNames(defParams) + const hexStr = `0x${calldata.slice(4).toString('hex')}` as `0x${string}` + // Convert strParams to viem's format + const viemParams = strParams.map((type) => { + // Convert tuple format from 'tuple(uint256,uint128)' to '(uint256,uint128)' + if (type.startsWith('tuple(')) { + return type.replace('tuple', '') + } + return type + }) - const abiParams = parseAbiParameters(viemParams.join(',')) - const decoded = decodeAbiParameters(abiParams, hexStr) + const abiParams = parseAbiParameters(viemParams.join(',')) + const decoded = decodeAbiParameters(abiParams, hexStr) - function couldBeNestedDef(x) { - return (x.length - 4) % 32 === 0 - } - decoded.forEach((paramData, i) => { - if (isBytesType(defParams[i])) { - let nestedDefIsPossible = true - if (isBytesArrItem(defParams[i])) { - // `bytes[]` type. Decode all underlying `bytes` items and - // do size checks on those. - // NOTE: We only do this for `bytes[]` but could, in the future, - // extend to more complex array structures if we see nested defs - // in this pattern. However, we have only ever seen `bytes[]`, which - // is typically used in `multicall` patterns - // Ensure paramData is an array for bytes[] type - if (Array.isArray(paramData)) { - paramData.forEach((nestedParamDatum) => { - // Ensure nestedParamDatum is a hex string - if ( - typeof nestedParamDatum !== 'string' || - !nestedParamDatum.startsWith('0x') - ) { - nestedDefIsPossible = false - return - } - const nestedParamDatumBuf = Buffer.from( - nestedParamDatum.slice(2), - 'hex', - ) - if (!couldBeNestedDef(nestedParamDatumBuf)) { - nestedDefIsPossible = false - } - }) - } else { - nestedDefIsPossible = false - } - } else if (isBytesItem(defParams[i])) { - // Regular `bytes` type - perform size check - if ( - typeof paramData !== 'string' || - !(paramData as string).startsWith('0x') - ) { - nestedDefIsPossible = false - } else { - const data = paramData as string - const paramDataBuf = Buffer.from(data.slice(2), 'hex') - nestedDefIsPossible = couldBeNestedDef(paramDataBuf) - } - } else { - // Unknown `bytes` item type - nestedDefIsPossible = false - } - // If the data could contain a nested def (determined based on - // data size of the item), add the paramData to the return array. - possibleNestedDefs.push(nestedDefIsPossible ? paramData : null) - } else { - // No nested defs for non-bytes types - possibleNestedDefs.push(null) - } - }) - return possibleNestedDefs + function couldBeNestedDef(x) { + return (x.length - 4) % 32 === 0 + } + decoded.forEach((paramData, i) => { + if (isBytesType(defParams[i])) { + let nestedDefIsPossible = true + if (isBytesArrItem(defParams[i])) { + // `bytes[]` type. Decode all underlying `bytes` items and + // do size checks on those. + // NOTE: We only do this for `bytes[]` but could, in the future, + // extend to more complex array structures if we see nested defs + // in this pattern. However, we have only ever seen `bytes[]`, which + // is typically used in `multicall` patterns + // Ensure paramData is an array for bytes[] type + if (Array.isArray(paramData)) { + paramData.forEach((nestedParamDatum) => { + // Ensure nestedParamDatum is a hex string + if ( + typeof nestedParamDatum !== 'string' || + !nestedParamDatum.startsWith('0x') + ) { + nestedDefIsPossible = false + return + } + const nestedParamDatumBuf = Buffer.from( + nestedParamDatum.slice(2), + 'hex', + ) + if (!couldBeNestedDef(nestedParamDatumBuf)) { + nestedDefIsPossible = false + } + }) + } else { + nestedDefIsPossible = false + } + } else if (isBytesItem(defParams[i])) { + // Regular `bytes` type - perform size check + if ( + typeof paramData !== 'string' || + !(paramData as string).startsWith('0x') + ) { + nestedDefIsPossible = false + } else { + const data = paramData as string + const paramDataBuf = Buffer.from(data.slice(2), 'hex') + nestedDefIsPossible = couldBeNestedDef(paramDataBuf) + } + } else { + // Unknown `bytes` item type + nestedDefIsPossible = false + } + // If the data could contain a nested def (determined based on + // data size of the item), add the paramData to the return array. + possibleNestedDefs.push(nestedDefIsPossible ? paramData : null) + } else { + // No nested defs for non-bytes types + possibleNestedDefs.push(null) + } + }) + return possibleNestedDefs } /** @@ -161,22 +161,22 @@ export const getNestedCalldata = (def, calldata) => { * @return - Possibly modified version of `def` */ export const replaceNestedDefs = (def, nestedDefs) => { - for (let i = 0; i < nestedDefs.length; i++) { - const isArrItem = isBytesArrItem(def[1 + i]) - const isItem = isBytesItem(def[1 + i]) - if (nestedDefs[i] !== null && (isArrItem || isItem)) { - // Update the def item type to indicate it will hold - // one or more nested definitions - def[1 + i][1] = EVM_TYPES.indexOf('nestedDef') - // Add nested def(s) in in an array. If this is an array - // item it means the nestedDefs should already be in an - // array. Otherwise we need to wrap the single nested - // def in an array to keep the data type consistent. - const defs = isArrItem ? nestedDefs[i] : [nestedDefs[i]] - def[1 + i] = def[1 + i].concat([defs]) - } - } - return def + for (let i = 0; i < nestedDefs.length; i++) { + const isArrItem = isBytesArrItem(def[1 + i]) + const isItem = isBytesItem(def[1 + i]) + if (nestedDefs[i] !== null && (isArrItem || isItem)) { + // Update the def item type to indicate it will hold + // one or more nested definitions + def[1 + i][1] = EVM_TYPES.indexOf('nestedDef') + // Add nested def(s) in in an array. If this is an array + // item it means the nestedDefs should already be in an + // array. Otherwise we need to wrap the single nested + // def in an array to keep the data type consistent. + const defs = isArrItem ? nestedDefs[i] : [nestedDefs[i]] + def[1 + i] = def[1 + i].concat([defs]) + } + } + return def } /** @@ -184,22 +184,22 @@ export const replaceNestedDefs = (def, nestedDefs) => { * @internal */ function getFuncSig(canonicalName: string): string { - return `0x${Buffer.from(Hash.keccak256(Buffer.from(canonicalName))) - .toString('hex') - .slice(0, 8)}` + return `0x${Buffer.from(Hash.keccak256(Buffer.from(canonicalName))) + .toString('hex') + .slice(0, 8)}` } /** * Ensure the sig is properly formatted */ function coerceSig(sig: string): string { - if (typeof sig !== 'string' || (sig.length !== 10 && sig.length !== 8)) { - throw new Error('`sig` must be a hex string with 4 bytes of data.') - } - if (sig.length === 8) { - sig = `0x${sig}` - } - return sig + if (typeof sig !== 'string' || (sig.length !== 10 && sig.length !== 8)) { + throw new Error('`sig` must be a hex string with 4 bytes of data.') + } + if (sig.length === 8) { + sig = `0x${sig}` + } + return sig } /** @@ -211,30 +211,30 @@ function coerceSig(sig: string): string { * @internal */ function getParamStrNames(defParams) { - const strNames = [] - for (let i = 0; i < defParams.length; i++) { - const param = defParams[i] - let s = EVM_TYPES[param[1]] - if (param[2]) { - s = `${s}${param[2] * 8}` - } - if (param[3].length > 0) { - param[3].forEach((d) => { - if (param[3][d] === 0) { - s = `${s}[]` - } else { - s = `${s}[${param[3][d]}]` - } - }) - } - if (param[4]) { - // Tuple - get nested type names - const nested = getParamStrNames(param[4]) - s = `${s}(${nested.join(',')})` - } - strNames.push(s) - } - return strNames + const strNames = [] + for (let i = 0; i < defParams.length; i++) { + const param = defParams[i] + let s = EVM_TYPES[param[1]] + if (param[2]) { + s = `${s}${param[2] * 8}` + } + if (param[3].length > 0) { + param[3].forEach((d) => { + if (param[3][d] === 0) { + s = `${s}[]` + } else { + s = `${s}[${param[3][d]}]` + } + }) + } + if (param[4]) { + // Tuple - get nested type names + const nested = getParamStrNames(param[4]) + s = `${s}(${nested.join(',')})` + } + strNames.push(s) + } + return strNames } /** @@ -243,16 +243,16 @@ function getParamStrNames(defParams) { * @internal */ function popTypeStrFromCanonical(subName: string): string { - if (isTuple(subName)) { - return getTupleName(subName) - } else if (subName.indexOf(',') > -1) { - // Normal non-tuple param - return subName.slice(0, subName.indexOf(',')) - } else if (subName.indexOf(')') > -1) { - // Last non-tuple param in the name - return subName.slice(0, subName.indexOf(')')) - } - throw new Error(BAD_CANONICAL_ERR) + if (isTuple(subName)) { + return getTupleName(subName) + } else if (subName.indexOf(',') > -1) { + // Normal non-tuple param + return subName.slice(0, subName.indexOf(',')) + } else if (subName.indexOf(')') > -1) { + // Last non-tuple param in the name + return subName.slice(0, subName.indexOf(')')) + } + throw new Error(BAD_CANONICAL_ERR) } /** @@ -261,34 +261,34 @@ function popTypeStrFromCanonical(subName: string): string { * @internal */ function parseTypeStr(typeStr: string): any[] { - // Non-tuples can be decoded without worrying about recursion - if (!isTuple(typeStr)) { - return [parseBasicTypeStr(typeStr)] - } - // Tuples may require recursion - const param: EVMParamInfo = { - szBytes: 0, - typeIdx: EVM_TYPES.indexOf('tuple'), - arraySzs: [], - } - // Get the full tuple param name and separate out the array stuff - let typeStrLessArr = getTupleName(typeStr, false) - const typeStrArr = typeStr.slice(typeStrLessArr.length) - param.arraySzs = getArraySzs(typeStrArr) - // Slice off the leading paren - typeStrLessArr = typeStrLessArr.slice(1) - // Parse each nested param - let paramArr = [] - while (typeStrLessArr.length > 0) { - const subType = popTypeStrFromCanonical(typeStrLessArr) - typeStrLessArr = typeStrLessArr.slice(subType.length + 1) - paramArr = paramArr.concat(parseTypeStr(subType)) - } - // There must be at least one sub-param in the tuple - if (!paramArr.length) { - throw new Error(BAD_CANONICAL_ERR) - } - return [param, paramArr] + // Non-tuples can be decoded without worrying about recursion + if (!isTuple(typeStr)) { + return [parseBasicTypeStr(typeStr)] + } + // Tuples may require recursion + const param: EVMParamInfo = { + szBytes: 0, + typeIdx: EVM_TYPES.indexOf('tuple'), + arraySzs: [], + } + // Get the full tuple param name and separate out the array stuff + let typeStrLessArr = getTupleName(typeStr, false) + const typeStrArr = typeStr.slice(typeStrLessArr.length) + param.arraySzs = getArraySzs(typeStrArr) + // Slice off the leading paren + typeStrLessArr = typeStrLessArr.slice(1) + // Parse each nested param + let paramArr = [] + while (typeStrLessArr.length > 0) { + const subType = popTypeStrFromCanonical(typeStrLessArr) + typeStrLessArr = typeStrLessArr.slice(subType.length + 1) + paramArr = paramArr.concat(parseTypeStr(subType)) + } + // There must be at least one sub-param in the tuple + if (!paramArr.length) { + throw new Error(BAD_CANONICAL_ERR) + } + return [param, paramArr] } /** @@ -296,32 +296,32 @@ function parseTypeStr(typeStr: string): any[] { * @internal */ function parseBasicTypeStr(typeStr: string): EVMParamInfo { - const param: EVMParamInfo = { - szBytes: 0, - typeIdx: 0, - arraySzs: [], - } - let found = false - EVM_TYPES.forEach((t, i) => { - if (typeStr.indexOf(t) > -1 && !found) { - param.typeIdx = i - param.arraySzs = getArraySzs(typeStr) - const arrStart = - param.arraySzs.length > 0 ? typeStr.indexOf('[') : typeStr.length - const typeStrNum = typeStr.slice(t.length, arrStart) - if (Number.parseInt(typeStrNum)) { - param.szBytes = Number.parseInt(typeStrNum) / 8 - if (param.szBytes > 32) { - throw new Error(BAD_CANONICAL_ERR) - } - } - found = true - } - }) - if (!found) { - throw new Error(BAD_CANONICAL_ERR) - } - return param + const param: EVMParamInfo = { + szBytes: 0, + typeIdx: 0, + arraySzs: [], + } + let found = false + EVM_TYPES.forEach((t, i) => { + if (typeStr.indexOf(t) > -1 && !found) { + param.typeIdx = i + param.arraySzs = getArraySzs(typeStr) + const arrStart = + param.arraySzs.length > 0 ? typeStr.indexOf('[') : typeStr.length + const typeStrNum = typeStr.slice(t.length, arrStart) + if (Number.parseInt(typeStrNum)) { + param.szBytes = Number.parseInt(typeStrNum) / 8 + if (param.szBytes > 32) { + throw new Error(BAD_CANONICAL_ERR) + } + } + found = true + } + }) + if (!found) { + throw new Error(BAD_CANONICAL_ERR) + } + return param } /** @@ -330,50 +330,50 @@ function parseBasicTypeStr(typeStr: string): EVMParamInfo { * @internal */ function parseDef( - item, - canonicalName = '', - def = [], - recursed = false, + item, + canonicalName = '', + def = [], + recursed = false, ): EVMDef { - // Function name. Can be an empty string. - if (!recursed) { - const nameStr = item.name || '' - def.push(nameStr) - canonicalName += nameStr - } - // Loop through params - if (item.inputs) { - canonicalName += '(' - item.inputs.forEach((input) => { - // Convert the input to a flat param that we can serialize - const flatParam = getFlatParam(input) - if (input.type.indexOf('tuple') > -1 && input.components) { - // For tuples we need to recurse - const recursed = parseDef( - { inputs: input.components }, - canonicalName, - [], - true, - ) - canonicalName = recursed.canonicalName - // Add brackets if this is a tuple array and also add a comma - canonicalName += `${input.type.slice(5)},` - flatParam.push(recursed.def) - } else { - canonicalName += input.type - canonicalName += ',' - } - def.push(flatParam) - }) - // Take off the last comma. Note that we do not want to slice if the last param was a tuple, - // since we want to keep that `)` - if (canonicalName[canonicalName.length - 1] === ',') { - canonicalName = canonicalName.slice(0, canonicalName.length - 1) - } - // Add the closing parens - canonicalName += ')' - } - return { def, canonicalName } + // Function name. Can be an empty string. + if (!recursed) { + const nameStr = item.name || '' + def.push(nameStr) + canonicalName += nameStr + } + // Loop through params + if (item.inputs) { + canonicalName += '(' + item.inputs.forEach((input) => { + // Convert the input to a flat param that we can serialize + const flatParam = getFlatParam(input) + if (input.type.indexOf('tuple') > -1 && input.components) { + // For tuples we need to recurse + const recursed = parseDef( + { inputs: input.components }, + canonicalName, + [], + true, + ) + canonicalName = recursed.canonicalName + // Add brackets if this is a tuple array and also add a comma + canonicalName += `${input.type.slice(5)},` + flatParam.push(recursed.def) + } else { + canonicalName += input.type + canonicalName += ',' + } + def.push(flatParam) + }) + // Take off the last comma. Note that we do not want to slice if the last param was a tuple, + // since we want to keep that `)` + if (canonicalName[canonicalName.length - 1] === ',') { + canonicalName = canonicalName.slice(0, canonicalName.length - 1) + } + // Add the closing parens + canonicalName += ')' + } + return { def, canonicalName } } /** @@ -383,28 +383,28 @@ function parseDef( * @internal */ function parseParamDef(def: any[], prefix = ''): any[] { - const parsedDef = [] - let numTuples = 0 - def.forEach((param, i) => { - if (Array.isArray(param)) { - // Arrays indicate nested params inside a tuple and always come after the initial tuple type - // info. Recurse to parse nested tuple params and append them to the most recent. - parsedDef[parsedDef.length - 1].push(parseParamDef(param, `${i}-`)) - } else { - // If this is not tuple info, add the flat param info to the def - parsedDef.push([ - `#${prefix}${i + 1 - numTuples}`, - param.typeIdx, - param.szBytes, - param.arraySzs, - ]) - } - // Tuple - if (param.typeIdx === EVM_TYPES.indexOf('tuple')) { - numTuples += 1 - } - }) - return parsedDef + const parsedDef = [] + let numTuples = 0 + def.forEach((param, i) => { + if (Array.isArray(param)) { + // Arrays indicate nested params inside a tuple and always come after the initial tuple type + // info. Recurse to parse nested tuple params and append them to the most recent. + parsedDef[parsedDef.length - 1].push(parseParamDef(param, `${i}-`)) + } else { + // If this is not tuple info, add the flat param info to the def + parsedDef.push([ + `#${prefix}${i + 1 - numTuples}`, + param.typeIdx, + param.szBytes, + param.arraySzs, + ]) + } + // Tuple + if (param.typeIdx === EVM_TYPES.indexOf('tuple')) { + numTuples += 1 + } + }) + return parsedDef } /** @@ -412,15 +412,15 @@ function parseParamDef(def: any[], prefix = ''): any[] { * @internal */ function getFlatParam(input): any[] { - if (!input.type) { - throw new Error('No type in input') - } - const param = [input.name] - const { typeIdx, szBytes, arraySzs } = getParamTypeInfo(input.type) - param.push(typeIdx) - param.push(szBytes) - param.push(arraySzs) - return param + if (!input.type) { + throw new Error('No type in input') + } + const param = [input.name] + const { typeIdx, szBytes, arraySzs } = getParamTypeInfo(input.type) + param.push(typeIdx) + param.push(szBytes) + param.push(arraySzs) + return param } /** @@ -435,34 +435,34 @@ function getFlatParam(input): any[] { * @internal */ function getParamTypeInfo(type: string): EVMParamInfo { - const param: EVMParamInfo = { - szBytes: 0, - typeIdx: 0, - arraySzs: [], - } - let baseType: string | undefined - EVM_TYPES.forEach((t, i) => { - if (type.indexOf(t) > -1 && !baseType) { - baseType = t - param.typeIdx = i - } - }) - // Get the array size, if any - param.arraySzs = getArraySzs(type) - // Determine where to search for expanded size - const szIdx = param.arraySzs.length > 0 ? type.indexOf('[') : type.length - if (['uint', 'int', 'bytes'].indexOf(baseType) > -1) { - // If this can have a fixed size, capture that - const szBits = Number.parseInt(type.slice(baseType.length, szIdx)) || 0 - if (szBits > 256) { - throw new Error('Invalid param size') - } - param.szBytes = szBits / 8 - } else { - // No fixed size in the type - param.szBytes = 0 - } - return param + const param: EVMParamInfo = { + szBytes: 0, + typeIdx: 0, + arraySzs: [], + } + let baseType: string | undefined + EVM_TYPES.forEach((t, i) => { + if (type.indexOf(t) > -1 && !baseType) { + baseType = t + param.typeIdx = i + } + }) + // Get the array size, if any + param.arraySzs = getArraySzs(type) + // Determine where to search for expanded size + const szIdx = param.arraySzs.length > 0 ? type.indexOf('[') : type.length + if (['uint', 'int', 'bytes'].indexOf(baseType) > -1) { + // If this can have a fixed size, capture that + const szBits = Number.parseInt(type.slice(baseType.length, szIdx)) || 0 + if (szBits > 256) { + throw new Error('Invalid param size') + } + param.szBytes = szBits / 8 + } else { + // No fixed size in the type + param.szBytes = 0 + } + return param } /** @@ -471,93 +471,93 @@ function getParamTypeInfo(type: string): EVMParamInfo { * @internal */ function getArraySzs(type: string): number[] { - if (typeof type !== 'string') { - throw new Error('Invalid type') - } - const szs = [] - let t1 = type - while (t1.length > 0) { - const openIdx = t1.indexOf('[') - if (openIdx < 0) { - return szs - } - const t2 = t1.slice(openIdx) - const closeIdx = t2.indexOf(']') - if (closeIdx < 0) { - throw new Error('Bad param type') - } - const t3 = t2.slice(1, closeIdx) - if (t3.length === 0) { - // Variable size - szs.push(0) - } else { - // Fixed size - szs.push(Number.parseInt(t3)) - } - t1 = t2.slice(closeIdx + 1) - } - return szs + if (typeof type !== 'string') { + throw new Error('Invalid type') + } + const szs = [] + let t1 = type + while (t1.length > 0) { + const openIdx = t1.indexOf('[') + if (openIdx < 0) { + return szs + } + const t2 = t1.slice(openIdx) + const closeIdx = t2.indexOf(']') + if (closeIdx < 0) { + throw new Error('Bad param type') + } + const t3 = t2.slice(1, closeIdx) + if (t3.length === 0) { + // Variable size + szs.push(0) + } else { + // Fixed size + szs.push(Number.parseInt(t3)) + } + t1 = t2.slice(closeIdx + 1) + } + return szs } /** @internal */ function getTupleName(name, withArr = true) { - let brackets = 0 - let addedFirstBracket = false - for (let i = 0; i < name.length; i++) { - if (name[i] === '(') { - brackets += 1 - addedFirstBracket = true - } else if (name[i] === ')') { - brackets -= 1 - } - let canBreak = - name[i + 1] === ',' || name[i + 1] === ')' || i === name.length - 1 - if (!withArr && name[i + 1] === '[') { - canBreak = true - } - if (!brackets && addedFirstBracket && canBreak) { - return name.slice(0, i + 1) - } - } - throw new Error(BAD_CANONICAL_ERR) + let brackets = 0 + let addedFirstBracket = false + for (let i = 0; i < name.length; i++) { + if (name[i] === '(') { + brackets += 1 + addedFirstBracket = true + } else if (name[i] === ')') { + brackets -= 1 + } + let canBreak = + name[i + 1] === ',' || name[i + 1] === ')' || i === name.length - 1 + if (!withArr && name[i + 1] === '[') { + canBreak = true + } + if (!brackets && addedFirstBracket && canBreak) { + return name.slice(0, i + 1) + } + } + throw new Error(BAD_CANONICAL_ERR) } /** @internal */ function isTuple(type: string): boolean { - return type[0] === '(' + return type[0] === '(' } /** @internal */ function isBytesType(param) { - return EVM_TYPES[param[1]] === 'bytes' + return EVM_TYPES[param[1]] === 'bytes' } function isBytesItem(param) { - return isBytesType(param) && param[3].length === 0 + return isBytesType(param) && param[3].length === 0 } function isBytesArrItem(param) { - return isBytesType(param) && param[3].length === 1 && param[3][0] === 0 + return isBytesType(param) && param[3].length === 1 && param[3][0] === 0 } const BAD_CANONICAL_ERR = 'Could not parse canonical function name.' const EVM_TYPES = [ - null, - 'address', - 'bool', - 'uint', - 'int', - 'bytes', - 'string', - 'tuple', - 'nestedDef', + null, + 'address', + 'bool', + 'uint', + 'int', + 'bytes', + 'string', + 'tuple', + 'nestedDef', ] type EVMParamInfo = { - szBytes: number - typeIdx: number - arraySzs: number[] + szBytes: number + typeIdx: number + arraySzs: number[] } type EVMDef = { - canonicalName: string - def: any + canonicalName: string + def: any } diff --git a/packages/sdk/src/calldata/index.ts b/packages/sdk/src/calldata/index.ts index 452f342f..bf7bb0f7 100644 --- a/packages/sdk/src/calldata/index.ts +++ b/packages/sdk/src/calldata/index.ts @@ -4,22 +4,22 @@ * request. It is optional. */ import { - getNestedCalldata, - parseCanonicalName, - parseSolidityJSONABI, - replaceNestedDefs, + getNestedCalldata, + parseCanonicalName, + parseSolidityJSONABI, + replaceNestedDefs, } from './evm' export const CALLDATA = { - EVM: { - type: 1, - parsers: { - parseSolidityJSONABI, - parseCanonicalName, - }, - processors: { - getNestedCalldata, - replaceNestedDefs, - }, - }, + EVM: { + type: 1, + parsers: { + parseSolidityJSONABI, + parseCanonicalName, + }, + processors: { + getNestedCalldata, + replaceNestedDefs, + }, + }, } diff --git a/packages/sdk/src/client.ts b/packages/sdk/src/client.ts index 7c93edc2..40999850 100644 --- a/packages/sdk/src/client.ts +++ b/packages/sdk/src/client.ts @@ -1,35 +1,35 @@ import { buildSaveClientFn } from './api/utilities' import { - BASE_URL, - DEFAULT_ACTIVE_WALLETS, - EMPTY_WALLET_UID, - getFwVersionConst, + BASE_URL, + DEFAULT_ACTIVE_WALLETS, + EMPTY_WALLET_UID, + getFwVersionConst, } from './constants' import { - addKvRecords, - connect, - fetchActiveWallet, - fetchEncData, - getAddresses, - getKvRecords, - pair, - removeKvRecords, - sign, + addKvRecords, + connect, + fetchActiveWallet, + fetchEncData, + getAddresses, + getKvRecords, + pair, + removeKvRecords, + sign, } from './functions/index' import { buildRetryWrapper } from './shared/functions' import { getPubKeyBytes } from './shared/utilities' import { validateEphemeralPub } from './shared/validators' import type { - ActiveWallets, - AddKvRecordsRequestParams, - FetchEncDataRequest, - GetAddressesRequestParams, - GetKvRecordsData, - GetKvRecordsRequestParams, - KeyPair, - RemoveKvRecordsRequestParams, - SignData, - SignRequestParams, + ActiveWallets, + AddKvRecordsRequestParams, + FetchEncDataRequest, + GetAddressesRequestParams, + GetKvRecordsData, + GetKvRecordsRequestParams, + KeyPair, + RemoveKvRecordsRequestParams, + SignData, + SignRequestParams, } from './types' import { getP256KeyPair, getP256KeyPairFromPub, randomBytes } from './util' @@ -45,411 +45,411 @@ import { getP256KeyPair, getP256KeyPairFromPub, randomBytes } from './util' * */ export class Client { - /** Is the Lattice paired with this Client. */ - public isPaired: boolean - /** The time to wait for a response before cancelling. */ - public timeout: number - /** The base of the remote url to which the SDK sends requests. */ - public baseUrl: string - /** @internal The `baseUrl` plus the `deviceId`. Set in {@link connect} when it completes successfully. */ - public url?: string - /** `name` is a human readable string associated with this app on the Lattice */ - private name: string - private key: KeyPair - /**`privKey` is used to generate a keypair, which is used for maintaining an encrypted messaging channel with the target Lattice */ - private privKey: Buffer | string - private retryCount: number - private fwVersion?: Buffer - private skipRetryOnWrongWallet: boolean - /** Temporary secret that is generated by the Lattice device */ - private _ephemeralPub!: KeyPair - /** The ID of the connected Lattice */ - private deviceId?: string - /** Information about the current wallet. Should be null unless we know a wallet is present */ - public activeWallets: ActiveWallets - /** A wrapper function for handling retries and injecting the {@link Client} class */ - private retryWrapper: (fn: any, params?: any) => Promise - /** Function to set the stored client data */ - private setStoredClient?: (clientData: string | null) => Promise + /** Is the Lattice paired with this Client. */ + public isPaired: boolean + /** The time to wait for a response before cancelling. */ + public timeout: number + /** The base of the remote url to which the SDK sends requests. */ + public baseUrl: string + /** @internal The `baseUrl` plus the `deviceId`. Set in {@link connect} when it completes successfully. */ + public url?: string + /** `name` is a human readable string associated with this app on the Lattice */ + private name: string + private key: KeyPair + /**`privKey` is used to generate a keypair, which is used for maintaining an encrypted messaging channel with the target Lattice */ + private privKey: Buffer | string + private retryCount: number + private fwVersion?: Buffer + private skipRetryOnWrongWallet: boolean + /** Temporary secret that is generated by the Lattice device */ + private _ephemeralPub!: KeyPair + /** The ID of the connected Lattice */ + private deviceId?: string + /** Information about the current wallet. Should be null unless we know a wallet is present */ + public activeWallets: ActiveWallets + /** A wrapper function for handling retries and injecting the {@link Client} class */ + private retryWrapper: (fn: any, params?: any) => Promise + /** Function to set the stored client data */ + private setStoredClient?: (clientData: string | null) => Promise - /** - * @param params - Parameters are passed as an object. - */ - constructor({ - baseUrl, - name, - privKey, - stateData, - timeout, - retryCount, - skipRetryOnWrongWallet, - deviceId, - setStoredClient, - }: { - /** The base URL of the signing server. */ - baseUrl?: string - /** The name of the client. */ - name?: string - /** The private key of the client.*/ - privKey?: Buffer | string - /** Number of times to retry a request if it fails. */ - retryCount?: number - /** The time to wait for a response before cancelling. */ - timeout?: number - /** User can pass in previous state data to rehydrate connected session */ - stateData?: string - /** If true we will not retry if we get a wrong wallet error code */ - skipRetryOnWrongWallet?: boolean - /** The ID of the connected Lattice */ - deviceId?: string - /** Function to set the stored client data */ - setStoredClient?: (clientData: string | null) => Promise - }) { - this.name = name || 'Unknown' - this.baseUrl = baseUrl || BASE_URL - this.deviceId = deviceId - this.isPaired = false - this.activeWallets = DEFAULT_ACTIVE_WALLETS - this.timeout = timeout || 60000 - this.retryCount = retryCount || 3 - this.skipRetryOnWrongWallet = skipRetryOnWrongWallet || false - this.privKey = privKey || randomBytes(32) - this.key = getP256KeyPair(this.privKey) - this.retryWrapper = buildRetryWrapper(this, this.retryCount) - this.setStoredClient = setStoredClient - ? buildSaveClientFn(setStoredClient) - : undefined + /** + * @param params - Parameters are passed as an object. + */ + constructor({ + baseUrl, + name, + privKey, + stateData, + timeout, + retryCount, + skipRetryOnWrongWallet, + deviceId, + setStoredClient, + }: { + /** The base URL of the signing server. */ + baseUrl?: string + /** The name of the client. */ + name?: string + /** The private key of the client.*/ + privKey?: Buffer | string + /** Number of times to retry a request if it fails. */ + retryCount?: number + /** The time to wait for a response before cancelling. */ + timeout?: number + /** User can pass in previous state data to rehydrate connected session */ + stateData?: string + /** If true we will not retry if we get a wrong wallet error code */ + skipRetryOnWrongWallet?: boolean + /** The ID of the connected Lattice */ + deviceId?: string + /** Function to set the stored client data */ + setStoredClient?: (clientData: string | null) => Promise + }) { + this.name = name || 'Unknown' + this.baseUrl = baseUrl || BASE_URL + this.deviceId = deviceId + this.isPaired = false + this.activeWallets = DEFAULT_ACTIVE_WALLETS + this.timeout = timeout || 60000 + this.retryCount = retryCount || 3 + this.skipRetryOnWrongWallet = skipRetryOnWrongWallet || false + this.privKey = privKey || randomBytes(32) + this.key = getP256KeyPair(this.privKey) + this.retryWrapper = buildRetryWrapper(this, this.retryCount) + this.setStoredClient = setStoredClient + ? buildSaveClientFn(setStoredClient) + : undefined - /** The user may pass in state data to rehydrate a session that was previously cached */ - if (stateData) { - this.unpackAndApplyStateData(stateData) - } - } + /** The user may pass in state data to rehydrate a session that was previously cached */ + if (stateData) { + this.unpackAndApplyStateData(stateData) + } + } - /** - * Get the public key associated with the client's static keypair. - * The public key is used for identifying the client to the Lattice. - * @internal - * @returns Buffer - */ - public get publicKey() { - return getPubKeyBytes(this.key) - } + /** + * Get the public key associated with the client's static keypair. + * The public key is used for identifying the client to the Lattice. + * @internal + * @returns Buffer + */ + public get publicKey() { + return getPubKeyBytes(this.key) + } - /** - * Get the pairing name for this client instance - */ - public getAppName() { - return this.name - } + /** + * Get the pairing name for this client instance + */ + public getAppName() { + return this.name + } - /** - * Get the `deviceId` for this client instance - */ - public getDeviceId() { - return this.deviceId - } + /** + * Get the `deviceId` for this client instance + */ + public getDeviceId() { + return this.deviceId + } - /** - * Get the shared secret, derived via ECDH from the local private key and the ephemeral public key - * @internal - * @returns Buffer - */ - public get sharedSecret() { - // Once every ~256 attempts, we will get a key that starts with a `00` byte, which can lead to - // problems initializing AES if we don't force a 32 byte BE buffer. - return Buffer.from( - this.key.derive(this.ephemeralPub.getPublic()).toArray('be', 32), - ) - } + /** + * Get the shared secret, derived via ECDH from the local private key and the ephemeral public key + * @internal + * @returns Buffer + */ + public get sharedSecret() { + // Once every ~256 attempts, we will get a key that starts with a `00` byte, which can lead to + // problems initializing AES if we don't force a 32 byte BE buffer. + return Buffer.from( + this.key.derive(this.ephemeralPub.getPublic()).toArray('be', 32), + ) + } - /** @internal */ - public get ephemeralPub() { - return this._ephemeralPub - } + /** @internal */ + public get ephemeralPub() { + return this._ephemeralPub + } - /** @internal */ - public set ephemeralPub(ephemeralPub: KeyPair) { - validateEphemeralPub(ephemeralPub) - this._ephemeralPub = ephemeralPub - } + /** @internal */ + public set ephemeralPub(ephemeralPub: KeyPair) { + validateEphemeralPub(ephemeralPub) + this._ephemeralPub = ephemeralPub + } - /** - * Attempt to contact a device based on its `deviceId`. The response should include an ephemeral - * public key, which is used to pair with the device in a later request. - * @category Lattice - */ - public async connect(deviceId: string) { - return this.retryWrapper(connect, { id: deviceId }) - } + /** + * Attempt to contact a device based on its `deviceId`. The response should include an ephemeral + * public key, which is used to pair with the device in a later request. + * @category Lattice + */ + public async connect(deviceId: string) { + return this.retryWrapper(connect, { id: deviceId }) + } - /** - * If a pairing secret is provided, `pair` uses it to sign a hash of the public key, name, and - * pairing secret. It then sends the name and signature to the device. If no pairing secret is - * provided, `pair` sends a zero-length name buffer to the device. - * @category Lattice - */ - public async pair(pairingSecret: string) { - return this.retryWrapper(pair, { pairingSecret }) - } + /** + * If a pairing secret is provided, `pair` uses it to sign a hash of the public key, name, and + * pairing secret. It then sends the name and signature to the device. If no pairing secret is + * provided, `pair` sends a zero-length name buffer to the device. + * @category Lattice + */ + public async pair(pairingSecret: string) { + return this.retryWrapper(pair, { pairingSecret }) + } - /** - * Takes a starting path and a number to get the addresses associated with the active wallet. - * @category Lattice - */ - public async getAddresses({ - startPath, - n = 1, - flag = 0, - iterIdx = 0, - }: GetAddressesRequestParams): Promise { - return this.retryWrapper(getAddresses, { startPath, n, flag, iterIdx }) - } + /** + * Takes a starting path and a number to get the addresses associated with the active wallet. + * @category Lattice + */ + public async getAddresses({ + startPath, + n = 1, + flag = 0, + iterIdx = 0, + }: GetAddressesRequestParams): Promise { + return this.retryWrapper(getAddresses, { startPath, n, flag, iterIdx }) + } - /** - * Builds and sends a request for signing to the Lattice. - * @category Lattice - */ - public async sign({ - data, - currency, - cachedData, - nextCode, - }: SignRequestParams): Promise { - return this.retryWrapper(sign, { data, currency, cachedData, nextCode }) - } + /** + * Builds and sends a request for signing to the Lattice. + * @category Lattice + */ + public async sign({ + data, + currency, + cachedData, + nextCode, + }: SignRequestParams): Promise { + return this.retryWrapper(sign, { data, currency, cachedData, nextCode }) + } - /** - * Fetch the active wallet in the Lattice. - */ - public async fetchActiveWallet(): Promise { - return this.retryWrapper(fetchActiveWallet) - } + /** + * Fetch the active wallet in the Lattice. + */ + public async fetchActiveWallet(): Promise { + return this.retryWrapper(fetchActiveWallet) + } - /** - * Takes in a set of key-value records and sends a request to add them to the Lattice. - * @category Lattice - */ - async addKvRecords({ - type = 0, - records, - caseSensitive = false, - }: AddKvRecordsRequestParams): Promise { - return this.retryWrapper(addKvRecords, { type, records, caseSensitive }) - } + /** + * Takes in a set of key-value records and sends a request to add them to the Lattice. + * @category Lattice + */ + async addKvRecords({ + type = 0, + records, + caseSensitive = false, + }: AddKvRecordsRequestParams): Promise { + return this.retryWrapper(addKvRecords, { type, records, caseSensitive }) + } - /** - * Fetches a list of key-value records from the Lattice. - * @category Lattice - */ - public async getKvRecords({ - type = 0, - n = 1, - start = 0, - }: GetKvRecordsRequestParams): Promise { - return this.retryWrapper(getKvRecords, { type, n, start }) - } + /** + * Fetches a list of key-value records from the Lattice. + * @category Lattice + */ + public async getKvRecords({ + type = 0, + n = 1, + start = 0, + }: GetKvRecordsRequestParams): Promise { + return this.retryWrapper(getKvRecords, { type, n, start }) + } - /** - * Takes in an array of ids and sends a request to remove them from the Lattice. - * @category Lattice - */ - public async removeKvRecords({ - type = 0, - ids = [], - }: RemoveKvRecordsRequestParams): Promise { - return this.retryWrapper(removeKvRecords, { type, ids }) - } + /** + * Takes in an array of ids and sends a request to remove them from the Lattice. + * @category Lattice + */ + public async removeKvRecords({ + type = 0, + ids = [], + }: RemoveKvRecordsRequestParams): Promise { + return this.retryWrapper(removeKvRecords, { type, ids }) + } - /** - * Fetch a record of encrypted data from the Lattice. - * Must specify a data type. Returns a Buffer containing - * data formatted according to the specified type. - * @category Lattice - */ - public async fetchEncryptedData( - params: FetchEncDataRequest, - ): Promise { - return this.retryWrapper(fetchEncData, params) - } + /** + * Fetch a record of encrypted data from the Lattice. + * Must specify a data type. Returns a Buffer containing + * data formatted according to the specified type. + * @category Lattice + */ + public async fetchEncryptedData( + params: FetchEncDataRequest, + ): Promise { + return this.retryWrapper(fetchEncData, params) + } - /** Get the active wallet */ - public getActiveWallet() { - if ( - this.activeWallets.external.uid && - !EMPTY_WALLET_UID.equals(this.activeWallets.external.uid) - ) { - return this.activeWallets.external - } else if ( - this.activeWallets.internal.uid && - !EMPTY_WALLET_UID.equals(this.activeWallets.internal.uid) - ) { - return this.activeWallets.internal - } else { - return undefined - } - } + /** Get the active wallet */ + public getActiveWallet() { + if ( + this.activeWallets.external.uid && + !EMPTY_WALLET_UID.equals(this.activeWallets.external.uid) + ) { + return this.activeWallets.external + } else if ( + this.activeWallets.internal.uid && + !EMPTY_WALLET_UID.equals(this.activeWallets.internal.uid) + ) { + return this.activeWallets.internal + } else { + return undefined + } + } - /** Check if the user has an active wallet */ - public hasActiveWallet() { - return !!this.getActiveWallet() - } + /** Check if the user has an active wallet */ + public hasActiveWallet() { + return !!this.getActiveWallet() + } - /** - * Reset the active wallets to empty values. - * @category Device Response - * @internal - */ - public resetActiveWallets() { - this.activeWallets = DEFAULT_ACTIVE_WALLETS - } + /** + * Reset the active wallets to empty values. + * @category Device Response + * @internal + */ + public resetActiveWallets() { + this.activeWallets = DEFAULT_ACTIVE_WALLETS + } - /** - * Get a JSON string containing state data that can be used to rehydrate a session. Pass the - * contents of this to the constructor as `stateData` to rehydrate. - * @internal - */ - public getStateData() { - return this.packStateData() - } + /** + * Get a JSON string containing state data that can be used to rehydrate a session. Pass the + * contents of this to the constructor as `stateData` to rehydrate. + * @internal + */ + public getStateData() { + return this.packStateData() + } - /** - * Returns the firmware version constants for the given firmware version. - * @internal - */ - public getFwConstants() { - return getFwVersionConst(this.fwVersion ?? Buffer.alloc(0)) - } + /** + * Returns the firmware version constants for the given firmware version. + * @internal + */ + public getFwConstants() { + return getFwVersionConst(this.fwVersion ?? Buffer.alloc(0)) + } - /** - * `getFwVersion` gets the firmware version of the paired device. - * @internal - */ - public getFwVersion(): { - fix: number - minor: number - major: number - } { - if (this.fwVersion && this.fwVersion.length >= 3) { - return { - fix: this.fwVersion[0], - minor: this.fwVersion[1], - major: this.fwVersion[2], - } - } - return { fix: 0, minor: 0, major: 0 } - } + /** + * `getFwVersion` gets the firmware version of the paired device. + * @internal + */ + public getFwVersion(): { + fix: number + minor: number + major: number + } { + if (this.fwVersion && this.fwVersion.length >= 3) { + return { + fix: this.fwVersion[0], + minor: this.fwVersion[1], + major: this.fwVersion[2], + } + } + return { fix: 0, minor: 0, major: 0 } + } - /** - * Handles the mutation of Client state in the primary functions. - */ - public mutate({ - deviceId, - ephemeralPub, - url, - isPaired, - fwVersion, - activeWallets, - }: { - deviceId?: string - ephemeralPub?: KeyPair - url?: string - isPaired?: boolean - fwVersion?: Buffer - activeWallets?: ActiveWallets - }) { - if (deviceId !== undefined) this.deviceId = deviceId - if (ephemeralPub !== undefined) this.ephemeralPub = ephemeralPub - if (url !== undefined) this.url = url - if (isPaired !== undefined) this.isPaired = isPaired - if (fwVersion !== undefined) this.fwVersion = fwVersion - if (activeWallets !== undefined) this.activeWallets = activeWallets + /** + * Handles the mutation of Client state in the primary functions. + */ + public mutate({ + deviceId, + ephemeralPub, + url, + isPaired, + fwVersion, + activeWallets, + }: { + deviceId?: string + ephemeralPub?: KeyPair + url?: string + isPaired?: boolean + fwVersion?: Buffer + activeWallets?: ActiveWallets + }) { + if (deviceId !== undefined) this.deviceId = deviceId + if (ephemeralPub !== undefined) this.ephemeralPub = ephemeralPub + if (url !== undefined) this.url = url + if (isPaired !== undefined) this.isPaired = isPaired + if (fwVersion !== undefined) this.fwVersion = fwVersion + if (activeWallets !== undefined) this.activeWallets = activeWallets - if (this.setStoredClient) { - this.setStoredClient(this.getStateData()) - } - } + if (this.setStoredClient) { + this.setStoredClient(this.getStateData()) + } + } - /** - * Return JSON-stringified version of state data. Can be used to rehydrate an SDK session without - * reconnecting to the target Lattice. - * @internal - */ - private packStateData() { - try { - const data = { - activeWallets: { - internal: { - uid: this.activeWallets.internal.uid?.toString('hex'), - name: this.activeWallets.internal.name?.toString(), - capabilities: this.activeWallets.internal.capabilities, - }, - external: { - uid: this.activeWallets.external.uid?.toString('hex'), - name: this.activeWallets.external.name?.toString(), - capabilities: this.activeWallets.external.capabilities, - }, - }, - ephemeralPub: this.ephemeralPub?.getPublic()?.encode('hex', false), - fwVersion: this.fwVersion?.toString('hex'), - deviceId: this.deviceId, - name: this.name, - baseUrl: this.baseUrl, - privKey: this.privKey.toString('hex'), - retryCount: this.retryCount, - timeout: this.timeout, - } - return JSON.stringify(data) - } catch (err) { - console.warn('Could not pack state data:', err) - return null - } - } + /** + * Return JSON-stringified version of state data. Can be used to rehydrate an SDK session without + * reconnecting to the target Lattice. + * @internal + */ + private packStateData() { + try { + const data = { + activeWallets: { + internal: { + uid: this.activeWallets.internal.uid?.toString('hex'), + name: this.activeWallets.internal.name?.toString(), + capabilities: this.activeWallets.internal.capabilities, + }, + external: { + uid: this.activeWallets.external.uid?.toString('hex'), + name: this.activeWallets.external.name?.toString(), + capabilities: this.activeWallets.external.capabilities, + }, + }, + ephemeralPub: this.ephemeralPub?.getPublic()?.encode('hex', false), + fwVersion: this.fwVersion?.toString('hex'), + deviceId: this.deviceId, + name: this.name, + baseUrl: this.baseUrl, + privKey: this.privKey.toString('hex'), + retryCount: this.retryCount, + timeout: this.timeout, + } + return JSON.stringify(data) + } catch (err) { + console.warn('Could not pack state data:', err) + return null + } + } - /** - * Unpack a JSON-stringified version of state data and apply it to state. This will allow us to - * rehydrate an old session. - * @internal - */ - private unpackAndApplyStateData(data: string) { - try { - const unpacked = JSON.parse(data) - // Attempt to parse the data - const internalWallet = { - uid: Buffer.from(unpacked.activeWallets.internal.uid, 'hex'), - name: unpacked.activeWallets.internal.name - ? Buffer.from(unpacked.activeWallets.internal.name) - : null, - capabilities: unpacked.activeWallets.internal.capabilities, - external: false, - } - const externalWallet = { - uid: Buffer.from(unpacked.activeWallets.external.uid, 'hex'), - name: unpacked.activeWallets.external.name - ? Buffer.from(unpacked.activeWallets.external.name) - : null, - capabilities: unpacked.activeWallets.external.capabilities, - external: true, - } - const ephemeralPubBytes = Buffer.from(unpacked.ephemeralPub, 'hex') - const fwVersionBytes = Buffer.from(unpacked.fwVersion, 'hex') - const privKeyBytes = Buffer.from(unpacked.privKey, 'hex') - // Apply unpacked params - this.activeWallets.internal = internalWallet - this.activeWallets.external = externalWallet - this.ephemeralPub = getP256KeyPairFromPub(ephemeralPubBytes) - this.fwVersion = fwVersionBytes - this.deviceId = unpacked.deviceId - this.name = unpacked.name - this.baseUrl = unpacked.baseUrl - this.url = `${this.baseUrl}/${this.deviceId}` - this.privKey = privKeyBytes - this.key = getP256KeyPair(this.privKey) - this.retryCount = unpacked.retryCount - this.timeout = unpacked.timeout - this.retryWrapper = buildRetryWrapper(this, this.retryCount) - } catch (err) { - console.warn('Could not apply state data:', err) - } - } + /** + * Unpack a JSON-stringified version of state data and apply it to state. This will allow us to + * rehydrate an old session. + * @internal + */ + private unpackAndApplyStateData(data: string) { + try { + const unpacked = JSON.parse(data) + // Attempt to parse the data + const internalWallet = { + uid: Buffer.from(unpacked.activeWallets.internal.uid, 'hex'), + name: unpacked.activeWallets.internal.name + ? Buffer.from(unpacked.activeWallets.internal.name) + : null, + capabilities: unpacked.activeWallets.internal.capabilities, + external: false, + } + const externalWallet = { + uid: Buffer.from(unpacked.activeWallets.external.uid, 'hex'), + name: unpacked.activeWallets.external.name + ? Buffer.from(unpacked.activeWallets.external.name) + : null, + capabilities: unpacked.activeWallets.external.capabilities, + external: true, + } + const ephemeralPubBytes = Buffer.from(unpacked.ephemeralPub, 'hex') + const fwVersionBytes = Buffer.from(unpacked.fwVersion, 'hex') + const privKeyBytes = Buffer.from(unpacked.privKey, 'hex') + // Apply unpacked params + this.activeWallets.internal = internalWallet + this.activeWallets.external = externalWallet + this.ephemeralPub = getP256KeyPairFromPub(ephemeralPubBytes) + this.fwVersion = fwVersionBytes + this.deviceId = unpacked.deviceId + this.name = unpacked.name + this.baseUrl = unpacked.baseUrl + this.url = `${this.baseUrl}/${this.deviceId}` + this.privKey = privKeyBytes + this.key = getP256KeyPair(this.privKey) + this.retryCount = unpacked.retryCount + this.timeout = unpacked.timeout + this.retryWrapper = buildRetryWrapper(this, this.retryCount) + } catch (err) { + console.warn('Could not apply state data:', err) + } + } } diff --git a/packages/sdk/src/constants.ts b/packages/sdk/src/constants.ts index cb681e51..6c48ea18 100644 --- a/packages/sdk/src/constants.ts +++ b/packages/sdk/src/constants.ts @@ -1,16 +1,16 @@ import { - LatticeEncDataSchema, - LatticeGetAddressesFlag, - LatticeSignBlsDst, - LatticeSignCurve, - LatticeSignEncoding, - LatticeSignHash, + LatticeEncDataSchema, + LatticeGetAddressesFlag, + LatticeSignBlsDst, + LatticeSignCurve, + LatticeSignEncoding, + LatticeSignHash, } from './protocol/latticeConstants' import type { - ActiveWallets, - FirmwareArr, - FirmwareConstants, - WalletPath, + ActiveWallets, + FirmwareArr, + FirmwareConstants, + WalletPath, } from './types/index.js' /** @@ -18,58 +18,58 @@ import type { * @public */ export const EXTERNAL = { - // Optional flags for `getAddresses` - GET_ADDR_FLAGS: { - SECP256K1_PUB: LatticeGetAddressesFlag.secp256k1Pubkey, - ED25519_PUB: LatticeGetAddressesFlag.ed25519Pubkey, - BLS12_381_G1_PUB: LatticeGetAddressesFlag.bls12_381Pubkey, - SECP256K1_XPUB: LatticeGetAddressesFlag.secp256k1Xpub, - }, - // Options for building general signing requests - SIGNING: { - HASHES: { - NONE: LatticeSignHash.none, - KECCAK256: LatticeSignHash.keccak256, - SHA256: LatticeSignHash.sha256, - }, - CURVES: { - SECP256K1: LatticeSignCurve.secp256k1, - ED25519: LatticeSignCurve.ed25519, - BLS12_381_G2: LatticeSignCurve.bls12_381, - }, - ENCODINGS: { - NONE: LatticeSignEncoding.none, - SOLANA: LatticeSignEncoding.solana, - EVM: LatticeSignEncoding.evm, - ETH_DEPOSIT: LatticeSignEncoding.eth_deposit, - EIP7702_AUTH: LatticeSignEncoding.eip7702_auth, - EIP7702_AUTH_LIST: LatticeSignEncoding.eip7702_auth_list, - }, - BLS_DST: { - BLS_DST_NUL: LatticeSignBlsDst.NUL, - BLS_DST_POP: LatticeSignBlsDst.POP, - }, - }, - // Options for exporting encrypted data - ENC_DATA: { - SCHEMAS: { - BLS_KEYSTORE_EIP2335_PBKDF_V4: LatticeEncDataSchema.eip2335, - }, - }, - ETH_CONSENSUS_SPEC: { - NETWORKS: { - MAINNET_GENESIS: { - networkName: 'mainnet', - forkVersion: Buffer.alloc(4), - // Empty root because there were no validators at genesis - validatorsRoot: Buffer.alloc(32), - }, - }, - DOMAINS: { - DEPOSIT: Buffer.from('03000000', 'hex'), - VOLUNTARY_EXIT: Buffer.from('04000000', 'hex'), - }, - }, + // Optional flags for `getAddresses` + GET_ADDR_FLAGS: { + SECP256K1_PUB: LatticeGetAddressesFlag.secp256k1Pubkey, + ED25519_PUB: LatticeGetAddressesFlag.ed25519Pubkey, + BLS12_381_G1_PUB: LatticeGetAddressesFlag.bls12_381Pubkey, + SECP256K1_XPUB: LatticeGetAddressesFlag.secp256k1Xpub, + }, + // Options for building general signing requests + SIGNING: { + HASHES: { + NONE: LatticeSignHash.none, + KECCAK256: LatticeSignHash.keccak256, + SHA256: LatticeSignHash.sha256, + }, + CURVES: { + SECP256K1: LatticeSignCurve.secp256k1, + ED25519: LatticeSignCurve.ed25519, + BLS12_381_G2: LatticeSignCurve.bls12_381, + }, + ENCODINGS: { + NONE: LatticeSignEncoding.none, + SOLANA: LatticeSignEncoding.solana, + EVM: LatticeSignEncoding.evm, + ETH_DEPOSIT: LatticeSignEncoding.eth_deposit, + EIP7702_AUTH: LatticeSignEncoding.eip7702_auth, + EIP7702_AUTH_LIST: LatticeSignEncoding.eip7702_auth_list, + }, + BLS_DST: { + BLS_DST_NUL: LatticeSignBlsDst.NUL, + BLS_DST_POP: LatticeSignBlsDst.POP, + }, + }, + // Options for exporting encrypted data + ENC_DATA: { + SCHEMAS: { + BLS_KEYSTORE_EIP2335_PBKDF_V4: LatticeEncDataSchema.eip2335, + }, + }, + ETH_CONSENSUS_SPEC: { + NETWORKS: { + MAINNET_GENESIS: { + networkName: 'mainnet', + forkVersion: Buffer.alloc(4), + // Empty root because there were no validators at genesis + validatorsRoot: Buffer.alloc(32), + }, + }, + DOMAINS: { + DEPOSIT: Buffer.from('03000000', 'hex'), + VOLUNTARY_EXIT: Buffer.from('04000000', 'hex'), + }, + }, } as const //=============================== @@ -77,26 +77,26 @@ export const EXTERNAL = { //=============================== /** @internal */ const addressSizes = { - BTC: 20, // 20 byte pubkeyhash - ETH: 20, // 20 byte address not including 0x prefix + BTC: 20, // 20 byte pubkeyhash + ETH: 20, // 20 byte address not including 0x prefix } as const /** @internal */ const CURRENCIES = { - ETH: 'ETH', - BTC: 'BTC', - ETH_MSG: 'ETH_MSG', + ETH: 'ETH', + BTC: 'BTC', + ETH_MSG: 'ETH_MSG', } as const /** @internal */ // THIS NEEDS TO BE A PROTOCOL CONSTANT TOO const signingSchema = { - BTC_TRANSFER: 0, - ETH_TRANSFER: 1, - ERC20_TRANSFER: 2, - ETH_MSG: 3, - EXTRA_DATA: 4, - GENERAL_SIGNING: 5, + BTC_TRANSFER: 0, + ETH_TRANSFER: 1, + ERC20_TRANSFER: 2, + ETH_MSG: 3, + EXTRA_DATA: 4, + GENERAL_SIGNING: 5, } as const /** @internal */ @@ -104,17 +104,17 @@ const HARDENED_OFFSET = 0x80000000 // Hardened offset /** @internal */ const BIP_CONSTANTS = { - PURPOSES: { - ETH: HARDENED_OFFSET + 44, - BTC_LEGACY: HARDENED_OFFSET + 44, - BTC_WRAPPED_SEGWIT: HARDENED_OFFSET + 49, - BTC_SEGWIT: HARDENED_OFFSET + 84, - }, - COINS: { - ETH: HARDENED_OFFSET + 60, - BTC: HARDENED_OFFSET, - BTC_TESTNET: HARDENED_OFFSET + 1, - }, + PURPOSES: { + ETH: HARDENED_OFFSET + 44, + BTC_LEGACY: HARDENED_OFFSET + 44, + BTC_WRAPPED_SEGWIT: HARDENED_OFFSET + 49, + BTC_SEGWIT: HARDENED_OFFSET + 84, + }, + COINS: { + ETH: HARDENED_OFFSET + 60, + BTC: HARDENED_OFFSET, + BTC_TESTNET: HARDENED_OFFSET + 1, + }, } as const /** @internal For all HSM-bound requests */ @@ -134,334 +134,334 @@ const BASE_URL = 'https://signing.gridpl.us' /** @internal */ const EIP712_ABI_LATTICE_FW_TYPE_MAP = { - address: 1, - bool: 2, - uint8: 3, - uint16: 4, - uint24: 5, - uint32: 6, - uint40: 7, - uint48: 8, - uint56: 9, - uint64: 10, - uint72: 11, - uint80: 12, - uint88: 13, - uint96: 14, - uint104: 15, - uint112: 16, - uint120: 17, - uint128: 18, - uint136: 19, - uint144: 20, - uint152: 21, - uint160: 22, - uint168: 23, - uint176: 24, - uint184: 25, - uint192: 26, - uint200: 27, - uint208: 28, - uint216: 29, - uint224: 30, - uint232: 31, - uint240: 32, - uint248: 33, - uint256: 34, - int8: 35, - int16: 36, - int24: 37, - int32: 38, - int40: 39, - int48: 40, - int56: 41, - int64: 42, - int72: 43, - int80: 44, - int88: 45, - int96: 46, - int104: 47, - int112: 48, - int120: 49, - int128: 50, - int136: 51, - int144: 52, - int152: 53, - int160: 54, - int168: 55, - int176: 56, - int184: 57, - int192: 58, - int200: 59, - int208: 60, - int216: 61, - int224: 62, - int232: 63, - int240: 64, - int248: 65, - int256: 66, - uint: 67, - bytes1: 69, - bytes2: 70, - bytes3: 71, - bytes4: 72, - bytes5: 73, - bytes6: 74, - bytes7: 75, - bytes8: 76, - bytes9: 77, - bytes10: 78, - bytes11: 79, - bytes12: 80, - bytes13: 81, - bytes14: 82, - bytes15: 83, - bytes16: 84, - bytes17: 85, - bytes18: 86, - bytes19: 87, - bytes20: 88, - bytes21: 89, - bytes22: 90, - bytes23: 91, - bytes24: 92, - bytes25: 93, - bytes26: 94, - bytes27: 95, - bytes28: 96, - bytes29: 97, - bytes30: 98, - bytes31: 99, - bytes32: 100, - bytes: 101, - string: 102, + address: 1, + bool: 2, + uint8: 3, + uint16: 4, + uint24: 5, + uint32: 6, + uint40: 7, + uint48: 8, + uint56: 9, + uint64: 10, + uint72: 11, + uint80: 12, + uint88: 13, + uint96: 14, + uint104: 15, + uint112: 16, + uint120: 17, + uint128: 18, + uint136: 19, + uint144: 20, + uint152: 21, + uint160: 22, + uint168: 23, + uint176: 24, + uint184: 25, + uint192: 26, + uint200: 27, + uint208: 28, + uint216: 29, + uint224: 30, + uint232: 31, + uint240: 32, + uint248: 33, + uint256: 34, + int8: 35, + int16: 36, + int24: 37, + int32: 38, + int40: 39, + int48: 40, + int56: 41, + int64: 42, + int72: 43, + int80: 44, + int88: 45, + int96: 46, + int104: 47, + int112: 48, + int120: 49, + int128: 50, + int136: 51, + int144: 52, + int152: 53, + int160: 54, + int168: 55, + int176: 56, + int184: 57, + int192: 58, + int200: 59, + int208: 60, + int216: 61, + int224: 62, + int232: 63, + int240: 64, + int248: 65, + int256: 66, + uint: 67, + bytes1: 69, + bytes2: 70, + bytes3: 71, + bytes4: 72, + bytes5: 73, + bytes6: 74, + bytes7: 75, + bytes8: 76, + bytes9: 77, + bytes10: 78, + bytes11: 79, + bytes12: 80, + bytes13: 81, + bytes14: 82, + bytes15: 83, + bytes16: 84, + bytes17: 85, + bytes18: 86, + bytes19: 87, + bytes20: 88, + bytes21: 89, + bytes22: 90, + bytes23: 91, + bytes24: 92, + bytes25: 93, + bytes26: 94, + bytes27: 95, + bytes28: 96, + bytes29: 97, + bytes30: 98, + bytes31: 99, + bytes32: 100, + bytes: 101, + string: 102, } /** @internal */ const ETH_ABI_LATTICE_FW_TYPE_MAP = { - ...EIP712_ABI_LATTICE_FW_TYPE_MAP, - tuple1: 103, - tuple2: 104, - tuple3: 105, - tuple4: 106, - tuple5: 107, - tuple6: 108, - tuple7: 109, - tuple8: 110, - tuple9: 111, - tuple10: 112, - tuple11: 113, - tuple12: 114, - tuple13: 115, - tuple14: 116, - tuple15: 117, - tuple16: 118, - tuple17: 119, // Firmware currently cannot support tuples larger than this + ...EIP712_ABI_LATTICE_FW_TYPE_MAP, + tuple1: 103, + tuple2: 104, + tuple3: 105, + tuple4: 106, + tuple5: 107, + tuple6: 108, + tuple7: 109, + tuple8: 110, + tuple9: 111, + tuple10: 112, + tuple11: 113, + tuple12: 114, + tuple13: 115, + tuple14: 116, + tuple15: 117, + tuple16: 118, + tuple17: 119, // Firmware currently cannot support tuples larger than this } /** @internal */ const ethMsgProtocol = { - SIGN_PERSONAL: { - str: 'signPersonal', - enumIdx: 0, // Enum index of this protocol in Lattice firmware - }, - TYPED_DATA: { - str: 'typedData', - enumIdx: 1, - rawDataMaxLen: 1629, // Max size of raw data payload in bytes - typeCodes: EIP712_ABI_LATTICE_FW_TYPE_MAP, // Enum indices of data types in Lattice firmware - }, + SIGN_PERSONAL: { + str: 'signPersonal', + enumIdx: 0, // Enum index of this protocol in Lattice firmware + }, + TYPED_DATA: { + str: 'typedData', + enumIdx: 1, + rawDataMaxLen: 1629, // Max size of raw data payload in bytes + typeCodes: EIP712_ABI_LATTICE_FW_TYPE_MAP, // Enum indices of data types in Lattice firmware + }, } /** @internal */ function getFwVersionConst(v: Buffer): FirmwareConstants { - const c: any = { - extraDataFrameSz: 0, - extraDataMaxFrames: 0, - genericSigning: {} as any, - } - function gte(v: Buffer, exp: FirmwareArr): boolean { - // Note that `v` fields come in as [fix|minor|major] - return ( - v[2] > exp[0] || - (v[2] === exp[0] && v[1] > exp[1]) || - (v[2] === exp[0] && v[1] === exp[1] && v[0] > exp[2]) || - (v[2] === exp[0] && v[1] === exp[1] && v[0] === exp[2]) - ) - } - // Very old legacy versions do not give a version number - const legacy = v.length === 0 - - // BASE FIELDS - //-------------------------------------- - - // Various size constants have changed on the firmware side over time and - // are captured here - if (!legacy && gte(v, [0, 10, 4])) { - // >=0.10.3 - c.reqMaxDataSz = 1678 - c.ethMaxGasPrice = 20000000000000 // 20000 gwei - c.addrFlagsAllowed = true - } else if (!legacy && gte(v, [0, 10, 0])) { - // >=0.10.0 - c.reqMaxDataSz = 1678 - c.ethMaxGasPrice = 20000000000000 // 20000 gwei - c.addrFlagsAllowed = true - } else { - // Legacy or <0.10.0 - c.reqMaxDataSz = 1152 - c.ethMaxGasPrice = 500000000000 // 500 gwei - c.addrFlagsAllowed = false - } - // These transformations apply to all versions. The subtraction - // of 128 bytes accounts for metadata and is for legacy reasons. - // For all modern versions, these are 1550 bytes. - // NOTE: Non-legacy ETH txs (e.g. EIP1559) will shrink - // this number. - // See `ETH_BASE_TX_MAX_DATA_SZ` and `ETH_MAX_BASE_MSG_SZ` in firmware - c.ethMaxDataSz = c.reqMaxDataSz - 128 - c.ethMaxMsgSz = c.ethMaxDataSz - // Max number of params in an EIP712 type. This was added to firmware - // to avoid blowing stack size. - c.eip712MaxTypeParams = 18 - - // ----- - // EXTRA FIELDS ADDED IN LATER FIRMWARE VERSIONS - // ----- - - // --- V0.10.X --- - // V0.10.4 introduced the ability to send signing requests over multiple - // data frames (i.e. in multiple requests) - if (!legacy && gte(v, [0, 10, 4])) { - c.extraDataFrameSz = 1500 // 1500 bytes per frame of extraData allowed - c.extraDataMaxFrames = 1 // 1 frame of extraData allowed - } - // V0.10.5 added the ability to use flexible address path sizes, which - // changes the `getAddress` API. It also added support for EIP712 - if (!legacy && gte(v, [0, 10, 5])) { - c.varAddrPathSzAllowed = true - c.eip712Supported = true - } - // V0.10.8 allows a user to sign a prehashed transaction if the payload - // is too big - if (!legacy && gte(v, [0, 10, 8])) { - c.prehashAllowed = true - } - // V0.10.10 allows a user to sign a prehashed ETH message if payload too big - if (!legacy && gte(v, [0, 10, 10])) { - c.ethMsgPreHashAllowed = true - } - - // --- 0.11.X --- - // V0.11.0 allows new ETH transaction types - if (!legacy && gte(v, [0, 11, 0])) { - c.allowedEthTxTypes = [ - 1, // eip2930 - 2, // eip1559 - ] - // This version added extra data fields to the ETH tx - c.ethMaxDataSz -= 10 - c.ethMaxMsgSz = c.ethMaxDataSz - } - // V0.11.2 changed how messages are displayed. For personal_sign messages - // we now write the header (`Signer: `) into the main body of the screen. - // This means personal sign message max size is slightly smaller than for - // EIP712 messages because in the latter case there is no header - // Note that `` has max size of 62 bytes (`m/X/X/...`) - if (!legacy && gte(v, [0, 11, 2])) { - c.personalSignHeaderSz = 72 - } - - // --- V0.12.X --- - // V0.12.0 added an API for creating, removing, and fetching key-val file - // records. For the purposes of this SDK, we only hook into one type of kv - // file: address names. - if (!legacy && gte(v, [0, 12, 0])) { - c.kvActionsAllowed = true - c.kvKeyMaxStrSz = 63 - c.kvValMaxStrSz = 63 - c.kvActionMaxNum = 10 - c.kvRemoveMaxNum = 100 - } - - // --- V0.13.X --- - // V0.13.0 added native segwit addresses and fixed a bug in exporting - // legacy bitcoin addresses - if (!legacy && gte(v, [0, 13, 0])) { - c.allowBtcLegacyAndSegwitAddrs = true - // Random address to be used when trying to deploy a contract - c.contractDeployKey = '0x08002e0fec8e6acf00835f43c9764f7364fa3f42' - } - - // --- V0.14.X --- - // V0.14.0 added support for a more robust API around ABI definitions - // and generic signing functionality - if (!legacy && gte(v, [0, 14, 0])) { - // Size of `category` buffer. Inclusive of null terminator byte. - c.abiCategorySz = 32 - c.abiMaxRmv = 200 // Max number of ABI defs that can be removed with - // a single request - // See `sizeof(GenericSigningRequest_t)` in firmware - c.genericSigning.baseReqSz = 1552 - // See `GENERIC_SIGNING_BASE_MSG_SZ` in firmware - c.genericSigning.baseDataSz = 1519 - c.genericSigning.hashTypes = EXTERNAL.SIGNING.HASHES - c.genericSigning.curveTypes = EXTERNAL.SIGNING.CURVES - c.genericSigning.encodingTypes = { - NONE: EXTERNAL.SIGNING.ENCODINGS.NONE, - SOLANA: EXTERNAL.SIGNING.ENCODINGS.SOLANA, - } - // Supported flags for `getAddresses` - c.getAddressFlags = [ - EXTERNAL.GET_ADDR_FLAGS.ED25519_PUB, - EXTERNAL.GET_ADDR_FLAGS.SECP256K1_PUB, - ] - // We updated the max number of params in EIP712 types - c.eip712MaxTypeParams = 36 - } - // DEPRECATED - // V0.14.1 Added the Terra decoder - // if (!legacy && gte(v, [0, 14, 1])) { - // c.genericSigning.encodingTypes.TERRA = EXTERNAL.SIGNING.ENCODINGS.TERRA; - // } - - // --- V0.15.X --- - // V0.15.0 added an EVM decoder and removed the legacy ETH signing pathway - if (!legacy && gte(v, [0, 15, 0])) { - c.genericSigning.encodingTypes.EVM = EXTERNAL.SIGNING.ENCODINGS.EVM - // We now use the general signing data field as the base - // Note that we have NOT removed the ETH_MSG type so we should - // not change ethMaxMsgSz - c.ethMaxDataSz = 1550 - 31 - // Max buffer size for get/add decoder requests - c.maxDecoderBufSz = 1600 - // Code used to write a calldata decoder - c.genericSigning.calldataDecoding = { - reserved: 2895728, - maxSz: 1024, - } - } - - // --- V0.17.X --- - // V0.17.0 added support for BLS12-381-G1 pubkeys and G2 sigs - if (!legacy && gte(v, [0, 17, 0])) { - c.getAddressFlags.push(EXTERNAL.GET_ADDR_FLAGS.BLS12_381_G1_PUB) - c.genericSigning.encodingTypes.ETH_DEPOSIT = - EXTERNAL.SIGNING.ENCODINGS.ETH_DEPOSIT - } - - // --- V0.18.X --- - // V0.18.0 added support for EIP7702 signing - // TODO: update patch version when this is released - if (!legacy && gte(v, [0, 18, 0])) { - c.genericSigning.encodingTypes = { - ...c.genericSigning.encodingTypes, - EIP7702_AUTH: EXTERNAL.SIGNING.ENCODINGS.EIP7702_AUTH, - EIP7702_AUTH_LIST: EXTERNAL.SIGNING.ENCODINGS.EIP7702_AUTH_LIST, - } - } - - return c + const c: any = { + extraDataFrameSz: 0, + extraDataMaxFrames: 0, + genericSigning: {} as any, + } + function gte(v: Buffer, exp: FirmwareArr): boolean { + // Note that `v` fields come in as [fix|minor|major] + return ( + v[2] > exp[0] || + (v[2] === exp[0] && v[1] > exp[1]) || + (v[2] === exp[0] && v[1] === exp[1] && v[0] > exp[2]) || + (v[2] === exp[0] && v[1] === exp[1] && v[0] === exp[2]) + ) + } + // Very old legacy versions do not give a version number + const legacy = v.length === 0 + + // BASE FIELDS + //-------------------------------------- + + // Various size constants have changed on the firmware side over time and + // are captured here + if (!legacy && gte(v, [0, 10, 4])) { + // >=0.10.3 + c.reqMaxDataSz = 1678 + c.ethMaxGasPrice = 20000000000000 // 20000 gwei + c.addrFlagsAllowed = true + } else if (!legacy && gte(v, [0, 10, 0])) { + // >=0.10.0 + c.reqMaxDataSz = 1678 + c.ethMaxGasPrice = 20000000000000 // 20000 gwei + c.addrFlagsAllowed = true + } else { + // Legacy or <0.10.0 + c.reqMaxDataSz = 1152 + c.ethMaxGasPrice = 500000000000 // 500 gwei + c.addrFlagsAllowed = false + } + // These transformations apply to all versions. The subtraction + // of 128 bytes accounts for metadata and is for legacy reasons. + // For all modern versions, these are 1550 bytes. + // NOTE: Non-legacy ETH txs (e.g. EIP1559) will shrink + // this number. + // See `ETH_BASE_TX_MAX_DATA_SZ` and `ETH_MAX_BASE_MSG_SZ` in firmware + c.ethMaxDataSz = c.reqMaxDataSz - 128 + c.ethMaxMsgSz = c.ethMaxDataSz + // Max number of params in an EIP712 type. This was added to firmware + // to avoid blowing stack size. + c.eip712MaxTypeParams = 18 + + // ----- + // EXTRA FIELDS ADDED IN LATER FIRMWARE VERSIONS + // ----- + + // --- V0.10.X --- + // V0.10.4 introduced the ability to send signing requests over multiple + // data frames (i.e. in multiple requests) + if (!legacy && gte(v, [0, 10, 4])) { + c.extraDataFrameSz = 1500 // 1500 bytes per frame of extraData allowed + c.extraDataMaxFrames = 1 // 1 frame of extraData allowed + } + // V0.10.5 added the ability to use flexible address path sizes, which + // changes the `getAddress` API. It also added support for EIP712 + if (!legacy && gte(v, [0, 10, 5])) { + c.varAddrPathSzAllowed = true + c.eip712Supported = true + } + // V0.10.8 allows a user to sign a prehashed transaction if the payload + // is too big + if (!legacy && gte(v, [0, 10, 8])) { + c.prehashAllowed = true + } + // V0.10.10 allows a user to sign a prehashed ETH message if payload too big + if (!legacy && gte(v, [0, 10, 10])) { + c.ethMsgPreHashAllowed = true + } + + // --- 0.11.X --- + // V0.11.0 allows new ETH transaction types + if (!legacy && gte(v, [0, 11, 0])) { + c.allowedEthTxTypes = [ + 1, // eip2930 + 2, // eip1559 + ] + // This version added extra data fields to the ETH tx + c.ethMaxDataSz -= 10 + c.ethMaxMsgSz = c.ethMaxDataSz + } + // V0.11.2 changed how messages are displayed. For personal_sign messages + // we now write the header (`Signer: `) into the main body of the screen. + // This means personal sign message max size is slightly smaller than for + // EIP712 messages because in the latter case there is no header + // Note that `` has max size of 62 bytes (`m/X/X/...`) + if (!legacy && gte(v, [0, 11, 2])) { + c.personalSignHeaderSz = 72 + } + + // --- V0.12.X --- + // V0.12.0 added an API for creating, removing, and fetching key-val file + // records. For the purposes of this SDK, we only hook into one type of kv + // file: address names. + if (!legacy && gte(v, [0, 12, 0])) { + c.kvActionsAllowed = true + c.kvKeyMaxStrSz = 63 + c.kvValMaxStrSz = 63 + c.kvActionMaxNum = 10 + c.kvRemoveMaxNum = 100 + } + + // --- V0.13.X --- + // V0.13.0 added native segwit addresses and fixed a bug in exporting + // legacy bitcoin addresses + if (!legacy && gte(v, [0, 13, 0])) { + c.allowBtcLegacyAndSegwitAddrs = true + // Random address to be used when trying to deploy a contract + c.contractDeployKey = '0x08002e0fec8e6acf00835f43c9764f7364fa3f42' + } + + // --- V0.14.X --- + // V0.14.0 added support for a more robust API around ABI definitions + // and generic signing functionality + if (!legacy && gte(v, [0, 14, 0])) { + // Size of `category` buffer. Inclusive of null terminator byte. + c.abiCategorySz = 32 + c.abiMaxRmv = 200 // Max number of ABI defs that can be removed with + // a single request + // See `sizeof(GenericSigningRequest_t)` in firmware + c.genericSigning.baseReqSz = 1552 + // See `GENERIC_SIGNING_BASE_MSG_SZ` in firmware + c.genericSigning.baseDataSz = 1519 + c.genericSigning.hashTypes = EXTERNAL.SIGNING.HASHES + c.genericSigning.curveTypes = EXTERNAL.SIGNING.CURVES + c.genericSigning.encodingTypes = { + NONE: EXTERNAL.SIGNING.ENCODINGS.NONE, + SOLANA: EXTERNAL.SIGNING.ENCODINGS.SOLANA, + } + // Supported flags for `getAddresses` + c.getAddressFlags = [ + EXTERNAL.GET_ADDR_FLAGS.ED25519_PUB, + EXTERNAL.GET_ADDR_FLAGS.SECP256K1_PUB, + ] + // We updated the max number of params in EIP712 types + c.eip712MaxTypeParams = 36 + } + // DEPRECATED + // V0.14.1 Added the Terra decoder + // if (!legacy && gte(v, [0, 14, 1])) { + // c.genericSigning.encodingTypes.TERRA = EXTERNAL.SIGNING.ENCODINGS.TERRA; + // } + + // --- V0.15.X --- + // V0.15.0 added an EVM decoder and removed the legacy ETH signing pathway + if (!legacy && gte(v, [0, 15, 0])) { + c.genericSigning.encodingTypes.EVM = EXTERNAL.SIGNING.ENCODINGS.EVM + // We now use the general signing data field as the base + // Note that we have NOT removed the ETH_MSG type so we should + // not change ethMaxMsgSz + c.ethMaxDataSz = 1550 - 31 + // Max buffer size for get/add decoder requests + c.maxDecoderBufSz = 1600 + // Code used to write a calldata decoder + c.genericSigning.calldataDecoding = { + reserved: 2895728, + maxSz: 1024, + } + } + + // --- V0.17.X --- + // V0.17.0 added support for BLS12-381-G1 pubkeys and G2 sigs + if (!legacy && gte(v, [0, 17, 0])) { + c.getAddressFlags.push(EXTERNAL.GET_ADDR_FLAGS.BLS12_381_G1_PUB) + c.genericSigning.encodingTypes.ETH_DEPOSIT = + EXTERNAL.SIGNING.ENCODINGS.ETH_DEPOSIT + } + + // --- V0.18.X --- + // V0.18.0 added support for EIP7702 signing + // TODO: update patch version when this is released + if (!legacy && gte(v, [0, 18, 0])) { + c.genericSigning.encodingTypes = { + ...c.genericSigning.encodingTypes, + EIP7702_AUTH: EXTERNAL.SIGNING.ENCODINGS.EIP7702_AUTH, + EIP7702_AUTH_LIST: EXTERNAL.SIGNING.ENCODINGS.EIP7702_AUTH_LIST, + } + } + + return c } /** @internal */ @@ -470,38 +470,38 @@ const ASCII_REGEX = /^[\u0000-\u007F]+$/ /** @internal */ const EXTERNAL_NETWORKS_BY_CHAIN_ID_URL = - 'https://gridplus.github.io/chains/chains.json' + 'https://gridplus.github.io/chains/chains.json' /** @internal - Max number of addresses to fetch */ const MAX_ADDR = 10 /** @internal */ const NETWORKS_BY_CHAIN_ID = { - 1: { - name: 'ethereum', - baseUrl: 'https://api.etherscan.io', - apiRoute: 'api?module=contract&action=getabi', - }, - 137: { - name: 'polygon', - baseUrl: 'https://api.polygonscan.com', - apiRoute: 'api?module=contract&action=getabi', - }, - 56: { - name: 'binance', - baseUrl: 'https://api.bscscan.com', - apiRoute: 'api?module=contract&action=getabi', - }, - 42220: { - name: 'celo', - baseUrl: 'https://api.celoscan.io', - apiRoute: 'api?module=contract&action=getabi', - }, - 43114: { - name: 'avalanche', - baseUrl: 'https://api.snowtrace.io', - apiRoute: 'api?module=contract&action=getabi', - }, + 1: { + name: 'ethereum', + baseUrl: 'https://api.etherscan.io', + apiRoute: 'api?module=contract&action=getabi', + }, + 137: { + name: 'polygon', + baseUrl: 'https://api.polygonscan.com', + apiRoute: 'api?module=contract&action=getabi', + }, + 56: { + name: 'binance', + baseUrl: 'https://api.bscscan.com', + apiRoute: 'api?module=contract&action=getabi', + }, + 42220: { + name: 'celo', + baseUrl: 'https://api.celoscan.io', + apiRoute: 'api?module=contract&action=getabi', + }, + 43114: { + name: 'avalanche', + baseUrl: 'https://api.snowtrace.io', + apiRoute: 'api?module=contract&action=getabi', + }, } /** @internal */ @@ -509,81 +509,81 @@ export const EMPTY_WALLET_UID = Buffer.alloc(32) /** @internal */ export const DEFAULT_ACTIVE_WALLETS: ActiveWallets = { - internal: { - uid: EMPTY_WALLET_UID, - external: false, - name: Buffer.alloc(0), - capabilities: 0, - }, - external: { - uid: EMPTY_WALLET_UID, - external: true, - name: Buffer.alloc(0), - capabilities: 0, - }, + internal: { + uid: EMPTY_WALLET_UID, + external: false, + name: Buffer.alloc(0), + capabilities: 0, + }, + external: { + uid: EMPTY_WALLET_UID, + external: true, + name: Buffer.alloc(0), + capabilities: 0, + }, } /** @internal */ export const DEFAULT_ETH_DERIVATION: WalletPath = [ - HARDENED_OFFSET + 44, - HARDENED_OFFSET + 60, - HARDENED_OFFSET, - 0, - 0, + HARDENED_OFFSET + 44, + HARDENED_OFFSET + 60, + HARDENED_OFFSET, + 0, + 0, ] /** @internal */ export const BTC_LEGACY_DERIVATION = [ - HARDENED_OFFSET + 44, - HARDENED_OFFSET + 0, - HARDENED_OFFSET, - 0, - 0, + HARDENED_OFFSET + 44, + HARDENED_OFFSET + 0, + HARDENED_OFFSET, + 0, + 0, ] /** @internal */ export const BTC_LEGACY_CHANGE_DERIVATION = [ - HARDENED_OFFSET + 44, - HARDENED_OFFSET + 0, - HARDENED_OFFSET, - 0, - 0, + HARDENED_OFFSET + 44, + HARDENED_OFFSET + 0, + HARDENED_OFFSET, + 0, + 0, ] /** @internal */ export const BTC_SEGWIT_DERIVATION = [ - HARDENED_OFFSET + 84, - HARDENED_OFFSET, - HARDENED_OFFSET, - 0, - 0, + HARDENED_OFFSET + 84, + HARDENED_OFFSET, + HARDENED_OFFSET, + 0, + 0, ] /** @internal */ export const BTC_SEGWIT_CHANGE_DERIVATION = [ - HARDENED_OFFSET + 84, - HARDENED_OFFSET, - HARDENED_OFFSET, - 1, - 0, + HARDENED_OFFSET + 84, + HARDENED_OFFSET, + HARDENED_OFFSET, + 1, + 0, ] /** @internal */ export const BTC_WRAPPED_SEGWIT_DERIVATION = [ - HARDENED_OFFSET + 49, - HARDENED_OFFSET, - HARDENED_OFFSET, - 0, - 0, + HARDENED_OFFSET + 49, + HARDENED_OFFSET, + HARDENED_OFFSET, + 0, + 0, ] /** @internal */ export const BTC_WRAPPED_SEGWIT_CHANGE_DERIVATION = [ - HARDENED_OFFSET + 49, - HARDENED_OFFSET, - HARDENED_OFFSET, - 0, - 0, + HARDENED_OFFSET + 49, + HARDENED_OFFSET, + HARDENED_OFFSET, + 0, + 0, ] /** @@ -618,46 +618,46 @@ export const BTC_SEGWIT_ZPUB_PATH = "84'/0'/0'" /** @internal */ export const SOLANA_DERIVATION = [ - HARDENED_OFFSET + 44, - HARDENED_OFFSET + 501, - HARDENED_OFFSET, - HARDENED_OFFSET, + HARDENED_OFFSET + 44, + HARDENED_OFFSET + 501, + HARDENED_OFFSET, + HARDENED_OFFSET, ] /** @internal */ export const LEDGER_LIVE_DERIVATION = [ - HARDENED_OFFSET + 49, - HARDENED_OFFSET + 60, - HARDENED_OFFSET, - 0, - 0, + HARDENED_OFFSET + 49, + HARDENED_OFFSET + 60, + HARDENED_OFFSET, + 0, + 0, ] /** @internal */ export const LEDGER_LEGACY_DERIVATION = [ - HARDENED_OFFSET + 49, - HARDENED_OFFSET + 60, - HARDENED_OFFSET, - 0, + HARDENED_OFFSET + 49, + HARDENED_OFFSET + 60, + HARDENED_OFFSET, + 0, ] export { - ASCII_REGEX, - getFwVersionConst, - BIP_CONSTANTS, - BASE_URL, - CURRENCIES, - MAX_ADDR, - NETWORKS_BY_CHAIN_ID, - EXTERNAL_NETWORKS_BY_CHAIN_ID_URL, - addressSizes, - ethMsgProtocol, - signingSchema, - REQUEST_TYPE_BYTE, - VERSION_BYTE, - HARDENED_OFFSET, - HANDLE_LARGER_CHAIN_ID, - MAX_CHAIN_ID_BYTES, - ETH_ABI_LATTICE_FW_TYPE_MAP, - EXTERNAL as PUBLIC, + ASCII_REGEX, + getFwVersionConst, + BIP_CONSTANTS, + BASE_URL, + CURRENCIES, + MAX_ADDR, + NETWORKS_BY_CHAIN_ID, + EXTERNAL_NETWORKS_BY_CHAIN_ID_URL, + addressSizes, + ethMsgProtocol, + signingSchema, + REQUEST_TYPE_BYTE, + VERSION_BYTE, + HARDENED_OFFSET, + HANDLE_LARGER_CHAIN_ID, + MAX_CHAIN_ID_BYTES, + ETH_ABI_LATTICE_FW_TYPE_MAP, + EXTERNAL as PUBLIC, } diff --git a/packages/sdk/src/ethereum.ts b/packages/sdk/src/ethereum.ts index 41b224f0..2599eaf8 100644 --- a/packages/sdk/src/ethereum.ts +++ b/packages/sdk/src/ethereum.ts @@ -8,34 +8,34 @@ import bdec from 'cbor-bigdecimal' import { Hash } from 'ox' import secp256k1 from 'secp256k1' import { - type Hex, - type TransactionSerializable, - hexToNumber, - serializeTransaction, + type Hex, + type TransactionSerializable, + hexToNumber, + serializeTransaction, } from 'viem' import { - ASCII_REGEX, - EXTERNAL, - HANDLE_LARGER_CHAIN_ID, - MAX_CHAIN_ID_BYTES, - ethMsgProtocol, + ASCII_REGEX, + EXTERNAL, + HANDLE_LARGER_CHAIN_ID, + MAX_CHAIN_ID_BYTES, + ethMsgProtocol, } from './constants' import { buildGenericSigningMsgRequest } from './genericSigning' import { LatticeSignSchema } from './protocol' import { type FlexibleTransaction, TransactionSchema } from './schemas' import { - type FirmwareConstants, - type SigningPath, - TRANSACTION_TYPE, - type TransactionRequest, + type FirmwareConstants, + type SigningPath, + TRANSACTION_TYPE, + type TransactionRequest, } from './types' import { - buildSignerPathBuf, - convertRecoveryToV, - ensureHexBuffer, - fixLen, - isAsciiStr, - splitFrames, + buildSignerPathBuf, + convertRecoveryToV, + ensureHexBuffer, + fixLen, + isAsciiStr, + splitFrames, } from './util' const { ecdsaRecover } = secp256k1 @@ -43,588 +43,588 @@ const { ecdsaRecover } = secp256k1 bdec(cbor) const buildEthereumMsgRequest = (input) => { - if (!input.payload || !input.protocol || !input.signerPath) - throw new Error( - 'You must provide `payload`, `signerPath`, and `protocol` arguments in the messsage request', - ) - if (input.signerPath.length > 5 || input.signerPath.length < 2) - throw new Error('Please provide a signer path with 2-5 indices') - const req = { - schema: LatticeSignSchema.ethereumMsg, - payload: null, - input, // Save the input for later - msg: null, // Save the buffered message for later - } - switch (input.protocol) { - case 'signPersonal': - return buildPersonalSignRequest(req, input) - case 'eip712': - if (!input.fwConstants.eip712Supported) - throw new Error( - 'EIP712 is not supported by your Lattice firmware version. Please upgrade.', - ) - return buildEIP712Request(req, input) - default: - throw new Error('Unsupported protocol') - } + if (!input.payload || !input.protocol || !input.signerPath) + throw new Error( + 'You must provide `payload`, `signerPath`, and `protocol` arguments in the messsage request', + ) + if (input.signerPath.length > 5 || input.signerPath.length < 2) + throw new Error('Please provide a signer path with 2-5 indices') + const req = { + schema: LatticeSignSchema.ethereumMsg, + payload: null, + input, // Save the input for later + msg: null, // Save the buffered message for later + } + switch (input.protocol) { + case 'signPersonal': + return buildPersonalSignRequest(req, input) + case 'eip712': + if (!input.fwConstants.eip712Supported) + throw new Error( + 'EIP712 is not supported by your Lattice firmware version. Please upgrade.', + ) + return buildEIP712Request(req, input) + default: + throw new Error('Unsupported protocol') + } } const validateEthereumMsgResponse = (res, req) => { - const { signer, sig } = res - const { input, msg, prehash = null } = req - if (input.protocol === 'signPersonal') { - // NOTE: We are currently hardcoding networkID=1 and useEIP155=false but these - // may be configurable in future versions - const hash = prehash - ? prehash - : Buffer.from( - Hash.keccak256( - Buffer.concat([get_personal_sign_prefix(msg.length), msg]), - ), - ) - // Get recovery param with a `v` value of [27,28] by setting `useEIP155=false` - return addRecoveryParam(hash, sig, signer, { - chainId: 1, - useEIP155: false, - }) - } else if (input.protocol === 'eip712') { - // Use the validationPayload that was created in buildEIP712Request - // This payload has been parsed with forJSParser=true, converting all numbers - // to the format that TypedDataUtils.eip712Hash expects - const rawPayloadForHashing = req.validationPayload || req.input.payload - const payloadForHashing = req.validationPayload - ? cloneTypedDataPayload(req.validationPayload) - : normalizeTypedDataForHashing(rawPayloadForHashing) - const encoded = TypedDataUtils.eip712Hash( - payloadForHashing, - SignTypedDataVersion.V4, - ) - const digest = prehash ? prehash : encoded - // Parse chainId - it could be a number, hex string, decimal string, or bigint - let chainId = - input.payload.domain?.chainId || payloadForHashing.domain?.chainId - if (typeof chainId === 'string') { - chainId = chainId.startsWith('0x') - ? Number.parseInt(chainId, 16) - : Number.parseInt(chainId, 10) - } else if (typeof chainId === 'bigint') { - chainId = Number(chainId) - } - // Get recovery param with a `v` value of [27,28] by setting `useEIP155=false` - return addRecoveryParam(digest, sig, signer, { chainId, useEIP155: false }) - } else { - throw new Error('Unsupported protocol') - } + const { signer, sig } = res + const { input, msg, prehash = null } = req + if (input.protocol === 'signPersonal') { + // NOTE: We are currently hardcoding networkID=1 and useEIP155=false but these + // may be configurable in future versions + const hash = prehash + ? prehash + : Buffer.from( + Hash.keccak256( + Buffer.concat([get_personal_sign_prefix(msg.length), msg]), + ), + ) + // Get recovery param with a `v` value of [27,28] by setting `useEIP155=false` + return addRecoveryParam(hash, sig, signer, { + chainId: 1, + useEIP155: false, + }) + } else if (input.protocol === 'eip712') { + // Use the validationPayload that was created in buildEIP712Request + // This payload has been parsed with forJSParser=true, converting all numbers + // to the format that TypedDataUtils.eip712Hash expects + const rawPayloadForHashing = req.validationPayload || req.input.payload + const payloadForHashing = req.validationPayload + ? cloneTypedDataPayload(req.validationPayload) + : normalizeTypedDataForHashing(rawPayloadForHashing) + const encoded = TypedDataUtils.eip712Hash( + payloadForHashing, + SignTypedDataVersion.V4, + ) + const digest = prehash ? prehash : encoded + // Parse chainId - it could be a number, hex string, decimal string, or bigint + let chainId = + input.payload.domain?.chainId || payloadForHashing.domain?.chainId + if (typeof chainId === 'string') { + chainId = chainId.startsWith('0x') + ? Number.parseInt(chainId, 16) + : Number.parseInt(chainId, 10) + } else if (typeof chainId === 'bigint') { + chainId = Number(chainId) + } + // Get recovery param with a `v` value of [27,28] by setting `useEIP155=false` + return addRecoveryParam(digest, sig, signer, { chainId, useEIP155: false }) + } else { + throw new Error('Unsupported protocol') + } } function normalizeTypedDataForHashing(value: any): any { - if (value === null || value === undefined) { - return value - } - - if (typeof value === 'string') { - const trimmed = value.trim() - if (/^0x[0-9a-fA-F]+$/.test(trimmed)) { - try { - const asBigInt = BigInt(trimmed) - if ( - asBigInt <= BigInt(Number.MAX_SAFE_INTEGER) && - asBigInt >= BigInt(Number.MIN_SAFE_INTEGER) - ) { - return Number(asBigInt) - } - return asBigInt.toString(10) - } catch { - return trimmed - } - } - return trimmed - } - - if (typeof value === 'bigint') { - const asNumber = Number(value) - return Number.isSafeInteger(asNumber) ? asNumber : value.toString(10) - } - - if (BN.isBigNumber(value)) { - const asNumber = Number(value.toString(10)) - return Number.isSafeInteger(asNumber) ? asNumber : value.toString(10) - } - - if ( - value && - typeof value === 'object' && - typeof value.toString === 'function' && - value.constructor && - value.constructor.name === 'BN' && - typeof value.toArray === 'function' - ) { - const str = value.toString(10) - const asNumber = Number(str) - return Number.isSafeInteger(asNumber) ? asNumber : str - } - - if (Buffer.isBuffer(value) || value instanceof Uint8Array) { - return `0x${Buffer.from(value).toString('hex')}` - } - - if (Array.isArray(value)) { - return value.map((item) => normalizeTypedDataForHashing(item)) - } - - if (typeof value === 'object') { - const normalized: Record = {} - for (const [key, entry] of Object.entries(value)) { - normalized[key] = normalizeTypedDataForHashing(entry) - } - return normalized - } - - return value + if (value === null || value === undefined) { + return value + } + + if (typeof value === 'string') { + const trimmed = value.trim() + if (/^0x[0-9a-fA-F]+$/.test(trimmed)) { + try { + const asBigInt = BigInt(trimmed) + if ( + asBigInt <= BigInt(Number.MAX_SAFE_INTEGER) && + asBigInt >= BigInt(Number.MIN_SAFE_INTEGER) + ) { + return Number(asBigInt) + } + return asBigInt.toString(10) + } catch { + return trimmed + } + } + return trimmed + } + + if (typeof value === 'bigint') { + const asNumber = Number(value) + return Number.isSafeInteger(asNumber) ? asNumber : value.toString(10) + } + + if (BN.isBigNumber(value)) { + const asNumber = Number(value.toString(10)) + return Number.isSafeInteger(asNumber) ? asNumber : value.toString(10) + } + + if ( + value && + typeof value === 'object' && + typeof value.toString === 'function' && + value.constructor && + value.constructor.name === 'BN' && + typeof value.toArray === 'function' + ) { + const str = value.toString(10) + const asNumber = Number(str) + return Number.isSafeInteger(asNumber) ? asNumber : str + } + + if (Buffer.isBuffer(value) || value instanceof Uint8Array) { + return `0x${Buffer.from(value).toString('hex')}` + } + + if (Array.isArray(value)) { + return value.map((item) => normalizeTypedDataForHashing(item)) + } + + if (typeof value === 'object') { + const normalized: Record = {} + for (const [key, entry] of Object.entries(value)) { + normalized[key] = normalizeTypedDataForHashing(entry) + } + return normalized + } + + return value } function cloneTypedDataPayload(payload: T): T { - if (payload === undefined) return payload - if (structuredCloneFn) { - return structuredCloneFn(payload) - } - return basicTypedDataClone(payload) + if (payload === undefined) return payload + if (structuredCloneFn) { + return structuredCloneFn(payload) + } + return basicTypedDataClone(payload) } function basicTypedDataClone(value: T): T { - if (value === null || typeof value !== 'object') { - return value - } - if (Buffer.isBuffer(value)) { - return Buffer.from(value) as T - } - if (value instanceof Uint8Array) { - return new Uint8Array(value) as T - } - if (Array.isArray(value)) { - return value.map((item) => basicTypedDataClone(item)) as unknown as T - } - if (BN.isBigNumber(value)) { - return new BN(value) as T - } - if ( - value && - typeof value === 'object' && - (value as { constructor?: { name?: string } }).constructor?.name === 'BN' && - typeof (value as { clone?: () => unknown }).clone === 'function' - ) { - return (value as unknown as { clone: () => unknown }).clone() as T - } - if (value instanceof Date) { - return new Date(value.getTime()) as T - } - const cloned: Record = {} - for (const [key, entry] of Object.entries(value as Record)) { - cloned[key] = basicTypedDataClone(entry) - } - return cloned as T + if (value === null || typeof value !== 'object') { + return value + } + if (Buffer.isBuffer(value)) { + return Buffer.from(value) as T + } + if (value instanceof Uint8Array) { + return new Uint8Array(value) as T + } + if (Array.isArray(value)) { + return value.map((item) => basicTypedDataClone(item)) as unknown as T + } + if (BN.isBigNumber(value)) { + return new BN(value) as T + } + if ( + value && + typeof value === 'object' && + (value as { constructor?: { name?: string } }).constructor?.name === 'BN' && + typeof (value as { clone?: () => unknown }).clone === 'function' + ) { + return (value as unknown as { clone: () => unknown }).clone() as T + } + if (value instanceof Date) { + return new Date(value.getTime()) as T + } + const cloned: Record = {} + for (const [key, entry] of Object.entries(value as Record)) { + cloned[key] = basicTypedDataClone(entry) + } + return cloned as T } type StructuredCloneFn = (value: T, transfer?: unknown) => T const structuredCloneFn: StructuredCloneFn | null = - typeof globalThis !== 'undefined' && - typeof (globalThis as { structuredClone?: unknown }).structuredClone === - 'function' - ? (globalThis as { structuredClone: StructuredCloneFn }).structuredClone - : null + typeof globalThis !== 'undefined' && + typeof (globalThis as { structuredClone?: unknown }).structuredClone === + 'function' + ? (globalThis as { structuredClone: StructuredCloneFn }).structuredClone + : null const buildEthereumTxRequest = (data) => { - try { - let { chainId = 1 } = data - const { signerPath, eip155 = null, fwConstants, type = null } = data - const { - contractDeployKey, - extraDataFrameSz, - extraDataMaxFrames, - prehashAllowed, - } = fwConstants - const EXTRA_DATA_ALLOWED = extraDataFrameSz > 0 && extraDataMaxFrames > 0 - const MAX_BASE_DATA_SZ = fwConstants.ethMaxDataSz - const VAR_PATH_SZ = fwConstants.varAddrPathSzAllowed - // Sanity checks: - // There are a handful of named chains we allow the user to reference (`chainIds`) - // Custom chainIDs should be either numerical or hex strings - if ( - typeof chainId !== 'number' && - isValidChainIdHexNumStr(chainId) === false - ) { - chainId = chainIds[chainId] - } - // If this was not a custom chainID and we cannot find the name of it, exit - if (!chainId) throw new Error('Unsupported chain ID or name') - // Sanity check on signePath - if (!signerPath) throw new Error('`signerPath` not provided') - - // Is this a contract deployment? - if (data.to === null && !contractDeployKey) { - throw new Error( - 'Contract deployment not supported. Please update your Lattice firmware.', - ) - } - const isDeployment = data.to === null && contractDeployKey - // We support eip1559 and eip2930 types (as well as legacy) - const eip1559IsAllowed = - fwConstants.allowedEthTxTypes && - fwConstants.allowedEthTxTypes.indexOf(2) > -1 - const eip2930IsAllowed = - fwConstants.allowedEthTxTypes && - fwConstants.allowedEthTxTypes.indexOf(1) > -1 - const isEip1559 = eip1559IsAllowed && (type === 2 || type === 'eip1559') - const isEip2930 = eip2930IsAllowed && (type === 1 || type === 'eip2930') - if (type !== null && !isEip1559 && !isEip2930) - throw new Error('Unsupported Ethereum transaction type') - // Determine if we should use EIP155 given the chainID. - // If we are explicitly told to use eip155, we will use it. Otherwise, - // we will look up if the specified chainId is associated with a chain - // that does not use EIP155 by default. Note that most do use EIP155. - let useEIP155 = chainUsesEIP155(chainId) - if (eip155 !== null && typeof eip155 === 'boolean') { - useEIP155 = eip155 - } else if (isEip1559 || isEip2930) { - // Newer transaction types do not use EIP155 since the chainId is serialized - useEIP155 = false - } - - // Hack for metamask, which sends value=null for 0 ETH transactions - if (!data.value) data.value = 0 - - //-------------- - // 1. BUILD THE RAW TX FOR FUTURE RLP ENCODING - //-------------- - // Ensure all fields are 0x-prefixed hex strings - const rawTx = [] - // Build the transaction buffer array - const chainIdBytes = ensureHexBuffer(chainId) - const nonceBytes = ensureHexBuffer(data.nonce) - let gasPriceBytes: Buffer - const gasLimitBytes = ensureHexBuffer(data.gasLimit) - // Handle contract deployment (indicated by `to` being `null`) - // For contract deployment we write a 20-byte key to the request - // buffer, which gets swapped for an empty buffer in firmware. - let toRlpElem: Buffer - let toBytes: Buffer - if (isDeployment) { - toRlpElem = Buffer.alloc(0) - toBytes = ensureHexBuffer(contractDeployKey) - } else { - toRlpElem = ensureHexBuffer(data.to) - toBytes = ensureHexBuffer(data.to) - } - const valueBytes = ensureHexBuffer(data.value) - const dataBytes = ensureHexBuffer(data.data) - - if (isEip1559 || isEip2930) { - // EIP1559 and EIP2930 transactions have a chainID field - rawTx.push(chainIdBytes) - } - rawTx.push(nonceBytes) - let maxPriorityFeePerGasBytes: Buffer - let maxFeePerGasBytes: Buffer - if (isEip1559) { - if (!data.maxPriorityFeePerGas) - throw new Error( - 'EIP1559 transactions must include `maxPriorityFeePerGas`', - ) - maxPriorityFeePerGasBytes = ensureHexBuffer(data.maxPriorityFeePerGas) - rawTx.push(maxPriorityFeePerGasBytes) - maxFeePerGasBytes = ensureHexBuffer(data.maxFeePerGas) - rawTx.push(maxFeePerGasBytes) - // EIP1559 renamed "gasPrice" to "maxFeePerGas", but firmware still - // uses `gasPrice` in the struct, so update that value here. - gasPriceBytes = maxFeePerGasBytes - } else { - // EIP1559 transactions do not have the gasPrice field - gasPriceBytes = ensureHexBuffer(data.gasPrice) - rawTx.push(gasPriceBytes) - } - rawTx.push(gasLimitBytes) - rawTx.push(toRlpElem) - rawTx.push(valueBytes) - rawTx.push(dataBytes) - // We do not currently support accessList in firmware so we need to prehash if - // the list is non-null - let PREHASH_FROM_ACCESS_LIST = false - if (isEip1559 || isEip2930) { - const accessList = [] - if (Array.isArray(data.accessList)) { - data.accessList.forEach((listItem) => { - const keys = [] - listItem.storageKeys.forEach((key) => { - keys.push(ensureHexBuffer(key)) - }) - accessList.push([ensureHexBuffer(listItem.address), keys]) - PREHASH_FROM_ACCESS_LIST = true - }) - } - rawTx.push(accessList) - } else if (useEIP155 === true) { - // Add empty v,r,s values for EIP155 legacy transactions - rawTx.push(chainIdBytes) // v (which is the same as chainId in EIP155 txs) - rawTx.push(ensureHexBuffer(null)) // r - rawTx.push(ensureHexBuffer(null)) // s - } - //-------------- - // 2. BUILD THE LATTICE REQUEST PAYLOAD - //-------------- - const ETH_TX_NON_DATA_SZ = 122 // Accounts for metadata and non-data params - const txReqPayload = Buffer.alloc(MAX_BASE_DATA_SZ + ETH_TX_NON_DATA_SZ) - let off = 0 - // 1. EIP155 switch and chainID - //------------------ - txReqPayload.writeUInt8(Number(useEIP155), off) - off++ - // NOTE: Originally we designed for a 1-byte chainID, but modern rollup chains use much larger - // chainID values. To account for these, we will put the chainID into the `data` buffer if it - // is >=255. Values up to UINT64_MAX will be allowed. - let chainIdBuf: Buffer - let chainIdBufSz = 0 - if (useChainIdBuffer(chainId) === true) { - chainIdBuf = getChainIdBuf(chainId) - chainIdBufSz = chainIdBuf.length - if (chainIdBufSz > MAX_CHAIN_ID_BYTES) - throw new Error('ChainID provided is too large.') - // Signal to Lattice firmware that it needs to read the chainId from the tx.data buffer - txReqPayload.writeUInt8(HANDLE_LARGER_CHAIN_ID, off) - off++ - } else { - // For chainIDs <255, write it to the chainId u8 slot in the main tx buffer - chainIdBuf = ensureHexBuffer(chainId) - if (chainIdBuf.length !== 1) throw new Error('Error parsing chainID') - chainIdBuf.copy(txReqPayload, off) - off += chainIdBuf.length - } - // 2. Signer Path - //------------------ - const signerPathBuf = buildSignerPathBuf(signerPath, VAR_PATH_SZ) - signerPathBuf.copy(txReqPayload, off) - off += signerPathBuf.length - - // 3. ETH TX request data - //------------------ - if (nonceBytes.length > 4) throw new Error('Nonce too large') - nonceBytes.copy(txReqPayload, off + (4 - nonceBytes.length)) - off += 4 - if (gasPriceBytes.length > 8) throw new Error('Gas price too large') - gasPriceBytes.copy(txReqPayload, off + (8 - gasPriceBytes.length)) - off += 8 - if (gasLimitBytes.length > 4) throw new Error('Gas limit too large') - gasLimitBytes.copy(txReqPayload, off + (4 - gasLimitBytes.length)) - off += 4 - if (toBytes.length !== 20) throw new Error('Invalid `to` address') - toBytes.copy(txReqPayload, off) - off += 20 - if (valueBytes.length > 32) throw new Error('Value too large') - valueBytes.copy(txReqPayload, off + (32 - valueBytes.length)) - off += 32 - - // Extra Tx data comes before `data` in the struct - let PREHASH_UNSUPPORTED = false - if (fwConstants.allowedEthTxTypes) { - // Some types may not be supported by firmware, so we will need to prehash - if (PREHASH_FROM_ACCESS_LIST) { - PREHASH_UNSUPPORTED = true - } - txReqPayload.writeUInt8(PREHASH_UNSUPPORTED ? 1 : 0, off) - off += 1 - // EIP1559 & EIP2930 struct version - if (isEip1559) { - txReqPayload.writeUInt8(2, off) - off += 1 // Eip1559 type enum value - if (maxPriorityFeePerGasBytes.length > 8) - throw new Error('maxPriorityFeePerGasBytes too large') - maxPriorityFeePerGasBytes.copy( - txReqPayload, - off + (8 - maxPriorityFeePerGasBytes.length), - ) - off += 8 // Skip EIP1559 params - } else if (isEip2930) { - txReqPayload.writeUInt8(1, off) - off += 1 // Eip2930 type enum value - off += 8 // Skip EIP1559 params - } else { - off += 9 // Skip EIP1559 and EIP2930 params - } - } - - // Flow data into extraData requests, which will follow-up transaction requests, if supported/applicable - const extraDataPayloads = [] - let prehash = null - - // Create the buffer, prefix with chainId (if needed) and add data slice - const dataSz = dataBytes.length || 0 - const chainIdExtraSz = chainIdBufSz > 0 ? chainIdBufSz + 1 : 0 - const dataToCopy = Buffer.alloc(dataSz + chainIdExtraSz) - if (chainIdExtraSz > 0) { - dataToCopy.writeUInt8(chainIdBufSz, 0) - chainIdBuf.copy(dataToCopy, 1) - } - dataBytes.copy(dataToCopy, chainIdExtraSz) - - if (dataSz > MAX_BASE_DATA_SZ) { - // Determine sizes and run through sanity checks - const totalSz = dataSz + chainIdExtraSz - const maxSzAllowed = - MAX_BASE_DATA_SZ + extraDataMaxFrames * extraDataFrameSz - - if (prehashAllowed && totalSz > maxSzAllowed) { - // If this payload is too large to send, but the Lattice allows a prehashed message, do that - prehash = Buffer.from( - Hash.keccak256(get_rlp_encoded_preimage(rawTx, type)), - ) - } else { - if ( - !EXTRA_DATA_ALLOWED || - (EXTRA_DATA_ALLOWED && totalSz > maxSzAllowed) - ) - throw new Error( - `Data field too large (got ${dataBytes.length}; must be <=${maxSzAllowed - chainIdExtraSz} bytes)`, - ) - // Split overflow data into extraData frames - const frames = splitFrames( - dataToCopy.slice(MAX_BASE_DATA_SZ), - extraDataFrameSz, - ) - frames.forEach((frame) => { - const szLE = Buffer.alloc(4) - szLE.writeUInt32LE(frame.length, 0) - extraDataPayloads.push(Buffer.concat([szLE, frame])) - }) - } - } else if (PREHASH_UNSUPPORTED) { - // If something is unsupported in firmware but we want to allow such transactions, - // we prehash the message here. - prehash = Buffer.from( - Hash.keccak256(get_rlp_encoded_preimage(rawTx, type)), - ) - } - - // Write the data size (does *NOT* include the chainId buffer, if that exists) - txReqPayload.writeUInt16BE(dataBytes.length, off) - off += 2 - // Copy in the chainId buffer if needed - if (chainIdBufSz > 0) { - txReqPayload.writeUInt8(chainIdBufSz, off) - off++ - chainIdBuf.copy(txReqPayload, off) - off += chainIdBufSz - } - // Copy the first slice of the data itself. If this payload has been pre-hashed, include it - // in the `data` field. This will result in a different Lattice screen being drawn. - if (prehash) { - prehash.copy(txReqPayload, off) - off += MAX_BASE_DATA_SZ - } else { - dataBytes.slice(0, MAX_BASE_DATA_SZ).copy(txReqPayload, off) - off += MAX_BASE_DATA_SZ - } - return { - rawTx, - type, - payload: txReqPayload.slice(0, off), - extraDataPayloads, - schema: LatticeSignSchema.ethereum, // We will use eth transfer for all ETH txs for v1 - chainId, - useEIP155, - signerPath, - } - } catch (err) { - return { err: err.message } - } + try { + let { chainId = 1 } = data + const { signerPath, eip155 = null, fwConstants, type = null } = data + const { + contractDeployKey, + extraDataFrameSz, + extraDataMaxFrames, + prehashAllowed, + } = fwConstants + const EXTRA_DATA_ALLOWED = extraDataFrameSz > 0 && extraDataMaxFrames > 0 + const MAX_BASE_DATA_SZ = fwConstants.ethMaxDataSz + const VAR_PATH_SZ = fwConstants.varAddrPathSzAllowed + // Sanity checks: + // There are a handful of named chains we allow the user to reference (`chainIds`) + // Custom chainIDs should be either numerical or hex strings + if ( + typeof chainId !== 'number' && + isValidChainIdHexNumStr(chainId) === false + ) { + chainId = chainIds[chainId] + } + // If this was not a custom chainID and we cannot find the name of it, exit + if (!chainId) throw new Error('Unsupported chain ID or name') + // Sanity check on signePath + if (!signerPath) throw new Error('`signerPath` not provided') + + // Is this a contract deployment? + if (data.to === null && !contractDeployKey) { + throw new Error( + 'Contract deployment not supported. Please update your Lattice firmware.', + ) + } + const isDeployment = data.to === null && contractDeployKey + // We support eip1559 and eip2930 types (as well as legacy) + const eip1559IsAllowed = + fwConstants.allowedEthTxTypes && + fwConstants.allowedEthTxTypes.indexOf(2) > -1 + const eip2930IsAllowed = + fwConstants.allowedEthTxTypes && + fwConstants.allowedEthTxTypes.indexOf(1) > -1 + const isEip1559 = eip1559IsAllowed && (type === 2 || type === 'eip1559') + const isEip2930 = eip2930IsAllowed && (type === 1 || type === 'eip2930') + if (type !== null && !isEip1559 && !isEip2930) + throw new Error('Unsupported Ethereum transaction type') + // Determine if we should use EIP155 given the chainID. + // If we are explicitly told to use eip155, we will use it. Otherwise, + // we will look up if the specified chainId is associated with a chain + // that does not use EIP155 by default. Note that most do use EIP155. + let useEIP155 = chainUsesEIP155(chainId) + if (eip155 !== null && typeof eip155 === 'boolean') { + useEIP155 = eip155 + } else if (isEip1559 || isEip2930) { + // Newer transaction types do not use EIP155 since the chainId is serialized + useEIP155 = false + } + + // Hack for metamask, which sends value=null for 0 ETH transactions + if (!data.value) data.value = 0 + + //-------------- + // 1. BUILD THE RAW TX FOR FUTURE RLP ENCODING + //-------------- + // Ensure all fields are 0x-prefixed hex strings + const rawTx = [] + // Build the transaction buffer array + const chainIdBytes = ensureHexBuffer(chainId) + const nonceBytes = ensureHexBuffer(data.nonce) + let gasPriceBytes: Buffer + const gasLimitBytes = ensureHexBuffer(data.gasLimit) + // Handle contract deployment (indicated by `to` being `null`) + // For contract deployment we write a 20-byte key to the request + // buffer, which gets swapped for an empty buffer in firmware. + let toRlpElem: Buffer + let toBytes: Buffer + if (isDeployment) { + toRlpElem = Buffer.alloc(0) + toBytes = ensureHexBuffer(contractDeployKey) + } else { + toRlpElem = ensureHexBuffer(data.to) + toBytes = ensureHexBuffer(data.to) + } + const valueBytes = ensureHexBuffer(data.value) + const dataBytes = ensureHexBuffer(data.data) + + if (isEip1559 || isEip2930) { + // EIP1559 and EIP2930 transactions have a chainID field + rawTx.push(chainIdBytes) + } + rawTx.push(nonceBytes) + let maxPriorityFeePerGasBytes: Buffer + let maxFeePerGasBytes: Buffer + if (isEip1559) { + if (!data.maxPriorityFeePerGas) + throw new Error( + 'EIP1559 transactions must include `maxPriorityFeePerGas`', + ) + maxPriorityFeePerGasBytes = ensureHexBuffer(data.maxPriorityFeePerGas) + rawTx.push(maxPriorityFeePerGasBytes) + maxFeePerGasBytes = ensureHexBuffer(data.maxFeePerGas) + rawTx.push(maxFeePerGasBytes) + // EIP1559 renamed "gasPrice" to "maxFeePerGas", but firmware still + // uses `gasPrice` in the struct, so update that value here. + gasPriceBytes = maxFeePerGasBytes + } else { + // EIP1559 transactions do not have the gasPrice field + gasPriceBytes = ensureHexBuffer(data.gasPrice) + rawTx.push(gasPriceBytes) + } + rawTx.push(gasLimitBytes) + rawTx.push(toRlpElem) + rawTx.push(valueBytes) + rawTx.push(dataBytes) + // We do not currently support accessList in firmware so we need to prehash if + // the list is non-null + let PREHASH_FROM_ACCESS_LIST = false + if (isEip1559 || isEip2930) { + const accessList = [] + if (Array.isArray(data.accessList)) { + data.accessList.forEach((listItem) => { + const keys = [] + listItem.storageKeys.forEach((key) => { + keys.push(ensureHexBuffer(key)) + }) + accessList.push([ensureHexBuffer(listItem.address), keys]) + PREHASH_FROM_ACCESS_LIST = true + }) + } + rawTx.push(accessList) + } else if (useEIP155 === true) { + // Add empty v,r,s values for EIP155 legacy transactions + rawTx.push(chainIdBytes) // v (which is the same as chainId in EIP155 txs) + rawTx.push(ensureHexBuffer(null)) // r + rawTx.push(ensureHexBuffer(null)) // s + } + //-------------- + // 2. BUILD THE LATTICE REQUEST PAYLOAD + //-------------- + const ETH_TX_NON_DATA_SZ = 122 // Accounts for metadata and non-data params + const txReqPayload = Buffer.alloc(MAX_BASE_DATA_SZ + ETH_TX_NON_DATA_SZ) + let off = 0 + // 1. EIP155 switch and chainID + //------------------ + txReqPayload.writeUInt8(Number(useEIP155), off) + off++ + // NOTE: Originally we designed for a 1-byte chainID, but modern rollup chains use much larger + // chainID values. To account for these, we will put the chainID into the `data` buffer if it + // is >=255. Values up to UINT64_MAX will be allowed. + let chainIdBuf: Buffer + let chainIdBufSz = 0 + if (useChainIdBuffer(chainId) === true) { + chainIdBuf = getChainIdBuf(chainId) + chainIdBufSz = chainIdBuf.length + if (chainIdBufSz > MAX_CHAIN_ID_BYTES) + throw new Error('ChainID provided is too large.') + // Signal to Lattice firmware that it needs to read the chainId from the tx.data buffer + txReqPayload.writeUInt8(HANDLE_LARGER_CHAIN_ID, off) + off++ + } else { + // For chainIDs <255, write it to the chainId u8 slot in the main tx buffer + chainIdBuf = ensureHexBuffer(chainId) + if (chainIdBuf.length !== 1) throw new Error('Error parsing chainID') + chainIdBuf.copy(txReqPayload, off) + off += chainIdBuf.length + } + // 2. Signer Path + //------------------ + const signerPathBuf = buildSignerPathBuf(signerPath, VAR_PATH_SZ) + signerPathBuf.copy(txReqPayload, off) + off += signerPathBuf.length + + // 3. ETH TX request data + //------------------ + if (nonceBytes.length > 4) throw new Error('Nonce too large') + nonceBytes.copy(txReqPayload, off + (4 - nonceBytes.length)) + off += 4 + if (gasPriceBytes.length > 8) throw new Error('Gas price too large') + gasPriceBytes.copy(txReqPayload, off + (8 - gasPriceBytes.length)) + off += 8 + if (gasLimitBytes.length > 4) throw new Error('Gas limit too large') + gasLimitBytes.copy(txReqPayload, off + (4 - gasLimitBytes.length)) + off += 4 + if (toBytes.length !== 20) throw new Error('Invalid `to` address') + toBytes.copy(txReqPayload, off) + off += 20 + if (valueBytes.length > 32) throw new Error('Value too large') + valueBytes.copy(txReqPayload, off + (32 - valueBytes.length)) + off += 32 + + // Extra Tx data comes before `data` in the struct + let PREHASH_UNSUPPORTED = false + if (fwConstants.allowedEthTxTypes) { + // Some types may not be supported by firmware, so we will need to prehash + if (PREHASH_FROM_ACCESS_LIST) { + PREHASH_UNSUPPORTED = true + } + txReqPayload.writeUInt8(PREHASH_UNSUPPORTED ? 1 : 0, off) + off += 1 + // EIP1559 & EIP2930 struct version + if (isEip1559) { + txReqPayload.writeUInt8(2, off) + off += 1 // Eip1559 type enum value + if (maxPriorityFeePerGasBytes.length > 8) + throw new Error('maxPriorityFeePerGasBytes too large') + maxPriorityFeePerGasBytes.copy( + txReqPayload, + off + (8 - maxPriorityFeePerGasBytes.length), + ) + off += 8 // Skip EIP1559 params + } else if (isEip2930) { + txReqPayload.writeUInt8(1, off) + off += 1 // Eip2930 type enum value + off += 8 // Skip EIP1559 params + } else { + off += 9 // Skip EIP1559 and EIP2930 params + } + } + + // Flow data into extraData requests, which will follow-up transaction requests, if supported/applicable + const extraDataPayloads = [] + let prehash = null + + // Create the buffer, prefix with chainId (if needed) and add data slice + const dataSz = dataBytes.length || 0 + const chainIdExtraSz = chainIdBufSz > 0 ? chainIdBufSz + 1 : 0 + const dataToCopy = Buffer.alloc(dataSz + chainIdExtraSz) + if (chainIdExtraSz > 0) { + dataToCopy.writeUInt8(chainIdBufSz, 0) + chainIdBuf.copy(dataToCopy, 1) + } + dataBytes.copy(dataToCopy, chainIdExtraSz) + + if (dataSz > MAX_BASE_DATA_SZ) { + // Determine sizes and run through sanity checks + const totalSz = dataSz + chainIdExtraSz + const maxSzAllowed = + MAX_BASE_DATA_SZ + extraDataMaxFrames * extraDataFrameSz + + if (prehashAllowed && totalSz > maxSzAllowed) { + // If this payload is too large to send, but the Lattice allows a prehashed message, do that + prehash = Buffer.from( + Hash.keccak256(get_rlp_encoded_preimage(rawTx, type)), + ) + } else { + if ( + !EXTRA_DATA_ALLOWED || + (EXTRA_DATA_ALLOWED && totalSz > maxSzAllowed) + ) + throw new Error( + `Data field too large (got ${dataBytes.length}; must be <=${maxSzAllowed - chainIdExtraSz} bytes)`, + ) + // Split overflow data into extraData frames + const frames = splitFrames( + dataToCopy.slice(MAX_BASE_DATA_SZ), + extraDataFrameSz, + ) + frames.forEach((frame) => { + const szLE = Buffer.alloc(4) + szLE.writeUInt32LE(frame.length, 0) + extraDataPayloads.push(Buffer.concat([szLE, frame])) + }) + } + } else if (PREHASH_UNSUPPORTED) { + // If something is unsupported in firmware but we want to allow such transactions, + // we prehash the message here. + prehash = Buffer.from( + Hash.keccak256(get_rlp_encoded_preimage(rawTx, type)), + ) + } + + // Write the data size (does *NOT* include the chainId buffer, if that exists) + txReqPayload.writeUInt16BE(dataBytes.length, off) + off += 2 + // Copy in the chainId buffer if needed + if (chainIdBufSz > 0) { + txReqPayload.writeUInt8(chainIdBufSz, off) + off++ + chainIdBuf.copy(txReqPayload, off) + off += chainIdBufSz + } + // Copy the first slice of the data itself. If this payload has been pre-hashed, include it + // in the `data` field. This will result in a different Lattice screen being drawn. + if (prehash) { + prehash.copy(txReqPayload, off) + off += MAX_BASE_DATA_SZ + } else { + dataBytes.slice(0, MAX_BASE_DATA_SZ).copy(txReqPayload, off) + off += MAX_BASE_DATA_SZ + } + return { + rawTx, + type, + payload: txReqPayload.slice(0, off), + extraDataPayloads, + schema: LatticeSignSchema.ethereum, // We will use eth transfer for all ETH txs for v1 + chainId, + useEIP155, + signerPath, + } + } catch (err) { + return { err: err.message } + } } // From ethereumjs-util function stripZeros(a) { - let first = a[0] - while (a.length > 0 && first.toString() === '0') { - a = a.slice(1) - first = a[0] - } - return a + let first = a[0] + while (a.length > 0 && first.toString() === '0') { + a = a.slice(1) + first = a[0] + } + return a } // Given a 64-byte signature [r,s] we need to figure out the v value // and attah the full signature to the end of the transaction payload const buildEthRawTx = (tx, sig, address) => { - // RLP-encode the data we sent to the lattice - const hash = Buffer.from( - Hash.keccak256(get_rlp_encoded_preimage(tx.rawTx, tx.type)), - ) - const newSig = addRecoveryParam(hash, sig, address, tx) - // Use the signature to generate a new raw transaction payload - // Strip the last 3 items and replace them with signature components - const newRawTx = tx.useEIP155 ? tx.rawTx.slice(0, -3) : tx.rawTx - newRawTx.push(newSig.v) - // Per `ethereumjs-tx`, RLP encoding should include signature components w/ stripped zeros - // See: https://github.com/ethereumjs/ethereumjs-tx/blob/master/src/transaction.ts#L187 - newRawTx.push(stripZeros(newSig.r)) - newRawTx.push(stripZeros(newSig.s)) - const rlpEncoded = Buffer.from(RLP.encode(newRawTx)) - const rlpEncodedWithSig = tx.type - ? Buffer.concat([Buffer.from([tx.type]), rlpEncoded]) - : rlpEncoded - - if ( - tx.type === TRANSACTION_TYPE.EIP7702_AUTH || - tx.type === TRANSACTION_TYPE.EIP7702_AUTH_LIST - ) { - // For EIP-7702 transactions, we return just the hex string - return rlpEncodedWithSig.toString('hex') - } - - return { rawTx: rlpEncodedWithSig.toString('hex'), sigWithV: newSig } + // RLP-encode the data we sent to the lattice + const hash = Buffer.from( + Hash.keccak256(get_rlp_encoded_preimage(tx.rawTx, tx.type)), + ) + const newSig = addRecoveryParam(hash, sig, address, tx) + // Use the signature to generate a new raw transaction payload + // Strip the last 3 items and replace them with signature components + const newRawTx = tx.useEIP155 ? tx.rawTx.slice(0, -3) : tx.rawTx + newRawTx.push(newSig.v) + // Per `ethereumjs-tx`, RLP encoding should include signature components w/ stripped zeros + // See: https://github.com/ethereumjs/ethereumjs-tx/blob/master/src/transaction.ts#L187 + newRawTx.push(stripZeros(newSig.r)) + newRawTx.push(stripZeros(newSig.s)) + const rlpEncoded = Buffer.from(RLP.encode(newRawTx)) + const rlpEncodedWithSig = tx.type + ? Buffer.concat([Buffer.from([tx.type]), rlpEncoded]) + : rlpEncoded + + if ( + tx.type === TRANSACTION_TYPE.EIP7702_AUTH || + tx.type === TRANSACTION_TYPE.EIP7702_AUTH_LIST + ) { + // For EIP-7702 transactions, we return just the hex string + return rlpEncodedWithSig.toString('hex') + } + + return { rawTx: rlpEncodedWithSig.toString('hex'), sigWithV: newSig } } // Attach a recovery parameter to a signature by brute-forcing ECRecover export function addRecoveryParam(hashBuf, sig, address, txData = {}) { - try { - // Rebuild the keccak256 hash here so we can `ecrecover` - const hash = new Uint8Array(hashBuf) - const expectedAddrBuf = Buffer.isBuffer(address) - ? address - : ensureHexBuffer(address, false) - if (expectedAddrBuf.length !== 20) - throw new Error('Invalid signer address provided.') - let v = 0 - // Fix signature componenet lengths to 32 bytes each - const r = fixLen(sig.r, 32) - sig.r = r - const s = fixLen(sig.s, 32) - sig.s = s - // Calculate the recovery param - const rs = new Uint8Array(Buffer.concat([r, s])) - let pubkey = ecdsaRecover(rs, v, hash, false).slice(1) - const expectedAddrHex = expectedAddrBuf.toString('hex') - const recoveredAddrs: string[] = [] - // If the first `v` value is a match, return the sig! - let recovered = pubToAddrStr(pubkey) - recoveredAddrs.push(recovered) - if (recovered === expectedAddrHex) { - sig.v = getRecoveryParam(v, txData) - return sig - } - // Otherwise, try the other `v` value - v = 1 - pubkey = ecdsaRecover(rs, v, hash, false).slice(1) - recovered = pubToAddrStr(pubkey) - recoveredAddrs.push(recovered) - if (recovered === expectedAddrHex) { - sig.v = getRecoveryParam(v, txData) - return sig - } else { - // If neither is a match, we should return an error - throw new Error( - `Invalid Ethereum signature returned. expected=${expectedAddrHex}, recovered=${recoveredAddrs.join(',')}`, - ) - } - } catch (err) { - if (err instanceof Error) throw err - throw new Error(String(err)) - } + try { + // Rebuild the keccak256 hash here so we can `ecrecover` + const hash = new Uint8Array(hashBuf) + const expectedAddrBuf = Buffer.isBuffer(address) + ? address + : ensureHexBuffer(address, false) + if (expectedAddrBuf.length !== 20) + throw new Error('Invalid signer address provided.') + let v = 0 + // Fix signature componenet lengths to 32 bytes each + const r = fixLen(sig.r, 32) + sig.r = r + const s = fixLen(sig.s, 32) + sig.s = s + // Calculate the recovery param + const rs = new Uint8Array(Buffer.concat([r, s])) + let pubkey = ecdsaRecover(rs, v, hash, false).slice(1) + const expectedAddrHex = expectedAddrBuf.toString('hex') + const recoveredAddrs: string[] = [] + // If the first `v` value is a match, return the sig! + let recovered = pubToAddrStr(pubkey) + recoveredAddrs.push(recovered) + if (recovered === expectedAddrHex) { + sig.v = getRecoveryParam(v, txData) + return sig + } + // Otherwise, try the other `v` value + v = 1 + pubkey = ecdsaRecover(rs, v, hash, false).slice(1) + recovered = pubToAddrStr(pubkey) + recoveredAddrs.push(recovered) + if (recovered === expectedAddrHex) { + sig.v = getRecoveryParam(v, txData) + return sig + } else { + // If neither is a match, we should return an error + throw new Error( + `Invalid Ethereum signature returned. expected=${expectedAddrHex}, recovered=${recoveredAddrs.join(',')}`, + ) + } + } catch (err) { + if (err instanceof Error) throw err + throw new Error(String(err)) + } } /** @@ -632,73 +632,73 @@ export function addRecoveryParam(hashBuf, sig, address, txData = {}) { * Handles Buffer v value conversion and yParity vs v for different transaction types. */ export function normalizeLatticeSignature( - latticeResult: any, - originalTx: TransactionSerializable, + latticeResult: any, + originalTx: TransactionSerializable, ) { - // Convert Buffer v value to number - let vValue: number - if (Buffer.isBuffer(latticeResult.sig.v)) { - // Read entire buffer as big-endian integer - // Single byte: = 0x26 = 38 - // Multi-byte: = 0x0135 = 309 (Polygon chainId 137 with EIP-155) - const bufferLength = latticeResult.sig.v.length - if (bufferLength === 1) { - vValue = latticeResult.sig.v.readUInt8(0) - } else if (bufferLength === 2) { - vValue = latticeResult.sig.v.readUInt16BE(0) - } else if (bufferLength <= 4) { - vValue = latticeResult.sig.v.readUInt32BE(Math.max(0, 4 - bufferLength)) - } else { - // For very large buffers, read as hex and convert - vValue = Number.parseInt(latticeResult.sig.v.toString('hex'), 16) - } - } else if (typeof latticeResult.sig.v === 'number') { - vValue = latticeResult.sig.v - } else if (typeof latticeResult.sig.v === 'string') { - vValue = hexToNumber(latticeResult.sig.v as Hex) - } else { - vValue = Number(latticeResult.sig.v) - } - - // For typed transactions (non-legacy), viem expects yParity instead of v - if (originalTx.type !== 'legacy') { - // Convert v to yParity (v is either 27/28 or 0/1) - const yParity = vValue >= 27 ? vValue - 27 : vValue - return { - ...originalTx, - r: latticeResult.sig.r as Hex, - s: latticeResult.sig.s as Hex, - yParity, - } - } else { - // Legacy transactions use v directly as BigInt - const result = { - ...originalTx, - r: latticeResult.sig.r as Hex, - s: latticeResult.sig.s as Hex, - v: BigInt(vValue), - } - - // For legacy transactions, remove the type field to ensure Viem treats it as legacy - result.type = undefined - - // Also remove any typed transaction fields that might confuse viem - result.maxFeePerGas = undefined - result.maxPriorityFeePerGas = undefined - result.accessList = undefined - result.authorizationList = undefined - - return result - } + // Convert Buffer v value to number + let vValue: number + if (Buffer.isBuffer(latticeResult.sig.v)) { + // Read entire buffer as big-endian integer + // Single byte: = 0x26 = 38 + // Multi-byte: = 0x0135 = 309 (Polygon chainId 137 with EIP-155) + const bufferLength = latticeResult.sig.v.length + if (bufferLength === 1) { + vValue = latticeResult.sig.v.readUInt8(0) + } else if (bufferLength === 2) { + vValue = latticeResult.sig.v.readUInt16BE(0) + } else if (bufferLength <= 4) { + vValue = latticeResult.sig.v.readUInt32BE(Math.max(0, 4 - bufferLength)) + } else { + // For very large buffers, read as hex and convert + vValue = Number.parseInt(latticeResult.sig.v.toString('hex'), 16) + } + } else if (typeof latticeResult.sig.v === 'number') { + vValue = latticeResult.sig.v + } else if (typeof latticeResult.sig.v === 'string') { + vValue = hexToNumber(latticeResult.sig.v as Hex) + } else { + vValue = Number(latticeResult.sig.v) + } + + // For typed transactions (non-legacy), viem expects yParity instead of v + if (originalTx.type !== 'legacy') { + // Convert v to yParity (v is either 27/28 or 0/1) + const yParity = vValue >= 27 ? vValue - 27 : vValue + return { + ...originalTx, + r: latticeResult.sig.r as Hex, + s: latticeResult.sig.s as Hex, + yParity, + } + } else { + // Legacy transactions use v directly as BigInt + const result = { + ...originalTx, + r: latticeResult.sig.r as Hex, + s: latticeResult.sig.s as Hex, + v: BigInt(vValue), + } + + // For legacy transactions, remove the type field to ensure Viem treats it as legacy + result.type = undefined + + // Also remove any typed transaction fields that might confuse viem + result.maxFeePerGas = undefined + result.maxPriorityFeePerGas = undefined + result.accessList = undefined + result.authorizationList = undefined + + return result + } } // Convert an RLP-serialized transaction (plus signature) into a transaction hash const hashTransaction = (serializedTx) => - Hash.keccak256(Buffer.from(serializedTx, 'hex')) + Hash.keccak256(Buffer.from(serializedTx, 'hex')) // Returns address string given public key buffer function pubToAddrStr(pub) { - return Buffer.from(Hash.keccak256(pub)).slice(-20).toString('hex') + return Buffer.from(Hash.keccak256(pub)).slice(-20).toString('hex') } // Convert a 0/1 `v` into a recovery param: @@ -706,459 +706,459 @@ function pubToAddrStr(pub) { // * For EIP155 transactions, return `(CHAIN_ID*2) + 35 + v` // Uses the consolidated convertRecoveryToV function from util.ts function getRecoveryParam(v, txData: any = {}) { - const result = convertRecoveryToV(v, txData) - - // convertRecoveryToV returns Buffer for typed transactions, BN for legacy - // Always return Buffer to maintain compatibility with existing code - if (Buffer.isBuffer(result)) { - return result - } else { - // Convert BN result to hex buffer - return ensureHexBuffer(`0x${result.toString(16)}`) - } + const result = convertRecoveryToV(v, txData) + + // convertRecoveryToV returns Buffer for typed transactions, BN for legacy + // Always return Buffer to maintain compatibility with existing code + if (Buffer.isBuffer(result)) { + return result + } else { + // Convert BN result to hex buffer + return ensureHexBuffer(`0x${result.toString(16)}`) + } } const chainIds = { - mainnet: 1, - roptsten: 3, - rinkeby: 4, - kovan: 42, - goerli: 5, + mainnet: 1, + roptsten: 3, + rinkeby: 4, + kovan: 42, + goerli: 5, } // Get a buffer containing the chainId value. // Returns a 1, 2, 4, or 8 byte buffer with the chainId encoded in big endian function getChainIdBuf(chainId) { - let b: Buffer - // If our chainID is a hex string, we can convert it to a hex - // buffer directly - if (true === isValidChainIdHexNumStr(chainId)) b = ensureHexBuffer(chainId) - // If our chainID is a base-10 number, parse with bignumber.js and convert to hex buffer - else b = ensureHexBuffer(`0x${new BN(chainId).toString(16)}`) - // Make sure the buffer is an allowed size - if (b.length > 8) throw new Error('ChainID provided is too large.') - // If this matches a u16, u32, or u64 size, return it now - if (b.length <= 2 || b.length === 4 || b.length === 8) return b - // For other size buffers, we need to pack into u32 or u64 before returning; - let buf: Buffer - if (b.length === 3) { - buf = Buffer.alloc(4) - buf.writeUInt32BE(chainId) - } else if (b.length <= 8) { - buf = Buffer.alloc(8) - b.copy(buf, 8 - b.length) - } - return buf + let b: Buffer + // If our chainID is a hex string, we can convert it to a hex + // buffer directly + if (true === isValidChainIdHexNumStr(chainId)) b = ensureHexBuffer(chainId) + // If our chainID is a base-10 number, parse with bignumber.js and convert to hex buffer + else b = ensureHexBuffer(`0x${new BN(chainId).toString(16)}`) + // Make sure the buffer is an allowed size + if (b.length > 8) throw new Error('ChainID provided is too large.') + // If this matches a u16, u32, or u64 size, return it now + if (b.length <= 2 || b.length === 4 || b.length === 8) return b + // For other size buffers, we need to pack into u32 or u64 before returning; + let buf: Buffer + if (b.length === 3) { + buf = Buffer.alloc(4) + buf.writeUInt32BE(chainId) + } else if (b.length <= 8) { + buf = Buffer.alloc(8) + b.copy(buf, 8 - b.length) + } + return buf } // Determine if the chain uses EIP155 by default, based on the chainID function chainUsesEIP155(chainID) { - switch (chainID) { - case 3: // ropsten - case 4: // rinkeby - return false - default: - // all others should use eip155 - return true - } + switch (chainID) { + case 3: // ropsten + case 4: // rinkeby + return false + default: + // all others should use eip155 + return true + } } // Determine if a valid number was passed in as a hex string function isValidChainIdHexNumStr(s) { - if (typeof s !== 'string') return false - if (s.slice(0, 2) !== '0x') return false - try { - const b = new BN(s, 16) - return b.isNaN() === false - } catch (err) { - console.error('Invalid chain ID hex string:', err) - return false - } + if (typeof s !== 'string') return false + if (s.slice(0, 2) !== '0x') return false + try { + const b = new BN(s, 16) + return b.isNaN() === false + } catch (err) { + console.error('Invalid chain ID hex string:', err) + return false + } } // If this is a nubmer that fits in one byte, we don't need to add it // to the `data` buffer of the main transaction. // Note the one edge case: we still need to use the `data` field for chainID=255. function useChainIdBuffer(id) { - const buf = getChainIdBuf(id) - if (buf.length === 1) return buf.readUInt8(0) === 255 - return true + const buf = getChainIdBuf(id) + if (buf.length === 1) return buf.readUInt8(0) === 255 + return true } function buildPersonalSignRequest(req, input) { - const MAX_BASE_MSG_SZ = input.fwConstants.ethMaxMsgSz - const VAR_PATH_SZ = input.fwConstants.varAddrPathSzAllowed - const L = 24 + MAX_BASE_MSG_SZ + 4 - let off = 0 - req.payload = Buffer.alloc(L) - req.payload.writeUInt8(ethMsgProtocol.SIGN_PERSONAL, 0) - off += 1 - // Write the signer path into the buffer - const signerPathBuf = buildSignerPathBuf(input.signerPath, VAR_PATH_SZ) - signerPathBuf.copy(req.payload, off) - off += signerPathBuf.length - // Write the payload buffer. The payload can come in either as a buffer or as a string - let payload = input.payload - // Determine if this is a hex string - let displayHex = false - if (typeof input.payload === 'string') { - if (input.payload.slice(0, 2) === '0x') { - payload = ensureHexBuffer(input.payload) - displayHex = - false === - ASCII_REGEX.test(Buffer.from(input.payload.slice(2), 'hex').toString()) - } else { - if (false === isAsciiStr(input.payload)) - throw new Error( - 'Currently, the Lattice can only display ASCII strings.', - ) - payload = Buffer.from(input.payload) - } - } else if (typeof input.displayHex === 'boolean') { - // If this is a buffer and the user has specified whether or not this - // is a hex buffer with the optional argument, write that - displayHex = input.displayHex - } else { - // Otherwise, determine if this buffer is an ASCII string. If it is, set `displayHex` accordingly. - // NOTE: THIS MEANS THAT NON-ASCII STRINGS WILL DISPLAY AS HEX SINCE WE CANNOT KNOW IF THE REQUESTER - // EXPECTED NON-ASCII CHARACTERS TO DISPLAY IN A STRING - // TODO: Develop a more elegant solution for this - if (!input.payload.toString) throw new Error('Unsupported input data type') - displayHex = false === ASCII_REGEX.test(input.payload.toString()) - } - const fwConst = input.fwConstants - let maxSzAllowed = - MAX_BASE_MSG_SZ + fwConst.extraDataMaxFrames * fwConst.extraDataFrameSz - if (fwConst.personalSignHeaderSz) { - // Account for the personal_sign header string - maxSzAllowed -= fwConst.personalSignHeaderSz - } - if (fwConst.ethMsgPreHashAllowed && payload.length > maxSzAllowed) { - // If this message will not fit and pre-hashing is allowed, do that - req.payload.writeUInt8(displayHex, off) - off += 1 - req.payload.writeUInt16LE(payload.length, off) - off += 2 - const prehash = Buffer.from( - Hash.keccak256( - Buffer.concat([get_personal_sign_prefix(payload.length), payload]), - ), - ) - prehash.copy(req.payload, off) - req.prehash = prehash - } else { - // Otherwise we can fit the payload. - // Flow data into extraData requests, which will follow-up transaction requests, if supported/applicable - const extraDataPayloads = getExtraData(payload, input) - // Write the payload and metadata into our buffer - req.extraDataPayloads = extraDataPayloads - req.msg = payload - req.payload.writeUInt8(displayHex, off) - off += 1 - req.payload.writeUInt16LE(payload.length, off) - off += 2 - payload.copy(req.payload, off) - } - return req + const MAX_BASE_MSG_SZ = input.fwConstants.ethMaxMsgSz + const VAR_PATH_SZ = input.fwConstants.varAddrPathSzAllowed + const L = 24 + MAX_BASE_MSG_SZ + 4 + let off = 0 + req.payload = Buffer.alloc(L) + req.payload.writeUInt8(ethMsgProtocol.SIGN_PERSONAL, 0) + off += 1 + // Write the signer path into the buffer + const signerPathBuf = buildSignerPathBuf(input.signerPath, VAR_PATH_SZ) + signerPathBuf.copy(req.payload, off) + off += signerPathBuf.length + // Write the payload buffer. The payload can come in either as a buffer or as a string + let payload = input.payload + // Determine if this is a hex string + let displayHex = false + if (typeof input.payload === 'string') { + if (input.payload.slice(0, 2) === '0x') { + payload = ensureHexBuffer(input.payload) + displayHex = + false === + ASCII_REGEX.test(Buffer.from(input.payload.slice(2), 'hex').toString()) + } else { + if (false === isAsciiStr(input.payload)) + throw new Error( + 'Currently, the Lattice can only display ASCII strings.', + ) + payload = Buffer.from(input.payload) + } + } else if (typeof input.displayHex === 'boolean') { + // If this is a buffer and the user has specified whether or not this + // is a hex buffer with the optional argument, write that + displayHex = input.displayHex + } else { + // Otherwise, determine if this buffer is an ASCII string. If it is, set `displayHex` accordingly. + // NOTE: THIS MEANS THAT NON-ASCII STRINGS WILL DISPLAY AS HEX SINCE WE CANNOT KNOW IF THE REQUESTER + // EXPECTED NON-ASCII CHARACTERS TO DISPLAY IN A STRING + // TODO: Develop a more elegant solution for this + if (!input.payload.toString) throw new Error('Unsupported input data type') + displayHex = false === ASCII_REGEX.test(input.payload.toString()) + } + const fwConst = input.fwConstants + let maxSzAllowed = + MAX_BASE_MSG_SZ + fwConst.extraDataMaxFrames * fwConst.extraDataFrameSz + if (fwConst.personalSignHeaderSz) { + // Account for the personal_sign header string + maxSzAllowed -= fwConst.personalSignHeaderSz + } + if (fwConst.ethMsgPreHashAllowed && payload.length > maxSzAllowed) { + // If this message will not fit and pre-hashing is allowed, do that + req.payload.writeUInt8(displayHex, off) + off += 1 + req.payload.writeUInt16LE(payload.length, off) + off += 2 + const prehash = Buffer.from( + Hash.keccak256( + Buffer.concat([get_personal_sign_prefix(payload.length), payload]), + ), + ) + prehash.copy(req.payload, off) + req.prehash = prehash + } else { + // Otherwise we can fit the payload. + // Flow data into extraData requests, which will follow-up transaction requests, if supported/applicable + const extraDataPayloads = getExtraData(payload, input) + // Write the payload and metadata into our buffer + req.extraDataPayloads = extraDataPayloads + req.msg = payload + req.payload.writeUInt8(displayHex, off) + off += 1 + req.payload.writeUInt16LE(payload.length, off) + off += 2 + payload.copy(req.payload, off) + } + return req } function buildEIP712Request(req, input) { - const { ethMaxMsgSz, varAddrPathSzAllowed, eip712MaxTypeParams } = - input.fwConstants - const { TYPED_DATA } = ethMsgProtocol - const L = 24 + ethMaxMsgSz + 4 - let off = 0 - req.payload = Buffer.alloc(L) - req.payload.writeUInt8(TYPED_DATA.enumIdx, 0) - off += 1 - // Write the signer path - const signerPathBuf = buildSignerPathBuf( - input.signerPath, - varAddrPathSzAllowed, - ) - signerPathBuf.copy(req.payload, off) - off += signerPathBuf.length - // Parse/clean the EIP712 payload, serialize with CBOR, and write to the payload - const data = cloneTypedDataPayload(input.payload) - if (!data.primaryType || !data.types[data.primaryType]) - throw new Error( - 'primaryType must be specified and the type must be included.', - ) - if (!data.message || !data.domain) - throw new Error('message and domain must be specified.') - if (0 > Object.keys(data.types).indexOf('EIP712Domain')) - throw new Error('EIP712Domain type must be defined.') - // Parse the payload to ensure we have valid EIP712 data types and that - // they are encoded such that Lattice firmware can parse them. - // We need two different encodings: one to send to the Lattice in a format that plays - // nicely with our firmware CBOR decoder. The other is formatted to be consumable by - // our EIP712 validation module. - // IMPORTANT: Create a new object for the validation payload instead of modifying input.payload - // in place, so that validation uses the correctly formatted data - const validationPayload = cloneTypedDataPayload(data) - validationPayload.message = parseEIP712Msg( - cloneTypedDataPayload(data.message), - cloneTypedDataPayload(data.primaryType), - cloneTypedDataPayload(data.types), - true, - ) - validationPayload.domain = parseEIP712Msg( - cloneTypedDataPayload(data.domain), - 'EIP712Domain', - cloneTypedDataPayload(data.types), - true, - ) - // Store the validation payload separately without modifying input.payload - req.validationPayload = validationPayload - - data.domain = parseEIP712Msg(data.domain, 'EIP712Domain', data.types, false) - data.message = parseEIP712Msg( - data.message, - data.primaryType, - data.types, - false, - ) - // Now build the message to be sent to the Lattice - const payload = Buffer.from(cbor.encode(data)) - const fwConst = input.fwConstants - const maxSzAllowed = - ethMaxMsgSz + fwConst.extraDataMaxFrames * fwConst.extraDataFrameSz - // Determine if we need to prehash - let shouldPrehash = payload.length > maxSzAllowed - Object.keys(data.types).forEach((k) => { - if (data.types[k].length > eip712MaxTypeParams) { - shouldPrehash = true - } - }) - if (fwConst.ethMsgPreHashAllowed && shouldPrehash) { - // If this payload is too large to send, but the Lattice allows a prehashed message, do that - req.payload.writeUInt16LE(payload.length, off) - off += 2 - const prehash = TypedDataUtils.eip712Hash( - req.validationPayload, - SignTypedDataVersion.V4, - ) - const prehashBuf = Buffer.from(prehash) - prehashBuf.copy(req.payload, off) - req.prehash = prehash - } else { - const extraDataPayloads = getExtraData(payload, input) - req.extraDataPayloads = extraDataPayloads - req.payload.writeUInt16LE(payload.length, off) - off += 2 - payload.copy(req.payload, off) - off += payload.length - // Slice out the part of the buffer that we didn't use. - req.payload = req.payload.slice(0, off) - } - return req + const { ethMaxMsgSz, varAddrPathSzAllowed, eip712MaxTypeParams } = + input.fwConstants + const { TYPED_DATA } = ethMsgProtocol + const L = 24 + ethMaxMsgSz + 4 + let off = 0 + req.payload = Buffer.alloc(L) + req.payload.writeUInt8(TYPED_DATA.enumIdx, 0) + off += 1 + // Write the signer path + const signerPathBuf = buildSignerPathBuf( + input.signerPath, + varAddrPathSzAllowed, + ) + signerPathBuf.copy(req.payload, off) + off += signerPathBuf.length + // Parse/clean the EIP712 payload, serialize with CBOR, and write to the payload + const data = cloneTypedDataPayload(input.payload) + if (!data.primaryType || !data.types[data.primaryType]) + throw new Error( + 'primaryType must be specified and the type must be included.', + ) + if (!data.message || !data.domain) + throw new Error('message and domain must be specified.') + if (0 > Object.keys(data.types).indexOf('EIP712Domain')) + throw new Error('EIP712Domain type must be defined.') + // Parse the payload to ensure we have valid EIP712 data types and that + // they are encoded such that Lattice firmware can parse them. + // We need two different encodings: one to send to the Lattice in a format that plays + // nicely with our firmware CBOR decoder. The other is formatted to be consumable by + // our EIP712 validation module. + // IMPORTANT: Create a new object for the validation payload instead of modifying input.payload + // in place, so that validation uses the correctly formatted data + const validationPayload = cloneTypedDataPayload(data) + validationPayload.message = parseEIP712Msg( + cloneTypedDataPayload(data.message), + cloneTypedDataPayload(data.primaryType), + cloneTypedDataPayload(data.types), + true, + ) + validationPayload.domain = parseEIP712Msg( + cloneTypedDataPayload(data.domain), + 'EIP712Domain', + cloneTypedDataPayload(data.types), + true, + ) + // Store the validation payload separately without modifying input.payload + req.validationPayload = validationPayload + + data.domain = parseEIP712Msg(data.domain, 'EIP712Domain', data.types, false) + data.message = parseEIP712Msg( + data.message, + data.primaryType, + data.types, + false, + ) + // Now build the message to be sent to the Lattice + const payload = Buffer.from(cbor.encode(data)) + const fwConst = input.fwConstants + const maxSzAllowed = + ethMaxMsgSz + fwConst.extraDataMaxFrames * fwConst.extraDataFrameSz + // Determine if we need to prehash + let shouldPrehash = payload.length > maxSzAllowed + Object.keys(data.types).forEach((k) => { + if (data.types[k].length > eip712MaxTypeParams) { + shouldPrehash = true + } + }) + if (fwConst.ethMsgPreHashAllowed && shouldPrehash) { + // If this payload is too large to send, but the Lattice allows a prehashed message, do that + req.payload.writeUInt16LE(payload.length, off) + off += 2 + const prehash = TypedDataUtils.eip712Hash( + req.validationPayload, + SignTypedDataVersion.V4, + ) + const prehashBuf = Buffer.from(prehash) + prehashBuf.copy(req.payload, off) + req.prehash = prehash + } else { + const extraDataPayloads = getExtraData(payload, input) + req.extraDataPayloads = extraDataPayloads + req.payload.writeUInt16LE(payload.length, off) + off += 2 + payload.copy(req.payload, off) + off += payload.length + // Slice out the part of the buffer that we didn't use. + req.payload = req.payload.slice(0, off) + } + return req } function getExtraData(payload, input) { - const { ethMaxMsgSz, extraDataFrameSz, extraDataMaxFrames } = - input.fwConstants - const MAX_BASE_MSG_SZ = ethMaxMsgSz - const EXTRA_DATA_ALLOWED = extraDataFrameSz > 0 && extraDataMaxFrames > 0 - const extraDataPayloads = [] - if (payload.length > MAX_BASE_MSG_SZ) { - // Determine sizes and run through sanity checks - const maxSzAllowed = MAX_BASE_MSG_SZ + extraDataMaxFrames * extraDataFrameSz - if (!EXTRA_DATA_ALLOWED) - throw new Error( - `Your message is ${payload.length} bytes, but can only be a maximum of ${MAX_BASE_MSG_SZ}`, - ) - else if (EXTRA_DATA_ALLOWED && payload.length > maxSzAllowed) - throw new Error( - `Your message is ${payload.length} bytes, but can only be a maximum of ${maxSzAllowed}`, - ) - // Split overflow data into extraData frames - const frames = splitFrames(payload.slice(MAX_BASE_MSG_SZ), extraDataFrameSz) - frames.forEach((frame) => { - const szLE = Buffer.alloc(4) - szLE.writeUInt32LE(frame.length, 0) - extraDataPayloads.push(Buffer.concat([szLE, frame])) - }) - } - return extraDataPayloads + const { ethMaxMsgSz, extraDataFrameSz, extraDataMaxFrames } = + input.fwConstants + const MAX_BASE_MSG_SZ = ethMaxMsgSz + const EXTRA_DATA_ALLOWED = extraDataFrameSz > 0 && extraDataMaxFrames > 0 + const extraDataPayloads = [] + if (payload.length > MAX_BASE_MSG_SZ) { + // Determine sizes and run through sanity checks + const maxSzAllowed = MAX_BASE_MSG_SZ + extraDataMaxFrames * extraDataFrameSz + if (!EXTRA_DATA_ALLOWED) + throw new Error( + `Your message is ${payload.length} bytes, but can only be a maximum of ${MAX_BASE_MSG_SZ}`, + ) + else if (EXTRA_DATA_ALLOWED && payload.length > maxSzAllowed) + throw new Error( + `Your message is ${payload.length} bytes, but can only be a maximum of ${maxSzAllowed}`, + ) + // Split overflow data into extraData frames + const frames = splitFrames(payload.slice(MAX_BASE_MSG_SZ), extraDataFrameSz) + frames.forEach((frame) => { + const szLE = Buffer.alloc(4) + szLE.writeUInt32LE(frame.length, 0) + extraDataPayloads.push(Buffer.concat([szLE, frame])) + }) + } + return extraDataPayloads } function parseEIP712Msg(msg, typeName, types, forJSParser = false) { - const type = types[typeName] - type.forEach((item) => { - const isArrayType = item.type.indexOf('[') > -1 - const singularType = isArrayType - ? item.type.slice(0, item.type.indexOf('[')) - : item.type - const isCustomType = Object.keys(types).indexOf(singularType) > -1 - if (isCustomType && Array.isArray(msg)) { - // For custom types we need to jump into the `msg` using the key (name of type) and - // parse that entire sub-struct as if it were a message. - // We will recurse into sub-structs until we reach a level where every item is an - // elementary (i.e. non-custom) type. - // For arrays, we need to loop through each message item. - for (let i = 0; i < msg.length; i++) { - msg[i][item.name] = parseEIP712Msg( - msg[i][item.name], - singularType, - types, - forJSParser, - ) - } - } else if (isCustomType) { - // Not an array means we can jump directly into the sub-struct to convert - msg[item.name] = parseEIP712Msg( - msg[item.name], - singularType, - types, - forJSParser, - ) - } else if (Array.isArray(msg)) { - // If we have an array for this particular type and the type we are parsing - // is *not* a custom type, loop through the array elements and convert the types. - for (let i = 0; i < msg.length; i++) { - if (isArrayType) { - // If this type is itself an array, loop through those elements and parse individually. - // This code is not reachable for custom types so we assume these are arrays of - // elementary types. - for (let j = 0; j < msg[i][item.name].length; j++) { - msg[i][item.name][j] = parseEIP712Item( - msg[i][item.name][j], - singularType, - forJSParser, - ) - } - } else { - // Non-arrays parse + replace one value for the elementary type - msg[i][item.name] = parseEIP712Item( - msg[i][item.name], - singularType, - forJSParser, - ) - } - } - } else if (isArrayType) { - // If we have an elementary array type and a non-array message level, - //loop through the array and parse + replace each item individually. - for (let i = 0; i < msg[item.name].length; i++) { - msg[item.name][i] = parseEIP712Item( - msg[item.name][i], - singularType, - forJSParser, - ) - } - } else { - // If this is a singular elementary type, simply parse + replace. - msg[item.name] = parseEIP712Item( - msg[item.name], - singularType, - forJSParser, - ) - } - }) - - return msg + const type = types[typeName] + type.forEach((item) => { + const isArrayType = item.type.indexOf('[') > -1 + const singularType = isArrayType + ? item.type.slice(0, item.type.indexOf('[')) + : item.type + const isCustomType = Object.keys(types).indexOf(singularType) > -1 + if (isCustomType && Array.isArray(msg)) { + // For custom types we need to jump into the `msg` using the key (name of type) and + // parse that entire sub-struct as if it were a message. + // We will recurse into sub-structs until we reach a level where every item is an + // elementary (i.e. non-custom) type. + // For arrays, we need to loop through each message item. + for (let i = 0; i < msg.length; i++) { + msg[i][item.name] = parseEIP712Msg( + msg[i][item.name], + singularType, + types, + forJSParser, + ) + } + } else if (isCustomType) { + // Not an array means we can jump directly into the sub-struct to convert + msg[item.name] = parseEIP712Msg( + msg[item.name], + singularType, + types, + forJSParser, + ) + } else if (Array.isArray(msg)) { + // If we have an array for this particular type and the type we are parsing + // is *not* a custom type, loop through the array elements and convert the types. + for (let i = 0; i < msg.length; i++) { + if (isArrayType) { + // If this type is itself an array, loop through those elements and parse individually. + // This code is not reachable for custom types so we assume these are arrays of + // elementary types. + for (let j = 0; j < msg[i][item.name].length; j++) { + msg[i][item.name][j] = parseEIP712Item( + msg[i][item.name][j], + singularType, + forJSParser, + ) + } + } else { + // Non-arrays parse + replace one value for the elementary type + msg[i][item.name] = parseEIP712Item( + msg[i][item.name], + singularType, + forJSParser, + ) + } + } + } else if (isArrayType) { + // If we have an elementary array type and a non-array message level, + //loop through the array and parse + replace each item individually. + for (let i = 0; i < msg[item.name].length; i++) { + msg[item.name][i] = parseEIP712Item( + msg[item.name][i], + singularType, + forJSParser, + ) + } + } else { + // If this is a singular elementary type, simply parse + replace. + msg[item.name] = parseEIP712Item( + msg[item.name], + singularType, + forJSParser, + ) + } + }) + + return msg } function parseEIP712Item(data, type, forJSParser = false) { - if (type === 'bytes') { - // Variable sized bytes need to be buffer type - data = ensureHexBuffer(data) - if (forJSParser) { - // For EIP712 encoding module it's easier to encode hex strings - data = `0x${data.toString('hex')}` - } - } else if (type.slice(0, 5) === 'bytes') { - // Fixed sizes bytes need to be buffer type. We also add some sanity checks. - const nBytes = Number.parseInt(type.slice(5)) - data = ensureHexBuffer(data) - // Edge case to handle empty bytesN values - if (data.length === 0) { - data = Buffer.alloc(nBytes) - } - if (data.length !== nBytes) - throw new Error(`Expected ${type} type, but got ${data.length} bytes`) - if (forJSParser) { - // For EIP712 encoding module it's easier to encode hex strings - data = `0x${data.toString('hex')}` - } - } else if (type === 'address') { - // Address must be a 20 byte buffer - data = ensureHexBuffer(data) - // Edge case to handle the 0-address - if (data.length === 0) { - data = Buffer.alloc(20) - } - if (data.length !== 20) - throw new Error( - `Address type must be 20 bytes, but got ${data.length} bytes`, - ) - // For EIP712 encoding module it's easier to encode hex strings - if (forJSParser) { - data = `0x${data.toString('hex')}` - } - } else if ( - ethMsgProtocol.TYPED_DATA.typeCodes[type] && - type.indexOf('uint') === -1 && - type.indexOf('int') > -1 - ) { - // Handle signed integers using bignumber.js directly - if (forJSParser) { - // For EIP712 encoding in this module we need hex strings for signed ints too - const bn = new BN(data) - // Preserve full precision by returning the decimal string representation - data = bn.toFixed() - } else { - // `bignumber.js` is needed for `cbor` encoding, which gets sent to the Lattice and plays - // nicely with its firmware cbor lib. - // NOTE: If we instantiate a `bignumber.js` object, it will not match what `borc` creates - // when run inside of the browser (i.e. MetaMask). Thus we introduce this hack to make sure - // we are creating a compatible type. - // TODO: Find another cbor lib that is compataible with the firmware's lib in a browser - // context. This is surprisingly difficult - I tried several libs and only cbor/borc have - // worked (borc is a supposedly "browser compatible" version of cbor) - data = new BN(data) - } - } else if ( - ethMsgProtocol.TYPED_DATA.typeCodes[type] && - (type.indexOf('uint') > -1 || type.indexOf('int') > -1) - ) { - // For uints, convert to a buffer and do some sanity checking. - // Note that we could probably just use bignumber.js directly as we do with - // signed ints, but this code is battle tested and we don't want to change it. - let b = ensureHexBuffer(data) - // Edge case to handle 0-value bignums - if (b.length === 0) { - b = Buffer.from('00', 'hex') - } - // Uint256s should be encoded as bignums. - if (forJSParser) { - // For EIP712 encoding in this module we need hex strings to represent the numbers - data = `0x${b.toString('hex')}` - } else { - // Load into bignumber.js used by cbor lib - data = new BN(b.toString('hex'), 16) - } - } else if (type === 'bool') { - // Booleans need to be cast to a u8 - data = data === true ? 1 : 0 - } - // Other types don't need to be modified - return data + if (type === 'bytes') { + // Variable sized bytes need to be buffer type + data = ensureHexBuffer(data) + if (forJSParser) { + // For EIP712 encoding module it's easier to encode hex strings + data = `0x${data.toString('hex')}` + } + } else if (type.slice(0, 5) === 'bytes') { + // Fixed sizes bytes need to be buffer type. We also add some sanity checks. + const nBytes = Number.parseInt(type.slice(5)) + data = ensureHexBuffer(data) + // Edge case to handle empty bytesN values + if (data.length === 0) { + data = Buffer.alloc(nBytes) + } + if (data.length !== nBytes) + throw new Error(`Expected ${type} type, but got ${data.length} bytes`) + if (forJSParser) { + // For EIP712 encoding module it's easier to encode hex strings + data = `0x${data.toString('hex')}` + } + } else if (type === 'address') { + // Address must be a 20 byte buffer + data = ensureHexBuffer(data) + // Edge case to handle the 0-address + if (data.length === 0) { + data = Buffer.alloc(20) + } + if (data.length !== 20) + throw new Error( + `Address type must be 20 bytes, but got ${data.length} bytes`, + ) + // For EIP712 encoding module it's easier to encode hex strings + if (forJSParser) { + data = `0x${data.toString('hex')}` + } + } else if ( + ethMsgProtocol.TYPED_DATA.typeCodes[type] && + type.indexOf('uint') === -1 && + type.indexOf('int') > -1 + ) { + // Handle signed integers using bignumber.js directly + if (forJSParser) { + // For EIP712 encoding in this module we need hex strings for signed ints too + const bn = new BN(data) + // Preserve full precision by returning the decimal string representation + data = bn.toFixed() + } else { + // `bignumber.js` is needed for `cbor` encoding, which gets sent to the Lattice and plays + // nicely with its firmware cbor lib. + // NOTE: If we instantiate a `bignumber.js` object, it will not match what `borc` creates + // when run inside of the browser (i.e. MetaMask). Thus we introduce this hack to make sure + // we are creating a compatible type. + // TODO: Find another cbor lib that is compataible with the firmware's lib in a browser + // context. This is surprisingly difficult - I tried several libs and only cbor/borc have + // worked (borc is a supposedly "browser compatible" version of cbor) + data = new BN(data) + } + } else if ( + ethMsgProtocol.TYPED_DATA.typeCodes[type] && + (type.indexOf('uint') > -1 || type.indexOf('int') > -1) + ) { + // For uints, convert to a buffer and do some sanity checking. + // Note that we could probably just use bignumber.js directly as we do with + // signed ints, but this code is battle tested and we don't want to change it. + let b = ensureHexBuffer(data) + // Edge case to handle 0-value bignums + if (b.length === 0) { + b = Buffer.from('00', 'hex') + } + // Uint256s should be encoded as bignums. + if (forJSParser) { + // For EIP712 encoding in this module we need hex strings to represent the numbers + data = `0x${b.toString('hex')}` + } else { + // Load into bignumber.js used by cbor lib + data = new BN(b.toString('hex'), 16) + } + } else if (type === 'bool') { + // Booleans need to be cast to a u8 + data = data === true ? 1 : 0 + } + // Other types don't need to be modified + return data } function get_personal_sign_prefix(L) { - return Buffer.from(`\u0019Ethereum Signed Message:\n${L.toString()}`, 'utf-8') + return Buffer.from(`\u0019Ethereum Signed Message:\n${L.toString()}`, 'utf-8') } function get_rlp_encoded_preimage(rawTx, txType) { - if (txType) { - return Buffer.concat([ - Buffer.from([txType]), - Buffer.from(RLP.encode(rawTx)), - ]) - } else { - return Buffer.from(RLP.encode(rawTx)) - } + if (txType) { + return Buffer.concat([ + Buffer.from([txType]), + Buffer.from(RLP.encode(rawTx)), + ]) + } else { + return Buffer.from(RLP.encode(rawTx)) + } } /** @@ -1174,28 +1174,28 @@ function get_rlp_encoded_preimage(rawTx, txType) { * @returns A `viem`-compatible `TransactionSerializable` object. */ export const normalizeToViemTransaction = ( - tx: unknown, + tx: unknown, ): TransactionSerializable => { - const parsed = TransactionSchema.parse(tx) - - return { - ...parsed, - to: parsed.to as Hex, - data: parsed.data as Hex, - gas: parsed.gas, - value: parsed.value, - nonce: parsed.nonce, - chainId: parsed.chainId, - gasPrice: 'gasPrice' in parsed ? parsed.gasPrice : undefined, - maxFeePerGas: 'maxFeePerGas' in parsed ? parsed.maxFeePerGas : undefined, - maxPriorityFeePerGas: - 'maxPriorityFeePerGas' in parsed - ? parsed.maxPriorityFeePerGas - : undefined, - accessList: 'accessList' in parsed ? parsed.accessList : undefined, - authorizationList: - 'authorizationList' in parsed ? parsed.authorizationList : undefined, - } + const parsed = TransactionSchema.parse(tx) + + return { + ...parsed, + to: parsed.to as Hex, + data: parsed.data as Hex, + gas: parsed.gas, + value: parsed.value, + nonce: parsed.nonce, + chainId: parsed.chainId, + gasPrice: 'gasPrice' in parsed ? parsed.gasPrice : undefined, + maxFeePerGas: 'maxFeePerGas' in parsed ? parsed.maxFeePerGas : undefined, + maxPriorityFeePerGas: + 'maxPriorityFeePerGas' in parsed + ? parsed.maxPriorityFeePerGas + : undefined, + accessList: 'accessList' in parsed ? parsed.accessList : undefined, + authorizationList: + 'authorizationList' in parsed ? parsed.authorizationList : undefined, + } } /** @@ -1203,20 +1203,20 @@ export const normalizeToViemTransaction = ( * Bridge function for firmware v0.15.0+ which removed legacy ETH signing paths. */ const convertEthereumTransactionToGenericRequest = ( - req: FlexibleTransaction, + req: FlexibleTransaction, ) => { - // Use the unified normalization and serialization pipeline. - // 1. Normalize the potentially varied input to a standard viem format. - const viemTx = normalizeToViemTransaction(req) - // 2. Serialize the transaction to RLP-encoded bytes. - const serializedTx = serializeTransaction(viemTx) - return Buffer.from(serializedTx.slice(2), 'hex') + // Use the unified normalization and serialization pipeline. + // 1. Normalize the potentially varied input to a standard viem format. + const viemTx = normalizeToViemTransaction(req) + // 2. Serialize the transaction to RLP-encoded bytes. + const serializedTx = serializeTransaction(viemTx) + return Buffer.from(serializedTx.slice(2), 'hex') } // Type for Ethereum generic signing request type EthereumGenericSigningRequestParams = FlexibleTransaction & { - fwConstants: FirmwareConstants - signerPath: SigningPath + fwConstants: FirmwareConstants + signerPath: SigningPath } /** @@ -1224,20 +1224,20 @@ type EthereumGenericSigningRequestParams = FlexibleTransaction & { * One-step function combining transaction conversion and generic signing setup. */ export const buildEthereumGenericSigningRequest = ( - req: EthereumGenericSigningRequestParams, + req: EthereumGenericSigningRequestParams, ) => { - const { fwConstants, signerPath, ...txData } = req - - const payload = convertEthereumTransactionToGenericRequest(txData) - - return buildGenericSigningMsgRequest({ - fwConstants, - encodingType: EXTERNAL.SIGNING.ENCODINGS.EVM, - curveType: EXTERNAL.SIGNING.CURVES.SECP256K1, - hashType: EXTERNAL.SIGNING.HASHES.KECCAK256, - signerPath, - payload, - }) + const { fwConstants, signerPath, ...txData } = req + + const payload = convertEthereumTransactionToGenericRequest(txData) + + return buildGenericSigningMsgRequest({ + fwConstants, + encodingType: EXTERNAL.SIGNING.ENCODINGS.EVM, + curveType: EXTERNAL.SIGNING.CURVES.SECP256K1, + hashType: EXTERNAL.SIGNING.HASHES.KECCAK256, + signerPath, + payload, + }) } /** @@ -1247,165 +1247,165 @@ export const buildEthereumGenericSigningRequest = ( * @returns The serialized transaction as a hex string */ export function serializeEIP7702Transaction(tx: TransactionRequest): Hex { - if ( - tx.type !== TRANSACTION_TYPE.EIP7702_AUTH_LIST && - tx.type !== TRANSACTION_TYPE.EIP7702_AUTH - ) { - throw new Error( - `Only EIP-7702 auth transactions (type ${TRANSACTION_TYPE.EIP7702_AUTH}) and auth-list transactions (type ${TRANSACTION_TYPE.EIP7702_AUTH_LIST}) are supported`, - ) - } - - // Type guard to ensure we have an EIP7702 transaction with appropriate authorization data - const hasAuthList = 'authorizationList' in tx - const hasSingleAuth = 'authorization' in tx - - if (!hasAuthList && !hasSingleAuth) { - throw new Error( - 'Transaction does not have authorization or authorizationList property', - ) - } - - // For type 4 transactions, convert single authorization to array format - let authorizationList: any[] - if (tx.type === TRANSACTION_TYPE.EIP7702_AUTH) { - if (!hasSingleAuth) { - throw new Error( - 'EIP-7702 auth transaction (type 4) must contain authorization property', - ) - } - authorizationList = [(tx as any).authorization] - } else { - // Type 5 transaction - only handle authorizationList field - if (hasAuthList) { - authorizationList = (tx as any).authorizationList - } else { - throw new Error( - 'EIP-7702 auth list transaction (type 5) must contain authorizationList property', - ) - } - } - - // Validate that all required fields exist - if ( - !authorizationList || - !Array.isArray(authorizationList) || - authorizationList.length === 0 - ) { - throw new Error( - 'EIP-7702 transaction must contain at least one authorization', - ) - } - - // Validate each authorization - authorizationList.forEach((auth, index) => { - if (!auth.address) { - throw new Error( - `Authorization at index ${index} is missing a contract address`, - ) - } - }) - - // Validate required transaction fields - if (!tx.to) { - throw new Error('EIP-7702 transaction must include a valid "to" address') - } - - // Convert to Viem's expected format - const viemTx = { - type: 'eip7702' as const, - chainId: tx.chainId, - nonce: tx.nonce, - maxPriorityFeePerGas: - typeof tx.maxPriorityFeePerGas === 'string' - ? BigInt(tx.maxPriorityFeePerGas) - : tx.maxPriorityFeePerGas, - maxFeePerGas: - typeof tx.maxFeePerGas === 'string' - ? BigInt(tx.maxFeePerGas) - : tx.maxFeePerGas, - gas: - typeof (tx as any).gas === 'string' - ? BigInt((tx as any).gas) - : (tx as any).gas || - (typeof (tx as any).gasLimit === 'string' - ? BigInt((tx as any).gasLimit) - : (tx as any).gasLimit), - to: tx.to as `0x${string}`, - value: typeof tx.value === 'string' ? BigInt(tx.value) : tx.value, - data: tx.data || '0x', - authorizationList: authorizationList.map((auth, idx) => { - // Create the Viem-formatted authorization - // Ensure proper address handling with 0x prefix - const address = auth.address || '' - const addressStr = - typeof address === 'string' - ? address.startsWith('0x') - ? address - : `0x${address}` - : '0x' - - if (!addressStr || addressStr === '0x') { - throw new Error( - `Authorization at index ${idx} is missing a valid address`, - ) - } - - // Handle viem's SignedAuthorization format - if ('signature' in auth && auth.signature) { - // Viem format with nested signature - return { - chainId: auth.chainId, - address: addressStr as `0x${string}`, - nonce: BigInt(auth.nonce || 0), - signature: auth.signature, - } - } else { - // Direct signature properties (r, s, yParity/v) - return { - chainId: auth.chainId, - address: addressStr as `0x${string}`, - nonce: BigInt(auth.nonce || 0), - signature: { - yParity: - typeof auth.yParity === 'number' - ? auth.yParity - : typeof auth.yParity === 'string' - ? auth.yParity === '0x01' || - auth.yParity === '0x1' || - auth.yParity === '1' - ? 1 - : 0 - : 0, - r: auth.r || '0x0', - s: auth.s || '0x0', - }, - } - } - }), - } - - return serializeTransaction(viemTx as any) + if ( + tx.type !== TRANSACTION_TYPE.EIP7702_AUTH_LIST && + tx.type !== TRANSACTION_TYPE.EIP7702_AUTH + ) { + throw new Error( + `Only EIP-7702 auth transactions (type ${TRANSACTION_TYPE.EIP7702_AUTH}) and auth-list transactions (type ${TRANSACTION_TYPE.EIP7702_AUTH_LIST}) are supported`, + ) + } + + // Type guard to ensure we have an EIP7702 transaction with appropriate authorization data + const hasAuthList = 'authorizationList' in tx + const hasSingleAuth = 'authorization' in tx + + if (!hasAuthList && !hasSingleAuth) { + throw new Error( + 'Transaction does not have authorization or authorizationList property', + ) + } + + // For type 4 transactions, convert single authorization to array format + let authorizationList: any[] + if (tx.type === TRANSACTION_TYPE.EIP7702_AUTH) { + if (!hasSingleAuth) { + throw new Error( + 'EIP-7702 auth transaction (type 4) must contain authorization property', + ) + } + authorizationList = [(tx as any).authorization] + } else { + // Type 5 transaction - only handle authorizationList field + if (hasAuthList) { + authorizationList = (tx as any).authorizationList + } else { + throw new Error( + 'EIP-7702 auth list transaction (type 5) must contain authorizationList property', + ) + } + } + + // Validate that all required fields exist + if ( + !authorizationList || + !Array.isArray(authorizationList) || + authorizationList.length === 0 + ) { + throw new Error( + 'EIP-7702 transaction must contain at least one authorization', + ) + } + + // Validate each authorization + authorizationList.forEach((auth, index) => { + if (!auth.address) { + throw new Error( + `Authorization at index ${index} is missing a contract address`, + ) + } + }) + + // Validate required transaction fields + if (!tx.to) { + throw new Error('EIP-7702 transaction must include a valid "to" address') + } + + // Convert to Viem's expected format + const viemTx = { + type: 'eip7702' as const, + chainId: tx.chainId, + nonce: tx.nonce, + maxPriorityFeePerGas: + typeof tx.maxPriorityFeePerGas === 'string' + ? BigInt(tx.maxPriorityFeePerGas) + : tx.maxPriorityFeePerGas, + maxFeePerGas: + typeof tx.maxFeePerGas === 'string' + ? BigInt(tx.maxFeePerGas) + : tx.maxFeePerGas, + gas: + typeof (tx as any).gas === 'string' + ? BigInt((tx as any).gas) + : (tx as any).gas || + (typeof (tx as any).gasLimit === 'string' + ? BigInt((tx as any).gasLimit) + : (tx as any).gasLimit), + to: tx.to as `0x${string}`, + value: typeof tx.value === 'string' ? BigInt(tx.value) : tx.value, + data: tx.data || '0x', + authorizationList: authorizationList.map((auth, idx) => { + // Create the Viem-formatted authorization + // Ensure proper address handling with 0x prefix + const address = auth.address || '' + const addressStr = + typeof address === 'string' + ? address.startsWith('0x') + ? address + : `0x${address}` + : '0x' + + if (!addressStr || addressStr === '0x') { + throw new Error( + `Authorization at index ${idx} is missing a valid address`, + ) + } + + // Handle viem's SignedAuthorization format + if ('signature' in auth && auth.signature) { + // Viem format with nested signature + return { + chainId: auth.chainId, + address: addressStr as `0x${string}`, + nonce: BigInt(auth.nonce || 0), + signature: auth.signature, + } + } else { + // Direct signature properties (r, s, yParity/v) + return { + chainId: auth.chainId, + address: addressStr as `0x${string}`, + nonce: BigInt(auth.nonce || 0), + signature: { + yParity: + typeof auth.yParity === 'number' + ? auth.yParity + : typeof auth.yParity === 'string' + ? auth.yParity === '0x01' || + auth.yParity === '0x1' || + auth.yParity === '1' + ? 1 + : 0 + : 0, + r: auth.r || '0x0', + s: auth.s || '0x0', + }, + } + } + }), + } + + return serializeTransaction(viemTx as any) } export const isEip7702Transaction = (tx: TransactionRequest): boolean => { - return ( - typeof tx === 'object' && - 'type' in tx && - (tx.type === TRANSACTION_TYPE.EIP7702_AUTH_LIST || - tx.type === TRANSACTION_TYPE.EIP7702_AUTH) - ) + return ( + typeof tx === 'object' && + 'type' in tx && + (tx.type === TRANSACTION_TYPE.EIP7702_AUTH_LIST || + tx.type === TRANSACTION_TYPE.EIP7702_AUTH) + ) } export default { - buildEthereumMsgRequest, - validateEthereumMsgResponse, - buildEthereumTxRequest, - buildEthRawTx, - hashTransaction, - chainIds, - ensureHexBuffer, - normalizeToViemTransaction, - convertEthereumTransactionToGenericRequest, - buildEthereumGenericSigningRequest, + buildEthereumMsgRequest, + validateEthereumMsgResponse, + buildEthereumTxRequest, + buildEthRawTx, + hashTransaction, + chainIds, + ensureHexBuffer, + normalizeToViemTransaction, + convertEthereumTransactionToGenericRequest, + buildEthereumGenericSigningRequest, } diff --git a/packages/sdk/src/functions/addKvRecords.ts b/packages/sdk/src/functions/addKvRecords.ts index bc13c511..5c650483 100644 --- a/packages/sdk/src/functions/addKvRecords.ts +++ b/packages/sdk/src/functions/addKvRecords.ts @@ -1,16 +1,16 @@ import { - LatticeSecureEncryptedRequestType, - encryptedSecureRequest, + LatticeSecureEncryptedRequestType, + encryptedSecureRequest, } from '../protocol' import { - validateConnectedClient, - validateKvRecord, - validateKvRecords, + validateConnectedClient, + validateKvRecord, + validateKvRecords, } from '../shared/validators' import type { - AddKvRecordsRequestFunctionParams, - FirmwareConstants, - KVRecords, + AddKvRecordsRequestFunctionParams, + FirmwareConstants, + KVRecords, } from '../types' /** @@ -20,79 +20,79 @@ import type { * @returns A callback with an error or null. */ export async function addKvRecords({ - client, - records, - type, - caseSensitive, + client, + records, + type, + caseSensitive, }: AddKvRecordsRequestFunctionParams): Promise { - const { url, sharedSecret, ephemeralPub, fwConstants } = - validateConnectedClient(client) - validateAddKvRequest({ records, fwConstants }) + const { url, sharedSecret, ephemeralPub, fwConstants } = + validateConnectedClient(client) + validateAddKvRequest({ records, fwConstants }) - // Build the data for this request - const data = encodeAddKvRecordsRequest({ - records, - type, - caseSensitive, - fwConstants, - }) + // Build the data for this request + const data = encodeAddKvRecordsRequest({ + records, + type, + caseSensitive, + fwConstants, + }) - const { decryptedData, newEphemeralPub } = await encryptedSecureRequest({ - data, - requestType: LatticeSecureEncryptedRequestType.addKvRecords, - sharedSecret, - ephemeralPub, - url, - }) + const { decryptedData, newEphemeralPub } = await encryptedSecureRequest({ + data, + requestType: LatticeSecureEncryptedRequestType.addKvRecords, + sharedSecret, + ephemeralPub, + url, + }) - client.mutate({ - ephemeralPub: newEphemeralPub, - }) + client.mutate({ + ephemeralPub: newEphemeralPub, + }) - return decryptedData + return decryptedData } export const validateAddKvRequest = ({ - records, - fwConstants, + records, + fwConstants, }: { - records: KVRecords - fwConstants: FirmwareConstants + records: KVRecords + fwConstants: FirmwareConstants }) => { - validateKvRecords(records, fwConstants) + validateKvRecords(records, fwConstants) } export const encodeAddKvRecordsRequest = ({ - records, - type, - caseSensitive, - fwConstants, + records, + type, + caseSensitive, + fwConstants, }: { - records: KVRecords - type: number - caseSensitive: boolean - fwConstants: FirmwareConstants + records: KVRecords + type: number + caseSensitive: boolean + fwConstants: FirmwareConstants }) => { - const payload = Buffer.alloc(1 + 139 * fwConstants.kvActionMaxNum) - payload.writeUInt8(Object.keys(records).length, 0) - let off = 1 - Object.entries(records).forEach(([_key, _val]) => { - const { key, val } = validateKvRecord({ key: _key, val: _val }, fwConstants) - // Skip the ID portion. This will get added by firmware. - payload.writeUInt32LE(0, off) - off += 4 - payload.writeUInt32LE(type, off) - off += 4 - payload.writeUInt8(caseSensitive ? 1 : 0, off) - off += 1 - payload.writeUInt8(String(key).length + 1, off) - off += 1 - Buffer.from(String(key)).copy(payload, off) - off += fwConstants.kvKeyMaxStrSz + 1 - payload.writeUInt8(String(val).length + 1, off) - off += 1 - Buffer.from(String(val)).copy(payload, off) - off += fwConstants.kvValMaxStrSz + 1 - }) - return payload + const payload = Buffer.alloc(1 + 139 * fwConstants.kvActionMaxNum) + payload.writeUInt8(Object.keys(records).length, 0) + let off = 1 + Object.entries(records).forEach(([_key, _val]) => { + const { key, val } = validateKvRecord({ key: _key, val: _val }, fwConstants) + // Skip the ID portion. This will get added by firmware. + payload.writeUInt32LE(0, off) + off += 4 + payload.writeUInt32LE(type, off) + off += 4 + payload.writeUInt8(caseSensitive ? 1 : 0, off) + off += 1 + payload.writeUInt8(String(key).length + 1, off) + off += 1 + Buffer.from(String(key)).copy(payload, off) + off += fwConstants.kvKeyMaxStrSz + 1 + payload.writeUInt8(String(val).length + 1, off) + off += 1 + Buffer.from(String(val)).copy(payload, off) + off += fwConstants.kvValMaxStrSz + 1 + }) + return payload } diff --git a/packages/sdk/src/functions/connect.ts b/packages/sdk/src/functions/connect.ts index f4b1d2fc..c5b6ed24 100644 --- a/packages/sdk/src/functions/connect.ts +++ b/packages/sdk/src/functions/connect.ts @@ -2,86 +2,86 @@ import { ProtocolConstants, connectSecureRequest } from '../protocol' import { doesFetchWalletsOnLoad } from '../shared/predicates' import { getSharedSecret, parseWallets } from '../shared/utilities' import { - validateBaseUrl, - validateDeviceId, - validateKey, + validateBaseUrl, + validateDeviceId, + validateKey, } from '../shared/validators' import type { - ActiveWallets, - ConnectRequestFunctionParams, - KeyPair, + ActiveWallets, + ConnectRequestFunctionParams, + KeyPair, } from '../types' import { aes256_decrypt, getP256KeyPairFromPub } from '../util' export async function connect({ - client, - id, + client, + id, }: ConnectRequestFunctionParams): Promise { - const { deviceId, key, baseUrl } = validateConnectRequest({ - deviceId: id, - // @ts-expect-error - private access - key: client.key, - baseUrl: client.baseUrl, - }) + const { deviceId, key, baseUrl } = validateConnectRequest({ + deviceId: id, + // @ts-expect-error - private access + key: client.key, + baseUrl: client.baseUrl, + }) - const url = `${baseUrl}/${deviceId}` + const url = `${baseUrl}/${deviceId}` - const respPayloadData = await connectSecureRequest({ - url, - pubkey: client.publicKey, - }) + const respPayloadData = await connectSecureRequest({ + url, + pubkey: client.publicKey, + }) - // Decode response data params. - // Response payload data is *not* encrypted. - const { isPaired, fwVersion, activeWallets, ephemeralPub } = - await decodeConnectResponse(respPayloadData, key) + // Decode response data params. + // Response payload data is *not* encrypted. + const { isPaired, fwVersion, activeWallets, ephemeralPub } = + await decodeConnectResponse(respPayloadData, key) - // Update client state with response data + // Update client state with response data - client.mutate({ - deviceId, - ephemeralPub, - url, - isPaired, - fwVersion, - activeWallets, - }) + client.mutate({ + deviceId, + ephemeralPub, + url, + isPaired, + fwVersion, + activeWallets, + }) - // If we are paired and are on older firmware (<0.14.1), we need a - // follow up request to sync wallet state. - if (isPaired && !doesFetchWalletsOnLoad(client.getFwVersion())) { - await client.fetchActiveWallet() - } + // If we are paired and are on older firmware (<0.14.1), we need a + // follow up request to sync wallet state. + if (isPaired && !doesFetchWalletsOnLoad(client.getFwVersion())) { + await client.fetchActiveWallet() + } - // Return flag indicating whether we are paired or not. - // If we are *not* already paired, the Lattice is now in - // pairing mode and expects a `finalizePairing` encrypted - // request as a follow up. - return isPaired + // Return flag indicating whether we are paired or not. + // If we are *not* already paired, the Lattice is now in + // pairing mode and expects a `finalizePairing` encrypted + // request as a follow up. + return isPaired } export const validateConnectRequest = ({ - deviceId, - key, - baseUrl, + deviceId, + key, + baseUrl, }: { - deviceId?: string - key?: KeyPair - baseUrl?: string + deviceId?: string + key?: KeyPair + baseUrl?: string }): { - deviceId: string - key: KeyPair - baseUrl: string + deviceId: string + key: KeyPair + baseUrl: string } => { - const validDeviceId = validateDeviceId(deviceId) - const validKey = validateKey(key) - const validBaseUrl = validateBaseUrl(baseUrl) + const validDeviceId = validateDeviceId(deviceId) + const validKey = validateKey(key) + const validBaseUrl = validateBaseUrl(baseUrl) - return { - deviceId: validDeviceId, - key: validKey, - baseUrl: validBaseUrl, - } + return { + deviceId: validDeviceId, + key: validKey, + baseUrl: validBaseUrl, + } } /** @@ -95,47 +95,47 @@ export const validateConnectRequest = ({ * @returns true if we are paired to the device already */ export const decodeConnectResponse = ( - response: Buffer, - key: KeyPair, + response: Buffer, + key: KeyPair, ): { - isPaired: boolean - fwVersion: Buffer - activeWallets: ActiveWallets | undefined - ephemeralPub: KeyPair + isPaired: boolean + fwVersion: Buffer + activeWallets: ActiveWallets | undefined + ephemeralPub: KeyPair } => { - let off = 0 - const isPaired = - response.readUInt8(off) === ProtocolConstants.pairingStatus.paired - off++ - // If we are already paired, we get the next ephemeral key - const pub = response.slice(off, off + 65).toString('hex') - off += 65 // Set the public key - const ephemeralPub = getP256KeyPairFromPub(pub) - // Grab the firmware version (will be 0-length for older fw versions) It is of format - // |fix|minor|major|reserved| - const fwVersion = response.slice(off, off + 4) - off += 4 + let off = 0 + const isPaired = + response.readUInt8(off) === ProtocolConstants.pairingStatus.paired + off++ + // If we are already paired, we get the next ephemeral key + const pub = response.slice(off, off + 65).toString('hex') + off += 65 // Set the public key + const ephemeralPub = getP256KeyPairFromPub(pub) + // Grab the firmware version (will be 0-length for older fw versions) It is of format + // |fix|minor|major|reserved| + const fwVersion = response.slice(off, off + 4) + off += 4 - // If we are already paired, the response will include some encrypted data about the current - // wallets This data was added in Lattice firmware v0.14.1 - if (isPaired) { - //TODO && this._fwVersionGTE(0, 14, 1)) { - // Later versions of firmware added wallet info - const encWalletData = response.slice(off, off + 160) - off += 160 - const sharedSecret = getSharedSecret(key, ephemeralPub) - const decWalletData = aes256_decrypt(encWalletData, sharedSecret) - // Sanity check to make sure the last part of the decrypted data is empty. The last 2 bytes - // are AES padding - if ( - decWalletData[decWalletData.length - 2] !== 0 || - decWalletData[decWalletData.length - 1] !== 0 - ) { - throw new Error('Failed to connect to Lattice.') - } - const activeWallets = parseWallets(decWalletData) - return { isPaired, fwVersion, activeWallets, ephemeralPub } - } - // return the state of our pairing - return { isPaired, fwVersion, activeWallets: undefined, ephemeralPub } + // If we are already paired, the response will include some encrypted data about the current + // wallets This data was added in Lattice firmware v0.14.1 + if (isPaired) { + //TODO && this._fwVersionGTE(0, 14, 1)) { + // Later versions of firmware added wallet info + const encWalletData = response.slice(off, off + 160) + off += 160 + const sharedSecret = getSharedSecret(key, ephemeralPub) + const decWalletData = aes256_decrypt(encWalletData, sharedSecret) + // Sanity check to make sure the last part of the decrypted data is empty. The last 2 bytes + // are AES padding + if ( + decWalletData[decWalletData.length - 2] !== 0 || + decWalletData[decWalletData.length - 1] !== 0 + ) { + throw new Error('Failed to connect to Lattice.') + } + const activeWallets = parseWallets(decWalletData) + return { isPaired, fwVersion, activeWallets, ephemeralPub } + } + // return the state of our pairing + return { isPaired, fwVersion, activeWallets: undefined, ephemeralPub } } diff --git a/packages/sdk/src/functions/fetchActiveWallet.ts b/packages/sdk/src/functions/fetchActiveWallet.ts index 582118cf..8b256816 100644 --- a/packages/sdk/src/functions/fetchActiveWallet.ts +++ b/packages/sdk/src/functions/fetchActiveWallet.ts @@ -1,15 +1,15 @@ import { EMPTY_WALLET_UID } from '../constants' import { - LatticeSecureEncryptedRequestType, - encryptedSecureRequest, + LatticeSecureEncryptedRequestType, + encryptedSecureRequest, } from '../protocol' import { - validateActiveWallets, - validateConnectedClient, + validateActiveWallets, + validateConnectedClient, } from '../shared/validators' import type { - ActiveWallets, - FetchActiveWalletRequestFunctionParams, + ActiveWallets, + FetchActiveWalletRequestFunctionParams, } from '../types' /** @@ -20,59 +20,59 @@ import type { * data. Otherwise it will return the info for the internal Lattice wallet. */ export async function fetchActiveWallet({ - client, + client, }: FetchActiveWalletRequestFunctionParams): Promise { - const { url, sharedSecret, ephemeralPub } = validateConnectedClient(client) + const { url, sharedSecret, ephemeralPub } = validateConnectedClient(client) - const { decryptedData, newEphemeralPub } = await encryptedSecureRequest({ - data: Buffer.alloc(0), - requestType: LatticeSecureEncryptedRequestType.getWallets, - sharedSecret, - ephemeralPub, - url, - }) + const { decryptedData, newEphemeralPub } = await encryptedSecureRequest({ + data: Buffer.alloc(0), + requestType: LatticeSecureEncryptedRequestType.getWallets, + sharedSecret, + ephemeralPub, + url, + }) - const activeWallets = decodeFetchActiveWalletResponse(decryptedData) - const validActiveWallets = validateActiveWallets(activeWallets) + const activeWallets = decodeFetchActiveWalletResponse(decryptedData) + const validActiveWallets = validateActiveWallets(activeWallets) - client.mutate({ - ephemeralPub: newEphemeralPub, - activeWallets: validActiveWallets, - }) + client.mutate({ + ephemeralPub: newEphemeralPub, + activeWallets: validActiveWallets, + }) - return validActiveWallets + return validActiveWallets } export const decodeFetchActiveWalletResponse = (data: Buffer) => { - // Read the external wallet data first. If it is non-null, the external wallet will be the - // active wallet of the device and we should save it. If the external wallet is blank, it means - // there is no card present and we should save and use the interal wallet. If both wallets are - // empty, it means the device still needs to be set up. - const walletDescriptorLen = 71 - // Internal first - const activeWallets: ActiveWallets = { - internal: { - uid: EMPTY_WALLET_UID, - external: false, - name: Buffer.alloc(0), - capabilities: 0, - }, - external: { - uid: EMPTY_WALLET_UID, - external: true, - name: Buffer.alloc(0), - capabilities: 0, - }, - } - let off = 0 - activeWallets.internal.uid = data.slice(off, off + 32) - activeWallets.internal.capabilities = data.readUInt32BE(off + 32) - activeWallets.internal.name = data.slice(off + 36, off + walletDescriptorLen) - // Offset the first item - off += walletDescriptorLen - // External - activeWallets.external.uid = data.slice(off, off + 32) - activeWallets.external.capabilities = data.readUInt32BE(off + 32) - activeWallets.external.name = data.slice(off + 36, off + walletDescriptorLen) - return activeWallets + // Read the external wallet data first. If it is non-null, the external wallet will be the + // active wallet of the device and we should save it. If the external wallet is blank, it means + // there is no card present and we should save and use the interal wallet. If both wallets are + // empty, it means the device still needs to be set up. + const walletDescriptorLen = 71 + // Internal first + const activeWallets: ActiveWallets = { + internal: { + uid: EMPTY_WALLET_UID, + external: false, + name: Buffer.alloc(0), + capabilities: 0, + }, + external: { + uid: EMPTY_WALLET_UID, + external: true, + name: Buffer.alloc(0), + capabilities: 0, + }, + } + let off = 0 + activeWallets.internal.uid = data.slice(off, off + 32) + activeWallets.internal.capabilities = data.readUInt32BE(off + 32) + activeWallets.internal.name = data.slice(off + 36, off + walletDescriptorLen) + // Offset the first item + off += walletDescriptorLen + // External + activeWallets.external.uid = data.slice(off, off + 32) + activeWallets.external.capabilities = data.readUInt32BE(off + 32) + activeWallets.external.name = data.slice(off + 36, off + walletDescriptorLen) + return activeWallets } diff --git a/packages/sdk/src/functions/fetchDecoder.ts b/packages/sdk/src/functions/fetchDecoder.ts index c9e7a65f..e0dd9caf 100644 --- a/packages/sdk/src/functions/fetchDecoder.ts +++ b/packages/sdk/src/functions/fetchDecoder.ts @@ -10,28 +10,28 @@ import { fetchCalldataDecoder } from '../util' * @returns An object containing the ABI and encoded definition of the contract. */ export async function fetchDecoder({ - data, - to, - chainId, + data, + to, + chainId, }: TransactionRequest): Promise { - try { - const client = await getClient() - validateConnectedClient(client) + try { + const client = await getClient() + validateConnectedClient(client) - const fwVersion = client.getFwVersion() - const supportsDecoderRecursion = - fwVersion.major > 0 || fwVersion.minor >= 16 + const fwVersion = client.getFwVersion() + const supportsDecoderRecursion = + fwVersion.major > 0 || fwVersion.minor >= 16 - const { def } = await fetchCalldataDecoder( - data, - to, - chainId, - supportsDecoderRecursion, - ) + const { def } = await fetchCalldataDecoder( + data, + to, + chainId, + supportsDecoderRecursion, + ) - return def - } catch (error) { - console.warn('Failed to fetch ABI:', error) - return undefined - } + return def + } catch (error) { + console.warn('Failed to fetch ABI:', error) + return undefined + } } diff --git a/packages/sdk/src/functions/fetchEncData.ts b/packages/sdk/src/functions/fetchEncData.ts index e5675b45..72fdaf14 100644 --- a/packages/sdk/src/functions/fetchEncData.ts +++ b/packages/sdk/src/functions/fetchEncData.ts @@ -5,212 +5,212 @@ import { v4 as uuidV4 } from 'uuid' import { EXTERNAL } from '../constants' import { - LatticeSecureEncryptedRequestType, - encryptedSecureRequest, + LatticeSecureEncryptedRequestType, + encryptedSecureRequest, } from '../protocol' import { getPathStr } from '../shared/utilities' import { - validateConnectedClient, - validateStartPath, - validateWallet, + validateConnectedClient, + validateStartPath, + validateWallet, } from '../shared/validators' import type { - EIP2335KeyExportData, - EIP2335KeyExportReq, - FetchEncDataRequestFunctionParams, - FirmwareVersion, - Wallet, + EIP2335KeyExportData, + EIP2335KeyExportReq, + FetchEncDataRequestFunctionParams, + FirmwareVersion, + Wallet, } from '../types' const { ENC_DATA } = EXTERNAL const ENC_DATA_ERR_STR = - 'Unknown encrypted data export type requested. Exiting.' + 'Unknown encrypted data export type requested. Exiting.' const ENC_DATA_REQ_DATA_SZ = 1025 const ENC_DATA_RESP_SZ = { - EIP2335: { - CIPHERTEXT: 32, - SALT: 32, - CHECKSUM: 32, - IV: 16, - PUBKEY: 48, - }, + EIP2335: { + CIPHERTEXT: 32, + SALT: 32, + CHECKSUM: 32, + IV: 16, + PUBKEY: 48, + }, } as const export async function fetchEncData({ - client, - schema, - params, + client, + schema, + params, }: FetchEncDataRequestFunctionParams): Promise { - const { url, sharedSecret, ephemeralPub, fwVersion } = - validateConnectedClient(client) - const activeWallet = validateWallet(client.getActiveWallet()) - validateFetchEncDataRequest({ params }) + const { url, sharedSecret, ephemeralPub, fwVersion } = + validateConnectedClient(client) + const activeWallet = validateWallet(client.getActiveWallet()) + validateFetchEncDataRequest({ params }) - const data = encodeFetchEncDataRequest({ - schema, - params, - fwVersion, - activeWallet, - }) + const data = encodeFetchEncDataRequest({ + schema, + params, + fwVersion, + activeWallet, + }) - const { decryptedData, newEphemeralPub } = await encryptedSecureRequest({ - data, - requestType: LatticeSecureEncryptedRequestType.fetchEncryptedData, - sharedSecret, - ephemeralPub, - url, - }) + const { decryptedData, newEphemeralPub } = await encryptedSecureRequest({ + data, + requestType: LatticeSecureEncryptedRequestType.fetchEncryptedData, + sharedSecret, + ephemeralPub, + url, + }) - client.mutate({ - ephemeralPub: newEphemeralPub, - }) + client.mutate({ + ephemeralPub: newEphemeralPub, + }) - return decodeFetchEncData({ data: decryptedData, schema, params }) + return decodeFetchEncData({ data: decryptedData, schema, params }) } export const validateFetchEncDataRequest = ({ - params, + params, }: { - params: EIP2335KeyExportReq + params: EIP2335KeyExportReq }) => { - // Validate derivation path - validateStartPath(params.path) + // Validate derivation path + validateStartPath(params.path) } export const encodeFetchEncDataRequest = ({ - schema, - params, - fwVersion, - activeWallet, + schema, + params, + fwVersion, + activeWallet, }: { - schema: number - params: EIP2335KeyExportReq - fwVersion: FirmwareVersion - activeWallet: Wallet + schema: number + params: EIP2335KeyExportReq + fwVersion: FirmwareVersion + activeWallet: Wallet }) => { - // Check firmware version - if (fwVersion.major < 1 && fwVersion.minor < 17) { - throw new Error( - 'Firmware version >=v0.17.0 is required for encrypted data export.', - ) - } - // Update params depending on what type of data is being exported - if (schema === ENC_DATA.SCHEMAS.BLS_KEYSTORE_EIP2335_PBKDF_V4) { - // Set the wallet UID to the client's current active wallet - params.walletUID = activeWallet.uid - } else { - throw new Error(ENC_DATA_ERR_STR) - } - // Build the payload data - const payload = Buffer.alloc(ENC_DATA_REQ_DATA_SZ) - let off = 0 - payload.writeUInt8(schema, off) - off += 1 - if (schema === ENC_DATA.SCHEMAS.BLS_KEYSTORE_EIP2335_PBKDF_V4) { - params.walletUID.copy(payload, off) - off += params.walletUID.length - payload.writeUInt8(params.path.length, off) - off += 1 - for (let i = 0; i < 5; i++) { - if (i <= params.path.length) { - payload.writeUInt32LE(params.path[i], off) - } - off += 4 - } - if (params.c) { - payload.writeUInt32LE(params.c, off) - } - off += 4 - return payload - } else { - throw new Error(ENC_DATA_ERR_STR) - } + // Check firmware version + if (fwVersion.major < 1 && fwVersion.minor < 17) { + throw new Error( + 'Firmware version >=v0.17.0 is required for encrypted data export.', + ) + } + // Update params depending on what type of data is being exported + if (schema === ENC_DATA.SCHEMAS.BLS_KEYSTORE_EIP2335_PBKDF_V4) { + // Set the wallet UID to the client's current active wallet + params.walletUID = activeWallet.uid + } else { + throw new Error(ENC_DATA_ERR_STR) + } + // Build the payload data + const payload = Buffer.alloc(ENC_DATA_REQ_DATA_SZ) + let off = 0 + payload.writeUInt8(schema, off) + off += 1 + if (schema === ENC_DATA.SCHEMAS.BLS_KEYSTORE_EIP2335_PBKDF_V4) { + params.walletUID.copy(payload, off) + off += params.walletUID.length + payload.writeUInt8(params.path.length, off) + off += 1 + for (let i = 0; i < 5; i++) { + if (i <= params.path.length) { + payload.writeUInt32LE(params.path[i], off) + } + off += 4 + } + if (params.c) { + payload.writeUInt32LE(params.c, off) + } + off += 4 + return payload + } else { + throw new Error(ENC_DATA_ERR_STR) + } } export const decodeFetchEncData = ({ - data, - schema, - params, + data, + schema, + params, }: { - schema: number - params: EIP2335KeyExportReq - data: Buffer + schema: number + params: EIP2335KeyExportReq + data: Buffer }): Buffer => { - let off = 0 - if (schema === ENC_DATA.SCHEMAS.BLS_KEYSTORE_EIP2335_PBKDF_V4) { - const respData = {} as EIP2335KeyExportData - const { CIPHERTEXT, SALT, CHECKSUM, IV, PUBKEY } = ENC_DATA_RESP_SZ.EIP2335 - const expectedSz = - 4 + // iterations = u32 - CIPHERTEXT + - SALT + - CHECKSUM + - IV + - PUBKEY - const dataSz = data.readUInt32LE(off) - off += 4 - if (dataSz !== expectedSz) { - throw new Error( - 'Invalid data returned from Lattice. Expected EIP2335 data.', - ) - } - respData.iterations = data.readUInt32LE(off) - off += 4 - respData.cipherText = data.slice(off, off + CIPHERTEXT) - off += CIPHERTEXT - respData.salt = data.slice(off, off + SALT) - off += SALT - respData.checksum = data.slice(off, off + CHECKSUM) - off += CHECKSUM - respData.iv = data.slice(off, off + IV) - off += IV - respData.pubkey = data.slice(off, off + PUBKEY) - off += PUBKEY - return formatEIP2335ExportData(respData, params.path) - } else { - throw new Error(ENC_DATA_ERR_STR) - } + let off = 0 + if (schema === ENC_DATA.SCHEMAS.BLS_KEYSTORE_EIP2335_PBKDF_V4) { + const respData = {} as EIP2335KeyExportData + const { CIPHERTEXT, SALT, CHECKSUM, IV, PUBKEY } = ENC_DATA_RESP_SZ.EIP2335 + const expectedSz = + 4 + // iterations = u32 + CIPHERTEXT + + SALT + + CHECKSUM + + IV + + PUBKEY + const dataSz = data.readUInt32LE(off) + off += 4 + if (dataSz !== expectedSz) { + throw new Error( + 'Invalid data returned from Lattice. Expected EIP2335 data.', + ) + } + respData.iterations = data.readUInt32LE(off) + off += 4 + respData.cipherText = data.slice(off, off + CIPHERTEXT) + off += CIPHERTEXT + respData.salt = data.slice(off, off + SALT) + off += SALT + respData.checksum = data.slice(off, off + CHECKSUM) + off += CHECKSUM + respData.iv = data.slice(off, off + IV) + off += IV + respData.pubkey = data.slice(off, off + PUBKEY) + off += PUBKEY + return formatEIP2335ExportData(respData, params.path) + } else { + throw new Error(ENC_DATA_ERR_STR) + } } const formatEIP2335ExportData = ( - resp: EIP2335KeyExportData, - path: number[], + resp: EIP2335KeyExportData, + path: number[], ): Buffer => { - try { - const { iterations, salt, checksum, iv, cipherText, pubkey } = resp - return Buffer.from( - JSON.stringify({ - version: 4, - uuid: uuidV4(), - path: getPathStr(path), - pubkey: pubkey.toString('hex'), - crypto: { - kdf: { - function: 'pbkdf2', - params: { - dklen: 32, - c: iterations, - prf: 'hmac-sha256', - salt: salt.toString('hex'), - }, - message: '', - }, - checksum: { - function: 'sha256', - params: {}, - message: checksum.toString('hex'), - }, - cipher: { - function: 'aes-128-ctr', - params: { - iv: iv.toString('hex'), - }, - message: cipherText.toString('hex'), - }, - }, - }), - ) - } catch (err) { - throw Error(`Failed to format EIP2335 return data: ${err.toString()}`) - } + try { + const { iterations, salt, checksum, iv, cipherText, pubkey } = resp + return Buffer.from( + JSON.stringify({ + version: 4, + uuid: uuidV4(), + path: getPathStr(path), + pubkey: pubkey.toString('hex'), + crypto: { + kdf: { + function: 'pbkdf2', + params: { + dklen: 32, + c: iterations, + prf: 'hmac-sha256', + salt: salt.toString('hex'), + }, + message: '', + }, + checksum: { + function: 'sha256', + params: {}, + message: checksum.toString('hex'), + }, + cipher: { + function: 'aes-128-ctr', + params: { + iv: iv.toString('hex'), + }, + message: cipherText.toString('hex'), + }, + }, + }), + ) + } catch (err) { + throw Error(`Failed to format EIP2335 return data: ${err.toString()}`) + } } diff --git a/packages/sdk/src/functions/getAddresses.ts b/packages/sdk/src/functions/getAddresses.ts index 08d244e2..8153caca 100644 --- a/packages/sdk/src/functions/getAddresses.ts +++ b/packages/sdk/src/functions/getAddresses.ts @@ -1,20 +1,20 @@ import { - LatticeGetAddressesFlag, - LatticeSecureEncryptedRequestType, - ProtocolConstants, - encryptedSecureRequest, + LatticeGetAddressesFlag, + LatticeSecureEncryptedRequestType, + ProtocolConstants, + encryptedSecureRequest, } from '../protocol' import { - validateConnectedClient, - validateIsUInt4, - validateNAddresses, - validateStartPath, - validateWallet, + validateConnectedClient, + validateIsUInt4, + validateNAddresses, + validateStartPath, + validateWallet, } from '../shared/validators' import type { - FirmwareConstants, - GetAddressesRequestFunctionParams, - Wallet, + FirmwareConstants, + GetAddressesRequestFunctionParams, + Wallet, } from '../types' import { isValidAssetPath } from '../util' @@ -25,190 +25,190 @@ import { isValidAssetPath } from '../util' * @returns An array of addresses or public keys. */ export async function getAddresses({ - client, - startPath: _startPath, - n: _n, - flag: _flag, - iterIdx, + client, + startPath: _startPath, + n: _n, + flag: _flag, + iterIdx, }: GetAddressesRequestFunctionParams): Promise { - const { url, sharedSecret, ephemeralPub, fwConstants } = - validateConnectedClient(client) - const activeWallet = validateWallet(client.getActiveWallet()) - - const { startPath, n, flag } = validateGetAddressesRequest({ - startPath: _startPath, - n: _n, - flag: _flag, - }) - - const data = encodeGetAddressesRequest({ - startPath, - n, - flag, - fwConstants, - wallet: activeWallet, - iterIdx, - }) - - const { decryptedData, newEphemeralPub } = await encryptedSecureRequest({ - data, - requestType: LatticeSecureEncryptedRequestType.getAddresses, - sharedSecret, - ephemeralPub, - url, - }) - - client.mutate({ - ephemeralPub: newEphemeralPub, - }) - - return decodeGetAddressesResponse(decryptedData, flag) + const { url, sharedSecret, ephemeralPub, fwConstants } = + validateConnectedClient(client) + const activeWallet = validateWallet(client.getActiveWallet()) + + const { startPath, n, flag } = validateGetAddressesRequest({ + startPath: _startPath, + n: _n, + flag: _flag, + }) + + const data = encodeGetAddressesRequest({ + startPath, + n, + flag, + fwConstants, + wallet: activeWallet, + iterIdx, + }) + + const { decryptedData, newEphemeralPub } = await encryptedSecureRequest({ + data, + requestType: LatticeSecureEncryptedRequestType.getAddresses, + sharedSecret, + ephemeralPub, + url, + }) + + client.mutate({ + ephemeralPub: newEphemeralPub, + }) + + return decodeGetAddressesResponse(decryptedData, flag) } export const validateGetAddressesRequest = ({ - startPath, - n, - flag, + startPath, + n, + flag, }: { - startPath?: number[] - n?: number - flag?: number + startPath?: number[] + n?: number + flag?: number }) => { - return { - startPath: validateStartPath(startPath), - n: validateNAddresses(n), - flag: validateIsUInt4(flag), - } + return { + startPath: validateStartPath(startPath), + n: validateNAddresses(n), + flag: validateIsUInt4(flag), + } } export const encodeGetAddressesRequest = ({ - startPath, - n, - flag, - fwConstants, - wallet, - iterIdx, + startPath, + n, + flag, + fwConstants, + wallet, + iterIdx, }: { - startPath: number[] - n: number - flag: number - fwConstants: FirmwareConstants - wallet: Wallet - iterIdx?: number + startPath: number[] + n: number + flag: number + fwConstants: FirmwareConstants + wallet: Wallet + iterIdx?: number }) => { - const flags = fwConstants.getAddressFlags || ([] as any[]) - const isPubkeyOnly = - flags.indexOf(flag) > -1 && - (flag === LatticeGetAddressesFlag.ed25519Pubkey || - flag === LatticeGetAddressesFlag.secp256k1Pubkey || - flag === LatticeGetAddressesFlag.bls12_381Pubkey) - const isXpub = flag === LatticeGetAddressesFlag.secp256k1Xpub - if (!isPubkeyOnly && !isXpub && !isValidAssetPath(startPath, fwConstants)) { - throw new Error( - 'Derivation path or flag is not supported. Try updating Lattice firmware.', - ) - } - - // Ensure path depth is valid (2-5 indices) - if (startPath.length < 2 || startPath.length > 5) { - throw new Error('Derivation path must include 2-5 indices.') - } - - // Validate iterIdx (0-5) - if (iterIdx < 0 || iterIdx > 5) { - throw new Error('Iteration index must be between 0 and 5.') - } - - // Ensure iterIdx is not greater than path depth - if (iterIdx > startPath.length) { - throw new Error('Iteration index cannot be greater than path depth.') - } - - const sz = 32 + 1 + 20 + 1 // walletUID + pathDepth_IterIdx + 5 u32 indices + count/flag - const payload = Buffer.alloc(sz) - let off = 0 - - // walletUID - wallet.uid.copy(payload, off) - off += 32 - - // pathDepth_IterIdx - const pathDepth_IterIdx = ((iterIdx & 0x0f) << 4) | (startPath.length & 0x0f) - payload.writeUInt8(pathDepth_IterIdx, off) - off += 1 - - // Build the start path (5x u32 indices) - for (let i = 0; i < 5; i++) { - const val = i < startPath.length ? startPath[i] : 0 - payload.writeUInt32BE(val, off) - off += 4 - } - - // Combine count and flag into a single byte - const countVal = n & 0x0f - const flagVal = (flag & 0x0f) << 4 - payload.writeUInt8(countVal | flagVal, off) - - return payload + const flags = fwConstants.getAddressFlags || ([] as any[]) + const isPubkeyOnly = + flags.indexOf(flag) > -1 && + (flag === LatticeGetAddressesFlag.ed25519Pubkey || + flag === LatticeGetAddressesFlag.secp256k1Pubkey || + flag === LatticeGetAddressesFlag.bls12_381Pubkey) + const isXpub = flag === LatticeGetAddressesFlag.secp256k1Xpub + if (!isPubkeyOnly && !isXpub && !isValidAssetPath(startPath, fwConstants)) { + throw new Error( + 'Derivation path or flag is not supported. Try updating Lattice firmware.', + ) + } + + // Ensure path depth is valid (2-5 indices) + if (startPath.length < 2 || startPath.length > 5) { + throw new Error('Derivation path must include 2-5 indices.') + } + + // Validate iterIdx (0-5) + if (iterIdx < 0 || iterIdx > 5) { + throw new Error('Iteration index must be between 0 and 5.') + } + + // Ensure iterIdx is not greater than path depth + if (iterIdx > startPath.length) { + throw new Error('Iteration index cannot be greater than path depth.') + } + + const sz = 32 + 1 + 20 + 1 // walletUID + pathDepth_IterIdx + 5 u32 indices + count/flag + const payload = Buffer.alloc(sz) + let off = 0 + + // walletUID + wallet.uid.copy(payload, off) + off += 32 + + // pathDepth_IterIdx + const pathDepth_IterIdx = ((iterIdx & 0x0f) << 4) | (startPath.length & 0x0f) + payload.writeUInt8(pathDepth_IterIdx, off) + off += 1 + + // Build the start path (5x u32 indices) + for (let i = 0; i < 5; i++) { + const val = i < startPath.length ? startPath[i] : 0 + payload.writeUInt32BE(val, off) + off += 4 + } + + // Combine count and flag into a single byte + const countVal = n & 0x0f + const flagVal = (flag & 0x0f) << 4 + payload.writeUInt8(countVal | flagVal, off) + + return payload } /** * @internal * @return an array of address strings or pubkey buffers */ export const decodeGetAddressesResponse = ( - data: Buffer, - flag: number, + data: Buffer, + flag: number, ): Buffer[] => { - let off = 0 - const addressOffset = - flag === LatticeGetAddressesFlag.ed25519Pubkey ? 113 : 65 - // Look for addresses until we reach the end (a 4 byte checksum) - const addrs: any[] = [] - // Pubkeys are formatted differently in the response - const arePubkeys = - flag === LatticeGetAddressesFlag.secp256k1Pubkey || - flag === LatticeGetAddressesFlag.ed25519Pubkey || - flag === LatticeGetAddressesFlag.bls12_381Pubkey - if (arePubkeys) { - off += 1 // skip uint8 representing pubkey type - } - const respDataLength = - ProtocolConstants.msgSizes.secure.data.response.encrypted[ - LatticeSecureEncryptedRequestType.getAddresses - ] - while (off < respDataLength) { - if (arePubkeys) { - // Pubkeys are shorter and are returned as buffers - const pubBytes = data.slice(off, off + addressOffset) - const isEmpty = pubBytes.every((byte: number) => byte === 0x00) - if (!isEmpty && flag === LatticeGetAddressesFlag.ed25519Pubkey) { - // ED25519 pubkeys are 32 bytes - addrs.push(pubBytes.slice(0, 32)) - } else if (!isEmpty && flag === LatticeGetAddressesFlag.bls12_381Pubkey) { - // BLS12_381_G1 keys are 48 bytes - addrs.push(pubBytes.slice(0, 48)) - } else if (!isEmpty) { - // Only other returned pubkeys are ECC, or 65 bytes Note that we return full - // (uncompressed) ECC pubkeys - addrs.push(pubBytes) - } - off += addressOffset - } else { - // Otherwise we are dealing with address strings or XPUB strings - const addrBytes = data.slice(off, off + ProtocolConstants.addrStrLen) - off += ProtocolConstants.addrStrLen - // Return the UTF-8 representation - const len = addrBytes.indexOf(0) // First 0 is the null terminator - if (len > 0) { - const cleanStr = addrBytes - .slice(0, len) - .toString() - // biome-ignore lint/suspicious/noControlCharactersInRegex: Intentional - stripping control characters - .replace(/[\u0000-\u001F\u007F-\u009F]/g, '') - addrs.push(cleanStr) - } - } - } - - return addrs + let off = 0 + const addressOffset = + flag === LatticeGetAddressesFlag.ed25519Pubkey ? 113 : 65 + // Look for addresses until we reach the end (a 4 byte checksum) + const addrs: any[] = [] + // Pubkeys are formatted differently in the response + const arePubkeys = + flag === LatticeGetAddressesFlag.secp256k1Pubkey || + flag === LatticeGetAddressesFlag.ed25519Pubkey || + flag === LatticeGetAddressesFlag.bls12_381Pubkey + if (arePubkeys) { + off += 1 // skip uint8 representing pubkey type + } + const respDataLength = + ProtocolConstants.msgSizes.secure.data.response.encrypted[ + LatticeSecureEncryptedRequestType.getAddresses + ] + while (off < respDataLength) { + if (arePubkeys) { + // Pubkeys are shorter and are returned as buffers + const pubBytes = data.slice(off, off + addressOffset) + const isEmpty = pubBytes.every((byte: number) => byte === 0x00) + if (!isEmpty && flag === LatticeGetAddressesFlag.ed25519Pubkey) { + // ED25519 pubkeys are 32 bytes + addrs.push(pubBytes.slice(0, 32)) + } else if (!isEmpty && flag === LatticeGetAddressesFlag.bls12_381Pubkey) { + // BLS12_381_G1 keys are 48 bytes + addrs.push(pubBytes.slice(0, 48)) + } else if (!isEmpty) { + // Only other returned pubkeys are ECC, or 65 bytes Note that we return full + // (uncompressed) ECC pubkeys + addrs.push(pubBytes) + } + off += addressOffset + } else { + // Otherwise we are dealing with address strings or XPUB strings + const addrBytes = data.slice(off, off + ProtocolConstants.addrStrLen) + off += ProtocolConstants.addrStrLen + // Return the UTF-8 representation + const len = addrBytes.indexOf(0) // First 0 is the null terminator + if (len > 0) { + const cleanStr = addrBytes + .slice(0, len) + .toString() + // biome-ignore lint/suspicious/noControlCharactersInRegex: Intentional - stripping control characters + .replace(/[\u0000-\u001F\u007F-\u009F]/g, '') + addrs.push(cleanStr) + } + } + } + + return addrs } diff --git a/packages/sdk/src/functions/getKvRecords.ts b/packages/sdk/src/functions/getKvRecords.ts index 5ad94a9e..595000e3 100644 --- a/packages/sdk/src/functions/getKvRecords.ts +++ b/packages/sdk/src/functions/getKvRecords.ts @@ -1,129 +1,129 @@ import { - LatticeSecureEncryptedRequestType, - encryptedSecureRequest, + LatticeSecureEncryptedRequestType, + encryptedSecureRequest, } from '../protocol' import { validateConnectedClient } from '../shared/validators' import type { - FirmwareConstants, - GetKvRecordsData, - GetKvRecordsRequestFunctionParams, + FirmwareConstants, + GetKvRecordsData, + GetKvRecordsRequestFunctionParams, } from '../types' export async function getKvRecords({ - client, - type: _type, - n: _n, - start: _start, + client, + type: _type, + n: _n, + start: _start, }: GetKvRecordsRequestFunctionParams): Promise { - const { url, sharedSecret, ephemeralPub, fwConstants } = - validateConnectedClient(client) + const { url, sharedSecret, ephemeralPub, fwConstants } = + validateConnectedClient(client) - const { type, n, start } = validateGetKvRequest({ - type: _type, - n: _n, - start: _start, - fwConstants, - }) + const { type, n, start } = validateGetKvRequest({ + type: _type, + n: _n, + start: _start, + fwConstants, + }) - const data = encodeGetKvRecordsRequest({ type, n, start }) + const data = encodeGetKvRecordsRequest({ type, n, start }) - const { decryptedData, newEphemeralPub } = await encryptedSecureRequest({ - data, - requestType: LatticeSecureEncryptedRequestType.getKvRecords, - sharedSecret, - ephemeralPub, - url, - }) + const { decryptedData, newEphemeralPub } = await encryptedSecureRequest({ + data, + requestType: LatticeSecureEncryptedRequestType.getKvRecords, + sharedSecret, + ephemeralPub, + url, + }) - client.mutate({ - ephemeralPub: newEphemeralPub, - }) + client.mutate({ + ephemeralPub: newEphemeralPub, + }) - return decodeGetKvRecordsResponse(decryptedData, fwConstants) + return decodeGetKvRecordsResponse(decryptedData, fwConstants) } export const validateGetKvRequest = ({ - fwConstants, - n, - type, - start, + fwConstants, + n, + type, + start, }: { - fwConstants: FirmwareConstants - n?: number - type?: number - start?: number + fwConstants: FirmwareConstants + n?: number + type?: number + start?: number }) => { - if (!fwConstants.kvActionsAllowed) { - throw new Error('Unsupported. Please update firmware.') - } - if (!n || n < 1) { - throw new Error('You must request at least one record.') - } - if (n > fwConstants.kvActionMaxNum) { - throw new Error( - `You may only request up to ${fwConstants.kvActionMaxNum} records at once.`, - ) - } - if (type !== 0 && !type) { - throw new Error('You must specify a type.') - } - if (start !== 0 && !start) { - throw new Error('You must specify a type.') - } + if (!fwConstants.kvActionsAllowed) { + throw new Error('Unsupported. Please update firmware.') + } + if (!n || n < 1) { + throw new Error('You must request at least one record.') + } + if (n > fwConstants.kvActionMaxNum) { + throw new Error( + `You may only request up to ${fwConstants.kvActionMaxNum} records at once.`, + ) + } + if (type !== 0 && !type) { + throw new Error('You must specify a type.') + } + if (start !== 0 && !start) { + throw new Error('You must specify a type.') + } - return { fwConstants, n, type, start } + return { fwConstants, n, type, start } } export const encodeGetKvRecordsRequest = ({ - type, - n, - start, + type, + n, + start, }: { - type: number - n: number - start: number + type: number + n: number + start: number }) => { - const payload = Buffer.alloc(9) - payload.writeUInt32LE(type, 0) - payload.writeUInt8(n, 4) - payload.writeUInt32LE(start, 5) - return payload + const payload = Buffer.alloc(9) + payload.writeUInt32LE(type, 0) + payload.writeUInt8(n, 4) + payload.writeUInt32LE(start, 5) + return payload } export const decodeGetKvRecordsResponse = ( - data: Buffer, - fwConstants: FirmwareConstants, + data: Buffer, + fwConstants: FirmwareConstants, ) => { - let off = 0 - const nTotal = data.readUInt32BE(off) - off += 4 - const nFetched = Number.parseInt(data.slice(off, off + 1).toString('hex'), 16) - off += 1 - if (nFetched > fwConstants.kvActionMaxNum) - throw new Error('Too many records fetched. Firmware error.') - const records: any = [] - for (let i = 0; i < nFetched; i++) { - const r: any = {} - r.id = data.readUInt32BE(off) - off += 4 - r.type = data.readUInt32BE(off) - off += 4 - r.caseSensitive = - Number.parseInt(data.slice(off, off + 1).toString('hex'), 16) === 1 - off += 1 - const keySz = Number.parseInt(data.slice(off, off + 1).toString('hex'), 16) - off += 1 - r.key = data.slice(off, off + keySz - 1).toString() - off += fwConstants.kvKeyMaxStrSz + 1 - const valSz = Number.parseInt(data.slice(off, off + 1).toString('hex'), 16) - off += 1 - r.val = data.slice(off, off + valSz - 1).toString() - off += fwConstants.kvValMaxStrSz + 1 - records.push(r) - } - return { - records, - total: nTotal, - fetched: nFetched, - } + let off = 0 + const nTotal = data.readUInt32BE(off) + off += 4 + const nFetched = Number.parseInt(data.slice(off, off + 1).toString('hex'), 16) + off += 1 + if (nFetched > fwConstants.kvActionMaxNum) + throw new Error('Too many records fetched. Firmware error.') + const records: any = [] + for (let i = 0; i < nFetched; i++) { + const r: any = {} + r.id = data.readUInt32BE(off) + off += 4 + r.type = data.readUInt32BE(off) + off += 4 + r.caseSensitive = + Number.parseInt(data.slice(off, off + 1).toString('hex'), 16) === 1 + off += 1 + const keySz = Number.parseInt(data.slice(off, off + 1).toString('hex'), 16) + off += 1 + r.key = data.slice(off, off + keySz - 1).toString() + off += fwConstants.kvKeyMaxStrSz + 1 + const valSz = Number.parseInt(data.slice(off, off + 1).toString('hex'), 16) + off += 1 + r.val = data.slice(off, off + valSz - 1).toString() + off += fwConstants.kvValMaxStrSz + 1 + records.push(r) + } + return { + records, + total: nTotal, + fetched: nFetched, + } } diff --git a/packages/sdk/src/functions/pair.ts b/packages/sdk/src/functions/pair.ts index 3c3894fe..eb300d3d 100644 --- a/packages/sdk/src/functions/pair.ts +++ b/packages/sdk/src/functions/pair.ts @@ -1,6 +1,6 @@ import { - LatticeSecureEncryptedRequestType, - encryptedSecureRequest, + LatticeSecureEncryptedRequestType, + encryptedSecureRequest, } from '../protocol' import { getPubKeyBytes } from '../shared/utilities' import { validateConnectedClient } from '../shared/validators' @@ -15,56 +15,56 @@ import { generateAppSecret, toPaddedDER } from '../util' * @returns The active wallet object. */ export async function pair({ - client, - pairingSecret, + client, + pairingSecret, }: PairRequestParams): Promise { - const { url, sharedSecret, ephemeralPub, appName, key } = - validateConnectedClient(client) - const data = encodePairRequest({ pairingSecret, key, appName }) + const { url, sharedSecret, ephemeralPub, appName, key } = + validateConnectedClient(client) + const data = encodePairRequest({ pairingSecret, key, appName }) - const { newEphemeralPub } = await encryptedSecureRequest({ - data, - requestType: LatticeSecureEncryptedRequestType.finalizePairing, - sharedSecret, - ephemeralPub, - url, - }) + const { newEphemeralPub } = await encryptedSecureRequest({ + data, + requestType: LatticeSecureEncryptedRequestType.finalizePairing, + sharedSecret, + ephemeralPub, + url, + }) - client.mutate({ - ephemeralPub: newEphemeralPub, - isPaired: true, - }) + client.mutate({ + ephemeralPub: newEphemeralPub, + isPaired: true, + }) - await client.fetchActiveWallet() - return client.hasActiveWallet() + await client.fetchActiveWallet() + return client.hasActiveWallet() } export const encodePairRequest = ({ - key, - pairingSecret, - appName, + key, + pairingSecret, + appName, }: { - key: KeyPair - pairingSecret: string - appName: string + key: KeyPair + pairingSecret: string + appName: string }) => { - // Build the payload data - const pubKeyBytes = getPubKeyBytes(key) - const nameBuf = Buffer.alloc(25) - if (pairingSecret.length > 0) { - // If a pairing secret of zero length is passed in, it usually indicates we want to cancel - // the pairing attempt. In this case we pass a zero-length name buffer so the firmware can - // know not to draw the error screen. Note that we still expect an error to come back - // (RESP_ERR_PAIR_FAIL) - nameBuf.write(appName) - } - const hash = generateAppSecret( - pubKeyBytes, - nameBuf, - Buffer.from(pairingSecret), - ) - const sig = key.sign(hash) - const derSig = toPaddedDER(sig) - const payload = Buffer.concat([nameBuf, derSig]) - return payload + // Build the payload data + const pubKeyBytes = getPubKeyBytes(key) + const nameBuf = Buffer.alloc(25) + if (pairingSecret.length > 0) { + // If a pairing secret of zero length is passed in, it usually indicates we want to cancel + // the pairing attempt. In this case we pass a zero-length name buffer so the firmware can + // know not to draw the error screen. Note that we still expect an error to come back + // (RESP_ERR_PAIR_FAIL) + nameBuf.write(appName) + } + const hash = generateAppSecret( + pubKeyBytes, + nameBuf, + Buffer.from(pairingSecret), + ) + const sig = key.sign(hash) + const derSig = toPaddedDER(sig) + const payload = Buffer.concat([nameBuf, derSig]) + return payload } diff --git a/packages/sdk/src/functions/removeKvRecords.ts b/packages/sdk/src/functions/removeKvRecords.ts index 1e95f034..25c010e0 100644 --- a/packages/sdk/src/functions/removeKvRecords.ts +++ b/packages/sdk/src/functions/removeKvRecords.ts @@ -1,11 +1,11 @@ import { - LatticeSecureEncryptedRequestType, - encryptedSecureRequest, + LatticeSecureEncryptedRequestType, + encryptedSecureRequest, } from '../protocol' import { validateConnectedClient } from '../shared/validators' import type { - FirmwareConstants, - RemoveKvRecordsRequestFunctionParams, + FirmwareConstants, + RemoveKvRecordsRequestFunctionParams, } from '../types' /** @@ -14,81 +14,81 @@ import type { * @returns A callback with an error or null. */ export async function removeKvRecords({ - client, - type: _type, - ids: _ids, + client, + type: _type, + ids: _ids, }: RemoveKvRecordsRequestFunctionParams): Promise { - const { url, sharedSecret, ephemeralPub, fwConstants } = - validateConnectedClient(client) + const { url, sharedSecret, ephemeralPub, fwConstants } = + validateConnectedClient(client) - const { type, ids } = validateRemoveKvRequest({ - fwConstants, - type: _type, - ids: _ids, - }) + const { type, ids } = validateRemoveKvRequest({ + fwConstants, + type: _type, + ids: _ids, + }) - const data = encodeRemoveKvRecordsRequest({ - type, - ids, - fwConstants, - }) + const data = encodeRemoveKvRecordsRequest({ + type, + ids, + fwConstants, + }) - const { decryptedData, newEphemeralPub } = await encryptedSecureRequest({ - data, - requestType: LatticeSecureEncryptedRequestType.removeKvRecords, - sharedSecret, - ephemeralPub, - url, - }) + const { decryptedData, newEphemeralPub } = await encryptedSecureRequest({ + data, + requestType: LatticeSecureEncryptedRequestType.removeKvRecords, + sharedSecret, + ephemeralPub, + url, + }) - client.mutate({ - ephemeralPub: newEphemeralPub, - }) + client.mutate({ + ephemeralPub: newEphemeralPub, + }) - return decryptedData + return decryptedData } export const validateRemoveKvRequest = ({ - fwConstants, - type, - ids, + fwConstants, + type, + ids, }: { - fwConstants: FirmwareConstants - type?: number - ids?: string[] + fwConstants: FirmwareConstants + type?: number + ids?: string[] }) => { - if (!fwConstants.kvActionsAllowed) { - throw new Error('Unsupported. Please update firmware.') - } - if (!Array.isArray(ids) || ids.length < 1) { - throw new Error('You must include one or more `ids` to removed.') - } - if (ids.length > fwConstants.kvRemoveMaxNum) { - throw new Error( - `Only up to ${fwConstants.kvRemoveMaxNum} records may be removed at once.`, - ) - } - if (type !== 0 && !type) { - throw new Error('You must specify a type.') - } - return { type, ids } + if (!fwConstants.kvActionsAllowed) { + throw new Error('Unsupported. Please update firmware.') + } + if (!Array.isArray(ids) || ids.length < 1) { + throw new Error('You must include one or more `ids` to removed.') + } + if (ids.length > fwConstants.kvRemoveMaxNum) { + throw new Error( + `Only up to ${fwConstants.kvRemoveMaxNum} records may be removed at once.`, + ) + } + if (type !== 0 && !type) { + throw new Error('You must specify a type.') + } + return { type, ids } } export const encodeRemoveKvRecordsRequest = ({ - fwConstants, - type, - ids, + fwConstants, + type, + ids, }: { - fwConstants: FirmwareConstants - type: number - ids: string[] + fwConstants: FirmwareConstants + type: number + ids: string[] }) => { - const payload = Buffer.alloc(5 + 4 * fwConstants.kvRemoveMaxNum) - payload.writeUInt32LE(type, 0) - payload.writeUInt8(ids.length, 4) - for (let i = 0; i < ids.length; i++) { - const id = Number.parseInt(ids[i] as string) - payload.writeUInt32LE(id, 5 + 4 * i) - } - return payload + const payload = Buffer.alloc(5 + 4 * fwConstants.kvRemoveMaxNum) + payload.writeUInt32LE(type, 0) + payload.writeUInt8(ids.length, 4) + for (let i = 0; i < ids.length; i++) { + const id = Number.parseInt(ids[i] as string) + payload.writeUInt32LE(id, 5 + 4 * i) + } + return payload } diff --git a/packages/sdk/src/functions/sign.ts b/packages/sdk/src/functions/sign.ts index f049421f..b4959665 100644 --- a/packages/sdk/src/functions/sign.ts +++ b/packages/sdk/src/functions/sign.ts @@ -5,20 +5,20 @@ import { CURRENCIES } from '../constants' import ethereum from '../ethereum' import { parseGenericSigningResponse } from '../genericSigning' import { - LatticeSecureEncryptedRequestType, - LatticeSignSchema, - encryptedSecureRequest, + LatticeSecureEncryptedRequestType, + LatticeSignSchema, + encryptedSecureRequest, } from '../protocol' import { buildTransaction } from '../shared/functions' import { validateConnectedClient, validateWallet } from '../shared/validators' import type { - BitcoinSignRequest, - DecodeSignResponseParams, - EncodeSignRequestParams, - SignData, - SignRequest, - SignRequestFunctionParams, - SigningRequestResponse, + BitcoinSignRequest, + DecodeSignResponseParams, + EncodeSignRequestParams, + SignData, + SignRequest, + SignRequestFunctionParams, + SigningRequestResponse, } from '../types' import { parseDER } from '../util' @@ -28,283 +28,283 @@ import { parseDER } from '../util' * @returns The response from the device. */ export async function sign({ - client, - data, - currency, - cachedData, - nextCode, + client, + data, + currency, + cachedData, + nextCode, }: SignRequestFunctionParams): Promise { - try { - const { url, sharedSecret, ephemeralPub, fwConstants } = - validateConnectedClient(client) - const wallet = validateWallet(client.getActiveWallet()) + try { + const { url, sharedSecret, ephemeralPub, fwConstants } = + validateConnectedClient(client) + const wallet = validateWallet(client.getActiveWallet()) - const { requestData, isGeneric } = buildTransaction({ - data, - currency, - fwConstants, - }) + const { requestData, isGeneric } = buildTransaction({ + data, + currency, + fwConstants, + }) - const { payload, hasExtraPayloads } = encodeSignRequest({ - fwConstants, - wallet, - requestData, - cachedData, - nextCode, - }) + const { payload, hasExtraPayloads } = encodeSignRequest({ + fwConstants, + wallet, + requestData, + cachedData, + nextCode, + }) - const { decryptedData, newEphemeralPub } = await encryptedSecureRequest({ - data: payload, - requestType: LatticeSecureEncryptedRequestType.sign, - sharedSecret, - ephemeralPub, - url, - }) + const { decryptedData, newEphemeralPub } = await encryptedSecureRequest({ + data: payload, + requestType: LatticeSecureEncryptedRequestType.sign, + sharedSecret, + ephemeralPub, + url, + }) - client.mutate({ - ephemeralPub: newEphemeralPub, - }) + client.mutate({ + ephemeralPub: newEphemeralPub, + }) - // If this request has multiple payloads, we need to recurse - // so that we can make the next request. - // It is chained to the first request using `nextCode` - if (hasExtraPayloads) { - return client.sign({ - data, - currency, - cachedData: requestData, - nextCode: decryptedData.slice(0, 8), - }) - } - // If this is the only (or final) request, - // decode response data and return - const decodedResponse = decodeSignResponse({ - data: decryptedData, - request: requestData as SignRequest, - isGeneric, - currency, - }) + // If this request has multiple payloads, we need to recurse + // so that we can make the next request. + // It is chained to the first request using `nextCode` + if (hasExtraPayloads) { + return client.sign({ + data, + currency, + cachedData: requestData, + nextCode: decryptedData.slice(0, 8), + }) + } + // If this is the only (or final) request, + // decode response data and return + const decodedResponse = decodeSignResponse({ + data: decryptedData, + request: requestData as SignRequest, + isGeneric, + currency, + }) - return decodedResponse - } catch (err) { - console.error('Error signing transaction:', { - message: err.message, - payload: JSON.stringify( - { - data, - currency, - }, - (_key, value) => (typeof value === 'bigint' ? value.toString() : value), - ), - }) - throw err - } + return decodedResponse + } catch (err) { + console.error('Error signing transaction:', { + message: err.message, + payload: JSON.stringify( + { + data, + currency, + }, + (_key, value) => (typeof value === 'bigint' ? value.toString() : value), + ), + }) + throw err + } } export const encodeSignRequest = ({ - fwConstants, - wallet, - requestData, - cachedData, - nextCode, + fwConstants, + wallet, + requestData, + cachedData, + nextCode, }: EncodeSignRequestParams) => { - let reqPayload: Buffer - let schema: number - let hasExtraPayloads = 0 - const typedRequestData = requestData as SignRequest & { - extraDataPayloads?: Buffer[] - } + let reqPayload: Buffer + let schema: number + let hasExtraPayloads = 0 + const typedRequestData = requestData as SignRequest & { + extraDataPayloads?: Buffer[] + } - if (cachedData && nextCode) { - const typedCachedData = cachedData as SignRequest & { - extraDataPayloads: Buffer[] - } - const nextExtraPayload = typedCachedData.extraDataPayloads.shift() - if (!nextExtraPayload) { - throw new Error( - 'No cached extra payload available for multipart sign request.', - ) - } - if (typedRequestData.extraDataPayloads) { - typedRequestData.extraDataPayloads = typedCachedData.extraDataPayloads - } - reqPayload = Buffer.concat([nextCode, nextExtraPayload]) - schema = LatticeSignSchema.extraData - hasExtraPayloads = Number( - (typedCachedData.extraDataPayloads?.length ?? 0) > 0, - ) - } else { - reqPayload = typedRequestData.payload - schema = typedRequestData.schema - hasExtraPayloads = Number( - (typedRequestData.extraDataPayloads?.length ?? 0) > 0, - ) - } + if (cachedData && nextCode) { + const typedCachedData = cachedData as SignRequest & { + extraDataPayloads: Buffer[] + } + const nextExtraPayload = typedCachedData.extraDataPayloads.shift() + if (!nextExtraPayload) { + throw new Error( + 'No cached extra payload available for multipart sign request.', + ) + } + if (typedRequestData.extraDataPayloads) { + typedRequestData.extraDataPayloads = typedCachedData.extraDataPayloads + } + reqPayload = Buffer.concat([nextCode, nextExtraPayload]) + schema = LatticeSignSchema.extraData + hasExtraPayloads = Number( + (typedCachedData.extraDataPayloads?.length ?? 0) > 0, + ) + } else { + reqPayload = typedRequestData.payload + schema = typedRequestData.schema + hasExtraPayloads = Number( + (typedRequestData.extraDataPayloads?.length ?? 0) > 0, + ) + } - const payload = Buffer.alloc(2 + fwConstants.reqMaxDataSz) - let off = 0 + const payload = Buffer.alloc(2 + fwConstants.reqMaxDataSz) + let off = 0 - payload.writeUInt8(hasExtraPayloads, off) - off += 1 - // Copy request schema (e.g. ETH or BTC transfer) - payload.writeUInt8(schema, off) - off += 1 - // Copy the wallet UID - wallet.uid?.copy(payload, off) - off += wallet.uid?.length ?? 0 - // Build data based on the type of request - reqPayload.copy(payload, off) - return { payload, hasExtraPayloads } + payload.writeUInt8(hasExtraPayloads, off) + off += 1 + // Copy request schema (e.g. ETH or BTC transfer) + payload.writeUInt8(schema, off) + off += 1 + // Copy the wallet UID + wallet.uid?.copy(payload, off) + off += wallet.uid?.length ?? 0 + // Build data based on the type of request + reqPayload.copy(payload, off) + return { payload, hasExtraPayloads } } export const decodeSignResponse = ({ - data, - request, - isGeneric, - currency, + data, + request, + isGeneric, + currency, }: DecodeSignResponseParams): SignData => { - let off = 0 - const derSigLen = 74 // DER signatures are 74 bytes - if (currency === CURRENCIES.BTC) { - const btcRequest = request as BitcoinSignRequest - const pkhLen = 20 // Pubkeyhashes are 20 bytes - const sigsLen = 760 // Up to 10x DER signatures - const changeVersion = bitcoin.getAddressFormat( - btcRequest.origData.changePath, - ) - const changePubKeyHash = data.slice(off, off + pkhLen) - off += pkhLen - const changeRecipient = bitcoin.getBitcoinAddress( - changePubKeyHash, - changeVersion, - ) - const compressedPubLength = 33 // Size of compressed public key - const pubkeys = [] as any[] - const sigs = [] as any[] - let n = 0 - // Parse the signature for each output -- they are returned in the serialized payload in form - // [pubkey, sig] There is one signature per output - while (off < data.length) { - // Exit out if we have seen all the returned sigs and pubkeys - if (data[off] !== 0x30) break - // Otherwise grab another set Note that all DER sigs returned fill the maximum 74 byte - // buffer, but also contain a length at off+1, which we use to parse the non-zero data. - // First get the signature from its slot - const sigStart = off - const sigEnd = off + 2 + data[off + 1] - sigs.push(data.slice(sigStart, sigEnd)) - off += derSigLen - // Next, shift by the full set of signatures to hit the respective pubkey NOTE: The data - // returned is: [, , ... ][, , ... ] - const pubStart = n * compressedPubLength + sigsLen - const pubEnd = (n + 1) * compressedPubLength + sigsLen - pubkeys.push(data.slice(pubStart, pubEnd)) - // Update offset to hit the next signature slot - n += 1 - } - // Build the transaction data to be serialized - const preSerializedData: any = { - inputs: [], - outputs: [], - } + let off = 0 + const derSigLen = 74 // DER signatures are 74 bytes + if (currency === CURRENCIES.BTC) { + const btcRequest = request as BitcoinSignRequest + const pkhLen = 20 // Pubkeyhashes are 20 bytes + const sigsLen = 760 // Up to 10x DER signatures + const changeVersion = bitcoin.getAddressFormat( + btcRequest.origData.changePath, + ) + const changePubKeyHash = data.slice(off, off + pkhLen) + off += pkhLen + const changeRecipient = bitcoin.getBitcoinAddress( + changePubKeyHash, + changeVersion, + ) + const compressedPubLength = 33 // Size of compressed public key + const pubkeys = [] as any[] + const sigs = [] as any[] + let n = 0 + // Parse the signature for each output -- they are returned in the serialized payload in form + // [pubkey, sig] There is one signature per output + while (off < data.length) { + // Exit out if we have seen all the returned sigs and pubkeys + if (data[off] !== 0x30) break + // Otherwise grab another set Note that all DER sigs returned fill the maximum 74 byte + // buffer, but also contain a length at off+1, which we use to parse the non-zero data. + // First get the signature from its slot + const sigStart = off + const sigEnd = off + 2 + data[off + 1] + sigs.push(data.slice(sigStart, sigEnd)) + off += derSigLen + // Next, shift by the full set of signatures to hit the respective pubkey NOTE: The data + // returned is: [, , ... ][, , ... ] + const pubStart = n * compressedPubLength + sigsLen + const pubEnd = (n + 1) * compressedPubLength + sigsLen + pubkeys.push(data.slice(pubStart, pubEnd)) + // Update offset to hit the next signature slot + n += 1 + } + // Build the transaction data to be serialized + const preSerializedData: any = { + inputs: [], + outputs: [], + } - // First output comes from request dta - preSerializedData.outputs.push({ - value: btcRequest.origData.value, - recipient: btcRequest.origData.recipient, - }) - if (btcRequest.changeData?.value && btcRequest.changeData.value > 0) { - // Second output comes from change data - preSerializedData.outputs.push({ - value: btcRequest.changeData.value, - recipient: changeRecipient, - }) - } + // First output comes from request dta + preSerializedData.outputs.push({ + value: btcRequest.origData.value, + recipient: btcRequest.origData.recipient, + }) + if (btcRequest.changeData?.value && btcRequest.changeData.value > 0) { + // Second output comes from change data + preSerializedData.outputs.push({ + value: btcRequest.changeData.value, + recipient: changeRecipient, + }) + } - // Add the inputs - for (let i = 0; i < sigs.length; i++) { - preSerializedData.inputs.push({ - hash: btcRequest.origData.prevOuts[i].txHash, - index: btcRequest.origData.prevOuts[i].index, - sig: sigs[i], - pubkey: pubkeys[i], - signerPath: btcRequest.origData.prevOuts[i].signerPath, - }) - } + // Add the inputs + for (let i = 0; i < sigs.length; i++) { + preSerializedData.inputs.push({ + hash: btcRequest.origData.prevOuts[i].txHash, + index: btcRequest.origData.prevOuts[i].index, + sig: sigs[i], + pubkey: pubkeys[i], + signerPath: btcRequest.origData.prevOuts[i].signerPath, + }) + } - // Finally, serialize the transaction - const serializedTx = bitcoin.serializeTx(preSerializedData) - // Generate the transaction hash so the user can look this transaction up later - const preImageTxHash = serializedTx - const txHashPre: Buffer = Buffer.from( - Hash.sha256(Buffer.from(preImageTxHash, 'hex')), - ) - // Add extra data for debugging/lookup purposes - return { - tx: serializedTx, - txHash: `0x${Buffer.from(Hash.sha256(txHashPre)).toString('hex')}` as Hex, - changeRecipient, - sigs, - } - } else if (currency === CURRENCIES.ETH && !isGeneric) { - const sig = parseDER(data.slice(off, off + 2 + data[off + 1])) - off += derSigLen - const ethAddr = data.slice(off, off + 20) - // Determine the `v` param and add it to the sig before returning - const result = ethereum.buildEthRawTx(request, sig, ethAddr) + // Finally, serialize the transaction + const serializedTx = bitcoin.serializeTx(preSerializedData) + // Generate the transaction hash so the user can look this transaction up later + const preImageTxHash = serializedTx + const txHashPre: Buffer = Buffer.from( + Hash.sha256(Buffer.from(preImageTxHash, 'hex')), + ) + // Add extra data for debugging/lookup purposes + return { + tx: serializedTx, + txHash: `0x${Buffer.from(Hash.sha256(txHashPre)).toString('hex')}` as Hex, + changeRecipient, + sigs, + } + } else if (currency === CURRENCIES.ETH && !isGeneric) { + const sig = parseDER(data.slice(off, off + 2 + data[off + 1])) + off += derSigLen + const ethAddr = data.slice(off, off + 20) + // Determine the `v` param and add it to the sig before returning + const result = ethereum.buildEthRawTx(request, sig, ethAddr) - // Handle both object and string returns from buildEthRawTx - if (typeof result === 'string') { - // EIP-7702 transactions return only the hex string - // Per EIP-7702: "The [EIP-2718] `ReceiptPayload` for this transaction is - // `rlp([status, cumulative_transaction_gas_used, logs_bloom, logs])`." - return { - tx: `0x${result}`, - txHash: `0x${ethereum.hashTransaction(result)}` as Hex, - sig: { - v: 0n, - r: `0x${''}` as Hex, - s: `0x${''}` as Hex, - }, - signer: `0x${ethAddr.toString('hex')}` as Address, - } - } else { - // Normal transactions return object with rawTx and sigWithV - const response: SignData = { - tx: `0x${result.rawTx}`, - txHash: `0x${ethereum.hashTransaction(result.rawTx)}` as Hex, - sig: { - v: BigInt(`0x${result.sigWithV.v.toString('hex')}`), - r: `0x${result.sigWithV.r.toString('hex')}` as Hex, - s: `0x${result.sigWithV.s.toString('hex')}` as Hex, - }, - signer: `0x${ethAddr.toString('hex')}` as Address, - } + // Handle both object and string returns from buildEthRawTx + if (typeof result === 'string') { + // EIP-7702 transactions return only the hex string + // Per EIP-7702: "The [EIP-2718] `ReceiptPayload` for this transaction is + // `rlp([status, cumulative_transaction_gas_used, logs_bloom, logs])`." + return { + tx: `0x${result}`, + txHash: `0x${ethereum.hashTransaction(result)}` as Hex, + sig: { + v: 0n, + r: `0x${''}` as Hex, + s: `0x${''}` as Hex, + }, + signer: `0x${ethAddr.toString('hex')}` as Address, + } + } else { + // Normal transactions return object with rawTx and sigWithV + const response: SignData = { + tx: `0x${result.rawTx}`, + txHash: `0x${ethereum.hashTransaction(result.rawTx)}` as Hex, + sig: { + v: BigInt(`0x${result.sigWithV.v.toString('hex')}`), + r: `0x${result.sigWithV.r.toString('hex')}` as Hex, + s: `0x${result.sigWithV.s.toString('hex')}` as Hex, + }, + signer: `0x${ethAddr.toString('hex')}` as Address, + } - // Add normalized viem-compatible signed transaction if original transaction data is available - // Note: For now, skip viem transaction normalization due to interface compatibility - // This can be added back when proper transaction data is available + // Add normalized viem-compatible signed transaction if original transaction data is available + // Note: For now, skip viem transaction normalization due to interface compatibility + // This can be added back when proper transaction data is available - return response - } - } else if (currency === CURRENCIES.ETH_MSG) { - const sig = parseDER(data.slice(off, off + 2 + data[off + 1])) - off += derSigLen - const signer = data.slice(off, off + 20) - const validatedSig = ethereum.validateEthereumMsgResponse( - { signer, sig }, - request, - ) - return { - sig: { - v: BigInt(`0x${validatedSig.v.toString('hex')}`), - r: `0x${validatedSig.r.toString('hex')}` as Hex, - s: `0x${validatedSig.s.toString('hex')}` as Hex, - }, - signer: `0x${signer.toString('hex')}` as Address, - } - } else { - // Generic signing request - return parseGenericSigningResponse(data, off, request) - } + return response + } + } else if (currency === CURRENCIES.ETH_MSG) { + const sig = parseDER(data.slice(off, off + 2 + data[off + 1])) + off += derSigLen + const signer = data.slice(off, off + 20) + const validatedSig = ethereum.validateEthereumMsgResponse( + { signer, sig }, + request, + ) + return { + sig: { + v: BigInt(`0x${validatedSig.v.toString('hex')}`), + r: `0x${validatedSig.r.toString('hex')}` as Hex, + s: `0x${validatedSig.s.toString('hex')}` as Hex, + }, + signer: `0x${signer.toString('hex')}` as Address, + } + } else { + // Generic signing request + return parseGenericSigningResponse(data, off, request) + } } diff --git a/packages/sdk/src/genericSigning.ts b/packages/sdk/src/genericSigning.ts index b5184d55..122c60a1 100644 --- a/packages/sdk/src/genericSigning.ts +++ b/packages/sdk/src/genericSigning.ts @@ -11,450 +11,450 @@ This payload should be coupled with: */ import { Hash } from 'ox' import { - type Hex, - type TransactionSerializable, - parseTransaction, - serializeTransaction, + type Hex, + type TransactionSerializable, + parseTransaction, + serializeTransaction, } from 'viem' // keccak256 now imported from ox via Hash module import { HARDENED_OFFSET } from './constants' import { Constants } from './index' import { LatticeSignSchema } from './protocol' import { - buildSignerPathBuf, - existsIn, - fixLen, - getV, - getYParity, - parseDER, - splitFrames, + buildSignerPathBuf, + existsIn, + fixLen, + getV, + getYParity, + parseDER, + splitFrames, } from './util' export const buildGenericSigningMsgRequest = (req) => { - const { - signerPath, - curveType, - hashType, - encodingType = null, - decoder = null, - omitPubkey = false, - fwConstants, - blsDst = Constants.SIGNING.BLS_DST.BLS_DST_NUL, - } = req - const { - extraDataFrameSz, - extraDataMaxFrames, - prehashAllowed, - genericSigning, - varAddrPathSzAllowed, - } = fwConstants - const { - curveTypes, - encodingTypes, - hashTypes, - baseDataSz, - baseReqSz, - calldataDecoding, - } = genericSigning - const encodedPayload = getEncodedPayload( - req.payload, - encodingType, - encodingTypes, - ) - const { encoding } = encodedPayload - let { payloadBuf } = encodedPayload - const origPayloadBuf = payloadBuf - let payloadDataSz = payloadBuf.length - // Size of data payload that can be included in the first/base request - const maxExpandedSz = baseDataSz + extraDataMaxFrames * extraDataFrameSz - // Sanity checks - if (!payloadDataSz) { - throw new Error('Payload could not be handled.') - } else if ( - !genericSigning || - !extraDataFrameSz || - !extraDataMaxFrames || - !prehashAllowed - ) { - throw new Error('Unsupported. Please update your Lattice firmware.') - } else if (!existsIn(curveType, curveTypes)) { - throw new Error('Unsupported curve type.') - } else if (!existsIn(hashType, hashTypes)) { - throw new Error('Unsupported hash type.') - } + const { + signerPath, + curveType, + hashType, + encodingType = null, + decoder = null, + omitPubkey = false, + fwConstants, + blsDst = Constants.SIGNING.BLS_DST.BLS_DST_NUL, + } = req + const { + extraDataFrameSz, + extraDataMaxFrames, + prehashAllowed, + genericSigning, + varAddrPathSzAllowed, + } = fwConstants + const { + curveTypes, + encodingTypes, + hashTypes, + baseDataSz, + baseReqSz, + calldataDecoding, + } = genericSigning + const encodedPayload = getEncodedPayload( + req.payload, + encodingType, + encodingTypes, + ) + const { encoding } = encodedPayload + let { payloadBuf } = encodedPayload + const origPayloadBuf = payloadBuf + let payloadDataSz = payloadBuf.length + // Size of data payload that can be included in the first/base request + const maxExpandedSz = baseDataSz + extraDataMaxFrames * extraDataFrameSz + // Sanity checks + if (!payloadDataSz) { + throw new Error('Payload could not be handled.') + } else if ( + !genericSigning || + !extraDataFrameSz || + !extraDataMaxFrames || + !prehashAllowed + ) { + throw new Error('Unsupported. Please update your Lattice firmware.') + } else if (!existsIn(curveType, curveTypes)) { + throw new Error('Unsupported curve type.') + } else if (!existsIn(hashType, hashTypes)) { + throw new Error('Unsupported hash type.') + } - // If there is a decoder attached to our payload, add it to - // the data field of the request. - const hasDecoder = - decoder && calldataDecoding && decoder.length <= calldataDecoding.maxSz - // Make sure the payload AND decoder data fits in the firmware buffer. - // If it doesn't, we can't include the decoder because the payload will likely - // be pre-hashed and the decoder data isn't part of the message to sign. - const decoderFits = - hasDecoder && payloadBuf.length + decoder.length <= maxExpandedSz - if (hasDecoder && decoderFits) { - const decoderBuf = Buffer.alloc(8 + decoder.length) - // First write th reserved word - decoderBuf.writeUInt32LE(calldataDecoding.reserved, 0) - // Then write size, then the data - decoderBuf.writeUInt32LE(decoder.length, 4) - Buffer.from(decoder).copy(decoderBuf, 8) - payloadBuf = Buffer.concat([payloadBuf, decoderBuf]) - } + // If there is a decoder attached to our payload, add it to + // the data field of the request. + const hasDecoder = + decoder && calldataDecoding && decoder.length <= calldataDecoding.maxSz + // Make sure the payload AND decoder data fits in the firmware buffer. + // If it doesn't, we can't include the decoder because the payload will likely + // be pre-hashed and the decoder data isn't part of the message to sign. + const decoderFits = + hasDecoder && payloadBuf.length + decoder.length <= maxExpandedSz + if (hasDecoder && decoderFits) { + const decoderBuf = Buffer.alloc(8 + decoder.length) + // First write th reserved word + decoderBuf.writeUInt32LE(calldataDecoding.reserved, 0) + // Then write size, then the data + decoderBuf.writeUInt32LE(decoder.length, 4) + Buffer.from(decoder).copy(decoderBuf, 8) + payloadBuf = Buffer.concat([payloadBuf, decoderBuf]) + } - // Ed25519 specific sanity checks - if (curveType === curveTypes.ED25519) { - if (hashType !== hashTypes.NONE) { - throw new Error('Signing on ed25519 requires unhashed message') - } - signerPath.forEach((idx) => { - if (idx < HARDENED_OFFSET) { - throw new Error( - 'Signing on ed25519 requires all signer path indices be hardened.', - ) - } - }) - } - // BLS12_381 specific processing - else if (curveType === curveTypes.BLS12_381_G2) { - // For BLS signing we need to prefix 4 bytes to represent the - // domain separator (DST). If none is provided, we use the default - // value of DST_NUL. - const blsDstBuf = Buffer.alloc(4) - blsDstBuf.writeUInt32LE(blsDst) - payloadBuf = Buffer.concat([blsDstBuf, payloadBuf]) - payloadDataSz += blsDstBuf.length - } + // Ed25519 specific sanity checks + if (curveType === curveTypes.ED25519) { + if (hashType !== hashTypes.NONE) { + throw new Error('Signing on ed25519 requires unhashed message') + } + signerPath.forEach((idx) => { + if (idx < HARDENED_OFFSET) { + throw new Error( + 'Signing on ed25519 requires all signer path indices be hardened.', + ) + } + }) + } + // BLS12_381 specific processing + else if (curveType === curveTypes.BLS12_381_G2) { + // For BLS signing we need to prefix 4 bytes to represent the + // domain separator (DST). If none is provided, we use the default + // value of DST_NUL. + const blsDstBuf = Buffer.alloc(4) + blsDstBuf.writeUInt32LE(blsDst) + payloadBuf = Buffer.concat([blsDstBuf, payloadBuf]) + payloadDataSz += blsDstBuf.length + } - // Build the request buffer with metadata and then the payload to sign. - const buf = Buffer.alloc(baseReqSz) - let off = 0 - buf.writeUInt32LE(encoding, off) - off += 4 - buf.writeUInt8(hashType, off) - off += 1 - buf.writeUInt8(curveType, off) - off += 1 - const signerPathBuf = buildSignerPathBuf(signerPath, varAddrPathSzAllowed) - signerPathBuf.copy(buf, off) - off += signerPathBuf.length - buf.writeUInt8(omitPubkey ? 1 : 0, off) - off += 1 + // Build the request buffer with metadata and then the payload to sign. + const buf = Buffer.alloc(baseReqSz) + let off = 0 + buf.writeUInt32LE(encoding, off) + off += 4 + buf.writeUInt8(hashType, off) + off += 1 + buf.writeUInt8(curveType, off) + off += 1 + const signerPathBuf = buildSignerPathBuf(signerPath, varAddrPathSzAllowed) + signerPathBuf.copy(buf, off) + off += signerPathBuf.length + buf.writeUInt8(omitPubkey ? 1 : 0, off) + off += 1 - // Flow data into extraData requests if applicable - const extraDataPayloads = [] - let prehash = null + // Flow data into extraData requests if applicable + const extraDataPayloads = [] + let prehash = null - let didPrehash = false - if (payloadBuf.length > baseDataSz) { - if (prehashAllowed && payloadBuf.length > maxExpandedSz) { - // If we prehash, we need to provide the full payload size - buf.writeUInt16LE(payloadBuf.length, off) - off += 2 - didPrehash = true - // If we have to prehash, only hash the actual payload data, i.e. exclude - // any optional calldata decoder data. - const payloadData = payloadBuf.slice(0, payloadDataSz) - // If this payload is too large to send, but the Lattice allows a prehashed message, do that - if (hashType === hashTypes.NONE) { - // This cannot be done for ED25519 signing, which must sign the full message - throw new Error( - 'Message too large to send and could not be prehashed (hashType=NONE).', - ) - } else if (hashType === hashTypes.KECCAK256) { - prehash = Buffer.from(Hash.keccak256(payloadData)) - } else if (hashType === hashTypes.SHA256) { - prehash = Buffer.from(Hash.sha256(payloadData)) - } else { - throw new Error('Unsupported hash type.') - } - } else { - // Split overflow data into extraData frames - const frames = splitFrames(payloadBuf.slice(baseDataSz), extraDataFrameSz) - frames.forEach((frame) => { - const szLE = Buffer.alloc(4) - szLE.writeUInt32LE(frame.length, 0) - extraDataPayloads.push(Buffer.concat([szLE, frame])) - }) - } - } + let didPrehash = false + if (payloadBuf.length > baseDataSz) { + if (prehashAllowed && payloadBuf.length > maxExpandedSz) { + // If we prehash, we need to provide the full payload size + buf.writeUInt16LE(payloadBuf.length, off) + off += 2 + didPrehash = true + // If we have to prehash, only hash the actual payload data, i.e. exclude + // any optional calldata decoder data. + const payloadData = payloadBuf.slice(0, payloadDataSz) + // If this payload is too large to send, but the Lattice allows a prehashed message, do that + if (hashType === hashTypes.NONE) { + // This cannot be done for ED25519 signing, which must sign the full message + throw new Error( + 'Message too large to send and could not be prehashed (hashType=NONE).', + ) + } else if (hashType === hashTypes.KECCAK256) { + prehash = Buffer.from(Hash.keccak256(payloadData)) + } else if (hashType === hashTypes.SHA256) { + prehash = Buffer.from(Hash.sha256(payloadData)) + } else { + throw new Error('Unsupported hash type.') + } + } else { + // Split overflow data into extraData frames + const frames = splitFrames(payloadBuf.slice(baseDataSz), extraDataFrameSz) + frames.forEach((frame) => { + const szLE = Buffer.alloc(4) + szLE.writeUInt32LE(frame.length, 0) + extraDataPayloads.push(Buffer.concat([szLE, frame])) + }) + } + } - // If we didn't prehash, we know the full request (including calldata info) fits. - // Set the payload size to only include message data. This will inform firmware - // where to slice off calldata info. - if (!didPrehash) { - buf.writeUInt16LE(payloadDataSz, off) - off += 2 - } + // If we didn't prehash, we know the full request (including calldata info) fits. + // Set the payload size to only include message data. This will inform firmware + // where to slice off calldata info. + if (!didPrehash) { + buf.writeUInt16LE(payloadDataSz, off) + off += 2 + } - // If the message had to be prehashed, we will only copy the hash data into the request. - // Otherwise copy as many payload bytes into the request as possible. Follow up data - // from `frames` will come in follow up requests. - const toCopy = prehash ? prehash : payloadBuf - toCopy.copy(buf, off) + // If the message had to be prehashed, we will only copy the hash data into the request. + // Otherwise copy as many payload bytes into the request as possible. Follow up data + // from `frames` will come in follow up requests. + const toCopy = prehash ? prehash : payloadBuf + toCopy.copy(buf, off) - // Return all the necessary data - return { - payload: buf, - extraDataPayloads, - schema: LatticeSignSchema.generic, - curveType, - encodingType, - hashType, - omitPubkey, - origPayloadBuf, - } + // Return all the necessary data + return { + payload: buf, + extraDataPayloads, + schema: LatticeSignSchema.generic, + curveType, + encodingType, + hashType, + omitPubkey, + origPayloadBuf, + } } export const parseGenericSigningResponse = (res, off, req) => { - const parsed = { - pubkey: null, - sig: null, - } - let digestFromResponse: Buffer | undefined - // Parse BIP44 path - // Parse pubkey and then sig - if (req.curveType === Constants.SIGNING.CURVES.SECP256K1) { - // Handle `GpEccPubkey256_t` - if (!req.omitPubkey) { - const compression = res.readUInt8(off) - off += 1 - if (compression === 0x02 || compression === 0x03) { - // Compressed key - only copy x - parsed.pubkey = Buffer.alloc(33) - parsed.pubkey.writeUInt8(compression, 0) - res.slice(off, off + 32).copy(parsed.pubkey, 1) - } else if (compression === 0x04) { - // Uncompressed key - parsed.pubkey = Buffer.alloc(65) - parsed.pubkey.writeUInt8(compression, 0) - res.slice(off).copy(parsed.pubkey, 1) - } else { - throw new Error('Bad compression byte in signing response.') - } - off += 64 - } else { - // Skip pubkey section - off += 65 - } - // Handle `GpECDSASig_t` - const sigLength = 2 + res[off + 1] - const derSlice = res.slice(off, off + sigLength) - const derSig = parseDER(derSlice) - // Remove any leading zeros in signature components to ensure - // the result is a 64 byte sig - const rBuf = fixLen(derSig.r, 32) - const sBuf = fixLen(derSig.s, 32) + const parsed = { + pubkey: null, + sig: null, + } + let digestFromResponse: Buffer | undefined + // Parse BIP44 path + // Parse pubkey and then sig + if (req.curveType === Constants.SIGNING.CURVES.SECP256K1) { + // Handle `GpEccPubkey256_t` + if (!req.omitPubkey) { + const compression = res.readUInt8(off) + off += 1 + if (compression === 0x02 || compression === 0x03) { + // Compressed key - only copy x + parsed.pubkey = Buffer.alloc(33) + parsed.pubkey.writeUInt8(compression, 0) + res.slice(off, off + 32).copy(parsed.pubkey, 1) + } else if (compression === 0x04) { + // Uncompressed key + parsed.pubkey = Buffer.alloc(65) + parsed.pubkey.writeUInt8(compression, 0) + res.slice(off).copy(parsed.pubkey, 1) + } else { + throw new Error('Bad compression byte in signing response.') + } + off += 64 + } else { + // Skip pubkey section + off += 65 + } + // Handle `GpECDSASig_t` + const sigLength = 2 + res[off + 1] + const derSlice = res.slice(off, off + sigLength) + const derSig = parseDER(derSlice) + // Remove any leading zeros in signature components to ensure + // the result is a 64 byte sig + const rBuf = fixLen(derSig.r, 32) + const sBuf = fixLen(derSig.s, 32) - parsed.sig = { - r: `0x${rBuf.toString('hex')}`, - s: `0x${sBuf.toString('hex')}`, - } - off += sigLength - if (res.length >= off + 32) { - digestFromResponse = Buffer.from(res.slice(off, off + 32)) - off += 32 - } + parsed.sig = { + r: `0x${rBuf.toString('hex')}`, + s: `0x${sBuf.toString('hex')}`, + } + off += sigLength + if (res.length >= off + 32) { + digestFromResponse = Buffer.from(res.slice(off, off + 32)) + off += 32 + } - if (req.encodingType === Constants.SIGNING.ENCODINGS.EVM) { - // Full EVM transaction - use getV for proper chainId/EIP-155 handling - const vBn = getV(req.origPayloadBuf, parsed) - parsed.sig.v = BigInt(vBn.toString()) - populateViemSignedTx(parsed.sig.v, req, parsed) - } else if ( - req.hashType === Constants.SIGNING.HASHES.KECCAK256 && - req.encodingType !== Constants.SIGNING.ENCODINGS.EVM - ) { - // Generic Keccak256 message - determine if it looks like a transaction - let isTransaction = false + if (req.encodingType === Constants.SIGNING.ENCODINGS.EVM) { + // Full EVM transaction - use getV for proper chainId/EIP-155 handling + const vBn = getV(req.origPayloadBuf, parsed) + parsed.sig.v = BigInt(vBn.toString()) + populateViemSignedTx(parsed.sig.v, req, parsed) + } else if ( + req.hashType === Constants.SIGNING.HASHES.KECCAK256 && + req.encodingType !== Constants.SIGNING.ENCODINGS.EVM + ) { + // Generic Keccak256 message - determine if it looks like a transaction + let isTransaction = false - try { - let bufferToDecode = req.origPayloadBuf + try { + let bufferToDecode = req.origPayloadBuf - // Try to skip EIP-2718 type byte if present - if (bufferToDecode[0] <= 0x7f) { - bufferToDecode = bufferToDecode.slice(1) - } + // Try to skip EIP-2718 type byte if present + if (bufferToDecode[0] <= 0x7f) { + bufferToDecode = bufferToDecode.slice(1) + } - const decoded = RLP.decode(bufferToDecode) - // A legacy transaction has 9 fields (or 6 if pre-EIP155) - isTransaction = Array.isArray(decoded) && decoded.length >= 6 - } catch { - isTransaction = false - } + const decoded = RLP.decode(bufferToDecode) + // A legacy transaction has 9 fields (or 6 if pre-EIP155) + isTransaction = Array.isArray(decoded) && decoded.length >= 6 + } catch { + isTransaction = false + } - if (isTransaction) { - try { - // If it looks like a transaction, use the robust getV - const vBn = getV(req.origPayloadBuf, parsed) - parsed.sig.v = BigInt(vBn.toString()) - populateViemSignedTx(parsed.sig.v, req, parsed) - } catch (err) { - console.error( - 'Failed to get V from transaction, using fallback:', - err, - ) - // Fall back to simple recovery if getV fails (e.g., malformed RLP) - // Use the correct hash type specified in the request - const msgHash = computeMessageHash(req, digestFromResponse) - const yParity = getYParity({ - messageHash: msgHash, - signature: parsed.sig, - publicKey: parsed.pubkey, - }) - parsed.sig.v = BigInt(27 + yParity) - } - } else { - // Generic message - use simple recovery (v = 27 + recoveryId) - // Use the correct hash type specified in the request - const msgHash = computeMessageHash(req, digestFromResponse) - const yParity = getYParity({ - messageHash: msgHash, - signature: parsed.sig, - publicKey: parsed.pubkey, - }) - parsed.sig.v = BigInt(27 + yParity) - } - } - } else if (req.curveType === Constants.SIGNING.CURVES.ED25519) { - if (!req.omitPubkey) { - // Handle `GpEdDSAPubkey_t` - parsed.pubkey = Buffer.alloc(32) - res.slice(off, off + 32).copy(parsed.pubkey) - } - off += 32 - // Handle `GpEdDSASig_t` - parsed.sig = { - r: `0x${res.slice(off, off + 32).toString('hex')}`, - s: `0x${res.slice(off + 32, off + 64).toString('hex')}`, - } - off += 64 - } else if (req.curveType === Constants.SIGNING.CURVES.BLS12_381_G2) { - if (!req.omitPubkey) { - // Handle `GpBLS12_381_G1Pub_t` - parsed.pubkey = Buffer.alloc(48) - res.slice(off, off + 48).copy(parsed.pubkey) - } - off += 48 - // Handle `GpBLS12_381_G2Sig_t` - parsed.sig = Buffer.alloc(96) - res.slice(off, off + 96).copy(parsed.sig) - off += 96 - } else { - throw new Error('Unsupported curve.') - } - return parsed + if (isTransaction) { + try { + // If it looks like a transaction, use the robust getV + const vBn = getV(req.origPayloadBuf, parsed) + parsed.sig.v = BigInt(vBn.toString()) + populateViemSignedTx(parsed.sig.v, req, parsed) + } catch (err) { + console.error( + 'Failed to get V from transaction, using fallback:', + err, + ) + // Fall back to simple recovery if getV fails (e.g., malformed RLP) + // Use the correct hash type specified in the request + const msgHash = computeMessageHash(req, digestFromResponse) + const yParity = getYParity({ + messageHash: msgHash, + signature: parsed.sig, + publicKey: parsed.pubkey, + }) + parsed.sig.v = BigInt(27 + yParity) + } + } else { + // Generic message - use simple recovery (v = 27 + recoveryId) + // Use the correct hash type specified in the request + const msgHash = computeMessageHash(req, digestFromResponse) + const yParity = getYParity({ + messageHash: msgHash, + signature: parsed.sig, + publicKey: parsed.pubkey, + }) + parsed.sig.v = BigInt(27 + yParity) + } + } + } else if (req.curveType === Constants.SIGNING.CURVES.ED25519) { + if (!req.omitPubkey) { + // Handle `GpEdDSAPubkey_t` + parsed.pubkey = Buffer.alloc(32) + res.slice(off, off + 32).copy(parsed.pubkey) + } + off += 32 + // Handle `GpEdDSASig_t` + parsed.sig = { + r: `0x${res.slice(off, off + 32).toString('hex')}`, + s: `0x${res.slice(off + 32, off + 64).toString('hex')}`, + } + off += 64 + } else if (req.curveType === Constants.SIGNING.CURVES.BLS12_381_G2) { + if (!req.omitPubkey) { + // Handle `GpBLS12_381_G1Pub_t` + parsed.pubkey = Buffer.alloc(48) + res.slice(off, off + 48).copy(parsed.pubkey) + } + off += 48 + // Handle `GpBLS12_381_G2Sig_t` + parsed.sig = Buffer.alloc(96) + res.slice(off, off + 96).copy(parsed.sig) + off += 96 + } else { + throw new Error('Unsupported curve.') + } + return parsed } function computeMessageHash( - req: { - hashType: number - origPayloadBuf: Buffer - }, - digestFromResponse?: Buffer, + req: { + hashType: number + origPayloadBuf: Buffer + }, + digestFromResponse?: Buffer, ): Buffer { - if ( - digestFromResponse && - digestFromResponse.length === 32 && - digestFromResponse.some((byte) => byte !== 0) - ) { - return digestFromResponse - } - if (req.hashType === Constants.SIGNING.HASHES.SHA256) { - return Buffer.from(Hash.sha256(req.origPayloadBuf)) - } - if (req.hashType === Constants.SIGNING.HASHES.KECCAK256) { - return Buffer.from(Hash.keccak256(req.origPayloadBuf)) - } - throw new Error('Unsupported hash type for message hash computation.') + if ( + digestFromResponse && + digestFromResponse.length === 32 && + digestFromResponse.some((byte) => byte !== 0) + ) { + return digestFromResponse + } + if (req.hashType === Constants.SIGNING.HASHES.SHA256) { + return Buffer.from(Hash.sha256(req.origPayloadBuf)) + } + if (req.hashType === Constants.SIGNING.HASHES.KECCAK256) { + return Buffer.from(Hash.keccak256(req.origPayloadBuf)) + } + throw new Error('Unsupported hash type for message hash computation.') } // Reconstruct a viem-compatible signed transaction string from the raw payload and // recovered signature so consumers can compare or broadcast without extra parsing. function populateViemSignedTx( - sigV: bigint, - req: any, - parsed: { sig: { r: string; s: string; v?: bigint }; viemTx?: string }, + sigV: bigint, + req: any, + parsed: { sig: { r: string; s: string; v?: bigint }; viemTx?: string }, ) { - if (req.encodingType !== Constants.SIGNING.ENCODINGS.EVM) return + if (req.encodingType !== Constants.SIGNING.ENCODINGS.EVM) return - try { - const rawTxHex = `0x${req.origPayloadBuf.toString('hex')}` as Hex - const parsedTx: any = parseTransaction(rawTxHex) + try { + const rawTxHex = `0x${req.origPayloadBuf.toString('hex')}` as Hex + const parsedTx: any = parseTransaction(rawTxHex) - const baseTx: any = { - chainId: parsedTx.chainId, - to: parsedTx.to ?? undefined, - value: parsedTx.value ?? 0n, - data: (parsedTx.data ?? '0x') as Hex, - nonce: parsedTx.nonce ?? 0n, - gas: parsedTx.gas ?? parsedTx.gasLimit ?? 0n, - } + const baseTx: any = { + chainId: parsedTx.chainId, + to: parsedTx.to ?? undefined, + value: parsedTx.value ?? 0n, + data: (parsedTx.data ?? '0x') as Hex, + nonce: parsedTx.nonce ?? 0n, + gas: parsedTx.gas ?? parsedTx.gasLimit ?? 0n, + } - if (parsedTx.maxFeePerGas !== undefined) { - baseTx.maxFeePerGas = parsedTx.maxFeePerGas - } - if (parsedTx.maxPriorityFeePerGas !== undefined) { - baseTx.maxPriorityFeePerGas = parsedTx.maxPriorityFeePerGas - } - if (parsedTx.gasPrice !== undefined) { - baseTx.gasPrice = parsedTx.gasPrice - } - if (parsedTx.accessList !== undefined) { - baseTx.accessList = parsedTx.accessList - } - if (parsedTx.authorizationList !== undefined) { - baseTx.authorizationList = parsedTx.authorizationList - } + if (parsedTx.maxFeePerGas !== undefined) { + baseTx.maxFeePerGas = parsedTx.maxFeePerGas + } + if (parsedTx.maxPriorityFeePerGas !== undefined) { + baseTx.maxPriorityFeePerGas = parsedTx.maxPriorityFeePerGas + } + if (parsedTx.gasPrice !== undefined) { + baseTx.gasPrice = parsedTx.gasPrice + } + if (parsedTx.accessList !== undefined) { + baseTx.accessList = parsedTx.accessList + } + if (parsedTx.authorizationList !== undefined) { + baseTx.authorizationList = parsedTx.authorizationList + } - if (parsedTx.type !== undefined && parsedTx.type !== null) { - baseTx.type = parsedTx.type - } + if (parsedTx.type !== undefined && parsedTx.type !== null) { + baseTx.type = parsedTx.type + } - const signature = - parsedTx.type === 'legacy' || parsedTx.type === undefined - ? { - v: sigV, - r: parsed.sig.r as Hex, - s: parsed.sig.s as Hex, - } - : { - yParity: Number(sigV), - r: parsed.sig.r as Hex, - s: parsed.sig.s as Hex, - } + const signature = + parsedTx.type === 'legacy' || parsedTx.type === undefined + ? { + v: sigV, + r: parsed.sig.r as Hex, + s: parsed.sig.s as Hex, + } + : { + yParity: Number(sigV), + r: parsed.sig.r as Hex, + s: parsed.sig.s as Hex, + } - parsed.viemTx = serializeTransaction( - baseTx as TransactionSerializable, - signature as any, - ) - } catch (_err) { - console.debug('Failed to build viemTx from response', _err) - } + parsed.viemTx = serializeTransaction( + baseTx as TransactionSerializable, + signature as any, + ) + } catch (_err) { + console.debug('Failed to build viemTx from response', _err) + } } export const getEncodedPayload = (payload, encoding, allowedEncodings) => { - if (!encoding) { - encoding = Constants.SIGNING.ENCODINGS.NONE - } - // Make sure the encoding type specified is supported by firmware - if (!existsIn(encoding, allowedEncodings)) { - throw new Error( - 'Encoding not supported by Lattice firmware. You may want to update.', - ) - } - let payloadBuf: Buffer - if (!payload) { - throw new Error('No payload included') - } - if (typeof payload === 'string' && payload.slice(0, 2) === '0x') { - payloadBuf = Buffer.from(payload.slice(2), 'hex') - } else { - payloadBuf = Buffer.from(payload) - } - // Build the request with the specified encoding type - return { - payloadBuf, - encoding, - } + if (!encoding) { + encoding = Constants.SIGNING.ENCODINGS.NONE + } + // Make sure the encoding type specified is supported by firmware + if (!existsIn(encoding, allowedEncodings)) { + throw new Error( + 'Encoding not supported by Lattice firmware. You may want to update.', + ) + } + let payloadBuf: Buffer + if (!payload) { + throw new Error('No payload included') + } + if (typeof payload === 'string' && payload.slice(0, 2) === '0x') { + payloadBuf = Buffer.from(payload.slice(2), 'hex') + } else { + payloadBuf = Buffer.from(payload) + } + // Build the request with the specified encoding type + return { + payloadBuf, + encoding, + } } diff --git a/packages/sdk/src/protocol/latticeConstants.ts b/packages/sdk/src/protocol/latticeConstants.ts index 7304a34d..e2e8e351 100644 --- a/packages/sdk/src/protocol/latticeConstants.ts +++ b/packages/sdk/src/protocol/latticeConstants.ts @@ -1,204 +1,204 @@ export enum LatticeResponseCode { - success = 0x00, - invalidMsg = 0x80, - unsupportedVersion = 0x81, - deviceBusy = 0x82, - userTimeout = 0x83, - userDeclined = 0x84, - pairFailed = 0x85, - pairDisabled = 0x86, - permissionDisabled = 0x87, - internalError = 0x88, - gceTimeout = 0x89, - wrongWallet = 0x8a, - deviceLocked = 0x8b, - disabled = 0x8c, - already = 0x8d, - invalidEphemId = 0x8e, + success = 0x00, + invalidMsg = 0x80, + unsupportedVersion = 0x81, + deviceBusy = 0x82, + userTimeout = 0x83, + userDeclined = 0x84, + pairFailed = 0x85, + pairDisabled = 0x86, + permissionDisabled = 0x87, + internalError = 0x88, + gceTimeout = 0x89, + wrongWallet = 0x8a, + deviceLocked = 0x8b, + disabled = 0x8c, + already = 0x8d, + invalidEphemId = 0x8e, } export enum LatticeSecureMsgType { - connect = 0x01, - encrypted = 0x02, + connect = 0x01, + encrypted = 0x02, } export enum LatticeProtocolVersion { - v1 = 0x01, + v1 = 0x01, } export enum LatticeMsgType { - response = 0x00, - secure = 0x02, + response = 0x00, + secure = 0x02, } export enum LatticeSecureEncryptedRequestType { - finalizePairing = 0, - getAddresses = 1, - sign = 3, - getWallets = 4, - getKvRecords = 7, - addKvRecords = 8, - removeKvRecords = 9, - fetchEncryptedData = 12, - test = 13, + finalizePairing = 0, + getAddresses = 1, + sign = 3, + getWallets = 4, + getKvRecords = 7, + addKvRecords = 8, + removeKvRecords = 9, + fetchEncryptedData = 12, + test = 13, } export enum LatticeGetAddressesFlag { - none = 0, // For formatted addresses - secp256k1Pubkey = 3, - ed25519Pubkey = 4, - bls12_381Pubkey = 5, - secp256k1Xpub = 6, // For Bitcoin XPUB + none = 0, // For formatted addresses + secp256k1Pubkey = 3, + ed25519Pubkey = 4, + bls12_381Pubkey = 5, + secp256k1Xpub = 6, // For Bitcoin XPUB } export enum LatticeSignSchema { - bitcoin = 0, - ethereum = 1, // Deprecated - ethereumMsg = 3, - extraData = 4, - generic = 5, + bitcoin = 0, + ethereum = 1, // Deprecated + ethereumMsg = 3, + extraData = 4, + generic = 5, } export enum LatticeSignHash { - none = 0, - keccak256 = 1, - sha256 = 2, + none = 0, + keccak256 = 1, + sha256 = 2, } export enum LatticeSignCurve { - secp256k1 = 0, - ed25519 = 1, - bls12_381 = 2, + secp256k1 = 0, + ed25519 = 1, + bls12_381 = 2, } export enum LatticeSignEncoding { - none = 1, - solana = 2, - evm = 4, - eth_deposit = 5, - eip7702_auth = 6, - eip7702_auth_list = 7, + none = 1, + solana = 2, + evm = 4, + eth_deposit = 5, + eip7702_auth = 6, + eip7702_auth_list = 7, } export enum LatticeSignBlsDst { - NUL = 1, - POP = 2, + NUL = 1, + POP = 2, } export enum LatticeEncDataSchema { - eip2335 = 0, + eip2335 = 0, } export const ProtocolConstants = { - // Lattice firmware uses a static initialization vector for - // message encryption/decryption. This is generally considered - // fine because each encryption/decryption uses a unique encryption - // secret (derived from the per-message ephemeral key pair). - aesIv: [ - 0x6d, 0x79, 0x73, 0x65, 0x63, 0x72, 0x65, 0x74, 0x70, 0x61, 0x73, 0x73, - 0x77, 0x6f, 0x72, 0x64, - ], - // Constant size of address buffers from the Lattice. - // Note that this size also captures public keys returned - // by the Lattice (addresses = strings, pubkeys = buffers) - addrStrLen: 129, - // Status of the client's pairing with the target Lattice - pairingStatus: { - notPaired: 0x00, - paired: 0x01, - }, - // Response types, codes, and error messages - responseMsg: { - [LatticeResponseCode.success]: '', - [LatticeResponseCode.invalidMsg]: 'Invalid Request', - [LatticeResponseCode.unsupportedVersion]: 'Unsupported Version', - [LatticeResponseCode.deviceBusy]: 'Device Busy', - [LatticeResponseCode.userTimeout]: 'Timeout waiting for user', - [LatticeResponseCode.userDeclined]: 'Request declined by user', - [LatticeResponseCode.pairFailed]: 'Pairing failed', - [LatticeResponseCode.pairDisabled]: 'Pairing is currently disabled', - [LatticeResponseCode.permissionDisabled]: - 'Automated signing is currently disabled', - [LatticeResponseCode.internalError]: 'Device Error', - [LatticeResponseCode.gceTimeout]: 'Device Timeout', - [LatticeResponseCode.wrongWallet]: 'Active wallet does not match request', - [LatticeResponseCode.deviceLocked]: 'Device Locked', - [LatticeResponseCode.disabled]: 'Feature Disabled', - [LatticeResponseCode.already]: 'Record already exists on device', - [LatticeResponseCode.invalidEphemId]: 'Request failed - needs resync', - }, - msgSizes: { - // General message header size. Valid for all Lattice messages - header: 8, - // Checksum must be appended to each message - checksum: 4, - // Lattice secure message constants. All requests from this SDK - // are secure messages. - secure: { - // Sizes of full payloads for secure messages - payload: { - request: { - // [ requestType (1 byte) | pubkey (65 bytes) ] - connect: 66, - // [ requestType (1 byte) | ephemeralId (4 bytes) | encryptedData (1728 bytes) ] - encrypted: 1733, - }, - // Note that the response payload always has status code as the - // first byte. This byte is removed as part of `request`, inside - // `parseLattice1Response`. These constants include the status - // code byte. - response: { - connect: 215, - // Encrypted responses are as follows: - // encryptedData (1728) | empty (1728) - // The latter half is empty due to an invalid type definition - // in Lattice firmware. (Someone made a C `struct` instead of - // a `union`, oops). - encrypted: 3457, - }, - }, - // Sizes for data inside secure message payloads - data: { - // All requests also have a `requestCode`, which is omitted - // from these constants. - request: { - connect: 65, - encrypted: { - // All encrypted requests are encrypted into a 1728 byte buffer - encryptedData: 1728, - // Individual request types have different data sizes. - [LatticeSecureEncryptedRequestType.finalizePairing]: 99, - [LatticeSecureEncryptedRequestType.getAddresses]: 54, - [LatticeSecureEncryptedRequestType.sign]: 1680, - [LatticeSecureEncryptedRequestType.getWallets]: 0, - [LatticeSecureEncryptedRequestType.getKvRecords]: 9, - [LatticeSecureEncryptedRequestType.addKvRecords]: 1391, - [LatticeSecureEncryptedRequestType.removeKvRecords]: 405, - [LatticeSecureEncryptedRequestType.fetchEncryptedData]: 1025, - [LatticeSecureEncryptedRequestType.test]: 506, - }, - }, - // All responses also have a `responseCode`, which is omitted - // from these constants. - response: { - encrypted: { - encryptedData: 1728, - // Once decrypted, the data size of the response - // payload will be determined by the request type. - // NOTE: All requests also have ephemeralPublicKey (65 bytes) and - // checksum (4 bytes), which are excluded from these sizes. - [LatticeSecureEncryptedRequestType.finalizePairing]: 0, - [LatticeSecureEncryptedRequestType.getAddresses]: 1290, - [LatticeSecureEncryptedRequestType.sign]: 1090, - [LatticeSecureEncryptedRequestType.getWallets]: 142, - [LatticeSecureEncryptedRequestType.getKvRecords]: 1395, - [LatticeSecureEncryptedRequestType.addKvRecords]: 0, - [LatticeSecureEncryptedRequestType.removeKvRecords]: 0, - [LatticeSecureEncryptedRequestType.fetchEncryptedData]: 1608, - [LatticeSecureEncryptedRequestType.test]: 1646, - }, - }, - }, - }, - }, + // Lattice firmware uses a static initialization vector for + // message encryption/decryption. This is generally considered + // fine because each encryption/decryption uses a unique encryption + // secret (derived from the per-message ephemeral key pair). + aesIv: [ + 0x6d, 0x79, 0x73, 0x65, 0x63, 0x72, 0x65, 0x74, 0x70, 0x61, 0x73, 0x73, + 0x77, 0x6f, 0x72, 0x64, + ], + // Constant size of address buffers from the Lattice. + // Note that this size also captures public keys returned + // by the Lattice (addresses = strings, pubkeys = buffers) + addrStrLen: 129, + // Status of the client's pairing with the target Lattice + pairingStatus: { + notPaired: 0x00, + paired: 0x01, + }, + // Response types, codes, and error messages + responseMsg: { + [LatticeResponseCode.success]: '', + [LatticeResponseCode.invalidMsg]: 'Invalid Request', + [LatticeResponseCode.unsupportedVersion]: 'Unsupported Version', + [LatticeResponseCode.deviceBusy]: 'Device Busy', + [LatticeResponseCode.userTimeout]: 'Timeout waiting for user', + [LatticeResponseCode.userDeclined]: 'Request declined by user', + [LatticeResponseCode.pairFailed]: 'Pairing failed', + [LatticeResponseCode.pairDisabled]: 'Pairing is currently disabled', + [LatticeResponseCode.permissionDisabled]: + 'Automated signing is currently disabled', + [LatticeResponseCode.internalError]: 'Device Error', + [LatticeResponseCode.gceTimeout]: 'Device Timeout', + [LatticeResponseCode.wrongWallet]: 'Active wallet does not match request', + [LatticeResponseCode.deviceLocked]: 'Device Locked', + [LatticeResponseCode.disabled]: 'Feature Disabled', + [LatticeResponseCode.already]: 'Record already exists on device', + [LatticeResponseCode.invalidEphemId]: 'Request failed - needs resync', + }, + msgSizes: { + // General message header size. Valid for all Lattice messages + header: 8, + // Checksum must be appended to each message + checksum: 4, + // Lattice secure message constants. All requests from this SDK + // are secure messages. + secure: { + // Sizes of full payloads for secure messages + payload: { + request: { + // [ requestType (1 byte) | pubkey (65 bytes) ] + connect: 66, + // [ requestType (1 byte) | ephemeralId (4 bytes) | encryptedData (1728 bytes) ] + encrypted: 1733, + }, + // Note that the response payload always has status code as the + // first byte. This byte is removed as part of `request`, inside + // `parseLattice1Response`. These constants include the status + // code byte. + response: { + connect: 215, + // Encrypted responses are as follows: + // encryptedData (1728) | empty (1728) + // The latter half is empty due to an invalid type definition + // in Lattice firmware. (Someone made a C `struct` instead of + // a `union`, oops). + encrypted: 3457, + }, + }, + // Sizes for data inside secure message payloads + data: { + // All requests also have a `requestCode`, which is omitted + // from these constants. + request: { + connect: 65, + encrypted: { + // All encrypted requests are encrypted into a 1728 byte buffer + encryptedData: 1728, + // Individual request types have different data sizes. + [LatticeSecureEncryptedRequestType.finalizePairing]: 99, + [LatticeSecureEncryptedRequestType.getAddresses]: 54, + [LatticeSecureEncryptedRequestType.sign]: 1680, + [LatticeSecureEncryptedRequestType.getWallets]: 0, + [LatticeSecureEncryptedRequestType.getKvRecords]: 9, + [LatticeSecureEncryptedRequestType.addKvRecords]: 1391, + [LatticeSecureEncryptedRequestType.removeKvRecords]: 405, + [LatticeSecureEncryptedRequestType.fetchEncryptedData]: 1025, + [LatticeSecureEncryptedRequestType.test]: 506, + }, + }, + // All responses also have a `responseCode`, which is omitted + // from these constants. + response: { + encrypted: { + encryptedData: 1728, + // Once decrypted, the data size of the response + // payload will be determined by the request type. + // NOTE: All requests also have ephemeralPublicKey (65 bytes) and + // checksum (4 bytes), which are excluded from these sizes. + [LatticeSecureEncryptedRequestType.finalizePairing]: 0, + [LatticeSecureEncryptedRequestType.getAddresses]: 1290, + [LatticeSecureEncryptedRequestType.sign]: 1090, + [LatticeSecureEncryptedRequestType.getWallets]: 142, + [LatticeSecureEncryptedRequestType.getKvRecords]: 1395, + [LatticeSecureEncryptedRequestType.addKvRecords]: 0, + [LatticeSecureEncryptedRequestType.removeKvRecords]: 0, + [LatticeSecureEncryptedRequestType.fetchEncryptedData]: 1608, + [LatticeSecureEncryptedRequestType.test]: 1646, + }, + }, + }, + }, + }, } as const diff --git a/packages/sdk/src/protocol/secureMessages.ts b/packages/sdk/src/protocol/secureMessages.ts index 58af96ba..3d514d75 100644 --- a/packages/sdk/src/protocol/secureMessages.ts +++ b/packages/sdk/src/protocol/secureMessages.ts @@ -1,20 +1,20 @@ import { getEphemeralId, request } from '../shared/functions' import { validateEphemeralPub } from '../shared/validators' import type { - DecryptedResponse, - KeyPair, - LatticeMessageHeader, - LatticeSecureConnectRequestPayloadData, - LatticeSecureDecryptedResponse, - LatticeSecureRequest, - LatticeSecureRequestPayload, + DecryptedResponse, + KeyPair, + LatticeMessageHeader, + LatticeSecureConnectRequestPayloadData, + LatticeSecureDecryptedResponse, + LatticeSecureRequest, + LatticeSecureRequestPayload, } from '../types' import { - aes256_decrypt, - aes256_encrypt, - checksum, - getP256KeyPairFromPub, - randomBytes, + aes256_decrypt, + aes256_encrypt, + checksum, + getP256KeyPairFromPub, + randomBytes, } from '../util' /** * All messages sent to the Lattice from this SDK will be @@ -37,11 +37,11 @@ import { * key, which you will need for the next encrypted request. */ import { - ProtocolConstants as Constants, - LatticeMsgType, - LatticeProtocolVersion, - type LatticeSecureEncryptedRequestType, - LatticeSecureMsgType, + ProtocolConstants as Constants, + LatticeMsgType, + LatticeProtocolVersion, + type LatticeSecureEncryptedRequestType, + LatticeSecureMsgType, } from './latticeConstants' const { msgSizes } = Constants @@ -56,29 +56,29 @@ const { secure: szs } = msgSizes * information about the connected Lattice. */ export async function connectSecureRequest({ - url, - pubkey, + url, + pubkey, }: { - url: string - pubkey: Buffer + url: string + pubkey: Buffer }): Promise { - // Build the secure request message - const payloadData = serializeSecureRequestConnectPayloadData({ - pubkey: pubkey, - }) - const msgId = randomBytes(4) - const msg = serializeSecureRequestMsg( - msgId, - LatticeSecureMsgType.connect, - payloadData, - ) - // Send request to the Lattice - const resp = await request({ url, payload: msg }) - if (resp.length !== szs.payload.response.connect - 1) { - throw new Error('Wrong Lattice response message size.') - } + // Build the secure request message + const payloadData = serializeSecureRequestConnectPayloadData({ + pubkey: pubkey, + }) + const msgId = randomBytes(4) + const msg = serializeSecureRequestMsg( + msgId, + LatticeSecureMsgType.connect, + payloadData, + ) + // Send request to the Lattice + const resp = await request({ url, payload: msg }) + if (resp.length !== szs.payload.response.connect - 1) { + throw new Error('Wrong Lattice response message size.') + } - return resp + return resp } /** @@ -93,61 +93,61 @@ export async function connectSecureRequest({ * @return {Buffer} Decrypted response data (excluding metadata) */ export async function encryptedSecureRequest({ - data, - requestType, - sharedSecret, - ephemeralPub, - url, + data, + requestType, + sharedSecret, + ephemeralPub, + url, }: { - data: Buffer - requestType: LatticeSecureEncryptedRequestType - sharedSecret: Buffer - ephemeralPub: KeyPair - url: string + data: Buffer + requestType: LatticeSecureEncryptedRequestType + sharedSecret: Buffer + ephemeralPub: KeyPair + url: string }): Promise { - // Generate a random message id for internal tracking - // of this specific request (internal on both sides). - const msgId = randomBytes(4) + // Generate a random message id for internal tracking + // of this specific request (internal on both sides). + const msgId = randomBytes(4) - // Serialize the request data into encrypted request - // payload data. - const payloadData = serializeSecureRequestEncryptedPayloadData({ - data, - requestType, - ephemeralPub, - sharedSecret, - }) + // Serialize the request data into encrypted request + // payload data. + const payloadData = serializeSecureRequestEncryptedPayloadData({ + data, + requestType, + ephemeralPub, + sharedSecret, + }) - // Serialize the payload data into an encrypted secure - // request message. - const msg = serializeSecureRequestMsg( - msgId, - LatticeSecureMsgType.encrypted, - payloadData, - ) + // Serialize the payload data into an encrypted secure + // request message. + const msg = serializeSecureRequestMsg( + msgId, + LatticeSecureMsgType.encrypted, + payloadData, + ) - // Send request to Lattice - const resp = await request({ - url, - payload: msg, - }) + // Send request to Lattice + const resp = await request({ + url, + payload: msg, + }) - // Deserialize the response payload data - if (resp.length !== szs.payload.response.encrypted - 1) { - throw new Error('Wrong Lattice response message size.') - } + // Deserialize the response payload data + if (resp.length !== szs.payload.response.encrypted - 1) { + throw new Error('Wrong Lattice response message size.') + } - const encPayloadData = resp.slice( - 0, - szs.data.response.encrypted.encryptedData, - ) + const encPayloadData = resp.slice( + 0, + szs.data.response.encrypted.encryptedData, + ) - // Return decrypted response payload data - return decryptEncryptedLatticeResponseData({ - encPayloadData, - requestType, - sharedSecret, - }) + // Return decrypted response payload data + return decryptEncryptedLatticeResponseData({ + encPayloadData, + requestType, + sharedSecret, + }) } /** @@ -160,86 +160,86 @@ export async function encryptedSecureRequest({ * @return {Buffer} Serialized message to be sent to Lattice */ function serializeSecureRequestMsg( - msgId: Buffer, - secureRequestType: LatticeSecureMsgType, - payloadData: Buffer, + msgId: Buffer, + secureRequestType: LatticeSecureMsgType, + payloadData: Buffer, ): Buffer { - // Sanity check request data - if (msgId.length !== 4) { - throw new Error('msgId must be four bytes') - } - if ( - secureRequestType !== LatticeSecureMsgType.connect && - secureRequestType !== LatticeSecureMsgType.encrypted - ) { - throw new Error('Invalid Lattice secure request type') - } + // Sanity check request data + if (msgId.length !== 4) { + throw new Error('msgId must be four bytes') + } + if ( + secureRequestType !== LatticeSecureMsgType.connect && + secureRequestType !== LatticeSecureMsgType.encrypted + ) { + throw new Error('Invalid Lattice secure request type') + } - // Validate the incoming payload data size. Note that the payload - // data is prepended with a secure request type byte, so the - // payload data size is one less than the expected size. - const isValidConnectPayloadDataSz = - secureRequestType === LatticeSecureMsgType.connect && - payloadData.length === szs.payload.request.connect - 1 - const isValidEncryptedPayloadDataSz = - secureRequestType === LatticeSecureMsgType.encrypted && - payloadData.length === szs.payload.request.encrypted - 1 + // Validate the incoming payload data size. Note that the payload + // data is prepended with a secure request type byte, so the + // payload data size is one less than the expected size. + const isValidConnectPayloadDataSz = + secureRequestType === LatticeSecureMsgType.connect && + payloadData.length === szs.payload.request.connect - 1 + const isValidEncryptedPayloadDataSz = + secureRequestType === LatticeSecureMsgType.encrypted && + payloadData.length === szs.payload.request.encrypted - 1 - // Build payload and size - let msgSz = msgSizes.header + msgSizes.checksum - let payloadLen: number - const payload: LatticeSecureRequestPayload = { - requestType: secureRequestType, - data: payloadData, - } - if (isValidConnectPayloadDataSz) { - payloadLen = szs.payload.request.connect - } else if (isValidEncryptedPayloadDataSz) { - payloadLen = szs.payload.request.encrypted - } else { - throw new Error('Invalid Lattice secure request payload size') - } - msgSz += payloadLen + // Build payload and size + let msgSz = msgSizes.header + msgSizes.checksum + let payloadLen: number + const payload: LatticeSecureRequestPayload = { + requestType: secureRequestType, + data: payloadData, + } + if (isValidConnectPayloadDataSz) { + payloadLen = szs.payload.request.connect + } else if (isValidEncryptedPayloadDataSz) { + payloadLen = szs.payload.request.encrypted + } else { + throw new Error('Invalid Lattice secure request payload size') + } + msgSz += payloadLen - // Construct the request in object form - const header: LatticeMessageHeader = { - version: LatticeProtocolVersion.v1, - type: LatticeMsgType.secure, - id: msgId, - len: payloadLen, - } - const req: LatticeSecureRequest = { - header, - payload, - } + // Construct the request in object form + const header: LatticeMessageHeader = { + version: LatticeProtocolVersion.v1, + type: LatticeMsgType.secure, + id: msgId, + len: payloadLen, + } + const req: LatticeSecureRequest = { + header, + payload, + } - // Now serialize the whole message - // Header | requestType | payloadData | checksum - const msg = Buffer.alloc(msgSz) - let off = 0 - // Header - msg.writeUInt8(req.header.version, off) - off += 1 - msg.writeUInt8(req.header.type, off) - off += 1 - req.header.id.copy(msg, off) - off += req.header.id.length - msg.writeUInt16BE(req.header.len, off) - off += 2 - // Payload - msg.writeUInt8(req.payload.requestType, off) - off += 1 - req.payload.data.copy(msg, off) - off += req.payload.data.length - // Checksum - msg.writeUInt32BE(checksum(msg.slice(0, off)), off) - off += 4 - if (off !== msgSz) { - throw new Error('Failed to build request message') - } + // Now serialize the whole message + // Header | requestType | payloadData | checksum + const msg = Buffer.alloc(msgSz) + let off = 0 + // Header + msg.writeUInt8(req.header.version, off) + off += 1 + msg.writeUInt8(req.header.type, off) + off += 1 + req.header.id.copy(msg, off) + off += req.header.id.length + msg.writeUInt16BE(req.header.len, off) + off += 2 + // Payload + msg.writeUInt8(req.payload.requestType, off) + off += 1 + req.payload.data.copy(msg, off) + off += req.payload.data.length + // Checksum + msg.writeUInt32BE(checksum(msg.slice(0, off)), off) + off += 4 + if (off !== msgSz) { + throw new Error('Failed to build request message') + } - // We have our serialized secure message! - return msg + // We have our serialized secure message! + return msg } /** @@ -248,11 +248,11 @@ function serializeSecureRequestMsg( * @return {Buffer} - 1700 bytes, of which only 65 are used */ function serializeSecureRequestConnectPayloadData( - payloadData: LatticeSecureConnectRequestPayloadData, + payloadData: LatticeSecureConnectRequestPayloadData, ): Buffer { - const serPayloadData = Buffer.alloc(szs.data.request.connect) - payloadData.pubkey.copy(serPayloadData, 0) - return serPayloadData + const serPayloadData = Buffer.alloc(szs.data.request.connect) + payloadData.pubkey.copy(serPayloadData, 0) + return serPayloadData } /** @@ -262,57 +262,57 @@ function serializeSecureRequestConnectPayloadData( * @return {Buffer} - 1700 bytes, all of which should be used */ function serializeSecureRequestEncryptedPayloadData({ - data, - requestType, - ephemeralPub, - sharedSecret, + data, + requestType, + ephemeralPub, + sharedSecret, }: { - data: Buffer - requestType: LatticeSecureEncryptedRequestType - ephemeralPub: KeyPair - sharedSecret: Buffer + data: Buffer + requestType: LatticeSecureEncryptedRequestType + ephemeralPub: KeyPair + sharedSecret: Buffer }): Buffer { - // Sanity checks request size - if (data.length > szs.data.request.encrypted.encryptedData) { - throw new Error('Encrypted request data too large') - } - // Make sure we have a shared secret. An error will be thrown - // if there is no ephemeral pub, indicating we need to reconnect. - validateEphemeralPub(ephemeralPub) + // Sanity checks request size + if (data.length > szs.data.request.encrypted.encryptedData) { + throw new Error('Encrypted request data too large') + } + // Make sure we have a shared secret. An error will be thrown + // if there is no ephemeral pub, indicating we need to reconnect. + validateEphemeralPub(ephemeralPub) - // Validate the request data size matches the desired request - const requestDataSize = szs.data.request.encrypted[requestType] - if (data.length !== requestDataSize) { - throw new Error( - `Invalid request datasize (wanted ${requestDataSize}, got ${data.length})`, - ) - } + // Validate the request data size matches the desired request + const requestDataSize = szs.data.request.encrypted[requestType] + if (data.length !== requestDataSize) { + throw new Error( + `Invalid request datasize (wanted ${requestDataSize}, got ${data.length})`, + ) + } - // Build the pre-encrypted data payload, which variable sized and of form: - // encryptedRequestType | data | checksum - const preEncryptedData = Buffer.alloc(1 + requestDataSize) - preEncryptedData[0] = requestType - data.copy(preEncryptedData, 1) - const preEncryptedDataChecksum = checksum(preEncryptedData) + // Build the pre-encrypted data payload, which variable sized and of form: + // encryptedRequestType | data | checksum + const preEncryptedData = Buffer.alloc(1 + requestDataSize) + preEncryptedData[0] = requestType + data.copy(preEncryptedData, 1) + const preEncryptedDataChecksum = checksum(preEncryptedData) - // Encrypt the data into a fixed size buffer. The buffer size should - // equal to the full message request less the 4-byte ephemeral id. - const _encryptedData = Buffer.alloc(szs.data.request.encrypted.encryptedData) - preEncryptedData.copy(_encryptedData, 0) - _encryptedData.writeUInt32LE( - preEncryptedDataChecksum, - preEncryptedData.length, - ) - const encryptedData = aes256_encrypt(_encryptedData, sharedSecret) + // Encrypt the data into a fixed size buffer. The buffer size should + // equal to the full message request less the 4-byte ephemeral id. + const _encryptedData = Buffer.alloc(szs.data.request.encrypted.encryptedData) + preEncryptedData.copy(_encryptedData, 0) + _encryptedData.writeUInt32LE( + preEncryptedDataChecksum, + preEncryptedData.length, + ) + const encryptedData = aes256_encrypt(_encryptedData, sharedSecret) - // Calculate ephemeral ID - const ephemeralId = getEphemeralId(sharedSecret) + // Calculate ephemeral ID + const ephemeralId = getEphemeralId(sharedSecret) - // Now we will serialize the payload data. - const serPayloadData = Buffer.alloc(szs.payload.request.encrypted - 1) - serPayloadData.writeUInt32LE(ephemeralId) - encryptedData.copy(serPayloadData, 4) - return serPayloadData + // Now we will serialize the payload data. + const serPayloadData = Buffer.alloc(szs.payload.request.encrypted - 1) + serPayloadData.writeUInt32LE(ephemeralId) + encryptedData.copy(serPayloadData, 4) + return serPayloadData } /** @@ -322,41 +322,41 @@ function serializeSecureRequestEncryptedPayloadData({ * @return {Buffer} Decrypted response data (excluding metadata) */ function decryptEncryptedLatticeResponseData({ - encPayloadData, - requestType, - sharedSecret, + encPayloadData, + requestType, + sharedSecret, }: { - encPayloadData: Buffer - requestType: LatticeSecureEncryptedRequestType - sharedSecret: Buffer + encPayloadData: Buffer + requestType: LatticeSecureEncryptedRequestType + sharedSecret: Buffer }) { - // Decrypt data using the *current* shared secret - const decData = aes256_decrypt(encPayloadData, sharedSecret) + // Decrypt data using the *current* shared secret + const decData = aes256_decrypt(encPayloadData, sharedSecret) - // Bulid the object - const ephemeralPubSz = 65 // secp256r1 pubkey - const checksumOffset = - ephemeralPubSz + szs.data.response.encrypted[requestType] - const respData: LatticeSecureDecryptedResponse = { - ephemeralPub: decData.slice(0, ephemeralPubSz), - data: decData.slice(ephemeralPubSz, checksumOffset), - checksum: decData.readUInt32BE(checksumOffset), - } + // Bulid the object + const ephemeralPubSz = 65 // secp256r1 pubkey + const checksumOffset = + ephemeralPubSz + szs.data.response.encrypted[requestType] + const respData: LatticeSecureDecryptedResponse = { + ephemeralPub: decData.slice(0, ephemeralPubSz), + data: decData.slice(ephemeralPubSz, checksumOffset), + checksum: decData.readUInt32BE(checksumOffset), + } - // Validate the checksum - const validChecksum = checksum(decData.slice(0, checksumOffset)) - if (respData.checksum !== validChecksum) { - throw new Error('Checksum mismatch in decrypted Lattice data') - } + // Validate the checksum + const validChecksum = checksum(decData.slice(0, checksumOffset)) + if (respData.checksum !== validChecksum) { + throw new Error('Checksum mismatch in decrypted Lattice data') + } - // Validate the response data size - const validSz = szs.data.response.encrypted[requestType] - if (respData.data.length !== validSz) { - throw new Error('Incorrect response data returned from Lattice') - } + // Validate the response data size + const validSz = szs.data.response.encrypted[requestType] + if (respData.data.length !== validSz) { + throw new Error('Incorrect response data returned from Lattice') + } - const newEphemeralPub = getP256KeyPairFromPub(respData.ephemeralPub) + const newEphemeralPub = getP256KeyPairFromPub(respData.ephemeralPub) - // Returned the decrypted data - return { decryptedData: respData.data, newEphemeralPub } + // Returned the decrypted data + return { decryptedData: respData.data, newEphemeralPub } } diff --git a/packages/sdk/src/schemas/transaction.ts b/packages/sdk/src/schemas/transaction.ts index 80772c99..34862707 100644 --- a/packages/sdk/src/schemas/transaction.ts +++ b/packages/sdk/src/schemas/transaction.ts @@ -5,166 +5,166 @@ import { TRANSACTION_TYPE } from '../types' // Helper to handle various numeric inputs and convert them to BigInt. // It also validates that the value is not negative. const toPositiveBigInt = z - .union([ - z.string().regex(/^(0x[0-9a-fA-F]+|[0-9]+)$/, 'Invalid number format'), - z.number(), - z.bigint(), - ]) - .transform((val, ctx) => { - try { - const b = - typeof val === 'string' && isHex(val) ? hexToBigInt(val) : BigInt(val) - if (b < 0n) { - ctx.addIssue({ - code: z.ZodIssueCode.custom, - message: 'Value must be non-negative', - }) - return z.NEVER - } - return b - } catch { - ctx.addIssue({ - code: z.ZodIssueCode.custom, - message: 'Invalid numeric value', - }) - return z.NEVER - } - }) + .union([ + z.string().regex(/^(0x[0-9a-fA-F]+|[0-9]+)$/, 'Invalid number format'), + z.number(), + z.bigint(), + ]) + .transform((val, ctx) => { + try { + const b = + typeof val === 'string' && isHex(val) ? hexToBigInt(val) : BigInt(val) + if (b < 0n) { + ctx.addIssue({ + code: z.ZodIssueCode.custom, + message: 'Value must be non-negative', + }) + return z.NEVER + } + return b + } catch { + ctx.addIssue({ + code: z.ZodIssueCode.custom, + message: 'Invalid numeric value', + }) + return z.NEVER + } + }) // Schema for gas-related fields, ensuring they are non-negative BigInts. const GasValueSchema = toPositiveBigInt.refine((val) => val >= 0n, { - message: 'Gas values must be non-negative', + message: 'Gas values must be non-negative', }) // Schema for chainId, ensuring it's a positive integer. const ChainIdSchema = z - .union([z.string(), z.number()]) - .transform((val) => - typeof val === 'string' && isHex(val) - ? Number(hexToBigInt(val as Hex)) - : Number(val), - ) - .refine((val) => Number.isInteger(val) && val > 0, { - message: 'Chain ID must be a positive integer', - }) + .union([z.string(), z.number()]) + .transform((val) => + typeof val === 'string' && isHex(val) + ? Number(hexToBigInt(val as Hex)) + : Number(val), + ) + .refine((val) => Number.isInteger(val) && val > 0, { + message: 'Chain ID must be a positive integer', + }) // Schema for an Ethereum address, which validates and checksums it. const AddressSchema = z - .string() - .refine(isAddress, 'Invalid address') - .transform((addr) => getAddress(addr)) + .string() + .refine(isAddress, 'Invalid address') + .transform((addr) => getAddress(addr)) // Schema for hex data, ensuring it's a valid hex string. const DataSchema = z - .string() - .refine(isHex, 'Data must be a valid hex string') - .default('0x') + .string() + .refine(isHex, 'Data must be a valid hex string') + .default('0x') const NonceSchema = z - .union([ - z.string().regex(/^(0x[0-9a-fA-F]+|[0-9]+)$/, 'Invalid nonce format'), - z.number().int().nonnegative(), - z.bigint(), - ]) - .transform((val, ctx) => { - try { - const bigVal = - typeof val === 'string' - ? isHex(val as Hex) - ? hexToBigInt(val as Hex) - : BigInt(val) - : typeof val === 'number' - ? BigInt(val) - : val + .union([ + z.string().regex(/^(0x[0-9a-fA-F]+|[0-9]+)$/, 'Invalid nonce format'), + z.number().int().nonnegative(), + z.bigint(), + ]) + .transform((val, ctx) => { + try { + const bigVal = + typeof val === 'string' + ? isHex(val as Hex) + ? hexToBigInt(val as Hex) + : BigInt(val) + : typeof val === 'number' + ? BigInt(val) + : val - if (bigVal < 0n) { - ctx.addIssue({ - code: z.ZodIssueCode.custom, - message: 'Nonce must be non-negative', - }) - return z.NEVER - } + if (bigVal < 0n) { + ctx.addIssue({ + code: z.ZodIssueCode.custom, + message: 'Nonce must be non-negative', + }) + return z.NEVER + } - const maxSafe = BigInt(Number.MAX_SAFE_INTEGER) - if (bigVal > maxSafe) { - ctx.addIssue({ - code: z.ZodIssueCode.custom, - message: 'Nonce exceeds JavaScript safe integer range', - }) - return z.NEVER - } + const maxSafe = BigInt(Number.MAX_SAFE_INTEGER) + if (bigVal > maxSafe) { + ctx.addIssue({ + code: z.ZodIssueCode.custom, + message: 'Nonce exceeds JavaScript safe integer range', + }) + return z.NEVER + } - return Number(bigVal) - } catch { - ctx.addIssue({ - code: z.ZodIssueCode.custom, - message: 'Invalid nonce value', - }) - return z.NEVER - } - }) + return Number(bigVal) + } catch { + ctx.addIssue({ + code: z.ZodIssueCode.custom, + message: 'Invalid nonce value', + }) + return z.NEVER + } + }) // Schema for access list entries. const AccessListEntrySchema = z.object({ - address: AddressSchema, - storageKeys: z.array( - z.string().refine(isHex, 'Storage key must be a hex string'), - ), + address: AddressSchema, + storageKeys: z.array( + z.string().refine(isHex, 'Storage key must be a hex string'), + ), }) // Schema for EIP-7702 authorization entries. const AuthorizationSchema = z.object({ - chainId: z.number().int().positive(), - address: AddressSchema, - nonce: z.number().int().nonnegative(), - yParity: z.number().optional().default(0), - r: z.string().refine(isHex).optional(), - s: z.string().refine(isHex).optional(), + chainId: z.number().int().positive(), + address: AddressSchema, + nonce: z.number().int().nonnegative(), + yParity: z.number().optional().default(0), + r: z.string().refine(isHex).optional(), + s: z.string().refine(isHex).optional(), }) // Base schema for all transaction types. const BaseTxSchema = z.object({ - to: AddressSchema.optional(), - value: toPositiveBigInt.optional(), - data: DataSchema, - nonce: NonceSchema.optional(), - gas: GasValueSchema.optional(), - gasLimit: GasValueSchema.optional(), - chainId: ChainIdSchema.optional().default(1), - accessList: z.array(AccessListEntrySchema).optional(), + to: AddressSchema.optional(), + value: toPositiveBigInt.optional(), + data: DataSchema, + nonce: NonceSchema.optional(), + gas: GasValueSchema.optional(), + gasLimit: GasValueSchema.optional(), + chainId: ChainIdSchema.optional().default(1), + accessList: z.array(AccessListEntrySchema).optional(), }) // Schema for Legacy (Type 0) transactions. const LegacyTxSchema = BaseTxSchema.extend({ - type: z - .union([z.literal('legacy'), z.literal(TRANSACTION_TYPE.LEGACY)]) - .optional(), - gasPrice: GasValueSchema, + type: z + .union([z.literal('legacy'), z.literal(TRANSACTION_TYPE.LEGACY)]) + .optional(), + gasPrice: GasValueSchema, }) // Schema for EIP-2930 (Type 1) transactions. const EIP2930TxSchema = BaseTxSchema.extend({ - type: z.union([z.literal('eip2930'), z.literal(TRANSACTION_TYPE.EIP2930)]), - gasPrice: GasValueSchema, + type: z.union([z.literal('eip2930'), z.literal(TRANSACTION_TYPE.EIP2930)]), + gasPrice: GasValueSchema, }) // Schema for EIP-1559 (Type 2) transactions. const EIP1559TxSchema = BaseTxSchema.extend({ - type: z.union([z.literal('eip1559'), z.literal(TRANSACTION_TYPE.EIP1559)]), - maxFeePerGas: GasValueSchema, - maxPriorityFeePerGas: GasValueSchema, + type: z.union([z.literal('eip1559'), z.literal(TRANSACTION_TYPE.EIP1559)]), + maxFeePerGas: GasValueSchema, + maxPriorityFeePerGas: GasValueSchema, }) // Schema for EIP-7702 (Type 4/5) transactions. const EIP7702TxSchema = BaseTxSchema.extend({ - type: z.union([ - z.literal('eip7702'), - z.literal(TRANSACTION_TYPE.EIP7702_AUTH), - z.literal(TRANSACTION_TYPE.EIP7702_AUTH_LIST), - ]), - maxFeePerGas: GasValueSchema, - maxPriorityFeePerGas: GasValueSchema, - authorizationList: z.array(AuthorizationSchema).min(1), + type: z.union([ + z.literal('eip7702'), + z.literal(TRANSACTION_TYPE.EIP7702_AUTH), + z.literal(TRANSACTION_TYPE.EIP7702_AUTH_LIST), + ]), + maxFeePerGas: GasValueSchema, + maxPriorityFeePerGas: GasValueSchema, + authorizationList: z.array(AuthorizationSchema).min(1), }) /** @@ -176,87 +176,87 @@ const EIP7702TxSchema = BaseTxSchema.extend({ * - Merging of `gas` and `gasLimit` fields. */ export const TransactionSchema = z - .any() - // Pre-process to check for circular references before zod touches it - .refine( - (val) => { - try { - JSON.stringify(val, (_, value) => - typeof value === 'bigint' ? value.toString() : value, - ) - return true - } catch { - return false - } - }, - { message: 'Circular reference detected in transaction object' }, - ) - .transform((tx) => { - // Prioritize gasLimit over gas - if (tx.gasLimit) { - tx.gas = tx.gasLimit - } + .any() + // Pre-process to check for circular references before zod touches it + .refine( + (val) => { + try { + JSON.stringify(val, (_, value) => + typeof value === 'bigint' ? value.toString() : value, + ) + return true + } catch { + return false + } + }, + { message: 'Circular reference detected in transaction object' }, + ) + .transform((tx) => { + // Prioritize gasLimit over gas + if (tx.gasLimit) { + tx.gas = tx.gasLimit + } - if (tx.data === null || tx.data === undefined || tx.data === '') { - tx.data = '0x' - } + if (tx.data === null || tx.data === undefined || tx.data === '') { + tx.data = '0x' + } - // Normalize EIP-7702 `authorization` to `authorizationList` - if (tx.authorization) { - tx.authorizationList = [tx.authorization] - } - return tx - }) - .transform((tx: any) => { - // Type inference and validation logic - const hasAuthList = !!tx.authorizationList - const hasMaxFee = !!tx.maxFeePerGas || !!tx.maxPriorityFeePerGas - const hasAccessList = !!tx.accessList - const hasGasPrice = !!tx.gasPrice + // Normalize EIP-7702 `authorization` to `authorizationList` + if (tx.authorization) { + tx.authorizationList = [tx.authorization] + } + return tx + }) + .transform((tx: any) => { + // Type inference and validation logic + const hasAuthList = !!tx.authorizationList + const hasMaxFee = !!tx.maxFeePerGas || !!tx.maxPriorityFeePerGas + const hasAccessList = !!tx.accessList + const hasGasPrice = !!tx.gasPrice - let type: 'eip7702' | 'eip1559' | 'eip2930' | 'legacy' = 'legacy' - let schema: z.ZodTypeAny = LegacyTxSchema + let type: 'eip7702' | 'eip1559' | 'eip2930' | 'legacy' = 'legacy' + let schema: z.ZodTypeAny = LegacyTxSchema - if ( - tx.type === 'eip7702' || - tx.type === 4 || - tx.type === 5 || - hasAuthList - ) { - type = 'eip7702' - schema = EIP7702TxSchema - } else if (tx.type === 'eip1559' || tx.type === 2 || hasMaxFee) { - type = 'eip1559' - schema = EIP1559TxSchema - } else if (tx.type === 'eip2930' || tx.type === 1 || hasAccessList) { - type = 'eip2930' - schema = EIP2930TxSchema - } + if ( + tx.type === 'eip7702' || + tx.type === 4 || + tx.type === 5 || + hasAuthList + ) { + type = 'eip7702' + schema = EIP7702TxSchema + } else if (tx.type === 'eip1559' || tx.type === 2 || hasMaxFee) { + type = 'eip1559' + schema = EIP1559TxSchema + } else if (tx.type === 'eip2930' || tx.type === 1 || hasAccessList) { + type = 'eip2930' + schema = EIP2930TxSchema + } - // For legacy, if gasPrice is missing, it's an invalid tx - if (type === 'legacy' && !hasGasPrice) { - throw new Error('Legacy transactions require a `gasPrice` field.') - } + // For legacy, if gasPrice is missing, it's an invalid tx + if (type === 'legacy' && !hasGasPrice) { + throw new Error('Legacy transactions require a `gasPrice` field.') + } - const result = schema.parse(tx) + const result = schema.parse(tx) - // Post-process the successfully parsed data - const data: any = result - data.type = type - if (type === 'legacy' && data.gas === undefined) { - data.gas = 21000n // Default gas for legacy transfers - } + // Post-process the successfully parsed data + const data: any = result + data.type = type + if (type === 'legacy' && data.gas === undefined) { + data.gas = 21000n // Default gas for legacy transfers + } - // Remove fields that are not part of the final type - if (type !== 'legacy' && type !== 'eip2930') data.gasPrice = undefined - if (type !== 'eip1559' && type !== 'eip7702') { - data.maxFeePerGas = undefined - data.maxPriorityFeePerGas = undefined - } - data.gasLimit = undefined - if (type !== 'eip7702') data.authorizationList = undefined + // Remove fields that are not part of the final type + if (type !== 'legacy' && type !== 'eip2930') data.gasPrice = undefined + if (type !== 'eip1559' && type !== 'eip7702') { + data.maxFeePerGas = undefined + data.maxPriorityFeePerGas = undefined + } + data.gasLimit = undefined + if (type !== 'eip7702') data.authorizationList = undefined - return data - }) + return data + }) export type FlexibleTransaction = z.infer diff --git a/packages/sdk/src/shared/errors.ts b/packages/sdk/src/shared/errors.ts index 6b3159f3..919f25c6 100644 --- a/packages/sdk/src/shared/errors.ts +++ b/packages/sdk/src/shared/errors.ts @@ -1,35 +1,35 @@ import { type LatticeResponseCode, ProtocolConstants } from '../protocol' const buildLatticeResponseErrorMessage = ({ - responseCode, - errorMessage, + responseCode, + errorMessage, }: { - responseCode?: LatticeResponseCode - errorMessage?: string + responseCode?: LatticeResponseCode + errorMessage?: string }) => { - const msg: string[] = [] - if (responseCode) { - msg.push(`${ProtocolConstants.responseMsg[responseCode]}`) - } - if (errorMessage) { - msg.push('Error Message: ') - msg.push(errorMessage) - } - return msg.join('\n') + const msg: string[] = [] + if (responseCode) { + msg.push(`${ProtocolConstants.responseMsg[responseCode]}`) + } + if (errorMessage) { + msg.push('Error Message: ') + msg.push(errorMessage) + } + return msg.join('\n') } export class LatticeResponseError extends Error { - constructor( - public responseCode?: LatticeResponseCode, - public errorMessage?: string, - ) { - const message = buildLatticeResponseErrorMessage({ - responseCode, - errorMessage, - }) - super(message) - this.name = 'LatticeResponseError' - this.responseCode = responseCode - this.errorMessage = errorMessage - } + constructor( + public responseCode?: LatticeResponseCode, + public errorMessage?: string, + ) { + const message = buildLatticeResponseErrorMessage({ + responseCode, + errorMessage, + }) + super(message) + this.name = 'LatticeResponseError' + this.responseCode = responseCode + this.errorMessage = errorMessage + } } diff --git a/packages/sdk/src/shared/functions.ts b/packages/sdk/src/shared/functions.ts index 4c6120b2..4dc62bc7 100644 --- a/packages/sdk/src/shared/functions.ts +++ b/packages/sdk/src/shared/functions.ts @@ -8,122 +8,122 @@ import type { Currency, FirmwareConstants, RequestParams } from '../types' import { fetchWithTimeout, parseLattice1Response } from '../util' import { LatticeResponseError } from './errors' import { - isDeviceBusy, - isInvalidEphemeralId, - isWrongWallet, - shouldUseEVMLegacyConverter, + isDeviceBusy, + isInvalidEphemeralId, + isWrongWallet, + shouldUseEVMLegacyConverter, } from './predicates' import { validateRequestError } from './validators' export const buildTransaction = ({ - data, - currency, - fwConstants, + data, + currency, + fwConstants, }: { - data: any - currency?: Currency - fwConstants: FirmwareConstants + data: any + currency?: Currency + fwConstants: FirmwareConstants }) => { - // All transaction requests must be put into the same sized buffer. This comes from - // sizeof(GpTransactionRequest_t), but note we remove the 2-byte schemaId since it is not - // returned from our resolver. Note that different firmware versions may have different data - // sizes. + // All transaction requests must be put into the same sized buffer. This comes from + // sizeof(GpTransactionRequest_t), but note we remove the 2-byte schemaId since it is not + // returned from our resolver. Note that different firmware versions may have different data + // sizes. - // TEMPORARY BRIDGE -- DEPRECATE ME In v0.15.0 Lattice firmware removed the legacy ETH - // signing path, so we need to convert such requests to general signing requests using the - // EVM decoder. NOTE: Not every request can be converted, so users should switch to using - // general signing requests for newer firmware versions. EIP1559 and EIP155 legacy - // requests will convert, but others may not. - if (currency === 'ETH' && shouldUseEVMLegacyConverter(fwConstants)) { - console.log( - 'Using the legacy ETH signing path. This will soon be deprecated. ' + - 'Please switch to general signing request.', - ) - let payload: Buffer | undefined - try { - payload = ethereum.convertEthereumTransactionToGenericRequest(data) - } catch (err) { - console.error('Failed to convert legacy Ethereum transaction:', err) - throw new Error( - 'Could not convert legacy request. Please switch to a general signing ' + - 'request. See gridplus-sdk docs for more information.', - ) - } - data = { - fwConstants, - encodingType: EXTERNAL.SIGNING.ENCODINGS.EVM, - curveType: EXTERNAL.SIGNING.CURVES.SECP256K1, - hashType: EXTERNAL.SIGNING.HASHES.KECCAK256, - signerPath: data.signerPath, - payload, - } - return { - requestData: buildGenericSigningMsgRequest({ ...data, fwConstants }), - isGeneric: true, - } - } else if (currency === 'ETH') { - // Legacy signing pathway -- should deprecate in the future - return { - requestData: ethereum.buildEthereumTxRequest({ ...data, fwConstants }), - isGeneric: false, - } - } else if (currency === 'ETH_MSG') { - return { - requestData: ethereum.buildEthereumMsgRequest({ ...data, fwConstants }), - isGeneric: false, - } - } else if (currency === 'BTC') { - return { - requestData: bitcoin.buildBitcoinTxRequest({ ...data, fwConstants }), - isGeneric: false, - } - } - return { - requestData: buildGenericSigningMsgRequest({ ...data, fwConstants }), - isGeneric: true, - } + // TEMPORARY BRIDGE -- DEPRECATE ME In v0.15.0 Lattice firmware removed the legacy ETH + // signing path, so we need to convert such requests to general signing requests using the + // EVM decoder. NOTE: Not every request can be converted, so users should switch to using + // general signing requests for newer firmware versions. EIP1559 and EIP155 legacy + // requests will convert, but others may not. + if (currency === 'ETH' && shouldUseEVMLegacyConverter(fwConstants)) { + console.log( + 'Using the legacy ETH signing path. This will soon be deprecated. ' + + 'Please switch to general signing request.', + ) + let payload: Buffer | undefined + try { + payload = ethereum.convertEthereumTransactionToGenericRequest(data) + } catch (err) { + console.error('Failed to convert legacy Ethereum transaction:', err) + throw new Error( + 'Could not convert legacy request. Please switch to a general signing ' + + 'request. See gridplus-sdk docs for more information.', + ) + } + data = { + fwConstants, + encodingType: EXTERNAL.SIGNING.ENCODINGS.EVM, + curveType: EXTERNAL.SIGNING.CURVES.SECP256K1, + hashType: EXTERNAL.SIGNING.HASHES.KECCAK256, + signerPath: data.signerPath, + payload, + } + return { + requestData: buildGenericSigningMsgRequest({ ...data, fwConstants }), + isGeneric: true, + } + } else if (currency === 'ETH') { + // Legacy signing pathway -- should deprecate in the future + return { + requestData: ethereum.buildEthereumTxRequest({ ...data, fwConstants }), + isGeneric: false, + } + } else if (currency === 'ETH_MSG') { + return { + requestData: ethereum.buildEthereumMsgRequest({ ...data, fwConstants }), + isGeneric: false, + } + } else if (currency === 'BTC') { + return { + requestData: bitcoin.buildBitcoinTxRequest({ ...data, fwConstants }), + isGeneric: false, + } + } + return { + requestData: buildGenericSigningMsgRequest({ ...data, fwConstants }), + isGeneric: true, + } } export const request = async ({ - url, - payload, - timeout = 60000, + url, + payload, + timeout = 60000, }: RequestParams) => { - return fetchWithTimeout(url, { - method: 'POST', - body: JSON.stringify({ data: payload }), - headers: { - 'Content-Type': 'application/json', - }, - timeout, - }) - .catch(validateRequestError) - .then((res) => res.json()) - .then((body) => { - // Handle formatting or generic HTTP errors - if (!body || !body.message) { - throw new Error('Invalid response') - } else if (body.status !== 200) { - throw new Error(`Error code ${body.status}: ${body.message}`) - } + return fetchWithTimeout(url, { + method: 'POST', + body: JSON.stringify({ data: payload }), + headers: { + 'Content-Type': 'application/json', + }, + timeout, + }) + .catch(validateRequestError) + .then((res) => res.json()) + .then((body) => { + // Handle formatting or generic HTTP errors + if (!body || !body.message) { + throw new Error('Invalid response') + } else if (body.status !== 200) { + throw new Error(`Error code ${body.status}: ${body.message}`) + } - const { data, errorMessage, responseCode } = parseLattice1Response( - body.message, - ) + const { data, errorMessage, responseCode } = parseLattice1Response( + body.message, + ) - if (errorMessage || responseCode) { - throw new LatticeResponseError(responseCode, errorMessage) - } + if (errorMessage || responseCode) { + throw new LatticeResponseError(responseCode, errorMessage) + } - return data - }) + return data + }) } /** * `sleep()` returns a Promise that resolves after a given number of milliseconds. */ function sleep(ms: number): Promise { - return new Promise((resolve) => setTimeout(resolve, ms)) + return new Promise((resolve) => setTimeout(resolve, ms)) } /** @@ -135,13 +135,13 @@ function sleep(ms: number): Promise { * @returns a {@link retryWrapper} function for handing retry logic */ export const buildRetryWrapper = (client: Client, retries: number) => { - return (fn, params?) => - retryWrapper({ - fn, - params: { ...params, client }, - retries, - client, - }) + return (fn, params?) => + retryWrapper({ + fn, + params: { ...params, client }, + retries, + client, + }) } /** @@ -154,47 +154,47 @@ export const buildRetryWrapper = (client: Client, retries: number) => { * @param client - The {@link Client} to use for side-effects */ export const retryWrapper = async ({ - fn, - params, - retries, - client, + fn, + params, + retries, + client, }: { - fn: (...args: any[]) => Promise - params: any - retries: number - client: any + fn: (...args: any[]) => Promise + params: any + retries: number + client: any }) => { - return fn({ ...params }).catch(async (err: Error) => { - if (err instanceof LatticeResponseError) { - /** `string` returned from the Lattice if there's an error */ - const errorMessage = err.errorMessage - /** `number` returned from the Lattice if there's an error */ - const responseCode = err.responseCode + return fn({ ...params }).catch(async (err: Error) => { + if (err instanceof LatticeResponseError) { + /** `string` returned from the Lattice if there's an error */ + const errorMessage = err.errorMessage + /** `number` returned from the Lattice if there's an error */ + const responseCode = err.responseCode - if ((errorMessage || responseCode) && retries) { - if (isDeviceBusy(responseCode)) { - await sleep(3000) - } else if ( - isWrongWallet(responseCode) && - !client.skipRetryOnWrongWallet - ) { - await client.fetchActiveWallet() - } else if (isInvalidEphemeralId(responseCode)) { - await client.connect(client.deviceId) - } else { - throw err - } + if ((errorMessage || responseCode) && retries) { + if (isDeviceBusy(responseCode)) { + await sleep(3000) + } else if ( + isWrongWallet(responseCode) && + !client.skipRetryOnWrongWallet + ) { + await client.fetchActiveWallet() + } else if (isInvalidEphemeralId(responseCode)) { + await client.connect(client.deviceId) + } else { + throw err + } - return retryWrapper({ - fn, - params, - retries: retries - 1, - client, - }) - } - } - throw err - }) + return retryWrapper({ + fn, + params, + retries: retries - 1, + client, + }) + } + } + throw err + }) } /** @@ -204,7 +204,7 @@ export const retryWrapper = async ({ * @returns Buffer */ export const getEphemeralId = (sharedSecret: Buffer) => { - // EphemId is the first 4 bytes of the hash of the shared secret - const hash = Buffer.from(Hash.sha256(sharedSecret)) - return Number.parseInt(hash.slice(0, 4).toString('hex'), 16) + // EphemId is the first 4 bytes of the hash of the shared secret + const hash = Buffer.from(Hash.sha256(sharedSecret)) + return Number.parseInt(hash.slice(0, 4).toString('hex'), 16) } diff --git a/packages/sdk/src/shared/predicates.ts b/packages/sdk/src/shared/predicates.ts index 265ec762..34d2bf93 100644 --- a/packages/sdk/src/shared/predicates.ts +++ b/packages/sdk/src/shared/predicates.ts @@ -3,17 +3,17 @@ import type { FirmwareConstants, FirmwareVersion } from '../types' import { isFWSupported } from './utilities' export const isDeviceBusy = (responseCode: number) => - responseCode === LatticeResponseCode.deviceBusy || - responseCode === LatticeResponseCode.gceTimeout + responseCode === LatticeResponseCode.deviceBusy || + responseCode === LatticeResponseCode.gceTimeout export const isWrongWallet = (responseCode: number) => - responseCode === LatticeResponseCode.wrongWallet + responseCode === LatticeResponseCode.wrongWallet export const isInvalidEphemeralId = (responseCode: number) => - responseCode === LatticeResponseCode.invalidEphemId + responseCode === LatticeResponseCode.invalidEphemId export const doesFetchWalletsOnLoad = (fwVersion: FirmwareVersion) => - isFWSupported(fwVersion, { major: 0, minor: 14, fix: 1 }) + isFWSupported(fwVersion, { major: 0, minor: 14, fix: 1 }) export const shouldUseEVMLegacyConverter = (fwConstants: FirmwareConstants) => - fwConstants.genericSigning?.encodingTypes?.EVM + fwConstants.genericSigning?.encodingTypes?.EVM diff --git a/packages/sdk/src/shared/utilities.ts b/packages/sdk/src/shared/utilities.ts index 66632b86..5d924212 100644 --- a/packages/sdk/src/shared/utilities.ts +++ b/packages/sdk/src/shared/utilities.ts @@ -9,18 +9,18 @@ import type { ActiveWallets, FirmwareVersion, KeyPair } from '../types' * @returns A Buffer containing the public key. */ export const getPubKeyBytes = (key: KeyPair, LE = false) => { - const k = key.getPublic() - const p = k.encode('hex', false) - const pb = Buffer.from(p, 'hex') - if (LE === true) { - // Need to flip X and Y components to little endian - const x = pb.slice(1, 33).reverse() - const y = pb.slice(33, 65).reverse() - // @ts-expect-error - TODO: Find out why Buffer won't accept pb[0] - return Buffer.concat([pb[0], x, y]) - } else { - return pb - } + const k = key.getPublic() + const p = k.encode('hex', false) + const pb = Buffer.from(p, 'hex') + if (LE === true) { + // Need to flip X and Y components to little endian + const x = pb.slice(1, 33).reverse() + const y = pb.slice(33, 65).reverse() + // @ts-expect-error - TODO: Find out why Buffer won't accept pb[0] + return Buffer.concat([pb[0], x, y]) + } else { + return pb + } } /** @@ -29,70 +29,70 @@ export const getPubKeyBytes = (key: KeyPair, LE = false) => { * @returns Buffer */ export const getSharedSecret = (key: KeyPair, ephemeralPub: KeyPair) => { - // Once every ~256 attempts, we will get a key that starts with a `00` byte, which can lead to - // problems initializing AES if we don't force a 32 byte BE buffer. - return Buffer.from(key.derive(ephemeralPub.getPublic()).toArray('be', 32)) + // Once every ~256 attempts, we will get a key that starts with a `00` byte, which can lead to + // problems initializing AES if we don't force a 32 byte BE buffer. + return Buffer.from(key.derive(ephemeralPub.getPublic()).toArray('be', 32)) } // Given a set of wallet data, which contains two wallet descriptors, parse the data and save it // to memory export const parseWallets = (walletData: any): ActiveWallets => { - // Read the external wallet data first. If it is non-null, the external wallet will be the - // active wallet of the device and we should save it. If the external wallet is blank, it means - // there is no card present and we should save and use the interal wallet. If both wallets are - // empty, it means the device still needs to be set up. - const walletDescriptorLen = 71 - // Internal first - let off = 0 - const activeWallets: ActiveWallets = { - internal: { - uid: undefined, - capabilities: undefined, - name: undefined, - external: false, - }, - external: { - uid: undefined, - capabilities: undefined, - name: undefined, - external: true, - }, - } - activeWallets.internal.uid = walletData.slice(off, off + 32) - // NOTE: `capabilities` and `name` were deprecated in Lattice firmware. - // They never provided any real information, but have been archived here - // since the response size has been preserved and we may bring them back - // in a different form. - // activeWallets.internal.capabilities = walletData.readUInt32BE(off + 32); - // activeWallets.internal.name = walletData.slice( - // off + 36, - // off + walletDescriptorLen, - // ); - // Offset the first item - off += walletDescriptorLen - // External - activeWallets.external.uid = walletData.slice(off, off + 32) - // activeWallets.external.capabilities = walletData.readUInt32BE(off + 32); - // activeWallets.external.name = walletData.slice( - // off + 36, - // off + walletDescriptorLen, - // ); + // Read the external wallet data first. If it is non-null, the external wallet will be the + // active wallet of the device and we should save it. If the external wallet is blank, it means + // there is no card present and we should save and use the interal wallet. If both wallets are + // empty, it means the device still needs to be set up. + const walletDescriptorLen = 71 + // Internal first + let off = 0 + const activeWallets: ActiveWallets = { + internal: { + uid: undefined, + capabilities: undefined, + name: undefined, + external: false, + }, + external: { + uid: undefined, + capabilities: undefined, + name: undefined, + external: true, + }, + } + activeWallets.internal.uid = walletData.slice(off, off + 32) + // NOTE: `capabilities` and `name` were deprecated in Lattice firmware. + // They never provided any real information, but have been archived here + // since the response size has been preserved and we may bring them back + // in a different form. + // activeWallets.internal.capabilities = walletData.readUInt32BE(off + 32); + // activeWallets.internal.name = walletData.slice( + // off + 36, + // off + walletDescriptorLen, + // ); + // Offset the first item + off += walletDescriptorLen + // External + activeWallets.external.uid = walletData.slice(off, off + 32) + // activeWallets.external.capabilities = walletData.readUInt32BE(off + 32); + // activeWallets.external.name = walletData.slice( + // off + 36, + // off + walletDescriptorLen, + // ); - return activeWallets + return activeWallets } // Determine if a provided firmware version matches or exceeds the current firmware version export const isFWSupported = ( - fwVersion: FirmwareVersion, - versionSupported: FirmwareVersion, + fwVersion: FirmwareVersion, + versionSupported: FirmwareVersion, ): boolean => { - const { major, minor, fix } = fwVersion - const { major: _major, minor: _minor, fix: _fix } = versionSupported - return ( - major > _major || - (major >= _major && minor > _minor) || - (major >= _major && minor >= _minor && fix >= _fix) - ) + const { major, minor, fix } = fwVersion + const { major: _major, minor: _minor, fix: _fix } = versionSupported + return ( + major > _major || + (major >= _major && minor > _minor) || + (major >= _major && minor >= _minor && fix >= _fix) + ) } /** @@ -100,13 +100,13 @@ export const isFWSupported = ( * @param path - Set of indices */ export const getPathStr = (path) => { - let pathStr = 'm' - path.forEach((idx) => { - if (idx >= HARDENED_OFFSET) { - pathStr += `/${idx - HARDENED_OFFSET}'` - } else { - pathStr += `/${idx}` - } - }) - return pathStr + let pathStr = 'm' + path.forEach((idx) => { + if (idx >= HARDENED_OFFSET) { + pathStr += `/${idx - HARDENED_OFFSET}'` + } else { + pathStr += `/${idx}` + } + }) + return pathStr } diff --git a/packages/sdk/src/shared/validators.ts b/packages/sdk/src/shared/validators.ts index 59fc0dbb..f4cfe65d 100644 --- a/packages/sdk/src/shared/validators.ts +++ b/packages/sdk/src/shared/validators.ts @@ -3,242 +3,242 @@ import isEmpty from 'lodash/isEmpty.js' import type { Client } from '../client' import { ASCII_REGEX, EMPTY_WALLET_UID, MAX_ADDR } from '../constants' import type { - ActiveWallets, - FirmwareConstants, - FirmwareVersion, - KVRecords, - KeyPair, - LatticeError, - Wallet, + ActiveWallets, + FirmwareConstants, + FirmwareVersion, + KVRecords, + KeyPair, + LatticeError, + Wallet, } from '../types' import { isUInt4 } from '../util' export const validateIsUInt4 = (n?: number) => { - if (typeof n !== 'number' || !isUInt4(n)) { - throw new Error('Must be an integer between 0 and 15 inclusive') - } - return n as UInt4 + if (typeof n !== 'number' || !isUInt4(n)) { + throw new Error('Must be an integer between 0 and 15 inclusive') + } + return n as UInt4 } export const validateNAddresses = (n?: number) => { - if (!n) { - throw new Error('The number of addresses is required.') - } - if (n > MAX_ADDR) { - throw new Error(`You may only request ${MAX_ADDR} addresses at once.`) - } - return n + if (!n) { + throw new Error('The number of addresses is required.') + } + if (n > MAX_ADDR) { + throw new Error(`You may only request ${MAX_ADDR} addresses at once.`) + } + return n } export const validateStartPath = (startPath?: number[]) => { - if (!startPath) { - throw new Error('Start path is required') - } - if (startPath.length < 1 || startPath.length > 5) - throw new Error('Path must include between 1 and 5 indices') + if (!startPath) { + throw new Error('Start path is required') + } + if (startPath.length < 1 || startPath.length > 5) + throw new Error('Path must include between 1 and 5 indices') - return startPath + return startPath } export const validateDeviceId = (deviceId?: string) => { - if (!deviceId) { - throw new Error( - 'No device ID has been stored. Please connect with your device ID first.', - ) - } - return deviceId + if (!deviceId) { + throw new Error( + 'No device ID has been stored. Please connect with your device ID first.', + ) + } + return deviceId } export const validateAppName = (name?: string) => { - if (!name) { - throw new Error('Name is required.') - } - if (name.length < 5 || name.length > 24) { - throw new Error( - 'Invalid length for name provided. Must be 5-24 characters.', - ) - } - return name + if (!name) { + throw new Error('Name is required.') + } + if (name.length < 5 || name.length > 24) { + throw new Error( + 'Invalid length for name provided. Must be 5-24 characters.', + ) + } + return name } export const validateUrl = (url?: string) => { - if (!url) { - throw new Error('URL does not exist. Please reconnect.') - } - try { - new URL(url) - } catch (err) { - console.error('Invalid URL format:', err) - throw new Error('Invalid URL provided. Please use a valid URL.') - } - return url + if (!url) { + throw new Error('URL does not exist. Please reconnect.') + } + try { + new URL(url) + } catch (err) { + console.error('Invalid URL format:', err) + throw new Error('Invalid URL provided. Please use a valid URL.') + } + return url } export const validateBaseUrl = (baseUrl?: string) => { - if (!baseUrl) { - throw new Error('Base URL is required.') - } - try { - new URL(baseUrl) - } catch (err) { - console.error('Invalid Base URL format:', err) - throw new Error('Invalid Base URL provided. Please use a valid URL.') - } - return baseUrl + if (!baseUrl) { + throw new Error('Base URL is required.') + } + try { + new URL(baseUrl) + } catch (err) { + console.error('Invalid Base URL format:', err) + throw new Error('Invalid Base URL provided. Please use a valid URL.') + } + return baseUrl } export const validateFwConstants = (fwConstants?: FirmwareConstants) => { - if (!fwConstants) { - throw new Error('Firmware constants do not exist. Please reconnect.') - } - return fwConstants + if (!fwConstants) { + throw new Error('Firmware constants do not exist. Please reconnect.') + } + return fwConstants } export const validateFwVersion = (fwVersion?: FirmwareVersion) => { - if (!fwVersion) { - throw new Error('Firmware version does not exist. Please reconnect.') - } - if ( - typeof fwVersion.fix !== 'number' || - typeof fwVersion.minor !== 'number' || - typeof fwVersion.major !== 'number' - ) { - throw new Error('Firmware version improperly formatted. Please reconnect.') - } - return fwVersion + if (!fwVersion) { + throw new Error('Firmware version does not exist. Please reconnect.') + } + if ( + typeof fwVersion.fix !== 'number' || + typeof fwVersion.minor !== 'number' || + typeof fwVersion.major !== 'number' + ) { + throw new Error('Firmware version improperly formatted. Please reconnect.') + } + return fwVersion } export const validateRequestError = (err: LatticeError) => { - const isTimeout = err.code === 'ECONNABORTED' && err.errno === 'ETIME' - if (isTimeout) { - throw new Error( - 'Timeout waiting for device. Please ensure it is connected to the internet and try again in a minute.', - ) - } - throw new Error(`Failed to make request to device:\n${err.message}`) + const isTimeout = err.code === 'ECONNABORTED' && err.errno === 'ETIME' + if (isTimeout) { + throw new Error( + 'Timeout waiting for device. Please ensure it is connected to the internet and try again in a minute.', + ) + } + throw new Error(`Failed to make request to device:\n${err.message}`) } export const validateWallet = (wallet?: Wallet) => { - if (!wallet || wallet === null) { - throw new Error('No active wallet.') - } - return wallet + if (!wallet || wallet === null) { + throw new Error('No active wallet.') + } + return wallet } export const validateConnectedClient = (client: Client) => { - const appName = validateAppName(client.getAppName()) - const ephemeralPub = validateEphemeralPub(client.ephemeralPub) - const sharedSecret = validateSharedSecret(client.sharedSecret) - const url = validateUrl(client.url) - const fwConstants = validateFwConstants(client.getFwConstants()) - const fwVersion = validateFwVersion(client.getFwVersion()) - // @ts-expect-error - Key is private - const key = validateKey(client.key) - - return { - appName, - ephemeralPub, - sharedSecret, - url, - fwConstants, - fwVersion, - key, - } + const appName = validateAppName(client.getAppName()) + const ephemeralPub = validateEphemeralPub(client.ephemeralPub) + const sharedSecret = validateSharedSecret(client.sharedSecret) + const url = validateUrl(client.url) + const fwConstants = validateFwConstants(client.getFwConstants()) + const fwVersion = validateFwVersion(client.getFwVersion()) + // @ts-expect-error - Key is private + const key = validateKey(client.key) + + return { + appName, + ephemeralPub, + sharedSecret, + url, + fwConstants, + fwVersion, + key, + } } export const validateEphemeralPub = (ephemeralPub?: KeyPair) => { - if (!ephemeralPub) { - throw new Error( - '`ephemeralPub` (ephemeral public key) is required. Please reconnect.', - ) - } - return ephemeralPub + if (!ephemeralPub) { + throw new Error( + '`ephemeralPub` (ephemeral public key) is required. Please reconnect.', + ) + } + return ephemeralPub } export const validateSharedSecret = (sharedSecret?: Buffer) => { - if (!sharedSecret) { - throw new Error('Shared secret required. Please reconnect.') - } - return sharedSecret + if (!sharedSecret) { + throw new Error('Shared secret required. Please reconnect.') + } + return sharedSecret } export const validateKey = (key?: KeyPair) => { - if (!key) { - throw new Error('Key is required. Please reconnect.') - } - return key + if (!key) { + throw new Error('Key is required. Please reconnect.') + } + return key } export const validateActiveWallets = (activeWallets?: ActiveWallets) => { - if ( - !activeWallets || - (activeWallets?.internal?.uid?.equals(EMPTY_WALLET_UID) && - activeWallets?.external?.uid?.equals(EMPTY_WALLET_UID)) - ) { - throw new Error('No active wallet.') - } - return activeWallets + if ( + !activeWallets || + (activeWallets?.internal?.uid?.equals(EMPTY_WALLET_UID) && + activeWallets?.external?.uid?.equals(EMPTY_WALLET_UID)) + ) { + throw new Error('No active wallet.') + } + return activeWallets } export const validateKvRecords = ( - records?: KVRecords, - fwConstants?: FirmwareConstants, + records?: KVRecords, + fwConstants?: FirmwareConstants, ) => { - if (!fwConstants || !fwConstants.kvActionsAllowed) { - throw new Error('Unsupported. Please update firmware.') - } else if (typeof records !== 'object' || Object.keys(records).length < 1) { - throw new Error( - 'One or more key-value mapping must be provided in `records` param.', - ) - } else if (Object.keys(records).length > fwConstants.kvActionMaxNum) { - throw new Error( - `Too many keys provided. Please only provide up to ${fwConstants.kvActionMaxNum}.`, - ) - } - return records + if (!fwConstants || !fwConstants.kvActionsAllowed) { + throw new Error('Unsupported. Please update firmware.') + } else if (typeof records !== 'object' || Object.keys(records).length < 1) { + throw new Error( + 'One or more key-value mapping must be provided in `records` param.', + ) + } else if (Object.keys(records).length > fwConstants.kvActionMaxNum) { + throw new Error( + `Too many keys provided. Please only provide up to ${fwConstants.kvActionMaxNum}.`, + ) + } + return records } export const validateKvRecord = ( - { key, val }: KVRecords, - fwConstants: FirmwareConstants, + { key, val }: KVRecords, + fwConstants: FirmwareConstants, ) => { - if ( - typeof key !== 'string' || - String(key).length > fwConstants.kvKeyMaxStrSz - ) { - throw new Error( - `Key ${key} too large. Must be <=${fwConstants.kvKeyMaxStrSz} characters.`, - ) - } else if ( - typeof val !== 'string' || - String(val).length > fwConstants.kvValMaxStrSz - ) { - throw new Error( - `Value ${val} too large. Must be <=${fwConstants.kvValMaxStrSz} characters.`, - ) - } else if (String(key).length === 0 || String(val).length === 0) { - throw new Error('Keys and values must be >0 characters.') - } else if (!ASCII_REGEX.test(key) || !ASCII_REGEX.test(val)) { - throw new Error('Unicode characters are not supported.') - } - return { key, val } + if ( + typeof key !== 'string' || + String(key).length > fwConstants.kvKeyMaxStrSz + ) { + throw new Error( + `Key ${key} too large. Must be <=${fwConstants.kvKeyMaxStrSz} characters.`, + ) + } else if ( + typeof val !== 'string' || + String(val).length > fwConstants.kvValMaxStrSz + ) { + throw new Error( + `Value ${val} too large. Must be <=${fwConstants.kvValMaxStrSz} characters.`, + ) + } else if (String(key).length === 0 || String(val).length === 0) { + throw new Error('Keys and values must be >0 characters.') + } else if (!ASCII_REGEX.test(key) || !ASCII_REGEX.test(val)) { + throw new Error('Unicode characters are not supported.') + } + return { key, val } } export const isValidBlockExplorerResponse = (data: any) => { - try { - const result = JSON.parse(data.result) - return !isEmpty(result) - } catch (err) { - console.error('Invalid block explorer response:', err) - return false - } + try { + const result = JSON.parse(data.result) + return !isEmpty(result) + } catch (err) { + console.error('Invalid block explorer response:', err) + return false + } } export const isValid4ByteResponse = (data: any) => { - try { - return !isEmpty(data.results) - } catch (err) { - console.error('Invalid 4byte response:', err) - return false - } + try { + return !isEmpty(data.results) + } catch (err) { + console.error('Invalid 4byte response:', err) + return false + } } diff --git a/packages/sdk/src/types/addKvRecords.ts b/packages/sdk/src/types/addKvRecords.ts index 5492d6b1..1887f550 100644 --- a/packages/sdk/src/types/addKvRecords.ts +++ b/packages/sdk/src/types/addKvRecords.ts @@ -2,12 +2,12 @@ import type { Client } from '../client' import type { KVRecords } from './shared' export interface AddKvRecordsRequestParams { - records: KVRecords - type?: number - caseSensitive?: boolean + records: KVRecords + type?: number + caseSensitive?: boolean } export interface AddKvRecordsRequestFunctionParams - extends AddKvRecordsRequestParams { - client: Client + extends AddKvRecordsRequestParams { + client: Client } diff --git a/packages/sdk/src/types/client.ts b/packages/sdk/src/types/client.ts index 5601e361..f6e681a4 100644 --- a/packages/sdk/src/types/client.ts +++ b/packages/sdk/src/types/client.ts @@ -11,20 +11,20 @@ export type SigningPath = number[] * Values can be Buffer (raw) or string (hex) depending on context. */ export interface LatticeSignature { - r: Buffer | string - s: Buffer | string - v?: Buffer | string | number | bigint + r: Buffer | string + s: Buffer | string + v?: Buffer | string | number | bigint } export interface SignData { - tx?: string - txHash?: Hash - changeRecipient?: string - sig?: LatticeSignature - sigs?: Buffer[] - signer?: Address - pubkey?: Buffer - err?: string + tx?: string + txHash?: Hash + changeRecipient?: string + sig?: LatticeSignature + sigs?: Buffer[] + signer?: Address + pubkey?: Buffer + err?: string } export type SigningRequestResponse = SignData | { pubkey: null; sig: null } @@ -35,49 +35,49 @@ export type SigningRequestResponse = SignData | { pubkey: null; sig: null } * This will be removed in a future version. */ export interface TransactionPayload { - type: number - gasPrice: number - nonce: number - gasLimit: number - to: string - value: number - data: string - maxFeePerGas: number - maxPriorityFeePerGas: number + type: number + gasPrice: number + nonce: number + gasLimit: number + to: string + value: number + data: string + maxFeePerGas: number + maxPriorityFeePerGas: number } export interface Wallet { - /** 32 byte id */ - uid: Buffer - /** 20 char (max) string */ - name: Buffer | null - /** 4 byte flag */ - capabilities: number - /** External or internal wallet */ - external: boolean + /** 32 byte id */ + uid: Buffer + /** 20 char (max) string */ + name: Buffer | null + /** 4 byte flag */ + capabilities: number + /** External or internal wallet */ + external: boolean } export interface ActiveWallets { - internal: Wallet - external: Wallet + internal: Wallet + external: Wallet } export interface RequestParams { - url: string - payload: any //TODO Fix this any - timeout?: number - retries?: number + url: string + payload: any //TODO Fix this any + timeout?: number + retries?: number } export interface ClientStateData { - activeWallets: ActiveWallets - ephemeralPub: KeyPair - fwVersion: Buffer - deviceId: string - name: string - baseUrl: string - privKey: Buffer - key: Buffer - retryCount: number - timeout: number + activeWallets: ActiveWallets + ephemeralPub: KeyPair + fwVersion: Buffer + deviceId: string + name: string + baseUrl: string + privKey: Buffer + key: Buffer + retryCount: number + timeout: number } diff --git a/packages/sdk/src/types/connect.ts b/packages/sdk/src/types/connect.ts index 7032b3d5..ba14d562 100644 --- a/packages/sdk/src/types/connect.ts +++ b/packages/sdk/src/types/connect.ts @@ -1,9 +1,9 @@ import type { Client } from '../client' export interface ConnectRequestParams { - id: string + id: string } export interface ConnectRequestFunctionParams extends ConnectRequestParams { - client: Client + client: Client } diff --git a/packages/sdk/src/types/declarations.d.ts b/packages/sdk/src/types/declarations.d.ts index b18a9a9e..d89a55a7 100644 --- a/packages/sdk/src/types/declarations.d.ts +++ b/packages/sdk/src/types/declarations.d.ts @@ -1,25 +1,25 @@ declare module 'aes-js' declare module 'hash.js/lib/hash/sha' declare module 'hash.js/lib/hash/ripemd.js' { - export function ripemd160(): { - update: (data: any) => any - digest: (encoding?: any) => any - } + export function ripemd160(): { + update: (data: any) => any + digest: (encoding?: any) => any + } } declare module 'lodash/inRange.js' { - const fn: (number: any, start?: any, end?: any) => boolean - export default fn + const fn: (number: any, start?: any, end?: any) => boolean + export default fn } declare module 'lodash/isInteger.js' { - const fn: (value: any) => boolean - export default fn + const fn: (value: any) => boolean + export default fn } declare module 'lodash/isEmpty.js' { - const fn: (value: any) => boolean - export default fn + const fn: (value: any) => boolean + export default fn } // Add more flexible typing to reduce strict type checking for complex modules declare global { - type NodeRequire = (id: string) => any + type NodeRequire = (id: string) => any } diff --git a/packages/sdk/src/types/fetchActiveWallet.ts b/packages/sdk/src/types/fetchActiveWallet.ts index 142bad44..39c2ce0b 100644 --- a/packages/sdk/src/types/fetchActiveWallet.ts +++ b/packages/sdk/src/types/fetchActiveWallet.ts @@ -1,9 +1,9 @@ import type { Client } from '../client' export interface FetchActiveWalletRequestFunctionParams { - client: Client + client: Client } export interface ValidatedFetchActiveWalletRequest { - sharedSecret: Buffer + sharedSecret: Buffer } diff --git a/packages/sdk/src/types/fetchEncData.ts b/packages/sdk/src/types/fetchEncData.ts index 785ede80..497b1e8c 100644 --- a/packages/sdk/src/types/fetchEncData.ts +++ b/packages/sdk/src/types/fetchEncData.ts @@ -1,26 +1,26 @@ import type { Client } from '../client' export interface EIP2335KeyExportReq { - path: number[] - c?: number - kdf?: number - walletUID?: Buffer + path: number[] + c?: number + kdf?: number + walletUID?: Buffer } export interface FetchEncDataRequest { - schema: number - params: EIP2335KeyExportReq // NOTE: This is a union, but only one type of request exists currently + schema: number + params: EIP2335KeyExportReq // NOTE: This is a union, but only one type of request exists currently } export interface FetchEncDataRequestFunctionParams extends FetchEncDataRequest { - client: Client + client: Client } export interface EIP2335KeyExportData { - iterations: number - cipherText: Buffer - salt: Buffer - checksum: Buffer - iv: Buffer - pubkey: Buffer + iterations: number + cipherText: Buffer + salt: Buffer + checksum: Buffer + iv: Buffer + pubkey: Buffer } diff --git a/packages/sdk/src/types/firmware.ts b/packages/sdk/src/types/firmware.ts index 7ace3ff7..62c8cf3d 100644 --- a/packages/sdk/src/types/firmware.ts +++ b/packages/sdk/src/types/firmware.ts @@ -3,63 +3,63 @@ import type { EXTERNAL } from '../constants' export type FirmwareArr = [number, number, number] export interface FirmwareVersion { - major: number - minor: number - fix: number + major: number + minor: number + fix: number } export interface GenericSigningData { - calldataDecoding: { - reserved: number - maxSz: number - } - baseReqSz: number - // See `GENERIC_SIGNING_BASE_MSG_SZ` in firmware - baseDataSz: number - hashTypes: typeof EXTERNAL.SIGNING.HASHES - curveTypes: typeof EXTERNAL.SIGNING.CURVES - encodingTypes: { - NONE: typeof EXTERNAL.SIGNING.ENCODINGS.NONE - SOLANA: typeof EXTERNAL.SIGNING.ENCODINGS.SOLANA - EVM?: typeof EXTERNAL.SIGNING.ENCODINGS.EVM - } + calldataDecoding: { + reserved: number + maxSz: number + } + baseReqSz: number + // See `GENERIC_SIGNING_BASE_MSG_SZ` in firmware + baseDataSz: number + hashTypes: typeof EXTERNAL.SIGNING.HASHES + curveTypes: typeof EXTERNAL.SIGNING.CURVES + encodingTypes: { + NONE: typeof EXTERNAL.SIGNING.ENCODINGS.NONE + SOLANA: typeof EXTERNAL.SIGNING.ENCODINGS.SOLANA + EVM?: typeof EXTERNAL.SIGNING.ENCODINGS.EVM + } } export interface FirmwareConstants { - abiCategorySz: number - abiMaxRmv: number - addrFlagsAllowed: boolean - allowBtcLegacyAndSegwitAddrs: boolean - allowedEthTxTypes: number[] - contractDeployKey: string - eip712MaxTypeParams: number - eip712Supported: boolean - ethMaxDataSz: number - ethMaxGasPrice: number - ethMaxMsgSz: number - ethMsgPreHashAllowed: boolean - extraDataFrameSz: number - extraDataMaxFrames: number - genericSigning: GenericSigningData - getAddressFlags: [ - typeof EXTERNAL.GET_ADDR_FLAGS.ED25519_PUB, - typeof EXTERNAL.GET_ADDR_FLAGS.SECP256K1_PUB, - ] - kvActionMaxNum: number - kvActionsAllowed: boolean - kvKeyMaxStrSz: number - kvRemoveMaxNum: number - kvValMaxStrSz: number - maxDecoderBufSz: number - personalSignHeaderSz: number - prehashAllowed: boolean - reqMaxDataSz: number - varAddrPathSzAllowed: boolean - flexibleAddrPaths?: boolean + abiCategorySz: number + abiMaxRmv: number + addrFlagsAllowed: boolean + allowBtcLegacyAndSegwitAddrs: boolean + allowedEthTxTypes: number[] + contractDeployKey: string + eip712MaxTypeParams: number + eip712Supported: boolean + ethMaxDataSz: number + ethMaxGasPrice: number + ethMaxMsgSz: number + ethMsgPreHashAllowed: boolean + extraDataFrameSz: number + extraDataMaxFrames: number + genericSigning: GenericSigningData + getAddressFlags: [ + typeof EXTERNAL.GET_ADDR_FLAGS.ED25519_PUB, + typeof EXTERNAL.GET_ADDR_FLAGS.SECP256K1_PUB, + ] + kvActionMaxNum: number + kvActionsAllowed: boolean + kvKeyMaxStrSz: number + kvRemoveMaxNum: number + kvValMaxStrSz: number + maxDecoderBufSz: number + personalSignHeaderSz: number + prehashAllowed: boolean + reqMaxDataSz: number + varAddrPathSzAllowed: boolean + flexibleAddrPaths?: boolean } export interface LatticeError { - code: string - errno: string - message: string + code: string + errno: string + message: string } diff --git a/packages/sdk/src/types/getAddresses.ts b/packages/sdk/src/types/getAddresses.ts index 91d1704a..fa780c28 100644 --- a/packages/sdk/src/types/getAddresses.ts +++ b/packages/sdk/src/types/getAddresses.ts @@ -1,13 +1,13 @@ import type { Client } from '../client' export interface GetAddressesRequestParams { - startPath: number[] - n: number - flag?: number - iterIdx?: number + startPath: number[] + n: number + flag?: number + iterIdx?: number } export interface GetAddressesRequestFunctionParams - extends GetAddressesRequestParams { - client: Client + extends GetAddressesRequestParams { + client: Client } diff --git a/packages/sdk/src/types/getKvRecords.ts b/packages/sdk/src/types/getKvRecords.ts index 73041dd4..a95c1df9 100644 --- a/packages/sdk/src/types/getKvRecords.ts +++ b/packages/sdk/src/types/getKvRecords.ts @@ -1,26 +1,26 @@ import type { Client } from '../client' export interface GetKvRecordsRequestParams { - type?: number - n?: number - start?: number + type?: number + n?: number + start?: number } export interface GetKvRecordsRequestFunctionParams - extends GetKvRecordsRequestParams { - client: Client + extends GetKvRecordsRequestParams { + client: Client } export type AddressTag = { - caseSensitive: boolean - id: number - key: string - type: number - val: string + caseSensitive: boolean + id: number + key: string + type: number + val: string } export interface GetKvRecordsData { - records: AddressTag[] - fetched: number - total: number + records: AddressTag[] + fetched: number + total: number } diff --git a/packages/sdk/src/types/index.ts b/packages/sdk/src/types/index.ts index 3260b316..770fbbaa 100644 --- a/packages/sdk/src/types/index.ts +++ b/packages/sdk/src/types/index.ts @@ -47,52 +47,52 @@ export * from './utils' // Exports from client.ts export type { - Currency, - SigningPath, - SignData, - SigningRequestResponse, - TransactionPayload, - Wallet, - ActiveWallets, - RequestParams, - ClientStateData, + Currency, + SigningPath, + SignData, + SigningRequestResponse, + TransactionPayload, + Wallet, + ActiveWallets, + RequestParams, + ClientStateData, } from './client' // Exports from addKvRecords.ts export type { - AddKvRecordsRequestParams, - AddKvRecordsRequestFunctionParams, + AddKvRecordsRequestParams, + AddKvRecordsRequestFunctionParams, } from './addKvRecords' // Exports from fetchEncData.ts export type { - EIP2335KeyExportReq, - FetchEncDataRequest, - FetchEncDataRequestFunctionParams, - EIP2335KeyExportData, + EIP2335KeyExportReq, + FetchEncDataRequest, + FetchEncDataRequestFunctionParams, + EIP2335KeyExportData, } from './fetchEncData' // Exports from getKvRecords.ts export type { - GetKvRecordsRequestParams, - GetKvRecordsRequestFunctionParams, - AddressTag, - GetKvRecordsData, + GetKvRecordsRequestParams, + GetKvRecordsRequestFunctionParams, + AddressTag, + GetKvRecordsData, } from './getKvRecords' // Exports from removeKvRecords.ts export type { - RemoveKvRecordsRequestParams, - RemoveKvRecordsRequestFunctionParams, + RemoveKvRecordsRequestParams, + RemoveKvRecordsRequestFunctionParams, } from './removeKvRecords' // Exports from shared.ts export type { - KVRecords, - EncrypterParams, - KeyPair, - WalletPath, - DecryptedResponse, + KVRecords, + EncrypterParams, + KeyPair, + WalletPath, + DecryptedResponse, } from './shared' // Note: We don't export from vitest.d.ts as it's a declaration file for Vitest diff --git a/packages/sdk/src/types/messages.ts b/packages/sdk/src/types/messages.ts index 4371d507..24616fb5 100644 --- a/packages/sdk/src/types/messages.ts +++ b/packages/sdk/src/types/messages.ts @@ -1,19 +1,19 @@ import type { LatticeMsgType } from '../protocol' export interface LatticeMessageHeader { - // Protocol version. Should always be 0x01 - // [uint8] - version: number - // Protocol request type. Should always be 0x02 - // for "secure" message type. - // [uint8] - type: LatticeMsgType - // Random message ID for internal tracking in firmware - // [4 bytes] - id: Buffer - // Length of payload data being used - // For an encrypted request, this indicates the - // size of the non-zero decrypted data. - // [uint16] - len: number + // Protocol version. Should always be 0x01 + // [uint8] + version: number + // Protocol request type. Should always be 0x02 + // for "secure" message type. + // [uint8] + type: LatticeMsgType + // Random message ID for internal tracking in firmware + // [4 bytes] + id: Buffer + // Length of payload data being used + // For an encrypted request, this indicates the + // size of the non-zero decrypted data. + // [uint16] + len: number } diff --git a/packages/sdk/src/types/pair.ts b/packages/sdk/src/types/pair.ts index d72b9391..ba75116a 100644 --- a/packages/sdk/src/types/pair.ts +++ b/packages/sdk/src/types/pair.ts @@ -1,6 +1,6 @@ import type { Client } from '../client' export interface PairRequestParams { - pairingSecret: string - client: Client + pairingSecret: string + client: Client } diff --git a/packages/sdk/src/types/removeKvRecords.ts b/packages/sdk/src/types/removeKvRecords.ts index 25d9e9c6..1b47016b 100644 --- a/packages/sdk/src/types/removeKvRecords.ts +++ b/packages/sdk/src/types/removeKvRecords.ts @@ -1,11 +1,11 @@ import type { Client } from '../client' export interface RemoveKvRecordsRequestParams { - type?: number - ids?: string[] + type?: number + ids?: string[] } export interface RemoveKvRecordsRequestFunctionParams - extends RemoveKvRecordsRequestParams { - client: Client + extends RemoveKvRecordsRequestParams { + client: Client } diff --git a/packages/sdk/src/types/secureMessages.ts b/packages/sdk/src/types/secureMessages.ts index adc83e0e..ccfe842d 100644 --- a/packages/sdk/src/types/secureMessages.ts +++ b/packages/sdk/src/types/secureMessages.ts @@ -2,58 +2,58 @@ import type { LatticeResponseCode, LatticeSecureMsgType } from '../protocol' import type { LatticeMessageHeader } from './messages' export interface LatticeSecureRequest { - // Message header - header: LatticeMessageHeader - // Request data - payload: LatticeSecureRequestPayload + // Message header + header: LatticeMessageHeader + // Request data + payload: LatticeSecureRequestPayload } export interface LatticeSecureRequestPayload { - // Indicates whether this is a connect (0x01) or - // encrypted (0x02) secure request - // [uint8] - requestType: LatticeSecureMsgType - // Request data - // [connect = 65 bytes, encrypted = 1732] - data: Buffer + // Indicates whether this is a connect (0x01) or + // encrypted (0x02) secure request + // [uint8] + requestType: LatticeSecureMsgType + // Request data + // [connect = 65 bytes, encrypted = 1732] + data: Buffer } export interface LatticeSecureConnectResponsePayload { - // [214 bytes] - data: Buffer + // [214 bytes] + data: Buffer } export interface LatticeSecureEncryptedResponsePayload { - // Error code - responseCode: LatticeResponseCode - // Response data - // [3392 bytes] - data: Buffer + // Error code + responseCode: LatticeResponseCode + // Response data + // [3392 bytes] + data: Buffer } export interface LatticeSecureConnectRequestPayloadData { - // Public key corresponding to the static Client keypair - // [65 bytes] - pubkey: Buffer + // Public key corresponding to the static Client keypair + // [65 bytes] + pubkey: Buffer } export interface LatticeSecureEncryptedRequestPayloadData { - // SHA256(sharedSecret).slice(0, 4) - // [uint32] - ephemeralId: number - // Encrypted data envelope - // [1728 bytes] - encryptedData: Buffer + // SHA256(sharedSecret).slice(0, 4) + // [uint32] + ephemeralId: number + // Encrypted data envelope + // [1728 bytes] + encryptedData: Buffer } export interface LatticeSecureDecryptedResponse { - // ECDSA public key that should replace the client's ephemeral key - // [65 bytes] - ephemeralPub: Buffer - // Decrypted response data - // [Variable size] - data: Buffer - // Checksum on response data (ephemeralKey | data) - // [uint32] - checksum: number + // ECDSA public key that should replace the client's ephemeral key + // [65 bytes] + ephemeralPub: Buffer + // Decrypted response data + // [Variable size] + data: Buffer + // Checksum on response data (ephemeralKey | data) + // [uint32] + checksum: number } diff --git a/packages/sdk/src/types/shared.ts b/packages/sdk/src/types/shared.ts index bc0ab9ce..c7b1edfb 100644 --- a/packages/sdk/src/types/shared.ts +++ b/packages/sdk/src/types/shared.ts @@ -1,12 +1,12 @@ import type { ec } from 'elliptic' export interface KVRecords { - [key: string]: string + [key: string]: string } export interface EncrypterParams { - payload: Buffer - sharedSecret: Buffer + payload: Buffer + sharedSecret: Buffer } export type KeyPair = ec.KeyPair @@ -14,6 +14,6 @@ export type KeyPair = ec.KeyPair export type WalletPath = [number, number, number, number, number] export interface DecryptedResponse { - decryptedData: Buffer - newEphemeralPub: KeyPair + decryptedData: Buffer + newEphemeralPub: KeyPair } diff --git a/packages/sdk/src/types/sign.ts b/packages/sdk/src/types/sign.ts index d5994e88..cf2ca116 100644 --- a/packages/sdk/src/types/sign.ts +++ b/packages/sdk/src/types/sign.ts @@ -1,11 +1,11 @@ import type { - AccessList, - Address, - Hex, - SignedAuthorization, - SignedAuthorizationList, - TypedData, - TypedDataDefinition, + AccessList, + Address, + Hex, + SignedAuthorization, + SignedAuthorizationList, + TypedData, + TypedDataDefinition, } from 'viem' import type { Client } from '../client' import type { Currency, SigningPath, Wallet } from './client' @@ -14,180 +14,180 @@ import type { FirmwareConstants } from './firmware' export type ETH_MESSAGE_PROTOCOLS = 'eip712' | 'signPersonal' export const TRANSACTION_TYPE = { - LEGACY: 0, - EIP2930: 1, - EIP1559: 2, - EIP7702_AUTH: 4, - EIP7702_AUTH_LIST: 5, + LEGACY: 0, + EIP2930: 1, + EIP1559: 2, + EIP7702_AUTH: 4, + EIP7702_AUTH_LIST: 5, } as const // Base transaction request with common fields type BaseTransactionRequest = { - from?: Address - to: Address - value?: Hex | bigint - data?: Hex - chainId: number - nonce: number - gasLimit?: Hex | bigint + from?: Address + to: Address + value?: Hex | bigint + data?: Hex + chainId: number + nonce: number + gasLimit?: Hex | bigint } // Legacy transaction request type LegacyTransactionRequest = BaseTransactionRequest & { - type: typeof TRANSACTION_TYPE.LEGACY - gasPrice: Hex | bigint + type: typeof TRANSACTION_TYPE.LEGACY + gasPrice: Hex | bigint } // EIP-2930 transaction request type EIP2930TransactionRequest = BaseTransactionRequest & { - type: typeof TRANSACTION_TYPE.EIP2930 - gasPrice: Hex | bigint - accessList?: AccessList + type: typeof TRANSACTION_TYPE.EIP2930 + gasPrice: Hex | bigint + accessList?: AccessList } // EIP-1559 transaction request type EIP1559TransactionRequest = BaseTransactionRequest & { - type: typeof TRANSACTION_TYPE.EIP1559 - maxFeePerGas: Hex | bigint - maxPriorityFeePerGas: Hex | bigint - accessList?: AccessList + type: typeof TRANSACTION_TYPE.EIP1559 + maxFeePerGas: Hex | bigint + maxPriorityFeePerGas: Hex | bigint + accessList?: AccessList } // EIP-7702 single authorization transaction request (type 4) export type EIP7702AuthTransactionRequest = BaseTransactionRequest & { - type: typeof TRANSACTION_TYPE.EIP7702_AUTH - maxFeePerGas: Hex | bigint - maxPriorityFeePerGas: Hex | bigint - accessList?: AccessList - authorization: SignedAuthorization + type: typeof TRANSACTION_TYPE.EIP7702_AUTH + maxFeePerGas: Hex | bigint + maxPriorityFeePerGas: Hex | bigint + accessList?: AccessList + authorization: SignedAuthorization } // EIP-7702 authorization list transaction request (type 5) export type EIP7702AuthListTransactionRequest = BaseTransactionRequest & { - type: typeof TRANSACTION_TYPE.EIP7702_AUTH_LIST - maxFeePerGas: Hex | bigint - maxPriorityFeePerGas: Hex | bigint - accessList?: AccessList - authorizationList: SignedAuthorizationList + type: typeof TRANSACTION_TYPE.EIP7702_AUTH_LIST + maxFeePerGas: Hex | bigint + maxPriorityFeePerGas: Hex | bigint + accessList?: AccessList + authorizationList: SignedAuthorizationList } // Main discriminated union for transaction requests export type TransactionRequest = - | LegacyTransactionRequest - | EIP2930TransactionRequest - | EIP1559TransactionRequest - | EIP7702AuthTransactionRequest - | EIP7702AuthListTransactionRequest + | LegacyTransactionRequest + | EIP2930TransactionRequest + | EIP1559TransactionRequest + | EIP7702AuthTransactionRequest + | EIP7702AuthListTransactionRequest export interface SigningPayload< - TTypedData extends TypedData | Record = TypedData, + TTypedData extends TypedData | Record = TypedData, > { - signerPath: SigningPath - payload: - | Uint8Array - | Uint8Array[] - | Buffer - | Buffer[] - | Hex - | EIP712MessagePayload - curveType: number - hashType: number - encodingType?: number - protocol?: ETH_MESSAGE_PROTOCOLS - decoder?: Buffer + signerPath: SigningPath + payload: + | Uint8Array + | Uint8Array[] + | Buffer + | Buffer[] + | Hex + | EIP712MessagePayload + curveType: number + hashType: number + encodingType?: number + protocol?: ETH_MESSAGE_PROTOCOLS + decoder?: Buffer } export interface SignRequestParams< - TTypedData extends TypedData | Record = TypedData, + TTypedData extends TypedData | Record = TypedData, > { - data: SigningPayload | BitcoinSignPayload - currency?: Currency - cachedData?: unknown - nextCode?: Buffer + data: SigningPayload | BitcoinSignPayload + currency?: Currency + cachedData?: unknown + nextCode?: Buffer } export interface SignRequestFunctionParams< - TTypedData extends TypedData | Record = TypedData, + TTypedData extends TypedData | Record = TypedData, > extends SignRequestParams { - client: Client + client: Client } export interface EncodeSignRequestParams { - fwConstants: FirmwareConstants - wallet: Wallet - requestData: unknown - cachedData?: unknown - nextCode?: Buffer + fwConstants: FirmwareConstants + wallet: Wallet + requestData: unknown + cachedData?: unknown + nextCode?: Buffer } export interface SignRequest { - payload: Buffer - schema: number + payload: Buffer + schema: number } export interface EthSignRequest extends SignRequest { - curveType: number - encodingType: number - hashType: number - omitPubkey: boolean - origPayloadBuf: Buffer - extraDataPayloads: Buffer[] + curveType: number + encodingType: number + hashType: number + omitPubkey: boolean + origPayloadBuf: Buffer + extraDataPayloads: Buffer[] } export interface EthMsgSignRequest extends SignRequest { - input: { - signerPath: SigningPath - payload: Buffer - protocol: string - fwConstants: FirmwareConstants - } + input: { + signerPath: SigningPath + payload: Buffer + protocol: string + fwConstants: FirmwareConstants + } } export interface BitcoinSignRequest extends SignRequest { - origData: { - prevOuts: PreviousOutput[] - recipient: string - value: number - fee: number - changePath: number[] - fwConstants: FirmwareConstants - } - changeData?: { value: number } + origData: { + prevOuts: PreviousOutput[] + recipient: string + value: number + fee: number + changePath: number[] + fwConstants: FirmwareConstants + } + changeData?: { value: number } } export type PreviousOutput = { - txHash: string - value: number - index: number - signerPath: number[] + txHash: string + value: number + index: number + signerPath: number[] } export type BitcoinSignPayload = { - prevOuts: PreviousOutput[] - recipient: string - value: number - fee: number - changePath: number[] + prevOuts: PreviousOutput[] + recipient: string + value: number + fee: number + changePath: number[] } export interface DecodeSignResponseParams { - data: Buffer - request: SignRequest - isGeneric: boolean - currency?: Currency + data: Buffer + request: SignRequest + isGeneric: boolean + currency?: Currency } // Align EIP712MessagePayload with Viem's TypedDataDefinition export interface EIP712MessagePayload< - TTypedData extends TypedData | Record = TypedData, - TPrimaryType extends keyof TTypedData | 'EIP712Domain' = keyof TTypedData, + TTypedData extends TypedData | Record = TypedData, + TPrimaryType extends keyof TTypedData | 'EIP712Domain' = keyof TTypedData, > { - types: TTypedData - domain: TTypedData extends TypedData - ? TypedDataDefinition['domain'] - : Record - primaryType: TPrimaryType - message: TTypedData extends TypedData - ? TypedDataDefinition['message'] - : Record + types: TTypedData + domain: TTypedData extends TypedData + ? TypedDataDefinition['domain'] + : Record + primaryType: TPrimaryType + message: TTypedData extends TypedData + ? TypedDataDefinition['message'] + : Record } diff --git a/packages/sdk/src/types/utils.ts b/packages/sdk/src/types/utils.ts index bc5ad835..7e50d708 100644 --- a/packages/sdk/src/types/utils.ts +++ b/packages/sdk/src/types/utils.ts @@ -1,34 +1,34 @@ import type { Client } from '../client' export interface TestRequestPayload { - payload: Buffer - testID: number - client: Client + payload: Buffer + testID: number + client: Client } export interface EthDepositInfo { - networkName: string - forkVersion: Buffer - validatorsRoot: Buffer + networkName: string + forkVersion: Buffer + validatorsRoot: Buffer } export interface EthDepositDataReq { - // (optional) BLS withdrawal key or ETH1 withdrawal address - withdrawalKey?: Buffer | string - // Amount to be deposited in GWei (10**9 wei) - amountGwei: number - // Info about the chain we are using. - // You probably shouldn't change this unless you know what you're doing. - info: EthDepositInfo - // In order to be compatible with Ethereum's online launchpad, you need - // to set the CLI version. Obviously we are not using the CLI here but - // we are following the protocol outlined in v2.3.0. - depositCliVersion: string + // (optional) BLS withdrawal key or ETH1 withdrawal address + withdrawalKey?: Buffer | string + // Amount to be deposited in GWei (10**9 wei) + amountGwei: number + // Info about the chain we are using. + // You probably shouldn't change this unless you know what you're doing. + info: EthDepositInfo + // In order to be compatible with Ethereum's online launchpad, you need + // to set the CLI version. Obviously we are not using the CLI here but + // we are following the protocol outlined in v2.3.0. + depositCliVersion: string } export interface EthDepositDataResp { - // Validator's pubkey as a hex string - pubkey: string - // JSON encoded deposit data - depositData: string + // Validator's pubkey as a hex string + pubkey: string + // JSON encoded deposit data + depositData: string } diff --git a/packages/sdk/src/types/vitest.d.ts b/packages/sdk/src/types/vitest.d.ts index 508f2564..8c44ccc2 100644 --- a/packages/sdk/src/types/vitest.d.ts +++ b/packages/sdk/src/types/vitest.d.ts @@ -1,8 +1,8 @@ export {} declare global { - namespace Vi { - interface JestAssertion { - toEqualElseLog(a: any, msg: string): any - } - } + namespace Vi { + interface JestAssertion { + toEqualElseLog(a: any, msg: string): any + } + } } diff --git a/packages/sdk/src/util.ts b/packages/sdk/src/util.ts index ba0433c3..d9f65e9b 100644 --- a/packages/sdk/src/util.ts +++ b/packages/sdk/src/util.ts @@ -16,16 +16,16 @@ const EC = elliptic.ec const { ecdsaRecover } = secp256k1 import { Calldata } from '.' import { - BIP_CONSTANTS, - EXTERNAL_NETWORKS_BY_CHAIN_ID_URL, - HARDENED_OFFSET, - NETWORKS_BY_CHAIN_ID, - VERSION_BYTE, + BIP_CONSTANTS, + EXTERNAL_NETWORKS_BY_CHAIN_ID_URL, + HARDENED_OFFSET, + NETWORKS_BY_CHAIN_ID, + VERSION_BYTE, } from './constants' import { LatticeResponseCode, ProtocolConstants } from './protocol' import { - isValid4ByteResponse, - isValidBlockExplorerResponse, + isValid4ByteResponse, + isValidBlockExplorerResponse, } from './shared/validators' import type { FirmwareConstants } from './types' @@ -38,88 +38,88 @@ let ec: any /** @internal Parse a response from the Lattice1 */ export const parseLattice1Response = ( - r: string, + r: string, ): { - errorMessage?: string - responseCode?: number - data?: Buffer + errorMessage?: string + responseCode?: number + data?: Buffer } => { - const parsed: { - errorMessage: string | null - data: Buffer | null - responseCode?: number - } = { - errorMessage: null, - data: null, - } - const b = Buffer.from(r, 'hex') - let off = 0 - - // Get protocol version - const protoVer = b.readUInt8(off) - off++ - if (protoVer !== VERSION_BYTE) { - parsed.errorMessage = 'Incorrect protocol version. Please update your SDK' - return parsed - } - - // Get the type of response - // Should always be 0x00 - const msgType = b.readUInt8(off) - off++ - if (msgType !== 0x00) { - parsed.errorMessage = 'Incorrect response from Lattice1' - return parsed - } - - // Get the payload - b.readUInt32BE(off) - off += 4 // First 4 bytes is the id, but we don't need that anymore - const len = b.readUInt16BE(off) - off += 2 - const payload = b.slice(off, off + len) - off += len - - // Get response code - const responseCode = payload.readUInt8(0) - if (responseCode !== LatticeResponseCode.success) { - const errMsg = ProtocolConstants.responseMsg[responseCode] - parsed.errorMessage = `[Lattice] ${errMsg ? errMsg : 'Unknown Error'}` - parsed.responseCode = responseCode - return parsed - } else { - parsed.data = payload.slice(1, payload.length) - } - - // Verify checksum - const cs = b.readUInt32BE(off) - const expectedCs = checksum(b.slice(0, b.length - 4)) - if (cs !== expectedCs) { - parsed.errorMessage = 'Invalid checksum from device response' - parsed.data = null - return parsed - } - - return parsed + const parsed: { + errorMessage: string | null + data: Buffer | null + responseCode?: number + } = { + errorMessage: null, + data: null, + } + const b = Buffer.from(r, 'hex') + let off = 0 + + // Get protocol version + const protoVer = b.readUInt8(off) + off++ + if (protoVer !== VERSION_BYTE) { + parsed.errorMessage = 'Incorrect protocol version. Please update your SDK' + return parsed + } + + // Get the type of response + // Should always be 0x00 + const msgType = b.readUInt8(off) + off++ + if (msgType !== 0x00) { + parsed.errorMessage = 'Incorrect response from Lattice1' + return parsed + } + + // Get the payload + b.readUInt32BE(off) + off += 4 // First 4 bytes is the id, but we don't need that anymore + const len = b.readUInt16BE(off) + off += 2 + const payload = b.slice(off, off + len) + off += len + + // Get response code + const responseCode = payload.readUInt8(0) + if (responseCode !== LatticeResponseCode.success) { + const errMsg = ProtocolConstants.responseMsg[responseCode] + parsed.errorMessage = `[Lattice] ${errMsg ? errMsg : 'Unknown Error'}` + parsed.responseCode = responseCode + return parsed + } else { + parsed.data = payload.slice(1, payload.length) + } + + // Verify checksum + const cs = b.readUInt32BE(off) + const expectedCs = checksum(b.slice(0, b.length - 4)) + if (cs !== expectedCs) { + parsed.errorMessage = 'Invalid checksum from device response' + parsed.data = null + return parsed + } + + return parsed } /** @internal */ export const checksum = (x: Buffer): number => { - // crc32 returns a signed integer - need to cast it to unsigned - // Note that this uses the default 0xedb88320 polynomial - return crc32.buf(x) >>> 0 // Need this to be a uint, hence the bit shift + // crc32 returns a signed integer - need to cast it to unsigned + // Note that this uses the default 0xedb88320 polynomial + return crc32.buf(x) >>> 0 // Need this to be a uint, hence the bit shift } // Get a 74-byte padded DER-encoded signature buffer // `sig` must be the signature output from elliptic.js /** @internal */ export const toPaddedDER = (sig: any): Buffer => { - // We use 74 as the maximum length of a DER signature. All sigs must - // be right-padded with zeros so that this can be a fixed size field - const b = Buffer.alloc(74) - const ds = Buffer.from(sig.toDER()) - ds.copy(b) - return b + // We use 74 as the maximum length of a DER signature. All sigs must + // be right-padded with zeros so that this can be a fixed size field + const b = Buffer.alloc(74) + const ds = Buffer.from(sig.toDER()) + ds.copy(b) + return b } //-------------------------------------------------- @@ -127,103 +127,103 @@ export const toPaddedDER = (sig: any): Buffer => { //-------------------------------------------------- /** @internal */ export const isValidAssetPath = ( - path: number[], - fwConstants: FirmwareConstants, + path: number[], + fwConstants: FirmwareConstants, ): boolean => { - const allowedPurposes = [ - PURPOSES.ETH, - PURPOSES.BTC_LEGACY, - PURPOSES.BTC_WRAPPED_SEGWIT, - PURPOSES.BTC_SEGWIT, - ] - const allowedCoins = [COINS.ETH, COINS.BTC, COINS.BTC_TESTNET] - // These coin types were given to us by MyCrypto. They should be allowed, but we expect - // an Ethereum-type address with these coin types. - // These all use SLIP44: https://github.com/satoshilabs/slips/blob/master/slip-0044.md - const allowedMyCryptoCoins = [ - 60, 61, 966, 700, 9006, 9000, 1007, 553, 178, 137, 37310, 108, 40, 889, - 1987, 820, 6060, 1620, 1313114, 76, 246529, 246785, 1001, 227, 916, 464, - 2221, 344, 73799, 246, - ] - // Make sure firmware supports this Bitcoin path - const isBitcoin = path[1] === COINS.BTC || path[1] === COINS.BTC_TESTNET - const isBitcoinNonWrappedSegwit = - isBitcoin && path[0] !== PURPOSES.BTC_WRAPPED_SEGWIT - if (isBitcoinNonWrappedSegwit && !fwConstants.allowBtcLegacyAndSegwitAddrs) - return false - // Make sure this path is otherwise valid - return ( - allowedPurposes.indexOf(path[0]) >= 0 && - (allowedCoins.indexOf(path[1]) >= 0 || - allowedMyCryptoCoins.indexOf(path[1] - HARDENED_OFFSET) > 0) - ) + const allowedPurposes = [ + PURPOSES.ETH, + PURPOSES.BTC_LEGACY, + PURPOSES.BTC_WRAPPED_SEGWIT, + PURPOSES.BTC_SEGWIT, + ] + const allowedCoins = [COINS.ETH, COINS.BTC, COINS.BTC_TESTNET] + // These coin types were given to us by MyCrypto. They should be allowed, but we expect + // an Ethereum-type address with these coin types. + // These all use SLIP44: https://github.com/satoshilabs/slips/blob/master/slip-0044.md + const allowedMyCryptoCoins = [ + 60, 61, 966, 700, 9006, 9000, 1007, 553, 178, 137, 37310, 108, 40, 889, + 1987, 820, 6060, 1620, 1313114, 76, 246529, 246785, 1001, 227, 916, 464, + 2221, 344, 73799, 246, + ] + // Make sure firmware supports this Bitcoin path + const isBitcoin = path[1] === COINS.BTC || path[1] === COINS.BTC_TESTNET + const isBitcoinNonWrappedSegwit = + isBitcoin && path[0] !== PURPOSES.BTC_WRAPPED_SEGWIT + if (isBitcoinNonWrappedSegwit && !fwConstants.allowBtcLegacyAndSegwitAddrs) + return false + // Make sure this path is otherwise valid + return ( + allowedPurposes.indexOf(path[0]) >= 0 && + (allowedCoins.indexOf(path[1]) >= 0 || + allowedMyCryptoCoins.indexOf(path[1] - HARDENED_OFFSET) > 0) + ) } /** @internal */ export const splitFrames = (data: Buffer, frameSz: number): Buffer[] => { - const frames = [] - const n = Math.ceil(data.length / frameSz) - let off = 0 - for (let i = 0; i < n; i++) { - frames.push(data.slice(off, off + frameSz)) - off += frameSz - } - return frames + const frames = [] + const n = Math.ceil(data.length / frameSz) + let off = 0 + for (let i = 0; i < n; i++) { + frames.push(data.slice(off, off + frameSz)) + off += frameSz + } + return frames } /** @internal */ function isBase10NumStr(x: string): boolean { - const bn = new BigNum(x).toFixed().split('.').join('') - const s = new String(x) - // Note that the JS native `String()` loses precision for large numbers, but we only - // want to validate the base of the number so we don't care about far out precision. - return bn.slice(0, 8) === s.slice(0, 8) + const bn = new BigNum(x).toFixed().split('.').join('') + const s = new String(x) + // Note that the JS native `String()` loses precision for large numbers, but we only + // want to validate the base of the number so we don't care about far out precision. + return bn.slice(0, 8) === s.slice(0, 8) } /** @internal Ensure a param is represented by a buffer */ export const ensureHexBuffer = ( - x: string | number | bigint | Buffer, - zeroIsNull = true, + x: string | number | bigint | Buffer, + zeroIsNull = true, ): Buffer => { - try { - const isZeroNumber = typeof x === 'number' && x === 0 - const isZeroBigInt = typeof x === 'bigint' && x === 0n - if (x === null || ((isZeroNumber || isZeroBigInt) && zeroIsNull === true)) - return Buffer.alloc(0) - const isDecimalInput = - typeof x === 'number' || - typeof x === 'bigint' || - (typeof x === 'string' && isBase10NumStr(x)) - let hexString: string - if (isDecimalInput) { - const formatted = - typeof x === 'bigint' ? x.toString(10) : (x as string | number) - hexString = new BigNum(formatted).toString(16) - } else if (typeof x === 'string' && x.slice(0, 2) === '0x') { - hexString = x.slice(2) - } else if (Buffer.isBuffer(x)) { - return x - } else { - hexString = x.toString() - } - if (hexString.length % 2 > 0) hexString = `0${hexString}` - if (hexString === '00' && !isDecimalInput) return Buffer.alloc(0) - return Buffer.from(hexString, 'hex') - } catch (_err) { - throw new Error( - `Cannot convert ${x.toString()} to hex buffer (${(_err as Error).message})`, - ) - } + try { + const isZeroNumber = typeof x === 'number' && x === 0 + const isZeroBigInt = typeof x === 'bigint' && x === 0n + if (x === null || ((isZeroNumber || isZeroBigInt) && zeroIsNull === true)) + return Buffer.alloc(0) + const isDecimalInput = + typeof x === 'number' || + typeof x === 'bigint' || + (typeof x === 'string' && isBase10NumStr(x)) + let hexString: string + if (isDecimalInput) { + const formatted = + typeof x === 'bigint' ? x.toString(10) : (x as string | number) + hexString = new BigNum(formatted).toString(16) + } else if (typeof x === 'string' && x.slice(0, 2) === '0x') { + hexString = x.slice(2) + } else if (Buffer.isBuffer(x)) { + return x + } else { + hexString = x.toString() + } + if (hexString.length % 2 > 0) hexString = `0${hexString}` + if (hexString === '00' && !isDecimalInput) return Buffer.alloc(0) + return Buffer.from(hexString, 'hex') + } catch (_err) { + throw new Error( + `Cannot convert ${x.toString()} to hex buffer (${(_err as Error).message})`, + ) + } } /** @internal */ export const fixLen = (msg: Buffer, length: number): Buffer => { - const buf = Buffer.alloc(length) - if (msg.length < length) { - msg.copy(buf, length - msg.length) - return buf - } - return msg.slice(-length) + const buf = Buffer.alloc(length) + if (msg.length < length) { + msg.copy(buf, length - msg.length) + return buf + } + return msg.slice(-length) } //-------------------------------------------------- @@ -231,72 +231,72 @@ export const fixLen = (msg: Buffer, length: number): Buffer => { //-------------------------------------------------- /** @internal */ export const aes256_encrypt = (data: Buffer, key: Buffer): Buffer => { - const iv = Buffer.from(ProtocolConstants.aesIv) - const aesCbc = new aes.ModeOfOperation.cbc(key, iv) - const paddedData = data.length % 16 === 0 ? data : aes.padding.pkcs7.pad(data) - return Buffer.from(aesCbc.encrypt(paddedData)) + const iv = Buffer.from(ProtocolConstants.aesIv) + const aesCbc = new aes.ModeOfOperation.cbc(key, iv) + const paddedData = data.length % 16 === 0 ? data : aes.padding.pkcs7.pad(data) + return Buffer.from(aesCbc.encrypt(paddedData)) } /** @internal */ export const aes256_decrypt = (data: Buffer, key: Buffer): Buffer => { - const iv = Buffer.from(ProtocolConstants.aesIv) - const aesCbc = new aes.ModeOfOperation.cbc(key, iv) - return Buffer.from(aesCbc.decrypt(data)) + const iv = Buffer.from(ProtocolConstants.aesIv) + const aesCbc = new aes.ModeOfOperation.cbc(key, iv) + return Buffer.from(aesCbc.decrypt(data)) } // Decode a DER signature. Returns signature object {r, s } or null if there is an error /** @internal */ export const parseDER = (sigBuf: Buffer) => { - if (sigBuf[0] !== 0x30 || sigBuf[2] !== 0x02) - throw new Error('Failed to decode DER signature') - let off = 3 - const rLen = sigBuf[off] - off++ - const r = sigBuf.slice(off, off + rLen) - off += rLen - if (sigBuf[off] !== 0x02) throw new Error('Failed to decode DER signature') - off++ - const sLen = sigBuf[off] - off++ - const s = sigBuf.slice(off, off + sLen) - return { r, s } + if (sigBuf[0] !== 0x30 || sigBuf[2] !== 0x02) + throw new Error('Failed to decode DER signature') + let off = 3 + const rLen = sigBuf[off] + off++ + const r = sigBuf.slice(off, off + rLen) + off += rLen + if (sigBuf[off] !== 0x02) throw new Error('Failed to decode DER signature') + off++ + const sLen = sigBuf[off] + off++ + const s = sigBuf.slice(off, off + sLen) + return { r, s } } /** @internal */ export const getP256KeyPair = (priv: Buffer | string): any => { - if (ec === undefined) ec = new EC('p256') - return ec.keyFromPrivate(priv, 'hex') + if (ec === undefined) ec = new EC('p256') + return ec.keyFromPrivate(priv, 'hex') } /** @internal */ export const getP256KeyPairFromPub = (pub: Buffer | string): any => { - if (ec === undefined) ec = new EC('p256') - // Convert Buffer to hex string if needed - const pubHex = Buffer.isBuffer(pub) ? pub.toString('hex') : pub - return ec.keyFromPublic(pubHex, 'hex') + if (ec === undefined) ec = new EC('p256') + // Convert Buffer to hex string if needed + const pubHex = Buffer.isBuffer(pub) ? pub.toString('hex') : pub + return ec.keyFromPublic(pubHex, 'hex') } /** @internal */ export const buildSignerPathBuf = ( - signerPath: number[], - varAddrPathSzAllowed: boolean, + signerPath: number[], + varAddrPathSzAllowed: boolean, ): Buffer => { - const buf = Buffer.alloc(24) - let off = 0 - if (varAddrPathSzAllowed && signerPath.length > 5) - throw new Error('Signer path must be <=5 indices.') - if (!varAddrPathSzAllowed && signerPath.length !== 5) - throw new Error( - 'Your Lattice firmware only supports 5-index derivation paths. Please upgrade.', - ) - buf.writeUInt32LE(signerPath.length, off) - off += 4 - for (let i = 0; i < 5; i++) { - if (i < signerPath.length) buf.writeUInt32LE(signerPath[i], off) - else buf.writeUInt32LE(0, off) - off += 4 - } - return buf + const buf = Buffer.alloc(24) + let off = 0 + if (varAddrPathSzAllowed && signerPath.length > 5) + throw new Error('Signer path must be <=5 indices.') + if (!varAddrPathSzAllowed && signerPath.length !== 5) + throw new Error( + 'Your Lattice firmware only supports 5-index derivation paths. Please upgrade.', + ) + buf.writeUInt32LE(signerPath.length, off) + off += 4 + for (let i = 0; i < 5; i++) { + if (i < signerPath.length) buf.writeUInt32LE(signerPath[i], off) + else buf.writeUInt32LE(0, off) + off += 4 + } + return buf } //-------------------------------------------------- @@ -304,35 +304,35 @@ export const buildSignerPathBuf = ( //-------------------------------------------------- /** @internal */ export const isAsciiStr = (str: string, allowFormatChars = false): boolean => { - if (typeof str !== 'string') { - return false - } - const extraChars = allowFormatChars - ? [ - 0x0020, // Space - 0x000a, // New line - ] - : [] - for (let i = 0; i < str.length; i++) { - const c = str.charCodeAt(i) - if (extraChars.indexOf(c) < 0 && (c < 0x0020 || c > 0x007f)) { - return false - } - } - return true + if (typeof str !== 'string') { + return false + } + const extraChars = allowFormatChars + ? [ + 0x0020, // Space + 0x000a, // New line + ] + : [] + for (let i = 0; i < str.length; i++) { + const c = str.charCodeAt(i) + if (extraChars.indexOf(c) < 0 && (c < 0x0020 || c > 0x007f)) { + return false + } + } + return true } /** @internal Check if a value exists in an object. Only checks first level of keys. */ export const existsIn = (val: T, obj: { [key: string]: T }): boolean => - Object.keys(obj).some((key) => obj[key] === val) + Object.keys(obj).some((key) => obj[key] === val) /** @internal Create a buffer of size `n` and fill it with random data */ export const randomBytes = (n: number): Buffer => { - const buf = Buffer.alloc(n) - for (let i = 0; i < n; i++) { - buf[i] = Math.round(Math.random() * 255) - } - return buf + const buf = Buffer.alloc(n) + for (let i = 0; i < n; i++) { + buf[i] = Math.round(Math.random() * 255) + } + return buf } /** @internal `isUInt4` accepts a number and returns true if it is a UInt4 */ @@ -343,40 +343,40 @@ export const isUInt4 = (n: number) => isInteger(n) && inRange(n, 0, 16) * returns the parsed JSON. */ async function fetchExternalNetworkForChainId( - chainId: number | string, + chainId: number | string, ): Promise<{ - [key: string]: { - name: string - baseUrl: string - apiRoute: string - } + [key: string]: { + name: string + baseUrl: string + apiRoute: string + } }> { - try { - const body = await fetch(EXTERNAL_NETWORKS_BY_CHAIN_ID_URL).then((res) => - res.json(), - ) - if (body) { - return body[chainId] - } else { - return undefined - } - } catch (_err) { - console.warn('Fetching external networks failed.\n', _err) - } + try { + const body = await fetch(EXTERNAL_NETWORKS_BY_CHAIN_ID_URL).then((res) => + res.json(), + ) + if (body) { + return body[chainId] + } else { + return undefined + } + } catch (_err) { + console.warn('Fetching external networks failed.\n', _err) + } } /** * Builds a URL for fetching calldata from block explorers for any supported chains * */ function buildUrlForSupportedChainAndAddress({ supportedChain, address }) { - const baseUrl = supportedChain.baseUrl - const apiRoute = supportedChain.apiRoute - const urlWithRoute = `${baseUrl}/${apiRoute}&address=${address}` + const baseUrl = supportedChain.baseUrl + const apiRoute = supportedChain.apiRoute + const urlWithRoute = `${baseUrl}/${apiRoute}&address=${address}` - const apiKey = process.env.ETHERSCAN_KEY - const apiKeyParam = apiKey ? `&apiKey=${process.env.ETHERSCAN_KEY}` : '' + const apiKey = process.env.ETHERSCAN_KEY + const apiKeyParam = apiKey ? `&apiKey=${process.env.ETHERSCAN_KEY}` : '' - return urlWithRoute + apiKeyParam + return urlWithRoute + apiKeyParam } /** @@ -384,127 +384,127 @@ function buildUrlForSupportedChainAndAddress({ supportedChain, address }) { * matches the selector. */ export function selectDefFrom4byteABI(abiData: any[], selector: string) { - if (abiData.length > 1) { - console.warn('WARNING: There are multiple results. Using the first one.') - } - let def: unknown[] | undefined - abiData - .sort((a, b) => { - const aTime = new Date(a.created_at).getTime() - const bTime = new Date(b.created_at).getTime() - return aTime - bTime - }) - .find((result) => { - try { - def = Calldata.EVM.parsers.parseCanonicalName( - selector, - result.text_signature, - ) - return !!def - } catch (_err) { - console.error('Failed to parse canonical name:', _err) - return false - } - }) - if (def) { - return def - } else { - throw new Error('Could not find definition for selector') - } + if (abiData.length > 1) { + console.warn('WARNING: There are multiple results. Using the first one.') + } + let def: unknown[] | undefined + abiData + .sort((a, b) => { + const aTime = new Date(a.created_at).getTime() + const bTime = new Date(b.created_at).getTime() + return aTime - bTime + }) + .find((result) => { + try { + def = Calldata.EVM.parsers.parseCanonicalName( + selector, + result.text_signature, + ) + return !!def + } catch (_err) { + console.error('Failed to parse canonical name:', _err) + return false + } + }) + if (def) { + return def + } else { + throw new Error('Could not find definition for selector') + } } export async function fetchWithTimeout( - url: string, - options: RequestInit & { timeout?: number }, + url: string, + options: RequestInit & { timeout?: number }, ): Promise { - const { timeout = 8000 } = options - const controller = new AbortController() - const timeoutId = setTimeout(() => controller.abort(), timeout) - const response = await fetch(url, { - ...options, - signal: controller.signal, - }) - clearTimeout(timeoutId) - return response + const { timeout = 8000 } = options + const controller = new AbortController() + const timeoutId = setTimeout(() => controller.abort(), timeout) + const response = await fetch(url, { + ...options, + signal: controller.signal, + }) + clearTimeout(timeoutId) + return response } async function fetchAndCache( - url: string, - opts?: RequestInit, + url: string, + opts?: RequestInit, ): Promise { - try { - if (globalThis.caches && globalThis.Request) { - const cache = await caches.open('gp-calldata') - const request = new Request(url, opts) - const match = await cache.match(request) - if (match) { - return match - } else { - const response = await fetch(request, opts) - const responseClone = response.clone() - const data = await response.json() - if ( - response.ok && - (isValidBlockExplorerResponse(data) || isValid4ByteResponse(data)) - ) { - await cache.put(request, responseClone) - return cache.match(request) - } - return response - } - } else { - return fetch(url, opts) - } - } catch (err) { - console.error(err) - throw err - } + try { + if (globalThis.caches && globalThis.Request) { + const cache = await caches.open('gp-calldata') + const request = new Request(url, opts) + const match = await cache.match(request) + if (match) { + return match + } else { + const response = await fetch(request, opts) + const responseClone = response.clone() + const data = await response.json() + if ( + response.ok && + (isValidBlockExplorerResponse(data) || isValid4ByteResponse(data)) + ) { + await cache.put(request, responseClone) + return cache.match(request) + } + return response + } + } else { + return fetch(url, opts) + } + } catch (err) { + console.error(err) + throw err + } } async function fetchSupportedChainData( - address: string, - supportedChain: number, + address: string, + supportedChain: number, ) { - const url = buildUrlForSupportedChainAndAddress({ address, supportedChain }) - return fetchAndCache(url) - .then((res) => res.json()) - .then((body) => { - if (body?.result) { - try { - return JSON.parse(body.result) - } catch { - throw new Error( - `Invalid JSON in response: ${body.result.substring(0, 50)}`, - ) - } - } else { - throw new Error('Server response was malformed') - } - }) - .catch((error) => { - console.log(error) - throw new Error('Fetching data from external network failed') - }) + const url = buildUrlForSupportedChainAndAddress({ address, supportedChain }) + return fetchAndCache(url) + .then((res) => res.json()) + .then((body) => { + if (body?.result) { + try { + return JSON.parse(body.result) + } catch { + throw new Error( + `Invalid JSON in response: ${body.result.substring(0, 50)}`, + ) + } + } else { + throw new Error('Server response was malformed') + } + }) + .catch((error) => { + console.log(error) + throw new Error('Fetching data from external network failed') + }) } async function fetch4byteData(selector: string): Promise { - const url = `https://www.4byte.directory/api/v1/signatures/?hex_signature=0x${selector}` - return await fetch(url) - .then((res) => res.json()) - .then((body) => { - if (body?.results) { - return body.results - } else { - throw new Error('No results found') - } - }) - .catch((err) => { - throw new Error(`Fetching data from 4byte failed: ${err.message}`) - }) + const url = `https://www.4byte.directory/api/v1/signatures/?hex_signature=0x${selector}` + return await fetch(url) + .then((res) => res.json()) + .then((body) => { + if (body?.results) { + return body.results + } else { + throw new Error('No results found') + } + }) + .catch((err) => { + throw new Error(`Fetching data from 4byte failed: ${err.message}`) + }) } function encodeDef(def: any) { - return Buffer.from(RLP.encode(def)) + return Buffer.from(RLP.encode(def)) } /** @@ -514,41 +514,41 @@ function encodeDef(def: any) { * @return - Updated `def` */ async function postProcessDef(def, calldata) { - // Replace all nested defs if applicable. This is done by looping - // through each param in the definition and if it is of type `bytes` - // or `bytes[]`, checking the param value in `calldata`. If the param - // value (or for `bytes[]` each underlying value) is of size (4 + 32*n) - // it could be nested calldata. We should use that item's selector(s) - // to look up nested definition(s). - const nestedCalldata = Calldata.EVM.processors.getNestedCalldata( - def, - calldata, - ) - const nestedDefs = await replaceNestedDefs(nestedCalldata) - // Need to recurse before doing the full replacement - for await (const [i] of nestedDefs.entries()) { - // If this is an array of nested defs, loop through each one and - // postprocess it. The first item of a single def is the function - // name so we need to check that it isn't a string in this case. - if (Array.isArray(nestedDefs[i]) && typeof nestedDefs[i][0] !== 'string') { - for await (const [j] of nestedDefs[i].entries()) { - if (nestedDefs[i][j] !== null) { - nestedDefs[i][j] = await postProcessDef( - nestedDefs[i][j], - Buffer.from(nestedCalldata[i][j].slice(2), 'hex'), - ) - } - } - } else if (nestedDefs[i] !== null) { - nestedDefs[i] = await postProcessDef( - nestedDefs[i], - Buffer.from(nestedCalldata[i].slice(2), 'hex'), - ) - } - } - // Replace any nested defs - const newDef = Calldata.EVM.processors.replaceNestedDefs(def, nestedDefs) - return newDef + // Replace all nested defs if applicable. This is done by looping + // through each param in the definition and if it is of type `bytes` + // or `bytes[]`, checking the param value in `calldata`. If the param + // value (or for `bytes[]` each underlying value) is of size (4 + 32*n) + // it could be nested calldata. We should use that item's selector(s) + // to look up nested definition(s). + const nestedCalldata = Calldata.EVM.processors.getNestedCalldata( + def, + calldata, + ) + const nestedDefs = await replaceNestedDefs(nestedCalldata) + // Need to recurse before doing the full replacement + for await (const [i] of nestedDefs.entries()) { + // If this is an array of nested defs, loop through each one and + // postprocess it. The first item of a single def is the function + // name so we need to check that it isn't a string in this case. + if (Array.isArray(nestedDefs[i]) && typeof nestedDefs[i][0] !== 'string') { + for await (const [j] of nestedDefs[i].entries()) { + if (nestedDefs[i][j] !== null) { + nestedDefs[i][j] = await postProcessDef( + nestedDefs[i][j], + Buffer.from(nestedCalldata[i][j].slice(2), 'hex'), + ) + } + } + } else if (nestedDefs[i] !== null) { + nestedDefs[i] = await postProcessDef( + nestedDefs[i], + Buffer.from(nestedCalldata[i].slice(2), 'hex'), + ) + } + } + // Replace any nested defs + const newDef = Calldata.EVM.processors.replaceNestedDefs(def, nestedDefs) + return newDef } /** @@ -565,50 +565,50 @@ async function postProcessDef(def, calldata) { * */ async function replaceNestedDefs(possNestedDefs) { - // For all possible nested defs, attempt to fetch the underlying def - const nestedDefs = [] - for await (const d of possNestedDefs) { - if (d !== null) { - if (Array.isArray(d)) { - const _nestedDefs = [] - let shouldInclude = true - for await (const _d of d) { - try { - const _nestedSelector = _d.slice(2, 10) - const _nestedAbi = await fetch4byteData(_nestedSelector) - const _nestedDef = selectDefFrom4byteABI( - _nestedAbi, - _nestedSelector, - ) - _nestedDefs.push(_nestedDef) - } catch (_err) { - console.error('Failed to fetch nested 4byte data:', _err) - shouldInclude = false - _nestedDefs.push(null) - } - } - if (shouldInclude) { - nestedDefs.push(_nestedDefs) - } else { - nestedDefs.push(null) - } - } else { - try { - const nestedSelector = d.slice(2, 10) - const nestedAbi = await fetch4byteData(nestedSelector) - const nestedDef = selectDefFrom4byteABI(nestedAbi, nestedSelector) - nestedDefs.push(nestedDef) - } catch (_err) { - console.error('Failed to fetch nested definition:', _err) - nestedDefs.push(null) - } - } - } else { - nestedDefs.push(null) - } - } - // For all nested defs, replace the - return nestedDefs + // For all possible nested defs, attempt to fetch the underlying def + const nestedDefs = [] + for await (const d of possNestedDefs) { + if (d !== null) { + if (Array.isArray(d)) { + const _nestedDefs = [] + let shouldInclude = true + for await (const _d of d) { + try { + const _nestedSelector = _d.slice(2, 10) + const _nestedAbi = await fetch4byteData(_nestedSelector) + const _nestedDef = selectDefFrom4byteABI( + _nestedAbi, + _nestedSelector, + ) + _nestedDefs.push(_nestedDef) + } catch (_err) { + console.error('Failed to fetch nested 4byte data:', _err) + shouldInclude = false + _nestedDefs.push(null) + } + } + if (shouldInclude) { + nestedDefs.push(_nestedDefs) + } else { + nestedDefs.push(null) + } + } else { + try { + const nestedSelector = d.slice(2, 10) + const nestedAbi = await fetch4byteData(nestedSelector) + const nestedDef = selectDefFrom4byteABI(nestedAbi, nestedSelector) + nestedDefs.push(nestedDef) + } catch (_err) { + console.error('Failed to fetch nested definition:', _err) + nestedDefs.push(null) + } + } + } else { + nestedDefs.push(null) + } + } + // For all nested defs, replace the + return nestedDefs } //-------------------------------------------------- @@ -620,72 +620,72 @@ async function replaceNestedDefs(possNestedDefs) { * Fetches calldata from a remote scanner based on the transaction's `chainId` */ export async function fetchCalldataDecoder( - _data: Uint8Array | string, - to: string, - _chainId: number | string, - recurse = true, + _data: Uint8Array | string, + to: string, + _chainId: number | string, + recurse = true, ) { - try { - // Exit if there is no data. The 2 comes from the 0x prefix, but a later - // check will confirm that there are at least 4 bytes of data in the buffer. - if (!_data || _data.length < 2) { - throw new Error('Data is either undefined or less than two bytes') - } - const isHexString = typeof _data === 'string' && _data.slice(0, 2) === '0x' - const data = isHexString - ? Buffer.from(_data.slice(2), 'hex') - : //@ts-expect-error - Buffer doesn't recognize Uint8Array type properly - Buffer.from(_data, 'hex') - - // For empty data (just '0x'), return early - no calldata to decode - if (data.length === 0) { - return { def: null, abi: null } - } - - if (data.length < 4) { - throw new Error( - 'Data must contain at least 4 bytes of data to define the selector', - ) - } - const selector = Buffer.from(data.slice(0, 4)).toString('hex') - // Convert the chainId to a number and use it to determine if we can call out to - // an etherscan-like explorer for richer data. - const chainId = Number(_chainId) - const cachedNetwork = NETWORKS_BY_CHAIN_ID[chainId] - const supportedChain = cachedNetwork - ? cachedNetwork - : await fetchExternalNetworkForChainId(chainId) - try { - if (supportedChain) { - const abi = await fetchSupportedChainData(to, supportedChain) - const parsedAbi = Calldata.EVM.parsers.parseSolidityJSONABI( - selector, - abi, - ) - let def = parsedAbi.def - if (recurse) { - def = await postProcessDef(def, data) - } - return { abi, def: encodeDef(def) } - } else { - throw new Error(`Chain (id: ${chainId}) is not supported`) - } - } catch (err) { - console.warn(err.message, '\n', 'Falling back to 4byte') - } - - // Fallback to checking 4byte - const abi = await fetch4byteData(selector) - let def = selectDefFrom4byteABI(abi, selector) - if (recurse) { - def = await postProcessDef(def, data) - } - return { abi, def: encodeDef(def) } - } catch (err) { - console.warn(`Fetching calldata failed: ${err.message}`) - } - - return { def: null, abi: null } + try { + // Exit if there is no data. The 2 comes from the 0x prefix, but a later + // check will confirm that there are at least 4 bytes of data in the buffer. + if (!_data || _data.length < 2) { + throw new Error('Data is either undefined or less than two bytes') + } + const isHexString = typeof _data === 'string' && _data.slice(0, 2) === '0x' + const data = isHexString + ? Buffer.from(_data.slice(2), 'hex') + : //@ts-expect-error - Buffer doesn't recognize Uint8Array type properly + Buffer.from(_data, 'hex') + + // For empty data (just '0x'), return early - no calldata to decode + if (data.length === 0) { + return { def: null, abi: null } + } + + if (data.length < 4) { + throw new Error( + 'Data must contain at least 4 bytes of data to define the selector', + ) + } + const selector = Buffer.from(data.slice(0, 4)).toString('hex') + // Convert the chainId to a number and use it to determine if we can call out to + // an etherscan-like explorer for richer data. + const chainId = Number(_chainId) + const cachedNetwork = NETWORKS_BY_CHAIN_ID[chainId] + const supportedChain = cachedNetwork + ? cachedNetwork + : await fetchExternalNetworkForChainId(chainId) + try { + if (supportedChain) { + const abi = await fetchSupportedChainData(to, supportedChain) + const parsedAbi = Calldata.EVM.parsers.parseSolidityJSONABI( + selector, + abi, + ) + let def = parsedAbi.def + if (recurse) { + def = await postProcessDef(def, data) + } + return { abi, def: encodeDef(def) } + } else { + throw new Error(`Chain (id: ${chainId}) is not supported`) + } + } catch (err) { + console.warn(err.message, '\n', 'Falling back to 4byte') + } + + // Fallback to checking 4byte + const abi = await fetch4byteData(selector) + let def = selectDefFrom4byteABI(abi, selector) + if (recurse) { + def = await postProcessDef(def, data) + } + return { abi, def: encodeDef(def) } + } catch (err) { + console.warn(`Fetching calldata failed: ${err.message}`) + } + + return { def: null, abi: null } } /** @@ -697,24 +697,24 @@ export async function fetchCalldataDecoder( * @public */ export const generateAppSecret = ( - deviceId: Buffer | string, - password: Buffer | string, - appName: Buffer | string, + deviceId: Buffer | string, + password: Buffer | string, + appName: Buffer | string, ): Buffer => { - const deviceIdBuffer = - typeof deviceId === 'string' ? Buffer.from(deviceId) : deviceId - const passwordBuffer = - typeof password === 'string' ? Buffer.from(password) : password - const appNameBuffer = - typeof appName === 'string' ? Buffer.from(appName) : appName + const deviceIdBuffer = + typeof deviceId === 'string' ? Buffer.from(deviceId) : deviceId + const passwordBuffer = + typeof password === 'string' ? Buffer.from(password) : password + const appNameBuffer = + typeof appName === 'string' ? Buffer.from(appName) : appName - const preImage = Buffer.concat([ - deviceIdBuffer, - passwordBuffer, - appNameBuffer, - ]) + const preImage = Buffer.concat([ + deviceIdBuffer, + passwordBuffer, + appNameBuffer, + ]) - return Buffer.from(Hash.sha256(preImage)) + return Buffer.from(Hash.sha256(preImage)) } /** @@ -724,141 +724,141 @@ export const generateAppSecret = ( * @returns BN object containing the `v` param */ export const getV = (tx: any, resp: any) => { - let chainId: string | undefined - let hash: Uint8Array - let type: string | number | undefined - let useEIP155 = false - - if (Buffer.isBuffer(tx) || typeof tx === 'string') { - const txHex = Buffer.isBuffer(tx) - ? (`0x${tx.toString('hex')}` as Hex) - : (tx as Hex) - const txBuf = Buffer.isBuffer(tx) ? tx : Buffer.from(tx.slice(2), 'hex') - - hash = Buffer.from(Hash.keccak256(txBuf)) - - try { - const parsedTx = parseTransaction(txHex) - type = parsedTx.type - - if (parsedTx.chainId !== undefined && parsedTx.chainId !== null) { - chainId = parsedTx.chainId.toString() - if (type === 'legacy') { - useEIP155 = true - } - } - - if (type === 'legacy' && !useEIP155) { - const legacyTxArray = RLP.decode(txBuf) - if (legacyTxArray.length >= 9) { - const vBuf = legacyTxArray[6] as Uint8Array - if (vBuf && vBuf.length > 0) { - chainId = new BN(vBuf).toString() - useEIP155 = true - } - } - } - } catch (err) { - console.error('Failed to parse transaction, trying legacy format:', err) - try { - const txBufRaw = Buffer.isBuffer(tx) - ? tx - : Buffer.from(tx.slice(2), 'hex') - const legacyTxArray = RLP.decode(txBufRaw) - - type = 'legacy' - if (legacyTxArray.length >= 9) { - const vBuf = legacyTxArray[6] as Uint8Array - if (vBuf && vBuf.length > 0) { - chainId = new BN(vBuf).toString() - useEIP155 = true - } - } - } catch { - throw new Error('Could not recover V. Bad transaction data.') - } - } - } else { - throw new Error( - 'Unsupported transaction format. Expected Buffer or hex string.', - ) - } - - const rBuf = Buffer.isBuffer(resp.sig.r) - ? resp.sig.r - : Buffer.from(resp.sig.r.slice(2), 'hex') - const sBuf = Buffer.isBuffer(resp.sig.s) - ? resp.sig.s - : Buffer.from(resp.sig.s.slice(2), 'hex') - const rs = new Uint8Array(Buffer.concat([rBuf, sBuf])) - const pubkeyInput = resp.pubkey - - if (!pubkeyInput) { - throw new Error('Response did not include a public key.') - } - - let pubkeyBuf: Buffer - if (Buffer.isBuffer(pubkeyInput)) { - pubkeyBuf = Buffer.from(pubkeyInput) - } else if (pubkeyInput instanceof Uint8Array) { - pubkeyBuf = Buffer.from(pubkeyInput) - } else if (typeof pubkeyInput === 'string') { - const hex = pubkeyInput.startsWith('0x') - ? pubkeyInput.slice(2) - : pubkeyInput - pubkeyBuf = Buffer.from(hex, 'hex') - } else { - pubkeyBuf = Buffer.from(pubkeyInput) - } - - if (pubkeyBuf.length === 64) { - pubkeyBuf = Buffer.concat([Buffer.from([0x04]), pubkeyBuf]) - } - - const isCompressedPubkey = - pubkeyBuf.length === 33 && (pubkeyBuf[0] === 0x02 || pubkeyBuf[0] === 0x03) - const isUncompressedPubkey = pubkeyBuf.length === 65 && pubkeyBuf[0] === 0x04 - - if (!isCompressedPubkey && !isUncompressedPubkey) { - throw new Error('Unsupported public key format returned by device.') - } - - const recovery0 = Buffer.from(ecdsaRecover(rs, 0, hash, isCompressedPubkey)) - const recovery1 = Buffer.from(ecdsaRecover(rs, 1, hash, isCompressedPubkey)) - - const pubkeyStr = pubkeyBuf.toString('hex') - const recovery0Str = recovery0.toString('hex') - const recovery1Str = recovery1.toString('hex') - - let recovery: number - if (pubkeyStr === recovery0Str) { - recovery = 0 - } else if (pubkeyStr === recovery1Str) { - recovery = 1 - } else { - throw new Error( - 'Failed to recover V parameter. Bad signature or transaction data.', - ) - } - - // Use the consolidated v parameter conversion logic - const result = convertRecoveryToV(recovery, { - chainId, - useEIP155, - type, - }) - - // Always return BN for consistent interface - convertRecoveryToV returns Buffer for typed txs - if (Buffer.isBuffer(result)) { - // For typed transactions that return recovery value (0 or 1) as buffer - if (result.length === 0) { - return new BN(0) // Empty buffer means 0 - } else { - return new BN(result.toString('hex'), 16) - } - } else { - return result // Already a BN - } + let chainId: string | undefined + let hash: Uint8Array + let type: string | number | undefined + let useEIP155 = false + + if (Buffer.isBuffer(tx) || typeof tx === 'string') { + const txHex = Buffer.isBuffer(tx) + ? (`0x${tx.toString('hex')}` as Hex) + : (tx as Hex) + const txBuf = Buffer.isBuffer(tx) ? tx : Buffer.from(tx.slice(2), 'hex') + + hash = Buffer.from(Hash.keccak256(txBuf)) + + try { + const parsedTx = parseTransaction(txHex) + type = parsedTx.type + + if (parsedTx.chainId !== undefined && parsedTx.chainId !== null) { + chainId = parsedTx.chainId.toString() + if (type === 'legacy') { + useEIP155 = true + } + } + + if (type === 'legacy' && !useEIP155) { + const legacyTxArray = RLP.decode(txBuf) + if (legacyTxArray.length >= 9) { + const vBuf = legacyTxArray[6] as Uint8Array + if (vBuf && vBuf.length > 0) { + chainId = new BN(vBuf).toString() + useEIP155 = true + } + } + } + } catch (err) { + console.error('Failed to parse transaction, trying legacy format:', err) + try { + const txBufRaw = Buffer.isBuffer(tx) + ? tx + : Buffer.from(tx.slice(2), 'hex') + const legacyTxArray = RLP.decode(txBufRaw) + + type = 'legacy' + if (legacyTxArray.length >= 9) { + const vBuf = legacyTxArray[6] as Uint8Array + if (vBuf && vBuf.length > 0) { + chainId = new BN(vBuf).toString() + useEIP155 = true + } + } + } catch { + throw new Error('Could not recover V. Bad transaction data.') + } + } + } else { + throw new Error( + 'Unsupported transaction format. Expected Buffer or hex string.', + ) + } + + const rBuf = Buffer.isBuffer(resp.sig.r) + ? resp.sig.r + : Buffer.from(resp.sig.r.slice(2), 'hex') + const sBuf = Buffer.isBuffer(resp.sig.s) + ? resp.sig.s + : Buffer.from(resp.sig.s.slice(2), 'hex') + const rs = new Uint8Array(Buffer.concat([rBuf, sBuf])) + const pubkeyInput = resp.pubkey + + if (!pubkeyInput) { + throw new Error('Response did not include a public key.') + } + + let pubkeyBuf: Buffer + if (Buffer.isBuffer(pubkeyInput)) { + pubkeyBuf = Buffer.from(pubkeyInput) + } else if (pubkeyInput instanceof Uint8Array) { + pubkeyBuf = Buffer.from(pubkeyInput) + } else if (typeof pubkeyInput === 'string') { + const hex = pubkeyInput.startsWith('0x') + ? pubkeyInput.slice(2) + : pubkeyInput + pubkeyBuf = Buffer.from(hex, 'hex') + } else { + pubkeyBuf = Buffer.from(pubkeyInput) + } + + if (pubkeyBuf.length === 64) { + pubkeyBuf = Buffer.concat([Buffer.from([0x04]), pubkeyBuf]) + } + + const isCompressedPubkey = + pubkeyBuf.length === 33 && (pubkeyBuf[0] === 0x02 || pubkeyBuf[0] === 0x03) + const isUncompressedPubkey = pubkeyBuf.length === 65 && pubkeyBuf[0] === 0x04 + + if (!isCompressedPubkey && !isUncompressedPubkey) { + throw new Error('Unsupported public key format returned by device.') + } + + const recovery0 = Buffer.from(ecdsaRecover(rs, 0, hash, isCompressedPubkey)) + const recovery1 = Buffer.from(ecdsaRecover(rs, 1, hash, isCompressedPubkey)) + + const pubkeyStr = pubkeyBuf.toString('hex') + const recovery0Str = recovery0.toString('hex') + const recovery1Str = recovery1.toString('hex') + + let recovery: number + if (pubkeyStr === recovery0Str) { + recovery = 0 + } else if (pubkeyStr === recovery1Str) { + recovery = 1 + } else { + throw new Error( + 'Failed to recover V parameter. Bad signature or transaction data.', + ) + } + + // Use the consolidated v parameter conversion logic + const result = convertRecoveryToV(recovery, { + chainId, + useEIP155, + type, + }) + + // Always return BN for consistent interface - convertRecoveryToV returns Buffer for typed txs + if (Buffer.isBuffer(result)) { + // For typed transactions that return recovery value (0 or 1) as buffer + if (result.length === 0) { + return new BN(0) // Empty buffer means 0 + } else { + return new BN(result.toString('hex'), 16) + } + } else { + return result // Already a BN + } } /** @@ -870,33 +870,33 @@ export const getV = (tx: any, resp: any) => { * @returns The properly formatted v value as Buffer or BN */ export const convertRecoveryToV = ( - recovery: number, - txData: any = {}, + recovery: number, + txData: any = {}, ): Buffer | InstanceType => { - const { chainId, useEIP155, type } = txData - - // For typed transactions (EIP-2930, EIP-1559, EIP-7702), we want the recoveryParam (0 or 1) - // rather than the `v` value because the `chainId` is already included in the - // transaction payload. - if ( - type === 1 || - type === 2 || - type === 4 || - type === 'eip2930' || - type === 'eip1559' || - type === 'eip7702' - ) { - return ensureHexBuffer(recovery, true) // 0 or 1, with 0 expected as an empty buffer - } else if (!useEIP155 || !chainId) { - // For ETH messages and non-EIP155 chains the set should be [27, 28] for `v` - return new BN(recovery).addn(27) - } - - // We will use EIP155 in most cases. Convert recovery to a bignum and operate on it. - // Note that the protocol calls for v = (CHAIN_ID*2) + 35/36, where 35 or 36 - // is decided on based on the ecrecover result. `recovery` is passed in as either 0 or 1 - // so we add 35 to that. - return new BN(chainId).muln(2).addn(35).addn(recovery) + const { chainId, useEIP155, type } = txData + + // For typed transactions (EIP-2930, EIP-1559, EIP-7702), we want the recoveryParam (0 or 1) + // rather than the `v` value because the `chainId` is already included in the + // transaction payload. + if ( + type === 1 || + type === 2 || + type === 4 || + type === 'eip2930' || + type === 'eip1559' || + type === 'eip7702' + ) { + return ensureHexBuffer(recovery, true) // 0 or 1, with 0 expected as an empty buffer + } else if (!useEIP155 || !chainId) { + // For ETH messages and non-EIP155 chains the set should be [27, 28] for `v` + return new BN(recovery).addn(27) + } + + // We will use EIP155 in most cases. Convert recovery to a bignum and operate on it. + // Note that the protocol calls for v = (CHAIN_ID*2) + 35/36, where 35 or 36 + // is decided on based on the ecrecover result. `recovery` is passed in as either 0 or 1 + // so we add 35 to that. + return new BN(chainId).muln(2).addn(35).addn(recovery) } /** @@ -913,108 +913,108 @@ export const convertRecoveryToV = ( * @returns 0 or 1 for the y-parity value */ export const getYParity = ( - messageHash: - | Buffer - | Uint8Array - | string - | { messageHash: any; signature: any; publicKey: any } - | any, - signature?: { r: any; s: any } | any, - publicKey?: Buffer | Uint8Array | string, + messageHash: + | Buffer + | Uint8Array + | string + | { messageHash: any; signature: any; publicKey: any } + | any, + signature?: { r: any; s: any } | any, + publicKey?: Buffer | Uint8Array | string, ): number => { - // Handle legacy object format for backward compatibility - if ( - typeof messageHash === 'object' && - messageHash && - 'messageHash' in messageHash - ) { - return getYParity( - messageHash.messageHash, - messageHash.signature, - messageHash.publicKey, - ) - } - - // Handle legacy transaction format for backward compatibility - if (signature?.sig && signature.pubkey && !publicKey) { - return getYParity(messageHash, signature.sig, signature.pubkey) - } - - // Validate required parameters - if (!signature || !publicKey) { - throw new Error('Response with sig and pubkey required for legacy format') - } - - if (!signature.r || !signature.s) { - throw new Error('Response with sig and pubkey required for legacy format') - } - - // Handle transaction objects with getMessageToSign - let hash = messageHash - if ( - typeof messageHash === 'object' && - messageHash && - typeof messageHash.getMessageToSign === 'function' - ) { - const type = messageHash._type - if (type !== undefined && type !== null) { - // EIP-1559 / EIP-2930 / future typed transactions - hash = messageHash.getMessageToSign(true) - } else { - // Legacy transaction objects - const preimage = RLP.encode(messageHash.getMessageToSign(false)) - hash = Buffer.from(Hash.keccak256(preimage)) - } - } else if (Buffer.isBuffer(messageHash) && messageHash.length !== 32) { - // If it's a buffer but not 32 bytes, hash it - hash = Buffer.from(Hash.keccak256(messageHash)) - } - - // Normalize inputs to Buffers - const toBuffer = (data: any): Buffer => { - if (!data) throw new Error('Invalid data') - if (Buffer.isBuffer(data)) return data - if (data instanceof Uint8Array) return Buffer.from(data) - if (typeof data === 'string') { - return Buffer.from(data.replace(/^0x/i, ''), 'hex') - } - throw new Error('Invalid data type') - } - - const hashBuf = toBuffer(hash) - const rBuf = toBuffer(signature.r) - const sBuf = toBuffer(signature.s) - const pubkeyBuf = toBuffer(publicKey) - - // For non-32 byte hashes, hash them (legacy support) - const finalHash = - hashBuf.length === 32 ? hashBuf : Buffer.from(Hash.keccak256(hashBuf)) - - // Combine r and s - const rs = new Uint8Array(Buffer.concat([rBuf, sBuf])) - const hashBytes = new Uint8Array(finalHash) - const isCompressed = pubkeyBuf.length === 33 - - // Try both recovery values - for (let recovery = 0; recovery <= 1; recovery++) { - try { - const recovered = ecdsaRecover(rs, recovery, hashBytes, isCompressed) - if (Buffer.from(recovered).equals(pubkeyBuf)) { - return recovery - } - } catch {} - } - - throw new Error( - 'Failed to recover Y parity. Bad signature or transaction data.', - ) + // Handle legacy object format for backward compatibility + if ( + typeof messageHash === 'object' && + messageHash && + 'messageHash' in messageHash + ) { + return getYParity( + messageHash.messageHash, + messageHash.signature, + messageHash.publicKey, + ) + } + + // Handle legacy transaction format for backward compatibility + if (signature?.sig && signature.pubkey && !publicKey) { + return getYParity(messageHash, signature.sig, signature.pubkey) + } + + // Validate required parameters + if (!signature || !publicKey) { + throw new Error('Response with sig and pubkey required for legacy format') + } + + if (!signature.r || !signature.s) { + throw new Error('Response with sig and pubkey required for legacy format') + } + + // Handle transaction objects with getMessageToSign + let hash = messageHash + if ( + typeof messageHash === 'object' && + messageHash && + typeof messageHash.getMessageToSign === 'function' + ) { + const type = messageHash._type + if (type !== undefined && type !== null) { + // EIP-1559 / EIP-2930 / future typed transactions + hash = messageHash.getMessageToSign(true) + } else { + // Legacy transaction objects + const preimage = RLP.encode(messageHash.getMessageToSign(false)) + hash = Buffer.from(Hash.keccak256(preimage)) + } + } else if (Buffer.isBuffer(messageHash) && messageHash.length !== 32) { + // If it's a buffer but not 32 bytes, hash it + hash = Buffer.from(Hash.keccak256(messageHash)) + } + + // Normalize inputs to Buffers + const toBuffer = (data: any): Buffer => { + if (!data) throw new Error('Invalid data') + if (Buffer.isBuffer(data)) return data + if (data instanceof Uint8Array) return Buffer.from(data) + if (typeof data === 'string') { + return Buffer.from(data.replace(/^0x/i, ''), 'hex') + } + throw new Error('Invalid data type') + } + + const hashBuf = toBuffer(hash) + const rBuf = toBuffer(signature.r) + const sBuf = toBuffer(signature.s) + const pubkeyBuf = toBuffer(publicKey) + + // For non-32 byte hashes, hash them (legacy support) + const finalHash = + hashBuf.length === 32 ? hashBuf : Buffer.from(Hash.keccak256(hashBuf)) + + // Combine r and s + const rs = new Uint8Array(Buffer.concat([rBuf, sBuf])) + const hashBytes = new Uint8Array(finalHash) + const isCompressed = pubkeyBuf.length === 33 + + // Try both recovery values + for (let recovery = 0; recovery <= 1; recovery++) { + try { + const recovered = ecdsaRecover(rs, recovery, hashBytes, isCompressed) + if (Buffer.from(recovered).equals(pubkeyBuf)) { + return recovery + } + } catch {} + } + + throw new Error( + 'Failed to recover Y parity. Bad signature or transaction data.', + ) } /** @internal */ export const EXTERNAL = { - fetchCalldataDecoder, - generateAppSecret, - getV, - getYParity, - convertRecoveryToV, + fetchCalldataDecoder, + generateAppSecret, + getV, + getYParity, + convertRecoveryToV, } From 3483db516741fb0e73d78498329f934b8f8af4a8 Mon Sep 17 00:00:00 2001 From: netbonus <151201453+netbonus@users.noreply.github.com> Date: Wed, 21 Jan 2026 15:21:59 -0500 Subject: [PATCH 08/14] style: enforce semicolons --- biome.json | 2 +- .../docs/src/components/HomepageFeatures.tsx | 18 +- packages/docs/src/pages/_index.tsx | 26 +- packages/example/src/App.tsx | 56 +- packages/example/src/Button.tsx | 18 +- packages/example/src/Lattice.tsx | 50 +- packages/example/src/main.tsx | 10 +- packages/sdk/src/__test__/e2e/api.test.ts | 287 +++--- packages/sdk/src/__test__/e2e/btc.test.ts | 156 +-- packages/sdk/src/__test__/e2e/eth.msg.test.ts | 220 ++-- .../__test__/e2e/ethereum/addresses.test.ts | 26 +- packages/sdk/src/__test__/e2e/general.test.ts | 240 ++--- packages/sdk/src/__test__/e2e/kv.test.ts | 226 ++--- .../src/__test__/e2e/non-exportable.test.ts | 184 ++-- .../sdk/src/__test__/e2e/signing/bls.test.ts | 192 ++-- .../__test__/e2e/signing/determinism.test.ts | 410 ++++---- .../__test__/e2e/signing/eip712-msg.test.ts | 18 +- .../__test__/e2e/signing/eip712-vectors.ts | 10 +- .../src/__test__/e2e/signing/evm-abi.test.ts | 30 +- .../src/__test__/e2e/signing/evm-tx.test.ts | 52 +- .../e2e/signing/solana/__mocks__/programs.ts | 4 +- .../signing/solana/solana.programs.test.ts | 32 +- .../e2e/signing/solana/solana.test.ts | 105 +- .../signing/solana/solana.versioned.test.ts | 128 +-- .../__test__/e2e/signing/unformatted.test.ts | 117 +-- .../sdk/src/__test__/e2e/signing/vectors.ts | 46 +- .../src/__test__/e2e/solana/addresses.test.ts | 46 +- packages/sdk/src/__test__/e2e/xpub.test.ts | 40 +- .../__test__/integration/__mocks__/4byte.ts | 12 +- .../integration/__mocks__/etherscan.ts | 8 +- .../integration/__mocks__/handlers.ts | 72 +- .../__test__/integration/__mocks__/server.ts | 6 +- .../__test__/integration/__mocks__/setup.ts | 10 +- .../integration/client.interop.test.ts | 24 +- .../src/__test__/integration/connect.test.ts | 94 +- .../integration/fetchCalldataDecoder.test.ts | 116 +-- .../__test__/unit/__mocks__/decoderData.ts | 28 +- packages/sdk/src/__test__/unit/api.test.ts | 68 +- .../unit/compareEIP7702Serialization.test.ts | 126 +-- .../sdk/src/__test__/unit/decoders.test.ts | 42 +- .../sdk/src/__test__/unit/eip7702.test.ts | 66 +- .../sdk/src/__test__/unit/encoders.test.ts | 122 +-- .../__test__/unit/ethereum.validate.test.ts | 76 +- .../src/__test__/unit/module.interop.test.ts | 90 +- .../unit/parseGenericSigningResponse.test.ts | 140 +-- .../unit/personalSignValidation.test.ts | 90 +- .../unit/selectDefFrom4byteABI.test.ts | 52 +- .../src/__test__/unit/signatureUtils.test.ts | 298 +++--- .../sdk/src/__test__/unit/validators.test.ts | 210 ++-- .../__test__/utils/__test__/builders.test.ts | 24 +- .../utils/__test__/serializers.test.ts | 20 +- packages/sdk/src/__test__/utils/builders.ts | 170 ++-- packages/sdk/src/__test__/utils/constants.ts | 2 +- .../sdk/src/__test__/utils/determinism.ts | 110 +- packages/sdk/src/__test__/utils/ethers.ts | 125 +-- packages/sdk/src/__test__/utils/getters.ts | 24 +- packages/sdk/src/__test__/utils/helpers.ts | 947 +++++++++--------- packages/sdk/src/__test__/utils/runners.ts | 44 +- .../sdk/src/__test__/utils/serializers.ts | 24 +- packages/sdk/src/__test__/utils/setup.ts | 44 +- .../sdk/src/__test__/utils/testConstants.ts | 6 +- .../sdk/src/__test__/utils/testEnvironment.ts | 8 +- .../sdk/src/__test__/utils/testRequest.ts | 28 +- .../sdk/src/__test__/utils/viemComparison.ts | 176 ++-- packages/sdk/src/__test__/utils/vitest.d.ts | 2 +- .../sdk/src/__test__/vectors/abi-vectors.ts | 2 +- packages/sdk/src/api/addressTags.ts | 44 +- packages/sdk/src/api/addresses.ts | 123 +-- packages/sdk/src/api/index.ts | 16 +- packages/sdk/src/api/setup.ts | 68 +- packages/sdk/src/api/signing.ts | 120 +-- packages/sdk/src/api/state.ts | 22 +- packages/sdk/src/api/utilities.ts | 106 +- packages/sdk/src/api/wallets.ts | 8 +- packages/sdk/src/bitcoin.ts | 510 +++++----- packages/sdk/src/calldata/evm.ts | 358 +++---- packages/sdk/src/calldata/index.ts | 4 +- packages/sdk/src/client.ts | 220 ++-- packages/sdk/src/constants.ts | 170 ++-- packages/sdk/src/ethereum.ts | 929 ++++++++--------- packages/sdk/src/functions/addKvRecords.ts | 79 +- packages/sdk/src/functions/connect.ts | 90 +- .../sdk/src/functions/fetchActiveWallet.ts | 44 +- packages/sdk/src/functions/fetchDecoder.ts | 24 +- packages/sdk/src/functions/fetchEncData.ts | 142 +-- packages/sdk/src/functions/getAddresses.ts | 128 +-- packages/sdk/src/functions/getKvRecords.ts | 111 +- packages/sdk/src/functions/index.ts | 18 +- packages/sdk/src/functions/pair.ts | 46 +- packages/sdk/src/functions/removeKvRecords.ts | 56 +- packages/sdk/src/functions/sign.ts | 186 ++-- packages/sdk/src/genericSigning.ts | 305 +++--- packages/sdk/src/index.ts | 10 +- packages/sdk/src/protocol/index.ts | 4 +- packages/sdk/src/protocol/latticeConstants.ts | 2 +- packages/sdk/src/protocol/secureMessages.ts | 190 ++-- packages/sdk/src/schemas/index.ts | 2 +- packages/sdk/src/schemas/transaction.ts | 126 +-- packages/sdk/src/shared/errors.ts | 28 +- packages/sdk/src/shared/functions.ts | 106 +- packages/sdk/src/shared/predicates.ts | 16 +- packages/sdk/src/shared/utilities.ts | 60 +- packages/sdk/src/shared/validators.ts | 182 ++-- packages/sdk/src/types/addKvRecords.ts | 12 +- packages/sdk/src/types/client.ts | 92 +- packages/sdk/src/types/connect.ts | 6 +- packages/sdk/src/types/declarations.d.ts | 24 +- packages/sdk/src/types/fetchActiveWallet.ts | 6 +- packages/sdk/src/types/fetchEncData.ts | 28 +- packages/sdk/src/types/firmware.ts | 92 +- packages/sdk/src/types/getAddresses.ts | 12 +- packages/sdk/src/types/getKvRecords.ts | 28 +- packages/sdk/src/types/index.ts | 42 +- packages/sdk/src/types/messages.ts | 10 +- packages/sdk/src/types/pair.ts | 6 +- packages/sdk/src/types/removeKvRecords.ts | 8 +- packages/sdk/src/types/secureMessages.ts | 30 +- packages/sdk/src/types/shared.ts | 16 +- packages/sdk/src/types/sign.ts | 192 ++-- packages/sdk/src/types/tiny-secp256k1.d.ts | 2 +- packages/sdk/src/types/utils.ts | 26 +- packages/sdk/src/types/vitest.d.ts | 4 +- packages/sdk/src/util.ts | 709 ++++++------- 123 files changed, 6157 insertions(+), 6096 deletions(-) diff --git a/biome.json b/biome.json index 7ce59fb0..aed23215 100644 --- a/biome.json +++ b/biome.json @@ -68,7 +68,7 @@ "jsxQuoteStyle": "double", "quoteProperties": "asNeeded", "trailingCommas": "all", - "semicolons": "asNeeded", + "semicolons": "always", "arrowParentheses": "always", "bracketSpacing": true, "bracketSameLine": false, diff --git a/packages/docs/src/components/HomepageFeatures.tsx b/packages/docs/src/components/HomepageFeatures.tsx index 8ca21f72..72309dd8 100644 --- a/packages/docs/src/components/HomepageFeatures.tsx +++ b/packages/docs/src/components/HomepageFeatures.tsx @@ -1,11 +1,11 @@ -import clsx from 'clsx' -import styles from './HomepageFeatures.module.css' +import clsx from 'clsx'; +import styles from './HomepageFeatures.module.css'; type FeatureItem = { - title: string - image: string - description: JSX.Element -} + title: string; + image: string; + description: JSX.Element; +}; const FeatureList: FeatureItem[] = [ { @@ -38,7 +38,7 @@ const FeatureList: FeatureItem[] = [ ), }, -] +]; function Feature({ title, image, description }: FeatureItem) { return ( @@ -51,7 +51,7 @@ function Feature({ title, image, description }: FeatureItem) {

    {description}

- ) + ); } export default function HomepageFeatures(): JSX.Element { @@ -65,5 +65,5 @@ export default function HomepageFeatures(): JSX.Element {
- ) + ); } diff --git a/packages/docs/src/pages/_index.tsx b/packages/docs/src/pages/_index.tsx index e89503ce..a52585e6 100644 --- a/packages/docs/src/pages/_index.tsx +++ b/packages/docs/src/pages/_index.tsx @@ -1,17 +1,17 @@ -import type React from 'react' -import Link from '@docusaurus/Link' -import useDocusaurusContext from '@docusaurus/useDocusaurusContext' -import Layout, { type Props as LayoutProps } from '@theme/Layout' -import clsx from 'clsx' -import styles from './index.module.css' +import type React from 'react'; +import Link from '@docusaurus/Link'; +import useDocusaurusContext from '@docusaurus/useDocusaurusContext'; +import Layout, { type Props as LayoutProps } from '@theme/Layout'; +import clsx from 'clsx'; +import styles from './index.module.css'; interface ExtendedLayoutProps extends LayoutProps { - title?: string - description?: string + title?: string; + description?: string; } function HomepageHeader() { - const { siteConfig } = useDocusaurusContext() + const { siteConfig } = useDocusaurusContext(); return (
@@ -27,12 +27,12 @@ function HomepageHeader() {
- ) + ); } export default function Home(): JSX.Element { - const { siteConfig } = useDocusaurusContext() - const ExtendedLayout = Layout as React.ComponentType + const { siteConfig } = useDocusaurusContext(); + const ExtendedLayout = Layout as React.ComponentType; return (
{/* */}
- ) + ); } diff --git a/packages/example/src/App.tsx b/packages/example/src/App.tsx index f5b59ed2..07d3d3e8 100644 --- a/packages/example/src/App.tsx +++ b/packages/example/src/App.tsx @@ -1,54 +1,54 @@ -import { useCallback, useEffect, useState } from 'react' -import { getClient, pair, setup } from 'gridplus-sdk' -import './App.css' -import { Lattice } from './Lattice' +import { useCallback, useEffect, useState } from 'react'; +import { getClient, pair, setup } from 'gridplus-sdk'; +import './App.css'; +import { Lattice } from './Lattice'; function App() { - const [label, setLabel] = useState('No Device') + const [label, setLabel] = useState('No Device'); const getStoredClient = useCallback( async () => window.localStorage.getItem('storedClient') || '', [], - ) + ); const setStoredClient = useCallback(async (storedClient: string | null) => { - if (!storedClient) return - window.localStorage.setItem('storedClient', storedClient) + if (!storedClient) return; + window.localStorage.setItem('storedClient', storedClient); - const client = await getClient() - setLabel(client?.getDeviceId() || 'No Device') - }, []) + const client = await getClient(); + setLabel(client?.getDeviceId() || 'No Device'); + }, []); useEffect(() => { const initClient = async () => { - const storedClient = await getStoredClient() + const storedClient = await getStoredClient(); if (storedClient) { - await setup({ getStoredClient, setStoredClient }) + await setup({ getStoredClient, setStoredClient }); } - } - initClient() - }, [getStoredClient, setStoredClient]) + }; + initClient(); + }, [getStoredClient, setStoredClient]); const submitInit = (e: any) => { - e.preventDefault() - const deviceId = e.currentTarget[0].value - const password = e.currentTarget[1].value - const name = e.currentTarget[2].value + e.preventDefault(); + const deviceId = e.currentTarget[0].value; + const password = e.currentTarget[1].value; + const name = e.currentTarget[2].value; setup({ deviceId, password, name, getStoredClient, setStoredClient, - }) - } + }); + }; const submitPair = (e: React.FormEvent) => { - e.preventDefault() + e.preventDefault(); // @ts-expect-error - bad html types - const pairingCode = e.currentTarget[0].value.toUpperCase() - pair(pairingCode) - } + const pairingCode = e.currentTarget[0].value.toUpperCase(); + pair(pairingCode); + }; return (
@@ -97,7 +97,7 @@ function App() {
- ) + ); } -export default App +export default App; diff --git a/packages/example/src/Button.tsx b/packages/example/src/Button.tsx index 30f2a500..8b0c8e90 100644 --- a/packages/example/src/Button.tsx +++ b/packages/example/src/Button.tsx @@ -1,20 +1,20 @@ -import { type ReactNode, useState } from 'react' +import { type ReactNode, useState } from 'react'; interface ButtonProps { - onClick: () => Promise - children: ReactNode + onClick: () => Promise; + children: ReactNode; } export const Button = ({ onClick, children }: ButtonProps) => { - const [isLoading, setIsLoading] = useState(false) + const [isLoading, setIsLoading] = useState(false); const handleOnClick = () => { - setIsLoading(true) - onClick().finally(() => setIsLoading(false)) - } + setIsLoading(true); + onClick().finally(() => setIsLoading(false)); + }; return ( - ) -} + ); +}; diff --git a/packages/example/src/Lattice.tsx b/packages/example/src/Lattice.tsx index 7f6c9665..d0f3a9dd 100644 --- a/packages/example/src/Lattice.tsx +++ b/packages/example/src/Lattice.tsx @@ -1,4 +1,4 @@ -import { useState } from 'react' +import { useState } from 'react'; import { addAddressTags, fetchAddressTags, @@ -8,23 +8,23 @@ import { sign, signMessage, type AddressTag, -} from 'gridplus-sdk' -import { Button } from './Button' +} from 'gridplus-sdk'; +import { Button } from './Button'; interface LatticeProps { - label: string + label: string; } export const Lattice = ({ label }: LatticeProps) => { - const [addresses, setAddresses] = useState([]) - const [addressTags, setAddressTags] = useState([]) - const [ledgerAddresses, setLedgerAddresses] = useState([]) + const [addresses, setAddresses] = useState([]); + const [addressTags, setAddressTags] = useState([]); + const [ledgerAddresses, setLedgerAddresses] = useState([]); // Example EIP-1559 transaction payload using raw hex format const getTxPayload = (): `0x${string}` => { // Pre-serialized EIP-1559 transaction for example purposes - return '0x02f8620180843b9aca00843b9aca0082c350940000000000000000000000000000000000000000880de0b6b3a764000080c0' - } + return '0x02f8620180843b9aca00843b9aca0082c350940000000000000000000000000000000000000000880de0b6b3a764000080c0'; + }; return (
{

{label}

- ) -} + ); +}; diff --git a/packages/example/src/main.tsx b/packages/example/src/main.tsx index 791f139e..5549107e 100644 --- a/packages/example/src/main.tsx +++ b/packages/example/src/main.tsx @@ -1,10 +1,10 @@ -import React from 'react' -import ReactDOM from 'react-dom/client' -import App from './App' -import './index.css' +import React from 'react'; +import ReactDOM from 'react-dom/client'; +import App from './App'; +import './index.css'; ReactDOM.createRoot(document.getElementById('root') as HTMLElement).render( , -) +); diff --git a/packages/sdk/src/__test__/e2e/api.test.ts b/packages/sdk/src/__test__/e2e/api.test.ts index 9a26569c..a7cc353b 100644 --- a/packages/sdk/src/__test__/e2e/api.test.ts +++ b/packages/sdk/src/__test__/e2e/api.test.ts @@ -1,10 +1,10 @@ vi.mock('../../functions/fetchDecoder.ts', () => ({ fetchDecoder: vi.fn().mockResolvedValue(undefined), -})) +})); vi.mock('../../util', async () => { const actual = - await vi.importActual('../../util') + await vi.importActual('../../util'); return { ...actual, fetchCalldataDecoder: vi.fn().mockResolvedValue({ @@ -17,10 +17,10 @@ vi.mock('../../util', async () => { }, ], }), - } -}) + }; +}); -import { RLP } from '@ethereumjs/rlp' +import { RLP } from '@ethereumjs/rlp'; import { fetchActiveWallets, fetchAddress, @@ -34,7 +34,7 @@ import { signBtcSegwitTx, signBtcWrappedSegwitTx, signMessage, -} from '../../api' +} from '../../api'; import { addAddressTags, fetchAddressTags, @@ -42,18 +42,18 @@ import { removeAddressTags, sign, signSolanaTx, -} from '../../api/index' -import { HARDENED_OFFSET } from '../../constants' -import { buildRandomMsg } from '../utils/builders' -import { BTC_PURPOSE_P2SH_P2WPKH, BTC_TESTNET_COIN } from '../utils/helpers' -import { setupClient } from '../utils/setup' -import { getClient } from './../../api/utilities' -import { dexlabProgram } from './signing/solana/__mocks__/programs' +} from '../../api/index'; +import { HARDENED_OFFSET } from '../../constants'; +import { buildRandomMsg } from '../utils/builders'; +import { BTC_PURPOSE_P2SH_P2WPKH, BTC_TESTNET_COIN } from '../utils/helpers'; +import { setupClient } from '../utils/setup'; +import { getClient } from './../../api/utilities'; +import { dexlabProgram } from './signing/solana/__mocks__/programs'; describe('API', () => { beforeAll(async () => { - await setupClient() - }) + await setupClient(); + }); describe('signing', () => { describe('bitcoin', () => { @@ -83,31 +83,31 @@ describe('API', () => { 1, 0, ], - } + }; test('legacy', async () => { - await signBtcLegacyTx(btcTxData) - }) + await signBtcLegacyTx(btcTxData); + }); test('segwit', async () => { - await signBtcSegwitTx(btcTxData) - }) + await signBtcSegwitTx(btcTxData); + }); test('wrapped segwit', async () => { - await signBtcWrappedSegwitTx(btcTxData) - }) - }) + await signBtcWrappedSegwitTx(btcTxData); + }); + }); describe('ethereum', () => { describe('messages', () => { test('signPersonal', async () => { - await signMessage('test message') - }) + await signMessage('test message'); + }); test('eip712', async () => { - const client = await getClient() - await signMessage(buildRandomMsg('eip712', client)) - }) - }) + const client = await getClient(); + await signMessage(buildRandomMsg('eip712', client)); + }); + }); describe('transactions', () => { const txData = { @@ -119,15 +119,15 @@ describe('API', () => { value: 1000000000000n, data: '0x38ed17390000000000000000000000000000000000000000000c1c173c5b782a5b154ab900000000000000000000000000000000000000000000000f380d77022fe8c32600000000000000000000000000000000000000000000000000000000000000a00000000000000000000000007ae7684581f0298241c3d6a6567a48d56b42b15c00000000000000000000000000000000000000000000000000000000622f8d27000000000000000000000000000000000000000000000000000000000000000300000000000000000000000095ad61b0a150d79219dcf64e1e6cc01f0b64c4ce000000000000000000000000c02aaa39b223fe8d0a0e5c4f27ead9083c756cc200000000000000000000000050522c769e01eb06c02bd299066509d8f97a69ae', gasPrice: 1200000000n, - } as const + } as const; test('generic', async () => { - await sign(txData) - }) + await sign(txData); + }); test('legacy', async () => { const toHex = (v: bigint | number) => - typeof v === 'bigint' ? `0x${v.toString(16)}` : v + typeof v === 'bigint' ? `0x${v.toString(16)}` : v; const rawTx = RLP.encode([ txData.nonce, toHex(txData.gasPrice), @@ -135,18 +135,18 @@ describe('API', () => { txData.to, toHex(txData.value), txData.data, - ]) - await sign(rawTx) - }) - }) - }) + ]); + await sign(rawTx); + }); + }); + }); describe('solana', () => { test('sign solana', async () => { - await signSolanaTx(dexlabProgram) - }) - }) - }) + await signSolanaTx(dexlabProgram); + }); + }); + }); describe('address tags', () => { beforeAll(async () => { @@ -159,98 +159,99 @@ describe('API', () => { 5000, ), ), - ]) + ]); } catch (err) { console.warn( 'Skipping address tag tests due to connectivity issue:', (err as Error).message, - ) + ); } - }) + }); it('addAddressTags', async () => { - const key = `tag-${Date.now()}` - await addAddressTags([{ [key]: 'test' }]) - const addressTags = await fetchAddressTags() - expect(addressTags.some((tag) => tag.key === key)).toBeTruthy() - const tagsToRemove = addressTags.filter((tag) => tag.key === key) + const key = `tag-${Date.now()}`; + await addAddressTags([{ [key]: 'test' }]); + const addressTags = await fetchAddressTags(); + expect(addressTags.some((tag) => tag.key === key)).toBeTruthy(); + const tagsToRemove = addressTags.filter((tag) => tag.key === key); if (tagsToRemove.length) { - await removeAddressTags(tagsToRemove) + await removeAddressTags(tagsToRemove); } - }) + }); it('fetchAddressTags', async () => { - const key = `fetch-tag-${Date.now()}` - await addAddressTags([{ [key]: 'value' }]) - const addressTags = await fetchAddressTags() - expect(addressTags.some((tag) => tag.key === key)).toBeTruthy() - const tagsToRemove = addressTags.filter((tag) => tag.key === key) + const key = `fetch-tag-${Date.now()}`; + await addAddressTags([{ [key]: 'value' }]); + const addressTags = await fetchAddressTags(); + expect(addressTags.some((tag) => tag.key === key)).toBeTruthy(); + const tagsToRemove = addressTags.filter((tag) => tag.key === key); if (tagsToRemove.length) { - await removeAddressTags(tagsToRemove) + await removeAddressTags(tagsToRemove); } - }) + }); it('removeAddressTags', async () => { - const key = `remove-tag-${Date.now()}` - await addAddressTags([{ [key]: 'value' }]) - const addressTags = await fetchAddressTags() - const tagsToRemove = addressTags.filter((tag) => tag.key === key) - expect(tagsToRemove).not.toHaveLength(0) - await removeAddressTags(tagsToRemove) - const remainingTags = await fetchAddressTags() - expect(remainingTags.some((tag) => tag.key === key)).toBeFalsy() - }) - }) + const key = `remove-tag-${Date.now()}`; + await addAddressTags([{ [key]: 'value' }]); + const addressTags = await fetchAddressTags(); + const tagsToRemove = addressTags.filter((tag) => tag.key === key); + expect(tagsToRemove).not.toHaveLength(0); + await removeAddressTags(tagsToRemove); + const remainingTags = await fetchAddressTags(); + expect(remainingTags.some((tag) => tag.key === key)).toBeFalsy(); + }); + }); describe('addresses', () => { describe('fetchAddresses', () => { test('fetchAddresses', async () => { - const addresses = await fetchAddresses() - expect(addresses).toHaveLength(10) - }) + const addresses = await fetchAddresses(); + expect(addresses).toHaveLength(10); + }); test('fetchAddresses[1]', async () => { - const addresses = await fetchAddresses({ n: 1 }) - expect(addresses).toHaveLength(1) - }) + const addresses = await fetchAddresses({ n: 1 }); + expect(addresses).toHaveLength(1); + }); test('fetchAddresses[12]', async () => { - const addresses = await fetchAddresses({ n: 12 }) - expect(addresses).toHaveLength(12) - }) + const addresses = await fetchAddresses({ n: 12 }); + expect(addresses).toHaveLength(12); + }); test('fetchBtcLegacyAddresses', async () => { - const addresses = await fetchBtcLegacyAddresses() - expect(addresses).toHaveLength(10) - }) + const addresses = await fetchBtcLegacyAddresses(); + expect(addresses).toHaveLength(10); + }); test('fetchBtcSegwitAddresses[12]', async () => { - const addresses = await fetchBtcSegwitAddresses({ n: 12 }) - expect(addresses).toHaveLength(12) - }) + const addresses = await fetchBtcSegwitAddresses({ n: 12 }); + expect(addresses).toHaveLength(12); + }); test('fetchLedgerLiveAddresses', async () => { - const addresses = await fetchLedgerLiveAddresses() - expect(addresses).toHaveLength(10) - }) + const addresses = await fetchLedgerLiveAddresses(); + expect(addresses).toHaveLength(10); + }); test('fetchSolanaAddresses', async () => { - const addresses = await fetchSolanaAddresses() - expect(addresses).toHaveLength(10) - }) + const addresses = await fetchSolanaAddresses(); + expect(addresses).toHaveLength(10); + }); test('fetchBip44ChangeAddresses', async () => { - const addresses = await fetchBip44ChangeAddresses() - expect(addresses).toHaveLength(10) - }) - }) + const addresses = await fetchBip44ChangeAddresses(); + expect(addresses).toHaveLength(10); + }); + }); describe('fetchAddressesByDerivationPath', () => { test('fetch single specific address', async () => { - const addresses = await fetchAddressesByDerivationPath("44'/60'/0'/0/0") - expect(addresses).toHaveLength(1) - expect(addresses[0]).toBeTruthy() - }) + const addresses = + await fetchAddressesByDerivationPath("44'/60'/0'/0/0"); + expect(addresses).toHaveLength(1); + expect(addresses[0]).toBeTruthy(); + }); test('fetch multiple addresses with wildcard', async () => { const addresses = await fetchAddressesByDerivationPath( @@ -258,12 +259,12 @@ describe('API', () => { { n: 5, }, - ) - expect(addresses).toHaveLength(5) + ); + expect(addresses).toHaveLength(5); addresses.forEach((address) => { - expect(address).toBeTruthy() - }) - }) + expect(address).toBeTruthy(); + }); + }); test('fetch addresses with offset', async () => { const addresses = await fetchAddressesByDerivationPath( @@ -272,12 +273,12 @@ describe('API', () => { n: 3, startPathIndex: 10, }, - ) - expect(addresses).toHaveLength(3) + ); + expect(addresses).toHaveLength(3); addresses.forEach((address) => { - expect(address).toBeTruthy() - }) - }) + expect(address).toBeTruthy(); + }); + }); test('fetch addresses with lowercase x wildcard', async () => { const addresses = await fetchAddressesByDerivationPath( @@ -285,12 +286,12 @@ describe('API', () => { { n: 2, }, - ) - expect(addresses).toHaveLength(2) + ); + expect(addresses).toHaveLength(2); addresses.forEach((address) => { - expect(address).toBeTruthy() - }) - }) + expect(address).toBeTruthy(); + }); + }); test('fetch addresses with wildcard in middle of path', async () => { const addresses = await fetchAddressesByDerivationPath( @@ -298,12 +299,12 @@ describe('API', () => { { n: 3, }, - ) - expect(addresses).toHaveLength(3) + ); + expect(addresses).toHaveLength(3); addresses.forEach((address) => { - expect(address).toBeTruthy() - }) - }) + expect(address).toBeTruthy(); + }); + }); test('fetch solana addresses with wildcard in middle of path', async () => { const addresses = await fetchAddressesByDerivationPath( @@ -311,18 +312,18 @@ describe('API', () => { { n: 1, }, - ) - expect(addresses).toHaveLength(1) + ); + expect(addresses).toHaveLength(1); addresses.forEach((address) => { - expect(address).toBeTruthy() - }) - }) + expect(address).toBeTruthy(); + }); + }); test('error on invalid derivation path', async () => { await expect( fetchAddressesByDerivationPath('invalid/path'), - ).rejects.toThrow() - }) + ).rejects.toThrow(); + }); test('fetch single address when n=1 with wildcard', async () => { const addresses = await fetchAddressesByDerivationPath( @@ -330,10 +331,10 @@ describe('API', () => { { n: 1, }, - ) - expect(addresses).toHaveLength(1) - expect(addresses[0]).toBeTruthy() - }) + ); + expect(addresses).toHaveLength(1); + expect(addresses[0]).toBeTruthy(); + }); test('fetch no addresses when n=0', async () => { const addresses = await fetchAddressesByDerivationPath( @@ -341,23 +342,23 @@ describe('API', () => { { n: 0, }, - ) - expect(addresses).toHaveLength(0) - }) - }) + ); + expect(addresses).toHaveLength(0); + }); + }); describe('fetchAddress', () => { test('fetchAddress', async () => { - const address = await fetchAddress() - expect(address).toBeTruthy() - }) - }) - }) + const address = await fetchAddress(); + expect(address).toBeTruthy(); + }); + }); + }); describe('fetchActiveWallets', () => { test('fetchActiveWallets', async () => { - const wallet = await fetchActiveWallets() - expect(wallet).toBeTruthy() - }) - }) -}) + const wallet = await fetchActiveWallets(); + expect(wallet).toBeTruthy(); + }); + }); +}); diff --git a/packages/sdk/src/__test__/e2e/btc.test.ts b/packages/sdk/src/__test__/e2e/btc.test.ts index 86e285ca..a345f787 100644 --- a/packages/sdk/src/__test__/e2e/btc.test.ts +++ b/packages/sdk/src/__test__/e2e/btc.test.ts @@ -12,58 +12,58 @@ * incorrect address derivations and signature mismatches. */ -import BIP32Factory, { type BIP32Interface } from 'bip32' -import * as ecc from 'tiny-secp256k1' -import type { Client } from '../../client' -import { getPrng, getTestnet } from '../utils/getters' +import BIP32Factory, { type BIP32Interface } from 'bip32'; +import * as ecc from 'tiny-secp256k1'; +import type { Client } from '../../client'; +import { getPrng, getTestnet } from '../utils/getters'; import { BTC_PURPOSE_P2PKH, BTC_PURPOSE_P2SH_P2WPKH, BTC_PURPOSE_P2WPKH, setup_btc_sig_test, stripDER, -} from '../utils/helpers' -import { setupClient } from '../utils/setup' -import { TEST_SEED } from '../utils/testConstants' +} from '../utils/helpers'; +import { setupClient } from '../utils/setup'; +import { TEST_SEED } from '../utils/testConstants'; -const prng = getPrng() -const bip32 = BIP32Factory(ecc) -const TEST_TESTNET = !!getTestnet() || false -let wallet: BIP32Interface | null = null -type InputObj = { hash: string; value: number; signerIdx: number; idx: number } +const prng = getPrng(); +const bip32 = BIP32Factory(ecc); +const TEST_TESTNET = !!getTestnet() || false; +let wallet: BIP32Interface | null = null; +type InputObj = { hash: string; value: number; signerIdx: number; idx: number }; // Build the inputs. By default we will build 10. Note that there are `n` tests for // *each category*, where `n` is the number of inputs. function rand32Bit() { - return Math.floor(prng.quick() * 2 ** 32) + return Math.floor(prng.quick() * 2 ** 32); } -const inputs: InputObj[] = [] -const count = 10 +const inputs: InputObj[] = []; +const count = 10; for (let i = 0; i < count; i++) { - const hash = Buffer.alloc(32) + const hash = Buffer.alloc(32); for (let j = 0; j < 8; j++) { // 32 bits of randomness per call - hash.writeUInt32BE(rand32Bit(), j * 4) + hash.writeUInt32BE(rand32Bit(), j * 4); } - const value = Math.floor(rand32Bit()) - const signerIdx = Math.floor(prng.quick() * 19) // Random signer (keep it inside initial cache of 20) - const idx = Math.floor(prng.quick() * 25) // Random previous output index (keep it small) - inputs.push({ hash: hash.toString('hex'), value, signerIdx, idx }) + const value = Math.floor(rand32Bit()); + const signerIdx = Math.floor(prng.quick() * 19); // Random signer (keep it inside initial cache of 20) + const idx = Math.floor(prng.quick() * 25); // Random previous output index (keep it small) + inputs.push({ hash: hash.toString('hex'), value, signerIdx, idx }); } async function testSign({ txReq, signingKeys, sigHashes, client }: any) { - const tx = await client.sign(txReq) - const len = tx?.sigs?.length ?? 0 - expect(len).toEqual(signingKeys.length) - expect(len).toEqual(sigHashes.length) + const tx = await client.sign(txReq); + const len = tx?.sigs?.length ?? 0; + expect(len).toEqual(signingKeys.length); + expect(len).toEqual(sigHashes.length); for (let i = 0; i < len; i++) { - const sig = stripDER(tx.sigs?.[i]) - const verification = signingKeys[i].verify(sigHashes[i], sig) + const sig = stripDER(tx.sigs?.[i]); + const verification = signingKeys[i].verify(sigHashes[i], sig); expect(verification).toEqualElseLog( true, `Signature validation failed for priv=${signingKeys[i].privateKey.toString('hex')}, ` + `hash=${sigHashes[i].toString('hex')}, sig=${sig.toString('hex')}`, - ) + ); } } @@ -73,49 +73,49 @@ async function runTestSet( inputsSlice: InputObj[], client, ) { - expect(wallet).not.toEqualElseLog(null, 'Wallet not available') + expect(wallet).not.toEqualElseLog(null, 'Wallet not available'); if (TEST_TESTNET) { // Testnet + change - opts.isTestnet = true - opts.useChange = true + opts.isTestnet = true; + opts.useChange = true; await testSign({ ...setup_btc_sig_test(opts, wallet, inputsSlice, prng), client, - }) + }); // Testnet + no change - opts.isTestnet = true - opts.useChange = false + opts.isTestnet = true; + opts.useChange = false; await testSign({ ...setup_btc_sig_test(opts, wallet, inputsSlice, prng), client, - }) + }); } // Mainnet + change - opts.isTestnet = false - opts.useChange = true + opts.isTestnet = false; + opts.useChange = true; await testSign({ ...setup_btc_sig_test(opts, wallet, inputsSlice, prng), client, - }) + }); // Mainnet + no change - opts.isTestnet = false - opts.useChange = false + opts.isTestnet = false; + opts.useChange = false; await testSign({ ...setup_btc_sig_test(opts, wallet, inputsSlice, prng), client, - }) + }); } describe('Bitcoin', () => { - let client: Client + let client: Client; beforeAll(async () => { - client = await setupClient() - wallet = bip32.fromSeed(TEST_SEED) - }) + client = await setupClient(); + wallet = bip32.fromSeed(TEST_SEED); + }); for (let i = 0; i < inputs.length; i++) { - const inputsSlice = inputs.slice(0, i + 1) + const inputsSlice = inputs.slice(0, i + 1); describe(`Input Set ${i}`, () => { describe('segwit spender (p2wpkh)', () => { @@ -123,78 +123,78 @@ describe('Bitcoin', () => { const opts = { spenderPurpose: BTC_PURPOSE_P2WPKH, recipientPurpose: BTC_PURPOSE_P2PKH, - } - await runTestSet(opts, wallet, inputsSlice, client) - }) + }; + await runTestSet(opts, wallet, inputsSlice, client); + }); it('p2wpkh->p2sh-p2wpkh', async () => { const opts = { spenderPurpose: BTC_PURPOSE_P2WPKH, recipientPurpose: BTC_PURPOSE_P2SH_P2WPKH, - } - await runTestSet(opts, wallet, inputsSlice, client) - }) + }; + await runTestSet(opts, wallet, inputsSlice, client); + }); it('p2wpkh->p2wpkh', async () => { const opts = { spenderPurpose: BTC_PURPOSE_P2WPKH, recipientPurpose: BTC_PURPOSE_P2WPKH, - } - await runTestSet(opts, wallet, inputsSlice, client) - }) - }) + }; + await runTestSet(opts, wallet, inputsSlice, client); + }); + }); describe('wrapped segwit spender (p2sh-p2wpkh)', () => { it('p2sh-p2wpkh->p2pkh', async () => { const opts = { spenderPurpose: BTC_PURPOSE_P2SH_P2WPKH, recipientPurpose: BTC_PURPOSE_P2PKH, - } - await runTestSet(opts, wallet, inputsSlice, client) - }) + }; + await runTestSet(opts, wallet, inputsSlice, client); + }); it('p2sh-p2wpkh->p2sh-p2wpkh', async () => { const opts = { spenderPurpose: BTC_PURPOSE_P2SH_P2WPKH, recipientPurpose: BTC_PURPOSE_P2SH_P2WPKH, - } - await runTestSet(opts, wallet, inputsSlice, client) - }) + }; + await runTestSet(opts, wallet, inputsSlice, client); + }); it('p2sh-p2wpkh->p2wpkh', async () => { const opts = { spenderPurpose: BTC_PURPOSE_P2SH_P2WPKH, recipientPurpose: BTC_PURPOSE_P2WPKH, - } - await runTestSet(opts, wallet, inputsSlice, client) - }) - }) + }; + await runTestSet(opts, wallet, inputsSlice, client); + }); + }); describe('legacy spender (p2pkh)', () => { it('p2pkh->p2pkh', async () => { const opts = { spenderPurpose: BTC_PURPOSE_P2PKH, recipientPurpose: BTC_PURPOSE_P2PKH, - } - await runTestSet(opts, wallet, inputsSlice, client) - }) + }; + await runTestSet(opts, wallet, inputsSlice, client); + }); it('p2pkh->p2sh-p2wpkh', async () => { const opts = { spenderPurpose: BTC_PURPOSE_P2PKH, recipientPurpose: BTC_PURPOSE_P2SH_P2WPKH, - } - await runTestSet(opts, wallet, inputsSlice, client) - }) + }; + await runTestSet(opts, wallet, inputsSlice, client); + }); it('p2pkh->p2wpkh', async () => { const opts = { spenderPurpose: BTC_PURPOSE_P2PKH, recipientPurpose: BTC_PURPOSE_P2WPKH, - } - await runTestSet(opts, wallet, inputsSlice, client) - }) - }) - }) + }; + await runTestSet(opts, wallet, inputsSlice, client); + }); + }); + }); } -}) +}); diff --git a/packages/sdk/src/__test__/e2e/eth.msg.test.ts b/packages/sdk/src/__test__/e2e/eth.msg.test.ts index 944ae95f..a827211e 100644 --- a/packages/sdk/src/__test__/e2e/eth.msg.test.ts +++ b/packages/sdk/src/__test__/e2e/eth.msg.test.ts @@ -16,87 +16,93 @@ * CMakeLists.txt file (for dev units) */ -import { HARDENED_OFFSET } from '../../constants' -import type { SigningPath } from '../../types' -import { randomBytes } from '../../util' -import { buildEthMsgReq, buildRandomMsg } from '../utils/builders' -import { runEthMsg } from '../utils/runners' -import { setupClient } from '../utils/setup' -import type { Client } from '../../client' +import { HARDENED_OFFSET } from '../../constants'; +import type { SigningPath } from '../../types'; +import { randomBytes } from '../../util'; +import { buildEthMsgReq, buildRandomMsg } from '../utils/builders'; +import { runEthMsg } from '../utils/runners'; +import { setupClient } from '../utils/setup'; +import type { Client } from '../../client'; describe('ETH Messages', () => { - let client: Client + let client: Client; beforeAll(async () => { - client = await setupClient() - }) + client = await setupClient(); + }); describe('Test ETH personalSign', () => { it('Should throw error when message contains non-ASCII characters', async () => { - const protocol = 'signPersonal' - const msg = '⚠️' - const msg2 = 'ASCII plus ⚠️' + const protocol = 'signPersonal'; + const msg = '⚠️'; + const msg2 = 'ASCII plus ⚠️'; await expect(client.sign(buildEthMsgReq(msg, protocol))).rejects.toThrow( /Lattice can only display ASCII/, - ) + ); await expect(client.sign(buildEthMsgReq(msg2, protocol))).rejects.toThrow( /Lattice can only display ASCII/, - ) - }) + ); + }); it('Should test ASCII buffers', async () => { await runEthMsg( buildEthMsgReq(Buffer.from('i am an ascii buffer'), 'signPersonal'), client, - ) + ); await runEthMsg( buildEthMsgReq(Buffer.from('{\n\ttest: foo\n}'), 'signPersonal'), client, - ) - }) + ); + }); it('Should test hex buffers', async () => { await runEthMsg( buildEthMsgReq(Buffer.from('abcdef', 'hex'), 'signPersonal'), client, - ) - }) + ); + }); it('Should test a message that needs to be prehashed', async () => { - await runEthMsg(buildEthMsgReq(randomBytes(4000), 'signPersonal'), client) - }) + await runEthMsg( + buildEthMsgReq(randomBytes(4000), 'signPersonal'), + client, + ); + }); it('Msg: sign_personal boundary conditions and auto-rejected requests', async () => { - const protocol = 'signPersonal' - const fwConstants = client.getFwConstants() + const protocol = 'signPersonal'; + const fwConstants = client.getFwConstants(); // `personal_sign` requests have a max size smaller than other requests because a header // is displayed in the text region of the screen. The size of this is captured // by `fwConstants.personalSignHeaderSz`. const maxMsgSz = fwConstants.ethMaxMsgSz + fwConstants.personalSignHeaderSz + - fwConstants.extraDataMaxFrames * fwConstants.extraDataFrameSz - const maxValid = `0x${randomBytes(maxMsgSz).toString('hex')}` - const minInvalid = `0x${randomBytes(maxMsgSz + 1).toString('hex')}` - const zeroInvalid = '0x' + fwConstants.extraDataMaxFrames * fwConstants.extraDataFrameSz; + const maxValid = `0x${randomBytes(maxMsgSz).toString('hex')}`; + const minInvalid = `0x${randomBytes(maxMsgSz + 1).toString('hex')}`; + const zeroInvalid = '0x'; // The largest non-hardened index which will take the most chars to print - const x = HARDENED_OFFSET - 1 + const x = HARDENED_OFFSET - 1; // Okay sooo this is a bit awkward. We have to use a known coin_type here (e.g. ETH) // or else firmware will return an error, but the maxSz is based on the max length // of a path, which is larger than we can actually print. // I guess all this tests is that the first one is shown in plaintext while the second // one (which is too large) gets prehashed. - const largeSignPath = [x, HARDENED_OFFSET + 60, x, x, x] as SigningPath - await runEthMsg(buildEthMsgReq(maxValid, protocol, largeSignPath), client) + const largeSignPath = [x, HARDENED_OFFSET + 60, x, x, x] as SigningPath; + await runEthMsg( + buildEthMsgReq(maxValid, protocol, largeSignPath), + client, + ); await runEthMsg( buildEthMsgReq(minInvalid, protocol, largeSignPath), client, - ) + ); // Using a zero length payload should auto-reject await expect( client.sign(buildEthMsgReq(zeroInvalid, protocol)), - ).rejects.toThrow(/Invalid Request/) - }) + ).rejects.toThrow(/Invalid Request/); + }); describe(`Test ${5} random payloads`, () => { for (let i = 0; i < 5; i++) { @@ -107,11 +113,11 @@ describe('ETH Messages', () => { 'signPersonal', ), client, - ) - }) + ); + }); } - }) - }) + }); + }); describe('Test ETH EIP712', () => { it('Should test a message that needs to be prehashed', async () => { @@ -137,9 +143,9 @@ describe('ETH Messages', () => { action: 'dYdX STARK Key', onlySignOn: randomBytes(4000).toString('hex'), }, - } - await runEthMsg(buildEthMsgReq(msg, 'eip712'), client) - }) + }; + await runEthMsg(buildEthMsgReq(msg, 'eip712'), client); + }); it('Should test an example from Blur NFT w/ 0 fees', async () => { const msg = { @@ -199,9 +205,9 @@ describe('ETH Messages', () => { // { rate: 1, recipient: '0x00000000006411739da1c40b106f8511de5d1fac'} ], }, - } - await runEthMsg(buildEthMsgReq(msg, 'eip712'), client) - }) + }; + await runEthMsg(buildEthMsgReq(msg, 'eip712'), client); + }); it('Should test simple dydx example', async () => { const msg = { @@ -226,9 +232,9 @@ describe('ETH Messages', () => { action: 'dYdX STARK Key', onlySignOn: 'https://trade.dydx.exchange', }, - } - await runEthMsg(buildEthMsgReq(msg, 'eip712'), client) - }) + }; + await runEthMsg(buildEthMsgReq(msg, 'eip712'), client); + }); it('Should test a Loopring message with non-standard numerical type', async () => { const msg = { @@ -266,9 +272,9 @@ describe('ETH Messages', () => { validUntil: 1631655383, nonce: 0, }, - } - await runEthMsg(buildEthMsgReq(msg, 'eip712'), client) - }) + }; + await runEthMsg(buildEthMsgReq(msg, 'eip712'), client); + }); it('Should test Vertex message', async () => { const msg = { @@ -302,9 +308,9 @@ describe('ETH Messages', () => { expiration: '4611687701117784255', nonce: '1764428860167815857', }, - } - await runEthMsg(buildEthMsgReq(msg, 'eip712'), client) - }) + }; + await runEthMsg(buildEthMsgReq(msg, 'eip712'), client); + }); it('Should test a large 1inch transaction', async () => { const msg = { @@ -394,9 +400,9 @@ describe('ETH Messages', () => { }, ], }, - } - await runEthMsg(buildEthMsgReq(msg, 'eip712'), client) - }) + }; + await runEthMsg(buildEthMsgReq(msg, 'eip712'), client); + }); it('Should test an example with 0 values', async () => { const msg = { @@ -425,9 +431,9 @@ describe('ETH Messages', () => { owner: '0x56626bd0d646ce9da4a12403b2c1ba00fb9e1c43', testArray: [], }, - } - await runEthMsg(buildEthMsgReq(msg, 'eip712'), client) - }) + }; + await runEthMsg(buildEthMsgReq(msg, 'eip712'), client); + }); it('Should test canonical EIP712 example', async () => { const msg = { @@ -466,9 +472,9 @@ describe('ETH Messages', () => { }, contents: 'foobar', }, - } - await runEthMsg(buildEthMsgReq(msg, 'eip712'), client) - }) + }; + await runEthMsg(buildEthMsgReq(msg, 'eip712'), client); + }); it('Should test canonical EIP712 example with 2nd level nesting', async () => { const msg = { @@ -517,9 +523,9 @@ describe('ETH Messages', () => { }, contents: 'foobar', }, - } - await runEthMsg(buildEthMsgReq(msg, 'eip712'), client) - }) + }; + await runEthMsg(buildEthMsgReq(msg, 'eip712'), client); + }); it('Should test canonical EIP712 example with 3rd level nesting', async () => { const msg = { @@ -578,9 +584,9 @@ describe('ETH Messages', () => { }, contents: 'foobar', }, - } - await runEthMsg(buildEthMsgReq(msg, 'eip712'), client) - }) + }; + await runEthMsg(buildEthMsgReq(msg, 'eip712'), client); + }); it('Should test canonical EIP712 example with 3rd level nesting and params in a different order', async () => { const msg = { @@ -639,9 +645,9 @@ describe('ETH Messages', () => { }, }, }, - } - await runEthMsg(buildEthMsgReq(msg, 'eip712'), client) - }) + }; + await runEthMsg(buildEthMsgReq(msg, 'eip712'), client); + }); it('Should test a payload with an array type', async () => { const msg = { @@ -685,9 +691,9 @@ describe('ETH Messages', () => { }, ], }, - } - await runEthMsg(buildEthMsgReq(msg, 'eip712'), client) - }) + }; + await runEthMsg(buildEthMsgReq(msg, 'eip712'), client); + }); it('Should test multiple array types', async () => { const msg = { @@ -743,9 +749,9 @@ describe('ETH Messages', () => { dummy: 52, integerArray: [1, 2, 3], }, - } - await runEthMsg(buildEthMsgReq(msg, 'eip712'), client) - }) + }; + await runEthMsg(buildEthMsgReq(msg, 'eip712'), client); + }); it('Should test a nested array', async () => { const msg = { @@ -786,9 +792,9 @@ describe('ETH Messages', () => { }, ], }, - } - await runEthMsg(buildEthMsgReq(msg, 'eip712'), client) - }) + }; + await runEthMsg(buildEthMsgReq(msg, 'eip712'), client); + }); it('Should test a nested array of custom type', async () => { const msg = { @@ -859,9 +865,9 @@ describe('ETH Messages', () => { }, ], }, - } - await runEthMsg(buildEthMsgReq(msg, 'eip712'), client) - }) + }; + await runEthMsg(buildEthMsgReq(msg, 'eip712'), client); + }); it('Should test a bunch of EIP712 data types', async () => { const msg = { @@ -935,9 +941,9 @@ describe('ETH Messages', () => { BOOL: true, ADDRESS: '0x078a8d6eba928e7ea787ed48f71c5936aed4625d', }, - } - await runEthMsg(buildEthMsgReq(msg, 'eip712'), client) - }) + }; + await runEthMsg(buildEthMsgReq(msg, 'eip712'), client); + }); it('Should test a payload with a nested type in multiple nesting levels', async () => { const msg = { @@ -985,9 +991,9 @@ describe('ETH Messages', () => { }, }, }, - } - await runEthMsg(buildEthMsgReq(msg, 'eip712'), client) - }) + }; + await runEthMsg(buildEthMsgReq(msg, 'eip712'), client); + }); it('Should test a payload that requires use of extraData frames', async () => { const msg = { @@ -1047,9 +1053,9 @@ describe('ETH Messages', () => { contents: 'stupidlylongstringthatshouldstretchintomultiplepageswhencopiedmanytimesstupidlylongstringthatshouldstretchintomultiplepageswhencopiedmanytimesstupidlylongstringthatshouldstretchintomultiplepageswhencopiedmanytimesstupidlylongstringthatshouldstretchintomultiplepageswhencopiedmanytimesstupidlylongstringthatshouldstretchintomultiplepageswhencopiedmanytimesstupidlylongstringthatshouldstretchintomultiplepageswhencopiedmanytimesstupidlylongstringthatshouldstretchintomultiplepageswhencopiedmanytimesstupidlylongstringthatshouldstretchintomultiplepageswhencopiedmanytimesstupidlylongstringthatshouldstretchintomultiplepageswhencopiedmanytimesstupidlylongstringthatshouldstretchintomultiplepageswhencopiedmanytimesstupidlylongstringthatshouldstretchintomultiplepageswhencopiedmanytimesstupidlylongstringthatshouldstretchintomultiplepageswhencopiedmanytimesstupidlylongstringthatshouldstretchintomultiplepageswhencopiedmanytimesstupidlylongstringthatshouldstretchintomultiplepageswhencopiedmanytimesstupidlylongstringthatshouldstretchintomultiplepageswhencopiedmanytimesstupidlylongstringthatshouldstretchintomultiplepageswhencopiedmanytimesstupidlylongstringthatshouldstretchintomultiplepageswhencopiedmanytimesstupidlylongstringthatshouldstretchintomultiplepageswhencopiedmanytimesstupidlylongstringthatshouldstretchintomultiplepageswhencopiedmanytimesstupidlylongstringthatshouldstretchintomultiplepageswhencopiedmanytimesstupidlylongstringthatshouldstretchintomultiplepageswhencopiedmanytimesstupidlylongstringthatshouldstretchintomultiplepageswhencopiedmanytimesstupidlylongstringthatshouldstretchintomultiplepageswhencopiedmanytimesstupidlylongstringthatshouldstretchintomultiplepageswhencopiedmanytimesstupidlylongstringthatshouldstretchintomultiplepageswhencopiedmanytimesstupidlylongstringthatshouldstretchintomultiplepageswhencopiedmanytimesstupidlylongstringthatshouldstretchintomultiplepageswhencopiedmanytimesstupidlylongstringthatshouldstretchintomultiplepageswhencopiedmanytimesstupidlylongstringthatshouldstretchintomultiplepageswhencopiedmanytimesstupidlylongstringthatshouldstretchintomultiplepageswhencopiedmanytimesstupidlylongstringthatshouldstretchintomultiplepageswhencopiedmanytimesstupidlylongstringthatshouldstretchintomultiplepageswhencopiedmanytimesstupidlylongstringthatshouldstretchintomultiplepageswhencopiedmanytimes', }, - } - await runEthMsg(buildEthMsgReq(msg, 'eip712'), client) - }) + }; + await runEthMsg(buildEthMsgReq(msg, 'eip712'), client); + }); it('Should test a message with very large types', async () => { const msg = { @@ -1206,9 +1212,9 @@ describe('ETH Messages', () => { salt: '35033335384310326785897317545538185126505283328747281434561962939625063440824', nonce: 0, }, - } - await runEthMsg(buildEthMsgReq(msg, 'eip712'), client) - }) + }; + await runEthMsg(buildEthMsgReq(msg, 'eip712'), client); + }); it('Should test random edge case #1', async () => { // This was a randomly generated payload which caused an edge case. @@ -1289,9 +1295,9 @@ describe('ETH Messages', () => { }, drift_patch_cable_bi: '0xb4', }, - } - await runEthMsg(buildEthMsgReq(msg, 'eip712'), client) - }) + }; + await runEthMsg(buildEthMsgReq(msg, 'eip712'), client); + }); it('Signs long primary types', async () => { const msg = { @@ -1348,9 +1354,9 @@ describe('ETH Messages', () => { nonce: 1718376161247, type: 'approveAgent', }, - } - await runEthMsg(buildEthMsgReq(msg, 'eip712'), client) - }) + }; + await runEthMsg(buildEthMsgReq(msg, 'eip712'), client); + }); describe('test 5 random payloads', () => { for (let i = 0; i < 5; i++) { @@ -1358,9 +1364,9 @@ describe('ETH Messages', () => { await runEthMsg( buildEthMsgReq(buildRandomMsg('eip712', client), 'eip712'), client, - ) - }) + ); + }); } - }) - }) -}) + }); + }); +}); diff --git a/packages/sdk/src/__test__/e2e/ethereum/addresses.test.ts b/packages/sdk/src/__test__/e2e/ethereum/addresses.test.ts index a24d40f6..641d810b 100644 --- a/packages/sdk/src/__test__/e2e/ethereum/addresses.test.ts +++ b/packages/sdk/src/__test__/e2e/ethereum/addresses.test.ts @@ -1,20 +1,20 @@ -import { question } from 'readline-sync' -import { pair } from '../../../api' -import { fetchAddresses } from '../../../api/addresses' -import { setupClient } from '../../utils/setup' +import { question } from 'readline-sync'; +import { pair } from '../../../api'; +import { fetchAddresses } from '../../../api/addresses'; +import { setupClient } from '../../utils/setup'; describe('Ethereum Addresses', () => { test('pair', async () => { - const isPaired = await setupClient() + const isPaired = await setupClient(); if (!isPaired) { - const secret = question('Please enter the pairing secret: ') - await pair(secret.toUpperCase()) + const secret = question('Please enter the pairing secret: '); + await pair(secret.toUpperCase()); } - }) + }); test('Should fetch addressess', async () => { - const addresses = await fetchAddresses() - expect(addresses.length).toBe(10) - expect(addresses.every((addr) => !addr.startsWith('11111'))).toBe(true) - }) -}) + const addresses = await fetchAddresses(); + expect(addresses.length).toBe(10); + expect(addresses.every((addr) => !addr.startsWith('11111'))).toBe(true); + }); +}); diff --git a/packages/sdk/src/__test__/e2e/general.test.ts b/packages/sdk/src/__test__/e2e/general.test.ts index f02f4b73..021d6e1f 100644 --- a/packages/sdk/src/__test__/e2e/general.test.ts +++ b/packages/sdk/src/__test__/e2e/general.test.ts @@ -16,13 +16,13 @@ * the connection you can run this without any `env` params and it will attempt to * pair with a target Lattice. */ -import { createTx } from '@ethereumjs/tx' -import { question } from 'readline-sync' -import { HARDENED_OFFSET } from '../../constants' -import { LatticeResponseCode, ProtocolConstants } from '../../protocol' -import { randomBytes } from '../../util' -import { buildEthSignRequest } from '../utils/builders' -import { getDeviceId } from '../utils/getters' +import { createTx } from '@ethereumjs/tx'; +import { question } from 'readline-sync'; +import { HARDENED_OFFSET } from '../../constants'; +import { LatticeResponseCode, ProtocolConstants } from '../../protocol'; +import { randomBytes } from '../../util'; +import { buildEthSignRequest } from '../utils/builders'; +import { getDeviceId } from '../utils/getters'; import { BTC_COIN, BTC_PURPOSE_P2PKH, @@ -31,131 +31,131 @@ import { BTC_TESTNET_COIN, ETH_COIN, setupTestClient, -} from '../utils/helpers' +} from '../utils/helpers'; -import { setupClient } from '../utils/setup' -import type { Client } from '../../client' +import { setupClient } from '../utils/setup'; +import type { Client } from '../../client'; -const id = getDeviceId() +const id = getDeviceId(); describe('General', () => { - let client: Client + let client: Client; beforeAll(async () => { - client = await setupClient() - }) + client = await setupClient(); + }); it('Should test SDK dehydration/rehydration', async () => { const addrData = { startPath: [BTC_PURPOSE_P2SH_P2WPKH, BTC_COIN, HARDENED_OFFSET, 0, 0], n: 1, - } + }; - const client1 = setupTestClient() - await client1.connect(id) - expect(client1.isPaired).toBeTruthy() - const addrs1 = await client1.getAddresses(addrData) + const client1 = setupTestClient(); + await client1.connect(id); + expect(client1.isPaired).toBeTruthy(); + const addrs1 = await client1.getAddresses(addrData); - const stateData = client1.getStateData() + const stateData = client1.getStateData(); - const client2 = setupTestClient(null, stateData) - await client2.connect(id) - expect(client2.isPaired).toBeTruthy() - const addrs2 = await client2.getAddresses(addrData) + const client2 = setupTestClient(null, stateData); + await client2.connect(id); + expect(client2.isPaired).toBeTruthy(); + const addrs2 = await client2.getAddresses(addrData); - expect(addrs1).toEqual(addrs2) - }) + expect(addrs1).toEqual(addrs2); + }); it('Should get addresses', async () => { - await client.connect(id) - const fwConstants = client.getFwConstants() + await client.connect(id); + const fwConstants = client.getFwConstants(); const addrData = { startPath: [BTC_PURPOSE_P2SH_P2WPKH, BTC_COIN, HARDENED_OFFSET, 0, 0], n: 5, - } - let addrs: string[] | undefined + }; + let addrs: string[] | undefined; // Bitcoin addresses // NOTE: The format of address will be based on the user's Lattice settings // By default, this will be P2SH(P2WPKH), i.e. addresses that start with `3` - addrs = (await client.getAddresses(addrData)) as string[] - expect(addrs.length).toEqual(5) - expect(addrs[0]?.[0]).toEqual('3') + addrs = (await client.getAddresses(addrData)) as string[]; + expect(addrs.length).toEqual(5); + expect(addrs[0]?.[0]).toEqual('3'); // Ethereum addresses - addrData.startPath[0] = BTC_PURPOSE_P2PKH - addrData.startPath[1] = ETH_COIN - addrData.n = 1 - addrs = (await client.getAddresses(addrData)) as string[] - expect(addrs.length).toEqual(1) - expect(addrs[0]?.slice(0, 2)).toEqual('0x') + addrData.startPath[0] = BTC_PURPOSE_P2PKH; + addrData.startPath[1] = ETH_COIN; + addrData.n = 1; + addrs = (await client.getAddresses(addrData)) as string[]; + expect(addrs.length).toEqual(1); + expect(addrs[0]?.slice(0, 2)).toEqual('0x'); // If firmware supports it, try shorter paths if (fwConstants.flexibleAddrPaths) { const flexData = { startPath: [BTC_PURPOSE_P2PKH, ETH_COIN, HARDENED_OFFSET, 0], n: 1, - } - addrs = (await client.getAddresses(flexData)) as string[] - expect(addrs.length).toEqual(1) - expect(addrs[0]?.slice(0, 2)).toEqual('0x') + }; + addrs = (await client.getAddresses(flexData)) as string[]; + expect(addrs.length).toEqual(1); + expect(addrs[0]?.slice(0, 2)).toEqual('0x'); } // Should fail for non-EVM purpose and non-matching coin_type - addrData.n = 1 + addrData.n = 1; try { - addrData.startPath[0] = BTC_PURPOSE_P2WPKH - await client.getAddresses(addrData) - throw new Error(null) + addrData.startPath[0] = BTC_PURPOSE_P2WPKH; + await client.getAddresses(addrData); + throw new Error(null); } catch (err: any) { - expect(err.message).not.toEqual(null) + expect(err.message).not.toEqual(null); } // Switch to BTC coin. Should work now. - addrData.startPath[1] = BTC_COIN + addrData.startPath[1] = BTC_COIN; // Bech32 - addrs = (await client.getAddresses(addrData)) as string[] - expect(addrs.length).toEqual(1) - expect(addrs[0]?.slice(0, 3)).to.be.oneOf(['bc1']) - addrData.startPath[0] = BTC_PURPOSE_P2SH_P2WPKH - addrData.n = 5 + addrs = (await client.getAddresses(addrData)) as string[]; + expect(addrs.length).toEqual(1); + expect(addrs[0]?.slice(0, 3)).to.be.oneOf(['bc1']); + addrData.startPath[0] = BTC_PURPOSE_P2SH_P2WPKH; + addrData.n = 5; - addrData.startPath[4] = 1000000 - addrData.n = 3 - addrs = (await client.getAddresses(addrData)) as string[] - expect(addrs.length).toEqual(addrData.n) - addrData.startPath[4] = 0 - addrData.n = 1 + addrData.startPath[4] = 1000000; + addrData.n = 3; + addrs = (await client.getAddresses(addrData)) as string[]; + expect(addrs.length).toEqual(addrData.n); + addrData.startPath[4] = 0; + addrData.n = 1; // Unsupported purpose (m//) - addrData.startPath[0] = 0 // Purpose 0 -- undefined + addrData.startPath[0] = 0; // Purpose 0 -- undefined try { - addrs = (await client.getAddresses(addrData)) as string[] + addrs = (await client.getAddresses(addrData)) as string[]; } catch (err: any) { - expect(err.message).not.toEqual(null) + expect(err.message).not.toEqual(null); } - addrData.startPath[0] = BTC_PURPOSE_P2SH_P2WPKH + addrData.startPath[0] = BTC_PURPOSE_P2SH_P2WPKH; // Unsupported currency - addrData.startPath[1] = HARDENED_OFFSET + 5 // 5' currency - aka unknown + addrData.startPath[1] = HARDENED_OFFSET + 5; // 5' currency - aka unknown try { - addrs = (await client.getAddresses(addrData)) as string[] - throw new Error(null) + addrs = (await client.getAddresses(addrData)) as string[]; + throw new Error(null); } catch (err: any) { - expect(err.message).not.toEqual(null) + expect(err.message).not.toEqual(null); } - addrData.startPath[1] = BTC_COIN + addrData.startPath[1] = BTC_COIN; // Too many addresses (n>10) - addrData.n = 11 + addrData.n = 11; try { - addrs = (await client.getAddresses(addrData)) as string[] - throw new Error(null) + addrs = (await client.getAddresses(addrData)) as string[]; + throw new Error(null); } catch (err: any) { - expect(err.message).not.toEqual(null) + expect(err.message).not.toEqual(null); } - }) + }); describe('Should sign Ethereum transactions', () => { it('should sign Legacy transactions', async () => { - const { req } = await buildEthSignRequest(client) - await client.sign(req) - }) + const { req } = await buildEthSignRequest(client); + await client.sign(req); + }); it('should sign newer transactions', async () => { const { txData, req, common } = await buildEthSignRequest(client, { @@ -166,34 +166,34 @@ describe('General', () => { to: '0xe242e54155b1abc71fc118065270cecaaf8b7768', value: 1000000000000, data: '0x17e914679b7e160613be4f8c2d3203d236286d74eb9192f6d6f71b9118a42bb033ccd8e8', - }) + }); // NOTE: This will display a prehashed payload for bridged general signing // requests because `ethMaxDataSz` represents the `data` field for legacy // requests, but it represents the entire payload for general signing requests. - const tx = createTx(txData, { common }) - req.data.payload = tx.getMessageToSign() - await client.sign(req) - }) + const tx = createTx(txData, { common }); + req.data.payload = tx.getMessageToSign(); + await client.sign(req); + }); it('should sign bad transactions', async (ctx: any) => { if (process.env.CI === '1') { - ctx.skip() - return + ctx.skip(); + return; } const { txData, req, maxDataSz, common } = - await buildEthSignRequest(client) + await buildEthSignRequest(client); await question( 'Please REJECT the next request if the warning screen displays. Press enter to continue.', - ) - txData.data = randomBytes(maxDataSz) - req.data.data = randomBytes(maxDataSz + 1) - const tx = createTx(txData, { common }) - req.data.payload = tx.getMessageToSign() + ); + txData.data = randomBytes(maxDataSz); + req.data.data = randomBytes(maxDataSz + 1); + const tx = createTx(txData, { common }); + req.data.payload = tx.getMessageToSign(); await expect(client.sign(req)).rejects.toThrow( `${ProtocolConstants.responseMsg[LatticeResponseCode.userDeclined]}`, - ) - }) - }) + ); + }); + }); describe('Should sign Bitcoin transactions', () => { it('Should sign legacy Bitcoin inputs', async () => { @@ -224,17 +224,17 @@ describe('General', () => { 1, 0, ], - } + }; const req = { currency: 'BTC' as const, data: txData, - } + }; // Sign a legit tx - const sigResp = await client.sign(req) - expect(sigResp.tx).not.toEqual(null) - expect(sigResp.txHash).not.toEqual(null) - }) + const sigResp = await client.sign(req); + expect(sigResp.tx).not.toEqual(null); + expect(sigResp.txHash).not.toEqual(null); + }); it('Should sign wrapped segwit Bitcoin inputs', async () => { const txData = { @@ -263,16 +263,16 @@ describe('General', () => { 1, 0, ], - } + }; const req = { currency: 'BTC' as const, data: txData, - } + }; // Sign a legit tx - const sigResp = await client.sign(req) - expect(sigResp.tx).not.toEqual(null) - expect(sigResp.txHash).not.toEqual(null) - }) + const sigResp = await client.sign(req); + expect(sigResp.tx).not.toEqual(null); + expect(sigResp.txHash).not.toEqual(null); + }); it('Should sign wrapped segwit Bitcoin inputs to a bech32 address', async () => { const txData = { @@ -301,16 +301,16 @@ describe('General', () => { 1, 0, ], - } + }; const req = { currency: 'BTC' as const, data: txData, - } + }; // Sign a legit tx - const sigResp = await client.sign(req) - expect(sigResp.tx).not.toEqual(null) - expect(sigResp.txHash).not.toEqual(null) - }) + const sigResp = await client.sign(req); + expect(sigResp.tx).not.toEqual(null); + expect(sigResp.txHash).not.toEqual(null); + }); it('Should sign an input from a native segwit account', async () => { const txData = { @@ -340,16 +340,16 @@ describe('General', () => { 1, 0, ], - } + }; const req = { currency: 'BTC' as const, data: txData, - } + }; // Sign a legit tx - const sigResp = await client.sign(req) - expect(sigResp.tx).not.toEqual(null) - expect(sigResp.txHash).not.toEqual(null) - expect(sigResp.changeRecipient?.slice(0, 2)).toEqual('tb') - }) - }) -}) + const sigResp = await client.sign(req); + expect(sigResp.tx).not.toEqual(null); + expect(sigResp.txHash).not.toEqual(null); + expect(sigResp.changeRecipient?.slice(0, 2)).toEqual('tb'); + }); + }); +}); diff --git a/packages/sdk/src/__test__/e2e/kv.test.ts b/packages/sdk/src/__test__/e2e/kv.test.ts index 022e6e5f..73dd85d3 100644 --- a/packages/sdk/src/__test__/e2e/kv.test.ts +++ b/packages/sdk/src/__test__/e2e/kv.test.ts @@ -3,25 +3,25 @@ * between a 64 byte key and a 64 byte value of any type. The main use case for these * at the time of writing is address tags. */ -import { question } from 'readline-sync' -import { HARDENED_OFFSET } from '../../constants' -import { LatticeResponseCode, ProtocolConstants } from '../../protocol' -import { DEFAULT_SIGNER } from '../utils/builders' -import { BTC_PURPOSE_P2PKH, ETH_COIN } from '../utils/helpers' +import { question } from 'readline-sync'; +import { HARDENED_OFFSET } from '../../constants'; +import { LatticeResponseCode, ProtocolConstants } from '../../protocol'; +import { DEFAULT_SIGNER } from '../utils/builders'; +import { BTC_PURPOSE_P2PKH, ETH_COIN } from '../utils/helpers'; -import { setupClient } from '../utils/setup' -import type { Client } from '../../client' -import type { SignRequestParams } from '../../types' +import { setupClient } from '../utils/setup'; +import type { Client } from '../../client'; +import type { SignRequestParams } from '../../types'; // Random address to test the screen with. // IMPORTANT NOTE: For Ethereum addresses you should always add the lower case variety since // requests come in at lower case -const UNISWAP_ADDR = '0x7a250d5630B4cF539739dF2C5dAcb4c659F2488D' -const UNISWAP_TAG = 'Uniswap V2 Router' -const RANDOM_ADDR = '0x30da3d7A865C934b389c919c737510054111AB3A' -const RANDOM_TAG = 'Test Address Name' -let _numStartingRecords = 0 -let _fetchedRecords: unknown[] = [] +const UNISWAP_ADDR = '0x7a250d5630B4cF539739dF2C5dAcb4c659F2488D'; +const UNISWAP_TAG = 'Uniswap V2 Router'; +const RANDOM_ADDR = '0x30da3d7A865C934b389c919c737510054111AB3A'; +const RANDOM_TAG = 'Test Address Name'; +let _numStartingRecords = 0; +let _fetchedRecords: unknown[] = []; const ETH_REQ = { currency: 'ETH', data: { @@ -34,124 +34,124 @@ const ETH_REQ = { data: null, chainId: 4, }, -} +}; describe('key-value', () => { - let client: Client + let client: Client; beforeAll(async () => { - client = await setupClient() - }) + client = await setupClient(); + }); it('Should ask if the user wants to reset state', async () => { - let answer = 'Y' + let answer = 'Y'; if (process.env.CI !== '1') { answer = question( 'Do you want to clear all kv records and start anew? (Y/N) ', - ) + ); } else { - answer = 'Y' + answer = 'Y'; } if (answer.toUpperCase() === 'Y') { - const batchSize = 10 - let lastTotal: number | null = null - let iterations = 0 + const batchSize = 10; + let lastTotal: number | null = null; + let iterations = 0; while (iterations < 100) { - iterations += 1 - const data = await client.getKvRecords({ n: batchSize, start: 0 }) - console.log('[getKvRecords] data:', JSON.stringify(data, null, 2)) + iterations += 1; + const data = await client.getKvRecords({ n: batchSize, start: 0 }); + console.log('[getKvRecords] data:', JSON.stringify(data, null, 2)); - const { records = [], total = 0 } = data + const { records = [], total = 0 } = data; if (!records.length || total === 0) { - break + break; } if (lastTotal !== null && total >= lastTotal) { console.warn( '[kv.test] KV cleanup halted to avoid infinite loop (no progress detected).', - ) - break + ); + break; } const ids = records .slice(0, batchSize) .map((record: any) => (record?.id ?? '').toString()) - .filter((id: string) => id.length > 0) + .filter((id: string) => id.length > 0); if (!ids.length) { - break + break; } - await client.removeKvRecords({ type: 0, ids }) + await client.removeKvRecords({ type: 0, ids }); if (total <= ids.length) { - break + break; } - lastTotal = total + lastTotal = total; } } - }) + }); it('Should make a request to an unknown address', async () => { await client.sign(ETH_REQ as unknown as SignRequestParams).catch((err) => { expect(err.message).toContain( ProtocolConstants.responseMsg[LatticeResponseCode.userDeclined], - ) - }) - }) + ); + }); + }); it('Should get the initial set of records', async () => { - const resp = await client.getKvRecords({ n: 2, start: 0 }) - _numStartingRecords = resp.total - }) + const resp = await client.getKvRecords({ n: 2, start: 0 }); + _numStartingRecords = resp.total; + }); it('Should add some key value records', async () => { const records = { [UNISWAP_ADDR]: UNISWAP_TAG, [RANDOM_ADDR]: RANDOM_TAG, - } - await client.addKvRecords({ records, caseSensitive: false, type: 0 }) - }) + }; + await client.addKvRecords({ records, caseSensitive: false, type: 0 }); + }); it('Should fail to add records with unicode characters', async () => { - const badKey = { '0x🔥🦍': 'Muh name' } - const badVal = { UNISWAP_ADDR: 'val🔥🦍' } + const badKey = { '0x🔥🦍': 'Muh name' }; + const badVal = { UNISWAP_ADDR: 'val🔥🦍' }; await expect(client.addKvRecords({ records: badKey })).rejects.toThrow( 'Unicode characters are not supported.', - ) + ); await expect(client.addKvRecords({ records: badVal })).rejects.toThrow( 'Unicode characters are not supported.', - ) - }) + ); + }); it('Should fail to add zero length keys and values', async () => { - const badKey = { '': 'Muh name' } - const badVal = { UNISWAP_ADDR: '' } + const badKey = { '': 'Muh name' }; + const badVal = { UNISWAP_ADDR: '' }; await expect(client.addKvRecords({ records: badKey })).rejects.toThrow( 'Keys and values must be >0 characters.', - ) + ); await expect(client.addKvRecords({ records: badVal })).rejects.toThrow( 'Keys and values must be >0 characters.', - ) - }) + ); + }); it('Should fetch the newly created records', async () => { const opts = { n: 2, start: _numStartingRecords, - } - const resp = await client.getKvRecords(opts) - const { records, total, fetched } = resp - _fetchedRecords = records - expect(total).toEqual(fetched + _numStartingRecords) - expect(records.length).toEqual(fetched) - expect(records.length).toEqual(2) - }) + }; + const resp = await client.getKvRecords(opts); + const { records, total, fetched } = resp; + _fetchedRecords = records; + expect(total).toEqual(fetched + _numStartingRecords); + expect(records.length).toEqual(fetched); + expect(records.length).toEqual(2); + }); it('Should make a request to an address which is now known', async () => { - await client.sign(ETH_REQ as unknown as SignRequestParams) - }) + await client.sign(ETH_REQ as unknown as SignRequestParams); + }); it('Should make an EIP712 request that uses the record', async () => { const msg = { @@ -174,7 +174,7 @@ describe('key-value', () => { owner: RANDOM_ADDR, ownerAddr: RANDOM_ADDR, }, - } + }; const req = { currency: 'ETH_MSG', data: { @@ -182,86 +182,86 @@ describe('key-value', () => { protocol: 'eip712', payload: msg, }, - } - await client.sign(req as unknown as SignRequestParams) - }) + }; + await client.sign(req as unknown as SignRequestParams); + }); it('Should make a request with calldata', async () => { // TODO: Add decoder data - const req = JSON.parse(JSON.stringify(ETH_REQ)) - req.data.data = `0x23b872dd00000000000000000000000057974eb88e50cc61049b44e43e90d3bc40fa61c0000000000000000000000000${RANDOM_ADDR.slice(2)}000000000000000000000000000000000000000000000000000000000000270f` - await client.sign(req) - }) + const req = JSON.parse(JSON.stringify(ETH_REQ)); + req.data.data = `0x23b872dd00000000000000000000000057974eb88e50cc61049b44e43e90d3bc40fa61c0000000000000000000000000${RANDOM_ADDR.slice(2)}000000000000000000000000000000000000000000000000000000000000270f`; + await client.sign(req); + }); it('Should remove key value records', async () => { - const idsToRemove: any[] = [] + const idsToRemove: any[] = []; _fetchedRecords.forEach((r: any) => { - idsToRemove.push(r.id) - }) - await client.removeKvRecords({ ids: idsToRemove }) - }) + idsToRemove.push(r.id); + }); + await client.removeKvRecords({ ids: idsToRemove }); + }); it('Should confirm the records we recently added are removed', async () => { const opts = { n: 1, start: _numStartingRecords, - } - const resp = await client.getKvRecords(opts) - const { records, total, fetched } = resp - expect(total).toEqual(_numStartingRecords) - expect(fetched).toEqual(0) - expect(records.length).toEqual(0) - }) + }; + const resp = await client.getKvRecords(opts); + const { records, total, fetched } = resp; + expect(total).toEqual(_numStartingRecords); + expect(fetched).toEqual(0); + expect(records.length).toEqual(0); + }); it('Should add the same record with case sensitivity', async () => { const records = { [RANDOM_ADDR]: 'Test Address Name', - } + }; await client.addKvRecords({ records, caseSensitive: true, type: 0, - }) - }) + }); + }); it('Should make another request to make sure case sensitivity is enforced', async () => { await client.sign(ETH_REQ as unknown as SignRequestParams).catch((err) => { expect(err.message).toContain( ProtocolConstants.responseMsg[LatticeResponseCode.userDeclined], - ) - }) - }) + ); + }); + }); it('Should get the id of the newly added record', async () => { const opts = { n: 1, start: _numStartingRecords, - } - const resp: any = await client.getKvRecords(opts) - const { records, total, fetched } = resp - expect(total).toEqual(_numStartingRecords + 1) - expect(fetched).toEqual(1) - expect(records.length).toEqual(1) - _fetchedRecords = records - }) + }; + const resp: any = await client.getKvRecords(opts); + const { records, total, fetched } = resp; + expect(total).toEqual(_numStartingRecords + 1); + expect(fetched).toEqual(1); + expect(records.length).toEqual(1); + _fetchedRecords = records; + }); it('Should remove the new record', async () => { - const idsToRemove: any = [] + const idsToRemove: any = []; _fetchedRecords.forEach((r: any) => { - idsToRemove.push(r.id) - }) - await client.removeKvRecords({ ids: idsToRemove }) - }) + idsToRemove.push(r.id); + }); + await client.removeKvRecords({ ids: idsToRemove }); + }); it('Should confirm there are no new records', async () => { const opts = { n: 1, start: _numStartingRecords, - } - const resp: any = await client.getKvRecords(opts) - const { records, total, fetched } = resp - expect(total).toEqual(_numStartingRecords) - expect(fetched).toEqual(0) - expect(records.length).toEqual(0) - }) -}) + }; + const resp: any = await client.getKvRecords(opts); + const { records, total, fetched } = resp; + expect(total).toEqual(_numStartingRecords); + expect(fetched).toEqual(0); + expect(records.length).toEqual(0); + }); +}); diff --git a/packages/sdk/src/__test__/e2e/non-exportable.test.ts b/packages/sdk/src/__test__/e2e/non-exportable.test.ts index 9ed9e6fd..7edd6d13 100644 --- a/packages/sdk/src/__test__/e2e/non-exportable.test.ts +++ b/packages/sdk/src/__test__/e2e/non-exportable.test.ts @@ -19,49 +19,49 @@ * make sure you set `CONFIG:DEBUG>:ENABLE_A90=0` or else you will probably brick * your A90 chip. */ -import { Common, Hardfork, Mainnet } from '@ethereumjs/common' -import { RLP } from '@ethereumjs/rlp' -import { createTx } from '@ethereumjs/tx' -import { question } from 'readline-sync' -import { Constants } from '../..' -import { DEFAULT_SIGNER } from '../utils/builders' -import { getSigStr, validateSig } from '../utils/helpers' -import { setupClient } from '../utils/setup' -import type { Client } from '../../client' -import type { SignRequestParams } from '../../types' +import { Common, Hardfork, Mainnet } from '@ethereumjs/common'; +import { RLP } from '@ethereumjs/rlp'; +import { createTx } from '@ethereumjs/tx'; +import { question } from 'readline-sync'; +import { Constants } from '../..'; +import { DEFAULT_SIGNER } from '../utils/builders'; +import { getSigStr, validateSig } from '../utils/helpers'; +import { setupClient } from '../utils/setup'; +import type { Client } from '../../client'; +import type { SignRequestParams } from '../../types'; -let runTests = true +let runTests = true; describe('Non-Exportable Seed', () => { - let client: Client + let client: Client; beforeAll(async () => { - client = await setupClient() - }) + client = await setupClient(); + }); describe('Setup', () => { it('Should ask if the user wants to test a card with a non-exportable seed', async (ctx: any) => { if (process.env.CI === '1') { - runTests = false - ctx.skip() - return + runTests = false; + ctx.skip(); + return; } // NOTE: non-exportable seeds were deprecated from the normal setup pathway in firmware v0.12.0 const result = await question( 'Do you have a non-exportable SafeCard seed loaded and wish to continue? (Y/N) ', - ) + ); if (result.toLowerCase() !== 'y') { - runTests = false + runTests = false; } - }) - }) + }); + }); describe('Test non-exportable seed on SafeCard', () => { beforeEach((ctx: any) => { if (!runTests) { - ctx.skip('Skipping tests due to lack of non-exportable seed SafeCard.') + ctx.skip('Skipping tests due to lack of non-exportable seed SafeCard.'); } - }) + }); it('Should test that ETH transaction sigs differ and validate on secp256k1', async () => { // Test ETH transactions const tx = createTx( @@ -81,7 +81,7 @@ describe('Non-Exportable Seed', () => { hardfork: Hardfork.London, }), }, - ) + ); const txReq = { data: { signerPath: DEFAULT_SIGNER, @@ -90,48 +90,48 @@ describe('Non-Exportable Seed', () => { hashType: Constants.SIGNING.HASHES.KECCAK256, encodingType: Constants.SIGNING.ENCODINGS.EVM, }, - } + }; // Validate that tx sigs are non-uniform - const unsignedMsg = tx.getMessageToSign() + const unsignedMsg = tx.getMessageToSign(); const unsigned = Array.isArray(unsignedMsg) ? RLP.encode(unsignedMsg) - : unsignedMsg - const tx1Resp = await client.sign(txReq) - validateSig(tx1Resp, unsigned) - const tx2Resp = await client.sign(txReq) - validateSig(tx2Resp, unsigned) - const tx3Resp = await client.sign(txReq) - validateSig(tx3Resp, unsigned) - const tx4Resp = await client.sign(txReq) - validateSig(tx4Resp, unsigned) - const tx5Resp = await client.sign(txReq) - validateSig(tx5Resp, unsigned) + : unsignedMsg; + const tx1Resp = await client.sign(txReq); + validateSig(tx1Resp, unsigned); + const tx2Resp = await client.sign(txReq); + validateSig(tx2Resp, unsigned); + const tx3Resp = await client.sign(txReq); + validateSig(tx3Resp, unsigned); + const tx4Resp = await client.sign(txReq); + validateSig(tx4Resp, unsigned); + const tx5Resp = await client.sign(txReq); + validateSig(tx5Resp, unsigned); // Check sig 1 - expect(getSigStr(tx1Resp, tx)).not.toEqual(getSigStr(tx2Resp, tx)) - expect(getSigStr(tx1Resp, tx)).not.toEqual(getSigStr(tx3Resp, tx)) - expect(getSigStr(tx1Resp, tx)).not.toEqual(getSigStr(tx4Resp, tx)) - expect(getSigStr(tx1Resp, tx)).not.toEqual(getSigStr(tx5Resp, tx)) + expect(getSigStr(tx1Resp, tx)).not.toEqual(getSigStr(tx2Resp, tx)); + expect(getSigStr(tx1Resp, tx)).not.toEqual(getSigStr(tx3Resp, tx)); + expect(getSigStr(tx1Resp, tx)).not.toEqual(getSigStr(tx4Resp, tx)); + expect(getSigStr(tx1Resp, tx)).not.toEqual(getSigStr(tx5Resp, tx)); // Check sig 2 - expect(getSigStr(tx2Resp, tx)).not.toEqual(getSigStr(tx1Resp, tx)) - expect(getSigStr(tx2Resp, tx)).not.toEqual(getSigStr(tx3Resp, tx)) - expect(getSigStr(tx2Resp, tx)).not.toEqual(getSigStr(tx4Resp, tx)) - expect(getSigStr(tx2Resp, tx)).not.toEqual(getSigStr(tx5Resp, tx)) + expect(getSigStr(tx2Resp, tx)).not.toEqual(getSigStr(tx1Resp, tx)); + expect(getSigStr(tx2Resp, tx)).not.toEqual(getSigStr(tx3Resp, tx)); + expect(getSigStr(tx2Resp, tx)).not.toEqual(getSigStr(tx4Resp, tx)); + expect(getSigStr(tx2Resp, tx)).not.toEqual(getSigStr(tx5Resp, tx)); // Check sig 3 - expect(getSigStr(tx3Resp, tx)).not.toEqual(getSigStr(tx1Resp, tx)) - expect(getSigStr(tx3Resp, tx)).not.toEqual(getSigStr(tx2Resp, tx)) - expect(getSigStr(tx3Resp, tx)).not.toEqual(getSigStr(tx4Resp, tx)) - expect(getSigStr(tx3Resp, tx)).not.toEqual(getSigStr(tx5Resp, tx)) + expect(getSigStr(tx3Resp, tx)).not.toEqual(getSigStr(tx1Resp, tx)); + expect(getSigStr(tx3Resp, tx)).not.toEqual(getSigStr(tx2Resp, tx)); + expect(getSigStr(tx3Resp, tx)).not.toEqual(getSigStr(tx4Resp, tx)); + expect(getSigStr(tx3Resp, tx)).not.toEqual(getSigStr(tx5Resp, tx)); // Check sig 4 - expect(getSigStr(tx4Resp, tx)).not.toEqual(getSigStr(tx1Resp, tx)) - expect(getSigStr(tx4Resp, tx)).not.toEqual(getSigStr(tx2Resp, tx)) - expect(getSigStr(tx4Resp, tx)).not.toEqual(getSigStr(tx3Resp, tx)) - expect(getSigStr(tx4Resp, tx)).not.toEqual(getSigStr(tx5Resp, tx)) + expect(getSigStr(tx4Resp, tx)).not.toEqual(getSigStr(tx1Resp, tx)); + expect(getSigStr(tx4Resp, tx)).not.toEqual(getSigStr(tx2Resp, tx)); + expect(getSigStr(tx4Resp, tx)).not.toEqual(getSigStr(tx3Resp, tx)); + expect(getSigStr(tx4Resp, tx)).not.toEqual(getSigStr(tx5Resp, tx)); // Check sig 5 - expect(getSigStr(tx5Resp, tx)).not.toEqual(getSigStr(tx1Resp, tx)) - expect(getSigStr(tx5Resp, tx)).not.toEqual(getSigStr(tx2Resp, tx)) - expect(getSigStr(tx5Resp, tx)).not.toEqual(getSigStr(tx3Resp, tx)) - expect(getSigStr(tx5Resp, tx)).not.toEqual(getSigStr(tx4Resp, tx)) - }) + expect(getSigStr(tx5Resp, tx)).not.toEqual(getSigStr(tx1Resp, tx)); + expect(getSigStr(tx5Resp, tx)).not.toEqual(getSigStr(tx2Resp, tx)); + expect(getSigStr(tx5Resp, tx)).not.toEqual(getSigStr(tx3Resp, tx)); + expect(getSigStr(tx5Resp, tx)).not.toEqual(getSigStr(tx4Resp, tx)); + }); it('Should test that ETH message sigs differ and validate on secp256k1', async () => { // Validate that signPersonal message sigs are non-uniform @@ -144,39 +144,49 @@ describe('Non-Exportable Seed', () => { curveType: Constants.SIGNING.CURVES.SECP256K1, hashType: Constants.SIGNING.HASHES.KECCAK256, }, - } + }; // NOTE: This uses the legacy signing pathway, which validates the signature // Once we move this to generic signing, we will need to validate these. - const msg1Resp = await client.sign(msgReq as unknown as SignRequestParams) - const msg2Resp = await client.sign(msgReq as unknown as SignRequestParams) - const msg3Resp = await client.sign(msgReq as unknown as SignRequestParams) - const msg4Resp = await client.sign(msgReq as unknown as SignRequestParams) - const msg5Resp = await client.sign(msgReq as unknown as SignRequestParams) + const msg1Resp = await client.sign( + msgReq as unknown as SignRequestParams, + ); + const msg2Resp = await client.sign( + msgReq as unknown as SignRequestParams, + ); + const msg3Resp = await client.sign( + msgReq as unknown as SignRequestParams, + ); + const msg4Resp = await client.sign( + msgReq as unknown as SignRequestParams, + ); + const msg5Resp = await client.sign( + msgReq as unknown as SignRequestParams, + ); // Check sig 1 - expect(getSigStr(msg1Resp)).not.toEqual(getSigStr(msg2Resp)) - expect(getSigStr(msg1Resp)).not.toEqual(getSigStr(msg3Resp)) - expect(getSigStr(msg1Resp)).not.toEqual(getSigStr(msg4Resp)) - expect(getSigStr(msg1Resp)).not.toEqual(getSigStr(msg5Resp)) + expect(getSigStr(msg1Resp)).not.toEqual(getSigStr(msg2Resp)); + expect(getSigStr(msg1Resp)).not.toEqual(getSigStr(msg3Resp)); + expect(getSigStr(msg1Resp)).not.toEqual(getSigStr(msg4Resp)); + expect(getSigStr(msg1Resp)).not.toEqual(getSigStr(msg5Resp)); // Check sig 2 - expect(getSigStr(msg2Resp)).not.toEqual(getSigStr(msg1Resp)) - expect(getSigStr(msg2Resp)).not.toEqual(getSigStr(msg3Resp)) - expect(getSigStr(msg2Resp)).not.toEqual(getSigStr(msg4Resp)) - expect(getSigStr(msg2Resp)).not.toEqual(getSigStr(msg5Resp)) + expect(getSigStr(msg2Resp)).not.toEqual(getSigStr(msg1Resp)); + expect(getSigStr(msg2Resp)).not.toEqual(getSigStr(msg3Resp)); + expect(getSigStr(msg2Resp)).not.toEqual(getSigStr(msg4Resp)); + expect(getSigStr(msg2Resp)).not.toEqual(getSigStr(msg5Resp)); // Check sig 3 - expect(getSigStr(msg3Resp)).not.toEqual(getSigStr(msg1Resp)) - expect(getSigStr(msg3Resp)).not.toEqual(getSigStr(msg2Resp)) - expect(getSigStr(msg3Resp)).not.toEqual(getSigStr(msg4Resp)) - expect(getSigStr(msg3Resp)).not.toEqual(getSigStr(msg5Resp)) + expect(getSigStr(msg3Resp)).not.toEqual(getSigStr(msg1Resp)); + expect(getSigStr(msg3Resp)).not.toEqual(getSigStr(msg2Resp)); + expect(getSigStr(msg3Resp)).not.toEqual(getSigStr(msg4Resp)); + expect(getSigStr(msg3Resp)).not.toEqual(getSigStr(msg5Resp)); // Check sig 4 - expect(getSigStr(msg4Resp)).not.toEqual(getSigStr(msg1Resp)) - expect(getSigStr(msg4Resp)).not.toEqual(getSigStr(msg2Resp)) - expect(getSigStr(msg4Resp)).not.toEqual(getSigStr(msg3Resp)) - expect(getSigStr(msg4Resp)).not.toEqual(getSigStr(msg5Resp)) + expect(getSigStr(msg4Resp)).not.toEqual(getSigStr(msg1Resp)); + expect(getSigStr(msg4Resp)).not.toEqual(getSigStr(msg2Resp)); + expect(getSigStr(msg4Resp)).not.toEqual(getSigStr(msg3Resp)); + expect(getSigStr(msg4Resp)).not.toEqual(getSigStr(msg5Resp)); // Check sig 5 - expect(getSigStr(msg5Resp)).not.toEqual(getSigStr(msg1Resp)) - expect(getSigStr(msg5Resp)).not.toEqual(getSigStr(msg2Resp)) - expect(getSigStr(msg5Resp)).not.toEqual(getSigStr(msg3Resp)) - expect(getSigStr(msg5Resp)).not.toEqual(getSigStr(msg4Resp)) - }) - }) -}) + expect(getSigStr(msg5Resp)).not.toEqual(getSigStr(msg1Resp)); + expect(getSigStr(msg5Resp)).not.toEqual(getSigStr(msg2Resp)); + expect(getSigStr(msg5Resp)).not.toEqual(getSigStr(msg3Resp)); + expect(getSigStr(msg5Resp)).not.toEqual(getSigStr(msg4Resp)); + }); + }); +}); diff --git a/packages/sdk/src/__test__/e2e/signing/bls.test.ts b/packages/sdk/src/__test__/e2e/signing/bls.test.ts index f66ffdd8..e37edf40 100644 --- a/packages/sdk/src/__test__/e2e/signing/bls.test.ts +++ b/packages/sdk/src/__test__/e2e/signing/bls.test.ts @@ -20,73 +20,73 @@ import { decrypt as decryptKeystore, isValidKeystore, verifyPassword, -} from '@chainsafe/bls-keystore' -import { getPublicKey, sign } from '@noble/bls12-381' -import { deriveSeedTree } from 'bls12-381-keygen' -import { question } from 'readline-sync' - -import type { Client } from '../../../client' -import { Constants } from '../../../index' -import { getPathStr } from '../../../shared/utilities' -import { getEncPw } from '../../utils/getters' -import { buildPath } from '../../utils/helpers' -import { setupClient } from '../../utils/setup' -import { TEST_SEED } from '../../utils/testConstants' - -let client: Client -let encPw: string | undefined -let supportsBLS: boolean | undefined -const DEPOSIT_PATH = [12381, 3600, 0, 0, 0] -const WITHDRAWAL_PATH = [12381, 3600, 0, 0] +} from '@chainsafe/bls-keystore'; +import { getPublicKey, sign } from '@noble/bls12-381'; +import { deriveSeedTree } from 'bls12-381-keygen'; +import { question } from 'readline-sync'; + +import type { Client } from '../../../client'; +import { Constants } from '../../../index'; +import { getPathStr } from '../../../shared/utilities'; +import { getEncPw } from '../../utils/getters'; +import { buildPath } from '../../utils/helpers'; +import { setupClient } from '../../utils/setup'; +import { TEST_SEED } from '../../utils/testConstants'; + +let client: Client; +let encPw: string | undefined; +let supportsBLS: boolean | undefined; +const DEPOSIT_PATH = [12381, 3600, 0, 0, 0]; +const WITHDRAWAL_PATH = [12381, 3600, 0, 0]; // Number of signers to test for each of deposit and withdrawal paths -const N_TEST_SIGS = 5 -const KNOWN_SEED = TEST_SEED +const N_TEST_SIGS = 5; +const KNOWN_SEED = TEST_SEED; describe('[BLS keys]', () => { beforeAll(async () => { - client = await setupClient() + client = await setupClient(); if (process.env.CI) { - encPw = process.env.ENC_PW + encPw = process.env.ENC_PW; } else { - encPw = getEncPw() + encPw = getEncPw(); if (!encPw) { - encPw = await question('Enter your Lattice encryption password: ') + encPw = await question('Enter your Lattice encryption password: '); } } // Check if firmware supports BLS (requires >= 0.17.0) - const fwVersion = client.getFwVersion() + const fwVersion = client.getFwVersion(); const versionStr = fwVersion ? `${fwVersion.major}.${fwVersion.minor}.${fwVersion.fix}` - : 'unknown' + : 'unknown'; - console.log(`\n[BLS Test] Firmware version: ${versionStr}`) - console.log('[BLS Test] Raw fwVersion:', fwVersion) + console.log(`\n[BLS Test] Firmware version: ${versionStr}`); + console.log('[BLS Test] Raw fwVersion:', fwVersion); - const fwConstants = client.getFwConstants() - console.log('[BLS Test] getAddressFlags:', fwConstants?.getAddressFlags) + const fwConstants = client.getFwConstants(); + console.log('[BLS Test] getAddressFlags:', fwConstants?.getAddressFlags); console.log( '[BLS Test] BLS12_381_G1_PUB constant:', Constants.GET_ADDR_FLAGS.BLS12_381_G1_PUB, - ) + ); supportsBLS = fwConstants?.getAddressFlags?.includes( Constants.GET_ADDR_FLAGS.BLS12_381_G1_PUB as number, - ) + ); - console.log(`[BLS Test] supportsBLS: ${supportsBLS}\n`) + console.log(`[BLS Test] supportsBLS: ${supportsBLS}\n`); if (!supportsBLS) { console.warn( `\nSkipping BLS tests: Firmware version ${versionStr} does not support BLS operations.\nBLS support requires firmware version >= 0.17.0\n`, - ) + ); } - }) + }); it('Should validate exported EIP2335 keystores', async (ctx) => { if (!supportsBLS) { - ctx.skip() - return + ctx.skip(); + return; } const req = { schema: Constants.ENC_DATA.SCHEMAS.BLS_KEYSTORE_EIP2335_PBKDF_V4, @@ -94,49 +94,49 @@ describe('[BLS keys]', () => { path: WITHDRAWAL_PATH, c: 999, // if this is not specified, the default value will be used }, - } - let encData: unknown + }; + let encData: unknown; // Test custom iteration count (c) - encData = await client.fetchEncryptedData(req) - await validateExportedKeystore(KNOWN_SEED, req.params.path, encPw, encData) + encData = await client.fetchEncryptedData(req); + await validateExportedKeystore(KNOWN_SEED, req.params.path, encPw, encData); // Test different paths - req.params.path = DEPOSIT_PATH - encData = await client.fetchEncryptedData(req) - await validateExportedKeystore(KNOWN_SEED, req.params.path, encPw, encData) - req.params.path[4] = 1847 - encData = await client.fetchEncryptedData(req) - await validateExportedKeystore(KNOWN_SEED, req.params.path, encPw, encData) + req.params.path = DEPOSIT_PATH; + encData = await client.fetchEncryptedData(req); + await validateExportedKeystore(KNOWN_SEED, req.params.path, encPw, encData); + req.params.path[4] = 1847; + encData = await client.fetchEncryptedData(req); + await validateExportedKeystore(KNOWN_SEED, req.params.path, encPw, encData); // Test default values - req.params.path = DEPOSIT_PATH - req.params.c = undefined - encData = await client.fetchEncryptedData(req) - await validateExportedKeystore(KNOWN_SEED, req.params.path, encPw, encData) - }) + req.params.path = DEPOSIT_PATH; + req.params.c = undefined; + encData = await client.fetchEncryptedData(req); + await validateExportedKeystore(KNOWN_SEED, req.params.path, encPw, encData); + }); for (let i = 0; i < N_TEST_SIGS; i++) { describe(`[Validate Derived Signature #${i + 1}/${N_TEST_SIGS}]`, () => { it(`Should validate derivation and signing at deposit index #${i + 1}`, async (ctx) => { if (!supportsBLS) { - ctx.skip() - return + ctx.skip(); + return; } - const depositPath = JSON.parse(JSON.stringify(DEPOSIT_PATH)) - depositPath[2] = i - await testBLSDerivationAndSig(KNOWN_SEED, depositPath) - }) + const depositPath = JSON.parse(JSON.stringify(DEPOSIT_PATH)); + depositPath[2] = i; + await testBLSDerivationAndSig(KNOWN_SEED, depositPath); + }); it(`Should validate derivation and signing at withdrawal index #${i + 1}`, async (ctx) => { if (!supportsBLS) { - ctx.skip() - return + ctx.skip(); + return; } - const withdrawalPath = JSON.parse(JSON.stringify(WITHDRAWAL_PATH)) - withdrawalPath[2] = i - await testBLSDerivationAndSig(KNOWN_SEED, withdrawalPath) - }) - }) + const withdrawalPath = JSON.parse(JSON.stringify(WITHDRAWAL_PATH)); + withdrawalPath[2] = i; + await testBLSDerivationAndSig(KNOWN_SEED, withdrawalPath); + }); + }); } -}) +}); //========================================================= // INTERNAL HELPERS @@ -146,8 +146,8 @@ async function getBLSPub(startPath: number[]) { startPath, n: 1, flag: Constants.GET_ADDR_FLAGS.BLS12_381_G1_PUB, - } as Parameters[0]) - return pubs[0] + } as Parameters[0]); + return pubs[0]; } async function signBLS(signerPath, message) { @@ -159,70 +159,70 @@ async function signBLS(signerPath, message) { encodingType: Constants.SIGNING.ENCODINGS.NONE, payload: message, }, - } - return await client.sign(signReq) + }; + return await client.sign(signReq); } async function testBLSDerivationAndSig(seed, signerPath) { - const msg = Buffer.from('64726e3da8', 'hex') - const priv = deriveSeedTree(seed, buildPath(signerPath)) - const latticePub = await getBLSPub(signerPath) - const latticeSig = await signBLS(signerPath, msg) - const refPub = getPublicKey(priv) - const refPubStr = Buffer.from(refPub).toString('hex') - const refSig = await sign(msg, priv) - const refSigStr = Buffer.from(refSig).toString('hex') + const msg = Buffer.from('64726e3da8', 'hex'); + const priv = deriveSeedTree(seed, buildPath(signerPath)); + const latticePub = await getBLSPub(signerPath); + const latticeSig = await signBLS(signerPath, msg); + const refPub = getPublicKey(priv); + const refPubStr = Buffer.from(refPub).toString('hex'); + const refSig = await sign(msg, priv); + const refSigStr = Buffer.from(refSig).toString('hex'); expect(latticePub.toString('hex')).to.equal( refPubStr, 'Deposit public key mismatch', - ) + ); expect(latticeSig.pubkey.toString('hex')).to.equal( refPubStr, 'Lattice signature returned wrong pubkey', - ) + ); expect( Buffer.from(latticeSig.sig as unknown as Buffer).toString('hex'), - ).to.equal(refSigStr, 'Signature mismatch') + ).to.equal(refSigStr, 'Signature mismatch'); } async function validateExportedKeystore(seed, path, pw, expKeystoreBuffer) { - const exportedKeystore = JSON.parse(expKeystoreBuffer.toString()) - const priv = deriveSeedTree(seed, buildPath(path)) - const pub = getPublicKey(priv) + const exportedKeystore = JSON.parse(expKeystoreBuffer.toString()); + const priv = deriveSeedTree(seed, buildPath(path)); + const pub = getPublicKey(priv); // Validate the keystore in isolation expect(isValidKeystore(exportedKeystore)).to.equal( true, 'Exported keystore invalid!', - ) - const expPwVerified = await verifyPassword(exportedKeystore, pw) + ); + const expPwVerified = await verifyPassword(exportedKeystore, pw); expect(expPwVerified).to.equal( true, `Password could not be verified in exported keystore. Expected "${pw}"`, - ) - const expDec = await decryptKeystore(exportedKeystore, pw) + ); + const expDec = await decryptKeystore(exportedKeystore, pw); expect(Buffer.from(expDec).toString('hex')).to.equal( Buffer.from(priv).toString('hex'), 'Exported keystore did not properly encrypt key!', - ) + ); expect(exportedKeystore.pubkey).to.equal( Buffer.from(pub).toString('hex'), 'Wrong public key exported from Lattice', - ) + ); // Generate an independent keystore and compare decrypted contents - const genKeystore = await createKeystore(pw, priv, pub, getPathStr(path)) + const genKeystore = await createKeystore(pw, priv, pub, getPathStr(path)); expect(isValidKeystore(genKeystore)).to.equal( true, 'Generated keystore invalid?', - ) - const genPwVerified = await verifyPassword(genKeystore, pw) + ); + const genPwVerified = await verifyPassword(genKeystore, pw); expect(genPwVerified).to.equal( true, 'Password could not be verified in generated keystore?', - ) - const genDec = await decryptKeystore(genKeystore, pw) + ); + const genDec = await decryptKeystore(genKeystore, pw); expect(Buffer.from(expDec).toString('hex')).to.equal( Buffer.from(genDec).toString('hex'), 'Exported encrypted privkey did not match factory test example...', - ) + ); } diff --git a/packages/sdk/src/__test__/e2e/signing/determinism.test.ts b/packages/sdk/src/__test__/e2e/signing/determinism.test.ts index 8f327e9a..064950ea 100644 --- a/packages/sdk/src/__test__/e2e/signing/determinism.test.ts +++ b/packages/sdk/src/__test__/e2e/signing/determinism.test.ts @@ -1,19 +1,19 @@ -import { HARDENED_OFFSET } from '../../../constants' -import type { SignRequestParams, WalletPath } from '../../../types' -import { randomBytes } from '../../../util' +import { HARDENED_OFFSET } from '../../../constants'; +import type { SignRequestParams, WalletPath } from '../../../types'; +import { randomBytes } from '../../../util'; import { DEFAULT_SIGNER, buildMsgReq, buildRandomVectors, buildTx, buildTxReq, -} from '../../utils/builders' +} from '../../utils/builders'; import { deriveAddress, signEip712JS, signPersonalJS, testUniformSigs, -} from '../../utils/determinism' +} from '../../utils/determinism'; /** * REQUIRED TEST MNEMONIC: * These tests require a SafeCard loaded with the standard test mnemonic: @@ -22,26 +22,26 @@ import { * Running with a different mnemonic will cause test failures due to * incorrect address derivations and signature mismatches. */ -import { getDeviceId } from '../../utils/getters' -import { BTC_PURPOSE_P2PKH, ETH_COIN, getSigStr } from '../../utils/helpers' -import { setupClient } from '../../utils/setup' -import { TEST_SEED } from '../../utils/testConstants' -import type { Client } from '../../../client' +import { getDeviceId } from '../../utils/getters'; +import { BTC_PURPOSE_P2PKH, ETH_COIN, getSigStr } from '../../utils/helpers'; +import { setupClient } from '../../utils/setup'; +import { TEST_SEED } from '../../utils/testConstants'; +import type { Client } from '../../../client'; describe('[Determinism]', () => { - let client: Client + let client: Client; beforeAll(async () => { - client = await setupClient() - }) + client = await setupClient(); + }); describe('Setup and validate seed', () => { it('Should re-connect to the Lattice and update the walletUID.', async () => { - expect(getDeviceId()).to.not.equal(null) - await client.connect(getDeviceId()) - expect(client.isPaired).toEqual(true) - expect(!!client.getActiveWallet()).toEqual(true) - }) + expect(getDeviceId()).to.not.equal(null); + await client.connect(getDeviceId()); + expect(client.isPaired).toEqual(true); + expect(!!client.getActiveWallet()).toEqual(true); + }); it('Should validate some Ledger addresses derived from the test seed', async () => { const path0 = [ @@ -50,208 +50,208 @@ describe('[Determinism]', () => { HARDENED_OFFSET, 0, 0, - ] as WalletPath - const addr0 = deriveAddress(TEST_SEED, path0) + ] as WalletPath; + const addr0 = deriveAddress(TEST_SEED, path0); const path1 = [ BTC_PURPOSE_P2PKH, ETH_COIN, HARDENED_OFFSET + 1, 0, 0, - ] as WalletPath - const addr1 = deriveAddress(TEST_SEED, path1) + ] as WalletPath; + const addr1 = deriveAddress(TEST_SEED, path1); const path8 = [ BTC_PURPOSE_P2PKH, ETH_COIN, HARDENED_OFFSET + 8, 0, 0, - ] as WalletPath - const addr8 = deriveAddress(TEST_SEED, path8) + ] as WalletPath; + const addr8 = deriveAddress(TEST_SEED, path8); // Fetch these addresses from the Lattice and validate const req = { currency: 'ETH', startPath: path0, n: 1, - } - const latAddr0 = await client.getAddresses(req) + }; + const latAddr0 = await client.getAddresses(req); expect((latAddr0[0] as string).toLowerCase()).toEqualElseLog( addr0.toLowerCase(), 'Incorrect address 0 fetched. Ensure your SafeCard is loaded with the test mnemonic: "test test test test test test test test test test test junk"', - ) - req.startPath = path1 - const latAddr1 = await client.getAddresses(req) + ); + req.startPath = path1; + const latAddr1 = await client.getAddresses(req); expect((latAddr1[0] as string).toLowerCase()).toEqualElseLog( addr1.toLowerCase(), 'Incorrect address 1 fetched. Ensure your SafeCard is loaded with the test mnemonic: "test test test test test test test test test test test junk"', - ) - req.startPath = path8 - const latAddr8 = await client.getAddresses(req) + ); + req.startPath = path8; + const latAddr8 = await client.getAddresses(req); expect((latAddr8[0] as string).toLowerCase()).toEqualElseLog( addr8.toLowerCase(), 'Incorrect address 8 fetched. Ensure your SafeCard is loaded with the test mnemonic: "test test test test test test test test test test test junk"', - ) - }) - }) + ); + }); + }); describe('Test uniformity of Ethereum transaction sigs', () => { it("Should validate uniformity sigs on m/44'/60'/0'/0/0", async () => { - const tx = buildTx() - const txReq = buildTxReq(tx) - txReq.data.signerPath[2] = HARDENED_OFFSET - await testUniformSigs(txReq, tx, client) - }) + const tx = buildTx(); + const txReq = buildTxReq(tx); + txReq.data.signerPath[2] = HARDENED_OFFSET; + await testUniformSigs(txReq, tx, client); + }); it("Should validate uniformity sigs on m/44'/60'/1'/0/0", async () => { - const tx = buildTx() - const txReq = buildTxReq(tx) - txReq.data.signerPath[2] = HARDENED_OFFSET + 1 - await testUniformSigs(txReq, tx, client) - }) + const tx = buildTx(); + const txReq = buildTxReq(tx); + txReq.data.signerPath[2] = HARDENED_OFFSET + 1; + await testUniformSigs(txReq, tx, client); + }); it("Should validate uniformity sigs on m/44'/60'/8'/0/0", async () => { - const tx = buildTx() - const txReq = buildTxReq(tx) - txReq.data.signerPath[2] = HARDENED_OFFSET + 8 - await testUniformSigs(txReq, tx, client) - }) + const tx = buildTx(); + const txReq = buildTxReq(tx); + txReq.data.signerPath[2] = HARDENED_OFFSET + 8; + await testUniformSigs(txReq, tx, client); + }); it("Should validate uniformity sigs on m/44'/60'/0'/0/0", async () => { - const tx = buildTx(`0x${randomBytes(4000).toString('hex')}`) - const txReq = buildTxReq(tx) - txReq.data.signerPath[2] = HARDENED_OFFSET - await testUniformSigs(txReq, tx, client) - }) + const tx = buildTx(`0x${randomBytes(4000).toString('hex')}`); + const txReq = buildTxReq(tx); + txReq.data.signerPath[2] = HARDENED_OFFSET; + await testUniformSigs(txReq, tx, client); + }); it("Should validate uniformity sigs on m/44'/60'/1'/0/0", async () => { - const tx = buildTx(`0x${randomBytes(4000).toString('hex')}`) - const txReq = buildTxReq(tx) - txReq.data.signerPath[2] = HARDENED_OFFSET + 1 - await testUniformSigs(txReq, tx, client) - }) + const tx = buildTx(`0x${randomBytes(4000).toString('hex')}`); + const txReq = buildTxReq(tx); + txReq.data.signerPath[2] = HARDENED_OFFSET + 1; + await testUniformSigs(txReq, tx, client); + }); it("Should validate uniformity sigs on m/44'/60'/8'/0/0", async () => { - const tx = buildTx(`0x${randomBytes(4000).toString('hex')}`) - const txReq = buildTxReq(tx) - txReq.data.signerPath[2] = HARDENED_OFFSET + 8 - await testUniformSigs(txReq, tx, client) - }) - }) + const tx = buildTx(`0x${randomBytes(4000).toString('hex')}`); + const txReq = buildTxReq(tx); + txReq.data.signerPath[2] = HARDENED_OFFSET + 8; + await testUniformSigs(txReq, tx, client); + }); + }); describe('Compare personal_sign signatures vs Ledger vectors (1)', () => { it('Should validate signature from addr0', async () => { - const msgReq = buildMsgReq() - msgReq.data.signerPath[2] = HARDENED_OFFSET - const res = await client.sign(msgReq as unknown as SignRequestParams) - const sig = getSigStr(res) - const jsSig = signPersonalJS(msgReq.data.payload, msgReq.data.signerPath) + const msgReq = buildMsgReq(); + msgReq.data.signerPath[2] = HARDENED_OFFSET; + const res = await client.sign(msgReq as unknown as SignRequestParams); + const sig = getSigStr(res); + const jsSig = signPersonalJS(msgReq.data.payload, msgReq.data.signerPath); expect(sig).toEqualElseLog( jsSig, 'Lattice sig does not match JS reference', - ) - }) + ); + }); it('Should validate signature from addr1', async () => { - const msgReq = buildMsgReq() - msgReq.data.signerPath[2] = HARDENED_OFFSET + 1 - const res = await client.sign(msgReq as unknown as SignRequestParams) - const sig = getSigStr(res) - const jsSig = signPersonalJS(msgReq.data.payload, msgReq.data.signerPath) + const msgReq = buildMsgReq(); + msgReq.data.signerPath[2] = HARDENED_OFFSET + 1; + const res = await client.sign(msgReq as unknown as SignRequestParams); + const sig = getSigStr(res); + const jsSig = signPersonalJS(msgReq.data.payload, msgReq.data.signerPath); expect(sig).toEqualElseLog( jsSig, 'Lattice sig does not match JS reference', - ) - }) + ); + }); it('Should validate signature from addr8', async () => { - const msgReq = buildMsgReq() - msgReq.data.signerPath[2] = HARDENED_OFFSET + 8 - const res = await client.sign(msgReq as unknown as SignRequestParams) - const sig = getSigStr(res) - const jsSig = signPersonalJS(msgReq.data.payload, msgReq.data.signerPath) + const msgReq = buildMsgReq(); + msgReq.data.signerPath[2] = HARDENED_OFFSET + 8; + const res = await client.sign(msgReq as unknown as SignRequestParams); + const sig = getSigStr(res); + const jsSig = signPersonalJS(msgReq.data.payload, msgReq.data.signerPath); expect(sig).toEqualElseLog( jsSig, 'Lattice sig does not match JS reference', - ) - }) - }) + ); + }); + }); describe('Compare personal_sign signatures vs Ledger vectors (2)', () => { it('Should validate signature from addr0', async () => { - const msgReq = buildMsgReq('hello ethereum this is another message') - msgReq.data.signerPath[2] = HARDENED_OFFSET - const res = await client.sign(msgReq as unknown as SignRequestParams) - const sig = getSigStr(res) - const jsSig = signPersonalJS(msgReq.data.payload, msgReq.data.signerPath) + const msgReq = buildMsgReq('hello ethereum this is another message'); + msgReq.data.signerPath[2] = HARDENED_OFFSET; + const res = await client.sign(msgReq as unknown as SignRequestParams); + const sig = getSigStr(res); + const jsSig = signPersonalJS(msgReq.data.payload, msgReq.data.signerPath); expect(sig).toEqualElseLog( jsSig, 'Lattice sig does not match JS reference', - ) - }) + ); + }); it('Should validate signature from addr1', async () => { - const msgReq = buildMsgReq('hello ethereum this is another message') - msgReq.data.signerPath[2] = HARDENED_OFFSET + 1 - const res = await client.sign(msgReq as unknown as SignRequestParams) - const sig = getSigStr(res) - const jsSig = signPersonalJS(msgReq.data.payload, msgReq.data.signerPath) + const msgReq = buildMsgReq('hello ethereum this is another message'); + msgReq.data.signerPath[2] = HARDENED_OFFSET + 1; + const res = await client.sign(msgReq as unknown as SignRequestParams); + const sig = getSigStr(res); + const jsSig = signPersonalJS(msgReq.data.payload, msgReq.data.signerPath); expect(sig).toEqualElseLog( jsSig, 'Lattice sig does not match JS reference', - ) - }) + ); + }); it('Should validate signature from addr8', async () => { - const msgReq = buildMsgReq('hello ethereum this is another message') - msgReq.data.signerPath[2] = HARDENED_OFFSET + 8 - const res = await client.sign(msgReq as unknown as SignRequestParams) - const sig = getSigStr(res) - const jsSig = signPersonalJS(msgReq.data.payload, msgReq.data.signerPath) + const msgReq = buildMsgReq('hello ethereum this is another message'); + msgReq.data.signerPath[2] = HARDENED_OFFSET + 8; + const res = await client.sign(msgReq as unknown as SignRequestParams); + const sig = getSigStr(res); + const jsSig = signPersonalJS(msgReq.data.payload, msgReq.data.signerPath); expect(sig).toEqualElseLog( jsSig, 'Lattice sig does not match JS reference', - ) - }) - }) + ); + }); + }); describe('Compare personal_sign signatures vs Ledger vectors (3)', () => { it('Should validate signature from addr0', async () => { - const msgReq = buildMsgReq('third vector yo') - msgReq.data.signerPath[2] = HARDENED_OFFSET - const res = await client.sign(msgReq as unknown as SignRequestParams) - const sig = getSigStr(res) - const jsSig = signPersonalJS(msgReq.data.payload, msgReq.data.signerPath) + const msgReq = buildMsgReq('third vector yo'); + msgReq.data.signerPath[2] = HARDENED_OFFSET; + const res = await client.sign(msgReq as unknown as SignRequestParams); + const sig = getSigStr(res); + const jsSig = signPersonalJS(msgReq.data.payload, msgReq.data.signerPath); expect(sig).toEqualElseLog( jsSig, 'Lattice sig does not match JS reference', - ) - }) + ); + }); it('Should validate signature from addr1', async () => { - const msgReq = buildMsgReq('third vector yo') - msgReq.data.signerPath[2] = HARDENED_OFFSET + 1 - const res = await client.sign(msgReq as unknown as SignRequestParams) - const sig = getSigStr(res) - const jsSig = signPersonalJS(msgReq.data.payload, msgReq.data.signerPath) + const msgReq = buildMsgReq('third vector yo'); + msgReq.data.signerPath[2] = HARDENED_OFFSET + 1; + const res = await client.sign(msgReq as unknown as SignRequestParams); + const sig = getSigStr(res); + const jsSig = signPersonalJS(msgReq.data.payload, msgReq.data.signerPath); expect(sig).toEqualElseLog( jsSig, 'Lattice sig does not match JS reference', - ) - }) + ); + }); it('Should validate signature from addr8', async () => { - const msgReq = buildMsgReq('third vector yo') - msgReq.data.signerPath[2] = HARDENED_OFFSET + 8 - const res = await client.sign(msgReq as unknown as SignRequestParams) - const sig = getSigStr(res) - const jsSig = signPersonalJS(msgReq.data.payload, msgReq.data.signerPath) + const msgReq = buildMsgReq('third vector yo'); + msgReq.data.signerPath[2] = HARDENED_OFFSET + 8; + const res = await client.sign(msgReq as unknown as SignRequestParams); + const sig = getSigStr(res); + const jsSig = signPersonalJS(msgReq.data.payload, msgReq.data.signerPath); expect(sig).toEqualElseLog( jsSig, 'Lattice sig does not match JS reference', - ) - }) - }) + ); + }); + }); describe('Compare EIP712 signatures vs Ledger vectors (1)', () => { const msgReq = { @@ -293,41 +293,41 @@ describe('[Determinism]', () => { }, }, }, - } + }; it('Should validate signature from addr0', async () => { - msgReq.data.signerPath[2] = HARDENED_OFFSET - const res = await client.sign(msgReq as unknown as SignRequestParams) - const sig = getSigStr(res) - const jsSig = signEip712JS(msgReq.data.payload, msgReq.data.signerPath) + msgReq.data.signerPath[2] = HARDENED_OFFSET; + const res = await client.sign(msgReq as unknown as SignRequestParams); + const sig = getSigStr(res); + const jsSig = signEip712JS(msgReq.data.payload, msgReq.data.signerPath); expect(sig).toEqualElseLog( jsSig, 'Lattice EIP712 sig does not match JS reference', - ) - }) + ); + }); it('Should validate signature from addr1', async () => { - msgReq.data.signerPath[2] = HARDENED_OFFSET + 1 - const res = await client.sign(msgReq as unknown as SignRequestParams) - const sig = getSigStr(res) - const jsSig = signEip712JS(msgReq.data.payload, msgReq.data.signerPath) + msgReq.data.signerPath[2] = HARDENED_OFFSET + 1; + const res = await client.sign(msgReq as unknown as SignRequestParams); + const sig = getSigStr(res); + const jsSig = signEip712JS(msgReq.data.payload, msgReq.data.signerPath); expect(sig).toEqualElseLog( jsSig, 'Lattice EIP712 sig does not match JS reference', - ) - }) + ); + }); it('Should validate signature from addr8', async () => { - msgReq.data.signerPath[2] = HARDENED_OFFSET + 8 - const res = await client.sign(msgReq as unknown as SignRequestParams) - const sig = getSigStr(res) - const jsSig = signEip712JS(msgReq.data.payload, msgReq.data.signerPath) + msgReq.data.signerPath[2] = HARDENED_OFFSET + 8; + const res = await client.sign(msgReq as unknown as SignRequestParams); + const sig = getSigStr(res); + const jsSig = signEip712JS(msgReq.data.payload, msgReq.data.signerPath); expect(sig).toEqualElseLog( jsSig, 'Lattice EIP712 sig does not match JS reference', - ) - }) - }) + ); + }); + }); describe('Compare EIP712 signatures vs Ledger vectors (2)', () => { const msgReq = { @@ -359,41 +359,41 @@ describe('[Determinism]', () => { }, }, }, - } + }; it('Should validate signature from addr0', async () => { - msgReq.data.signerPath[2] = HARDENED_OFFSET - const res = await client.sign(msgReq as unknown as SignRequestParams) - const sig = getSigStr(res) - const jsSig = signEip712JS(msgReq.data.payload, msgReq.data.signerPath) + msgReq.data.signerPath[2] = HARDENED_OFFSET; + const res = await client.sign(msgReq as unknown as SignRequestParams); + const sig = getSigStr(res); + const jsSig = signEip712JS(msgReq.data.payload, msgReq.data.signerPath); expect(sig).toEqualElseLog( jsSig, 'Lattice EIP712 sig does not match JS reference', - ) - }) + ); + }); it('Should validate signature from addr1', async () => { - msgReq.data.signerPath[2] = HARDENED_OFFSET + 1 - const res = await client.sign(msgReq as unknown as SignRequestParams) - const sig = getSigStr(res) - const jsSig = signEip712JS(msgReq.data.payload, msgReq.data.signerPath) + msgReq.data.signerPath[2] = HARDENED_OFFSET + 1; + const res = await client.sign(msgReq as unknown as SignRequestParams); + const sig = getSigStr(res); + const jsSig = signEip712JS(msgReq.data.payload, msgReq.data.signerPath); expect(sig).toEqualElseLog( jsSig, 'Lattice EIP712 sig does not match JS reference', - ) - }) + ); + }); it('Should validate signature from addr8', async () => { - msgReq.data.signerPath[2] = HARDENED_OFFSET + 8 - const res = await client.sign(msgReq as unknown as SignRequestParams) - const sig = getSigStr(res) - const jsSig = signEip712JS(msgReq.data.payload, msgReq.data.signerPath) + msgReq.data.signerPath[2] = HARDENED_OFFSET + 8; + const res = await client.sign(msgReq as unknown as SignRequestParams); + const sig = getSigStr(res); + const jsSig = signEip712JS(msgReq.data.payload, msgReq.data.signerPath); expect(sig).toEqualElseLog( jsSig, 'Lattice EIP712 sig does not match JS reference', - ) - }) - }) + ); + }); + }); describe('Compare EIP712 signatures vs Ledger vectors (3)', () => { const msgReq = { @@ -425,43 +425,43 @@ describe('[Determinism]', () => { }, }, }, - } + }; it('Should validate signature from addr0', async () => { - msgReq.data.signerPath[2] = HARDENED_OFFSET - const res = await client.sign(msgReq as unknown as SignRequestParams) - const sig = getSigStr(res) - const jsSig = signEip712JS(msgReq.data.payload, msgReq.data.signerPath) + msgReq.data.signerPath[2] = HARDENED_OFFSET; + const res = await client.sign(msgReq as unknown as SignRequestParams); + const sig = getSigStr(res); + const jsSig = signEip712JS(msgReq.data.payload, msgReq.data.signerPath); expect(sig).toEqualElseLog( jsSig, 'Lattice EIP712 sig does not match JS reference', - ) - }) + ); + }); it('Should validate signature from addr1', async () => { - msgReq.data.signerPath[2] = HARDENED_OFFSET + 1 - const res = await client.sign(msgReq as unknown as SignRequestParams) - const sig = getSigStr(res) - const jsSig = signEip712JS(msgReq.data.payload, msgReq.data.signerPath) + msgReq.data.signerPath[2] = HARDENED_OFFSET + 1; + const res = await client.sign(msgReq as unknown as SignRequestParams); + const sig = getSigStr(res); + const jsSig = signEip712JS(msgReq.data.payload, msgReq.data.signerPath); expect(sig).toEqualElseLog( jsSig, 'Lattice EIP712 sig does not match JS reference', - ) - }) + ); + }); it('Should validate signature from addr8', async () => { - msgReq.data.signerPath[2] = HARDENED_OFFSET + 8 - const res = await client.sign(msgReq as unknown as SignRequestParams) - const sig = getSigStr(res) - const jsSig = signEip712JS(msgReq.data.payload, msgReq.data.signerPath) + msgReq.data.signerPath[2] = HARDENED_OFFSET + 8; + const res = await client.sign(msgReq as unknown as SignRequestParams); + const sig = getSigStr(res); + const jsSig = signEip712JS(msgReq.data.payload, msgReq.data.signerPath); expect(sig).toEqualElseLog( jsSig, 'Lattice EIP712 sig does not match JS reference', - ) - }) - }) + ); + }); + }); describe('Test random personal_sign messages against JS signatures', () => { - const randomVectors = buildRandomVectors() - const signerPathOffsets = [0, 1, 8] + const randomVectors = buildRandomVectors(); + const signerPathOffsets = [0, 1, 8]; randomVectors.forEach((payload, i) => { signerPathOffsets.forEach(async (offset) => { @@ -473,14 +473,14 @@ describe('[Determinism]', () => { protocol: 'signPersonal', payload, }, - } - req.data.signerPath[2] = HARDENED_OFFSET + offset - const jsSig = signPersonalJS(req.data.payload, req.data.signerPath) - const res = await client.sign(req as unknown as SignRequestParams) - const sig = getSigStr(res) - expect(sig).toEqualElseLog(jsSig, `Addr${offset} sig failed`) - }) - }) - }) - }) -}) + }; + req.data.signerPath[2] = HARDENED_OFFSET + offset; + const jsSig = signPersonalJS(req.data.payload, req.data.signerPath); + const res = await client.sign(req as unknown as SignRequestParams); + const sig = getSigStr(res); + expect(sig).toEqualElseLog(jsSig, `Addr${offset} sig failed`); + }); + }); + }); + }); +}); diff --git a/packages/sdk/src/__test__/e2e/signing/eip712-msg.test.ts b/packages/sdk/src/__test__/e2e/signing/eip712-msg.test.ts index 27be0dd3..8fec45b5 100644 --- a/packages/sdk/src/__test__/e2e/signing/eip712-msg.test.ts +++ b/packages/sdk/src/__test__/e2e/signing/eip712-msg.test.ts @@ -1,21 +1,21 @@ -import { setupClient } from '../../utils/setup' +import { setupClient } from '../../utils/setup'; /** * EIP-712 Typed Data Message Signing Test Suite * * Tests EIP-712 message signing compatibility between Lattice and viem. * Replaces the forge-based contract test with a pure signature comparison approach. */ -import { signAndCompareEIP712Message } from '../../utils/viemComparison' -import { EIP712_MESSAGE_VECTORS } from './eip712-vectors' +import { signAndCompareEIP712Message } from '../../utils/viemComparison'; +import { EIP712_MESSAGE_VECTORS } from './eip712-vectors'; describe('EIP-712 Message Signing - Viem Compatibility', () => { beforeAll(async () => { - await setupClient() - }) + await setupClient(); + }); EIP712_MESSAGE_VECTORS.forEach((vector, index) => { it(`${vector.name} (${index + 1}/${EIP712_MESSAGE_VECTORS.length})`, async () => { - await signAndCompareEIP712Message(vector.message, vector.name) - }) - }) -}) + await signAndCompareEIP712Message(vector.message, vector.name); + }); + }); +}); diff --git a/packages/sdk/src/__test__/e2e/signing/eip712-vectors.ts b/packages/sdk/src/__test__/e2e/signing/eip712-vectors.ts index ae89e2ba..2a06886c 100644 --- a/packages/sdk/src/__test__/e2e/signing/eip712-vectors.ts +++ b/packages/sdk/src/__test__/e2e/signing/eip712-vectors.ts @@ -5,14 +5,14 @@ * Each vector contains domain, types, primaryType, and message data. */ -import type { EIP712TestMessage } from '../../utils/viemComparison' +import type { EIP712TestMessage } from '../../utils/viemComparison'; // Mock contract address for EIP-712 domain -const MOCK_CONTRACT_ADDRESS = '0x1234567890123456789012345678901234567890' +const MOCK_CONTRACT_ADDRESS = '0x1234567890123456789012345678901234567890'; export const EIP712_MESSAGE_VECTORS: Array<{ - name: string - message: EIP712TestMessage + name: string; + message: EIP712TestMessage; }> = [ { name: 'Negative Amount - Basic negative integer', @@ -318,4 +318,4 @@ export const EIP712_MESSAGE_VECTORS: Array<{ }, }, }, -] +]; diff --git a/packages/sdk/src/__test__/e2e/signing/evm-abi.test.ts b/packages/sdk/src/__test__/e2e/signing/evm-abi.test.ts index ed09cd5e..9eb36624 100644 --- a/packages/sdk/src/__test__/e2e/signing/evm-abi.test.ts +++ b/packages/sdk/src/__test__/e2e/signing/evm-abi.test.ts @@ -3,25 +3,25 @@ * These transactions use contract addresses so the device can fetch ABI data dynamically. */ -import { sign } from '../../../api' -import { setupClient } from '../../utils/setup' -import { ABI_TEST_VECTORS } from '../../vectors/abi-vectors' +import { sign } from '../../../api'; +import { setupClient } from '../../utils/setup'; +import { ABI_TEST_VECTORS } from '../../vectors/abi-vectors'; describe('[EVM ABI] ABI Decoding Tests', () => { beforeAll(async () => { - await setupClient() - }) + await setupClient(); + }); describe('ABI Patterns with Complex Structures', () => { ABI_TEST_VECTORS.forEach((testCase, index) => { it(`Should test ${testCase.name} (${index + 1}/${ABI_TEST_VECTORS.length})`, async () => { - const result = await sign(testCase.tx) - expect(result).toBeDefined() - expect(result.sig).toBeDefined() - expect(result.sig.r).toBeDefined() - expect(result.sig.s).toBeDefined() - expect(result.sig.v).toBeDefined() - }) - }) - }) -}) + const result = await sign(testCase.tx); + expect(result).toBeDefined(); + expect(result.sig).toBeDefined(); + expect(result.sig.r).toBeDefined(); + expect(result.sig.s).toBeDefined(); + expect(result.sig.v).toBeDefined(); + }); + }); + }); +}); diff --git a/packages/sdk/src/__test__/e2e/signing/evm-tx.test.ts b/packages/sdk/src/__test__/e2e/signing/evm-tx.test.ts index dfc0d4ca..e5d52bc4 100644 --- a/packages/sdk/src/__test__/e2e/signing/evm-tx.test.ts +++ b/packages/sdk/src/__test__/e2e/signing/evm-tx.test.ts @@ -6,58 +6,58 @@ * This replaces all individual EVM test files to avoid duplication and provide unified testing. */ -import { setupClient } from '../../utils/setup' -import { signAndCompareTransaction } from '../../utils/viemComparison' +import { setupClient } from '../../utils/setup'; +import { signAndCompareTransaction } from '../../utils/viemComparison'; import { EDGE_CASE_TEST_VECTORS, EIP1559_TEST_VECTORS, EIP2930_TEST_VECTORS, EIP7702_TEST_VECTORS, LEGACY_VECTORS, -} from './vectors' +} from './vectors'; describe('EVM Transaction Signing - Unified Test Suite', () => { beforeAll(async () => { - await setupClient() - }) + await setupClient(); + }); describe('Legacy Transactions', () => { LEGACY_VECTORS.forEach((vector, index) => { it(`${vector.name} (${index + 1}/${LEGACY_VECTORS.length})`, async () => { - await signAndCompareTransaction(vector.tx, vector.name) - }) - }) - }) + await signAndCompareTransaction(vector.tx, vector.name); + }); + }); + }); describe('EIP-1559 Transactions (Fee Market)', () => { EIP1559_TEST_VECTORS.forEach((vector, index) => { it(`${vector.name} (${index + 1}/${EIP1559_TEST_VECTORS.length})`, async () => { - await signAndCompareTransaction(vector.tx, vector.name) - }) - }) - }) + await signAndCompareTransaction(vector.tx, vector.name); + }); + }); + }); describe('EIP-2930 Transactions (Access Lists)', () => { EIP2930_TEST_VECTORS.forEach((vector, index) => { it(`${vector.name} (${index + 1}/${EIP2930_TEST_VECTORS.length})`, async () => { - await signAndCompareTransaction(vector.tx, vector.name) - }) - }) - }) + await signAndCompareTransaction(vector.tx, vector.name); + }); + }); + }); describe('EIP-7702 Transactions (Account Abstraction)', () => { EIP7702_TEST_VECTORS.forEach((vector, index) => { it(`${vector.name} (${index + 1}/${EIP7702_TEST_VECTORS.length})`, async () => { - await signAndCompareTransaction(vector.tx, vector.name) - }) - }) - }) + await signAndCompareTransaction(vector.tx, vector.name); + }); + }); + }); describe('Edge Cases & Boundary Conditions', () => { EDGE_CASE_TEST_VECTORS.forEach((vector, index) => { it(`${vector.name} (${index + 1}/${EDGE_CASE_TEST_VECTORS.length})`, async () => { - await signAndCompareTransaction(vector.tx, vector.name) - }) - }) - }) -}) + await signAndCompareTransaction(vector.tx, vector.name); + }); + }); + }); +}); diff --git a/packages/sdk/src/__test__/e2e/signing/solana/__mocks__/programs.ts b/packages/sdk/src/__test__/e2e/signing/solana/__mocks__/programs.ts index 13f702a2..f72b1ec9 100644 --- a/packages/sdk/src/__test__/e2e/signing/solana/__mocks__/programs.ts +++ b/packages/sdk/src/__test__/e2e/signing/solana/__mocks__/programs.ts @@ -52,7 +52,7 @@ export const raydiumProgram = Buffer.from([ 21, 0, 22, 1, 1, 20, 7, 0, 9, 0, 15, 14, 23, 22, 0, 17, 18, 23, 10, 16, 7, 6, 13, 2, 19, 12, 11, 3, 4, 8, 5, 18, 1, 9, 0, 17, 9, 64, 66, 15, 0, 0, 0, 0, 0, 89, 241, 0, 0, 0, 0, 0, 0, 23, 3, 1, 0, 0, 1, 9, -]) +]); export const dexlabProgram = Buffer.from([ 3, 0, 7, 11, 47, 41, 101, 134, 4, 204, 35, 219, 9, 43, 55, 20, 255, 76, 29, @@ -84,4 +84,4 @@ export const dexlabProgram = Buffer.from([ 0, 0, 240, 29, 31, 0, 0, 0, 0, 0, 165, 0, 0, 0, 0, 0, 0, 0, 6, 221, 246, 225, 215, 101, 161, 147, 217, 203, 225, 70, 206, 235, 121, 172, 28, 180, 133, 237, 95, 91, 55, 145, 58, 140, 245, 133, 126, 255, 0, 169, 10, 4, 2, 7, 0, 9, 1, 1, -]) +]); diff --git a/packages/sdk/src/__test__/e2e/signing/solana/solana.programs.test.ts b/packages/sdk/src/__test__/e2e/signing/solana/solana.programs.test.ts index 58944657..3ecadd40 100644 --- a/packages/sdk/src/__test__/e2e/signing/solana/solana.programs.test.ts +++ b/packages/sdk/src/__test__/e2e/signing/solana/solana.programs.test.ts @@ -6,20 +6,20 @@ * Running with a different mnemonic will cause test failures due to * incorrect key derivations and signature mismatches. */ -import { Constants } from '../../../..' -import type { Client } from '../../../../client' -import { setupClient } from '../../../utils/setup' -import { dexlabProgram, raydiumProgram } from './__mocks__/programs' +import { Constants } from '../../../..'; +import type { Client } from '../../../../client'; +import { setupClient } from '../../../utils/setup'; +import { dexlabProgram, raydiumProgram } from './__mocks__/programs'; describe('Solana Programs', () => { - let client: Client + let client: Client; beforeAll(async () => { - client = await setupClient() - }) + client = await setupClient(); + }); it('should sign Dexlab program', async () => { - const payload = dexlabProgram + const payload = dexlabProgram; const signedMessage = await client.sign({ data: { signerPath: [0x80000000 + 44, 0x80000000 + 501, 0x80000000], @@ -28,12 +28,12 @@ describe('Solana Programs', () => { encodingType: Constants.SIGNING.ENCODINGS.SOLANA, payload, }, - }) - expect(signedMessage).toBeTruthy() - }) + }); + expect(signedMessage).toBeTruthy(); + }); it('should sign Raydium program', async () => { - const payload = raydiumProgram + const payload = raydiumProgram; const signedMessage = await client.sign({ data: { signerPath: [0x80000000 + 44, 0x80000000 + 501, 0x80000000], @@ -42,7 +42,7 @@ describe('Solana Programs', () => { encodingType: Constants.SIGNING.ENCODINGS.SOLANA, payload, }, - }) - expect(signedMessage).toBeTruthy() - }) -}) + }); + expect(signedMessage).toBeTruthy(); + }); +}); diff --git a/packages/sdk/src/__test__/e2e/signing/solana/solana.test.ts b/packages/sdk/src/__test__/e2e/signing/solana/solana.test.ts index 99bc116f..94a0e596 100644 --- a/packages/sdk/src/__test__/e2e/signing/solana/solana.test.ts +++ b/packages/sdk/src/__test__/e2e/signing/solana/solana.test.ts @@ -11,16 +11,16 @@ import { PublicKey as SolanaPublicKey, SystemProgram as SolanaSystemProgram, Transaction as SolanaTransaction, -} from '@solana/web3.js' -import { Constants } from '../../../..' -import { HARDENED_OFFSET } from '../../../../constants' -import { ensureHexBuffer } from '../../../../util' -import { getPrng } from '../../../utils/getters' -import { deriveED25519Key, prandomBuf } from '../../../utils/helpers' -import { runGeneric } from '../../../utils/runners' -import { setupClient } from '../../../utils/setup' -import { TEST_SEED } from '../../../utils/testConstants' -import type { Client } from '../../../../client' +} from '@solana/web3.js'; +import { Constants } from '../../../..'; +import { HARDENED_OFFSET } from '../../../../constants'; +import { ensureHexBuffer } from '../../../../util'; +import { getPrng } from '../../../utils/getters'; +import { deriveED25519Key, prandomBuf } from '../../../utils/helpers'; +import { runGeneric } from '../../../utils/runners'; +import { setupClient } from '../../../utils/setup'; +import { TEST_SEED } from '../../../utils/testConstants'; +import type { Client } from '../../../../client'; //--------------------------------------- // STATE DATA @@ -30,15 +30,15 @@ const DEFAULT_SOLANA_SIGNER_PATH = [ HARDENED_OFFSET + 501, HARDENED_OFFSET, HARDENED_OFFSET, -] -const prng = getPrng() +]; +const prng = getPrng(); describe('[Solana]', () => { - let client: Client + let client: Client; beforeAll(async () => { - client = await setupClient() - }) + client = await setupClient(); + }); const getReq = (overrides: any) => ({ data: { @@ -48,7 +48,7 @@ describe('[Solana]', () => { payload: null, ...overrides, }, - }) + }); it('Should validate Solana transaction encoding', async () => { // Build a Solana transaction with two signers, each derived from the Lattice's seed. @@ -58,56 +58,57 @@ describe('[Solana]', () => { // NOTE: Solana addresses are just base58 encoded public keys. We do not // currently support exporting of Solana addresses in firmware but we can // derive them here using the exported seed. - const seed = TEST_SEED - const derivedAPath = [...DEFAULT_SOLANA_SIGNER_PATH] - const derivedBPath = [...DEFAULT_SOLANA_SIGNER_PATH] - derivedBPath[3] += 1 - const derivedCPath = [...DEFAULT_SOLANA_SIGNER_PATH] - derivedCPath[3] += 2 - const derivedA = deriveED25519Key(derivedAPath, seed) - const derivedB = deriveED25519Key(derivedBPath, seed) - const derivedC = deriveED25519Key(derivedCPath, seed) - const pubA = new SolanaPublicKey(derivedA.pub) - const pubB = new SolanaPublicKey(derivedB.pub) - const pubC = new SolanaPublicKey(derivedC.pub) + const seed = TEST_SEED; + const derivedAPath = [...DEFAULT_SOLANA_SIGNER_PATH]; + const derivedBPath = [...DEFAULT_SOLANA_SIGNER_PATH]; + derivedBPath[3] += 1; + const derivedCPath = [...DEFAULT_SOLANA_SIGNER_PATH]; + derivedCPath[3] += 2; + const derivedA = deriveED25519Key(derivedAPath, seed); + const derivedB = deriveED25519Key(derivedBPath, seed); + const derivedC = deriveED25519Key(derivedCPath, seed); + const pubA = new SolanaPublicKey(derivedA.pub); + const pubB = new SolanaPublicKey(derivedB.pub); + const pubC = new SolanaPublicKey(derivedC.pub); // Define transaction instructions const transfer1 = SolanaSystemProgram.transfer({ fromPubkey: pubA, toPubkey: pubC, lamports: 111, - }) + }); const transfer2 = SolanaSystemProgram.transfer({ fromPubkey: pubB, toPubkey: pubC, lamports: 222, - }) + }); // Generate a pseudorandom blockhash, which is just a public key appearently. - const randBuf = prandomBuf(prng, 32, true) - const recentBlockhash = SolanaKeypair.fromSeed(randBuf).publicKey.toBase58() + const randBuf = prandomBuf(prng, 32, true); + const recentBlockhash = + SolanaKeypair.fromSeed(randBuf).publicKey.toBase58(); // Build a transaction and sign it using Solana's JS lib const txJs = new SolanaTransaction({ recentBlockhash }).add( transfer1, transfer2, - ) - txJs.setSigners(pubA, pubB) + ); + txJs.setSigners(pubA, pubB); txJs.sign( SolanaKeypair.fromSeed(derivedA.priv), SolanaKeypair.fromSeed(derivedB.priv), - ) - const serTxJs = txJs.serialize().toString('hex') + ); + const serTxJs = txJs.serialize().toString('hex'); // Build a copy of the transaction and get the serialized payload for signing in firmware. const txFw = new SolanaTransaction({ recentBlockhash }).add( transfer1, transfer2, - ) - txFw.setSigners(pubA, pubB) + ); + txFw.setSigners(pubA, pubB); // We want to sign the Solana message, not the full transaction - const payload = txFw.compileMessage().serialize() - const payloadHex = `0x${payload.toString('hex')}` + const payload = txFw.compileMessage().serialize(); + const payloadHex = `0x${payload.toString('hex')}`; // Sign payload from Lattice and add signatures to tx object const sigA = await runGeneric( @@ -118,13 +119,13 @@ describe('[Solana]', () => { client, ).then((resp) => { if (!resp.sig?.r || !resp.sig?.s) { - throw new Error('Missing signature components in response') + throw new Error('Missing signature components in response'); } return Buffer.concat([ ensureHexBuffer(resp.sig.r as string | Buffer), ensureHexBuffer(resp.sig.s as string | Buffer), - ]) - }) + ]); + }); const sigB = await runGeneric( getReq({ @@ -134,18 +135,18 @@ describe('[Solana]', () => { client, ).then((resp) => { if (!resp.sig?.r || !resp.sig?.s) { - throw new Error('Missing signature components in response') + throw new Error('Missing signature components in response'); } return Buffer.concat([ ensureHexBuffer(resp.sig.r as string | Buffer), ensureHexBuffer(resp.sig.s as string | Buffer), - ]) - }) - txFw.addSignature(pubA, sigA) - txFw.addSignature(pubB, sigB) + ]); + }); + txFw.addSignature(pubA, sigA); + txFw.addSignature(pubB, sigB); // Validate the signatures from the Lattice match those of the Solana library - const serTxFw = txFw.serialize().toString('hex') - expect(serTxFw).toEqual(serTxJs) - }) -}) + const serTxFw = txFw.serialize().toString('hex'); + expect(serTxFw).toEqual(serTxJs); + }); +}); diff --git a/packages/sdk/src/__test__/e2e/signing/solana/solana.versioned.test.ts b/packages/sdk/src/__test__/e2e/signing/solana/solana.versioned.test.ts index 65ee878f..249d05c5 100644 --- a/packages/sdk/src/__test__/e2e/signing/solana/solana.versioned.test.ts +++ b/packages/sdk/src/__test__/e2e/signing/solana/solana.versioned.test.ts @@ -17,66 +17,66 @@ import { type TransactionInstruction, TransactionMessage, VersionedTransaction, -} from '@solana/web3.js' -import { fetchSolanaAddresses, signSolanaTx } from '../../../..' -import { setupClient } from '../../../utils/setup' +} from '@solana/web3.js'; +import { fetchSolanaAddresses, signSolanaTx } from '../../../..'; +import { setupClient } from '../../../utils/setup'; -const SOLANA_RPC = new Connection('https://api.devnet.solana.com', 'confirmed') +const SOLANA_RPC = new Connection('https://api.devnet.solana.com', 'confirmed'); const fetchSigningWallet = async () => { - const addresses = await fetchSolanaAddresses({ n: 1 }) - const solanaAddr = addresses[0] - const pubkey = new PublicKey(solanaAddr) - expect(PublicKey.isOnCurve(pubkey)).toBeTruthy() - return pubkey -} + const addresses = await fetchSolanaAddresses({ n: 1 }); + const solanaAddr = addresses[0]; + const pubkey = new PublicKey(solanaAddr); + expect(PublicKey.isOnCurve(pubkey)).toBeTruthy(); + return pubkey; +}; const requestAirdrop = async (wallet: PublicKey, lamports: number) => { try { - await SOLANA_RPC.requestAirdrop(wallet, lamports * LAMPORTS_PER_SOL) - await new Promise((resolve) => setTimeout(resolve, 20000)) + await SOLANA_RPC.requestAirdrop(wallet, lamports * LAMPORTS_PER_SOL); + await new Promise((resolve) => setTimeout(resolve, 20000)); } catch (error) { /** * The faucet is flakey, so you might need to request funds manually from the faucet at https://faucet.solana.com/ * Also, for Solana to work, your address must have interacted with the network previously. */ - console.log(error) + console.log(error); } -} +}; describe('solana.versioned', () => { - let SIGNER_WALLET: PublicKey - let DESTINATION_WALLET_1: Keypair - let DESTINATION_WALLET_2: Keypair - let latestBlockhash: { blockhash: string; lastValidBlockHeight: number } + let SIGNER_WALLET: PublicKey; + let DESTINATION_WALLET_1: Keypair; + let DESTINATION_WALLET_2: Keypair; + let latestBlockhash: { blockhash: string; lastValidBlockHeight: number }; beforeAll(async () => { - await setupClient() + await setupClient(); - SIGNER_WALLET = await fetchSigningWallet() - DESTINATION_WALLET_1 = Keypair.generate() - DESTINATION_WALLET_2 = Keypair.generate() - latestBlockhash = await SOLANA_RPC.getLatestBlockhash('confirmed') - }) + SIGNER_WALLET = await fetchSigningWallet(); + DESTINATION_WALLET_1 = Keypair.generate(); + DESTINATION_WALLET_2 = Keypair.generate(); + latestBlockhash = await SOLANA_RPC.getLatestBlockhash('confirmed'); + }); test('sign solana', async () => { - SIGNER_WALLET = await fetchSigningWallet() + SIGNER_WALLET = await fetchSigningWallet(); const txInstructions: TransactionInstruction[] = [ SystemProgram.transfer({ fromPubkey: SIGNER_WALLET, toPubkey: DESTINATION_WALLET_1.publicKey, lamports: 0.01 * LAMPORTS_PER_SOL, }), - ] + ]; const messageV0 = new TransactionMessage({ payerKey: SIGNER_WALLET, recentBlockhash: latestBlockhash.blockhash, instructions: txInstructions, - }).compileToV0Message() + }).compileToV0Message(); - const signedTx = await signSolanaTx(Buffer.from(messageV0.serialize())) - expect(signedTx).toBeTruthy() - }) + const signedTx = await signSolanaTx(Buffer.from(messageV0.serialize())); + expect(signedTx).toBeTruthy(); + }); test('sign solana multiple instructions', async () => { const txInstructions = [ @@ -90,32 +90,32 @@ describe('solana.versioned', () => { toPubkey: DESTINATION_WALLET_2.publicKey, lamports: 0.005 * LAMPORTS_PER_SOL, }), - ] + ]; const message = new TransactionMessage({ payerKey: SIGNER_WALLET, recentBlockhash: latestBlockhash.blockhash, instructions: txInstructions, - }).compileToV0Message() + }).compileToV0Message(); - const signedTx = await signSolanaTx(Buffer.from(message.serialize())) - expect(signedTx).toBeTruthy() - }) + const signedTx = await signSolanaTx(Buffer.from(message.serialize())); + expect(signedTx).toBeTruthy(); + }); test('sign solana zero lamport transfer', async () => { const txInstruction = SystemProgram.transfer({ fromPubkey: SIGNER_WALLET, toPubkey: DESTINATION_WALLET_1.publicKey, lamports: 0, - }) + }); const message = new TransactionMessage({ payerKey: SIGNER_WALLET, recentBlockhash: latestBlockhash.blockhash, instructions: [txInstruction], - }).compileToV0Message() + }).compileToV0Message(); - const signedTx = await signSolanaTx(Buffer.from(message.serialize())) - expect(signedTx).toBeTruthy() - }) + const signedTx = await signSolanaTx(Buffer.from(message.serialize())); + expect(signedTx).toBeTruthy(); + }); // Skipping this test because VersionedTransaction are getting rejected by the device (LatticeResponseCode.userDeclined) test.skip('simulate versioned solana transaction', async () => { @@ -123,39 +123,39 @@ describe('solana.versioned', () => { fromPubkey: SIGNER_WALLET, toPubkey: DESTINATION_WALLET_1.publicKey, lamports: 0.001 * LAMPORTS_PER_SOL, - }) + }); - const transaction = new Transaction() - transaction.add(txInstruction) - transaction.recentBlockhash = latestBlockhash.blockhash - transaction.feePayer = SIGNER_WALLET + const transaction = new Transaction(); + transaction.add(txInstruction); + transaction.recentBlockhash = latestBlockhash.blockhash; + transaction.feePayer = SIGNER_WALLET; // Serialize the transaction to get the wire format const serializedTransaction = transaction.serialize({ requireAllSignatures: false, - }) + }); // Create a VersionedTransaction from the serialized data const versionedTransaction = VersionedTransaction.deserialize( serializedTransaction, - ) + ); const signedTx = await signSolanaTx( Buffer.from(versionedTransaction.serialize()), - ) - expect(signedTx).toBeTruthy() - }) + ); + expect(signedTx).toBeTruthy(); + }); test('simulate versioned solana transaction with multiple instructions', async () => { - const payer = Keypair.generate() - await requestAirdrop(payer.publicKey, 1) + const payer = Keypair.generate(); + await requestAirdrop(payer.publicKey, 1); const [transactionInstruction, pubkey] = await AddressLookupTableProgram.createLookupTable({ payer: payer.publicKey, authority: payer.publicKey, recentSlot: await SOLANA_RPC.getSlot(), - }) + }); await AddressLookupTableProgram.extendLookupTable({ payer: payer.publicKey, @@ -165,18 +165,18 @@ describe('solana.versioned', () => { DESTINATION_WALLET_1.publicKey, DESTINATION_WALLET_2.publicKey, ], - }) + }); const messageV0 = new TransactionMessage({ payerKey: SIGNER_WALLET, recentBlockhash: latestBlockhash.blockhash, instructions: [transactionInstruction], - }).compileToV0Message() + }).compileToV0Message(); - const signedTx = await signSolanaTx(Buffer.from(messageV0.serialize())) + const signedTx = await signSolanaTx(Buffer.from(messageV0.serialize())); - expect(signedTx).toBeDefined() - }) + expect(signedTx).toBeDefined(); + }); // Skipping this test because the messages are getting rejected by the device (LatticeResponseCode.userDeclined) test.skip('simulate versioned solana transactions from nufi', async () => { @@ -186,9 +186,9 @@ describe('solana.versioned', () => { '800100131b7154e1c10d16949038dd040363bca1f9d6501c694f6e0325ad817166fb2e91ee0c503f9578fb1f7621e5a0c2c384547f22cd4116be06e523219f332b61d41644801401dbe3c1c1f14a4eca5605ce5071e3301e79a79d95ecc50c73b977286be44ffdb307c4cce0aa81999d925e13f482e6deb72c8cc1d31ebf6b95478bd11f4f0cd8b42c34cd1d82beaec835fd525d1e90c248cf6849b277e78015a46e48789107a241e063d943cba135e306d4f8059f2ecf7a69744b993531a9d767674c85c7f6d2eabe47377602308731d0ac6ee6483c3a0f34b6aff13d37e53e00719032e928a52bdc5c44449874879e44c88ed5eb62b77a2b5e672ab9f50010479abc43e10306466fe5211732ffecadba72c39be7bc8ce5bbc5f7126b2c439b3a4000000006b5efb175d780346c18fa61f588190e5981c4668d651d67ad73c36e31180fdf9aca3abb5a18a0fe31a5031e7765dc6264a19fa84b3925e256dc2b4e53974b3c06b43c95041249ed7dedfc195aec0bc3ff2760a53409c79a7607b29fa3a12ad61393a26044ecc1adf84705420dd79ceeeca7be0dc63cd55a3994d9aba59b0e4303da8d1d21a468a26ba2183fd61de731ca4d5d61fd81296f97d916d81fd074ba18a633ac3c601aff49b0ffb943b9cdc1d89793f880bd8765868fd1ab42aa3b670286c0be1afb0442e17ee761789fe71c6e6914dd5771f7993092c9ca8bb05f3ae77546322de214d7944e9846236fe15e4f47731268ee9d6f60dcd058c96ea60a930890e9e6be0f4b35f0bf2f4c373f074ecc0404d221729981f11bb0dd5586fa55ac0b630d53e1fbf40c31f6163a1fed819daad6c34ec6eebbd2c9094ab8f0868ad7baf66ee465a9f41bc1fb7e3f72b663d4bfd34fe585e91fbd1f2753437cbad2569d176621a5d281ef6683261d8cc0da378f1139f1c01996edda54eb109ac4348ea0872c378762af7b1423c9c5b5ad1dd48584fcdbdb10e4922e01573228b8cd362cd18826752b72a9bf8c3d1fd7e3faeec4d44bd0f8988a902511c22b6ab46ba8557b567c6cbcd3e23efe17a9df8269dddf11d65a8f703314b84688b0bb9954004e7014187f35fb8aa37fc7e7cfe9c21e1e3def76baa18ab0668b61cf8bb66c55c082dfa8853bec08eeb7402096300a48b0b7259205ca703a1eaa032a21f6dcf2b4502abaead6d2b5495f0ea97222e2cd76aefb60c8d59d435e7a51cab236333b989b9593098437cfe28786fd22445b730c237a17d1110c8fef327d5f058e0308000502882502000800090333000000000000000922010a021b1c1d1e09030000040909040b0506070c0d0e0f101112131415161718191a7166063d1201daebea164609000000000016460900b9b0b7828d68598fa5ddf8fd4e8ba1315b7afae1785fb6ce5632d8699dd56fb15e4372ff949cf950d5dd678d5622184bc37b32d3a396f9d41ab609e65b1496b3040000000017262704000000010000008a02585c0a0000000000016400017b6eea3282722d20e3d0b0ce1eef6cf588aa519c82103084255fc33deb476d72000416170015', 'hex', ), - ) + ); - expect(signedTx).toBeDefined() + expect(signedTx).toBeDefined(); // signMessage const signedMessage = await signSolanaTx( @@ -196,7 +196,7 @@ describe('solana.versioned', () => { '6d616769636564656e2e696f2077616e747320796f7520746f207369676e20696e207769746820796f757220536f6c616e61206163636f756e743a0a386451393746414e6368337a75346348426942793556365a716478555365547858465873624b62436e6451710a0a57656c636f6d6520746f204d61676963204564656e2e205369676e696e6720697320746865206f6e6c79207761792077652063616e207472756c79206b6e6f77207468617420796f752061726520746865206f776e6572206f66207468652077616c6c657420796f752061726520636f6e6e656374696e672e205369676e696e67206973206120736166652c206761732d6c657373207472616e73616374696f6e207468617420646f6573206e6f7420696e20616e79207761792067697665204d61676963204564656e207065726d697373696f6e20746f20706572666f726d20616e79207472616e73616374696f6e73207769746820796f75722077616c6c65742e0a0a5552493a2068747470733a2f2f6d616769636564656e2e696f2f706f70756c61722d636f6c6c656374696f6e730a56657273696f6e3a20310a436861696e2049443a206d61696e6e65740a4e6f6e63653a2038373066313166646234373734353962393433353730316463366532353035350a4973737565642041743a20323032342d30372d30325431353a32383a34382e3736305a0a526571756573742049443a2063313331346235622d656365382d346234662d613837392d333839346464613336346534', 'hex', ), - ) - expect(signedMessage).toBeDefined() - }) -}) + ); + expect(signedMessage).toBeDefined(); + }); +}); diff --git a/packages/sdk/src/__test__/e2e/signing/unformatted.test.ts b/packages/sdk/src/__test__/e2e/signing/unformatted.test.ts index 4894ccad..5ae2bcef 100644 --- a/packages/sdk/src/__test__/e2e/signing/unformatted.test.ts +++ b/packages/sdk/src/__test__/e2e/signing/unformatted.test.ts @@ -1,28 +1,28 @@ -import { Constants } from '../../..' -import { HARDENED_OFFSET } from '../../../constants' -import { getNumIter } from '../../utils/builders' -import { getPrng } from '../../utils/getters' -import { ethPersonalSignMsg, prandomBuf } from '../../utils/helpers' -import { runGeneric } from '../../utils/runners' -import { setupClient } from '../../utils/setup' -import type { Client } from '../../../client' - -const prng = getPrng() -const numIter = getNumIter() +import { Constants } from '../../..'; +import { HARDENED_OFFSET } from '../../../constants'; +import { getNumIter } from '../../utils/builders'; +import { getPrng } from '../../utils/getters'; +import { ethPersonalSignMsg, prandomBuf } from '../../utils/helpers'; +import { runGeneric } from '../../utils/runners'; +import { setupClient } from '../../utils/setup'; +import type { Client } from '../../../client'; + +const prng = getPrng(); +const numIter = getNumIter(); const DEFAULT_SIGNER = [ HARDENED_OFFSET + 44, HARDENED_OFFSET + 60, HARDENED_OFFSET, 0, 0, -] +]; describe('[Unformatted]', () => { - let client: Client + let client: Client; beforeAll(async () => { - client = await setupClient() - }) + client = await setupClient(); + }); const getReq = (overrides: any) => ({ data: { @@ -32,14 +32,15 @@ describe('[Unformatted]', () => { payload: null, ...overrides, }, - }) + }); it('Should test pre-hashed messages', async () => { - const fwConstants = client.getFwConstants() - const { extraDataFrameSz, extraDataMaxFrames, genericSigning } = fwConstants - const { baseDataSz } = genericSigning + const fwConstants = client.getFwConstants(); + const { extraDataFrameSz, extraDataMaxFrames, genericSigning } = + fwConstants; + const { baseDataSz } = genericSigning; // Max size that won't be prehashed - const maxSz = baseDataSz + extraDataMaxFrames * extraDataFrameSz + const maxSz = baseDataSz + extraDataMaxFrames * extraDataFrameSz; // Use extraData frames await runGeneric( @@ -47,7 +48,7 @@ describe('[Unformatted]', () => { payload: `0x${prandomBuf(prng, maxSz, true).toString('hex')}`, }), client, - ) + ); // Prehash (keccak256) await runGeneric( @@ -55,7 +56,7 @@ describe('[Unformatted]', () => { payload: `0x${prandomBuf(prng, maxSz + 1, true).toString('hex')}`, }), client, - ) + ); // Prehash (sha256) await runGeneric( @@ -64,8 +65,8 @@ describe('[Unformatted]', () => { hashType: Constants.SIGNING.HASHES.SHA256, }), client, - ) - }) + ); + }); it('Should test ASCII text formatting', async () => { // Build a payload that uses spaces and newlines @@ -81,17 +82,17 @@ describe('[Unformatted]', () => { ), }), client, - ) - }) + ); + }); it('Should validate SECP256K1/KECCAK signature against derived key', async () => { // ASCII message encoding - await runGeneric(getReq({ payload: 'test' }), client) + await runGeneric(getReq({ payload: 'test' }), client); // Hex message encoding - const req = getReq({ payload: '0x123456' }) - await runGeneric(req, client) - }) + const req = getReq({ payload: '0x123456' }); + await runGeneric(req, client); + }); it('Should validate ED25519/NULL signature against derived key', async () => { const req = getReq({ @@ -101,22 +102,22 @@ describe('[Unformatted]', () => { hashType: Constants.SIGNING.HASHES.NONE, // ED25519 derivation requires hardened indices signerPath: DEFAULT_SIGNER.slice(0, 3), - }) + }); // Make generic signing request - await runGeneric(req, client) - }) + await runGeneric(req, client); + }); it('Should validate SECP256K1/KECCAK signature against ETH_MSG request (legacy)', async () => { // Generic request - const msg = 'Testing personal_sign' - const psMsg = ethPersonalSignMsg(msg) + const msg = 'Testing personal_sign'; + const psMsg = ethPersonalSignMsg(msg); // NOTE: The request contains some non ASCII characters so it will get // encoded as hex automatically. const req = getReq({ payload: psMsg, - }) + }); - const respGeneric = await runGeneric(req, client) + const respGeneric = await runGeneric(req, client); // Legacy request const legacyReq = { @@ -128,45 +129,45 @@ describe('[Unformatted]', () => { curveType: Constants.SIGNING.CURVES.SECP256K1, hashType: Constants.SIGNING.HASHES.KECCAK256, }, - } + }; const respLegacy = await client.sign( legacyReq as Parameters[0], - ) + ); - const genSigR = (respGeneric.sig?.r as Buffer)?.toString('hex') ?? '' - const genSigS = (respGeneric.sig?.s as Buffer)?.toString('hex') ?? '' - const legSigR = (respLegacy.sig?.r as Buffer)?.toString('hex') ?? '' - const legSigS = (respLegacy.sig?.s as Buffer)?.toString('hex') ?? '' + const genSigR = (respGeneric.sig?.r as Buffer)?.toString('hex') ?? ''; + const genSigS = (respGeneric.sig?.s as Buffer)?.toString('hex') ?? ''; + const legSigR = (respLegacy.sig?.r as Buffer)?.toString('hex') ?? ''; + const legSigS = (respLegacy.sig?.s as Buffer)?.toString('hex') ?? ''; - const genSig = `${genSigR}${genSigS}` - const legSig = `${legSigR}${legSigS}` + const genSig = `${genSigR}${genSigS}`; + const legSig = `${legSigR}${legSigS}`; expect(genSig).toEqualElseLog( legSig, 'Legacy and generic requests produced different sigs.', - ) - }) + ); + }); for (let i = 0; i < numIter; i++) { it(`Should test random payloads - #${i}`, async () => { - const fwConstants = client.getFwConstants() + const fwConstants = client.getFwConstants(); const req = getReq({ hashType: Constants.SIGNING.HASHES.KECCAK256, curveType: Constants.SIGNING.CURVES.SECP256K1, payload: prandomBuf(prng, fwConstants.genericSigning.baseDataSz), - }) + }); // 1. Secp256k1/keccak256 - await runGeneric(req, client) + await runGeneric(req, client); // 2. Secp256k1/sha256 - req.data.hashType = Constants.SIGNING.HASHES.SHA256 - await runGeneric(req, client) + req.data.hashType = Constants.SIGNING.HASHES.SHA256; + await runGeneric(req, client); // 3. Ed25519 - req.data.curveType = Constants.SIGNING.CURVES.ED25519 - req.data.hashType = Constants.SIGNING.HASHES.NONE - req.data.signerPath = DEFAULT_SIGNER.slice(0, 3) - await runGeneric(req, client) - }) + req.data.curveType = Constants.SIGNING.CURVES.ED25519; + req.data.hashType = Constants.SIGNING.HASHES.NONE; + req.data.signerPath = DEFAULT_SIGNER.slice(0, 3); + await runGeneric(req, client); + }); } -}) +}); diff --git a/packages/sdk/src/__test__/e2e/signing/vectors.ts b/packages/sdk/src/__test__/e2e/signing/vectors.ts index d80b6dbd..752df529 100644 --- a/packages/sdk/src/__test__/e2e/signing/vectors.ts +++ b/packages/sdk/src/__test__/e2e/signing/vectors.ts @@ -33,12 +33,12 @@ * that a hardware wallet needs to handle correctly. */ -import type { TransactionSerializable } from 'viem' +import type { TransactionSerializable } from 'viem'; export interface TestVector { - name: string - tx: TransactionSerializable - category?: string + name: string; + tx: TransactionSerializable; + category?: string; } // ============================================================================= @@ -116,7 +116,7 @@ export const LEGACY_VECTORS: TestVector[] = [ }, category: 'polygon', }, -] +]; // ============================================================================= // EIP-1559 TRANSACTION VECTORS (Fee Market) @@ -210,7 +210,7 @@ export const EIP1559_TEST_VECTORS: TestVector[] = [ }, category: 'polygon', }, -] +]; // ============================================================================= // EIP-2930 TRANSACTION VECTORS (Access Lists) @@ -323,7 +323,7 @@ export const EIP2930_TEST_VECTORS: TestVector[] = [ }, category: 'defi-complex', }, -] +]; // ============================================================================= // EIP-7702 TRANSACTION VECTORS (Account Abstraction) @@ -411,7 +411,7 @@ export const EIP7702_TEST_VECTORS: TestVector[] = [ }, category: 'high-value-auth', }, -] +]; // ============================================================================= // EDGE CASES & BOUNDARY CONDITIONS @@ -713,7 +713,7 @@ export const EDGE_CASE_TEST_VECTORS: TestVector[] = [ }, category: 'eip1559-with-access-list', }, -] +]; // ============================================================================= // COMPREHENSIVE DETERMINISTIC TEST VECTORS @@ -782,7 +782,7 @@ export const DERIVATION_PATH_VECTORS: TestVector[] = [ }, category: 'derivation-path-1', }, -] +]; /** * Test specific network configurations that are commonly used @@ -833,7 +833,7 @@ export const NETWORK_SPECIFIC_VECTORS: TestVector[] = [ }, category: 'sepolia', }, -] +]; /** * Test payload size boundaries to ensure proper handling of large transactions @@ -914,7 +914,7 @@ export const PAYLOAD_SIZE_VECTORS: TestVector[] = [ }, category: 'very-large-data', }, -] +]; /** * Comprehensive boundary condition test vectors @@ -979,7 +979,7 @@ export const BOUNDARY_CONDITION_VECTORS: TestVector[] = [ }, category: 'max-gas', }, -] +]; /** * Test specific transaction patterns from real-world usage @@ -1030,7 +1030,7 @@ export const REAL_WORLD_PATTERN_VECTORS: TestVector[] = [ }, category: 'multi-send', }, -] +]; /** * All comprehensive test vectors combined for easy access @@ -1046,7 +1046,7 @@ export const ALL_COMPREHENSIVE_VECTORS: TestVector[] = [ ...PAYLOAD_SIZE_VECTORS, ...BOUNDARY_CONDITION_VECTORS, ...REAL_WORLD_PATTERN_VECTORS, -] +]; /** * Get vectors by category for targeted testing @@ -1054,24 +1054,24 @@ export const ALL_COMPREHENSIVE_VECTORS: TestVector[] = [ export function getVectorsByCategory(category: string): TestVector[] { return ALL_COMPREHENSIVE_VECTORS.filter( (vector) => vector.category === category, - ) + ); } /** * Get a specific number of vectors from each transaction type for balanced testing */ export function getBalancedTestVectors(perType = 3): TestVector[] { - const legacyVectors = LEGACY_VECTORS.slice(0, perType) - const eip1559Vectors = EIP1559_TEST_VECTORS.slice(0, perType) - const eip2930Vectors = EIP2930_TEST_VECTORS.slice(0, perType) - const eip7702Vectors = EIP7702_TEST_VECTORS.slice(0, perType) + const legacyVectors = LEGACY_VECTORS.slice(0, perType); + const eip1559Vectors = EIP1559_TEST_VECTORS.slice(0, perType); + const eip2930Vectors = EIP2930_TEST_VECTORS.slice(0, perType); + const eip7702Vectors = EIP7702_TEST_VECTORS.slice(0, perType); return [ ...legacyVectors, ...eip1559Vectors, ...eip2930Vectors, ...eip7702Vectors, - ] + ]; } /** @@ -1082,7 +1082,7 @@ export function getBoundaryTestVectors(): TestVector[] { ...BOUNDARY_CONDITION_VECTORS, ...EDGE_CASE_TEST_VECTORS, ...PAYLOAD_SIZE_VECTORS, - ] + ]; } /** @@ -1093,5 +1093,5 @@ export function getNetworkTestVectors(): TestVector[] { ...NETWORK_SPECIFIC_VECTORS, // Add some edge cases with different networks ...EDGE_CASE_TEST_VECTORS.filter((v) => v.category?.includes('chain')), - ] + ]; } diff --git a/packages/sdk/src/__test__/e2e/solana/addresses.test.ts b/packages/sdk/src/__test__/e2e/solana/addresses.test.ts index 5652ee5a..f6b55b3d 100644 --- a/packages/sdk/src/__test__/e2e/solana/addresses.test.ts +++ b/packages/sdk/src/__test__/e2e/solana/addresses.test.ts @@ -1,44 +1,44 @@ -import { PublicKey } from '@solana/web3.js' -import { question } from 'readline-sync' -import { pair } from '../../../api' -import { fetchSolanaAddresses } from '../../../api/addresses' -import { setupClient } from '../../utils/setup' +import { PublicKey } from '@solana/web3.js'; +import { question } from 'readline-sync'; +import { pair } from '../../../api'; +import { fetchSolanaAddresses } from '../../../api/addresses'; +import { setupClient } from '../../utils/setup'; describe('Solana Addresses', () => { test('pair', async () => { - const isPaired = await setupClient() + const isPaired = await setupClient(); if (!isPaired) { - const secret = question('Please enter the pairing secret: ') - await pair(secret.toUpperCase()) + const secret = question('Please enter the pairing secret: '); + await pair(secret.toUpperCase()); } - }) + }); test('Should fetch a single Solana Ed25519 public key using fetchSolanaAddresses', async () => { const addresses = await fetchSolanaAddresses({ n: 10, - }) + }); const addrs = addresses .filter((addr) => { try { // Check if the key is a valid Ed25519 public key - const pk = new PublicKey(addr) - const isOnCurve = PublicKey.isOnCurve(pk.toBytes()) - expect(isOnCurve).toBe(true) - return true + const pk = new PublicKey(addr); + const isOnCurve = PublicKey.isOnCurve(pk.toBytes()); + expect(isOnCurve).toBe(true); + return true; } catch (e) { - console.error('Invalid Solana public key:', e) - return false + console.error('Invalid Solana public key:', e); + return false; } }) .map((addr) => { - const pk = new PublicKey(addr) - return pk.toBase58() - }) + const pk = new PublicKey(addr); + return pk.toBase58(); + }); // Ensure we got at least one valid address - expect(addrs.length).toBeGreaterThan(0) + expect(addrs.length).toBeGreaterThan(0); // Ensure none of the addresses start with '11111' - expect(addrs.every((addr) => !addr.startsWith('11111'))).toBe(true) - }) -}) + expect(addrs.every((addr) => !addr.startsWith('11111'))).toBe(true); + }); +}); diff --git a/packages/sdk/src/__test__/e2e/xpub.test.ts b/packages/sdk/src/__test__/e2e/xpub.test.ts index d4e8b5d4..b57802b7 100644 --- a/packages/sdk/src/__test__/e2e/xpub.test.ts +++ b/packages/sdk/src/__test__/e2e/xpub.test.ts @@ -1,29 +1,29 @@ -import { fetchBtcXpub, fetchBtcYpub, fetchBtcZpub } from '../../api' -import { setupClient } from '../utils/setup' +import { fetchBtcXpub, fetchBtcYpub, fetchBtcZpub } from '../../api'; +import { setupClient } from '../utils/setup'; describe('XPUB', () => { beforeAll(async () => { - await setupClient() - }) + await setupClient(); + }); test('fetchBtcXpub returns xpub', async () => { - const xpub = await fetchBtcXpub() - expect(xpub).toBeTruthy() - expect(xpub.startsWith('xpub')).toBe(true) - expect(xpub.length).toBeGreaterThan(100) - }) + const xpub = await fetchBtcXpub(); + expect(xpub).toBeTruthy(); + expect(xpub.startsWith('xpub')).toBe(true); + expect(xpub.length).toBeGreaterThan(100); + }); test('fetchBtcYpub returns ypub', async () => { - const ypub = await fetchBtcYpub() - expect(ypub).toBeTruthy() - expect(ypub.startsWith('ypub')).toBe(true) - expect(ypub.length).toBeGreaterThan(100) - }) + const ypub = await fetchBtcYpub(); + expect(ypub).toBeTruthy(); + expect(ypub.startsWith('ypub')).toBe(true); + expect(ypub.length).toBeGreaterThan(100); + }); test('fetchBtcZpub returns zpub', async () => { - const zpub = await fetchBtcZpub() - expect(zpub).toBeTruthy() - expect(zpub.startsWith('zpub')).toBe(true) - expect(zpub.length).toBeGreaterThan(100) - }) -}) + const zpub = await fetchBtcZpub(); + expect(zpub).toBeTruthy(); + expect(zpub.startsWith('zpub')).toBe(true); + expect(zpub.length).toBeGreaterThan(100); + }); +}); diff --git a/packages/sdk/src/__test__/integration/__mocks__/4byte.ts b/packages/sdk/src/__test__/integration/__mocks__/4byte.ts index ebb80cee..4bdb39f8 100644 --- a/packages/sdk/src/__test__/integration/__mocks__/4byte.ts +++ b/packages/sdk/src/__test__/integration/__mocks__/4byte.ts @@ -29,7 +29,7 @@ export const fourbyteResponse0xa9059cbb = { text_signature: 'func_2093253501(bytes)', }, ], -} +}; export const fourbyteResponse0x38ed1739 = { results: [ @@ -42,7 +42,7 @@ export const fourbyteResponse0x38ed1739 = { 'swapExactTokensForTokens(uint256,uint256,address[],address,uint256)', }, ], -} +}; export const fourbyteResponseac9650d8 = { results: [ @@ -54,7 +54,7 @@ export const fourbyteResponseac9650d8 = { bytes_signature: '¬\x96PØ', }, ], -} +}; export const fourbyteResponse0c49ccbe = { results: [ @@ -67,7 +67,7 @@ export const fourbyteResponse0c49ccbe = { bytes_signature: '\\fI̾', }, ], -} +}; export const fourbyteResponsefc6f7865 = { results: [ @@ -79,7 +79,7 @@ export const fourbyteResponsefc6f7865 = { bytes_signature: 'üoxe', }, ], -} +}; export const fourbyteResponse0x6a761202 = { results: [ @@ -92,4 +92,4 @@ export const fourbyteResponse0x6a761202 = { bytes_signature: 'jv\\u0012\\u0002', }, ], -} +}; diff --git a/packages/sdk/src/__test__/integration/__mocks__/etherscan.ts b/packages/sdk/src/__test__/integration/__mocks__/etherscan.ts index b206043c..4cdd2629 100644 --- a/packages/sdk/src/__test__/integration/__mocks__/etherscan.ts +++ b/packages/sdk/src/__test__/integration/__mocks__/etherscan.ts @@ -69,7 +69,7 @@ export const etherscanResponse0xa0b86991 = [ name: 'Upgraded', type: 'event', }, -] +]; export const etherscanResponse0x7a250d56 = [ { @@ -1043,7 +1043,7 @@ export const etherscanResponse0x7a250d56 = [ stateMutability: 'payable', type: 'receive', }, -] +]; export const etherscanResponse0xc36442b6 = [ { @@ -2267,7 +2267,7 @@ export const etherscanResponse0xc36442b6 = [ stateMutability: 'payable', type: 'receive', }, -] +]; export const etherscanResponse0x06412d7e = [ { @@ -2871,4 +2871,4 @@ export const etherscanResponse0x06412d7e = [ type: 'function', }, { stateMutability: 'payable', type: 'receive' }, -] +]; diff --git a/packages/sdk/src/__test__/integration/__mocks__/handlers.ts b/packages/sdk/src/__test__/integration/__mocks__/handlers.ts index 495c59d4..80e26132 100644 --- a/packages/sdk/src/__test__/integration/__mocks__/handlers.ts +++ b/packages/sdk/src/__test__/integration/__mocks__/handlers.ts @@ -1,4 +1,4 @@ -import { http, HttpResponse } from 'msw' +import { http, HttpResponse } from 'msw'; import { fourbyteResponse0c49ccbe, fourbyteResponse0x6a761202, @@ -6,94 +6,94 @@ import { fourbyteResponse0xa9059cbb, fourbyteResponseac9650d8, fourbyteResponsefc6f7865, -} from './4byte' -import addKvRecordsResponse from './addKvRecords.json' -import connectResponse from './connect.json' +} from './4byte'; +import addKvRecordsResponse from './addKvRecords.json'; +import connectResponse from './connect.json'; import { etherscanResponse0x06412d7e, etherscanResponse0x7a250d56, etherscanResponse0xa0b86991, etherscanResponse0xc36442b6, -} from './etherscan' -import fetchActiveWalletResponse from './fetchActiveWallet.json' -import getAddressesResponse from './getAddresses.json' -import getKvRecordsResponse from './getKvRecords.json' -import removeKvRecordsResponse from './removeKvRecords.json' -import signResponse from './sign.json' +} from './etherscan'; +import fetchActiveWalletResponse from './fetchActiveWallet.json'; +import getAddressesResponse from './getAddresses.json'; +import getKvRecordsResponse from './getKvRecords.json'; +import removeKvRecordsResponse from './removeKvRecords.json'; +import signResponse from './sign.json'; export const handlers = [ http.post('https://signing.gridpl.us/test/connect', () => { - return HttpResponse.json(connectResponse) + return HttpResponse.json(connectResponse); }), http.post('https://signing.gridpl.us/test/getAddresses', () => { - return HttpResponse.json(getAddressesResponse) + return HttpResponse.json(getAddressesResponse); }), http.post('https://signing.gridpl.us/test/sign', () => { - return HttpResponse.json(signResponse) + return HttpResponse.json(signResponse); }), http.post('https://signing.gridpl.us/test/fetchActiveWallet', () => { - return HttpResponse.json(fetchActiveWalletResponse) + return HttpResponse.json(fetchActiveWalletResponse); }), http.post('https://signing.gridpl.us/test/addKvRecords', () => { - return HttpResponse.json(addKvRecordsResponse) + return HttpResponse.json(addKvRecordsResponse); }), http.post('https://signing.gridpl.us/test/getKvRecords', () => { - return HttpResponse.json(getKvRecordsResponse) + return HttpResponse.json(getKvRecordsResponse); }), http.post('https://signing.gridpl.us/test/removeKvRecords', () => { - return HttpResponse.json(removeKvRecordsResponse) + return HttpResponse.json(removeKvRecordsResponse); }), http.get('https://api.etherscan.io/api', ({ request }) => { - const url = new URL(request.url) - const module = url.searchParams.get('module') - const action = url.searchParams.get('action') - const address = url.searchParams.get('address') + const url = new URL(request.url); + const module = url.searchParams.get('module'); + const action = url.searchParams.get('action'); + const address = url.searchParams.get('address'); if (module === 'contract' && action === 'getabi') { if (address === '0xa0b86991c6218b36c1d19d4a2e9eb0ce3606eb48') { return HttpResponse.json({ result: JSON.stringify(etherscanResponse0xa0b86991), - }) + }); } if (address === '0x7a250d5630b4cf539739df2c5dacb4c659f2488d') { return HttpResponse.json({ result: JSON.stringify(etherscanResponse0x7a250d56), - }) + }); } if (address === '0xc36442b4a4522e871399cd717abdd847ab11fe88') { return HttpResponse.json({ result: JSON.stringify(etherscanResponse0xc36442b6), - }) + }); } if (address === '0x06412d7ebfbf66c25607e2ed24c1d207043be327') { return HttpResponse.json({ result: JSON.stringify(etherscanResponse0x06412d7e), - }) + }); } } - return new HttpResponse(null, { status: 404 }) + return new HttpResponse(null, { status: 404 }); }), http.get('https://www.4byte.directory/api/v1/signatures', ({ request }) => { - const url = new URL(request.url) - const hexSignature = url.searchParams.get('hex_signature') + const url = new URL(request.url); + const hexSignature = url.searchParams.get('hex_signature'); if (hexSignature === '0xa9059cbb') { - return HttpResponse.json(fourbyteResponse0xa9059cbb) + return HttpResponse.json(fourbyteResponse0xa9059cbb); } if (hexSignature === '0x38ed1739') { - return HttpResponse.json(fourbyteResponse0x38ed1739) + return HttpResponse.json(fourbyteResponse0x38ed1739); } if (hexSignature === '0xac9650d8') { - return HttpResponse.json(fourbyteResponseac9650d8) + return HttpResponse.json(fourbyteResponseac9650d8); } if (hexSignature === '0x0c49ccbe') { - return HttpResponse.json(fourbyteResponse0c49ccbe) + return HttpResponse.json(fourbyteResponse0c49ccbe); } if (hexSignature === '0xfc6f7865') { - return HttpResponse.json(fourbyteResponsefc6f7865) + return HttpResponse.json(fourbyteResponsefc6f7865); } if (hexSignature === '0x6a761202') { - return HttpResponse.json(fourbyteResponse0x6a761202) + return HttpResponse.json(fourbyteResponse0x6a761202); } - return new HttpResponse(null, { status: 404 }) + return new HttpResponse(null, { status: 404 }); }), -] +]; diff --git a/packages/sdk/src/__test__/integration/__mocks__/server.ts b/packages/sdk/src/__test__/integration/__mocks__/server.ts index 5dc2fb14..573814cc 100644 --- a/packages/sdk/src/__test__/integration/__mocks__/server.ts +++ b/packages/sdk/src/__test__/integration/__mocks__/server.ts @@ -1,5 +1,5 @@ -import { setupServer } from 'msw/node' -import { handlers } from './handlers' +import { setupServer } from 'msw/node'; +import { handlers } from './handlers'; // This configures a request mocking server with the given request handlers. -export const server = setupServer(...handlers) +export const server = setupServer(...handlers); diff --git a/packages/sdk/src/__test__/integration/__mocks__/setup.ts b/packages/sdk/src/__test__/integration/__mocks__/setup.ts index 01d96008..e0f2a27b 100644 --- a/packages/sdk/src/__test__/integration/__mocks__/setup.ts +++ b/packages/sdk/src/__test__/integration/__mocks__/setup.ts @@ -1,7 +1,7 @@ -import { server } from './server' +import { server } from './server'; export const setup = () => { - beforeAll(() => server.listen({ onUnhandledRequest: 'bypass' })) - afterAll(() => server.close()) - afterEach(() => server.resetHandlers()) -} + beforeAll(() => server.listen({ onUnhandledRequest: 'bypass' })); + afterAll(() => server.close()); + afterEach(() => server.resetHandlers()); +}; diff --git a/packages/sdk/src/__test__/integration/client.interop.test.ts b/packages/sdk/src/__test__/integration/client.interop.test.ts index b438e206..defe7638 100644 --- a/packages/sdk/src/__test__/integration/client.interop.test.ts +++ b/packages/sdk/src/__test__/integration/client.interop.test.ts @@ -1,23 +1,23 @@ -import { fetchActiveWallets, setup } from '../../api' -import { getDeviceId } from '../utils/getters' -import { setupTestClient } from '../utils/helpers' -import { getStoredClient, setStoredClient } from '../utils/setup' +import { fetchActiveWallets, setup } from '../../api'; +import { getDeviceId } from '../utils/getters'; +import { setupTestClient } from '../utils/helpers'; +import { getStoredClient, setStoredClient } from '../utils/setup'; /** * This test is used to test the interoperability between the Class-based API and the Functional API. */ describe.skip('client interop', () => { it('should setup the Client, then use that client data to', async () => { - const client = setupTestClient() - const isPaired = await client.connect(getDeviceId()) - expect(isPaired).toBe(true) + const client = setupTestClient(); + const isPaired = await client.connect(getDeviceId()); + expect(isPaired).toBe(true); await setup({ getStoredClient, setStoredClient, - }) + }); - const activeWallets = await fetchActiveWallets() - expect(activeWallets).toBeTruthy() - }) -}) + const activeWallets = await fetchActiveWallets(); + expect(activeWallets).toBeTruthy(); + }); +}); diff --git a/packages/sdk/src/__test__/integration/connect.test.ts b/packages/sdk/src/__test__/integration/connect.test.ts index 26f3568c..29ce587c 100644 --- a/packages/sdk/src/__test__/integration/connect.test.ts +++ b/packages/sdk/src/__test__/integration/connect.test.ts @@ -1,72 +1,72 @@ -import { HARDENED_OFFSET } from '../../constants' -import { buildEthSignRequest } from '../utils/builders' -import { getDeviceId } from '../utils/getters' -import { BTC_PURPOSE_P2PKH, ETH_COIN, setupTestClient } from '../utils/helpers' +import { HARDENED_OFFSET } from '../../constants'; +import { buildEthSignRequest } from '../utils/builders'; +import { getDeviceId } from '../utils/getters'; +import { BTC_PURPOSE_P2PKH, ETH_COIN, setupTestClient } from '../utils/helpers'; describe('connect', () => { it('should test connect', async () => { - const client = setupTestClient() - const isPaired = await client.connect(getDeviceId()) - expect(isPaired).toMatchSnapshot() - }) + const client = setupTestClient(); + const isPaired = await client.connect(getDeviceId()); + expect(isPaired).toMatchSnapshot(); + }); it('should test fetchActiveWallet', async () => { - const client = setupTestClient() - await client.connect(getDeviceId()) - await client.fetchActiveWallet() - }) + const client = setupTestClient(); + await client.connect(getDeviceId()); + await client.fetchActiveWallet(); + }); it('should test getAddresses', async () => { - const client = setupTestClient() - await client.connect(getDeviceId()) + const client = setupTestClient(); + await client.connect(getDeviceId()); - const startPath = [BTC_PURPOSE_P2PKH, ETH_COIN, HARDENED_OFFSET, 0, 0] + const startPath = [BTC_PURPOSE_P2PKH, ETH_COIN, HARDENED_OFFSET, 0, 0]; - const addrs = await client.getAddresses({ startPath, n: 1 }) - expect(addrs).toMatchSnapshot() - }) + const addrs = await client.getAddresses({ startPath, n: 1 }); + expect(addrs).toMatchSnapshot(); + }); it('should test sign', async () => { - const client = setupTestClient() - await client.connect(getDeviceId()) + const client = setupTestClient(); + await client.connect(getDeviceId()); - const { req } = await buildEthSignRequest(client) - const signData = await client.sign(req) - expect(signData).toMatchSnapshot() - }) + const { req } = await buildEthSignRequest(client); + const signData = await client.sign(req); + expect(signData).toMatchSnapshot(); + }); it('should test fetchActiveWallet', async () => { - const client = setupTestClient() - await client.connect(getDeviceId()) + const client = setupTestClient(); + await client.connect(getDeviceId()); - const activeWallet = await client.fetchActiveWallet() - expect(activeWallet).toMatchSnapshot() - }) + const activeWallet = await client.fetchActiveWallet(); + expect(activeWallet).toMatchSnapshot(); + }); it('should test getKvRecords', async () => { - const client = setupTestClient() - await client.connect(getDeviceId()) + const client = setupTestClient(); + await client.connect(getDeviceId()); - const activeWallet = await client.getKvRecords({ start: 0 }) - expect(activeWallet).toMatchSnapshot() - }) + const activeWallet = await client.getKvRecords({ start: 0 }); + expect(activeWallet).toMatchSnapshot(); + }); it('should test addKvRecords', async () => { - const client = setupTestClient() - await client.connect(getDeviceId()) + const client = setupTestClient(); + await client.connect(getDeviceId()); const activeWallet = await client.addKvRecords({ records: { test2: 'test2' }, - }) - expect(activeWallet).toMatchSnapshot() - }) + }); + expect(activeWallet).toMatchSnapshot(); + }); it('should test removeKvRecords', async () => { - const client = setupTestClient() - await client.connect(getDeviceId()) - await client.addKvRecords({ records: { test: `${Math.random()}` } }) - const { records } = await client.getKvRecords({ start: 0 }) + const client = setupTestClient(); + await client.connect(getDeviceId()); + await client.addKvRecords({ records: { test: `${Math.random()}` } }); + const { records } = await client.getKvRecords({ start: 0 }); const activeWallet = await client.removeKvRecords({ ids: records.map((r) => `${r.id}`), - }) - expect(activeWallet).toMatchSnapshot() - }) -}) + }); + expect(activeWallet).toMatchSnapshot(); + }); +}); diff --git a/packages/sdk/src/__test__/integration/fetchCalldataDecoder.test.ts b/packages/sdk/src/__test__/integration/fetchCalldataDecoder.test.ts index fc873fe4..e5abe6f4 100644 --- a/packages/sdk/src/__test__/integration/fetchCalldataDecoder.test.ts +++ b/packages/sdk/src/__test__/integration/fetchCalldataDecoder.test.ts @@ -1,107 +1,107 @@ -import { fetchCalldataDecoder } from '../../util' -import { setup as setupMockServiceWorker } from './__mocks__/setup' +import { fetchCalldataDecoder } from '../../util'; +import { setup as setupMockServiceWorker } from './__mocks__/setup'; describe('fetchCalldataDecoder', () => { // Mocks out responses from Etherscan and 4byte - setupMockServiceWorker() + setupMockServiceWorker(); beforeAll(() => { // Disable this mock to restore console logs when updating tests - console.warn = vi.fn() - }) + console.warn = vi.fn(); + }); afterAll(() => { - vi.clearAllMocks() - }) + vi.clearAllMocks(); + }); test('decode calldata', async () => { const data = - '0x38ed17390000000000000000000000000000000000000000000c1c173c5b782a5b154ab900000000000000000000000000000000000000000000000f380d77022fe8c32600000000000000000000000000000000000000000000000000000000000000a00000000000000000000000007ae7684581f0298241c3d6a6567a48d56b42b15c00000000000000000000000000000000000000000000000000000000622f8d27000000000000000000000000000000000000000000000000000000000000000300000000000000000000000095ad61b0a150d79219dcf64e1e6cc01f0b64c4ce000000000000000000000000c02aaa39b223fe8d0a0e5c4f27ead9083c756cc200000000000000000000000050522c769e01eb06c02bd299066509d8f97a69ae' - const to = '0x7a250d5630b4cf539739df2c5dacb4c659f2488d' - const decoded = await fetchCalldataDecoder(data, to, '1') - expect(decoded).toMatchSnapshot() - }) + '0x38ed17390000000000000000000000000000000000000000000c1c173c5b782a5b154ab900000000000000000000000000000000000000000000000f380d77022fe8c32600000000000000000000000000000000000000000000000000000000000000a00000000000000000000000007ae7684581f0298241c3d6a6567a48d56b42b15c00000000000000000000000000000000000000000000000000000000622f8d27000000000000000000000000000000000000000000000000000000000000000300000000000000000000000095ad61b0a150d79219dcf64e1e6cc01f0b64c4ce000000000000000000000000c02aaa39b223fe8d0a0e5c4f27ead9083c756cc200000000000000000000000050522c769e01eb06c02bd299066509d8f97a69ae'; + const to = '0x7a250d5630b4cf539739df2c5dacb4c659f2488d'; + const decoded = await fetchCalldataDecoder(data, to, '1'); + expect(decoded).toMatchSnapshot(); + }); test('decode calldata as Buffer', async () => { const data = Buffer.from( '38ed17390000000000000000000000000000000000000000000c1c173c5b782a5b154ab900000000000000000000000000000000000000000000000f380d77022fe8c32600000000000000000000000000000000000000000000000000000000000000a00000000000000000000000007ae7684581f0298241c3d6a6567a48d56b42b15c00000000000000000000000000000000000000000000000000000000622f8d27000000000000000000000000000000000000000000000000000000000000000300000000000000000000000095ad61b0a150d79219dcf64e1e6cc01f0b64c4ce000000000000000000000000c02aaa39b223fe8d0a0e5c4f27ead9083c756cc200000000000000000000000050522c769e01eb06c02bd299066509d8f97a69ae', 'hex', - ) - const to = '0x7a250d5630b4cf539739df2c5dacb4c659f2488d' - const decoded = await fetchCalldataDecoder(data, to, '1') - expect(decoded).toMatchSnapshot() - }) + ); + const to = '0x7a250d5630b4cf539739df2c5dacb4c659f2488d'; + const decoded = await fetchCalldataDecoder(data, to, '1'); + expect(decoded).toMatchSnapshot(); + }); test('decode proxy calldata', async () => { const data = - '0xa9059cbb0000000000000000000000004ffbf741b0a64e8bd1f9d89fc9b5584cc5227b700000000000000000000000000000000000000000000000000000003052aacdb8' - const to = '0xa0b86991c6218b36c1d19d4a2e9eb0ce3606eb48' - const decoded = await fetchCalldataDecoder(data, to, '1') - expect(decoded).toMatchSnapshot() - }) + '0xa9059cbb0000000000000000000000004ffbf741b0a64e8bd1f9d89fc9b5584cc5227b700000000000000000000000000000000000000000000000000000003052aacdb8'; + const to = '0xa0b86991c6218b36c1d19d4a2e9eb0ce3606eb48'; + const decoded = await fetchCalldataDecoder(data, to, '1'); + expect(decoded).toMatchSnapshot(); + }); test('fallback to 4byte', async () => { const data = - '0x38ed17390000000000000000000000000000000000000000000c1c173c5b782a5b154ab900000000000000000000000000000000000000000000000f380d77022fe8c32600000000000000000000000000000000000000000000000000000000000000a00000000000000000000000007ae7684581f0298241c3d6a6567a48d56b42b15c00000000000000000000000000000000000000000000000000000000622f8d27000000000000000000000000000000000000000000000000000000000000000300000000000000000000000095ad61b0a150d79219dcf64e1e6cc01f0b64c4ce000000000000000000000000c02aaa39b223fe8d0a0e5c4f27ead9083c756cc200000000000000000000000050522c769e01eb06c02bd299066509d8f97a69ae' - const to = '0x7a250d5630b4cf539739df2c5dacb4c659f2488d' - const decoded = await fetchCalldataDecoder(data, to, -1) - expect(decoded).toMatchSnapshot() - }) + '0x38ed17390000000000000000000000000000000000000000000c1c173c5b782a5b154ab900000000000000000000000000000000000000000000000f380d77022fe8c32600000000000000000000000000000000000000000000000000000000000000a00000000000000000000000007ae7684581f0298241c3d6a6567a48d56b42b15c00000000000000000000000000000000000000000000000000000000622f8d27000000000000000000000000000000000000000000000000000000000000000300000000000000000000000095ad61b0a150d79219dcf64e1e6cc01f0b64c4ce000000000000000000000000c02aaa39b223fe8d0a0e5c4f27ead9083c756cc200000000000000000000000050522c769e01eb06c02bd299066509d8f97a69ae'; + const to = '0x7a250d5630b4cf539739df2c5dacb4c659f2488d'; + const decoded = await fetchCalldataDecoder(data, to, -1); + expect(decoded).toMatchSnapshot(); + }); test('decode calldata from external chain', async () => { const data = - '0x38ed17390000000000000000000000000000000000000000000c1c173c5b782a5b154ab900000000000000000000000000000000000000000000000f380d77022fe8c32600000000000000000000000000000000000000000000000000000000000000a00000000000000000000000007ae7684581f0298241c3d6a6567a48d56b42b15c00000000000000000000000000000000000000000000000000000000622f8d27000000000000000000000000000000000000000000000000000000000000000300000000000000000000000095ad61b0a150d79219dcf64e1e6cc01f0b64c4ce000000000000000000000000c02aaa39b223fe8d0a0e5c4f27ead9083c756cc200000000000000000000000050522c769e01eb06c02bd299066509d8f97a69ae' - const to = '0xa0b86991c6218b36c1d19d4a2e9eb0ce3606eb48' - const decoded = await fetchCalldataDecoder(data, to, '1337') - expect(decoded).toMatchSnapshot() - }) + '0x38ed17390000000000000000000000000000000000000000000c1c173c5b782a5b154ab900000000000000000000000000000000000000000000000f380d77022fe8c32600000000000000000000000000000000000000000000000000000000000000a00000000000000000000000007ae7684581f0298241c3d6a6567a48d56b42b15c00000000000000000000000000000000000000000000000000000000622f8d27000000000000000000000000000000000000000000000000000000000000000300000000000000000000000095ad61b0a150d79219dcf64e1e6cc01f0b64c4ce000000000000000000000000c02aaa39b223fe8d0a0e5c4f27ead9083c756cc200000000000000000000000050522c769e01eb06c02bd299066509d8f97a69ae'; + const to = '0xa0b86991c6218b36c1d19d4a2e9eb0ce3606eb48'; + const decoded = await fetchCalldataDecoder(data, to, '1337'); + expect(decoded).toMatchSnapshot(); + }); test('decode nested calldata: multicall(bytes[])', async () => { const data = - '0xac9650d8000000000000000000000000000000000000000000000000000000000000002000000000000000000000000000000000000000000000000000000000000000020000000000000000000000000000000000000000000000000000000000000040000000000000000000000000000000000000000000000000000000000000012000000000000000000000000000000000000000000000000000000000000000a40c49ccbe000000000000000000000000000000000000000000000000000000000002d6da00000000000000000000000000000000000000000000000000000b100a58bd63000000000000000000000000000000000000000000000000000016ac77cb53fe00000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000061ef0508000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000084fc6f7865000000000000000000000000000000000000000000000000000000000002d6da0000000000000000000000004ce6aea89f059915ae5efbf34a2a8adc544ae09e00000000000000000000000000000000ffffffffffffffffffffffffffffffff00000000000000000000000000000000ffffffffffffffffffffffffffffffff00000000000000000000000000000000000000000000000000000000' - const to = '0xc36442b4a4522e871399cd717abdd847ab11fe88' - const { def } = await fetchCalldataDecoder(data, to, '1', true) - expect({ def }).toMatchSnapshot() - }) + '0xac9650d8000000000000000000000000000000000000000000000000000000000000002000000000000000000000000000000000000000000000000000000000000000020000000000000000000000000000000000000000000000000000000000000040000000000000000000000000000000000000000000000000000000000000012000000000000000000000000000000000000000000000000000000000000000a40c49ccbe000000000000000000000000000000000000000000000000000000000002d6da00000000000000000000000000000000000000000000000000000b100a58bd63000000000000000000000000000000000000000000000000000016ac77cb53fe00000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000061ef0508000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000084fc6f7865000000000000000000000000000000000000000000000000000000000002d6da0000000000000000000000004ce6aea89f059915ae5efbf34a2a8adc544ae09e00000000000000000000000000000000ffffffffffffffffffffffffffffffff00000000000000000000000000000000ffffffffffffffffffffffffffffffff00000000000000000000000000000000000000000000000000000000'; + const to = '0xc36442b4a4522e871399cd717abdd847ab11fe88'; + const { def } = await fetchCalldataDecoder(data, to, '1', true); + expect({ def }).toMatchSnapshot(); + }); test('decode nested calldata: execTransaction(address,uint256,(multicall(bytes[])),uint8,uint256,uint256,uint256,address,address,bytes)', async () => { const data = - '0x6a761202000000000000000000000000c36442b4a4522e871399cd717abdd847ab11fe880000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000014000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000036e3f000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000003a00000000000000000000000000000000000000000000000000000000000000224ac9650d8000000000000000000000000000000000000000000000000000000000000002000000000000000000000000000000000000000000000000000000000000000020000000000000000000000000000000000000000000000000000000000000040000000000000000000000000000000000000000000000000000000000000012000000000000000000000000000000000000000000000000000000000000000a40c49ccbe0000000000000000000000000000000000000000000000000000000000042677000000000000000000000000000000000000000000000000002223dbc72b15a70000000000000000000000000000000000000000000000000000014f8d4596e400000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000062e90b1b000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000084fc6f7865000000000000000000000000000000000000000000000000000000000004267700000000000000000000000006412d7ebfbf66c25607e2ed24c1d207043be32700000000000000000000000000000000ffffffffffffffffffffffffffffffff00000000000000000000000000000000ffffffffffffffffffffffffffffffff000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000c351d12bf187b0b2cb69bcf58076f8ce9197ebc4ff5fbb97ca5f36d296cfc55ffe4e62e761ab9d1a5d27577d76dc18f1e851e6ec87a239bedcbbef8bb52bd811501bc23b15b14d797a0cc444b9ab5e0a9340b8f1341210778446c948f5120b282b105ae55d0aef0bf62af4e614d42a9c99b2724272486433a191ed16ecaaab81dab61c0000000000000000000000009789d9d99409bf01699b8988da8886647418998e0000000000000000000000000000000000000000000000000000000000000000010000000000000000000000000000000000000000000000000000000000' - const to = '0x06412d7ebfbf66c25607e2ed24c1d207043be327' - const { def } = await fetchCalldataDecoder(data, to, '1', true) - expect({ def }).toMatchSnapshot() - }) + '0x6a761202000000000000000000000000c36442b4a4522e871399cd717abdd847ab11fe880000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000014000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000036e3f000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000003a00000000000000000000000000000000000000000000000000000000000000224ac9650d8000000000000000000000000000000000000000000000000000000000000002000000000000000000000000000000000000000000000000000000000000000020000000000000000000000000000000000000000000000000000000000000040000000000000000000000000000000000000000000000000000000000000012000000000000000000000000000000000000000000000000000000000000000a40c49ccbe0000000000000000000000000000000000000000000000000000000000042677000000000000000000000000000000000000000000000000002223dbc72b15a70000000000000000000000000000000000000000000000000000014f8d4596e400000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000062e90b1b000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000084fc6f7865000000000000000000000000000000000000000000000000000000000004267700000000000000000000000006412d7ebfbf66c25607e2ed24c1d207043be32700000000000000000000000000000000ffffffffffffffffffffffffffffffff00000000000000000000000000000000ffffffffffffffffffffffffffffffff000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000c351d12bf187b0b2cb69bcf58076f8ce9197ebc4ff5fbb97ca5f36d296cfc55ffe4e62e761ab9d1a5d27577d76dc18f1e851e6ec87a239bedcbbef8bb52bd811501bc23b15b14d797a0cc444b9ab5e0a9340b8f1341210778446c948f5120b282b105ae55d0aef0bf62af4e614d42a9c99b2724272486433a191ed16ecaaab81dab61c0000000000000000000000009789d9d99409bf01699b8988da8886647418998e0000000000000000000000000000000000000000000000000000000000000000010000000000000000000000000000000000000000000000000000000000'; + const to = '0x06412d7ebfbf66c25607e2ed24c1d207043be327'; + const { def } = await fetchCalldataDecoder(data, to, '1', true); + expect({ def }).toMatchSnapshot(); + }); test('handle too short data', async () => { - const data = '0x001' - const to = '0xa0b86991c6218b36c1d19d4a2e9eb0ce3606eb48' - const decoded = await fetchCalldataDecoder(data, to, '1') + const data = '0x001'; + const to = '0xa0b86991c6218b36c1d19d4a2e9eb0ce3606eb48'; + const decoded = await fetchCalldataDecoder(data, to, '1'); expect(decoded).toMatchInlineSnapshot(` { "abi": null, "def": null, } - `) - }) + `); + }); test('handle no data', async () => { - const data = '' - const to = '0xa0b86991c6218b36c1d19d4a2e9eb0ce3606eb48' - const decoded = await fetchCalldataDecoder(data, to, '1') + const data = ''; + const to = '0xa0b86991c6218b36c1d19d4a2e9eb0ce3606eb48'; + const decoded = await fetchCalldataDecoder(data, to, '1'); expect(decoded).toMatchInlineSnapshot(` { "abi": null, "def": null, } - `) - }) + `); + }); // TODO: add api key to fix this test test.skip('decode Celo calldata', async () => { const data = - '0xf2fde38b000000000000000000000000b538e8dcd297450bdef46222f3ceb33bb1e921b3' - const to = '0x96d59127ccd1c0e3749e733ee04f0dfbd2f808c8' - const decoded = await fetchCalldataDecoder(data, to, '42220') - expect(decoded).toMatchSnapshot() - }) -}) + '0xf2fde38b000000000000000000000000b538e8dcd297450bdef46222f3ceb33bb1e921b3'; + const to = '0x96d59127ccd1c0e3749e733ee04f0dfbd2f808c8'; + const decoded = await fetchCalldataDecoder(data, to, '42220'); + expect(decoded).toMatchSnapshot(); + }); +}); diff --git a/packages/sdk/src/__test__/unit/__mocks__/decoderData.ts b/packages/sdk/src/__test__/unit/__mocks__/decoderData.ts index 2a761f79..faec69c6 100644 --- a/packages/sdk/src/__test__/unit/__mocks__/decoderData.ts +++ b/packages/sdk/src/__test__/unit/__mocks__/decoderData.ts @@ -6,30 +6,30 @@ import { LatticeEncDataSchema, LatticeGetAddressesFlag, -} from '../../../protocol' -import { getP256KeyPair } from '../../../util' +} from '../../../protocol'; +import { getP256KeyPair } from '../../../util'; export const clientKeyPair = getP256KeyPair( Buffer.from( '3fb53b677f73e4d2b8c89c303f6f6b349f0075ad88ea126cb9f6632085815dca', 'hex', ), -) +); export const decoderTestsFwConstants = JSON.parse( '{"extraDataFrameSz":1500,"extraDataMaxFrames":1,"genericSigning":{"baseReqSz":1552,"baseDataSz":1519,"hashTypes":{"NONE":0,"KECCAK256":1,"SHA256":2},"curveTypes":{"SECP256K1":0,"ED25519":1,"BLS12_381_G2":2},"encodingTypes":{"NONE":1,"SOLANA":2,"EVM":4,"ETH_DEPOSIT":5},"calldataDecoding":{"reserved":2895728,"maxSz":1024}},"reqMaxDataSz":1678,"ethMaxGasPrice":20000000000000,"addrFlagsAllowed":true,"ethMaxDataSz":1519,"ethMaxMsgSz":1540,"eip712MaxTypeParams":36,"varAddrPathSzAllowed":true,"eip712Supported":true,"prehashAllowed":true,"ethMsgPreHashAllowed":true,"allowedEthTxTypes":[1,2],"personalSignHeaderSz":72,"kvActionsAllowed":true,"kvKeyMaxStrSz":63,"kvValMaxStrSz":63,"kvActionMaxNum":10,"kvRemoveMaxNum":100,"allowBtcLegacyAndSegwitAddrs":true,"contractDeployKey":"0x08002e0fec8e6acf00835f43c9764f7364fa3f42","abiCategorySz":32,"abiMaxRmv":200,"getAddressFlags":[4,3,5],"maxDecoderBufSz":1600}', -) +); export const connectDecoderData = Buffer.from( '0104de558941cc182423e1fa6b0ee81b2c17c6203d0c2897929f900480a8b879261993500d7c0bb5b80f75e2ca462681fcaa20d0261775d3204c6ee461c9250ee1d60011000022cfce60cc7995770a9d2c5080351ae2068c71cecb766de54c65053f5662337809baf7d9ddaaafca95180611fb37601c8eebc1d9f967c061edee1511309a70fbe2491a266f84dc5d1c868b6e129170fa3b777ebd9af7c047fd6dff2a8a563cf6b8ea8c6157b33bc5a0614c65369ffc3c4d35537f37f197f9bae12c574b9847174e38c41b0302833ebbd2101237703794', 'hex', -) +); -export const getAddressesFlag = LatticeGetAddressesFlag.none +export const getAddressesFlag = LatticeGetAddressesFlag.none; export const getAddressesDecoderData = Buffer.from( '334d32465857534a7569584d4d79737179534d52566e4d655935335141545a664459000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000033435757456366725447394441336648376955714562506b6d504c6b5a396d416838000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000033384b734472704a3556524553516150364163785072704875554a6d5270397a646800000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000003351665444387372784a636742685958667557443555505955375146616258476733000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000033426a685344394a386b50553648346b52475071614d5a58485a4134726f447a344c0000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000', 'hex', -) +); export const signGenericRequest = { payload: Buffer.from( @@ -46,11 +46,11 @@ export const signGenericRequest = { '01f84b01808447868c0082c35094e242e54155b1abc71fc118065270cecaaf8b776885e8d4a51000a417e914679b7e160613be4f8c2d3203d236286d74eb9192f6d6f71b9118a42bb033ccd8e8c0', 'hex', ), -} +}; export const signGenericDecoderData = Buffer.from( '04a50d7d8e5bf6353086dfaff71652a223aa13e02273a2b6bf5a145314b544be1281ac8f78d035874a06b11e3df68e45f7630b2e6ba3be0f51f916fbb6f0a6403930440220640b2c690858ab8d0b9500f9ed64c9aa6b7467b77f1199b061aa96ea780aadaa022048f830f9290dd1b3eaf1922e08a8c992873be1162bd6d5bef681cf911328abe500000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000', 'hex', -) +); export const signBitcoinRequest = { schema: 0, @@ -79,18 +79,18 @@ export const signBitcoinRequest = { 'f00500000054000080010000800000008001000000000000001c110000c47d816ef0a39d6497963ebcf24d05242d51ada74370110100000000000105000000540000800100008000000080000000000000000000000000002c01000000000004b2efdbdd3340d2bc547671ce3993a6f05d70343c07578f9d7f5626fdfc06fa35', 'hex', ), -} +}; export const signBitcoinDecoderData = Buffer.from( '6bb07ddb748b655c8478581af3e128335c16eca0304502210084e356184a7dc1e05a08808cb6da03f9e5d1c37be0f382a761eac2a266a4737f0220172d99b82cf78bf5bb4299e4e663f13e1e4578d144ffeabc474631fb1ac1a4fe00000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000002d423e4c1cc57744a0e7365954e7a632ab272f5d0167337f69227c58e6e2d113e000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000', 'hex', -) +); // Uses `decoderTestsFwConstants` export const getKvRecordsDecoderData = Buffer.from( '00000001013f53e5d800000000012b3078333064613364374138363543393334623338396339313963373337353130303534313131414233410000000000000000000000000000000000000000000012546573742041646472657373204e616d650000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000', 'hex', -) +); export const fetchEncryptedDataRequest = { schema: LatticeEncDataSchema.eip2335, @@ -102,8 +102,8 @@ export const fetchEncryptedDataRequest = { 'hex', ), }, -} +}; export const fetchEncryptedDataDecoderData = Buffer.from( 'a4000000e703000077051b0f03811b1b0bdb48e44977ac70c685312aa6df31c8b3491a9de63b6266f3d76007aba10d19c51ffd031101ac78089c7325d3168d492509735d7f064bfc7f057f2a33451a665c3a8e16dc552e0b1c386f922a80dfd7208ef98afa499dcd2fc5b879b78281c8e5b699904dbbaba690a9a242b1aa0cd4458398c77497200e485f55c16b1e7a3146ac74bff42872d2f76e63689be7066f557e985ebd671114000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000', 'hex', -) +); diff --git a/packages/sdk/src/__test__/unit/api.test.ts b/packages/sdk/src/__test__/unit/api.test.ts index 33dcf6ef..99a08dce 100644 --- a/packages/sdk/src/__test__/unit/api.test.ts +++ b/packages/sdk/src/__test__/unit/api.test.ts @@ -1,59 +1,59 @@ -import { parseDerivationPath } from '../../api/utilities' +import { parseDerivationPath } from '../../api/utilities'; describe('parseDerivationPath', () => { it('parses a simple derivation path correctly', () => { - const result = parseDerivationPath('44/0/0/0') - expect(result).toEqual([44, 0, 0, 0]) - }) + const result = parseDerivationPath('44/0/0/0'); + expect(result).toEqual([44, 0, 0, 0]); + }); it('parses a derivation path with hardened indices correctly', () => { - const result = parseDerivationPath("44'/0'/0'/0") - expect(result).toEqual([0x8000002c, 0x80000000, 0x80000000, 0]) - }) + const result = parseDerivationPath("44'/0'/0'/0"); + expect(result).toEqual([0x8000002c, 0x80000000, 0x80000000, 0]); + }); it('handles mixed hardened and non-hardened indices', () => { - const result = parseDerivationPath("44'/60/0'/0/0") - expect(result).toEqual([0x8000002c, 60, 0x80000000, 0, 0]) - }) + const result = parseDerivationPath("44'/60/0'/0/0"); + expect(result).toEqual([0x8000002c, 60, 0x80000000, 0, 0]); + }); it('interprets lowercase x as 0', () => { - const result = parseDerivationPath('44/x/0/0') - expect(result).toEqual([44, 0, 0, 0]) - }) + const result = parseDerivationPath('44/x/0/0'); + expect(result).toEqual([44, 0, 0, 0]); + }); it('interprets uppercase X as 0', () => { - const result = parseDerivationPath('44/X/0/0') - expect(result).toEqual([44, 0, 0, 0]) - }) + const result = parseDerivationPath('44/X/0/0'); + expect(result).toEqual([44, 0, 0, 0]); + }); it("interprets X' as hardened zero", () => { - const result = parseDerivationPath("44'/X'/0/0") - expect(result).toEqual([0x8000002c, 0x80000000, 0, 0]) - }) + const result = parseDerivationPath("44'/X'/0/0"); + expect(result).toEqual([0x8000002c, 0x80000000, 0, 0]); + }); it("interprets x' as hardened zero", () => { - const result = parseDerivationPath("44'/x'/0/0") - expect(result).toEqual([0x8000002c, 0x80000000, 0, 0]) - }) + const result = parseDerivationPath("44'/x'/0/0"); + expect(result).toEqual([0x8000002c, 0x80000000, 0, 0]); + }); it('handles a complex path with all features', () => { - const result = parseDerivationPath("44'/501'/X'/0'") - expect(result).toEqual([0x8000002c, 0x800001f5, 0x80000000, 0x80000000]) - }) + const result = parseDerivationPath("44'/501'/X'/0'"); + expect(result).toEqual([0x8000002c, 0x800001f5, 0x80000000, 0x80000000]); + }); it('returns an empty array for an empty path', () => { - const result = parseDerivationPath('') - expect(result).toEqual([]) - }) + const result = parseDerivationPath(''); + expect(result).toEqual([]); + }); it('handles leading slash correctly', () => { - const result = parseDerivationPath('/44/0/0/0') - expect(result).toEqual([44, 0, 0, 0]) - }) + const result = parseDerivationPath('/44/0/0/0'); + expect(result).toEqual([44, 0, 0, 0]); + }); it('throws error for invalid input', () => { expect(() => parseDerivationPath('invalid/path')).toThrow( 'Invalid part in derivation path: invalid', - ) - }) -}) + ); + }); +}); diff --git a/packages/sdk/src/__test__/unit/compareEIP7702Serialization.test.ts b/packages/sdk/src/__test__/unit/compareEIP7702Serialization.test.ts index 5ffe859d..4eb31b8e 100644 --- a/packages/sdk/src/__test__/unit/compareEIP7702Serialization.test.ts +++ b/packages/sdk/src/__test__/unit/compareEIP7702Serialization.test.ts @@ -1,10 +1,10 @@ -import { Hash } from 'ox' -import { parseEther, serializeTransaction, toHex } from 'viem' -import { serializeEIP7702Transaction } from '../../ethereum' +import { Hash } from 'ox'; +import { parseEther, serializeTransaction, toHex } from 'viem'; +import { serializeEIP7702Transaction } from '../../ethereum'; import type { EIP7702AuthListTransactionRequest as EIP7702AuthListTransaction, EIP7702AuthTransactionRequest as EIP7702AuthTransaction, -} from '../../types' +} from '../../types'; describe('EIP7702 Transaction Serialization Comparison', () => { /** @@ -31,7 +31,7 @@ describe('EIP7702 Transaction Serialization Comparison', () => { r: '0x0000000000000000000000000000000000000000000000000000000000000001', s: '0x0000000000000000000000000000000000000000000000000000000000000002', }, - } + }; // Convert to Viem's transaction format const viemTx = { @@ -57,17 +57,17 @@ describe('EIP7702 Transaction Serialization Comparison', () => { }, }, ], - } + }; // Serialize using our implementation - const ourSerialized = serializeEIP7702Transaction(tx) + const ourSerialized = serializeEIP7702Transaction(tx); // Serialize using Viem - const viemSerialized = serializeTransaction(viemTx as any) + const viemSerialized = serializeTransaction(viemTx as any); // Output raw serialized data for debugging - console.log('Our serialized (minimal):', ourSerialized) - console.log('Viem serialized (minimal):', viemSerialized) + console.log('Our serialized (minimal):', ourSerialized); + console.log('Viem serialized (minimal):', viemSerialized); // Output serialized by byte console.log( @@ -76,18 +76,18 @@ describe('EIP7702 Transaction Serialization Comparison', () => { .toString('hex') .match(/.{1,2}/g) ?.join(' '), - ) + ); console.log( 'Viem bytes:', Buffer.from(viemSerialized.slice(2), 'hex') .toString('hex') .match(/.{1,2}/g) ?.join(' '), - ) + ); // Compare the serialized transactions - expect(ourSerialized).toEqual(viemSerialized) - }) + expect(ourSerialized).toEqual(viemSerialized); + }); /** * Test case comparing our implementation of EIP-7702 transaction serialization with Viem's implementation @@ -116,7 +116,7 @@ describe('EIP7702 Transaction Serialization Comparison', () => { r: '0x1111111111111111111111111111111111111111111111111111111111111111', s: '0x2222222222222222222222222222222222222222222222222222222222222222', }, - } + }; // Convert to Viem's transaction format const viemTx = { @@ -142,27 +142,27 @@ describe('EIP7702 Transaction Serialization Comparison', () => { }, }, ], - } + }; // Serialize using our implementation - const ourSerialized = serializeEIP7702Transaction(tx) + const ourSerialized = serializeEIP7702Transaction(tx); // Serialize using Viem - const viemSerialized = serializeTransaction(viemTx as any) + const viemSerialized = serializeTransaction(viemTx as any); // Compute hashes for comparison - const ourHash = `0x${Buffer.from(Hash.keccak256(Buffer.from(ourSerialized.slice(2), 'hex'))).toString('hex')}` - const viemHash = `0x${Buffer.from(Hash.keccak256(Buffer.from(viemSerialized.slice(2), 'hex'))).toString('hex')}` + const ourHash = `0x${Buffer.from(Hash.keccak256(Buffer.from(ourSerialized.slice(2), 'hex'))).toString('hex')}`; + const viemHash = `0x${Buffer.from(Hash.keccak256(Buffer.from(viemSerialized.slice(2), 'hex'))).toString('hex')}`; // Output for debugging - console.log('Our serialized:', ourSerialized) - console.log('Viem serialized:', viemSerialized) - console.log('Our hash:', ourHash) - console.log('Viem hash:', viemHash) + console.log('Our serialized:', ourSerialized); + console.log('Viem serialized:', viemSerialized); + console.log('Our hash:', ourHash); + console.log('Viem hash:', viemHash); // Compare the serialized transactions - expect(ourSerialized).toEqual(viemSerialized) - }) + expect(ourSerialized).toEqual(viemSerialized); + }); /** * Test case for serializing an EIP-7702 authorization list transaction (type 5) @@ -198,7 +198,7 @@ describe('EIP7702 Transaction Serialization Comparison', () => { s: '0x4444444444444444444444444444444444444444444444444444444444444444', }, ], - } + }; // Convert to Viem's transaction format const viemTx = { @@ -234,27 +234,27 @@ describe('EIP7702 Transaction Serialization Comparison', () => { }, }, ], - } + }; // Serialize using our implementation - const ourSerialized = serializeEIP7702Transaction(tx) + const ourSerialized = serializeEIP7702Transaction(tx); // Serialize using Viem - const viemSerialized = serializeTransaction(viemTx as any) + const viemSerialized = serializeTransaction(viemTx as any); // Compute hashes for comparison - const ourHash = `0x${Buffer.from(Hash.keccak256(Buffer.from(ourSerialized.slice(2), 'hex'))).toString('hex')}` - const viemHash = `0x${Buffer.from(Hash.keccak256(Buffer.from(viemSerialized.slice(2), 'hex'))).toString('hex')}` + const ourHash = `0x${Buffer.from(Hash.keccak256(Buffer.from(ourSerialized.slice(2), 'hex'))).toString('hex')}`; + const viemHash = `0x${Buffer.from(Hash.keccak256(Buffer.from(viemSerialized.slice(2), 'hex'))).toString('hex')}`; // Output for debugging - console.log('Our serialized (auth list):', ourSerialized) - console.log('Viem serialized (auth list):', viemSerialized) - console.log('Our hash (auth list):', ourHash) - console.log('Viem hash (auth list):', viemHash) + console.log('Our serialized (auth list):', ourSerialized); + console.log('Viem serialized (auth list):', viemSerialized); + console.log('Our hash (auth list):', ourHash); + console.log('Viem hash (auth list):', viemHash); // Compare the serialized transactions - expect(ourSerialized).toEqual(viemSerialized) - }) + expect(ourSerialized).toEqual(viemSerialized); + }); /** * Test case using realistic transaction values @@ -281,7 +281,7 @@ describe('EIP7702 Transaction Serialization Comparison', () => { r: '0x0000000000000000000000000000000000000000000000000000000000000001', s: '0x0000000000000000000000000000000000000000000000000000000000000002', }, - } + }; // Convert to Viem's transaction format const viemTx = { @@ -307,27 +307,27 @@ describe('EIP7702 Transaction Serialization Comparison', () => { }, }, ], - } + }; // Serialize using our implementation - const ourSerialized = serializeEIP7702Transaction(tx) + const ourSerialized = serializeEIP7702Transaction(tx); // Serialize using Viem - const viemSerialized = serializeTransaction(viemTx as any) + const viemSerialized = serializeTransaction(viemTx as any); // Compute hashes for comparison - const ourHash = `0x${Buffer.from(Hash.keccak256(Buffer.from(ourSerialized.slice(2), 'hex'))).toString('hex')}` - const viemHash = `0x${Buffer.from(Hash.keccak256(Buffer.from(viemSerialized.slice(2), 'hex'))).toString('hex')}` + const ourHash = `0x${Buffer.from(Hash.keccak256(Buffer.from(ourSerialized.slice(2), 'hex'))).toString('hex')}`; + const viemHash = `0x${Buffer.from(Hash.keccak256(Buffer.from(viemSerialized.slice(2), 'hex'))).toString('hex')}`; // Output for debugging - console.log('Our serialized (realistic):', ourSerialized) - console.log('Viem serialized (realistic):', viemSerialized) - console.log('Our hash (realistic):', ourHash) - console.log('Viem hash (realistic):', viemHash) + console.log('Our serialized (realistic):', ourSerialized); + console.log('Viem serialized (realistic):', viemSerialized); + console.log('Our hash (realistic):', ourHash); + console.log('Viem hash (realistic):', viemHash); // Compare the serialized transactions - expect(ourSerialized).toEqual(viemSerialized) - }) + expect(ourSerialized).toEqual(viemSerialized); + }); /** * Test case with contract auth (when authorization has nonce) @@ -353,7 +353,7 @@ describe('EIP7702 Transaction Serialization Comparison', () => { r: '0x1111111111111111111111111111111111111111111111111111111111111111', s: '0x2222222222222222222222222222222222222222222222222222222222222222', }, - } + }; // Convert to Viem's transaction format const viemTx = { @@ -379,25 +379,25 @@ describe('EIP7702 Transaction Serialization Comparison', () => { }, }, ], - } + }; // Serialize using our implementation - const ourSerialized = serializeEIP7702Transaction(tx) + const ourSerialized = serializeEIP7702Transaction(tx); // Serialize using Viem - const viemSerialized = serializeTransaction(viemTx as any) + const viemSerialized = serializeTransaction(viemTx as any); // Compute hashes for comparison - const ourHash = `0x${Buffer.from(Hash.keccak256(Buffer.from(ourSerialized.slice(2), 'hex'))).toString('hex')}` - const viemHash = `0x${Buffer.from(Hash.keccak256(Buffer.from(viemSerialized.slice(2), 'hex'))).toString('hex')}` + const ourHash = `0x${Buffer.from(Hash.keccak256(Buffer.from(ourSerialized.slice(2), 'hex'))).toString('hex')}`; + const viemHash = `0x${Buffer.from(Hash.keccak256(Buffer.from(viemSerialized.slice(2), 'hex'))).toString('hex')}`; // Output for debugging - console.log('Our serialized (contract auth):', ourSerialized) - console.log('Viem serialized (contract auth):', viemSerialized) - console.log('Our hash (contract auth):', ourHash) - console.log('Viem hash (contract auth):', viemHash) + console.log('Our serialized (contract auth):', ourSerialized); + console.log('Viem serialized (contract auth):', viemSerialized); + console.log('Our hash (contract auth):', ourHash); + console.log('Viem hash (contract auth):', viemHash); // Compare the serialized transactions - expect(ourSerialized).toEqual(viemSerialized) - }) -}) + expect(ourSerialized).toEqual(viemSerialized); + }); +}); diff --git a/packages/sdk/src/__test__/unit/decoders.test.ts b/packages/sdk/src/__test__/unit/decoders.test.ts index 64080439..9ab692fc 100644 --- a/packages/sdk/src/__test__/unit/decoders.test.ts +++ b/packages/sdk/src/__test__/unit/decoders.test.ts @@ -4,8 +4,8 @@ import { decodeGetAddressesResponse, decodeGetKvRecordsResponse, decodeSignResponse, -} from '../../functions' -import type { DecodeSignResponseParams } from '../../types' +} from '../../functions'; +import type { DecodeSignResponseParams } from '../../types'; import { clientKeyPair, connectDecoderData, @@ -19,20 +19,20 @@ import { signBitcoinRequest, signGenericDecoderData, signGenericRequest, -} from './__mocks__/decoderData' +} from './__mocks__/decoderData'; describe('decoders', () => { test('connect', () => { expect( decodeConnectResponse(connectDecoderData, clientKeyPair), - ).toMatchSnapshot() - }) + ).toMatchSnapshot(); + }); test('getAddresses', () => { expect( decodeGetAddressesResponse(getAddressesDecoderData, getAddressesFlag), - ).toMatchSnapshot() - }) + ).toMatchSnapshot(); + }); test('sign - bitcoin', () => { const params: DecodeSignResponseParams = { @@ -40,18 +40,18 @@ describe('decoders', () => { request: signBitcoinRequest, isGeneric: false, currency: 'BTC', - } - expect(decodeSignResponse(params)).toMatchSnapshot() - }) + }; + expect(decodeSignResponse(params)).toMatchSnapshot(); + }); test('sign - generic', () => { const params: DecodeSignResponseParams = { data: signGenericDecoderData, request: signGenericRequest, isGeneric: true, - } - expect(decodeSignResponse(params)).toMatchSnapshot() - }) + }; + expect(decodeSignResponse(params)).toMatchSnapshot(); + }); test('getKvRecords', () => { expect( @@ -59,8 +59,8 @@ describe('decoders', () => { getKvRecordsDecoderData, decoderTestsFwConstants, ), - ).toMatchSnapshot() - }) + ).toMatchSnapshot(); + }); test('fetchEncryptedData', () => { // This test is different than the others because one part of the data is @@ -69,9 +69,9 @@ describe('decoders', () => { const decoded = decodeFetchEncData({ data: fetchEncryptedDataDecoderData, ...fetchEncryptedDataRequest, - }) - const decodedDerp = JSON.parse(decoded.toString()) - decodedDerp.uuid = '00000000-0000-0000-0000-000000000000' - expect(Buffer.from(JSON.stringify(decodedDerp))).toMatchSnapshot() - }) -}) + }); + const decodedDerp = JSON.parse(decoded.toString()); + decodedDerp.uuid = '00000000-0000-0000-0000-000000000000'; + expect(Buffer.from(JSON.stringify(decodedDerp))).toMatchSnapshot(); + }); +}); diff --git a/packages/sdk/src/__test__/unit/eip7702.test.ts b/packages/sdk/src/__test__/unit/eip7702.test.ts index 3ffb73b9..080f111f 100644 --- a/packages/sdk/src/__test__/unit/eip7702.test.ts +++ b/packages/sdk/src/__test__/unit/eip7702.test.ts @@ -1,10 +1,10 @@ -import { Hash } from 'ox' -import { parseEther, toHex } from 'viem' -import { serializeEIP7702Transaction } from '../../ethereum' +import { Hash } from 'ox'; +import { parseEther, toHex } from 'viem'; +import { serializeEIP7702Transaction } from '../../ethereum'; import type { EIP7702AuthListTransactionRequest, EIP7702AuthTransactionRequest, -} from '../../types' +} from '../../types'; describe('EIP-7702 Transaction Serialization', () => { /** @@ -39,26 +39,26 @@ describe('EIP-7702 Transaction Serialization', () => { r: '0xbfa71d3b2c96bd4f573ee8e2b0da387999eb521b8c09f68499f4ed528cbeeb40', s: '0x171bb6415a3ff1207ddf5314aa05ffc168bd82f3abd0c8a7c91ef22ff58c4698', }, - } + }; // Serialize the transaction - const serialized = serializeEIP7702Transaction(tx) + const serialized = serializeEIP7702Transaction(tx); // Compute the keccak256 hash of the serialized transaction - const txHash = `0x${Buffer.from(Hash.keccak256(Buffer.from(serialized.slice(2), 'hex'))).toString('hex')}` + const txHash = `0x${Buffer.from(Hash.keccak256(Buffer.from(serialized.slice(2), 'hex'))).toString('hex')}`; // Store the serialized value for debugging - console.log('Serialized transaction:', serialized) - console.log('Transaction hash:', txHash) + console.log('Serialized transaction:', serialized); + console.log('Transaction hash:', txHash); // Store the expected serialized form (can be replaced with actual expected value) // For now we'll assert that serialization produces consistent results - const initialRun = serializeEIP7702Transaction(tx) - expect(serialized).toEqual(initialRun) + const initialRun = serializeEIP7702Transaction(tx); + expect(serialized).toEqual(initialRun); // Ensure the serialized transaction starts with the transaction type (0x04) - expect(serialized.startsWith('0x04')).toBe(true) - }) + expect(serialized.startsWith('0x04')).toBe(true); + }); /** * Test case for serializing an EIP-7702 authorization list transaction (type 5). @@ -96,26 +96,26 @@ describe('EIP-7702 Transaction Serialization', () => { s: '0x7e03cfbc948cf6b8c4cd946d511b3ea1c4c64e8c70e0259573183ef22d565034', }, ], - } + }; // Serialize the transaction - const serialized = serializeEIP7702Transaction(tx) + const serialized = serializeEIP7702Transaction(tx); // Compute the keccak256 hash of the serialized transaction - const txHash = `0x${Buffer.from(Hash.keccak256(Buffer.from(serialized.slice(2), 'hex'))).toString('hex')}` + const txHash = `0x${Buffer.from(Hash.keccak256(Buffer.from(serialized.slice(2), 'hex'))).toString('hex')}`; // Store the serialized value for debugging - console.log('Serialized auth list transaction:', serialized) - console.log('Transaction hash:', txHash) + console.log('Serialized auth list transaction:', serialized); + console.log('Transaction hash:', txHash); // Store the expected serialized form (can be replaced with actual expected value) // For now we'll assert that serialization produces consistent results - const initialRun = serializeEIP7702Transaction(tx) - expect(serialized).toEqual(initialRun) + const initialRun = serializeEIP7702Transaction(tx); + expect(serialized).toEqual(initialRun); // Ensure the serialized transaction starts with the transaction type (0x05) - expect(serialized.startsWith('0x04')).toBe(true) - }) + expect(serialized.startsWith('0x04')).toBe(true); + }); /** * Test case for comparing serialization against a known good hash. @@ -148,25 +148,25 @@ describe('EIP-7702 Transaction Serialization', () => { r: '0x7afecf0fa2f0c5f3cee3bf477dc4b0787afaecf5c8b0e2f7ec6c47c893bb06f0', s: '0x2e019bd0bb7b96a5beb6f92c63bc7d72f19f6b960d50f8b1c0c4f6bc690e95f4', }, - } + }; // Serialize the transaction - const serialized = serializeEIP7702Transaction(tx) + const serialized = serializeEIP7702Transaction(tx); // Compute the keccak256 hash of the serialized transaction - const txHash = `0x${Buffer.from(Hash.keccak256(Buffer.from(serialized.slice(2), 'hex'))).toString('hex')}` + const txHash = `0x${Buffer.from(Hash.keccak256(Buffer.from(serialized.slice(2), 'hex'))).toString('hex')}`; - console.log('Reference serialized transaction:', serialized) - console.log('Reference transaction hash:', txHash) + console.log('Reference serialized transaction:', serialized); + console.log('Reference transaction hash:', txHash); // The expected hash would be provided by a reference implementation // For now,. I will assert consistency across multiple serializations - const secondRun = serializeEIP7702Transaction(tx) - expect(serialized).toEqual(secondRun) + const secondRun = serializeEIP7702Transaction(tx); + expect(serialized).toEqual(secondRun); // Store the hash for future reference - this can be replaced with a // verified correct hash once available from a reference implementation - const knownGoodHash = txHash - expect(txHash).toEqual(knownGoodHash) - }) -}) + const knownGoodHash = txHash; + expect(txHash).toEqual(knownGoodHash); + }); +}); diff --git a/packages/sdk/src/__test__/unit/encoders.test.ts b/packages/sdk/src/__test__/unit/encoders.test.ts index abd0e623..81ef41b8 100644 --- a/packages/sdk/src/__test__/unit/encoders.test.ts +++ b/packages/sdk/src/__test__/unit/encoders.test.ts @@ -1,4 +1,4 @@ -import { EXTERNAL } from '../../constants' +import { EXTERNAL } from '../../constants'; import { encodeAddKvRecordsRequest, encodeGetAddressesRequest, @@ -6,120 +6,120 @@ import { encodePairRequest, encodeRemoveKvRecordsRequest, encodeSignRequest, -} from '../../functions' -import { buildTransaction } from '../../shared/functions' -import { getP256KeyPair } from '../../util' +} from '../../functions'; +import { buildTransaction } from '../../shared/functions'; +import { getP256KeyPair } from '../../util'; import { buildFirmwareConstants, buildGetAddressesObject, buildSignObject, buildWallet, getFwVersionsList, -} from '../utils/builders' +} from '../utils/builders'; describe('encoders', () => { - let mockRandom: any + let mockRandom: any; beforeAll(() => { - mockRandom = vi.spyOn(globalThis.Math, 'random').mockReturnValue(0.1) - }) + mockRandom = vi.spyOn(globalThis.Math, 'random').mockReturnValue(0.1); + }); afterAll(() => { - mockRandom.mockRestore() - }) + mockRandom.mockRestore(); + }); describe('pair', () => { test('pair encoder', () => { - const privKey = Buffer.alloc(32, '1') - expect(privKey.toString()).toMatchSnapshot() - const key = getP256KeyPair(privKey) + const privKey = Buffer.alloc(32, '1'); + expect(privKey.toString()).toMatchSnapshot(); + const key = getP256KeyPair(privKey); const payload = encodePairRequest({ key, pairingSecret: 'testtest', appName: 'testtest', - }) - const payloadAsString = payload.toString('hex') - expect(payloadAsString).toMatchSnapshot() - }) - }) + }); + const payloadAsString = payload.toString('hex'); + expect(payloadAsString).toMatchSnapshot(); + }); + }); describe('getAddresses', () => { test('encodeGetAddressesRequest with default flag', () => { - const payload = encodeGetAddressesRequest(buildGetAddressesObject()) - const payloadAsString = payload.toString('hex') - expect(payloadAsString).toMatchSnapshot() - }) + const payload = encodeGetAddressesRequest(buildGetAddressesObject()); + const payloadAsString = payload.toString('hex'); + expect(payloadAsString).toMatchSnapshot(); + }); test('encodeGetAddressesRequest with ED25519_PUB', () => { const mockObject = buildGetAddressesObject({ flag: EXTERNAL.GET_ADDR_FLAGS.ED25519_PUB, - }) - const payload = encodeGetAddressesRequest(mockObject) - const payloadAsString = payload.toString('hex') - expect(payloadAsString).toMatchSnapshot() - }) + }); + const payload = encodeGetAddressesRequest(mockObject); + const payloadAsString = payload.toString('hex'); + expect(payloadAsString).toMatchSnapshot(); + }); test('encodeGetAddressesRequest with SECP256K1_PUB', () => { const mockObject = buildGetAddressesObject({ flag: EXTERNAL.GET_ADDR_FLAGS.SECP256K1_PUB, - }) - const payload = encodeGetAddressesRequest(mockObject) - const payloadAsString = payload.toString('hex') - expect(payloadAsString).toMatchSnapshot() - }) - }) + }); + const payload = encodeGetAddressesRequest(mockObject); + const payloadAsString = payload.toString('hex'); + expect(payloadAsString).toMatchSnapshot(); + }); + }); describe('sign', () => { test.each(getFwVersionsList())( 'should test sign encoder with firmware v%d.%d.%d', (major, minor, patch) => { - const fwVersion = Buffer.from([patch, minor, major]) - const txObj = buildSignObject(fwVersion) - const tx = buildTransaction(txObj) + const fwVersion = Buffer.from([patch, minor, major]); + const txObj = buildSignObject(fwVersion); + const tx = buildTransaction(txObj); const req = { ...txObj, ...tx, wallet: buildWallet(), - } - const { payload } = encodeSignRequest(req) - const payloadAsString = payload.toString('hex') - expect(payloadAsString).toMatchSnapshot() + }; + const { payload } = encodeSignRequest(req); + const payloadAsString = payload.toString('hex'); + expect(payloadAsString).toMatchSnapshot(); }, - ) - }) + ); + }); describe('KvRecords', () => { test('getKvRecords', () => { - const mockObject = { type: 0, n: 1, start: 0 } - const payload = encodeGetKvRecordsRequest(mockObject) - const payloadAsString = payload.toString('hex') - expect(payloadAsString).toMatchSnapshot() - }) + const mockObject = { type: 0, n: 1, start: 0 }; + const payload = encodeGetKvRecordsRequest(mockObject); + const payloadAsString = payload.toString('hex'); + expect(payloadAsString).toMatchSnapshot(); + }); test('addKvRecords', () => { - const fwConstants = buildFirmwareConstants() + const fwConstants = buildFirmwareConstants(); const mockObject = { type: 0, records: { key: 'value' }, caseSensitive: false, fwConstants, - } - const payload = encodeAddKvRecordsRequest(mockObject) - const payloadAsString = payload.toString('hex') - expect(payloadAsString).toMatchSnapshot() - }) + }; + const payload = encodeAddKvRecordsRequest(mockObject); + const payloadAsString = payload.toString('hex'); + expect(payloadAsString).toMatchSnapshot(); + }); test('removeKvRecords', () => { - const fwConstants = buildFirmwareConstants() + const fwConstants = buildFirmwareConstants(); const mockObject = { type: 0, ids: ['0'], caseSensitive: false, fwConstants, - } - const payload = encodeRemoveKvRecordsRequest(mockObject) - const payloadAsString = payload.toString('hex') - expect(payloadAsString).toMatchSnapshot() - }) - }) -}) + }; + const payload = encodeRemoveKvRecordsRequest(mockObject); + const payloadAsString = payload.toString('hex'); + expect(payloadAsString).toMatchSnapshot(); + }); + }); +}); diff --git a/packages/sdk/src/__test__/unit/ethereum.validate.test.ts b/packages/sdk/src/__test__/unit/ethereum.validate.test.ts index c61d865f..18c696c9 100644 --- a/packages/sdk/src/__test__/unit/ethereum.validate.test.ts +++ b/packages/sdk/src/__test__/unit/ethereum.validate.test.ts @@ -3,13 +3,13 @@ import { SignTypedDataVersion, TypedDataUtils, type TypedMessage, -} from '@metamask/eth-sig-util' -import { ecsign, privateToAddress } from 'ethereumjs-util' -import { mnemonicToAccount } from 'viem/accounts' -import { HARDENED_OFFSET } from '../../constants' -import ethereum from '../../ethereum' -import { DEFAULT_SIGNER, buildFirmwareConstants } from '../utils/builders' -import { TEST_MNEMONIC } from '../utils/testConstants' +} from '@metamask/eth-sig-util'; +import { ecsign, privateToAddress } from 'ethereumjs-util'; +import { mnemonicToAccount } from 'viem/accounts'; +import { HARDENED_OFFSET } from '../../constants'; +import ethereum from '../../ethereum'; +import { DEFAULT_SIGNER, buildFirmwareConstants } from '../utils/builders'; +import { TEST_MNEMONIC } from '../utils/testConstants'; const typedData: TypedMessage = { types: { @@ -27,54 +27,60 @@ const typedData: TypedMessage = { target: 'Ethereum', born: 2015, }, -} +}; describe('validateEthereumMsgResponse', () => { it('recovers expected signature for EIP712 payload', () => { - const account = mnemonicToAccount(TEST_MNEMONIC) - const hdKey = account.getHdKey() - if (!hdKey.privateKey) throw new Error('No private key') - const priv = Buffer.from(hdKey.privateKey) - const signer = privateToAddress(priv) - const digest = TypedDataUtils.eip712Hash(typedData, SignTypedDataVersion.V4) - const sig = ecsign(Buffer.from(digest), priv) - const fwConstants = buildFirmwareConstants() + const account = mnemonicToAccount(TEST_MNEMONIC); + const hdKey = account.getHdKey(); + if (!hdKey.privateKey) throw new Error('No private key'); + const priv = Buffer.from(hdKey.privateKey); + const signer = privateToAddress(priv); + const digest = TypedDataUtils.eip712Hash( + typedData, + SignTypedDataVersion.V4, + ); + const sig = ecsign(Buffer.from(digest), priv); + const fwConstants = buildFirmwareConstants(); const request = ethereum.buildEthereumMsgRequest({ signerPath: DEFAULT_SIGNER, protocol: 'eip712', payload: JSON.parse(JSON.stringify(typedData)), fwConstants, - }) + }); const result = ethereum.validateEthereumMsgResponse( { signer: `0x${signer.toString('hex')}`, sig: { r: Buffer.from(sig.r), s: Buffer.from(sig.s) }, }, request, - ) + ); - expect(result.v.toString('hex')).toBe('1c') - }) + expect(result.v.toString('hex')).toBe('1c'); + }); it('validates response using buildEthereumMsgRequest request context', () => { - const fwConstants = buildFirmwareConstants() - const signerPath = [...DEFAULT_SIGNER] - signerPath[2] = HARDENED_OFFSET + const fwConstants = buildFirmwareConstants(); + const signerPath = [...DEFAULT_SIGNER]; + signerPath[2] = HARDENED_OFFSET; const request = ethereum.buildEthereumMsgRequest({ signerPath, protocol: 'eip712', payload: JSON.parse(JSON.stringify(typedData)), fwConstants, - }) + }); - const account = mnemonicToAccount(TEST_MNEMONIC) - const hdKey = account.getHdKey() - if (!hdKey.privateKey) throw new Error('No private key') - const priv = Buffer.from(hdKey.privateKey) - const signer = privateToAddress(priv) - const digest = TypedDataUtils.eip712Hash(typedData, SignTypedDataVersion.V4) - const sig = ecsign(Buffer.from(digest), priv) + const account = mnemonicToAccount(TEST_MNEMONIC); + const hdKey = account.getHdKey(); + if (!hdKey.privateKey) throw new Error('No private key'); + const priv = Buffer.from(hdKey.privateKey); + const signer = privateToAddress(priv); + const digest = TypedDataUtils.eip712Hash( + typedData, + SignTypedDataVersion.V4, + ); + const sig = ecsign(Buffer.from(digest), priv); const result = ethereum.validateEthereumMsgResponse( { @@ -82,8 +88,8 @@ describe('validateEthereumMsgResponse', () => { sig: { r: Buffer.from(sig.r), s: Buffer.from(sig.s) }, }, request, - ) + ); - expect(result.v.toString('hex')).toBe('1c') - }) -}) + expect(result.v.toString('hex')).toBe('1c'); + }); +}); diff --git a/packages/sdk/src/__test__/unit/module.interop.test.ts b/packages/sdk/src/__test__/unit/module.interop.test.ts index 619bb0ec..98bed751 100644 --- a/packages/sdk/src/__test__/unit/module.interop.test.ts +++ b/packages/sdk/src/__test__/unit/module.interop.test.ts @@ -1,80 +1,80 @@ -import { execSync, spawnSync } from 'node:child_process' +import { execSync, spawnSync } from 'node:child_process'; import { existsSync, mkdirSync, mkdtempSync, rmSync, symlinkSync, -} from 'node:fs' -import os from 'node:os' -import path from 'node:path' -import { fileURLToPath } from 'node:url' +} from 'node:fs'; +import os from 'node:os'; +import path from 'node:path'; +import { fileURLToPath } from 'node:url'; -const __filename = fileURLToPath(import.meta.url) -const __dirname = path.dirname(__filename) -const packageRoot = path.resolve(__dirname, '../../..') -const cjsOutput = path.resolve(packageRoot, 'dist/index.cjs') -const esmOutput = path.resolve(packageRoot, 'dist/index.mjs') -const packageName = 'gridplus-sdk' +const __filename = fileURLToPath(import.meta.url); +const __dirname = path.dirname(__filename); +const packageRoot = path.resolve(__dirname, '../../..'); +const cjsOutput = path.resolve(packageRoot, 'dist/index.cjs'); +const esmOutput = path.resolve(packageRoot, 'dist/index.mjs'); +const packageName = 'gridplus-sdk'; -let built = false -let fixtureDir: string | undefined +let built = false; +let fixtureDir: string | undefined; const ensureBuildArtifacts = () => { if (built) { - return + return; } - console.log('Building package with pnpm run build ...') + console.log('Building package with pnpm run build ...'); execSync('pnpm run build', { cwd: packageRoot, stdio: 'inherit', - }) + }); if (!existsSync(cjsOutput) || !existsSync(esmOutput)) { - throw new Error('Expected dual build outputs were not generated') + throw new Error('Expected dual build outputs were not generated'); } - built = true -} + built = true; +}; const ensureLinkedFixture = () => { if (fixtureDir) { - return fixtureDir + return fixtureDir; } - const tmpDir = mkdtempSync(path.join(os.tmpdir(), 'gridplus-sdk-interop-')) - const nodeModulesDir = path.join(tmpDir, 'node_modules') - mkdirSync(nodeModulesDir, { recursive: true }) - const linkTarget = path.join(nodeModulesDir, packageName) - symlinkSync(packageRoot, linkTarget, 'junction') - fixtureDir = tmpDir - return fixtureDir -} + const tmpDir = mkdtempSync(path.join(os.tmpdir(), 'gridplus-sdk-interop-')); + const nodeModulesDir = path.join(tmpDir, 'node_modules'); + mkdirSync(nodeModulesDir, { recursive: true }); + const linkTarget = path.join(nodeModulesDir, packageName); + symlinkSync(packageRoot, linkTarget, 'junction'); + fixtureDir = tmpDir; + return fixtureDir; +}; const runNodeCheck = (args: string[]) => { - const cwd = ensureLinkedFixture() + const cwd = ensureLinkedFixture(); const result = spawnSync(process.execPath, args, { cwd, env: { ...process.env }, encoding: 'utf-8', - }) + }); if (result.error) { - throw result.error + throw result.error; } if (result.status !== 0) { throw new Error( `Node command failed (${result.status}):\n${result.stderr || result.stdout}`, - ) + ); } -} +}; describe('package module interoperability', () => { beforeAll(() => { - ensureBuildArtifacts() - }) + ensureBuildArtifacts(); + }); afterAll(() => { if (fixtureDir) { - rmSync(fixtureDir, { recursive: true, force: true }) - fixtureDir = undefined + rmSync(fixtureDir, { recursive: true, force: true }); + fixtureDir = undefined; } - }) + }); it('exposes CommonJS entry via require()', () => { const script = ` @@ -85,9 +85,9 @@ describe('package module interoperability', () => { if (typeof sdk.Client !== 'function') { throw new Error('Client export missing'); } - ` - runNodeCheck(['-e', script]) - }) + `; + runNodeCheck(['-e', script]); + }); it('exposes ESM entry via dynamic import()', () => { const script = ` @@ -98,7 +98,7 @@ describe('package module interoperability', () => { if (typeof sdk.Client !== 'function') { throw new Error('Client export missing'); } - ` - runNodeCheck(['--input-type=module', '-e', script]) - }) -}) + `; + runNodeCheck(['--input-type=module', '-e', script]); + }); +}); diff --git a/packages/sdk/src/__test__/unit/parseGenericSigningResponse.test.ts b/packages/sdk/src/__test__/unit/parseGenericSigningResponse.test.ts index cc95ed43..8874c50d 100644 --- a/packages/sdk/src/__test__/unit/parseGenericSigningResponse.test.ts +++ b/packages/sdk/src/__test__/unit/parseGenericSigningResponse.test.ts @@ -1,124 +1,124 @@ -import { Buffer } from 'node:buffer' -import { RLP } from '@ethereumjs/rlp' -import { Hash } from 'ox' -import secp256k1 from 'secp256k1' -import { parseGenericSigningResponse } from '../../genericSigning' -import { Constants } from '../../index' +import { Buffer } from 'node:buffer'; +import { RLP } from '@ethereumjs/rlp'; +import { Hash } from 'ox'; +import secp256k1 from 'secp256k1'; +import { parseGenericSigningResponse } from '../../genericSigning'; +import { Constants } from '../../index'; describe('parseGenericSigningResponse', () => { // Helper to create a DER signature const createDERSignature = (r: Buffer, s: Buffer): Buffer => { - const rLen = r.length - const sLen = s.length - const totalLen = 4 + rLen + sLen - const sig = Buffer.alloc(totalLen + 2) - - sig[0] = 0x30 // DER sequence - sig[1] = totalLen - sig[2] = 0x02 // Integer type - sig[3] = rLen - r.copy(sig, 4) - sig[4 + rLen] = 0x02 // Integer type - sig[4 + rLen + 1] = sLen - s.copy(sig, 4 + rLen + 2) + const rLen = r.length; + const sLen = s.length; + const totalLen = 4 + rLen + sLen; + const sig = Buffer.alloc(totalLen + 2); + + sig[0] = 0x30; // DER sequence + sig[1] = totalLen; + sig[2] = 0x02; // Integer type + sig[3] = rLen; + r.copy(sig, 4); + sig[4 + rLen] = 0x02; // Integer type + sig[4 + rLen + 1] = sLen; + s.copy(sig, 4 + rLen + 2); // Pad to 74 bytes (standard for Lattice) - const padded = Buffer.alloc(74) - sig.copy(padded, 0) - return padded - } + const padded = Buffer.alloc(74); + sig.copy(padded, 0); + return padded; + }; it('should handle generic KECCAK256 message (not EVM transaction)', () => { // Simulate signing a plain text message "Test!" - const payload = Buffer.from('Test!') - const hash = Buffer.from(Hash.keccak256(payload)) + const payload = Buffer.from('Test!'); + const hash = Buffer.from(Hash.keccak256(payload)); // Create a fake signature const privateKey = Buffer.from( '0101010101010101010101010101010101010101010101010101010101010101', 'hex', - ) - const sigObj = secp256k1.ecdsaSign(hash, privateKey) - const publicKey = secp256k1.publicKeyCreate(privateKey, false) + ); + const sigObj = secp256k1.ecdsaSign(hash, privateKey); + const publicKey = secp256k1.publicKeyCreate(privateKey, false); // Create DER-encoded signature response const derSig = createDERSignature( Buffer.from(sigObj.signature.slice(0, 32)), Buffer.from(sigObj.signature.slice(32, 64)), - ) + ); // Create mock response buffer const mockResponse = Buffer.concat([ Buffer.from([0x04]), // Uncompressed pubkey prefix publicKey.slice(1), // Remove compression prefix from secp256k1 output (64 bytes) derSig, - ]) + ]); const req = { curveType: Constants.SIGNING.CURVES.SECP256K1, hashType: Constants.SIGNING.HASHES.KECCAK256, encodingType: null, // Not EVM encoding origPayloadBuf: payload, - } + }; - const result = parseGenericSigningResponse(mockResponse, 0, req) + const result = parseGenericSigningResponse(mockResponse, 0, req); - expect(result).toBeDefined() - expect(result.sig).toBeDefined() - expect(result.sig.v).toBeDefined() - expect(typeof result.sig.v).toBe('bigint') + expect(result).toBeDefined(); + expect(result.sig).toBeDefined(); + expect(result.sig.v).toBeDefined(); + expect(typeof result.sig.v).toBe('bigint'); // For non-EVM generic messages, v should be 27 or 28 - expect([27n, 28n]).toContain(result.sig.v) - }) + expect([27n, 28n]).toContain(result.sig.v); + }); it('should handle EVM transaction encoding', () => { // Simulate an unsigned legacy transaction const unsignedTx = Buffer.from( 'e9808504a817c800825208943535353535353535353535353535353535353535880de0b6b3a764000080', 'hex', - ) - const hash = Buffer.from(Hash.keccak256(unsignedTx)) + ); + const hash = Buffer.from(Hash.keccak256(unsignedTx)); // Create a fake signature const privateKey = Buffer.from( '0101010101010101010101010101010101010101010101010101010101010101', 'hex', - ) - const sigObj = secp256k1.ecdsaSign(hash, privateKey) - const publicKey = secp256k1.publicKeyCreate(privateKey, false) + ); + const sigObj = secp256k1.ecdsaSign(hash, privateKey); + const publicKey = secp256k1.publicKeyCreate(privateKey, false); // Create DER-encoded signature response const derSig = createDERSignature( Buffer.from(sigObj.signature.slice(0, 32)), Buffer.from(sigObj.signature.slice(32, 64)), - ) + ); // Create mock response buffer const mockResponse = Buffer.concat([ Buffer.from([0x04]), // Uncompressed pubkey prefix publicKey.slice(1), // Remove compression prefix from secp256k1 output (64 bytes) derSig, - ]) + ]); const req = { curveType: Constants.SIGNING.CURVES.SECP256K1, hashType: Constants.SIGNING.HASHES.KECCAK256, encodingType: Constants.SIGNING.ENCODINGS.EVM, origPayloadBuf: unsignedTx, - } + }; - const result = parseGenericSigningResponse(mockResponse, 0, req) + const result = parseGenericSigningResponse(mockResponse, 0, req); - expect(result).toBeDefined() - expect(result.sig).toBeDefined() - expect(result.sig.v).toBeDefined() - expect(typeof result.sig.v).toBe('bigint') + expect(result).toBeDefined(); + expect(result.sig).toBeDefined(); + expect(result.sig.v).toBeDefined(); + expect(typeof result.sig.v).toBe('bigint'); // For pre-EIP155 transactions, v should be 27 or 28 - const vNumber = Number(result.sig.v) - expect([27, 28]).toContain(vNumber) - }) + const vNumber = Number(result.sig.v); + expect([27, 28]).toContain(vNumber); + }); it('should handle RLP-encoded data that looks like a transaction', () => { // Create an RLP-encoded array with 6+ elements (looks like a transaction) @@ -129,43 +129,43 @@ describe('parseGenericSigningResponse', () => { Buffer.from([0x04]), // to Buffer.from([0x05]), // value Buffer.from([0x06]), // data - ] - const rlpEncoded = Buffer.from(RLP.encode(txLikeData)) - const hash = Buffer.from(Hash.keccak256(rlpEncoded)) + ]; + const rlpEncoded = Buffer.from(RLP.encode(txLikeData)); + const hash = Buffer.from(Hash.keccak256(rlpEncoded)); // Create a fake signature const privateKey = Buffer.from( '0101010101010101010101010101010101010101010101010101010101010101', 'hex', - ) - const sigObj = secp256k1.ecdsaSign(hash, privateKey) - const publicKey = secp256k1.publicKeyCreate(privateKey, false) + ); + const sigObj = secp256k1.ecdsaSign(hash, privateKey); + const publicKey = secp256k1.publicKeyCreate(privateKey, false); // Create DER-encoded signature response const derSig = createDERSignature( Buffer.from(sigObj.signature.slice(0, 32)), Buffer.from(sigObj.signature.slice(32, 64)), - ) + ); // Create mock response buffer const mockResponse = Buffer.concat([ Buffer.from([0x04]), // Uncompressed pubkey prefix publicKey.slice(1), // Remove compression prefix from secp256k1 output (64 bytes) derSig, - ]) + ]); const req = { curveType: Constants.SIGNING.CURVES.SECP256K1, hashType: Constants.SIGNING.HASHES.KECCAK256, encodingType: null, // Not explicitly EVM origPayloadBuf: rlpEncoded, - } + }; - const result = parseGenericSigningResponse(mockResponse, 0, req) + const result = parseGenericSigningResponse(mockResponse, 0, req); - expect(result).toBeDefined() - expect(result.sig).toBeDefined() - expect(result.sig.v).toBeDefined() - expect(typeof result.sig.v).toBe('bigint') - }) -}) + expect(result).toBeDefined(); + expect(result.sig).toBeDefined(); + expect(result.sig.v).toBeDefined(); + expect(typeof result.sig.v).toBe('bigint'); + }); +}); diff --git a/packages/sdk/src/__test__/unit/personalSignValidation.test.ts b/packages/sdk/src/__test__/unit/personalSignValidation.test.ts index 7aa3789e..ee225c57 100644 --- a/packages/sdk/src/__test__/unit/personalSignValidation.test.ts +++ b/packages/sdk/src/__test__/unit/personalSignValidation.test.ts @@ -1,7 +1,7 @@ -import { Buffer } from 'node:buffer' -import { Hash } from 'ox' -import secp256k1 from 'secp256k1' -import { addRecoveryParam } from '../../ethereum' +import { Buffer } from 'node:buffer'; +import { Hash } from 'ox'; +import secp256k1 from 'secp256k1'; +import { addRecoveryParam } from '../../ethereum'; describe('Personal Sign Validation - Issue Fix', () => { /** @@ -15,124 +15,124 @@ describe('Personal Sign Validation - Issue Fix', () => { const privateKey = Buffer.from( '0101010101010101010101010101010101010101010101010101010101010101', 'hex', - ) - const publicKey = secp256k1.publicKeyCreate(privateKey, false) + ); + const publicKey = secp256k1.publicKeyCreate(privateKey, false); // Create a test message - const message = Buffer.from('Test message', 'utf8') + const message = Buffer.from('Test message', 'utf8'); // Build personal sign prefix and hash const prefix = Buffer.from( `\u0019Ethereum Signed Message:\n${message.length.toString()}`, 'utf-8', - ) + ); const messageHash = Buffer.from( Hash.keccak256(Buffer.concat([prefix, message])), - ) + ); // Sign the message - const sigObj = secp256k1.ecdsaSign(messageHash, privateKey) + const sigObj = secp256k1.ecdsaSign(messageHash, privateKey); // Prepare signature object const sig = { r: Buffer.from(sigObj.signature.slice(0, 32)), s: Buffer.from(sigObj.signature.slice(32, 64)), - } + }; // Get the Ethereum address from the public key // This matches what the firmware returns - const pubkeyWithoutPrefix = publicKey.slice(1) // Remove 0x04 prefix + const pubkeyWithoutPrefix = publicKey.slice(1); // Remove 0x04 prefix const addressBuffer = Buffer.from( Hash.keccak256(pubkeyWithoutPrefix), - ).slice(-20) + ).slice(-20); // This is the function that was failing before the fix // It should now correctly add the recovery parameter const result = addRecoveryParam(messageHash, sig, addressBuffer, { chainId: 1, useEIP155: false, - }) + }); // Verify the signature has a valid v value (27 or 28) - expect(result.v).toBeDefined() + expect(result.v).toBeDefined(); const vValue = Buffer.isBuffer(result.v) ? result.v.readUInt8(0) - : Number(result.v) - expect([27, 28]).toContain(vValue) + : Number(result.v); + expect([27, 28]).toContain(vValue); // Verify r and s are buffers of correct length - expect(Buffer.isBuffer(result.r)).toBe(true) - expect(Buffer.isBuffer(result.s)).toBe(true) - expect(result.r.length).toBe(32) - expect(result.s.length).toBe(32) - }) + expect(Buffer.isBuffer(result.r)).toBe(true); + expect(Buffer.isBuffer(result.s)).toBe(true); + expect(result.r.length).toBe(32); + expect(result.s.length).toBe(32); + }); it('should throw error when signature does not match address', () => { // Create a test message hash - const messageHash = Buffer.from(Hash.keccak256(Buffer.from('test'))) + const messageHash = Buffer.from(Hash.keccak256(Buffer.from('test'))); // Create a random signature const sig = { r: Buffer.from('1'.repeat(64), 'hex'), s: Buffer.from('2'.repeat(64), 'hex'), - } + }; // Use a random address that won't match the signature - const wrongAddress = Buffer.from('3'.repeat(40), 'hex') + const wrongAddress = Buffer.from('3'.repeat(40), 'hex'); // This should throw because the signature doesn't match the address expect(() => { addRecoveryParam(messageHash, sig, wrongAddress, { chainId: 1, useEIP155: false, - }) - }).toThrow() // Just verify it throws, exact message may vary - }) + }); + }).toThrow(); // Just verify it throws, exact message may vary + }); it('should handle the exact scenario from Ambire bug report', () => { // This is the exact scenario reported by Kalo from Ambire - const testPayload = '0x54657374206d657373616765' // "Test message" in hex - const payloadBuffer = Buffer.from(testPayload.slice(2), 'hex') + const testPayload = '0x54657374206d657373616765'; // "Test message" in hex + const payloadBuffer = Buffer.from(testPayload.slice(2), 'hex'); // Build personal sign hash const prefix = Buffer.from( `\u0019Ethereum Signed Message:\n${payloadBuffer.length.toString()}`, 'utf-8', - ) + ); const messageHash = Buffer.from( Hash.keccak256(Buffer.concat([prefix, payloadBuffer])), - ) + ); // Create a valid signature for this message const privateKey = Buffer.from( '0101010101010101010101010101010101010101010101010101010101010101', 'hex', - ) - const publicKey = secp256k1.publicKeyCreate(privateKey, false) - const sigObj = secp256k1.ecdsaSign(messageHash, privateKey) + ); + const publicKey = secp256k1.publicKeyCreate(privateKey, false); + const sigObj = secp256k1.ecdsaSign(messageHash, privateKey); const sig = { r: Buffer.from(sigObj.signature.slice(0, 32)), s: Buffer.from(sigObj.signature.slice(32, 64)), - } + }; // Get address from public key - const pubkeyWithoutPrefix = publicKey.slice(1) + const pubkeyWithoutPrefix = publicKey.slice(1); const addressBuffer = Buffer.from( Hash.keccak256(pubkeyWithoutPrefix), - ).slice(-20) + ).slice(-20); // This should NOT throw with the fix in place expect(() => { const result = addRecoveryParam(messageHash, sig, addressBuffer, { chainId: 1, useEIP155: false, - }) + }); // Verify we got a valid result - expect(result.v).toBeDefined() - expect(result.r).toBeDefined() - expect(result.s).toBeDefined() - }).not.toThrow() - }) -}) + expect(result.v).toBeDefined(); + expect(result.r).toBeDefined(); + expect(result.s).toBeDefined(); + }).not.toThrow(); + }); +}); diff --git a/packages/sdk/src/__test__/unit/selectDefFrom4byteABI.test.ts b/packages/sdk/src/__test__/unit/selectDefFrom4byteABI.test.ts index fd92cc1c..46a79a18 100644 --- a/packages/sdk/src/__test__/unit/selectDefFrom4byteABI.test.ts +++ b/packages/sdk/src/__test__/unit/selectDefFrom4byteABI.test.ts @@ -1,14 +1,14 @@ -import { selectDefFrom4byteABI } from '../../util' +import { selectDefFrom4byteABI } from '../../util'; describe('selectDefFrom4byteAbi', () => { beforeAll(() => { // Disable this mock to restore console logs when testing - console.warn = vi.fn() - }) + console.warn = vi.fn(); + }); afterAll(() => { - vi.clearAllMocks() - }) + vi.clearAllMocks(); + }); test('select correct result', () => { const result = [ @@ -35,10 +35,10 @@ describe('selectDefFrom4byteAbi', () => { id: 171806, text_signature: 'swapToken', }, - ] - const selector = '0x38ed1739' - expect(selectDefFrom4byteABI(result, selector)).toMatchSnapshot() - }) + ]; + const selector = '0x38ed1739'; + expect(selectDefFrom4byteABI(result, selector)).toMatchSnapshot(); + }); test('handle no match', () => { const result = [ @@ -49,10 +49,10 @@ describe('selectDefFrom4byteAbi', () => { id: 171806, text_signature: 'swapToken', }, - ] - const selector = '0x38ed1739' - expect(() => selectDefFrom4byteABI(result, selector)).toThrowError() - }) + ]; + const selector = '0x38ed1739'; + expect(() => selectDefFrom4byteABI(result, selector)).toThrowError(); + }); test('handle no selector', () => { const result = [ @@ -63,20 +63,20 @@ describe('selectDefFrom4byteAbi', () => { id: 171806, text_signature: 'swapToken', }, - ] - const selector = undefined - expect(() => selectDefFrom4byteABI(result, selector)).toThrowError() - }) + ]; + const selector = undefined; + expect(() => selectDefFrom4byteABI(result, selector)).toThrowError(); + }); test('handle no result', () => { - const result = undefined - const selector = '0x38ed1739' - expect(() => selectDefFrom4byteABI(result, selector)).toThrowError() - }) + const result = undefined; + const selector = '0x38ed1739'; + expect(() => selectDefFrom4byteABI(result, selector)).toThrowError(); + }); test('handle bad data', () => { - const result = [] - const selector = '' - expect(() => selectDefFrom4byteABI(result, selector)).toThrowError() - }) -}) + const result = []; + const selector = ''; + expect(() => selectDefFrom4byteABI(result, selector)).toThrowError(); + }); +}); diff --git a/packages/sdk/src/__test__/unit/signatureUtils.test.ts b/packages/sdk/src/__test__/unit/signatureUtils.test.ts index 8d82e5d4..f1dfbec7 100644 --- a/packages/sdk/src/__test__/unit/signatureUtils.test.ts +++ b/packages/sdk/src/__test__/unit/signatureUtils.test.ts @@ -1,7 +1,7 @@ -import { Buffer } from 'node:buffer' -import { Hash } from 'ox' -import secp256k1 from 'secp256k1' -import { getV, getYParity, randomBytes } from '../../util' +import { Buffer } from 'node:buffer'; +import { Hash } from 'ox'; +import secp256k1 from 'secp256k1'; +import { getV, getYParity, randomBytes } from '../../util'; describe('getYParity', () => { // Helper function to create a valid signature @@ -10,13 +10,13 @@ describe('getYParity', () => { const privateKey = Buffer.from( '0101010101010101010101010101010101010101010101010101010101010101', 'hex', - ) + ); // Sign the message - const sigObj = secp256k1.ecdsaSign(messageHash, privateKey) + const sigObj = secp256k1.ecdsaSign(messageHash, privateKey); // Get the public key - const publicKey = secp256k1.publicKeyCreate(privateKey, false) + const publicKey = secp256k1.publicKeyCreate(privateKey, false); return { signature: { @@ -25,29 +25,29 @@ describe('getYParity', () => { }, publicKey: Buffer.from(publicKey), recovery: sigObj.recid, - } - } + }; + }; describe('Simple signature format', () => { it('should handle simple format with Buffer inputs', () => { - const messageHash = randomBytes(32) + const messageHash = randomBytes(32); const { signature, publicKey, recovery } = - createValidSignature(messageHash) + createValidSignature(messageHash); const yParity = getYParity({ messageHash, signature, publicKey, - }) + }); - expect(yParity).toBe(recovery) - expect([0, 1]).toContain(yParity) - }) + expect(yParity).toBe(recovery); + expect([0, 1]).toContain(yParity); + }); it('should handle simple format with hex string inputs', () => { - const messageHash = randomBytes(32) + const messageHash = randomBytes(32); const { signature, publicKey, recovery } = - createValidSignature(messageHash) + createValidSignature(messageHash); const yParity = getYParity({ messageHash: `0x${messageHash.toString('hex')}`, @@ -56,20 +56,20 @@ describe('getYParity', () => { s: `0x${signature.s.toString('hex')}`, }, publicKey: `0x${publicKey.toString('hex')}`, - }) + }); - expect(yParity).toBe(recovery) - }) + expect(yParity).toBe(recovery); + }); it('should handle simple format with compressed public key', () => { - const messageHash = randomBytes(32) + const messageHash = randomBytes(32); const privateKey = Buffer.from( '0101010101010101010101010101010101010101010101010101010101010101', 'hex', - ) + ); - const sigObj = secp256k1.ecdsaSign(messageHash, privateKey) - const compressedPubkey = secp256k1.publicKeyCreate(privateKey, true) + const sigObj = secp256k1.ecdsaSign(messageHash, privateKey); + const compressedPubkey = secp256k1.publicKeyCreate(privateKey, true); const yParity = getYParity({ messageHash, @@ -78,15 +78,15 @@ describe('getYParity', () => { s: Buffer.from(sigObj.signature.slice(32, 64)), }, publicKey: Buffer.from(compressedPubkey), - }) + }); - expect(yParity).toBe(sigObj.recid) - }) + expect(yParity).toBe(sigObj.recid); + }); it('should handle mixed format inputs', () => { - const messageHash = randomBytes(32) + const messageHash = randomBytes(32); const { signature, publicKey, recovery } = - createValidSignature(messageHash) + createValidSignature(messageHash); const yParity = getYParity({ messageHash: messageHash.toString('hex'), // No 0x prefix @@ -95,73 +95,73 @@ describe('getYParity', () => { s: `0x${signature.s.toString('hex')}`, // Hex string }, publicKey, // Buffer - }) + }); - expect(yParity).toBe(recovery) - }) - }) + expect(yParity).toBe(recovery); + }); + }); describe('Legacy format with transaction and response', () => { it('should handle Buffer transaction input', () => { - const tx = randomBytes(100) - const hash = Buffer.from(Hash.keccak256(tx)) - const { signature, publicKey, recovery } = createValidSignature(hash) + const tx = randomBytes(100); + const hash = Buffer.from(Hash.keccak256(tx)); + const { signature, publicKey, recovery } = createValidSignature(hash); const resp = { sig: signature, pubkey: publicKey, - } + }; - const yParity = getYParity(tx, resp) - expect(yParity).toBe(recovery) - }) + const yParity = getYParity(tx, resp); + expect(yParity).toBe(recovery); + }); it('should handle hex string as pre-computed hash', () => { // When passing a hex string, it's treated as a pre-computed hash - const hash = randomBytes(32) - const txHex = `0x${hash.toString('hex')}` - const { signature, publicKey, recovery } = createValidSignature(hash) + const hash = randomBytes(32); + const txHex = `0x${hash.toString('hex')}`; + const { signature, publicKey, recovery } = createValidSignature(hash); const resp = { sig: signature, pubkey: publicKey, - } + }; - const yParity = getYParity(txHex, resp) - expect(yParity).toBe(recovery) - }) + const yParity = getYParity(txHex, resp); + expect(yParity).toBe(recovery); + }); it('should handle transaction object with getMessageToSign method', () => { - const messageData = randomBytes(32) + const messageData = randomBytes(32); const mockTx = { _type: 2, // EIP-1559 getMessageToSign: () => messageData, - } + }; const { signature, publicKey, recovery } = - createValidSignature(messageData) + createValidSignature(messageData); const resp = { sig: signature, pubkey: publicKey, - } + }; - const yParity = getYParity(mockTx, resp) - expect(yParity).toBe(recovery) - }) + const yParity = getYParity(mockTx, resp); + expect(yParity).toBe(recovery); + }); it.skip('should handle legacy transaction object', () => { // Skip this test for now - legacy transaction handling is complex // and would require proper RLP encoding to test correctly - }) + }); it('should handle Uint8Array inputs', () => { - const messageHash = new Uint8Array(32) - messageHash.fill(42) + const messageHash = new Uint8Array(32); + messageHash.fill(42); const { signature, publicKey, recovery } = createValidSignature( Buffer.from(messageHash), - ) + ); const resp = { sig: { @@ -169,78 +169,78 @@ describe('getYParity', () => { s: new Uint8Array(signature.s), }, pubkey: new Uint8Array(publicKey), - } + }; - const yParity = getYParity(messageHash, resp) - expect(yParity).toBe(recovery) - }) + const yParity = getYParity(messageHash, resp); + expect(yParity).toBe(recovery); + }); it.skip('should handle direct 32-byte hash input', () => { // Skip for now - this test relies on specific behavior that may differ - }) + }); it('should handle 32-byte hash as Uint8Array', () => { - const hash = new Uint8Array(32) + const hash = new Uint8Array(32); for (let i = 0; i < 32; i++) { - hash[i] = Math.floor(Math.random() * 256) + hash[i] = Math.floor(Math.random() * 256); } const { signature, publicKey, recovery } = createValidSignature( Buffer.from(hash), - ) + ); const resp = { sig: signature, pubkey: publicKey, - } + }; - const yParity = getYParity(hash, resp) - expect(yParity).toBe(recovery) - }) + const yParity = getYParity(hash, resp); + expect(yParity).toBe(recovery); + }); it('should hash non-32-byte inputs', () => { - const shortData = randomBytes(20) - const expectedHash = Buffer.from(Hash.keccak256(shortData)) + const shortData = randomBytes(20); + const expectedHash = Buffer.from(Hash.keccak256(shortData)); const { signature, publicKey, recovery } = - createValidSignature(expectedHash) + createValidSignature(expectedHash); const resp = { sig: signature, pubkey: publicKey, - } + }; - const yParity = getYParity(shortData, resp) - expect(yParity).toBe(recovery) - }) - }) + const yParity = getYParity(shortData, resp); + expect(yParity).toBe(recovery); + }); + }); describe('Error handling', () => { it('should throw error if legacy format missing response', () => { - const tx = randomBytes(32) + const tx = randomBytes(32); expect(() => getYParity(tx)).toThrow( 'Response with sig and pubkey required for legacy format', - ) - }) + ); + }); it('should throw error if response missing sig', () => { - const tx = randomBytes(32) - const resp = { pubkey: randomBytes(65) } + const tx = randomBytes(32); + const resp = { pubkey: randomBytes(65) }; expect(() => getYParity(tx, resp)).toThrow( 'Response with sig and pubkey required for legacy format', - ) - }) + ); + }); it('should throw error if response missing pubkey', () => { - const tx = randomBytes(32) - const resp = { sig: { r: randomBytes(32), s: randomBytes(32) } } + const tx = randomBytes(32); + const resp = { sig: { r: randomBytes(32), s: randomBytes(32) } }; expect(() => getYParity(tx, resp)).toThrow( 'Response with sig and pubkey required for legacy format', - ) - }) + ); + }); it('should throw error if recovery fails', () => { - const messageHash = randomBytes(32) - const wrongHash = randomBytes(32) - const { signature, publicKey } = createValidSignature(wrongHash) + const messageHash = randomBytes(32); + const wrongHash = randomBytes(32); + const { signature, publicKey } = createValidSignature(wrongHash); expect(() => getYParity({ @@ -250,17 +250,17 @@ describe('getYParity', () => { }), ).toThrow( 'Failed to recover Y parity. Bad signature or transaction data.', - ) - }) + ); + }); it('should throw error with invalid signature', () => { - const messageHash = randomBytes(32) + const messageHash = randomBytes(32); const invalidSig = { r: randomBytes(32), s: randomBytes(32), - } - const randomPubkey = randomBytes(65) - randomPubkey[0] = 0x04 // Ensure valid uncompressed format + }; + const randomPubkey = randomBytes(65); + randomPubkey[0] = 0x04; // Ensure valid uncompressed format expect(() => getYParity({ @@ -268,32 +268,32 @@ describe('getYParity', () => { signature: invalidSig, publicKey: randomPubkey, }), - ).toThrow() // Just check that it throws, don't check exact message - }) - }) + ).toThrow(); // Just check that it throws, don't check exact message + }); + }); describe('Real world scenarios', () => { it('should handle EIP-7702 authorization signature', () => { // Simulate the exact scenario from signAuthorization - const MAGIC = Buffer.from([0x05]) + const MAGIC = Buffer.from([0x05]); // This would normally use RLP.encode but we'll create a test message const message = Buffer.concat([ MAGIC, Buffer.from('test_rlp_encoded_data', 'utf8'), - ]) + ]); - const messageHash = Buffer.from(Hash.keccak256(message)) + const messageHash = Buffer.from(Hash.keccak256(message)); const { signature, publicKey, recovery } = - createValidSignature(messageHash) + createValidSignature(messageHash); // Test both Buffer format (as returned by device) const yParity1 = getYParity({ messageHash, signature, publicKey, - }) - expect(yParity1).toBe(recovery) + }); + expect(yParity1).toBe(recovery); // Test with hex string format (as might be used in API) const yParity2 = getYParity({ @@ -303,47 +303,47 @@ describe('getYParity', () => { s: `0x${signature.s.toString('hex')}`, }, publicKey, - }) - expect(yParity2).toBe(recovery) - }) + }); + expect(yParity2).toBe(recovery); + }); it('should return consistent y-parity for multiple calls with same data', () => { - const messageHash = randomBytes(32) - const { signature, publicKey } = createValidSignature(messageHash) + const messageHash = randomBytes(32); + const { signature, publicKey } = createValidSignature(messageHash); const yParity1 = getYParity({ messageHash, signature, publicKey, - }) + }); const yParity2 = getYParity({ messageHash, signature, publicKey, - }) + }); - expect(yParity1).toBe(yParity2) - }) + expect(yParity1).toBe(yParity2); + }); it('should handle real signature that should return y-parity of 1', () => { // Use a specific private key that we know produces recovery id 1 for a specific message - let foundYParityOne = false + let foundYParityOne = false; // Try multiple messages until we get one with y-parity 1 for (let i = 0; i < 100; i++) { const messageHash = Buffer.from( Hash.keccak256(Buffer.from(`test message ${i}`)), - ) + ); const privateKey = Buffer.from( '0101010101010101010101010101010101010101010101010101010101010101', 'hex', - ) + ); - const sigObj = secp256k1.ecdsaSign(messageHash, privateKey) + const sigObj = secp256k1.ecdsaSign(messageHash, privateKey); if (sigObj.recid === 1) { - const publicKey = secp256k1.publicKeyCreate(privateKey, false) + const publicKey = secp256k1.publicKeyCreate(privateKey, false); const yParity = getYParity({ messageHash, @@ -352,18 +352,18 @@ describe('getYParity', () => { s: Buffer.from(sigObj.signature.slice(32, 64)), }, publicKey: Buffer.from(publicKey), - }) + }); - expect(yParity).toBe(1) - foundYParityOne = true - break + expect(yParity).toBe(1); + foundYParityOne = true; + break; } } - expect(foundYParityOne).toBe(true) - }) - }) -}) + expect(foundYParityOne).toBe(true); + }); + }); +}); describe('getV function', () => { // Helper to create a valid signature @@ -374,10 +374,10 @@ describe('getV function', () => { Buffer.from( '0101010101010101010101010101010101010101010101010101010101010101', 'hex', - ) + ); - const sigObj = secp256k1.ecdsaSign(messageHash, privKey) - const publicKey = secp256k1.publicKeyCreate(privKey, false) + const sigObj = secp256k1.ecdsaSign(messageHash, privKey); + const publicKey = secp256k1.publicKeyCreate(privKey, false); return { sig: { @@ -386,31 +386,31 @@ describe('getV function', () => { }, pubkey: Buffer.from(publicKey), recovery: sigObj.recid, - } - } + }; + }; it('should handle unsigned legacy transaction with valid signature', () => { // A simple unsigned legacy transaction const unsignedTxRLP = Buffer.from( 'e9808504a817c800825208943535353535353535353535353535353535353535880de0b6b3a764000080', 'hex', - ) + ); // Hash the transaction - const hash = Buffer.from(Hash.keccak256(unsignedTxRLP)) + const hash = Buffer.from(Hash.keccak256(unsignedTxRLP)); // Create a valid signature for this hash - const resp = createValidSignature(hash) + const resp = createValidSignature(hash); // Should return correct v value (27 or 28 for non-EIP155) - const v = getV(unsignedTxRLP, resp) - expect(v.toNumber()).toBe(27 + resp.recovery) - }) + const v = getV(unsignedTxRLP, resp); + expect(v.toNumber()).toBe(27 + resp.recovery); + }); it('should throw error when pubkey does not match signature', () => { // This is a signed legacy transaction const signedTx = - '0xf86c0a8504a817c800825208943535353535353535353535353535353535353535880de0b6b3a76400008025a0134f5038e0e6a96741e17a82c8df13e9dc10c3b0e9e956cf7dcf21e1e3b73f9fa0638cf1b1f9dd5e6e8e6b9a8e6e8e6b9a8e6e8e6b9a8e6e8e6b9a8e6e8e6b9a8' + '0xf86c0a8504a817c800825208943535353535353535353535353535353535353535880de0b6b3a76400008025a0134f5038e0e6a96741e17a82c8df13e9dc10c3b0e9e956cf7dcf21e1e3b73f9fa0638cf1b1f9dd5e6e8e6b9a8e6e8e6b9a8e6e8e6b9a8e6e8e6b9a8e6e8e6b9a8'; const mockResp = { sig: { @@ -425,14 +425,14 @@ describe('getV function', () => { }, // This is a fake pubkey, so recovery will fail pubkey: Buffer.from(`04${'1'.repeat(128)}`, 'hex'), - } + }; - expect(() => getV(signedTx, mockResp)).toThrow() - }) + expect(() => getV(signedTx, mockResp)).toThrow(); + }); it('should throw error when signature is invalid', () => { const txHex = - '0xe9808504a817c800825208943535353535353535353535353535353535353535880de0b6b3a764000080' + '0xe9808504a817c800825208943535353535353535353535353535353535353535880de0b6b3a764000080'; const mockResp = { sig: { @@ -440,8 +440,8 @@ describe('getV function', () => { s: `0x${'2'.repeat(64)}`, // 32 bytes as hex string }, pubkey: Buffer.from(`04${'1'.repeat(128)}`, 'hex'), - } + }; - expect(() => getV(txHex, mockResp)).toThrow() - }) -}) + expect(() => getV(txHex, mockResp)).toThrow(); + }); +}); diff --git a/packages/sdk/src/__test__/unit/validators.test.ts b/packages/sdk/src/__test__/unit/validators.test.ts index c930ca6e..a50978ed 100644 --- a/packages/sdk/src/__test__/unit/validators.test.ts +++ b/packages/sdk/src/__test__/unit/validators.test.ts @@ -1,69 +1,69 @@ -import { normalizeToViemTransaction } from '../../ethereum' +import { normalizeToViemTransaction } from '../../ethereum'; import { validateAddKvRequest, validateConnectRequest, validateGetAddressesRequest, validateGetKvRequest, validateRemoveKvRequest, -} from '../../functions' +} from '../../functions'; import { isValid4ByteResponse, isValidBlockExplorerResponse, -} from '../../shared/validators' +} from '../../shared/validators'; import { buildGetAddressesObject, buildValidateConnectObject, buildValidateRequestObject, -} from '../utils/builders' +} from '../utils/builders'; describe('validators', () => { describe('connect', () => { test('should successfully validate', () => { - validateConnectRequest(buildValidateConnectObject()) - }) + validateConnectRequest(buildValidateConnectObject()); + }); // NOTE: There aren't many possible error conditions because // the Client constructor has lots of fallback values. However, // we should validate that you can't set a null ephemeral pub. test('should throw errors on validation failure', () => { - const req = buildValidateConnectObject({ name: '' }) + const req = buildValidateConnectObject({ name: '' }); expect(() => { - req.client.ephemeralPub = null - }).toThrowError() - }) - }) + req.client.ephemeralPub = null; + }).toThrowError(); + }); + }); describe('getAddresses', () => { test('should successfully validate', () => { - const getAddressesBundle = buildGetAddressesObject({}) - validateGetAddressesRequest(getAddressesBundle) - }) + const getAddressesBundle = buildGetAddressesObject({}); + validateGetAddressesRequest(getAddressesBundle); + }); test('encodeGetAddressesRequest should throw with invalid startPath', () => { - const startPath = [0x80000000 + 44, 0x80000000 + 60, 0, 0, 0, 0, 0] - const fwVersion = Buffer.from([0, 0, 0]) + const startPath = [0x80000000 + 44, 0x80000000 + 60, 0, 0, 0, 0, 0]; + const fwVersion = Buffer.from([0, 0, 0]); const testEncodingFunction = () => validateGetAddressesRequest( buildGetAddressesObject({ startPath, fwVersion }), - ) - expect(testEncodingFunction).toThrowError() - }) - }) + ); + expect(testEncodingFunction).toThrowError(); + }); + }); describe('KvRecords', () => { describe('addKvRecords', () => { test('should successfully validate', () => { const validateAddKvBundle: any = buildValidateRequestObject({ records: { key: 'value' }, - }) - validateAddKvRequest(validateAddKvBundle) - }) + }); + validateAddKvRequest(validateAddKvBundle); + }); test('should throw errors on validation failure', () => { - const validateAddKvBundle: any = buildValidateRequestObject({}) - expect(() => validateAddKvRequest(validateAddKvBundle)).toThrowError() - }) - }) + const validateAddKvBundle: any = buildValidateRequestObject({}); + expect(() => validateAddKvRequest(validateAddKvBundle)).toThrowError(); + }); + }); describe('getKvRecords', () => { test('should successfully validate', () => { @@ -71,33 +71,33 @@ describe('validators', () => { n: 1, type: 1, start: 0, - }) - validateGetKvRequest(validateGetKvBundle) - }) + }); + validateGetKvRequest(validateGetKvBundle); + }); test('should throw errors on validation failure', () => { - const validateGetKvBundle: any = buildValidateRequestObject({ n: 0 }) - expect(() => validateGetKvRequest(validateGetKvBundle)).toThrowError() - }) - }) + const validateGetKvBundle: any = buildValidateRequestObject({ n: 0 }); + expect(() => validateGetKvRequest(validateGetKvBundle)).toThrowError(); + }); + }); describe('removeKvRecords', () => { test('should successfully validate', () => { const validateRemoveKvBundle: any = buildValidateRequestObject({ ids: [1], type: 1, - }) - validateRemoveKvRequest(validateRemoveKvBundle) - }) + }); + validateRemoveKvRequest(validateRemoveKvBundle); + }); test('should throw errors on validation failure', () => { - const validateRemoveKvBundle: any = buildValidateRequestObject({}) + const validateRemoveKvBundle: any = buildValidateRequestObject({}); expect(() => validateRemoveKvRequest(validateRemoveKvBundle), - ).toThrowError() - }) - }) - }) + ).toThrowError(); + }); + }); + }); describe('abi data responses', () => { describe('block explorers', () => { @@ -105,18 +105,18 @@ describe('validators', () => { const response: any = { result: '[{"inputs":[],"stateMutability":"nonpayable","type":"constructor"},{"stateMutability":"payable","type":"fallback"},{"inputs":[],"name":"implementation","outputs":[{"internalType":"address","name":"","type":"address"}],"stateMutability":"view","type":"function"},{"stateMutability":"payable","type":"receive"}]', - } - expect(isValidBlockExplorerResponse(response)).toBe(true) - }) + }; + expect(isValidBlockExplorerResponse(response)).toBe(true); + }); test('should validate as false bad data', () => { const response: any = { result: 'Max rate limit reached, please use API Key for higher rate limit', - } - expect(isValidBlockExplorerResponse(response)).toBe(false) - }) - }) + }; + expect(isValidBlockExplorerResponse(response)).toBe(false); + }); + }); describe('4byte', () => { test('should successfully validate etherscan data', () => { @@ -130,18 +130,18 @@ describe('validators', () => { bytes_signature: 'test', }, ], - } - expect(isValid4ByteResponse(response)).toBe(true) - }) + }; + expect(isValid4ByteResponse(response)).toBe(true); + }); test('should validate as false bad data', () => { const response: any = { results: [], - } - expect(isValid4ByteResponse(response)).toBe(false) - }) - }) - }) + }; + expect(isValid4ByteResponse(response)).toBe(false); + }); + }); + }); describe('transaction validation', () => { describe('EIP-7702 transactions', () => { @@ -154,11 +154,11 @@ describe('validators', () => { { chainId: 1, address: `0x${'2'.repeat(40)}`, nonce: 0 }, ], gasPrice: '15000000000', - } + }; - expect(() => normalizeToViemTransaction(tx)).toThrow() - }) - }) + expect(() => normalizeToViemTransaction(tx)).toThrow(); + }); + }); describe('negative values', () => { test('rejects negative value', () => { @@ -166,10 +166,10 @@ describe('validators', () => { to: `0x${'1'.repeat(40)}`, value: -100, gasPrice: '10000000000', - } + }; - expect(() => normalizeToViemTransaction(tx)).toThrow() - }) + expect(() => normalizeToViemTransaction(tx)).toThrow(); + }); test('rejects negative nonce', () => { const tx = { @@ -177,21 +177,21 @@ describe('validators', () => { value: '100', gasPrice: '10000000000', nonce: -1, - } + }; - expect(() => normalizeToViemTransaction(tx)).toThrow() - }) + expect(() => normalizeToViemTransaction(tx)).toThrow(); + }); test('rejects negative gas price', () => { const tx = { to: `0x${'1'.repeat(40)}`, value: '100', gasPrice: -10, - } + }; - expect(() => normalizeToViemTransaction(tx)).toThrow() - }) - }) + expect(() => normalizeToViemTransaction(tx)).toThrow(); + }); + }); describe('invalid data types', () => { test('rejects boolean data field', () => { @@ -202,11 +202,11 @@ describe('validators', () => { gasPrice: Number.NaN, nonce: null, data: false, - } + }; - expect(() => normalizeToViemTransaction(tx as any)).toThrow() - }) - }) + expect(() => normalizeToViemTransaction(tx as any)).toThrow(); + }); + }); describe('authorization list validation', () => { test('rejects invalid authorization data', () => { @@ -223,11 +223,11 @@ describe('validators', () => { nonce: undefined, }, ], - } + }; - expect(() => normalizeToViemTransaction(tx as any)).toThrow() - }) - }) + expect(() => normalizeToViemTransaction(tx as any)).toThrow(); + }); + }); describe('circular references', () => { test('rejects circular references', () => { @@ -237,14 +237,14 @@ describe('validators', () => { chainId: 1, maxFeePerGas: '20000000000', maxPriorityFeePerGas: '2000000000', - } + }; - tx.self = tx - tx.authorizationList = [tx] + tx.self = tx; + tx.authorizationList = [tx]; - expect(() => normalizeToViemTransaction(tx)).toThrow() - }) - }) + expect(() => normalizeToViemTransaction(tx)).toThrow(); + }); + }); describe('gas field handling', () => { test('gasLimit takes precedence over gas', () => { @@ -255,11 +255,11 @@ describe('validators', () => { gasPrice: '15000000000', gas: '50000', gasLimit: '21000', - } + }; - const result = normalizeToViemTransaction(tx) - expect(result.gas).toBe(21000n) - }) + const result = normalizeToViemTransaction(tx); + expect(result.gas).toBe(21000n); + }); test('accepts zero gas values', () => { const tx = { @@ -268,14 +268,14 @@ describe('validators', () => { chainId: 1, gasPrice: '0', gasLimit: '0', - } + }; - const result = normalizeToViemTransaction(tx) - expect(result.gas).toBe(0n) - expect(result.type).toBe('legacy') - expect((result as any).gasPrice).toBe(0n) - }) - }) + const result = normalizeToViemTransaction(tx); + expect(result.gas).toBe(0n); + expect(result.type).toBe('legacy'); + expect((result as any).gasPrice).toBe(0n); + }); + }); describe('chainId validation', () => { test('rejects zero chainId', () => { @@ -284,10 +284,10 @@ describe('validators', () => { value: '1000000000000000000', chainId: 0, gasPrice: '15000000000', - } + }; - expect(() => normalizeToViemTransaction(tx)).toThrow() - }) + expect(() => normalizeToViemTransaction(tx)).toThrow(); + }); test('rejects non-integer chainId', () => { const tx = { @@ -295,10 +295,10 @@ describe('validators', () => { value: '1000000000000000000', chainId: 1.5, gasPrice: '15000000000', - } + }; - expect(() => normalizeToViemTransaction(tx)).toThrow() - }) - }) - }) -}) + expect(() => normalizeToViemTransaction(tx)).toThrow(); + }); + }); + }); +}); diff --git a/packages/sdk/src/__test__/utils/__test__/builders.test.ts b/packages/sdk/src/__test__/utils/__test__/builders.test.ts index 0e6504f9..4c618103 100644 --- a/packages/sdk/src/__test__/utils/__test__/builders.test.ts +++ b/packages/sdk/src/__test__/utils/__test__/builders.test.ts @@ -1,12 +1,16 @@ -import { buildEvmReq, buildRandomVectors, getFwVersionsList } from '../builders' +import { + buildEvmReq, + buildRandomVectors, + getFwVersionsList, +} from '../builders'; describe('building', () => { test('should test client', () => { - expect(getFwVersionsList()).toMatchSnapshot() - }) + expect(getFwVersionsList()).toMatchSnapshot(); + }); test('RANDOM_VEC', () => { - const RANDOM_VEC = buildRandomVectors(10) + const RANDOM_VEC = buildRandomVectors(10); expect(RANDOM_VEC).toMatchInlineSnapshot(` [ "9f2c1f8", @@ -20,15 +24,15 @@ describe('building', () => { "1121991", "2851e10c", ] - `) - }) + `); + }); test('buildEvmReq', () => { const testObj = buildEvmReq({ common: 'test', data: { payload: 'test' }, txData: { data: 'test', type: undefined }, - }) + }); expect(testObj).toMatchInlineSnapshot(` { "common": "test", @@ -56,6 +60,6 @@ describe('building', () => { "value": 100, }, } - `) - }) -}) + `); + }); +}); diff --git a/packages/sdk/src/__test__/utils/__test__/serializers.test.ts b/packages/sdk/src/__test__/utils/__test__/serializers.test.ts index 8e9c5582..d8a99eb4 100644 --- a/packages/sdk/src/__test__/utils/__test__/serializers.test.ts +++ b/packages/sdk/src/__test__/utils/__test__/serializers.test.ts @@ -1,7 +1,7 @@ import { deserializeObjectWithBuffers, serializeObjectWithBuffers, -} from '../serializers' +} from '../serializers'; describe('serializers', () => { test('serialize obj', () => { @@ -12,8 +12,8 @@ describe('serializers', () => { d: 2, e: Buffer.from('test'), }, - } - const serialized = serializeObjectWithBuffers(obj) + }; + const serialized = serializeObjectWithBuffers(obj); expect(serialized).toMatchInlineSnapshot(` { "a": 1, @@ -29,8 +29,8 @@ describe('serializers', () => { }, }, } - `) - }) + `); + }); test('deserialize obj', () => { const obj = { @@ -46,9 +46,9 @@ describe('serializers', () => { value: '74657374', }, }, - } + }; - const serialized = deserializeObjectWithBuffers(obj) + const serialized = deserializeObjectWithBuffers(obj); expect(serialized).toMatchInlineSnapshot(` { "a": 1, @@ -74,6 +74,6 @@ describe('serializers', () => { }, }, } - `) - }) -}) + `); + }); +}); diff --git a/packages/sdk/src/__test__/utils/builders.ts b/packages/sdk/src/__test__/utils/builders.ts index 76b2e592..0e3b8a50 100644 --- a/packages/sdk/src/__test__/utils/builders.ts +++ b/packages/sdk/src/__test__/utils/builders.ts @@ -1,35 +1,39 @@ -import { Common, Hardfork, Mainnet } from '@ethereumjs/common' -import { RLP } from '@ethereumjs/rlp' -import { type TypedTransaction, createTx } from '@ethereumjs/tx' -import { generate as randomWords } from 'random-words' -import { Constants } from '../..' -import { Client } from '../../client' -import { CURRENCIES, HARDENED_OFFSET, getFwVersionConst } from '../../constants' -import type { Currency, SignRequestParams, SigningPath } from '../../types' -import type { FirmwareConstants } from '../../types/firmware' -import { randomBytes } from '../../util' -import { MSG_PAYLOAD_METADATA_SZ } from './constants' -import { getN, getPrng } from './getters' +import { Common, Hardfork, Mainnet } from '@ethereumjs/common'; +import { RLP } from '@ethereumjs/rlp'; +import { type TypedTransaction, createTx } from '@ethereumjs/tx'; +import { generate as randomWords } from 'random-words'; +import { Constants } from '../..'; +import { Client } from '../../client'; +import { + CURRENCIES, + HARDENED_OFFSET, + getFwVersionConst, +} from '../../constants'; +import type { Currency, SignRequestParams, SigningPath } from '../../types'; +import type { FirmwareConstants } from '../../types/firmware'; +import { randomBytes } from '../../util'; +import { MSG_PAYLOAD_METADATA_SZ } from './constants'; +import { getN, getPrng } from './getters'; import { BTC_PURPOSE_P2PKH, ETH_COIN, buildRandomEip712Object, getTestVectors, -} from './helpers' +} from './helpers'; -const prng = getPrng() +const prng = getPrng(); export const getFwVersionsList = () => { - const arr: number[][] = [] + const arr: number[][] = []; Array.from({ length: 1 }, (x, i) => { Array.from({ length: 10 }, (y, j) => { Array.from({ length: 5 }, (z, k) => { - arr.push([i, j + 10, k]) - }) - }) - }) - return arr -} + arr.push([i, j + 10, k]); + }); + }); + }); + return arr; +}; export const buildFirmwareConstants = (...overrides: any) => { return { @@ -67,8 +71,8 @@ export const buildFirmwareConstants = (...overrides: any) => { reqMaxDataSz: 1678, varAddrPathSzAllowed: true, ...overrides, - } as FirmwareConstants -} + } as FirmwareConstants; +}; export const buildWallet = (overrides?) => ({ uid: Buffer.from( @@ -78,7 +82,7 @@ export const buildWallet = (overrides?) => ({ capabilities: 1, external: true, ...overrides, -}) +}); export const buildGetAddressesObject = (overrides?) => ({ startPath: [0x80000000 + 44, 0x80000000 + 60, 0x80000000, 0, 0], @@ -87,10 +91,10 @@ export const buildGetAddressesObject = (overrides?) => ({ fwConstants: buildFirmwareConstants(), wallet: buildWallet(), ...overrides, -}) +}); export const buildSignObject = (fwVersion, overrides?) => { - const fwConstants = getFwVersionConst(fwVersion) + const fwConstants = getFwVersionConst(fwVersion); return { data: { to: '0xc0c8f96C2fE011cc96770D2e37CfbfeAFB585F0e', @@ -105,30 +109,30 @@ export const buildSignObject = (fwVersion, overrides?) => { currency: CURRENCIES.ETH as Currency, fwConstants, ...overrides, - } -} + }; +}; export const buildSharedSecret = () => { return Buffer.from([ 89, 60, 130, 80, 168, 252, 34, 136, 230, 71, 230, 158, 51, 13, 239, 237, 6, 246, 71, 232, 232, 175, 193, 106, 106, 185, 38, 1, 163, 14, 225, 101, - ]) -} + ]); +}; export const getNumIter = (n: number | string | undefined = getN()) => - n ? Number.parseInt(`${n}`) : 5 + n ? Number.parseInt(`${n}`) : 5; /** Generate a bunch of random test vectors using the PRNG */ export const buildRandomVectors = (n: number | string | undefined = getN()) => { - const numIter = getNumIter(n) + const numIter = getNumIter(n); // Generate a bunch of random test vectors using the PRNG - const RANDOM_VEC: any[] = [] + const RANDOM_VEC: any[] = []; for (let i = 0; i < numIter; i++) { - RANDOM_VEC.push(Math.floor(1000000000 * prng.quick()).toString(16)) + RANDOM_VEC.push(Math.floor(1000000000 * prng.quick()).toString(16)); } - return RANDOM_VEC -} + return RANDOM_VEC; +}; export const DEFAULT_SIGNER = [ BTC_PURPOSE_P2PKH, @@ -136,7 +140,7 @@ export const DEFAULT_SIGNER = [ HARDENED_OFFSET, 0, 0, -] +]; export const buildTx = (data: `0x${string}` = '0xdeadbeef') => { return createTx( @@ -156,24 +160,24 @@ export const buildTx = (data: `0x${string}` = '0xdeadbeef') => { hardfork: Hardfork.London, }), }, - ) -} + ); +}; export const buildEthSignRequest = async ( client: Client, txDataOverrides?: any, ): Promise => { if (client.getFwVersion()?.major === 0 && client.getFwVersion()?.minor < 15) { - console.warn('Please update firmware. Skipping ETH signing tests.') - return + console.warn('Please update firmware. Skipping ETH signing tests.'); + return; } - const fwConstants = client.getFwConstants() - const signerPath = [BTC_PURPOSE_P2PKH, ETH_COIN, HARDENED_OFFSET, 0, 0] + const fwConstants = client.getFwConstants(); + const signerPath = [BTC_PURPOSE_P2PKH, ETH_COIN, HARDENED_OFFSET, 0, 0]; const common = new Common({ chain: Mainnet, hardfork: Hardfork.London, - }) + }); const txData = { type: 2, maxFeePerGas: 1200000000, @@ -184,8 +188,8 @@ export const buildEthSignRequest = async ( value: 1000000000000, data: '0x17e914679b7e160613be4f8c2d3203d236286d74eb9192f6d6f71b9118a42bb033ccd8e8', ...txDataOverrides, - } - const tx = createTx(txData, { common }) + }; + const tx = createTx(txData, { common }); const req = { data: { signerPath, @@ -194,10 +198,10 @@ export const buildEthSignRequest = async ( hashType: Constants.SIGNING.HASHES.KECCAK256, encodingType: Constants.SIGNING.ENCODINGS.EVM, }, - } + }; const maxDataSz = fwConstants.ethMaxDataSz + - fwConstants.extraDataMaxFrames * fwConstants.extraDataFrameSz + fwConstants.extraDataMaxFrames * fwConstants.extraDataFrameSz; return { fwConstants, signerPath, @@ -206,8 +210,8 @@ export const buildEthSignRequest = async ( tx, req, maxDataSz, - } -} + }; +}; export const buildTxReq = (tx: TypedTransaction) => ({ data: { @@ -217,7 +221,7 @@ export const buildTxReq = (tx: TypedTransaction) => ({ hashType: Constants.SIGNING.HASHES.KECCAK256, encodingType: Constants.SIGNING.ENCODINGS.EVM, }, -}) +}); export const buildMsgReq = ( payload = 'hello ethereum', @@ -231,18 +235,18 @@ export const buildMsgReq = ( curveType: Constants.SIGNING.CURVES.SECP256K1, hashType: Constants.SIGNING.HASHES.KECCAK256, }, -}) +}); export const buildEvmReq = (overrides?: { - data?: any - txData?: any - common?: any + data?: any; + txData?: any; + common?: any; }) => { - let chainInfo = null + let chainInfo = null; if (overrides?.common) { - chainInfo = overrides.common + chainInfo = overrides.common; } else { - chainInfo = new Common({ chain: Mainnet, hardfork: Hardfork.London }) + chainInfo = new Common({ chain: Mainnet, hardfork: Hardfork.London }); } const req = { data: { @@ -265,42 +269,42 @@ export const buildEvmReq = (overrides?: { ...overrides?.txData, }, common: chainInfo, - } - return req -} + }; + return req; +}; export const buildEncDefs = (vectors: any) => { const encDefs = vectors.canonicalNames.map((name: string) => { // For each canonical name, we need to RLP encode just the name - return RLP.encode([name]) - }) + return RLP.encode([name]); + }); // The calldata is already in hex format, we just need to ensure it has 0x prefix const encDefsCalldata = vectors.canonicalNames.map( (_: string, idx: number) => { - const calldata = `0x${idx.toString(16).padStart(8, '0')}` - return calldata + const calldata = `0x${idx.toString(16).padStart(8, '0')}`; + return calldata; }, - ) + ); - return { encDefs, encDefsCalldata } -} + return { encDefs, encDefsCalldata }; +}; export function buildRandomMsg(type, client: Client) { function randInt(n: number) { - return Math.floor(n * prng.quick()) + return Math.floor(n * prng.quick()); } if (type === 'signPersonal') { // A random string will do - const isHexStr = randInt(2) > 0 - const fwConstants = client.getFwConstants() - const L = randInt(fwConstants.ethMaxDataSz - MSG_PAYLOAD_METADATA_SZ) - if (isHexStr) return `0x${randomBytes(L).toString('hex')}` + const isHexStr = randInt(2) > 0; + const fwConstants = client.getFwConstants(); + const L = randInt(fwConstants.ethMaxDataSz - MSG_PAYLOAD_METADATA_SZ); + if (isHexStr) return `0x${randomBytes(L).toString('hex')}`; // Get L hex bytes (represented with a string with 2*L chars) - else return randomWords({ exactly: L, join: ' ' }).slice(0, L) // Get L ASCII characters (bytes) + else return randomWords({ exactly: L, join: ' ' }).slice(0, L); // Get L ASCII characters (bytes) } else if (type === 'eip712') { - return buildRandomEip712Object(randInt) + return buildRandomEip712Object(randInt); } } @@ -324,7 +328,7 @@ export function buildEthMsgReq( payload, protocol, }, - } + }; } export const buildValidateConnectObject = (overrides?) => ({ @@ -332,25 +336,25 @@ export const buildValidateConnectObject = (overrides?) => ({ key: 'test', baseUrl: 'https://www.test.com', ...overrides, -}) +}); export const buildValidateRequestObject = (overrides?) => { - const fwConstants = buildFirmwareConstants() + const fwConstants = buildFirmwareConstants(); return { fwConstants, ...overrides, - } -} + }; +}; // Most of the endpoint validators (for encrypted requests) // will require a connected client instance. export function buildMockConnectedClient(opts) { - const _stateData = JSON.parse(getTestVectors().dehydratedClientState) + const _stateData = JSON.parse(getTestVectors().dehydratedClientState); const stateData = { ..._stateData, ...opts, - } + }; return new Client({ stateData: JSON.stringify(stateData), - }) + }); } diff --git a/packages/sdk/src/__test__/utils/constants.ts b/packages/sdk/src/__test__/utils/constants.ts index b040bb7c..fb0daad1 100644 --- a/packages/sdk/src/__test__/utils/constants.ts +++ b/packages/sdk/src/__test__/utils/constants.ts @@ -1 +1 @@ -export const MSG_PAYLOAD_METADATA_SZ = 28 // Metadata that must go in ETH_MSG requests +export const MSG_PAYLOAD_METADATA_SZ = 28; // Metadata that must go in ETH_MSG requests diff --git a/packages/sdk/src/__test__/utils/determinism.ts b/packages/sdk/src/__test__/utils/determinism.ts index 762eb2ed..236de499 100644 --- a/packages/sdk/src/__test__/utils/determinism.ts +++ b/packages/sdk/src/__test__/utils/determinism.ts @@ -1,77 +1,77 @@ -import type { TypedTransaction } from '@ethereumjs/tx' -import { SignTypedDataVersion, TypedDataUtils } from '@metamask/eth-sig-util' -import BIP32Factory from 'bip32' -import { ecsign, privateToAddress } from 'ethereumjs-util' -import { Hash } from 'ox' -import * as ecc from 'tiny-secp256k1' -import type { Client } from '../../client' -import { getPathStr } from '../../shared/utilities' -import type { SigningPath } from '../../types' -import { ethPersonalSignMsg, getSigStr } from './helpers' -import { TEST_SEED } from './testConstants' +import type { TypedTransaction } from '@ethereumjs/tx'; +import { SignTypedDataVersion, TypedDataUtils } from '@metamask/eth-sig-util'; +import BIP32Factory from 'bip32'; +import { ecsign, privateToAddress } from 'ethereumjs-util'; +import { Hash } from 'ox'; +import * as ecc from 'tiny-secp256k1'; +import type { Client } from '../../client'; +import { getPathStr } from '../../shared/utilities'; +import type { SigningPath } from '../../types'; +import { ethPersonalSignMsg, getSigStr } from './helpers'; +import { TEST_SEED } from './testConstants'; export async function testUniformSigs( payload: any, tx: TypedTransaction, client: Client, ) { - const tx1Resp = await client.sign(payload) - const tx2Resp = await client.sign(payload) - const tx3Resp = await client.sign(payload) - const tx4Resp = await client.sign(payload) - const tx5Resp = await client.sign(payload) + const tx1Resp = await client.sign(payload); + const tx2Resp = await client.sign(payload); + const tx3Resp = await client.sign(payload); + const tx4Resp = await client.sign(payload); + const tx5Resp = await client.sign(payload); // Check sig 1 - expect(getSigStr(tx1Resp, tx)).toEqual(getSigStr(tx2Resp, tx)) - expect(getSigStr(tx1Resp, tx)).toEqual(getSigStr(tx3Resp, tx)) - expect(getSigStr(tx1Resp, tx)).toEqual(getSigStr(tx4Resp, tx)) - expect(getSigStr(tx1Resp, tx)).toEqual(getSigStr(tx5Resp, tx)) + expect(getSigStr(tx1Resp, tx)).toEqual(getSigStr(tx2Resp, tx)); + expect(getSigStr(tx1Resp, tx)).toEqual(getSigStr(tx3Resp, tx)); + expect(getSigStr(tx1Resp, tx)).toEqual(getSigStr(tx4Resp, tx)); + expect(getSigStr(tx1Resp, tx)).toEqual(getSigStr(tx5Resp, tx)); // Check sig 2 - expect(getSigStr(tx2Resp, tx)).toEqual(getSigStr(tx1Resp, tx)) - expect(getSigStr(tx2Resp, tx)).toEqual(getSigStr(tx3Resp, tx)) - expect(getSigStr(tx2Resp, tx)).toEqual(getSigStr(tx4Resp, tx)) - expect(getSigStr(tx2Resp, tx)).toEqual(getSigStr(tx5Resp, tx)) + expect(getSigStr(tx2Resp, tx)).toEqual(getSigStr(tx1Resp, tx)); + expect(getSigStr(tx2Resp, tx)).toEqual(getSigStr(tx3Resp, tx)); + expect(getSigStr(tx2Resp, tx)).toEqual(getSigStr(tx4Resp, tx)); + expect(getSigStr(tx2Resp, tx)).toEqual(getSigStr(tx5Resp, tx)); // Check sig 3 - expect(getSigStr(tx3Resp, tx)).toEqual(getSigStr(tx1Resp, tx)) - expect(getSigStr(tx3Resp, tx)).toEqual(getSigStr(tx2Resp, tx)) - expect(getSigStr(tx3Resp, tx)).toEqual(getSigStr(tx4Resp, tx)) - expect(getSigStr(tx3Resp, tx)).toEqual(getSigStr(tx5Resp, tx)) + expect(getSigStr(tx3Resp, tx)).toEqual(getSigStr(tx1Resp, tx)); + expect(getSigStr(tx3Resp, tx)).toEqual(getSigStr(tx2Resp, tx)); + expect(getSigStr(tx3Resp, tx)).toEqual(getSigStr(tx4Resp, tx)); + expect(getSigStr(tx3Resp, tx)).toEqual(getSigStr(tx5Resp, tx)); // Check sig 4 - expect(getSigStr(tx4Resp, tx)).toEqual(getSigStr(tx1Resp, tx)) - expect(getSigStr(tx4Resp, tx)).toEqual(getSigStr(tx2Resp, tx)) - expect(getSigStr(tx4Resp, tx)).toEqual(getSigStr(tx3Resp, tx)) - expect(getSigStr(tx4Resp, tx)).toEqual(getSigStr(tx5Resp, tx)) + expect(getSigStr(tx4Resp, tx)).toEqual(getSigStr(tx1Resp, tx)); + expect(getSigStr(tx4Resp, tx)).toEqual(getSigStr(tx2Resp, tx)); + expect(getSigStr(tx4Resp, tx)).toEqual(getSigStr(tx3Resp, tx)); + expect(getSigStr(tx4Resp, tx)).toEqual(getSigStr(tx5Resp, tx)); // Check sig 5 - expect(getSigStr(tx5Resp, tx)).toEqual(getSigStr(tx1Resp, tx)) - expect(getSigStr(tx5Resp, tx)).toEqual(getSigStr(tx2Resp, tx)) - expect(getSigStr(tx5Resp, tx)).toEqual(getSigStr(tx3Resp, tx)) - expect(getSigStr(tx5Resp, tx)).toEqual(getSigStr(tx4Resp, tx)) + expect(getSigStr(tx5Resp, tx)).toEqual(getSigStr(tx1Resp, tx)); + expect(getSigStr(tx5Resp, tx)).toEqual(getSigStr(tx2Resp, tx)); + expect(getSigStr(tx5Resp, tx)).toEqual(getSigStr(tx3Resp, tx)); + expect(getSigStr(tx5Resp, tx)).toEqual(getSigStr(tx4Resp, tx)); } export function deriveAddress(seed: Buffer, path: SigningPath) { - const bip32 = BIP32Factory(ecc) - const wallet = bip32.fromSeed(seed) - const priv = wallet.derivePath(getPathStr(path)).privateKey - return `0x${privateToAddress(priv).toString('hex')}` + const bip32 = BIP32Factory(ecc); + const wallet = bip32.fromSeed(seed); + const priv = wallet.derivePath(getPathStr(path)).privateKey; + return `0x${privateToAddress(priv).toString('hex')}`; } export function signPersonalJS(_msg: string, path: SigningPath) { - const bip32 = BIP32Factory(ecc) - const wallet = bip32.fromSeed(TEST_SEED) - const priv = wallet.derivePath(getPathStr(path)).privateKey - const msg = ethPersonalSignMsg(_msg) - const hash = Buffer.from(Hash.keccak256(Buffer.from(msg))) - const sig = ecsign(hash, priv) - const v = (sig.v - 27).toString(16).padStart(2, '0') - return `${sig.r.toString('hex')}${sig.s.toString('hex')}${v}` + const bip32 = BIP32Factory(ecc); + const wallet = bip32.fromSeed(TEST_SEED); + const priv = wallet.derivePath(getPathStr(path)).privateKey; + const msg = ethPersonalSignMsg(_msg); + const hash = Buffer.from(Hash.keccak256(Buffer.from(msg))); + const sig = ecsign(hash, priv); + const v = (sig.v - 27).toString(16).padStart(2, '0'); + return `${sig.r.toString('hex')}${sig.s.toString('hex')}${v}`; } export function signEip712JS(payload: any, path: SigningPath) { - const bip32 = BIP32Factory(ecc) - const wallet = bip32.fromSeed(TEST_SEED) - const priv = wallet.derivePath(getPathStr(path)).privateKey + const bip32 = BIP32Factory(ecc); + const wallet = bip32.fromSeed(TEST_SEED); + const priv = wallet.derivePath(getPathStr(path)).privateKey; // Calculate the EIP712 hash using the same method as the SDK validation - const hash = TypedDataUtils.eip712Hash(payload, SignTypedDataVersion.V4) - const sig = ecsign(Buffer.from(hash), priv) - const v = (sig.v - 27).toString(16).padStart(2, '0') - return `${sig.r.toString('hex')}${sig.s.toString('hex')}${v}` + const hash = TypedDataUtils.eip712Hash(payload, SignTypedDataVersion.V4); + const sig = ecsign(Buffer.from(hash), priv); + const v = (sig.v - 27).toString(16).padStart(2, '0'); + return `${sig.r.toString('hex')}${sig.s.toString('hex')}${v}`; } diff --git a/packages/sdk/src/__test__/utils/ethers.ts b/packages/sdk/src/__test__/utils/ethers.ts index c8a14bb9..a0dbf54f 100644 --- a/packages/sdk/src/__test__/utils/ethers.ts +++ b/packages/sdk/src/__test__/utils/ethers.ts @@ -7,17 +7,17 @@ const EVM_TYPES = [ 'bytes', 'string', 'tuple', -] +]; export function convertDecoderToEthers(def: unknown[]) { - const converted = getConvertedDef(def) - const types: any[] = [] - const data: any[] = [] + const converted = getConvertedDef(def); + const types: any[] = []; + const data: any[] = []; converted.forEach((i: any) => { - types.push(i.type) - data.push(i.data) - }) - return { types, data } + types.push(i.type); + data.push(i.data); + }); + return { types, data }; } // Convert an encoded def into a combination of ethers-compatable @@ -25,128 +25,129 @@ export function convertDecoderToEthers(def: unknown[]) { // doesn't matter much for these tests, which mainly just test // structure of the definitions function getConvertedDef(def: unknown[]) { - const converted: { type: string | null; data: unknown }[] = [] + const converted: { type: string | null; data: unknown }[] = []; def.forEach((param: unknown) => { - const p = param as { toString: (fmt: string) => string }[] - const arrSzs = p[3] as { toString: (fmt: string) => string }[] - const evmType = EVM_TYPES[Number.parseInt(p[1].toString('hex'), 16)] - let type = evmType - const numBytes = Number.parseInt(p[2].toString('hex'), 16) + const p = param as { toString: (fmt: string) => string }[]; + const arrSzs = p[3] as { toString: (fmt: string) => string }[]; + const evmType = EVM_TYPES[Number.parseInt(p[1].toString('hex'), 16)]; + let type = evmType; + const numBytes = Number.parseInt(p[2].toString('hex'), 16); if (numBytes > 0) { - type = `${type}${numBytes * 8}` + type = `${type}${numBytes * 8}`; } // Handle tuples by recursively generating data - let tupleData: unknown[] | undefined + let tupleData: unknown[] | undefined; if (evmType === 'tuple') { - tupleData = [] - type = `${type}(` - const tupleDef = getConvertedDef(p[4] as unknown[]) + tupleData = []; + type = `${type}(`; + const tupleDef = getConvertedDef(p[4] as unknown[]); tupleDef.forEach((tupleParam) => { - type = `${type}${tupleParam.type}, ` - tupleData?.push(tupleParam.data) - }) - type = type.slice(0, type.length - 2) - type = `${type})` + type = `${type}${tupleParam.type}, `; + tupleData?.push(tupleParam.data); + }); + type = type.slice(0, type.length - 2); + type = `${type})`; } // Get the data of a single function (i.e. excluding arrays) - const funcData = tupleData ? tupleData : genParamData(p) + const funcData = tupleData ? tupleData : genParamData(p); // Apply the data to arrays for (let i = 0; i < arrSzs.length; i++) { - const sz = Number.parseInt(arrSzs[i].toString('hex')) + const sz = Number.parseInt(arrSzs[i].toString('hex')); if (Number.isNaN(sz)) { // This is a 0 size, which means we need to // define a size to generate data - type = `${type}[]` + type = `${type}[]`; } else { - type = `${type}[${sz}]` + type = `${type}[${sz}]`; } } // If this param is a tuple we need to copy base data // across all dimensions. The individual params are already // arraified this way, but not the tuple type if (tupleData) { - converted.push({ type, data: getArrayData(p, funcData) }) + converted.push({ type, data: getArrayData(p, funcData) }); } else { - converted.push({ type, data: funcData }) + converted.push({ type, data: funcData }); } - }) - return converted + }); + return converted; } function genTupleData(tupleParam: unknown[]) { - const nestedData: unknown[] = [] + const nestedData: unknown[] = []; tupleParam.forEach((nestedParam: unknown) => { - const np = nestedParam as { toString: (fmt: string) => string }[] + const np = nestedParam as { toString: (fmt: string) => string }[]; nestedData.push( genData(EVM_TYPES[Number.parseInt(np[1].toString('hex'), 16)] ?? '', np), - ) - }) - return nestedData + ); + }); + return nestedData; } function genParamData(param: { toString: (fmt: string) => string }[]) { - const evmType = EVM_TYPES[Number.parseInt(param[1].toString('hex'), 16)] ?? '' - const baseData = genData(evmType, param) - return getArrayData(param, baseData) + const evmType = + EVM_TYPES[Number.parseInt(param[1].toString('hex'), 16)] ?? ''; + const baseData = genData(evmType, param); + return getArrayData(param, baseData); } function getArrayData( param: { toString: (fmt: string) => string }[], baseData: unknown, ) { - let arrayData: unknown[] | undefined - let data: unknown - const arrSzs = param[3] as unknown as { toString: (fmt: string) => string }[] + let arrayData: unknown[] | undefined; + let data: unknown; + const arrSzs = param[3] as unknown as { toString: (fmt: string) => string }[]; for (let i = 0; i < arrSzs.length; i++) { // let sz = parseInt(arrSzs[i].toString('hex')); TODO: fix this - const dimData: unknown[] = [] + const dimData: unknown[] = []; let sz = Number.parseInt( (param[3] as unknown as { toString: (fmt: string) => string }[])[ i ].toString('hex'), - ) + ); if (Number.isNaN(sz)) { - sz = 2 //1; + sz = 2; //1; } if (!arrayData) { - arrayData = [] + arrayData = []; } - const lastDimData = JSON.parse(JSON.stringify(arrayData)) + const lastDimData = JSON.parse(JSON.stringify(arrayData)); for (let j = 0; j < sz; j++) { if (i === 0) { - dimData.push(baseData) + dimData.push(baseData); } else { - dimData.push(lastDimData) + dimData.push(lastDimData); } } - arrayData = dimData + arrayData = dimData; } if (!data) { - data = arrayData ? arrayData : baseData + data = arrayData ? arrayData : baseData; } - return data + return data; } function genData(type: string, param: { toString: (fmt: string) => string }[]) { switch (type) { case 'address': - return '0xdead00000000000000000000000000000000beef' + return '0xdead00000000000000000000000000000000beef'; case 'bool': - return true + return true; case 'uint': - return 9 + return 9; case 'int': - return -9 + return -9; case 'bytes': - return '0xdeadbeef' + return '0xdeadbeef'; case 'string': - return 'string' + return 'string'; case 'tuple': if (!param || param.length < 4) { - throw new Error('Invalid tuple data') + throw new Error('Invalid tuple data'); } - return genTupleData(param[4] as unknown[]) + return genTupleData(param[4] as unknown[]); default: - throw new Error('Unrecognized type') + throw new Error('Unrecognized type'); } } diff --git a/packages/sdk/src/__test__/utils/getters.ts b/packages/sdk/src/__test__/utils/getters.ts index f8bdb15a..f9ccf730 100644 --- a/packages/sdk/src/__test__/utils/getters.ts +++ b/packages/sdk/src/__test__/utils/getters.ts @@ -1,16 +1,16 @@ -import seedrandom from 'seedrandom' +import seedrandom from 'seedrandom'; export const getEnv = () => { - if (!process.env) throw new Error('env cannot be found') - return process.env -} -export const getDeviceId = (): string => getEnv().DEVICE_ID ?? '' -export const getN = (): number => Number.parseInt(getEnv().N ?? '5') -export const getSeed = (): string => getEnv().SEED ?? 'myrandomseed' -export const getTestnet = (): string => getEnv().TESTNET ?? '' -export const getEtherscanKey = (): string => getEnv().ETHERSCAN_KEY ?? '' -export const getEncPw = (): string => getEnv().ENC_PW ?? null + if (!process.env) throw new Error('env cannot be found'); + return process.env; +}; +export const getDeviceId = (): string => getEnv().DEVICE_ID ?? ''; +export const getN = (): number => Number.parseInt(getEnv().N ?? '5'); +export const getSeed = (): string => getEnv().SEED ?? 'myrandomseed'; +export const getTestnet = (): string => getEnv().TESTNET ?? ''; +export const getEtherscanKey = (): string => getEnv().ETHERSCAN_KEY ?? ''; +export const getEncPw = (): string => getEnv().ENC_PW ?? null; export const getPrng = (seed?: string) => { - return seedrandom(seed ? seed : getSeed()) -} + return seedrandom(seed ? seed : getSeed()); +}; diff --git a/packages/sdk/src/__test__/utils/helpers.ts b/packages/sdk/src/__test__/utils/helpers.ts index af886bca..75189c15 100644 --- a/packages/sdk/src/__test__/utils/helpers.ts +++ b/packages/sdk/src/__test__/utils/helpers.ts @@ -1,72 +1,76 @@ -import { readFileSync } from 'node:fs' -import type { TypedTransaction } from '@ethereumjs/tx' -import BIP32Factory from 'bip32' -import { wordlists } from 'bip39' -import bitcoin, { type Payment } from 'bitcoinjs-lib' -import BN from 'bn.js' -import { ECPairFactory } from 'ecpair' +import { readFileSync } from 'node:fs'; +import type { TypedTransaction } from '@ethereumjs/tx'; +import BIP32Factory from 'bip32'; +import { wordlists } from 'bip39'; +import bitcoin, { type Payment } from 'bitcoinjs-lib'; +import BN from 'bn.js'; +import { ECPairFactory } from 'ecpair'; import { derivePath as deriveEDKey, getPublicKey as getEDPubkey, -} from 'ed25519-hd-key' -import { ec as EC } from 'elliptic' -import { privateToAddress } from 'ethereumjs-util' -import { jsonc } from 'jsonc' -import { Hash } from 'ox' -import { ecdsaRecover } from 'secp256k1' -import * as ecc from 'tiny-secp256k1' -import nacl from 'tweetnacl' -import { Constants } from '../..' -import { Client } from '../../client' -import { BIP_CONSTANTS, HARDENED_OFFSET, ethMsgProtocol } from '../../constants' -import { ProtocolConstants } from '../../protocol' -import { getPathStr } from '../../shared/utilities' +} from 'ed25519-hd-key'; +import { ec as EC } from 'elliptic'; +import { privateToAddress } from 'ethereumjs-util'; +import { jsonc } from 'jsonc'; +import { Hash } from 'ox'; +import { ecdsaRecover } from 'secp256k1'; +import * as ecc from 'tiny-secp256k1'; +import nacl from 'tweetnacl'; +import { Constants } from '../..'; +import { Client } from '../../client'; +import { + BIP_CONSTANTS, + HARDENED_OFFSET, + ethMsgProtocol, +} from '../../constants'; +import { ProtocolConstants } from '../../protocol'; +import { getPathStr } from '../../shared/utilities'; import { ensureHexBuffer, getV, getYParity, parseDER, randomBytes, -} from '../../util' -import { getEnv } from './getters' -import { setStoredClient } from './setup' +} from '../../util'; +import { getEnv } from './getters'; +import { setStoredClient } from './setup'; -const SIGHASH_ALL = 0x01 -const secp256k1 = new EC('secp256k1') -const bip32 = BIP32Factory(ecc) -const ECPair = ECPairFactory(ecc) +const SIGHASH_ALL = 0x01; +const secp256k1 = new EC('secp256k1'); +const bip32 = BIP32Factory(ecc); +const ECPair = ECPairFactory(ecc); const normalizeSigComponent = (component: any): Buffer => { if (component === null || component === undefined) { - return Buffer.alloc(0) + return Buffer.alloc(0); } if (Buffer.isBuffer(component)) { - return component + return component; } if (component instanceof Uint8Array) { - return Buffer.from(component) + return Buffer.from(component); } if (typeof component === 'bigint') { - const hex = component.toString(16) - return Buffer.from(hex.padStart(hex.length + (hex.length % 2), '0'), 'hex') + const hex = component.toString(16); + return Buffer.from(hex.padStart(hex.length + (hex.length % 2), '0'), 'hex'); } if (typeof component === 'number') { - return ensureHexBuffer(component) + return ensureHexBuffer(component); } if (typeof component === 'string') { - return ensureHexBuffer(component) + return ensureHexBuffer(component); } if (typeof component?.toArray === 'function') { - return Buffer.from(component.toArray('be')) + return Buffer.from(component.toArray('be')); } if (typeof component?.toBuffer === 'function') { - return Buffer.from(component.toBuffer()) + return Buffer.from(component.toBuffer()); } if (typeof component?.toString === 'function') { - return ensureHexBuffer(component.toString()) + return ensureHexBuffer(component.toString()); } - throw new Error('Unsupported signature component format') -} + throw new Error('Unsupported signature component format'); +}; /** * Get the appropriate V parameter for a transaction signature based on transaction type. @@ -76,13 +80,13 @@ const normalizeSigComponent = (component: any): Buffer => { export const getSignatureVParam = (tx: any, resp: any): string => { if (tx._type && tx._type > 0) { // For EIP-1559 and newer transaction types, use yParity (0 or 1) - return getYParity(tx, resp).toString(16).padStart(2, '0') + return getYParity(tx, resp).toString(16).padStart(2, '0'); } else { // For legacy transactions, return full V value - const vBn = getV(tx, resp) - return vBn.toString(16).padStart(2, '0') + const vBn = getV(tx, resp); + return vBn.toString(16).padStart(2, '0'); } -} +}; /** * Get the appropriate V parameter as BN for signature validation based on transaction type. @@ -92,91 +96,92 @@ export const getSignatureVParam = (tx: any, resp: any): string => { export const getSignatureVBN = (tx: any, resp: any): BN => { if (tx._type && tx._type > 0) { // For EIP-1559 and newer transaction types, get y-parity (0 or 1) - return new BN(getYParity(tx, resp)) + return new BN(getYParity(tx, resp)); } else { // For legacy transactions, use the v value from signature - return new BN(resp.sig.v) + return new BN(resp.sig.v); } -} +}; // NOTE: We use the HARDEN(49) purpose for p2sh(p2wpkh) address derivations. // For p2pkh-derived addresses, we use the legacy 44' purpose // For p2wpkh-derived addresse (not yet supported) we will use 84' -export const BTC_PURPOSE_P2WPKH = BIP_CONSTANTS.PURPOSES.BTC_SEGWIT -export const BTC_PURPOSE_P2SH_P2WPKH = BIP_CONSTANTS.PURPOSES.BTC_WRAPPED_SEGWIT -export const BTC_PURPOSE_P2PKH = BIP_CONSTANTS.PURPOSES.BTC_LEGACY -export const BTC_COIN = BIP_CONSTANTS.COINS.BTC -export const BTC_TESTNET_COIN = BIP_CONSTANTS.COINS.BTC_TESTNET -export const ETH_COIN = BIP_CONSTANTS.COINS.ETH +export const BTC_PURPOSE_P2WPKH = BIP_CONSTANTS.PURPOSES.BTC_SEGWIT; +export const BTC_PURPOSE_P2SH_P2WPKH = + BIP_CONSTANTS.PURPOSES.BTC_WRAPPED_SEGWIT; +export const BTC_PURPOSE_P2PKH = BIP_CONSTANTS.PURPOSES.BTC_LEGACY; +export const BTC_COIN = BIP_CONSTANTS.COINS.BTC; +export const BTC_TESTNET_COIN = BIP_CONSTANTS.COINS.BTC_TESTNET; +export const ETH_COIN = BIP_CONSTANTS.COINS.ETH; export const REUSABLE_KEY = - '3fb53b677f73e4d2b8c89c303f6f6b349f0075ad88ea126cb9f6632085815dca' + '3fb53b677f73e4d2b8c89c303f6f6b349f0075ad88ea126cb9f6632085815dca'; export function setupTestClient( env = getEnv() as any, stateData?: any, ): Client { if (stateData) { - return new Client({ stateData }) + return new Client({ stateData }); } const setup: any = { name: env.APP_NAME || 'SDK Test', baseUrl: env.baseUrl || 'https://signing.gridpl.us', timeout: 120000, setStoredClient, - } + }; // If the user passes a deviceID in the env, we assume they have previously // connected to the Lattice. if (env.DEVICE_ID) { - setup.privKey = Buffer.from(REUSABLE_KEY, 'hex') + setup.privKey = Buffer.from(REUSABLE_KEY, 'hex'); } // Separate check -- if we are connecting for the first time but want to be able // to reconnect quickly with the same device ID as an env var, we need to pair // with a reusable key if (Number.parseInt(env.REUSE_KEY) === 1) { - setup.privKey = Buffer.from(REUSABLE_KEY, 'hex') + setup.privKey = Buffer.from(REUSABLE_KEY, 'hex'); } // Initialize a global SDK client - const client = new Client(setup) - return client + const client = new Client(setup); + return client; } export const unharden = (x) => { - return x >= HARDENED_OFFSET ? x - HARDENED_OFFSET : x -} + return x >= HARDENED_OFFSET ? x - HARDENED_OFFSET : x; +}; export const buildPath = (indices) => { - let path = 'm' + let path = 'm'; indices.forEach((idx) => { - path += `/${unharden(idx)}${idx >= HARDENED_OFFSET ? "'" : ''}` - }) - return path -} + path += `/${unharden(idx)}${idx >= HARDENED_OFFSET ? "'" : ''}`; + }); + return path; +}; export function _getSumInputs(inputs) { - let sum = 0 + let sum = 0; inputs.forEach((input) => { - sum += input.value - }) - return sum + sum += input.value; + }); + return sum; } export function _get_btc_addr(pubkey, purpose, network) { - const pk = Buffer.isBuffer(pubkey) ? pubkey : Buffer.from(pubkey) - let obj: Payment + const pk = Buffer.isBuffer(pubkey) ? pubkey : Buffer.from(pubkey); + let obj: Payment; if (purpose === BTC_PURPOSE_P2SH_P2WPKH) { // Wrapped segwit requires p2sh wrapping obj = bitcoin.payments.p2sh({ redeem: bitcoin.payments.p2wpkh({ pubkey: pk, network }), network, - }) + }); } else if (purpose === BTC_PURPOSE_P2WPKH) { - obj = bitcoin.payments.p2wpkh({ pubkey: pk, network }) + obj = bitcoin.payments.p2wpkh({ pubkey: pk, network }); } else { // Native segwit and legacy addresses are treated teh same - obj = bitcoin.payments.p2pkh({ pubkey: pk, network }) + obj = bitcoin.payments.p2pkh({ pubkey: pk, network }); } - return obj.address + return obj.address; } export function _start_tx_builder( @@ -188,48 +193,48 @@ export function _start_tx_builder( network, purpose, ) { - const tx = new bitcoin.Transaction() + const tx = new bitcoin.Transaction(); // Match serialization logic (version 2) used by device and serializer - tx.version = 2 - const inputSum = _getSumInputs(inputs) - const recipientScript = bitcoin.address.toOutputScript(recipient, network) - tx.addOutput(recipientScript, value) - const changeValue = inputSum - value - fee + tx.version = 2; + const inputSum = _getSumInputs(inputs); + const recipientScript = bitcoin.address.toOutputScript(recipient, network); + tx.addOutput(recipientScript, value); + const changeValue = inputSum - value - fee; if (changeValue > 0) { - const networkIdx = network === bitcoin.networks.testnet ? 1 : 0 - const path = buildPath([purpose, harden(networkIdx), harden(0), 1, 0]) - const btc_0_change = wallet.derivePath(path) + const networkIdx = network === bitcoin.networks.testnet ? 1 : 0; + const path = buildPath([purpose, harden(networkIdx), harden(0), 1, 0]); + const btc_0_change = wallet.derivePath(path); const btc_0_change_pub = ECPair.fromPublicKey( btc_0_change.publicKey, - ).publicKey - const changeAddr = _get_btc_addr(btc_0_change_pub, purpose, network) - const changeScript = bitcoin.address.toOutputScript(changeAddr, network) - tx.addOutput(changeScript, changeValue) + ).publicKey; + const changeAddr = _get_btc_addr(btc_0_change_pub, purpose, network); + const changeScript = bitcoin.address.toOutputScript(changeAddr, network); + tx.addOutput(changeScript, changeValue); } else if (changeValue < 0) { - throw new Error('Value + fee > sumInputs!') + throw new Error('Value + fee > sumInputs!'); } - const inputsMeta: { scriptCode: Buffer; value: number }[] = [] + const inputsMeta: { scriptCode: Buffer; value: number }[] = []; inputs.forEach((input) => { - const hashLE = Buffer.from(input.hash, 'hex').reverse() - tx.addInput(hashLE, input.idx) + const hashLE = Buffer.from(input.hash, 'hex').reverse(); + tx.addInput(hashLE, input.idx); const coin = - network === bitcoin.networks.testnet ? BTC_TESTNET_COIN : BTC_COIN - const path = buildPath([purpose, coin, harden(0), 0, input.signerIdx]) - const keyPair = wallet.derivePath(path) - const pubkeyBuf = Buffer.from(keyPair.publicKey) - const p2pkh = bitcoin.payments.p2pkh({ pubkey: pubkeyBuf, network }) + network === bitcoin.networks.testnet ? BTC_TESTNET_COIN : BTC_COIN; + const path = buildPath([purpose, coin, harden(0), 0, input.signerIdx]); + const keyPair = wallet.derivePath(path); + const pubkeyBuf = Buffer.from(keyPair.publicKey); + const p2pkh = bitcoin.payments.p2pkh({ pubkey: pubkeyBuf, network }); // For P2WPKH and P2SH-P2WPKH the BIP143 scriptCode is the standard P2PKH script - if (!p2pkh.output) throw new Error('No P2PKH output') - const scriptCode = p2pkh.output - inputsMeta.push({ scriptCode, value: input.value }) - }) - return { tx, inputsMeta } + if (!p2pkh.output) throw new Error('No P2PKH output'); + const scriptCode = p2pkh.output; + inputsMeta.push({ scriptCode, value: input.value }); + }); + return { tx, inputsMeta }; } function _build_sighashes(txb_or_tx, purpose) { - const hashes: any = [] - const txb = txb_or_tx as any - const isLegacy = purpose === BTC_PURPOSE_P2PKH + const hashes: any = []; + const txb = txb_or_tx as any; + const isLegacy = purpose === BTC_PURPOSE_P2PKH; if (txb.inputsMeta) { txb.inputsMeta.forEach((meta, i) => { hashes.push( @@ -241,8 +246,8 @@ function _build_sighashes(txb_or_tx, purpose) { meta.value, SIGHASH_ALL, ), - ) - }) + ); + }); } else { // Fallback for prior structure (should not be used) txb.__inputs.forEach((input, i) => { @@ -255,10 +260,10 @@ function _build_sighashes(txb_or_tx, purpose) { input.value, SIGHASH_ALL, ), - ) - }) + ); + }); } - return hashes + return hashes; } function _get_reference_sighashes( @@ -272,7 +277,7 @@ function _get_reference_sighashes( ) { const network = isTestnet ? bitcoin.networks.testnet - : bitcoin.networks.bitcoin + : bitcoin.networks.bitcoin; const built = _start_tx_builder( wallet, recipient, @@ -281,9 +286,9 @@ function _get_reference_sighashes( inputs, network, purpose, - ) + ); // built has shape { tx, inputsMeta } - return _build_sighashes(built, purpose) + return _build_sighashes(built, purpose); } function _btc_tx_request_builder( @@ -294,42 +299,42 @@ function _btc_tx_request_builder( isTestnet, purpose, ) { - const currencyIdx = isTestnet ? BTC_TESTNET_COIN : BTC_COIN + const currencyIdx = isTestnet ? BTC_TESTNET_COIN : BTC_COIN; const txData = { prevOuts: [] as any[], recipient, value, fee, changePath: [purpose, currencyIdx, HARDENED_OFFSET, 1, 0], - } + }; inputs.forEach((input) => { txData.prevOuts.push({ txHash: input.hash, value: input.value, index: input.idx, signerPath: [purpose, currencyIdx, HARDENED_OFFSET, 0, input.signerIdx], - }) - }) + }); + }); return { currency: 'BTC', data: txData, - } + }; } // Convert DER signature to buffer of form `${r}${s}` export function stripDER(derSig) { - const parsed = parseDER(derSig) + const parsed = parseDER(derSig); // Left-pad r and s to 32 bytes and concatenate (no extra normalization) - const r = Buffer.from(parsed.r.slice(-32)) - const s = Buffer.from(parsed.s.slice(-32)) - const sig = Buffer.alloc(64) - r.copy(sig, 32 - r.length) - s.copy(sig, 64 - s.length) - return sig + const r = Buffer.from(parsed.r.slice(-32)); + const s = Buffer.from(parsed.s.slice(-32)); + const sig = Buffer.alloc(64); + r.copy(sig, 32 - r.length); + s.copy(sig, 64 - s.length); + return sig; } function _get_signing_keys(wallet, inputs, isTestnet, purpose) { - const currencyIdx = isTestnet ? 1 : 0 + const currencyIdx = isTestnet ? 1 : 0; return inputs.map((input) => { const path = buildPath([ purpose, @@ -337,42 +342,42 @@ function _get_signing_keys(wallet, inputs, isTestnet, purpose) { harden(0), 0, input.signerIdx, - ]) - const node = wallet.derivePath(path) - const priv = Buffer.from(node.privateKey) - const key = secp256k1.keyFromPrivate(priv) + ]); + const node = wallet.derivePath(path); + const priv = Buffer.from(node.privateKey); + const key = secp256k1.keyFromPrivate(priv); return { privateKey: priv, verify(hash: Buffer, sig: Buffer) { return key.verify(hash, { r: sig.slice(0, 32).toString('hex'), s: sig.slice(32).toString('hex'), - }) + }); }, - } - }) + }; + }); } function _generate_btc_address(isTestnet, purpose, rand) { - const priv = Buffer.alloc(32) + const priv = Buffer.alloc(32); for (let j = 0; j < 8; j++) { // 32 bits of randomness per call - priv.writeUInt32BE(Math.floor(rand.quick() * 2 ** 32), j * 4) + priv.writeUInt32BE(Math.floor(rand.quick() * 2 ** 32), j * 4); } - const keyPair = ECPair.fromPrivateKey(priv) + const keyPair = ECPair.fromPrivateKey(priv); const network = - isTestnet === true ? bitcoin.networks.testnet : bitcoin.networks.bitcoin - return _get_btc_addr(keyPair.publicKey, purpose, network) + isTestnet === true ? bitcoin.networks.testnet : bitcoin.networks.bitcoin; + return _get_btc_addr(keyPair.publicKey, purpose, network); } export function setup_btc_sig_test(opts, wallet, inputs, rand) { - const { isTestnet, useChange, spenderPurpose, recipientPurpose } = opts - const recipient = _generate_btc_address(isTestnet, recipientPurpose, rand) - const sumInputs = _getSumInputs(inputs) - const fee = Math.floor(rand.quick() * 50000) + const { isTestnet, useChange, spenderPurpose, recipientPurpose } = opts; + const recipient = _generate_btc_address(isTestnet, recipientPurpose, rand); + const sumInputs = _getSumInputs(inputs); + const fee = Math.floor(rand.quick() * 50000); const _value = - useChange === true ? Math.floor(rand.quick() * sumInputs) : sumInputs - const value = _value - fee + useChange === true ? Math.floor(rand.quick() * sumInputs) : sumInputs; + const value = _value - fee; const sigHashes = _get_reference_sighashes( wallet, recipient, @@ -381,13 +386,13 @@ export function setup_btc_sig_test(opts, wallet, inputs, rand) { inputs, isTestnet, spenderPurpose, - ) + ); const signingKeys = _get_signing_keys( wallet, inputs, isTestnet, spenderPurpose, - ) + ); const txReq = _btc_tx_request_builder( inputs, recipient, @@ -395,45 +400,45 @@ export function setup_btc_sig_test(opts, wallet, inputs, rand) { fee, isTestnet, spenderPurpose, - ) + ); return { sigHashes, signingKeys, txReq, - } + }; } export const harden = (x) => { - return x + HARDENED_OFFSET -} + return x + HARDENED_OFFSET; +}; export const prandomBuf = (prng, maxSz, forceSize = false) => { // Build a random payload that can fit in the base request - const sz = forceSize ? maxSz : Math.floor(maxSz * prng.quick()) - const buf = Buffer.alloc(sz) + const sz = forceSize ? maxSz : Math.floor(maxSz * prng.quick()); + const buf = Buffer.alloc(sz); for (let i = 0; i < sz; i++) { - buf[i] = Math.floor(0xff * prng.quick()) + buf[i] = Math.floor(0xff * prng.quick()); } - return buf -} + return buf; +}; export const deriveED25519Key = (path, seed) => { - const { key } = deriveEDKey(getPathStr(path), seed) - const pub = getEDPubkey(key, false) // `false` removes the leading zero byte + const { key } = deriveEDKey(getPathStr(path), seed); + const pub = getEDPubkey(key, false); // `false` removes the leading zero byte return { priv: key, pub, - } -} + }; +}; export const deriveSECP256K1Key = (path, seed) => { - const wallet = bip32.fromSeed(seed) - const key = wallet.derivePath(getPathStr(path)) + const wallet = bip32.fromSeed(seed); + const key = wallet.derivePath(getPathStr(path)); return { priv: key.privateKey, pub: key.publicKey, - } -} + }; +}; //============================================================ // Wallet Job integration test helpers @@ -449,7 +454,7 @@ export const jobTypes = { WALLET_JOB_LOAD_SEED: 3, WALLET_JOB_EXPORT_SEED: 4, WALLET_JOB_DELETE_SEED: 5, -} +}; export const gpErrors = { GP_SUCCESS: 0x00, GP_EINVAL: 0xffffffff + 1 - 22, // (4294967061) @@ -460,162 +465,162 @@ export const gpErrors = { GP_EAGAIN: 0xffffffff + 1 - 11, // (4294967050) GP_FAILURE: 0xffffffff + 1 - 128, // (4294967168) GP_EWALLET: 0xffffffff + 1 - 113, // (4294967183) -} +}; //--------------------------------------------------- // General helpers //--------------------------------------------------- export const getCodeMsg = (code, expected) => { if (code !== expected) { - let codeTxt = code - let expectedTxt = expected + let codeTxt = code; + let expectedTxt = expected; Object.keys(gpErrors).forEach((key) => { if (code === gpErrors[key]) { - codeTxt = key + codeTxt = key; } if (expected === gpErrors[key]) { - expectedTxt = key + expectedTxt = key; } - }) - return `Incorrect response code. Got ${codeTxt}. Expected ${expectedTxt}` + }); + return `Incorrect response code. Got ${codeTxt}. Expected ${expectedTxt}`; } - return '' -} + return ''; +}; export const parseWalletJobResp = (res, v) => { const jobRes = { resultStatus: null, result: null, - } - jobRes.resultStatus = res.readUInt32LE(0) - const dataLen = res.readUInt16LE(4) + }; + jobRes.resultStatus = res.readUInt32LE(0); + const dataLen = res.readUInt16LE(4); if (v.length === 0 || (v[1] < 10 && v[2] === 0)) { // Legacy fw versions ( res.result.readUInt32LE(0) +export const jobResErrCode = (res) => res.result.readUInt32LE(0); // Have to do this weird copy because `Buffer`s from the client are not real buffers // which is a vestige of requiring support on react native export const copyBuffer = (x) => { - return Buffer.from(x.toString('hex'), 'hex') -} + return Buffer.from(x.toString('hex'), 'hex'); +}; // Convert a set of indices to a human readable bip32 path export const stringifyPath = (parent) => { const convert = (parent) => { return parent >= HARDENED_OFFSET ? `${parent - HARDENED_OFFSET}'` - : `${parent}` - } + : `${parent}`; + }; if (parent.idx) { // BIP32 style encoding - let s = 'm' + let s = 'm'; for (let i = 0; i < parent.pathDepth; i++) { - s += `/${convert(parent.idx[i])}` + s += `/${convert(parent.idx[i])}`; } - return s + return s; } - let d = parent.pathDepth - let s = 'm' - if (d <= 0) return s + let d = parent.pathDepth; + let s = 'm'; + if (d <= 0) return s; if (parent.purpose !== undefined) { - s += `/${convert(parent.purpose)}` - d-- - if (d <= 0) return s + s += `/${convert(parent.purpose)}`; + d--; + if (d <= 0) return s; } if (parent.coin !== undefined) { - s += `/${convert(parent.coin)}` - d-- - if (d <= 0) return s + s += `/${convert(parent.coin)}`; + d--; + if (d <= 0) return s; } if (parent.account !== undefined) { - s += `/${convert(parent.account)}` - d-- - if (d <= 0) return s + s += `/${convert(parent.account)}`; + d--; + if (d <= 0) return s; } if (parent.change !== undefined) { - s += `/${convert(parent.change)}` - d-- - if (d <= 0) return s - } - if (parent.addr !== undefined) s += `/${convert(parent.addr)}` - d-- - return s -} + s += `/${convert(parent.change)}`; + d--; + if (d <= 0) return s; + } + if (parent.addr !== undefined) s += `/${convert(parent.addr)}`; + d--; + return s; +}; //--------------------------------------------------- // Get Addresses helpers //--------------------------------------------------- export const serializeGetAddressesJobData = (data) => { - const req = Buffer.alloc(33) - let off = 0 - req.writeUInt32LE(data.path.pathDepth, off) - off += 4 + const req = Buffer.alloc(33); + let off = 0; + req.writeUInt32LE(data.path.pathDepth, off); + off += 4; for (let i = 0; i < 5; i++) { - req.writeUInt32LE(i < data.path.pathDepth ? data.path.idx[i] : 0, off) - off += 4 + req.writeUInt32LE(i < data.path.pathDepth ? data.path.idx[i] : 0, off); + off += 4; } - req.writeUInt32LE(data.iterIdx, off) - off += 4 - req.writeUInt32LE(data.count, off) - off += 4 + req.writeUInt32LE(data.iterIdx, off); + off += 4; + req.writeUInt32LE(data.count, off); + off += 4; // Deprecated skipCache flag. It isn't used by firmware anymore. - req.writeUInt8(data.flag || 0, off) - return req -} + req.writeUInt8(data.flag || 0, off); + return req; +}; export const deserializeGetAddressesJobResult = (res) => { - let off = 0 + let off = 0; const getAddrResult = { count: 0, addresses: [] as any[], pubOnly: undefined, - } - getAddrResult.pubOnly = res.readUInt8(off) - off += 1 - getAddrResult.count = res.readUInt8(off) - off += 3 // Skip a 2-byte empty shim value (for backwards compatibility) + }; + getAddrResult.pubOnly = res.readUInt8(off); + off += 1; + getAddrResult.count = res.readUInt8(off); + off += 3; // Skip a 2-byte empty shim value (for backwards compatibility) for (let i = 0; i < getAddrResult.count; i++) { - const _addr = res.slice(off, off + ProtocolConstants.addrStrLen) - off += ProtocolConstants.addrStrLen + const _addr = res.slice(off, off + ProtocolConstants.addrStrLen); + off += ProtocolConstants.addrStrLen; for (let j = 0; j < _addr.length; j++) if (_addr[j] === 0x00) { - getAddrResult.addresses.push(_addr.slice(0, j).toString('utf8')) - break + getAddrResult.addresses.push(_addr.slice(0, j).toString('utf8')); + break; } } - return getAddrResult -} + return getAddrResult; +}; export const validateBTCAddresses = (resp, jobData, seed, useTestnet?) => { - expect(resp.count).toEqual(jobData.count) - const wallet = bip32.fromSeed(seed) - const path = JSON.parse(JSON.stringify(jobData.path)) + expect(resp.count).toEqual(jobData.count); + const wallet = bip32.fromSeed(seed); + const path = JSON.parse(JSON.stringify(jobData.path)); const network = - useTestnet === true ? bitcoin.networks.testnet : bitcoin.networks.bitcoin + useTestnet === true ? bitcoin.networks.testnet : bitcoin.networks.bitcoin; for (let i = 0; i < jobData.count; i++) { - path.idx[jobData.iterIdx] = jobData.path.idx[jobData.iterIdx] + i + path.idx[jobData.iterIdx] = jobData.path.idx[jobData.iterIdx] + i; // Validate the address - const purpose = jobData.path.idx[0] - const pubkey = wallet.derivePath(stringifyPath(path)).publicKey - let address: string + const purpose = jobData.path.idx[0]; + const pubkey = wallet.derivePath(stringifyPath(path)).publicKey; + let address: string; if (purpose === BTC_PURPOSE_P2WPKH) { // Bech32 address = bitcoin.payments.p2wpkh({ pubkey: Buffer.from(pubkey), network, - }).address + }).address; } else if (purpose === BTC_PURPOSE_P2SH_P2WPKH) { // Wrapped segwit address = bitcoin.payments.p2sh({ @@ -623,34 +628,34 @@ export const validateBTCAddresses = (resp, jobData, seed, useTestnet?) => { pubkey: Buffer.from(pubkey), network, }), - }).address + }).address; } else { // Legacy // This is the default and any unrecognized purpose will yield a legacy address. address = bitcoin.payments.p2pkh({ pubkey: Buffer.from(pubkey), network, - }).address + }).address; } - expect(address).toEqual(resp.addresses[i]) + expect(address).toEqual(resp.addresses[i]); } -} +}; export const validateETHAddresses = (resp, jobData, seed) => { - expect(resp.count).toEqual(jobData.count) + expect(resp.count).toEqual(jobData.count); // Confirm it is an Ethereum address - expect(resp.addresses[0].slice(0, 2)).toEqual('0x') - expect(resp.addresses[0].length).toEqual(42) + expect(resp.addresses[0].slice(0, 2)).toEqual('0x'); + expect(resp.addresses[0].length).toEqual(42); // Confirm we can derive the same address from the previously exported seed - const wallet = bip32.fromSeed(seed) - const path = JSON.parse(JSON.stringify(jobData.path)) + const wallet = bip32.fromSeed(seed); + const path = JSON.parse(JSON.stringify(jobData.path)); for (let i = 0; i < jobData.count; i++) { - path.idx[jobData.iterIdx] = jobData.path.idx[jobData.iterIdx] + i - const priv = wallet.derivePath(stringifyPath(path)).privateKey - const addr = `0x${privateToAddress(priv).toString('hex')}` - expect(addr).toEqual(resp.addresses[i]) + path.idx[jobData.iterIdx] = jobData.path.idx[jobData.iterIdx] + i; + const priv = wallet.derivePath(stringifyPath(path)).privateKey; + const addr = `0x${privateToAddress(priv).toString('hex')}`; + expect(addr).toEqual(resp.addresses[i]); } -} +}; export const validateDerivedPublicKeys = ( pubKeys, @@ -658,31 +663,31 @@ export const validateDerivedPublicKeys = ( seed, flag?: number, ) => { - const wallet = bip32.fromSeed(seed) + const wallet = bip32.fromSeed(seed); // We assume the keys were derived in sequential order pubKeys.forEach((pub, i) => { - const path = JSON.parse(JSON.stringify(firstPath)) - path[path.length - 1] += i + const path = JSON.parse(JSON.stringify(firstPath)); + path[path.length - 1] += i; if (flag === Constants.GET_ADDR_FLAGS.ED25519_PUB) { // ED25519 requires its own derivation - const key = deriveED25519Key(path, seed) + const key = deriveED25519Key(path, seed); expect(pub.toString('hex')).toEqualElseLog( key.pub.toString('hex'), 'Exported ED25519 pubkey incorrect', - ) + ); } else { // Otherwise this is a SECP256K1 pubkey - const priv = wallet.derivePath(getPathStr(path)).privateKey + const priv = wallet.derivePath(getPathStr(path)).privateKey; expect(pub.toString('hex')).toEqualElseLog( secp256k1.keyFromPrivate(priv).getPublic().encode('hex', false), 'Exported SECP256K1 pubkey incorrect', - ) + ); } - }) -} + }); +}; export const ethPersonalSignMsg = (msg) => - `\u0019Ethereum Signed Message:\n${String(msg.length)}${msg}` + `\u0019Ethereum Signed Message:\n${String(msg.length)}${msg}`; //--------------------------------------------------- // Sign Transaction helpers @@ -692,43 +697,43 @@ export const serializeSignTxJobDataLegacy = (data) => { // (see `WalletJobData_SignTx_t` and `SignatureRequest_t`) // in firmware for more info on legacy vs generic (new) // wallet job requests - const n = data.sigReq.length - const req = Buffer.alloc(4 + 56 * n) - let off = 0 - req.writeUInt32LE(data.numRequests, 0) - off += 4 + const n = data.sigReq.length; + const req = Buffer.alloc(4 + 56 * n); + let off = 0; + req.writeUInt32LE(data.numRequests, 0); + off += 4; for (let i = 0; i < n; i++) { - const r = data.sigReq[i] - r.data.copy(req, off) - off += r.data.length - req.writeUInt32LE(r.signerPath.pathDepth, off) - off += 4 - req.writeUInt32LE(r.signerPath.purpose, off) - off += 4 - req.writeUInt32LE(r.signerPath.coin, off) - off += 4 - req.writeUInt32LE(r.signerPath.account, off) - off += 4 - req.writeUInt32LE(r.signerPath.change, off) - off += 4 - req.writeUInt32LE(r.signerPath.addr, off) - off += 4 - } - return req -} + const r = data.sigReq[i]; + r.data.copy(req, off); + off += r.data.length; + req.writeUInt32LE(r.signerPath.pathDepth, off); + off += 4; + req.writeUInt32LE(r.signerPath.purpose, off); + off += 4; + req.writeUInt32LE(r.signerPath.coin, off); + off += 4; + req.writeUInt32LE(r.signerPath.account, off); + off += 4; + req.writeUInt32LE(r.signerPath.change, off); + off += 4; + req.writeUInt32LE(r.signerPath.addr, off); + off += 4; + } + return req; +}; export const deserializeSignTxJobResult = (res: any) => { - let off = 0 + let off = 0; const getTxResult: any = { numOutputs: null, outputs: [], - } - getTxResult.numOutputs = res.readUInt32LE(off) - off += 4 - const PK_LEN = 65 // uncompressed pubkey - const SIG_LEN = 74 // DER sig - const outputSz = 6 * 4 + PK_LEN + SIG_LEN - let _off = 0 + }; + getTxResult.numOutputs = res.readUInt32LE(off); + off += 4; + const PK_LEN = 65; // uncompressed pubkey + const SIG_LEN = 74; // DER sig + const outputSz = 6 * 4 + PK_LEN + SIG_LEN; + let _off = 0; for (let i = 0; i < getTxResult.numOutputs; i++) { const o = { signerPath: { @@ -741,177 +746,177 @@ export const deserializeSignTxJobResult = (res: any) => { }, pubkey: null as any, sig: null as any, - } - const _o = res.slice(off, off + outputSz) - off += outputSz - _off = 0 - o.signerPath.pathDepth = _o.readUInt32LE(_off) - _off += 4 - o.signerPath.purpose = _o.readUInt32LE(_off) - _off += 4 - o.signerPath.coin = _o.readUInt32LE(_off) - _off += 4 - o.signerPath.account = _o.readUInt32LE(_off) - _off += 4 - o.signerPath.change = _o.readUInt32LE(_off) - _off += 4 - o.signerPath.addr = _o.readUInt32LE(_off) - _off += 4 + }; + const _o = res.slice(off, off + outputSz); + off += outputSz; + _off = 0; + o.signerPath.pathDepth = _o.readUInt32LE(_off); + _off += 4; + o.signerPath.purpose = _o.readUInt32LE(_off); + _off += 4; + o.signerPath.coin = _o.readUInt32LE(_off); + _off += 4; + o.signerPath.account = _o.readUInt32LE(_off); + _off += 4; + o.signerPath.change = _o.readUInt32LE(_off); + _off += 4; + o.signerPath.addr = _o.readUInt32LE(_off); + _off += 4; o.pubkey = secp256k1.keyFromPublic( _o.slice(_off, _off + 65).toString('hex'), 'hex', - ) - _off += PK_LEN + ); + _off += PK_LEN; // We get back a DER signature in 74 bytes, but not all the bytes are necessarily // used. The second byte contains the DER sig length, so we need to use that. - const derLen = _o[_off + 1] + const derLen = _o[_off + 1]; o.sig = Buffer.from( _o.slice(_off, _off + 2 + derLen).toString('hex'), 'hex', - ) - getTxResult.outputs.push(o) + ); + getTxResult.outputs.push(o); } - return getTxResult -} + return getTxResult; +}; //--------------------------------------------------- // Export Seed helpers //--------------------------------------------------- -export const serializeExportSeedJobData = () => Buffer.alloc(0) +export const serializeExportSeedJobData = () => Buffer.alloc(0); export const deserializeExportSeedJobResult = (res) => { - let off = 0 - const seed = res.slice(off, 64) - off += 64 - const words = [] + let off = 0; + const seed = res.slice(off, 64); + off += 64; + const words = []; for (let i = 0; i < 24; i++) { - const wordIdx = res.slice(off, off + 4).readUInt32LE(0) - words.push(wordlists.english[wordIdx]) - off += 4 + const wordIdx = res.slice(off, off + 4).readUInt32LE(0); + words.push(wordlists.english[wordIdx]); + off += 4; } - const numWords = res.slice(off, off + 4).readUInt32LE(0) - off += 4 + const numWords = res.slice(off, off + 4).readUInt32LE(0); + off += 4; return { seed, mnemonic: words.slice(0, numWords).join(' '), - } -} + }; +}; //--------------------------------------------------- // Delete Seed helpers //--------------------------------------------------- export const serializeDeleteSeedJobData = (data) => { - const req = Buffer.alloc(1) - req.writeUInt8(data.iface, 0) - return req -} + const req = Buffer.alloc(1); + req.writeUInt8(data.iface, 0); + return req; +}; //--------------------------------------------------- // Load Seed helpers //--------------------------------------------------- export const serializeLoadSeedJobData = (data) => { - const req = Buffer.alloc(217) - let off = 0 - req.writeUInt8(data.iface, off) - off += 1 - data.seed.copy(req, off) - off += data.seed.length - req.writeUInt8(data.exportability, off) - off += 1 + const req = Buffer.alloc(217); + let off = 0; + req.writeUInt8(data.iface, off); + off += 1; + data.seed.copy(req, off); + off += data.seed.length; + req.writeUInt8(data.exportability, off); + off += 1; if (data.mnemonic) { // Serialize the mnemonic - const mWords = data.mnemonic.split(' ') + const mWords = data.mnemonic.split(' '); for (let i = 0; i < mWords.length; i++) { - req.writeUint32LE(wordlists.english.indexOf(mWords[i]), off + i * 4) + req.writeUint32LE(wordlists.english.indexOf(mWords[i]), off + i * 4); } // Strangely the struct is written with the length of // words after the words themselves lol (24 words * 4 bytes per word = 96) // (Preserved for fear of any unintended consequences to chaning `BIP39Phrase_t` in fw) - req.writeUInt32LE(mWords.length, off + 96) + req.writeUInt32LE(mWords.length, off + 96); // Ignore the passphrase since we only use this wallet job // helper to test loading a mnemonic onto the card's extraData // buffer, which does not include the passphrase. } - return req -} + return req; +}; //--------------------------------------------------- // Struct builders //--------------------------------------------------- export const buildRandomEip712Object = (randInt) => { function randStr(n) { - const words = wordlists.english - let s = '' + const words = wordlists.english; + let s = ''; while (s.length < n) { - s += `${words?.[randInt(words?.length)]}_` + s += `${words?.[randInt(words?.length)]}_`; } - return s.slice(0, n) + return s.slice(0, n); } function getRandomName(upperCase = false, sz = 20) { - const name = randStr(sz) + const name = randStr(sz); if (upperCase === true) - return `${name.slice(0, 1).toUpperCase()}${name.slice(1)}` - return name + return `${name.slice(0, 1).toUpperCase()}${name.slice(1)}`; + return name; } function getRandomEIP712Type(customTypes: any[] = []) { const types = Object.keys(customTypes).concat( Object.keys(ethMsgProtocol.TYPED_DATA.typeCodes), - ) + ); return { name: getRandomName(), type: types[randInt(types.length)], - } + }; } function getRandomEIP712Val(type) { if (type !== 'bytes' && type.slice(0, 5) === 'bytes') { - return `0x${randomBytes(Number.parseInt(type.slice(5))).toString('hex')}` + return `0x${randomBytes(Number.parseInt(type.slice(5))).toString('hex')}`; } if (type === 'uint' || type.indexOf('uint') === 0) { - const bits = Number.parseInt(type.slice(4) || '256', 10) - const byteLength = Math.max(1, Math.ceil(bits / 8)) - return `0x${randomBytes(byteLength).toString('hex')}` + const bits = Number.parseInt(type.slice(4) || '256', 10); + const byteLength = Math.max(1, Math.ceil(bits / 8)); + return `0x${randomBytes(byteLength).toString('hex')}`; } if (type === 'int' || type.indexOf('int') === 0) { - const bits = Number.parseInt(type.slice(3) || '256', 10) - const byteLength = Math.max(1, Math.ceil(bits / 8)) - const raw = randomBytes(byteLength).toString('hex') - const modulus = 1n << BigInt(bits) - const halfModulus = modulus >> 1n - let value = BigInt(`0x${raw}`) - value = ((value % modulus) + modulus) % modulus + const bits = Number.parseInt(type.slice(3) || '256', 10); + const byteLength = Math.max(1, Math.ceil(bits / 8)); + const raw = randomBytes(byteLength).toString('hex'); + const modulus = 1n << BigInt(bits); + const halfModulus = modulus >> 1n; + let value = BigInt(`0x${raw}`); + value = ((value % modulus) + modulus) % modulus; if (value >= halfModulus) { - value -= modulus + value -= modulus; } - return value.toString() + return value.toString(); } switch (type) { case 'bytes': - return `0x${randomBytes(1 + randInt(50)).toString('hex')}` + return `0x${randomBytes(1 + randInt(50)).toString('hex')}`; case 'string': - return randStr(100) + return randStr(100); case 'bool': - return randInt(1) > 0 + return randInt(1) > 0; case 'address': - return `0x${randomBytes(20).toString('hex')}` + return `0x${randomBytes(20).toString('hex')}`; default: - throw new Error('unsupported eip712 type') + throw new Error('unsupported eip712 type'); } } function buildCustomTypeVal(typeName, msg) { - const val = {} - const subTypes = msg.types[typeName] + const val = {}; + const subTypes = msg.types[typeName]; subTypes.forEach((subType) => { if (Object.keys(msg.types).indexOf(subType.type) > -1) { // If this is a custom type we need to recurse - val[subType.name] = buildCustomTypeVal(subType.type, msg) + val[subType.name] = buildCustomTypeVal(subType.type, msg); } else { - val[subType.name] = getRandomEIP712Val(subType.type) + val[subType.name] = getRandomEIP712Val(subType.type); } - }) - return val + }); + return val; } const msg = { @@ -931,95 +936,95 @@ export const buildRandomEip712Object = (randInt) => { verifyingContract: `0x${randomBytes(20).toString('hex')}`, }, message: {}, - } - msg.types[msg.primaryType] = [] + }; + msg.types[msg.primaryType] = []; // Create custom types and add them to the types definitions - const numCustomTypes = 1 + randInt(3) - const numDefaulTypes = 1 + randInt(3) - const customTypesMap: any = {} + const numCustomTypes = 1 + randInt(3); + const numDefaulTypes = 1 + randInt(3); + const customTypesMap: any = {}; for (let i = 0; i < numCustomTypes; i++) { - const subTypes: any[] = [] + const subTypes: any[] = []; for (let j = 0; j < 1 + randInt(3); j++) { - subTypes.push(getRandomEIP712Type(customTypesMap)) + subTypes.push(getRandomEIP712Type(customTypesMap)); } // Capitalize custom type names to distinguish them - let typeName = getRandomName(true) - typeName = `${typeName.slice(0, 1).toUpperCase()}${typeName.slice(1)}` - customTypesMap[typeName] = subTypes + let typeName = getRandomName(true); + typeName = `${typeName.slice(0, 1).toUpperCase()}${typeName.slice(1)}`; + customTypesMap[typeName] = subTypes; // Record the type - msg.types[typeName] = subTypes + msg.types[typeName] = subTypes; // Add a record in the primary type. We will need to create a value later. msg.types[msg.primaryType].push({ name: getRandomName(), type: typeName, - }) + }); } // Generate default (i.e. "atomic") types to mix into the message for (let i = 0; i < numDefaulTypes; i++) { - const t = getRandomEIP712Type() + const t = getRandomEIP712Type(); // Add to the primary type definition - msg.types[msg.primaryType].push(t) + msg.types[msg.primaryType].push(t); } // Generate random values msg.types[msg.primaryType].forEach((typeDef) => { if (Object.keys(msg.types).indexOf(typeDef.type) === -1) { // Normal EIP712 atomic type - msg.message[typeDef.name] = getRandomEIP712Val(typeDef.type) + msg.message[typeDef.name] = getRandomEIP712Val(typeDef.type); } else { // Custom type - msg.message[typeDef.name] = buildCustomTypeVal(typeDef.type, msg) + msg.message[typeDef.name] = buildCustomTypeVal(typeDef.type, msg); } - }) - return msg -} + }); + return msg; +}; //--------------------------------------------------- // Generic signing //--------------------------------------------------- export const validateGenericSig = (seed, sig, payloadBuf, req, pubkey?) => { - const { signerPath, hashType, curveType } = req - const HASHES = Constants.SIGNING.HASHES - const CURVES = Constants.SIGNING.CURVES - let hash: Buffer + const { signerPath, hashType, curveType } = req; + const HASHES = Constants.SIGNING.HASHES; + const CURVES = Constants.SIGNING.CURVES; + let hash: Buffer; if (curveType === CURVES.SECP256K1) { if (hashType === HASHES.SHA256) { - hash = Buffer.from(Hash.sha256(payloadBuf)) + hash = Buffer.from(Hash.sha256(payloadBuf)); } else if (hashType === HASHES.KECCAK256) { - hash = Buffer.from(Hash.keccak256(payloadBuf)) + hash = Buffer.from(Hash.keccak256(payloadBuf)); } else { - throw new Error('Bad params') + throw new Error('Bad params'); } - const { priv } = deriveSECP256K1Key(signerPath, seed) - const key = secp256k1.keyFromPrivate(priv) + const { priv } = deriveSECP256K1Key(signerPath, seed); + const key = secp256k1.keyFromPrivate(priv); const normalizedSig = { r: normalizeSigComponent(sig.r).toString('hex'), s: normalizeSigComponent(sig.s).toString('hex'), - } + }; expect(key.verify(hash, normalizedSig)).toEqualElseLog( true, 'Signature failed verification.', - ) + ); } else if (curveType === CURVES.ED25519) { if (hashType !== HASHES.NONE) { - throw new Error('Bad params') + throw new Error('Bad params'); } - const { pub } = deriveED25519Key(signerPath, seed) + const { pub } = deriveED25519Key(signerPath, seed); const signature = Buffer.concat([ normalizeSigComponent(sig.r), normalizeSigComponent(sig.s), - ]) - const edPublicKey = pubkey ? normalizeSigComponent(pubkey) : pub + ]); + const edPublicKey = pubkey ? normalizeSigComponent(pubkey) : pub; const isValid = nacl.sign.detached.verify( new Uint8Array(payloadBuf), new Uint8Array(signature), new Uint8Array(edPublicKey), - ) - expect(isValid).toEqualElseLog(true, 'Signature failed verification.') + ); + expect(isValid).toEqualElseLog(true, 'Signature failed verification.'); } else { - throw new Error('Bad params') + throw new Error('Bad params'); } -} +}; /** * Get a RSV formatted signature string @@ -1027,65 +1032,67 @@ export const validateGenericSig = (seed, sig, payloadBuf, req, pubkey?) => { * @param tx - optional, an @ethereumjs/tx Transaction object */ export const getSigStr = (resp: any, tx?: TypedTransaction) => { - let v: string + let v: string; if (resp.sig.v !== undefined) { - const vBuf = normalizeSigComponent(resp.sig.v) - const vHex = vBuf.toString('hex') - let vInt = vHex ? Number.parseInt(vHex, 16) : 0 + const vBuf = normalizeSigComponent(resp.sig.v); + const vHex = vBuf.toString('hex'); + let vInt = vHex ? Number.parseInt(vHex, 16) : 0; if (!Number.isFinite(vInt)) { - vInt = 0 + vInt = 0; } if (vInt >= 27) { - vInt -= 27 + vInt -= 27; } - v = vInt.toString(16).padStart(2, '0') + v = vInt.toString(16).padStart(2, '0'); } else if (tx) { - v = getSignatureVParam(tx, resp) + v = getSignatureVParam(tx, resp); } else { - throw new Error('Could not build sig string') + throw new Error('Could not build sig string'); } - const rHex = normalizeSigComponent(resp.sig.r).toString('hex') - const sHex = normalizeSigComponent(resp.sig.s).toString('hex') - return `${rHex}${sHex}${v}` -} + const rHex = normalizeSigComponent(resp.sig.r).toString('hex'); + const sHex = normalizeSigComponent(resp.sig.s).toString('hex'); + return `${rHex}${sHex}${v}`; +}; export function toBuffer(data: string | number | Buffer | Uint8Array): Buffer { if (data === null || data === undefined) { - throw new Error('Invalid data') + throw new Error('Invalid data'); } if (Buffer.isBuffer(data)) { - return data + return data; } if (data instanceof Uint8Array) { - return Buffer.from(data.buffer, data.byteOffset, data.length) + return Buffer.from(data.buffer, data.byteOffset, data.length); } if (typeof data === 'number') { - return ensureHexBuffer(data) + return ensureHexBuffer(data); } if (typeof data === 'string') { - const trimmed = data.trim() + const trimmed = data.trim(); const isHex = trimmed.startsWith('0x') || - (/^[0-9a-fA-F]+$/.test(trimmed) && trimmed.length % 2 === 0) - return isHex ? ensureHexBuffer(trimmed) : Buffer.from(trimmed, 'utf8') + (/^[0-9a-fA-F]+$/.test(trimmed) && trimmed.length % 2 === 0); + return isHex ? ensureHexBuffer(trimmed) : Buffer.from(trimmed, 'utf8'); } - throw new Error('Unsupported data type') + throw new Error('Unsupported data type'); } export function toUint8Array(data: Buffer): Uint8Array { - return new Uint8Array(data.buffer, data.byteOffset, data.length) + return new Uint8Array(data.buffer, data.byteOffset, data.length); } export function ensureHash32( message: string | number | Buffer | Uint8Array, ): Uint8Array { - const msgBuffer = toBuffer(message) + const msgBuffer = toBuffer(message); const digest = - msgBuffer.length === 32 ? msgBuffer : Buffer.from(Hash.keccak256(msgBuffer)) + msgBuffer.length === 32 + ? msgBuffer + : Buffer.from(Hash.keccak256(msgBuffer)); if (digest.length !== 32) { - throw new Error('Failed to derive 32-byte hash for signature validation.') + throw new Error('Failed to derive 32-byte hash for signature validation.'); } - return toUint8Array(digest) + return toUint8Array(digest); } export function validateSig( @@ -1093,47 +1100,47 @@ export function validateSig( message: string | number | Buffer | Uint8Array, ) { if (!resp.sig?.r || !resp.sig?.s || !resp.pubkey) { - throw new Error('Missing signature components') + throw new Error('Missing signature components'); } - const rBuf = normalizeSigComponent(resp.sig.r) - const sBuf = normalizeSigComponent(resp.sig.s) - const rs = new Uint8Array(Buffer.concat([rBuf, sBuf])) - const hash = ensureHash32(message) + const rBuf = normalizeSigComponent(resp.sig.r); + const sBuf = normalizeSigComponent(resp.sig.s); + const rs = new Uint8Array(Buffer.concat([rBuf, sBuf])); + const hash = ensureHash32(message); - const pubkeyInput = toBuffer(resp.pubkey) + const pubkeyInput = toBuffer(resp.pubkey); const normalizedPubkey = pubkeyInput.length === 64 ? Buffer.concat([Buffer.from([0x04]), pubkeyInput]) - : pubkeyInput + : pubkeyInput; const isCompressed = normalizedPubkey.length === 33 && - (normalizedPubkey[0] === 0x02 || normalizedPubkey[0] === 0x03) + (normalizedPubkey[0] === 0x02 || normalizedPubkey[0] === 0x03); const recoveredA = Buffer.from( ecdsaRecover(rs, 0, hash, isCompressed), - ).toString('hex') + ).toString('hex'); const recoveredB = Buffer.from( ecdsaRecover(rs, 1, hash, isCompressed), - ).toString('hex') - const expected = normalizedPubkey.toString('hex') + ).toString('hex'); + const expected = normalizedPubkey.toString('hex'); if (expected !== recoveredA && expected !== recoveredB) { - throw new Error('Signature did not validate.') + throw new Error('Signature did not validate.'); } } export const compressPubKey = (pub) => { if (pub.length !== 65) { - return pub + return pub; } - const compressed = Buffer.alloc(33) - pub.slice(1, 33).copy(compressed, 1) + const compressed = Buffer.alloc(33); + pub.slice(1, 33).copy(compressed, 1); if (pub[64] % 2) { - compressed[0] = 0x03 + compressed[0] = 0x03; } else { - compressed[0] = 0x02 + compressed[0] = 0x02; } - return compressed -} + return compressed; +}; function _stripTrailingCommas(input: string): string { // Use non-backtracking pattern to avoid ReDoS vulnerability @@ -1141,12 +1148,12 @@ function _stripTrailingCommas(input: string): string { return input.replace( /,([\s]*(?:\/\/[^\n]*\n[\s]*|\/\*[^*]*\*+(?:[^/*][^*]*\*+)*\/[\s]*)*)([}\]])/g, '$1$2', - ) + ); } export const getTestVectors = () => { const raw = readFileSync( `${process.cwd()}/src/__test__/vectors.jsonc`, - ).toString() - return jsonc.parse(_stripTrailingCommas(raw)) -} + ).toString(); + return jsonc.parse(_stripTrailingCommas(raw)); +}; diff --git a/packages/sdk/src/__test__/utils/runners.ts b/packages/sdk/src/__test__/utils/runners.ts index b408cd9e..1302e5a7 100644 --- a/packages/sdk/src/__test__/utils/runners.ts +++ b/packages/sdk/src/__test__/utils/runners.ts @@ -1,44 +1,44 @@ -import type { Client } from '../../client' -import { getEncodedPayload } from '../../genericSigning' +import type { Client } from '../../client'; +import { getEncodedPayload } from '../../genericSigning'; import type { SigningPayload, SignRequestParams, TestRequestPayload, -} from '../../types' -import { parseWalletJobResp, validateGenericSig } from './helpers' -import { TEST_SEED } from './testConstants' -import { testRequest } from './testRequest' +} from '../../types'; +import { parseWalletJobResp, validateGenericSig } from './helpers'; +import { TEST_SEED } from './testConstants'; +import { testRequest } from './testRequest'; export async function runTestCase( payload: TestRequestPayload, expectedCode: number, ) { - const res = await testRequest(payload) + const res = await testRequest(payload); //@ts-expect-error - Accessing private property - const fwVersion = payload.client.fwVersion - const parsedRes = parseWalletJobResp(res, fwVersion) - expect(parsedRes.resultStatus).toEqual(expectedCode) - return parsedRes + const fwVersion = payload.client.fwVersion; + const parsedRes = parseWalletJobResp(res, fwVersion); + expect(parsedRes.resultStatus).toEqual(expectedCode); + return parsedRes; } export async function runGeneric(request: SignRequestParams, client: Client) { - const response = await client.sign(request) + const response = await client.sign(request); // runGeneric is only used for generic signing, not Bitcoin - const data = request.data as SigningPayload + const data = request.data as SigningPayload; // If no encoding type is specified we encode in hex or ascii - const encodingType = data.encodingType || null - const allowedEncodings = client.getFwConstants().genericSigning.encodingTypes + const encodingType = data.encodingType || null; + const allowedEncodings = client.getFwConstants().genericSigning.encodingTypes; const { payloadBuf } = getEncodedPayload( data.payload, encodingType, allowedEncodings, - ) - const seed = TEST_SEED - validateGenericSig(seed, response.sig, payloadBuf, data, response.pubkey) - return response + ); + const seed = TEST_SEED; + validateGenericSig(seed, response.sig, payloadBuf, data, response.pubkey); + return response; } export const runEthMsg = async (req: SignRequestParams, client: Client) => { - const sig = await client.sign(req) - expect(sig.sig).not.toEqual(null) -} + const sig = await client.sign(req); + expect(sig.sig).not.toEqual(null); +}; diff --git a/packages/sdk/src/__test__/utils/serializers.ts b/packages/sdk/src/__test__/utils/serializers.ts index fd85c5e7..b081f0b4 100644 --- a/packages/sdk/src/__test__/utils/serializers.ts +++ b/packages/sdk/src/__test__/utils/serializers.ts @@ -1,25 +1,25 @@ export const serializeObjectWithBuffers = (obj: any) => { return Object.entries(obj).reduce((acc: any, [key, value]) => { if (value instanceof Buffer) { - acc[key] = { isBuffer: true, value: value.toString('hex') } + acc[key] = { isBuffer: true, value: value.toString('hex') }; } else if (typeof value === 'object') { - acc[key] = serializeObjectWithBuffers(value) + acc[key] = serializeObjectWithBuffers(value); } else { - acc[key] = value + acc[key] = value; } - return acc - }, {}) -} + return acc; + }, {}); +}; export const deserializeObjectWithBuffers = (obj: any) => { return Object.entries(obj).reduce((acc: any, [key, value]: any) => { if (value?.isBuffer) { - acc[key] = Buffer.from(value.value, 'hex') + acc[key] = Buffer.from(value.value, 'hex'); } else if (typeof value === 'object') { - acc[key] = deserializeObjectWithBuffers(value) + acc[key] = deserializeObjectWithBuffers(value); } else { - acc[key] = value + acc[key] = value; } - return acc - }, {}) -} + return acc; + }, {}); +}; diff --git a/packages/sdk/src/__test__/utils/setup.ts b/packages/sdk/src/__test__/utils/setup.ts index 75d3bdf2..4cadb90f 100644 --- a/packages/sdk/src/__test__/utils/setup.ts +++ b/packages/sdk/src/__test__/utils/setup.ts @@ -1,35 +1,35 @@ -import * as fs from 'node:fs' -import readlineSync from 'readline-sync' -import { getClient, pair, setup } from '../../api' +import * as fs from 'node:fs'; +import readlineSync from 'readline-sync'; +import { getClient, pair, setup } from '../../api'; -const question = readlineSync.question +const question = readlineSync.question; -const TEMP_CLIENT_FILE = './client.temp' +const TEMP_CLIENT_FILE = './client.temp'; export async function setStoredClient(data: string) { try { - fs.writeFileSync(TEMP_CLIENT_FILE, data) + fs.writeFileSync(TEMP_CLIENT_FILE, data); } catch (err) { - console.error('Failed to store client data:', err) - return + console.error('Failed to store client data:', err); + return; } } export async function getStoredClient() { try { - return fs.readFileSync(TEMP_CLIENT_FILE, 'utf8') + return fs.readFileSync(TEMP_CLIENT_FILE, 'utf8'); } catch (err) { - console.error('Failed to read stored client data:', err) - return '' + console.error('Failed to read stored client data:', err); + return ''; } } export async function setupClient() { - const deviceId = process.env.DEVICE_ID - const baseUrl = process.env.baseUrl || 'https://signing.gridpl.us' - const password = process.env.PASSWORD || 'password' - const name = process.env.APP_NAME || 'SDK Test' - let pairingSecret = process.env.PAIRING_SECRET + const deviceId = process.env.DEVICE_ID; + const baseUrl = process.env.baseUrl || 'https://signing.gridpl.us'; + const password = process.env.PASSWORD || 'password'; + const name = process.env.APP_NAME || 'SDK Test'; + let pairingSecret = process.env.PAIRING_SECRET; const isPaired = await setup({ deviceId, password, @@ -37,20 +37,20 @@ export async function setupClient() { baseUrl, getStoredClient, setStoredClient, - }) + }); if (!isPaired) { if (!pairingSecret) { if (process.env.CI) { throw new Error( 'Pairing secret is required. If simulator is running, set PAIRING_SECRET environment variable.', - ) + ); } - pairingSecret = question('Enter pairing secret:') + pairingSecret = question('Enter pairing secret:'); if (!pairingSecret) { - throw new Error('Pairing secret is required.') + throw new Error('Pairing secret is required.'); } } - await pair(pairingSecret.toUpperCase()) + await pair(pairingSecret.toUpperCase()); } - return getClient() + return getClient(); } diff --git a/packages/sdk/src/__test__/utils/testConstants.ts b/packages/sdk/src/__test__/utils/testConstants.ts index 186c5c0c..9c996f73 100644 --- a/packages/sdk/src/__test__/utils/testConstants.ts +++ b/packages/sdk/src/__test__/utils/testConstants.ts @@ -1,4 +1,4 @@ -import { mnemonicToSeedSync } from 'bip39' +import { mnemonicToSeedSync } from 'bip39'; /** * Common test constants used across the GridPlus SDK test suite * @@ -13,11 +13,11 @@ import { mnemonicToSeedSync } from 'bip39' * test behavior and deterministic results. */ export const TEST_MNEMONIC = - 'test test test test test test test test test test test junk' + 'test test test test test test test test test test test junk'; /** * Shared seed derived from TEST_MNEMONIC * * Consumers can reuse this to avoid re-deriving the seed in each test. */ -export const TEST_SEED = mnemonicToSeedSync(TEST_MNEMONIC) +export const TEST_SEED = mnemonicToSeedSync(TEST_MNEMONIC); diff --git a/packages/sdk/src/__test__/utils/testEnvironment.ts b/packages/sdk/src/__test__/utils/testEnvironment.ts index 17937018..8f23e7c5 100644 --- a/packages/sdk/src/__test__/utils/testEnvironment.ts +++ b/packages/sdk/src/__test__/utils/testEnvironment.ts @@ -1,6 +1,6 @@ -import * as dotenv from 'dotenv' +import * as dotenv from 'dotenv'; -dotenv.config() +dotenv.config(); expect.extend({ toEqualElseLog(received: unknown, expected: unknown, message?: string) { @@ -8,6 +8,6 @@ expect.extend({ pass: received === expected, message: () => message ?? `Expected ${String(received)} to equal ${String(expected)}`, - } + }; }, -}) +}); diff --git a/packages/sdk/src/__test__/utils/testRequest.ts b/packages/sdk/src/__test__/utils/testRequest.ts index 8d50b70a..8332e70c 100644 --- a/packages/sdk/src/__test__/utils/testRequest.ts +++ b/packages/sdk/src/__test__/utils/testRequest.ts @@ -1,8 +1,8 @@ import { LatticeSecureEncryptedRequestType, encryptedSecureRequest, -} from '../../protocol' -import type { TestRequestPayload } from '../../types' +} from '../../protocol'; +import type { TestRequestPayload } from '../../types'; /** * `test` takes a data object with a testID and a payload, and sends them to the device. @@ -16,17 +16,17 @@ export const testRequest = async ({ if (!payload) { throw new Error( 'First argument must contain `testID` and `payload` fields.', - ) + ); } - const sharedSecret = client.sharedSecret - const ephemeralPub = client.ephemeralPub - const url = client.url + const sharedSecret = client.sharedSecret; + const ephemeralPub = client.ephemeralPub; + const url = client.url; - const TEST_DATA_SZ = 500 - const data = Buffer.alloc(TEST_DATA_SZ + 6) - data.writeUInt32BE(testID, 0) - data.writeUInt16BE(payload.length, 4) - payload.copy(data, 6) + const TEST_DATA_SZ = 500; + const data = Buffer.alloc(TEST_DATA_SZ + 6); + data.writeUInt32BE(testID, 0); + data.writeUInt16BE(payload.length, 4); + payload.copy(data, 6); const { decryptedData } = await encryptedSecureRequest({ data, @@ -34,6 +34,6 @@ export const testRequest = async ({ sharedSecret, ephemeralPub, url, - }) - return decryptedData -} + }); + return decryptedData; +}; diff --git a/packages/sdk/src/__test__/utils/viemComparison.ts b/packages/sdk/src/__test__/utils/viemComparison.ts index 466d6aa6..4f5fcab8 100644 --- a/packages/sdk/src/__test__/utils/viemComparison.ts +++ b/packages/sdk/src/__test__/utils/viemComparison.ts @@ -5,99 +5,99 @@ import { type TypedDataDefinition, parseTransaction, serializeTransaction, -} from 'viem' -import { privateKeyToAccount } from 'viem/accounts' -import { sign, signMessage } from '../../api' -import { normalizeLatticeSignature } from '../../ethereum' -import { ensureHexBuffer } from '../../util' -import { deriveAddress } from './determinism' -import { TEST_SEED } from './testConstants' +} from 'viem'; +import { privateKeyToAccount } from 'viem/accounts'; +import { sign, signMessage } from '../../api'; +import { normalizeLatticeSignature } from '../../ethereum'; +import { ensureHexBuffer } from '../../util'; +import { deriveAddress } from './determinism'; +import { TEST_SEED } from './testConstants'; // Utility function to create foundry account address for comparison export const getFoundryAddress = (): Address => { // Use first account derivation path: m/44'/60'/0'/0/0 - const foundryPath = [44 + 0x80000000, 60 + 0x80000000, 0x80000000, 0, 0] - return deriveAddress(TEST_SEED, foundryPath as any) as Address -} + const foundryPath = [44 + 0x80000000, 60 + 0x80000000, 0x80000000, 0, 0]; + return deriveAddress(TEST_SEED, foundryPath as any) as Address; +}; // Get Foundry private key for signing export const getFoundryPrivateKey = (): Hex => { - return '0xac0974bec39a17e36ba4a6b4d238ff944bacb478cbed5efcae784d7bf4f2ff80' // Foundry test account #0 private key -} + return '0xac0974bec39a17e36ba4a6b4d238ff944bacb478cbed5efcae784d7bf4f2ff80'; // Foundry test account #0 private key +}; // Create foundry account for actual signing export const getFoundryAccount = () => { - const privateKey = getFoundryPrivateKey() - return privateKeyToAccount(privateKey) -} + const privateKey = getFoundryPrivateKey(); + return privateKeyToAccount(privateKey); +}; // Transaction type for our test vectors - use viem's TransactionSerializable -export type TestTransaction = TransactionSerializable +export type TestTransaction = TransactionSerializable; // Sign transaction with both Lattice and viem, then compare export const signAndCompareTransaction = async ( tx: TestTransaction, testName: string, ) => { - const foundryAccount = getFoundryAccount() + const foundryAccount = getFoundryAccount(); try { // Sign with Lattice using the new sign API that accepts TransactionSerializable directly const latticeResult = await sign(tx).catch((err) => { if (err.responseCode === 128) { - err.message = `NOTE: You must have \`FEATURE_TEST_RUNNER=1\` enabled in firmware to run these tests.\n${err.message}` + err.message = `NOTE: You must have \`FEATURE_TEST_RUNNER=1\` enabled in firmware to run these tests.\n${err.message}`; } if (err.responseCode === 132) { - err.message = `NOTE: Please approve the transaction on your Lattice device.\n${err.message}` + err.message = `NOTE: Please approve the transaction on your Lattice device.\n${err.message}`; } - throw err - }) + throw err; + }); // Sign with viem wallet (use original transaction) - const viemSignedTx = await foundryAccount.signTransaction(tx) + const viemSignedTx = await foundryAccount.signTransaction(tx); // Parse the viem signed transaction to extract signature components - const parsedViemTx = parseTransaction(viemSignedTx as `0x${string}`) + const parsedViemTx = parseTransaction(viemSignedTx as `0x${string}`); // Verify Lattice signature structure - expect(latticeResult.sig).toBeDefined() - expect(latticeResult.sig.r).toBeDefined() - expect(latticeResult.sig.s).toBeDefined() + expect(latticeResult.sig).toBeDefined(); + expect(latticeResult.sig.r).toBeDefined(); + expect(latticeResult.sig.s).toBeDefined(); // For legacy transactions, expect v; for modern transactions, v might be undefined if (tx.type === 'legacy') { - expect(latticeResult.sig.v).toBeDefined() + expect(latticeResult.sig.v).toBeDefined(); } // Verify viem signature components exist - expect(parsedViemTx.r).toBeDefined() - expect(parsedViemTx.s).toBeDefined() + expect(parsedViemTx.r).toBeDefined(); + expect(parsedViemTx.s).toBeDefined(); // For typed transactions (EIP-1559, EIP-2930, EIP-7702), check yParity // For legacy transactions, check v if (tx.type !== 'legacy') { - expect(parsedViemTx.yParity).toBeDefined() + expect(parsedViemTx.yParity).toBeDefined(); } else { - expect(parsedViemTx.v).toBeDefined() + expect(parsedViemTx.v).toBeDefined(); } // Get the signed transaction from Lattice - let latticeSignedTx: string + let latticeSignedTx: string; // Check if the new viemTx field is available (automatic normalization) if ((latticeResult as any).viemTx) { - latticeSignedTx = (latticeResult as any).viemTx + latticeSignedTx = (latticeResult as any).viemTx; } else if (latticeResult.tx) { // Use the provided signed transaction - latticeSignedTx = latticeResult.tx + latticeSignedTx = latticeResult.tx; } else { // Fallback to manual normalization for backward compatibility - const normalizedSignedTx = normalizeLatticeSignature(latticeResult, tx) - latticeSignedTx = serializeTransaction(normalizedSignedTx) + const normalizedSignedTx = normalizeLatticeSignature(latticeResult, tx); + latticeSignedTx = serializeTransaction(normalizedSignedTx); } // The most important comparison: verify both produce the same serialized transaction - expect(latticeSignedTx).toBe(viemSignedTx) + expect(latticeSignedTx).toBe(viemSignedTx); // Additional verification: compare signature components // Lattice returns r,s as hex strings with 0x prefix or as Buffer @@ -105,48 +105,48 @@ export const signAndCompareTransaction = async ( const hexString = typeof value === 'string' ? value - : `0x${Buffer.from(value).toString('hex')}` - const stripped = hexString.replace(/^0x/, '').toLowerCase() - return `0x${stripped.padStart(64, '0')}` - } + : `0x${Buffer.from(value).toString('hex')}`; + const stripped = hexString.replace(/^0x/, '').toLowerCase(); + return `0x${stripped.padStart(64, '0')}`; + }; - const latticeR = normalizeSigComponent(latticeResult.sig.r) - const latticeS = normalizeSigComponent(latticeResult.sig.s) + const latticeR = normalizeSigComponent(latticeResult.sig.r); + const latticeS = normalizeSigComponent(latticeResult.sig.s); if (!parsedViemTx.r || !parsedViemTx.s) - throw new Error('Missing signature components') - const viemR = normalizeSigComponent(parsedViemTx.r) - const viemS = normalizeSigComponent(parsedViemTx.s) + throw new Error('Missing signature components'); + const viemR = normalizeSigComponent(parsedViemTx.r); + const viemS = normalizeSigComponent(parsedViemTx.s); // Verify r and s components match exactly - expect(latticeR).toBe(viemR) - expect(latticeS).toBe(viemS) + expect(latticeR).toBe(viemR); + expect(latticeS).toBe(viemS); return { lattice: latticeResult, viem: viemSignedTx, success: true, - } + }; } catch (error) { - console.error(`❌ Test failed for ${testName}:`, error.message) + console.error(`❌ Test failed for ${testName}:`, error.message); - throw error + throw error; } -} +}; // EIP-712 message type for test vectors export type EIP712TestMessage = { - domain: TypedDataDefinition['domain'] - types: TypedDataDefinition['types'] - primaryType: string - message: Record -} + domain: TypedDataDefinition['domain']; + types: TypedDataDefinition['types']; + primaryType: string; + message: Record; +}; // Sign EIP-712 typed data with both Lattice and viem, then compare export const signAndCompareEIP712Message = async ( eip712Message: EIP712TestMessage, testName: string, ) => { - const foundryAccount = getFoundryAccount() + const foundryAccount = getFoundryAccount(); try { // Sign with viem wallet @@ -155,7 +155,7 @@ export const signAndCompareEIP712Message = async ( types: eip712Message.types, primaryType: eip712Message.primaryType, message: eip712Message.message, - }) + }); // Sign with Lattice - need to add EIP712Domain to types const latticeTypes = { @@ -166,76 +166,76 @@ export const signAndCompareEIP712Message = async ( { name: 'chainId', type: 'uint256' }, { name: 'verifyingContract', type: 'address' }, ], - } + }; const latticePayload = { types: latticeTypes, domain: eip712Message.domain, primaryType: eip712Message.primaryType, message: eip712Message.message, - } + }; const latticeResult = await signMessage(latticePayload).catch((err) => { if (err.responseCode === 128) { - err.message = `NOTE: You must have \`FEATURE_TEST_RUNNER=1\` enabled in firmware to run these tests.\n${err.message}` + err.message = `NOTE: You must have \`FEATURE_TEST_RUNNER=1\` enabled in firmware to run these tests.\n${err.message}`; } if (err.responseCode === 132) { - err.message = `NOTE: Please approve the message signature on your Lattice device.\n${err.message}` + err.message = `NOTE: Please approve the message signature on your Lattice device.\n${err.message}`; } - throw err - }) + throw err; + }); // Normalize signature components const normalizeHex = (value: any): string => { if (value === null || value === undefined) { - return '' + return ''; } if (typeof value === 'bigint') { - let hex = value.toString(16) - if (hex.length % 2 !== 0) hex = `0${hex}` - return hex + let hex = value.toString(16); + if (hex.length % 2 !== 0) hex = `0${hex}`; + return hex; } if (typeof value === 'number') { - return value.toString(16) + return value.toString(16); } if (typeof value === 'string') { - return value.startsWith('0x') ? value.slice(2) : value + return value.startsWith('0x') ? value.slice(2) : value; } if (Buffer.isBuffer(value) || value instanceof Uint8Array) { - return Buffer.from(value).toString('hex') + return Buffer.from(value).toString('hex'); } if (typeof value?.toString === 'function') { - const str = value.toString() + const str = value.toString(); if (/^0x[0-9a-f]+$/i.test(str)) { - return str.slice(2) + return str.slice(2); } if (/^[0-9a-f]+$/i.test(str)) { - return str + return str; } } - return ensureHexBuffer(value as string | number | Buffer).toString('hex') - } + return ensureHexBuffer(value as string | number | Buffer).toString('hex'); + }; - const rHex = normalizeHex(latticeResult.sig.r) - const sHex = normalizeHex(latticeResult.sig.s) - let vHex = normalizeHex(latticeResult.sig.v) + const rHex = normalizeHex(latticeResult.sig.r); + const sHex = normalizeHex(latticeResult.sig.s); + let vHex = normalizeHex(latticeResult.sig.v); if (!vHex) { - vHex = '00' + vHex = '00'; } - vHex = vHex.padStart(2, '0') + vHex = vHex.padStart(2, '0'); - const latticeSignature = `0x${rHex}${sHex}${vHex}` + const latticeSignature = `0x${rHex}${sHex}${vHex}`; // Compare signatures - expect(latticeSignature).toBe(viemSignature) + expect(latticeSignature).toBe(viemSignature); return { lattice: latticeResult, viem: viemSignature, success: true, - } + }; } catch (error) { - console.error(`❌ Test failed for ${testName}:`, error.message) - throw error + console.error(`❌ Test failed for ${testName}:`, error.message); + throw error; } -} +}; diff --git a/packages/sdk/src/__test__/utils/vitest.d.ts b/packages/sdk/src/__test__/utils/vitest.d.ts index d21d99a3..9934228b 100644 --- a/packages/sdk/src/__test__/utils/vitest.d.ts +++ b/packages/sdk/src/__test__/utils/vitest.d.ts @@ -1,7 +1,7 @@ /// interface CustomMatchers { - toEqualElseLog(expected: unknown, message?: string): R + toEqualElseLog(expected: unknown, message?: string): R; } declare module 'vitest' { diff --git a/packages/sdk/src/__test__/vectors/abi-vectors.ts b/packages/sdk/src/__test__/vectors/abi-vectors.ts index 581eee4a..d4eb43c8 100644 --- a/packages/sdk/src/__test__/vectors/abi-vectors.ts +++ b/packages/sdk/src/__test__/vectors/abi-vectors.ts @@ -134,4 +134,4 @@ export const ABI_TEST_VECTORS = [ }, category: 'complex-params', }, -] +]; diff --git a/packages/sdk/src/api/addressTags.ts b/packages/sdk/src/api/addressTags.ts index 70c675f1..4e4ebcf5 100644 --- a/packages/sdk/src/api/addressTags.ts +++ b/packages/sdk/src/api/addressTags.ts @@ -1,7 +1,7 @@ -import type { Client } from '../client' -import { MAX_ADDR } from '../constants' -import type { AddressTag } from '../types' -import { queue } from './utilities' +import type { Client } from '../client'; +import { MAX_ADDR } from '../constants'; +import type { AddressTag } from '../types'; +import { queue } from './utilities'; /** * Sends request to the Lattice to add Address Tags. @@ -11,13 +11,13 @@ export const addAddressTags = async ( ): Promise => { // convert an array of objects to an object const records = tags.reduce((acc, tag) => { - const key = Object.keys(tag)[0] - acc[key] = tag[key] - return acc - }, {}) + const key = Object.keys(tag)[0]; + acc[key] = tag[key]; + return acc; + }, {}); - return queue((client) => client.addKvRecords({ records })) -} + return queue((client) => client.addKvRecords({ records })); +}; /** * Fetches Address Tags from the Lattice. @@ -26,9 +26,9 @@ export const fetchAddressTags = async ({ n = MAX_ADDR, start = 0, }: { n?: number; start?: number } = {}) => { - const addressTags: AddressTag[] = [] - let remainingToFetch = n - let fetched = start + const addressTags: AddressTag[] = []; + let remainingToFetch = n; + let fetched = start; while (remainingToFetch > 0) { await queue((client) => @@ -38,14 +38,14 @@ export const fetchAddressTags = async ({ n: remainingToFetch > MAX_ADDR ? MAX_ADDR : remainingToFetch, }) .then(async (res) => { - addressTags.push(...res.records) - fetched = res.fetched + fetched - remainingToFetch = res.total - fetched + addressTags.push(...res.records); + fetched = res.fetched + fetched; + remainingToFetch = res.total - fetched; }), - ) + ); } - return addressTags -} + return addressTags; +}; /** * Removes Address Tags from the Lattice. @@ -53,6 +53,6 @@ export const fetchAddressTags = async ({ export const removeAddressTags = async ( tags: AddressTag[], ): Promise => { - const ids = tags.map((tag) => `${tag.id}`) - return queue((client: Client) => client.removeKvRecords({ ids })) -} + const ids = tags.map((tag) => `${tag.id}`); + return queue((client: Client) => client.removeKvRecords({ ids })); +}; diff --git a/packages/sdk/src/api/addresses.ts b/packages/sdk/src/api/addresses.ts index 5e713037..c2414af3 100644 --- a/packages/sdk/src/api/addresses.ts +++ b/packages/sdk/src/api/addresses.ts @@ -14,32 +14,32 @@ import { LEDGER_LIVE_DERIVATION, MAX_ADDR, SOLANA_DERIVATION, -} from '../constants' -import { LatticeGetAddressesFlag } from '../protocol/latticeConstants' -import type { GetAddressesRequestParams, WalletPath } from '../types' +} from '../constants'; +import { LatticeGetAddressesFlag } from '../protocol/latticeConstants'; +import type { GetAddressesRequestParams, WalletPath } from '../types'; import { getFlagFromPath, getStartPath, parseDerivationPathComponents, queue, -} from './utilities' +} from './utilities'; type FetchAddressesParams = { - n?: number - startPathIndex?: number - flag?: number -} + n?: number; + startPathIndex?: number; + flag?: number; +}; export const fetchAddresses = async ( overrides?: Partial, ) => { - let allAddresses: string[] = [] - let totalFetched = 0 - const totalToFetch = overrides?.n || MAX_ADDR + let allAddresses: string[] = []; + let totalFetched = 0; + const totalToFetch = overrides?.n || MAX_ADDR; while (totalFetched < totalToFetch) { - const batchSize = Math.min(MAX_ADDR, totalToFetch - totalFetched) - const startPath = getStartPath(DEFAULT_ETH_DERIVATION, totalFetched) + const batchSize = Math.min(MAX_ADDR, totalToFetch - totalFetched); + const startPath = getStartPath(DEFAULT_ETH_DERIVATION, totalFetched); await queue((client) => client .getAddresses({ @@ -49,15 +49,15 @@ export const fetchAddresses = async ( }) .then((addresses: string[]) => { if (addresses.length > 0) { - allAddresses = [...allAddresses, ...addresses] - totalFetched += addresses.length + allAddresses = [...allAddresses, ...addresses]; + totalFetched += addresses.length; } }), - ) + ); } - return allAddresses -} + return allAddresses; +}; /** * Fetches a single address from the device. @@ -74,8 +74,8 @@ export const fetchAddress = async ( ? getStartPath(DEFAULT_ETH_DERIVATION, path) : path, n: 1, - }).then((addrs) => addrs[0]) -} + }).then((addrs) => addrs[0]); +}; function createFetchBtcAddressesFunction(derivationPath: number[]) { return async ( @@ -87,26 +87,26 @@ function createFetchBtcAddressesFunction(derivationPath: number[]) { return fetchAddresses({ startPath: getStartPath(derivationPath, startPathIndex), n, - }) - } + }); + }; } export const fetchBtcLegacyAddresses = createFetchBtcAddressesFunction( BTC_LEGACY_DERIVATION, -) +); export const fetchBtcSegwitAddresses = createFetchBtcAddressesFunction( BTC_SEGWIT_DERIVATION, -) +); export const fetchBtcWrappedSegwitAddresses = createFetchBtcAddressesFunction( BTC_WRAPPED_SEGWIT_DERIVATION, -) +); export const fetchBtcLegacyChangeAddresses = createFetchBtcAddressesFunction( BTC_LEGACY_CHANGE_DERIVATION, -) +); export const fetchBtcSegwitChangeAddresses = createFetchBtcAddressesFunction( BTC_SEGWIT_CHANGE_DERIVATION, -) +); export const fetchBtcWrappedSegwitChangeAddresses = - createFetchBtcAddressesFunction(BTC_WRAPPED_SEGWIT_CHANGE_DERIVATION) + createFetchBtcAddressesFunction(BTC_WRAPPED_SEGWIT_CHANGE_DERIVATION); export const fetchSolanaAddresses = async ( { n, startPathIndex }: FetchAddressesParams = { @@ -118,8 +118,8 @@ export const fetchSolanaAddresses = async ( startPath: getStartPath(SOLANA_DERIVATION, startPathIndex, 2), n, flag: 4, - }) -} + }); +}; export const fetchLedgerLiveAddresses = async ( { n, startPathIndex }: FetchAddressesParams = { @@ -127,7 +127,7 @@ export const fetchLedgerLiveAddresses = async ( startPathIndex: 0, }, ) => { - const addresses = [] + const addresses = []; for (let i = 0; i < n; i++) { addresses.push( queue((client) => @@ -142,10 +142,10 @@ export const fetchLedgerLiveAddresses = async ( }) .then((addresses) => addresses.map((address) => `${address}`)), ), - ) + ); } - return Promise.all(addresses) -} + return Promise.all(addresses); +}; export const fetchLedgerLegacyAddresses = async ( { n, startPathIndex }: FetchAddressesParams = { @@ -153,7 +153,7 @@ export const fetchLedgerLegacyAddresses = async ( startPathIndex: 0, }, ) => { - const addresses = [] + const addresses = []; for (let i = 0; i < n; i++) { addresses.push( queue((client) => @@ -168,16 +168,16 @@ export const fetchLedgerLegacyAddresses = async ( }) .then((addresses) => addresses.map((address) => `${address}`)), ), - ) + ); } - return Promise.all(addresses) -} + return Promise.all(addresses); +}; export const fetchBip44ChangeAddresses = async ({ n = MAX_ADDR, startPathIndex = 0, }: FetchAddressesParams = {}) => { - const addresses = [] + const addresses = []; for (let i = 0; i < n; i++) { addresses.push( queue((client) => { @@ -186,30 +186,30 @@ export const fetchBip44ChangeAddresses = async ({ 501 + HARDENED_OFFSET, startPathIndex + i + HARDENED_OFFSET, 0 + HARDENED_OFFSET, - ] + ]; return client .getAddresses({ startPath, n: 1, flag: 4, }) - .then((addresses) => addresses.map((address) => `${address}`)) + .then((addresses) => addresses.map((address) => `${address}`)); }), - ) + ); } - return Promise.all(addresses) -} + return Promise.all(addresses); +}; export async function fetchAddressesByDerivationPath( path: string, { n = 1, startPathIndex = 0, flag }: FetchAddressesParams = {}, ): Promise { - const components = path.split('/').filter(Boolean) - const parsedPath = parseDerivationPathComponents(components) - const _flag = getFlagFromPath(parsedPath) + const components = path.split('/').filter(Boolean); + const parsedPath = parseDerivationPathComponents(components); + const _flag = getFlagFromPath(parsedPath); const wildcardIndex = components.findIndex((part) => part.toLowerCase().includes('x'), - ) + ); if (wildcardIndex === -1) { return queue((client) => @@ -218,13 +218,14 @@ export async function fetchAddressesByDerivationPath( flag: flag || _flag, n, }), - ) + ); } - const addresses: string[] = [] + const addresses: string[] = []; for (let i = 0; i < n; i++) { - const currentPath = [...parsedPath] - currentPath[wildcardIndex] = currentPath[wildcardIndex] + startPathIndex + i + const currentPath = [...parsedPath]; + currentPath[wildcardIndex] = + currentPath[wildcardIndex] + startPathIndex + i; const result = await queue((client) => client.getAddresses({ @@ -232,11 +233,11 @@ export async function fetchAddressesByDerivationPath( flag: flag || _flag, n: 1, }), - ) - addresses.push(...result) + ); + addresses.push(...result); } - return addresses + return addresses; } /** @@ -246,8 +247,8 @@ export async function fetchAddressesByDerivationPath( export async function fetchBtcXpub(): Promise { const result = await fetchAddressesByDerivationPath(BTC_LEGACY_XPUB_PATH, { flag: LatticeGetAddressesFlag.secp256k1Xpub, - }) - return result[0] + }); + return result[0]; } /** @@ -260,8 +261,8 @@ export async function fetchBtcYpub(): Promise { { flag: LatticeGetAddressesFlag.secp256k1Xpub, }, - ) - return result[0] + ); + return result[0]; } /** @@ -271,6 +272,6 @@ export async function fetchBtcYpub(): Promise { export async function fetchBtcZpub(): Promise { const result = await fetchAddressesByDerivationPath(BTC_SEGWIT_ZPUB_PATH, { flag: LatticeGetAddressesFlag.secp256k1Xpub, - }) - return result[0] + }); + return result[0]; } diff --git a/packages/sdk/src/api/index.ts b/packages/sdk/src/api/index.ts index 655eb91b..4013e0f1 100644 --- a/packages/sdk/src/api/index.ts +++ b/packages/sdk/src/api/index.ts @@ -1,15 +1,15 @@ -export { getClient, parseDerivationPath } from './utilities' +export { getClient, parseDerivationPath } from './utilities'; -export * from './addresses' -export * from './addressTags' -export * from './signing' -export * from './wallets' -export * from './setup' +export * from './addresses'; +export * from './addressTags'; +export * from './signing'; +export * from './wallets'; +export * from './setup'; export { BTC_LEGACY_XPUB_PATH, BTC_WRAPPED_SEGWIT_YPUB_PATH, BTC_SEGWIT_ZPUB_PATH, -} from '../constants' +} from '../constants'; -export type { AddressTag } from '../types' +export type { AddressTag } from '../types'; diff --git a/packages/sdk/src/api/setup.ts b/packages/sdk/src/api/setup.ts index 1bee97ed..6471528f 100644 --- a/packages/sdk/src/api/setup.ts +++ b/packages/sdk/src/api/setup.ts @@ -1,7 +1,7 @@ -import { Utils } from '..' -import { Client } from '../client' -import { loadClient, saveClient, setLoadClient, setSaveClient } from './state' -import { buildLoadClientFn, buildSaveClientFn, queue } from './utilities' +import { Utils } from '..'; +import { Client } from '../client'; +import { loadClient, saveClient, setLoadClient, setSaveClient } from './state'; +import { buildLoadClientFn, buildSaveClientFn, queue } from './utilities'; /** * @interface {Object} SetupParameters - parameters for the setup function @@ -14,18 +14,18 @@ import { buildLoadClientFn, buildSaveClientFn, queue } from './utilities' */ type SetupParameters = | { - deviceId: string - password: string - name: string - appSecret?: string - getStoredClient: () => Promise - setStoredClient: (clientData: string | null) => Promise - baseUrl?: string + deviceId: string; + password: string; + name: string; + appSecret?: string; + getStoredClient: () => Promise; + setStoredClient: (clientData: string | null) => Promise; + baseUrl?: string; } | { - getStoredClient: () => Promise - setStoredClient: (clientData: string | null) => Promise - } + getStoredClient: () => Promise; + setStoredClient: (clientData: string | null) => Promise; + }; /** * `setup` initializes the Client and executes `connect()` if necessary. It returns a promise that @@ -43,43 +43,43 @@ type SetupParameters = * */ export const setup = async (params: SetupParameters): Promise => { - if (!params.getStoredClient) throw new Error('Client data getter required') - setLoadClient(buildLoadClientFn(params.getStoredClient)) + if (!params.getStoredClient) throw new Error('Client data getter required'); + setLoadClient(buildLoadClientFn(params.getStoredClient)); - if (!params.setStoredClient) throw new Error('Client data setter required') - setSaveClient(buildSaveClientFn(params.setStoredClient)) + if (!params.setStoredClient) throw new Error('Client data setter required'); + setSaveClient(buildSaveClientFn(params.setStoredClient)); if ('deviceId' in params && 'password' in params && 'name' in params) { const privKey = params.appSecret || - Utils.generateAppSecret(params.deviceId, params.password, params.name) + Utils.generateAppSecret(params.deviceId, params.password, params.name); const client = new Client({ deviceId: params.deviceId, privKey, name: params.name, baseUrl: params.baseUrl, - }) + }); return client.connect(params.deviceId).then(async (isPaired) => { - await saveClient(client.getStateData()) - return isPaired - }) + await saveClient(client.getStateData()); + return isPaired; + }); } else { - const client = await loadClient() - if (!client) throw new Error('Client not initialized') - const deviceId = client.getDeviceId() + const client = await loadClient(); + if (!client) throw new Error('Client not initialized'); + const deviceId = client.getDeviceId(); if (!client.ephemeralPub && deviceId) { - return connect(deviceId) + return connect(deviceId); } else { - await saveClient(client.getStateData()) - return Promise.resolve(true) + await saveClient(client.getStateData()); + return Promise.resolve(true); } } -} +}; export const connect = async (deviceId: string): Promise => { - return queue((client) => client.connect(deviceId)) -} + return queue((client) => client.connect(deviceId)); +}; export const pair = async (pairingCode: string): Promise => { - return queue((client) => client.pair(pairingCode)) -} + return queue((client) => client.pair(pairingCode)); +}; diff --git a/packages/sdk/src/api/signing.ts b/packages/sdk/src/api/signing.ts index fe3b785e..fd98777a 100644 --- a/packages/sdk/src/api/signing.ts +++ b/packages/sdk/src/api/signing.ts @@ -1,5 +1,5 @@ -import { RLP } from '@ethereumjs/rlp' -import { Hash } from 'ox' +import { RLP } from '@ethereumjs/rlp'; +import { Hash } from 'ox'; import { type Address, type Authorization, @@ -7,8 +7,8 @@ import { type TransactionSerializable, type TransactionSerializableEIP7702, serializeTransaction, -} from 'viem' -import { Constants } from '..' +} from 'viem'; +import { Constants } from '..'; import { BTC_LEGACY_DERIVATION, BTC_SEGWIT_DERIVATION, @@ -16,8 +16,8 @@ import { CURRENCIES, DEFAULT_ETH_DERIVATION, SOLANA_DERIVATION, -} from '../constants' -import { fetchDecoder } from '../functions/fetchDecoder' +} from '../constants'; +import { fetchDecoder } from '../functions/fetchDecoder'; import type { BitcoinSignPayload, EIP712MessagePayload, @@ -25,47 +25,47 @@ import type { SignRequestParams, SigningPayload, TransactionRequest, -} from '../types' -import { getYParity } from '../util' -import { isEIP712Payload, queue } from './utilities' +} from '../types'; +import { getYParity } from '../util'; +import { isEIP712Payload, queue } from './utilities'; // Define the authorization request type based on Viem's structure type AuthorizationRequest = { - chainId: number - nonce: number -} & ({ address: Address } | { contractAddress: Address }) + chainId: number; + nonce: number; +} & ({ address: Address } | { contractAddress: Address }); /** * Sign a transaction using Viem-compatible transaction types */ -type RawTransaction = Hex | Uint8Array | Buffer +type RawTransaction = Hex | Uint8Array | Buffer; export const sign = async ( transaction: TransactionSerializable | RawTransaction, overrides?: Omit, ): Promise => { - const isRaw = isRawTransaction(transaction) + const isRaw = isRawTransaction(transaction); const serializedTx = isRaw ? normalizeRawTransaction(transaction) - : serializeTransaction(transaction as TransactionSerializable) + : serializeTransaction(transaction as TransactionSerializable); // Determine the encoding type based on transaction type let encodingType: | typeof Constants.SIGNING.ENCODINGS.EVM | typeof Constants.SIGNING.ENCODINGS.EIP7702_AUTH | typeof Constants.SIGNING.ENCODINGS.EIP7702_AUTH_LIST = - Constants.SIGNING.ENCODINGS.EVM + Constants.SIGNING.ENCODINGS.EVM; if (!isRaw && (transaction as TransactionSerializable).type === 'eip7702') { - const eip7702Tx = transaction as TransactionSerializableEIP7702 + const eip7702Tx = transaction as TransactionSerializableEIP7702; const hasAuthList = - eip7702Tx.authorizationList && eip7702Tx.authorizationList.length > 0 + eip7702Tx.authorizationList && eip7702Tx.authorizationList.length > 0; encodingType = hasAuthList ? Constants.SIGNING.ENCODINGS.EIP7702_AUTH_LIST - : Constants.SIGNING.ENCODINGS.EIP7702_AUTH + : Constants.SIGNING.ENCODINGS.EIP7702_AUTH; } // Only fetch decoder if we have the required fields - let decoder: Buffer | undefined + let decoder: Buffer | undefined; if ( !isRaw && 'data' in (transaction as TransactionSerializable) && @@ -76,7 +76,7 @@ export const sign = async ( data: (transaction as TransactionSerializable).data, to: (transaction as TransactionSerializable).to, chainId: (transaction as TransactionSerializable).chainId, - } as TransactionRequest) + } as TransactionRequest); } const payload: SigningPayload = { @@ -86,10 +86,10 @@ export const sign = async ( encodingType, payload: serializedTx, decoder, - } + }; - return queue((client) => client.sign({ data: payload, ...overrides })) -} + return queue((client) => client.sign({ data: payload, ...overrides })); +}; /** * Sign a message with support for EIP-712 typed data and const assertions @@ -109,15 +109,15 @@ export function signMessage( hashType: Constants.SIGNING.HASHES.KECCAK256, protocol: isEIP712Payload(payload) ? 'eip712' : 'signPersonal', payload: payload as SigningPayload>['payload'], - } + }; const tx: SignRequestParams = { data: basePayload as SignRequestParams['data'], currency: overrides?.currency ?? CURRENCIES.ETH_MSG, ...(overrides ?? {}), - } + }; - return queue((client) => client.sign(tx)) + return queue((client) => client.sign(tx)); } function isRawTransaction( @@ -127,14 +127,14 @@ function isRawTransaction( typeof value === 'string' || value instanceof Uint8Array || Buffer.isBuffer(value) - ) + ); } function normalizeRawTransaction(tx: RawTransaction): Hex | Buffer { if (typeof tx === 'string') { - return tx.startsWith('0x') ? (tx as Hex) : (`0x${tx}` as Hex) + return tx.startsWith('0x') ? (tx as Hex) : (`0x${tx}` as Hex); } - return Buffer.from(tx) + return Buffer.from(tx); } /** @@ -147,20 +147,20 @@ export const signAuthorization = async ( ): Promise => { // EIP-7702 authorization message is: MAGIC || rlp([chain_id, address, nonce]) // MAGIC = 0x05 per EIP-7702 spec - const MAGIC = Buffer.from([0x05]) + const MAGIC = Buffer.from([0x05]); // Handle the address/contractAddress alias const address = 'address' in authorization ? authorization.address - : authorization.contractAddress + : authorization.contractAddress; const message = Buffer.concat([ MAGIC, Buffer.from( RLP.encode([authorization.chainId, address, authorization.nonce]), ), - ]) + ]); const payload: SigningPayload = { signerPath: DEFAULT_ETH_DERIVATION, @@ -168,26 +168,26 @@ export const signAuthorization = async ( hashType: Constants.SIGNING.HASHES.KECCAK256, encodingType: Constants.SIGNING.ENCODINGS.EIP7702_AUTH, payload: message, - } + }; // Get the signature with all components const response = await queue((client) => client.sign({ data: payload, ...overrides }), - ) + ); // Extract signature components if they exist if (response.sig && response.pubkey) { // Calculate the correct y-parity value - const messageHash = Buffer.from(Hash.keccak256(message)) - const yParity = getYParity(messageHash, response.sig, response.pubkey) + const messageHash = Buffer.from(Hash.keccak256(message)); + const yParity = getYParity(messageHash, response.sig, response.pubkey); // Handle both Buffer and string formats for r and s const rValue = Buffer.isBuffer(response.sig.r) ? `0x${response.sig.r.toString('hex')}` - : response.sig.r + : response.sig.r; const sValue = Buffer.isBuffer(response.sig.s) ? `0x${response.sig.s.toString('hex')}` - : response.sig.s + : response.sig.s; // Create a complete Authorization object with all required signature components const result: Authorization = { @@ -197,13 +197,13 @@ export const signAuthorization = async ( yParity, r: rValue as Hex, s: sValue as Hex, - } + }; - return result + return result; } - throw new Error('Failed to get signature from device') -} + throw new Error('Failed to get signature from device'); +}; /** * Sign an EIP-7702 transaction using Viem-compatible types @@ -211,7 +211,7 @@ export const signAuthorization = async ( export const signAuthorizationList = async ( tx: TransactionSerializableEIP7702, ): Promise => { - const serializedTx = serializeTransaction(tx) + const serializedTx = serializeTransaction(tx); const payload: SigningPayload = { signerPath: DEFAULT_ETH_DERIVATION, @@ -219,13 +219,13 @@ export const signAuthorizationList = async ( hashType: Constants.SIGNING.HASHES.KECCAK256, encodingType: Constants.SIGNING.ENCODINGS.EIP7702_AUTH_LIST, payload: serializedTx, - } + }; - const signedPayload = await queue((client) => client.sign({ data: payload })) + const signedPayload = await queue((client) => client.sign({ data: payload })); // Return the SignData structure from Lattice, not the converted signature - return signedPayload -} + return signedPayload; +}; export const signBtcLegacyTx = async ( payload: BitcoinSignPayload, @@ -236,9 +236,9 @@ export const signBtcLegacyTx = async ( ...payload, }, currency: CURRENCIES.BTC, - } - return queue((client) => client.sign(tx)) -} + }; + return queue((client) => client.sign(tx)); +}; export const signBtcSegwitTx = async ( payload: BitcoinSignPayload, @@ -249,9 +249,9 @@ export const signBtcSegwitTx = async ( ...payload, }, currency: CURRENCIES.BTC, - } - return queue((client) => client.sign(tx)) -} + }; + return queue((client) => client.sign(tx)); +}; export const signBtcWrappedSegwitTx = async ( payload: BitcoinSignPayload, @@ -262,9 +262,9 @@ export const signBtcWrappedSegwitTx = async ( ...payload, }, currency: CURRENCIES.BTC, - } - return queue((client) => client.sign(tx)) -} + }; + return queue((client) => client.sign(tx)); +}; export const signSolanaTx = async ( payload: Buffer, @@ -279,6 +279,6 @@ export const signSolanaTx = async ( payload, ...overrides, }, - } - return queue((client) => client.sign(tx)) -} + }; + return queue((client) => client.sign(tx)); +}; diff --git a/packages/sdk/src/api/state.ts b/packages/sdk/src/api/state.ts index 75771e36..22aac567 100644 --- a/packages/sdk/src/api/state.ts +++ b/packages/sdk/src/api/state.ts @@ -1,23 +1,23 @@ -import type { Client } from '../client' +import type { Client } from '../client'; -export let saveClient: (clientData: string | null) => Promise +export let saveClient: (clientData: string | null) => Promise; export const setSaveClient = ( fn: (clientData: string | null) => Promise, ) => { - saveClient = fn -} + saveClient = fn; +}; -export let loadClient: () => Promise +export let loadClient: () => Promise; export const setLoadClient = (fn: () => Promise) => { - loadClient = fn -} + loadClient = fn; +}; -let functionQueue: Promise +let functionQueue: Promise; -export const getFunctionQueue = () => functionQueue +export const getFunctionQueue = () => functionQueue; export const setFunctionQueue = (queue: Promise) => { - functionQueue = queue -} + functionQueue = queue; +}; diff --git a/packages/sdk/src/api/utilities.ts b/packages/sdk/src/api/utilities.ts index b737639e..20072a67 100644 --- a/packages/sdk/src/api/utilities.ts +++ b/packages/sdk/src/api/utilities.ts @@ -1,11 +1,11 @@ -import { Client } from '../client' -import { EXTERNAL, HARDENED_OFFSET } from '../constants' +import { Client } from '../client'; +import { EXTERNAL, HARDENED_OFFSET } from '../constants'; import { getFunctionQueue, loadClient, saveClient, setFunctionQueue, -} from './state' +} from './state'; /** * `queue` is a function that wraps all functional API calls. It limits the number of concurrent @@ -17,10 +17,10 @@ import { * @internal */ export const queue = async (fn: (client: Client) => Promise) => { - const client = await loadClient() - if (!client) throw new Error('Client not initialized') + const client = await loadClient(); + if (!client) throw new Error('Client not initialized'); if (!getFunctionQueue()) { - setFunctionQueue(Promise.resolve()) + setFunctionQueue(Promise.resolve()); } setFunctionQueue( getFunctionQueue().then( @@ -28,97 +28,97 @@ export const queue = async (fn: (client: Client) => Promise) => { await fn(client) .catch((err) => { // Empty the queue if any function call fails - setFunctionQueue(Promise.resolve()) - throw err + setFunctionQueue(Promise.resolve()); + throw err; }) .then((returnValue) => { - saveClient(client.getStateData()) - return returnValue + saveClient(client.getStateData()); + return returnValue; }), ), - ) - return getFunctionQueue() -} + ); + return getFunctionQueue(); +}; export const getClient = async (): Promise => { - const client = loadClient ? await loadClient() : undefined - if (!client) throw new Error('Client not initialized') - return client -} + const client = loadClient ? await loadClient() : undefined; + if (!client) throw new Error('Client not initialized'); + return client; +}; const encodeClientData = (clientData: string) => { - return Buffer.from(clientData).toString('base64') -} + return Buffer.from(clientData).toString('base64'); +}; const decodeClientData = (clientData: string) => { - return Buffer.from(clientData, 'base64').toString() -} + return Buffer.from(clientData, 'base64').toString(); +}; export const buildSaveClientFn = ( setStoredClient: (clientData: string | null) => Promise, ) => { return async (clientData: string | null) => { - if (!clientData) return - const encodedData = encodeClientData(clientData) - await setStoredClient(encodedData) - } -} + if (!clientData) return; + const encodedData = encodeClientData(clientData); + await setStoredClient(encodedData); + }; +}; export const buildLoadClientFn = (getStoredClient: () => Promise) => { return async () => { - const clientData = await getStoredClient() - if (!clientData) return undefined - const stateData = decodeClientData(clientData) - if (!stateData) return undefined - const client = new Client({ stateData }) - if (!client) throw new Error('Client not initialized') - return client - } -} + const clientData = await getStoredClient(); + if (!clientData) return undefined; + const stateData = decodeClientData(clientData); + if (!stateData) return undefined; + const client = new Client({ stateData }); + if (!client) throw new Error('Client not initialized'); + return client; + }; +}; export const getStartPath = ( defaultStartPath: number[], addressIndex = 0, // The value to increment `defaultStartPath` pathIndex = 4, // Which index in `defaultStartPath` array to increment ): number[] => { - const startPath = [...defaultStartPath] + const startPath = [...defaultStartPath]; if (addressIndex > 0) { - startPath[pathIndex] = defaultStartPath[pathIndex] + addressIndex + startPath[pathIndex] = defaultStartPath[pathIndex] + addressIndex; } - return startPath -} + return startPath; +}; export const isEIP712Payload = (payload: any) => typeof payload !== 'string' && 'types' in payload && 'domain' in payload && 'primaryType' in payload && - 'message' in payload + 'message' in payload; export function parseDerivationPath(path: string): number[] { - if (!path) return [] - const components = path.split('/').filter(Boolean) - return parseDerivationPathComponents(components) + if (!path) return []; + const components = path.split('/').filter(Boolean); + return parseDerivationPathComponents(components); } export function parseDerivationPathComponents(components: string[]): number[] { return components.map((part) => { - const lowerPart = part.toLowerCase() - if (lowerPart === 'x') return 0 // Wildcard - if (lowerPart === "x'") return HARDENED_OFFSET // Hardened wildcard + const lowerPart = part.toLowerCase(); + if (lowerPart === 'x') return 0; // Wildcard + if (lowerPart === "x'") return HARDENED_OFFSET; // Hardened wildcard if (part.endsWith("'")) - return Number.parseInt(part.slice(0, -1)) + HARDENED_OFFSET - const val = Number.parseInt(part) + return Number.parseInt(part.slice(0, -1)) + HARDENED_OFFSET; + const val = Number.parseInt(part); if (Number.isNaN(val)) { - throw new Error(`Invalid part in derivation path: ${part}`) + throw new Error(`Invalid part in derivation path: ${part}`); } - return val - }) + return val; + }); } export function getFlagFromPath(path: number[]): number | undefined { if (path.length >= 2 && path[1] === 501 + HARDENED_OFFSET) { - return EXTERNAL.GET_ADDR_FLAGS.ED25519_PUB // SOLANA + return EXTERNAL.GET_ADDR_FLAGS.ED25519_PUB; // SOLANA } - return undefined + return undefined; } diff --git a/packages/sdk/src/api/wallets.ts b/packages/sdk/src/api/wallets.ts index e716c9fc..a3c9b698 100644 --- a/packages/sdk/src/api/wallets.ts +++ b/packages/sdk/src/api/wallets.ts @@ -1,9 +1,9 @@ -import type { ActiveWallets } from '../types' -import { queue } from './utilities' +import type { ActiveWallets } from '../types'; +import { queue } from './utilities'; /** * Fetches the active wallets */ export const fetchActiveWallets = async (): Promise => { - return queue((client) => client.fetchActiveWallet()) -} + return queue((client) => client.fetchActiveWallet()); +}; diff --git a/packages/sdk/src/bitcoin.ts b/packages/sdk/src/bitcoin.ts index fee75215..190fa812 100644 --- a/packages/sdk/src/bitcoin.ts +++ b/packages/sdk/src/bitcoin.ts @@ -1,13 +1,13 @@ // Util for Bitcoin-specific functionality -import { bech32 } from 'bech32' -import bs58check from 'bs58check' -import { ripemd160 } from 'hash.js/lib/hash/ripemd.js' -import { Hash } from 'ox' -import { BIP_CONSTANTS } from './constants' -import { LatticeSignSchema } from './protocol' -const DEFAULT_SEQUENCE = 0xffffffff -const DEFAULT_SIGHASH_BUFFER = Buffer.from('01', 'hex') // SIGHASH_ALL = 0x01 -const { PURPOSES, COINS } = BIP_CONSTANTS +import { bech32 } from 'bech32'; +import bs58check from 'bs58check'; +import { ripemd160 } from 'hash.js/lib/hash/ripemd.js'; +import { Hash } from 'ox'; +import { BIP_CONSTANTS } from './constants'; +import { LatticeSignSchema } from './protocol'; +const DEFAULT_SEQUENCE = 0xffffffff; +const DEFAULT_SIGHASH_BUFFER = Buffer.from('01', 'hex'); // SIGHASH_ALL = 0x01 +const { PURPOSES, COINS } = BIP_CONSTANTS; const OP = { ZERO: 0x00, HASH160: 0xa9, @@ -15,20 +15,20 @@ const OP = { EQUAL: 0x87, EQUALVERIFY: 0x88, CHECKSIG: 0xac, -} -const SEGWIT_V0 = 0x00 -const SEGWIT_NATIVE_V0_PREFIX = 'bc' -const SEGWIT_NATIVE_V0_TESTNET_PREFIX = 'tb' +}; +const SEGWIT_V0 = 0x00; +const SEGWIT_NATIVE_V0_PREFIX = 'bc'; +const SEGWIT_NATIVE_V0_TESTNET_PREFIX = 'tb'; -const FMT_SEGWIT_NATIVE_V0 = 0xd0 -const FMT_SEGWIT_NATIVE_V0_TESTNET = 0xf0 -const FMT_SEGWIT_WRAPPED = 0x05 -const FMT_SEGWIT_WRAPPED_TESTNET = 0xc4 -const FMT_LEGACY = 0x00 -const FMT_LEGACY_TESTNET = 0x6f -const BTC_SCRIPT_TYPE_P2PKH = 0x01 -const BTC_SCRIPT_TYPE_P2SH_P2WPKH = 0x03 -const BTC_SCRIPT_TYPE_P2WPKH_V0 = 0x04 +const FMT_SEGWIT_NATIVE_V0 = 0xd0; +const FMT_SEGWIT_NATIVE_V0_TESTNET = 0xf0; +const FMT_SEGWIT_WRAPPED = 0x05; +const FMT_SEGWIT_WRAPPED_TESTNET = 0xc4; +const FMT_LEGACY = 0x00; +const FMT_LEGACY_TESTNET = 0x6f; +const BTC_SCRIPT_TYPE_P2PKH = 0x01; +const BTC_SCRIPT_TYPE_P2SH_P2WPKH = 0x03; +const BTC_SCRIPT_TYPE_P2WPKH_V0 = 0x04; // We need to build two different objects here: // 1. bitcoinjs-lib TransactionBuilder object, which will be used in conjunction @@ -50,66 +50,66 @@ const BTC_SCRIPT_TYPE_P2WPKH_V0 = 0x04 // `version`: Transaction version of the inputs. All inputs must be of the same version! // `isSegwit`: a boolean which determines how we serialize the data and parameterize txb const buildBitcoinTxRequest = (data) => { - const { prevOuts, recipient, value, changePath, fee } = data - if (!changePath) throw new Error('No changePath provided.') + const { prevOuts, recipient, value, changePath, fee } = data; + if (!changePath) throw new Error('No changePath provided.'); if (changePath.length !== 5) - throw new Error('Please provide a full change path.') + throw new Error('Please provide a full change path.'); // Serialize the request - const payload = Buffer.alloc(59 + 69 * prevOuts.length) - let off = 0 + const payload = Buffer.alloc(59 + 69 * prevOuts.length); + let off = 0; // Change version byte (a.k.a. address format byte) - const changeFmt = getAddressFormat(changePath) - payload.writeUInt8(changeFmt, 0) - off++ + const changeFmt = getAddressFormat(changePath); + payload.writeUInt8(changeFmt, 0); + off++; // Build the change data - payload.writeUInt32LE(changePath.length, off) - off += 4 + payload.writeUInt32LE(changePath.length, off); + off += 4; for (let i = 0; i < changePath.length; i++) { - payload.writeUInt32LE(changePath[i], off) - off += 4 + payload.writeUInt32LE(changePath[i], off); + off += 4; } // Fee is a param - payload.writeUInt32LE(fee, off) - off += 4 - const dec = decodeAddress(recipient) + payload.writeUInt32LE(fee, off); + off += 4; + const dec = decodeAddress(recipient); // Parameterize the recipient output - payload.writeUInt8(dec.versionByte, off) - off++ - dec.pkh.copy(payload, off) - off += dec.pkh.length - writeUInt64LE(value, payload, off) - off += 8 + payload.writeUInt8(dec.versionByte, off); + off++; + dec.pkh.copy(payload, off); + off += dec.pkh.length; + writeUInt64LE(value, payload, off); + off += 8; // Build the inputs from the previous outputs - payload.writeUInt8(prevOuts.length, off) - off++ - let inputSum = 0 + payload.writeUInt8(prevOuts.length, off); + off++; + let inputSum = 0; prevOuts.forEach((input) => { if (!input.signerPath || input.signerPath.length !== 5) { - throw new Error('Full recipient path not specified ') + throw new Error('Full recipient path not specified '); } - payload.writeUInt32LE(input.signerPath.length, off) - off += 4 + payload.writeUInt32LE(input.signerPath.length, off); + off += 4; for (let i = 0; i < input.signerPath.length; i++) { - payload.writeUInt32LE(input.signerPath[i], off) - off += 4 + payload.writeUInt32LE(input.signerPath[i], off); + off += 4; } - payload.writeUInt32LE(input.index, off) - off += 4 - writeUInt64LE(input.value, payload, off) - off += 8 - inputSum += input.value - const scriptType = getScriptType(input) - payload.writeUInt8(scriptType, off) - off++ + payload.writeUInt32LE(input.index, off); + off += 4; + writeUInt64LE(input.value, payload, off); + off += 8; + inputSum += input.value; + const scriptType = getScriptType(input); + payload.writeUInt8(scriptType, off); + off++; if (!Buffer.isBuffer(input.txHash)) - input.txHash = Buffer.from(input.txHash, 'hex') - input.txHash.copy(payload, off) - off += input.txHash.length - }) + input.txHash = Buffer.from(input.txHash, 'hex'); + input.txHash.copy(payload, off); + off += input.txHash.length; + }); // Send them back! return { payload, @@ -119,8 +119,8 @@ const buildBitcoinTxRequest = (data) => { // This data helps fill in the change output value: inputSum - (value + fee), }, - } -} + }; +}; // Serialize a transaction consisting of inputs, outputs, and some // metadata @@ -130,286 +130,288 @@ const buildBitcoinTxRequest = (data) => { // (NOTE: either ALL are being spent, or none are) // -- lockTime = Will probably always be 0 const serializeTx = (data) => { - const { inputs, outputs, lockTime = 0 } = data - let payload = Buffer.alloc(4) - let off = 0 + const { inputs, outputs, lockTime = 0 } = data; + let payload = Buffer.alloc(4); + let off = 0; // Always use version 2 - const version = 2 - const useWitness = needsWitness(inputs) - payload.writeUInt32LE(version, off) - off += 4 + const version = 2; + const useWitness = needsWitness(inputs); + payload.writeUInt32LE(version, off); + off += 4; if (useWitness) { - payload = concat(payload, Buffer.from('00', 'hex')) // marker = 0x00 - payload = concat(payload, Buffer.from('01', 'hex')) // flag = 0x01 + payload = concat(payload, Buffer.from('00', 'hex')); // marker = 0x00 + payload = concat(payload, Buffer.from('01', 'hex')); // flag = 0x01 } // Serialize signed inputs - const numInputs = getVarInt(inputs.length) - payload = concat(payload, numInputs) - off += numInputs.length + const numInputs = getVarInt(inputs.length); + payload = concat(payload, numInputs); + off += numInputs.length; inputs.forEach((input) => { - payload = concat(payload, input.hash.reverse()) - off += input.hash.length - const index = getU32LE(input.index) - payload = concat(payload, index) - off += index.length - const scriptType = getScriptType(input) + payload = concat(payload, input.hash.reverse()); + off += input.hash.length; + const index = getU32LE(input.index); + payload = concat(payload, index); + off += index.length; + const scriptType = getScriptType(input); // Build the sigScript. Note that p2wpkh does not have a scriptSig. if (scriptType === BTC_SCRIPT_TYPE_P2SH_P2WPKH) { // Build a vector (varSlice of varSlice) containing the redeemScript - const redeemScript = buildRedeemScript(input.pubkey) - const redeemScriptLen = getVarInt(redeemScript.length) - const slice = Buffer.concat([redeemScriptLen, redeemScript]) - const sliceLen = getVarInt(slice.length) - payload = concat(payload, sliceLen) - off += sliceLen.length - payload = concat(payload, slice) - off += slice.length + const redeemScript = buildRedeemScript(input.pubkey); + const redeemScriptLen = getVarInt(redeemScript.length); + const slice = Buffer.concat([redeemScriptLen, redeemScript]); + const sliceLen = getVarInt(slice.length); + payload = concat(payload, sliceLen); + off += sliceLen.length; + payload = concat(payload, slice); + off += slice.length; } else if (scriptType === BTC_SCRIPT_TYPE_P2PKH) { // Build the signature + pubkey script to spend this input - const slice = buildSig(input.sig, input.pubkey) - payload = concat(payload, slice) - off += slice.length + const slice = buildSig(input.sig, input.pubkey); + payload = concat(payload, slice); + off += slice.length; } else if (scriptType === BTC_SCRIPT_TYPE_P2WPKH_V0) { - const emptyScript = Buffer.from('00', 'hex') - payload = concat(payload, emptyScript) - off += 1 + const emptyScript = Buffer.from('00', 'hex'); + payload = concat(payload, emptyScript); + off += 1; } // Use the default sequence for all transactions - const sequence = getU32LE(DEFAULT_SEQUENCE) - payload = concat(payload, sequence) - off += sequence.length - }) + const sequence = getU32LE(DEFAULT_SEQUENCE); + payload = concat(payload, sequence); + off += sequence.length; + }); // Serialize outputs - const numOutputs = getVarInt(outputs.length) - payload = concat(payload, numOutputs) - off += numOutputs.length + const numOutputs = getVarInt(outputs.length); + payload = concat(payload, numOutputs); + off += numOutputs.length; outputs.forEach((output) => { - const value = getU64LE(output.value) - payload = concat(payload, value) - off += value.length + const value = getU64LE(output.value); + payload = concat(payload, value); + off += value.length; // Build the output locking script and write it as a var slice - const script = buildLockingScript(output.recipient) - const scriptLen = getVarInt(script.length) - payload = concat(payload, scriptLen) - off += scriptLen.length - payload = concat(payload, script) - off += script.length - }) + const script = buildLockingScript(output.recipient); + const scriptLen = getVarInt(script.length); + payload = concat(payload, scriptLen); + off += scriptLen.length; + payload = concat(payload, script); + off += script.length; + }); // Add witness data if needed if (useWitness) { - const sigs = [] - const pubkeys = [] + const sigs = []; + const pubkeys = []; for (let i = 0; i < inputs.length; i++) { - sigs.push(inputs[i].sig) - pubkeys.push(inputs[i].pubkey) + sigs.push(inputs[i].sig); + pubkeys.push(inputs[i].pubkey); } - const witnessSlice = buildWitness(sigs, pubkeys) - payload = concat(payload, witnessSlice) - off += witnessSlice.length + const witnessSlice = buildWitness(sigs, pubkeys); + payload = concat(payload, witnessSlice); + off += witnessSlice.length; } // Finish with locktime - return Buffer.concat([payload, getU32LE(lockTime)]).toString('hex') -} + return Buffer.concat([payload, getU32LE(lockTime)]).toString('hex'); +}; // Convert a pubkeyhash to a bitcoin base58check address with a version byte const getBitcoinAddress = (pubkeyhash, version) => { - let bech32Prefix = null - let bech32Version = null + let bech32Prefix = null; + let bech32Version = null; if (version === FMT_SEGWIT_NATIVE_V0) { - bech32Prefix = SEGWIT_NATIVE_V0_PREFIX - bech32Version = SEGWIT_V0 + bech32Prefix = SEGWIT_NATIVE_V0_PREFIX; + bech32Version = SEGWIT_V0; } else if (version === FMT_SEGWIT_NATIVE_V0_TESTNET) { - bech32Prefix = SEGWIT_NATIVE_V0_TESTNET_PREFIX - bech32Version = SEGWIT_V0 + bech32Prefix = SEGWIT_NATIVE_V0_TESTNET_PREFIX; + bech32Version = SEGWIT_V0; } if (bech32Prefix !== null && bech32Version !== null) { - const words = bech32.toWords(pubkeyhash) - words.unshift(bech32Version) - return bech32.encode(bech32Prefix, words) + const words = bech32.toWords(pubkeyhash); + words.unshift(bech32Version); + return bech32.encode(bech32Prefix, words); } else { - return bs58check.encode(Buffer.concat([Buffer.from([version]), pubkeyhash])) + return bs58check.encode( + Buffer.concat([Buffer.from([version]), pubkeyhash]), + ); } -} +}; // Builder utils //----------------------- function buildRedeemScript(pubkey) { - const redeemScript = Buffer.alloc(22) - const shaHash = Buffer.from(Hash.sha256(pubkey)) + const redeemScript = Buffer.alloc(22); + const shaHash = Buffer.from(Hash.sha256(pubkey)); const pubkeyhash = Buffer.from( ripemd160().update(shaHash).digest('hex'), 'hex', - ) - redeemScript.writeUInt8(OP.ZERO, 0) - redeemScript.writeUInt8(pubkeyhash.length, 1) - pubkeyhash.copy(redeemScript, 2) - return redeemScript + ); + redeemScript.writeUInt8(OP.ZERO, 0); + redeemScript.writeUInt8(pubkeyhash.length, 1); + pubkeyhash.copy(redeemScript, 2); + return redeemScript; } // Var slice of signature + var slice of pubkey function buildSig(sig, pubkey) { - sig = Buffer.concat([sig, DEFAULT_SIGHASH_BUFFER]) - const sigLen = getVarInt(sig.length) - const pubkeyLen = getVarInt(pubkey.length) - const slice = Buffer.concat([sigLen, sig, pubkeyLen, pubkey]) - const len = getVarInt(slice.length) - return Buffer.concat([len, slice]) + sig = Buffer.concat([sig, DEFAULT_SIGHASH_BUFFER]); + const sigLen = getVarInt(sig.length); + const pubkeyLen = getVarInt(pubkey.length); + const slice = Buffer.concat([sigLen, sig, pubkeyLen, pubkey]); + const len = getVarInt(slice.length); + return Buffer.concat([len, slice]); } // Witness is written as a "vector", which is a list of varSlices // prefixed by the number of items function buildWitness(sigs, pubkeys) { - let witness = Buffer.alloc(0) + let witness = Buffer.alloc(0); // Two items in each vector (sig, pubkey) - const len = Buffer.alloc(1) - len.writeUInt8(2, 0) + const len = Buffer.alloc(1); + len.writeUInt8(2, 0); for (let i = 0; i < sigs.length; i++) { - const sig = Buffer.concat([sigs[i], DEFAULT_SIGHASH_BUFFER]) - const sigLen = getVarInt(sig.length) - const pubkey = pubkeys[i] - const pubkeyLen = getVarInt(pubkey.length) - witness = Buffer.concat([witness, len, sigLen, sig, pubkeyLen, pubkey]) + const sig = Buffer.concat([sigs[i], DEFAULT_SIGHASH_BUFFER]); + const sigLen = getVarInt(sig.length); + const pubkey = pubkeys[i]; + const pubkeyLen = getVarInt(pubkey.length); + witness = Buffer.concat([witness, len, sigLen, sig, pubkeyLen, pubkey]); } - return witness + return witness; } // Locking script buiders //----------------------- function buildLockingScript(address) { - const dec = decodeAddress(address) + const dec = decodeAddress(address); switch (dec.versionByte) { case FMT_SEGWIT_NATIVE_V0: case FMT_SEGWIT_NATIVE_V0_TESTNET: - return buildP2wpkhLockingScript(dec.pkh) + return buildP2wpkhLockingScript(dec.pkh); case FMT_SEGWIT_WRAPPED: case FMT_SEGWIT_WRAPPED_TESTNET: - return buildP2shLockingScript(dec.pkh) + return buildP2shLockingScript(dec.pkh); case FMT_LEGACY: case FMT_LEGACY_TESTNET: - return buildP2pkhLockingScript(dec.pkh) + return buildP2pkhLockingScript(dec.pkh); default: throw new Error( `Unknown version byte: ${dec.versionByte}. Cannot build BTC transaction.`, - ) + ); } } function buildP2pkhLockingScript(pubkeyhash) { - const out = Buffer.alloc(5 + pubkeyhash.length) - let off = 0 - out.writeUInt8(OP.DUP, off) - off++ - out.writeUInt8(OP.HASH160, off) - off++ - out.writeUInt8(pubkeyhash.length, off) - off++ - pubkeyhash.copy(out, off) - off += pubkeyhash.length - out.writeUInt8(OP.EQUALVERIFY, off) - off++ - out.writeUInt8(OP.CHECKSIG, off) - off++ - return out + const out = Buffer.alloc(5 + pubkeyhash.length); + let off = 0; + out.writeUInt8(OP.DUP, off); + off++; + out.writeUInt8(OP.HASH160, off); + off++; + out.writeUInt8(pubkeyhash.length, off); + off++; + pubkeyhash.copy(out, off); + off += pubkeyhash.length; + out.writeUInt8(OP.EQUALVERIFY, off); + off++; + out.writeUInt8(OP.CHECKSIG, off); + off++; + return out; } function buildP2shLockingScript(pubkeyhash) { - const out = Buffer.alloc(3 + pubkeyhash.length) - let off = 0 - out.writeUInt8(OP.HASH160, off) - off++ - out.writeUInt8(pubkeyhash.length, off) - off++ - pubkeyhash.copy(out, off) - off += pubkeyhash.length - out.writeUInt8(OP.EQUAL, off) - off++ - return out + const out = Buffer.alloc(3 + pubkeyhash.length); + let off = 0; + out.writeUInt8(OP.HASH160, off); + off++; + out.writeUInt8(pubkeyhash.length, off); + off++; + pubkeyhash.copy(out, off); + off += pubkeyhash.length; + out.writeUInt8(OP.EQUAL, off); + off++; + return out; } function buildP2wpkhLockingScript(pubkeyhash) { - const out = Buffer.alloc(2 + pubkeyhash.length) - out.writeUInt8(OP.ZERO, 0) - out.writeUInt8(pubkeyhash.length, 1) - pubkeyhash.copy(out, 2) - return out + const out = Buffer.alloc(2 + pubkeyhash.length); + out.writeUInt8(OP.ZERO, 0); + out.writeUInt8(pubkeyhash.length, 1); + pubkeyhash.copy(out, 2); + return out; } // Static Utils //---------------------- function concat(base, addition) { - return Buffer.concat([base, addition]) + return Buffer.concat([base, addition]); } function getU64LE(x) { - const buffer = Buffer.alloc(8) - writeUInt64LE(x, buffer, 0) - return buffer + const buffer = Buffer.alloc(8); + writeUInt64LE(x, buffer, 0); + return buffer; } function getU32LE(x) { - const buffer = Buffer.alloc(4) - buffer.writeUInt32LE(x, 0) - return buffer + const buffer = Buffer.alloc(4); + buffer.writeUInt32LE(x, 0); + return buffer; } function getVarInt(x) { - let buffer: Buffer + let buffer: Buffer; if (x < 0xfd) { - buffer = Buffer.alloc(1) - buffer.writeUInt8(x, 0) + buffer = Buffer.alloc(1); + buffer.writeUInt8(x, 0); } else if (x <= 0xffff) { - buffer = Buffer.alloc(3) - buffer.writeUInt8(0xfd, 0) - buffer.writeUInt16LE(x, 1) + buffer = Buffer.alloc(3); + buffer.writeUInt8(0xfd, 0); + buffer.writeUInt16LE(x, 1); } else if (x < 0xffffffff) { - buffer = Buffer.alloc(5) - buffer.writeUInt8(0xfe, 0) - buffer.writeUInt32LE(x, 1) + buffer = Buffer.alloc(5); + buffer.writeUInt8(0xfe, 0); + buffer.writeUInt32LE(x, 1); } else { - buffer = Buffer.alloc(9) - buffer.writeUInt8(0xff, 0) - buffer.writeUInt32LE(x >>> 0, 1) - buffer.writeUInt32LE((x / 0x100000000) | 0, 5) + buffer = Buffer.alloc(9); + buffer.writeUInt8(0xff, 0); + buffer.writeUInt32LE(x >>> 0, 1); + buffer.writeUInt32LE((x / 0x100000000) | 0, 5); } - return buffer + return buffer; } function writeUInt64LE(n, buf, off) { - if (typeof n === 'number') n = n.toString(16) - const preBuf = Buffer.alloc(8) - const nStr = n.length % 2 === 0 ? n.toString(16) : `0${n.toString(16)}` - const nBuf = Buffer.from(nStr, 'hex') - nBuf.reverse().copy(preBuf, 0) - preBuf.copy(buf, off) - return preBuf + if (typeof n === 'number') n = n.toString(16); + const preBuf = Buffer.alloc(8); + const nStr = n.length % 2 === 0 ? n.toString(16) : `0${n.toString(16)}`; + const nBuf = Buffer.from(nStr, 'hex'); + nBuf.reverse().copy(preBuf, 0); + preBuf.copy(buf, off); + return preBuf; } function decodeAddress(address) { - let versionByte: number | undefined - let pkh: Buffer | undefined + let versionByte: number | undefined; + let pkh: Buffer | undefined; try { // Attempt to base58 decode the address. This will work for older // P2PKH, P2SH, and P2SH-P2WPKH addresses - versionByte = bs58check.decode(address)[0] - pkh = Buffer.from(bs58check.decode(address).slice(1)) + versionByte = bs58check.decode(address)[0]; + pkh = Buffer.from(bs58check.decode(address).slice(1)); } catch (err) { - console.error('Failed to decode base58 address, trying bech32:', err) + console.error('Failed to decode base58 address, trying bech32:', err); // If we could not base58 decode, the address must be bech32 encoded. // If neither decoding method works, the address is invalid. try { - const bech32Dec = bech32.decode(address) + const bech32Dec = bech32.decode(address); if (bech32Dec.prefix === SEGWIT_NATIVE_V0_PREFIX) { - versionByte = FMT_SEGWIT_NATIVE_V0 + versionByte = FMT_SEGWIT_NATIVE_V0; } else if (bech32Dec.prefix === SEGWIT_NATIVE_V0_TESTNET_PREFIX) { - versionByte = FMT_SEGWIT_NATIVE_V0_TESTNET + versionByte = FMT_SEGWIT_NATIVE_V0_TESTNET; } else { - throw new Error('Unsupported prefix: must be bc or tb.') + throw new Error('Unsupported prefix: must be bc or tb.'); } // Make sure we decoded if (bech32Dec.words[0] !== 0) { throw new Error( `Unsupported segwit version: must be 0, got ${bech32Dec.words[0]}`, - ) + ); } // Make sure address type is supported. // We currently only support P2WPKH addresses, which bech-32decode to 33 words. @@ -417,45 +419,45 @@ function decodeAddress(address) { // Not sure what other address types could exist, but if they exist we don't // support them either. if (bech32Dec.words.length !== 33) { - const isP2wpsh = bech32Dec.words.length === 53 + const isP2wpsh = bech32Dec.words.length === 53; throw new Error( `Unsupported address${isP2wpsh ? ' (P2WSH not supported)' : ''}: ${address}`, - ) + ); } - pkh = Buffer.from(bech32.fromWords(bech32Dec.words.slice(1))) + pkh = Buffer.from(bech32.fromWords(bech32Dec.words.slice(1))); } catch (err) { - throw new Error(`Unable to decode address: ${address}: ${err.message}`) + throw new Error(`Unable to decode address: ${address}: ${err.message}`); } } - return { versionByte, pkh } + return { versionByte, pkh }; } // Determine the address format (a.k.a. "version") depending on the // purpose of the dervation path. function getAddressFormat(path) { - if (path.length < 2) throw new Error('Path must be >1 index') - const purpose = path[0] - const coin = path[1] + if (path.length < 2) throw new Error('Path must be >1 index'); + const purpose = path[0]; + const coin = path[1]; if (purpose === PURPOSES.BTC_SEGWIT && coin === COINS.BTC) { - return FMT_SEGWIT_NATIVE_V0 + return FMT_SEGWIT_NATIVE_V0; } else if (purpose === PURPOSES.BTC_SEGWIT && coin === COINS.BTC_TESTNET) { - return FMT_SEGWIT_NATIVE_V0_TESTNET + return FMT_SEGWIT_NATIVE_V0_TESTNET; } else if (purpose === PURPOSES.BTC_WRAPPED_SEGWIT && coin === COINS.BTC) { - return FMT_SEGWIT_WRAPPED + return FMT_SEGWIT_WRAPPED; } else if ( purpose === PURPOSES.BTC_WRAPPED_SEGWIT && coin === COINS.BTC_TESTNET ) { - return FMT_SEGWIT_WRAPPED_TESTNET + return FMT_SEGWIT_WRAPPED_TESTNET; } else if (purpose === PURPOSES.BTC_LEGACY && coin === COINS.BTC) { - return FMT_LEGACY + return FMT_LEGACY; } else if (purpose === PURPOSES.BTC_LEGACY && coin === COINS.BTC_TESTNET) { - return FMT_LEGACY_TESTNET + return FMT_LEGACY_TESTNET; } else { throw new Error( 'Invalid Bitcoin path provided. Cannot determine address format.', - ) + ); } } @@ -466,15 +468,15 @@ function getAddressFormat(path) { function getScriptType(input) { switch (input.signerPath[0]) { case PURPOSES.BTC_LEGACY: - return BTC_SCRIPT_TYPE_P2PKH + return BTC_SCRIPT_TYPE_P2PKH; case PURPOSES.BTC_WRAPPED_SEGWIT: - return BTC_SCRIPT_TYPE_P2SH_P2WPKH + return BTC_SCRIPT_TYPE_P2SH_P2WPKH; case PURPOSES.BTC_SEGWIT: - return BTC_SCRIPT_TYPE_P2WPKH_V0 + return BTC_SCRIPT_TYPE_P2WPKH_V0; default: throw new Error( `Unsupported path purpose (${input.signerPath[0]}): cannot determine BTC script type.`, - ) + ); } } @@ -482,16 +484,16 @@ function getScriptType(input) { // This will return true if any input is p2sh(p2wpkh) or p2wpkh. // We determine the script type based on the derivation path. function needsWitness(inputs) { - let w = false + let w = false; inputs.forEach((input) => { if ( input.signerPath[0] === PURPOSES.BTC_SEGWIT || input.signerPath[0] === PURPOSES.BTC_WRAPPED_SEGWIT ) { - w = true + w = true; } - }) - return w + }); + return w; } export default { @@ -499,4 +501,4 @@ export default { serializeTx, getBitcoinAddress, getAddressFormat, -} +}; diff --git a/packages/sdk/src/calldata/evm.ts b/packages/sdk/src/calldata/evm.ts index a1861c72..4e8449b9 100644 --- a/packages/sdk/src/calldata/evm.ts +++ b/packages/sdk/src/calldata/evm.ts @@ -1,5 +1,5 @@ -import { Hash } from 'ox' -import { decodeAbiParameters, parseAbiParameters } from 'viem' +import { Hash } from 'ox'; +import { decodeAbiParameters, parseAbiParameters } from 'viem'; /** * Look through an ABI definition to see if there is a function that matches the signature provided. * @param sig a 0x-prefixed hex string containing 4 bytes of info @@ -11,21 +11,21 @@ export const parseSolidityJSONABI = ( sig: string, abi: any[], ): { def: EVMDef } => { - sig = coerceSig(sig) + sig = coerceSig(sig); // Find the first match in the ABI const match = abi .filter((item) => item.type === 'function') .find((item) => { - const def = parseDef(item) - const funcSig = getFuncSig(def.canonicalName) - return funcSig === sig - }) + const def = parseDef(item); + const funcSig = getFuncSig(def.canonicalName); + return funcSig === sig; + }); if (match) { - const def = parseDef(match).def - return { def } + const def = parseDef(match).def; + return { def }; } - throw new Error('Unable to find matching function in ABI') -} + throw new Error('Unable to find matching function in ABI'); +}; /** * Convert a canonical name into an ABI definition that can be included with calldata to a general @@ -36,28 +36,28 @@ export const parseSolidityJSONABI = ( * @public */ export const parseCanonicalName = (sig: string, name: string) => { - sig = coerceSig(sig) + sig = coerceSig(sig); if (sig !== getFuncSig(name)) { - throw new Error('Name does not match provided sig.') + throw new Error('Name does not match provided sig.'); } - const def = [] + const def = []; // Get the function name - const paramStart = name.indexOf('(') + const paramStart = name.indexOf('('); if (paramStart < 0) { - throw new Error(BAD_CANONICAL_ERR) + throw new Error(BAD_CANONICAL_ERR); } - def.push(name.slice(0, paramStart)) - name = name.slice(paramStart + 1) - let paramDef = [] + def.push(name.slice(0, paramStart)); + name = name.slice(paramStart + 1); + let paramDef = []; while (name.length > 1) { // scan until the terminating ')' - const typeStr = popTypeStrFromCanonical(name) - paramDef = paramDef.concat(parseTypeStr(typeStr)) - name = name.slice(typeStr.length + 1) + const typeStr = popTypeStrFromCanonical(name); + paramDef = paramDef.concat(parseTypeStr(typeStr)); + name = name.slice(typeStr.length + 1); } - const parsedParamDef = parseParamDef(paramDef) - return def.concat(parsedParamDef) -} + const parsedParamDef = parseParamDef(paramDef); + return def.concat(parsedParamDef); +}; /** * Pull out nested calldata which may correspond to nested ABI definitions. @@ -71,29 +71,29 @@ export const parseCanonicalName = (sig: string, name: string) => { * checked as a possible nested def */ export const getNestedCalldata = (def, calldata) => { - const possibleNestedDefs = [] + const possibleNestedDefs = []; // Skip past first item, which is the function name - const defParams = def.slice(1) - const strParams = getParamStrNames(defParams) - const hexStr = `0x${calldata.slice(4).toString('hex')}` as `0x${string}` + const defParams = def.slice(1); + const strParams = getParamStrNames(defParams); + const hexStr = `0x${calldata.slice(4).toString('hex')}` as `0x${string}`; // Convert strParams to viem's format const viemParams = strParams.map((type) => { // Convert tuple format from 'tuple(uint256,uint128)' to '(uint256,uint128)' if (type.startsWith('tuple(')) { - return type.replace('tuple', '') + return type.replace('tuple', ''); } - return type - }) + return type; + }); - const abiParams = parseAbiParameters(viemParams.join(',')) - const decoded = decodeAbiParameters(abiParams, hexStr) + const abiParams = parseAbiParameters(viemParams.join(',')); + const decoded = decodeAbiParameters(abiParams, hexStr); function couldBeNestedDef(x) { - return (x.length - 4) % 32 === 0 + return (x.length - 4) % 32 === 0; } decoded.forEach((paramData, i) => { if (isBytesType(defParams[i])) { - let nestedDefIsPossible = true + let nestedDefIsPossible = true; if (isBytesArrItem(defParams[i])) { // `bytes[]` type. Decode all underlying `bytes` items and // do size checks on those. @@ -109,19 +109,19 @@ export const getNestedCalldata = (def, calldata) => { typeof nestedParamDatum !== 'string' || !nestedParamDatum.startsWith('0x') ) { - nestedDefIsPossible = false - return + nestedDefIsPossible = false; + return; } const nestedParamDatumBuf = Buffer.from( nestedParamDatum.slice(2), 'hex', - ) + ); if (!couldBeNestedDef(nestedParamDatumBuf)) { - nestedDefIsPossible = false + nestedDefIsPossible = false; } - }) + }); } else { - nestedDefIsPossible = false + nestedDefIsPossible = false; } } else if (isBytesItem(defParams[i])) { // Regular `bytes` type - perform size check @@ -129,26 +129,26 @@ export const getNestedCalldata = (def, calldata) => { typeof paramData !== 'string' || !(paramData as string).startsWith('0x') ) { - nestedDefIsPossible = false + nestedDefIsPossible = false; } else { - const data = paramData as string - const paramDataBuf = Buffer.from(data.slice(2), 'hex') - nestedDefIsPossible = couldBeNestedDef(paramDataBuf) + const data = paramData as string; + const paramDataBuf = Buffer.from(data.slice(2), 'hex'); + nestedDefIsPossible = couldBeNestedDef(paramDataBuf); } } else { // Unknown `bytes` item type - nestedDefIsPossible = false + nestedDefIsPossible = false; } // If the data could contain a nested def (determined based on // data size of the item), add the paramData to the return array. - possibleNestedDefs.push(nestedDefIsPossible ? paramData : null) + possibleNestedDefs.push(nestedDefIsPossible ? paramData : null); } else { // No nested defs for non-bytes types - possibleNestedDefs.push(null) + possibleNestedDefs.push(null); } - }) - return possibleNestedDefs -} + }); + return possibleNestedDefs; +}; /** * If applicable, update decoder data to represent nested @@ -162,22 +162,22 @@ export const getNestedCalldata = (def, calldata) => { */ export const replaceNestedDefs = (def, nestedDefs) => { for (let i = 0; i < nestedDefs.length; i++) { - const isArrItem = isBytesArrItem(def[1 + i]) - const isItem = isBytesItem(def[1 + i]) + const isArrItem = isBytesArrItem(def[1 + i]); + const isItem = isBytesItem(def[1 + i]); if (nestedDefs[i] !== null && (isArrItem || isItem)) { // Update the def item type to indicate it will hold // one or more nested definitions - def[1 + i][1] = EVM_TYPES.indexOf('nestedDef') + def[1 + i][1] = EVM_TYPES.indexOf('nestedDef'); // Add nested def(s) in in an array. If this is an array // item it means the nestedDefs should already be in an // array. Otherwise we need to wrap the single nested // def in an array to keep the data type consistent. - const defs = isArrItem ? nestedDefs[i] : [nestedDefs[i]] - def[1 + i] = def[1 + i].concat([defs]) + const defs = isArrItem ? nestedDefs[i] : [nestedDefs[i]]; + def[1 + i] = def[1 + i].concat([defs]); } } - return def -} + return def; +}; /** * Convert a canonical name to a function selector (a.k.a. "sig") @@ -186,7 +186,7 @@ export const replaceNestedDefs = (def, nestedDefs) => { function getFuncSig(canonicalName: string): string { return `0x${Buffer.from(Hash.keccak256(Buffer.from(canonicalName))) .toString('hex') - .slice(0, 8)}` + .slice(0, 8)}`; } /** @@ -194,12 +194,12 @@ function getFuncSig(canonicalName: string): string { */ function coerceSig(sig: string): string { if (typeof sig !== 'string' || (sig.length !== 10 && sig.length !== 8)) { - throw new Error('`sig` must be a hex string with 4 bytes of data.') + throw new Error('`sig` must be a hex string with 4 bytes of data.'); } if (sig.length === 8) { - sig = `0x${sig}` + sig = `0x${sig}`; } - return sig + return sig; } /** @@ -211,30 +211,30 @@ function coerceSig(sig: string): string { * @internal */ function getParamStrNames(defParams) { - const strNames = [] + const strNames = []; for (let i = 0; i < defParams.length; i++) { - const param = defParams[i] - let s = EVM_TYPES[param[1]] + const param = defParams[i]; + let s = EVM_TYPES[param[1]]; if (param[2]) { - s = `${s}${param[2] * 8}` + s = `${s}${param[2] * 8}`; } if (param[3].length > 0) { param[3].forEach((d) => { if (param[3][d] === 0) { - s = `${s}[]` + s = `${s}[]`; } else { - s = `${s}[${param[3][d]}]` + s = `${s}[${param[3][d]}]`; } - }) + }); } if (param[4]) { // Tuple - get nested type names - const nested = getParamStrNames(param[4]) - s = `${s}(${nested.join(',')})` + const nested = getParamStrNames(param[4]); + s = `${s}(${nested.join(',')})`; } - strNames.push(s) + strNames.push(s); } - return strNames + return strNames; } /** @@ -244,15 +244,15 @@ function getParamStrNames(defParams) { */ function popTypeStrFromCanonical(subName: string): string { if (isTuple(subName)) { - return getTupleName(subName) + return getTupleName(subName); } else if (subName.indexOf(',') > -1) { // Normal non-tuple param - return subName.slice(0, subName.indexOf(',')) + return subName.slice(0, subName.indexOf(',')); } else if (subName.indexOf(')') > -1) { // Last non-tuple param in the name - return subName.slice(0, subName.indexOf(')')) + return subName.slice(0, subName.indexOf(')')); } - throw new Error(BAD_CANONICAL_ERR) + throw new Error(BAD_CANONICAL_ERR); } /** @@ -263,32 +263,32 @@ function popTypeStrFromCanonical(subName: string): string { function parseTypeStr(typeStr: string): any[] { // Non-tuples can be decoded without worrying about recursion if (!isTuple(typeStr)) { - return [parseBasicTypeStr(typeStr)] + return [parseBasicTypeStr(typeStr)]; } // Tuples may require recursion const param: EVMParamInfo = { szBytes: 0, typeIdx: EVM_TYPES.indexOf('tuple'), arraySzs: [], - } + }; // Get the full tuple param name and separate out the array stuff - let typeStrLessArr = getTupleName(typeStr, false) - const typeStrArr = typeStr.slice(typeStrLessArr.length) - param.arraySzs = getArraySzs(typeStrArr) + let typeStrLessArr = getTupleName(typeStr, false); + const typeStrArr = typeStr.slice(typeStrLessArr.length); + param.arraySzs = getArraySzs(typeStrArr); // Slice off the leading paren - typeStrLessArr = typeStrLessArr.slice(1) + typeStrLessArr = typeStrLessArr.slice(1); // Parse each nested param - let paramArr = [] + let paramArr = []; while (typeStrLessArr.length > 0) { - const subType = popTypeStrFromCanonical(typeStrLessArr) - typeStrLessArr = typeStrLessArr.slice(subType.length + 1) - paramArr = paramArr.concat(parseTypeStr(subType)) + const subType = popTypeStrFromCanonical(typeStrLessArr); + typeStrLessArr = typeStrLessArr.slice(subType.length + 1); + paramArr = paramArr.concat(parseTypeStr(subType)); } // There must be at least one sub-param in the tuple if (!paramArr.length) { - throw new Error(BAD_CANONICAL_ERR) + throw new Error(BAD_CANONICAL_ERR); } - return [param, paramArr] + return [param, paramArr]; } /** @@ -300,28 +300,28 @@ function parseBasicTypeStr(typeStr: string): EVMParamInfo { szBytes: 0, typeIdx: 0, arraySzs: [], - } - let found = false + }; + let found = false; EVM_TYPES.forEach((t, i) => { if (typeStr.indexOf(t) > -1 && !found) { - param.typeIdx = i - param.arraySzs = getArraySzs(typeStr) + param.typeIdx = i; + param.arraySzs = getArraySzs(typeStr); const arrStart = - param.arraySzs.length > 0 ? typeStr.indexOf('[') : typeStr.length - const typeStrNum = typeStr.slice(t.length, arrStart) + param.arraySzs.length > 0 ? typeStr.indexOf('[') : typeStr.length; + const typeStrNum = typeStr.slice(t.length, arrStart); if (Number.parseInt(typeStrNum)) { - param.szBytes = Number.parseInt(typeStrNum) / 8 + param.szBytes = Number.parseInt(typeStrNum) / 8; if (param.szBytes > 32) { - throw new Error(BAD_CANONICAL_ERR) + throw new Error(BAD_CANONICAL_ERR); } } - found = true + found = true; } - }) + }); if (!found) { - throw new Error(BAD_CANONICAL_ERR) + throw new Error(BAD_CANONICAL_ERR); } - return param + return param; } /** @@ -337,16 +337,16 @@ function parseDef( ): EVMDef { // Function name. Can be an empty string. if (!recursed) { - const nameStr = item.name || '' - def.push(nameStr) - canonicalName += nameStr + const nameStr = item.name || ''; + def.push(nameStr); + canonicalName += nameStr; } // Loop through params if (item.inputs) { - canonicalName += '(' + canonicalName += '('; item.inputs.forEach((input) => { // Convert the input to a flat param that we can serialize - const flatParam = getFlatParam(input) + const flatParam = getFlatParam(input); if (input.type.indexOf('tuple') > -1 && input.components) { // For tuples we need to recurse const recursed = parseDef( @@ -354,26 +354,26 @@ function parseDef( canonicalName, [], true, - ) - canonicalName = recursed.canonicalName + ); + canonicalName = recursed.canonicalName; // Add brackets if this is a tuple array and also add a comma - canonicalName += `${input.type.slice(5)},` - flatParam.push(recursed.def) + canonicalName += `${input.type.slice(5)},`; + flatParam.push(recursed.def); } else { - canonicalName += input.type - canonicalName += ',' + canonicalName += input.type; + canonicalName += ','; } - def.push(flatParam) - }) + def.push(flatParam); + }); // Take off the last comma. Note that we do not want to slice if the last param was a tuple, // since we want to keep that `)` if (canonicalName[canonicalName.length - 1] === ',') { - canonicalName = canonicalName.slice(0, canonicalName.length - 1) + canonicalName = canonicalName.slice(0, canonicalName.length - 1); } // Add the closing parens - canonicalName += ')' + canonicalName += ')'; } - return { def, canonicalName } + return { def, canonicalName }; } /** @@ -383,13 +383,13 @@ function parseDef( * @internal */ function parseParamDef(def: any[], prefix = ''): any[] { - const parsedDef = [] - let numTuples = 0 + const parsedDef = []; + let numTuples = 0; def.forEach((param, i) => { if (Array.isArray(param)) { // Arrays indicate nested params inside a tuple and always come after the initial tuple type // info. Recurse to parse nested tuple params and append them to the most recent. - parsedDef[parsedDef.length - 1].push(parseParamDef(param, `${i}-`)) + parsedDef[parsedDef.length - 1].push(parseParamDef(param, `${i}-`)); } else { // If this is not tuple info, add the flat param info to the def parsedDef.push([ @@ -397,14 +397,14 @@ function parseParamDef(def: any[], prefix = ''): any[] { param.typeIdx, param.szBytes, param.arraySzs, - ]) + ]); } // Tuple if (param.typeIdx === EVM_TYPES.indexOf('tuple')) { - numTuples += 1 + numTuples += 1; } - }) - return parsedDef + }); + return parsedDef; } /** @@ -413,14 +413,14 @@ function parseParamDef(def: any[], prefix = ''): any[] { */ function getFlatParam(input): any[] { if (!input.type) { - throw new Error('No type in input') + throw new Error('No type in input'); } - const param = [input.name] - const { typeIdx, szBytes, arraySzs } = getParamTypeInfo(input.type) - param.push(typeIdx) - param.push(szBytes) - param.push(arraySzs) - return param + const param = [input.name]; + const { typeIdx, szBytes, arraySzs } = getParamTypeInfo(input.type); + param.push(typeIdx); + param.push(szBytes); + param.push(arraySzs); + return param; } /** @@ -439,30 +439,30 @@ function getParamTypeInfo(type: string): EVMParamInfo { szBytes: 0, typeIdx: 0, arraySzs: [], - } - let baseType: string | undefined + }; + let baseType: string | undefined; EVM_TYPES.forEach((t, i) => { if (type.indexOf(t) > -1 && !baseType) { - baseType = t - param.typeIdx = i + baseType = t; + param.typeIdx = i; } - }) + }); // Get the array size, if any - param.arraySzs = getArraySzs(type) + param.arraySzs = getArraySzs(type); // Determine where to search for expanded size - const szIdx = param.arraySzs.length > 0 ? type.indexOf('[') : type.length + const szIdx = param.arraySzs.length > 0 ? type.indexOf('[') : type.length; if (['uint', 'int', 'bytes'].indexOf(baseType) > -1) { // If this can have a fixed size, capture that - const szBits = Number.parseInt(type.slice(baseType.length, szIdx)) || 0 + const szBits = Number.parseInt(type.slice(baseType.length, szIdx)) || 0; if (szBits > 256) { - throw new Error('Invalid param size') + throw new Error('Invalid param size'); } - param.szBytes = szBits / 8 + param.szBytes = szBits / 8; } else { // No fixed size in the type - param.szBytes = 0 + param.szBytes = 0; } - return param + return param; } /** @@ -472,73 +472,73 @@ function getParamTypeInfo(type: string): EVMParamInfo { */ function getArraySzs(type: string): number[] { if (typeof type !== 'string') { - throw new Error('Invalid type') + throw new Error('Invalid type'); } - const szs = [] - let t1 = type + const szs = []; + let t1 = type; while (t1.length > 0) { - const openIdx = t1.indexOf('[') + const openIdx = t1.indexOf('['); if (openIdx < 0) { - return szs + return szs; } - const t2 = t1.slice(openIdx) - const closeIdx = t2.indexOf(']') + const t2 = t1.slice(openIdx); + const closeIdx = t2.indexOf(']'); if (closeIdx < 0) { - throw new Error('Bad param type') + throw new Error('Bad param type'); } - const t3 = t2.slice(1, closeIdx) + const t3 = t2.slice(1, closeIdx); if (t3.length === 0) { // Variable size - szs.push(0) + szs.push(0); } else { // Fixed size - szs.push(Number.parseInt(t3)) + szs.push(Number.parseInt(t3)); } - t1 = t2.slice(closeIdx + 1) + t1 = t2.slice(closeIdx + 1); } - return szs + return szs; } /** @internal */ function getTupleName(name, withArr = true) { - let brackets = 0 - let addedFirstBracket = false + let brackets = 0; + let addedFirstBracket = false; for (let i = 0; i < name.length; i++) { if (name[i] === '(') { - brackets += 1 - addedFirstBracket = true + brackets += 1; + addedFirstBracket = true; } else if (name[i] === ')') { - brackets -= 1 + brackets -= 1; } let canBreak = - name[i + 1] === ',' || name[i + 1] === ')' || i === name.length - 1 + name[i + 1] === ',' || name[i + 1] === ')' || i === name.length - 1; if (!withArr && name[i + 1] === '[') { - canBreak = true + canBreak = true; } if (!brackets && addedFirstBracket && canBreak) { - return name.slice(0, i + 1) + return name.slice(0, i + 1); } } - throw new Error(BAD_CANONICAL_ERR) + throw new Error(BAD_CANONICAL_ERR); } /** @internal */ function isTuple(type: string): boolean { - return type[0] === '(' + return type[0] === '('; } /** @internal */ function isBytesType(param) { - return EVM_TYPES[param[1]] === 'bytes' + return EVM_TYPES[param[1]] === 'bytes'; } function isBytesItem(param) { - return isBytesType(param) && param[3].length === 0 + return isBytesType(param) && param[3].length === 0; } function isBytesArrItem(param) { - return isBytesType(param) && param[3].length === 1 && param[3][0] === 0 + return isBytesType(param) && param[3].length === 1 && param[3][0] === 0; } -const BAD_CANONICAL_ERR = 'Could not parse canonical function name.' +const BAD_CANONICAL_ERR = 'Could not parse canonical function name.'; const EVM_TYPES = [ null, 'address', @@ -549,15 +549,15 @@ const EVM_TYPES = [ 'string', 'tuple', 'nestedDef', -] +]; type EVMParamInfo = { - szBytes: number - typeIdx: number - arraySzs: number[] -} + szBytes: number; + typeIdx: number; + arraySzs: number[]; +}; type EVMDef = { - canonicalName: string - def: any -} + canonicalName: string; + def: any; +}; diff --git a/packages/sdk/src/calldata/index.ts b/packages/sdk/src/calldata/index.ts index bf7bb0f7..c5d46b8e 100644 --- a/packages/sdk/src/calldata/index.ts +++ b/packages/sdk/src/calldata/index.ts @@ -8,7 +8,7 @@ import { parseCanonicalName, parseSolidityJSONABI, replaceNestedDefs, -} from './evm' +} from './evm'; export const CALLDATA = { EVM: { @@ -22,4 +22,4 @@ export const CALLDATA = { replaceNestedDefs, }, }, -} +}; diff --git a/packages/sdk/src/client.ts b/packages/sdk/src/client.ts index 40999850..797a6159 100644 --- a/packages/sdk/src/client.ts +++ b/packages/sdk/src/client.ts @@ -1,10 +1,10 @@ -import { buildSaveClientFn } from './api/utilities' +import { buildSaveClientFn } from './api/utilities'; import { BASE_URL, DEFAULT_ACTIVE_WALLETS, EMPTY_WALLET_UID, getFwVersionConst, -} from './constants' +} from './constants'; import { addKvRecords, connect, @@ -15,10 +15,10 @@ import { pair, removeKvRecords, sign, -} from './functions/index' -import { buildRetryWrapper } from './shared/functions' -import { getPubKeyBytes } from './shared/utilities' -import { validateEphemeralPub } from './shared/validators' +} from './functions/index'; +import { buildRetryWrapper } from './shared/functions'; +import { getPubKeyBytes } from './shared/utilities'; +import { validateEphemeralPub } from './shared/validators'; import type { ActiveWallets, AddKvRecordsRequestParams, @@ -30,8 +30,8 @@ import type { RemoveKvRecordsRequestParams, SignData, SignRequestParams, -} from './types' -import { getP256KeyPair, getP256KeyPairFromPub, randomBytes } from './util' +} from './types'; +import { getP256KeyPair, getP256KeyPairFromPub, randomBytes } from './util'; /** * `Client` is a class-based interface for managing a Lattice device. @@ -46,31 +46,31 @@ import { getP256KeyPair, getP256KeyPairFromPub, randomBytes } from './util' */ export class Client { /** Is the Lattice paired with this Client. */ - public isPaired: boolean + public isPaired: boolean; /** The time to wait for a response before cancelling. */ - public timeout: number + public timeout: number; /** The base of the remote url to which the SDK sends requests. */ - public baseUrl: string + public baseUrl: string; /** @internal The `baseUrl` plus the `deviceId`. Set in {@link connect} when it completes successfully. */ - public url?: string + public url?: string; /** `name` is a human readable string associated with this app on the Lattice */ - private name: string - private key: KeyPair + private name: string; + private key: KeyPair; /**`privKey` is used to generate a keypair, which is used for maintaining an encrypted messaging channel with the target Lattice */ - private privKey: Buffer | string - private retryCount: number - private fwVersion?: Buffer - private skipRetryOnWrongWallet: boolean + private privKey: Buffer | string; + private retryCount: number; + private fwVersion?: Buffer; + private skipRetryOnWrongWallet: boolean; /** Temporary secret that is generated by the Lattice device */ - private _ephemeralPub!: KeyPair + private _ephemeralPub!: KeyPair; /** The ID of the connected Lattice */ - private deviceId?: string + private deviceId?: string; /** Information about the current wallet. Should be null unless we know a wallet is present */ - public activeWallets: ActiveWallets + public activeWallets: ActiveWallets; /** A wrapper function for handling retries and injecting the {@link Client} class */ - private retryWrapper: (fn: any, params?: any) => Promise + private retryWrapper: (fn: any, params?: any) => Promise; /** Function to set the stored client data */ - private setStoredClient?: (clientData: string | null) => Promise + private setStoredClient?: (clientData: string | null) => Promise; /** * @param params - Parameters are passed as an object. @@ -87,42 +87,42 @@ export class Client { setStoredClient, }: { /** The base URL of the signing server. */ - baseUrl?: string + baseUrl?: string; /** The name of the client. */ - name?: string + name?: string; /** The private key of the client.*/ - privKey?: Buffer | string + privKey?: Buffer | string; /** Number of times to retry a request if it fails. */ - retryCount?: number + retryCount?: number; /** The time to wait for a response before cancelling. */ - timeout?: number + timeout?: number; /** User can pass in previous state data to rehydrate connected session */ - stateData?: string + stateData?: string; /** If true we will not retry if we get a wrong wallet error code */ - skipRetryOnWrongWallet?: boolean + skipRetryOnWrongWallet?: boolean; /** The ID of the connected Lattice */ - deviceId?: string + deviceId?: string; /** Function to set the stored client data */ - setStoredClient?: (clientData: string | null) => Promise + setStoredClient?: (clientData: string | null) => Promise; }) { - this.name = name || 'Unknown' - this.baseUrl = baseUrl || BASE_URL - this.deviceId = deviceId - this.isPaired = false - this.activeWallets = DEFAULT_ACTIVE_WALLETS - this.timeout = timeout || 60000 - this.retryCount = retryCount || 3 - this.skipRetryOnWrongWallet = skipRetryOnWrongWallet || false - this.privKey = privKey || randomBytes(32) - this.key = getP256KeyPair(this.privKey) - this.retryWrapper = buildRetryWrapper(this, this.retryCount) + this.name = name || 'Unknown'; + this.baseUrl = baseUrl || BASE_URL; + this.deviceId = deviceId; + this.isPaired = false; + this.activeWallets = DEFAULT_ACTIVE_WALLETS; + this.timeout = timeout || 60000; + this.retryCount = retryCount || 3; + this.skipRetryOnWrongWallet = skipRetryOnWrongWallet || false; + this.privKey = privKey || randomBytes(32); + this.key = getP256KeyPair(this.privKey); + this.retryWrapper = buildRetryWrapper(this, this.retryCount); this.setStoredClient = setStoredClient ? buildSaveClientFn(setStoredClient) - : undefined + : undefined; /** The user may pass in state data to rehydrate a session that was previously cached */ if (stateData) { - this.unpackAndApplyStateData(stateData) + this.unpackAndApplyStateData(stateData); } } @@ -133,21 +133,21 @@ export class Client { * @returns Buffer */ public get publicKey() { - return getPubKeyBytes(this.key) + return getPubKeyBytes(this.key); } /** * Get the pairing name for this client instance */ public getAppName() { - return this.name + return this.name; } /** * Get the `deviceId` for this client instance */ public getDeviceId() { - return this.deviceId + return this.deviceId; } /** @@ -160,18 +160,18 @@ export class Client { // problems initializing AES if we don't force a 32 byte BE buffer. return Buffer.from( this.key.derive(this.ephemeralPub.getPublic()).toArray('be', 32), - ) + ); } /** @internal */ public get ephemeralPub() { - return this._ephemeralPub + return this._ephemeralPub; } /** @internal */ public set ephemeralPub(ephemeralPub: KeyPair) { - validateEphemeralPub(ephemeralPub) - this._ephemeralPub = ephemeralPub + validateEphemeralPub(ephemeralPub); + this._ephemeralPub = ephemeralPub; } /** @@ -180,7 +180,7 @@ export class Client { * @category Lattice */ public async connect(deviceId: string) { - return this.retryWrapper(connect, { id: deviceId }) + return this.retryWrapper(connect, { id: deviceId }); } /** @@ -190,7 +190,7 @@ export class Client { * @category Lattice */ public async pair(pairingSecret: string) { - return this.retryWrapper(pair, { pairingSecret }) + return this.retryWrapper(pair, { pairingSecret }); } /** @@ -203,7 +203,7 @@ export class Client { flag = 0, iterIdx = 0, }: GetAddressesRequestParams): Promise { - return this.retryWrapper(getAddresses, { startPath, n, flag, iterIdx }) + return this.retryWrapper(getAddresses, { startPath, n, flag, iterIdx }); } /** @@ -216,14 +216,14 @@ export class Client { cachedData, nextCode, }: SignRequestParams): Promise { - return this.retryWrapper(sign, { data, currency, cachedData, nextCode }) + return this.retryWrapper(sign, { data, currency, cachedData, nextCode }); } /** * Fetch the active wallet in the Lattice. */ public async fetchActiveWallet(): Promise { - return this.retryWrapper(fetchActiveWallet) + return this.retryWrapper(fetchActiveWallet); } /** @@ -235,7 +235,7 @@ export class Client { records, caseSensitive = false, }: AddKvRecordsRequestParams): Promise { - return this.retryWrapper(addKvRecords, { type, records, caseSensitive }) + return this.retryWrapper(addKvRecords, { type, records, caseSensitive }); } /** @@ -247,7 +247,7 @@ export class Client { n = 1, start = 0, }: GetKvRecordsRequestParams): Promise { - return this.retryWrapper(getKvRecords, { type, n, start }) + return this.retryWrapper(getKvRecords, { type, n, start }); } /** @@ -258,7 +258,7 @@ export class Client { type = 0, ids = [], }: RemoveKvRecordsRequestParams): Promise { - return this.retryWrapper(removeKvRecords, { type, ids }) + return this.retryWrapper(removeKvRecords, { type, ids }); } /** @@ -270,7 +270,7 @@ export class Client { public async fetchEncryptedData( params: FetchEncDataRequest, ): Promise { - return this.retryWrapper(fetchEncData, params) + return this.retryWrapper(fetchEncData, params); } /** Get the active wallet */ @@ -279,20 +279,20 @@ export class Client { this.activeWallets.external.uid && !EMPTY_WALLET_UID.equals(this.activeWallets.external.uid) ) { - return this.activeWallets.external + return this.activeWallets.external; } else if ( this.activeWallets.internal.uid && !EMPTY_WALLET_UID.equals(this.activeWallets.internal.uid) ) { - return this.activeWallets.internal + return this.activeWallets.internal; } else { - return undefined + return undefined; } } /** Check if the user has an active wallet */ public hasActiveWallet() { - return !!this.getActiveWallet() + return !!this.getActiveWallet(); } /** @@ -301,7 +301,7 @@ export class Client { * @internal */ public resetActiveWallets() { - this.activeWallets = DEFAULT_ACTIVE_WALLETS + this.activeWallets = DEFAULT_ACTIVE_WALLETS; } /** @@ -310,7 +310,7 @@ export class Client { * @internal */ public getStateData() { - return this.packStateData() + return this.packStateData(); } /** @@ -318,7 +318,7 @@ export class Client { * @internal */ public getFwConstants() { - return getFwVersionConst(this.fwVersion ?? Buffer.alloc(0)) + return getFwVersionConst(this.fwVersion ?? Buffer.alloc(0)); } /** @@ -326,18 +326,18 @@ export class Client { * @internal */ public getFwVersion(): { - fix: number - minor: number - major: number + fix: number; + minor: number; + major: number; } { if (this.fwVersion && this.fwVersion.length >= 3) { return { fix: this.fwVersion[0], minor: this.fwVersion[1], major: this.fwVersion[2], - } + }; } - return { fix: 0, minor: 0, major: 0 } + return { fix: 0, minor: 0, major: 0 }; } /** @@ -351,22 +351,22 @@ export class Client { fwVersion, activeWallets, }: { - deviceId?: string - ephemeralPub?: KeyPair - url?: string - isPaired?: boolean - fwVersion?: Buffer - activeWallets?: ActiveWallets + deviceId?: string; + ephemeralPub?: KeyPair; + url?: string; + isPaired?: boolean; + fwVersion?: Buffer; + activeWallets?: ActiveWallets; }) { - if (deviceId !== undefined) this.deviceId = deviceId - if (ephemeralPub !== undefined) this.ephemeralPub = ephemeralPub - if (url !== undefined) this.url = url - if (isPaired !== undefined) this.isPaired = isPaired - if (fwVersion !== undefined) this.fwVersion = fwVersion - if (activeWallets !== undefined) this.activeWallets = activeWallets + if (deviceId !== undefined) this.deviceId = deviceId; + if (ephemeralPub !== undefined) this.ephemeralPub = ephemeralPub; + if (url !== undefined) this.url = url; + if (isPaired !== undefined) this.isPaired = isPaired; + if (fwVersion !== undefined) this.fwVersion = fwVersion; + if (activeWallets !== undefined) this.activeWallets = activeWallets; if (this.setStoredClient) { - this.setStoredClient(this.getStateData()) + this.setStoredClient(this.getStateData()); } } @@ -398,11 +398,11 @@ export class Client { privKey: this.privKey.toString('hex'), retryCount: this.retryCount, timeout: this.timeout, - } - return JSON.stringify(data) + }; + return JSON.stringify(data); } catch (err) { - console.warn('Could not pack state data:', err) - return null + console.warn('Could not pack state data:', err); + return null; } } @@ -413,7 +413,7 @@ export class Client { */ private unpackAndApplyStateData(data: string) { try { - const unpacked = JSON.parse(data) + const unpacked = JSON.parse(data); // Attempt to parse the data const internalWallet = { uid: Buffer.from(unpacked.activeWallets.internal.uid, 'hex'), @@ -422,7 +422,7 @@ export class Client { : null, capabilities: unpacked.activeWallets.internal.capabilities, external: false, - } + }; const externalWallet = { uid: Buffer.from(unpacked.activeWallets.external.uid, 'hex'), name: unpacked.activeWallets.external.name @@ -430,26 +430,26 @@ export class Client { : null, capabilities: unpacked.activeWallets.external.capabilities, external: true, - } - const ephemeralPubBytes = Buffer.from(unpacked.ephemeralPub, 'hex') - const fwVersionBytes = Buffer.from(unpacked.fwVersion, 'hex') - const privKeyBytes = Buffer.from(unpacked.privKey, 'hex') + }; + const ephemeralPubBytes = Buffer.from(unpacked.ephemeralPub, 'hex'); + const fwVersionBytes = Buffer.from(unpacked.fwVersion, 'hex'); + const privKeyBytes = Buffer.from(unpacked.privKey, 'hex'); // Apply unpacked params - this.activeWallets.internal = internalWallet - this.activeWallets.external = externalWallet - this.ephemeralPub = getP256KeyPairFromPub(ephemeralPubBytes) - this.fwVersion = fwVersionBytes - this.deviceId = unpacked.deviceId - this.name = unpacked.name - this.baseUrl = unpacked.baseUrl - this.url = `${this.baseUrl}/${this.deviceId}` - this.privKey = privKeyBytes - this.key = getP256KeyPair(this.privKey) - this.retryCount = unpacked.retryCount - this.timeout = unpacked.timeout - this.retryWrapper = buildRetryWrapper(this, this.retryCount) + this.activeWallets.internal = internalWallet; + this.activeWallets.external = externalWallet; + this.ephemeralPub = getP256KeyPairFromPub(ephemeralPubBytes); + this.fwVersion = fwVersionBytes; + this.deviceId = unpacked.deviceId; + this.name = unpacked.name; + this.baseUrl = unpacked.baseUrl; + this.url = `${this.baseUrl}/${this.deviceId}`; + this.privKey = privKeyBytes; + this.key = getP256KeyPair(this.privKey); + this.retryCount = unpacked.retryCount; + this.timeout = unpacked.timeout; + this.retryWrapper = buildRetryWrapper(this, this.retryCount); } catch (err) { - console.warn('Could not apply state data:', err) + console.warn('Could not apply state data:', err); } } } diff --git a/packages/sdk/src/constants.ts b/packages/sdk/src/constants.ts index 6c48ea18..d29182d9 100644 --- a/packages/sdk/src/constants.ts +++ b/packages/sdk/src/constants.ts @@ -5,13 +5,13 @@ import { LatticeSignCurve, LatticeSignEncoding, LatticeSignHash, -} from './protocol/latticeConstants' +} from './protocol/latticeConstants'; import type { ActiveWallets, FirmwareArr, FirmwareConstants, WalletPath, -} from './types/index.js' +} from './types/index.js'; /** * Externally exported constants used for building requests @@ -70,7 +70,7 @@ export const EXTERNAL = { VOLUNTARY_EXIT: Buffer.from('04000000', 'hex'), }, }, -} as const +} as const; //=============================== // INTERNAL CONSTANTS @@ -79,14 +79,14 @@ export const EXTERNAL = { const addressSizes = { BTC: 20, // 20 byte pubkeyhash ETH: 20, // 20 byte address not including 0x prefix -} as const +} as const; /** @internal */ const CURRENCIES = { ETH: 'ETH', BTC: 'BTC', ETH_MSG: 'ETH_MSG', -} as const +} as const; /** @internal */ // THIS NEEDS TO BE A PROTOCOL CONSTANT TOO @@ -97,10 +97,10 @@ const signingSchema = { ETH_MSG: 3, EXTRA_DATA: 4, GENERAL_SIGNING: 5, -} as const +} as const; /** @internal */ -const HARDENED_OFFSET = 0x80000000 // Hardened offset +const HARDENED_OFFSET = 0x80000000; // Hardened offset /** @internal */ const BIP_CONSTANTS = { @@ -115,22 +115,22 @@ const BIP_CONSTANTS = { BTC: HARDENED_OFFSET, BTC_TESTNET: HARDENED_OFFSET + 1, }, -} as const +} as const; /** @internal For all HSM-bound requests */ -const REQUEST_TYPE_BYTE = 0x02 +const REQUEST_TYPE_BYTE = 0x02; /** @internal */ -const VERSION_BYTE = 1 +const VERSION_BYTE = 1; /** @internal ChainId value to signify larger chainID is in data buffer */ -const HANDLE_LARGER_CHAIN_ID = 255 +const HANDLE_LARGER_CHAIN_ID = 255; /** @internal Max number of bytes to contain larger chainID in data buffer */ -const MAX_CHAIN_ID_BYTES = 8 +const MAX_CHAIN_ID_BYTES = 8; /** @internal */ -const BASE_URL = 'https://signing.gridpl.us' +const BASE_URL = 'https://signing.gridpl.us'; /** @internal */ const EIP712_ABI_LATTICE_FW_TYPE_MAP = { @@ -235,7 +235,7 @@ const EIP712_ABI_LATTICE_FW_TYPE_MAP = { bytes32: 100, bytes: 101, string: 102, -} +}; /** @internal */ const ETH_ABI_LATTICE_FW_TYPE_MAP = { @@ -257,7 +257,7 @@ const ETH_ABI_LATTICE_FW_TYPE_MAP = { tuple15: 117, tuple16: 118, tuple17: 119, // Firmware currently cannot support tuples larger than this -} +}; /** @internal */ const ethMsgProtocol = { @@ -271,7 +271,7 @@ const ethMsgProtocol = { rawDataMaxLen: 1629, // Max size of raw data payload in bytes typeCodes: EIP712_ABI_LATTICE_FW_TYPE_MAP, // Enum indices of data types in Lattice firmware }, -} +}; /** @internal */ function getFwVersionConst(v: Buffer): FirmwareConstants { @@ -279,7 +279,7 @@ function getFwVersionConst(v: Buffer): FirmwareConstants { extraDataFrameSz: 0, extraDataMaxFrames: 0, genericSigning: {} as any, - } + }; function gte(v: Buffer, exp: FirmwareArr): boolean { // Note that `v` fields come in as [fix|minor|major] return ( @@ -287,10 +287,10 @@ function getFwVersionConst(v: Buffer): FirmwareConstants { (v[2] === exp[0] && v[1] > exp[1]) || (v[2] === exp[0] && v[1] === exp[1] && v[0] > exp[2]) || (v[2] === exp[0] && v[1] === exp[1] && v[0] === exp[2]) - ) + ); } // Very old legacy versions do not give a version number - const legacy = v.length === 0 + const legacy = v.length === 0; // BASE FIELDS //-------------------------------------- @@ -299,19 +299,19 @@ function getFwVersionConst(v: Buffer): FirmwareConstants { // are captured here if (!legacy && gte(v, [0, 10, 4])) { // >=0.10.3 - c.reqMaxDataSz = 1678 - c.ethMaxGasPrice = 20000000000000 // 20000 gwei - c.addrFlagsAllowed = true + c.reqMaxDataSz = 1678; + c.ethMaxGasPrice = 20000000000000; // 20000 gwei + c.addrFlagsAllowed = true; } else if (!legacy && gte(v, [0, 10, 0])) { // >=0.10.0 - c.reqMaxDataSz = 1678 - c.ethMaxGasPrice = 20000000000000 // 20000 gwei - c.addrFlagsAllowed = true + c.reqMaxDataSz = 1678; + c.ethMaxGasPrice = 20000000000000; // 20000 gwei + c.addrFlagsAllowed = true; } else { // Legacy or <0.10.0 - c.reqMaxDataSz = 1152 - c.ethMaxGasPrice = 500000000000 // 500 gwei - c.addrFlagsAllowed = false + c.reqMaxDataSz = 1152; + c.ethMaxGasPrice = 500000000000; // 500 gwei + c.addrFlagsAllowed = false; } // These transformations apply to all versions. The subtraction // of 128 bytes accounts for metadata and is for legacy reasons. @@ -319,11 +319,11 @@ function getFwVersionConst(v: Buffer): FirmwareConstants { // NOTE: Non-legacy ETH txs (e.g. EIP1559) will shrink // this number. // See `ETH_BASE_TX_MAX_DATA_SZ` and `ETH_MAX_BASE_MSG_SZ` in firmware - c.ethMaxDataSz = c.reqMaxDataSz - 128 - c.ethMaxMsgSz = c.ethMaxDataSz + c.ethMaxDataSz = c.reqMaxDataSz - 128; + c.ethMaxMsgSz = c.ethMaxDataSz; // Max number of params in an EIP712 type. This was added to firmware // to avoid blowing stack size. - c.eip712MaxTypeParams = 18 + c.eip712MaxTypeParams = 18; // ----- // EXTRA FIELDS ADDED IN LATER FIRMWARE VERSIONS @@ -333,23 +333,23 @@ function getFwVersionConst(v: Buffer): FirmwareConstants { // V0.10.4 introduced the ability to send signing requests over multiple // data frames (i.e. in multiple requests) if (!legacy && gte(v, [0, 10, 4])) { - c.extraDataFrameSz = 1500 // 1500 bytes per frame of extraData allowed - c.extraDataMaxFrames = 1 // 1 frame of extraData allowed + c.extraDataFrameSz = 1500; // 1500 bytes per frame of extraData allowed + c.extraDataMaxFrames = 1; // 1 frame of extraData allowed } // V0.10.5 added the ability to use flexible address path sizes, which // changes the `getAddress` API. It also added support for EIP712 if (!legacy && gte(v, [0, 10, 5])) { - c.varAddrPathSzAllowed = true - c.eip712Supported = true + c.varAddrPathSzAllowed = true; + c.eip712Supported = true; } // V0.10.8 allows a user to sign a prehashed transaction if the payload // is too big if (!legacy && gte(v, [0, 10, 8])) { - c.prehashAllowed = true + c.prehashAllowed = true; } // V0.10.10 allows a user to sign a prehashed ETH message if payload too big if (!legacy && gte(v, [0, 10, 10])) { - c.ethMsgPreHashAllowed = true + c.ethMsgPreHashAllowed = true; } // --- 0.11.X --- @@ -358,10 +358,10 @@ function getFwVersionConst(v: Buffer): FirmwareConstants { c.allowedEthTxTypes = [ 1, // eip2930 2, // eip1559 - ] + ]; // This version added extra data fields to the ETH tx - c.ethMaxDataSz -= 10 - c.ethMaxMsgSz = c.ethMaxDataSz + c.ethMaxDataSz -= 10; + c.ethMaxMsgSz = c.ethMaxDataSz; } // V0.11.2 changed how messages are displayed. For personal_sign messages // we now write the header (`Signer: `) into the main body of the screen. @@ -369,7 +369,7 @@ function getFwVersionConst(v: Buffer): FirmwareConstants { // EIP712 messages because in the latter case there is no header // Note that `` has max size of 62 bytes (`m/X/X/...`) if (!legacy && gte(v, [0, 11, 2])) { - c.personalSignHeaderSz = 72 + c.personalSignHeaderSz = 72; } // --- V0.12.X --- @@ -377,20 +377,20 @@ function getFwVersionConst(v: Buffer): FirmwareConstants { // records. For the purposes of this SDK, we only hook into one type of kv // file: address names. if (!legacy && gte(v, [0, 12, 0])) { - c.kvActionsAllowed = true - c.kvKeyMaxStrSz = 63 - c.kvValMaxStrSz = 63 - c.kvActionMaxNum = 10 - c.kvRemoveMaxNum = 100 + c.kvActionsAllowed = true; + c.kvKeyMaxStrSz = 63; + c.kvValMaxStrSz = 63; + c.kvActionMaxNum = 10; + c.kvRemoveMaxNum = 100; } // --- V0.13.X --- // V0.13.0 added native segwit addresses and fixed a bug in exporting // legacy bitcoin addresses if (!legacy && gte(v, [0, 13, 0])) { - c.allowBtcLegacyAndSegwitAddrs = true + c.allowBtcLegacyAndSegwitAddrs = true; // Random address to be used when trying to deploy a contract - c.contractDeployKey = '0x08002e0fec8e6acf00835f43c9764f7364fa3f42' + c.contractDeployKey = '0x08002e0fec8e6acf00835f43c9764f7364fa3f42'; } // --- V0.14.X --- @@ -398,26 +398,26 @@ function getFwVersionConst(v: Buffer): FirmwareConstants { // and generic signing functionality if (!legacy && gte(v, [0, 14, 0])) { // Size of `category` buffer. Inclusive of null terminator byte. - c.abiCategorySz = 32 - c.abiMaxRmv = 200 // Max number of ABI defs that can be removed with + c.abiCategorySz = 32; + c.abiMaxRmv = 200; // Max number of ABI defs that can be removed with // a single request // See `sizeof(GenericSigningRequest_t)` in firmware - c.genericSigning.baseReqSz = 1552 + c.genericSigning.baseReqSz = 1552; // See `GENERIC_SIGNING_BASE_MSG_SZ` in firmware - c.genericSigning.baseDataSz = 1519 - c.genericSigning.hashTypes = EXTERNAL.SIGNING.HASHES - c.genericSigning.curveTypes = EXTERNAL.SIGNING.CURVES + c.genericSigning.baseDataSz = 1519; + c.genericSigning.hashTypes = EXTERNAL.SIGNING.HASHES; + c.genericSigning.curveTypes = EXTERNAL.SIGNING.CURVES; c.genericSigning.encodingTypes = { NONE: EXTERNAL.SIGNING.ENCODINGS.NONE, SOLANA: EXTERNAL.SIGNING.ENCODINGS.SOLANA, - } + }; // Supported flags for `getAddresses` c.getAddressFlags = [ EXTERNAL.GET_ADDR_FLAGS.ED25519_PUB, EXTERNAL.GET_ADDR_FLAGS.SECP256K1_PUB, - ] + ]; // We updated the max number of params in EIP712 types - c.eip712MaxTypeParams = 36 + c.eip712MaxTypeParams = 36; } // DEPRECATED // V0.14.1 Added the Terra decoder @@ -428,26 +428,26 @@ function getFwVersionConst(v: Buffer): FirmwareConstants { // --- V0.15.X --- // V0.15.0 added an EVM decoder and removed the legacy ETH signing pathway if (!legacy && gte(v, [0, 15, 0])) { - c.genericSigning.encodingTypes.EVM = EXTERNAL.SIGNING.ENCODINGS.EVM + c.genericSigning.encodingTypes.EVM = EXTERNAL.SIGNING.ENCODINGS.EVM; // We now use the general signing data field as the base // Note that we have NOT removed the ETH_MSG type so we should // not change ethMaxMsgSz - c.ethMaxDataSz = 1550 - 31 + c.ethMaxDataSz = 1550 - 31; // Max buffer size for get/add decoder requests - c.maxDecoderBufSz = 1600 + c.maxDecoderBufSz = 1600; // Code used to write a calldata decoder c.genericSigning.calldataDecoding = { reserved: 2895728, maxSz: 1024, - } + }; } // --- V0.17.X --- // V0.17.0 added support for BLS12-381-G1 pubkeys and G2 sigs if (!legacy && gte(v, [0, 17, 0])) { - c.getAddressFlags.push(EXTERNAL.GET_ADDR_FLAGS.BLS12_381_G1_PUB) + c.getAddressFlags.push(EXTERNAL.GET_ADDR_FLAGS.BLS12_381_G1_PUB); c.genericSigning.encodingTypes.ETH_DEPOSIT = - EXTERNAL.SIGNING.ENCODINGS.ETH_DEPOSIT + EXTERNAL.SIGNING.ENCODINGS.ETH_DEPOSIT; } // --- V0.18.X --- @@ -458,22 +458,22 @@ function getFwVersionConst(v: Buffer): FirmwareConstants { ...c.genericSigning.encodingTypes, EIP7702_AUTH: EXTERNAL.SIGNING.ENCODINGS.EIP7702_AUTH, EIP7702_AUTH_LIST: EXTERNAL.SIGNING.ENCODINGS.EIP7702_AUTH_LIST, - } + }; } - return c + return c; } /** @internal */ // biome-ignore lint/suspicious/noControlCharactersInRegex: Intentional - matching ASCII range -const ASCII_REGEX = /^[\u0000-\u007F]+$/ +const ASCII_REGEX = /^[\u0000-\u007F]+$/; /** @internal */ const EXTERNAL_NETWORKS_BY_CHAIN_ID_URL = - 'https://gridplus.github.io/chains/chains.json' + 'https://gridplus.github.io/chains/chains.json'; /** @internal - Max number of addresses to fetch */ -const MAX_ADDR = 10 +const MAX_ADDR = 10; /** @internal */ const NETWORKS_BY_CHAIN_ID = { @@ -502,10 +502,10 @@ const NETWORKS_BY_CHAIN_ID = { baseUrl: 'https://api.snowtrace.io', apiRoute: 'api?module=contract&action=getabi', }, -} +}; /** @internal */ -export const EMPTY_WALLET_UID = Buffer.alloc(32) +export const EMPTY_WALLET_UID = Buffer.alloc(32); /** @internal */ export const DEFAULT_ACTIVE_WALLETS: ActiveWallets = { @@ -521,7 +521,7 @@ export const DEFAULT_ACTIVE_WALLETS: ActiveWallets = { name: Buffer.alloc(0), capabilities: 0, }, -} +}; /** @internal */ export const DEFAULT_ETH_DERIVATION: WalletPath = [ @@ -530,7 +530,7 @@ export const DEFAULT_ETH_DERIVATION: WalletPath = [ HARDENED_OFFSET, 0, 0, -] +]; /** @internal */ export const BTC_LEGACY_DERIVATION = [ @@ -539,7 +539,7 @@ export const BTC_LEGACY_DERIVATION = [ HARDENED_OFFSET, 0, 0, -] +]; /** @internal */ export const BTC_LEGACY_CHANGE_DERIVATION = [ @@ -548,7 +548,7 @@ export const BTC_LEGACY_CHANGE_DERIVATION = [ HARDENED_OFFSET, 0, 0, -] +]; /** @internal */ export const BTC_SEGWIT_DERIVATION = [ @@ -557,7 +557,7 @@ export const BTC_SEGWIT_DERIVATION = [ HARDENED_OFFSET, 0, 0, -] +]; /** @internal */ export const BTC_SEGWIT_CHANGE_DERIVATION = [ @@ -566,7 +566,7 @@ export const BTC_SEGWIT_CHANGE_DERIVATION = [ HARDENED_OFFSET, 1, 0, -] +]; /** @internal */ export const BTC_WRAPPED_SEGWIT_DERIVATION = [ @@ -575,7 +575,7 @@ export const BTC_WRAPPED_SEGWIT_DERIVATION = [ HARDENED_OFFSET, 0, 0, -] +]; /** @internal */ export const BTC_WRAPPED_SEGWIT_CHANGE_DERIVATION = [ @@ -584,7 +584,7 @@ export const BTC_WRAPPED_SEGWIT_CHANGE_DERIVATION = [ HARDENED_OFFSET, 0, 0, -] +]; /** * Derivation path for Bitcoin legacy xpub (BIP44). @@ -594,7 +594,7 @@ export const BTC_WRAPPED_SEGWIT_CHANGE_DERIVATION = [ * flag: LatticeGetAddressesFlag.secp256k1Xpub * }); */ -export const BTC_LEGACY_XPUB_PATH = "44'/0'/0'" +export const BTC_LEGACY_XPUB_PATH = "44'/0'/0'"; /** * Derivation path for Bitcoin wrapped segwit ypub (BIP49). @@ -604,7 +604,7 @@ export const BTC_LEGACY_XPUB_PATH = "44'/0'/0'" * flag: LatticeGetAddressesFlag.secp256k1Xpub * }); */ -export const BTC_WRAPPED_SEGWIT_YPUB_PATH = "49'/0'/0'" +export const BTC_WRAPPED_SEGWIT_YPUB_PATH = "49'/0'/0'"; /** * Derivation path for Bitcoin native segwit zpub (BIP84). @@ -614,7 +614,7 @@ export const BTC_WRAPPED_SEGWIT_YPUB_PATH = "49'/0'/0'" * flag: LatticeGetAddressesFlag.secp256k1Xpub * }); */ -export const BTC_SEGWIT_ZPUB_PATH = "84'/0'/0'" +export const BTC_SEGWIT_ZPUB_PATH = "84'/0'/0'"; /** @internal */ export const SOLANA_DERIVATION = [ @@ -622,7 +622,7 @@ export const SOLANA_DERIVATION = [ HARDENED_OFFSET + 501, HARDENED_OFFSET, HARDENED_OFFSET, -] +]; /** @internal */ export const LEDGER_LIVE_DERIVATION = [ @@ -631,7 +631,7 @@ export const LEDGER_LIVE_DERIVATION = [ HARDENED_OFFSET, 0, 0, -] +]; /** @internal */ export const LEDGER_LEGACY_DERIVATION = [ @@ -639,7 +639,7 @@ export const LEDGER_LEGACY_DERIVATION = [ HARDENED_OFFSET + 60, HARDENED_OFFSET, 0, -] +]; export { ASCII_REGEX, @@ -660,4 +660,4 @@ export { MAX_CHAIN_ID_BYTES, ETH_ABI_LATTICE_FW_TYPE_MAP, EXTERNAL as PUBLIC, -} +}; diff --git a/packages/sdk/src/ethereum.ts b/packages/sdk/src/ethereum.ts index 2599eaf8..15f2ae82 100644 --- a/packages/sdk/src/ethereum.ts +++ b/packages/sdk/src/ethereum.ts @@ -1,34 +1,34 @@ -import { RLP } from '@ethereumjs/rlp' -import { SignTypedDataVersion, TypedDataUtils } from '@metamask/eth-sig-util' +import { RLP } from '@ethereumjs/rlp'; +import { SignTypedDataVersion, TypedDataUtils } from '@metamask/eth-sig-util'; // Utils for Ethereum transactions. This is effecitvely a shim of ethereumjs-util, which // does not have browser (or, by proxy, React-Native) support. -import BN from 'bignumber.js' -import cbor from 'cbor' -import bdec from 'cbor-bigdecimal' -import { Hash } from 'ox' -import secp256k1 from 'secp256k1' +import BN from 'bignumber.js'; +import cbor from 'cbor'; +import bdec from 'cbor-bigdecimal'; +import { Hash } from 'ox'; +import secp256k1 from 'secp256k1'; import { type Hex, type TransactionSerializable, hexToNumber, serializeTransaction, -} from 'viem' +} from 'viem'; import { ASCII_REGEX, EXTERNAL, HANDLE_LARGER_CHAIN_ID, MAX_CHAIN_ID_BYTES, ethMsgProtocol, -} from './constants' -import { buildGenericSigningMsgRequest } from './genericSigning' -import { LatticeSignSchema } from './protocol' -import { type FlexibleTransaction, TransactionSchema } from './schemas' +} from './constants'; +import { buildGenericSigningMsgRequest } from './genericSigning'; +import { LatticeSignSchema } from './protocol'; +import { type FlexibleTransaction, TransactionSchema } from './schemas'; import { type FirmwareConstants, type SigningPath, TRANSACTION_TYPE, type TransactionRequest, -} from './types' +} from './types'; import { buildSignerPathBuf, convertRecoveryToV, @@ -36,42 +36,42 @@ import { fixLen, isAsciiStr, splitFrames, -} from './util' +} from './util'; -const { ecdsaRecover } = secp256k1 +const { ecdsaRecover } = secp256k1; -bdec(cbor) +bdec(cbor); const buildEthereumMsgRequest = (input) => { if (!input.payload || !input.protocol || !input.signerPath) throw new Error( 'You must provide `payload`, `signerPath`, and `protocol` arguments in the messsage request', - ) + ); if (input.signerPath.length > 5 || input.signerPath.length < 2) - throw new Error('Please provide a signer path with 2-5 indices') + throw new Error('Please provide a signer path with 2-5 indices'); const req = { schema: LatticeSignSchema.ethereumMsg, payload: null, input, // Save the input for later msg: null, // Save the buffered message for later - } + }; switch (input.protocol) { case 'signPersonal': - return buildPersonalSignRequest(req, input) + return buildPersonalSignRequest(req, input); case 'eip712': if (!input.fwConstants.eip712Supported) throw new Error( 'EIP712 is not supported by your Lattice firmware version. Please upgrade.', - ) - return buildEIP712Request(req, input) + ); + return buildEIP712Request(req, input); default: - throw new Error('Unsupported protocol') + throw new Error('Unsupported protocol'); } -} +}; const validateEthereumMsgResponse = (res, req) => { - const { signer, sig } = res - const { input, msg, prehash = null } = req + const { signer, sig } = res; + const { input, msg, prehash = null } = req; if (input.protocol === 'signPersonal') { // NOTE: We are currently hardcoding networkID=1 and useEIP155=false but these // may be configurable in future versions @@ -81,74 +81,74 @@ const validateEthereumMsgResponse = (res, req) => { Hash.keccak256( Buffer.concat([get_personal_sign_prefix(msg.length), msg]), ), - ) + ); // Get recovery param with a `v` value of [27,28] by setting `useEIP155=false` return addRecoveryParam(hash, sig, signer, { chainId: 1, useEIP155: false, - }) + }); } else if (input.protocol === 'eip712') { // Use the validationPayload that was created in buildEIP712Request // This payload has been parsed with forJSParser=true, converting all numbers // to the format that TypedDataUtils.eip712Hash expects - const rawPayloadForHashing = req.validationPayload || req.input.payload + const rawPayloadForHashing = req.validationPayload || req.input.payload; const payloadForHashing = req.validationPayload ? cloneTypedDataPayload(req.validationPayload) - : normalizeTypedDataForHashing(rawPayloadForHashing) + : normalizeTypedDataForHashing(rawPayloadForHashing); const encoded = TypedDataUtils.eip712Hash( payloadForHashing, SignTypedDataVersion.V4, - ) - const digest = prehash ? prehash : encoded + ); + const digest = prehash ? prehash : encoded; // Parse chainId - it could be a number, hex string, decimal string, or bigint let chainId = - input.payload.domain?.chainId || payloadForHashing.domain?.chainId + input.payload.domain?.chainId || payloadForHashing.domain?.chainId; if (typeof chainId === 'string') { chainId = chainId.startsWith('0x') ? Number.parseInt(chainId, 16) - : Number.parseInt(chainId, 10) + : Number.parseInt(chainId, 10); } else if (typeof chainId === 'bigint') { - chainId = Number(chainId) + chainId = Number(chainId); } // Get recovery param with a `v` value of [27,28] by setting `useEIP155=false` - return addRecoveryParam(digest, sig, signer, { chainId, useEIP155: false }) + return addRecoveryParam(digest, sig, signer, { chainId, useEIP155: false }); } else { - throw new Error('Unsupported protocol') + throw new Error('Unsupported protocol'); } -} +}; function normalizeTypedDataForHashing(value: any): any { if (value === null || value === undefined) { - return value + return value; } if (typeof value === 'string') { - const trimmed = value.trim() + const trimmed = value.trim(); if (/^0x[0-9a-fA-F]+$/.test(trimmed)) { try { - const asBigInt = BigInt(trimmed) + const asBigInt = BigInt(trimmed); if ( asBigInt <= BigInt(Number.MAX_SAFE_INTEGER) && asBigInt >= BigInt(Number.MIN_SAFE_INTEGER) ) { - return Number(asBigInt) + return Number(asBigInt); } - return asBigInt.toString(10) + return asBigInt.toString(10); } catch { - return trimmed + return trimmed; } } - return trimmed + return trimmed; } if (typeof value === 'bigint') { - const asNumber = Number(value) - return Number.isSafeInteger(asNumber) ? asNumber : value.toString(10) + const asNumber = Number(value); + return Number.isSafeInteger(asNumber) ? asNumber : value.toString(10); } if (BN.isBigNumber(value)) { - const asNumber = Number(value.toString(10)) - return Number.isSafeInteger(asNumber) ? asNumber : value.toString(10) + const asNumber = Number(value.toString(10)); + return Number.isSafeInteger(asNumber) ? asNumber : value.toString(10); } if ( @@ -159,53 +159,53 @@ function normalizeTypedDataForHashing(value: any): any { value.constructor.name === 'BN' && typeof value.toArray === 'function' ) { - const str = value.toString(10) - const asNumber = Number(str) - return Number.isSafeInteger(asNumber) ? asNumber : str + const str = value.toString(10); + const asNumber = Number(str); + return Number.isSafeInteger(asNumber) ? asNumber : str; } if (Buffer.isBuffer(value) || value instanceof Uint8Array) { - return `0x${Buffer.from(value).toString('hex')}` + return `0x${Buffer.from(value).toString('hex')}`; } if (Array.isArray(value)) { - return value.map((item) => normalizeTypedDataForHashing(item)) + return value.map((item) => normalizeTypedDataForHashing(item)); } if (typeof value === 'object') { - const normalized: Record = {} + const normalized: Record = {}; for (const [key, entry] of Object.entries(value)) { - normalized[key] = normalizeTypedDataForHashing(entry) + normalized[key] = normalizeTypedDataForHashing(entry); } - return normalized + return normalized; } - return value + return value; } function cloneTypedDataPayload(payload: T): T { - if (payload === undefined) return payload + if (payload === undefined) return payload; if (structuredCloneFn) { - return structuredCloneFn(payload) + return structuredCloneFn(payload); } - return basicTypedDataClone(payload) + return basicTypedDataClone(payload); } function basicTypedDataClone(value: T): T { if (value === null || typeof value !== 'object') { - return value + return value; } if (Buffer.isBuffer(value)) { - return Buffer.from(value) as T + return Buffer.from(value) as T; } if (value instanceof Uint8Array) { - return new Uint8Array(value) as T + return new Uint8Array(value) as T; } if (Array.isArray(value)) { - return value.map((item) => basicTypedDataClone(item)) as unknown as T + return value.map((item) => basicTypedDataClone(item)) as unknown as T; } if (BN.isBigNumber(value)) { - return new BN(value) as T + return new BN(value) as T; } if ( value && @@ -213,39 +213,39 @@ function basicTypedDataClone(value: T): T { (value as { constructor?: { name?: string } }).constructor?.name === 'BN' && typeof (value as { clone?: () => unknown }).clone === 'function' ) { - return (value as unknown as { clone: () => unknown }).clone() as T + return (value as unknown as { clone: () => unknown }).clone() as T; } if (value instanceof Date) { - return new Date(value.getTime()) as T + return new Date(value.getTime()) as T; } - const cloned: Record = {} + const cloned: Record = {}; for (const [key, entry] of Object.entries(value as Record)) { - cloned[key] = basicTypedDataClone(entry) + cloned[key] = basicTypedDataClone(entry); } - return cloned as T + return cloned as T; } -type StructuredCloneFn = (value: T, transfer?: unknown) => T +type StructuredCloneFn = (value: T, transfer?: unknown) => T; const structuredCloneFn: StructuredCloneFn | null = typeof globalThis !== 'undefined' && typeof (globalThis as { structuredClone?: unknown }).structuredClone === 'function' ? (globalThis as { structuredClone: StructuredCloneFn }).structuredClone - : null + : null; const buildEthereumTxRequest = (data) => { try { - let { chainId = 1 } = data - const { signerPath, eip155 = null, fwConstants, type = null } = data + let { chainId = 1 } = data; + const { signerPath, eip155 = null, fwConstants, type = null } = data; const { contractDeployKey, extraDataFrameSz, extraDataMaxFrames, prehashAllowed, - } = fwConstants - const EXTRA_DATA_ALLOWED = extraDataFrameSz > 0 && extraDataMaxFrames > 0 - const MAX_BASE_DATA_SZ = fwConstants.ethMaxDataSz - const VAR_PATH_SZ = fwConstants.varAddrPathSzAllowed + } = fwConstants; + const EXTRA_DATA_ALLOWED = extraDataFrameSz > 0 && extraDataMaxFrames > 0; + const MAX_BASE_DATA_SZ = fwConstants.ethMaxDataSz; + const VAR_PATH_SZ = fwConstants.varAddrPathSzAllowed; // Sanity checks: // There are a handful of named chains we allow the user to reference (`chainIds`) // Custom chainIDs should be either numerical or hex strings @@ -253,229 +253,229 @@ const buildEthereumTxRequest = (data) => { typeof chainId !== 'number' && isValidChainIdHexNumStr(chainId) === false ) { - chainId = chainIds[chainId] + chainId = chainIds[chainId]; } // If this was not a custom chainID and we cannot find the name of it, exit - if (!chainId) throw new Error('Unsupported chain ID or name') + if (!chainId) throw new Error('Unsupported chain ID or name'); // Sanity check on signePath - if (!signerPath) throw new Error('`signerPath` not provided') + if (!signerPath) throw new Error('`signerPath` not provided'); // Is this a contract deployment? if (data.to === null && !contractDeployKey) { throw new Error( 'Contract deployment not supported. Please update your Lattice firmware.', - ) + ); } - const isDeployment = data.to === null && contractDeployKey + const isDeployment = data.to === null && contractDeployKey; // We support eip1559 and eip2930 types (as well as legacy) const eip1559IsAllowed = fwConstants.allowedEthTxTypes && - fwConstants.allowedEthTxTypes.indexOf(2) > -1 + fwConstants.allowedEthTxTypes.indexOf(2) > -1; const eip2930IsAllowed = fwConstants.allowedEthTxTypes && - fwConstants.allowedEthTxTypes.indexOf(1) > -1 - const isEip1559 = eip1559IsAllowed && (type === 2 || type === 'eip1559') - const isEip2930 = eip2930IsAllowed && (type === 1 || type === 'eip2930') + fwConstants.allowedEthTxTypes.indexOf(1) > -1; + const isEip1559 = eip1559IsAllowed && (type === 2 || type === 'eip1559'); + const isEip2930 = eip2930IsAllowed && (type === 1 || type === 'eip2930'); if (type !== null && !isEip1559 && !isEip2930) - throw new Error('Unsupported Ethereum transaction type') + throw new Error('Unsupported Ethereum transaction type'); // Determine if we should use EIP155 given the chainID. // If we are explicitly told to use eip155, we will use it. Otherwise, // we will look up if the specified chainId is associated with a chain // that does not use EIP155 by default. Note that most do use EIP155. - let useEIP155 = chainUsesEIP155(chainId) + let useEIP155 = chainUsesEIP155(chainId); if (eip155 !== null && typeof eip155 === 'boolean') { - useEIP155 = eip155 + useEIP155 = eip155; } else if (isEip1559 || isEip2930) { // Newer transaction types do not use EIP155 since the chainId is serialized - useEIP155 = false + useEIP155 = false; } // Hack for metamask, which sends value=null for 0 ETH transactions - if (!data.value) data.value = 0 + if (!data.value) data.value = 0; //-------------- // 1. BUILD THE RAW TX FOR FUTURE RLP ENCODING //-------------- // Ensure all fields are 0x-prefixed hex strings - const rawTx = [] + const rawTx = []; // Build the transaction buffer array - const chainIdBytes = ensureHexBuffer(chainId) - const nonceBytes = ensureHexBuffer(data.nonce) - let gasPriceBytes: Buffer - const gasLimitBytes = ensureHexBuffer(data.gasLimit) + const chainIdBytes = ensureHexBuffer(chainId); + const nonceBytes = ensureHexBuffer(data.nonce); + let gasPriceBytes: Buffer; + const gasLimitBytes = ensureHexBuffer(data.gasLimit); // Handle contract deployment (indicated by `to` being `null`) // For contract deployment we write a 20-byte key to the request // buffer, which gets swapped for an empty buffer in firmware. - let toRlpElem: Buffer - let toBytes: Buffer + let toRlpElem: Buffer; + let toBytes: Buffer; if (isDeployment) { - toRlpElem = Buffer.alloc(0) - toBytes = ensureHexBuffer(contractDeployKey) + toRlpElem = Buffer.alloc(0); + toBytes = ensureHexBuffer(contractDeployKey); } else { - toRlpElem = ensureHexBuffer(data.to) - toBytes = ensureHexBuffer(data.to) + toRlpElem = ensureHexBuffer(data.to); + toBytes = ensureHexBuffer(data.to); } - const valueBytes = ensureHexBuffer(data.value) - const dataBytes = ensureHexBuffer(data.data) + const valueBytes = ensureHexBuffer(data.value); + const dataBytes = ensureHexBuffer(data.data); if (isEip1559 || isEip2930) { // EIP1559 and EIP2930 transactions have a chainID field - rawTx.push(chainIdBytes) + rawTx.push(chainIdBytes); } - rawTx.push(nonceBytes) - let maxPriorityFeePerGasBytes: Buffer - let maxFeePerGasBytes: Buffer + rawTx.push(nonceBytes); + let maxPriorityFeePerGasBytes: Buffer; + let maxFeePerGasBytes: Buffer; if (isEip1559) { if (!data.maxPriorityFeePerGas) throw new Error( 'EIP1559 transactions must include `maxPriorityFeePerGas`', - ) - maxPriorityFeePerGasBytes = ensureHexBuffer(data.maxPriorityFeePerGas) - rawTx.push(maxPriorityFeePerGasBytes) - maxFeePerGasBytes = ensureHexBuffer(data.maxFeePerGas) - rawTx.push(maxFeePerGasBytes) + ); + maxPriorityFeePerGasBytes = ensureHexBuffer(data.maxPriorityFeePerGas); + rawTx.push(maxPriorityFeePerGasBytes); + maxFeePerGasBytes = ensureHexBuffer(data.maxFeePerGas); + rawTx.push(maxFeePerGasBytes); // EIP1559 renamed "gasPrice" to "maxFeePerGas", but firmware still // uses `gasPrice` in the struct, so update that value here. - gasPriceBytes = maxFeePerGasBytes + gasPriceBytes = maxFeePerGasBytes; } else { // EIP1559 transactions do not have the gasPrice field - gasPriceBytes = ensureHexBuffer(data.gasPrice) - rawTx.push(gasPriceBytes) + gasPriceBytes = ensureHexBuffer(data.gasPrice); + rawTx.push(gasPriceBytes); } - rawTx.push(gasLimitBytes) - rawTx.push(toRlpElem) - rawTx.push(valueBytes) - rawTx.push(dataBytes) + rawTx.push(gasLimitBytes); + rawTx.push(toRlpElem); + rawTx.push(valueBytes); + rawTx.push(dataBytes); // We do not currently support accessList in firmware so we need to prehash if // the list is non-null - let PREHASH_FROM_ACCESS_LIST = false + let PREHASH_FROM_ACCESS_LIST = false; if (isEip1559 || isEip2930) { - const accessList = [] + const accessList = []; if (Array.isArray(data.accessList)) { data.accessList.forEach((listItem) => { - const keys = [] + const keys = []; listItem.storageKeys.forEach((key) => { - keys.push(ensureHexBuffer(key)) - }) - accessList.push([ensureHexBuffer(listItem.address), keys]) - PREHASH_FROM_ACCESS_LIST = true - }) + keys.push(ensureHexBuffer(key)); + }); + accessList.push([ensureHexBuffer(listItem.address), keys]); + PREHASH_FROM_ACCESS_LIST = true; + }); } - rawTx.push(accessList) + rawTx.push(accessList); } else if (useEIP155 === true) { // Add empty v,r,s values for EIP155 legacy transactions - rawTx.push(chainIdBytes) // v (which is the same as chainId in EIP155 txs) - rawTx.push(ensureHexBuffer(null)) // r - rawTx.push(ensureHexBuffer(null)) // s + rawTx.push(chainIdBytes); // v (which is the same as chainId in EIP155 txs) + rawTx.push(ensureHexBuffer(null)); // r + rawTx.push(ensureHexBuffer(null)); // s } //-------------- // 2. BUILD THE LATTICE REQUEST PAYLOAD //-------------- - const ETH_TX_NON_DATA_SZ = 122 // Accounts for metadata and non-data params - const txReqPayload = Buffer.alloc(MAX_BASE_DATA_SZ + ETH_TX_NON_DATA_SZ) - let off = 0 + const ETH_TX_NON_DATA_SZ = 122; // Accounts for metadata and non-data params + const txReqPayload = Buffer.alloc(MAX_BASE_DATA_SZ + ETH_TX_NON_DATA_SZ); + let off = 0; // 1. EIP155 switch and chainID //------------------ - txReqPayload.writeUInt8(Number(useEIP155), off) - off++ + txReqPayload.writeUInt8(Number(useEIP155), off); + off++; // NOTE: Originally we designed for a 1-byte chainID, but modern rollup chains use much larger // chainID values. To account for these, we will put the chainID into the `data` buffer if it // is >=255. Values up to UINT64_MAX will be allowed. - let chainIdBuf: Buffer - let chainIdBufSz = 0 + let chainIdBuf: Buffer; + let chainIdBufSz = 0; if (useChainIdBuffer(chainId) === true) { - chainIdBuf = getChainIdBuf(chainId) - chainIdBufSz = chainIdBuf.length + chainIdBuf = getChainIdBuf(chainId); + chainIdBufSz = chainIdBuf.length; if (chainIdBufSz > MAX_CHAIN_ID_BYTES) - throw new Error('ChainID provided is too large.') + throw new Error('ChainID provided is too large.'); // Signal to Lattice firmware that it needs to read the chainId from the tx.data buffer - txReqPayload.writeUInt8(HANDLE_LARGER_CHAIN_ID, off) - off++ + txReqPayload.writeUInt8(HANDLE_LARGER_CHAIN_ID, off); + off++; } else { // For chainIDs <255, write it to the chainId u8 slot in the main tx buffer - chainIdBuf = ensureHexBuffer(chainId) - if (chainIdBuf.length !== 1) throw new Error('Error parsing chainID') - chainIdBuf.copy(txReqPayload, off) - off += chainIdBuf.length + chainIdBuf = ensureHexBuffer(chainId); + if (chainIdBuf.length !== 1) throw new Error('Error parsing chainID'); + chainIdBuf.copy(txReqPayload, off); + off += chainIdBuf.length; } // 2. Signer Path //------------------ - const signerPathBuf = buildSignerPathBuf(signerPath, VAR_PATH_SZ) - signerPathBuf.copy(txReqPayload, off) - off += signerPathBuf.length + const signerPathBuf = buildSignerPathBuf(signerPath, VAR_PATH_SZ); + signerPathBuf.copy(txReqPayload, off); + off += signerPathBuf.length; // 3. ETH TX request data //------------------ - if (nonceBytes.length > 4) throw new Error('Nonce too large') - nonceBytes.copy(txReqPayload, off + (4 - nonceBytes.length)) - off += 4 - if (gasPriceBytes.length > 8) throw new Error('Gas price too large') - gasPriceBytes.copy(txReqPayload, off + (8 - gasPriceBytes.length)) - off += 8 - if (gasLimitBytes.length > 4) throw new Error('Gas limit too large') - gasLimitBytes.copy(txReqPayload, off + (4 - gasLimitBytes.length)) - off += 4 - if (toBytes.length !== 20) throw new Error('Invalid `to` address') - toBytes.copy(txReqPayload, off) - off += 20 - if (valueBytes.length > 32) throw new Error('Value too large') - valueBytes.copy(txReqPayload, off + (32 - valueBytes.length)) - off += 32 + if (nonceBytes.length > 4) throw new Error('Nonce too large'); + nonceBytes.copy(txReqPayload, off + (4 - nonceBytes.length)); + off += 4; + if (gasPriceBytes.length > 8) throw new Error('Gas price too large'); + gasPriceBytes.copy(txReqPayload, off + (8 - gasPriceBytes.length)); + off += 8; + if (gasLimitBytes.length > 4) throw new Error('Gas limit too large'); + gasLimitBytes.copy(txReqPayload, off + (4 - gasLimitBytes.length)); + off += 4; + if (toBytes.length !== 20) throw new Error('Invalid `to` address'); + toBytes.copy(txReqPayload, off); + off += 20; + if (valueBytes.length > 32) throw new Error('Value too large'); + valueBytes.copy(txReqPayload, off + (32 - valueBytes.length)); + off += 32; // Extra Tx data comes before `data` in the struct - let PREHASH_UNSUPPORTED = false + let PREHASH_UNSUPPORTED = false; if (fwConstants.allowedEthTxTypes) { // Some types may not be supported by firmware, so we will need to prehash if (PREHASH_FROM_ACCESS_LIST) { - PREHASH_UNSUPPORTED = true + PREHASH_UNSUPPORTED = true; } - txReqPayload.writeUInt8(PREHASH_UNSUPPORTED ? 1 : 0, off) - off += 1 + txReqPayload.writeUInt8(PREHASH_UNSUPPORTED ? 1 : 0, off); + off += 1; // EIP1559 & EIP2930 struct version if (isEip1559) { - txReqPayload.writeUInt8(2, off) - off += 1 // Eip1559 type enum value + txReqPayload.writeUInt8(2, off); + off += 1; // Eip1559 type enum value if (maxPriorityFeePerGasBytes.length > 8) - throw new Error('maxPriorityFeePerGasBytes too large') + throw new Error('maxPriorityFeePerGasBytes too large'); maxPriorityFeePerGasBytes.copy( txReqPayload, off + (8 - maxPriorityFeePerGasBytes.length), - ) - off += 8 // Skip EIP1559 params + ); + off += 8; // Skip EIP1559 params } else if (isEip2930) { - txReqPayload.writeUInt8(1, off) - off += 1 // Eip2930 type enum value - off += 8 // Skip EIP1559 params + txReqPayload.writeUInt8(1, off); + off += 1; // Eip2930 type enum value + off += 8; // Skip EIP1559 params } else { - off += 9 // Skip EIP1559 and EIP2930 params + off += 9; // Skip EIP1559 and EIP2930 params } } // Flow data into extraData requests, which will follow-up transaction requests, if supported/applicable - const extraDataPayloads = [] - let prehash = null + const extraDataPayloads = []; + let prehash = null; // Create the buffer, prefix with chainId (if needed) and add data slice - const dataSz = dataBytes.length || 0 - const chainIdExtraSz = chainIdBufSz > 0 ? chainIdBufSz + 1 : 0 - const dataToCopy = Buffer.alloc(dataSz + chainIdExtraSz) + const dataSz = dataBytes.length || 0; + const chainIdExtraSz = chainIdBufSz > 0 ? chainIdBufSz + 1 : 0; + const dataToCopy = Buffer.alloc(dataSz + chainIdExtraSz); if (chainIdExtraSz > 0) { - dataToCopy.writeUInt8(chainIdBufSz, 0) - chainIdBuf.copy(dataToCopy, 1) + dataToCopy.writeUInt8(chainIdBufSz, 0); + chainIdBuf.copy(dataToCopy, 1); } - dataBytes.copy(dataToCopy, chainIdExtraSz) + dataBytes.copy(dataToCopy, chainIdExtraSz); if (dataSz > MAX_BASE_DATA_SZ) { // Determine sizes and run through sanity checks - const totalSz = dataSz + chainIdExtraSz + const totalSz = dataSz + chainIdExtraSz; const maxSzAllowed = - MAX_BASE_DATA_SZ + extraDataMaxFrames * extraDataFrameSz + MAX_BASE_DATA_SZ + extraDataMaxFrames * extraDataFrameSz; if (prehashAllowed && totalSz > maxSzAllowed) { // If this payload is too large to send, but the Lattice allows a prehashed message, do that prehash = Buffer.from( Hash.keccak256(get_rlp_encoded_preimage(rawTx, type)), - ) + ); } else { if ( !EXTRA_DATA_ALLOWED || @@ -483,44 +483,44 @@ const buildEthereumTxRequest = (data) => { ) throw new Error( `Data field too large (got ${dataBytes.length}; must be <=${maxSzAllowed - chainIdExtraSz} bytes)`, - ) + ); // Split overflow data into extraData frames const frames = splitFrames( dataToCopy.slice(MAX_BASE_DATA_SZ), extraDataFrameSz, - ) + ); frames.forEach((frame) => { - const szLE = Buffer.alloc(4) - szLE.writeUInt32LE(frame.length, 0) - extraDataPayloads.push(Buffer.concat([szLE, frame])) - }) + const szLE = Buffer.alloc(4); + szLE.writeUInt32LE(frame.length, 0); + extraDataPayloads.push(Buffer.concat([szLE, frame])); + }); } } else if (PREHASH_UNSUPPORTED) { // If something is unsupported in firmware but we want to allow such transactions, // we prehash the message here. prehash = Buffer.from( Hash.keccak256(get_rlp_encoded_preimage(rawTx, type)), - ) + ); } // Write the data size (does *NOT* include the chainId buffer, if that exists) - txReqPayload.writeUInt16BE(dataBytes.length, off) - off += 2 + txReqPayload.writeUInt16BE(dataBytes.length, off); + off += 2; // Copy in the chainId buffer if needed if (chainIdBufSz > 0) { - txReqPayload.writeUInt8(chainIdBufSz, off) - off++ - chainIdBuf.copy(txReqPayload, off) - off += chainIdBufSz + txReqPayload.writeUInt8(chainIdBufSz, off); + off++; + chainIdBuf.copy(txReqPayload, off); + off += chainIdBufSz; } // Copy the first slice of the data itself. If this payload has been pre-hashed, include it // in the `data` field. This will result in a different Lattice screen being drawn. if (prehash) { - prehash.copy(txReqPayload, off) - off += MAX_BASE_DATA_SZ + prehash.copy(txReqPayload, off); + off += MAX_BASE_DATA_SZ; } else { - dataBytes.slice(0, MAX_BASE_DATA_SZ).copy(txReqPayload, off) - off += MAX_BASE_DATA_SZ + dataBytes.slice(0, MAX_BASE_DATA_SZ).copy(txReqPayload, off); + off += MAX_BASE_DATA_SZ; } return { rawTx, @@ -531,20 +531,20 @@ const buildEthereumTxRequest = (data) => { chainId, useEIP155, signerPath, - } + }; } catch (err) { - return { err: err.message } + return { err: err.message }; } -} +}; // From ethereumjs-util function stripZeros(a) { - let first = a[0] + let first = a[0]; while (a.length > 0 && first.toString() === '0') { - a = a.slice(1) - first = a[0] + a = a.slice(1); + first = a[0]; } - return a + return a; } // Given a 64-byte signature [r,s] we need to figure out the v value @@ -553,77 +553,77 @@ const buildEthRawTx = (tx, sig, address) => { // RLP-encode the data we sent to the lattice const hash = Buffer.from( Hash.keccak256(get_rlp_encoded_preimage(tx.rawTx, tx.type)), - ) - const newSig = addRecoveryParam(hash, sig, address, tx) + ); + const newSig = addRecoveryParam(hash, sig, address, tx); // Use the signature to generate a new raw transaction payload // Strip the last 3 items and replace them with signature components - const newRawTx = tx.useEIP155 ? tx.rawTx.slice(0, -3) : tx.rawTx - newRawTx.push(newSig.v) + const newRawTx = tx.useEIP155 ? tx.rawTx.slice(0, -3) : tx.rawTx; + newRawTx.push(newSig.v); // Per `ethereumjs-tx`, RLP encoding should include signature components w/ stripped zeros // See: https://github.com/ethereumjs/ethereumjs-tx/blob/master/src/transaction.ts#L187 - newRawTx.push(stripZeros(newSig.r)) - newRawTx.push(stripZeros(newSig.s)) - const rlpEncoded = Buffer.from(RLP.encode(newRawTx)) + newRawTx.push(stripZeros(newSig.r)); + newRawTx.push(stripZeros(newSig.s)); + const rlpEncoded = Buffer.from(RLP.encode(newRawTx)); const rlpEncodedWithSig = tx.type ? Buffer.concat([Buffer.from([tx.type]), rlpEncoded]) - : rlpEncoded + : rlpEncoded; if ( tx.type === TRANSACTION_TYPE.EIP7702_AUTH || tx.type === TRANSACTION_TYPE.EIP7702_AUTH_LIST ) { // For EIP-7702 transactions, we return just the hex string - return rlpEncodedWithSig.toString('hex') + return rlpEncodedWithSig.toString('hex'); } - return { rawTx: rlpEncodedWithSig.toString('hex'), sigWithV: newSig } -} + return { rawTx: rlpEncodedWithSig.toString('hex'), sigWithV: newSig }; +}; // Attach a recovery parameter to a signature by brute-forcing ECRecover export function addRecoveryParam(hashBuf, sig, address, txData = {}) { try { // Rebuild the keccak256 hash here so we can `ecrecover` - const hash = new Uint8Array(hashBuf) + const hash = new Uint8Array(hashBuf); const expectedAddrBuf = Buffer.isBuffer(address) ? address - : ensureHexBuffer(address, false) + : ensureHexBuffer(address, false); if (expectedAddrBuf.length !== 20) - throw new Error('Invalid signer address provided.') - let v = 0 + throw new Error('Invalid signer address provided.'); + let v = 0; // Fix signature componenet lengths to 32 bytes each - const r = fixLen(sig.r, 32) - sig.r = r - const s = fixLen(sig.s, 32) - sig.s = s + const r = fixLen(sig.r, 32); + sig.r = r; + const s = fixLen(sig.s, 32); + sig.s = s; // Calculate the recovery param - const rs = new Uint8Array(Buffer.concat([r, s])) - let pubkey = ecdsaRecover(rs, v, hash, false).slice(1) - const expectedAddrHex = expectedAddrBuf.toString('hex') - const recoveredAddrs: string[] = [] + const rs = new Uint8Array(Buffer.concat([r, s])); + let pubkey = ecdsaRecover(rs, v, hash, false).slice(1); + const expectedAddrHex = expectedAddrBuf.toString('hex'); + const recoveredAddrs: string[] = []; // If the first `v` value is a match, return the sig! - let recovered = pubToAddrStr(pubkey) - recoveredAddrs.push(recovered) + let recovered = pubToAddrStr(pubkey); + recoveredAddrs.push(recovered); if (recovered === expectedAddrHex) { - sig.v = getRecoveryParam(v, txData) - return sig + sig.v = getRecoveryParam(v, txData); + return sig; } // Otherwise, try the other `v` value - v = 1 - pubkey = ecdsaRecover(rs, v, hash, false).slice(1) - recovered = pubToAddrStr(pubkey) - recoveredAddrs.push(recovered) + v = 1; + pubkey = ecdsaRecover(rs, v, hash, false).slice(1); + recovered = pubToAddrStr(pubkey); + recoveredAddrs.push(recovered); if (recovered === expectedAddrHex) { - sig.v = getRecoveryParam(v, txData) - return sig + sig.v = getRecoveryParam(v, txData); + return sig; } else { // If neither is a match, we should return an error throw new Error( `Invalid Ethereum signature returned. expected=${expectedAddrHex}, recovered=${recoveredAddrs.join(',')}`, - ) + ); } } catch (err) { - if (err instanceof Error) throw err - throw new Error(String(err)) + if (err instanceof Error) throw err; + throw new Error(String(err)); } } @@ -636,40 +636,40 @@ export function normalizeLatticeSignature( originalTx: TransactionSerializable, ) { // Convert Buffer v value to number - let vValue: number + let vValue: number; if (Buffer.isBuffer(latticeResult.sig.v)) { // Read entire buffer as big-endian integer // Single byte: = 0x26 = 38 // Multi-byte: = 0x0135 = 309 (Polygon chainId 137 with EIP-155) - const bufferLength = latticeResult.sig.v.length + const bufferLength = latticeResult.sig.v.length; if (bufferLength === 1) { - vValue = latticeResult.sig.v.readUInt8(0) + vValue = latticeResult.sig.v.readUInt8(0); } else if (bufferLength === 2) { - vValue = latticeResult.sig.v.readUInt16BE(0) + vValue = latticeResult.sig.v.readUInt16BE(0); } else if (bufferLength <= 4) { - vValue = latticeResult.sig.v.readUInt32BE(Math.max(0, 4 - bufferLength)) + vValue = latticeResult.sig.v.readUInt32BE(Math.max(0, 4 - bufferLength)); } else { // For very large buffers, read as hex and convert - vValue = Number.parseInt(latticeResult.sig.v.toString('hex'), 16) + vValue = Number.parseInt(latticeResult.sig.v.toString('hex'), 16); } } else if (typeof latticeResult.sig.v === 'number') { - vValue = latticeResult.sig.v + vValue = latticeResult.sig.v; } else if (typeof latticeResult.sig.v === 'string') { - vValue = hexToNumber(latticeResult.sig.v as Hex) + vValue = hexToNumber(latticeResult.sig.v as Hex); } else { - vValue = Number(latticeResult.sig.v) + vValue = Number(latticeResult.sig.v); } // For typed transactions (non-legacy), viem expects yParity instead of v if (originalTx.type !== 'legacy') { // Convert v to yParity (v is either 27/28 or 0/1) - const yParity = vValue >= 27 ? vValue - 27 : vValue + const yParity = vValue >= 27 ? vValue - 27 : vValue; return { ...originalTx, r: latticeResult.sig.r as Hex, s: latticeResult.sig.s as Hex, yParity, - } + }; } else { // Legacy transactions use v directly as BigInt const result = { @@ -677,28 +677,28 @@ export function normalizeLatticeSignature( r: latticeResult.sig.r as Hex, s: latticeResult.sig.s as Hex, v: BigInt(vValue), - } + }; // For legacy transactions, remove the type field to ensure Viem treats it as legacy - result.type = undefined + result.type = undefined; // Also remove any typed transaction fields that might confuse viem - result.maxFeePerGas = undefined - result.maxPriorityFeePerGas = undefined - result.accessList = undefined - result.authorizationList = undefined + result.maxFeePerGas = undefined; + result.maxPriorityFeePerGas = undefined; + result.accessList = undefined; + result.authorizationList = undefined; - return result + return result; } } // Convert an RLP-serialized transaction (plus signature) into a transaction hash const hashTransaction = (serializedTx) => - Hash.keccak256(Buffer.from(serializedTx, 'hex')) + Hash.keccak256(Buffer.from(serializedTx, 'hex')); // Returns address string given public key buffer function pubToAddrStr(pub) { - return Buffer.from(Hash.keccak256(pub)).slice(-20).toString('hex') + return Buffer.from(Hash.keccak256(pub)).slice(-20).toString('hex'); } // Convert a 0/1 `v` into a recovery param: @@ -706,15 +706,15 @@ function pubToAddrStr(pub) { // * For EIP155 transactions, return `(CHAIN_ID*2) + 35 + v` // Uses the consolidated convertRecoveryToV function from util.ts function getRecoveryParam(v, txData: any = {}) { - const result = convertRecoveryToV(v, txData) + const result = convertRecoveryToV(v, txData); // convertRecoveryToV returns Buffer for typed transactions, BN for legacy // Always return Buffer to maintain compatibility with existing code if (Buffer.isBuffer(result)) { - return result + return result; } else { // Convert BN result to hex buffer - return ensureHexBuffer(`0x${result.toString(16)}`) + return ensureHexBuffer(`0x${result.toString(16)}`); } } @@ -724,31 +724,31 @@ const chainIds = { rinkeby: 4, kovan: 42, goerli: 5, -} +}; // Get a buffer containing the chainId value. // Returns a 1, 2, 4, or 8 byte buffer with the chainId encoded in big endian function getChainIdBuf(chainId) { - let b: Buffer + let b: Buffer; // If our chainID is a hex string, we can convert it to a hex // buffer directly - if (true === isValidChainIdHexNumStr(chainId)) b = ensureHexBuffer(chainId) + if (true === isValidChainIdHexNumStr(chainId)) b = ensureHexBuffer(chainId); // If our chainID is a base-10 number, parse with bignumber.js and convert to hex buffer - else b = ensureHexBuffer(`0x${new BN(chainId).toString(16)}`) + else b = ensureHexBuffer(`0x${new BN(chainId).toString(16)}`); // Make sure the buffer is an allowed size - if (b.length > 8) throw new Error('ChainID provided is too large.') + if (b.length > 8) throw new Error('ChainID provided is too large.'); // If this matches a u16, u32, or u64 size, return it now - if (b.length <= 2 || b.length === 4 || b.length === 8) return b + if (b.length <= 2 || b.length === 4 || b.length === 8) return b; // For other size buffers, we need to pack into u32 or u64 before returning; - let buf: Buffer + let buf: Buffer; if (b.length === 3) { - buf = Buffer.alloc(4) - buf.writeUInt32BE(chainId) + buf = Buffer.alloc(4); + buf.writeUInt32BE(chainId); } else if (b.length <= 8) { - buf = Buffer.alloc(8) - b.copy(buf, 8 - b.length) + buf = Buffer.alloc(8); + b.copy(buf, 8 - b.length); } - return buf + return buf; } // Determine if the chain uses EIP155 by default, based on the chainID @@ -756,23 +756,23 @@ function chainUsesEIP155(chainID) { switch (chainID) { case 3: // ropsten case 4: // rinkeby - return false + return false; default: // all others should use eip155 - return true + return true; } } // Determine if a valid number was passed in as a hex string function isValidChainIdHexNumStr(s) { - if (typeof s !== 'string') return false - if (s.slice(0, 2) !== '0x') return false + if (typeof s !== 'string') return false; + if (s.slice(0, 2) !== '0x') return false; try { - const b = new BN(s, 16) - return b.isNaN() === false + const b = new BN(s, 16); + return b.isNaN() === false; } catch (err) { - console.error('Invalid chain ID hex string:', err) - return false + console.error('Invalid chain ID hex string:', err); + return false; } } @@ -780,114 +780,114 @@ function isValidChainIdHexNumStr(s) { // to the `data` buffer of the main transaction. // Note the one edge case: we still need to use the `data` field for chainID=255. function useChainIdBuffer(id) { - const buf = getChainIdBuf(id) - if (buf.length === 1) return buf.readUInt8(0) === 255 - return true + const buf = getChainIdBuf(id); + if (buf.length === 1) return buf.readUInt8(0) === 255; + return true; } function buildPersonalSignRequest(req, input) { - const MAX_BASE_MSG_SZ = input.fwConstants.ethMaxMsgSz - const VAR_PATH_SZ = input.fwConstants.varAddrPathSzAllowed - const L = 24 + MAX_BASE_MSG_SZ + 4 - let off = 0 - req.payload = Buffer.alloc(L) - req.payload.writeUInt8(ethMsgProtocol.SIGN_PERSONAL, 0) - off += 1 + const MAX_BASE_MSG_SZ = input.fwConstants.ethMaxMsgSz; + const VAR_PATH_SZ = input.fwConstants.varAddrPathSzAllowed; + const L = 24 + MAX_BASE_MSG_SZ + 4; + let off = 0; + req.payload = Buffer.alloc(L); + req.payload.writeUInt8(ethMsgProtocol.SIGN_PERSONAL, 0); + off += 1; // Write the signer path into the buffer - const signerPathBuf = buildSignerPathBuf(input.signerPath, VAR_PATH_SZ) - signerPathBuf.copy(req.payload, off) - off += signerPathBuf.length + const signerPathBuf = buildSignerPathBuf(input.signerPath, VAR_PATH_SZ); + signerPathBuf.copy(req.payload, off); + off += signerPathBuf.length; // Write the payload buffer. The payload can come in either as a buffer or as a string - let payload = input.payload + let payload = input.payload; // Determine if this is a hex string - let displayHex = false + let displayHex = false; if (typeof input.payload === 'string') { if (input.payload.slice(0, 2) === '0x') { - payload = ensureHexBuffer(input.payload) + payload = ensureHexBuffer(input.payload); displayHex = false === - ASCII_REGEX.test(Buffer.from(input.payload.slice(2), 'hex').toString()) + ASCII_REGEX.test(Buffer.from(input.payload.slice(2), 'hex').toString()); } else { if (false === isAsciiStr(input.payload)) throw new Error( 'Currently, the Lattice can only display ASCII strings.', - ) - payload = Buffer.from(input.payload) + ); + payload = Buffer.from(input.payload); } } else if (typeof input.displayHex === 'boolean') { // If this is a buffer and the user has specified whether or not this // is a hex buffer with the optional argument, write that - displayHex = input.displayHex + displayHex = input.displayHex; } else { // Otherwise, determine if this buffer is an ASCII string. If it is, set `displayHex` accordingly. // NOTE: THIS MEANS THAT NON-ASCII STRINGS WILL DISPLAY AS HEX SINCE WE CANNOT KNOW IF THE REQUESTER // EXPECTED NON-ASCII CHARACTERS TO DISPLAY IN A STRING // TODO: Develop a more elegant solution for this - if (!input.payload.toString) throw new Error('Unsupported input data type') - displayHex = false === ASCII_REGEX.test(input.payload.toString()) + if (!input.payload.toString) throw new Error('Unsupported input data type'); + displayHex = false === ASCII_REGEX.test(input.payload.toString()); } - const fwConst = input.fwConstants + const fwConst = input.fwConstants; let maxSzAllowed = - MAX_BASE_MSG_SZ + fwConst.extraDataMaxFrames * fwConst.extraDataFrameSz + MAX_BASE_MSG_SZ + fwConst.extraDataMaxFrames * fwConst.extraDataFrameSz; if (fwConst.personalSignHeaderSz) { // Account for the personal_sign header string - maxSzAllowed -= fwConst.personalSignHeaderSz + maxSzAllowed -= fwConst.personalSignHeaderSz; } if (fwConst.ethMsgPreHashAllowed && payload.length > maxSzAllowed) { // If this message will not fit and pre-hashing is allowed, do that - req.payload.writeUInt8(displayHex, off) - off += 1 - req.payload.writeUInt16LE(payload.length, off) - off += 2 + req.payload.writeUInt8(displayHex, off); + off += 1; + req.payload.writeUInt16LE(payload.length, off); + off += 2; const prehash = Buffer.from( Hash.keccak256( Buffer.concat([get_personal_sign_prefix(payload.length), payload]), ), - ) - prehash.copy(req.payload, off) - req.prehash = prehash + ); + prehash.copy(req.payload, off); + req.prehash = prehash; } else { // Otherwise we can fit the payload. // Flow data into extraData requests, which will follow-up transaction requests, if supported/applicable - const extraDataPayloads = getExtraData(payload, input) + const extraDataPayloads = getExtraData(payload, input); // Write the payload and metadata into our buffer - req.extraDataPayloads = extraDataPayloads - req.msg = payload - req.payload.writeUInt8(displayHex, off) - off += 1 - req.payload.writeUInt16LE(payload.length, off) - off += 2 - payload.copy(req.payload, off) + req.extraDataPayloads = extraDataPayloads; + req.msg = payload; + req.payload.writeUInt8(displayHex, off); + off += 1; + req.payload.writeUInt16LE(payload.length, off); + off += 2; + payload.copy(req.payload, off); } - return req + return req; } function buildEIP712Request(req, input) { const { ethMaxMsgSz, varAddrPathSzAllowed, eip712MaxTypeParams } = - input.fwConstants - const { TYPED_DATA } = ethMsgProtocol - const L = 24 + ethMaxMsgSz + 4 - let off = 0 - req.payload = Buffer.alloc(L) - req.payload.writeUInt8(TYPED_DATA.enumIdx, 0) - off += 1 + input.fwConstants; + const { TYPED_DATA } = ethMsgProtocol; + const L = 24 + ethMaxMsgSz + 4; + let off = 0; + req.payload = Buffer.alloc(L); + req.payload.writeUInt8(TYPED_DATA.enumIdx, 0); + off += 1; // Write the signer path const signerPathBuf = buildSignerPathBuf( input.signerPath, varAddrPathSzAllowed, - ) - signerPathBuf.copy(req.payload, off) - off += signerPathBuf.length + ); + signerPathBuf.copy(req.payload, off); + off += signerPathBuf.length; // Parse/clean the EIP712 payload, serialize with CBOR, and write to the payload - const data = cloneTypedDataPayload(input.payload) + const data = cloneTypedDataPayload(input.payload); if (!data.primaryType || !data.types[data.primaryType]) throw new Error( 'primaryType must be specified and the type must be included.', - ) + ); if (!data.message || !data.domain) - throw new Error('message and domain must be specified.') + throw new Error('message and domain must be specified.'); if (0 > Object.keys(data.types).indexOf('EIP712Domain')) - throw new Error('EIP712Domain type must be defined.') + throw new Error('EIP712Domain type must be defined.'); // Parse the payload to ensure we have valid EIP712 data types and that // they are encoded such that Lattice firmware can parse them. // We need two different encodings: one to send to the Lattice in a format that plays @@ -895,101 +895,105 @@ function buildEIP712Request(req, input) { // our EIP712 validation module. // IMPORTANT: Create a new object for the validation payload instead of modifying input.payload // in place, so that validation uses the correctly formatted data - const validationPayload = cloneTypedDataPayload(data) + const validationPayload = cloneTypedDataPayload(data); validationPayload.message = parseEIP712Msg( cloneTypedDataPayload(data.message), cloneTypedDataPayload(data.primaryType), cloneTypedDataPayload(data.types), true, - ) + ); validationPayload.domain = parseEIP712Msg( cloneTypedDataPayload(data.domain), 'EIP712Domain', cloneTypedDataPayload(data.types), true, - ) + ); // Store the validation payload separately without modifying input.payload - req.validationPayload = validationPayload + req.validationPayload = validationPayload; - data.domain = parseEIP712Msg(data.domain, 'EIP712Domain', data.types, false) + data.domain = parseEIP712Msg(data.domain, 'EIP712Domain', data.types, false); data.message = parseEIP712Msg( data.message, data.primaryType, data.types, false, - ) + ); // Now build the message to be sent to the Lattice - const payload = Buffer.from(cbor.encode(data)) - const fwConst = input.fwConstants + const payload = Buffer.from(cbor.encode(data)); + const fwConst = input.fwConstants; const maxSzAllowed = - ethMaxMsgSz + fwConst.extraDataMaxFrames * fwConst.extraDataFrameSz + ethMaxMsgSz + fwConst.extraDataMaxFrames * fwConst.extraDataFrameSz; // Determine if we need to prehash - let shouldPrehash = payload.length > maxSzAllowed + let shouldPrehash = payload.length > maxSzAllowed; Object.keys(data.types).forEach((k) => { if (data.types[k].length > eip712MaxTypeParams) { - shouldPrehash = true + shouldPrehash = true; } - }) + }); if (fwConst.ethMsgPreHashAllowed && shouldPrehash) { // If this payload is too large to send, but the Lattice allows a prehashed message, do that - req.payload.writeUInt16LE(payload.length, off) - off += 2 + req.payload.writeUInt16LE(payload.length, off); + off += 2; const prehash = TypedDataUtils.eip712Hash( req.validationPayload, SignTypedDataVersion.V4, - ) - const prehashBuf = Buffer.from(prehash) - prehashBuf.copy(req.payload, off) - req.prehash = prehash + ); + const prehashBuf = Buffer.from(prehash); + prehashBuf.copy(req.payload, off); + req.prehash = prehash; } else { - const extraDataPayloads = getExtraData(payload, input) - req.extraDataPayloads = extraDataPayloads - req.payload.writeUInt16LE(payload.length, off) - off += 2 - payload.copy(req.payload, off) - off += payload.length + const extraDataPayloads = getExtraData(payload, input); + req.extraDataPayloads = extraDataPayloads; + req.payload.writeUInt16LE(payload.length, off); + off += 2; + payload.copy(req.payload, off); + off += payload.length; // Slice out the part of the buffer that we didn't use. - req.payload = req.payload.slice(0, off) + req.payload = req.payload.slice(0, off); } - return req + return req; } function getExtraData(payload, input) { const { ethMaxMsgSz, extraDataFrameSz, extraDataMaxFrames } = - input.fwConstants - const MAX_BASE_MSG_SZ = ethMaxMsgSz - const EXTRA_DATA_ALLOWED = extraDataFrameSz > 0 && extraDataMaxFrames > 0 - const extraDataPayloads = [] + input.fwConstants; + const MAX_BASE_MSG_SZ = ethMaxMsgSz; + const EXTRA_DATA_ALLOWED = extraDataFrameSz > 0 && extraDataMaxFrames > 0; + const extraDataPayloads = []; if (payload.length > MAX_BASE_MSG_SZ) { // Determine sizes and run through sanity checks - const maxSzAllowed = MAX_BASE_MSG_SZ + extraDataMaxFrames * extraDataFrameSz + const maxSzAllowed = + MAX_BASE_MSG_SZ + extraDataMaxFrames * extraDataFrameSz; if (!EXTRA_DATA_ALLOWED) throw new Error( `Your message is ${payload.length} bytes, but can only be a maximum of ${MAX_BASE_MSG_SZ}`, - ) + ); else if (EXTRA_DATA_ALLOWED && payload.length > maxSzAllowed) throw new Error( `Your message is ${payload.length} bytes, but can only be a maximum of ${maxSzAllowed}`, - ) + ); // Split overflow data into extraData frames - const frames = splitFrames(payload.slice(MAX_BASE_MSG_SZ), extraDataFrameSz) + const frames = splitFrames( + payload.slice(MAX_BASE_MSG_SZ), + extraDataFrameSz, + ); frames.forEach((frame) => { - const szLE = Buffer.alloc(4) - szLE.writeUInt32LE(frame.length, 0) - extraDataPayloads.push(Buffer.concat([szLE, frame])) - }) + const szLE = Buffer.alloc(4); + szLE.writeUInt32LE(frame.length, 0); + extraDataPayloads.push(Buffer.concat([szLE, frame])); + }); } - return extraDataPayloads + return extraDataPayloads; } function parseEIP712Msg(msg, typeName, types, forJSParser = false) { - const type = types[typeName] + const type = types[typeName]; type.forEach((item) => { - const isArrayType = item.type.indexOf('[') > -1 + const isArrayType = item.type.indexOf('[') > -1; const singularType = isArrayType ? item.type.slice(0, item.type.indexOf('[')) - : item.type - const isCustomType = Object.keys(types).indexOf(singularType) > -1 + : item.type; + const isCustomType = Object.keys(types).indexOf(singularType) > -1; if (isCustomType && Array.isArray(msg)) { // For custom types we need to jump into the `msg` using the key (name of type) and // parse that entire sub-struct as if it were a message. @@ -1002,7 +1006,7 @@ function parseEIP712Msg(msg, typeName, types, forJSParser = false) { singularType, types, forJSParser, - ) + ); } } else if (isCustomType) { // Not an array means we can jump directly into the sub-struct to convert @@ -1011,7 +1015,7 @@ function parseEIP712Msg(msg, typeName, types, forJSParser = false) { singularType, types, forJSParser, - ) + ); } else if (Array.isArray(msg)) { // If we have an array for this particular type and the type we are parsing // is *not* a custom type, loop through the array elements and convert the types. @@ -1025,7 +1029,7 @@ function parseEIP712Msg(msg, typeName, types, forJSParser = false) { msg[i][item.name][j], singularType, forJSParser, - ) + ); } } else { // Non-arrays parse + replace one value for the elementary type @@ -1033,7 +1037,7 @@ function parseEIP712Msg(msg, typeName, types, forJSParser = false) { msg[i][item.name], singularType, forJSParser, - ) + ); } } } else if (isArrayType) { @@ -1044,7 +1048,7 @@ function parseEIP712Msg(msg, typeName, types, forJSParser = false) { msg[item.name][i], singularType, forJSParser, - ) + ); } } else { // If this is a singular elementary type, simply parse + replace. @@ -1052,49 +1056,49 @@ function parseEIP712Msg(msg, typeName, types, forJSParser = false) { msg[item.name], singularType, forJSParser, - ) + ); } - }) + }); - return msg + return msg; } function parseEIP712Item(data, type, forJSParser = false) { if (type === 'bytes') { // Variable sized bytes need to be buffer type - data = ensureHexBuffer(data) + data = ensureHexBuffer(data); if (forJSParser) { // For EIP712 encoding module it's easier to encode hex strings - data = `0x${data.toString('hex')}` + data = `0x${data.toString('hex')}`; } } else if (type.slice(0, 5) === 'bytes') { // Fixed sizes bytes need to be buffer type. We also add some sanity checks. - const nBytes = Number.parseInt(type.slice(5)) - data = ensureHexBuffer(data) + const nBytes = Number.parseInt(type.slice(5)); + data = ensureHexBuffer(data); // Edge case to handle empty bytesN values if (data.length === 0) { - data = Buffer.alloc(nBytes) + data = Buffer.alloc(nBytes); } if (data.length !== nBytes) - throw new Error(`Expected ${type} type, but got ${data.length} bytes`) + throw new Error(`Expected ${type} type, but got ${data.length} bytes`); if (forJSParser) { // For EIP712 encoding module it's easier to encode hex strings - data = `0x${data.toString('hex')}` + data = `0x${data.toString('hex')}`; } } else if (type === 'address') { // Address must be a 20 byte buffer - data = ensureHexBuffer(data) + data = ensureHexBuffer(data); // Edge case to handle the 0-address if (data.length === 0) { - data = Buffer.alloc(20) + data = Buffer.alloc(20); } if (data.length !== 20) throw new Error( `Address type must be 20 bytes, but got ${data.length} bytes`, - ) + ); // For EIP712 encoding module it's easier to encode hex strings if (forJSParser) { - data = `0x${data.toString('hex')}` + data = `0x${data.toString('hex')}`; } } else if ( ethMsgProtocol.TYPED_DATA.typeCodes[type] && @@ -1104,9 +1108,9 @@ function parseEIP712Item(data, type, forJSParser = false) { // Handle signed integers using bignumber.js directly if (forJSParser) { // For EIP712 encoding in this module we need hex strings for signed ints too - const bn = new BN(data) + const bn = new BN(data); // Preserve full precision by returning the decimal string representation - data = bn.toFixed() + data = bn.toFixed(); } else { // `bignumber.js` is needed for `cbor` encoding, which gets sent to the Lattice and plays // nicely with its firmware cbor lib. @@ -1116,7 +1120,7 @@ function parseEIP712Item(data, type, forJSParser = false) { // TODO: Find another cbor lib that is compataible with the firmware's lib in a browser // context. This is surprisingly difficult - I tried several libs and only cbor/borc have // worked (borc is a supposedly "browser compatible" version of cbor) - data = new BN(data) + data = new BN(data); } } else if ( ethMsgProtocol.TYPED_DATA.typeCodes[type] && @@ -1125,29 +1129,32 @@ function parseEIP712Item(data, type, forJSParser = false) { // For uints, convert to a buffer and do some sanity checking. // Note that we could probably just use bignumber.js directly as we do with // signed ints, but this code is battle tested and we don't want to change it. - let b = ensureHexBuffer(data) + let b = ensureHexBuffer(data); // Edge case to handle 0-value bignums if (b.length === 0) { - b = Buffer.from('00', 'hex') + b = Buffer.from('00', 'hex'); } // Uint256s should be encoded as bignums. if (forJSParser) { // For EIP712 encoding in this module we need hex strings to represent the numbers - data = `0x${b.toString('hex')}` + data = `0x${b.toString('hex')}`; } else { // Load into bignumber.js used by cbor lib - data = new BN(b.toString('hex'), 16) + data = new BN(b.toString('hex'), 16); } } else if (type === 'bool') { // Booleans need to be cast to a u8 - data = data === true ? 1 : 0 + data = data === true ? 1 : 0; } // Other types don't need to be modified - return data + return data; } function get_personal_sign_prefix(L) { - return Buffer.from(`\u0019Ethereum Signed Message:\n${L.toString()}`, 'utf-8') + return Buffer.from( + `\u0019Ethereum Signed Message:\n${L.toString()}`, + 'utf-8', + ); } function get_rlp_encoded_preimage(rawTx, txType) { @@ -1155,9 +1162,9 @@ function get_rlp_encoded_preimage(rawTx, txType) { return Buffer.concat([ Buffer.from([txType]), Buffer.from(RLP.encode(rawTx)), - ]) + ]); } else { - return Buffer.from(RLP.encode(rawTx)) + return Buffer.from(RLP.encode(rawTx)); } } @@ -1176,7 +1183,7 @@ function get_rlp_encoded_preimage(rawTx, txType) { export const normalizeToViemTransaction = ( tx: unknown, ): TransactionSerializable => { - const parsed = TransactionSchema.parse(tx) + const parsed = TransactionSchema.parse(tx); return { ...parsed, @@ -1195,8 +1202,8 @@ export const normalizeToViemTransaction = ( accessList: 'accessList' in parsed ? parsed.accessList : undefined, authorizationList: 'authorizationList' in parsed ? parsed.authorizationList : undefined, - } -} + }; +}; /** * Convert Ethereum transaction to serialized bytes for generic signing. @@ -1207,17 +1214,17 @@ const convertEthereumTransactionToGenericRequest = ( ) => { // Use the unified normalization and serialization pipeline. // 1. Normalize the potentially varied input to a standard viem format. - const viemTx = normalizeToViemTransaction(req) + const viemTx = normalizeToViemTransaction(req); // 2. Serialize the transaction to RLP-encoded bytes. - const serializedTx = serializeTransaction(viemTx) - return Buffer.from(serializedTx.slice(2), 'hex') -} + const serializedTx = serializeTransaction(viemTx); + return Buffer.from(serializedTx.slice(2), 'hex'); +}; // Type for Ethereum generic signing request type EthereumGenericSigningRequestParams = FlexibleTransaction & { - fwConstants: FirmwareConstants - signerPath: SigningPath -} + fwConstants: FirmwareConstants; + signerPath: SigningPath; +}; /** * Build complete generic signing request for Ethereum transactions. @@ -1226,9 +1233,9 @@ type EthereumGenericSigningRequestParams = FlexibleTransaction & { export const buildEthereumGenericSigningRequest = ( req: EthereumGenericSigningRequestParams, ) => { - const { fwConstants, signerPath, ...txData } = req + const { fwConstants, signerPath, ...txData } = req; - const payload = convertEthereumTransactionToGenericRequest(txData) + const payload = convertEthereumTransactionToGenericRequest(txData); return buildGenericSigningMsgRequest({ fwConstants, @@ -1237,8 +1244,8 @@ export const buildEthereumGenericSigningRequest = ( hashType: EXTERNAL.SIGNING.HASHES.KECCAK256, signerPath, payload, - }) -} + }); +}; /** * Serializes an EIP7702 transaction using Viem. @@ -1253,36 +1260,36 @@ export function serializeEIP7702Transaction(tx: TransactionRequest): Hex { ) { throw new Error( `Only EIP-7702 auth transactions (type ${TRANSACTION_TYPE.EIP7702_AUTH}) and auth-list transactions (type ${TRANSACTION_TYPE.EIP7702_AUTH_LIST}) are supported`, - ) + ); } // Type guard to ensure we have an EIP7702 transaction with appropriate authorization data - const hasAuthList = 'authorizationList' in tx - const hasSingleAuth = 'authorization' in tx + const hasAuthList = 'authorizationList' in tx; + const hasSingleAuth = 'authorization' in tx; if (!hasAuthList && !hasSingleAuth) { throw new Error( 'Transaction does not have authorization or authorizationList property', - ) + ); } // For type 4 transactions, convert single authorization to array format - let authorizationList: any[] + let authorizationList: any[]; if (tx.type === TRANSACTION_TYPE.EIP7702_AUTH) { if (!hasSingleAuth) { throw new Error( 'EIP-7702 auth transaction (type 4) must contain authorization property', - ) + ); } - authorizationList = [(tx as any).authorization] + authorizationList = [(tx as any).authorization]; } else { // Type 5 transaction - only handle authorizationList field if (hasAuthList) { - authorizationList = (tx as any).authorizationList + authorizationList = (tx as any).authorizationList; } else { throw new Error( 'EIP-7702 auth list transaction (type 5) must contain authorizationList property', - ) + ); } } @@ -1294,7 +1301,7 @@ export function serializeEIP7702Transaction(tx: TransactionRequest): Hex { ) { throw new Error( 'EIP-7702 transaction must contain at least one authorization', - ) + ); } // Validate each authorization @@ -1302,13 +1309,13 @@ export function serializeEIP7702Transaction(tx: TransactionRequest): Hex { if (!auth.address) { throw new Error( `Authorization at index ${index} is missing a contract address`, - ) + ); } - }) + }); // Validate required transaction fields if (!tx.to) { - throw new Error('EIP-7702 transaction must include a valid "to" address') + throw new Error('EIP-7702 transaction must include a valid "to" address'); } // Convert to Viem's expected format @@ -1337,18 +1344,18 @@ export function serializeEIP7702Transaction(tx: TransactionRequest): Hex { authorizationList: authorizationList.map((auth, idx) => { // Create the Viem-formatted authorization // Ensure proper address handling with 0x prefix - const address = auth.address || '' + const address = auth.address || ''; const addressStr = typeof address === 'string' ? address.startsWith('0x') ? address : `0x${address}` - : '0x' + : '0x'; if (!addressStr || addressStr === '0x') { throw new Error( `Authorization at index ${idx} is missing a valid address`, - ) + ); } // Handle viem's SignedAuthorization format @@ -1359,7 +1366,7 @@ export function serializeEIP7702Transaction(tx: TransactionRequest): Hex { address: addressStr as `0x${string}`, nonce: BigInt(auth.nonce || 0), signature: auth.signature, - } + }; } else { // Direct signature properties (r, s, yParity/v) return { @@ -1380,12 +1387,12 @@ export function serializeEIP7702Transaction(tx: TransactionRequest): Hex { r: auth.r || '0x0', s: auth.s || '0x0', }, - } + }; } }), - } + }; - return serializeTransaction(viemTx as any) + return serializeTransaction(viemTx as any); } export const isEip7702Transaction = (tx: TransactionRequest): boolean => { @@ -1394,8 +1401,8 @@ export const isEip7702Transaction = (tx: TransactionRequest): boolean => { 'type' in tx && (tx.type === TRANSACTION_TYPE.EIP7702_AUTH_LIST || tx.type === TRANSACTION_TYPE.EIP7702_AUTH) - ) -} + ); +}; export default { buildEthereumMsgRequest, @@ -1408,4 +1415,4 @@ export default { normalizeToViemTransaction, convertEthereumTransactionToGenericRequest, buildEthereumGenericSigningRequest, -} +}; diff --git a/packages/sdk/src/functions/addKvRecords.ts b/packages/sdk/src/functions/addKvRecords.ts index 5c650483..0ce54783 100644 --- a/packages/sdk/src/functions/addKvRecords.ts +++ b/packages/sdk/src/functions/addKvRecords.ts @@ -1,17 +1,17 @@ import { LatticeSecureEncryptedRequestType, encryptedSecureRequest, -} from '../protocol' +} from '../protocol'; import { validateConnectedClient, validateKvRecord, validateKvRecords, -} from '../shared/validators' +} from '../shared/validators'; import type { AddKvRecordsRequestFunctionParams, FirmwareConstants, KVRecords, -} from '../types' +} from '../types'; /** * `addKvRecords` takes in a set of key-value records and sends a request to add them to the @@ -26,8 +26,8 @@ export async function addKvRecords({ caseSensitive, }: AddKvRecordsRequestFunctionParams): Promise { const { url, sharedSecret, ephemeralPub, fwConstants } = - validateConnectedClient(client) - validateAddKvRequest({ records, fwConstants }) + validateConnectedClient(client); + validateAddKvRequest({ records, fwConstants }); // Build the data for this request const data = encodeAddKvRecordsRequest({ @@ -35,7 +35,7 @@ export async function addKvRecords({ type, caseSensitive, fwConstants, - }) + }); const { decryptedData, newEphemeralPub } = await encryptedSecureRequest({ data, @@ -43,24 +43,24 @@ export async function addKvRecords({ sharedSecret, ephemeralPub, url, - }) + }); client.mutate({ ephemeralPub: newEphemeralPub, - }) + }); - return decryptedData + return decryptedData; } export const validateAddKvRequest = ({ records, fwConstants, }: { - records: KVRecords - fwConstants: FirmwareConstants + records: KVRecords; + fwConstants: FirmwareConstants; }) => { - validateKvRecords(records, fwConstants) -} + validateKvRecords(records, fwConstants); +}; export const encodeAddKvRecordsRequest = ({ records, @@ -68,31 +68,34 @@ export const encodeAddKvRecordsRequest = ({ caseSensitive, fwConstants, }: { - records: KVRecords - type: number - caseSensitive: boolean - fwConstants: FirmwareConstants + records: KVRecords; + type: number; + caseSensitive: boolean; + fwConstants: FirmwareConstants; }) => { - const payload = Buffer.alloc(1 + 139 * fwConstants.kvActionMaxNum) - payload.writeUInt8(Object.keys(records).length, 0) - let off = 1 + const payload = Buffer.alloc(1 + 139 * fwConstants.kvActionMaxNum); + payload.writeUInt8(Object.keys(records).length, 0); + let off = 1; Object.entries(records).forEach(([_key, _val]) => { - const { key, val } = validateKvRecord({ key: _key, val: _val }, fwConstants) + const { key, val } = validateKvRecord( + { key: _key, val: _val }, + fwConstants, + ); // Skip the ID portion. This will get added by firmware. - payload.writeUInt32LE(0, off) - off += 4 - payload.writeUInt32LE(type, off) - off += 4 - payload.writeUInt8(caseSensitive ? 1 : 0, off) - off += 1 - payload.writeUInt8(String(key).length + 1, off) - off += 1 - Buffer.from(String(key)).copy(payload, off) - off += fwConstants.kvKeyMaxStrSz + 1 - payload.writeUInt8(String(val).length + 1, off) - off += 1 - Buffer.from(String(val)).copy(payload, off) - off += fwConstants.kvValMaxStrSz + 1 - }) - return payload -} + payload.writeUInt32LE(0, off); + off += 4; + payload.writeUInt32LE(type, off); + off += 4; + payload.writeUInt8(caseSensitive ? 1 : 0, off); + off += 1; + payload.writeUInt8(String(key).length + 1, off); + off += 1; + Buffer.from(String(key)).copy(payload, off); + off += fwConstants.kvKeyMaxStrSz + 1; + payload.writeUInt8(String(val).length + 1, off); + off += 1; + Buffer.from(String(val)).copy(payload, off); + off += fwConstants.kvValMaxStrSz + 1; + }); + return payload; +}; diff --git a/packages/sdk/src/functions/connect.ts b/packages/sdk/src/functions/connect.ts index c5b6ed24..bd03b931 100644 --- a/packages/sdk/src/functions/connect.ts +++ b/packages/sdk/src/functions/connect.ts @@ -1,17 +1,17 @@ -import { ProtocolConstants, connectSecureRequest } from '../protocol' -import { doesFetchWalletsOnLoad } from '../shared/predicates' -import { getSharedSecret, parseWallets } from '../shared/utilities' +import { ProtocolConstants, connectSecureRequest } from '../protocol'; +import { doesFetchWalletsOnLoad } from '../shared/predicates'; +import { getSharedSecret, parseWallets } from '../shared/utilities'; import { validateBaseUrl, validateDeviceId, validateKey, -} from '../shared/validators' +} from '../shared/validators'; import type { ActiveWallets, ConnectRequestFunctionParams, KeyPair, -} from '../types' -import { aes256_decrypt, getP256KeyPairFromPub } from '../util' +} from '../types'; +import { aes256_decrypt, getP256KeyPairFromPub } from '../util'; export async function connect({ client, @@ -22,19 +22,19 @@ export async function connect({ // @ts-expect-error - private access key: client.key, baseUrl: client.baseUrl, - }) + }); - const url = `${baseUrl}/${deviceId}` + const url = `${baseUrl}/${deviceId}`; const respPayloadData = await connectSecureRequest({ url, pubkey: client.publicKey, - }) + }); // Decode response data params. // Response payload data is *not* encrypted. const { isPaired, fwVersion, activeWallets, ephemeralPub } = - await decodeConnectResponse(respPayloadData, key) + await decodeConnectResponse(respPayloadData, key); // Update client state with response data @@ -45,19 +45,19 @@ export async function connect({ isPaired, fwVersion, activeWallets, - }) + }); // If we are paired and are on older firmware (<0.14.1), we need a // follow up request to sync wallet state. if (isPaired && !doesFetchWalletsOnLoad(client.getFwVersion())) { - await client.fetchActiveWallet() + await client.fetchActiveWallet(); } // Return flag indicating whether we are paired or not. // If we are *not* already paired, the Lattice is now in // pairing mode and expects a `finalizePairing` encrypted // request as a follow up. - return isPaired + return isPaired; } export const validateConnectRequest = ({ @@ -65,24 +65,24 @@ export const validateConnectRequest = ({ key, baseUrl, }: { - deviceId?: string - key?: KeyPair - baseUrl?: string + deviceId?: string; + key?: KeyPair; + baseUrl?: string; }): { - deviceId: string - key: KeyPair - baseUrl: string + deviceId: string; + key: KeyPair; + baseUrl: string; } => { - const validDeviceId = validateDeviceId(deviceId) - const validKey = validateKey(key) - const validBaseUrl = validateBaseUrl(baseUrl) + const validDeviceId = validateDeviceId(deviceId); + const validKey = validateKey(key); + const validBaseUrl = validateBaseUrl(baseUrl); return { deviceId: validDeviceId, key: validKey, baseUrl: validBaseUrl, - } -} + }; +}; /** * `decodeConnectResponse` will call `StartPairingMode` on the device, which gives the user 60 seconds to @@ -98,44 +98,44 @@ export const decodeConnectResponse = ( response: Buffer, key: KeyPair, ): { - isPaired: boolean - fwVersion: Buffer - activeWallets: ActiveWallets | undefined - ephemeralPub: KeyPair + isPaired: boolean; + fwVersion: Buffer; + activeWallets: ActiveWallets | undefined; + ephemeralPub: KeyPair; } => { - let off = 0 + let off = 0; const isPaired = - response.readUInt8(off) === ProtocolConstants.pairingStatus.paired - off++ + response.readUInt8(off) === ProtocolConstants.pairingStatus.paired; + off++; // If we are already paired, we get the next ephemeral key - const pub = response.slice(off, off + 65).toString('hex') - off += 65 // Set the public key - const ephemeralPub = getP256KeyPairFromPub(pub) + const pub = response.slice(off, off + 65).toString('hex'); + off += 65; // Set the public key + const ephemeralPub = getP256KeyPairFromPub(pub); // Grab the firmware version (will be 0-length for older fw versions) It is of format // |fix|minor|major|reserved| - const fwVersion = response.slice(off, off + 4) - off += 4 + const fwVersion = response.slice(off, off + 4); + off += 4; // If we are already paired, the response will include some encrypted data about the current // wallets This data was added in Lattice firmware v0.14.1 if (isPaired) { //TODO && this._fwVersionGTE(0, 14, 1)) { // Later versions of firmware added wallet info - const encWalletData = response.slice(off, off + 160) - off += 160 - const sharedSecret = getSharedSecret(key, ephemeralPub) - const decWalletData = aes256_decrypt(encWalletData, sharedSecret) + const encWalletData = response.slice(off, off + 160); + off += 160; + const sharedSecret = getSharedSecret(key, ephemeralPub); + const decWalletData = aes256_decrypt(encWalletData, sharedSecret); // Sanity check to make sure the last part of the decrypted data is empty. The last 2 bytes // are AES padding if ( decWalletData[decWalletData.length - 2] !== 0 || decWalletData[decWalletData.length - 1] !== 0 ) { - throw new Error('Failed to connect to Lattice.') + throw new Error('Failed to connect to Lattice.'); } - const activeWallets = parseWallets(decWalletData) - return { isPaired, fwVersion, activeWallets, ephemeralPub } + const activeWallets = parseWallets(decWalletData); + return { isPaired, fwVersion, activeWallets, ephemeralPub }; } // return the state of our pairing - return { isPaired, fwVersion, activeWallets: undefined, ephemeralPub } -} + return { isPaired, fwVersion, activeWallets: undefined, ephemeralPub }; +}; diff --git a/packages/sdk/src/functions/fetchActiveWallet.ts b/packages/sdk/src/functions/fetchActiveWallet.ts index 8b256816..c426bbfc 100644 --- a/packages/sdk/src/functions/fetchActiveWallet.ts +++ b/packages/sdk/src/functions/fetchActiveWallet.ts @@ -1,16 +1,16 @@ -import { EMPTY_WALLET_UID } from '../constants' +import { EMPTY_WALLET_UID } from '../constants'; import { LatticeSecureEncryptedRequestType, encryptedSecureRequest, -} from '../protocol' +} from '../protocol'; import { validateActiveWallets, validateConnectedClient, -} from '../shared/validators' +} from '../shared/validators'; import type { ActiveWallets, FetchActiveWalletRequestFunctionParams, -} from '../types' +} from '../types'; /** * Fetch the active wallet in the device. @@ -22,7 +22,7 @@ import type { export async function fetchActiveWallet({ client, }: FetchActiveWalletRequestFunctionParams): Promise { - const { url, sharedSecret, ephemeralPub } = validateConnectedClient(client) + const { url, sharedSecret, ephemeralPub } = validateConnectedClient(client); const { decryptedData, newEphemeralPub } = await encryptedSecureRequest({ data: Buffer.alloc(0), @@ -30,17 +30,17 @@ export async function fetchActiveWallet({ sharedSecret, ephemeralPub, url, - }) + }); - const activeWallets = decodeFetchActiveWalletResponse(decryptedData) - const validActiveWallets = validateActiveWallets(activeWallets) + const activeWallets = decodeFetchActiveWalletResponse(decryptedData); + const validActiveWallets = validateActiveWallets(activeWallets); client.mutate({ ephemeralPub: newEphemeralPub, activeWallets: validActiveWallets, - }) + }); - return validActiveWallets + return validActiveWallets; } export const decodeFetchActiveWalletResponse = (data: Buffer) => { @@ -48,7 +48,7 @@ export const decodeFetchActiveWalletResponse = (data: Buffer) => { // active wallet of the device and we should save it. If the external wallet is blank, it means // there is no card present and we should save and use the interal wallet. If both wallets are // empty, it means the device still needs to be set up. - const walletDescriptorLen = 71 + const walletDescriptorLen = 71; // Internal first const activeWallets: ActiveWallets = { internal: { @@ -63,16 +63,16 @@ export const decodeFetchActiveWalletResponse = (data: Buffer) => { name: Buffer.alloc(0), capabilities: 0, }, - } - let off = 0 - activeWallets.internal.uid = data.slice(off, off + 32) - activeWallets.internal.capabilities = data.readUInt32BE(off + 32) - activeWallets.internal.name = data.slice(off + 36, off + walletDescriptorLen) + }; + let off = 0; + activeWallets.internal.uid = data.slice(off, off + 32); + activeWallets.internal.capabilities = data.readUInt32BE(off + 32); + activeWallets.internal.name = data.slice(off + 36, off + walletDescriptorLen); // Offset the first item - off += walletDescriptorLen + off += walletDescriptorLen; // External - activeWallets.external.uid = data.slice(off, off + 32) - activeWallets.external.capabilities = data.readUInt32BE(off + 32) - activeWallets.external.name = data.slice(off + 36, off + walletDescriptorLen) - return activeWallets -} + activeWallets.external.uid = data.slice(off, off + 32); + activeWallets.external.capabilities = data.readUInt32BE(off + 32); + activeWallets.external.name = data.slice(off + 36, off + walletDescriptorLen); + return activeWallets; +}; diff --git a/packages/sdk/src/functions/fetchDecoder.ts b/packages/sdk/src/functions/fetchDecoder.ts index e0dd9caf..70b4367c 100644 --- a/packages/sdk/src/functions/fetchDecoder.ts +++ b/packages/sdk/src/functions/fetchDecoder.ts @@ -1,8 +1,8 @@ -import { validateConnectedClient } from '../shared/validators' +import { validateConnectedClient } from '../shared/validators'; -import { getClient } from '../api' -import type { TransactionRequest } from '../types' -import { fetchCalldataDecoder } from '../util' +import { getClient } from '../api'; +import type { TransactionRequest } from '../types'; +import { fetchCalldataDecoder } from '../util'; /** * `fetchDecoder` fetches the ABI for a given contract address and chain ID. @@ -15,23 +15,23 @@ export async function fetchDecoder({ chainId, }: TransactionRequest): Promise { try { - const client = await getClient() - validateConnectedClient(client) + const client = await getClient(); + validateConnectedClient(client); - const fwVersion = client.getFwVersion() + const fwVersion = client.getFwVersion(); const supportsDecoderRecursion = - fwVersion.major > 0 || fwVersion.minor >= 16 + fwVersion.major > 0 || fwVersion.minor >= 16; const { def } = await fetchCalldataDecoder( data, to, chainId, supportsDecoderRecursion, - ) + ); - return def + return def; } catch (error) { - console.warn('Failed to fetch ABI:', error) - return undefined + console.warn('Failed to fetch ABI:', error); + return undefined; } } diff --git a/packages/sdk/src/functions/fetchEncData.ts b/packages/sdk/src/functions/fetchEncData.ts index 72fdaf14..3b71139f 100644 --- a/packages/sdk/src/functions/fetchEncData.ts +++ b/packages/sdk/src/functions/fetchEncData.ts @@ -2,30 +2,30 @@ * Export encrypted data from the Lattice. Data must conform * to known schema, e.g. EIP2335 derived privkey export. */ -import { v4 as uuidV4 } from 'uuid' -import { EXTERNAL } from '../constants' +import { v4 as uuidV4 } from 'uuid'; +import { EXTERNAL } from '../constants'; import { LatticeSecureEncryptedRequestType, encryptedSecureRequest, -} from '../protocol' -import { getPathStr } from '../shared/utilities' +} from '../protocol'; +import { getPathStr } from '../shared/utilities'; import { validateConnectedClient, validateStartPath, validateWallet, -} from '../shared/validators' +} from '../shared/validators'; import type { EIP2335KeyExportData, EIP2335KeyExportReq, FetchEncDataRequestFunctionParams, FirmwareVersion, Wallet, -} from '../types' +} from '../types'; -const { ENC_DATA } = EXTERNAL +const { ENC_DATA } = EXTERNAL; const ENC_DATA_ERR_STR = - 'Unknown encrypted data export type requested. Exiting.' -const ENC_DATA_REQ_DATA_SZ = 1025 + 'Unknown encrypted data export type requested. Exiting.'; +const ENC_DATA_REQ_DATA_SZ = 1025; const ENC_DATA_RESP_SZ = { EIP2335: { CIPHERTEXT: 32, @@ -34,7 +34,7 @@ const ENC_DATA_RESP_SZ = { IV: 16, PUBKEY: 48, }, -} as const +} as const; export async function fetchEncData({ client, @@ -42,16 +42,16 @@ export async function fetchEncData({ params, }: FetchEncDataRequestFunctionParams): Promise { const { url, sharedSecret, ephemeralPub, fwVersion } = - validateConnectedClient(client) - const activeWallet = validateWallet(client.getActiveWallet()) - validateFetchEncDataRequest({ params }) + validateConnectedClient(client); + const activeWallet = validateWallet(client.getActiveWallet()); + validateFetchEncDataRequest({ params }); const data = encodeFetchEncDataRequest({ schema, params, fwVersion, activeWallet, - }) + }); const { decryptedData, newEphemeralPub } = await encryptedSecureRequest({ data, @@ -59,23 +59,23 @@ export async function fetchEncData({ sharedSecret, ephemeralPub, url, - }) + }); client.mutate({ ephemeralPub: newEphemeralPub, - }) + }); - return decodeFetchEncData({ data: decryptedData, schema, params }) + return decodeFetchEncData({ data: decryptedData, schema, params }); } export const validateFetchEncDataRequest = ({ params, }: { - params: EIP2335KeyExportReq + params: EIP2335KeyExportReq; }) => { // Validate derivation path - validateStartPath(params.path) -} + validateStartPath(params.path); +}; export const encodeFetchEncDataRequest = ({ schema, @@ -83,101 +83,101 @@ export const encodeFetchEncDataRequest = ({ fwVersion, activeWallet, }: { - schema: number - params: EIP2335KeyExportReq - fwVersion: FirmwareVersion - activeWallet: Wallet + schema: number; + params: EIP2335KeyExportReq; + fwVersion: FirmwareVersion; + activeWallet: Wallet; }) => { // Check firmware version if (fwVersion.major < 1 && fwVersion.minor < 17) { throw new Error( 'Firmware version >=v0.17.0 is required for encrypted data export.', - ) + ); } // Update params depending on what type of data is being exported if (schema === ENC_DATA.SCHEMAS.BLS_KEYSTORE_EIP2335_PBKDF_V4) { // Set the wallet UID to the client's current active wallet - params.walletUID = activeWallet.uid + params.walletUID = activeWallet.uid; } else { - throw new Error(ENC_DATA_ERR_STR) + throw new Error(ENC_DATA_ERR_STR); } // Build the payload data - const payload = Buffer.alloc(ENC_DATA_REQ_DATA_SZ) - let off = 0 - payload.writeUInt8(schema, off) - off += 1 + const payload = Buffer.alloc(ENC_DATA_REQ_DATA_SZ); + let off = 0; + payload.writeUInt8(schema, off); + off += 1; if (schema === ENC_DATA.SCHEMAS.BLS_KEYSTORE_EIP2335_PBKDF_V4) { - params.walletUID.copy(payload, off) - off += params.walletUID.length - payload.writeUInt8(params.path.length, off) - off += 1 + params.walletUID.copy(payload, off); + off += params.walletUID.length; + payload.writeUInt8(params.path.length, off); + off += 1; for (let i = 0; i < 5; i++) { if (i <= params.path.length) { - payload.writeUInt32LE(params.path[i], off) + payload.writeUInt32LE(params.path[i], off); } - off += 4 + off += 4; } if (params.c) { - payload.writeUInt32LE(params.c, off) + payload.writeUInt32LE(params.c, off); } - off += 4 - return payload + off += 4; + return payload; } else { - throw new Error(ENC_DATA_ERR_STR) + throw new Error(ENC_DATA_ERR_STR); } -} +}; export const decodeFetchEncData = ({ data, schema, params, }: { - schema: number - params: EIP2335KeyExportReq - data: Buffer + schema: number; + params: EIP2335KeyExportReq; + data: Buffer; }): Buffer => { - let off = 0 + let off = 0; if (schema === ENC_DATA.SCHEMAS.BLS_KEYSTORE_EIP2335_PBKDF_V4) { - const respData = {} as EIP2335KeyExportData - const { CIPHERTEXT, SALT, CHECKSUM, IV, PUBKEY } = ENC_DATA_RESP_SZ.EIP2335 + const respData = {} as EIP2335KeyExportData; + const { CIPHERTEXT, SALT, CHECKSUM, IV, PUBKEY } = ENC_DATA_RESP_SZ.EIP2335; const expectedSz = 4 + // iterations = u32 CIPHERTEXT + SALT + CHECKSUM + IV + - PUBKEY - const dataSz = data.readUInt32LE(off) - off += 4 + PUBKEY; + const dataSz = data.readUInt32LE(off); + off += 4; if (dataSz !== expectedSz) { throw new Error( 'Invalid data returned from Lattice. Expected EIP2335 data.', - ) + ); } - respData.iterations = data.readUInt32LE(off) - off += 4 - respData.cipherText = data.slice(off, off + CIPHERTEXT) - off += CIPHERTEXT - respData.salt = data.slice(off, off + SALT) - off += SALT - respData.checksum = data.slice(off, off + CHECKSUM) - off += CHECKSUM - respData.iv = data.slice(off, off + IV) - off += IV - respData.pubkey = data.slice(off, off + PUBKEY) - off += PUBKEY - return formatEIP2335ExportData(respData, params.path) + respData.iterations = data.readUInt32LE(off); + off += 4; + respData.cipherText = data.slice(off, off + CIPHERTEXT); + off += CIPHERTEXT; + respData.salt = data.slice(off, off + SALT); + off += SALT; + respData.checksum = data.slice(off, off + CHECKSUM); + off += CHECKSUM; + respData.iv = data.slice(off, off + IV); + off += IV; + respData.pubkey = data.slice(off, off + PUBKEY); + off += PUBKEY; + return formatEIP2335ExportData(respData, params.path); } else { - throw new Error(ENC_DATA_ERR_STR) + throw new Error(ENC_DATA_ERR_STR); } -} +}; const formatEIP2335ExportData = ( resp: EIP2335KeyExportData, path: number[], ): Buffer => { try { - const { iterations, salt, checksum, iv, cipherText, pubkey } = resp + const { iterations, salt, checksum, iv, cipherText, pubkey } = resp; return Buffer.from( JSON.stringify({ version: 4, @@ -209,8 +209,8 @@ const formatEIP2335ExportData = ( }, }, }), - ) + ); } catch (err) { - throw Error(`Failed to format EIP2335 return data: ${err.toString()}`) + throw Error(`Failed to format EIP2335 return data: ${err.toString()}`); } -} +}; diff --git a/packages/sdk/src/functions/getAddresses.ts b/packages/sdk/src/functions/getAddresses.ts index 8153caca..da366d23 100644 --- a/packages/sdk/src/functions/getAddresses.ts +++ b/packages/sdk/src/functions/getAddresses.ts @@ -3,20 +3,20 @@ import { LatticeSecureEncryptedRequestType, ProtocolConstants, encryptedSecureRequest, -} from '../protocol' +} from '../protocol'; import { validateConnectedClient, validateIsUInt4, validateNAddresses, validateStartPath, validateWallet, -} from '../shared/validators' +} from '../shared/validators'; import type { FirmwareConstants, GetAddressesRequestFunctionParams, Wallet, -} from '../types' -import { isValidAssetPath } from '../util' +} from '../types'; +import { isValidAssetPath } from '../util'; /** * `getAddresses` takes a starting path and a number to get the addresses or public keys associated @@ -32,14 +32,14 @@ export async function getAddresses({ iterIdx, }: GetAddressesRequestFunctionParams): Promise { const { url, sharedSecret, ephemeralPub, fwConstants } = - validateConnectedClient(client) - const activeWallet = validateWallet(client.getActiveWallet()) + validateConnectedClient(client); + const activeWallet = validateWallet(client.getActiveWallet()); const { startPath, n, flag } = validateGetAddressesRequest({ startPath: _startPath, n: _n, flag: _flag, - }) + }); const data = encodeGetAddressesRequest({ startPath, @@ -48,7 +48,7 @@ export async function getAddresses({ fwConstants, wallet: activeWallet, iterIdx, - }) + }); const { decryptedData, newEphemeralPub } = await encryptedSecureRequest({ data, @@ -56,13 +56,13 @@ export async function getAddresses({ sharedSecret, ephemeralPub, url, - }) + }); client.mutate({ ephemeralPub: newEphemeralPub, - }) + }); - return decodeGetAddressesResponse(decryptedData, flag) + return decodeGetAddressesResponse(decryptedData, flag); } export const validateGetAddressesRequest = ({ @@ -70,16 +70,16 @@ export const validateGetAddressesRequest = ({ n, flag, }: { - startPath?: number[] - n?: number - flag?: number + startPath?: number[]; + n?: number; + flag?: number; }) => { return { startPath: validateStartPath(startPath), n: validateNAddresses(n), flag: validateIsUInt4(flag), - } -} + }; +}; export const encodeGetAddressesRequest = ({ startPath, @@ -89,68 +89,68 @@ export const encodeGetAddressesRequest = ({ wallet, iterIdx, }: { - startPath: number[] - n: number - flag: number - fwConstants: FirmwareConstants - wallet: Wallet - iterIdx?: number + startPath: number[]; + n: number; + flag: number; + fwConstants: FirmwareConstants; + wallet: Wallet; + iterIdx?: number; }) => { - const flags = fwConstants.getAddressFlags || ([] as any[]) + const flags = fwConstants.getAddressFlags || ([] as any[]); const isPubkeyOnly = flags.indexOf(flag) > -1 && (flag === LatticeGetAddressesFlag.ed25519Pubkey || flag === LatticeGetAddressesFlag.secp256k1Pubkey || - flag === LatticeGetAddressesFlag.bls12_381Pubkey) - const isXpub = flag === LatticeGetAddressesFlag.secp256k1Xpub + flag === LatticeGetAddressesFlag.bls12_381Pubkey); + const isXpub = flag === LatticeGetAddressesFlag.secp256k1Xpub; if (!isPubkeyOnly && !isXpub && !isValidAssetPath(startPath, fwConstants)) { throw new Error( 'Derivation path or flag is not supported. Try updating Lattice firmware.', - ) + ); } // Ensure path depth is valid (2-5 indices) if (startPath.length < 2 || startPath.length > 5) { - throw new Error('Derivation path must include 2-5 indices.') + throw new Error('Derivation path must include 2-5 indices.'); } // Validate iterIdx (0-5) if (iterIdx < 0 || iterIdx > 5) { - throw new Error('Iteration index must be between 0 and 5.') + throw new Error('Iteration index must be between 0 and 5.'); } // Ensure iterIdx is not greater than path depth if (iterIdx > startPath.length) { - throw new Error('Iteration index cannot be greater than path depth.') + throw new Error('Iteration index cannot be greater than path depth.'); } - const sz = 32 + 1 + 20 + 1 // walletUID + pathDepth_IterIdx + 5 u32 indices + count/flag - const payload = Buffer.alloc(sz) - let off = 0 + const sz = 32 + 1 + 20 + 1; // walletUID + pathDepth_IterIdx + 5 u32 indices + count/flag + const payload = Buffer.alloc(sz); + let off = 0; // walletUID - wallet.uid.copy(payload, off) - off += 32 + wallet.uid.copy(payload, off); + off += 32; // pathDepth_IterIdx - const pathDepth_IterIdx = ((iterIdx & 0x0f) << 4) | (startPath.length & 0x0f) - payload.writeUInt8(pathDepth_IterIdx, off) - off += 1 + const pathDepth_IterIdx = ((iterIdx & 0x0f) << 4) | (startPath.length & 0x0f); + payload.writeUInt8(pathDepth_IterIdx, off); + off += 1; // Build the start path (5x u32 indices) for (let i = 0; i < 5; i++) { - const val = i < startPath.length ? startPath[i] : 0 - payload.writeUInt32BE(val, off) - off += 4 + const val = i < startPath.length ? startPath[i] : 0; + payload.writeUInt32BE(val, off); + off += 4; } // Combine count and flag into a single byte - const countVal = n & 0x0f - const flagVal = (flag & 0x0f) << 4 - payload.writeUInt8(countVal | flagVal, off) + const countVal = n & 0x0f; + const flagVal = (flag & 0x0f) << 4; + payload.writeUInt8(countVal | flagVal, off); - return payload -} + return payload; +}; /** * @internal * @return an array of address strings or pubkey buffers @@ -159,56 +159,56 @@ export const decodeGetAddressesResponse = ( data: Buffer, flag: number, ): Buffer[] => { - let off = 0 + let off = 0; const addressOffset = - flag === LatticeGetAddressesFlag.ed25519Pubkey ? 113 : 65 + flag === LatticeGetAddressesFlag.ed25519Pubkey ? 113 : 65; // Look for addresses until we reach the end (a 4 byte checksum) - const addrs: any[] = [] + const addrs: any[] = []; // Pubkeys are formatted differently in the response const arePubkeys = flag === LatticeGetAddressesFlag.secp256k1Pubkey || flag === LatticeGetAddressesFlag.ed25519Pubkey || - flag === LatticeGetAddressesFlag.bls12_381Pubkey + flag === LatticeGetAddressesFlag.bls12_381Pubkey; if (arePubkeys) { - off += 1 // skip uint8 representing pubkey type + off += 1; // skip uint8 representing pubkey type } const respDataLength = ProtocolConstants.msgSizes.secure.data.response.encrypted[ LatticeSecureEncryptedRequestType.getAddresses - ] + ]; while (off < respDataLength) { if (arePubkeys) { // Pubkeys are shorter and are returned as buffers - const pubBytes = data.slice(off, off + addressOffset) - const isEmpty = pubBytes.every((byte: number) => byte === 0x00) + const pubBytes = data.slice(off, off + addressOffset); + const isEmpty = pubBytes.every((byte: number) => byte === 0x00); if (!isEmpty && flag === LatticeGetAddressesFlag.ed25519Pubkey) { // ED25519 pubkeys are 32 bytes - addrs.push(pubBytes.slice(0, 32)) + addrs.push(pubBytes.slice(0, 32)); } else if (!isEmpty && flag === LatticeGetAddressesFlag.bls12_381Pubkey) { // BLS12_381_G1 keys are 48 bytes - addrs.push(pubBytes.slice(0, 48)) + addrs.push(pubBytes.slice(0, 48)); } else if (!isEmpty) { // Only other returned pubkeys are ECC, or 65 bytes Note that we return full // (uncompressed) ECC pubkeys - addrs.push(pubBytes) + addrs.push(pubBytes); } - off += addressOffset + off += addressOffset; } else { // Otherwise we are dealing with address strings or XPUB strings - const addrBytes = data.slice(off, off + ProtocolConstants.addrStrLen) - off += ProtocolConstants.addrStrLen + const addrBytes = data.slice(off, off + ProtocolConstants.addrStrLen); + off += ProtocolConstants.addrStrLen; // Return the UTF-8 representation - const len = addrBytes.indexOf(0) // First 0 is the null terminator + const len = addrBytes.indexOf(0); // First 0 is the null terminator if (len > 0) { const cleanStr = addrBytes .slice(0, len) .toString() // biome-ignore lint/suspicious/noControlCharactersInRegex: Intentional - stripping control characters - .replace(/[\u0000-\u001F\u007F-\u009F]/g, '') - addrs.push(cleanStr) + .replace(/[\u0000-\u001F\u007F-\u009F]/g, ''); + addrs.push(cleanStr); } } } - return addrs -} + return addrs; +}; diff --git a/packages/sdk/src/functions/getKvRecords.ts b/packages/sdk/src/functions/getKvRecords.ts index 595000e3..4c788592 100644 --- a/packages/sdk/src/functions/getKvRecords.ts +++ b/packages/sdk/src/functions/getKvRecords.ts @@ -1,13 +1,13 @@ import { LatticeSecureEncryptedRequestType, encryptedSecureRequest, -} from '../protocol' -import { validateConnectedClient } from '../shared/validators' +} from '../protocol'; +import { validateConnectedClient } from '../shared/validators'; import type { FirmwareConstants, GetKvRecordsData, GetKvRecordsRequestFunctionParams, -} from '../types' +} from '../types'; export async function getKvRecords({ client, @@ -16,16 +16,16 @@ export async function getKvRecords({ start: _start, }: GetKvRecordsRequestFunctionParams): Promise { const { url, sharedSecret, ephemeralPub, fwConstants } = - validateConnectedClient(client) + validateConnectedClient(client); const { type, n, start } = validateGetKvRequest({ type: _type, n: _n, start: _start, fwConstants, - }) + }); - const data = encodeGetKvRecordsRequest({ type, n, start }) + const data = encodeGetKvRecordsRequest({ type, n, start }); const { decryptedData, newEphemeralPub } = await encryptedSecureRequest({ data, @@ -33,13 +33,13 @@ export async function getKvRecords({ sharedSecret, ephemeralPub, url, - }) + }); client.mutate({ ephemeralPub: newEphemeralPub, - }) + }); - return decodeGetKvRecordsResponse(decryptedData, fwConstants) + return decodeGetKvRecordsResponse(decryptedData, fwConstants); } export const validateGetKvRequest = ({ @@ -48,82 +48,85 @@ export const validateGetKvRequest = ({ type, start, }: { - fwConstants: FirmwareConstants - n?: number - type?: number - start?: number + fwConstants: FirmwareConstants; + n?: number; + type?: number; + start?: number; }) => { if (!fwConstants.kvActionsAllowed) { - throw new Error('Unsupported. Please update firmware.') + throw new Error('Unsupported. Please update firmware.'); } if (!n || n < 1) { - throw new Error('You must request at least one record.') + throw new Error('You must request at least one record.'); } if (n > fwConstants.kvActionMaxNum) { throw new Error( `You may only request up to ${fwConstants.kvActionMaxNum} records at once.`, - ) + ); } if (type !== 0 && !type) { - throw new Error('You must specify a type.') + throw new Error('You must specify a type.'); } if (start !== 0 && !start) { - throw new Error('You must specify a type.') + throw new Error('You must specify a type.'); } - return { fwConstants, n, type, start } -} + return { fwConstants, n, type, start }; +}; export const encodeGetKvRecordsRequest = ({ type, n, start, }: { - type: number - n: number - start: number + type: number; + n: number; + start: number; }) => { - const payload = Buffer.alloc(9) - payload.writeUInt32LE(type, 0) - payload.writeUInt8(n, 4) - payload.writeUInt32LE(start, 5) - return payload -} + const payload = Buffer.alloc(9); + payload.writeUInt32LE(type, 0); + payload.writeUInt8(n, 4); + payload.writeUInt32LE(start, 5); + return payload; +}; export const decodeGetKvRecordsResponse = ( data: Buffer, fwConstants: FirmwareConstants, ) => { - let off = 0 - const nTotal = data.readUInt32BE(off) - off += 4 - const nFetched = Number.parseInt(data.slice(off, off + 1).toString('hex'), 16) - off += 1 + let off = 0; + const nTotal = data.readUInt32BE(off); + off += 4; + const nFetched = Number.parseInt( + data.slice(off, off + 1).toString('hex'), + 16, + ); + off += 1; if (nFetched > fwConstants.kvActionMaxNum) - throw new Error('Too many records fetched. Firmware error.') - const records: any = [] + throw new Error('Too many records fetched. Firmware error.'); + const records: any = []; for (let i = 0; i < nFetched; i++) { - const r: any = {} - r.id = data.readUInt32BE(off) - off += 4 - r.type = data.readUInt32BE(off) - off += 4 + const r: any = {}; + r.id = data.readUInt32BE(off); + off += 4; + r.type = data.readUInt32BE(off); + off += 4; r.caseSensitive = - Number.parseInt(data.slice(off, off + 1).toString('hex'), 16) === 1 - off += 1 - const keySz = Number.parseInt(data.slice(off, off + 1).toString('hex'), 16) - off += 1 - r.key = data.slice(off, off + keySz - 1).toString() - off += fwConstants.kvKeyMaxStrSz + 1 - const valSz = Number.parseInt(data.slice(off, off + 1).toString('hex'), 16) - off += 1 - r.val = data.slice(off, off + valSz - 1).toString() - off += fwConstants.kvValMaxStrSz + 1 - records.push(r) + Number.parseInt(data.slice(off, off + 1).toString('hex'), 16) === 1; + off += 1; + const keySz = Number.parseInt(data.slice(off, off + 1).toString('hex'), 16); + off += 1; + r.key = data.slice(off, off + keySz - 1).toString(); + off += fwConstants.kvKeyMaxStrSz + 1; + const valSz = Number.parseInt(data.slice(off, off + 1).toString('hex'), 16); + off += 1; + r.val = data.slice(off, off + valSz - 1).toString(); + off += fwConstants.kvValMaxStrSz + 1; + records.push(r); } return { records, total: nTotal, fetched: nFetched, - } -} + }; +}; diff --git a/packages/sdk/src/functions/index.ts b/packages/sdk/src/functions/index.ts index 9e4bc417..dea08a11 100644 --- a/packages/sdk/src/functions/index.ts +++ b/packages/sdk/src/functions/index.ts @@ -1,9 +1,9 @@ -export * from './addKvRecords' -export * from './connect' -export * from './fetchEncData' -export * from './fetchActiveWallet' -export * from './getAddresses' -export * from './getKvRecords' -export * from './pair' -export * from './removeKvRecords' -export * from './sign' +export * from './addKvRecords'; +export * from './connect'; +export * from './fetchEncData'; +export * from './fetchActiveWallet'; +export * from './getAddresses'; +export * from './getKvRecords'; +export * from './pair'; +export * from './removeKvRecords'; +export * from './sign'; diff --git a/packages/sdk/src/functions/pair.ts b/packages/sdk/src/functions/pair.ts index eb300d3d..aa119d0a 100644 --- a/packages/sdk/src/functions/pair.ts +++ b/packages/sdk/src/functions/pair.ts @@ -1,11 +1,11 @@ import { LatticeSecureEncryptedRequestType, encryptedSecureRequest, -} from '../protocol' -import { getPubKeyBytes } from '../shared/utilities' -import { validateConnectedClient } from '../shared/validators' -import type { KeyPair, PairRequestParams } from '../types' -import { generateAppSecret, toPaddedDER } from '../util' +} from '../protocol'; +import { getPubKeyBytes } from '../shared/utilities'; +import { validateConnectedClient } from '../shared/validators'; +import type { KeyPair, PairRequestParams } from '../types'; +import { generateAppSecret, toPaddedDER } from '../util'; /** * If a pairing secret is provided, `pair` uses it to sign a hash of the public key, name, and @@ -19,8 +19,8 @@ export async function pair({ pairingSecret, }: PairRequestParams): Promise { const { url, sharedSecret, ephemeralPub, appName, key } = - validateConnectedClient(client) - const data = encodePairRequest({ pairingSecret, key, appName }) + validateConnectedClient(client); + const data = encodePairRequest({ pairingSecret, key, appName }); const { newEphemeralPub } = await encryptedSecureRequest({ data, @@ -28,15 +28,15 @@ export async function pair({ sharedSecret, ephemeralPub, url, - }) + }); client.mutate({ ephemeralPub: newEphemeralPub, isPaired: true, - }) + }); - await client.fetchActiveWallet() - return client.hasActiveWallet() + await client.fetchActiveWallet(); + return client.hasActiveWallet(); } export const encodePairRequest = ({ @@ -44,27 +44,27 @@ export const encodePairRequest = ({ pairingSecret, appName, }: { - key: KeyPair - pairingSecret: string - appName: string + key: KeyPair; + pairingSecret: string; + appName: string; }) => { // Build the payload data - const pubKeyBytes = getPubKeyBytes(key) - const nameBuf = Buffer.alloc(25) + const pubKeyBytes = getPubKeyBytes(key); + const nameBuf = Buffer.alloc(25); if (pairingSecret.length > 0) { // If a pairing secret of zero length is passed in, it usually indicates we want to cancel // the pairing attempt. In this case we pass a zero-length name buffer so the firmware can // know not to draw the error screen. Note that we still expect an error to come back // (RESP_ERR_PAIR_FAIL) - nameBuf.write(appName) + nameBuf.write(appName); } const hash = generateAppSecret( pubKeyBytes, nameBuf, Buffer.from(pairingSecret), - ) - const sig = key.sign(hash) - const derSig = toPaddedDER(sig) - const payload = Buffer.concat([nameBuf, derSig]) - return payload -} + ); + const sig = key.sign(hash); + const derSig = toPaddedDER(sig); + const payload = Buffer.concat([nameBuf, derSig]); + return payload; +}; diff --git a/packages/sdk/src/functions/removeKvRecords.ts b/packages/sdk/src/functions/removeKvRecords.ts index 25c010e0..b78637eb 100644 --- a/packages/sdk/src/functions/removeKvRecords.ts +++ b/packages/sdk/src/functions/removeKvRecords.ts @@ -1,12 +1,12 @@ import { LatticeSecureEncryptedRequestType, encryptedSecureRequest, -} from '../protocol' -import { validateConnectedClient } from '../shared/validators' +} from '../protocol'; +import { validateConnectedClient } from '../shared/validators'; import type { FirmwareConstants, RemoveKvRecordsRequestFunctionParams, -} from '../types' +} from '../types'; /** * `removeKvRecords` takes in an array of ids and sends a request to remove them from the Lattice. @@ -19,19 +19,19 @@ export async function removeKvRecords({ ids: _ids, }: RemoveKvRecordsRequestFunctionParams): Promise { const { url, sharedSecret, ephemeralPub, fwConstants } = - validateConnectedClient(client) + validateConnectedClient(client); const { type, ids } = validateRemoveKvRequest({ fwConstants, type: _type, ids: _ids, - }) + }); const data = encodeRemoveKvRecordsRequest({ type, ids, fwConstants, - }) + }); const { decryptedData, newEphemeralPub } = await encryptedSecureRequest({ data, @@ -39,13 +39,13 @@ export async function removeKvRecords({ sharedSecret, ephemeralPub, url, - }) + }); client.mutate({ ephemeralPub: newEphemeralPub, - }) + }); - return decryptedData + return decryptedData; } export const validateRemoveKvRequest = ({ @@ -53,42 +53,42 @@ export const validateRemoveKvRequest = ({ type, ids, }: { - fwConstants: FirmwareConstants - type?: number - ids?: string[] + fwConstants: FirmwareConstants; + type?: number; + ids?: string[]; }) => { if (!fwConstants.kvActionsAllowed) { - throw new Error('Unsupported. Please update firmware.') + throw new Error('Unsupported. Please update firmware.'); } if (!Array.isArray(ids) || ids.length < 1) { - throw new Error('You must include one or more `ids` to removed.') + throw new Error('You must include one or more `ids` to removed.'); } if (ids.length > fwConstants.kvRemoveMaxNum) { throw new Error( `Only up to ${fwConstants.kvRemoveMaxNum} records may be removed at once.`, - ) + ); } if (type !== 0 && !type) { - throw new Error('You must specify a type.') + throw new Error('You must specify a type.'); } - return { type, ids } -} + return { type, ids }; +}; export const encodeRemoveKvRecordsRequest = ({ fwConstants, type, ids, }: { - fwConstants: FirmwareConstants - type: number - ids: string[] + fwConstants: FirmwareConstants; + type: number; + ids: string[]; }) => { - const payload = Buffer.alloc(5 + 4 * fwConstants.kvRemoveMaxNum) - payload.writeUInt32LE(type, 0) - payload.writeUInt8(ids.length, 4) + const payload = Buffer.alloc(5 + 4 * fwConstants.kvRemoveMaxNum); + payload.writeUInt32LE(type, 0); + payload.writeUInt8(ids.length, 4); for (let i = 0; i < ids.length; i++) { - const id = Number.parseInt(ids[i] as string) - payload.writeUInt32LE(id, 5 + 4 * i) + const id = Number.parseInt(ids[i] as string); + payload.writeUInt32LE(id, 5 + 4 * i); } - return payload -} + return payload; +}; diff --git a/packages/sdk/src/functions/sign.ts b/packages/sdk/src/functions/sign.ts index b4959665..fafbbb3f 100644 --- a/packages/sdk/src/functions/sign.ts +++ b/packages/sdk/src/functions/sign.ts @@ -1,16 +1,16 @@ -import { Hash } from 'ox' -import type { Address, Hex } from 'viem' -import bitcoin from '../bitcoin' -import { CURRENCIES } from '../constants' -import ethereum from '../ethereum' -import { parseGenericSigningResponse } from '../genericSigning' +import { Hash } from 'ox'; +import type { Address, Hex } from 'viem'; +import bitcoin from '../bitcoin'; +import { CURRENCIES } from '../constants'; +import ethereum from '../ethereum'; +import { parseGenericSigningResponse } from '../genericSigning'; import { LatticeSecureEncryptedRequestType, LatticeSignSchema, encryptedSecureRequest, -} from '../protocol' -import { buildTransaction } from '../shared/functions' -import { validateConnectedClient, validateWallet } from '../shared/validators' +} from '../protocol'; +import { buildTransaction } from '../shared/functions'; +import { validateConnectedClient, validateWallet } from '../shared/validators'; import type { BitcoinSignRequest, DecodeSignResponseParams, @@ -19,8 +19,8 @@ import type { SignRequest, SignRequestFunctionParams, SigningRequestResponse, -} from '../types' -import { parseDER } from '../util' +} from '../types'; +import { parseDER } from '../util'; /** * `sign` builds and sends a request for signing to the device. @@ -36,14 +36,14 @@ export async function sign({ }: SignRequestFunctionParams): Promise { try { const { url, sharedSecret, ephemeralPub, fwConstants } = - validateConnectedClient(client) - const wallet = validateWallet(client.getActiveWallet()) + validateConnectedClient(client); + const wallet = validateWallet(client.getActiveWallet()); const { requestData, isGeneric } = buildTransaction({ data, currency, fwConstants, - }) + }); const { payload, hasExtraPayloads } = encodeSignRequest({ fwConstants, @@ -51,7 +51,7 @@ export async function sign({ requestData, cachedData, nextCode, - }) + }); const { decryptedData, newEphemeralPub } = await encryptedSecureRequest({ data: payload, @@ -59,11 +59,11 @@ export async function sign({ sharedSecret, ephemeralPub, url, - }) + }); client.mutate({ ephemeralPub: newEphemeralPub, - }) + }); // If this request has multiple payloads, we need to recurse // so that we can make the next request. @@ -74,7 +74,7 @@ export async function sign({ currency, cachedData: requestData, nextCode: decryptedData.slice(0, 8), - }) + }); } // If this is the only (or final) request, // decode response data and return @@ -83,9 +83,9 @@ export async function sign({ request: requestData as SignRequest, isGeneric, currency, - }) + }); - return decodedResponse + return decodedResponse; } catch (err) { console.error('Error signing transaction:', { message: err.message, @@ -96,8 +96,8 @@ export async function sign({ }, (_key, value) => (typeof value === 'bigint' ? value.toString() : value), ), - }) - throw err + }); + throw err; } } @@ -108,54 +108,54 @@ export const encodeSignRequest = ({ cachedData, nextCode, }: EncodeSignRequestParams) => { - let reqPayload: Buffer - let schema: number - let hasExtraPayloads = 0 + let reqPayload: Buffer; + let schema: number; + let hasExtraPayloads = 0; const typedRequestData = requestData as SignRequest & { - extraDataPayloads?: Buffer[] - } + extraDataPayloads?: Buffer[]; + }; if (cachedData && nextCode) { const typedCachedData = cachedData as SignRequest & { - extraDataPayloads: Buffer[] - } - const nextExtraPayload = typedCachedData.extraDataPayloads.shift() + extraDataPayloads: Buffer[]; + }; + const nextExtraPayload = typedCachedData.extraDataPayloads.shift(); if (!nextExtraPayload) { throw new Error( 'No cached extra payload available for multipart sign request.', - ) + ); } if (typedRequestData.extraDataPayloads) { - typedRequestData.extraDataPayloads = typedCachedData.extraDataPayloads + typedRequestData.extraDataPayloads = typedCachedData.extraDataPayloads; } - reqPayload = Buffer.concat([nextCode, nextExtraPayload]) - schema = LatticeSignSchema.extraData + reqPayload = Buffer.concat([nextCode, nextExtraPayload]); + schema = LatticeSignSchema.extraData; hasExtraPayloads = Number( (typedCachedData.extraDataPayloads?.length ?? 0) > 0, - ) + ); } else { - reqPayload = typedRequestData.payload - schema = typedRequestData.schema + reqPayload = typedRequestData.payload; + schema = typedRequestData.schema; hasExtraPayloads = Number( (typedRequestData.extraDataPayloads?.length ?? 0) > 0, - ) + ); } - const payload = Buffer.alloc(2 + fwConstants.reqMaxDataSz) - let off = 0 + const payload = Buffer.alloc(2 + fwConstants.reqMaxDataSz); + let off = 0; - payload.writeUInt8(hasExtraPayloads, off) - off += 1 + payload.writeUInt8(hasExtraPayloads, off); + off += 1; // Copy request schema (e.g. ETH or BTC transfer) - payload.writeUInt8(schema, off) - off += 1 + payload.writeUInt8(schema, off); + off += 1; // Copy the wallet UID - wallet.uid?.copy(payload, off) - off += wallet.uid?.length ?? 0 + wallet.uid?.copy(payload, off); + off += wallet.uid?.length ?? 0; // Build data based on the type of request - reqPayload.copy(payload, off) - return { payload, hasExtraPayloads } -} + reqPayload.copy(payload, off); + return { payload, hasExtraPayloads }; +}; export const decodeSignResponse = ({ data, @@ -163,62 +163,62 @@ export const decodeSignResponse = ({ isGeneric, currency, }: DecodeSignResponseParams): SignData => { - let off = 0 - const derSigLen = 74 // DER signatures are 74 bytes + let off = 0; + const derSigLen = 74; // DER signatures are 74 bytes if (currency === CURRENCIES.BTC) { - const btcRequest = request as BitcoinSignRequest - const pkhLen = 20 // Pubkeyhashes are 20 bytes - const sigsLen = 760 // Up to 10x DER signatures + const btcRequest = request as BitcoinSignRequest; + const pkhLen = 20; // Pubkeyhashes are 20 bytes + const sigsLen = 760; // Up to 10x DER signatures const changeVersion = bitcoin.getAddressFormat( btcRequest.origData.changePath, - ) - const changePubKeyHash = data.slice(off, off + pkhLen) - off += pkhLen + ); + const changePubKeyHash = data.slice(off, off + pkhLen); + off += pkhLen; const changeRecipient = bitcoin.getBitcoinAddress( changePubKeyHash, changeVersion, - ) - const compressedPubLength = 33 // Size of compressed public key - const pubkeys = [] as any[] - const sigs = [] as any[] - let n = 0 + ); + const compressedPubLength = 33; // Size of compressed public key + const pubkeys = [] as any[]; + const sigs = [] as any[]; + let n = 0; // Parse the signature for each output -- they are returned in the serialized payload in form // [pubkey, sig] There is one signature per output while (off < data.length) { // Exit out if we have seen all the returned sigs and pubkeys - if (data[off] !== 0x30) break + if (data[off] !== 0x30) break; // Otherwise grab another set Note that all DER sigs returned fill the maximum 74 byte // buffer, but also contain a length at off+1, which we use to parse the non-zero data. // First get the signature from its slot - const sigStart = off - const sigEnd = off + 2 + data[off + 1] - sigs.push(data.slice(sigStart, sigEnd)) - off += derSigLen + const sigStart = off; + const sigEnd = off + 2 + data[off + 1]; + sigs.push(data.slice(sigStart, sigEnd)); + off += derSigLen; // Next, shift by the full set of signatures to hit the respective pubkey NOTE: The data // returned is: [, , ... ][, , ... ] - const pubStart = n * compressedPubLength + sigsLen - const pubEnd = (n + 1) * compressedPubLength + sigsLen - pubkeys.push(data.slice(pubStart, pubEnd)) + const pubStart = n * compressedPubLength + sigsLen; + const pubEnd = (n + 1) * compressedPubLength + sigsLen; + pubkeys.push(data.slice(pubStart, pubEnd)); // Update offset to hit the next signature slot - n += 1 + n += 1; } // Build the transaction data to be serialized const preSerializedData: any = { inputs: [], outputs: [], - } + }; // First output comes from request dta preSerializedData.outputs.push({ value: btcRequest.origData.value, recipient: btcRequest.origData.recipient, - }) + }); if (btcRequest.changeData?.value && btcRequest.changeData.value > 0) { // Second output comes from change data preSerializedData.outputs.push({ value: btcRequest.changeData.value, recipient: changeRecipient, - }) + }); } // Add the inputs @@ -229,29 +229,29 @@ export const decodeSignResponse = ({ sig: sigs[i], pubkey: pubkeys[i], signerPath: btcRequest.origData.prevOuts[i].signerPath, - }) + }); } // Finally, serialize the transaction - const serializedTx = bitcoin.serializeTx(preSerializedData) + const serializedTx = bitcoin.serializeTx(preSerializedData); // Generate the transaction hash so the user can look this transaction up later - const preImageTxHash = serializedTx + const preImageTxHash = serializedTx; const txHashPre: Buffer = Buffer.from( Hash.sha256(Buffer.from(preImageTxHash, 'hex')), - ) + ); // Add extra data for debugging/lookup purposes return { tx: serializedTx, txHash: `0x${Buffer.from(Hash.sha256(txHashPre)).toString('hex')}` as Hex, changeRecipient, sigs, - } + }; } else if (currency === CURRENCIES.ETH && !isGeneric) { - const sig = parseDER(data.slice(off, off + 2 + data[off + 1])) - off += derSigLen - const ethAddr = data.slice(off, off + 20) + const sig = parseDER(data.slice(off, off + 2 + data[off + 1])); + off += derSigLen; + const ethAddr = data.slice(off, off + 20); // Determine the `v` param and add it to the sig before returning - const result = ethereum.buildEthRawTx(request, sig, ethAddr) + const result = ethereum.buildEthRawTx(request, sig, ethAddr); // Handle both object and string returns from buildEthRawTx if (typeof result === 'string') { @@ -267,7 +267,7 @@ export const decodeSignResponse = ({ s: `0x${''}` as Hex, }, signer: `0x${ethAddr.toString('hex')}` as Address, - } + }; } else { // Normal transactions return object with rawTx and sigWithV const response: SignData = { @@ -279,22 +279,22 @@ export const decodeSignResponse = ({ s: `0x${result.sigWithV.s.toString('hex')}` as Hex, }, signer: `0x${ethAddr.toString('hex')}` as Address, - } + }; // Add normalized viem-compatible signed transaction if original transaction data is available // Note: For now, skip viem transaction normalization due to interface compatibility // This can be added back when proper transaction data is available - return response + return response; } } else if (currency === CURRENCIES.ETH_MSG) { - const sig = parseDER(data.slice(off, off + 2 + data[off + 1])) - off += derSigLen - const signer = data.slice(off, off + 20) + const sig = parseDER(data.slice(off, off + 2 + data[off + 1])); + off += derSigLen; + const signer = data.slice(off, off + 20); const validatedSig = ethereum.validateEthereumMsgResponse( { signer, sig }, request, - ) + ); return { sig: { v: BigInt(`0x${validatedSig.v.toString('hex')}`), @@ -302,9 +302,9 @@ export const decodeSignResponse = ({ s: `0x${validatedSig.s.toString('hex')}` as Hex, }, signer: `0x${signer.toString('hex')}` as Address, - } + }; } else { // Generic signing request - return parseGenericSigningResponse(data, off, request) + return parseGenericSigningResponse(data, off, request); } -} +}; diff --git a/packages/sdk/src/genericSigning.ts b/packages/sdk/src/genericSigning.ts index 122c60a1..baf80d16 100644 --- a/packages/sdk/src/genericSigning.ts +++ b/packages/sdk/src/genericSigning.ts @@ -1,4 +1,4 @@ -import { RLP } from '@ethereumjs/rlp' +import { RLP } from '@ethereumjs/rlp'; /** Generic signing module. Any payload can be sent to the Lattice and will be displayed in full (note that \n and \t characters will be @@ -9,17 +9,17 @@ This payload should be coupled with: * Curve on which to derive the signing key * Hash function to use on the message */ -import { Hash } from 'ox' +import { Hash } from 'ox'; import { type Hex, type TransactionSerializable, parseTransaction, serializeTransaction, -} from 'viem' +} from 'viem'; // keccak256 now imported from ox via Hash module -import { HARDENED_OFFSET } from './constants' -import { Constants } from './index' -import { LatticeSignSchema } from './protocol' +import { HARDENED_OFFSET } from './constants'; +import { Constants } from './index'; +import { LatticeSignSchema } from './protocol'; import { buildSignerPathBuf, existsIn, @@ -28,7 +28,7 @@ import { getYParity, parseDER, splitFrames, -} from './util' +} from './util'; export const buildGenericSigningMsgRequest = (req) => { const { @@ -40,14 +40,14 @@ export const buildGenericSigningMsgRequest = (req) => { omitPubkey = false, fwConstants, blsDst = Constants.SIGNING.BLS_DST.BLS_DST_NUL, - } = req + } = req; const { extraDataFrameSz, extraDataMaxFrames, prehashAllowed, genericSigning, varAddrPathSzAllowed, - } = fwConstants + } = fwConstants; const { curveTypes, encodingTypes, @@ -55,127 +55,130 @@ export const buildGenericSigningMsgRequest = (req) => { baseDataSz, baseReqSz, calldataDecoding, - } = genericSigning + } = genericSigning; const encodedPayload = getEncodedPayload( req.payload, encodingType, encodingTypes, - ) - const { encoding } = encodedPayload - let { payloadBuf } = encodedPayload - const origPayloadBuf = payloadBuf - let payloadDataSz = payloadBuf.length + ); + const { encoding } = encodedPayload; + let { payloadBuf } = encodedPayload; + const origPayloadBuf = payloadBuf; + let payloadDataSz = payloadBuf.length; // Size of data payload that can be included in the first/base request - const maxExpandedSz = baseDataSz + extraDataMaxFrames * extraDataFrameSz + const maxExpandedSz = baseDataSz + extraDataMaxFrames * extraDataFrameSz; // Sanity checks if (!payloadDataSz) { - throw new Error('Payload could not be handled.') + throw new Error('Payload could not be handled.'); } else if ( !genericSigning || !extraDataFrameSz || !extraDataMaxFrames || !prehashAllowed ) { - throw new Error('Unsupported. Please update your Lattice firmware.') + throw new Error('Unsupported. Please update your Lattice firmware.'); } else if (!existsIn(curveType, curveTypes)) { - throw new Error('Unsupported curve type.') + throw new Error('Unsupported curve type.'); } else if (!existsIn(hashType, hashTypes)) { - throw new Error('Unsupported hash type.') + throw new Error('Unsupported hash type.'); } // If there is a decoder attached to our payload, add it to // the data field of the request. const hasDecoder = - decoder && calldataDecoding && decoder.length <= calldataDecoding.maxSz + decoder && calldataDecoding && decoder.length <= calldataDecoding.maxSz; // Make sure the payload AND decoder data fits in the firmware buffer. // If it doesn't, we can't include the decoder because the payload will likely // be pre-hashed and the decoder data isn't part of the message to sign. const decoderFits = - hasDecoder && payloadBuf.length + decoder.length <= maxExpandedSz + hasDecoder && payloadBuf.length + decoder.length <= maxExpandedSz; if (hasDecoder && decoderFits) { - const decoderBuf = Buffer.alloc(8 + decoder.length) + const decoderBuf = Buffer.alloc(8 + decoder.length); // First write th reserved word - decoderBuf.writeUInt32LE(calldataDecoding.reserved, 0) + decoderBuf.writeUInt32LE(calldataDecoding.reserved, 0); // Then write size, then the data - decoderBuf.writeUInt32LE(decoder.length, 4) - Buffer.from(decoder).copy(decoderBuf, 8) - payloadBuf = Buffer.concat([payloadBuf, decoderBuf]) + decoderBuf.writeUInt32LE(decoder.length, 4); + Buffer.from(decoder).copy(decoderBuf, 8); + payloadBuf = Buffer.concat([payloadBuf, decoderBuf]); } // Ed25519 specific sanity checks if (curveType === curveTypes.ED25519) { if (hashType !== hashTypes.NONE) { - throw new Error('Signing on ed25519 requires unhashed message') + throw new Error('Signing on ed25519 requires unhashed message'); } signerPath.forEach((idx) => { if (idx < HARDENED_OFFSET) { throw new Error( 'Signing on ed25519 requires all signer path indices be hardened.', - ) + ); } - }) + }); } // BLS12_381 specific processing else if (curveType === curveTypes.BLS12_381_G2) { // For BLS signing we need to prefix 4 bytes to represent the // domain separator (DST). If none is provided, we use the default // value of DST_NUL. - const blsDstBuf = Buffer.alloc(4) - blsDstBuf.writeUInt32LE(blsDst) - payloadBuf = Buffer.concat([blsDstBuf, payloadBuf]) - payloadDataSz += blsDstBuf.length + const blsDstBuf = Buffer.alloc(4); + blsDstBuf.writeUInt32LE(blsDst); + payloadBuf = Buffer.concat([blsDstBuf, payloadBuf]); + payloadDataSz += blsDstBuf.length; } // Build the request buffer with metadata and then the payload to sign. - const buf = Buffer.alloc(baseReqSz) - let off = 0 - buf.writeUInt32LE(encoding, off) - off += 4 - buf.writeUInt8(hashType, off) - off += 1 - buf.writeUInt8(curveType, off) - off += 1 - const signerPathBuf = buildSignerPathBuf(signerPath, varAddrPathSzAllowed) - signerPathBuf.copy(buf, off) - off += signerPathBuf.length - buf.writeUInt8(omitPubkey ? 1 : 0, off) - off += 1 + const buf = Buffer.alloc(baseReqSz); + let off = 0; + buf.writeUInt32LE(encoding, off); + off += 4; + buf.writeUInt8(hashType, off); + off += 1; + buf.writeUInt8(curveType, off); + off += 1; + const signerPathBuf = buildSignerPathBuf(signerPath, varAddrPathSzAllowed); + signerPathBuf.copy(buf, off); + off += signerPathBuf.length; + buf.writeUInt8(omitPubkey ? 1 : 0, off); + off += 1; // Flow data into extraData requests if applicable - const extraDataPayloads = [] - let prehash = null + const extraDataPayloads = []; + let prehash = null; - let didPrehash = false + let didPrehash = false; if (payloadBuf.length > baseDataSz) { if (prehashAllowed && payloadBuf.length > maxExpandedSz) { // If we prehash, we need to provide the full payload size - buf.writeUInt16LE(payloadBuf.length, off) - off += 2 - didPrehash = true + buf.writeUInt16LE(payloadBuf.length, off); + off += 2; + didPrehash = true; // If we have to prehash, only hash the actual payload data, i.e. exclude // any optional calldata decoder data. - const payloadData = payloadBuf.slice(0, payloadDataSz) + const payloadData = payloadBuf.slice(0, payloadDataSz); // If this payload is too large to send, but the Lattice allows a prehashed message, do that if (hashType === hashTypes.NONE) { // This cannot be done for ED25519 signing, which must sign the full message throw new Error( 'Message too large to send and could not be prehashed (hashType=NONE).', - ) + ); } else if (hashType === hashTypes.KECCAK256) { - prehash = Buffer.from(Hash.keccak256(payloadData)) + prehash = Buffer.from(Hash.keccak256(payloadData)); } else if (hashType === hashTypes.SHA256) { - prehash = Buffer.from(Hash.sha256(payloadData)) + prehash = Buffer.from(Hash.sha256(payloadData)); } else { - throw new Error('Unsupported hash type.') + throw new Error('Unsupported hash type.'); } } else { // Split overflow data into extraData frames - const frames = splitFrames(payloadBuf.slice(baseDataSz), extraDataFrameSz) + const frames = splitFrames( + payloadBuf.slice(baseDataSz), + extraDataFrameSz, + ); frames.forEach((frame) => { - const szLE = Buffer.alloc(4) - szLE.writeUInt32LE(frame.length, 0) - extraDataPayloads.push(Buffer.concat([szLE, frame])) - }) + const szLE = Buffer.alloc(4); + szLE.writeUInt32LE(frame.length, 0); + extraDataPayloads.push(Buffer.concat([szLE, frame])); + }); } } @@ -183,15 +186,15 @@ export const buildGenericSigningMsgRequest = (req) => { // Set the payload size to only include message data. This will inform firmware // where to slice off calldata info. if (!didPrehash) { - buf.writeUInt16LE(payloadDataSz, off) - off += 2 + buf.writeUInt16LE(payloadDataSz, off); + off += 2; } // If the message had to be prehashed, we will only copy the hash data into the request. // Otherwise copy as many payload bytes into the request as possible. Follow up data // from `frames` will come in follow up requests. - const toCopy = prehash ? prehash : payloadBuf - toCopy.copy(buf, off) + const toCopy = prehash ? prehash : payloadBuf; + toCopy.copy(buf, off); // Return all the necessary data return { @@ -203,153 +206,153 @@ export const buildGenericSigningMsgRequest = (req) => { hashType, omitPubkey, origPayloadBuf, - } -} + }; +}; export const parseGenericSigningResponse = (res, off, req) => { const parsed = { pubkey: null, sig: null, - } - let digestFromResponse: Buffer | undefined + }; + let digestFromResponse: Buffer | undefined; // Parse BIP44 path // Parse pubkey and then sig if (req.curveType === Constants.SIGNING.CURVES.SECP256K1) { // Handle `GpEccPubkey256_t` if (!req.omitPubkey) { - const compression = res.readUInt8(off) - off += 1 + const compression = res.readUInt8(off); + off += 1; if (compression === 0x02 || compression === 0x03) { // Compressed key - only copy x - parsed.pubkey = Buffer.alloc(33) - parsed.pubkey.writeUInt8(compression, 0) - res.slice(off, off + 32).copy(parsed.pubkey, 1) + parsed.pubkey = Buffer.alloc(33); + parsed.pubkey.writeUInt8(compression, 0); + res.slice(off, off + 32).copy(parsed.pubkey, 1); } else if (compression === 0x04) { // Uncompressed key - parsed.pubkey = Buffer.alloc(65) - parsed.pubkey.writeUInt8(compression, 0) - res.slice(off).copy(parsed.pubkey, 1) + parsed.pubkey = Buffer.alloc(65); + parsed.pubkey.writeUInt8(compression, 0); + res.slice(off).copy(parsed.pubkey, 1); } else { - throw new Error('Bad compression byte in signing response.') + throw new Error('Bad compression byte in signing response.'); } - off += 64 + off += 64; } else { // Skip pubkey section - off += 65 + off += 65; } // Handle `GpECDSASig_t` - const sigLength = 2 + res[off + 1] - const derSlice = res.slice(off, off + sigLength) - const derSig = parseDER(derSlice) + const sigLength = 2 + res[off + 1]; + const derSlice = res.slice(off, off + sigLength); + const derSig = parseDER(derSlice); // Remove any leading zeros in signature components to ensure // the result is a 64 byte sig - const rBuf = fixLen(derSig.r, 32) - const sBuf = fixLen(derSig.s, 32) + const rBuf = fixLen(derSig.r, 32); + const sBuf = fixLen(derSig.s, 32); parsed.sig = { r: `0x${rBuf.toString('hex')}`, s: `0x${sBuf.toString('hex')}`, - } - off += sigLength + }; + off += sigLength; if (res.length >= off + 32) { - digestFromResponse = Buffer.from(res.slice(off, off + 32)) - off += 32 + digestFromResponse = Buffer.from(res.slice(off, off + 32)); + off += 32; } if (req.encodingType === Constants.SIGNING.ENCODINGS.EVM) { // Full EVM transaction - use getV for proper chainId/EIP-155 handling - const vBn = getV(req.origPayloadBuf, parsed) - parsed.sig.v = BigInt(vBn.toString()) - populateViemSignedTx(parsed.sig.v, req, parsed) + const vBn = getV(req.origPayloadBuf, parsed); + parsed.sig.v = BigInt(vBn.toString()); + populateViemSignedTx(parsed.sig.v, req, parsed); } else if ( req.hashType === Constants.SIGNING.HASHES.KECCAK256 && req.encodingType !== Constants.SIGNING.ENCODINGS.EVM ) { // Generic Keccak256 message - determine if it looks like a transaction - let isTransaction = false + let isTransaction = false; try { - let bufferToDecode = req.origPayloadBuf + let bufferToDecode = req.origPayloadBuf; // Try to skip EIP-2718 type byte if present if (bufferToDecode[0] <= 0x7f) { - bufferToDecode = bufferToDecode.slice(1) + bufferToDecode = bufferToDecode.slice(1); } - const decoded = RLP.decode(bufferToDecode) + const decoded = RLP.decode(bufferToDecode); // A legacy transaction has 9 fields (or 6 if pre-EIP155) - isTransaction = Array.isArray(decoded) && decoded.length >= 6 + isTransaction = Array.isArray(decoded) && decoded.length >= 6; } catch { - isTransaction = false + isTransaction = false; } if (isTransaction) { try { // If it looks like a transaction, use the robust getV - const vBn = getV(req.origPayloadBuf, parsed) - parsed.sig.v = BigInt(vBn.toString()) - populateViemSignedTx(parsed.sig.v, req, parsed) + const vBn = getV(req.origPayloadBuf, parsed); + parsed.sig.v = BigInt(vBn.toString()); + populateViemSignedTx(parsed.sig.v, req, parsed); } catch (err) { console.error( 'Failed to get V from transaction, using fallback:', err, - ) + ); // Fall back to simple recovery if getV fails (e.g., malformed RLP) // Use the correct hash type specified in the request - const msgHash = computeMessageHash(req, digestFromResponse) + const msgHash = computeMessageHash(req, digestFromResponse); const yParity = getYParity({ messageHash: msgHash, signature: parsed.sig, publicKey: parsed.pubkey, - }) - parsed.sig.v = BigInt(27 + yParity) + }); + parsed.sig.v = BigInt(27 + yParity); } } else { // Generic message - use simple recovery (v = 27 + recoveryId) // Use the correct hash type specified in the request - const msgHash = computeMessageHash(req, digestFromResponse) + const msgHash = computeMessageHash(req, digestFromResponse); const yParity = getYParity({ messageHash: msgHash, signature: parsed.sig, publicKey: parsed.pubkey, - }) - parsed.sig.v = BigInt(27 + yParity) + }); + parsed.sig.v = BigInt(27 + yParity); } } } else if (req.curveType === Constants.SIGNING.CURVES.ED25519) { if (!req.omitPubkey) { // Handle `GpEdDSAPubkey_t` - parsed.pubkey = Buffer.alloc(32) - res.slice(off, off + 32).copy(parsed.pubkey) + parsed.pubkey = Buffer.alloc(32); + res.slice(off, off + 32).copy(parsed.pubkey); } - off += 32 + off += 32; // Handle `GpEdDSASig_t` parsed.sig = { r: `0x${res.slice(off, off + 32).toString('hex')}`, s: `0x${res.slice(off + 32, off + 64).toString('hex')}`, - } - off += 64 + }; + off += 64; } else if (req.curveType === Constants.SIGNING.CURVES.BLS12_381_G2) { if (!req.omitPubkey) { // Handle `GpBLS12_381_G1Pub_t` - parsed.pubkey = Buffer.alloc(48) - res.slice(off, off + 48).copy(parsed.pubkey) + parsed.pubkey = Buffer.alloc(48); + res.slice(off, off + 48).copy(parsed.pubkey); } - off += 48 + off += 48; // Handle `GpBLS12_381_G2Sig_t` - parsed.sig = Buffer.alloc(96) - res.slice(off, off + 96).copy(parsed.sig) - off += 96 + parsed.sig = Buffer.alloc(96); + res.slice(off, off + 96).copy(parsed.sig); + off += 96; } else { - throw new Error('Unsupported curve.') + throw new Error('Unsupported curve.'); } - return parsed -} + return parsed; +}; function computeMessageHash( req: { - hashType: number - origPayloadBuf: Buffer + hashType: number; + origPayloadBuf: Buffer; }, digestFromResponse?: Buffer, ): Buffer { @@ -358,15 +361,15 @@ function computeMessageHash( digestFromResponse.length === 32 && digestFromResponse.some((byte) => byte !== 0) ) { - return digestFromResponse + return digestFromResponse; } if (req.hashType === Constants.SIGNING.HASHES.SHA256) { - return Buffer.from(Hash.sha256(req.origPayloadBuf)) + return Buffer.from(Hash.sha256(req.origPayloadBuf)); } if (req.hashType === Constants.SIGNING.HASHES.KECCAK256) { - return Buffer.from(Hash.keccak256(req.origPayloadBuf)) + return Buffer.from(Hash.keccak256(req.origPayloadBuf)); } - throw new Error('Unsupported hash type for message hash computation.') + throw new Error('Unsupported hash type for message hash computation.'); } // Reconstruct a viem-compatible signed transaction string from the raw payload and @@ -376,11 +379,11 @@ function populateViemSignedTx( req: any, parsed: { sig: { r: string; s: string; v?: bigint }; viemTx?: string }, ) { - if (req.encodingType !== Constants.SIGNING.ENCODINGS.EVM) return + if (req.encodingType !== Constants.SIGNING.ENCODINGS.EVM) return; try { - const rawTxHex = `0x${req.origPayloadBuf.toString('hex')}` as Hex - const parsedTx: any = parseTransaction(rawTxHex) + const rawTxHex = `0x${req.origPayloadBuf.toString('hex')}` as Hex; + const parsedTx: any = parseTransaction(rawTxHex); const baseTx: any = { chainId: parsedTx.chainId, @@ -389,26 +392,26 @@ function populateViemSignedTx( data: (parsedTx.data ?? '0x') as Hex, nonce: parsedTx.nonce ?? 0n, gas: parsedTx.gas ?? parsedTx.gasLimit ?? 0n, - } + }; if (parsedTx.maxFeePerGas !== undefined) { - baseTx.maxFeePerGas = parsedTx.maxFeePerGas + baseTx.maxFeePerGas = parsedTx.maxFeePerGas; } if (parsedTx.maxPriorityFeePerGas !== undefined) { - baseTx.maxPriorityFeePerGas = parsedTx.maxPriorityFeePerGas + baseTx.maxPriorityFeePerGas = parsedTx.maxPriorityFeePerGas; } if (parsedTx.gasPrice !== undefined) { - baseTx.gasPrice = parsedTx.gasPrice + baseTx.gasPrice = parsedTx.gasPrice; } if (parsedTx.accessList !== undefined) { - baseTx.accessList = parsedTx.accessList + baseTx.accessList = parsedTx.accessList; } if (parsedTx.authorizationList !== undefined) { - baseTx.authorizationList = parsedTx.authorizationList + baseTx.authorizationList = parsedTx.authorizationList; } if (parsedTx.type !== undefined && parsedTx.type !== null) { - baseTx.type = parsedTx.type + baseTx.type = parsedTx.type; } const signature = @@ -422,39 +425,39 @@ function populateViemSignedTx( yParity: Number(sigV), r: parsed.sig.r as Hex, s: parsed.sig.s as Hex, - } + }; parsed.viemTx = serializeTransaction( baseTx as TransactionSerializable, signature as any, - ) + ); } catch (_err) { - console.debug('Failed to build viemTx from response', _err) + console.debug('Failed to build viemTx from response', _err); } } export const getEncodedPayload = (payload, encoding, allowedEncodings) => { if (!encoding) { - encoding = Constants.SIGNING.ENCODINGS.NONE + encoding = Constants.SIGNING.ENCODINGS.NONE; } // Make sure the encoding type specified is supported by firmware if (!existsIn(encoding, allowedEncodings)) { throw new Error( 'Encoding not supported by Lattice firmware. You may want to update.', - ) + ); } - let payloadBuf: Buffer + let payloadBuf: Buffer; if (!payload) { - throw new Error('No payload included') + throw new Error('No payload included'); } if (typeof payload === 'string' && payload.slice(0, 2) === '0x') { - payloadBuf = Buffer.from(payload.slice(2), 'hex') + payloadBuf = Buffer.from(payload.slice(2), 'hex'); } else { - payloadBuf = Buffer.from(payload) + payloadBuf = Buffer.from(payload); } // Build the request with the specified encoding type return { payloadBuf, encoding, - } -} + }; +}; diff --git a/packages/sdk/src/index.ts b/packages/sdk/src/index.ts index a3095dc3..8dfc3e0d 100644 --- a/packages/sdk/src/index.ts +++ b/packages/sdk/src/index.ts @@ -1,5 +1,5 @@ -export { CALLDATA as Calldata } from './calldata/index' -export { Client } from './client' -export { EXTERNAL as Constants } from './constants' -export { EXTERNAL as Utils } from './util' -export * from './api' +export { CALLDATA as Calldata } from './calldata/index'; +export { Client } from './client'; +export { EXTERNAL as Constants } from './constants'; +export { EXTERNAL as Utils } from './util'; +export * from './api'; diff --git a/packages/sdk/src/protocol/index.ts b/packages/sdk/src/protocol/index.ts index 3109ca44..71228be3 100644 --- a/packages/sdk/src/protocol/index.ts +++ b/packages/sdk/src/protocol/index.ts @@ -1,2 +1,2 @@ -export * from './latticeConstants' -export * from './secureMessages' +export * from './latticeConstants'; +export * from './secureMessages'; diff --git a/packages/sdk/src/protocol/latticeConstants.ts b/packages/sdk/src/protocol/latticeConstants.ts index e2e8e351..041564aa 100644 --- a/packages/sdk/src/protocol/latticeConstants.ts +++ b/packages/sdk/src/protocol/latticeConstants.ts @@ -201,4 +201,4 @@ export const ProtocolConstants = { }, }, }, -} as const +} as const; diff --git a/packages/sdk/src/protocol/secureMessages.ts b/packages/sdk/src/protocol/secureMessages.ts index 3d514d75..5a7acf59 100644 --- a/packages/sdk/src/protocol/secureMessages.ts +++ b/packages/sdk/src/protocol/secureMessages.ts @@ -1,5 +1,5 @@ -import { getEphemeralId, request } from '../shared/functions' -import { validateEphemeralPub } from '../shared/validators' +import { getEphemeralId, request } from '../shared/functions'; +import { validateEphemeralPub } from '../shared/validators'; import type { DecryptedResponse, KeyPair, @@ -8,14 +8,14 @@ import type { LatticeSecureDecryptedResponse, LatticeSecureRequest, LatticeSecureRequestPayload, -} from '../types' +} from '../types'; import { aes256_decrypt, aes256_encrypt, checksum, getP256KeyPairFromPub, randomBytes, -} from '../util' +} from '../util'; /** * All messages sent to the Lattice from this SDK will be * "secure messages", of which there are two types: @@ -42,10 +42,10 @@ import { LatticeProtocolVersion, type LatticeSecureEncryptedRequestType, LatticeSecureMsgType, -} from './latticeConstants' +} from './latticeConstants'; -const { msgSizes } = Constants -const { secure: szs } = msgSizes +const { msgSizes } = Constants; +const { secure: szs } = msgSizes; /** * Build and make a request to connect to a specific Lattice @@ -59,26 +59,26 @@ export async function connectSecureRequest({ url, pubkey, }: { - url: string - pubkey: Buffer + url: string; + pubkey: Buffer; }): Promise { // Build the secure request message const payloadData = serializeSecureRequestConnectPayloadData({ pubkey: pubkey, - }) - const msgId = randomBytes(4) + }); + const msgId = randomBytes(4); const msg = serializeSecureRequestMsg( msgId, LatticeSecureMsgType.connect, payloadData, - ) + ); // Send request to the Lattice - const resp = await request({ url, payload: msg }) + const resp = await request({ url, payload: msg }); if (resp.length !== szs.payload.response.connect - 1) { - throw new Error('Wrong Lattice response message size.') + throw new Error('Wrong Lattice response message size.'); } - return resp + return resp; } /** @@ -99,15 +99,15 @@ export async function encryptedSecureRequest({ ephemeralPub, url, }: { - data: Buffer - requestType: LatticeSecureEncryptedRequestType - sharedSecret: Buffer - ephemeralPub: KeyPair - url: string + data: Buffer; + requestType: LatticeSecureEncryptedRequestType; + sharedSecret: Buffer; + ephemeralPub: KeyPair; + url: string; }): Promise { // Generate a random message id for internal tracking // of this specific request (internal on both sides). - const msgId = randomBytes(4) + const msgId = randomBytes(4); // Serialize the request data into encrypted request // payload data. @@ -116,7 +116,7 @@ export async function encryptedSecureRequest({ requestType, ephemeralPub, sharedSecret, - }) + }); // Serialize the payload data into an encrypted secure // request message. @@ -124,30 +124,30 @@ export async function encryptedSecureRequest({ msgId, LatticeSecureMsgType.encrypted, payloadData, - ) + ); // Send request to Lattice const resp = await request({ url, payload: msg, - }) + }); // Deserialize the response payload data if (resp.length !== szs.payload.response.encrypted - 1) { - throw new Error('Wrong Lattice response message size.') + throw new Error('Wrong Lattice response message size.'); } const encPayloadData = resp.slice( 0, szs.data.response.encrypted.encryptedData, - ) + ); // Return decrypted response payload data return decryptEncryptedLatticeResponseData({ encPayloadData, requestType, sharedSecret, - }) + }); } /** @@ -166,13 +166,13 @@ function serializeSecureRequestMsg( ): Buffer { // Sanity check request data if (msgId.length !== 4) { - throw new Error('msgId must be four bytes') + throw new Error('msgId must be four bytes'); } if ( secureRequestType !== LatticeSecureMsgType.connect && secureRequestType !== LatticeSecureMsgType.encrypted ) { - throw new Error('Invalid Lattice secure request type') + throw new Error('Invalid Lattice secure request type'); } // Validate the incoming payload data size. Note that the payload @@ -180,26 +180,26 @@ function serializeSecureRequestMsg( // payload data size is one less than the expected size. const isValidConnectPayloadDataSz = secureRequestType === LatticeSecureMsgType.connect && - payloadData.length === szs.payload.request.connect - 1 + payloadData.length === szs.payload.request.connect - 1; const isValidEncryptedPayloadDataSz = secureRequestType === LatticeSecureMsgType.encrypted && - payloadData.length === szs.payload.request.encrypted - 1 + payloadData.length === szs.payload.request.encrypted - 1; // Build payload and size - let msgSz = msgSizes.header + msgSizes.checksum - let payloadLen: number + let msgSz = msgSizes.header + msgSizes.checksum; + let payloadLen: number; const payload: LatticeSecureRequestPayload = { requestType: secureRequestType, data: payloadData, - } + }; if (isValidConnectPayloadDataSz) { - payloadLen = szs.payload.request.connect + payloadLen = szs.payload.request.connect; } else if (isValidEncryptedPayloadDataSz) { - payloadLen = szs.payload.request.encrypted + payloadLen = szs.payload.request.encrypted; } else { - throw new Error('Invalid Lattice secure request payload size') + throw new Error('Invalid Lattice secure request payload size'); } - msgSz += payloadLen + msgSz += payloadLen; // Construct the request in object form const header: LatticeMessageHeader = { @@ -207,39 +207,39 @@ function serializeSecureRequestMsg( type: LatticeMsgType.secure, id: msgId, len: payloadLen, - } + }; const req: LatticeSecureRequest = { header, payload, - } + }; // Now serialize the whole message // Header | requestType | payloadData | checksum - const msg = Buffer.alloc(msgSz) - let off = 0 + const msg = Buffer.alloc(msgSz); + let off = 0; // Header - msg.writeUInt8(req.header.version, off) - off += 1 - msg.writeUInt8(req.header.type, off) - off += 1 - req.header.id.copy(msg, off) - off += req.header.id.length - msg.writeUInt16BE(req.header.len, off) - off += 2 + msg.writeUInt8(req.header.version, off); + off += 1; + msg.writeUInt8(req.header.type, off); + off += 1; + req.header.id.copy(msg, off); + off += req.header.id.length; + msg.writeUInt16BE(req.header.len, off); + off += 2; // Payload - msg.writeUInt8(req.payload.requestType, off) - off += 1 - req.payload.data.copy(msg, off) - off += req.payload.data.length + msg.writeUInt8(req.payload.requestType, off); + off += 1; + req.payload.data.copy(msg, off); + off += req.payload.data.length; // Checksum - msg.writeUInt32BE(checksum(msg.slice(0, off)), off) - off += 4 + msg.writeUInt32BE(checksum(msg.slice(0, off)), off); + off += 4; if (off !== msgSz) { - throw new Error('Failed to build request message') + throw new Error('Failed to build request message'); } // We have our serialized secure message! - return msg + return msg; } /** @@ -250,9 +250,9 @@ function serializeSecureRequestMsg( function serializeSecureRequestConnectPayloadData( payloadData: LatticeSecureConnectRequestPayloadData, ): Buffer { - const serPayloadData = Buffer.alloc(szs.data.request.connect) - payloadData.pubkey.copy(serPayloadData, 0) - return serPayloadData + const serPayloadData = Buffer.alloc(szs.data.request.connect); + payloadData.pubkey.copy(serPayloadData, 0); + return serPayloadData; } /** @@ -267,52 +267,52 @@ function serializeSecureRequestEncryptedPayloadData({ ephemeralPub, sharedSecret, }: { - data: Buffer - requestType: LatticeSecureEncryptedRequestType - ephemeralPub: KeyPair - sharedSecret: Buffer + data: Buffer; + requestType: LatticeSecureEncryptedRequestType; + ephemeralPub: KeyPair; + sharedSecret: Buffer; }): Buffer { // Sanity checks request size if (data.length > szs.data.request.encrypted.encryptedData) { - throw new Error('Encrypted request data too large') + throw new Error('Encrypted request data too large'); } // Make sure we have a shared secret. An error will be thrown // if there is no ephemeral pub, indicating we need to reconnect. - validateEphemeralPub(ephemeralPub) + validateEphemeralPub(ephemeralPub); // Validate the request data size matches the desired request - const requestDataSize = szs.data.request.encrypted[requestType] + const requestDataSize = szs.data.request.encrypted[requestType]; if (data.length !== requestDataSize) { throw new Error( `Invalid request datasize (wanted ${requestDataSize}, got ${data.length})`, - ) + ); } // Build the pre-encrypted data payload, which variable sized and of form: // encryptedRequestType | data | checksum - const preEncryptedData = Buffer.alloc(1 + requestDataSize) - preEncryptedData[0] = requestType - data.copy(preEncryptedData, 1) - const preEncryptedDataChecksum = checksum(preEncryptedData) + const preEncryptedData = Buffer.alloc(1 + requestDataSize); + preEncryptedData[0] = requestType; + data.copy(preEncryptedData, 1); + const preEncryptedDataChecksum = checksum(preEncryptedData); // Encrypt the data into a fixed size buffer. The buffer size should // equal to the full message request less the 4-byte ephemeral id. - const _encryptedData = Buffer.alloc(szs.data.request.encrypted.encryptedData) - preEncryptedData.copy(_encryptedData, 0) + const _encryptedData = Buffer.alloc(szs.data.request.encrypted.encryptedData); + preEncryptedData.copy(_encryptedData, 0); _encryptedData.writeUInt32LE( preEncryptedDataChecksum, preEncryptedData.length, - ) - const encryptedData = aes256_encrypt(_encryptedData, sharedSecret) + ); + const encryptedData = aes256_encrypt(_encryptedData, sharedSecret); // Calculate ephemeral ID - const ephemeralId = getEphemeralId(sharedSecret) + const ephemeralId = getEphemeralId(sharedSecret); // Now we will serialize the payload data. - const serPayloadData = Buffer.alloc(szs.payload.request.encrypted - 1) - serPayloadData.writeUInt32LE(ephemeralId) - encryptedData.copy(serPayloadData, 4) - return serPayloadData + const serPayloadData = Buffer.alloc(szs.payload.request.encrypted - 1); + serPayloadData.writeUInt32LE(ephemeralId); + encryptedData.copy(serPayloadData, 4); + return serPayloadData; } /** @@ -326,37 +326,37 @@ function decryptEncryptedLatticeResponseData({ requestType, sharedSecret, }: { - encPayloadData: Buffer - requestType: LatticeSecureEncryptedRequestType - sharedSecret: Buffer + encPayloadData: Buffer; + requestType: LatticeSecureEncryptedRequestType; + sharedSecret: Buffer; }) { // Decrypt data using the *current* shared secret - const decData = aes256_decrypt(encPayloadData, sharedSecret) + const decData = aes256_decrypt(encPayloadData, sharedSecret); // Bulid the object - const ephemeralPubSz = 65 // secp256r1 pubkey + const ephemeralPubSz = 65; // secp256r1 pubkey const checksumOffset = - ephemeralPubSz + szs.data.response.encrypted[requestType] + ephemeralPubSz + szs.data.response.encrypted[requestType]; const respData: LatticeSecureDecryptedResponse = { ephemeralPub: decData.slice(0, ephemeralPubSz), data: decData.slice(ephemeralPubSz, checksumOffset), checksum: decData.readUInt32BE(checksumOffset), - } + }; // Validate the checksum - const validChecksum = checksum(decData.slice(0, checksumOffset)) + const validChecksum = checksum(decData.slice(0, checksumOffset)); if (respData.checksum !== validChecksum) { - throw new Error('Checksum mismatch in decrypted Lattice data') + throw new Error('Checksum mismatch in decrypted Lattice data'); } // Validate the response data size - const validSz = szs.data.response.encrypted[requestType] + const validSz = szs.data.response.encrypted[requestType]; if (respData.data.length !== validSz) { - throw new Error('Incorrect response data returned from Lattice') + throw new Error('Incorrect response data returned from Lattice'); } - const newEphemeralPub = getP256KeyPairFromPub(respData.ephemeralPub) + const newEphemeralPub = getP256KeyPairFromPub(respData.ephemeralPub); // Returned the decrypted data - return { decryptedData: respData.data, newEphemeralPub } + return { decryptedData: respData.data, newEphemeralPub }; } diff --git a/packages/sdk/src/schemas/index.ts b/packages/sdk/src/schemas/index.ts index e97aa5c3..e8f45da2 100644 --- a/packages/sdk/src/schemas/index.ts +++ b/packages/sdk/src/schemas/index.ts @@ -1 +1 @@ -export * from './transaction' +export * from './transaction'; diff --git a/packages/sdk/src/schemas/transaction.ts b/packages/sdk/src/schemas/transaction.ts index 34862707..63f06eed 100644 --- a/packages/sdk/src/schemas/transaction.ts +++ b/packages/sdk/src/schemas/transaction.ts @@ -1,6 +1,6 @@ -import { type Hex, getAddress, hexToBigInt, isAddress, isHex } from 'viem' -import { z } from 'zod' -import { TRANSACTION_TYPE } from '../types' +import { type Hex, getAddress, hexToBigInt, isAddress, isHex } from 'viem'; +import { z } from 'zod'; +import { TRANSACTION_TYPE } from '../types'; // Helper to handle various numeric inputs and convert them to BigInt. // It also validates that the value is not negative. @@ -13,28 +13,28 @@ const toPositiveBigInt = z .transform((val, ctx) => { try { const b = - typeof val === 'string' && isHex(val) ? hexToBigInt(val) : BigInt(val) + typeof val === 'string' && isHex(val) ? hexToBigInt(val) : BigInt(val); if (b < 0n) { ctx.addIssue({ code: z.ZodIssueCode.custom, message: 'Value must be non-negative', - }) - return z.NEVER + }); + return z.NEVER; } - return b + return b; } catch { ctx.addIssue({ code: z.ZodIssueCode.custom, message: 'Invalid numeric value', - }) - return z.NEVER + }); + return z.NEVER; } - }) + }); // Schema for gas-related fields, ensuring they are non-negative BigInts. const GasValueSchema = toPositiveBigInt.refine((val) => val >= 0n, { message: 'Gas values must be non-negative', -}) +}); // Schema for chainId, ensuring it's a positive integer. const ChainIdSchema = z @@ -46,19 +46,19 @@ const ChainIdSchema = z ) .refine((val) => Number.isInteger(val) && val > 0, { message: 'Chain ID must be a positive integer', - }) + }); // Schema for an Ethereum address, which validates and checksums it. const AddressSchema = z .string() .refine(isAddress, 'Invalid address') - .transform((addr) => getAddress(addr)) + .transform((addr) => getAddress(addr)); // Schema for hex data, ensuring it's a valid hex string. const DataSchema = z .string() .refine(isHex, 'Data must be a valid hex string') - .default('0x') + .default('0x'); const NonceSchema = z .union([ @@ -75,34 +75,34 @@ const NonceSchema = z : BigInt(val) : typeof val === 'number' ? BigInt(val) - : val + : val; if (bigVal < 0n) { ctx.addIssue({ code: z.ZodIssueCode.custom, message: 'Nonce must be non-negative', - }) - return z.NEVER + }); + return z.NEVER; } - const maxSafe = BigInt(Number.MAX_SAFE_INTEGER) + const maxSafe = BigInt(Number.MAX_SAFE_INTEGER); if (bigVal > maxSafe) { ctx.addIssue({ code: z.ZodIssueCode.custom, message: 'Nonce exceeds JavaScript safe integer range', - }) - return z.NEVER + }); + return z.NEVER; } - return Number(bigVal) + return Number(bigVal); } catch { ctx.addIssue({ code: z.ZodIssueCode.custom, message: 'Invalid nonce value', - }) - return z.NEVER + }); + return z.NEVER; } - }) + }); // Schema for access list entries. const AccessListEntrySchema = z.object({ @@ -110,7 +110,7 @@ const AccessListEntrySchema = z.object({ storageKeys: z.array( z.string().refine(isHex, 'Storage key must be a hex string'), ), -}) +}); // Schema for EIP-7702 authorization entries. const AuthorizationSchema = z.object({ @@ -120,7 +120,7 @@ const AuthorizationSchema = z.object({ yParity: z.number().optional().default(0), r: z.string().refine(isHex).optional(), s: z.string().refine(isHex).optional(), -}) +}); // Base schema for all transaction types. const BaseTxSchema = z.object({ @@ -132,7 +132,7 @@ const BaseTxSchema = z.object({ gasLimit: GasValueSchema.optional(), chainId: ChainIdSchema.optional().default(1), accessList: z.array(AccessListEntrySchema).optional(), -}) +}); // Schema for Legacy (Type 0) transactions. const LegacyTxSchema = BaseTxSchema.extend({ @@ -140,20 +140,20 @@ const LegacyTxSchema = BaseTxSchema.extend({ .union([z.literal('legacy'), z.literal(TRANSACTION_TYPE.LEGACY)]) .optional(), gasPrice: GasValueSchema, -}) +}); // Schema for EIP-2930 (Type 1) transactions. const EIP2930TxSchema = BaseTxSchema.extend({ type: z.union([z.literal('eip2930'), z.literal(TRANSACTION_TYPE.EIP2930)]), gasPrice: GasValueSchema, -}) +}); // Schema for EIP-1559 (Type 2) transactions. const EIP1559TxSchema = BaseTxSchema.extend({ type: z.union([z.literal('eip1559'), z.literal(TRANSACTION_TYPE.EIP1559)]), maxFeePerGas: GasValueSchema, maxPriorityFeePerGas: GasValueSchema, -}) +}); // Schema for EIP-7702 (Type 4/5) transactions. const EIP7702TxSchema = BaseTxSchema.extend({ @@ -165,7 +165,7 @@ const EIP7702TxSchema = BaseTxSchema.extend({ maxFeePerGas: GasValueSchema, maxPriorityFeePerGas: GasValueSchema, authorizationList: z.array(AuthorizationSchema).min(1), -}) +}); /** * A comprehensive zod schema that validates and normalizes a flexible transaction input. @@ -183,10 +183,10 @@ export const TransactionSchema = z try { JSON.stringify(val, (_, value) => typeof value === 'bigint' ? value.toString() : value, - ) - return true + ); + return true; } catch { - return false + return false; } }, { message: 'Circular reference detected in transaction object' }, @@ -194,28 +194,28 @@ export const TransactionSchema = z .transform((tx) => { // Prioritize gasLimit over gas if (tx.gasLimit) { - tx.gas = tx.gasLimit + tx.gas = tx.gasLimit; } if (tx.data === null || tx.data === undefined || tx.data === '') { - tx.data = '0x' + tx.data = '0x'; } // Normalize EIP-7702 `authorization` to `authorizationList` if (tx.authorization) { - tx.authorizationList = [tx.authorization] + tx.authorizationList = [tx.authorization]; } - return tx + return tx; }) .transform((tx: any) => { // Type inference and validation logic - const hasAuthList = !!tx.authorizationList - const hasMaxFee = !!tx.maxFeePerGas || !!tx.maxPriorityFeePerGas - const hasAccessList = !!tx.accessList - const hasGasPrice = !!tx.gasPrice + const hasAuthList = !!tx.authorizationList; + const hasMaxFee = !!tx.maxFeePerGas || !!tx.maxPriorityFeePerGas; + const hasAccessList = !!tx.accessList; + const hasGasPrice = !!tx.gasPrice; - let type: 'eip7702' | 'eip1559' | 'eip2930' | 'legacy' = 'legacy' - let schema: z.ZodTypeAny = LegacyTxSchema + let type: 'eip7702' | 'eip1559' | 'eip2930' | 'legacy' = 'legacy'; + let schema: z.ZodTypeAny = LegacyTxSchema; if ( tx.type === 'eip7702' || @@ -223,40 +223,40 @@ export const TransactionSchema = z tx.type === 5 || hasAuthList ) { - type = 'eip7702' - schema = EIP7702TxSchema + type = 'eip7702'; + schema = EIP7702TxSchema; } else if (tx.type === 'eip1559' || tx.type === 2 || hasMaxFee) { - type = 'eip1559' - schema = EIP1559TxSchema + type = 'eip1559'; + schema = EIP1559TxSchema; } else if (tx.type === 'eip2930' || tx.type === 1 || hasAccessList) { - type = 'eip2930' - schema = EIP2930TxSchema + type = 'eip2930'; + schema = EIP2930TxSchema; } // For legacy, if gasPrice is missing, it's an invalid tx if (type === 'legacy' && !hasGasPrice) { - throw new Error('Legacy transactions require a `gasPrice` field.') + throw new Error('Legacy transactions require a `gasPrice` field.'); } - const result = schema.parse(tx) + const result = schema.parse(tx); // Post-process the successfully parsed data - const data: any = result - data.type = type + const data: any = result; + data.type = type; if (type === 'legacy' && data.gas === undefined) { - data.gas = 21000n // Default gas for legacy transfers + data.gas = 21000n; // Default gas for legacy transfers } // Remove fields that are not part of the final type - if (type !== 'legacy' && type !== 'eip2930') data.gasPrice = undefined + if (type !== 'legacy' && type !== 'eip2930') data.gasPrice = undefined; if (type !== 'eip1559' && type !== 'eip7702') { - data.maxFeePerGas = undefined - data.maxPriorityFeePerGas = undefined + data.maxFeePerGas = undefined; + data.maxPriorityFeePerGas = undefined; } - data.gasLimit = undefined - if (type !== 'eip7702') data.authorizationList = undefined + data.gasLimit = undefined; + if (type !== 'eip7702') data.authorizationList = undefined; - return data - }) + return data; + }); -export type FlexibleTransaction = z.infer +export type FlexibleTransaction = z.infer; diff --git a/packages/sdk/src/shared/errors.ts b/packages/sdk/src/shared/errors.ts index 919f25c6..c3e828db 100644 --- a/packages/sdk/src/shared/errors.ts +++ b/packages/sdk/src/shared/errors.ts @@ -1,22 +1,22 @@ -import { type LatticeResponseCode, ProtocolConstants } from '../protocol' +import { type LatticeResponseCode, ProtocolConstants } from '../protocol'; const buildLatticeResponseErrorMessage = ({ responseCode, errorMessage, }: { - responseCode?: LatticeResponseCode - errorMessage?: string + responseCode?: LatticeResponseCode; + errorMessage?: string; }) => { - const msg: string[] = [] + const msg: string[] = []; if (responseCode) { - msg.push(`${ProtocolConstants.responseMsg[responseCode]}`) + msg.push(`${ProtocolConstants.responseMsg[responseCode]}`); } if (errorMessage) { - msg.push('Error Message: ') - msg.push(errorMessage) + msg.push('Error Message: '); + msg.push(errorMessage); } - return msg.join('\n') -} + return msg.join('\n'); +}; export class LatticeResponseError extends Error { constructor( @@ -26,10 +26,10 @@ export class LatticeResponseError extends Error { const message = buildLatticeResponseErrorMessage({ responseCode, errorMessage, - }) - super(message) - this.name = 'LatticeResponseError' - this.responseCode = responseCode - this.errorMessage = errorMessage + }); + super(message); + this.name = 'LatticeResponseError'; + this.responseCode = responseCode; + this.errorMessage = errorMessage; } } diff --git a/packages/sdk/src/shared/functions.ts b/packages/sdk/src/shared/functions.ts index 4dc62bc7..e03aa25e 100644 --- a/packages/sdk/src/shared/functions.ts +++ b/packages/sdk/src/shared/functions.ts @@ -1,28 +1,28 @@ -import { Hash } from 'ox' -import type { Client } from '..' -import bitcoin from '../bitcoin' -import { EXTERNAL } from '../constants' -import ethereum from '../ethereum' -import { buildGenericSigningMsgRequest } from '../genericSigning' -import type { Currency, FirmwareConstants, RequestParams } from '../types' -import { fetchWithTimeout, parseLattice1Response } from '../util' -import { LatticeResponseError } from './errors' +import { Hash } from 'ox'; +import type { Client } from '..'; +import bitcoin from '../bitcoin'; +import { EXTERNAL } from '../constants'; +import ethereum from '../ethereum'; +import { buildGenericSigningMsgRequest } from '../genericSigning'; +import type { Currency, FirmwareConstants, RequestParams } from '../types'; +import { fetchWithTimeout, parseLattice1Response } from '../util'; +import { LatticeResponseError } from './errors'; import { isDeviceBusy, isInvalidEphemeralId, isWrongWallet, shouldUseEVMLegacyConverter, -} from './predicates' -import { validateRequestError } from './validators' +} from './predicates'; +import { validateRequestError } from './validators'; export const buildTransaction = ({ data, currency, fwConstants, }: { - data: any - currency?: Currency - fwConstants: FirmwareConstants + data: any; + currency?: Currency; + fwConstants: FirmwareConstants; }) => { // All transaction requests must be put into the same sized buffer. This comes from // sizeof(GpTransactionRequest_t), but note we remove the 2-byte schemaId since it is not @@ -38,16 +38,16 @@ export const buildTransaction = ({ console.log( 'Using the legacy ETH signing path. This will soon be deprecated. ' + 'Please switch to general signing request.', - ) - let payload: Buffer | undefined + ); + let payload: Buffer | undefined; try { - payload = ethereum.convertEthereumTransactionToGenericRequest(data) + payload = ethereum.convertEthereumTransactionToGenericRequest(data); } catch (err) { - console.error('Failed to convert legacy Ethereum transaction:', err) + console.error('Failed to convert legacy Ethereum transaction:', err); throw new Error( 'Could not convert legacy request. Please switch to a general signing ' + 'request. See gridplus-sdk docs for more information.', - ) + ); } data = { fwConstants, @@ -56,33 +56,33 @@ export const buildTransaction = ({ hashType: EXTERNAL.SIGNING.HASHES.KECCAK256, signerPath: data.signerPath, payload, - } + }; return { requestData: buildGenericSigningMsgRequest({ ...data, fwConstants }), isGeneric: true, - } + }; } else if (currency === 'ETH') { // Legacy signing pathway -- should deprecate in the future return { requestData: ethereum.buildEthereumTxRequest({ ...data, fwConstants }), isGeneric: false, - } + }; } else if (currency === 'ETH_MSG') { return { requestData: ethereum.buildEthereumMsgRequest({ ...data, fwConstants }), isGeneric: false, - } + }; } else if (currency === 'BTC') { return { requestData: bitcoin.buildBitcoinTxRequest({ ...data, fwConstants }), isGeneric: false, - } + }; } return { requestData: buildGenericSigningMsgRequest({ ...data, fwConstants }), isGeneric: true, - } -} + }; +}; export const request = async ({ url, @@ -102,28 +102,28 @@ export const request = async ({ .then((body) => { // Handle formatting or generic HTTP errors if (!body || !body.message) { - throw new Error('Invalid response') + throw new Error('Invalid response'); } else if (body.status !== 200) { - throw new Error(`Error code ${body.status}: ${body.message}`) + throw new Error(`Error code ${body.status}: ${body.message}`); } const { data, errorMessage, responseCode } = parseLattice1Response( body.message, - ) + ); if (errorMessage || responseCode) { - throw new LatticeResponseError(responseCode, errorMessage) + throw new LatticeResponseError(responseCode, errorMessage); } - return data - }) -} + return data; + }); +}; /** * `sleep()` returns a Promise that resolves after a given number of milliseconds. */ function sleep(ms: number): Promise { - return new Promise((resolve) => setTimeout(resolve, ms)) + return new Promise((resolve) => setTimeout(resolve, ms)); } /** @@ -141,8 +141,8 @@ export const buildRetryWrapper = (client: Client, retries: number) => { params: { ...params, client }, retries, client, - }) -} + }); +}; /** * Retries a function call if the error message or response code is present and the number of @@ -159,30 +159,30 @@ export const retryWrapper = async ({ retries, client, }: { - fn: (...args: any[]) => Promise - params: any - retries: number - client: any + fn: (...args: any[]) => Promise; + params: any; + retries: number; + client: any; }) => { return fn({ ...params }).catch(async (err: Error) => { if (err instanceof LatticeResponseError) { /** `string` returned from the Lattice if there's an error */ - const errorMessage = err.errorMessage + const errorMessage = err.errorMessage; /** `number` returned from the Lattice if there's an error */ - const responseCode = err.responseCode + const responseCode = err.responseCode; if ((errorMessage || responseCode) && retries) { if (isDeviceBusy(responseCode)) { - await sleep(3000) + await sleep(3000); } else if ( isWrongWallet(responseCode) && !client.skipRetryOnWrongWallet ) { - await client.fetchActiveWallet() + await client.fetchActiveWallet(); } else if (isInvalidEphemeralId(responseCode)) { - await client.connect(client.deviceId) + await client.connect(client.deviceId); } else { - throw err + throw err; } return retryWrapper({ @@ -190,12 +190,12 @@ export const retryWrapper = async ({ params, retries: retries - 1, client, - }) + }); } } - throw err - }) -} + throw err; + }); +}; /** * Get the ephemeral id, which is the first 4 bytes of the shared secret generated from the local @@ -205,6 +205,6 @@ export const retryWrapper = async ({ */ export const getEphemeralId = (sharedSecret: Buffer) => { // EphemId is the first 4 bytes of the hash of the shared secret - const hash = Buffer.from(Hash.sha256(sharedSecret)) - return Number.parseInt(hash.slice(0, 4).toString('hex'), 16) -} + const hash = Buffer.from(Hash.sha256(sharedSecret)); + return Number.parseInt(hash.slice(0, 4).toString('hex'), 16); +}; diff --git a/packages/sdk/src/shared/predicates.ts b/packages/sdk/src/shared/predicates.ts index 34d2bf93..25477720 100644 --- a/packages/sdk/src/shared/predicates.ts +++ b/packages/sdk/src/shared/predicates.ts @@ -1,19 +1,19 @@ -import { LatticeResponseCode } from '../protocol' -import type { FirmwareConstants, FirmwareVersion } from '../types' -import { isFWSupported } from './utilities' +import { LatticeResponseCode } from '../protocol'; +import type { FirmwareConstants, FirmwareVersion } from '../types'; +import { isFWSupported } from './utilities'; export const isDeviceBusy = (responseCode: number) => responseCode === LatticeResponseCode.deviceBusy || - responseCode === LatticeResponseCode.gceTimeout + responseCode === LatticeResponseCode.gceTimeout; export const isWrongWallet = (responseCode: number) => - responseCode === LatticeResponseCode.wrongWallet + responseCode === LatticeResponseCode.wrongWallet; export const isInvalidEphemeralId = (responseCode: number) => - responseCode === LatticeResponseCode.invalidEphemId + responseCode === LatticeResponseCode.invalidEphemId; export const doesFetchWalletsOnLoad = (fwVersion: FirmwareVersion) => - isFWSupported(fwVersion, { major: 0, minor: 14, fix: 1 }) + isFWSupported(fwVersion, { major: 0, minor: 14, fix: 1 }); export const shouldUseEVMLegacyConverter = (fwConstants: FirmwareConstants) => - fwConstants.genericSigning?.encodingTypes?.EVM + fwConstants.genericSigning?.encodingTypes?.EVM; diff --git a/packages/sdk/src/shared/utilities.ts b/packages/sdk/src/shared/utilities.ts index 5d924212..08367194 100644 --- a/packages/sdk/src/shared/utilities.ts +++ b/packages/sdk/src/shared/utilities.ts @@ -1,5 +1,5 @@ -import { HARDENED_OFFSET } from '../constants' -import type { ActiveWallets, FirmwareVersion, KeyPair } from '../types' +import { HARDENED_OFFSET } from '../constants'; +import type { ActiveWallets, FirmwareVersion, KeyPair } from '../types'; /** * Get 64 bytes representing the public key This is the uncompressed key without the leading 04 @@ -9,19 +9,19 @@ import type { ActiveWallets, FirmwareVersion, KeyPair } from '../types' * @returns A Buffer containing the public key. */ export const getPubKeyBytes = (key: KeyPair, LE = false) => { - const k = key.getPublic() - const p = k.encode('hex', false) - const pb = Buffer.from(p, 'hex') + const k = key.getPublic(); + const p = k.encode('hex', false); + const pb = Buffer.from(p, 'hex'); if (LE === true) { // Need to flip X and Y components to little endian - const x = pb.slice(1, 33).reverse() - const y = pb.slice(33, 65).reverse() + const x = pb.slice(1, 33).reverse(); + const y = pb.slice(33, 65).reverse(); // @ts-expect-error - TODO: Find out why Buffer won't accept pb[0] - return Buffer.concat([pb[0], x, y]) + return Buffer.concat([pb[0], x, y]); } else { - return pb + return pb; } -} +}; /** * Get the shared secret, derived via ECDH from the local private key and the ephemeral public key @@ -31,8 +31,8 @@ export const getPubKeyBytes = (key: KeyPair, LE = false) => { export const getSharedSecret = (key: KeyPair, ephemeralPub: KeyPair) => { // Once every ~256 attempts, we will get a key that starts with a `00` byte, which can lead to // problems initializing AES if we don't force a 32 byte BE buffer. - return Buffer.from(key.derive(ephemeralPub.getPublic()).toArray('be', 32)) -} + return Buffer.from(key.derive(ephemeralPub.getPublic()).toArray('be', 32)); +}; // Given a set of wallet data, which contains two wallet descriptors, parse the data and save it // to memory @@ -41,9 +41,9 @@ export const parseWallets = (walletData: any): ActiveWallets => { // active wallet of the device and we should save it. If the external wallet is blank, it means // there is no card present and we should save and use the interal wallet. If both wallets are // empty, it means the device still needs to be set up. - const walletDescriptorLen = 71 + const walletDescriptorLen = 71; // Internal first - let off = 0 + let off = 0; const activeWallets: ActiveWallets = { internal: { uid: undefined, @@ -57,8 +57,8 @@ export const parseWallets = (walletData: any): ActiveWallets => { name: undefined, external: true, }, - } - activeWallets.internal.uid = walletData.slice(off, off + 32) + }; + activeWallets.internal.uid = walletData.slice(off, off + 32); // NOTE: `capabilities` and `name` were deprecated in Lattice firmware. // They never provided any real information, but have been archived here // since the response size has been preserved and we may bring them back @@ -69,44 +69,44 @@ export const parseWallets = (walletData: any): ActiveWallets => { // off + walletDescriptorLen, // ); // Offset the first item - off += walletDescriptorLen + off += walletDescriptorLen; // External - activeWallets.external.uid = walletData.slice(off, off + 32) + activeWallets.external.uid = walletData.slice(off, off + 32); // activeWallets.external.capabilities = walletData.readUInt32BE(off + 32); // activeWallets.external.name = walletData.slice( // off + 36, // off + walletDescriptorLen, // ); - return activeWallets -} + return activeWallets; +}; // Determine if a provided firmware version matches or exceeds the current firmware version export const isFWSupported = ( fwVersion: FirmwareVersion, versionSupported: FirmwareVersion, ): boolean => { - const { major, minor, fix } = fwVersion - const { major: _major, minor: _minor, fix: _fix } = versionSupported + const { major, minor, fix } = fwVersion; + const { major: _major, minor: _minor, fix: _fix } = versionSupported; return ( major > _major || (major >= _major && minor > _minor) || (major >= _major && minor >= _minor && fix >= _fix) - ) -} + ); +}; /** * Convert a set of BIP39 path indices to a string * @param path - Set of indices */ export const getPathStr = (path) => { - let pathStr = 'm' + let pathStr = 'm'; path.forEach((idx) => { if (idx >= HARDENED_OFFSET) { - pathStr += `/${idx - HARDENED_OFFSET}'` + pathStr += `/${idx - HARDENED_OFFSET}'`; } else { - pathStr += `/${idx}` + pathStr += `/${idx}`; } - }) - return pathStr -} + }); + return pathStr; +}; diff --git a/packages/sdk/src/shared/validators.ts b/packages/sdk/src/shared/validators.ts index f4cfe65d..5b5a9da9 100644 --- a/packages/sdk/src/shared/validators.ts +++ b/packages/sdk/src/shared/validators.ts @@ -1,7 +1,7 @@ -import type { UInt4 } from 'bitwise/types' -import isEmpty from 'lodash/isEmpty.js' -import type { Client } from '../client' -import { ASCII_REGEX, EMPTY_WALLET_UID, MAX_ADDR } from '../constants' +import type { UInt4 } from 'bitwise/types'; +import isEmpty from 'lodash/isEmpty.js'; +import type { Client } from '../client'; +import { ASCII_REGEX, EMPTY_WALLET_UID, MAX_ADDR } from '../constants'; import type { ActiveWallets, FirmwareConstants, @@ -10,130 +10,130 @@ import type { KeyPair, LatticeError, Wallet, -} from '../types' -import { isUInt4 } from '../util' +} from '../types'; +import { isUInt4 } from '../util'; export const validateIsUInt4 = (n?: number) => { if (typeof n !== 'number' || !isUInt4(n)) { - throw new Error('Must be an integer between 0 and 15 inclusive') + throw new Error('Must be an integer between 0 and 15 inclusive'); } - return n as UInt4 -} + return n as UInt4; +}; export const validateNAddresses = (n?: number) => { if (!n) { - throw new Error('The number of addresses is required.') + throw new Error('The number of addresses is required.'); } if (n > MAX_ADDR) { - throw new Error(`You may only request ${MAX_ADDR} addresses at once.`) + throw new Error(`You may only request ${MAX_ADDR} addresses at once.`); } - return n -} + return n; +}; export const validateStartPath = (startPath?: number[]) => { if (!startPath) { - throw new Error('Start path is required') + throw new Error('Start path is required'); } if (startPath.length < 1 || startPath.length > 5) - throw new Error('Path must include between 1 and 5 indices') + throw new Error('Path must include between 1 and 5 indices'); - return startPath -} + return startPath; +}; export const validateDeviceId = (deviceId?: string) => { if (!deviceId) { throw new Error( 'No device ID has been stored. Please connect with your device ID first.', - ) + ); } - return deviceId -} + return deviceId; +}; export const validateAppName = (name?: string) => { if (!name) { - throw new Error('Name is required.') + throw new Error('Name is required.'); } if (name.length < 5 || name.length > 24) { throw new Error( 'Invalid length for name provided. Must be 5-24 characters.', - ) + ); } - return name -} + return name; +}; export const validateUrl = (url?: string) => { if (!url) { - throw new Error('URL does not exist. Please reconnect.') + throw new Error('URL does not exist. Please reconnect.'); } try { - new URL(url) + new URL(url); } catch (err) { - console.error('Invalid URL format:', err) - throw new Error('Invalid URL provided. Please use a valid URL.') + console.error('Invalid URL format:', err); + throw new Error('Invalid URL provided. Please use a valid URL.'); } - return url -} + return url; +}; export const validateBaseUrl = (baseUrl?: string) => { if (!baseUrl) { - throw new Error('Base URL is required.') + throw new Error('Base URL is required.'); } try { - new URL(baseUrl) + new URL(baseUrl); } catch (err) { - console.error('Invalid Base URL format:', err) - throw new Error('Invalid Base URL provided. Please use a valid URL.') + console.error('Invalid Base URL format:', err); + throw new Error('Invalid Base URL provided. Please use a valid URL.'); } - return baseUrl -} + return baseUrl; +}; export const validateFwConstants = (fwConstants?: FirmwareConstants) => { if (!fwConstants) { - throw new Error('Firmware constants do not exist. Please reconnect.') + throw new Error('Firmware constants do not exist. Please reconnect.'); } - return fwConstants -} + return fwConstants; +}; export const validateFwVersion = (fwVersion?: FirmwareVersion) => { if (!fwVersion) { - throw new Error('Firmware version does not exist. Please reconnect.') + throw new Error('Firmware version does not exist. Please reconnect.'); } if ( typeof fwVersion.fix !== 'number' || typeof fwVersion.minor !== 'number' || typeof fwVersion.major !== 'number' ) { - throw new Error('Firmware version improperly formatted. Please reconnect.') + throw new Error('Firmware version improperly formatted. Please reconnect.'); } - return fwVersion -} + return fwVersion; +}; export const validateRequestError = (err: LatticeError) => { - const isTimeout = err.code === 'ECONNABORTED' && err.errno === 'ETIME' + const isTimeout = err.code === 'ECONNABORTED' && err.errno === 'ETIME'; if (isTimeout) { throw new Error( 'Timeout waiting for device. Please ensure it is connected to the internet and try again in a minute.', - ) + ); } - throw new Error(`Failed to make request to device:\n${err.message}`) -} + throw new Error(`Failed to make request to device:\n${err.message}`); +}; export const validateWallet = (wallet?: Wallet) => { if (!wallet || wallet === null) { - throw new Error('No active wallet.') + throw new Error('No active wallet.'); } - return wallet -} + return wallet; +}; export const validateConnectedClient = (client: Client) => { - const appName = validateAppName(client.getAppName()) - const ephemeralPub = validateEphemeralPub(client.ephemeralPub) - const sharedSecret = validateSharedSecret(client.sharedSecret) - const url = validateUrl(client.url) - const fwConstants = validateFwConstants(client.getFwConstants()) - const fwVersion = validateFwVersion(client.getFwVersion()) + const appName = validateAppName(client.getAppName()); + const ephemeralPub = validateEphemeralPub(client.ephemeralPub); + const sharedSecret = validateSharedSecret(client.sharedSecret); + const url = validateUrl(client.url); + const fwConstants = validateFwConstants(client.getFwConstants()); + const fwVersion = validateFwVersion(client.getFwVersion()); // @ts-expect-error - Key is private - const key = validateKey(client.key) + const key = validateKey(client.key); return { appName, @@ -143,31 +143,31 @@ export const validateConnectedClient = (client: Client) => { fwConstants, fwVersion, key, - } -} + }; +}; export const validateEphemeralPub = (ephemeralPub?: KeyPair) => { if (!ephemeralPub) { throw new Error( '`ephemeralPub` (ephemeral public key) is required. Please reconnect.', - ) + ); } - return ephemeralPub -} + return ephemeralPub; +}; export const validateSharedSecret = (sharedSecret?: Buffer) => { if (!sharedSecret) { - throw new Error('Shared secret required. Please reconnect.') + throw new Error('Shared secret required. Please reconnect.'); } - return sharedSecret -} + return sharedSecret; +}; export const validateKey = (key?: KeyPair) => { if (!key) { - throw new Error('Key is required. Please reconnect.') + throw new Error('Key is required. Please reconnect.'); } - return key -} + return key; +}; export const validateActiveWallets = (activeWallets?: ActiveWallets) => { if ( @@ -175,28 +175,28 @@ export const validateActiveWallets = (activeWallets?: ActiveWallets) => { (activeWallets?.internal?.uid?.equals(EMPTY_WALLET_UID) && activeWallets?.external?.uid?.equals(EMPTY_WALLET_UID)) ) { - throw new Error('No active wallet.') + throw new Error('No active wallet.'); } - return activeWallets -} + return activeWallets; +}; export const validateKvRecords = ( records?: KVRecords, fwConstants?: FirmwareConstants, ) => { if (!fwConstants || !fwConstants.kvActionsAllowed) { - throw new Error('Unsupported. Please update firmware.') + throw new Error('Unsupported. Please update firmware.'); } else if (typeof records !== 'object' || Object.keys(records).length < 1) { throw new Error( 'One or more key-value mapping must be provided in `records` param.', - ) + ); } else if (Object.keys(records).length > fwConstants.kvActionMaxNum) { throw new Error( `Too many keys provided. Please only provide up to ${fwConstants.kvActionMaxNum}.`, - ) + ); } - return records -} + return records; +}; export const validateKvRecord = ( { key, val }: KVRecords, @@ -208,37 +208,37 @@ export const validateKvRecord = ( ) { throw new Error( `Key ${key} too large. Must be <=${fwConstants.kvKeyMaxStrSz} characters.`, - ) + ); } else if ( typeof val !== 'string' || String(val).length > fwConstants.kvValMaxStrSz ) { throw new Error( `Value ${val} too large. Must be <=${fwConstants.kvValMaxStrSz} characters.`, - ) + ); } else if (String(key).length === 0 || String(val).length === 0) { - throw new Error('Keys and values must be >0 characters.') + throw new Error('Keys and values must be >0 characters.'); } else if (!ASCII_REGEX.test(key) || !ASCII_REGEX.test(val)) { - throw new Error('Unicode characters are not supported.') + throw new Error('Unicode characters are not supported.'); } - return { key, val } -} + return { key, val }; +}; export const isValidBlockExplorerResponse = (data: any) => { try { - const result = JSON.parse(data.result) - return !isEmpty(result) + const result = JSON.parse(data.result); + return !isEmpty(result); } catch (err) { - console.error('Invalid block explorer response:', err) - return false + console.error('Invalid block explorer response:', err); + return false; } -} +}; export const isValid4ByteResponse = (data: any) => { try { - return !isEmpty(data.results) + return !isEmpty(data.results); } catch (err) { - console.error('Invalid 4byte response:', err) - return false + console.error('Invalid 4byte response:', err); + return false; } -} +}; diff --git a/packages/sdk/src/types/addKvRecords.ts b/packages/sdk/src/types/addKvRecords.ts index 1887f550..892f0de9 100644 --- a/packages/sdk/src/types/addKvRecords.ts +++ b/packages/sdk/src/types/addKvRecords.ts @@ -1,13 +1,13 @@ -import type { Client } from '../client' -import type { KVRecords } from './shared' +import type { Client } from '../client'; +import type { KVRecords } from './shared'; export interface AddKvRecordsRequestParams { - records: KVRecords - type?: number - caseSensitive?: boolean + records: KVRecords; + type?: number; + caseSensitive?: boolean; } export interface AddKvRecordsRequestFunctionParams extends AddKvRecordsRequestParams { - client: Client + client: Client; } diff --git a/packages/sdk/src/types/client.ts b/packages/sdk/src/types/client.ts index f6e681a4..61554783 100644 --- a/packages/sdk/src/types/client.ts +++ b/packages/sdk/src/types/client.ts @@ -1,33 +1,33 @@ -import type { Address, Hash } from 'viem' -import type { CURRENCIES } from '../constants' -import type { KeyPair } from './shared' +import type { Address, Hash } from 'viem'; +import type { CURRENCIES } from '../constants'; +import type { KeyPair } from './shared'; -export type Currency = keyof typeof CURRENCIES +export type Currency = keyof typeof CURRENCIES; -export type SigningPath = number[] +export type SigningPath = number[]; /** * Signature components as returned by the Lattice device. * Values can be Buffer (raw) or string (hex) depending on context. */ export interface LatticeSignature { - r: Buffer | string - s: Buffer | string - v?: Buffer | string | number | bigint + r: Buffer | string; + s: Buffer | string; + v?: Buffer | string | number | bigint; } export interface SignData { - tx?: string - txHash?: Hash - changeRecipient?: string - sig?: LatticeSignature - sigs?: Buffer[] - signer?: Address - pubkey?: Buffer - err?: string + tx?: string; + txHash?: Hash; + changeRecipient?: string; + sig?: LatticeSignature; + sigs?: Buffer[]; + signer?: Address; + pubkey?: Buffer; + err?: string; } -export type SigningRequestResponse = SignData | { pubkey: null; sig: null } +export type SigningRequestResponse = SignData | { pubkey: null; sig: null }; /** * @deprecated This type uses legacy field names and number types instead of viem-compatible bigint. @@ -35,49 +35,49 @@ export type SigningRequestResponse = SignData | { pubkey: null; sig: null } * This will be removed in a future version. */ export interface TransactionPayload { - type: number - gasPrice: number - nonce: number - gasLimit: number - to: string - value: number - data: string - maxFeePerGas: number - maxPriorityFeePerGas: number + type: number; + gasPrice: number; + nonce: number; + gasLimit: number; + to: string; + value: number; + data: string; + maxFeePerGas: number; + maxPriorityFeePerGas: number; } export interface Wallet { /** 32 byte id */ - uid: Buffer + uid: Buffer; /** 20 char (max) string */ - name: Buffer | null + name: Buffer | null; /** 4 byte flag */ - capabilities: number + capabilities: number; /** External or internal wallet */ - external: boolean + external: boolean; } export interface ActiveWallets { - internal: Wallet - external: Wallet + internal: Wallet; + external: Wallet; } export interface RequestParams { - url: string - payload: any //TODO Fix this any - timeout?: number - retries?: number + url: string; + payload: any; //TODO Fix this any + timeout?: number; + retries?: number; } export interface ClientStateData { - activeWallets: ActiveWallets - ephemeralPub: KeyPair - fwVersion: Buffer - deviceId: string - name: string - baseUrl: string - privKey: Buffer - key: Buffer - retryCount: number - timeout: number + activeWallets: ActiveWallets; + ephemeralPub: KeyPair; + fwVersion: Buffer; + deviceId: string; + name: string; + baseUrl: string; + privKey: Buffer; + key: Buffer; + retryCount: number; + timeout: number; } diff --git a/packages/sdk/src/types/connect.ts b/packages/sdk/src/types/connect.ts index ba14d562..c07ad41e 100644 --- a/packages/sdk/src/types/connect.ts +++ b/packages/sdk/src/types/connect.ts @@ -1,9 +1,9 @@ -import type { Client } from '../client' +import type { Client } from '../client'; export interface ConnectRequestParams { - id: string + id: string; } export interface ConnectRequestFunctionParams extends ConnectRequestParams { - client: Client + client: Client; } diff --git a/packages/sdk/src/types/declarations.d.ts b/packages/sdk/src/types/declarations.d.ts index d89a55a7..cb9cc0aa 100644 --- a/packages/sdk/src/types/declarations.d.ts +++ b/packages/sdk/src/types/declarations.d.ts @@ -1,25 +1,25 @@ -declare module 'aes-js' -declare module 'hash.js/lib/hash/sha' +declare module 'aes-js'; +declare module 'hash.js/lib/hash/sha'; declare module 'hash.js/lib/hash/ripemd.js' { export function ripemd160(): { - update: (data: any) => any - digest: (encoding?: any) => any - } + update: (data: any) => any; + digest: (encoding?: any) => any; + }; } declare module 'lodash/inRange.js' { - const fn: (number: any, start?: any, end?: any) => boolean - export default fn + const fn: (number: any, start?: any, end?: any) => boolean; + export default fn; } declare module 'lodash/isInteger.js' { - const fn: (value: any) => boolean - export default fn + const fn: (value: any) => boolean; + export default fn; } declare module 'lodash/isEmpty.js' { - const fn: (value: any) => boolean - export default fn + const fn: (value: any) => boolean; + export default fn; } // Add more flexible typing to reduce strict type checking for complex modules declare global { - type NodeRequire = (id: string) => any + type NodeRequire = (id: string) => any; } diff --git a/packages/sdk/src/types/fetchActiveWallet.ts b/packages/sdk/src/types/fetchActiveWallet.ts index 39c2ce0b..da6c7dae 100644 --- a/packages/sdk/src/types/fetchActiveWallet.ts +++ b/packages/sdk/src/types/fetchActiveWallet.ts @@ -1,9 +1,9 @@ -import type { Client } from '../client' +import type { Client } from '../client'; export interface FetchActiveWalletRequestFunctionParams { - client: Client + client: Client; } export interface ValidatedFetchActiveWalletRequest { - sharedSecret: Buffer + sharedSecret: Buffer; } diff --git a/packages/sdk/src/types/fetchEncData.ts b/packages/sdk/src/types/fetchEncData.ts index 497b1e8c..f6d1d76b 100644 --- a/packages/sdk/src/types/fetchEncData.ts +++ b/packages/sdk/src/types/fetchEncData.ts @@ -1,26 +1,26 @@ -import type { Client } from '../client' +import type { Client } from '../client'; export interface EIP2335KeyExportReq { - path: number[] - c?: number - kdf?: number - walletUID?: Buffer + path: number[]; + c?: number; + kdf?: number; + walletUID?: Buffer; } export interface FetchEncDataRequest { - schema: number - params: EIP2335KeyExportReq // NOTE: This is a union, but only one type of request exists currently + schema: number; + params: EIP2335KeyExportReq; // NOTE: This is a union, but only one type of request exists currently } export interface FetchEncDataRequestFunctionParams extends FetchEncDataRequest { - client: Client + client: Client; } export interface EIP2335KeyExportData { - iterations: number - cipherText: Buffer - salt: Buffer - checksum: Buffer - iv: Buffer - pubkey: Buffer + iterations: number; + cipherText: Buffer; + salt: Buffer; + checksum: Buffer; + iv: Buffer; + pubkey: Buffer; } diff --git a/packages/sdk/src/types/firmware.ts b/packages/sdk/src/types/firmware.ts index 62c8cf3d..d3b6bab3 100644 --- a/packages/sdk/src/types/firmware.ts +++ b/packages/sdk/src/types/firmware.ts @@ -1,65 +1,65 @@ -import type { EXTERNAL } from '../constants' +import type { EXTERNAL } from '../constants'; -export type FirmwareArr = [number, number, number] +export type FirmwareArr = [number, number, number]; export interface FirmwareVersion { - major: number - minor: number - fix: number + major: number; + minor: number; + fix: number; } export interface GenericSigningData { calldataDecoding: { - reserved: number - maxSz: number - } - baseReqSz: number + reserved: number; + maxSz: number; + }; + baseReqSz: number; // See `GENERIC_SIGNING_BASE_MSG_SZ` in firmware - baseDataSz: number - hashTypes: typeof EXTERNAL.SIGNING.HASHES - curveTypes: typeof EXTERNAL.SIGNING.CURVES + baseDataSz: number; + hashTypes: typeof EXTERNAL.SIGNING.HASHES; + curveTypes: typeof EXTERNAL.SIGNING.CURVES; encodingTypes: { - NONE: typeof EXTERNAL.SIGNING.ENCODINGS.NONE - SOLANA: typeof EXTERNAL.SIGNING.ENCODINGS.SOLANA - EVM?: typeof EXTERNAL.SIGNING.ENCODINGS.EVM - } + NONE: typeof EXTERNAL.SIGNING.ENCODINGS.NONE; + SOLANA: typeof EXTERNAL.SIGNING.ENCODINGS.SOLANA; + EVM?: typeof EXTERNAL.SIGNING.ENCODINGS.EVM; + }; } export interface FirmwareConstants { - abiCategorySz: number - abiMaxRmv: number - addrFlagsAllowed: boolean - allowBtcLegacyAndSegwitAddrs: boolean - allowedEthTxTypes: number[] - contractDeployKey: string - eip712MaxTypeParams: number - eip712Supported: boolean - ethMaxDataSz: number - ethMaxGasPrice: number - ethMaxMsgSz: number - ethMsgPreHashAllowed: boolean - extraDataFrameSz: number - extraDataMaxFrames: number - genericSigning: GenericSigningData + abiCategorySz: number; + abiMaxRmv: number; + addrFlagsAllowed: boolean; + allowBtcLegacyAndSegwitAddrs: boolean; + allowedEthTxTypes: number[]; + contractDeployKey: string; + eip712MaxTypeParams: number; + eip712Supported: boolean; + ethMaxDataSz: number; + ethMaxGasPrice: number; + ethMaxMsgSz: number; + ethMsgPreHashAllowed: boolean; + extraDataFrameSz: number; + extraDataMaxFrames: number; + genericSigning: GenericSigningData; getAddressFlags: [ typeof EXTERNAL.GET_ADDR_FLAGS.ED25519_PUB, typeof EXTERNAL.GET_ADDR_FLAGS.SECP256K1_PUB, - ] - kvActionMaxNum: number - kvActionsAllowed: boolean - kvKeyMaxStrSz: number - kvRemoveMaxNum: number - kvValMaxStrSz: number - maxDecoderBufSz: number - personalSignHeaderSz: number - prehashAllowed: boolean - reqMaxDataSz: number - varAddrPathSzAllowed: boolean - flexibleAddrPaths?: boolean + ]; + kvActionMaxNum: number; + kvActionsAllowed: boolean; + kvKeyMaxStrSz: number; + kvRemoveMaxNum: number; + kvValMaxStrSz: number; + maxDecoderBufSz: number; + personalSignHeaderSz: number; + prehashAllowed: boolean; + reqMaxDataSz: number; + varAddrPathSzAllowed: boolean; + flexibleAddrPaths?: boolean; } export interface LatticeError { - code: string - errno: string - message: string + code: string; + errno: string; + message: string; } diff --git a/packages/sdk/src/types/getAddresses.ts b/packages/sdk/src/types/getAddresses.ts index fa780c28..7ec1bff5 100644 --- a/packages/sdk/src/types/getAddresses.ts +++ b/packages/sdk/src/types/getAddresses.ts @@ -1,13 +1,13 @@ -import type { Client } from '../client' +import type { Client } from '../client'; export interface GetAddressesRequestParams { - startPath: number[] - n: number - flag?: number - iterIdx?: number + startPath: number[]; + n: number; + flag?: number; + iterIdx?: number; } export interface GetAddressesRequestFunctionParams extends GetAddressesRequestParams { - client: Client + client: Client; } diff --git a/packages/sdk/src/types/getKvRecords.ts b/packages/sdk/src/types/getKvRecords.ts index a95c1df9..f4c1d0e0 100644 --- a/packages/sdk/src/types/getKvRecords.ts +++ b/packages/sdk/src/types/getKvRecords.ts @@ -1,26 +1,26 @@ -import type { Client } from '../client' +import type { Client } from '../client'; export interface GetKvRecordsRequestParams { - type?: number - n?: number - start?: number + type?: number; + n?: number; + start?: number; } export interface GetKvRecordsRequestFunctionParams extends GetKvRecordsRequestParams { - client: Client + client: Client; } export type AddressTag = { - caseSensitive: boolean - id: number - key: string - type: number - val: string -} + caseSensitive: boolean; + id: number; + key: string; + type: number; + val: string; +}; export interface GetKvRecordsData { - records: AddressTag[] - fetched: number - total: number + records: AddressTag[]; + fetched: number; + total: number; } diff --git a/packages/sdk/src/types/index.ts b/packages/sdk/src/types/index.ts index 770fbbaa..b111da12 100644 --- a/packages/sdk/src/types/index.ts +++ b/packages/sdk/src/types/index.ts @@ -1,47 +1,47 @@ // Re-export everything from client.ts -export * from './client' +export * from './client'; // Re-export everything from addKvRecords.ts -export * from './addKvRecords' +export * from './addKvRecords'; // Re-export everything from connect.ts -export * from './connect' +export * from './connect'; // Re-export everything from fetchActiveWallet.ts -export * from './fetchActiveWallet' +export * from './fetchActiveWallet'; // Re-export everything from fetchEncData.ts -export * from './fetchEncData' +export * from './fetchEncData'; // Re-export everything from firmware.ts -export * from './firmware' +export * from './firmware'; // Re-export everything from getAddresses.ts -export * from './getAddresses' +export * from './getAddresses'; // Re-export everything from getKvRecords.ts -export * from './getKvRecords' +export * from './getKvRecords'; // Re-export everything from messages.ts -export * from './messages' +export * from './messages'; // Re-export everything from pair.ts -export * from './pair' +export * from './pair'; // Re-export everything from removeKvRecords.ts -export * from './removeKvRecords' +export * from './removeKvRecords'; // Re-export everything from secureMessages.ts -export * from './secureMessages' +export * from './secureMessages'; // Re-export everything from shared.ts -export * from './shared' +export * from './shared'; // Re-export everything from sign.ts -export * from './sign' +export * from './sign'; // Re-export everything from utils.ts -export * from './utils' +export * from './utils'; // We don't need to export from vitest.d.ts as it's a declaration file for Vitest @@ -56,13 +56,13 @@ export type { ActiveWallets, RequestParams, ClientStateData, -} from './client' +} from './client'; // Exports from addKvRecords.ts export type { AddKvRecordsRequestParams, AddKvRecordsRequestFunctionParams, -} from './addKvRecords' +} from './addKvRecords'; // Exports from fetchEncData.ts export type { @@ -70,7 +70,7 @@ export type { FetchEncDataRequest, FetchEncDataRequestFunctionParams, EIP2335KeyExportData, -} from './fetchEncData' +} from './fetchEncData'; // Exports from getKvRecords.ts export type { @@ -78,13 +78,13 @@ export type { GetKvRecordsRequestFunctionParams, AddressTag, GetKvRecordsData, -} from './getKvRecords' +} from './getKvRecords'; // Exports from removeKvRecords.ts export type { RemoveKvRecordsRequestParams, RemoveKvRecordsRequestFunctionParams, -} from './removeKvRecords' +} from './removeKvRecords'; // Exports from shared.ts export type { @@ -93,7 +93,7 @@ export type { KeyPair, WalletPath, DecryptedResponse, -} from './shared' +} from './shared'; // Note: We don't export from vitest.d.ts as it's a declaration file for Vitest diff --git a/packages/sdk/src/types/messages.ts b/packages/sdk/src/types/messages.ts index 24616fb5..78a77821 100644 --- a/packages/sdk/src/types/messages.ts +++ b/packages/sdk/src/types/messages.ts @@ -1,19 +1,19 @@ -import type { LatticeMsgType } from '../protocol' +import type { LatticeMsgType } from '../protocol'; export interface LatticeMessageHeader { // Protocol version. Should always be 0x01 // [uint8] - version: number + version: number; // Protocol request type. Should always be 0x02 // for "secure" message type. // [uint8] - type: LatticeMsgType + type: LatticeMsgType; // Random message ID for internal tracking in firmware // [4 bytes] - id: Buffer + id: Buffer; // Length of payload data being used // For an encrypted request, this indicates the // size of the non-zero decrypted data. // [uint16] - len: number + len: number; } diff --git a/packages/sdk/src/types/pair.ts b/packages/sdk/src/types/pair.ts index ba75116a..6fb54b83 100644 --- a/packages/sdk/src/types/pair.ts +++ b/packages/sdk/src/types/pair.ts @@ -1,6 +1,6 @@ -import type { Client } from '../client' +import type { Client } from '../client'; export interface PairRequestParams { - pairingSecret: string - client: Client + pairingSecret: string; + client: Client; } diff --git a/packages/sdk/src/types/removeKvRecords.ts b/packages/sdk/src/types/removeKvRecords.ts index 1b47016b..a8cfc747 100644 --- a/packages/sdk/src/types/removeKvRecords.ts +++ b/packages/sdk/src/types/removeKvRecords.ts @@ -1,11 +1,11 @@ -import type { Client } from '../client' +import type { Client } from '../client'; export interface RemoveKvRecordsRequestParams { - type?: number - ids?: string[] + type?: number; + ids?: string[]; } export interface RemoveKvRecordsRequestFunctionParams extends RemoveKvRecordsRequestParams { - client: Client + client: Client; } diff --git a/packages/sdk/src/types/secureMessages.ts b/packages/sdk/src/types/secureMessages.ts index ccfe842d..ebbb9127 100644 --- a/packages/sdk/src/types/secureMessages.ts +++ b/packages/sdk/src/types/secureMessages.ts @@ -1,59 +1,59 @@ -import type { LatticeResponseCode, LatticeSecureMsgType } from '../protocol' -import type { LatticeMessageHeader } from './messages' +import type { LatticeResponseCode, LatticeSecureMsgType } from '../protocol'; +import type { LatticeMessageHeader } from './messages'; export interface LatticeSecureRequest { // Message header - header: LatticeMessageHeader + header: LatticeMessageHeader; // Request data - payload: LatticeSecureRequestPayload + payload: LatticeSecureRequestPayload; } export interface LatticeSecureRequestPayload { // Indicates whether this is a connect (0x01) or // encrypted (0x02) secure request // [uint8] - requestType: LatticeSecureMsgType + requestType: LatticeSecureMsgType; // Request data // [connect = 65 bytes, encrypted = 1732] - data: Buffer + data: Buffer; } export interface LatticeSecureConnectResponsePayload { // [214 bytes] - data: Buffer + data: Buffer; } export interface LatticeSecureEncryptedResponsePayload { // Error code - responseCode: LatticeResponseCode + responseCode: LatticeResponseCode; // Response data // [3392 bytes] - data: Buffer + data: Buffer; } export interface LatticeSecureConnectRequestPayloadData { // Public key corresponding to the static Client keypair // [65 bytes] - pubkey: Buffer + pubkey: Buffer; } export interface LatticeSecureEncryptedRequestPayloadData { // SHA256(sharedSecret).slice(0, 4) // [uint32] - ephemeralId: number + ephemeralId: number; // Encrypted data envelope // [1728 bytes] - encryptedData: Buffer + encryptedData: Buffer; } export interface LatticeSecureDecryptedResponse { // ECDSA public key that should replace the client's ephemeral key // [65 bytes] - ephemeralPub: Buffer + ephemeralPub: Buffer; // Decrypted response data // [Variable size] - data: Buffer + data: Buffer; // Checksum on response data (ephemeralKey | data) // [uint32] - checksum: number + checksum: number; } diff --git a/packages/sdk/src/types/shared.ts b/packages/sdk/src/types/shared.ts index c7b1edfb..9592cb4c 100644 --- a/packages/sdk/src/types/shared.ts +++ b/packages/sdk/src/types/shared.ts @@ -1,19 +1,19 @@ -import type { ec } from 'elliptic' +import type { ec } from 'elliptic'; export interface KVRecords { - [key: string]: string + [key: string]: string; } export interface EncrypterParams { - payload: Buffer - sharedSecret: Buffer + payload: Buffer; + sharedSecret: Buffer; } -export type KeyPair = ec.KeyPair +export type KeyPair = ec.KeyPair; -export type WalletPath = [number, number, number, number, number] +export type WalletPath = [number, number, number, number, number]; export interface DecryptedResponse { - decryptedData: Buffer - newEphemeralPub: KeyPair + decryptedData: Buffer; + newEphemeralPub: KeyPair; } diff --git a/packages/sdk/src/types/sign.ts b/packages/sdk/src/types/sign.ts index cf2ca116..f9ed2121 100644 --- a/packages/sdk/src/types/sign.ts +++ b/packages/sdk/src/types/sign.ts @@ -6,12 +6,12 @@ import type { SignedAuthorizationList, TypedData, TypedDataDefinition, -} from 'viem' -import type { Client } from '../client' -import type { Currency, SigningPath, Wallet } from './client' -import type { FirmwareConstants } from './firmware' +} from 'viem'; +import type { Client } from '../client'; +import type { Currency, SigningPath, Wallet } from './client'; +import type { FirmwareConstants } from './firmware'; -export type ETH_MESSAGE_PROTOCOLS = 'eip712' | 'signPersonal' +export type ETH_MESSAGE_PROTOCOLS = 'eip712' | 'signPersonal'; export const TRANSACTION_TYPE = { LEGACY: 0, @@ -19,57 +19,57 @@ export const TRANSACTION_TYPE = { EIP1559: 2, EIP7702_AUTH: 4, EIP7702_AUTH_LIST: 5, -} as const +} as const; // Base transaction request with common fields type BaseTransactionRequest = { - from?: Address - to: Address - value?: Hex | bigint - data?: Hex - chainId: number - nonce: number - gasLimit?: Hex | bigint -} + from?: Address; + to: Address; + value?: Hex | bigint; + data?: Hex; + chainId: number; + nonce: number; + gasLimit?: Hex | bigint; +}; // Legacy transaction request type LegacyTransactionRequest = BaseTransactionRequest & { - type: typeof TRANSACTION_TYPE.LEGACY - gasPrice: Hex | bigint -} + type: typeof TRANSACTION_TYPE.LEGACY; + gasPrice: Hex | bigint; +}; // EIP-2930 transaction request type EIP2930TransactionRequest = BaseTransactionRequest & { - type: typeof TRANSACTION_TYPE.EIP2930 - gasPrice: Hex | bigint - accessList?: AccessList -} + type: typeof TRANSACTION_TYPE.EIP2930; + gasPrice: Hex | bigint; + accessList?: AccessList; +}; // EIP-1559 transaction request type EIP1559TransactionRequest = BaseTransactionRequest & { - type: typeof TRANSACTION_TYPE.EIP1559 - maxFeePerGas: Hex | bigint - maxPriorityFeePerGas: Hex | bigint - accessList?: AccessList -} + type: typeof TRANSACTION_TYPE.EIP1559; + maxFeePerGas: Hex | bigint; + maxPriorityFeePerGas: Hex | bigint; + accessList?: AccessList; +}; // EIP-7702 single authorization transaction request (type 4) export type EIP7702AuthTransactionRequest = BaseTransactionRequest & { - type: typeof TRANSACTION_TYPE.EIP7702_AUTH - maxFeePerGas: Hex | bigint - maxPriorityFeePerGas: Hex | bigint - accessList?: AccessList - authorization: SignedAuthorization -} + type: typeof TRANSACTION_TYPE.EIP7702_AUTH; + maxFeePerGas: Hex | bigint; + maxPriorityFeePerGas: Hex | bigint; + accessList?: AccessList; + authorization: SignedAuthorization; +}; // EIP-7702 authorization list transaction request (type 5) export type EIP7702AuthListTransactionRequest = BaseTransactionRequest & { - type: typeof TRANSACTION_TYPE.EIP7702_AUTH_LIST - maxFeePerGas: Hex | bigint - maxPriorityFeePerGas: Hex | bigint - accessList?: AccessList - authorizationList: SignedAuthorizationList -} + type: typeof TRANSACTION_TYPE.EIP7702_AUTH_LIST; + maxFeePerGas: Hex | bigint; + maxPriorityFeePerGas: Hex | bigint; + accessList?: AccessList; + authorizationList: SignedAuthorizationList; +}; // Main discriminated union for transaction requests export type TransactionRequest = @@ -77,104 +77,104 @@ export type TransactionRequest = | EIP2930TransactionRequest | EIP1559TransactionRequest | EIP7702AuthTransactionRequest - | EIP7702AuthListTransactionRequest + | EIP7702AuthListTransactionRequest; export interface SigningPayload< TTypedData extends TypedData | Record = TypedData, > { - signerPath: SigningPath + signerPath: SigningPath; payload: | Uint8Array | Uint8Array[] | Buffer | Buffer[] | Hex - | EIP712MessagePayload - curveType: number - hashType: number - encodingType?: number - protocol?: ETH_MESSAGE_PROTOCOLS - decoder?: Buffer + | EIP712MessagePayload; + curveType: number; + hashType: number; + encodingType?: number; + protocol?: ETH_MESSAGE_PROTOCOLS; + decoder?: Buffer; } export interface SignRequestParams< TTypedData extends TypedData | Record = TypedData, > { - data: SigningPayload | BitcoinSignPayload - currency?: Currency - cachedData?: unknown - nextCode?: Buffer + data: SigningPayload | BitcoinSignPayload; + currency?: Currency; + cachedData?: unknown; + nextCode?: Buffer; } export interface SignRequestFunctionParams< TTypedData extends TypedData | Record = TypedData, > extends SignRequestParams { - client: Client + client: Client; } export interface EncodeSignRequestParams { - fwConstants: FirmwareConstants - wallet: Wallet - requestData: unknown - cachedData?: unknown - nextCode?: Buffer + fwConstants: FirmwareConstants; + wallet: Wallet; + requestData: unknown; + cachedData?: unknown; + nextCode?: Buffer; } export interface SignRequest { - payload: Buffer - schema: number + payload: Buffer; + schema: number; } export interface EthSignRequest extends SignRequest { - curveType: number - encodingType: number - hashType: number - omitPubkey: boolean - origPayloadBuf: Buffer - extraDataPayloads: Buffer[] + curveType: number; + encodingType: number; + hashType: number; + omitPubkey: boolean; + origPayloadBuf: Buffer; + extraDataPayloads: Buffer[]; } export interface EthMsgSignRequest extends SignRequest { input: { - signerPath: SigningPath - payload: Buffer - protocol: string - fwConstants: FirmwareConstants - } + signerPath: SigningPath; + payload: Buffer; + protocol: string; + fwConstants: FirmwareConstants; + }; } export interface BitcoinSignRequest extends SignRequest { origData: { - prevOuts: PreviousOutput[] - recipient: string - value: number - fee: number - changePath: number[] - fwConstants: FirmwareConstants - } - changeData?: { value: number } + prevOuts: PreviousOutput[]; + recipient: string; + value: number; + fee: number; + changePath: number[]; + fwConstants: FirmwareConstants; + }; + changeData?: { value: number }; } export type PreviousOutput = { - txHash: string - value: number - index: number - signerPath: number[] -} + txHash: string; + value: number; + index: number; + signerPath: number[]; +}; export type BitcoinSignPayload = { - prevOuts: PreviousOutput[] - recipient: string - value: number - fee: number - changePath: number[] -} + prevOuts: PreviousOutput[]; + recipient: string; + value: number; + fee: number; + changePath: number[]; +}; export interface DecodeSignResponseParams { - data: Buffer - request: SignRequest - isGeneric: boolean - currency?: Currency + data: Buffer; + request: SignRequest; + isGeneric: boolean; + currency?: Currency; } // Align EIP712MessagePayload with Viem's TypedDataDefinition @@ -182,12 +182,12 @@ export interface EIP712MessagePayload< TTypedData extends TypedData | Record = TypedData, TPrimaryType extends keyof TTypedData | 'EIP712Domain' = keyof TTypedData, > { - types: TTypedData + types: TTypedData; domain: TTypedData extends TypedData ? TypedDataDefinition['domain'] - : Record - primaryType: TPrimaryType + : Record; + primaryType: TPrimaryType; message: TTypedData extends TypedData ? TypedDataDefinition['message'] - : Record + : Record; } diff --git a/packages/sdk/src/types/tiny-secp256k1.d.ts b/packages/sdk/src/types/tiny-secp256k1.d.ts index 6e83e4b0..6199738e 100644 --- a/packages/sdk/src/types/tiny-secp256k1.d.ts +++ b/packages/sdk/src/types/tiny-secp256k1.d.ts @@ -1 +1 @@ -declare module 'tiny-secp256k1' +declare module 'tiny-secp256k1'; diff --git a/packages/sdk/src/types/utils.ts b/packages/sdk/src/types/utils.ts index 7e50d708..c82b10cc 100644 --- a/packages/sdk/src/types/utils.ts +++ b/packages/sdk/src/types/utils.ts @@ -1,34 +1,34 @@ -import type { Client } from '../client' +import type { Client } from '../client'; export interface TestRequestPayload { - payload: Buffer - testID: number - client: Client + payload: Buffer; + testID: number; + client: Client; } export interface EthDepositInfo { - networkName: string - forkVersion: Buffer - validatorsRoot: Buffer + networkName: string; + forkVersion: Buffer; + validatorsRoot: Buffer; } export interface EthDepositDataReq { // (optional) BLS withdrawal key or ETH1 withdrawal address - withdrawalKey?: Buffer | string + withdrawalKey?: Buffer | string; // Amount to be deposited in GWei (10**9 wei) - amountGwei: number + amountGwei: number; // Info about the chain we are using. // You probably shouldn't change this unless you know what you're doing. - info: EthDepositInfo + info: EthDepositInfo; // In order to be compatible with Ethereum's online launchpad, you need // to set the CLI version. Obviously we are not using the CLI here but // we are following the protocol outlined in v2.3.0. - depositCliVersion: string + depositCliVersion: string; } export interface EthDepositDataResp { // Validator's pubkey as a hex string - pubkey: string + pubkey: string; // JSON encoded deposit data - depositData: string + depositData: string; } diff --git a/packages/sdk/src/types/vitest.d.ts b/packages/sdk/src/types/vitest.d.ts index 8c44ccc2..2e25ea81 100644 --- a/packages/sdk/src/types/vitest.d.ts +++ b/packages/sdk/src/types/vitest.d.ts @@ -1,8 +1,8 @@ -export {} +export {}; declare global { namespace Vi { interface JestAssertion { - toEqualElseLog(a: any, msg: string): any + toEqualElseLog(a: any, msg: string): any; } } } diff --git a/packages/sdk/src/util.ts b/packages/sdk/src/util.ts index d9f65e9b..ee80e6ff 100644 --- a/packages/sdk/src/util.ts +++ b/packages/sdk/src/util.ts @@ -1,36 +1,36 @@ -import { Buffer } from 'node:buffer' +import { Buffer } from 'node:buffer'; // Static utility functions -import { RLP } from '@ethereumjs/rlp' -import aes from 'aes-js' -import BigNum from 'bignumber.js' -import { BN } from 'bn.js' -import crc32 from 'crc-32' -import elliptic from 'elliptic' -import inRange from 'lodash/inRange.js' -import isInteger from 'lodash/isInteger.js' -import { Hash } from 'ox' -import secp256k1 from 'secp256k1' -import { type Hex, parseTransaction } from 'viem' - -const EC = elliptic.ec -const { ecdsaRecover } = secp256k1 -import { Calldata } from '.' +import { RLP } from '@ethereumjs/rlp'; +import aes from 'aes-js'; +import BigNum from 'bignumber.js'; +import { BN } from 'bn.js'; +import crc32 from 'crc-32'; +import elliptic from 'elliptic'; +import inRange from 'lodash/inRange.js'; +import isInteger from 'lodash/isInteger.js'; +import { Hash } from 'ox'; +import secp256k1 from 'secp256k1'; +import { type Hex, parseTransaction } from 'viem'; + +const EC = elliptic.ec; +const { ecdsaRecover } = secp256k1; +import { Calldata } from '.'; import { BIP_CONSTANTS, EXTERNAL_NETWORKS_BY_CHAIN_ID_URL, HARDENED_OFFSET, NETWORKS_BY_CHAIN_ID, VERSION_BYTE, -} from './constants' -import { LatticeResponseCode, ProtocolConstants } from './protocol' +} from './constants'; +import { LatticeResponseCode, ProtocolConstants } from './protocol'; import { isValid4ByteResponse, isValidBlockExplorerResponse, -} from './shared/validators' -import type { FirmwareConstants } from './types' +} from './shared/validators'; +import type { FirmwareConstants } from './types'; -const { COINS, PURPOSES } = BIP_CONSTANTS -let ec: any +const { COINS, PURPOSES } = BIP_CONSTANTS; +let ec: any; //-------------------------------------------------- // LATTICE UTILS @@ -40,75 +40,75 @@ let ec: any export const parseLattice1Response = ( r: string, ): { - errorMessage?: string - responseCode?: number - data?: Buffer + errorMessage?: string; + responseCode?: number; + data?: Buffer; } => { const parsed: { - errorMessage: string | null - data: Buffer | null - responseCode?: number + errorMessage: string | null; + data: Buffer | null; + responseCode?: number; } = { errorMessage: null, data: null, - } - const b = Buffer.from(r, 'hex') - let off = 0 + }; + const b = Buffer.from(r, 'hex'); + let off = 0; // Get protocol version - const protoVer = b.readUInt8(off) - off++ + const protoVer = b.readUInt8(off); + off++; if (protoVer !== VERSION_BYTE) { - parsed.errorMessage = 'Incorrect protocol version. Please update your SDK' - return parsed + parsed.errorMessage = 'Incorrect protocol version. Please update your SDK'; + return parsed; } // Get the type of response // Should always be 0x00 - const msgType = b.readUInt8(off) - off++ + const msgType = b.readUInt8(off); + off++; if (msgType !== 0x00) { - parsed.errorMessage = 'Incorrect response from Lattice1' - return parsed + parsed.errorMessage = 'Incorrect response from Lattice1'; + return parsed; } // Get the payload - b.readUInt32BE(off) - off += 4 // First 4 bytes is the id, but we don't need that anymore - const len = b.readUInt16BE(off) - off += 2 - const payload = b.slice(off, off + len) - off += len + b.readUInt32BE(off); + off += 4; // First 4 bytes is the id, but we don't need that anymore + const len = b.readUInt16BE(off); + off += 2; + const payload = b.slice(off, off + len); + off += len; // Get response code - const responseCode = payload.readUInt8(0) + const responseCode = payload.readUInt8(0); if (responseCode !== LatticeResponseCode.success) { - const errMsg = ProtocolConstants.responseMsg[responseCode] - parsed.errorMessage = `[Lattice] ${errMsg ? errMsg : 'Unknown Error'}` - parsed.responseCode = responseCode - return parsed + const errMsg = ProtocolConstants.responseMsg[responseCode]; + parsed.errorMessage = `[Lattice] ${errMsg ? errMsg : 'Unknown Error'}`; + parsed.responseCode = responseCode; + return parsed; } else { - parsed.data = payload.slice(1, payload.length) + parsed.data = payload.slice(1, payload.length); } // Verify checksum - const cs = b.readUInt32BE(off) - const expectedCs = checksum(b.slice(0, b.length - 4)) + const cs = b.readUInt32BE(off); + const expectedCs = checksum(b.slice(0, b.length - 4)); if (cs !== expectedCs) { - parsed.errorMessage = 'Invalid checksum from device response' - parsed.data = null - return parsed + parsed.errorMessage = 'Invalid checksum from device response'; + parsed.data = null; + return parsed; } - return parsed -} + return parsed; +}; /** @internal */ export const checksum = (x: Buffer): number => { // crc32 returns a signed integer - need to cast it to unsigned // Note that this uses the default 0xedb88320 polynomial - return crc32.buf(x) >>> 0 // Need this to be a uint, hence the bit shift -} + return crc32.buf(x) >>> 0; // Need this to be a uint, hence the bit shift +}; // Get a 74-byte padded DER-encoded signature buffer // `sig` must be the signature output from elliptic.js @@ -116,11 +116,11 @@ export const checksum = (x: Buffer): number => { export const toPaddedDER = (sig: any): Buffer => { // We use 74 as the maximum length of a DER signature. All sigs must // be right-padded with zeros so that this can be a fixed size field - const b = Buffer.alloc(74) - const ds = Buffer.from(sig.toDER()) - ds.copy(b) - return b -} + const b = Buffer.alloc(74); + const ds = Buffer.from(sig.toDER()); + ds.copy(b); + return b; +}; //-------------------------------------------------- // TRANSACTION UTILS @@ -135,8 +135,8 @@ export const isValidAssetPath = ( PURPOSES.BTC_LEGACY, PURPOSES.BTC_WRAPPED_SEGWIT, PURPOSES.BTC_SEGWIT, - ] - const allowedCoins = [COINS.ETH, COINS.BTC, COINS.BTC_TESTNET] + ]; + const allowedCoins = [COINS.ETH, COINS.BTC, COINS.BTC_TESTNET]; // These coin types were given to us by MyCrypto. They should be allowed, but we expect // an Ethereum-type address with these coin types. // These all use SLIP44: https://github.com/satoshilabs/slips/blob/master/slip-0044.md @@ -144,40 +144,40 @@ export const isValidAssetPath = ( 60, 61, 966, 700, 9006, 9000, 1007, 553, 178, 137, 37310, 108, 40, 889, 1987, 820, 6060, 1620, 1313114, 76, 246529, 246785, 1001, 227, 916, 464, 2221, 344, 73799, 246, - ] + ]; // Make sure firmware supports this Bitcoin path - const isBitcoin = path[1] === COINS.BTC || path[1] === COINS.BTC_TESTNET + const isBitcoin = path[1] === COINS.BTC || path[1] === COINS.BTC_TESTNET; const isBitcoinNonWrappedSegwit = - isBitcoin && path[0] !== PURPOSES.BTC_WRAPPED_SEGWIT + isBitcoin && path[0] !== PURPOSES.BTC_WRAPPED_SEGWIT; if (isBitcoinNonWrappedSegwit && !fwConstants.allowBtcLegacyAndSegwitAddrs) - return false + return false; // Make sure this path is otherwise valid return ( allowedPurposes.indexOf(path[0]) >= 0 && (allowedCoins.indexOf(path[1]) >= 0 || allowedMyCryptoCoins.indexOf(path[1] - HARDENED_OFFSET) > 0) - ) -} + ); +}; /** @internal */ export const splitFrames = (data: Buffer, frameSz: number): Buffer[] => { - const frames = [] - const n = Math.ceil(data.length / frameSz) - let off = 0 + const frames = []; + const n = Math.ceil(data.length / frameSz); + let off = 0; for (let i = 0; i < n; i++) { - frames.push(data.slice(off, off + frameSz)) - off += frameSz + frames.push(data.slice(off, off + frameSz)); + off += frameSz; } - return frames -} + return frames; +}; /** @internal */ function isBase10NumStr(x: string): boolean { - const bn = new BigNum(x).toFixed().split('.').join('') - const s = new String(x) + const bn = new BigNum(x).toFixed().split('.').join(''); + const s = new String(x); // Note that the JS native `String()` loses precision for large numbers, but we only // want to validate the base of the number so we don't care about far out precision. - return bn.slice(0, 8) === s.slice(0, 8) + return bn.slice(0, 8) === s.slice(0, 8); } /** @internal Ensure a param is represented by a buffer */ @@ -186,118 +186,119 @@ export const ensureHexBuffer = ( zeroIsNull = true, ): Buffer => { try { - const isZeroNumber = typeof x === 'number' && x === 0 - const isZeroBigInt = typeof x === 'bigint' && x === 0n + const isZeroNumber = typeof x === 'number' && x === 0; + const isZeroBigInt = typeof x === 'bigint' && x === 0n; if (x === null || ((isZeroNumber || isZeroBigInt) && zeroIsNull === true)) - return Buffer.alloc(0) + return Buffer.alloc(0); const isDecimalInput = typeof x === 'number' || typeof x === 'bigint' || - (typeof x === 'string' && isBase10NumStr(x)) - let hexString: string + (typeof x === 'string' && isBase10NumStr(x)); + let hexString: string; if (isDecimalInput) { const formatted = - typeof x === 'bigint' ? x.toString(10) : (x as string | number) - hexString = new BigNum(formatted).toString(16) + typeof x === 'bigint' ? x.toString(10) : (x as string | number); + hexString = new BigNum(formatted).toString(16); } else if (typeof x === 'string' && x.slice(0, 2) === '0x') { - hexString = x.slice(2) + hexString = x.slice(2); } else if (Buffer.isBuffer(x)) { - return x + return x; } else { - hexString = x.toString() + hexString = x.toString(); } - if (hexString.length % 2 > 0) hexString = `0${hexString}` - if (hexString === '00' && !isDecimalInput) return Buffer.alloc(0) - return Buffer.from(hexString, 'hex') + if (hexString.length % 2 > 0) hexString = `0${hexString}`; + if (hexString === '00' && !isDecimalInput) return Buffer.alloc(0); + return Buffer.from(hexString, 'hex'); } catch (_err) { throw new Error( `Cannot convert ${x.toString()} to hex buffer (${(_err as Error).message})`, - ) + ); } -} +}; /** @internal */ export const fixLen = (msg: Buffer, length: number): Buffer => { - const buf = Buffer.alloc(length) + const buf = Buffer.alloc(length); if (msg.length < length) { - msg.copy(buf, length - msg.length) - return buf + msg.copy(buf, length - msg.length); + return buf; } - return msg.slice(-length) -} + return msg.slice(-length); +}; //-------------------------------------------------- // CRYPTO UTILS //-------------------------------------------------- /** @internal */ export const aes256_encrypt = (data: Buffer, key: Buffer): Buffer => { - const iv = Buffer.from(ProtocolConstants.aesIv) - const aesCbc = new aes.ModeOfOperation.cbc(key, iv) - const paddedData = data.length % 16 === 0 ? data : aes.padding.pkcs7.pad(data) - return Buffer.from(aesCbc.encrypt(paddedData)) -} + const iv = Buffer.from(ProtocolConstants.aesIv); + const aesCbc = new aes.ModeOfOperation.cbc(key, iv); + const paddedData = + data.length % 16 === 0 ? data : aes.padding.pkcs7.pad(data); + return Buffer.from(aesCbc.encrypt(paddedData)); +}; /** @internal */ export const aes256_decrypt = (data: Buffer, key: Buffer): Buffer => { - const iv = Buffer.from(ProtocolConstants.aesIv) - const aesCbc = new aes.ModeOfOperation.cbc(key, iv) - return Buffer.from(aesCbc.decrypt(data)) -} + const iv = Buffer.from(ProtocolConstants.aesIv); + const aesCbc = new aes.ModeOfOperation.cbc(key, iv); + return Buffer.from(aesCbc.decrypt(data)); +}; // Decode a DER signature. Returns signature object {r, s } or null if there is an error /** @internal */ export const parseDER = (sigBuf: Buffer) => { if (sigBuf[0] !== 0x30 || sigBuf[2] !== 0x02) - throw new Error('Failed to decode DER signature') - let off = 3 - const rLen = sigBuf[off] - off++ - const r = sigBuf.slice(off, off + rLen) - off += rLen - if (sigBuf[off] !== 0x02) throw new Error('Failed to decode DER signature') - off++ - const sLen = sigBuf[off] - off++ - const s = sigBuf.slice(off, off + sLen) - return { r, s } -} + throw new Error('Failed to decode DER signature'); + let off = 3; + const rLen = sigBuf[off]; + off++; + const r = sigBuf.slice(off, off + rLen); + off += rLen; + if (sigBuf[off] !== 0x02) throw new Error('Failed to decode DER signature'); + off++; + const sLen = sigBuf[off]; + off++; + const s = sigBuf.slice(off, off + sLen); + return { r, s }; +}; /** @internal */ export const getP256KeyPair = (priv: Buffer | string): any => { - if (ec === undefined) ec = new EC('p256') - return ec.keyFromPrivate(priv, 'hex') -} + if (ec === undefined) ec = new EC('p256'); + return ec.keyFromPrivate(priv, 'hex'); +}; /** @internal */ export const getP256KeyPairFromPub = (pub: Buffer | string): any => { - if (ec === undefined) ec = new EC('p256') + if (ec === undefined) ec = new EC('p256'); // Convert Buffer to hex string if needed - const pubHex = Buffer.isBuffer(pub) ? pub.toString('hex') : pub - return ec.keyFromPublic(pubHex, 'hex') -} + const pubHex = Buffer.isBuffer(pub) ? pub.toString('hex') : pub; + return ec.keyFromPublic(pubHex, 'hex'); +}; /** @internal */ export const buildSignerPathBuf = ( signerPath: number[], varAddrPathSzAllowed: boolean, ): Buffer => { - const buf = Buffer.alloc(24) - let off = 0 + const buf = Buffer.alloc(24); + let off = 0; if (varAddrPathSzAllowed && signerPath.length > 5) - throw new Error('Signer path must be <=5 indices.') + throw new Error('Signer path must be <=5 indices.'); if (!varAddrPathSzAllowed && signerPath.length !== 5) throw new Error( 'Your Lattice firmware only supports 5-index derivation paths. Please upgrade.', - ) - buf.writeUInt32LE(signerPath.length, off) - off += 4 + ); + buf.writeUInt32LE(signerPath.length, off); + off += 4; for (let i = 0; i < 5; i++) { - if (i < signerPath.length) buf.writeUInt32LE(signerPath[i], off) - else buf.writeUInt32LE(0, off) - off += 4 + if (i < signerPath.length) buf.writeUInt32LE(signerPath[i], off); + else buf.writeUInt32LE(0, off); + off += 4; } - return buf -} + return buf; +}; //-------------------------------------------------- // OTHER UTILS @@ -305,38 +306,38 @@ export const buildSignerPathBuf = ( /** @internal */ export const isAsciiStr = (str: string, allowFormatChars = false): boolean => { if (typeof str !== 'string') { - return false + return false; } const extraChars = allowFormatChars ? [ 0x0020, // Space 0x000a, // New line ] - : [] + : []; for (let i = 0; i < str.length; i++) { - const c = str.charCodeAt(i) + const c = str.charCodeAt(i); if (extraChars.indexOf(c) < 0 && (c < 0x0020 || c > 0x007f)) { - return false + return false; } } - return true -} + return true; +}; /** @internal Check if a value exists in an object. Only checks first level of keys. */ export const existsIn = (val: T, obj: { [key: string]: T }): boolean => - Object.keys(obj).some((key) => obj[key] === val) + Object.keys(obj).some((key) => obj[key] === val); /** @internal Create a buffer of size `n` and fill it with random data */ export const randomBytes = (n: number): Buffer => { - const buf = Buffer.alloc(n) + const buf = Buffer.alloc(n); for (let i = 0; i < n; i++) { - buf[i] = Math.round(Math.random() * 255) + buf[i] = Math.round(Math.random() * 255); } - return buf -} + return buf; +}; /** @internal `isUInt4` accepts a number and returns true if it is a UInt4 */ -export const isUInt4 = (n: number) => isInteger(n) && inRange(n, 0, 16) +export const isUInt4 = (n: number) => isInteger(n) && inRange(n, 0, 16); /** * Fetches an external JSON file containing networks indexed by chain id from a GridPlus repo, and @@ -346,22 +347,22 @@ async function fetchExternalNetworkForChainId( chainId: number | string, ): Promise<{ [key: string]: { - name: string - baseUrl: string - apiRoute: string - } + name: string; + baseUrl: string; + apiRoute: string; + }; }> { try { const body = await fetch(EXTERNAL_NETWORKS_BY_CHAIN_ID_URL).then((res) => res.json(), - ) + ); if (body) { - return body[chainId] + return body[chainId]; } else { - return undefined + return undefined; } } catch (_err) { - console.warn('Fetching external networks failed.\n', _err) + console.warn('Fetching external networks failed.\n', _err); } } @@ -369,14 +370,14 @@ async function fetchExternalNetworkForChainId( * Builds a URL for fetching calldata from block explorers for any supported chains * */ function buildUrlForSupportedChainAndAddress({ supportedChain, address }) { - const baseUrl = supportedChain.baseUrl - const apiRoute = supportedChain.apiRoute - const urlWithRoute = `${baseUrl}/${apiRoute}&address=${address}` + const baseUrl = supportedChain.baseUrl; + const apiRoute = supportedChain.apiRoute; + const urlWithRoute = `${baseUrl}/${apiRoute}&address=${address}`; - const apiKey = process.env.ETHERSCAN_KEY - const apiKeyParam = apiKey ? `&apiKey=${process.env.ETHERSCAN_KEY}` : '' + const apiKey = process.env.ETHERSCAN_KEY; + const apiKeyParam = apiKey ? `&apiKey=${process.env.ETHERSCAN_KEY}` : ''; - return urlWithRoute + apiKeyParam + return urlWithRoute + apiKeyParam; } /** @@ -385,31 +386,31 @@ function buildUrlForSupportedChainAndAddress({ supportedChain, address }) { */ export function selectDefFrom4byteABI(abiData: any[], selector: string) { if (abiData.length > 1) { - console.warn('WARNING: There are multiple results. Using the first one.') + console.warn('WARNING: There are multiple results. Using the first one.'); } - let def: unknown[] | undefined + let def: unknown[] | undefined; abiData .sort((a, b) => { - const aTime = new Date(a.created_at).getTime() - const bTime = new Date(b.created_at).getTime() - return aTime - bTime + const aTime = new Date(a.created_at).getTime(); + const bTime = new Date(b.created_at).getTime(); + return aTime - bTime; }) .find((result) => { try { def = Calldata.EVM.parsers.parseCanonicalName( selector, result.text_signature, - ) - return !!def + ); + return !!def; } catch (_err) { - console.error('Failed to parse canonical name:', _err) - return false + console.error('Failed to parse canonical name:', _err); + return false; } - }) + }); if (def) { - return def + return def; } else { - throw new Error('Could not find definition for selector') + throw new Error('Could not find definition for selector'); } } @@ -417,15 +418,15 @@ export async function fetchWithTimeout( url: string, options: RequestInit & { timeout?: number }, ): Promise { - const { timeout = 8000 } = options - const controller = new AbortController() - const timeoutId = setTimeout(() => controller.abort(), timeout) + const { timeout = 8000 } = options; + const controller = new AbortController(); + const timeoutId = setTimeout(() => controller.abort(), timeout); const response = await fetch(url, { ...options, signal: controller.signal, - }) - clearTimeout(timeoutId) - return response + }); + clearTimeout(timeoutId); + return response; } async function fetchAndCache( @@ -434,30 +435,30 @@ async function fetchAndCache( ): Promise { try { if (globalThis.caches && globalThis.Request) { - const cache = await caches.open('gp-calldata') - const request = new Request(url, opts) - const match = await cache.match(request) + const cache = await caches.open('gp-calldata'); + const request = new Request(url, opts); + const match = await cache.match(request); if (match) { - return match + return match; } else { - const response = await fetch(request, opts) - const responseClone = response.clone() - const data = await response.json() + const response = await fetch(request, opts); + const responseClone = response.clone(); + const data = await response.json(); if ( response.ok && (isValidBlockExplorerResponse(data) || isValid4ByteResponse(data)) ) { - await cache.put(request, responseClone) - return cache.match(request) + await cache.put(request, responseClone); + return cache.match(request); } - return response + return response; } } else { - return fetch(url, opts) + return fetch(url, opts); } } catch (err) { - console.error(err) - throw err + console.error(err); + throw err; } } @@ -465,46 +466,46 @@ async function fetchSupportedChainData( address: string, supportedChain: number, ) { - const url = buildUrlForSupportedChainAndAddress({ address, supportedChain }) + const url = buildUrlForSupportedChainAndAddress({ address, supportedChain }); return fetchAndCache(url) .then((res) => res.json()) .then((body) => { if (body?.result) { try { - return JSON.parse(body.result) + return JSON.parse(body.result); } catch { throw new Error( `Invalid JSON in response: ${body.result.substring(0, 50)}`, - ) + ); } } else { - throw new Error('Server response was malformed') + throw new Error('Server response was malformed'); } }) .catch((error) => { - console.log(error) - throw new Error('Fetching data from external network failed') - }) + console.log(error); + throw new Error('Fetching data from external network failed'); + }); } async function fetch4byteData(selector: string): Promise { - const url = `https://www.4byte.directory/api/v1/signatures/?hex_signature=0x${selector}` + const url = `https://www.4byte.directory/api/v1/signatures/?hex_signature=0x${selector}`; return await fetch(url) .then((res) => res.json()) .then((body) => { if (body?.results) { - return body.results + return body.results; } else { - throw new Error('No results found') + throw new Error('No results found'); } }) .catch((err) => { - throw new Error(`Fetching data from 4byte failed: ${err.message}`) - }) + throw new Error(`Fetching data from 4byte failed: ${err.message}`); + }); } function encodeDef(def: any) { - return Buffer.from(RLP.encode(def)) + return Buffer.from(RLP.encode(def)); } /** @@ -523,8 +524,8 @@ async function postProcessDef(def, calldata) { const nestedCalldata = Calldata.EVM.processors.getNestedCalldata( def, calldata, - ) - const nestedDefs = await replaceNestedDefs(nestedCalldata) + ); + const nestedDefs = await replaceNestedDefs(nestedCalldata); // Need to recurse before doing the full replacement for await (const [i] of nestedDefs.entries()) { // If this is an array of nested defs, loop through each one and @@ -536,19 +537,19 @@ async function postProcessDef(def, calldata) { nestedDefs[i][j] = await postProcessDef( nestedDefs[i][j], Buffer.from(nestedCalldata[i][j].slice(2), 'hex'), - ) + ); } } } else if (nestedDefs[i] !== null) { nestedDefs[i] = await postProcessDef( nestedDefs[i], Buffer.from(nestedCalldata[i].slice(2), 'hex'), - ) + ); } } // Replace any nested defs - const newDef = Calldata.EVM.processors.replaceNestedDefs(def, nestedDefs) - return newDef + const newDef = Calldata.EVM.processors.replaceNestedDefs(def, nestedDefs); + return newDef; } /** @@ -566,49 +567,49 @@ async function postProcessDef(def, calldata) { */ async function replaceNestedDefs(possNestedDefs) { // For all possible nested defs, attempt to fetch the underlying def - const nestedDefs = [] + const nestedDefs = []; for await (const d of possNestedDefs) { if (d !== null) { if (Array.isArray(d)) { - const _nestedDefs = [] - let shouldInclude = true + const _nestedDefs = []; + let shouldInclude = true; for await (const _d of d) { try { - const _nestedSelector = _d.slice(2, 10) - const _nestedAbi = await fetch4byteData(_nestedSelector) + const _nestedSelector = _d.slice(2, 10); + const _nestedAbi = await fetch4byteData(_nestedSelector); const _nestedDef = selectDefFrom4byteABI( _nestedAbi, _nestedSelector, - ) - _nestedDefs.push(_nestedDef) + ); + _nestedDefs.push(_nestedDef); } catch (_err) { - console.error('Failed to fetch nested 4byte data:', _err) - shouldInclude = false - _nestedDefs.push(null) + console.error('Failed to fetch nested 4byte data:', _err); + shouldInclude = false; + _nestedDefs.push(null); } } if (shouldInclude) { - nestedDefs.push(_nestedDefs) + nestedDefs.push(_nestedDefs); } else { - nestedDefs.push(null) + nestedDefs.push(null); } } else { try { - const nestedSelector = d.slice(2, 10) - const nestedAbi = await fetch4byteData(nestedSelector) - const nestedDef = selectDefFrom4byteABI(nestedAbi, nestedSelector) - nestedDefs.push(nestedDef) + const nestedSelector = d.slice(2, 10); + const nestedAbi = await fetch4byteData(nestedSelector); + const nestedDef = selectDefFrom4byteABI(nestedAbi, nestedSelector); + nestedDefs.push(nestedDef); } catch (_err) { - console.error('Failed to fetch nested definition:', _err) - nestedDefs.push(null) + console.error('Failed to fetch nested definition:', _err); + nestedDefs.push(null); } } } else { - nestedDefs.push(null) + nestedDefs.push(null); } } // For all nested defs, replace the - return nestedDefs + return nestedDefs; } //-------------------------------------------------- @@ -629,63 +630,63 @@ export async function fetchCalldataDecoder( // Exit if there is no data. The 2 comes from the 0x prefix, but a later // check will confirm that there are at least 4 bytes of data in the buffer. if (!_data || _data.length < 2) { - throw new Error('Data is either undefined or less than two bytes') + throw new Error('Data is either undefined or less than two bytes'); } - const isHexString = typeof _data === 'string' && _data.slice(0, 2) === '0x' + const isHexString = typeof _data === 'string' && _data.slice(0, 2) === '0x'; const data = isHexString ? Buffer.from(_data.slice(2), 'hex') : //@ts-expect-error - Buffer doesn't recognize Uint8Array type properly - Buffer.from(_data, 'hex') + Buffer.from(_data, 'hex'); // For empty data (just '0x'), return early - no calldata to decode if (data.length === 0) { - return { def: null, abi: null } + return { def: null, abi: null }; } if (data.length < 4) { throw new Error( 'Data must contain at least 4 bytes of data to define the selector', - ) + ); } - const selector = Buffer.from(data.slice(0, 4)).toString('hex') + const selector = Buffer.from(data.slice(0, 4)).toString('hex'); // Convert the chainId to a number and use it to determine if we can call out to // an etherscan-like explorer for richer data. - const chainId = Number(_chainId) - const cachedNetwork = NETWORKS_BY_CHAIN_ID[chainId] + const chainId = Number(_chainId); + const cachedNetwork = NETWORKS_BY_CHAIN_ID[chainId]; const supportedChain = cachedNetwork ? cachedNetwork - : await fetchExternalNetworkForChainId(chainId) + : await fetchExternalNetworkForChainId(chainId); try { if (supportedChain) { - const abi = await fetchSupportedChainData(to, supportedChain) + const abi = await fetchSupportedChainData(to, supportedChain); const parsedAbi = Calldata.EVM.parsers.parseSolidityJSONABI( selector, abi, - ) - let def = parsedAbi.def + ); + let def = parsedAbi.def; if (recurse) { - def = await postProcessDef(def, data) + def = await postProcessDef(def, data); } - return { abi, def: encodeDef(def) } + return { abi, def: encodeDef(def) }; } else { - throw new Error(`Chain (id: ${chainId}) is not supported`) + throw new Error(`Chain (id: ${chainId}) is not supported`); } } catch (err) { - console.warn(err.message, '\n', 'Falling back to 4byte') + console.warn(err.message, '\n', 'Falling back to 4byte'); } // Fallback to checking 4byte - const abi = await fetch4byteData(selector) - let def = selectDefFrom4byteABI(abi, selector) + const abi = await fetch4byteData(selector); + let def = selectDefFrom4byteABI(abi, selector); if (recurse) { - def = await postProcessDef(def, data) + def = await postProcessDef(def, data); } - return { abi, def: encodeDef(def) } + return { abi, def: encodeDef(def) }; } catch (err) { - console.warn(`Fetching calldata failed: ${err.message}`) + console.warn(`Fetching calldata failed: ${err.message}`); } - return { def: null, abi: null } + return { def: null, abi: null }; } /** @@ -702,20 +703,20 @@ export const generateAppSecret = ( appName: Buffer | string, ): Buffer => { const deviceIdBuffer = - typeof deviceId === 'string' ? Buffer.from(deviceId) : deviceId + typeof deviceId === 'string' ? Buffer.from(deviceId) : deviceId; const passwordBuffer = - typeof password === 'string' ? Buffer.from(password) : password + typeof password === 'string' ? Buffer.from(password) : password; const appNameBuffer = - typeof appName === 'string' ? Buffer.from(appName) : appName + typeof appName === 'string' ? Buffer.from(appName) : appName; const preImage = Buffer.concat([ deviceIdBuffer, passwordBuffer, appNameBuffer, - ]) + ]); - return Buffer.from(Hash.sha256(preImage)) -} + return Buffer.from(Hash.sha256(preImage)); +}; /** * Get the `v` component of signature using viem parsing. @@ -724,121 +725,121 @@ export const generateAppSecret = ( * @returns BN object containing the `v` param */ export const getV = (tx: any, resp: any) => { - let chainId: string | undefined - let hash: Uint8Array - let type: string | number | undefined - let useEIP155 = false + let chainId: string | undefined; + let hash: Uint8Array; + let type: string | number | undefined; + let useEIP155 = false; if (Buffer.isBuffer(tx) || typeof tx === 'string') { const txHex = Buffer.isBuffer(tx) ? (`0x${tx.toString('hex')}` as Hex) - : (tx as Hex) - const txBuf = Buffer.isBuffer(tx) ? tx : Buffer.from(tx.slice(2), 'hex') + : (tx as Hex); + const txBuf = Buffer.isBuffer(tx) ? tx : Buffer.from(tx.slice(2), 'hex'); - hash = Buffer.from(Hash.keccak256(txBuf)) + hash = Buffer.from(Hash.keccak256(txBuf)); try { - const parsedTx = parseTransaction(txHex) - type = parsedTx.type + const parsedTx = parseTransaction(txHex); + type = parsedTx.type; if (parsedTx.chainId !== undefined && parsedTx.chainId !== null) { - chainId = parsedTx.chainId.toString() + chainId = parsedTx.chainId.toString(); if (type === 'legacy') { - useEIP155 = true + useEIP155 = true; } } if (type === 'legacy' && !useEIP155) { - const legacyTxArray = RLP.decode(txBuf) + const legacyTxArray = RLP.decode(txBuf); if (legacyTxArray.length >= 9) { - const vBuf = legacyTxArray[6] as Uint8Array + const vBuf = legacyTxArray[6] as Uint8Array; if (vBuf && vBuf.length > 0) { - chainId = new BN(vBuf).toString() - useEIP155 = true + chainId = new BN(vBuf).toString(); + useEIP155 = true; } } } } catch (err) { - console.error('Failed to parse transaction, trying legacy format:', err) + console.error('Failed to parse transaction, trying legacy format:', err); try { const txBufRaw = Buffer.isBuffer(tx) ? tx - : Buffer.from(tx.slice(2), 'hex') - const legacyTxArray = RLP.decode(txBufRaw) + : Buffer.from(tx.slice(2), 'hex'); + const legacyTxArray = RLP.decode(txBufRaw); - type = 'legacy' + type = 'legacy'; if (legacyTxArray.length >= 9) { - const vBuf = legacyTxArray[6] as Uint8Array + const vBuf = legacyTxArray[6] as Uint8Array; if (vBuf && vBuf.length > 0) { - chainId = new BN(vBuf).toString() - useEIP155 = true + chainId = new BN(vBuf).toString(); + useEIP155 = true; } } } catch { - throw new Error('Could not recover V. Bad transaction data.') + throw new Error('Could not recover V. Bad transaction data.'); } } } else { throw new Error( 'Unsupported transaction format. Expected Buffer or hex string.', - ) + ); } const rBuf = Buffer.isBuffer(resp.sig.r) ? resp.sig.r - : Buffer.from(resp.sig.r.slice(2), 'hex') + : Buffer.from(resp.sig.r.slice(2), 'hex'); const sBuf = Buffer.isBuffer(resp.sig.s) ? resp.sig.s - : Buffer.from(resp.sig.s.slice(2), 'hex') - const rs = new Uint8Array(Buffer.concat([rBuf, sBuf])) - const pubkeyInput = resp.pubkey + : Buffer.from(resp.sig.s.slice(2), 'hex'); + const rs = new Uint8Array(Buffer.concat([rBuf, sBuf])); + const pubkeyInput = resp.pubkey; if (!pubkeyInput) { - throw new Error('Response did not include a public key.') + throw new Error('Response did not include a public key.'); } - let pubkeyBuf: Buffer + let pubkeyBuf: Buffer; if (Buffer.isBuffer(pubkeyInput)) { - pubkeyBuf = Buffer.from(pubkeyInput) + pubkeyBuf = Buffer.from(pubkeyInput); } else if (pubkeyInput instanceof Uint8Array) { - pubkeyBuf = Buffer.from(pubkeyInput) + pubkeyBuf = Buffer.from(pubkeyInput); } else if (typeof pubkeyInput === 'string') { const hex = pubkeyInput.startsWith('0x') ? pubkeyInput.slice(2) - : pubkeyInput - pubkeyBuf = Buffer.from(hex, 'hex') + : pubkeyInput; + pubkeyBuf = Buffer.from(hex, 'hex'); } else { - pubkeyBuf = Buffer.from(pubkeyInput) + pubkeyBuf = Buffer.from(pubkeyInput); } if (pubkeyBuf.length === 64) { - pubkeyBuf = Buffer.concat([Buffer.from([0x04]), pubkeyBuf]) + pubkeyBuf = Buffer.concat([Buffer.from([0x04]), pubkeyBuf]); } const isCompressedPubkey = - pubkeyBuf.length === 33 && (pubkeyBuf[0] === 0x02 || pubkeyBuf[0] === 0x03) - const isUncompressedPubkey = pubkeyBuf.length === 65 && pubkeyBuf[0] === 0x04 + pubkeyBuf.length === 33 && (pubkeyBuf[0] === 0x02 || pubkeyBuf[0] === 0x03); + const isUncompressedPubkey = pubkeyBuf.length === 65 && pubkeyBuf[0] === 0x04; if (!isCompressedPubkey && !isUncompressedPubkey) { - throw new Error('Unsupported public key format returned by device.') + throw new Error('Unsupported public key format returned by device.'); } - const recovery0 = Buffer.from(ecdsaRecover(rs, 0, hash, isCompressedPubkey)) - const recovery1 = Buffer.from(ecdsaRecover(rs, 1, hash, isCompressedPubkey)) + const recovery0 = Buffer.from(ecdsaRecover(rs, 0, hash, isCompressedPubkey)); + const recovery1 = Buffer.from(ecdsaRecover(rs, 1, hash, isCompressedPubkey)); - const pubkeyStr = pubkeyBuf.toString('hex') - const recovery0Str = recovery0.toString('hex') - const recovery1Str = recovery1.toString('hex') + const pubkeyStr = pubkeyBuf.toString('hex'); + const recovery0Str = recovery0.toString('hex'); + const recovery1Str = recovery1.toString('hex'); - let recovery: number + let recovery: number; if (pubkeyStr === recovery0Str) { - recovery = 0 + recovery = 0; } else if (pubkeyStr === recovery1Str) { - recovery = 1 + recovery = 1; } else { throw new Error( 'Failed to recover V parameter. Bad signature or transaction data.', - ) + ); } // Use the consolidated v parameter conversion logic @@ -846,20 +847,20 @@ export const getV = (tx: any, resp: any) => { chainId, useEIP155, type, - }) + }); // Always return BN for consistent interface - convertRecoveryToV returns Buffer for typed txs if (Buffer.isBuffer(result)) { // For typed transactions that return recovery value (0 or 1) as buffer if (result.length === 0) { - return new BN(0) // Empty buffer means 0 + return new BN(0); // Empty buffer means 0 } else { - return new BN(result.toString('hex'), 16) + return new BN(result.toString('hex'), 16); } } else { - return result // Already a BN + return result; // Already a BN } -} +}; /** * Convert a recovery parameter (0/1) to the proper v value format based on transaction type. @@ -873,7 +874,7 @@ export const convertRecoveryToV = ( recovery: number, txData: any = {}, ): Buffer | InstanceType => { - const { chainId, useEIP155, type } = txData + const { chainId, useEIP155, type } = txData; // For typed transactions (EIP-2930, EIP-1559, EIP-7702), we want the recoveryParam (0 or 1) // rather than the `v` value because the `chainId` is already included in the @@ -886,18 +887,18 @@ export const convertRecoveryToV = ( type === 'eip1559' || type === 'eip7702' ) { - return ensureHexBuffer(recovery, true) // 0 or 1, with 0 expected as an empty buffer + return ensureHexBuffer(recovery, true); // 0 or 1, with 0 expected as an empty buffer } else if (!useEIP155 || !chainId) { // For ETH messages and non-EIP155 chains the set should be [27, 28] for `v` - return new BN(recovery).addn(27) + return new BN(recovery).addn(27); } // We will use EIP155 in most cases. Convert recovery to a bignum and operate on it. // Note that the protocol calls for v = (CHAIN_ID*2) + 35/36, where 35 or 36 // is decided on based on the ecrecover result. `recovery` is passed in as either 0 or 1 // so we add 35 to that. - return new BN(chainId).muln(2).addn(35).addn(recovery) -} + return new BN(chainId).muln(2).addn(35).addn(recovery); +}; /** * Get the y-parity value for a signature by recovering the public key. @@ -932,83 +933,83 @@ export const getYParity = ( messageHash.messageHash, messageHash.signature, messageHash.publicKey, - ) + ); } // Handle legacy transaction format for backward compatibility if (signature?.sig && signature.pubkey && !publicKey) { - return getYParity(messageHash, signature.sig, signature.pubkey) + return getYParity(messageHash, signature.sig, signature.pubkey); } // Validate required parameters if (!signature || !publicKey) { - throw new Error('Response with sig and pubkey required for legacy format') + throw new Error('Response with sig and pubkey required for legacy format'); } if (!signature.r || !signature.s) { - throw new Error('Response with sig and pubkey required for legacy format') + throw new Error('Response with sig and pubkey required for legacy format'); } // Handle transaction objects with getMessageToSign - let hash = messageHash + let hash = messageHash; if ( typeof messageHash === 'object' && messageHash && typeof messageHash.getMessageToSign === 'function' ) { - const type = messageHash._type + const type = messageHash._type; if (type !== undefined && type !== null) { // EIP-1559 / EIP-2930 / future typed transactions - hash = messageHash.getMessageToSign(true) + hash = messageHash.getMessageToSign(true); } else { // Legacy transaction objects - const preimage = RLP.encode(messageHash.getMessageToSign(false)) - hash = Buffer.from(Hash.keccak256(preimage)) + const preimage = RLP.encode(messageHash.getMessageToSign(false)); + hash = Buffer.from(Hash.keccak256(preimage)); } } else if (Buffer.isBuffer(messageHash) && messageHash.length !== 32) { // If it's a buffer but not 32 bytes, hash it - hash = Buffer.from(Hash.keccak256(messageHash)) + hash = Buffer.from(Hash.keccak256(messageHash)); } // Normalize inputs to Buffers const toBuffer = (data: any): Buffer => { - if (!data) throw new Error('Invalid data') - if (Buffer.isBuffer(data)) return data - if (data instanceof Uint8Array) return Buffer.from(data) + if (!data) throw new Error('Invalid data'); + if (Buffer.isBuffer(data)) return data; + if (data instanceof Uint8Array) return Buffer.from(data); if (typeof data === 'string') { - return Buffer.from(data.replace(/^0x/i, ''), 'hex') + return Buffer.from(data.replace(/^0x/i, ''), 'hex'); } - throw new Error('Invalid data type') - } + throw new Error('Invalid data type'); + }; - const hashBuf = toBuffer(hash) - const rBuf = toBuffer(signature.r) - const sBuf = toBuffer(signature.s) - const pubkeyBuf = toBuffer(publicKey) + const hashBuf = toBuffer(hash); + const rBuf = toBuffer(signature.r); + const sBuf = toBuffer(signature.s); + const pubkeyBuf = toBuffer(publicKey); // For non-32 byte hashes, hash them (legacy support) const finalHash = - hashBuf.length === 32 ? hashBuf : Buffer.from(Hash.keccak256(hashBuf)) + hashBuf.length === 32 ? hashBuf : Buffer.from(Hash.keccak256(hashBuf)); // Combine r and s - const rs = new Uint8Array(Buffer.concat([rBuf, sBuf])) - const hashBytes = new Uint8Array(finalHash) - const isCompressed = pubkeyBuf.length === 33 + const rs = new Uint8Array(Buffer.concat([rBuf, sBuf])); + const hashBytes = new Uint8Array(finalHash); + const isCompressed = pubkeyBuf.length === 33; // Try both recovery values for (let recovery = 0; recovery <= 1; recovery++) { try { - const recovered = ecdsaRecover(rs, recovery, hashBytes, isCompressed) + const recovered = ecdsaRecover(rs, recovery, hashBytes, isCompressed); if (Buffer.from(recovered).equals(pubkeyBuf)) { - return recovery + return recovery; } } catch {} } throw new Error( 'Failed to recover Y parity. Bad signature or transaction data.', - ) -} + ); +}; /** @internal */ export const EXTERNAL = { @@ -1017,4 +1018,4 @@ export const EXTERNAL = { getV, getYParity, convertRecoveryToV, -} +}; From bb45145a9f579d8253803a72e2c37675c822bbce Mon Sep 17 00:00:00 2001 From: netbonus <151201453+netbonus@users.noreply.github.com> Date: Wed, 21 Jan 2026 15:24:06 -0500 Subject: [PATCH 09/14] style: use 240-char line width --- biome.json | 1 + .../docs/src/components/HomepageFeatures.tsx | 17 +- packages/docs/src/pages/_index.tsx | 10 +- packages/example/src/App.tsx | 21 +- packages/example/src/Lattice.tsx | 11 +- packages/sdk/src/__test__/e2e/api.test.ts | 130 ++--- packages/sdk/src/__test__/e2e/btc.test.ts | 21 +- packages/sdk/src/__test__/e2e/eth.msg.test.ts | 114 ++--- packages/sdk/src/__test__/e2e/general.test.ts | 97 +--- packages/sdk/src/__test__/e2e/kv.test.ts | 32 +- .../src/__test__/e2e/non-exportable.test.ts | 28 +- .../sdk/src/__test__/e2e/signing/bls.test.ts | 73 +-- .../__test__/e2e/signing/determinism.test.ts | 144 +----- .../__test__/e2e/signing/eip712-vectors.ts | 8 +- .../src/__test__/e2e/signing/evm-tx.test.ts | 8 +- .../e2e/signing/solana/__mocks__/programs.ts | 110 ++--- .../e2e/signing/solana/solana.test.ts | 42 +- .../signing/solana/solana.versioned.test.ts | 37 +- .../__test__/e2e/signing/unformatted.test.ts | 20 +- .../sdk/src/__test__/e2e/signing/vectors.ts | 95 +--- .../__test__/integration/__mocks__/4byte.ts | 9 +- .../integration/__mocks__/etherscan.ts | 12 +- .../integration/__mocks__/handlers.ts | 16 +- .../integration/fetchCalldataDecoder.test.ts | 6 +- .../__test__/unit/__mocks__/decoderData.ts | 27 +- packages/sdk/src/__test__/unit/api.test.ts | 4 +- .../unit/compareEIP7702Serialization.test.ts | 5 +- .../sdk/src/__test__/unit/decoders.test.ts | 23 +- .../sdk/src/__test__/unit/eip7702.test.ts | 5 +- .../sdk/src/__test__/unit/encoders.test.ts | 46 +- .../__test__/unit/ethereum.validate.test.ts | 17 +- .../src/__test__/unit/module.interop.test.ts | 12 +- .../unit/parseGenericSigningResponse.test.ts | 35 +- .../unit/personalSignValidation.test.ts | 40 +- .../unit/selectDefFrom4byteABI.test.ts | 6 +- .../src/__test__/unit/signatureUtils.test.ts | 91 +--- .../sdk/src/__test__/unit/validators.test.ts | 35 +- .../__test__/utils/__test__/builders.test.ts | 6 +- .../utils/__test__/serializers.test.ts | 5 +- packages/sdk/src/__test__/utils/builders.ts | 70 +-- .../sdk/src/__test__/utils/determinism.ts | 6 +- packages/sdk/src/__test__/utils/ethers.ts | 29 +- packages/sdk/src/__test__/utils/helpers.ts | 246 ++-------- packages/sdk/src/__test__/utils/runners.ts | 17 +- packages/sdk/src/__test__/utils/setup.ts | 4 +- .../sdk/src/__test__/utils/testConstants.ts | 3 +- .../sdk/src/__test__/utils/testEnvironment.ts | 3 +- .../sdk/src/__test__/utils/testRequest.ts | 15 +- .../sdk/src/__test__/utils/viemComparison.ts | 27 +- packages/sdk/src/api/addressTags.ts | 13 +- packages/sdk/src/api/addresses.ts | 88 +--- packages/sdk/src/api/setup.ts | 4 +- packages/sdk/src/api/signing.ts | 126 +---- packages/sdk/src/api/state.ts | 4 +- packages/sdk/src/api/utilities.ts | 21 +- packages/sdk/src/bitcoin.ts | 45 +- packages/sdk/src/calldata/evm.ts | 59 +-- packages/sdk/src/calldata/index.ts | 7 +- packages/sdk/src/client.ts | 93 +--- packages/sdk/src/constants.ts | 112 +---- packages/sdk/src/ethereum.ts | 456 ++++-------------- packages/sdk/src/functions/addKvRecords.ts | 32 +- packages/sdk/src/functions/connect.ts | 28 +- .../sdk/src/functions/fetchActiveWallet.ts | 19 +- packages/sdk/src/functions/fetchDecoder.ts | 16 +- packages/sdk/src/functions/fetchEncData.ts | 44 +- packages/sdk/src/functions/getAddresses.ts | 60 +-- packages/sdk/src/functions/getKvRecords.ts | 41 +- packages/sdk/src/functions/pair.ts | 19 +- packages/sdk/src/functions/removeKvRecords.ts | 23 +- packages/sdk/src/functions/sign.ts | 72 +-- packages/sdk/src/genericSigning.ts | 108 +---- packages/sdk/src/protocol/latticeConstants.ts | 8 +- packages/sdk/src/protocol/secureMessages.ts | 78 +-- packages/sdk/src/schemas/transaction.ts | 130 ++--- packages/sdk/src/shared/functions.ts | 32 +- packages/sdk/src/shared/predicates.ts | 16 +- packages/sdk/src/shared/utilities.ts | 11 +- packages/sdk/src/shared/validators.ts | 77 +-- packages/sdk/src/types/addKvRecords.ts | 3 +- packages/sdk/src/types/firmware.ts | 5 +- packages/sdk/src/types/getAddresses.ts | 3 +- packages/sdk/src/types/getKvRecords.ts | 3 +- packages/sdk/src/types/removeKvRecords.ts | 3 +- packages/sdk/src/types/sign.ts | 52 +- packages/sdk/src/util.ts | 266 +++------- 86 files changed, 842 insertions(+), 3174 deletions(-) diff --git a/biome.json b/biome.json index aed23215..097adda7 100644 --- a/biome.json +++ b/biome.json @@ -11,6 +11,7 @@ "indentStyle": "space", "indentWidth": 2, "lineEnding": "lf", + "lineWidth": 240, "attributePosition": "auto" }, "linter": { diff --git a/packages/docs/src/components/HomepageFeatures.tsx b/packages/docs/src/components/HomepageFeatures.tsx index 72309dd8..8aea02f5 100644 --- a/packages/docs/src/components/HomepageFeatures.tsx +++ b/packages/docs/src/components/HomepageFeatures.tsx @@ -11,32 +11,21 @@ const FeatureList: FeatureItem[] = [ { title: 'Easy to Use', image: '/img/undraw_docusaurus_mountain.svg', - description: ( - <> - Docusaurus was designed from the ground up to be easily installed and - used to get your website up and running quickly. - - ), + description: <>Docusaurus was designed from the ground up to be easily installed and used to get your website up and running quickly., }, { title: 'Focus on What Matters', image: '/img/undraw_docusaurus_tree.svg', description: ( <> - Docusaurus lets you focus on your docs, and we'll do the chores. Go - ahead and move your docs into the docs directory. + Docusaurus lets you focus on your docs, and we'll do the chores. Go ahead and move your docs into the docs directory. ), }, { title: 'Powered by React', image: '/img/undraw_docusaurus_react.svg', - description: ( - <> - Extend or customize your website layout by reusing React. Docusaurus can - be extended while reusing the same header and footer. - - ), + description: <>Extend or customize your website layout by reusing React. Docusaurus can be extended while reusing the same header and footer., }, ]; diff --git a/packages/docs/src/pages/_index.tsx b/packages/docs/src/pages/_index.tsx index a52585e6..18527f39 100644 --- a/packages/docs/src/pages/_index.tsx +++ b/packages/docs/src/pages/_index.tsx @@ -18,10 +18,7 @@ function HomepageHeader() {

{siteConfig.title}

{siteConfig.tagline}

- + Getting Started
@@ -34,10 +31,7 @@ export default function Home(): JSX.Element { const { siteConfig } = useDocusaurusContext(); const ExtendedLayout = Layout as React.ComponentType; return ( - +
{/* */}
diff --git a/packages/example/src/App.tsx b/packages/example/src/App.tsx index 07d3d3e8..92f9c96a 100644 --- a/packages/example/src/App.tsx +++ b/packages/example/src/App.tsx @@ -6,10 +6,7 @@ import { Lattice } from './Lattice'; function App() { const [label, setLabel] = useState('No Device'); - const getStoredClient = useCallback( - async () => window.localStorage.getItem('storedClient') || '', - [], - ); + const getStoredClient = useCallback(async () => window.localStorage.getItem('storedClient') || '', []); const setStoredClient = useCallback(async (storedClient: string | null) => { if (!storedClient) return; @@ -63,17 +60,10 @@ function App() { border: '1px solid black', }} > -
+ - +
@@ -86,10 +76,7 @@ function App() { border: '1px solid black', }} > -
+
diff --git a/packages/example/src/Lattice.tsx b/packages/example/src/Lattice.tsx index d0f3a9dd..5f4200ae 100644 --- a/packages/example/src/Lattice.tsx +++ b/packages/example/src/Lattice.tsx @@ -1,14 +1,5 @@ import { useState } from 'react'; -import { - addAddressTags, - fetchAddressTags, - fetchAddresses, - fetchLedgerLiveAddresses, - removeAddressTags, - sign, - signMessage, - type AddressTag, -} from 'gridplus-sdk'; +import { addAddressTags, fetchAddressTags, fetchAddresses, fetchLedgerLiveAddresses, removeAddressTags, sign, signMessage, type AddressTag } from 'gridplus-sdk'; import { Button } from './Button'; interface LatticeProps { diff --git a/packages/sdk/src/__test__/e2e/api.test.ts b/packages/sdk/src/__test__/e2e/api.test.ts index a7cc353b..dfa025d4 100644 --- a/packages/sdk/src/__test__/e2e/api.test.ts +++ b/packages/sdk/src/__test__/e2e/api.test.ts @@ -3,8 +3,7 @@ vi.mock('../../functions/fetchDecoder.ts', () => ({ })); vi.mock('../../util', async () => { - const actual = - await vi.importActual('../../util'); + const actual = await vi.importActual('../../util'); return { ...actual, fetchCalldataDecoder: vi.fn().mockResolvedValue({ @@ -35,14 +34,7 @@ import { signBtcWrappedSegwitTx, signMessage, } from '../../api'; -import { - addAddressTags, - fetchAddressTags, - fetchLedgerLiveAddresses, - removeAddressTags, - sign, - signSolanaTx, -} from '../../api/index'; +import { addAddressTags, fetchAddressTags, fetchLedgerLiveAddresses, removeAddressTags, sign, signSolanaTx } from '../../api/index'; import { HARDENED_OFFSET } from '../../constants'; import { buildRandomMsg } from '../utils/builders'; import { BTC_PURPOSE_P2SH_P2WPKH, BTC_TESTNET_COIN } from '../utils/helpers'; @@ -60,29 +52,16 @@ describe('API', () => { const btcTxData = { prevOuts: [ { - txHash: - '6e78493091f80d89a92ae3152df7fbfbdc44df09cf01a9b76c5113c02eaf2e0f', + txHash: '6e78493091f80d89a92ae3152df7fbfbdc44df09cf01a9b76c5113c02eaf2e0f', value: 10000, index: 1, - signerPath: [ - BTC_PURPOSE_P2SH_P2WPKH, - BTC_TESTNET_COIN, - HARDENED_OFFSET, - 0, - 0, - ], + signerPath: [BTC_PURPOSE_P2SH_P2WPKH, BTC_TESTNET_COIN, HARDENED_OFFSET, 0, 0], }, ], recipient: 'mhifA1DwiMPHTjSJM8FFSL8ibrzWaBCkVT', value: 1000, fee: 1000, - changePath: [ - BTC_PURPOSE_P2SH_P2WPKH, - BTC_TESTNET_COIN, - HARDENED_OFFSET, - 1, - 0, - ], + changePath: [BTC_PURPOSE_P2SH_P2WPKH, BTC_TESTNET_COIN, HARDENED_OFFSET, 1, 0], }; test('legacy', async () => { await signBtcLegacyTx(btcTxData); @@ -126,16 +105,8 @@ describe('API', () => { }); test('legacy', async () => { - const toHex = (v: bigint | number) => - typeof v === 'bigint' ? `0x${v.toString(16)}` : v; - const rawTx = RLP.encode([ - txData.nonce, - toHex(txData.gasPrice), - toHex(txData.gas), - txData.to, - toHex(txData.value), - txData.data, - ]); + const toHex = (v: bigint | number) => (typeof v === 'bigint' ? `0x${v.toString(16)}` : v); + const rawTx = RLP.encode([txData.nonce, toHex(txData.gasPrice), toHex(txData.gas), txData.to, toHex(txData.value), txData.data]); await sign(rawTx); }); }); @@ -151,20 +122,9 @@ describe('API', () => { describe('address tags', () => { beforeAll(async () => { try { - await Promise.race([ - fetchAddressTags({ n: 1 }), - new Promise((_, reject) => - setTimeout( - () => reject(new Error('Address tag RPC timed out')), - 5000, - ), - ), - ]); + await Promise.race([fetchAddressTags({ n: 1 }), new Promise((_, reject) => setTimeout(() => reject(new Error('Address tag RPC timed out')), 5000))]); } catch (err) { - console.warn( - 'Skipping address tag tests due to connectivity issue:', - (err as Error).message, - ); + console.warn('Skipping address tag tests due to connectivity issue:', (err as Error).message); } }); @@ -247,19 +207,15 @@ describe('API', () => { describe('fetchAddressesByDerivationPath', () => { test('fetch single specific address', async () => { - const addresses = - await fetchAddressesByDerivationPath("44'/60'/0'/0/0"); + const addresses = await fetchAddressesByDerivationPath("44'/60'/0'/0/0"); expect(addresses).toHaveLength(1); expect(addresses[0]).toBeTruthy(); }); test('fetch multiple addresses with wildcard', async () => { - const addresses = await fetchAddressesByDerivationPath( - "44'/60'/0'/0/X", - { - n: 5, - }, - ); + const addresses = await fetchAddressesByDerivationPath("44'/60'/0'/0/X", { + n: 5, + }); expect(addresses).toHaveLength(5); addresses.forEach((address) => { expect(address).toBeTruthy(); @@ -267,13 +223,10 @@ describe('API', () => { }); test('fetch addresses with offset', async () => { - const addresses = await fetchAddressesByDerivationPath( - "44'/60'/0'/0/X", - { - n: 3, - startPathIndex: 10, - }, - ); + const addresses = await fetchAddressesByDerivationPath("44'/60'/0'/0/X", { + n: 3, + startPathIndex: 10, + }); expect(addresses).toHaveLength(3); addresses.forEach((address) => { expect(address).toBeTruthy(); @@ -281,12 +234,9 @@ describe('API', () => { }); test('fetch addresses with lowercase x wildcard', async () => { - const addresses = await fetchAddressesByDerivationPath( - "44'/60'/0'/0/x", - { - n: 2, - }, - ); + const addresses = await fetchAddressesByDerivationPath("44'/60'/0'/0/x", { + n: 2, + }); expect(addresses).toHaveLength(2); addresses.forEach((address) => { expect(address).toBeTruthy(); @@ -294,12 +244,9 @@ describe('API', () => { }); test('fetch addresses with wildcard in middle of path', async () => { - const addresses = await fetchAddressesByDerivationPath( - "44'/60'/X'/0/0", - { - n: 3, - }, - ); + const addresses = await fetchAddressesByDerivationPath("44'/60'/X'/0/0", { + n: 3, + }); expect(addresses).toHaveLength(3); addresses.forEach((address) => { expect(address).toBeTruthy(); @@ -307,12 +254,9 @@ describe('API', () => { }); test('fetch solana addresses with wildcard in middle of path', async () => { - const addresses = await fetchAddressesByDerivationPath( - "44'/501'/X'/0'", - { - n: 1, - }, - ); + const addresses = await fetchAddressesByDerivationPath("44'/501'/X'/0'", { + n: 1, + }); expect(addresses).toHaveLength(1); addresses.forEach((address) => { expect(address).toBeTruthy(); @@ -320,29 +264,21 @@ describe('API', () => { }); test('error on invalid derivation path', async () => { - await expect( - fetchAddressesByDerivationPath('invalid/path'), - ).rejects.toThrow(); + await expect(fetchAddressesByDerivationPath('invalid/path')).rejects.toThrow(); }); test('fetch single address when n=1 with wildcard', async () => { - const addresses = await fetchAddressesByDerivationPath( - "44'/60'/0'/0/X", - { - n: 1, - }, - ); + const addresses = await fetchAddressesByDerivationPath("44'/60'/0'/0/X", { + n: 1, + }); expect(addresses).toHaveLength(1); expect(addresses[0]).toBeTruthy(); }); test('fetch no addresses when n=0', async () => { - const addresses = await fetchAddressesByDerivationPath( - "44'/60'/0'/0/X", - { - n: 0, - }, - ); + const addresses = await fetchAddressesByDerivationPath("44'/60'/0'/0/X", { + n: 0, + }); expect(addresses).toHaveLength(0); }); }); diff --git a/packages/sdk/src/__test__/e2e/btc.test.ts b/packages/sdk/src/__test__/e2e/btc.test.ts index a345f787..1cbaede9 100644 --- a/packages/sdk/src/__test__/e2e/btc.test.ts +++ b/packages/sdk/src/__test__/e2e/btc.test.ts @@ -16,13 +16,7 @@ import BIP32Factory, { type BIP32Interface } from 'bip32'; import * as ecc from 'tiny-secp256k1'; import type { Client } from '../../client'; import { getPrng, getTestnet } from '../utils/getters'; -import { - BTC_PURPOSE_P2PKH, - BTC_PURPOSE_P2SH_P2WPKH, - BTC_PURPOSE_P2WPKH, - setup_btc_sig_test, - stripDER, -} from '../utils/helpers'; +import { BTC_PURPOSE_P2PKH, BTC_PURPOSE_P2SH_P2WPKH, BTC_PURPOSE_P2WPKH, setup_btc_sig_test, stripDER } from '../utils/helpers'; import { setupClient } from '../utils/setup'; import { TEST_SEED } from '../utils/testConstants'; @@ -59,20 +53,11 @@ async function testSign({ txReq, signingKeys, sigHashes, client }: any) { for (let i = 0; i < len; i++) { const sig = stripDER(tx.sigs?.[i]); const verification = signingKeys[i].verify(sigHashes[i], sig); - expect(verification).toEqualElseLog( - true, - `Signature validation failed for priv=${signingKeys[i].privateKey.toString('hex')}, ` + - `hash=${sigHashes[i].toString('hex')}, sig=${sig.toString('hex')}`, - ); + expect(verification).toEqualElseLog(true, `Signature validation failed for priv=${signingKeys[i].privateKey.toString('hex')}, ` + `hash=${sigHashes[i].toString('hex')}, sig=${sig.toString('hex')}`); } } -async function runTestSet( - opts: any, - wallet: BIP32Interface | null, - inputsSlice: InputObj[], - client, -) { +async function runTestSet(opts: any, wallet: BIP32Interface | null, inputsSlice: InputObj[], client) { expect(wallet).not.toEqualElseLog(null, 'Wallet not available'); if (TEST_TESTNET) { // Testnet + change diff --git a/packages/sdk/src/__test__/e2e/eth.msg.test.ts b/packages/sdk/src/__test__/e2e/eth.msg.test.ts index a827211e..c87bc286 100644 --- a/packages/sdk/src/__test__/e2e/eth.msg.test.ts +++ b/packages/sdk/src/__test__/e2e/eth.msg.test.ts @@ -36,37 +36,21 @@ describe('ETH Messages', () => { const protocol = 'signPersonal'; const msg = '⚠️'; const msg2 = 'ASCII plus ⚠️'; - await expect(client.sign(buildEthMsgReq(msg, protocol))).rejects.toThrow( - /Lattice can only display ASCII/, - ); - await expect(client.sign(buildEthMsgReq(msg2, protocol))).rejects.toThrow( - /Lattice can only display ASCII/, - ); + await expect(client.sign(buildEthMsgReq(msg, protocol))).rejects.toThrow(/Lattice can only display ASCII/); + await expect(client.sign(buildEthMsgReq(msg2, protocol))).rejects.toThrow(/Lattice can only display ASCII/); }); it('Should test ASCII buffers', async () => { - await runEthMsg( - buildEthMsgReq(Buffer.from('i am an ascii buffer'), 'signPersonal'), - client, - ); - await runEthMsg( - buildEthMsgReq(Buffer.from('{\n\ttest: foo\n}'), 'signPersonal'), - client, - ); + await runEthMsg(buildEthMsgReq(Buffer.from('i am an ascii buffer'), 'signPersonal'), client); + await runEthMsg(buildEthMsgReq(Buffer.from('{\n\ttest: foo\n}'), 'signPersonal'), client); }); it('Should test hex buffers', async () => { - await runEthMsg( - buildEthMsgReq(Buffer.from('abcdef', 'hex'), 'signPersonal'), - client, - ); + await runEthMsg(buildEthMsgReq(Buffer.from('abcdef', 'hex'), 'signPersonal'), client); }); it('Should test a message that needs to be prehashed', async () => { - await runEthMsg( - buildEthMsgReq(randomBytes(4000), 'signPersonal'), - client, - ); + await runEthMsg(buildEthMsgReq(randomBytes(4000), 'signPersonal'), client); }); it('Msg: sign_personal boundary conditions and auto-rejected requests', async () => { @@ -75,10 +59,7 @@ describe('ETH Messages', () => { // `personal_sign` requests have a max size smaller than other requests because a header // is displayed in the text region of the screen. The size of this is captured // by `fwConstants.personalSignHeaderSz`. - const maxMsgSz = - fwConstants.ethMaxMsgSz + - fwConstants.personalSignHeaderSz + - fwConstants.extraDataMaxFrames * fwConstants.extraDataFrameSz; + const maxMsgSz = fwConstants.ethMaxMsgSz + fwConstants.personalSignHeaderSz + fwConstants.extraDataMaxFrames * fwConstants.extraDataFrameSz; const maxValid = `0x${randomBytes(maxMsgSz).toString('hex')}`; const minInvalid = `0x${randomBytes(maxMsgSz + 1).toString('hex')}`; const zeroInvalid = '0x'; @@ -90,30 +71,16 @@ describe('ETH Messages', () => { // I guess all this tests is that the first one is shown in plaintext while the second // one (which is too large) gets prehashed. const largeSignPath = [x, HARDENED_OFFSET + 60, x, x, x] as SigningPath; - await runEthMsg( - buildEthMsgReq(maxValid, protocol, largeSignPath), - client, - ); - await runEthMsg( - buildEthMsgReq(minInvalid, protocol, largeSignPath), - client, - ); + await runEthMsg(buildEthMsgReq(maxValid, protocol, largeSignPath), client); + await runEthMsg(buildEthMsgReq(minInvalid, protocol, largeSignPath), client); // Using a zero length payload should auto-reject - await expect( - client.sign(buildEthMsgReq(zeroInvalid, protocol)), - ).rejects.toThrow(/Invalid Request/); + await expect(client.sign(buildEthMsgReq(zeroInvalid, protocol))).rejects.toThrow(/Invalid Request/); }); describe(`Test ${5} random payloads`, () => { for (let i = 0; i < 5; i++) { it(`Payload: ${i}`, async () => { - await runEthMsg( - buildEthMsgReq( - buildRandomMsg('signPersonal', client), - 'signPersonal', - ), - client, - ); + await runEthMsg(buildEthMsgReq(buildRandomMsg('signPersonal', client), 'signPersonal'), client); }); } }); @@ -191,8 +158,7 @@ describe('ETH Messages', () => { side: '1', matchingPolicy: '0x00000000006411739da1c40b106f8511de5d1fac', collection: '0x7a15b36cb834aea88553de69077d3777460d73ac', - tokenId: - '5280336779268220421569573059971679349075200194886069432279714075018412552192', + tokenId: '5280336779268220421569573059971679349075200194886069432279714075018412552192', amount: '1', paymentToken: '0x0000000000000000000000000000000000000000', price: '990000000000000000', @@ -267,8 +233,7 @@ describe('ETH Messages', () => { accountID: 32494, feeTokenID: 0, maxFee: 100, - publicKey: - '11413934541425201845815969801249874136651857829494005371571206042985258823663', + publicKey: '11413934541425201845815969801249874136651857829494005371571206042985258823663', validUntil: 1631655383, nonce: 0, }, @@ -301,8 +266,7 @@ describe('ETH Messages', () => { verifyingContract: '0xf03f457a30e598d5020164a339727ef40f2b8fbc', }, message: { - sender: - '0x841fe4876763357975d60da128d8a54bb045d76a64656661756c740000000000', + sender: '0x841fe4876763357975d60da128d8a54bb045d76a64656661756c740000000000', priceX18: '28898000000000000000000', amount: '-10000000000000000', expiration: '4611687701117784255', @@ -321,21 +285,17 @@ describe('ETH Messages', () => { version: '1', }, message: { - getMakerAmount: - '0xf4a215c30000000000000000000000000000000000000000000000000de0b6b3a764000000000000000000000000000000000000000000000000000018fae27693b40000', - getTakerAmount: - '0x296637bf0000000000000000000000000000000000000000000000000de0b6b3a764000000000000000000000000000000000000000000000000000018fae27693b40000', + getMakerAmount: '0xf4a215c30000000000000000000000000000000000000000000000000de0b6b3a764000000000000000000000000000000000000000000000000000018fae27693b40000', + getTakerAmount: '0x296637bf0000000000000000000000000000000000000000000000000de0b6b3a764000000000000000000000000000000000000000000000000000018fae27693b40000', interaction: '0x', makerAsset: '0x0d500b1d8e8ef31e21c99d1db9a6444d3adf1270', - makerAssetData: - '0x23b872dd0000000000000000000000003e3e2ccdd7bae6bbd4a64e8d16ca8842061335eb00000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000de0b6b3a7640000', + makerAssetData: '0x23b872dd0000000000000000000000003e3e2ccdd7bae6bbd4a64e8d16ca8842061335eb00000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000de0b6b3a7640000', permit: '0x', predicate: '0x961d5b1e000000000000000000000000000000000000000000000000000000000000004000000000000000000000000000000000000000000000000000000000000000a00000000000000000000000000000000000000000000000000000000000000002000000000000000000000000b707d89d29c189421163515c59e42147371d6857000000000000000000000000b707d89d29c189421163515c59e42147371d68570000000000000000000000000000000000000000000000000000000000000002000000000000000000000000000000000000000000000000000000000000004000000000000000000000000000000000000000000000000000000000000000c00000000000000000000000000000000000000000000000000000000000000044cf6fc6e30000000000000000000000003e3e2ccdd7bae6bbd4a64e8d16ca8842061335eb000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000002463592c2b00000000000000000000000000000000000000000000000000000000613e28e500000000000000000000000000000000000000000000000000000000', salt: '885135864076', takerAsset: '0x8f3cf7ad23cd3cadbd9735aff958023239c6a063', - takerAssetData: - '0x23b872dd00000000000000000000000000000000000000000000000000000000000000000000000000000000000000003e3e2ccdd7bae6bbd4a64e8d16ca8842061335eb00000000000000000000000000000000000000000000000018fae27693b40000', + takerAssetData: '0x23b872dd00000000000000000000000000000000000000000000000000000000000000000000000000000000000000003e3e2ccdd7bae6bbd4a64e8d16ca8842061335eb00000000000000000000000000000000000000000000000018fae27693b40000', }, primaryType: 'Order', types: { @@ -680,13 +640,11 @@ describe('ETH Messages', () => { message: { allocations: [ { - reactorKey: - '0x6f686d2d64656661756c74000000000000000000000000000000000000000000', + reactorKey: '0x6f686d2d64656661756c74000000000000000000000000000000000000000000', amount: '1', }, { - reactorKey: - '0x6f686d2d64656661756c74000000000000000000000000000000000000000000', + reactorKey: '0x6f686d2d64656661756c74000000000000000000000000000000000000000000', amount: '2', }, ], @@ -736,13 +694,11 @@ describe('ETH Messages', () => { integer: 56, allocations: [ { - reactorKey: - '0x6f686d2d64656661756c74000000000000000000000000000000000000000000', + reactorKey: '0x6f686d2d64656661756c74000000000000000000000000000000000000000000', amount: '1', }, { - reactorKey: - '0x6f686d2d64656661756c74000000000000000000000000000000000000000000', + reactorKey: '0x6f686d2d64656661756c74000000000000000000000000000000000000000000', amount: '2', }, ], @@ -781,13 +737,11 @@ describe('ETH Messages', () => { message: { allocations: [ { - reactorKey: - '0x6f686d2d64656661756c74000000000000000000000000000000000000000000', + reactorKey: '0x6f686d2d64656661756c74000000000000000000000000000000000000000000', amount: ['1', '2'], }, { - reactorKey: - '0x6f686d2d64656661756c74000000000000000000000000000000000000000000', + reactorKey: '0x6f686d2d64656661756c74000000000000000000000000000000000000000000', amount: ['2', '3'], }, ], @@ -840,8 +794,7 @@ describe('ETH Messages', () => { test: 'hello', allocations: [ { - reactorKey: - '0x6f686d2d64656661756c74000000000000000000000000000000000000000000', + reactorKey: '0x6f686d2d64656661756c74000000000000000000000000000000000000000000', dummy: [ { foo: '0xabcd', @@ -852,8 +805,7 @@ describe('ETH Messages', () => { ], }, { - reactorKey: - '0x6f686d2d64656661756c74000000000000000000000000000000000000000000', + reactorKey: '0x6f686d2d64656661756c74000000000000000000000000000000000000000000', dummy: [ { foo: '0xdeadbeef', @@ -931,12 +883,9 @@ describe('ETH Messages', () => { BYTES16: '0x7ace034ab088fdd434f1e817f32171a0', BYTES20: '0x4ab51f2d5bfdc0f1b96f83358d5f356c98583573', BYTES21: '0x6ecdc19b30c7fa712ba334458d77377b6a586bbab5', - BYTES31: - '0x06c21824a98643f96643b3220962f441210b007f4c19dfdf0dea53d097fc28', - BYTES32: - '0x59cfcbf35256451756b02fa644d3d0748bd98f5904febf3433e6df19b4df7452', - BYTES: - '0x0354b2c449772905b2598a93f5da69962f0444e0a6e2429e8f844f1011446f6fe81815846fb6ebe2d213968d1f8532749735f5702f565db0429b2fe596d295d9c06241389fe97fb2f3b91e1e0f2d978fb26e366737451f1193097bd0a2332e0bfc0cdb631005', + BYTES31: '0x06c21824a98643f96643b3220962f441210b007f4c19dfdf0dea53d097fc28', + BYTES32: '0x59cfcbf35256451756b02fa644d3d0748bd98f5904febf3433e6df19b4df7452', + BYTES: '0x0354b2c449772905b2598a93f5da69962f0444e0a6e2429e8f844f1011446f6fe81815846fb6ebe2d213968d1f8532749735f5702f565db0429b2fe596d295d9c06241389fe97fb2f3b91e1e0f2d978fb26e366737451f1193097bd0a2332e0bfc0cdb631005', STRING: 'I am a string hello there human', BOOL: true, ADDRESS: '0x078a8d6eba928e7ea787ed48f71c5936aed4625d', @@ -1361,10 +1310,7 @@ describe('ETH Messages', () => { describe('test 5 random payloads', () => { for (let i = 0; i < 5; i++) { it(`Payload #${i}`, async () => { - await runEthMsg( - buildEthMsgReq(buildRandomMsg('eip712', client), 'eip712'), - client, - ); + await runEthMsg(buildEthMsgReq(buildRandomMsg('eip712', client), 'eip712'), client); }); } }); diff --git a/packages/sdk/src/__test__/e2e/general.test.ts b/packages/sdk/src/__test__/e2e/general.test.ts index 021d6e1f..38f5c0f8 100644 --- a/packages/sdk/src/__test__/e2e/general.test.ts +++ b/packages/sdk/src/__test__/e2e/general.test.ts @@ -23,15 +23,7 @@ import { LatticeResponseCode, ProtocolConstants } from '../../protocol'; import { randomBytes } from '../../util'; import { buildEthSignRequest } from '../utils/builders'; import { getDeviceId } from '../utils/getters'; -import { - BTC_COIN, - BTC_PURPOSE_P2PKH, - BTC_PURPOSE_P2SH_P2WPKH, - BTC_PURPOSE_P2WPKH, - BTC_TESTNET_COIN, - ETH_COIN, - setupTestClient, -} from '../utils/helpers'; +import { BTC_COIN, BTC_PURPOSE_P2PKH, BTC_PURPOSE_P2SH_P2WPKH, BTC_PURPOSE_P2WPKH, BTC_TESTNET_COIN, ETH_COIN, setupTestClient } from '../utils/helpers'; import { setupClient } from '../utils/setup'; import type { Client } from '../../client'; @@ -180,18 +172,13 @@ describe('General', () => { ctx.skip(); return; } - const { txData, req, maxDataSz, common } = - await buildEthSignRequest(client); - await question( - 'Please REJECT the next request if the warning screen displays. Press enter to continue.', - ); + const { txData, req, maxDataSz, common } = await buildEthSignRequest(client); + await question('Please REJECT the next request if the warning screen displays. Press enter to continue.'); txData.data = randomBytes(maxDataSz); req.data.data = randomBytes(maxDataSz + 1); const tx = createTx(txData, { common }); req.data.payload = tx.getMessageToSign(); - await expect(client.sign(req)).rejects.toThrow( - `${ProtocolConstants.responseMsg[LatticeResponseCode.userDeclined]}`, - ); + await expect(client.sign(req)).rejects.toThrow(`${ProtocolConstants.responseMsg[LatticeResponseCode.userDeclined]}`); }); }); @@ -200,30 +187,17 @@ describe('General', () => { const txData = { prevOuts: [ { - txHash: - '6e78493091f80d89a92ae3152df7fbfbdc44df09cf01a9b76c5113c02eaf2e0f', + txHash: '6e78493091f80d89a92ae3152df7fbfbdc44df09cf01a9b76c5113c02eaf2e0f', value: 10000, index: 1, - signerPath: [ - BTC_PURPOSE_P2SH_P2WPKH, - BTC_TESTNET_COIN, - HARDENED_OFFSET, - 0, - 0, - ], + signerPath: [BTC_PURPOSE_P2SH_P2WPKH, BTC_TESTNET_COIN, HARDENED_OFFSET, 0, 0], }, ], recipient: 'mhifA1DwiMPHTjSJM8FFSL8ibrzWaBCkVT', value: 1000, fee: 1000, // isSegwit: false, // old encoding - changePath: [ - BTC_PURPOSE_P2SH_P2WPKH, - BTC_TESTNET_COIN, - HARDENED_OFFSET, - 1, - 0, - ], + changePath: [BTC_PURPOSE_P2SH_P2WPKH, BTC_TESTNET_COIN, HARDENED_OFFSET, 1, 0], }; const req = { currency: 'BTC' as const, @@ -240,29 +214,16 @@ describe('General', () => { const txData = { prevOuts: [ { - txHash: - 'ab8288ef207f11186af98db115aa7120aa36ceb783e8792fb7b2f39c88109a99', + txHash: 'ab8288ef207f11186af98db115aa7120aa36ceb783e8792fb7b2f39c88109a99', value: 10000, index: 1, - signerPath: [ - BTC_PURPOSE_P2SH_P2WPKH, - BTC_TESTNET_COIN, - HARDENED_OFFSET, - 0, - 0, - ], + signerPath: [BTC_PURPOSE_P2SH_P2WPKH, BTC_TESTNET_COIN, HARDENED_OFFSET, 0, 0], }, ], recipient: '2NGZrVvZG92qGYqzTLjCAewvPZ7JE8S8VxE', value: 1000, fee: 1000, - changePath: [ - BTC_PURPOSE_P2SH_P2WPKH, - BTC_TESTNET_COIN, - HARDENED_OFFSET, - 1, - 0, - ], + changePath: [BTC_PURPOSE_P2SH_P2WPKH, BTC_TESTNET_COIN, HARDENED_OFFSET, 1, 0], }; const req = { currency: 'BTC' as const, @@ -278,29 +239,16 @@ describe('General', () => { const txData = { prevOuts: [ { - txHash: - 'f93d0a77f58b4274d84f427d647f1f27e38b4db79fd975691e15109fde7ea06e', + txHash: 'f93d0a77f58b4274d84f427d647f1f27e38b4db79fd975691e15109fde7ea06e', value: 1802440, index: 1, - signerPath: [ - BTC_PURPOSE_P2SH_P2WPKH, - BTC_TESTNET_COIN, - HARDENED_OFFSET, - 1, - 0, - ], + signerPath: [BTC_PURPOSE_P2SH_P2WPKH, BTC_TESTNET_COIN, HARDENED_OFFSET, 1, 0], }, ], recipient: 'tb1qym0z2a939lefrgw67ep5flhf43dvpg3h4s96tn', value: 1000, fee: 1000, - changePath: [ - BTC_PURPOSE_P2SH_P2WPKH, - BTC_TESTNET_COIN, - HARDENED_OFFSET, - 1, - 0, - ], + changePath: [BTC_PURPOSE_P2SH_P2WPKH, BTC_TESTNET_COIN, HARDENED_OFFSET, 1, 0], }; const req = { currency: 'BTC' as const, @@ -316,30 +264,17 @@ describe('General', () => { const txData = { prevOuts: [ { - txHash: - 'b2efdbdd3340d2bc547671ce3993a6f05d70343c07578f9d7f5626fdfc06fa35', + txHash: 'b2efdbdd3340d2bc547671ce3993a6f05d70343c07578f9d7f5626fdfc06fa35', value: 76800, index: 0, - signerPath: [ - BTC_PURPOSE_P2WPKH, - BTC_TESTNET_COIN, - HARDENED_OFFSET, - 0, - 0, - ], + signerPath: [BTC_PURPOSE_P2WPKH, BTC_TESTNET_COIN, HARDENED_OFFSET, 0, 0], }, ], recipient: '2N4gqWT4oqWL2gz9ps92z9fm2Bg3FUkqG7Q', value: 70000, fee: 4380, isSegwit: true, - changePath: [ - BTC_PURPOSE_P2WPKH, - BTC_TESTNET_COIN, - HARDENED_OFFSET, - 1, - 0, - ], + changePath: [BTC_PURPOSE_P2WPKH, BTC_TESTNET_COIN, HARDENED_OFFSET, 1, 0], }; const req = { currency: 'BTC' as const, diff --git a/packages/sdk/src/__test__/e2e/kv.test.ts b/packages/sdk/src/__test__/e2e/kv.test.ts index 73dd85d3..85af4b10 100644 --- a/packages/sdk/src/__test__/e2e/kv.test.ts +++ b/packages/sdk/src/__test__/e2e/kv.test.ts @@ -46,9 +46,7 @@ describe('key-value', () => { it('Should ask if the user wants to reset state', async () => { let answer = 'Y'; if (process.env.CI !== '1') { - answer = question( - 'Do you want to clear all kv records and start anew? (Y/N) ', - ); + answer = question('Do you want to clear all kv records and start anew? (Y/N) '); } else { answer = 'Y'; } @@ -68,9 +66,7 @@ describe('key-value', () => { } if (lastTotal !== null && total >= lastTotal) { - console.warn( - '[kv.test] KV cleanup halted to avoid infinite loop (no progress detected).', - ); + console.warn('[kv.test] KV cleanup halted to avoid infinite loop (no progress detected).'); break; } @@ -95,9 +91,7 @@ describe('key-value', () => { it('Should make a request to an unknown address', async () => { await client.sign(ETH_REQ as unknown as SignRequestParams).catch((err) => { - expect(err.message).toContain( - ProtocolConstants.responseMsg[LatticeResponseCode.userDeclined], - ); + expect(err.message).toContain(ProtocolConstants.responseMsg[LatticeResponseCode.userDeclined]); }); }); @@ -117,23 +111,15 @@ describe('key-value', () => { it('Should fail to add records with unicode characters', async () => { const badKey = { '0x🔥🦍': 'Muh name' }; const badVal = { UNISWAP_ADDR: 'val🔥🦍' }; - await expect(client.addKvRecords({ records: badKey })).rejects.toThrow( - 'Unicode characters are not supported.', - ); - await expect(client.addKvRecords({ records: badVal })).rejects.toThrow( - 'Unicode characters are not supported.', - ); + await expect(client.addKvRecords({ records: badKey })).rejects.toThrow('Unicode characters are not supported.'); + await expect(client.addKvRecords({ records: badVal })).rejects.toThrow('Unicode characters are not supported.'); }); it('Should fail to add zero length keys and values', async () => { const badKey = { '': 'Muh name' }; const badVal = { UNISWAP_ADDR: '' }; - await expect(client.addKvRecords({ records: badKey })).rejects.toThrow( - 'Keys and values must be >0 characters.', - ); - await expect(client.addKvRecords({ records: badVal })).rejects.toThrow( - 'Keys and values must be >0 characters.', - ); + await expect(client.addKvRecords({ records: badKey })).rejects.toThrow('Keys and values must be >0 characters.'); + await expect(client.addKvRecords({ records: badVal })).rejects.toThrow('Keys and values must be >0 characters.'); }); it('Should fetch the newly created records', async () => { @@ -226,9 +212,7 @@ describe('key-value', () => { it('Should make another request to make sure case sensitivity is enforced', async () => { await client.sign(ETH_REQ as unknown as SignRequestParams).catch((err) => { - expect(err.message).toContain( - ProtocolConstants.responseMsg[LatticeResponseCode.userDeclined], - ); + expect(err.message).toContain(ProtocolConstants.responseMsg[LatticeResponseCode.userDeclined]); }); }); diff --git a/packages/sdk/src/__test__/e2e/non-exportable.test.ts b/packages/sdk/src/__test__/e2e/non-exportable.test.ts index 7edd6d13..96301c02 100644 --- a/packages/sdk/src/__test__/e2e/non-exportable.test.ts +++ b/packages/sdk/src/__test__/e2e/non-exportable.test.ts @@ -47,9 +47,7 @@ describe('Non-Exportable Seed', () => { return; } // NOTE: non-exportable seeds were deprecated from the normal setup pathway in firmware v0.12.0 - const result = await question( - 'Do you have a non-exportable SafeCard seed loaded and wish to continue? (Y/N) ', - ); + const result = await question('Do you have a non-exportable SafeCard seed loaded and wish to continue? (Y/N) '); if (result.toLowerCase() !== 'y') { runTests = false; } @@ -93,9 +91,7 @@ describe('Non-Exportable Seed', () => { }; // Validate that tx sigs are non-uniform const unsignedMsg = tx.getMessageToSign(); - const unsigned = Array.isArray(unsignedMsg) - ? RLP.encode(unsignedMsg) - : unsignedMsg; + const unsigned = Array.isArray(unsignedMsg) ? RLP.encode(unsignedMsg) : unsignedMsg; const tx1Resp = await client.sign(txReq); validateSig(tx1Resp, unsigned); const tx2Resp = await client.sign(txReq); @@ -147,21 +143,11 @@ describe('Non-Exportable Seed', () => { }; // NOTE: This uses the legacy signing pathway, which validates the signature // Once we move this to generic signing, we will need to validate these. - const msg1Resp = await client.sign( - msgReq as unknown as SignRequestParams, - ); - const msg2Resp = await client.sign( - msgReq as unknown as SignRequestParams, - ); - const msg3Resp = await client.sign( - msgReq as unknown as SignRequestParams, - ); - const msg4Resp = await client.sign( - msgReq as unknown as SignRequestParams, - ); - const msg5Resp = await client.sign( - msgReq as unknown as SignRequestParams, - ); + const msg1Resp = await client.sign(msgReq as unknown as SignRequestParams); + const msg2Resp = await client.sign(msgReq as unknown as SignRequestParams); + const msg3Resp = await client.sign(msgReq as unknown as SignRequestParams); + const msg4Resp = await client.sign(msgReq as unknown as SignRequestParams); + const msg5Resp = await client.sign(msgReq as unknown as SignRequestParams); // Check sig 1 expect(getSigStr(msg1Resp)).not.toEqual(getSigStr(msg2Resp)); expect(getSigStr(msg1Resp)).not.toEqual(getSigStr(msg3Resp)); diff --git a/packages/sdk/src/__test__/e2e/signing/bls.test.ts b/packages/sdk/src/__test__/e2e/signing/bls.test.ts index e37edf40..ec424ba0 100644 --- a/packages/sdk/src/__test__/e2e/signing/bls.test.ts +++ b/packages/sdk/src/__test__/e2e/signing/bls.test.ts @@ -15,12 +15,7 @@ * Running with a different mnemonic will cause test failures due to * incorrect key derivations. */ -import { - create as createKeystore, - decrypt as decryptKeystore, - isValidKeystore, - verifyPassword, -} from '@chainsafe/bls-keystore'; +import { create as createKeystore, decrypt as decryptKeystore, isValidKeystore, verifyPassword } from '@chainsafe/bls-keystore'; import { getPublicKey, sign } from '@noble/bls12-381'; import { deriveSeedTree } from 'bls12-381-keygen'; import { question } from 'readline-sync'; @@ -56,30 +51,21 @@ describe('[BLS keys]', () => { // Check if firmware supports BLS (requires >= 0.17.0) const fwVersion = client.getFwVersion(); - const versionStr = fwVersion - ? `${fwVersion.major}.${fwVersion.minor}.${fwVersion.fix}` - : 'unknown'; + const versionStr = fwVersion ? `${fwVersion.major}.${fwVersion.minor}.${fwVersion.fix}` : 'unknown'; console.log(`\n[BLS Test] Firmware version: ${versionStr}`); console.log('[BLS Test] Raw fwVersion:', fwVersion); const fwConstants = client.getFwConstants(); console.log('[BLS Test] getAddressFlags:', fwConstants?.getAddressFlags); - console.log( - '[BLS Test] BLS12_381_G1_PUB constant:', - Constants.GET_ADDR_FLAGS.BLS12_381_G1_PUB, - ); + console.log('[BLS Test] BLS12_381_G1_PUB constant:', Constants.GET_ADDR_FLAGS.BLS12_381_G1_PUB); - supportsBLS = fwConstants?.getAddressFlags?.includes( - Constants.GET_ADDR_FLAGS.BLS12_381_G1_PUB as number, - ); + supportsBLS = fwConstants?.getAddressFlags?.includes(Constants.GET_ADDR_FLAGS.BLS12_381_G1_PUB as number); console.log(`[BLS Test] supportsBLS: ${supportsBLS}\n`); if (!supportsBLS) { - console.warn( - `\nSkipping BLS tests: Firmware version ${versionStr} does not support BLS operations.\nBLS support requires firmware version >= 0.17.0\n`, - ); + console.warn(`\nSkipping BLS tests: Firmware version ${versionStr} does not support BLS operations.\nBLS support requires firmware version >= 0.17.0\n`); } }); @@ -172,17 +158,9 @@ async function testBLSDerivationAndSig(seed, signerPath) { const refPubStr = Buffer.from(refPub).toString('hex'); const refSig = await sign(msg, priv); const refSigStr = Buffer.from(refSig).toString('hex'); - expect(latticePub.toString('hex')).to.equal( - refPubStr, - 'Deposit public key mismatch', - ); - expect(latticeSig.pubkey.toString('hex')).to.equal( - refPubStr, - 'Lattice signature returned wrong pubkey', - ); - expect( - Buffer.from(latticeSig.sig as unknown as Buffer).toString('hex'), - ).to.equal(refSigStr, 'Signature mismatch'); + expect(latticePub.toString('hex')).to.equal(refPubStr, 'Deposit public key mismatch'); + expect(latticeSig.pubkey.toString('hex')).to.equal(refPubStr, 'Lattice signature returned wrong pubkey'); + expect(Buffer.from(latticeSig.sig as unknown as Buffer).toString('hex')).to.equal(refSigStr, 'Signature mismatch'); } async function validateExportedKeystore(seed, path, pw, expKeystoreBuffer) { const exportedKeystore = JSON.parse(expKeystoreBuffer.toString()); @@ -190,39 +168,18 @@ async function validateExportedKeystore(seed, path, pw, expKeystoreBuffer) { const pub = getPublicKey(priv); // Validate the keystore in isolation - expect(isValidKeystore(exportedKeystore)).to.equal( - true, - 'Exported keystore invalid!', - ); + expect(isValidKeystore(exportedKeystore)).to.equal(true, 'Exported keystore invalid!'); const expPwVerified = await verifyPassword(exportedKeystore, pw); - expect(expPwVerified).to.equal( - true, - `Password could not be verified in exported keystore. Expected "${pw}"`, - ); + expect(expPwVerified).to.equal(true, `Password could not be verified in exported keystore. Expected "${pw}"`); const expDec = await decryptKeystore(exportedKeystore, pw); - expect(Buffer.from(expDec).toString('hex')).to.equal( - Buffer.from(priv).toString('hex'), - 'Exported keystore did not properly encrypt key!', - ); - expect(exportedKeystore.pubkey).to.equal( - Buffer.from(pub).toString('hex'), - 'Wrong public key exported from Lattice', - ); + expect(Buffer.from(expDec).toString('hex')).to.equal(Buffer.from(priv).toString('hex'), 'Exported keystore did not properly encrypt key!'); + expect(exportedKeystore.pubkey).to.equal(Buffer.from(pub).toString('hex'), 'Wrong public key exported from Lattice'); // Generate an independent keystore and compare decrypted contents const genKeystore = await createKeystore(pw, priv, pub, getPathStr(path)); - expect(isValidKeystore(genKeystore)).to.equal( - true, - 'Generated keystore invalid?', - ); + expect(isValidKeystore(genKeystore)).to.equal(true, 'Generated keystore invalid?'); const genPwVerified = await verifyPassword(genKeystore, pw); - expect(genPwVerified).to.equal( - true, - 'Password could not be verified in generated keystore?', - ); + expect(genPwVerified).to.equal(true, 'Password could not be verified in generated keystore?'); const genDec = await decryptKeystore(genKeystore, pw); - expect(Buffer.from(expDec).toString('hex')).to.equal( - Buffer.from(genDec).toString('hex'), - 'Exported encrypted privkey did not match factory test example...', - ); + expect(Buffer.from(expDec).toString('hex')).to.equal(Buffer.from(genDec).toString('hex'), 'Exported encrypted privkey did not match factory test example...'); } diff --git a/packages/sdk/src/__test__/e2e/signing/determinism.test.ts b/packages/sdk/src/__test__/e2e/signing/determinism.test.ts index 064950ea..15f6888d 100644 --- a/packages/sdk/src/__test__/e2e/signing/determinism.test.ts +++ b/packages/sdk/src/__test__/e2e/signing/determinism.test.ts @@ -1,19 +1,8 @@ import { HARDENED_OFFSET } from '../../../constants'; import type { SignRequestParams, WalletPath } from '../../../types'; import { randomBytes } from '../../../util'; -import { - DEFAULT_SIGNER, - buildMsgReq, - buildRandomVectors, - buildTx, - buildTxReq, -} from '../../utils/builders'; -import { - deriveAddress, - signEip712JS, - signPersonalJS, - testUniformSigs, -} from '../../utils/determinism'; +import { DEFAULT_SIGNER, buildMsgReq, buildRandomVectors, buildTx, buildTxReq } from '../../utils/builders'; +import { deriveAddress, signEip712JS, signPersonalJS, testUniformSigs } from '../../utils/determinism'; /** * REQUIRED TEST MNEMONIC: * These tests require a SafeCard loaded with the standard test mnemonic: @@ -44,29 +33,11 @@ describe('[Determinism]', () => { }); it('Should validate some Ledger addresses derived from the test seed', async () => { - const path0 = [ - BTC_PURPOSE_P2PKH, - ETH_COIN, - HARDENED_OFFSET, - 0, - 0, - ] as WalletPath; + const path0 = [BTC_PURPOSE_P2PKH, ETH_COIN, HARDENED_OFFSET, 0, 0] as WalletPath; const addr0 = deriveAddress(TEST_SEED, path0); - const path1 = [ - BTC_PURPOSE_P2PKH, - ETH_COIN, - HARDENED_OFFSET + 1, - 0, - 0, - ] as WalletPath; + const path1 = [BTC_PURPOSE_P2PKH, ETH_COIN, HARDENED_OFFSET + 1, 0, 0] as WalletPath; const addr1 = deriveAddress(TEST_SEED, path1); - const path8 = [ - BTC_PURPOSE_P2PKH, - ETH_COIN, - HARDENED_OFFSET + 8, - 0, - 0, - ] as WalletPath; + const path8 = [BTC_PURPOSE_P2PKH, ETH_COIN, HARDENED_OFFSET + 8, 0, 0] as WalletPath; const addr8 = deriveAddress(TEST_SEED, path8); // Fetch these addresses from the Lattice and validate @@ -76,22 +47,13 @@ describe('[Determinism]', () => { n: 1, }; const latAddr0 = await client.getAddresses(req); - expect((latAddr0[0] as string).toLowerCase()).toEqualElseLog( - addr0.toLowerCase(), - 'Incorrect address 0 fetched. Ensure your SafeCard is loaded with the test mnemonic: "test test test test test test test test test test test junk"', - ); + expect((latAddr0[0] as string).toLowerCase()).toEqualElseLog(addr0.toLowerCase(), 'Incorrect address 0 fetched. Ensure your SafeCard is loaded with the test mnemonic: "test test test test test test test test test test test junk"'); req.startPath = path1; const latAddr1 = await client.getAddresses(req); - expect((latAddr1[0] as string).toLowerCase()).toEqualElseLog( - addr1.toLowerCase(), - 'Incorrect address 1 fetched. Ensure your SafeCard is loaded with the test mnemonic: "test test test test test test test test test test test junk"', - ); + expect((latAddr1[0] as string).toLowerCase()).toEqualElseLog(addr1.toLowerCase(), 'Incorrect address 1 fetched. Ensure your SafeCard is loaded with the test mnemonic: "test test test test test test test test test test test junk"'); req.startPath = path8; const latAddr8 = await client.getAddresses(req); - expect((latAddr8[0] as string).toLowerCase()).toEqualElseLog( - addr8.toLowerCase(), - 'Incorrect address 8 fetched. Ensure your SafeCard is loaded with the test mnemonic: "test test test test test test test test test test test junk"', - ); + expect((latAddr8[0] as string).toLowerCase()).toEqualElseLog(addr8.toLowerCase(), 'Incorrect address 8 fetched. Ensure your SafeCard is loaded with the test mnemonic: "test test test test test test test test test test test junk"'); }); }); @@ -146,10 +108,7 @@ describe('[Determinism]', () => { const res = await client.sign(msgReq as unknown as SignRequestParams); const sig = getSigStr(res); const jsSig = signPersonalJS(msgReq.data.payload, msgReq.data.signerPath); - expect(sig).toEqualElseLog( - jsSig, - 'Lattice sig does not match JS reference', - ); + expect(sig).toEqualElseLog(jsSig, 'Lattice sig does not match JS reference'); }); it('Should validate signature from addr1', async () => { @@ -158,10 +117,7 @@ describe('[Determinism]', () => { const res = await client.sign(msgReq as unknown as SignRequestParams); const sig = getSigStr(res); const jsSig = signPersonalJS(msgReq.data.payload, msgReq.data.signerPath); - expect(sig).toEqualElseLog( - jsSig, - 'Lattice sig does not match JS reference', - ); + expect(sig).toEqualElseLog(jsSig, 'Lattice sig does not match JS reference'); }); it('Should validate signature from addr8', async () => { @@ -170,10 +126,7 @@ describe('[Determinism]', () => { const res = await client.sign(msgReq as unknown as SignRequestParams); const sig = getSigStr(res); const jsSig = signPersonalJS(msgReq.data.payload, msgReq.data.signerPath); - expect(sig).toEqualElseLog( - jsSig, - 'Lattice sig does not match JS reference', - ); + expect(sig).toEqualElseLog(jsSig, 'Lattice sig does not match JS reference'); }); }); @@ -184,10 +137,7 @@ describe('[Determinism]', () => { const res = await client.sign(msgReq as unknown as SignRequestParams); const sig = getSigStr(res); const jsSig = signPersonalJS(msgReq.data.payload, msgReq.data.signerPath); - expect(sig).toEqualElseLog( - jsSig, - 'Lattice sig does not match JS reference', - ); + expect(sig).toEqualElseLog(jsSig, 'Lattice sig does not match JS reference'); }); it('Should validate signature from addr1', async () => { @@ -196,10 +146,7 @@ describe('[Determinism]', () => { const res = await client.sign(msgReq as unknown as SignRequestParams); const sig = getSigStr(res); const jsSig = signPersonalJS(msgReq.data.payload, msgReq.data.signerPath); - expect(sig).toEqualElseLog( - jsSig, - 'Lattice sig does not match JS reference', - ); + expect(sig).toEqualElseLog(jsSig, 'Lattice sig does not match JS reference'); }); it('Should validate signature from addr8', async () => { @@ -208,10 +155,7 @@ describe('[Determinism]', () => { const res = await client.sign(msgReq as unknown as SignRequestParams); const sig = getSigStr(res); const jsSig = signPersonalJS(msgReq.data.payload, msgReq.data.signerPath); - expect(sig).toEqualElseLog( - jsSig, - 'Lattice sig does not match JS reference', - ); + expect(sig).toEqualElseLog(jsSig, 'Lattice sig does not match JS reference'); }); }); @@ -222,10 +166,7 @@ describe('[Determinism]', () => { const res = await client.sign(msgReq as unknown as SignRequestParams); const sig = getSigStr(res); const jsSig = signPersonalJS(msgReq.data.payload, msgReq.data.signerPath); - expect(sig).toEqualElseLog( - jsSig, - 'Lattice sig does not match JS reference', - ); + expect(sig).toEqualElseLog(jsSig, 'Lattice sig does not match JS reference'); }); it('Should validate signature from addr1', async () => { @@ -234,10 +175,7 @@ describe('[Determinism]', () => { const res = await client.sign(msgReq as unknown as SignRequestParams); const sig = getSigStr(res); const jsSig = signPersonalJS(msgReq.data.payload, msgReq.data.signerPath); - expect(sig).toEqualElseLog( - jsSig, - 'Lattice sig does not match JS reference', - ); + expect(sig).toEqualElseLog(jsSig, 'Lattice sig does not match JS reference'); }); it('Should validate signature from addr8', async () => { @@ -246,10 +184,7 @@ describe('[Determinism]', () => { const res = await client.sign(msgReq as unknown as SignRequestParams); const sig = getSigStr(res); const jsSig = signPersonalJS(msgReq.data.payload, msgReq.data.signerPath); - expect(sig).toEqualElseLog( - jsSig, - 'Lattice sig does not match JS reference', - ); + expect(sig).toEqualElseLog(jsSig, 'Lattice sig does not match JS reference'); }); }); @@ -300,10 +235,7 @@ describe('[Determinism]', () => { const res = await client.sign(msgReq as unknown as SignRequestParams); const sig = getSigStr(res); const jsSig = signEip712JS(msgReq.data.payload, msgReq.data.signerPath); - expect(sig).toEqualElseLog( - jsSig, - 'Lattice EIP712 sig does not match JS reference', - ); + expect(sig).toEqualElseLog(jsSig, 'Lattice EIP712 sig does not match JS reference'); }); it('Should validate signature from addr1', async () => { @@ -311,10 +243,7 @@ describe('[Determinism]', () => { const res = await client.sign(msgReq as unknown as SignRequestParams); const sig = getSigStr(res); const jsSig = signEip712JS(msgReq.data.payload, msgReq.data.signerPath); - expect(sig).toEqualElseLog( - jsSig, - 'Lattice EIP712 sig does not match JS reference', - ); + expect(sig).toEqualElseLog(jsSig, 'Lattice EIP712 sig does not match JS reference'); }); it('Should validate signature from addr8', async () => { @@ -322,10 +251,7 @@ describe('[Determinism]', () => { const res = await client.sign(msgReq as unknown as SignRequestParams); const sig = getSigStr(res); const jsSig = signEip712JS(msgReq.data.payload, msgReq.data.signerPath); - expect(sig).toEqualElseLog( - jsSig, - 'Lattice EIP712 sig does not match JS reference', - ); + expect(sig).toEqualElseLog(jsSig, 'Lattice EIP712 sig does not match JS reference'); }); }); @@ -366,10 +292,7 @@ describe('[Determinism]', () => { const res = await client.sign(msgReq as unknown as SignRequestParams); const sig = getSigStr(res); const jsSig = signEip712JS(msgReq.data.payload, msgReq.data.signerPath); - expect(sig).toEqualElseLog( - jsSig, - 'Lattice EIP712 sig does not match JS reference', - ); + expect(sig).toEqualElseLog(jsSig, 'Lattice EIP712 sig does not match JS reference'); }); it('Should validate signature from addr1', async () => { @@ -377,10 +300,7 @@ describe('[Determinism]', () => { const res = await client.sign(msgReq as unknown as SignRequestParams); const sig = getSigStr(res); const jsSig = signEip712JS(msgReq.data.payload, msgReq.data.signerPath); - expect(sig).toEqualElseLog( - jsSig, - 'Lattice EIP712 sig does not match JS reference', - ); + expect(sig).toEqualElseLog(jsSig, 'Lattice EIP712 sig does not match JS reference'); }); it('Should validate signature from addr8', async () => { @@ -388,10 +308,7 @@ describe('[Determinism]', () => { const res = await client.sign(msgReq as unknown as SignRequestParams); const sig = getSigStr(res); const jsSig = signEip712JS(msgReq.data.payload, msgReq.data.signerPath); - expect(sig).toEqualElseLog( - jsSig, - 'Lattice EIP712 sig does not match JS reference', - ); + expect(sig).toEqualElseLog(jsSig, 'Lattice EIP712 sig does not match JS reference'); }); }); @@ -432,30 +349,21 @@ describe('[Determinism]', () => { const res = await client.sign(msgReq as unknown as SignRequestParams); const sig = getSigStr(res); const jsSig = signEip712JS(msgReq.data.payload, msgReq.data.signerPath); - expect(sig).toEqualElseLog( - jsSig, - 'Lattice EIP712 sig does not match JS reference', - ); + expect(sig).toEqualElseLog(jsSig, 'Lattice EIP712 sig does not match JS reference'); }); it('Should validate signature from addr1', async () => { msgReq.data.signerPath[2] = HARDENED_OFFSET + 1; const res = await client.sign(msgReq as unknown as SignRequestParams); const sig = getSigStr(res); const jsSig = signEip712JS(msgReq.data.payload, msgReq.data.signerPath); - expect(sig).toEqualElseLog( - jsSig, - 'Lattice EIP712 sig does not match JS reference', - ); + expect(sig).toEqualElseLog(jsSig, 'Lattice EIP712 sig does not match JS reference'); }); it('Should validate signature from addr8', async () => { msgReq.data.signerPath[2] = HARDENED_OFFSET + 8; const res = await client.sign(msgReq as unknown as SignRequestParams); const sig = getSigStr(res); const jsSig = signEip712JS(msgReq.data.payload, msgReq.data.signerPath); - expect(sig).toEqualElseLog( - jsSig, - 'Lattice EIP712 sig does not match JS reference', - ); + expect(sig).toEqualElseLog(jsSig, 'Lattice EIP712 sig does not match JS reference'); }); }); diff --git a/packages/sdk/src/__test__/e2e/signing/eip712-vectors.ts b/packages/sdk/src/__test__/e2e/signing/eip712-vectors.ts index 2a06886c..f6e73103 100644 --- a/packages/sdk/src/__test__/e2e/signing/eip712-vectors.ts +++ b/packages/sdk/src/__test__/e2e/signing/eip712-vectors.ts @@ -256,9 +256,7 @@ export const EIP712_MESSAGE_VECTORS: Array<{ }, primaryType: 'Data', message: { - value: BigInt( - '115792089237316195423570985008687907853269984665640564039457584007913129639935', - ), + value: BigInt('115792089237316195423570985008687907853269984665640564039457584007913129639935'), }, }, }, @@ -276,9 +274,7 @@ export const EIP712_MESSAGE_VECTORS: Array<{ }, primaryType: 'Data', message: { - value: BigInt( - '-57896044618658097711785492504343953926634992332820282019728792003956564819968', - ), + value: BigInt('-57896044618658097711785492504343953926634992332820282019728792003956564819968'), }, }, }, diff --git a/packages/sdk/src/__test__/e2e/signing/evm-tx.test.ts b/packages/sdk/src/__test__/e2e/signing/evm-tx.test.ts index e5d52bc4..9a7a551b 100644 --- a/packages/sdk/src/__test__/e2e/signing/evm-tx.test.ts +++ b/packages/sdk/src/__test__/e2e/signing/evm-tx.test.ts @@ -8,13 +8,7 @@ import { setupClient } from '../../utils/setup'; import { signAndCompareTransaction } from '../../utils/viemComparison'; -import { - EDGE_CASE_TEST_VECTORS, - EIP1559_TEST_VECTORS, - EIP2930_TEST_VECTORS, - EIP7702_TEST_VECTORS, - LEGACY_VECTORS, -} from './vectors'; +import { EDGE_CASE_TEST_VECTORS, EIP1559_TEST_VECTORS, EIP2930_TEST_VECTORS, EIP7702_TEST_VECTORS, LEGACY_VECTORS } from './vectors'; describe('EVM Transaction Signing - Unified Test Suite', () => { beforeAll(async () => { diff --git a/packages/sdk/src/__test__/e2e/signing/solana/__mocks__/programs.ts b/packages/sdk/src/__test__/e2e/signing/solana/__mocks__/programs.ts index f72b1ec9..f6c57373 100644 --- a/packages/sdk/src/__test__/e2e/signing/solana/__mocks__/programs.ts +++ b/packages/sdk/src/__test__/e2e/signing/solana/__mocks__/programs.ts @@ -1,87 +1,33 @@ export const raydiumProgram = Buffer.from([ - 2, 0, 10, 24, 47, 41, 101, 134, 4, 204, 35, 219, 9, 43, 55, 20, 255, 76, 29, - 27, 175, 197, 102, 175, 108, 72, 231, 146, 134, 30, 219, 42, 168, 236, 17, 3, - 209, 66, 76, 96, 212, 139, 188, 184, 209, 19, 177, 79, 32, 176, 155, 148, 130, - 200, 98, 110, 128, 160, 186, 226, 136, 168, 203, 105, 117, 38, 171, 161, 41, - 11, 235, 184, 125, 115, 158, 2, 137, 19, 123, 58, 193, 31, 193, 215, 57, 0, - 240, 162, 154, 197, 182, 102, 96, 91, 101, 29, 31, 63, 168, 145, 47, 189, 251, - 138, 30, 226, 174, 123, 162, 139, 147, 54, 101, 251, 231, 142, 230, 173, 232, - 212, 153, 225, 58, 113, 24, 53, 97, 26, 89, 169, 83, 10, 48, 190, 92, 28, 22, - 83, 66, 168, 232, 41, 135, 46, 21, 208, 110, 217, 210, 243, 1, 237, 247, 237, - 183, 71, 1, 193, 117, 86, 188, 217, 134, 204, 52, 147, 214, 106, 218, 145, 18, - 3, 14, 152, 187, 194, 134, 97, 177, 146, 183, 102, 40, 90, 33, 217, 240, 113, - 89, 142, 64, 120, 22, 21, 175, 245, 69, 184, 172, 211, 86, 215, 29, 176, 82, - 209, 2, 198, 205, 207, 75, 152, 201, 35, 86, 169, 205, 157, 142, 144, 96, 235, - 228, 249, 204, 249, 212, 189, 80, 236, 218, 125, 206, 4, 54, 254, 165, 182, - 236, 127, 232, 154, 117, 171, 105, 133, 186, 163, 206, 201, 159, 70, 10, 17, - 138, 239, 21, 37, 109, 139, 81, 59, 76, 57, 145, 209, 124, 73, 151, 15, 83, - 117, 205, 87, 153, 166, 158, 188, 141, 157, 153, 230, 131, 244, 4, 118, 170, - 60, 182, 39, 25, 110, 87, 174, 54, 186, 81, 129, 162, 212, 79, 70, 190, 180, - 232, 143, 72, 102, 119, 242, 172, 80, 2, 53, 168, 117, 184, 99, 184, 206, 230, - 204, 5, 165, 141, 30, 37, 25, 159, 230, 130, 247, 252, 139, 86, 49, 132, 94, - 254, 8, 198, 72, 16, 173, 202, 16, 222, 102, 65, 202, 40, 218, 190, 74, 34, - 21, 163, 163, 179, 232, 137, 88, 91, 104, 174, 112, 225, 188, 55, 104, 213, - 116, 77, 143, 100, 55, 181, 95, 3, 149, 44, 132, 146, 146, 85, 62, 245, 56, - 164, 243, 10, 248, 172, 153, 97, 85, 86, 177, 82, 92, 148, 248, 130, 133, 54, - 160, 65, 92, 148, 142, 53, 148, 190, 85, 136, 146, 82, 19, 186, 209, 204, 116, - 25, 155, 69, 186, 207, 152, 45, 54, 94, 70, 191, 239, 106, 17, 161, 122, 157, - 199, 83, 30, 23, 42, 103, 226, 74, 230, 111, 113, 173, 56, 0, 0, 0, 0, 0, 0, - 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, - 55, 153, 140, 203, 242, 208, 69, 139, 97, 92, 188, 198, 177, 163, 103, 196, - 116, 158, 159, 239, 115, 6, 98, 46, 27, 27, 88, 145, 1, 32, 188, 154, 65, 87, - 176, 88, 15, 49, 197, 252, 228, 74, 98, 88, 45, 188, 249, 215, 142, 231, 89, - 67, 160, 132, 163, 147, 179, 80, 54, 141, 34, 137, 147, 8, 75, 217, 73, 196, - 54, 2, 195, 63, 32, 119, 144, 237, 22, 163, 82, 76, 161, 185, 151, 92, 241, - 33, 162, 169, 12, 255, 236, 125, 248, 182, 138, 205, 95, 183, 40, 175, 220, - 220, 4, 120, 120, 238, 132, 58, 111, 235, 68, 175, 40, 205, 83, 163, 72, 40, - 217, 144, 59, 90, 213, 230, 151, 25, 85, 42, 133, 15, 45, 110, 2, 164, 122, - 248, 36, 208, 154, 182, 157, 196, 45, 112, 203, 40, 203, 250, 36, 159, 183, - 238, 87, 185, 210, 86, 193, 39, 98, 239, 140, 151, 37, 143, 78, 36, 137, 241, - 187, 61, 16, 41, 20, 142, 13, 131, 11, 90, 19, 153, 218, 255, 16, 132, 4, 142, - 123, 216, 219, 233, 248, 89, 6, 155, 136, 87, 254, 171, 129, 132, 251, 104, - 127, 99, 70, 24, 192, 53, 218, 196, 57, 220, 26, 235, 59, 85, 152, 160, 240, - 0, 0, 0, 0, 1, 6, 167, 213, 23, 25, 44, 92, 81, 33, 140, 201, 76, 61, 74, 241, - 127, 88, 218, 238, 8, 155, 161, 253, 68, 227, 219, 217, 138, 0, 0, 0, 0, 6, - 221, 246, 225, 215, 101, 161, 147, 217, 203, 225, 70, 206, 235, 121, 172, 28, - 180, 133, 237, 95, 91, 55, 145, 58, 140, 245, 133, 126, 255, 0, 169, 238, 204, - 15, 249, 117, 156, 236, 123, 109, 247, 173, 57, 139, 143, 19, 21, 15, 180, - 230, 189, 171, 230, 228, 215, 211, 229, 22, 94, 108, 135, 17, 93, 5, 14, 2, 0, - 1, 52, 0, 0, 0, 0, 48, 96, 46, 0, 0, 0, 0, 0, 165, 0, 0, 0, 0, 0, 0, 0, 6, - 221, 246, 225, 215, 101, 161, 147, 217, 203, 225, 70, 206, 235, 121, 172, 28, - 180, 133, 237, 95, 91, 55, 145, 58, 140, 245, 133, 126, 255, 0, 169, 23, 4, 1, - 21, 0, 22, 1, 1, 20, 7, 0, 9, 0, 15, 14, 23, 22, 0, 17, 18, 23, 10, 16, 7, 6, - 13, 2, 19, 12, 11, 3, 4, 8, 5, 18, 1, 9, 0, 17, 9, 64, 66, 15, 0, 0, 0, 0, 0, - 89, 241, 0, 0, 0, 0, 0, 0, 23, 3, 1, 0, 0, 1, 9, + 2, 0, 10, 24, 47, 41, 101, 134, 4, 204, 35, 219, 9, 43, 55, 20, 255, 76, 29, 27, 175, 197, 102, 175, 108, 72, 231, 146, 134, 30, 219, 42, 168, 236, 17, 3, 209, 66, 76, 96, 212, 139, 188, 184, 209, 19, 177, 79, 32, 176, 155, 148, 130, 200, + 98, 110, 128, 160, 186, 226, 136, 168, 203, 105, 117, 38, 171, 161, 41, 11, 235, 184, 125, 115, 158, 2, 137, 19, 123, 58, 193, 31, 193, 215, 57, 0, 240, 162, 154, 197, 182, 102, 96, 91, 101, 29, 31, 63, 168, 145, 47, 189, 251, 138, 30, + 226, 174, 123, 162, 139, 147, 54, 101, 251, 231, 142, 230, 173, 232, 212, 153, 225, 58, 113, 24, 53, 97, 26, 89, 169, 83, 10, 48, 190, 92, 28, 22, 83, 66, 168, 232, 41, 135, 46, 21, 208, 110, 217, 210, 243, 1, 237, 247, 237, 183, 71, 1, + 193, 117, 86, 188, 217, 134, 204, 52, 147, 214, 106, 218, 145, 18, 3, 14, 152, 187, 194, 134, 97, 177, 146, 183, 102, 40, 90, 33, 217, 240, 113, 89, 142, 64, 120, 22, 21, 175, 245, 69, 184, 172, 211, 86, 215, 29, 176, 82, 209, 2, 198, + 205, 207, 75, 152, 201, 35, 86, 169, 205, 157, 142, 144, 96, 235, 228, 249, 204, 249, 212, 189, 80, 236, 218, 125, 206, 4, 54, 254, 165, 182, 236, 127, 232, 154, 117, 171, 105, 133, 186, 163, 206, 201, 159, 70, 10, 17, 138, 239, 21, 37, + 109, 139, 81, 59, 76, 57, 145, 209, 124, 73, 151, 15, 83, 117, 205, 87, 153, 166, 158, 188, 141, 157, 153, 230, 131, 244, 4, 118, 170, 60, 182, 39, 25, 110, 87, 174, 54, 186, 81, 129, 162, 212, 79, 70, 190, 180, 232, 143, 72, 102, 119, + 242, 172, 80, 2, 53, 168, 117, 184, 99, 184, 206, 230, 204, 5, 165, 141, 30, 37, 25, 159, 230, 130, 247, 252, 139, 86, 49, 132, 94, 254, 8, 198, 72, 16, 173, 202, 16, 222, 102, 65, 202, 40, 218, 190, 74, 34, 21, 163, 163, 179, 232, 137, + 88, 91, 104, 174, 112, 225, 188, 55, 104, 213, 116, 77, 143, 100, 55, 181, 95, 3, 149, 44, 132, 146, 146, 85, 62, 245, 56, 164, 243, 10, 248, 172, 153, 97, 85, 86, 177, 82, 92, 148, 248, 130, 133, 54, 160, 65, 92, 148, 142, 53, 148, 190, + 85, 136, 146, 82, 19, 186, 209, 204, 116, 25, 155, 69, 186, 207, 152, 45, 54, 94, 70, 191, 239, 106, 17, 161, 122, 157, 199, 83, 30, 23, 42, 103, 226, 74, 230, 111, 113, 173, 56, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, + 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 55, 153, 140, 203, 242, 208, 69, 139, 97, 92, 188, 198, 177, 163, 103, 196, 116, 158, 159, 239, 115, 6, 98, 46, 27, 27, 88, 145, 1, 32, 188, 154, 65, 87, 176, 88, 15, 49, 197, 252, 228, 74, 98, 88, 45, + 188, 249, 215, 142, 231, 89, 67, 160, 132, 163, 147, 179, 80, 54, 141, 34, 137, 147, 8, 75, 217, 73, 196, 54, 2, 195, 63, 32, 119, 144, 237, 22, 163, 82, 76, 161, 185, 151, 92, 241, 33, 162, 169, 12, 255, 236, 125, 248, 182, 138, 205, 95, + 183, 40, 175, 220, 220, 4, 120, 120, 238, 132, 58, 111, 235, 68, 175, 40, 205, 83, 163, 72, 40, 217, 144, 59, 90, 213, 230, 151, 25, 85, 42, 133, 15, 45, 110, 2, 164, 122, 248, 36, 208, 154, 182, 157, 196, 45, 112, 203, 40, 203, 250, 36, + 159, 183, 238, 87, 185, 210, 86, 193, 39, 98, 239, 140, 151, 37, 143, 78, 36, 137, 241, 187, 61, 16, 41, 20, 142, 13, 131, 11, 90, 19, 153, 218, 255, 16, 132, 4, 142, 123, 216, 219, 233, 248, 89, 6, 155, 136, 87, 254, 171, 129, 132, 251, + 104, 127, 99, 70, 24, 192, 53, 218, 196, 57, 220, 26, 235, 59, 85, 152, 160, 240, 0, 0, 0, 0, 1, 6, 167, 213, 23, 25, 44, 92, 81, 33, 140, 201, 76, 61, 74, 241, 127, 88, 218, 238, 8, 155, 161, 253, 68, 227, 219, 217, 138, 0, 0, 0, 0, 6, + 221, 246, 225, 215, 101, 161, 147, 217, 203, 225, 70, 206, 235, 121, 172, 28, 180, 133, 237, 95, 91, 55, 145, 58, 140, 245, 133, 126, 255, 0, 169, 238, 204, 15, 249, 117, 156, 236, 123, 109, 247, 173, 57, 139, 143, 19, 21, 15, 180, 230, + 189, 171, 230, 228, 215, 211, 229, 22, 94, 108, 135, 17, 93, 5, 14, 2, 0, 1, 52, 0, 0, 0, 0, 48, 96, 46, 0, 0, 0, 0, 0, 165, 0, 0, 0, 0, 0, 0, 0, 6, 221, 246, 225, 215, 101, 161, 147, 217, 203, 225, 70, 206, 235, 121, 172, 28, 180, 133, + 237, 95, 91, 55, 145, 58, 140, 245, 133, 126, 255, 0, 169, 23, 4, 1, 21, 0, 22, 1, 1, 20, 7, 0, 9, 0, 15, 14, 23, 22, 0, 17, 18, 23, 10, 16, 7, 6, 13, 2, 19, 12, 11, 3, 4, 8, 5, 18, 1, 9, 0, 17, 9, 64, 66, 15, 0, 0, 0, 0, 0, 89, 241, 0, + 0, 0, 0, 0, 0, 23, 3, 1, 0, 0, 1, 9, ]); export const dexlabProgram = Buffer.from([ - 3, 0, 7, 11, 47, 41, 101, 134, 4, 204, 35, 219, 9, 43, 55, 20, 255, 76, 29, - 27, 175, 197, 102, 175, 108, 72, 231, 146, 134, 30, 219, 42, 168, 236, 17, 3, - 27, 22, 186, 110, 40, 115, 180, 32, 87, 1, 133, 235, 70, 100, 93, 110, 49, - 176, 47, 100, 89, 135, 44, 204, 229, 104, 63, 16, 169, 85, 62, 17, 87, 228, - 120, 25, 237, 212, 48, 216, 155, 158, 74, 24, 15, 13, 148, 130, 112, 67, 62, - 67, 34, 39, 96, 92, 200, 155, 110, 50, 187, 157, 163, 92, 87, 174, 54, 186, - 81, 129, 162, 212, 79, 70, 190, 180, 232, 143, 72, 102, 119, 242, 172, 80, 2, - 53, 168, 117, 184, 99, 184, 206, 230, 204, 5, 165, 0, 0, 0, 0, 0, 0, 0, 0, 0, - 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 55, 153, - 140, 203, 242, 208, 69, 139, 97, 92, 188, 198, 177, 163, 103, 196, 116, 158, - 159, 239, 115, 6, 98, 46, 27, 27, 88, 145, 1, 32, 188, 154, 140, 151, 37, 143, - 78, 36, 137, 241, 187, 61, 16, 41, 20, 142, 13, 131, 11, 90, 19, 153, 218, - 255, 16, 132, 4, 142, 123, 216, 219, 233, 248, 89, 198, 250, 122, 243, 190, - 219, 173, 58, 61, 101, 243, 106, 171, 201, 116, 49, 177, 187, 228, 194, 210, - 246, 224, 228, 124, 166, 2, 3, 69, 47, 93, 97, 6, 155, 136, 87, 254, 171, 129, - 132, 251, 104, 127, 99, 70, 24, 192, 53, 218, 196, 57, 220, 26, 235, 59, 85, - 152, 160, 240, 0, 0, 0, 0, 1, 6, 167, 213, 23, 25, 44, 92, 81, 33, 140, 201, - 76, 61, 74, 241, 127, 88, 218, 238, 8, 155, 161, 253, 68, 227, 219, 217, 138, - 0, 0, 0, 0, 6, 221, 246, 225, 215, 101, 161, 147, 217, 203, 225, 70, 206, 235, - 121, 172, 28, 180, 133, 237, 95, 91, 55, 145, 58, 140, 245, 133, 126, 255, 0, - 169, 2, 206, 198, 46, 159, 44, 171, 207, 167, 152, 197, 80, 224, 171, 133, - 195, 162, 248, 176, 1, 213, 55, 144, 195, 89, 153, 30, 36, 158, 74, 236, 222, - 5, 4, 2, 0, 1, 52, 0, 0, 0, 0, 48, 96, 46, 0, 0, 0, 0, 0, 165, 0, 0, 0, 0, 0, - 0, 0, 6, 221, 246, 225, 215, 101, 161, 147, 217, 203, 225, 70, 206, 235, 121, - 172, 28, 180, 133, 237, 95, 91, 55, 145, 58, 140, 245, 133, 126, 255, 0, 169, - 10, 4, 1, 8, 0, 9, 1, 1, 6, 7, 0, 3, 0, 5, 4, 10, 9, 0, 4, 2, 0, 2, 52, 0, 0, - 0, 0, 240, 29, 31, 0, 0, 0, 0, 0, 165, 0, 0, 0, 0, 0, 0, 0, 6, 221, 246, 225, - 215, 101, 161, 147, 217, 203, 225, 70, 206, 235, 121, 172, 28, 180, 133, 237, - 95, 91, 55, 145, 58, 140, 245, 133, 126, 255, 0, 169, 10, 4, 2, 7, 0, 9, 1, 1, + 3, 0, 7, 11, 47, 41, 101, 134, 4, 204, 35, 219, 9, 43, 55, 20, 255, 76, 29, 27, 175, 197, 102, 175, 108, 72, 231, 146, 134, 30, 219, 42, 168, 236, 17, 3, 27, 22, 186, 110, 40, 115, 180, 32, 87, 1, 133, 235, 70, 100, 93, 110, 49, 176, 47, + 100, 89, 135, 44, 204, 229, 104, 63, 16, 169, 85, 62, 17, 87, 228, 120, 25, 237, 212, 48, 216, 155, 158, 74, 24, 15, 13, 148, 130, 112, 67, 62, 67, 34, 39, 96, 92, 200, 155, 110, 50, 187, 157, 163, 92, 87, 174, 54, 186, 81, 129, 162, 212, + 79, 70, 190, 180, 232, 143, 72, 102, 119, 242, 172, 80, 2, 53, 168, 117, 184, 99, 184, 206, 230, 204, 5, 165, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 55, 153, 140, 203, 242, 208, 69, + 139, 97, 92, 188, 198, 177, 163, 103, 196, 116, 158, 159, 239, 115, 6, 98, 46, 27, 27, 88, 145, 1, 32, 188, 154, 140, 151, 37, 143, 78, 36, 137, 241, 187, 61, 16, 41, 20, 142, 13, 131, 11, 90, 19, 153, 218, 255, 16, 132, 4, 142, 123, 216, + 219, 233, 248, 89, 198, 250, 122, 243, 190, 219, 173, 58, 61, 101, 243, 106, 171, 201, 116, 49, 177, 187, 228, 194, 210, 246, 224, 228, 124, 166, 2, 3, 69, 47, 93, 97, 6, 155, 136, 87, 254, 171, 129, 132, 251, 104, 127, 99, 70, 24, 192, + 53, 218, 196, 57, 220, 26, 235, 59, 85, 152, 160, 240, 0, 0, 0, 0, 1, 6, 167, 213, 23, 25, 44, 92, 81, 33, 140, 201, 76, 61, 74, 241, 127, 88, 218, 238, 8, 155, 161, 253, 68, 227, 219, 217, 138, 0, 0, 0, 0, 6, 221, 246, 225, 215, 101, + 161, 147, 217, 203, 225, 70, 206, 235, 121, 172, 28, 180, 133, 237, 95, 91, 55, 145, 58, 140, 245, 133, 126, 255, 0, 169, 2, 206, 198, 46, 159, 44, 171, 207, 167, 152, 197, 80, 224, 171, 133, 195, 162, 248, 176, 1, 213, 55, 144, 195, 89, + 153, 30, 36, 158, 74, 236, 222, 5, 4, 2, 0, 1, 52, 0, 0, 0, 0, 48, 96, 46, 0, 0, 0, 0, 0, 165, 0, 0, 0, 0, 0, 0, 0, 6, 221, 246, 225, 215, 101, 161, 147, 217, 203, 225, 70, 206, 235, 121, 172, 28, 180, 133, 237, 95, 91, 55, 145, 58, 140, + 245, 133, 126, 255, 0, 169, 10, 4, 1, 8, 0, 9, 1, 1, 6, 7, 0, 3, 0, 5, 4, 10, 9, 0, 4, 2, 0, 2, 52, 0, 0, 0, 0, 240, 29, 31, 0, 0, 0, 0, 0, 165, 0, 0, 0, 0, 0, 0, 0, 6, 221, 246, 225, 215, 101, 161, 147, 217, 203, 225, 70, 206, 235, 121, + 172, 28, 180, 133, 237, 95, 91, 55, 145, 58, 140, 245, 133, 126, 255, 0, 169, 10, 4, 2, 7, 0, 9, 1, 1, ]); diff --git a/packages/sdk/src/__test__/e2e/signing/solana/solana.test.ts b/packages/sdk/src/__test__/e2e/signing/solana/solana.test.ts index 94a0e596..550572f7 100644 --- a/packages/sdk/src/__test__/e2e/signing/solana/solana.test.ts +++ b/packages/sdk/src/__test__/e2e/signing/solana/solana.test.ts @@ -6,12 +6,7 @@ * Running with a different mnemonic will cause test failures due to * incorrect key derivations and signature mismatches. */ -import { - Keypair as SolanaKeypair, - PublicKey as SolanaPublicKey, - SystemProgram as SolanaSystemProgram, - Transaction as SolanaTransaction, -} from '@solana/web3.js'; +import { Keypair as SolanaKeypair, PublicKey as SolanaPublicKey, SystemProgram as SolanaSystemProgram, Transaction as SolanaTransaction } from '@solana/web3.js'; import { Constants } from '../../../..'; import { HARDENED_OFFSET } from '../../../../constants'; import { ensureHexBuffer } from '../../../../util'; @@ -25,12 +20,7 @@ import type { Client } from '../../../../client'; //--------------------------------------- // STATE DATA //--------------------------------------- -const DEFAULT_SOLANA_SIGNER_PATH = [ - HARDENED_OFFSET + 44, - HARDENED_OFFSET + 501, - HARDENED_OFFSET, - HARDENED_OFFSET, -]; +const DEFAULT_SOLANA_SIGNER_PATH = [HARDENED_OFFSET + 44, HARDENED_OFFSET + 501, HARDENED_OFFSET, HARDENED_OFFSET]; const prng = getPrng(); describe('[Solana]', () => { @@ -85,26 +75,16 @@ describe('[Solana]', () => { // Generate a pseudorandom blockhash, which is just a public key appearently. const randBuf = prandomBuf(prng, 32, true); - const recentBlockhash = - SolanaKeypair.fromSeed(randBuf).publicKey.toBase58(); + const recentBlockhash = SolanaKeypair.fromSeed(randBuf).publicKey.toBase58(); // Build a transaction and sign it using Solana's JS lib - const txJs = new SolanaTransaction({ recentBlockhash }).add( - transfer1, - transfer2, - ); + const txJs = new SolanaTransaction({ recentBlockhash }).add(transfer1, transfer2); txJs.setSigners(pubA, pubB); - txJs.sign( - SolanaKeypair.fromSeed(derivedA.priv), - SolanaKeypair.fromSeed(derivedB.priv), - ); + txJs.sign(SolanaKeypair.fromSeed(derivedA.priv), SolanaKeypair.fromSeed(derivedB.priv)); const serTxJs = txJs.serialize().toString('hex'); // Build a copy of the transaction and get the serialized payload for signing in firmware. - const txFw = new SolanaTransaction({ recentBlockhash }).add( - transfer1, - transfer2, - ); + const txFw = new SolanaTransaction({ recentBlockhash }).add(transfer1, transfer2); txFw.setSigners(pubA, pubB); // We want to sign the Solana message, not the full transaction const payload = txFw.compileMessage().serialize(); @@ -121,10 +101,7 @@ describe('[Solana]', () => { if (!resp.sig?.r || !resp.sig?.s) { throw new Error('Missing signature components in response'); } - return Buffer.concat([ - ensureHexBuffer(resp.sig.r as string | Buffer), - ensureHexBuffer(resp.sig.s as string | Buffer), - ]); + return Buffer.concat([ensureHexBuffer(resp.sig.r as string | Buffer), ensureHexBuffer(resp.sig.s as string | Buffer)]); }); const sigB = await runGeneric( @@ -137,10 +114,7 @@ describe('[Solana]', () => { if (!resp.sig?.r || !resp.sig?.s) { throw new Error('Missing signature components in response'); } - return Buffer.concat([ - ensureHexBuffer(resp.sig.r as string | Buffer), - ensureHexBuffer(resp.sig.s as string | Buffer), - ]); + return Buffer.concat([ensureHexBuffer(resp.sig.r as string | Buffer), ensureHexBuffer(resp.sig.s as string | Buffer)]); }); txFw.addSignature(pubA, sigA); txFw.addSignature(pubB, sigB); diff --git a/packages/sdk/src/__test__/e2e/signing/solana/solana.versioned.test.ts b/packages/sdk/src/__test__/e2e/signing/solana/solana.versioned.test.ts index 249d05c5..863bb160 100644 --- a/packages/sdk/src/__test__/e2e/signing/solana/solana.versioned.test.ts +++ b/packages/sdk/src/__test__/e2e/signing/solana/solana.versioned.test.ts @@ -6,18 +6,7 @@ * Running with a different mnemonic will cause test failures due to * incorrect key derivations and signature mismatches. */ -import { - AddressLookupTableProgram, - Connection, - Keypair, - LAMPORTS_PER_SOL, - PublicKey, - SystemProgram, - Transaction, - type TransactionInstruction, - TransactionMessage, - VersionedTransaction, -} from '@solana/web3.js'; +import { AddressLookupTableProgram, Connection, Keypair, LAMPORTS_PER_SOL, PublicKey, SystemProgram, Transaction, type TransactionInstruction, TransactionMessage, VersionedTransaction } from '@solana/web3.js'; import { fetchSolanaAddresses, signSolanaTx } from '../../../..'; import { setupClient } from '../../../utils/setup'; @@ -136,13 +125,9 @@ describe('solana.versioned', () => { }); // Create a VersionedTransaction from the serialized data - const versionedTransaction = VersionedTransaction.deserialize( - serializedTransaction, - ); + const versionedTransaction = VersionedTransaction.deserialize(serializedTransaction); - const signedTx = await signSolanaTx( - Buffer.from(versionedTransaction.serialize()), - ); + const signedTx = await signSolanaTx(Buffer.from(versionedTransaction.serialize())); expect(signedTx).toBeTruthy(); }); @@ -150,21 +135,17 @@ describe('solana.versioned', () => { const payer = Keypair.generate(); await requestAirdrop(payer.publicKey, 1); - const [transactionInstruction, pubkey] = - await AddressLookupTableProgram.createLookupTable({ - payer: payer.publicKey, - authority: payer.publicKey, - recentSlot: await SOLANA_RPC.getSlot(), - }); + const [transactionInstruction, pubkey] = await AddressLookupTableProgram.createLookupTable({ + payer: payer.publicKey, + authority: payer.publicKey, + recentSlot: await SOLANA_RPC.getSlot(), + }); await AddressLookupTableProgram.extendLookupTable({ payer: payer.publicKey, authority: payer.publicKey, lookupTable: pubkey, - addresses: [ - DESTINATION_WALLET_1.publicKey, - DESTINATION_WALLET_2.publicKey, - ], + addresses: [DESTINATION_WALLET_1.publicKey, DESTINATION_WALLET_2.publicKey], }); const messageV0 = new TransactionMessage({ diff --git a/packages/sdk/src/__test__/e2e/signing/unformatted.test.ts b/packages/sdk/src/__test__/e2e/signing/unformatted.test.ts index 5ae2bcef..64e7456a 100644 --- a/packages/sdk/src/__test__/e2e/signing/unformatted.test.ts +++ b/packages/sdk/src/__test__/e2e/signing/unformatted.test.ts @@ -9,13 +9,7 @@ import type { Client } from '../../../client'; const prng = getPrng(); const numIter = getNumIter(); -const DEFAULT_SIGNER = [ - HARDENED_OFFSET + 44, - HARDENED_OFFSET + 60, - HARDENED_OFFSET, - 0, - 0, -]; +const DEFAULT_SIGNER = [HARDENED_OFFSET + 44, HARDENED_OFFSET + 60, HARDENED_OFFSET, 0, 0]; describe('[Unformatted]', () => { let client: Client; @@ -36,8 +30,7 @@ describe('[Unformatted]', () => { it('Should test pre-hashed messages', async () => { const fwConstants = client.getFwConstants(); - const { extraDataFrameSz, extraDataMaxFrames, genericSigning } = - fwConstants; + const { extraDataFrameSz, extraDataMaxFrames, genericSigning } = fwConstants; const { baseDataSz } = genericSigning; // Max size that won't be prehashed const maxSz = baseDataSz + extraDataMaxFrames * extraDataFrameSz; @@ -130,9 +123,7 @@ describe('[Unformatted]', () => { hashType: Constants.SIGNING.HASHES.KECCAK256, }, }; - const respLegacy = await client.sign( - legacyReq as Parameters[0], - ); + const respLegacy = await client.sign(legacyReq as Parameters[0]); const genSigR = (respGeneric.sig?.r as Buffer)?.toString('hex') ?? ''; const genSigS = (respGeneric.sig?.s as Buffer)?.toString('hex') ?? ''; @@ -141,10 +132,7 @@ describe('[Unformatted]', () => { const genSig = `${genSigR}${genSigS}`; const legSig = `${legSigR}${legSigS}`; - expect(genSig).toEqualElseLog( - legSig, - 'Legacy and generic requests produced different sigs.', - ); + expect(genSig).toEqualElseLog(legSig, 'Legacy and generic requests produced different sigs.'); }); for (let i = 0; i < numIter; i++) { diff --git a/packages/sdk/src/__test__/e2e/signing/vectors.ts b/packages/sdk/src/__test__/e2e/signing/vectors.ts index 752df529..27032852 100644 --- a/packages/sdk/src/__test__/e2e/signing/vectors.ts +++ b/packages/sdk/src/__test__/e2e/signing/vectors.ts @@ -229,11 +229,8 @@ export const EIP2930_TEST_VECTORS: TestVector[] = [ chainId: 1, accessList: [ { - address: - '0xe242e54155b1abc71fc118065270cecaaf8b7768' as `0x${string}`, - storageKeys: [ - '0x0000000000000000000000000000000000000000000000000000000000000001' as `0x${string}`, - ], + address: '0xe242e54155b1abc71fc118065270cecaaf8b7768' as `0x${string}`, + storageKeys: ['0x0000000000000000000000000000000000000000000000000000000000000001' as `0x${string}`], }, ], }, @@ -252,19 +249,12 @@ export const EIP2930_TEST_VECTORS: TestVector[] = [ chainId: 1, accessList: [ { - address: - '0x6B175474E89094C44Da98b954EedeAC495271d0F' as `0x${string}`, // DAI contract - storageKeys: [ - '0x0000000000000000000000000000000000000000000000000000000000000001' as `0x${string}`, - '0x0000000000000000000000000000000000000000000000000000000000000002' as `0x${string}`, - ], + address: '0x6B175474E89094C44Da98b954EedeAC495271d0F' as `0x${string}`, // DAI contract + storageKeys: ['0x0000000000000000000000000000000000000000000000000000000000000001' as `0x${string}`, '0x0000000000000000000000000000000000000000000000000000000000000002' as `0x${string}`], }, { - address: - '0xe242e54155b1abc71fc118065270cecaaf8b7768' as `0x${string}`, // Recipient - storageKeys: [ - '0x0000000000000000000000000000000000000000000000000000000000000003' as `0x${string}`, - ], + address: '0xe242e54155b1abc71fc118065270cecaaf8b7768' as `0x${string}`, // Recipient + storageKeys: ['0x0000000000000000000000000000000000000000000000000000000000000003' as `0x${string}`], }, ], }, @@ -297,27 +287,16 @@ export const EIP2930_TEST_VECTORS: TestVector[] = [ chainId: 1, accessList: [ { - address: - '0x7a250d5630B4cF539739dF2C5dAcb4c659F2488D' as `0x${string}`, // Router - storageKeys: [ - '0x0000000000000000000000000000000000000000000000000000000000000001' as `0x${string}`, - '0x0000000000000000000000000000000000000000000000000000000000000002' as `0x${string}`, - ], + address: '0x7a250d5630B4cF539739dF2C5dAcb4c659F2488D' as `0x${string}`, // Router + storageKeys: ['0x0000000000000000000000000000000000000000000000000000000000000001' as `0x${string}`, '0x0000000000000000000000000000000000000000000000000000000000000002' as `0x${string}`], }, { - address: - '0x6B175474E89094C44Da98b954EedeAC495271d0F' as `0x${string}`, // DAI - storageKeys: [ - '0x0000000000000000000000000000000000000000000000000000000000000003' as `0x${string}`, - '0x0000000000000000000000000000000000000000000000000000000000000004' as `0x${string}`, - ], + address: '0x6B175474E89094C44Da98b954EedeAC495271d0F' as `0x${string}`, // DAI + storageKeys: ['0x0000000000000000000000000000000000000000000000000000000000000003' as `0x${string}`, '0x0000000000000000000000000000000000000000000000000000000000000004' as `0x${string}`], }, { - address: - '0xdAC17F958D2ee523a2206206994597C13D831ec7' as `0x${string}`, // USDT - storageKeys: [ - '0x0000000000000000000000000000000000000000000000000000000000000005' as `0x${string}`, - ], + address: '0xdAC17F958D2ee523a2206206994597C13D831ec7' as `0x${string}`, // USDT + storageKeys: ['0x0000000000000000000000000000000000000000000000000000000000000005' as `0x${string}`], }, ], }, @@ -346,8 +325,7 @@ export const EIP7702_TEST_VECTORS: TestVector[] = [ authorizationList: [ { chainId: 1, - address: - '0x000000004f43c49e93c970e84001853a70923b03' as `0x${string}`, + address: '0x000000004f43c49e93c970e84001853a70923b03' as `0x${string}`, nonce: 1, yParity: 1, r: '0xc9f7e0af53f516744bc34827bef7236df3123c3a07a601dca75d7698416adc4a' as `0x${string}`, @@ -373,8 +351,7 @@ export const EIP7702_TEST_VECTORS: TestVector[] = [ authorizationList: [ { chainId: 1, - address: - '0x000000004f43c49e93c970e84001853a70923b03' as `0x${string}`, + address: '0x000000004f43c49e93c970e84001853a70923b03' as `0x${string}`, nonce: 1, yParity: 0, r: '0x948c69c40057e9fd4c9bb55506ef764bf80d1bbaf980fe8c09d9d9c0b67d0e49' as `0x${string}`, @@ -400,8 +377,7 @@ export const EIP7702_TEST_VECTORS: TestVector[] = [ authorizationList: [ { chainId: 1, - address: - '0x163193c89de836e82bb121bd0dbcaba7e8ba67dc' as `0x${string}`, + address: '0x163193c89de836e82bb121bd0dbcaba7e8ba67dc' as `0x${string}`, nonce: 4999, yParity: 1, r: '0x806cbbf8a3cfb25b660e03147984ff95725252b6c95aceed91d5c0bfca6ad0d1' as `0x${string}`, @@ -614,9 +590,7 @@ export const EDGE_CASE_TEST_VECTORS: TestVector[] = [ tx: { type: 'eip1559', to: '0xe242e54155b1abc71fc118065270cecaaf8b7768' as `0x${string}`, - value: BigInt( - '0xffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff', - ), // Max uint256 + value: BigInt('0xffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff'), // Max uint256 data: '0x' as `0x${string}`, // Add missing data field nonce: 0, maxFeePerGas: BigInt('20000000000'), // 20 gwei @@ -669,15 +643,11 @@ export const EDGE_CASE_TEST_VECTORS: TestVector[] = [ chainId: 1, accessList: [ { - address: - '0xe242e54155b1abc71fc118065270cecaaf8b7768' as `0x${string}`, - storageKeys: [ - '0x7154f8b310ad6ce97ce3b15e3419d9863865dfe2d8635802f7f4a52a206255a6' as `0x${string}`, - ], + address: '0xe242e54155b1abc71fc118065270cecaaf8b7768' as `0x${string}`, + storageKeys: ['0x7154f8b310ad6ce97ce3b15e3419d9863865dfe2d8635802f7f4a52a206255a6' as `0x${string}`], }, { - address: - '0xe0f8ff08ef0242c461da688b8b85e438db724860' as `0x${string}`, + address: '0xe0f8ff08ef0242c461da688b8b85e438db724860' as `0x${string}`, storageKeys: [], // Empty storage keys }, ], @@ -698,15 +668,11 @@ export const EDGE_CASE_TEST_VECTORS: TestVector[] = [ chainId: 1, accessList: [ { - address: - '0xe242e54155b1abc71fc118065270cecaaf8b7768' as `0x${string}`, - storageKeys: [ - '0x7154f8b310ad6ce97ce3b15e3419d9863865dfe2d8635802f7f4a52a206255a6' as `0x${string}`, - ], + address: '0xe242e54155b1abc71fc118065270cecaaf8b7768' as `0x${string}`, + storageKeys: ['0x7154f8b310ad6ce97ce3b15e3419d9863865dfe2d8635802f7f4a52a206255a6' as `0x${string}`], }, { - address: - '0xe0f8ff08ef0242c461da688b8b85e438db724860' as `0x${string}`, + address: '0xe0f8ff08ef0242c461da688b8b85e438db724860' as `0x${string}`, storageKeys: [], // Empty storage keys }, ], @@ -1052,9 +1018,7 @@ export const ALL_COMPREHENSIVE_VECTORS: TestVector[] = [ * Get vectors by category for targeted testing */ export function getVectorsByCategory(category: string): TestVector[] { - return ALL_COMPREHENSIVE_VECTORS.filter( - (vector) => vector.category === category, - ); + return ALL_COMPREHENSIVE_VECTORS.filter((vector) => vector.category === category); } /** @@ -1066,23 +1030,14 @@ export function getBalancedTestVectors(perType = 3): TestVector[] { const eip2930Vectors = EIP2930_TEST_VECTORS.slice(0, perType); const eip7702Vectors = EIP7702_TEST_VECTORS.slice(0, perType); - return [ - ...legacyVectors, - ...eip1559Vectors, - ...eip2930Vectors, - ...eip7702Vectors, - ]; + return [...legacyVectors, ...eip1559Vectors, ...eip2930Vectors, ...eip7702Vectors]; } /** * Get vectors for boundary testing specifically */ export function getBoundaryTestVectors(): TestVector[] { - return [ - ...BOUNDARY_CONDITION_VECTORS, - ...EDGE_CASE_TEST_VECTORS, - ...PAYLOAD_SIZE_VECTORS, - ]; + return [...BOUNDARY_CONDITION_VECTORS, ...EDGE_CASE_TEST_VECTORS, ...PAYLOAD_SIZE_VECTORS]; } /** diff --git a/packages/sdk/src/__test__/integration/__mocks__/4byte.ts b/packages/sdk/src/__test__/integration/__mocks__/4byte.ts index 4bdb39f8..dea18f81 100644 --- a/packages/sdk/src/__test__/integration/__mocks__/4byte.ts +++ b/packages/sdk/src/__test__/integration/__mocks__/4byte.ts @@ -38,8 +38,7 @@ export const fourbyteResponse0x38ed1739 = { created_at: '2020-08-09T08:56:14.110995Z', hex_signature: '0x38ed1739', id: 171806, - text_signature: - 'swapExactTokensForTokens(uint256,uint256,address[],address,uint256)', + text_signature: 'swapExactTokensForTokens(uint256,uint256,address[],address,uint256)', }, ], }; @@ -61,8 +60,7 @@ export const fourbyteResponse0c49ccbe = { { id: 186682, created_at: '2021-05-09T03:48:17.627742Z', - text_signature: - 'decreaseLiquidity((uint256,uint128,uint256,uint256,uint256))', + text_signature: 'decreaseLiquidity((uint256,uint128,uint256,uint256,uint256))', hex_signature: '0x0c49ccbe', bytes_signature: '\\fI̾', }, @@ -86,8 +84,7 @@ export const fourbyteResponse0x6a761202 = { { id: 169422, created_at: '2020-01-28T10:40:07.614936Z', - text_signature: - 'execTransaction(address,uint256,bytes,uint8,uint256,uint256,uint256,address,address,bytes)', + text_signature: 'execTransaction(address,uint256,bytes,uint8,uint256,uint256,uint256,address,address,bytes)', hex_signature: '0x6a761202', bytes_signature: 'jv\\u0012\\u0002', }, diff --git a/packages/sdk/src/__test__/integration/__mocks__/etherscan.ts b/packages/sdk/src/__test__/integration/__mocks__/etherscan.ts index 4cdd2629..61f8f422 100644 --- a/packages/sdk/src/__test__/integration/__mocks__/etherscan.ts +++ b/packages/sdk/src/__test__/integration/__mocks__/etherscan.ts @@ -1447,8 +1447,7 @@ export const etherscanResponse0xc36442b6 = [ type: 'uint256', }, ], - internalType: - 'struct INonfungiblePositionManager.DecreaseLiquidityParams', + internalType: 'struct INonfungiblePositionManager.DecreaseLiquidityParams', name: 'params', type: 'tuple', }, @@ -1536,8 +1535,7 @@ export const etherscanResponse0xc36442b6 = [ type: 'uint256', }, ], - internalType: - 'struct INonfungiblePositionManager.IncreaseLiquidityParams', + internalType: 'struct INonfungiblePositionManager.IncreaseLiquidityParams', name: 'params', type: 'tuple', }, @@ -2528,8 +2526,7 @@ export const etherscanResponse0x06412d7e = [ { internalType: 'uint256', name: 'amount1Min', type: 'uint256' }, { internalType: 'uint256', name: 'deadline', type: 'uint256' }, ], - internalType: - 'struct INonfungiblePositionManager.DecreaseLiquidityParams', + internalType: 'struct INonfungiblePositionManager.DecreaseLiquidityParams', name: 'params', type: 'tuple', }, @@ -2567,8 +2564,7 @@ export const etherscanResponse0x06412d7e = [ { internalType: 'uint256', name: 'amount1Min', type: 'uint256' }, { internalType: 'uint256', name: 'deadline', type: 'uint256' }, ], - internalType: - 'struct INonfungiblePositionManager.IncreaseLiquidityParams', + internalType: 'struct INonfungiblePositionManager.IncreaseLiquidityParams', name: 'params', type: 'tuple', }, diff --git a/packages/sdk/src/__test__/integration/__mocks__/handlers.ts b/packages/sdk/src/__test__/integration/__mocks__/handlers.ts index 80e26132..4b6a025f 100644 --- a/packages/sdk/src/__test__/integration/__mocks__/handlers.ts +++ b/packages/sdk/src/__test__/integration/__mocks__/handlers.ts @@ -1,20 +1,8 @@ import { http, HttpResponse } from 'msw'; -import { - fourbyteResponse0c49ccbe, - fourbyteResponse0x6a761202, - fourbyteResponse0x38ed1739, - fourbyteResponse0xa9059cbb, - fourbyteResponseac9650d8, - fourbyteResponsefc6f7865, -} from './4byte'; +import { fourbyteResponse0c49ccbe, fourbyteResponse0x6a761202, fourbyteResponse0x38ed1739, fourbyteResponse0xa9059cbb, fourbyteResponseac9650d8, fourbyteResponsefc6f7865 } from './4byte'; import addKvRecordsResponse from './addKvRecords.json'; import connectResponse from './connect.json'; -import { - etherscanResponse0x06412d7e, - etherscanResponse0x7a250d56, - etherscanResponse0xa0b86991, - etherscanResponse0xc36442b6, -} from './etherscan'; +import { etherscanResponse0x06412d7e, etherscanResponse0x7a250d56, etherscanResponse0xa0b86991, etherscanResponse0xc36442b6 } from './etherscan'; import fetchActiveWalletResponse from './fetchActiveWallet.json'; import getAddressesResponse from './getAddresses.json'; import getKvRecordsResponse from './getKvRecords.json'; diff --git a/packages/sdk/src/__test__/integration/fetchCalldataDecoder.test.ts b/packages/sdk/src/__test__/integration/fetchCalldataDecoder.test.ts index e5abe6f4..c466c20b 100644 --- a/packages/sdk/src/__test__/integration/fetchCalldataDecoder.test.ts +++ b/packages/sdk/src/__test__/integration/fetchCalldataDecoder.test.ts @@ -33,8 +33,7 @@ describe('fetchCalldataDecoder', () => { }); test('decode proxy calldata', async () => { - const data = - '0xa9059cbb0000000000000000000000004ffbf741b0a64e8bd1f9d89fc9b5584cc5227b700000000000000000000000000000000000000000000000000000003052aacdb8'; + const data = '0xa9059cbb0000000000000000000000004ffbf741b0a64e8bd1f9d89fc9b5584cc5227b700000000000000000000000000000000000000000000000000000003052aacdb8'; const to = '0xa0b86991c6218b36c1d19d4a2e9eb0ce3606eb48'; const decoded = await fetchCalldataDecoder(data, to, '1'); expect(decoded).toMatchSnapshot(); @@ -98,8 +97,7 @@ describe('fetchCalldataDecoder', () => { // TODO: add api key to fix this test test.skip('decode Celo calldata', async () => { - const data = - '0xf2fde38b000000000000000000000000b538e8dcd297450bdef46222f3ceb33bb1e921b3'; + const data = '0xf2fde38b000000000000000000000000b538e8dcd297450bdef46222f3ceb33bb1e921b3'; const to = '0x96d59127ccd1c0e3749e733ee04f0dfbd2f808c8'; const decoded = await fetchCalldataDecoder(data, to, '42220'); expect(decoded).toMatchSnapshot(); diff --git a/packages/sdk/src/__test__/unit/__mocks__/decoderData.ts b/packages/sdk/src/__test__/unit/__mocks__/decoderData.ts index faec69c6..26c85fc0 100644 --- a/packages/sdk/src/__test__/unit/__mocks__/decoderData.ts +++ b/packages/sdk/src/__test__/unit/__mocks__/decoderData.ts @@ -3,18 +3,10 @@ * For encrypted requests, the results are decrypted. * For each response, the response code and checksum are removed. */ -import { - LatticeEncDataSchema, - LatticeGetAddressesFlag, -} from '../../../protocol'; +import { LatticeEncDataSchema, LatticeGetAddressesFlag } from '../../../protocol'; import { getP256KeyPair } from '../../../util'; -export const clientKeyPair = getP256KeyPair( - Buffer.from( - '3fb53b677f73e4d2b8c89c303f6f6b349f0075ad88ea126cb9f6632085815dca', - 'hex', - ), -); +export const clientKeyPair = getP256KeyPair(Buffer.from('3fb53b677f73e4d2b8c89c303f6f6b349f0075ad88ea126cb9f6632085815dca', 'hex')); export const decoderTestsFwConstants = JSON.parse( '{"extraDataFrameSz":1500,"extraDataMaxFrames":1,"genericSigning":{"baseReqSz":1552,"baseDataSz":1519,"hashTypes":{"NONE":0,"KECCAK256":1,"SHA256":2},"curveTypes":{"SECP256K1":0,"ED25519":1,"BLS12_381_G2":2},"encodingTypes":{"NONE":1,"SOLANA":2,"EVM":4,"ETH_DEPOSIT":5},"calldataDecoding":{"reserved":2895728,"maxSz":1024}},"reqMaxDataSz":1678,"ethMaxGasPrice":20000000000000,"addrFlagsAllowed":true,"ethMaxDataSz":1519,"ethMaxMsgSz":1540,"eip712MaxTypeParams":36,"varAddrPathSzAllowed":true,"eip712Supported":true,"prehashAllowed":true,"ethMsgPreHashAllowed":true,"allowedEthTxTypes":[1,2],"personalSignHeaderSz":72,"kvActionsAllowed":true,"kvKeyMaxStrSz":63,"kvValMaxStrSz":63,"kvActionMaxNum":10,"kvRemoveMaxNum":100,"allowBtcLegacyAndSegwitAddrs":true,"contractDeployKey":"0x08002e0fec8e6acf00835f43c9764f7364fa3f42","abiCategorySz":32,"abiMaxRmv":200,"getAddressFlags":[4,3,5],"maxDecoderBufSz":1600}', @@ -42,10 +34,7 @@ export const signGenericRequest = { encodingType: 4, hashType: 1, omitPubkey: false, - origPayloadBuf: Buffer.from( - '01f84b01808447868c0082c35094e242e54155b1abc71fc118065270cecaaf8b776885e8d4a51000a417e914679b7e160613be4f8c2d3203d236286d74eb9192f6d6f71b9118a42bb033ccd8e8c0', - 'hex', - ), + origPayloadBuf: Buffer.from('01f84b01808447868c0082c35094e242e54155b1abc71fc118065270cecaaf8b776885e8d4a51000a417e914679b7e160613be4f8c2d3203d236286d74eb9192f6d6f71b9118a42bb033ccd8e8c0', 'hex'), }; export const signGenericDecoderData = Buffer.from( '04a50d7d8e5bf6353086dfaff71652a223aa13e02273a2b6bf5a145314b544be1281ac8f78d035874a06b11e3df68e45f7630b2e6ba3be0f51f916fbb6f0a6403930440220640b2c690858ab8d0b9500f9ed64c9aa6b7467b77f1199b061aa96ea780aadaa022048f830f9290dd1b3eaf1922e08a8c992873be1162bd6d5bef681cf911328abe500000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000', @@ -58,10 +47,7 @@ export const signBitcoinRequest = { origData: { prevOuts: [ { - txHash: Buffer.from( - 'b2efdbdd3340d2bc547671ce3993a6f05d70343c07578f9d7f5626fdfc06fa35', - 'hex', - ), + txHash: Buffer.from('b2efdbdd3340d2bc547671ce3993a6f05d70343c07578f9d7f5626fdfc06fa35', 'hex'), value: 76800, index: 0, signerPath: [2147483732, 2147483649, 2147483648, 0, 0], @@ -97,10 +83,7 @@ export const fetchEncryptedDataRequest = { params: { path: [12381, 3600, 0, 0], c: 999, - walletUID: Buffer.from( - '6ae62c0c96c1e039fc97bfeb7c2428c093fe7f0b6188a434bbac7b652c3e4012', - 'hex', - ), + walletUID: Buffer.from('6ae62c0c96c1e039fc97bfeb7c2428c093fe7f0b6188a434bbac7b652c3e4012', 'hex'), }, }; export const fetchEncryptedDataDecoderData = Buffer.from( diff --git a/packages/sdk/src/__test__/unit/api.test.ts b/packages/sdk/src/__test__/unit/api.test.ts index 99a08dce..ba8d3a02 100644 --- a/packages/sdk/src/__test__/unit/api.test.ts +++ b/packages/sdk/src/__test__/unit/api.test.ts @@ -52,8 +52,6 @@ describe('parseDerivationPath', () => { }); it('throws error for invalid input', () => { - expect(() => parseDerivationPath('invalid/path')).toThrow( - 'Invalid part in derivation path: invalid', - ); + expect(() => parseDerivationPath('invalid/path')).toThrow('Invalid part in derivation path: invalid'); }); }); diff --git a/packages/sdk/src/__test__/unit/compareEIP7702Serialization.test.ts b/packages/sdk/src/__test__/unit/compareEIP7702Serialization.test.ts index 4eb31b8e..1856fa33 100644 --- a/packages/sdk/src/__test__/unit/compareEIP7702Serialization.test.ts +++ b/packages/sdk/src/__test__/unit/compareEIP7702Serialization.test.ts @@ -1,10 +1,7 @@ import { Hash } from 'ox'; import { parseEther, serializeTransaction, toHex } from 'viem'; import { serializeEIP7702Transaction } from '../../ethereum'; -import type { - EIP7702AuthListTransactionRequest as EIP7702AuthListTransaction, - EIP7702AuthTransactionRequest as EIP7702AuthTransaction, -} from '../../types'; +import type { EIP7702AuthListTransactionRequest as EIP7702AuthListTransaction, EIP7702AuthTransactionRequest as EIP7702AuthTransaction } from '../../types'; describe('EIP7702 Transaction Serialization Comparison', () => { /** diff --git a/packages/sdk/src/__test__/unit/decoders.test.ts b/packages/sdk/src/__test__/unit/decoders.test.ts index 9ab692fc..6b2b5a44 100644 --- a/packages/sdk/src/__test__/unit/decoders.test.ts +++ b/packages/sdk/src/__test__/unit/decoders.test.ts @@ -1,10 +1,4 @@ -import { - decodeConnectResponse, - decodeFetchEncData, - decodeGetAddressesResponse, - decodeGetKvRecordsResponse, - decodeSignResponse, -} from '../../functions'; +import { decodeConnectResponse, decodeFetchEncData, decodeGetAddressesResponse, decodeGetKvRecordsResponse, decodeSignResponse } from '../../functions'; import type { DecodeSignResponseParams } from '../../types'; import { clientKeyPair, @@ -23,15 +17,11 @@ import { describe('decoders', () => { test('connect', () => { - expect( - decodeConnectResponse(connectDecoderData, clientKeyPair), - ).toMatchSnapshot(); + expect(decodeConnectResponse(connectDecoderData, clientKeyPair)).toMatchSnapshot(); }); test('getAddresses', () => { - expect( - decodeGetAddressesResponse(getAddressesDecoderData, getAddressesFlag), - ).toMatchSnapshot(); + expect(decodeGetAddressesResponse(getAddressesDecoderData, getAddressesFlag)).toMatchSnapshot(); }); test('sign - bitcoin', () => { @@ -54,12 +44,7 @@ describe('decoders', () => { }); test('getKvRecords', () => { - expect( - decodeGetKvRecordsResponse( - getKvRecordsDecoderData, - decoderTestsFwConstants, - ), - ).toMatchSnapshot(); + expect(decodeGetKvRecordsResponse(getKvRecordsDecoderData, decoderTestsFwConstants)).toMatchSnapshot(); }); test('fetchEncryptedData', () => { diff --git a/packages/sdk/src/__test__/unit/eip7702.test.ts b/packages/sdk/src/__test__/unit/eip7702.test.ts index 080f111f..7d596ef7 100644 --- a/packages/sdk/src/__test__/unit/eip7702.test.ts +++ b/packages/sdk/src/__test__/unit/eip7702.test.ts @@ -1,10 +1,7 @@ import { Hash } from 'ox'; import { parseEther, toHex } from 'viem'; import { serializeEIP7702Transaction } from '../../ethereum'; -import type { - EIP7702AuthListTransactionRequest, - EIP7702AuthTransactionRequest, -} from '../../types'; +import type { EIP7702AuthListTransactionRequest, EIP7702AuthTransactionRequest } from '../../types'; describe('EIP-7702 Transaction Serialization', () => { /** diff --git a/packages/sdk/src/__test__/unit/encoders.test.ts b/packages/sdk/src/__test__/unit/encoders.test.ts index 81ef41b8..1dbab8b0 100644 --- a/packages/sdk/src/__test__/unit/encoders.test.ts +++ b/packages/sdk/src/__test__/unit/encoders.test.ts @@ -1,21 +1,8 @@ import { EXTERNAL } from '../../constants'; -import { - encodeAddKvRecordsRequest, - encodeGetAddressesRequest, - encodeGetKvRecordsRequest, - encodePairRequest, - encodeRemoveKvRecordsRequest, - encodeSignRequest, -} from '../../functions'; +import { encodeAddKvRecordsRequest, encodeGetAddressesRequest, encodeGetKvRecordsRequest, encodePairRequest, encodeRemoveKvRecordsRequest, encodeSignRequest } from '../../functions'; import { buildTransaction } from '../../shared/functions'; import { getP256KeyPair } from '../../util'; -import { - buildFirmwareConstants, - buildGetAddressesObject, - buildSignObject, - buildWallet, - getFwVersionsList, -} from '../utils/builders'; +import { buildFirmwareConstants, buildGetAddressesObject, buildSignObject, buildWallet, getFwVersionsList } from '../utils/builders'; describe('encoders', () => { let mockRandom: any; @@ -70,22 +57,19 @@ describe('encoders', () => { }); describe('sign', () => { - test.each(getFwVersionsList())( - 'should test sign encoder with firmware v%d.%d.%d', - (major, minor, patch) => { - const fwVersion = Buffer.from([patch, minor, major]); - const txObj = buildSignObject(fwVersion); - const tx = buildTransaction(txObj); - const req = { - ...txObj, - ...tx, - wallet: buildWallet(), - }; - const { payload } = encodeSignRequest(req); - const payloadAsString = payload.toString('hex'); - expect(payloadAsString).toMatchSnapshot(); - }, - ); + test.each(getFwVersionsList())('should test sign encoder with firmware v%d.%d.%d', (major, minor, patch) => { + const fwVersion = Buffer.from([patch, minor, major]); + const txObj = buildSignObject(fwVersion); + const tx = buildTransaction(txObj); + const req = { + ...txObj, + ...tx, + wallet: buildWallet(), + }; + const { payload } = encodeSignRequest(req); + const payloadAsString = payload.toString('hex'); + expect(payloadAsString).toMatchSnapshot(); + }); }); describe('KvRecords', () => { diff --git a/packages/sdk/src/__test__/unit/ethereum.validate.test.ts b/packages/sdk/src/__test__/unit/ethereum.validate.test.ts index 18c696c9..0785c8fe 100644 --- a/packages/sdk/src/__test__/unit/ethereum.validate.test.ts +++ b/packages/sdk/src/__test__/unit/ethereum.validate.test.ts @@ -1,9 +1,4 @@ -import { - type MessageTypes, - SignTypedDataVersion, - TypedDataUtils, - type TypedMessage, -} from '@metamask/eth-sig-util'; +import { type MessageTypes, SignTypedDataVersion, TypedDataUtils, type TypedMessage } from '@metamask/eth-sig-util'; import { ecsign, privateToAddress } from 'ethereumjs-util'; import { mnemonicToAccount } from 'viem/accounts'; import { HARDENED_OFFSET } from '../../constants'; @@ -36,10 +31,7 @@ describe('validateEthereumMsgResponse', () => { if (!hdKey.privateKey) throw new Error('No private key'); const priv = Buffer.from(hdKey.privateKey); const signer = privateToAddress(priv); - const digest = TypedDataUtils.eip712Hash( - typedData, - SignTypedDataVersion.V4, - ); + const digest = TypedDataUtils.eip712Hash(typedData, SignTypedDataVersion.V4); const sig = ecsign(Buffer.from(digest), priv); const fwConstants = buildFirmwareConstants(); const request = ethereum.buildEthereumMsgRequest({ @@ -76,10 +68,7 @@ describe('validateEthereumMsgResponse', () => { if (!hdKey.privateKey) throw new Error('No private key'); const priv = Buffer.from(hdKey.privateKey); const signer = privateToAddress(priv); - const digest = TypedDataUtils.eip712Hash( - typedData, - SignTypedDataVersion.V4, - ); + const digest = TypedDataUtils.eip712Hash(typedData, SignTypedDataVersion.V4); const sig = ecsign(Buffer.from(digest), priv); const result = ethereum.validateEthereumMsgResponse( diff --git a/packages/sdk/src/__test__/unit/module.interop.test.ts b/packages/sdk/src/__test__/unit/module.interop.test.ts index 98bed751..18025691 100644 --- a/packages/sdk/src/__test__/unit/module.interop.test.ts +++ b/packages/sdk/src/__test__/unit/module.interop.test.ts @@ -1,11 +1,5 @@ import { execSync, spawnSync } from 'node:child_process'; -import { - existsSync, - mkdirSync, - mkdtempSync, - rmSync, - symlinkSync, -} from 'node:fs'; +import { existsSync, mkdirSync, mkdtempSync, rmSync, symlinkSync } from 'node:fs'; import os from 'node:os'; import path from 'node:path'; import { fileURLToPath } from 'node:url'; @@ -59,9 +53,7 @@ const runNodeCheck = (args: string[]) => { throw result.error; } if (result.status !== 0) { - throw new Error( - `Node command failed (${result.status}):\n${result.stderr || result.stdout}`, - ); + throw new Error(`Node command failed (${result.status}):\n${result.stderr || result.stdout}`); } }; diff --git a/packages/sdk/src/__test__/unit/parseGenericSigningResponse.test.ts b/packages/sdk/src/__test__/unit/parseGenericSigningResponse.test.ts index 8874c50d..da12a4b3 100644 --- a/packages/sdk/src/__test__/unit/parseGenericSigningResponse.test.ts +++ b/packages/sdk/src/__test__/unit/parseGenericSigningResponse.test.ts @@ -34,18 +34,12 @@ describe('parseGenericSigningResponse', () => { const hash = Buffer.from(Hash.keccak256(payload)); // Create a fake signature - const privateKey = Buffer.from( - '0101010101010101010101010101010101010101010101010101010101010101', - 'hex', - ); + const privateKey = Buffer.from('0101010101010101010101010101010101010101010101010101010101010101', 'hex'); const sigObj = secp256k1.ecdsaSign(hash, privateKey); const publicKey = secp256k1.publicKeyCreate(privateKey, false); // Create DER-encoded signature response - const derSig = createDERSignature( - Buffer.from(sigObj.signature.slice(0, 32)), - Buffer.from(sigObj.signature.slice(32, 64)), - ); + const derSig = createDERSignature(Buffer.from(sigObj.signature.slice(0, 32)), Buffer.from(sigObj.signature.slice(32, 64))); // Create mock response buffer const mockResponse = Buffer.concat([ @@ -74,25 +68,16 @@ describe('parseGenericSigningResponse', () => { it('should handle EVM transaction encoding', () => { // Simulate an unsigned legacy transaction - const unsignedTx = Buffer.from( - 'e9808504a817c800825208943535353535353535353535353535353535353535880de0b6b3a764000080', - 'hex', - ); + const unsignedTx = Buffer.from('e9808504a817c800825208943535353535353535353535353535353535353535880de0b6b3a764000080', 'hex'); const hash = Buffer.from(Hash.keccak256(unsignedTx)); // Create a fake signature - const privateKey = Buffer.from( - '0101010101010101010101010101010101010101010101010101010101010101', - 'hex', - ); + const privateKey = Buffer.from('0101010101010101010101010101010101010101010101010101010101010101', 'hex'); const sigObj = secp256k1.ecdsaSign(hash, privateKey); const publicKey = secp256k1.publicKeyCreate(privateKey, false); // Create DER-encoded signature response - const derSig = createDERSignature( - Buffer.from(sigObj.signature.slice(0, 32)), - Buffer.from(sigObj.signature.slice(32, 64)), - ); + const derSig = createDERSignature(Buffer.from(sigObj.signature.slice(0, 32)), Buffer.from(sigObj.signature.slice(32, 64))); // Create mock response buffer const mockResponse = Buffer.concat([ @@ -134,18 +119,12 @@ describe('parseGenericSigningResponse', () => { const hash = Buffer.from(Hash.keccak256(rlpEncoded)); // Create a fake signature - const privateKey = Buffer.from( - '0101010101010101010101010101010101010101010101010101010101010101', - 'hex', - ); + const privateKey = Buffer.from('0101010101010101010101010101010101010101010101010101010101010101', 'hex'); const sigObj = secp256k1.ecdsaSign(hash, privateKey); const publicKey = secp256k1.publicKeyCreate(privateKey, false); // Create DER-encoded signature response - const derSig = createDERSignature( - Buffer.from(sigObj.signature.slice(0, 32)), - Buffer.from(sigObj.signature.slice(32, 64)), - ); + const derSig = createDERSignature(Buffer.from(sigObj.signature.slice(0, 32)), Buffer.from(sigObj.signature.slice(32, 64))); // Create mock response buffer const mockResponse = Buffer.concat([ diff --git a/packages/sdk/src/__test__/unit/personalSignValidation.test.ts b/packages/sdk/src/__test__/unit/personalSignValidation.test.ts index ee225c57..d6d39cb7 100644 --- a/packages/sdk/src/__test__/unit/personalSignValidation.test.ts +++ b/packages/sdk/src/__test__/unit/personalSignValidation.test.ts @@ -12,23 +12,15 @@ describe('Personal Sign Validation - Issue Fix', () => { it('should correctly validate personal message signature', () => { // Create a test private key and derive public key - const privateKey = Buffer.from( - '0101010101010101010101010101010101010101010101010101010101010101', - 'hex', - ); + const privateKey = Buffer.from('0101010101010101010101010101010101010101010101010101010101010101', 'hex'); const publicKey = secp256k1.publicKeyCreate(privateKey, false); // Create a test message const message = Buffer.from('Test message', 'utf8'); // Build personal sign prefix and hash - const prefix = Buffer.from( - `\u0019Ethereum Signed Message:\n${message.length.toString()}`, - 'utf-8', - ); - const messageHash = Buffer.from( - Hash.keccak256(Buffer.concat([prefix, message])), - ); + const prefix = Buffer.from(`\u0019Ethereum Signed Message:\n${message.length.toString()}`, 'utf-8'); + const messageHash = Buffer.from(Hash.keccak256(Buffer.concat([prefix, message]))); // Sign the message const sigObj = secp256k1.ecdsaSign(messageHash, privateKey); @@ -42,9 +34,7 @@ describe('Personal Sign Validation - Issue Fix', () => { // Get the Ethereum address from the public key // This matches what the firmware returns const pubkeyWithoutPrefix = publicKey.slice(1); // Remove 0x04 prefix - const addressBuffer = Buffer.from( - Hash.keccak256(pubkeyWithoutPrefix), - ).slice(-20); + const addressBuffer = Buffer.from(Hash.keccak256(pubkeyWithoutPrefix)).slice(-20); // This is the function that was failing before the fix // It should now correctly add the recovery parameter @@ -55,9 +45,7 @@ describe('Personal Sign Validation - Issue Fix', () => { // Verify the signature has a valid v value (27 or 28) expect(result.v).toBeDefined(); - const vValue = Buffer.isBuffer(result.v) - ? result.v.readUInt8(0) - : Number(result.v); + const vValue = Buffer.isBuffer(result.v) ? result.v.readUInt8(0) : Number(result.v); expect([27, 28]).toContain(vValue); // Verify r and s are buffers of correct length @@ -95,19 +83,11 @@ describe('Personal Sign Validation - Issue Fix', () => { const payloadBuffer = Buffer.from(testPayload.slice(2), 'hex'); // Build personal sign hash - const prefix = Buffer.from( - `\u0019Ethereum Signed Message:\n${payloadBuffer.length.toString()}`, - 'utf-8', - ); - const messageHash = Buffer.from( - Hash.keccak256(Buffer.concat([prefix, payloadBuffer])), - ); + const prefix = Buffer.from(`\u0019Ethereum Signed Message:\n${payloadBuffer.length.toString()}`, 'utf-8'); + const messageHash = Buffer.from(Hash.keccak256(Buffer.concat([prefix, payloadBuffer]))); // Create a valid signature for this message - const privateKey = Buffer.from( - '0101010101010101010101010101010101010101010101010101010101010101', - 'hex', - ); + const privateKey = Buffer.from('0101010101010101010101010101010101010101010101010101010101010101', 'hex'); const publicKey = secp256k1.publicKeyCreate(privateKey, false); const sigObj = secp256k1.ecdsaSign(messageHash, privateKey); @@ -118,9 +98,7 @@ describe('Personal Sign Validation - Issue Fix', () => { // Get address from public key const pubkeyWithoutPrefix = publicKey.slice(1); - const addressBuffer = Buffer.from( - Hash.keccak256(pubkeyWithoutPrefix), - ).slice(-20); + const addressBuffer = Buffer.from(Hash.keccak256(pubkeyWithoutPrefix)).slice(-20); // This should NOT throw with the fix in place expect(() => { diff --git a/packages/sdk/src/__test__/unit/selectDefFrom4byteABI.test.ts b/packages/sdk/src/__test__/unit/selectDefFrom4byteABI.test.ts index 46a79a18..08bb7005 100644 --- a/packages/sdk/src/__test__/unit/selectDefFrom4byteABI.test.ts +++ b/packages/sdk/src/__test__/unit/selectDefFrom4byteABI.test.ts @@ -17,16 +17,14 @@ describe('selectDefFrom4byteAbi', () => { created_at: '2020-08-09T08:56:14.110995Z', hex_signature: '0x38ed1739', id: 171801, - text_signature: - 'swapExactTokensForTokens(uint256,uint256,address[],address,uint256)', + text_signature: 'swapExactTokensForTokens(uint256,uint256,address[],address,uint256)', }, { bytes_signature: '8í9', created_at: '2020-01-09T08:56:14.110995Z', hex_signature: '0x38ed1739', id: 171806, - text_signature: - 'swapExactTokensForTokens(uint256,uint256,address[],address,uint256)', + text_signature: 'swapExactTokensForTokens(uint256,uint256,address[],address,uint256)', }, { bytes_signature: '', diff --git a/packages/sdk/src/__test__/unit/signatureUtils.test.ts b/packages/sdk/src/__test__/unit/signatureUtils.test.ts index f1dfbec7..73a2e44b 100644 --- a/packages/sdk/src/__test__/unit/signatureUtils.test.ts +++ b/packages/sdk/src/__test__/unit/signatureUtils.test.ts @@ -7,10 +7,7 @@ describe('getYParity', () => { // Helper function to create a valid signature const createValidSignature = (messageHash: Buffer) => { // Create a deterministic private key for testing - const privateKey = Buffer.from( - '0101010101010101010101010101010101010101010101010101010101010101', - 'hex', - ); + const privateKey = Buffer.from('0101010101010101010101010101010101010101010101010101010101010101', 'hex'); // Sign the message const sigObj = secp256k1.ecdsaSign(messageHash, privateKey); @@ -31,8 +28,7 @@ describe('getYParity', () => { describe('Simple signature format', () => { it('should handle simple format with Buffer inputs', () => { const messageHash = randomBytes(32); - const { signature, publicKey, recovery } = - createValidSignature(messageHash); + const { signature, publicKey, recovery } = createValidSignature(messageHash); const yParity = getYParity({ messageHash, @@ -46,8 +42,7 @@ describe('getYParity', () => { it('should handle simple format with hex string inputs', () => { const messageHash = randomBytes(32); - const { signature, publicKey, recovery } = - createValidSignature(messageHash); + const { signature, publicKey, recovery } = createValidSignature(messageHash); const yParity = getYParity({ messageHash: `0x${messageHash.toString('hex')}`, @@ -63,10 +58,7 @@ describe('getYParity', () => { it('should handle simple format with compressed public key', () => { const messageHash = randomBytes(32); - const privateKey = Buffer.from( - '0101010101010101010101010101010101010101010101010101010101010101', - 'hex', - ); + const privateKey = Buffer.from('0101010101010101010101010101010101010101010101010101010101010101', 'hex'); const sigObj = secp256k1.ecdsaSign(messageHash, privateKey); const compressedPubkey = secp256k1.publicKeyCreate(privateKey, true); @@ -85,8 +77,7 @@ describe('getYParity', () => { it('should handle mixed format inputs', () => { const messageHash = randomBytes(32); - const { signature, publicKey, recovery } = - createValidSignature(messageHash); + const { signature, publicKey, recovery } = createValidSignature(messageHash); const yParity = getYParity({ messageHash: messageHash.toString('hex'), // No 0x prefix @@ -138,8 +129,7 @@ describe('getYParity', () => { getMessageToSign: () => messageData, }; - const { signature, publicKey, recovery } = - createValidSignature(messageData); + const { signature, publicKey, recovery } = createValidSignature(messageData); const resp = { sig: signature, @@ -159,9 +149,7 @@ describe('getYParity', () => { const messageHash = new Uint8Array(32); messageHash.fill(42); - const { signature, publicKey, recovery } = createValidSignature( - Buffer.from(messageHash), - ); + const { signature, publicKey, recovery } = createValidSignature(Buffer.from(messageHash)); const resp = { sig: { @@ -184,9 +172,7 @@ describe('getYParity', () => { for (let i = 0; i < 32; i++) { hash[i] = Math.floor(Math.random() * 256); } - const { signature, publicKey, recovery } = createValidSignature( - Buffer.from(hash), - ); + const { signature, publicKey, recovery } = createValidSignature(Buffer.from(hash)); const resp = { sig: signature, @@ -200,8 +186,7 @@ describe('getYParity', () => { it('should hash non-32-byte inputs', () => { const shortData = randomBytes(20); const expectedHash = Buffer.from(Hash.keccak256(shortData)); - const { signature, publicKey, recovery } = - createValidSignature(expectedHash); + const { signature, publicKey, recovery } = createValidSignature(expectedHash); const resp = { sig: signature, @@ -216,25 +201,19 @@ describe('getYParity', () => { describe('Error handling', () => { it('should throw error if legacy format missing response', () => { const tx = randomBytes(32); - expect(() => getYParity(tx)).toThrow( - 'Response with sig and pubkey required for legacy format', - ); + expect(() => getYParity(tx)).toThrow('Response with sig and pubkey required for legacy format'); }); it('should throw error if response missing sig', () => { const tx = randomBytes(32); const resp = { pubkey: randomBytes(65) }; - expect(() => getYParity(tx, resp)).toThrow( - 'Response with sig and pubkey required for legacy format', - ); + expect(() => getYParity(tx, resp)).toThrow('Response with sig and pubkey required for legacy format'); }); it('should throw error if response missing pubkey', () => { const tx = randomBytes(32); const resp = { sig: { r: randomBytes(32), s: randomBytes(32) } }; - expect(() => getYParity(tx, resp)).toThrow( - 'Response with sig and pubkey required for legacy format', - ); + expect(() => getYParity(tx, resp)).toThrow('Response with sig and pubkey required for legacy format'); }); it('should throw error if recovery fails', () => { @@ -248,9 +227,7 @@ describe('getYParity', () => { signature, publicKey, }), - ).toThrow( - 'Failed to recover Y parity. Bad signature or transaction data.', - ); + ).toThrow('Failed to recover Y parity. Bad signature or transaction data.'); }); it('should throw error with invalid signature', () => { @@ -278,14 +255,10 @@ describe('getYParity', () => { const MAGIC = Buffer.from([0x05]); // This would normally use RLP.encode but we'll create a test message - const message = Buffer.concat([ - MAGIC, - Buffer.from('test_rlp_encoded_data', 'utf8'), - ]); + const message = Buffer.concat([MAGIC, Buffer.from('test_rlp_encoded_data', 'utf8')]); const messageHash = Buffer.from(Hash.keccak256(message)); - const { signature, publicKey, recovery } = - createValidSignature(messageHash); + const { signature, publicKey, recovery } = createValidSignature(messageHash); // Test both Buffer format (as returned by device) const yParity1 = getYParity({ @@ -332,13 +305,8 @@ describe('getYParity', () => { // Try multiple messages until we get one with y-parity 1 for (let i = 0; i < 100; i++) { - const messageHash = Buffer.from( - Hash.keccak256(Buffer.from(`test message ${i}`)), - ); - const privateKey = Buffer.from( - '0101010101010101010101010101010101010101010101010101010101010101', - 'hex', - ); + const messageHash = Buffer.from(Hash.keccak256(Buffer.from(`test message ${i}`))); + const privateKey = Buffer.from('0101010101010101010101010101010101010101010101010101010101010101', 'hex'); const sigObj = secp256k1.ecdsaSign(messageHash, privateKey); @@ -369,12 +337,7 @@ describe('getV function', () => { // Helper to create a valid signature const createValidSignature = (messageHash: Buffer, privateKey?: Buffer) => { // Use deterministic key if not provided - const privKey = - privateKey || - Buffer.from( - '0101010101010101010101010101010101010101010101010101010101010101', - 'hex', - ); + const privKey = privateKey || Buffer.from('0101010101010101010101010101010101010101010101010101010101010101', 'hex'); const sigObj = secp256k1.ecdsaSign(messageHash, privKey); const publicKey = secp256k1.publicKeyCreate(privKey, false); @@ -391,10 +354,7 @@ describe('getV function', () => { it('should handle unsigned legacy transaction with valid signature', () => { // A simple unsigned legacy transaction - const unsignedTxRLP = Buffer.from( - 'e9808504a817c800825208943535353535353535353535353535353535353535880de0b6b3a764000080', - 'hex', - ); + const unsignedTxRLP = Buffer.from('e9808504a817c800825208943535353535353535353535353535353535353535880de0b6b3a764000080', 'hex'); // Hash the transaction const hash = Buffer.from(Hash.keccak256(unsignedTxRLP)); @@ -414,14 +374,8 @@ describe('getV function', () => { const mockResp = { sig: { - r: Buffer.from( - '134f5038e0e6a96741e17a82c8df13e9dc10c3b0e9e956cf7dcf21e1e3b73f9f', - 'hex', - ), - s: Buffer.from( - '638cf1b1f9dd5e6e8e6b9a8e6e8e6b9a8e6e8e6b9a8e6e8e6b9a8e6e8e6b9a8', - 'hex', - ), + r: Buffer.from('134f5038e0e6a96741e17a82c8df13e9dc10c3b0e9e956cf7dcf21e1e3b73f9f', 'hex'), + s: Buffer.from('638cf1b1f9dd5e6e8e6b9a8e6e8e6b9a8e6e8e6b9a8e6e8e6b9a8e6e8e6b9a8', 'hex'), }, // This is a fake pubkey, so recovery will fail pubkey: Buffer.from(`04${'1'.repeat(128)}`, 'hex'), @@ -431,8 +385,7 @@ describe('getV function', () => { }); it('should throw error when signature is invalid', () => { - const txHex = - '0xe9808504a817c800825208943535353535353535353535353535353535353535880de0b6b3a764000080'; + const txHex = '0xe9808504a817c800825208943535353535353535353535353535353535353535880de0b6b3a764000080'; const mockResp = { sig: { diff --git a/packages/sdk/src/__test__/unit/validators.test.ts b/packages/sdk/src/__test__/unit/validators.test.ts index a50978ed..8e93e938 100644 --- a/packages/sdk/src/__test__/unit/validators.test.ts +++ b/packages/sdk/src/__test__/unit/validators.test.ts @@ -1,20 +1,7 @@ import { normalizeToViemTransaction } from '../../ethereum'; -import { - validateAddKvRequest, - validateConnectRequest, - validateGetAddressesRequest, - validateGetKvRequest, - validateRemoveKvRequest, -} from '../../functions'; -import { - isValid4ByteResponse, - isValidBlockExplorerResponse, -} from '../../shared/validators'; -import { - buildGetAddressesObject, - buildValidateConnectObject, - buildValidateRequestObject, -} from '../utils/builders'; +import { validateAddKvRequest, validateConnectRequest, validateGetAddressesRequest, validateGetKvRequest, validateRemoveKvRequest } from '../../functions'; +import { isValid4ByteResponse, isValidBlockExplorerResponse } from '../../shared/validators'; +import { buildGetAddressesObject, buildValidateConnectObject, buildValidateRequestObject } from '../utils/builders'; describe('validators', () => { describe('connect', () => { @@ -42,10 +29,7 @@ describe('validators', () => { test('encodeGetAddressesRequest should throw with invalid startPath', () => { const startPath = [0x80000000 + 44, 0x80000000 + 60, 0, 0, 0, 0, 0]; const fwVersion = Buffer.from([0, 0, 0]); - const testEncodingFunction = () => - validateGetAddressesRequest( - buildGetAddressesObject({ startPath, fwVersion }), - ); + const testEncodingFunction = () => validateGetAddressesRequest(buildGetAddressesObject({ startPath, fwVersion })); expect(testEncodingFunction).toThrowError(); }); }); @@ -92,9 +76,7 @@ describe('validators', () => { test('should throw errors on validation failure', () => { const validateRemoveKvBundle: any = buildValidateRequestObject({}); - expect(() => - validateRemoveKvRequest(validateRemoveKvBundle), - ).toThrowError(); + expect(() => validateRemoveKvRequest(validateRemoveKvBundle)).toThrowError(); }); }); }); @@ -111,8 +93,7 @@ describe('validators', () => { test('should validate as false bad data', () => { const response: any = { - result: - 'Max rate limit reached, please use API Key for higher rate limit', + result: 'Max rate limit reached, please use API Key for higher rate limit', }; expect(isValidBlockExplorerResponse(response)).toBe(false); }); @@ -150,9 +131,7 @@ describe('validators', () => { to: `0x${'1'.repeat(40)}`, value: '1000000000000000000', chainId: 1, - authorizationList: [ - { chainId: 1, address: `0x${'2'.repeat(40)}`, nonce: 0 }, - ], + authorizationList: [{ chainId: 1, address: `0x${'2'.repeat(40)}`, nonce: 0 }], gasPrice: '15000000000', }; diff --git a/packages/sdk/src/__test__/utils/__test__/builders.test.ts b/packages/sdk/src/__test__/utils/__test__/builders.test.ts index 4c618103..84b30911 100644 --- a/packages/sdk/src/__test__/utils/__test__/builders.test.ts +++ b/packages/sdk/src/__test__/utils/__test__/builders.test.ts @@ -1,8 +1,4 @@ -import { - buildEvmReq, - buildRandomVectors, - getFwVersionsList, -} from '../builders'; +import { buildEvmReq, buildRandomVectors, getFwVersionsList } from '../builders'; describe('building', () => { test('should test client', () => { diff --git a/packages/sdk/src/__test__/utils/__test__/serializers.test.ts b/packages/sdk/src/__test__/utils/__test__/serializers.test.ts index d8a99eb4..b5f29727 100644 --- a/packages/sdk/src/__test__/utils/__test__/serializers.test.ts +++ b/packages/sdk/src/__test__/utils/__test__/serializers.test.ts @@ -1,7 +1,4 @@ -import { - deserializeObjectWithBuffers, - serializeObjectWithBuffers, -} from '../serializers'; +import { deserializeObjectWithBuffers, serializeObjectWithBuffers } from '../serializers'; describe('serializers', () => { test('serialize obj', () => { diff --git a/packages/sdk/src/__test__/utils/builders.ts b/packages/sdk/src/__test__/utils/builders.ts index 0e3b8a50..46fad59f 100644 --- a/packages/sdk/src/__test__/utils/builders.ts +++ b/packages/sdk/src/__test__/utils/builders.ts @@ -4,22 +4,13 @@ import { type TypedTransaction, createTx } from '@ethereumjs/tx'; import { generate as randomWords } from 'random-words'; import { Constants } from '../..'; import { Client } from '../../client'; -import { - CURRENCIES, - HARDENED_OFFSET, - getFwVersionConst, -} from '../../constants'; +import { CURRENCIES, HARDENED_OFFSET, getFwVersionConst } from '../../constants'; import type { Currency, SignRequestParams, SigningPath } from '../../types'; import type { FirmwareConstants } from '../../types/firmware'; import { randomBytes } from '../../util'; import { MSG_PAYLOAD_METADATA_SZ } from './constants'; import { getN, getPrng } from './getters'; -import { - BTC_PURPOSE_P2PKH, - ETH_COIN, - buildRandomEip712Object, - getTestVectors, -} from './helpers'; +import { BTC_PURPOSE_P2PKH, ETH_COIN, buildRandomEip712Object, getTestVectors } from './helpers'; const prng = getPrng(); @@ -75,10 +66,7 @@ export const buildFirmwareConstants = (...overrides: any) => { }; export const buildWallet = (overrides?) => ({ - uid: Buffer.from( - '162b56efe561c12bc93f703dc7026b3ec3d53923270c9259e2b08015fb9defd2', - 'hex', - ), + uid: Buffer.from('162b56efe561c12bc93f703dc7026b3ec3d53923270c9259e2b08015fb9defd2', 'hex'), capabilities: 1, external: true, ...overrides, @@ -113,14 +101,10 @@ export const buildSignObject = (fwVersion, overrides?) => { }; export const buildSharedSecret = () => { - return Buffer.from([ - 89, 60, 130, 80, 168, 252, 34, 136, 230, 71, 230, 158, 51, 13, 239, 237, 6, - 246, 71, 232, 232, 175, 193, 106, 106, 185, 38, 1, 163, 14, 225, 101, - ]); + return Buffer.from([89, 60, 130, 80, 168, 252, 34, 136, 230, 71, 230, 158, 51, 13, 239, 237, 6, 246, 71, 232, 232, 175, 193, 106, 106, 185, 38, 1, 163, 14, 225, 101]); }; -export const getNumIter = (n: number | string | undefined = getN()) => - n ? Number.parseInt(`${n}`) : 5; +export const getNumIter = (n: number | string | undefined = getN()) => (n ? Number.parseInt(`${n}`) : 5); /** Generate a bunch of random test vectors using the PRNG */ export const buildRandomVectors = (n: number | string | undefined = getN()) => { @@ -134,13 +118,7 @@ export const buildRandomVectors = (n: number | string | undefined = getN()) => { return RANDOM_VEC; }; -export const DEFAULT_SIGNER = [ - BTC_PURPOSE_P2PKH, - ETH_COIN, - HARDENED_OFFSET, - 0, - 0, -]; +export const DEFAULT_SIGNER = [BTC_PURPOSE_P2PKH, ETH_COIN, HARDENED_OFFSET, 0, 0]; export const buildTx = (data: `0x${string}` = '0xdeadbeef') => { return createTx( @@ -163,10 +141,7 @@ export const buildTx = (data: `0x${string}` = '0xdeadbeef') => { ); }; -export const buildEthSignRequest = async ( - client: Client, - txDataOverrides?: any, -): Promise => { +export const buildEthSignRequest = async (client: Client, txDataOverrides?: any): Promise => { if (client.getFwVersion()?.major === 0 && client.getFwVersion()?.minor < 15) { console.warn('Please update firmware. Skipping ETH signing tests.'); return; @@ -199,9 +174,7 @@ export const buildEthSignRequest = async ( encodingType: Constants.SIGNING.ENCODINGS.EVM, }, }; - const maxDataSz = - fwConstants.ethMaxDataSz + - fwConstants.extraDataMaxFrames * fwConstants.extraDataFrameSz; + const maxDataSz = fwConstants.ethMaxDataSz + fwConstants.extraDataMaxFrames * fwConstants.extraDataFrameSz; return { fwConstants, signerPath, @@ -223,10 +196,7 @@ export const buildTxReq = (tx: TypedTransaction) => ({ }, }); -export const buildMsgReq = ( - payload = 'hello ethereum', - protocol: 'signPersonal' | 'eip712' = 'signPersonal', -) => ({ +export const buildMsgReq = (payload = 'hello ethereum', protocol: 'signPersonal' | 'eip712' = 'signPersonal') => ({ currency: 'ETH_MSG' as const, data: { signerPath: DEFAULT_SIGNER, @@ -280,12 +250,10 @@ export const buildEncDefs = (vectors: any) => { }); // The calldata is already in hex format, we just need to ensure it has 0x prefix - const encDefsCalldata = vectors.canonicalNames.map( - (_: string, idx: number) => { - const calldata = `0x${idx.toString(16).padStart(8, '0')}`; - return calldata; - }, - ); + const encDefsCalldata = vectors.canonicalNames.map((_: string, idx: number) => { + const calldata = `0x${idx.toString(16).padStart(8, '0')}`; + return calldata; + }); return { encDefs, encDefsCalldata }; }; @@ -308,17 +276,7 @@ export function buildRandomMsg(type, client: Client) { } } -export function buildEthMsgReq( - payload: any, - protocol: 'signPersonal' | 'eip712', - signerPath = [ - BTC_PURPOSE_P2PKH, - ETH_COIN, - HARDENED_OFFSET, - 0, - 0, - ] as SigningPath, -): SignRequestParams { +export function buildEthMsgReq(payload: any, protocol: 'signPersonal' | 'eip712', signerPath = [BTC_PURPOSE_P2PKH, ETH_COIN, HARDENED_OFFSET, 0, 0] as SigningPath): SignRequestParams { return { currency: CURRENCIES.ETH_MSG, data: { diff --git a/packages/sdk/src/__test__/utils/determinism.ts b/packages/sdk/src/__test__/utils/determinism.ts index 236de499..516c0ec7 100644 --- a/packages/sdk/src/__test__/utils/determinism.ts +++ b/packages/sdk/src/__test__/utils/determinism.ts @@ -10,11 +10,7 @@ import type { SigningPath } from '../../types'; import { ethPersonalSignMsg, getSigStr } from './helpers'; import { TEST_SEED } from './testConstants'; -export async function testUniformSigs( - payload: any, - tx: TypedTransaction, - client: Client, -) { +export async function testUniformSigs(payload: any, tx: TypedTransaction, client: Client) { const tx1Resp = await client.sign(payload); const tx2Resp = await client.sign(payload); const tx3Resp = await client.sign(payload); diff --git a/packages/sdk/src/__test__/utils/ethers.ts b/packages/sdk/src/__test__/utils/ethers.ts index a0dbf54f..f2571eb1 100644 --- a/packages/sdk/src/__test__/utils/ethers.ts +++ b/packages/sdk/src/__test__/utils/ethers.ts @@ -1,13 +1,4 @@ -const EVM_TYPES = [ - null, - 'address', - 'bool', - 'uint', - 'int', - 'bytes', - 'string', - 'tuple', -]; +const EVM_TYPES = [null, 'address', 'bool', 'uint', 'int', 'bytes', 'string', 'tuple']; export function convertDecoderToEthers(def: unknown[]) { const converted = getConvertedDef(def); @@ -77,35 +68,25 @@ function genTupleData(tupleParam: unknown[]) { const nestedData: unknown[] = []; tupleParam.forEach((nestedParam: unknown) => { const np = nestedParam as { toString: (fmt: string) => string }[]; - nestedData.push( - genData(EVM_TYPES[Number.parseInt(np[1].toString('hex'), 16)] ?? '', np), - ); + nestedData.push(genData(EVM_TYPES[Number.parseInt(np[1].toString('hex'), 16)] ?? '', np)); }); return nestedData; } function genParamData(param: { toString: (fmt: string) => string }[]) { - const evmType = - EVM_TYPES[Number.parseInt(param[1].toString('hex'), 16)] ?? ''; + const evmType = EVM_TYPES[Number.parseInt(param[1].toString('hex'), 16)] ?? ''; const baseData = genData(evmType, param); return getArrayData(param, baseData); } -function getArrayData( - param: { toString: (fmt: string) => string }[], - baseData: unknown, -) { +function getArrayData(param: { toString: (fmt: string) => string }[], baseData: unknown) { let arrayData: unknown[] | undefined; let data: unknown; const arrSzs = param[3] as unknown as { toString: (fmt: string) => string }[]; for (let i = 0; i < arrSzs.length; i++) { // let sz = parseInt(arrSzs[i].toString('hex')); TODO: fix this const dimData: unknown[] = []; - let sz = Number.parseInt( - (param[3] as unknown as { toString: (fmt: string) => string }[])[ - i - ].toString('hex'), - ); + let sz = Number.parseInt((param[3] as unknown as { toString: (fmt: string) => string }[])[i].toString('hex')); if (Number.isNaN(sz)) { sz = 2; //1; } diff --git a/packages/sdk/src/__test__/utils/helpers.ts b/packages/sdk/src/__test__/utils/helpers.ts index 75189c15..fe2d188b 100644 --- a/packages/sdk/src/__test__/utils/helpers.ts +++ b/packages/sdk/src/__test__/utils/helpers.ts @@ -5,10 +5,7 @@ import { wordlists } from 'bip39'; import bitcoin, { type Payment } from 'bitcoinjs-lib'; import BN from 'bn.js'; import { ECPairFactory } from 'ecpair'; -import { - derivePath as deriveEDKey, - getPublicKey as getEDPubkey, -} from 'ed25519-hd-key'; +import { derivePath as deriveEDKey, getPublicKey as getEDPubkey } from 'ed25519-hd-key'; import { ec as EC } from 'elliptic'; import { privateToAddress } from 'ethereumjs-util'; import { jsonc } from 'jsonc'; @@ -18,20 +15,10 @@ import * as ecc from 'tiny-secp256k1'; import nacl from 'tweetnacl'; import { Constants } from '../..'; import { Client } from '../../client'; -import { - BIP_CONSTANTS, - HARDENED_OFFSET, - ethMsgProtocol, -} from '../../constants'; +import { BIP_CONSTANTS, HARDENED_OFFSET, ethMsgProtocol } from '../../constants'; import { ProtocolConstants } from '../../protocol'; import { getPathStr } from '../../shared/utilities'; -import { - ensureHexBuffer, - getV, - getYParity, - parseDER, - randomBytes, -} from '../../util'; +import { ensureHexBuffer, getV, getYParity, parseDER, randomBytes } from '../../util'; import { getEnv } from './getters'; import { setStoredClient } from './setup'; @@ -107,19 +94,14 @@ export const getSignatureVBN = (tx: any, resp: any): BN => { // For p2pkh-derived addresses, we use the legacy 44' purpose // For p2wpkh-derived addresse (not yet supported) we will use 84' export const BTC_PURPOSE_P2WPKH = BIP_CONSTANTS.PURPOSES.BTC_SEGWIT; -export const BTC_PURPOSE_P2SH_P2WPKH = - BIP_CONSTANTS.PURPOSES.BTC_WRAPPED_SEGWIT; +export const BTC_PURPOSE_P2SH_P2WPKH = BIP_CONSTANTS.PURPOSES.BTC_WRAPPED_SEGWIT; export const BTC_PURPOSE_P2PKH = BIP_CONSTANTS.PURPOSES.BTC_LEGACY; export const BTC_COIN = BIP_CONSTANTS.COINS.BTC; export const BTC_TESTNET_COIN = BIP_CONSTANTS.COINS.BTC_TESTNET; export const ETH_COIN = BIP_CONSTANTS.COINS.ETH; -export const REUSABLE_KEY = - '3fb53b677f73e4d2b8c89c303f6f6b349f0075ad88ea126cb9f6632085815dca'; +export const REUSABLE_KEY = '3fb53b677f73e4d2b8c89c303f6f6b349f0075ad88ea126cb9f6632085815dca'; -export function setupTestClient( - env = getEnv() as any, - stateData?: any, -): Client { +export function setupTestClient(env = getEnv() as any, stateData?: any): Client { if (stateData) { return new Client({ stateData }); } @@ -184,15 +166,7 @@ export function _get_btc_addr(pubkey, purpose, network) { return obj.address; } -export function _start_tx_builder( - wallet, - recipient, - value, - fee, - inputs, - network, - purpose, -) { +export function _start_tx_builder(wallet, recipient, value, fee, inputs, network, purpose) { const tx = new bitcoin.Transaction(); // Match serialization logic (version 2) used by device and serializer tx.version = 2; @@ -204,9 +178,7 @@ export function _start_tx_builder( const networkIdx = network === bitcoin.networks.testnet ? 1 : 0; const path = buildPath([purpose, harden(networkIdx), harden(0), 1, 0]); const btc_0_change = wallet.derivePath(path); - const btc_0_change_pub = ECPair.fromPublicKey( - btc_0_change.publicKey, - ).publicKey; + const btc_0_change_pub = ECPair.fromPublicKey(btc_0_change.publicKey).publicKey; const changeAddr = _get_btc_addr(btc_0_change_pub, purpose, network); const changeScript = bitcoin.address.toOutputScript(changeAddr, network); tx.addOutput(changeScript, changeValue); @@ -217,8 +189,7 @@ export function _start_tx_builder( inputs.forEach((input) => { const hashLE = Buffer.from(input.hash, 'hex').reverse(); tx.addInput(hashLE, input.idx); - const coin = - network === bitcoin.networks.testnet ? BTC_TESTNET_COIN : BTC_COIN; + const coin = network === bitcoin.networks.testnet ? BTC_TESTNET_COIN : BTC_COIN; const path = buildPath([purpose, coin, harden(0), 0, input.signerIdx]); const keyPair = wallet.derivePath(path); const pubkeyBuf = Buffer.from(keyPair.publicKey); @@ -237,68 +208,25 @@ function _build_sighashes(txb_or_tx, purpose) { const isLegacy = purpose === BTC_PURPOSE_P2PKH; if (txb.inputsMeta) { txb.inputsMeta.forEach((meta, i) => { - hashes.push( - isLegacy - ? txb.tx.hashForSignature(i, meta.scriptCode, SIGHASH_ALL) - : txb.tx.hashForWitnessV0( - i, - meta.scriptCode, - meta.value, - SIGHASH_ALL, - ), - ); + hashes.push(isLegacy ? txb.tx.hashForSignature(i, meta.scriptCode, SIGHASH_ALL) : txb.tx.hashForWitnessV0(i, meta.scriptCode, meta.value, SIGHASH_ALL)); }); } else { // Fallback for prior structure (should not be used) txb.__inputs.forEach((input, i) => { - hashes.push( - isLegacy - ? txb.__tx.hashForSignature(i, input.signScript, SIGHASH_ALL) - : txb.__tx.hashForWitnessV0( - i, - input.signScript, - input.value, - SIGHASH_ALL, - ), - ); + hashes.push(isLegacy ? txb.__tx.hashForSignature(i, input.signScript, SIGHASH_ALL) : txb.__tx.hashForWitnessV0(i, input.signScript, input.value, SIGHASH_ALL)); }); } return hashes; } -function _get_reference_sighashes( - wallet, - recipient, - value, - fee, - inputs, - isTestnet, - purpose, -) { - const network = isTestnet - ? bitcoin.networks.testnet - : bitcoin.networks.bitcoin; - const built = _start_tx_builder( - wallet, - recipient, - value, - fee, - inputs, - network, - purpose, - ); +function _get_reference_sighashes(wallet, recipient, value, fee, inputs, isTestnet, purpose) { + const network = isTestnet ? bitcoin.networks.testnet : bitcoin.networks.bitcoin; + const built = _start_tx_builder(wallet, recipient, value, fee, inputs, network, purpose); // built has shape { tx, inputsMeta } return _build_sighashes(built, purpose); } -function _btc_tx_request_builder( - inputs, - recipient, - value, - fee, - isTestnet, - purpose, -) { +function _btc_tx_request_builder(inputs, recipient, value, fee, isTestnet, purpose) { const currencyIdx = isTestnet ? BTC_TESTNET_COIN : BTC_COIN; const txData = { prevOuts: [] as any[], @@ -336,13 +264,7 @@ export function stripDER(derSig) { function _get_signing_keys(wallet, inputs, isTestnet, purpose) { const currencyIdx = isTestnet ? 1 : 0; return inputs.map((input) => { - const path = buildPath([ - purpose, - harden(currencyIdx), - harden(0), - 0, - input.signerIdx, - ]); + const path = buildPath([purpose, harden(currencyIdx), harden(0), 0, input.signerIdx]); const node = wallet.derivePath(path); const priv = Buffer.from(node.privateKey); const key = secp256k1.keyFromPrivate(priv); @@ -365,8 +287,7 @@ function _generate_btc_address(isTestnet, purpose, rand) { priv.writeUInt32BE(Math.floor(rand.quick() * 2 ** 32), j * 4); } const keyPair = ECPair.fromPrivateKey(priv); - const network = - isTestnet === true ? bitcoin.networks.testnet : bitcoin.networks.bitcoin; + const network = isTestnet === true ? bitcoin.networks.testnet : bitcoin.networks.bitcoin; return _get_btc_addr(keyPair.publicKey, purpose, network); } @@ -375,32 +296,11 @@ export function setup_btc_sig_test(opts, wallet, inputs, rand) { const recipient = _generate_btc_address(isTestnet, recipientPurpose, rand); const sumInputs = _getSumInputs(inputs); const fee = Math.floor(rand.quick() * 50000); - const _value = - useChange === true ? Math.floor(rand.quick() * sumInputs) : sumInputs; + const _value = useChange === true ? Math.floor(rand.quick() * sumInputs) : sumInputs; const value = _value - fee; - const sigHashes = _get_reference_sighashes( - wallet, - recipient, - value, - fee, - inputs, - isTestnet, - spenderPurpose, - ); - const signingKeys = _get_signing_keys( - wallet, - inputs, - isTestnet, - spenderPurpose, - ); - const txReq = _btc_tx_request_builder( - inputs, - recipient, - value, - fee, - isTestnet, - spenderPurpose, - ); + const sigHashes = _get_reference_sighashes(wallet, recipient, value, fee, inputs, isTestnet, spenderPurpose); + const signingKeys = _get_signing_keys(wallet, inputs, isTestnet, spenderPurpose); + const txReq = _btc_tx_request_builder(inputs, recipient, value, fee, isTestnet, spenderPurpose); return { sigHashes, signingKeys, @@ -518,9 +418,7 @@ export const copyBuffer = (x) => { // Convert a set of indices to a human readable bip32 path export const stringifyPath = (parent) => { const convert = (parent) => { - return parent >= HARDENED_OFFSET - ? `${parent - HARDENED_OFFSET}'` - : `${parent}`; + return parent >= HARDENED_OFFSET ? `${parent - HARDENED_OFFSET}'` : `${parent}`; }; if (parent.idx) { // BIP32 style encoding @@ -607,8 +505,7 @@ export const validateBTCAddresses = (resp, jobData, seed, useTestnet?) => { expect(resp.count).toEqual(jobData.count); const wallet = bip32.fromSeed(seed); const path = JSON.parse(JSON.stringify(jobData.path)); - const network = - useTestnet === true ? bitcoin.networks.testnet : bitcoin.networks.bitcoin; + const network = useTestnet === true ? bitcoin.networks.testnet : bitcoin.networks.bitcoin; for (let i = 0; i < jobData.count; i++) { path.idx[jobData.iterIdx] = jobData.path.idx[jobData.iterIdx] + i; // Validate the address @@ -657,12 +554,7 @@ export const validateETHAddresses = (resp, jobData, seed) => { } }; -export const validateDerivedPublicKeys = ( - pubKeys, - firstPath, - seed, - flag?: number, -) => { +export const validateDerivedPublicKeys = (pubKeys, firstPath, seed, flag?: number) => { const wallet = bip32.fromSeed(seed); // We assume the keys were derived in sequential order pubKeys.forEach((pub, i) => { @@ -671,23 +563,16 @@ export const validateDerivedPublicKeys = ( if (flag === Constants.GET_ADDR_FLAGS.ED25519_PUB) { // ED25519 requires its own derivation const key = deriveED25519Key(path, seed); - expect(pub.toString('hex')).toEqualElseLog( - key.pub.toString('hex'), - 'Exported ED25519 pubkey incorrect', - ); + expect(pub.toString('hex')).toEqualElseLog(key.pub.toString('hex'), 'Exported ED25519 pubkey incorrect'); } else { // Otherwise this is a SECP256K1 pubkey const priv = wallet.derivePath(getPathStr(path)).privateKey; - expect(pub.toString('hex')).toEqualElseLog( - secp256k1.keyFromPrivate(priv).getPublic().encode('hex', false), - 'Exported SECP256K1 pubkey incorrect', - ); + expect(pub.toString('hex')).toEqualElseLog(secp256k1.keyFromPrivate(priv).getPublic().encode('hex', false), 'Exported SECP256K1 pubkey incorrect'); } }); }; -export const ethPersonalSignMsg = (msg) => - `\u0019Ethereum Signed Message:\n${String(msg.length)}${msg}`; +export const ethPersonalSignMsg = (msg) => `\u0019Ethereum Signed Message:\n${String(msg.length)}${msg}`; //--------------------------------------------------- // Sign Transaction helpers @@ -762,18 +647,12 @@ export const deserializeSignTxJobResult = (res: any) => { _off += 4; o.signerPath.addr = _o.readUInt32LE(_off); _off += 4; - o.pubkey = secp256k1.keyFromPublic( - _o.slice(_off, _off + 65).toString('hex'), - 'hex', - ); + o.pubkey = secp256k1.keyFromPublic(_o.slice(_off, _off + 65).toString('hex'), 'hex'); _off += PK_LEN; // We get back a DER signature in 74 bytes, but not all the bytes are necessarily // used. The second byte contains the DER sig length, so we need to use that. const derLen = _o[_off + 1]; - o.sig = Buffer.from( - _o.slice(_off, _off + 2 + derLen).toString('hex'), - 'hex', - ); + o.sig = Buffer.from(_o.slice(_off, _off + 2 + derLen).toString('hex'), 'hex'); getTxResult.outputs.push(o); } @@ -855,14 +734,11 @@ export const buildRandomEip712Object = (randInt) => { } function getRandomName(upperCase = false, sz = 20) { const name = randStr(sz); - if (upperCase === true) - return `${name.slice(0, 1).toUpperCase()}${name.slice(1)}`; + if (upperCase === true) return `${name.slice(0, 1).toUpperCase()}${name.slice(1)}`; return name; } function getRandomEIP712Type(customTypes: any[] = []) { - const types = Object.keys(customTypes).concat( - Object.keys(ethMsgProtocol.TYPED_DATA.typeCodes), - ); + const types = Object.keys(customTypes).concat(Object.keys(ethMsgProtocol.TYPED_DATA.typeCodes)); return { name: getRandomName(), type: types[randInt(types.length)], @@ -1001,25 +877,15 @@ export const validateGenericSig = (seed, sig, payloadBuf, req, pubkey?) => { r: normalizeSigComponent(sig.r).toString('hex'), s: normalizeSigComponent(sig.s).toString('hex'), }; - expect(key.verify(hash, normalizedSig)).toEqualElseLog( - true, - 'Signature failed verification.', - ); + expect(key.verify(hash, normalizedSig)).toEqualElseLog(true, 'Signature failed verification.'); } else if (curveType === CURVES.ED25519) { if (hashType !== HASHES.NONE) { throw new Error('Bad params'); } const { pub } = deriveED25519Key(signerPath, seed); - const signature = Buffer.concat([ - normalizeSigComponent(sig.r), - normalizeSigComponent(sig.s), - ]); + const signature = Buffer.concat([normalizeSigComponent(sig.r), normalizeSigComponent(sig.s)]); const edPublicKey = pubkey ? normalizeSigComponent(pubkey) : pub; - const isValid = nacl.sign.detached.verify( - new Uint8Array(payloadBuf), - new Uint8Array(signature), - new Uint8Array(edPublicKey), - ); + const isValid = nacl.sign.detached.verify(new Uint8Array(payloadBuf), new Uint8Array(signature), new Uint8Array(edPublicKey)); expect(isValid).toEqualElseLog(true, 'Signature failed verification.'); } else { throw new Error('Bad params'); @@ -1069,9 +935,7 @@ export function toBuffer(data: string | number | Buffer | Uint8Array): Buffer { } if (typeof data === 'string') { const trimmed = data.trim(); - const isHex = - trimmed.startsWith('0x') || - (/^[0-9a-fA-F]+$/.test(trimmed) && trimmed.length % 2 === 0); + const isHex = trimmed.startsWith('0x') || (/^[0-9a-fA-F]+$/.test(trimmed) && trimmed.length % 2 === 0); return isHex ? ensureHexBuffer(trimmed) : Buffer.from(trimmed, 'utf8'); } throw new Error('Unsupported data type'); @@ -1081,24 +945,16 @@ export function toUint8Array(data: Buffer): Uint8Array { return new Uint8Array(data.buffer, data.byteOffset, data.length); } -export function ensureHash32( - message: string | number | Buffer | Uint8Array, -): Uint8Array { +export function ensureHash32(message: string | number | Buffer | Uint8Array): Uint8Array { const msgBuffer = toBuffer(message); - const digest = - msgBuffer.length === 32 - ? msgBuffer - : Buffer.from(Hash.keccak256(msgBuffer)); + const digest = msgBuffer.length === 32 ? msgBuffer : Buffer.from(Hash.keccak256(msgBuffer)); if (digest.length !== 32) { throw new Error('Failed to derive 32-byte hash for signature validation.'); } return toUint8Array(digest); } -export function validateSig( - resp: any, - message: string | number | Buffer | Uint8Array, -) { +export function validateSig(resp: any, message: string | number | Buffer | Uint8Array) { if (!resp.sig?.r || !resp.sig?.s || !resp.pubkey) { throw new Error('Missing signature components'); } @@ -1108,20 +964,11 @@ export function validateSig( const hash = ensureHash32(message); const pubkeyInput = toBuffer(resp.pubkey); - const normalizedPubkey = - pubkeyInput.length === 64 - ? Buffer.concat([Buffer.from([0x04]), pubkeyInput]) - : pubkeyInput; - const isCompressed = - normalizedPubkey.length === 33 && - (normalizedPubkey[0] === 0x02 || normalizedPubkey[0] === 0x03); + const normalizedPubkey = pubkeyInput.length === 64 ? Buffer.concat([Buffer.from([0x04]), pubkeyInput]) : pubkeyInput; + const isCompressed = normalizedPubkey.length === 33 && (normalizedPubkey[0] === 0x02 || normalizedPubkey[0] === 0x03); - const recoveredA = Buffer.from( - ecdsaRecover(rs, 0, hash, isCompressed), - ).toString('hex'); - const recoveredB = Buffer.from( - ecdsaRecover(rs, 1, hash, isCompressed), - ).toString('hex'); + const recoveredA = Buffer.from(ecdsaRecover(rs, 0, hash, isCompressed)).toString('hex'); + const recoveredB = Buffer.from(ecdsaRecover(rs, 1, hash, isCompressed)).toString('hex'); const expected = normalizedPubkey.toString('hex'); if (expected !== recoveredA && expected !== recoveredB) { throw new Error('Signature did not validate.'); @@ -1145,15 +992,10 @@ export const compressPubKey = (pub) => { function _stripTrailingCommas(input: string): string { // Use non-backtracking pattern to avoid ReDoS vulnerability // Unrolled loop for multi-line comments: \/\*[^*]*\*+(?:[^/*][^*]*\*+)*\/ - return input.replace( - /,([\s]*(?:\/\/[^\n]*\n[\s]*|\/\*[^*]*\*+(?:[^/*][^*]*\*+)*\/[\s]*)*)([}\]])/g, - '$1$2', - ); + return input.replace(/,([\s]*(?:\/\/[^\n]*\n[\s]*|\/\*[^*]*\*+(?:[^/*][^*]*\*+)*\/[\s]*)*)([}\]])/g, '$1$2'); } export const getTestVectors = () => { - const raw = readFileSync( - `${process.cwd()}/src/__test__/vectors.jsonc`, - ).toString(); + const raw = readFileSync(`${process.cwd()}/src/__test__/vectors.jsonc`).toString(); return jsonc.parse(_stripTrailingCommas(raw)); }; diff --git a/packages/sdk/src/__test__/utils/runners.ts b/packages/sdk/src/__test__/utils/runners.ts index 1302e5a7..86d8d477 100644 --- a/packages/sdk/src/__test__/utils/runners.ts +++ b/packages/sdk/src/__test__/utils/runners.ts @@ -1,18 +1,11 @@ import type { Client } from '../../client'; import { getEncodedPayload } from '../../genericSigning'; -import type { - SigningPayload, - SignRequestParams, - TestRequestPayload, -} from '../../types'; +import type { SigningPayload, SignRequestParams, TestRequestPayload } from '../../types'; import { parseWalletJobResp, validateGenericSig } from './helpers'; import { TEST_SEED } from './testConstants'; import { testRequest } from './testRequest'; -export async function runTestCase( - payload: TestRequestPayload, - expectedCode: number, -) { +export async function runTestCase(payload: TestRequestPayload, expectedCode: number) { const res = await testRequest(payload); //@ts-expect-error - Accessing private property const fwVersion = payload.client.fwVersion; @@ -28,11 +21,7 @@ export async function runGeneric(request: SignRequestParams, client: Client) { // If no encoding type is specified we encode in hex or ascii const encodingType = data.encodingType || null; const allowedEncodings = client.getFwConstants().genericSigning.encodingTypes; - const { payloadBuf } = getEncodedPayload( - data.payload, - encodingType, - allowedEncodings, - ); + const { payloadBuf } = getEncodedPayload(data.payload, encodingType, allowedEncodings); const seed = TEST_SEED; validateGenericSig(seed, response.sig, payloadBuf, data, response.pubkey); return response; diff --git a/packages/sdk/src/__test__/utils/setup.ts b/packages/sdk/src/__test__/utils/setup.ts index 4cadb90f..59b09bb4 100644 --- a/packages/sdk/src/__test__/utils/setup.ts +++ b/packages/sdk/src/__test__/utils/setup.ts @@ -41,9 +41,7 @@ export async function setupClient() { if (!isPaired) { if (!pairingSecret) { if (process.env.CI) { - throw new Error( - 'Pairing secret is required. If simulator is running, set PAIRING_SECRET environment variable.', - ); + throw new Error('Pairing secret is required. If simulator is running, set PAIRING_SECRET environment variable.'); } pairingSecret = question('Enter pairing secret:'); if (!pairingSecret) { diff --git a/packages/sdk/src/__test__/utils/testConstants.ts b/packages/sdk/src/__test__/utils/testConstants.ts index 9c996f73..41dbd8b5 100644 --- a/packages/sdk/src/__test__/utils/testConstants.ts +++ b/packages/sdk/src/__test__/utils/testConstants.ts @@ -12,8 +12,7 @@ import { mnemonicToSeedSync } from 'bip39'; * This mnemonic is used across multiple test files to ensure consistent * test behavior and deterministic results. */ -export const TEST_MNEMONIC = - 'test test test test test test test test test test test junk'; +export const TEST_MNEMONIC = 'test test test test test test test test test test test junk'; /** * Shared seed derived from TEST_MNEMONIC diff --git a/packages/sdk/src/__test__/utils/testEnvironment.ts b/packages/sdk/src/__test__/utils/testEnvironment.ts index 8f23e7c5..368518b2 100644 --- a/packages/sdk/src/__test__/utils/testEnvironment.ts +++ b/packages/sdk/src/__test__/utils/testEnvironment.ts @@ -6,8 +6,7 @@ expect.extend({ toEqualElseLog(received: unknown, expected: unknown, message?: string) { return { pass: received === expected, - message: () => - message ?? `Expected ${String(received)} to equal ${String(expected)}`, + message: () => message ?? `Expected ${String(received)} to equal ${String(expected)}`, }; }, }); diff --git a/packages/sdk/src/__test__/utils/testRequest.ts b/packages/sdk/src/__test__/utils/testRequest.ts index 8332e70c..0f00be28 100644 --- a/packages/sdk/src/__test__/utils/testRequest.ts +++ b/packages/sdk/src/__test__/utils/testRequest.ts @@ -1,22 +1,13 @@ -import { - LatticeSecureEncryptedRequestType, - encryptedSecureRequest, -} from '../../protocol'; +import { LatticeSecureEncryptedRequestType, encryptedSecureRequest } from '../../protocol'; import type { TestRequestPayload } from '../../types'; /** * `test` takes a data object with a testID and a payload, and sends them to the device. * @category Lattice */ -export const testRequest = async ({ - payload, - testID, - client, -}: TestRequestPayload) => { +export const testRequest = async ({ payload, testID, client }: TestRequestPayload) => { if (!payload) { - throw new Error( - 'First argument must contain `testID` and `payload` fields.', - ); + throw new Error('First argument must contain `testID` and `payload` fields.'); } const sharedSecret = client.sharedSecret; const ephemeralPub = client.ephemeralPub; diff --git a/packages/sdk/src/__test__/utils/viemComparison.ts b/packages/sdk/src/__test__/utils/viemComparison.ts index 4f5fcab8..0c092e3f 100644 --- a/packages/sdk/src/__test__/utils/viemComparison.ts +++ b/packages/sdk/src/__test__/utils/viemComparison.ts @@ -1,11 +1,4 @@ -import { - type Address, - type Hex, - type TransactionSerializable, - type TypedDataDefinition, - parseTransaction, - serializeTransaction, -} from 'viem'; +import { type Address, type Hex, type TransactionSerializable, type TypedDataDefinition, parseTransaction, serializeTransaction } from 'viem'; import { privateKeyToAccount } from 'viem/accounts'; import { sign, signMessage } from '../../api'; import { normalizeLatticeSignature } from '../../ethereum'; @@ -35,10 +28,7 @@ export const getFoundryAccount = () => { export type TestTransaction = TransactionSerializable; // Sign transaction with both Lattice and viem, then compare -export const signAndCompareTransaction = async ( - tx: TestTransaction, - testName: string, -) => { +export const signAndCompareTransaction = async (tx: TestTransaction, testName: string) => { const foundryAccount = getFoundryAccount(); try { @@ -102,18 +92,14 @@ export const signAndCompareTransaction = async ( // Additional verification: compare signature components // Lattice returns r,s as hex strings with 0x prefix or as Buffer const normalizeSigComponent = (value: string | Buffer) => { - const hexString = - typeof value === 'string' - ? value - : `0x${Buffer.from(value).toString('hex')}`; + const hexString = typeof value === 'string' ? value : `0x${Buffer.from(value).toString('hex')}`; const stripped = hexString.replace(/^0x/, '').toLowerCase(); return `0x${stripped.padStart(64, '0')}`; }; const latticeR = normalizeSigComponent(latticeResult.sig.r); const latticeS = normalizeSigComponent(latticeResult.sig.s); - if (!parsedViemTx.r || !parsedViemTx.s) - throw new Error('Missing signature components'); + if (!parsedViemTx.r || !parsedViemTx.s) throw new Error('Missing signature components'); const viemR = normalizeSigComponent(parsedViemTx.r); const viemS = normalizeSigComponent(parsedViemTx.s); @@ -142,10 +128,7 @@ export type EIP712TestMessage = { }; // Sign EIP-712 typed data with both Lattice and viem, then compare -export const signAndCompareEIP712Message = async ( - eip712Message: EIP712TestMessage, - testName: string, -) => { +export const signAndCompareEIP712Message = async (eip712Message: EIP712TestMessage, testName: string) => { const foundryAccount = getFoundryAccount(); try { diff --git a/packages/sdk/src/api/addressTags.ts b/packages/sdk/src/api/addressTags.ts index 4e4ebcf5..cb7b5ad7 100644 --- a/packages/sdk/src/api/addressTags.ts +++ b/packages/sdk/src/api/addressTags.ts @@ -6,9 +6,7 @@ import { queue } from './utilities'; /** * Sends request to the Lattice to add Address Tags. */ -export const addAddressTags = async ( - tags: [{ [key: string]: string }], -): Promise => { +export const addAddressTags = async (tags: [{ [key: string]: string }]): Promise => { // convert an array of objects to an object const records = tags.reduce((acc, tag) => { const key = Object.keys(tag)[0]; @@ -22,10 +20,7 @@ export const addAddressTags = async ( /** * Fetches Address Tags from the Lattice. */ -export const fetchAddressTags = async ({ - n = MAX_ADDR, - start = 0, -}: { n?: number; start?: number } = {}) => { +export const fetchAddressTags = async ({ n = MAX_ADDR, start = 0 }: { n?: number; start?: number } = {}) => { const addressTags: AddressTag[] = []; let remainingToFetch = n; let fetched = start; @@ -50,9 +45,7 @@ export const fetchAddressTags = async ({ /** * Removes Address Tags from the Lattice. */ -export const removeAddressTags = async ( - tags: AddressTag[], -): Promise => { +export const removeAddressTags = async (tags: AddressTag[]): Promise => { const ids = tags.map((tag) => `${tag.id}`); return queue((client: Client) => client.removeKvRecords({ ids })); }; diff --git a/packages/sdk/src/api/addresses.ts b/packages/sdk/src/api/addresses.ts index c2414af3..7fa68ed1 100644 --- a/packages/sdk/src/api/addresses.ts +++ b/packages/sdk/src/api/addresses.ts @@ -17,12 +17,7 @@ import { } from '../constants'; import { LatticeGetAddressesFlag } from '../protocol/latticeConstants'; import type { GetAddressesRequestParams, WalletPath } from '../types'; -import { - getFlagFromPath, - getStartPath, - parseDerivationPathComponents, - queue, -} from './utilities'; +import { getFlagFromPath, getStartPath, parseDerivationPathComponents, queue } from './utilities'; type FetchAddressesParams = { n?: number; @@ -30,9 +25,7 @@ type FetchAddressesParams = { flag?: number; }; -export const fetchAddresses = async ( - overrides?: Partial, -) => { +export const fetchAddresses = async (overrides?: Partial) => { let allAddresses: string[] = []; let totalFetched = 0; const totalToFetch = overrides?.n || MAX_ADDR; @@ -65,14 +58,9 @@ export const fetchAddresses = async ( * @note By default, this function fetches m/44'/60'/0'/0/0 * @param path - either the index of ETH signing path or the derivation path to fetch */ -export const fetchAddress = async ( - path: number | WalletPath = 0, -): Promise => { +export const fetchAddress = async (path: number | WalletPath = 0): Promise => { return fetchAddresses({ - startPath: - typeof path === 'number' - ? getStartPath(DEFAULT_ETH_DERIVATION, path) - : path, + startPath: typeof path === 'number' ? getStartPath(DEFAULT_ETH_DERIVATION, path) : path, n: 1, }).then((addrs) => addrs[0]); }; @@ -90,23 +78,12 @@ function createFetchBtcAddressesFunction(derivationPath: number[]) { }); }; } -export const fetchBtcLegacyAddresses = createFetchBtcAddressesFunction( - BTC_LEGACY_DERIVATION, -); -export const fetchBtcSegwitAddresses = createFetchBtcAddressesFunction( - BTC_SEGWIT_DERIVATION, -); -export const fetchBtcWrappedSegwitAddresses = createFetchBtcAddressesFunction( - BTC_WRAPPED_SEGWIT_DERIVATION, -); -export const fetchBtcLegacyChangeAddresses = createFetchBtcAddressesFunction( - BTC_LEGACY_CHANGE_DERIVATION, -); -export const fetchBtcSegwitChangeAddresses = createFetchBtcAddressesFunction( - BTC_SEGWIT_CHANGE_DERIVATION, -); -export const fetchBtcWrappedSegwitChangeAddresses = - createFetchBtcAddressesFunction(BTC_WRAPPED_SEGWIT_CHANGE_DERIVATION); +export const fetchBtcLegacyAddresses = createFetchBtcAddressesFunction(BTC_LEGACY_DERIVATION); +export const fetchBtcSegwitAddresses = createFetchBtcAddressesFunction(BTC_SEGWIT_DERIVATION); +export const fetchBtcWrappedSegwitAddresses = createFetchBtcAddressesFunction(BTC_WRAPPED_SEGWIT_DERIVATION); +export const fetchBtcLegacyChangeAddresses = createFetchBtcAddressesFunction(BTC_LEGACY_CHANGE_DERIVATION); +export const fetchBtcSegwitChangeAddresses = createFetchBtcAddressesFunction(BTC_SEGWIT_CHANGE_DERIVATION); +export const fetchBtcWrappedSegwitChangeAddresses = createFetchBtcAddressesFunction(BTC_WRAPPED_SEGWIT_CHANGE_DERIVATION); export const fetchSolanaAddresses = async ( { n, startPathIndex }: FetchAddressesParams = { @@ -133,11 +110,7 @@ export const fetchLedgerLiveAddresses = async ( queue((client) => client .getAddresses({ - startPath: getStartPath( - LEDGER_LIVE_DERIVATION, - startPathIndex + i, - 2, - ), + startPath: getStartPath(LEDGER_LIVE_DERIVATION, startPathIndex + i, 2), n: 1, }) .then((addresses) => addresses.map((address) => `${address}`)), @@ -159,11 +132,7 @@ export const fetchLedgerLegacyAddresses = async ( queue((client) => client .getAddresses({ - startPath: getStartPath( - LEDGER_LEGACY_DERIVATION, - startPathIndex + i, - 3, - ), + startPath: getStartPath(LEDGER_LEGACY_DERIVATION, startPathIndex + i, 3), n: 1, }) .then((addresses) => addresses.map((address) => `${address}`)), @@ -173,20 +142,12 @@ export const fetchLedgerLegacyAddresses = async ( return Promise.all(addresses); }; -export const fetchBip44ChangeAddresses = async ({ - n = MAX_ADDR, - startPathIndex = 0, -}: FetchAddressesParams = {}) => { +export const fetchBip44ChangeAddresses = async ({ n = MAX_ADDR, startPathIndex = 0 }: FetchAddressesParams = {}) => { const addresses = []; for (let i = 0; i < n; i++) { addresses.push( queue((client) => { - const startPath = [ - 44 + HARDENED_OFFSET, - 501 + HARDENED_OFFSET, - startPathIndex + i + HARDENED_OFFSET, - 0 + HARDENED_OFFSET, - ]; + const startPath = [44 + HARDENED_OFFSET, 501 + HARDENED_OFFSET, startPathIndex + i + HARDENED_OFFSET, 0 + HARDENED_OFFSET]; return client .getAddresses({ startPath, @@ -200,16 +161,11 @@ export const fetchBip44ChangeAddresses = async ({ return Promise.all(addresses); }; -export async function fetchAddressesByDerivationPath( - path: string, - { n = 1, startPathIndex = 0, flag }: FetchAddressesParams = {}, -): Promise { +export async function fetchAddressesByDerivationPath(path: string, { n = 1, startPathIndex = 0, flag }: FetchAddressesParams = {}): Promise { const components = path.split('/').filter(Boolean); const parsedPath = parseDerivationPathComponents(components); const _flag = getFlagFromPath(parsedPath); - const wildcardIndex = components.findIndex((part) => - part.toLowerCase().includes('x'), - ); + const wildcardIndex = components.findIndex((part) => part.toLowerCase().includes('x')); if (wildcardIndex === -1) { return queue((client) => @@ -224,8 +180,7 @@ export async function fetchAddressesByDerivationPath( const addresses: string[] = []; for (let i = 0; i < n; i++) { const currentPath = [...parsedPath]; - currentPath[wildcardIndex] = - currentPath[wildcardIndex] + startPathIndex + i; + currentPath[wildcardIndex] = currentPath[wildcardIndex] + startPathIndex + i; const result = await queue((client) => client.getAddresses({ @@ -256,12 +211,9 @@ export async function fetchBtcXpub(): Promise { * @returns ypub string */ export async function fetchBtcYpub(): Promise { - const result = await fetchAddressesByDerivationPath( - BTC_WRAPPED_SEGWIT_YPUB_PATH, - { - flag: LatticeGetAddressesFlag.secp256k1Xpub, - }, - ); + const result = await fetchAddressesByDerivationPath(BTC_WRAPPED_SEGWIT_YPUB_PATH, { + flag: LatticeGetAddressesFlag.secp256k1Xpub, + }); return result[0]; } diff --git a/packages/sdk/src/api/setup.ts b/packages/sdk/src/api/setup.ts index 6471528f..f4d555ea 100644 --- a/packages/sdk/src/api/setup.ts +++ b/packages/sdk/src/api/setup.ts @@ -50,9 +50,7 @@ export const setup = async (params: SetupParameters): Promise => { setSaveClient(buildSaveClientFn(params.setStoredClient)); if ('deviceId' in params && 'password' in params && 'name' in params) { - const privKey = - params.appSecret || - Utils.generateAppSecret(params.deviceId, params.password, params.name); + const privKey = params.appSecret || Utils.generateAppSecret(params.deviceId, params.password, params.name); const client = new Client({ deviceId: params.deviceId, privKey, diff --git a/packages/sdk/src/api/signing.ts b/packages/sdk/src/api/signing.ts index fd98777a..aaccd57b 100644 --- a/packages/sdk/src/api/signing.ts +++ b/packages/sdk/src/api/signing.ts @@ -1,31 +1,10 @@ import { RLP } from '@ethereumjs/rlp'; import { Hash } from 'ox'; -import { - type Address, - type Authorization, - type Hex, - type TransactionSerializable, - type TransactionSerializableEIP7702, - serializeTransaction, -} from 'viem'; +import { type Address, type Authorization, type Hex, type TransactionSerializable, type TransactionSerializableEIP7702, serializeTransaction } from 'viem'; import { Constants } from '..'; -import { - BTC_LEGACY_DERIVATION, - BTC_SEGWIT_DERIVATION, - BTC_WRAPPED_SEGWIT_DERIVATION, - CURRENCIES, - DEFAULT_ETH_DERIVATION, - SOLANA_DERIVATION, -} from '../constants'; +import { BTC_LEGACY_DERIVATION, BTC_SEGWIT_DERIVATION, BTC_WRAPPED_SEGWIT_DERIVATION, CURRENCIES, DEFAULT_ETH_DERIVATION, SOLANA_DERIVATION } from '../constants'; import { fetchDecoder } from '../functions/fetchDecoder'; -import type { - BitcoinSignPayload, - EIP712MessagePayload, - SignData, - SignRequestParams, - SigningPayload, - TransactionRequest, -} from '../types'; +import type { BitcoinSignPayload, EIP712MessagePayload, SignData, SignRequestParams, SigningPayload, TransactionRequest } from '../types'; import { getYParity } from '../util'; import { isEIP712Payload, queue } from './utilities'; @@ -40,38 +19,21 @@ type AuthorizationRequest = { */ type RawTransaction = Hex | Uint8Array | Buffer; -export const sign = async ( - transaction: TransactionSerializable | RawTransaction, - overrides?: Omit, -): Promise => { +export const sign = async (transaction: TransactionSerializable | RawTransaction, overrides?: Omit): Promise => { const isRaw = isRawTransaction(transaction); - const serializedTx = isRaw - ? normalizeRawTransaction(transaction) - : serializeTransaction(transaction as TransactionSerializable); + const serializedTx = isRaw ? normalizeRawTransaction(transaction) : serializeTransaction(transaction as TransactionSerializable); // Determine the encoding type based on transaction type - let encodingType: - | typeof Constants.SIGNING.ENCODINGS.EVM - | typeof Constants.SIGNING.ENCODINGS.EIP7702_AUTH - | typeof Constants.SIGNING.ENCODINGS.EIP7702_AUTH_LIST = - Constants.SIGNING.ENCODINGS.EVM; + let encodingType: typeof Constants.SIGNING.ENCODINGS.EVM | typeof Constants.SIGNING.ENCODINGS.EIP7702_AUTH | typeof Constants.SIGNING.ENCODINGS.EIP7702_AUTH_LIST = Constants.SIGNING.ENCODINGS.EVM; if (!isRaw && (transaction as TransactionSerializable).type === 'eip7702') { const eip7702Tx = transaction as TransactionSerializableEIP7702; - const hasAuthList = - eip7702Tx.authorizationList && eip7702Tx.authorizationList.length > 0; - encodingType = hasAuthList - ? Constants.SIGNING.ENCODINGS.EIP7702_AUTH_LIST - : Constants.SIGNING.ENCODINGS.EIP7702_AUTH; + const hasAuthList = eip7702Tx.authorizationList && eip7702Tx.authorizationList.length > 0; + encodingType = hasAuthList ? Constants.SIGNING.ENCODINGS.EIP7702_AUTH_LIST : Constants.SIGNING.ENCODINGS.EIP7702_AUTH; } // Only fetch decoder if we have the required fields let decoder: Buffer | undefined; - if ( - !isRaw && - 'data' in (transaction as TransactionSerializable) && - 'to' in (transaction as TransactionSerializable) && - 'chainId' in (transaction as TransactionSerializable) - ) { + if (!isRaw && 'data' in (transaction as TransactionSerializable) && 'to' in (transaction as TransactionSerializable) && 'chainId' in (transaction as TransactionSerializable)) { decoder = await fetchDecoder({ data: (transaction as TransactionSerializable).data, to: (transaction as TransactionSerializable).to, @@ -94,15 +56,7 @@ export const sign = async ( /** * Sign a message with support for EIP-712 typed data and const assertions */ -export function signMessage( - payload: - | string - | Uint8Array - | Buffer - | Buffer[] - | EIP712MessagePayload>, - overrides?: Omit, -): Promise { +export function signMessage(payload: string | Uint8Array | Buffer | Buffer[] | EIP712MessagePayload>, overrides?: Omit): Promise { const basePayload: SigningPayload> = { signerPath: DEFAULT_ETH_DERIVATION, curveType: Constants.SIGNING.CURVES.SECP256K1, @@ -120,14 +74,8 @@ export function signMessage( return queue((client) => client.sign(tx)); } -function isRawTransaction( - value: TransactionSerializable | RawTransaction, -): value is RawTransaction { - return ( - typeof value === 'string' || - value instanceof Uint8Array || - Buffer.isBuffer(value) - ); +function isRawTransaction(value: TransactionSerializable | RawTransaction): value is RawTransaction { + return typeof value === 'string' || value instanceof Uint8Array || Buffer.isBuffer(value); } function normalizeRawTransaction(tx: RawTransaction): Hex | Buffer { @@ -141,26 +89,15 @@ function normalizeRawTransaction(tx: RawTransaction): Hex | Buffer { * Signs an EIP-7702 authorization to set code for an externally owned account (EOA). * Returns a Viem-compatible authorization object. */ -export const signAuthorization = async ( - authorization: AuthorizationRequest, - overrides?: Omit, -): Promise => { +export const signAuthorization = async (authorization: AuthorizationRequest, overrides?: Omit): Promise => { // EIP-7702 authorization message is: MAGIC || rlp([chain_id, address, nonce]) // MAGIC = 0x05 per EIP-7702 spec const MAGIC = Buffer.from([0x05]); // Handle the address/contractAddress alias - const address = - 'address' in authorization - ? authorization.address - : authorization.contractAddress; + const address = 'address' in authorization ? authorization.address : authorization.contractAddress; - const message = Buffer.concat([ - MAGIC, - Buffer.from( - RLP.encode([authorization.chainId, address, authorization.nonce]), - ), - ]); + const message = Buffer.concat([MAGIC, Buffer.from(RLP.encode([authorization.chainId, address, authorization.nonce]))]); const payload: SigningPayload = { signerPath: DEFAULT_ETH_DERIVATION, @@ -171,9 +108,7 @@ export const signAuthorization = async ( }; // Get the signature with all components - const response = await queue((client) => - client.sign({ data: payload, ...overrides }), - ); + const response = await queue((client) => client.sign({ data: payload, ...overrides })); // Extract signature components if they exist if (response.sig && response.pubkey) { @@ -182,12 +117,8 @@ export const signAuthorization = async ( const yParity = getYParity(messageHash, response.sig, response.pubkey); // Handle both Buffer and string formats for r and s - const rValue = Buffer.isBuffer(response.sig.r) - ? `0x${response.sig.r.toString('hex')}` - : response.sig.r; - const sValue = Buffer.isBuffer(response.sig.s) - ? `0x${response.sig.s.toString('hex')}` - : response.sig.s; + const rValue = Buffer.isBuffer(response.sig.r) ? `0x${response.sig.r.toString('hex')}` : response.sig.r; + const sValue = Buffer.isBuffer(response.sig.s) ? `0x${response.sig.s.toString('hex')}` : response.sig.s; // Create a complete Authorization object with all required signature components const result: Authorization = { @@ -208,9 +139,7 @@ export const signAuthorization = async ( /** * Sign an EIP-7702 transaction using Viem-compatible types */ -export const signAuthorizationList = async ( - tx: TransactionSerializableEIP7702, -): Promise => { +export const signAuthorizationList = async (tx: TransactionSerializableEIP7702): Promise => { const serializedTx = serializeTransaction(tx); const payload: SigningPayload = { @@ -227,9 +156,7 @@ export const signAuthorizationList = async ( return signedPayload; }; -export const signBtcLegacyTx = async ( - payload: BitcoinSignPayload, -): Promise => { +export const signBtcLegacyTx = async (payload: BitcoinSignPayload): Promise => { const tx = { data: { signerPath: BTC_LEGACY_DERIVATION, @@ -240,9 +167,7 @@ export const signBtcLegacyTx = async ( return queue((client) => client.sign(tx)); }; -export const signBtcSegwitTx = async ( - payload: BitcoinSignPayload, -): Promise => { +export const signBtcSegwitTx = async (payload: BitcoinSignPayload): Promise => { const tx = { data: { signerPath: BTC_SEGWIT_DERIVATION, @@ -253,9 +178,7 @@ export const signBtcSegwitTx = async ( return queue((client) => client.sign(tx)); }; -export const signBtcWrappedSegwitTx = async ( - payload: BitcoinSignPayload, -): Promise => { +export const signBtcWrappedSegwitTx = async (payload: BitcoinSignPayload): Promise => { const tx = { data: { signerPath: BTC_WRAPPED_SEGWIT_DERIVATION, @@ -266,10 +189,7 @@ export const signBtcWrappedSegwitTx = async ( return queue((client) => client.sign(tx)); }; -export const signSolanaTx = async ( - payload: Buffer, - overrides?: SignRequestParams, -): Promise => { +export const signSolanaTx = async (payload: Buffer, overrides?: SignRequestParams): Promise => { const tx = { data: { signerPath: SOLANA_DERIVATION, diff --git a/packages/sdk/src/api/state.ts b/packages/sdk/src/api/state.ts index 22aac567..8871a597 100644 --- a/packages/sdk/src/api/state.ts +++ b/packages/sdk/src/api/state.ts @@ -2,9 +2,7 @@ import type { Client } from '../client'; export let saveClient: (clientData: string | null) => Promise; -export const setSaveClient = ( - fn: (clientData: string | null) => Promise, -) => { +export const setSaveClient = (fn: (clientData: string | null) => Promise) => { saveClient = fn; }; diff --git a/packages/sdk/src/api/utilities.ts b/packages/sdk/src/api/utilities.ts index 20072a67..fef1146e 100644 --- a/packages/sdk/src/api/utilities.ts +++ b/packages/sdk/src/api/utilities.ts @@ -1,11 +1,6 @@ import { Client } from '../client'; import { EXTERNAL, HARDENED_OFFSET } from '../constants'; -import { - getFunctionQueue, - loadClient, - saveClient, - setFunctionQueue, -} from './state'; +import { getFunctionQueue, loadClient, saveClient, setFunctionQueue } from './state'; /** * `queue` is a function that wraps all functional API calls. It limits the number of concurrent @@ -54,9 +49,7 @@ const decodeClientData = (clientData: string) => { return Buffer.from(clientData, 'base64').toString(); }; -export const buildSaveClientFn = ( - setStoredClient: (clientData: string | null) => Promise, -) => { +export const buildSaveClientFn = (setStoredClient: (clientData: string | null) => Promise) => { return async (clientData: string | null) => { if (!clientData) return; const encodedData = encodeClientData(clientData); @@ -88,12 +81,7 @@ export const getStartPath = ( return startPath; }; -export const isEIP712Payload = (payload: any) => - typeof payload !== 'string' && - 'types' in payload && - 'domain' in payload && - 'primaryType' in payload && - 'message' in payload; +export const isEIP712Payload = (payload: any) => typeof payload !== 'string' && 'types' in payload && 'domain' in payload && 'primaryType' in payload && 'message' in payload; export function parseDerivationPath(path: string): number[] { if (!path) return []; @@ -106,8 +94,7 @@ export function parseDerivationPathComponents(components: string[]): number[] { const lowerPart = part.toLowerCase(); if (lowerPart === 'x') return 0; // Wildcard if (lowerPart === "x'") return HARDENED_OFFSET; // Hardened wildcard - if (part.endsWith("'")) - return Number.parseInt(part.slice(0, -1)) + HARDENED_OFFSET; + if (part.endsWith("'")) return Number.parseInt(part.slice(0, -1)) + HARDENED_OFFSET; const val = Number.parseInt(part); if (Number.isNaN(val)) { throw new Error(`Invalid part in derivation path: ${part}`); diff --git a/packages/sdk/src/bitcoin.ts b/packages/sdk/src/bitcoin.ts index 190fa812..e1547fda 100644 --- a/packages/sdk/src/bitcoin.ts +++ b/packages/sdk/src/bitcoin.ts @@ -52,8 +52,7 @@ const BTC_SCRIPT_TYPE_P2WPKH_V0 = 0x04; const buildBitcoinTxRequest = (data) => { const { prevOuts, recipient, value, changePath, fee } = data; if (!changePath) throw new Error('No changePath provided.'); - if (changePath.length !== 5) - throw new Error('Please provide a full change path.'); + if (changePath.length !== 5) throw new Error('Please provide a full change path.'); // Serialize the request const payload = Buffer.alloc(59 + 69 * prevOuts.length); let off = 0; @@ -105,8 +104,7 @@ const buildBitcoinTxRequest = (data) => { const scriptType = getScriptType(input); payload.writeUInt8(scriptType, off); off++; - if (!Buffer.isBuffer(input.txHash)) - input.txHash = Buffer.from(input.txHash, 'hex'); + if (!Buffer.isBuffer(input.txHash)) input.txHash = Buffer.from(input.txHash, 'hex'); input.txHash.copy(payload, off); off += input.txHash.length; }); @@ -227,9 +225,7 @@ const getBitcoinAddress = (pubkeyhash, version) => { words.unshift(bech32Version); return bech32.encode(bech32Prefix, words); } else { - return bs58check.encode( - Buffer.concat([Buffer.from([version]), pubkeyhash]), - ); + return bs58check.encode(Buffer.concat([Buffer.from([version]), pubkeyhash])); } }; @@ -238,10 +234,7 @@ const getBitcoinAddress = (pubkeyhash, version) => { function buildRedeemScript(pubkey) { const redeemScript = Buffer.alloc(22); const shaHash = Buffer.from(Hash.sha256(pubkey)); - const pubkeyhash = Buffer.from( - ripemd160().update(shaHash).digest('hex'), - 'hex', - ); + const pubkeyhash = Buffer.from(ripemd160().update(shaHash).digest('hex'), 'hex'); redeemScript.writeUInt8(OP.ZERO, 0); redeemScript.writeUInt8(pubkeyhash.length, 1); pubkeyhash.copy(redeemScript, 2); @@ -290,9 +283,7 @@ function buildLockingScript(address) { case FMT_LEGACY_TESTNET: return buildP2pkhLockingScript(dec.pkh); default: - throw new Error( - `Unknown version byte: ${dec.versionByte}. Cannot build BTC transaction.`, - ); + throw new Error(`Unknown version byte: ${dec.versionByte}. Cannot build BTC transaction.`); } } @@ -409,9 +400,7 @@ function decodeAddress(address) { } // Make sure we decoded if (bech32Dec.words[0] !== 0) { - throw new Error( - `Unsupported segwit version: must be 0, got ${bech32Dec.words[0]}`, - ); + throw new Error(`Unsupported segwit version: must be 0, got ${bech32Dec.words[0]}`); } // Make sure address type is supported. // We currently only support P2WPKH addresses, which bech-32decode to 33 words. @@ -420,9 +409,7 @@ function decodeAddress(address) { // support them either. if (bech32Dec.words.length !== 33) { const isP2wpsh = bech32Dec.words.length === 53; - throw new Error( - `Unsupported address${isP2wpsh ? ' (P2WSH not supported)' : ''}: ${address}`, - ); + throw new Error(`Unsupported address${isP2wpsh ? ' (P2WSH not supported)' : ''}: ${address}`); } pkh = Buffer.from(bech32.fromWords(bech32Dec.words.slice(1))); @@ -445,19 +432,14 @@ function getAddressFormat(path) { return FMT_SEGWIT_NATIVE_V0_TESTNET; } else if (purpose === PURPOSES.BTC_WRAPPED_SEGWIT && coin === COINS.BTC) { return FMT_SEGWIT_WRAPPED; - } else if ( - purpose === PURPOSES.BTC_WRAPPED_SEGWIT && - coin === COINS.BTC_TESTNET - ) { + } else if (purpose === PURPOSES.BTC_WRAPPED_SEGWIT && coin === COINS.BTC_TESTNET) { return FMT_SEGWIT_WRAPPED_TESTNET; } else if (purpose === PURPOSES.BTC_LEGACY && coin === COINS.BTC) { return FMT_LEGACY; } else if (purpose === PURPOSES.BTC_LEGACY && coin === COINS.BTC_TESTNET) { return FMT_LEGACY_TESTNET; } else { - throw new Error( - 'Invalid Bitcoin path provided. Cannot determine address format.', - ); + throw new Error('Invalid Bitcoin path provided. Cannot determine address format.'); } } @@ -474,9 +456,7 @@ function getScriptType(input) { case PURPOSES.BTC_SEGWIT: return BTC_SCRIPT_TYPE_P2WPKH_V0; default: - throw new Error( - `Unsupported path purpose (${input.signerPath[0]}): cannot determine BTC script type.`, - ); + throw new Error(`Unsupported path purpose (${input.signerPath[0]}): cannot determine BTC script type.`); } } @@ -486,10 +466,7 @@ function getScriptType(input) { function needsWitness(inputs) { let w = false; inputs.forEach((input) => { - if ( - input.signerPath[0] === PURPOSES.BTC_SEGWIT || - input.signerPath[0] === PURPOSES.BTC_WRAPPED_SEGWIT - ) { + if (input.signerPath[0] === PURPOSES.BTC_SEGWIT || input.signerPath[0] === PURPOSES.BTC_WRAPPED_SEGWIT) { w = true; } }); diff --git a/packages/sdk/src/calldata/evm.ts b/packages/sdk/src/calldata/evm.ts index 4e8449b9..3103c4ce 100644 --- a/packages/sdk/src/calldata/evm.ts +++ b/packages/sdk/src/calldata/evm.ts @@ -7,10 +7,7 @@ import { decodeAbiParameters, parseAbiParameters } from 'viem'; * @returns Buffer containing RLP-serialized array of calldata info to pass to signing request * @public */ -export const parseSolidityJSONABI = ( - sig: string, - abi: any[], -): { def: EVMDef } => { +export const parseSolidityJSONABI = (sig: string, abi: any[]): { def: EVMDef } => { sig = coerceSig(sig); // Find the first match in the ABI const match = abi @@ -105,17 +102,11 @@ export const getNestedCalldata = (def, calldata) => { if (Array.isArray(paramData)) { paramData.forEach((nestedParamDatum) => { // Ensure nestedParamDatum is a hex string - if ( - typeof nestedParamDatum !== 'string' || - !nestedParamDatum.startsWith('0x') - ) { + if (typeof nestedParamDatum !== 'string' || !nestedParamDatum.startsWith('0x')) { nestedDefIsPossible = false; return; } - const nestedParamDatumBuf = Buffer.from( - nestedParamDatum.slice(2), - 'hex', - ); + const nestedParamDatumBuf = Buffer.from(nestedParamDatum.slice(2), 'hex'); if (!couldBeNestedDef(nestedParamDatumBuf)) { nestedDefIsPossible = false; } @@ -125,10 +116,7 @@ export const getNestedCalldata = (def, calldata) => { } } else if (isBytesItem(defParams[i])) { // Regular `bytes` type - perform size check - if ( - typeof paramData !== 'string' || - !(paramData as string).startsWith('0x') - ) { + if (typeof paramData !== 'string' || !(paramData as string).startsWith('0x')) { nestedDefIsPossible = false; } else { const data = paramData as string; @@ -306,8 +294,7 @@ function parseBasicTypeStr(typeStr: string): EVMParamInfo { if (typeStr.indexOf(t) > -1 && !found) { param.typeIdx = i; param.arraySzs = getArraySzs(typeStr); - const arrStart = - param.arraySzs.length > 0 ? typeStr.indexOf('[') : typeStr.length; + const arrStart = param.arraySzs.length > 0 ? typeStr.indexOf('[') : typeStr.length; const typeStrNum = typeStr.slice(t.length, arrStart); if (Number.parseInt(typeStrNum)) { param.szBytes = Number.parseInt(typeStrNum) / 8; @@ -329,12 +316,7 @@ function parseBasicTypeStr(typeStr: string): EVMParamInfo { * (EVMDef). This function may recurse if there are tuple types. * @internal */ -function parseDef( - item, - canonicalName = '', - def = [], - recursed = false, -): EVMDef { +function parseDef(item, canonicalName = '', def = [], recursed = false): EVMDef { // Function name. Can be an empty string. if (!recursed) { const nameStr = item.name || ''; @@ -349,12 +331,7 @@ function parseDef( const flatParam = getFlatParam(input); if (input.type.indexOf('tuple') > -1 && input.components) { // For tuples we need to recurse - const recursed = parseDef( - { inputs: input.components }, - canonicalName, - [], - true, - ); + const recursed = parseDef({ inputs: input.components }, canonicalName, [], true); canonicalName = recursed.canonicalName; // Add brackets if this is a tuple array and also add a comma canonicalName += `${input.type.slice(5)},`; @@ -392,12 +369,7 @@ function parseParamDef(def: any[], prefix = ''): any[] { parsedDef[parsedDef.length - 1].push(parseParamDef(param, `${i}-`)); } else { // If this is not tuple info, add the flat param info to the def - parsedDef.push([ - `#${prefix}${i + 1 - numTuples}`, - param.typeIdx, - param.szBytes, - param.arraySzs, - ]); + parsedDef.push([`#${prefix}${i + 1 - numTuples}`, param.typeIdx, param.szBytes, param.arraySzs]); } // Tuple if (param.typeIdx === EVM_TYPES.indexOf('tuple')) { @@ -510,8 +482,7 @@ function getTupleName(name, withArr = true) { } else if (name[i] === ')') { brackets -= 1; } - let canBreak = - name[i + 1] === ',' || name[i + 1] === ')' || i === name.length - 1; + let canBreak = name[i + 1] === ',' || name[i + 1] === ')' || i === name.length - 1; if (!withArr && name[i + 1] === '[') { canBreak = true; } @@ -539,17 +510,7 @@ function isBytesArrItem(param) { } const BAD_CANONICAL_ERR = 'Could not parse canonical function name.'; -const EVM_TYPES = [ - null, - 'address', - 'bool', - 'uint', - 'int', - 'bytes', - 'string', - 'tuple', - 'nestedDef', -]; +const EVM_TYPES = [null, 'address', 'bool', 'uint', 'int', 'bytes', 'string', 'tuple', 'nestedDef']; type EVMParamInfo = { szBytes: number; diff --git a/packages/sdk/src/calldata/index.ts b/packages/sdk/src/calldata/index.ts index c5d46b8e..c51e31ec 100644 --- a/packages/sdk/src/calldata/index.ts +++ b/packages/sdk/src/calldata/index.ts @@ -3,12 +3,7 @@ * calldata decoder info is packed into the request, it is used to decode the calldata in the * request. It is optional. */ -import { - getNestedCalldata, - parseCanonicalName, - parseSolidityJSONABI, - replaceNestedDefs, -} from './evm'; +import { getNestedCalldata, parseCanonicalName, parseSolidityJSONABI, replaceNestedDefs } from './evm'; export const CALLDATA = { EVM: { diff --git a/packages/sdk/src/client.ts b/packages/sdk/src/client.ts index 797a6159..bd76226e 100644 --- a/packages/sdk/src/client.ts +++ b/packages/sdk/src/client.ts @@ -1,36 +1,10 @@ import { buildSaveClientFn } from './api/utilities'; -import { - BASE_URL, - DEFAULT_ACTIVE_WALLETS, - EMPTY_WALLET_UID, - getFwVersionConst, -} from './constants'; -import { - addKvRecords, - connect, - fetchActiveWallet, - fetchEncData, - getAddresses, - getKvRecords, - pair, - removeKvRecords, - sign, -} from './functions/index'; +import { BASE_URL, DEFAULT_ACTIVE_WALLETS, EMPTY_WALLET_UID, getFwVersionConst } from './constants'; +import { addKvRecords, connect, fetchActiveWallet, fetchEncData, getAddresses, getKvRecords, pair, removeKvRecords, sign } from './functions/index'; import { buildRetryWrapper } from './shared/functions'; import { getPubKeyBytes } from './shared/utilities'; import { validateEphemeralPub } from './shared/validators'; -import type { - ActiveWallets, - AddKvRecordsRequestParams, - FetchEncDataRequest, - GetAddressesRequestParams, - GetKvRecordsData, - GetKvRecordsRequestParams, - KeyPair, - RemoveKvRecordsRequestParams, - SignData, - SignRequestParams, -} from './types'; +import type { ActiveWallets, AddKvRecordsRequestParams, FetchEncDataRequest, GetAddressesRequestParams, GetKvRecordsData, GetKvRecordsRequestParams, KeyPair, RemoveKvRecordsRequestParams, SignData, SignRequestParams } from './types'; import { getP256KeyPair, getP256KeyPairFromPub, randomBytes } from './util'; /** @@ -116,9 +90,7 @@ export class Client { this.privKey = privKey || randomBytes(32); this.key = getP256KeyPair(this.privKey); this.retryWrapper = buildRetryWrapper(this, this.retryCount); - this.setStoredClient = setStoredClient - ? buildSaveClientFn(setStoredClient) - : undefined; + this.setStoredClient = setStoredClient ? buildSaveClientFn(setStoredClient) : undefined; /** The user may pass in state data to rehydrate a session that was previously cached */ if (stateData) { @@ -158,9 +130,7 @@ export class Client { public get sharedSecret() { // Once every ~256 attempts, we will get a key that starts with a `00` byte, which can lead to // problems initializing AES if we don't force a 32 byte BE buffer. - return Buffer.from( - this.key.derive(this.ephemeralPub.getPublic()).toArray('be', 32), - ); + return Buffer.from(this.key.derive(this.ephemeralPub.getPublic()).toArray('be', 32)); } /** @internal */ @@ -197,12 +167,7 @@ export class Client { * Takes a starting path and a number to get the addresses associated with the active wallet. * @category Lattice */ - public async getAddresses({ - startPath, - n = 1, - flag = 0, - iterIdx = 0, - }: GetAddressesRequestParams): Promise { + public async getAddresses({ startPath, n = 1, flag = 0, iterIdx = 0 }: GetAddressesRequestParams): Promise { return this.retryWrapper(getAddresses, { startPath, n, flag, iterIdx }); } @@ -210,12 +175,7 @@ export class Client { * Builds and sends a request for signing to the Lattice. * @category Lattice */ - public async sign({ - data, - currency, - cachedData, - nextCode, - }: SignRequestParams): Promise { + public async sign({ data, currency, cachedData, nextCode }: SignRequestParams): Promise { return this.retryWrapper(sign, { data, currency, cachedData, nextCode }); } @@ -230,11 +190,7 @@ export class Client { * Takes in a set of key-value records and sends a request to add them to the Lattice. * @category Lattice */ - async addKvRecords({ - type = 0, - records, - caseSensitive = false, - }: AddKvRecordsRequestParams): Promise { + async addKvRecords({ type = 0, records, caseSensitive = false }: AddKvRecordsRequestParams): Promise { return this.retryWrapper(addKvRecords, { type, records, caseSensitive }); } @@ -242,11 +198,7 @@ export class Client { * Fetches a list of key-value records from the Lattice. * @category Lattice */ - public async getKvRecords({ - type = 0, - n = 1, - start = 0, - }: GetKvRecordsRequestParams): Promise { + public async getKvRecords({ type = 0, n = 1, start = 0 }: GetKvRecordsRequestParams): Promise { return this.retryWrapper(getKvRecords, { type, n, start }); } @@ -254,10 +206,7 @@ export class Client { * Takes in an array of ids and sends a request to remove them from the Lattice. * @category Lattice */ - public async removeKvRecords({ - type = 0, - ids = [], - }: RemoveKvRecordsRequestParams): Promise { + public async removeKvRecords({ type = 0, ids = [] }: RemoveKvRecordsRequestParams): Promise { return this.retryWrapper(removeKvRecords, { type, ids }); } @@ -267,23 +216,15 @@ export class Client { * data formatted according to the specified type. * @category Lattice */ - public async fetchEncryptedData( - params: FetchEncDataRequest, - ): Promise { + public async fetchEncryptedData(params: FetchEncDataRequest): Promise { return this.retryWrapper(fetchEncData, params); } /** Get the active wallet */ public getActiveWallet() { - if ( - this.activeWallets.external.uid && - !EMPTY_WALLET_UID.equals(this.activeWallets.external.uid) - ) { + if (this.activeWallets.external.uid && !EMPTY_WALLET_UID.equals(this.activeWallets.external.uid)) { return this.activeWallets.external; - } else if ( - this.activeWallets.internal.uid && - !EMPTY_WALLET_UID.equals(this.activeWallets.internal.uid) - ) { + } else if (this.activeWallets.internal.uid && !EMPTY_WALLET_UID.equals(this.activeWallets.internal.uid)) { return this.activeWallets.internal; } else { return undefined; @@ -417,17 +358,13 @@ export class Client { // Attempt to parse the data const internalWallet = { uid: Buffer.from(unpacked.activeWallets.internal.uid, 'hex'), - name: unpacked.activeWallets.internal.name - ? Buffer.from(unpacked.activeWallets.internal.name) - : null, + name: unpacked.activeWallets.internal.name ? Buffer.from(unpacked.activeWallets.internal.name) : null, capabilities: unpacked.activeWallets.internal.capabilities, external: false, }; const externalWallet = { uid: Buffer.from(unpacked.activeWallets.external.uid, 'hex'), - name: unpacked.activeWallets.external.name - ? Buffer.from(unpacked.activeWallets.external.name) - : null, + name: unpacked.activeWallets.external.name ? Buffer.from(unpacked.activeWallets.external.name) : null, capabilities: unpacked.activeWallets.external.capabilities, external: true, }; diff --git a/packages/sdk/src/constants.ts b/packages/sdk/src/constants.ts index d29182d9..a67d2d81 100644 --- a/packages/sdk/src/constants.ts +++ b/packages/sdk/src/constants.ts @@ -1,17 +1,5 @@ -import { - LatticeEncDataSchema, - LatticeGetAddressesFlag, - LatticeSignBlsDst, - LatticeSignCurve, - LatticeSignEncoding, - LatticeSignHash, -} from './protocol/latticeConstants'; -import type { - ActiveWallets, - FirmwareArr, - FirmwareConstants, - WalletPath, -} from './types/index.js'; +import { LatticeEncDataSchema, LatticeGetAddressesFlag, LatticeSignBlsDst, LatticeSignCurve, LatticeSignEncoding, LatticeSignHash } from './protocol/latticeConstants'; +import type { ActiveWallets, FirmwareArr, FirmwareConstants, WalletPath } from './types/index.js'; /** * Externally exported constants used for building requests @@ -282,12 +270,7 @@ function getFwVersionConst(v: Buffer): FirmwareConstants { }; function gte(v: Buffer, exp: FirmwareArr): boolean { // Note that `v` fields come in as [fix|minor|major] - return ( - v[2] > exp[0] || - (v[2] === exp[0] && v[1] > exp[1]) || - (v[2] === exp[0] && v[1] === exp[1] && v[0] > exp[2]) || - (v[2] === exp[0] && v[1] === exp[1] && v[0] === exp[2]) - ); + return v[2] > exp[0] || (v[2] === exp[0] && v[1] > exp[1]) || (v[2] === exp[0] && v[1] === exp[1] && v[0] > exp[2]) || (v[2] === exp[0] && v[1] === exp[1] && v[0] === exp[2]); } // Very old legacy versions do not give a version number const legacy = v.length === 0; @@ -412,10 +395,7 @@ function getFwVersionConst(v: Buffer): FirmwareConstants { SOLANA: EXTERNAL.SIGNING.ENCODINGS.SOLANA, }; // Supported flags for `getAddresses` - c.getAddressFlags = [ - EXTERNAL.GET_ADDR_FLAGS.ED25519_PUB, - EXTERNAL.GET_ADDR_FLAGS.SECP256K1_PUB, - ]; + c.getAddressFlags = [EXTERNAL.GET_ADDR_FLAGS.ED25519_PUB, EXTERNAL.GET_ADDR_FLAGS.SECP256K1_PUB]; // We updated the max number of params in EIP712 types c.eip712MaxTypeParams = 36; } @@ -446,8 +426,7 @@ function getFwVersionConst(v: Buffer): FirmwareConstants { // V0.17.0 added support for BLS12-381-G1 pubkeys and G2 sigs if (!legacy && gte(v, [0, 17, 0])) { c.getAddressFlags.push(EXTERNAL.GET_ADDR_FLAGS.BLS12_381_G1_PUB); - c.genericSigning.encodingTypes.ETH_DEPOSIT = - EXTERNAL.SIGNING.ENCODINGS.ETH_DEPOSIT; + c.genericSigning.encodingTypes.ETH_DEPOSIT = EXTERNAL.SIGNING.ENCODINGS.ETH_DEPOSIT; } // --- V0.18.X --- @@ -469,8 +448,7 @@ function getFwVersionConst(v: Buffer): FirmwareConstants { const ASCII_REGEX = /^[\u0000-\u007F]+$/; /** @internal */ -const EXTERNAL_NETWORKS_BY_CHAIN_ID_URL = - 'https://gridplus.github.io/chains/chains.json'; +const EXTERNAL_NETWORKS_BY_CHAIN_ID_URL = 'https://gridplus.github.io/chains/chains.json'; /** @internal - Max number of addresses to fetch */ const MAX_ADDR = 10; @@ -524,67 +502,25 @@ export const DEFAULT_ACTIVE_WALLETS: ActiveWallets = { }; /** @internal */ -export const DEFAULT_ETH_DERIVATION: WalletPath = [ - HARDENED_OFFSET + 44, - HARDENED_OFFSET + 60, - HARDENED_OFFSET, - 0, - 0, -]; +export const DEFAULT_ETH_DERIVATION: WalletPath = [HARDENED_OFFSET + 44, HARDENED_OFFSET + 60, HARDENED_OFFSET, 0, 0]; /** @internal */ -export const BTC_LEGACY_DERIVATION = [ - HARDENED_OFFSET + 44, - HARDENED_OFFSET + 0, - HARDENED_OFFSET, - 0, - 0, -]; +export const BTC_LEGACY_DERIVATION = [HARDENED_OFFSET + 44, HARDENED_OFFSET + 0, HARDENED_OFFSET, 0, 0]; /** @internal */ -export const BTC_LEGACY_CHANGE_DERIVATION = [ - HARDENED_OFFSET + 44, - HARDENED_OFFSET + 0, - HARDENED_OFFSET, - 0, - 0, -]; +export const BTC_LEGACY_CHANGE_DERIVATION = [HARDENED_OFFSET + 44, HARDENED_OFFSET + 0, HARDENED_OFFSET, 0, 0]; /** @internal */ -export const BTC_SEGWIT_DERIVATION = [ - HARDENED_OFFSET + 84, - HARDENED_OFFSET, - HARDENED_OFFSET, - 0, - 0, -]; +export const BTC_SEGWIT_DERIVATION = [HARDENED_OFFSET + 84, HARDENED_OFFSET, HARDENED_OFFSET, 0, 0]; /** @internal */ -export const BTC_SEGWIT_CHANGE_DERIVATION = [ - HARDENED_OFFSET + 84, - HARDENED_OFFSET, - HARDENED_OFFSET, - 1, - 0, -]; +export const BTC_SEGWIT_CHANGE_DERIVATION = [HARDENED_OFFSET + 84, HARDENED_OFFSET, HARDENED_OFFSET, 1, 0]; /** @internal */ -export const BTC_WRAPPED_SEGWIT_DERIVATION = [ - HARDENED_OFFSET + 49, - HARDENED_OFFSET, - HARDENED_OFFSET, - 0, - 0, -]; +export const BTC_WRAPPED_SEGWIT_DERIVATION = [HARDENED_OFFSET + 49, HARDENED_OFFSET, HARDENED_OFFSET, 0, 0]; /** @internal */ -export const BTC_WRAPPED_SEGWIT_CHANGE_DERIVATION = [ - HARDENED_OFFSET + 49, - HARDENED_OFFSET, - HARDENED_OFFSET, - 0, - 0, -]; +export const BTC_WRAPPED_SEGWIT_CHANGE_DERIVATION = [HARDENED_OFFSET + 49, HARDENED_OFFSET, HARDENED_OFFSET, 0, 0]; /** * Derivation path for Bitcoin legacy xpub (BIP44). @@ -617,29 +553,13 @@ export const BTC_WRAPPED_SEGWIT_YPUB_PATH = "49'/0'/0'"; export const BTC_SEGWIT_ZPUB_PATH = "84'/0'/0'"; /** @internal */ -export const SOLANA_DERIVATION = [ - HARDENED_OFFSET + 44, - HARDENED_OFFSET + 501, - HARDENED_OFFSET, - HARDENED_OFFSET, -]; +export const SOLANA_DERIVATION = [HARDENED_OFFSET + 44, HARDENED_OFFSET + 501, HARDENED_OFFSET, HARDENED_OFFSET]; /** @internal */ -export const LEDGER_LIVE_DERIVATION = [ - HARDENED_OFFSET + 49, - HARDENED_OFFSET + 60, - HARDENED_OFFSET, - 0, - 0, -]; +export const LEDGER_LIVE_DERIVATION = [HARDENED_OFFSET + 49, HARDENED_OFFSET + 60, HARDENED_OFFSET, 0, 0]; /** @internal */ -export const LEDGER_LEGACY_DERIVATION = [ - HARDENED_OFFSET + 49, - HARDENED_OFFSET + 60, - HARDENED_OFFSET, - 0, -]; +export const LEDGER_LEGACY_DERIVATION = [HARDENED_OFFSET + 49, HARDENED_OFFSET + 60, HARDENED_OFFSET, 0]; export { ASCII_REGEX, diff --git a/packages/sdk/src/ethereum.ts b/packages/sdk/src/ethereum.ts index 15f2ae82..d1383bad 100644 --- a/packages/sdk/src/ethereum.ts +++ b/packages/sdk/src/ethereum.ts @@ -7,48 +7,21 @@ import cbor from 'cbor'; import bdec from 'cbor-bigdecimal'; import { Hash } from 'ox'; import secp256k1 from 'secp256k1'; -import { - type Hex, - type TransactionSerializable, - hexToNumber, - serializeTransaction, -} from 'viem'; -import { - ASCII_REGEX, - EXTERNAL, - HANDLE_LARGER_CHAIN_ID, - MAX_CHAIN_ID_BYTES, - ethMsgProtocol, -} from './constants'; +import { type Hex, type TransactionSerializable, hexToNumber, serializeTransaction } from 'viem'; +import { ASCII_REGEX, EXTERNAL, HANDLE_LARGER_CHAIN_ID, MAX_CHAIN_ID_BYTES, ethMsgProtocol } from './constants'; import { buildGenericSigningMsgRequest } from './genericSigning'; import { LatticeSignSchema } from './protocol'; import { type FlexibleTransaction, TransactionSchema } from './schemas'; -import { - type FirmwareConstants, - type SigningPath, - TRANSACTION_TYPE, - type TransactionRequest, -} from './types'; -import { - buildSignerPathBuf, - convertRecoveryToV, - ensureHexBuffer, - fixLen, - isAsciiStr, - splitFrames, -} from './util'; +import { type FirmwareConstants, type SigningPath, TRANSACTION_TYPE, type TransactionRequest } from './types'; +import { buildSignerPathBuf, convertRecoveryToV, ensureHexBuffer, fixLen, isAsciiStr, splitFrames } from './util'; const { ecdsaRecover } = secp256k1; bdec(cbor); const buildEthereumMsgRequest = (input) => { - if (!input.payload || !input.protocol || !input.signerPath) - throw new Error( - 'You must provide `payload`, `signerPath`, and `protocol` arguments in the messsage request', - ); - if (input.signerPath.length > 5 || input.signerPath.length < 2) - throw new Error('Please provide a signer path with 2-5 indices'); + if (!input.payload || !input.protocol || !input.signerPath) throw new Error('You must provide `payload`, `signerPath`, and `protocol` arguments in the messsage request'); + if (input.signerPath.length > 5 || input.signerPath.length < 2) throw new Error('Please provide a signer path with 2-5 indices'); const req = { schema: LatticeSignSchema.ethereumMsg, payload: null, @@ -59,10 +32,7 @@ const buildEthereumMsgRequest = (input) => { case 'signPersonal': return buildPersonalSignRequest(req, input); case 'eip712': - if (!input.fwConstants.eip712Supported) - throw new Error( - 'EIP712 is not supported by your Lattice firmware version. Please upgrade.', - ); + if (!input.fwConstants.eip712Supported) throw new Error('EIP712 is not supported by your Lattice firmware version. Please upgrade.'); return buildEIP712Request(req, input); default: throw new Error('Unsupported protocol'); @@ -75,13 +45,7 @@ const validateEthereumMsgResponse = (res, req) => { if (input.protocol === 'signPersonal') { // NOTE: We are currently hardcoding networkID=1 and useEIP155=false but these // may be configurable in future versions - const hash = prehash - ? prehash - : Buffer.from( - Hash.keccak256( - Buffer.concat([get_personal_sign_prefix(msg.length), msg]), - ), - ); + const hash = prehash ? prehash : Buffer.from(Hash.keccak256(Buffer.concat([get_personal_sign_prefix(msg.length), msg]))); // Get recovery param with a `v` value of [27,28] by setting `useEIP155=false` return addRecoveryParam(hash, sig, signer, { chainId: 1, @@ -92,21 +56,13 @@ const validateEthereumMsgResponse = (res, req) => { // This payload has been parsed with forJSParser=true, converting all numbers // to the format that TypedDataUtils.eip712Hash expects const rawPayloadForHashing = req.validationPayload || req.input.payload; - const payloadForHashing = req.validationPayload - ? cloneTypedDataPayload(req.validationPayload) - : normalizeTypedDataForHashing(rawPayloadForHashing); - const encoded = TypedDataUtils.eip712Hash( - payloadForHashing, - SignTypedDataVersion.V4, - ); + const payloadForHashing = req.validationPayload ? cloneTypedDataPayload(req.validationPayload) : normalizeTypedDataForHashing(rawPayloadForHashing); + const encoded = TypedDataUtils.eip712Hash(payloadForHashing, SignTypedDataVersion.V4); const digest = prehash ? prehash : encoded; // Parse chainId - it could be a number, hex string, decimal string, or bigint - let chainId = - input.payload.domain?.chainId || payloadForHashing.domain?.chainId; + let chainId = input.payload.domain?.chainId || payloadForHashing.domain?.chainId; if (typeof chainId === 'string') { - chainId = chainId.startsWith('0x') - ? Number.parseInt(chainId, 16) - : Number.parseInt(chainId, 10); + chainId = chainId.startsWith('0x') ? Number.parseInt(chainId, 16) : Number.parseInt(chainId, 10); } else if (typeof chainId === 'bigint') { chainId = Number(chainId); } @@ -127,10 +83,7 @@ function normalizeTypedDataForHashing(value: any): any { if (/^0x[0-9a-fA-F]+$/.test(trimmed)) { try { const asBigInt = BigInt(trimmed); - if ( - asBigInt <= BigInt(Number.MAX_SAFE_INTEGER) && - asBigInt >= BigInt(Number.MIN_SAFE_INTEGER) - ) { + if (asBigInt <= BigInt(Number.MAX_SAFE_INTEGER) && asBigInt >= BigInt(Number.MIN_SAFE_INTEGER)) { return Number(asBigInt); } return asBigInt.toString(10); @@ -151,14 +104,7 @@ function normalizeTypedDataForHashing(value: any): any { return Number.isSafeInteger(asNumber) ? asNumber : value.toString(10); } - if ( - value && - typeof value === 'object' && - typeof value.toString === 'function' && - value.constructor && - value.constructor.name === 'BN' && - typeof value.toArray === 'function' - ) { + if (value && typeof value === 'object' && typeof value.toString === 'function' && value.constructor && value.constructor.name === 'BN' && typeof value.toArray === 'function') { const str = value.toString(10); const asNumber = Number(str); return Number.isSafeInteger(asNumber) ? asNumber : str; @@ -207,12 +153,7 @@ function basicTypedDataClone(value: T): T { if (BN.isBigNumber(value)) { return new BN(value) as T; } - if ( - value && - typeof value === 'object' && - (value as { constructor?: { name?: string } }).constructor?.name === 'BN' && - typeof (value as { clone?: () => unknown }).clone === 'function' - ) { + if (value && typeof value === 'object' && (value as { constructor?: { name?: string } }).constructor?.name === 'BN' && typeof (value as { clone?: () => unknown }).clone === 'function') { return (value as unknown as { clone: () => unknown }).clone() as T; } if (value instanceof Date) { @@ -227,32 +168,20 @@ function basicTypedDataClone(value: T): T { type StructuredCloneFn = (value: T, transfer?: unknown) => T; const structuredCloneFn: StructuredCloneFn | null = - typeof globalThis !== 'undefined' && - typeof (globalThis as { structuredClone?: unknown }).structuredClone === - 'function' - ? (globalThis as { structuredClone: StructuredCloneFn }).structuredClone - : null; + typeof globalThis !== 'undefined' && typeof (globalThis as { structuredClone?: unknown }).structuredClone === 'function' ? (globalThis as { structuredClone: StructuredCloneFn }).structuredClone : null; const buildEthereumTxRequest = (data) => { try { let { chainId = 1 } = data; const { signerPath, eip155 = null, fwConstants, type = null } = data; - const { - contractDeployKey, - extraDataFrameSz, - extraDataMaxFrames, - prehashAllowed, - } = fwConstants; + const { contractDeployKey, extraDataFrameSz, extraDataMaxFrames, prehashAllowed } = fwConstants; const EXTRA_DATA_ALLOWED = extraDataFrameSz > 0 && extraDataMaxFrames > 0; const MAX_BASE_DATA_SZ = fwConstants.ethMaxDataSz; const VAR_PATH_SZ = fwConstants.varAddrPathSzAllowed; // Sanity checks: // There are a handful of named chains we allow the user to reference (`chainIds`) // Custom chainIDs should be either numerical or hex strings - if ( - typeof chainId !== 'number' && - isValidChainIdHexNumStr(chainId) === false - ) { + if (typeof chainId !== 'number' && isValidChainIdHexNumStr(chainId) === false) { chainId = chainIds[chainId]; } // If this was not a custom chainID and we cannot find the name of it, exit @@ -262,22 +191,15 @@ const buildEthereumTxRequest = (data) => { // Is this a contract deployment? if (data.to === null && !contractDeployKey) { - throw new Error( - 'Contract deployment not supported. Please update your Lattice firmware.', - ); + throw new Error('Contract deployment not supported. Please update your Lattice firmware.'); } const isDeployment = data.to === null && contractDeployKey; // We support eip1559 and eip2930 types (as well as legacy) - const eip1559IsAllowed = - fwConstants.allowedEthTxTypes && - fwConstants.allowedEthTxTypes.indexOf(2) > -1; - const eip2930IsAllowed = - fwConstants.allowedEthTxTypes && - fwConstants.allowedEthTxTypes.indexOf(1) > -1; + const eip1559IsAllowed = fwConstants.allowedEthTxTypes && fwConstants.allowedEthTxTypes.indexOf(2) > -1; + const eip2930IsAllowed = fwConstants.allowedEthTxTypes && fwConstants.allowedEthTxTypes.indexOf(1) > -1; const isEip1559 = eip1559IsAllowed && (type === 2 || type === 'eip1559'); const isEip2930 = eip2930IsAllowed && (type === 1 || type === 'eip2930'); - if (type !== null && !isEip1559 && !isEip2930) - throw new Error('Unsupported Ethereum transaction type'); + if (type !== null && !isEip1559 && !isEip2930) throw new Error('Unsupported Ethereum transaction type'); // Determine if we should use EIP155 given the chainID. // If we are explicitly told to use eip155, we will use it. Otherwise, // we will look up if the specified chainId is associated with a chain @@ -326,10 +248,7 @@ const buildEthereumTxRequest = (data) => { let maxPriorityFeePerGasBytes: Buffer; let maxFeePerGasBytes: Buffer; if (isEip1559) { - if (!data.maxPriorityFeePerGas) - throw new Error( - 'EIP1559 transactions must include `maxPriorityFeePerGas`', - ); + if (!data.maxPriorityFeePerGas) throw new Error('EIP1559 transactions must include `maxPriorityFeePerGas`'); maxPriorityFeePerGasBytes = ensureHexBuffer(data.maxPriorityFeePerGas); rawTx.push(maxPriorityFeePerGasBytes); maxFeePerGasBytes = ensureHexBuffer(data.maxFeePerGas); @@ -386,8 +305,7 @@ const buildEthereumTxRequest = (data) => { if (useChainIdBuffer(chainId) === true) { chainIdBuf = getChainIdBuf(chainId); chainIdBufSz = chainIdBuf.length; - if (chainIdBufSz > MAX_CHAIN_ID_BYTES) - throw new Error('ChainID provided is too large.'); + if (chainIdBufSz > MAX_CHAIN_ID_BYTES) throw new Error('ChainID provided is too large.'); // Signal to Lattice firmware that it needs to read the chainId from the tx.data buffer txReqPayload.writeUInt8(HANDLE_LARGER_CHAIN_ID, off); off++; @@ -435,12 +353,8 @@ const buildEthereumTxRequest = (data) => { if (isEip1559) { txReqPayload.writeUInt8(2, off); off += 1; // Eip1559 type enum value - if (maxPriorityFeePerGasBytes.length > 8) - throw new Error('maxPriorityFeePerGasBytes too large'); - maxPriorityFeePerGasBytes.copy( - txReqPayload, - off + (8 - maxPriorityFeePerGasBytes.length), - ); + if (maxPriorityFeePerGasBytes.length > 8) throw new Error('maxPriorityFeePerGasBytes too large'); + maxPriorityFeePerGasBytes.copy(txReqPayload, off + (8 - maxPriorityFeePerGasBytes.length)); off += 8; // Skip EIP1559 params } else if (isEip2930) { txReqPayload.writeUInt8(1, off); @@ -468,27 +382,15 @@ const buildEthereumTxRequest = (data) => { if (dataSz > MAX_BASE_DATA_SZ) { // Determine sizes and run through sanity checks const totalSz = dataSz + chainIdExtraSz; - const maxSzAllowed = - MAX_BASE_DATA_SZ + extraDataMaxFrames * extraDataFrameSz; + const maxSzAllowed = MAX_BASE_DATA_SZ + extraDataMaxFrames * extraDataFrameSz; if (prehashAllowed && totalSz > maxSzAllowed) { // If this payload is too large to send, but the Lattice allows a prehashed message, do that - prehash = Buffer.from( - Hash.keccak256(get_rlp_encoded_preimage(rawTx, type)), - ); + prehash = Buffer.from(Hash.keccak256(get_rlp_encoded_preimage(rawTx, type))); } else { - if ( - !EXTRA_DATA_ALLOWED || - (EXTRA_DATA_ALLOWED && totalSz > maxSzAllowed) - ) - throw new Error( - `Data field too large (got ${dataBytes.length}; must be <=${maxSzAllowed - chainIdExtraSz} bytes)`, - ); + if (!EXTRA_DATA_ALLOWED || (EXTRA_DATA_ALLOWED && totalSz > maxSzAllowed)) throw new Error(`Data field too large (got ${dataBytes.length}; must be <=${maxSzAllowed - chainIdExtraSz} bytes)`); // Split overflow data into extraData frames - const frames = splitFrames( - dataToCopy.slice(MAX_BASE_DATA_SZ), - extraDataFrameSz, - ); + const frames = splitFrames(dataToCopy.slice(MAX_BASE_DATA_SZ), extraDataFrameSz); frames.forEach((frame) => { const szLE = Buffer.alloc(4); szLE.writeUInt32LE(frame.length, 0); @@ -498,9 +400,7 @@ const buildEthereumTxRequest = (data) => { } else if (PREHASH_UNSUPPORTED) { // If something is unsupported in firmware but we want to allow such transactions, // we prehash the message here. - prehash = Buffer.from( - Hash.keccak256(get_rlp_encoded_preimage(rawTx, type)), - ); + prehash = Buffer.from(Hash.keccak256(get_rlp_encoded_preimage(rawTx, type))); } // Write the data size (does *NOT* include the chainId buffer, if that exists) @@ -551,9 +451,7 @@ function stripZeros(a) { // and attah the full signature to the end of the transaction payload const buildEthRawTx = (tx, sig, address) => { // RLP-encode the data we sent to the lattice - const hash = Buffer.from( - Hash.keccak256(get_rlp_encoded_preimage(tx.rawTx, tx.type)), - ); + const hash = Buffer.from(Hash.keccak256(get_rlp_encoded_preimage(tx.rawTx, tx.type))); const newSig = addRecoveryParam(hash, sig, address, tx); // Use the signature to generate a new raw transaction payload // Strip the last 3 items and replace them with signature components @@ -564,14 +462,9 @@ const buildEthRawTx = (tx, sig, address) => { newRawTx.push(stripZeros(newSig.r)); newRawTx.push(stripZeros(newSig.s)); const rlpEncoded = Buffer.from(RLP.encode(newRawTx)); - const rlpEncodedWithSig = tx.type - ? Buffer.concat([Buffer.from([tx.type]), rlpEncoded]) - : rlpEncoded; - - if ( - tx.type === TRANSACTION_TYPE.EIP7702_AUTH || - tx.type === TRANSACTION_TYPE.EIP7702_AUTH_LIST - ) { + const rlpEncodedWithSig = tx.type ? Buffer.concat([Buffer.from([tx.type]), rlpEncoded]) : rlpEncoded; + + if (tx.type === TRANSACTION_TYPE.EIP7702_AUTH || tx.type === TRANSACTION_TYPE.EIP7702_AUTH_LIST) { // For EIP-7702 transactions, we return just the hex string return rlpEncodedWithSig.toString('hex'); } @@ -584,11 +477,8 @@ export function addRecoveryParam(hashBuf, sig, address, txData = {}) { try { // Rebuild the keccak256 hash here so we can `ecrecover` const hash = new Uint8Array(hashBuf); - const expectedAddrBuf = Buffer.isBuffer(address) - ? address - : ensureHexBuffer(address, false); - if (expectedAddrBuf.length !== 20) - throw new Error('Invalid signer address provided.'); + const expectedAddrBuf = Buffer.isBuffer(address) ? address : ensureHexBuffer(address, false); + if (expectedAddrBuf.length !== 20) throw new Error('Invalid signer address provided.'); let v = 0; // Fix signature componenet lengths to 32 bytes each const r = fixLen(sig.r, 32); @@ -617,9 +507,7 @@ export function addRecoveryParam(hashBuf, sig, address, txData = {}) { return sig; } else { // If neither is a match, we should return an error - throw new Error( - `Invalid Ethereum signature returned. expected=${expectedAddrHex}, recovered=${recoveredAddrs.join(',')}`, - ); + throw new Error(`Invalid Ethereum signature returned. expected=${expectedAddrHex}, recovered=${recoveredAddrs.join(',')}`); } } catch (err) { if (err instanceof Error) throw err; @@ -631,10 +519,7 @@ export function addRecoveryParam(hashBuf, sig, address, txData = {}) { * Normalize Lattice signature components to viem format. * Handles Buffer v value conversion and yParity vs v for different transaction types. */ -export function normalizeLatticeSignature( - latticeResult: any, - originalTx: TransactionSerializable, -) { +export function normalizeLatticeSignature(latticeResult: any, originalTx: TransactionSerializable) { // Convert Buffer v value to number let vValue: number; if (Buffer.isBuffer(latticeResult.sig.v)) { @@ -693,8 +578,7 @@ export function normalizeLatticeSignature( } // Convert an RLP-serialized transaction (plus signature) into a transaction hash -const hashTransaction = (serializedTx) => - Hash.keccak256(Buffer.from(serializedTx, 'hex')); +const hashTransaction = (serializedTx) => Hash.keccak256(Buffer.from(serializedTx, 'hex')); // Returns address string given public key buffer function pubToAddrStr(pub) { @@ -804,14 +688,9 @@ function buildPersonalSignRequest(req, input) { if (typeof input.payload === 'string') { if (input.payload.slice(0, 2) === '0x') { payload = ensureHexBuffer(input.payload); - displayHex = - false === - ASCII_REGEX.test(Buffer.from(input.payload.slice(2), 'hex').toString()); + displayHex = false === ASCII_REGEX.test(Buffer.from(input.payload.slice(2), 'hex').toString()); } else { - if (false === isAsciiStr(input.payload)) - throw new Error( - 'Currently, the Lattice can only display ASCII strings.', - ); + if (false === isAsciiStr(input.payload)) throw new Error('Currently, the Lattice can only display ASCII strings.'); payload = Buffer.from(input.payload); } } else if (typeof input.displayHex === 'boolean') { @@ -827,8 +706,7 @@ function buildPersonalSignRequest(req, input) { displayHex = false === ASCII_REGEX.test(input.payload.toString()); } const fwConst = input.fwConstants; - let maxSzAllowed = - MAX_BASE_MSG_SZ + fwConst.extraDataMaxFrames * fwConst.extraDataFrameSz; + let maxSzAllowed = MAX_BASE_MSG_SZ + fwConst.extraDataMaxFrames * fwConst.extraDataFrameSz; if (fwConst.personalSignHeaderSz) { // Account for the personal_sign header string maxSzAllowed -= fwConst.personalSignHeaderSz; @@ -839,11 +717,7 @@ function buildPersonalSignRequest(req, input) { off += 1; req.payload.writeUInt16LE(payload.length, off); off += 2; - const prehash = Buffer.from( - Hash.keccak256( - Buffer.concat([get_personal_sign_prefix(payload.length), payload]), - ), - ); + const prehash = Buffer.from(Hash.keccak256(Buffer.concat([get_personal_sign_prefix(payload.length), payload]))); prehash.copy(req.payload, off); req.prehash = prehash; } else { @@ -863,8 +737,7 @@ function buildPersonalSignRequest(req, input) { } function buildEIP712Request(req, input) { - const { ethMaxMsgSz, varAddrPathSzAllowed, eip712MaxTypeParams } = - input.fwConstants; + const { ethMaxMsgSz, varAddrPathSzAllowed, eip712MaxTypeParams } = input.fwConstants; const { TYPED_DATA } = ethMsgProtocol; const L = 24 + ethMaxMsgSz + 4; let off = 0; @@ -872,22 +745,14 @@ function buildEIP712Request(req, input) { req.payload.writeUInt8(TYPED_DATA.enumIdx, 0); off += 1; // Write the signer path - const signerPathBuf = buildSignerPathBuf( - input.signerPath, - varAddrPathSzAllowed, - ); + const signerPathBuf = buildSignerPathBuf(input.signerPath, varAddrPathSzAllowed); signerPathBuf.copy(req.payload, off); off += signerPathBuf.length; // Parse/clean the EIP712 payload, serialize with CBOR, and write to the payload const data = cloneTypedDataPayload(input.payload); - if (!data.primaryType || !data.types[data.primaryType]) - throw new Error( - 'primaryType must be specified and the type must be included.', - ); - if (!data.message || !data.domain) - throw new Error('message and domain must be specified.'); - if (0 > Object.keys(data.types).indexOf('EIP712Domain')) - throw new Error('EIP712Domain type must be defined.'); + if (!data.primaryType || !data.types[data.primaryType]) throw new Error('primaryType must be specified and the type must be included.'); + if (!data.message || !data.domain) throw new Error('message and domain must be specified.'); + if (0 > Object.keys(data.types).indexOf('EIP712Domain')) throw new Error('EIP712Domain type must be defined.'); // Parse the payload to ensure we have valid EIP712 data types and that // they are encoded such that Lattice firmware can parse them. // We need two different encodings: one to send to the Lattice in a format that plays @@ -896,33 +761,17 @@ function buildEIP712Request(req, input) { // IMPORTANT: Create a new object for the validation payload instead of modifying input.payload // in place, so that validation uses the correctly formatted data const validationPayload = cloneTypedDataPayload(data); - validationPayload.message = parseEIP712Msg( - cloneTypedDataPayload(data.message), - cloneTypedDataPayload(data.primaryType), - cloneTypedDataPayload(data.types), - true, - ); - validationPayload.domain = parseEIP712Msg( - cloneTypedDataPayload(data.domain), - 'EIP712Domain', - cloneTypedDataPayload(data.types), - true, - ); + validationPayload.message = parseEIP712Msg(cloneTypedDataPayload(data.message), cloneTypedDataPayload(data.primaryType), cloneTypedDataPayload(data.types), true); + validationPayload.domain = parseEIP712Msg(cloneTypedDataPayload(data.domain), 'EIP712Domain', cloneTypedDataPayload(data.types), true); // Store the validation payload separately without modifying input.payload req.validationPayload = validationPayload; data.domain = parseEIP712Msg(data.domain, 'EIP712Domain', data.types, false); - data.message = parseEIP712Msg( - data.message, - data.primaryType, - data.types, - false, - ); + data.message = parseEIP712Msg(data.message, data.primaryType, data.types, false); // Now build the message to be sent to the Lattice const payload = Buffer.from(cbor.encode(data)); const fwConst = input.fwConstants; - const maxSzAllowed = - ethMaxMsgSz + fwConst.extraDataMaxFrames * fwConst.extraDataFrameSz; + const maxSzAllowed = ethMaxMsgSz + fwConst.extraDataMaxFrames * fwConst.extraDataFrameSz; // Determine if we need to prehash let shouldPrehash = payload.length > maxSzAllowed; Object.keys(data.types).forEach((k) => { @@ -934,10 +783,7 @@ function buildEIP712Request(req, input) { // If this payload is too large to send, but the Lattice allows a prehashed message, do that req.payload.writeUInt16LE(payload.length, off); off += 2; - const prehash = TypedDataUtils.eip712Hash( - req.validationPayload, - SignTypedDataVersion.V4, - ); + const prehash = TypedDataUtils.eip712Hash(req.validationPayload, SignTypedDataVersion.V4); const prehashBuf = Buffer.from(prehash); prehashBuf.copy(req.payload, off); req.prehash = prehash; @@ -955,28 +801,17 @@ function buildEIP712Request(req, input) { } function getExtraData(payload, input) { - const { ethMaxMsgSz, extraDataFrameSz, extraDataMaxFrames } = - input.fwConstants; + const { ethMaxMsgSz, extraDataFrameSz, extraDataMaxFrames } = input.fwConstants; const MAX_BASE_MSG_SZ = ethMaxMsgSz; const EXTRA_DATA_ALLOWED = extraDataFrameSz > 0 && extraDataMaxFrames > 0; const extraDataPayloads = []; if (payload.length > MAX_BASE_MSG_SZ) { // Determine sizes and run through sanity checks - const maxSzAllowed = - MAX_BASE_MSG_SZ + extraDataMaxFrames * extraDataFrameSz; - if (!EXTRA_DATA_ALLOWED) - throw new Error( - `Your message is ${payload.length} bytes, but can only be a maximum of ${MAX_BASE_MSG_SZ}`, - ); - else if (EXTRA_DATA_ALLOWED && payload.length > maxSzAllowed) - throw new Error( - `Your message is ${payload.length} bytes, but can only be a maximum of ${maxSzAllowed}`, - ); + const maxSzAllowed = MAX_BASE_MSG_SZ + extraDataMaxFrames * extraDataFrameSz; + if (!EXTRA_DATA_ALLOWED) throw new Error(`Your message is ${payload.length} bytes, but can only be a maximum of ${MAX_BASE_MSG_SZ}`); + else if (EXTRA_DATA_ALLOWED && payload.length > maxSzAllowed) throw new Error(`Your message is ${payload.length} bytes, but can only be a maximum of ${maxSzAllowed}`); // Split overflow data into extraData frames - const frames = splitFrames( - payload.slice(MAX_BASE_MSG_SZ), - extraDataFrameSz, - ); + const frames = splitFrames(payload.slice(MAX_BASE_MSG_SZ), extraDataFrameSz); frames.forEach((frame) => { const szLE = Buffer.alloc(4); szLE.writeUInt32LE(frame.length, 0); @@ -990,9 +825,7 @@ function parseEIP712Msg(msg, typeName, types, forJSParser = false) { const type = types[typeName]; type.forEach((item) => { const isArrayType = item.type.indexOf('[') > -1; - const singularType = isArrayType - ? item.type.slice(0, item.type.indexOf('[')) - : item.type; + const singularType = isArrayType ? item.type.slice(0, item.type.indexOf('[')) : item.type; const isCustomType = Object.keys(types).indexOf(singularType) > -1; if (isCustomType && Array.isArray(msg)) { // For custom types we need to jump into the `msg` using the key (name of type) and @@ -1001,21 +834,11 @@ function parseEIP712Msg(msg, typeName, types, forJSParser = false) { // elementary (i.e. non-custom) type. // For arrays, we need to loop through each message item. for (let i = 0; i < msg.length; i++) { - msg[i][item.name] = parseEIP712Msg( - msg[i][item.name], - singularType, - types, - forJSParser, - ); + msg[i][item.name] = parseEIP712Msg(msg[i][item.name], singularType, types, forJSParser); } } else if (isCustomType) { // Not an array means we can jump directly into the sub-struct to convert - msg[item.name] = parseEIP712Msg( - msg[item.name], - singularType, - types, - forJSParser, - ); + msg[item.name] = parseEIP712Msg(msg[item.name], singularType, types, forJSParser); } else if (Array.isArray(msg)) { // If we have an array for this particular type and the type we are parsing // is *not* a custom type, loop through the array elements and convert the types. @@ -1025,38 +848,22 @@ function parseEIP712Msg(msg, typeName, types, forJSParser = false) { // This code is not reachable for custom types so we assume these are arrays of // elementary types. for (let j = 0; j < msg[i][item.name].length; j++) { - msg[i][item.name][j] = parseEIP712Item( - msg[i][item.name][j], - singularType, - forJSParser, - ); + msg[i][item.name][j] = parseEIP712Item(msg[i][item.name][j], singularType, forJSParser); } } else { // Non-arrays parse + replace one value for the elementary type - msg[i][item.name] = parseEIP712Item( - msg[i][item.name], - singularType, - forJSParser, - ); + msg[i][item.name] = parseEIP712Item(msg[i][item.name], singularType, forJSParser); } } } else if (isArrayType) { // If we have an elementary array type and a non-array message level, //loop through the array and parse + replace each item individually. for (let i = 0; i < msg[item.name].length; i++) { - msg[item.name][i] = parseEIP712Item( - msg[item.name][i], - singularType, - forJSParser, - ); + msg[item.name][i] = parseEIP712Item(msg[item.name][i], singularType, forJSParser); } } else { // If this is a singular elementary type, simply parse + replace. - msg[item.name] = parseEIP712Item( - msg[item.name], - singularType, - forJSParser, - ); + msg[item.name] = parseEIP712Item(msg[item.name], singularType, forJSParser); } }); @@ -1079,8 +886,7 @@ function parseEIP712Item(data, type, forJSParser = false) { if (data.length === 0) { data = Buffer.alloc(nBytes); } - if (data.length !== nBytes) - throw new Error(`Expected ${type} type, but got ${data.length} bytes`); + if (data.length !== nBytes) throw new Error(`Expected ${type} type, but got ${data.length} bytes`); if (forJSParser) { // For EIP712 encoding module it's easier to encode hex strings data = `0x${data.toString('hex')}`; @@ -1092,19 +898,12 @@ function parseEIP712Item(data, type, forJSParser = false) { if (data.length === 0) { data = Buffer.alloc(20); } - if (data.length !== 20) - throw new Error( - `Address type must be 20 bytes, but got ${data.length} bytes`, - ); + if (data.length !== 20) throw new Error(`Address type must be 20 bytes, but got ${data.length} bytes`); // For EIP712 encoding module it's easier to encode hex strings if (forJSParser) { data = `0x${data.toString('hex')}`; } - } else if ( - ethMsgProtocol.TYPED_DATA.typeCodes[type] && - type.indexOf('uint') === -1 && - type.indexOf('int') > -1 - ) { + } else if (ethMsgProtocol.TYPED_DATA.typeCodes[type] && type.indexOf('uint') === -1 && type.indexOf('int') > -1) { // Handle signed integers using bignumber.js directly if (forJSParser) { // For EIP712 encoding in this module we need hex strings for signed ints too @@ -1122,10 +921,7 @@ function parseEIP712Item(data, type, forJSParser = false) { // worked (borc is a supposedly "browser compatible" version of cbor) data = new BN(data); } - } else if ( - ethMsgProtocol.TYPED_DATA.typeCodes[type] && - (type.indexOf('uint') > -1 || type.indexOf('int') > -1) - ) { + } else if (ethMsgProtocol.TYPED_DATA.typeCodes[type] && (type.indexOf('uint') > -1 || type.indexOf('int') > -1)) { // For uints, convert to a buffer and do some sanity checking. // Note that we could probably just use bignumber.js directly as we do with // signed ints, but this code is battle tested and we don't want to change it. @@ -1151,18 +947,12 @@ function parseEIP712Item(data, type, forJSParser = false) { } function get_personal_sign_prefix(L) { - return Buffer.from( - `\u0019Ethereum Signed Message:\n${L.toString()}`, - 'utf-8', - ); + return Buffer.from(`\u0019Ethereum Signed Message:\n${L.toString()}`, 'utf-8'); } function get_rlp_encoded_preimage(rawTx, txType) { if (txType) { - return Buffer.concat([ - Buffer.from([txType]), - Buffer.from(RLP.encode(rawTx)), - ]); + return Buffer.concat([Buffer.from([txType]), Buffer.from(RLP.encode(rawTx))]); } else { return Buffer.from(RLP.encode(rawTx)); } @@ -1180,9 +970,7 @@ function get_rlp_encoded_preimage(rawTx, txType) { * hex strings, numbers, bigints). * @returns A `viem`-compatible `TransactionSerializable` object. */ -export const normalizeToViemTransaction = ( - tx: unknown, -): TransactionSerializable => { +export const normalizeToViemTransaction = (tx: unknown): TransactionSerializable => { const parsed = TransactionSchema.parse(tx); return { @@ -1195,13 +983,9 @@ export const normalizeToViemTransaction = ( chainId: parsed.chainId, gasPrice: 'gasPrice' in parsed ? parsed.gasPrice : undefined, maxFeePerGas: 'maxFeePerGas' in parsed ? parsed.maxFeePerGas : undefined, - maxPriorityFeePerGas: - 'maxPriorityFeePerGas' in parsed - ? parsed.maxPriorityFeePerGas - : undefined, + maxPriorityFeePerGas: 'maxPriorityFeePerGas' in parsed ? parsed.maxPriorityFeePerGas : undefined, accessList: 'accessList' in parsed ? parsed.accessList : undefined, - authorizationList: - 'authorizationList' in parsed ? parsed.authorizationList : undefined, + authorizationList: 'authorizationList' in parsed ? parsed.authorizationList : undefined, }; }; @@ -1209,9 +993,7 @@ export const normalizeToViemTransaction = ( * Convert Ethereum transaction to serialized bytes for generic signing. * Bridge function for firmware v0.15.0+ which removed legacy ETH signing paths. */ -const convertEthereumTransactionToGenericRequest = ( - req: FlexibleTransaction, -) => { +const convertEthereumTransactionToGenericRequest = (req: FlexibleTransaction) => { // Use the unified normalization and serialization pipeline. // 1. Normalize the potentially varied input to a standard viem format. const viemTx = normalizeToViemTransaction(req); @@ -1230,9 +1012,7 @@ type EthereumGenericSigningRequestParams = FlexibleTransaction & { * Build complete generic signing request for Ethereum transactions. * One-step function combining transaction conversion and generic signing setup. */ -export const buildEthereumGenericSigningRequest = ( - req: EthereumGenericSigningRequestParams, -) => { +export const buildEthereumGenericSigningRequest = (req: EthereumGenericSigningRequestParams) => { const { fwConstants, signerPath, ...txData } = req; const payload = convertEthereumTransactionToGenericRequest(txData); @@ -1254,13 +1034,8 @@ export const buildEthereumGenericSigningRequest = ( * @returns The serialized transaction as a hex string */ export function serializeEIP7702Transaction(tx: TransactionRequest): Hex { - if ( - tx.type !== TRANSACTION_TYPE.EIP7702_AUTH_LIST && - tx.type !== TRANSACTION_TYPE.EIP7702_AUTH - ) { - throw new Error( - `Only EIP-7702 auth transactions (type ${TRANSACTION_TYPE.EIP7702_AUTH}) and auth-list transactions (type ${TRANSACTION_TYPE.EIP7702_AUTH_LIST}) are supported`, - ); + if (tx.type !== TRANSACTION_TYPE.EIP7702_AUTH_LIST && tx.type !== TRANSACTION_TYPE.EIP7702_AUTH) { + throw new Error(`Only EIP-7702 auth transactions (type ${TRANSACTION_TYPE.EIP7702_AUTH}) and auth-list transactions (type ${TRANSACTION_TYPE.EIP7702_AUTH_LIST}) are supported`); } // Type guard to ensure we have an EIP7702 transaction with appropriate authorization data @@ -1268,18 +1043,14 @@ export function serializeEIP7702Transaction(tx: TransactionRequest): Hex { const hasSingleAuth = 'authorization' in tx; if (!hasAuthList && !hasSingleAuth) { - throw new Error( - 'Transaction does not have authorization or authorizationList property', - ); + throw new Error('Transaction does not have authorization or authorizationList property'); } // For type 4 transactions, convert single authorization to array format let authorizationList: any[]; if (tx.type === TRANSACTION_TYPE.EIP7702_AUTH) { if (!hasSingleAuth) { - throw new Error( - 'EIP-7702 auth transaction (type 4) must contain authorization property', - ); + throw new Error('EIP-7702 auth transaction (type 4) must contain authorization property'); } authorizationList = [(tx as any).authorization]; } else { @@ -1287,29 +1058,19 @@ export function serializeEIP7702Transaction(tx: TransactionRequest): Hex { if (hasAuthList) { authorizationList = (tx as any).authorizationList; } else { - throw new Error( - 'EIP-7702 auth list transaction (type 5) must contain authorizationList property', - ); + throw new Error('EIP-7702 auth list transaction (type 5) must contain authorizationList property'); } } // Validate that all required fields exist - if ( - !authorizationList || - !Array.isArray(authorizationList) || - authorizationList.length === 0 - ) { - throw new Error( - 'EIP-7702 transaction must contain at least one authorization', - ); + if (!authorizationList || !Array.isArray(authorizationList) || authorizationList.length === 0) { + throw new Error('EIP-7702 transaction must contain at least one authorization'); } // Validate each authorization authorizationList.forEach((auth, index) => { if (!auth.address) { - throw new Error( - `Authorization at index ${index} is missing a contract address`, - ); + throw new Error(`Authorization at index ${index} is missing a contract address`); } }); @@ -1323,21 +1084,9 @@ export function serializeEIP7702Transaction(tx: TransactionRequest): Hex { type: 'eip7702' as const, chainId: tx.chainId, nonce: tx.nonce, - maxPriorityFeePerGas: - typeof tx.maxPriorityFeePerGas === 'string' - ? BigInt(tx.maxPriorityFeePerGas) - : tx.maxPriorityFeePerGas, - maxFeePerGas: - typeof tx.maxFeePerGas === 'string' - ? BigInt(tx.maxFeePerGas) - : tx.maxFeePerGas, - gas: - typeof (tx as any).gas === 'string' - ? BigInt((tx as any).gas) - : (tx as any).gas || - (typeof (tx as any).gasLimit === 'string' - ? BigInt((tx as any).gasLimit) - : (tx as any).gasLimit), + maxPriorityFeePerGas: typeof tx.maxPriorityFeePerGas === 'string' ? BigInt(tx.maxPriorityFeePerGas) : tx.maxPriorityFeePerGas, + maxFeePerGas: typeof tx.maxFeePerGas === 'string' ? BigInt(tx.maxFeePerGas) : tx.maxFeePerGas, + gas: typeof (tx as any).gas === 'string' ? BigInt((tx as any).gas) : (tx as any).gas || (typeof (tx as any).gasLimit === 'string' ? BigInt((tx as any).gasLimit) : (tx as any).gasLimit), to: tx.to as `0x${string}`, value: typeof tx.value === 'string' ? BigInt(tx.value) : tx.value, data: tx.data || '0x', @@ -1345,17 +1094,10 @@ export function serializeEIP7702Transaction(tx: TransactionRequest): Hex { // Create the Viem-formatted authorization // Ensure proper address handling with 0x prefix const address = auth.address || ''; - const addressStr = - typeof address === 'string' - ? address.startsWith('0x') - ? address - : `0x${address}` - : '0x'; + const addressStr = typeof address === 'string' ? (address.startsWith('0x') ? address : `0x${address}`) : '0x'; if (!addressStr || addressStr === '0x') { - throw new Error( - `Authorization at index ${idx} is missing a valid address`, - ); + throw new Error(`Authorization at index ${idx} is missing a valid address`); } // Handle viem's SignedAuthorization format @@ -1374,16 +1116,7 @@ export function serializeEIP7702Transaction(tx: TransactionRequest): Hex { address: addressStr as `0x${string}`, nonce: BigInt(auth.nonce || 0), signature: { - yParity: - typeof auth.yParity === 'number' - ? auth.yParity - : typeof auth.yParity === 'string' - ? auth.yParity === '0x01' || - auth.yParity === '0x1' || - auth.yParity === '1' - ? 1 - : 0 - : 0, + yParity: typeof auth.yParity === 'number' ? auth.yParity : typeof auth.yParity === 'string' ? (auth.yParity === '0x01' || auth.yParity === '0x1' || auth.yParity === '1' ? 1 : 0) : 0, r: auth.r || '0x0', s: auth.s || '0x0', }, @@ -1396,12 +1129,7 @@ export function serializeEIP7702Transaction(tx: TransactionRequest): Hex { } export const isEip7702Transaction = (tx: TransactionRequest): boolean => { - return ( - typeof tx === 'object' && - 'type' in tx && - (tx.type === TRANSACTION_TYPE.EIP7702_AUTH_LIST || - tx.type === TRANSACTION_TYPE.EIP7702_AUTH) - ); + return typeof tx === 'object' && 'type' in tx && (tx.type === TRANSACTION_TYPE.EIP7702_AUTH_LIST || tx.type === TRANSACTION_TYPE.EIP7702_AUTH); }; export default { diff --git a/packages/sdk/src/functions/addKvRecords.ts b/packages/sdk/src/functions/addKvRecords.ts index 0ce54783..34f7eed2 100644 --- a/packages/sdk/src/functions/addKvRecords.ts +++ b/packages/sdk/src/functions/addKvRecords.ts @@ -1,17 +1,6 @@ -import { - LatticeSecureEncryptedRequestType, - encryptedSecureRequest, -} from '../protocol'; -import { - validateConnectedClient, - validateKvRecord, - validateKvRecords, -} from '../shared/validators'; -import type { - AddKvRecordsRequestFunctionParams, - FirmwareConstants, - KVRecords, -} from '../types'; +import { LatticeSecureEncryptedRequestType, encryptedSecureRequest } from '../protocol'; +import { validateConnectedClient, validateKvRecord, validateKvRecords } from '../shared/validators'; +import type { AddKvRecordsRequestFunctionParams, FirmwareConstants, KVRecords } from '../types'; /** * `addKvRecords` takes in a set of key-value records and sends a request to add them to the @@ -19,14 +8,8 @@ import type { * @category Lattice * @returns A callback with an error or null. */ -export async function addKvRecords({ - client, - records, - type, - caseSensitive, -}: AddKvRecordsRequestFunctionParams): Promise { - const { url, sharedSecret, ephemeralPub, fwConstants } = - validateConnectedClient(client); +export async function addKvRecords({ client, records, type, caseSensitive }: AddKvRecordsRequestFunctionParams): Promise { + const { url, sharedSecret, ephemeralPub, fwConstants } = validateConnectedClient(client); validateAddKvRequest({ records, fwConstants }); // Build the data for this request @@ -77,10 +60,7 @@ export const encodeAddKvRecordsRequest = ({ payload.writeUInt8(Object.keys(records).length, 0); let off = 1; Object.entries(records).forEach(([_key, _val]) => { - const { key, val } = validateKvRecord( - { key: _key, val: _val }, - fwConstants, - ); + const { key, val } = validateKvRecord({ key: _key, val: _val }, fwConstants); // Skip the ID portion. This will get added by firmware. payload.writeUInt32LE(0, off); off += 4; diff --git a/packages/sdk/src/functions/connect.ts b/packages/sdk/src/functions/connect.ts index bd03b931..10e7ca39 100644 --- a/packages/sdk/src/functions/connect.ts +++ b/packages/sdk/src/functions/connect.ts @@ -1,22 +1,11 @@ import { ProtocolConstants, connectSecureRequest } from '../protocol'; import { doesFetchWalletsOnLoad } from '../shared/predicates'; import { getSharedSecret, parseWallets } from '../shared/utilities'; -import { - validateBaseUrl, - validateDeviceId, - validateKey, -} from '../shared/validators'; -import type { - ActiveWallets, - ConnectRequestFunctionParams, - KeyPair, -} from '../types'; +import { validateBaseUrl, validateDeviceId, validateKey } from '../shared/validators'; +import type { ActiveWallets, ConnectRequestFunctionParams, KeyPair } from '../types'; import { aes256_decrypt, getP256KeyPairFromPub } from '../util'; -export async function connect({ - client, - id, -}: ConnectRequestFunctionParams): Promise { +export async function connect({ client, id }: ConnectRequestFunctionParams): Promise { const { deviceId, key, baseUrl } = validateConnectRequest({ deviceId: id, // @ts-expect-error - private access @@ -33,8 +22,7 @@ export async function connect({ // Decode response data params. // Response payload data is *not* encrypted. - const { isPaired, fwVersion, activeWallets, ephemeralPub } = - await decodeConnectResponse(respPayloadData, key); + const { isPaired, fwVersion, activeWallets, ephemeralPub } = await decodeConnectResponse(respPayloadData, key); // Update client state with response data @@ -104,8 +92,7 @@ export const decodeConnectResponse = ( ephemeralPub: KeyPair; } => { let off = 0; - const isPaired = - response.readUInt8(off) === ProtocolConstants.pairingStatus.paired; + const isPaired = response.readUInt8(off) === ProtocolConstants.pairingStatus.paired; off++; // If we are already paired, we get the next ephemeral key const pub = response.slice(off, off + 65).toString('hex'); @@ -127,10 +114,7 @@ export const decodeConnectResponse = ( const decWalletData = aes256_decrypt(encWalletData, sharedSecret); // Sanity check to make sure the last part of the decrypted data is empty. The last 2 bytes // are AES padding - if ( - decWalletData[decWalletData.length - 2] !== 0 || - decWalletData[decWalletData.length - 1] !== 0 - ) { + if (decWalletData[decWalletData.length - 2] !== 0 || decWalletData[decWalletData.length - 1] !== 0) { throw new Error('Failed to connect to Lattice.'); } const activeWallets = parseWallets(decWalletData); diff --git a/packages/sdk/src/functions/fetchActiveWallet.ts b/packages/sdk/src/functions/fetchActiveWallet.ts index c426bbfc..b414252b 100644 --- a/packages/sdk/src/functions/fetchActiveWallet.ts +++ b/packages/sdk/src/functions/fetchActiveWallet.ts @@ -1,16 +1,7 @@ import { EMPTY_WALLET_UID } from '../constants'; -import { - LatticeSecureEncryptedRequestType, - encryptedSecureRequest, -} from '../protocol'; -import { - validateActiveWallets, - validateConnectedClient, -} from '../shared/validators'; -import type { - ActiveWallets, - FetchActiveWalletRequestFunctionParams, -} from '../types'; +import { LatticeSecureEncryptedRequestType, encryptedSecureRequest } from '../protocol'; +import { validateActiveWallets, validateConnectedClient } from '../shared/validators'; +import type { ActiveWallets, FetchActiveWalletRequestFunctionParams } from '../types'; /** * Fetch the active wallet in the device. @@ -19,9 +10,7 @@ import type { * unlocked, the external interface is considered "active" and this will return its {@link Wallet} * data. Otherwise it will return the info for the internal Lattice wallet. */ -export async function fetchActiveWallet({ - client, -}: FetchActiveWalletRequestFunctionParams): Promise { +export async function fetchActiveWallet({ client }: FetchActiveWalletRequestFunctionParams): Promise { const { url, sharedSecret, ephemeralPub } = validateConnectedClient(client); const { decryptedData, newEphemeralPub } = await encryptedSecureRequest({ diff --git a/packages/sdk/src/functions/fetchDecoder.ts b/packages/sdk/src/functions/fetchDecoder.ts index 70b4367c..ca206eda 100644 --- a/packages/sdk/src/functions/fetchDecoder.ts +++ b/packages/sdk/src/functions/fetchDecoder.ts @@ -9,25 +9,15 @@ import { fetchCalldataDecoder } from '../util'; * @category Lattice * @returns An object containing the ABI and encoded definition of the contract. */ -export async function fetchDecoder({ - data, - to, - chainId, -}: TransactionRequest): Promise { +export async function fetchDecoder({ data, to, chainId }: TransactionRequest): Promise { try { const client = await getClient(); validateConnectedClient(client); const fwVersion = client.getFwVersion(); - const supportsDecoderRecursion = - fwVersion.major > 0 || fwVersion.minor >= 16; + const supportsDecoderRecursion = fwVersion.major > 0 || fwVersion.minor >= 16; - const { def } = await fetchCalldataDecoder( - data, - to, - chainId, - supportsDecoderRecursion, - ); + const { def } = await fetchCalldataDecoder(data, to, chainId, supportsDecoderRecursion); return def; } catch (error) { diff --git a/packages/sdk/src/functions/fetchEncData.ts b/packages/sdk/src/functions/fetchEncData.ts index 3b71139f..49abae5b 100644 --- a/packages/sdk/src/functions/fetchEncData.ts +++ b/packages/sdk/src/functions/fetchEncData.ts @@ -4,27 +4,13 @@ */ import { v4 as uuidV4 } from 'uuid'; import { EXTERNAL } from '../constants'; -import { - LatticeSecureEncryptedRequestType, - encryptedSecureRequest, -} from '../protocol'; +import { LatticeSecureEncryptedRequestType, encryptedSecureRequest } from '../protocol'; import { getPathStr } from '../shared/utilities'; -import { - validateConnectedClient, - validateStartPath, - validateWallet, -} from '../shared/validators'; -import type { - EIP2335KeyExportData, - EIP2335KeyExportReq, - FetchEncDataRequestFunctionParams, - FirmwareVersion, - Wallet, -} from '../types'; +import { validateConnectedClient, validateStartPath, validateWallet } from '../shared/validators'; +import type { EIP2335KeyExportData, EIP2335KeyExportReq, FetchEncDataRequestFunctionParams, FirmwareVersion, Wallet } from '../types'; const { ENC_DATA } = EXTERNAL; -const ENC_DATA_ERR_STR = - 'Unknown encrypted data export type requested. Exiting.'; +const ENC_DATA_ERR_STR = 'Unknown encrypted data export type requested. Exiting.'; const ENC_DATA_REQ_DATA_SZ = 1025; const ENC_DATA_RESP_SZ = { EIP2335: { @@ -36,13 +22,8 @@ const ENC_DATA_RESP_SZ = { }, } as const; -export async function fetchEncData({ - client, - schema, - params, -}: FetchEncDataRequestFunctionParams): Promise { - const { url, sharedSecret, ephemeralPub, fwVersion } = - validateConnectedClient(client); +export async function fetchEncData({ client, schema, params }: FetchEncDataRequestFunctionParams): Promise { + const { url, sharedSecret, ephemeralPub, fwVersion } = validateConnectedClient(client); const activeWallet = validateWallet(client.getActiveWallet()); validateFetchEncDataRequest({ params }); @@ -90,9 +71,7 @@ export const encodeFetchEncDataRequest = ({ }) => { // Check firmware version if (fwVersion.major < 1 && fwVersion.minor < 17) { - throw new Error( - 'Firmware version >=v0.17.0 is required for encrypted data export.', - ); + throw new Error('Firmware version >=v0.17.0 is required for encrypted data export.'); } // Update params depending on what type of data is being exported if (schema === ENC_DATA.SCHEMAS.BLS_KEYSTORE_EIP2335_PBKDF_V4) { @@ -150,9 +129,7 @@ export const decodeFetchEncData = ({ const dataSz = data.readUInt32LE(off); off += 4; if (dataSz !== expectedSz) { - throw new Error( - 'Invalid data returned from Lattice. Expected EIP2335 data.', - ); + throw new Error('Invalid data returned from Lattice. Expected EIP2335 data.'); } respData.iterations = data.readUInt32LE(off); off += 4; @@ -172,10 +149,7 @@ export const decodeFetchEncData = ({ } }; -const formatEIP2335ExportData = ( - resp: EIP2335KeyExportData, - path: number[], -): Buffer => { +const formatEIP2335ExportData = (resp: EIP2335KeyExportData, path: number[]): Buffer => { try { const { iterations, salt, checksum, iv, cipherText, pubkey } = resp; return Buffer.from( diff --git a/packages/sdk/src/functions/getAddresses.ts b/packages/sdk/src/functions/getAddresses.ts index da366d23..245e9ed7 100644 --- a/packages/sdk/src/functions/getAddresses.ts +++ b/packages/sdk/src/functions/getAddresses.ts @@ -1,21 +1,6 @@ -import { - LatticeGetAddressesFlag, - LatticeSecureEncryptedRequestType, - ProtocolConstants, - encryptedSecureRequest, -} from '../protocol'; -import { - validateConnectedClient, - validateIsUInt4, - validateNAddresses, - validateStartPath, - validateWallet, -} from '../shared/validators'; -import type { - FirmwareConstants, - GetAddressesRequestFunctionParams, - Wallet, -} from '../types'; +import { LatticeGetAddressesFlag, LatticeSecureEncryptedRequestType, ProtocolConstants, encryptedSecureRequest } from '../protocol'; +import { validateConnectedClient, validateIsUInt4, validateNAddresses, validateStartPath, validateWallet } from '../shared/validators'; +import type { FirmwareConstants, GetAddressesRequestFunctionParams, Wallet } from '../types'; import { isValidAssetPath } from '../util'; /** @@ -24,15 +9,8 @@ import { isValidAssetPath } from '../util'; * @category Lattice * @returns An array of addresses or public keys. */ -export async function getAddresses({ - client, - startPath: _startPath, - n: _n, - flag: _flag, - iterIdx, -}: GetAddressesRequestFunctionParams): Promise { - const { url, sharedSecret, ephemeralPub, fwConstants } = - validateConnectedClient(client); +export async function getAddresses({ client, startPath: _startPath, n: _n, flag: _flag, iterIdx }: GetAddressesRequestFunctionParams): Promise { + const { url, sharedSecret, ephemeralPub, fwConstants } = validateConnectedClient(client); const activeWallet = validateWallet(client.getActiveWallet()); const { startPath, n, flag } = validateGetAddressesRequest({ @@ -97,16 +75,10 @@ export const encodeGetAddressesRequest = ({ iterIdx?: number; }) => { const flags = fwConstants.getAddressFlags || ([] as any[]); - const isPubkeyOnly = - flags.indexOf(flag) > -1 && - (flag === LatticeGetAddressesFlag.ed25519Pubkey || - flag === LatticeGetAddressesFlag.secp256k1Pubkey || - flag === LatticeGetAddressesFlag.bls12_381Pubkey); + const isPubkeyOnly = flags.indexOf(flag) > -1 && (flag === LatticeGetAddressesFlag.ed25519Pubkey || flag === LatticeGetAddressesFlag.secp256k1Pubkey || flag === LatticeGetAddressesFlag.bls12_381Pubkey); const isXpub = flag === LatticeGetAddressesFlag.secp256k1Xpub; if (!isPubkeyOnly && !isXpub && !isValidAssetPath(startPath, fwConstants)) { - throw new Error( - 'Derivation path or flag is not supported. Try updating Lattice firmware.', - ); + throw new Error('Derivation path or flag is not supported. Try updating Lattice firmware.'); } // Ensure path depth is valid (2-5 indices) @@ -155,27 +127,17 @@ export const encodeGetAddressesRequest = ({ * @internal * @return an array of address strings or pubkey buffers */ -export const decodeGetAddressesResponse = ( - data: Buffer, - flag: number, -): Buffer[] => { +export const decodeGetAddressesResponse = (data: Buffer, flag: number): Buffer[] => { let off = 0; - const addressOffset = - flag === LatticeGetAddressesFlag.ed25519Pubkey ? 113 : 65; + const addressOffset = flag === LatticeGetAddressesFlag.ed25519Pubkey ? 113 : 65; // Look for addresses until we reach the end (a 4 byte checksum) const addrs: any[] = []; // Pubkeys are formatted differently in the response - const arePubkeys = - flag === LatticeGetAddressesFlag.secp256k1Pubkey || - flag === LatticeGetAddressesFlag.ed25519Pubkey || - flag === LatticeGetAddressesFlag.bls12_381Pubkey; + const arePubkeys = flag === LatticeGetAddressesFlag.secp256k1Pubkey || flag === LatticeGetAddressesFlag.ed25519Pubkey || flag === LatticeGetAddressesFlag.bls12_381Pubkey; if (arePubkeys) { off += 1; // skip uint8 representing pubkey type } - const respDataLength = - ProtocolConstants.msgSizes.secure.data.response.encrypted[ - LatticeSecureEncryptedRequestType.getAddresses - ]; + const respDataLength = ProtocolConstants.msgSizes.secure.data.response.encrypted[LatticeSecureEncryptedRequestType.getAddresses]; while (off < respDataLength) { if (arePubkeys) { // Pubkeys are shorter and are returned as buffers diff --git a/packages/sdk/src/functions/getKvRecords.ts b/packages/sdk/src/functions/getKvRecords.ts index 4c788592..657e8b45 100644 --- a/packages/sdk/src/functions/getKvRecords.ts +++ b/packages/sdk/src/functions/getKvRecords.ts @@ -1,22 +1,9 @@ -import { - LatticeSecureEncryptedRequestType, - encryptedSecureRequest, -} from '../protocol'; +import { LatticeSecureEncryptedRequestType, encryptedSecureRequest } from '../protocol'; import { validateConnectedClient } from '../shared/validators'; -import type { - FirmwareConstants, - GetKvRecordsData, - GetKvRecordsRequestFunctionParams, -} from '../types'; +import type { FirmwareConstants, GetKvRecordsData, GetKvRecordsRequestFunctionParams } from '../types'; -export async function getKvRecords({ - client, - type: _type, - n: _n, - start: _start, -}: GetKvRecordsRequestFunctionParams): Promise { - const { url, sharedSecret, ephemeralPub, fwConstants } = - validateConnectedClient(client); +export async function getKvRecords({ client, type: _type, n: _n, start: _start }: GetKvRecordsRequestFunctionParams): Promise { + const { url, sharedSecret, ephemeralPub, fwConstants } = validateConnectedClient(client); const { type, n, start } = validateGetKvRequest({ type: _type, @@ -60,9 +47,7 @@ export const validateGetKvRequest = ({ throw new Error('You must request at least one record.'); } if (n > fwConstants.kvActionMaxNum) { - throw new Error( - `You may only request up to ${fwConstants.kvActionMaxNum} records at once.`, - ); + throw new Error(`You may only request up to ${fwConstants.kvActionMaxNum} records at once.`); } if (type !== 0 && !type) { throw new Error('You must specify a type.'); @@ -90,20 +75,13 @@ export const encodeGetKvRecordsRequest = ({ return payload; }; -export const decodeGetKvRecordsResponse = ( - data: Buffer, - fwConstants: FirmwareConstants, -) => { +export const decodeGetKvRecordsResponse = (data: Buffer, fwConstants: FirmwareConstants) => { let off = 0; const nTotal = data.readUInt32BE(off); off += 4; - const nFetched = Number.parseInt( - data.slice(off, off + 1).toString('hex'), - 16, - ); + const nFetched = Number.parseInt(data.slice(off, off + 1).toString('hex'), 16); off += 1; - if (nFetched > fwConstants.kvActionMaxNum) - throw new Error('Too many records fetched. Firmware error.'); + if (nFetched > fwConstants.kvActionMaxNum) throw new Error('Too many records fetched. Firmware error.'); const records: any = []; for (let i = 0; i < nFetched; i++) { const r: any = {}; @@ -111,8 +89,7 @@ export const decodeGetKvRecordsResponse = ( off += 4; r.type = data.readUInt32BE(off); off += 4; - r.caseSensitive = - Number.parseInt(data.slice(off, off + 1).toString('hex'), 16) === 1; + r.caseSensitive = Number.parseInt(data.slice(off, off + 1).toString('hex'), 16) === 1; off += 1; const keySz = Number.parseInt(data.slice(off, off + 1).toString('hex'), 16); off += 1; diff --git a/packages/sdk/src/functions/pair.ts b/packages/sdk/src/functions/pair.ts index aa119d0a..d33ce923 100644 --- a/packages/sdk/src/functions/pair.ts +++ b/packages/sdk/src/functions/pair.ts @@ -1,7 +1,4 @@ -import { - LatticeSecureEncryptedRequestType, - encryptedSecureRequest, -} from '../protocol'; +import { LatticeSecureEncryptedRequestType, encryptedSecureRequest } from '../protocol'; import { getPubKeyBytes } from '../shared/utilities'; import { validateConnectedClient } from '../shared/validators'; import type { KeyPair, PairRequestParams } from '../types'; @@ -14,12 +11,8 @@ import { generateAppSecret, toPaddedDER } from '../util'; * @category Lattice * @returns The active wallet object. */ -export async function pair({ - client, - pairingSecret, -}: PairRequestParams): Promise { - const { url, sharedSecret, ephemeralPub, appName, key } = - validateConnectedClient(client); +export async function pair({ client, pairingSecret }: PairRequestParams): Promise { + const { url, sharedSecret, ephemeralPub, appName, key } = validateConnectedClient(client); const data = encodePairRequest({ pairingSecret, key, appName }); const { newEphemeralPub } = await encryptedSecureRequest({ @@ -58,11 +51,7 @@ export const encodePairRequest = ({ // (RESP_ERR_PAIR_FAIL) nameBuf.write(appName); } - const hash = generateAppSecret( - pubKeyBytes, - nameBuf, - Buffer.from(pairingSecret), - ); + const hash = generateAppSecret(pubKeyBytes, nameBuf, Buffer.from(pairingSecret)); const sig = key.sign(hash); const derSig = toPaddedDER(sig); const payload = Buffer.concat([nameBuf, derSig]); diff --git a/packages/sdk/src/functions/removeKvRecords.ts b/packages/sdk/src/functions/removeKvRecords.ts index b78637eb..5a7623d4 100644 --- a/packages/sdk/src/functions/removeKvRecords.ts +++ b/packages/sdk/src/functions/removeKvRecords.ts @@ -1,25 +1,14 @@ -import { - LatticeSecureEncryptedRequestType, - encryptedSecureRequest, -} from '../protocol'; +import { LatticeSecureEncryptedRequestType, encryptedSecureRequest } from '../protocol'; import { validateConnectedClient } from '../shared/validators'; -import type { - FirmwareConstants, - RemoveKvRecordsRequestFunctionParams, -} from '../types'; +import type { FirmwareConstants, RemoveKvRecordsRequestFunctionParams } from '../types'; /** * `removeKvRecords` takes in an array of ids and sends a request to remove them from the Lattice. * @category Lattice * @returns A callback with an error or null. */ -export async function removeKvRecords({ - client, - type: _type, - ids: _ids, -}: RemoveKvRecordsRequestFunctionParams): Promise { - const { url, sharedSecret, ephemeralPub, fwConstants } = - validateConnectedClient(client); +export async function removeKvRecords({ client, type: _type, ids: _ids }: RemoveKvRecordsRequestFunctionParams): Promise { + const { url, sharedSecret, ephemeralPub, fwConstants } = validateConnectedClient(client); const { type, ids } = validateRemoveKvRequest({ fwConstants, @@ -64,9 +53,7 @@ export const validateRemoveKvRequest = ({ throw new Error('You must include one or more `ids` to removed.'); } if (ids.length > fwConstants.kvRemoveMaxNum) { - throw new Error( - `Only up to ${fwConstants.kvRemoveMaxNum} records may be removed at once.`, - ); + throw new Error(`Only up to ${fwConstants.kvRemoveMaxNum} records may be removed at once.`); } if (type !== 0 && !type) { throw new Error('You must specify a type.'); diff --git a/packages/sdk/src/functions/sign.ts b/packages/sdk/src/functions/sign.ts index fafbbb3f..76fc9a47 100644 --- a/packages/sdk/src/functions/sign.ts +++ b/packages/sdk/src/functions/sign.ts @@ -4,22 +4,10 @@ import bitcoin from '../bitcoin'; import { CURRENCIES } from '../constants'; import ethereum from '../ethereum'; import { parseGenericSigningResponse } from '../genericSigning'; -import { - LatticeSecureEncryptedRequestType, - LatticeSignSchema, - encryptedSecureRequest, -} from '../protocol'; +import { LatticeSecureEncryptedRequestType, LatticeSignSchema, encryptedSecureRequest } from '../protocol'; import { buildTransaction } from '../shared/functions'; import { validateConnectedClient, validateWallet } from '../shared/validators'; -import type { - BitcoinSignRequest, - DecodeSignResponseParams, - EncodeSignRequestParams, - SignData, - SignRequest, - SignRequestFunctionParams, - SigningRequestResponse, -} from '../types'; +import type { BitcoinSignRequest, DecodeSignResponseParams, EncodeSignRequestParams, SignData, SignRequest, SignRequestFunctionParams, SigningRequestResponse } from '../types'; import { parseDER } from '../util'; /** @@ -27,16 +15,9 @@ import { parseDER } from '../util'; * @category Lattice * @returns The response from the device. */ -export async function sign({ - client, - data, - currency, - cachedData, - nextCode, -}: SignRequestFunctionParams): Promise { +export async function sign({ client, data, currency, cachedData, nextCode }: SignRequestFunctionParams): Promise { try { - const { url, sharedSecret, ephemeralPub, fwConstants } = - validateConnectedClient(client); + const { url, sharedSecret, ephemeralPub, fwConstants } = validateConnectedClient(client); const wallet = validateWallet(client.getActiveWallet()); const { requestData, isGeneric } = buildTransaction({ @@ -101,13 +82,7 @@ export async function sign({ } } -export const encodeSignRequest = ({ - fwConstants, - wallet, - requestData, - cachedData, - nextCode, -}: EncodeSignRequestParams) => { +export const encodeSignRequest = ({ fwConstants, wallet, requestData, cachedData, nextCode }: EncodeSignRequestParams) => { let reqPayload: Buffer; let schema: number; let hasExtraPayloads = 0; @@ -121,24 +96,18 @@ export const encodeSignRequest = ({ }; const nextExtraPayload = typedCachedData.extraDataPayloads.shift(); if (!nextExtraPayload) { - throw new Error( - 'No cached extra payload available for multipart sign request.', - ); + throw new Error('No cached extra payload available for multipart sign request.'); } if (typedRequestData.extraDataPayloads) { typedRequestData.extraDataPayloads = typedCachedData.extraDataPayloads; } reqPayload = Buffer.concat([nextCode, nextExtraPayload]); schema = LatticeSignSchema.extraData; - hasExtraPayloads = Number( - (typedCachedData.extraDataPayloads?.length ?? 0) > 0, - ); + hasExtraPayloads = Number((typedCachedData.extraDataPayloads?.length ?? 0) > 0); } else { reqPayload = typedRequestData.payload; schema = typedRequestData.schema; - hasExtraPayloads = Number( - (typedRequestData.extraDataPayloads?.length ?? 0) > 0, - ); + hasExtraPayloads = Number((typedRequestData.extraDataPayloads?.length ?? 0) > 0); } const payload = Buffer.alloc(2 + fwConstants.reqMaxDataSz); @@ -157,27 +126,17 @@ export const encodeSignRequest = ({ return { payload, hasExtraPayloads }; }; -export const decodeSignResponse = ({ - data, - request, - isGeneric, - currency, -}: DecodeSignResponseParams): SignData => { +export const decodeSignResponse = ({ data, request, isGeneric, currency }: DecodeSignResponseParams): SignData => { let off = 0; const derSigLen = 74; // DER signatures are 74 bytes if (currency === CURRENCIES.BTC) { const btcRequest = request as BitcoinSignRequest; const pkhLen = 20; // Pubkeyhashes are 20 bytes const sigsLen = 760; // Up to 10x DER signatures - const changeVersion = bitcoin.getAddressFormat( - btcRequest.origData.changePath, - ); + const changeVersion = bitcoin.getAddressFormat(btcRequest.origData.changePath); const changePubKeyHash = data.slice(off, off + pkhLen); off += pkhLen; - const changeRecipient = bitcoin.getBitcoinAddress( - changePubKeyHash, - changeVersion, - ); + const changeRecipient = bitcoin.getBitcoinAddress(changePubKeyHash, changeVersion); const compressedPubLength = 33; // Size of compressed public key const pubkeys = [] as any[]; const sigs = [] as any[]; @@ -236,9 +195,7 @@ export const decodeSignResponse = ({ const serializedTx = bitcoin.serializeTx(preSerializedData); // Generate the transaction hash so the user can look this transaction up later const preImageTxHash = serializedTx; - const txHashPre: Buffer = Buffer.from( - Hash.sha256(Buffer.from(preImageTxHash, 'hex')), - ); + const txHashPre: Buffer = Buffer.from(Hash.sha256(Buffer.from(preImageTxHash, 'hex'))); // Add extra data for debugging/lookup purposes return { tx: serializedTx, @@ -291,10 +248,7 @@ export const decodeSignResponse = ({ const sig = parseDER(data.slice(off, off + 2 + data[off + 1])); off += derSigLen; const signer = data.slice(off, off + 20); - const validatedSig = ethereum.validateEthereumMsgResponse( - { signer, sig }, - request, - ); + const validatedSig = ethereum.validateEthereumMsgResponse({ signer, sig }, request); return { sig: { v: BigInt(`0x${validatedSig.v.toString('hex')}`), diff --git a/packages/sdk/src/genericSigning.ts b/packages/sdk/src/genericSigning.ts index baf80d16..1f18b3d3 100644 --- a/packages/sdk/src/genericSigning.ts +++ b/packages/sdk/src/genericSigning.ts @@ -10,57 +10,18 @@ This payload should be coupled with: * Hash function to use on the message */ import { Hash } from 'ox'; -import { - type Hex, - type TransactionSerializable, - parseTransaction, - serializeTransaction, -} from 'viem'; +import { type Hex, type TransactionSerializable, parseTransaction, serializeTransaction } from 'viem'; // keccak256 now imported from ox via Hash module import { HARDENED_OFFSET } from './constants'; import { Constants } from './index'; import { LatticeSignSchema } from './protocol'; -import { - buildSignerPathBuf, - existsIn, - fixLen, - getV, - getYParity, - parseDER, - splitFrames, -} from './util'; +import { buildSignerPathBuf, existsIn, fixLen, getV, getYParity, parseDER, splitFrames } from './util'; export const buildGenericSigningMsgRequest = (req) => { - const { - signerPath, - curveType, - hashType, - encodingType = null, - decoder = null, - omitPubkey = false, - fwConstants, - blsDst = Constants.SIGNING.BLS_DST.BLS_DST_NUL, - } = req; - const { - extraDataFrameSz, - extraDataMaxFrames, - prehashAllowed, - genericSigning, - varAddrPathSzAllowed, - } = fwConstants; - const { - curveTypes, - encodingTypes, - hashTypes, - baseDataSz, - baseReqSz, - calldataDecoding, - } = genericSigning; - const encodedPayload = getEncodedPayload( - req.payload, - encodingType, - encodingTypes, - ); + const { signerPath, curveType, hashType, encodingType = null, decoder = null, omitPubkey = false, fwConstants, blsDst = Constants.SIGNING.BLS_DST.BLS_DST_NUL } = req; + const { extraDataFrameSz, extraDataMaxFrames, prehashAllowed, genericSigning, varAddrPathSzAllowed } = fwConstants; + const { curveTypes, encodingTypes, hashTypes, baseDataSz, baseReqSz, calldataDecoding } = genericSigning; + const encodedPayload = getEncodedPayload(req.payload, encodingType, encodingTypes); const { encoding } = encodedPayload; let { payloadBuf } = encodedPayload; const origPayloadBuf = payloadBuf; @@ -70,12 +31,7 @@ export const buildGenericSigningMsgRequest = (req) => { // Sanity checks if (!payloadDataSz) { throw new Error('Payload could not be handled.'); - } else if ( - !genericSigning || - !extraDataFrameSz || - !extraDataMaxFrames || - !prehashAllowed - ) { + } else if (!genericSigning || !extraDataFrameSz || !extraDataMaxFrames || !prehashAllowed) { throw new Error('Unsupported. Please update your Lattice firmware.'); } else if (!existsIn(curveType, curveTypes)) { throw new Error('Unsupported curve type.'); @@ -85,13 +41,11 @@ export const buildGenericSigningMsgRequest = (req) => { // If there is a decoder attached to our payload, add it to // the data field of the request. - const hasDecoder = - decoder && calldataDecoding && decoder.length <= calldataDecoding.maxSz; + const hasDecoder = decoder && calldataDecoding && decoder.length <= calldataDecoding.maxSz; // Make sure the payload AND decoder data fits in the firmware buffer. // If it doesn't, we can't include the decoder because the payload will likely // be pre-hashed and the decoder data isn't part of the message to sign. - const decoderFits = - hasDecoder && payloadBuf.length + decoder.length <= maxExpandedSz; + const decoderFits = hasDecoder && payloadBuf.length + decoder.length <= maxExpandedSz; if (hasDecoder && decoderFits) { const decoderBuf = Buffer.alloc(8 + decoder.length); // First write th reserved word @@ -109,9 +63,7 @@ export const buildGenericSigningMsgRequest = (req) => { } signerPath.forEach((idx) => { if (idx < HARDENED_OFFSET) { - throw new Error( - 'Signing on ed25519 requires all signer path indices be hardened.', - ); + throw new Error('Signing on ed25519 requires all signer path indices be hardened.'); } }); } @@ -158,9 +110,7 @@ export const buildGenericSigningMsgRequest = (req) => { // If this payload is too large to send, but the Lattice allows a prehashed message, do that if (hashType === hashTypes.NONE) { // This cannot be done for ED25519 signing, which must sign the full message - throw new Error( - 'Message too large to send and could not be prehashed (hashType=NONE).', - ); + throw new Error('Message too large to send and could not be prehashed (hashType=NONE).'); } else if (hashType === hashTypes.KECCAK256) { prehash = Buffer.from(Hash.keccak256(payloadData)); } else if (hashType === hashTypes.SHA256) { @@ -170,10 +120,7 @@ export const buildGenericSigningMsgRequest = (req) => { } } else { // Split overflow data into extraData frames - const frames = splitFrames( - payloadBuf.slice(baseDataSz), - extraDataFrameSz, - ); + const frames = splitFrames(payloadBuf.slice(baseDataSz), extraDataFrameSz); frames.forEach((frame) => { const szLE = Buffer.alloc(4); szLE.writeUInt32LE(frame.length, 0); @@ -264,10 +211,7 @@ export const parseGenericSigningResponse = (res, off, req) => { const vBn = getV(req.origPayloadBuf, parsed); parsed.sig.v = BigInt(vBn.toString()); populateViemSignedTx(parsed.sig.v, req, parsed); - } else if ( - req.hashType === Constants.SIGNING.HASHES.KECCAK256 && - req.encodingType !== Constants.SIGNING.ENCODINGS.EVM - ) { + } else if (req.hashType === Constants.SIGNING.HASHES.KECCAK256 && req.encodingType !== Constants.SIGNING.ENCODINGS.EVM) { // Generic Keccak256 message - determine if it looks like a transaction let isTransaction = false; @@ -293,10 +237,7 @@ export const parseGenericSigningResponse = (res, off, req) => { parsed.sig.v = BigInt(vBn.toString()); populateViemSignedTx(parsed.sig.v, req, parsed); } catch (err) { - console.error( - 'Failed to get V from transaction, using fallback:', - err, - ); + console.error('Failed to get V from transaction, using fallback:', err); // Fall back to simple recovery if getV fails (e.g., malformed RLP) // Use the correct hash type specified in the request const msgHash = computeMessageHash(req, digestFromResponse); @@ -356,11 +297,7 @@ function computeMessageHash( }, digestFromResponse?: Buffer, ): Buffer { - if ( - digestFromResponse && - digestFromResponse.length === 32 && - digestFromResponse.some((byte) => byte !== 0) - ) { + if (digestFromResponse && digestFromResponse.length === 32 && digestFromResponse.some((byte) => byte !== 0)) { return digestFromResponse; } if (req.hashType === Constants.SIGNING.HASHES.SHA256) { @@ -374,11 +311,7 @@ function computeMessageHash( // Reconstruct a viem-compatible signed transaction string from the raw payload and // recovered signature so consumers can compare or broadcast without extra parsing. -function populateViemSignedTx( - sigV: bigint, - req: any, - parsed: { sig: { r: string; s: string; v?: bigint }; viemTx?: string }, -) { +function populateViemSignedTx(sigV: bigint, req: any, parsed: { sig: { r: string; s: string; v?: bigint }; viemTx?: string }) { if (req.encodingType !== Constants.SIGNING.ENCODINGS.EVM) return; try { @@ -427,10 +360,7 @@ function populateViemSignedTx( s: parsed.sig.s as Hex, }; - parsed.viemTx = serializeTransaction( - baseTx as TransactionSerializable, - signature as any, - ); + parsed.viemTx = serializeTransaction(baseTx as TransactionSerializable, signature as any); } catch (_err) { console.debug('Failed to build viemTx from response', _err); } @@ -442,9 +372,7 @@ export const getEncodedPayload = (payload, encoding, allowedEncodings) => { } // Make sure the encoding type specified is supported by firmware if (!existsIn(encoding, allowedEncodings)) { - throw new Error( - 'Encoding not supported by Lattice firmware. You may want to update.', - ); + throw new Error('Encoding not supported by Lattice firmware. You may want to update.'); } let payloadBuf: Buffer; if (!payload) { diff --git a/packages/sdk/src/protocol/latticeConstants.ts b/packages/sdk/src/protocol/latticeConstants.ts index 041564aa..2589a17e 100644 --- a/packages/sdk/src/protocol/latticeConstants.ts +++ b/packages/sdk/src/protocol/latticeConstants.ts @@ -94,10 +94,7 @@ export const ProtocolConstants = { // message encryption/decryption. This is generally considered // fine because each encryption/decryption uses a unique encryption // secret (derived from the per-message ephemeral key pair). - aesIv: [ - 0x6d, 0x79, 0x73, 0x65, 0x63, 0x72, 0x65, 0x74, 0x70, 0x61, 0x73, 0x73, - 0x77, 0x6f, 0x72, 0x64, - ], + aesIv: [0x6d, 0x79, 0x73, 0x65, 0x63, 0x72, 0x65, 0x74, 0x70, 0x61, 0x73, 0x73, 0x77, 0x6f, 0x72, 0x64], // Constant size of address buffers from the Lattice. // Note that this size also captures public keys returned // by the Lattice (addresses = strings, pubkeys = buffers) @@ -117,8 +114,7 @@ export const ProtocolConstants = { [LatticeResponseCode.userDeclined]: 'Request declined by user', [LatticeResponseCode.pairFailed]: 'Pairing failed', [LatticeResponseCode.pairDisabled]: 'Pairing is currently disabled', - [LatticeResponseCode.permissionDisabled]: - 'Automated signing is currently disabled', + [LatticeResponseCode.permissionDisabled]: 'Automated signing is currently disabled', [LatticeResponseCode.internalError]: 'Device Error', [LatticeResponseCode.gceTimeout]: 'Device Timeout', [LatticeResponseCode.wrongWallet]: 'Active wallet does not match request', diff --git a/packages/sdk/src/protocol/secureMessages.ts b/packages/sdk/src/protocol/secureMessages.ts index 5a7acf59..78a0576c 100644 --- a/packages/sdk/src/protocol/secureMessages.ts +++ b/packages/sdk/src/protocol/secureMessages.ts @@ -1,21 +1,7 @@ import { getEphemeralId, request } from '../shared/functions'; import { validateEphemeralPub } from '../shared/validators'; -import type { - DecryptedResponse, - KeyPair, - LatticeMessageHeader, - LatticeSecureConnectRequestPayloadData, - LatticeSecureDecryptedResponse, - LatticeSecureRequest, - LatticeSecureRequestPayload, -} from '../types'; -import { - aes256_decrypt, - aes256_encrypt, - checksum, - getP256KeyPairFromPub, - randomBytes, -} from '../util'; +import type { DecryptedResponse, KeyPair, LatticeMessageHeader, LatticeSecureConnectRequestPayloadData, LatticeSecureDecryptedResponse, LatticeSecureRequest, LatticeSecureRequestPayload } from '../types'; +import { aes256_decrypt, aes256_encrypt, checksum, getP256KeyPairFromPub, randomBytes } from '../util'; /** * All messages sent to the Lattice from this SDK will be * "secure messages", of which there are two types: @@ -36,13 +22,7 @@ import { * The response to this request will contain a new ephemral public * key, which you will need for the next encrypted request. */ -import { - ProtocolConstants as Constants, - LatticeMsgType, - LatticeProtocolVersion, - type LatticeSecureEncryptedRequestType, - LatticeSecureMsgType, -} from './latticeConstants'; +import { ProtocolConstants as Constants, LatticeMsgType, LatticeProtocolVersion, type LatticeSecureEncryptedRequestType, LatticeSecureMsgType } from './latticeConstants'; const { msgSizes } = Constants; const { secure: szs } = msgSizes; @@ -67,11 +47,7 @@ export async function connectSecureRequest({ pubkey: pubkey, }); const msgId = randomBytes(4); - const msg = serializeSecureRequestMsg( - msgId, - LatticeSecureMsgType.connect, - payloadData, - ); + const msg = serializeSecureRequestMsg(msgId, LatticeSecureMsgType.connect, payloadData); // Send request to the Lattice const resp = await request({ url, payload: msg }); if (resp.length !== szs.payload.response.connect - 1) { @@ -120,11 +96,7 @@ export async function encryptedSecureRequest({ // Serialize the payload data into an encrypted secure // request message. - const msg = serializeSecureRequestMsg( - msgId, - LatticeSecureMsgType.encrypted, - payloadData, - ); + const msg = serializeSecureRequestMsg(msgId, LatticeSecureMsgType.encrypted, payloadData); // Send request to Lattice const resp = await request({ @@ -137,10 +109,7 @@ export async function encryptedSecureRequest({ throw new Error('Wrong Lattice response message size.'); } - const encPayloadData = resp.slice( - 0, - szs.data.response.encrypted.encryptedData, - ); + const encPayloadData = resp.slice(0, szs.data.response.encrypted.encryptedData); // Return decrypted response payload data return decryptEncryptedLatticeResponseData({ @@ -159,31 +128,20 @@ export async function encryptedSecureRequest({ * @param payloadData - Request data * @return {Buffer} Serialized message to be sent to Lattice */ -function serializeSecureRequestMsg( - msgId: Buffer, - secureRequestType: LatticeSecureMsgType, - payloadData: Buffer, -): Buffer { +function serializeSecureRequestMsg(msgId: Buffer, secureRequestType: LatticeSecureMsgType, payloadData: Buffer): Buffer { // Sanity check request data if (msgId.length !== 4) { throw new Error('msgId must be four bytes'); } - if ( - secureRequestType !== LatticeSecureMsgType.connect && - secureRequestType !== LatticeSecureMsgType.encrypted - ) { + if (secureRequestType !== LatticeSecureMsgType.connect && secureRequestType !== LatticeSecureMsgType.encrypted) { throw new Error('Invalid Lattice secure request type'); } // Validate the incoming payload data size. Note that the payload // data is prepended with a secure request type byte, so the // payload data size is one less than the expected size. - const isValidConnectPayloadDataSz = - secureRequestType === LatticeSecureMsgType.connect && - payloadData.length === szs.payload.request.connect - 1; - const isValidEncryptedPayloadDataSz = - secureRequestType === LatticeSecureMsgType.encrypted && - payloadData.length === szs.payload.request.encrypted - 1; + const isValidConnectPayloadDataSz = secureRequestType === LatticeSecureMsgType.connect && payloadData.length === szs.payload.request.connect - 1; + const isValidEncryptedPayloadDataSz = secureRequestType === LatticeSecureMsgType.encrypted && payloadData.length === szs.payload.request.encrypted - 1; // Build payload and size let msgSz = msgSizes.header + msgSizes.checksum; @@ -247,9 +205,7 @@ function serializeSecureRequestMsg( * Serialize payload data for a Lattice secure request: connect * @return {Buffer} - 1700 bytes, of which only 65 are used */ -function serializeSecureRequestConnectPayloadData( - payloadData: LatticeSecureConnectRequestPayloadData, -): Buffer { +function serializeSecureRequestConnectPayloadData(payloadData: LatticeSecureConnectRequestPayloadData): Buffer { const serPayloadData = Buffer.alloc(szs.data.request.connect); payloadData.pubkey.copy(serPayloadData, 0); return serPayloadData; @@ -283,9 +239,7 @@ function serializeSecureRequestEncryptedPayloadData({ // Validate the request data size matches the desired request const requestDataSize = szs.data.request.encrypted[requestType]; if (data.length !== requestDataSize) { - throw new Error( - `Invalid request datasize (wanted ${requestDataSize}, got ${data.length})`, - ); + throw new Error(`Invalid request datasize (wanted ${requestDataSize}, got ${data.length})`); } // Build the pre-encrypted data payload, which variable sized and of form: @@ -299,10 +253,7 @@ function serializeSecureRequestEncryptedPayloadData({ // equal to the full message request less the 4-byte ephemeral id. const _encryptedData = Buffer.alloc(szs.data.request.encrypted.encryptedData); preEncryptedData.copy(_encryptedData, 0); - _encryptedData.writeUInt32LE( - preEncryptedDataChecksum, - preEncryptedData.length, - ); + _encryptedData.writeUInt32LE(preEncryptedDataChecksum, preEncryptedData.length); const encryptedData = aes256_encrypt(_encryptedData, sharedSecret); // Calculate ephemeral ID @@ -335,8 +286,7 @@ function decryptEncryptedLatticeResponseData({ // Bulid the object const ephemeralPubSz = 65; // secp256r1 pubkey - const checksumOffset = - ephemeralPubSz + szs.data.response.encrypted[requestType]; + const checksumOffset = ephemeralPubSz + szs.data.response.encrypted[requestType]; const respData: LatticeSecureDecryptedResponse = { ephemeralPub: decData.slice(0, ephemeralPubSz), data: decData.slice(ephemeralPubSz, checksumOffset), diff --git a/packages/sdk/src/schemas/transaction.ts b/packages/sdk/src/schemas/transaction.ts index 63f06eed..20d0a174 100644 --- a/packages/sdk/src/schemas/transaction.ts +++ b/packages/sdk/src/schemas/transaction.ts @@ -4,32 +4,25 @@ import { TRANSACTION_TYPE } from '../types'; // Helper to handle various numeric inputs and convert them to BigInt. // It also validates that the value is not negative. -const toPositiveBigInt = z - .union([ - z.string().regex(/^(0x[0-9a-fA-F]+|[0-9]+)$/, 'Invalid number format'), - z.number(), - z.bigint(), - ]) - .transform((val, ctx) => { - try { - const b = - typeof val === 'string' && isHex(val) ? hexToBigInt(val) : BigInt(val); - if (b < 0n) { - ctx.addIssue({ - code: z.ZodIssueCode.custom, - message: 'Value must be non-negative', - }); - return z.NEVER; - } - return b; - } catch { +const toPositiveBigInt = z.union([z.string().regex(/^(0x[0-9a-fA-F]+|[0-9]+)$/, 'Invalid number format'), z.number(), z.bigint()]).transform((val, ctx) => { + try { + const b = typeof val === 'string' && isHex(val) ? hexToBigInt(val) : BigInt(val); + if (b < 0n) { ctx.addIssue({ code: z.ZodIssueCode.custom, - message: 'Invalid numeric value', + message: 'Value must be non-negative', }); return z.NEVER; } - }); + return b; + } catch { + ctx.addIssue({ + code: z.ZodIssueCode.custom, + message: 'Invalid numeric value', + }); + return z.NEVER; + } +}); // Schema for gas-related fields, ensuring they are non-negative BigInts. const GasValueSchema = toPositiveBigInt.refine((val) => val >= 0n, { @@ -39,11 +32,7 @@ const GasValueSchema = toPositiveBigInt.refine((val) => val >= 0n, { // Schema for chainId, ensuring it's a positive integer. const ChainIdSchema = z .union([z.string(), z.number()]) - .transform((val) => - typeof val === 'string' && isHex(val) - ? Number(hexToBigInt(val as Hex)) - : Number(val), - ) + .transform((val) => (typeof val === 'string' && isHex(val) ? Number(hexToBigInt(val as Hex)) : Number(val))) .refine((val) => Number.isInteger(val) && val > 0, { message: 'Chain ID must be a positive integer', }); @@ -55,61 +44,43 @@ const AddressSchema = z .transform((addr) => getAddress(addr)); // Schema for hex data, ensuring it's a valid hex string. -const DataSchema = z - .string() - .refine(isHex, 'Data must be a valid hex string') - .default('0x'); - -const NonceSchema = z - .union([ - z.string().regex(/^(0x[0-9a-fA-F]+|[0-9]+)$/, 'Invalid nonce format'), - z.number().int().nonnegative(), - z.bigint(), - ]) - .transform((val, ctx) => { - try { - const bigVal = - typeof val === 'string' - ? isHex(val as Hex) - ? hexToBigInt(val as Hex) - : BigInt(val) - : typeof val === 'number' - ? BigInt(val) - : val; +const DataSchema = z.string().refine(isHex, 'Data must be a valid hex string').default('0x'); - if (bigVal < 0n) { - ctx.addIssue({ - code: z.ZodIssueCode.custom, - message: 'Nonce must be non-negative', - }); - return z.NEVER; - } +const NonceSchema = z.union([z.string().regex(/^(0x[0-9a-fA-F]+|[0-9]+)$/, 'Invalid nonce format'), z.number().int().nonnegative(), z.bigint()]).transform((val, ctx) => { + try { + const bigVal = typeof val === 'string' ? (isHex(val as Hex) ? hexToBigInt(val as Hex) : BigInt(val)) : typeof val === 'number' ? BigInt(val) : val; - const maxSafe = BigInt(Number.MAX_SAFE_INTEGER); - if (bigVal > maxSafe) { - ctx.addIssue({ - code: z.ZodIssueCode.custom, - message: 'Nonce exceeds JavaScript safe integer range', - }); - return z.NEVER; - } + if (bigVal < 0n) { + ctx.addIssue({ + code: z.ZodIssueCode.custom, + message: 'Nonce must be non-negative', + }); + return z.NEVER; + } - return Number(bigVal); - } catch { + const maxSafe = BigInt(Number.MAX_SAFE_INTEGER); + if (bigVal > maxSafe) { ctx.addIssue({ code: z.ZodIssueCode.custom, - message: 'Invalid nonce value', + message: 'Nonce exceeds JavaScript safe integer range', }); return z.NEVER; } - }); + + return Number(bigVal); + } catch { + ctx.addIssue({ + code: z.ZodIssueCode.custom, + message: 'Invalid nonce value', + }); + return z.NEVER; + } +}); // Schema for access list entries. const AccessListEntrySchema = z.object({ address: AddressSchema, - storageKeys: z.array( - z.string().refine(isHex, 'Storage key must be a hex string'), - ), + storageKeys: z.array(z.string().refine(isHex, 'Storage key must be a hex string')), }); // Schema for EIP-7702 authorization entries. @@ -136,9 +107,7 @@ const BaseTxSchema = z.object({ // Schema for Legacy (Type 0) transactions. const LegacyTxSchema = BaseTxSchema.extend({ - type: z - .union([z.literal('legacy'), z.literal(TRANSACTION_TYPE.LEGACY)]) - .optional(), + type: z.union([z.literal('legacy'), z.literal(TRANSACTION_TYPE.LEGACY)]).optional(), gasPrice: GasValueSchema, }); @@ -157,11 +126,7 @@ const EIP1559TxSchema = BaseTxSchema.extend({ // Schema for EIP-7702 (Type 4/5) transactions. const EIP7702TxSchema = BaseTxSchema.extend({ - type: z.union([ - z.literal('eip7702'), - z.literal(TRANSACTION_TYPE.EIP7702_AUTH), - z.literal(TRANSACTION_TYPE.EIP7702_AUTH_LIST), - ]), + type: z.union([z.literal('eip7702'), z.literal(TRANSACTION_TYPE.EIP7702_AUTH), z.literal(TRANSACTION_TYPE.EIP7702_AUTH_LIST)]), maxFeePerGas: GasValueSchema, maxPriorityFeePerGas: GasValueSchema, authorizationList: z.array(AuthorizationSchema).min(1), @@ -181,9 +146,7 @@ export const TransactionSchema = z .refine( (val) => { try { - JSON.stringify(val, (_, value) => - typeof value === 'bigint' ? value.toString() : value, - ); + JSON.stringify(val, (_, value) => (typeof value === 'bigint' ? value.toString() : value)); return true; } catch { return false; @@ -217,12 +180,7 @@ export const TransactionSchema = z let type: 'eip7702' | 'eip1559' | 'eip2930' | 'legacy' = 'legacy'; let schema: z.ZodTypeAny = LegacyTxSchema; - if ( - tx.type === 'eip7702' || - tx.type === 4 || - tx.type === 5 || - hasAuthList - ) { + if (tx.type === 'eip7702' || tx.type === 4 || tx.type === 5 || hasAuthList) { type = 'eip7702'; schema = EIP7702TxSchema; } else if (tx.type === 'eip1559' || tx.type === 2 || hasMaxFee) { diff --git a/packages/sdk/src/shared/functions.ts b/packages/sdk/src/shared/functions.ts index e03aa25e..6d4121c3 100644 --- a/packages/sdk/src/shared/functions.ts +++ b/packages/sdk/src/shared/functions.ts @@ -7,12 +7,7 @@ import { buildGenericSigningMsgRequest } from '../genericSigning'; import type { Currency, FirmwareConstants, RequestParams } from '../types'; import { fetchWithTimeout, parseLattice1Response } from '../util'; import { LatticeResponseError } from './errors'; -import { - isDeviceBusy, - isInvalidEphemeralId, - isWrongWallet, - shouldUseEVMLegacyConverter, -} from './predicates'; +import { isDeviceBusy, isInvalidEphemeralId, isWrongWallet, shouldUseEVMLegacyConverter } from './predicates'; import { validateRequestError } from './validators'; export const buildTransaction = ({ @@ -35,19 +30,13 @@ export const buildTransaction = ({ // general signing requests for newer firmware versions. EIP1559 and EIP155 legacy // requests will convert, but others may not. if (currency === 'ETH' && shouldUseEVMLegacyConverter(fwConstants)) { - console.log( - 'Using the legacy ETH signing path. This will soon be deprecated. ' + - 'Please switch to general signing request.', - ); + console.log('Using the legacy ETH signing path. This will soon be deprecated. ' + 'Please switch to general signing request.'); let payload: Buffer | undefined; try { payload = ethereum.convertEthereumTransactionToGenericRequest(data); } catch (err) { console.error('Failed to convert legacy Ethereum transaction:', err); - throw new Error( - 'Could not convert legacy request. Please switch to a general signing ' + - 'request. See gridplus-sdk docs for more information.', - ); + throw new Error('Could not convert legacy request. Please switch to a general signing ' + 'request. See gridplus-sdk docs for more information.'); } data = { fwConstants, @@ -84,11 +73,7 @@ export const buildTransaction = ({ }; }; -export const request = async ({ - url, - payload, - timeout = 60000, -}: RequestParams) => { +export const request = async ({ url, payload, timeout = 60000 }: RequestParams) => { return fetchWithTimeout(url, { method: 'POST', body: JSON.stringify({ data: payload }), @@ -107,9 +92,7 @@ export const request = async ({ throw new Error(`Error code ${body.status}: ${body.message}`); } - const { data, errorMessage, responseCode } = parseLattice1Response( - body.message, - ); + const { data, errorMessage, responseCode } = parseLattice1Response(body.message); if (errorMessage || responseCode) { throw new LatticeResponseError(responseCode, errorMessage); @@ -174,10 +157,7 @@ export const retryWrapper = async ({ if ((errorMessage || responseCode) && retries) { if (isDeviceBusy(responseCode)) { await sleep(3000); - } else if ( - isWrongWallet(responseCode) && - !client.skipRetryOnWrongWallet - ) { + } else if (isWrongWallet(responseCode) && !client.skipRetryOnWrongWallet) { await client.fetchActiveWallet(); } else if (isInvalidEphemeralId(responseCode)) { await client.connect(client.deviceId); diff --git a/packages/sdk/src/shared/predicates.ts b/packages/sdk/src/shared/predicates.ts index 25477720..45afe241 100644 --- a/packages/sdk/src/shared/predicates.ts +++ b/packages/sdk/src/shared/predicates.ts @@ -2,18 +2,12 @@ import { LatticeResponseCode } from '../protocol'; import type { FirmwareConstants, FirmwareVersion } from '../types'; import { isFWSupported } from './utilities'; -export const isDeviceBusy = (responseCode: number) => - responseCode === LatticeResponseCode.deviceBusy || - responseCode === LatticeResponseCode.gceTimeout; +export const isDeviceBusy = (responseCode: number) => responseCode === LatticeResponseCode.deviceBusy || responseCode === LatticeResponseCode.gceTimeout; -export const isWrongWallet = (responseCode: number) => - responseCode === LatticeResponseCode.wrongWallet; +export const isWrongWallet = (responseCode: number) => responseCode === LatticeResponseCode.wrongWallet; -export const isInvalidEphemeralId = (responseCode: number) => - responseCode === LatticeResponseCode.invalidEphemId; +export const isInvalidEphemeralId = (responseCode: number) => responseCode === LatticeResponseCode.invalidEphemId; -export const doesFetchWalletsOnLoad = (fwVersion: FirmwareVersion) => - isFWSupported(fwVersion, { major: 0, minor: 14, fix: 1 }); +export const doesFetchWalletsOnLoad = (fwVersion: FirmwareVersion) => isFWSupported(fwVersion, { major: 0, minor: 14, fix: 1 }); -export const shouldUseEVMLegacyConverter = (fwConstants: FirmwareConstants) => - fwConstants.genericSigning?.encodingTypes?.EVM; +export const shouldUseEVMLegacyConverter = (fwConstants: FirmwareConstants) => fwConstants.genericSigning?.encodingTypes?.EVM; diff --git a/packages/sdk/src/shared/utilities.ts b/packages/sdk/src/shared/utilities.ts index 08367194..1a59617e 100644 --- a/packages/sdk/src/shared/utilities.ts +++ b/packages/sdk/src/shared/utilities.ts @@ -82,17 +82,10 @@ export const parseWallets = (walletData: any): ActiveWallets => { }; // Determine if a provided firmware version matches or exceeds the current firmware version -export const isFWSupported = ( - fwVersion: FirmwareVersion, - versionSupported: FirmwareVersion, -): boolean => { +export const isFWSupported = (fwVersion: FirmwareVersion, versionSupported: FirmwareVersion): boolean => { const { major, minor, fix } = fwVersion; const { major: _major, minor: _minor, fix: _fix } = versionSupported; - return ( - major > _major || - (major >= _major && minor > _minor) || - (major >= _major && minor >= _minor && fix >= _fix) - ); + return major > _major || (major >= _major && minor > _minor) || (major >= _major && minor >= _minor && fix >= _fix); }; /** diff --git a/packages/sdk/src/shared/validators.ts b/packages/sdk/src/shared/validators.ts index 5b5a9da9..add1607a 100644 --- a/packages/sdk/src/shared/validators.ts +++ b/packages/sdk/src/shared/validators.ts @@ -2,15 +2,7 @@ import type { UInt4 } from 'bitwise/types'; import isEmpty from 'lodash/isEmpty.js'; import type { Client } from '../client'; import { ASCII_REGEX, EMPTY_WALLET_UID, MAX_ADDR } from '../constants'; -import type { - ActiveWallets, - FirmwareConstants, - FirmwareVersion, - KVRecords, - KeyPair, - LatticeError, - Wallet, -} from '../types'; +import type { ActiveWallets, FirmwareConstants, FirmwareVersion, KVRecords, KeyPair, LatticeError, Wallet } from '../types'; import { isUInt4 } from '../util'; export const validateIsUInt4 = (n?: number) => { @@ -34,17 +26,14 @@ export const validateStartPath = (startPath?: number[]) => { if (!startPath) { throw new Error('Start path is required'); } - if (startPath.length < 1 || startPath.length > 5) - throw new Error('Path must include between 1 and 5 indices'); + if (startPath.length < 1 || startPath.length > 5) throw new Error('Path must include between 1 and 5 indices'); return startPath; }; export const validateDeviceId = (deviceId?: string) => { if (!deviceId) { - throw new Error( - 'No device ID has been stored. Please connect with your device ID first.', - ); + throw new Error('No device ID has been stored. Please connect with your device ID first.'); } return deviceId; }; @@ -54,9 +43,7 @@ export const validateAppName = (name?: string) => { throw new Error('Name is required.'); } if (name.length < 5 || name.length > 24) { - throw new Error( - 'Invalid length for name provided. Must be 5-24 characters.', - ); + throw new Error('Invalid length for name provided. Must be 5-24 characters.'); } return name; }; @@ -98,11 +85,7 @@ export const validateFwVersion = (fwVersion?: FirmwareVersion) => { if (!fwVersion) { throw new Error('Firmware version does not exist. Please reconnect.'); } - if ( - typeof fwVersion.fix !== 'number' || - typeof fwVersion.minor !== 'number' || - typeof fwVersion.major !== 'number' - ) { + if (typeof fwVersion.fix !== 'number' || typeof fwVersion.minor !== 'number' || typeof fwVersion.major !== 'number') { throw new Error('Firmware version improperly formatted. Please reconnect.'); } return fwVersion; @@ -111,9 +94,7 @@ export const validateFwVersion = (fwVersion?: FirmwareVersion) => { export const validateRequestError = (err: LatticeError) => { const isTimeout = err.code === 'ECONNABORTED' && err.errno === 'ETIME'; if (isTimeout) { - throw new Error( - 'Timeout waiting for device. Please ensure it is connected to the internet and try again in a minute.', - ); + throw new Error('Timeout waiting for device. Please ensure it is connected to the internet and try again in a minute.'); } throw new Error(`Failed to make request to device:\n${err.message}`); }; @@ -148,9 +129,7 @@ export const validateConnectedClient = (client: Client) => { export const validateEphemeralPub = (ephemeralPub?: KeyPair) => { if (!ephemeralPub) { - throw new Error( - '`ephemeralPub` (ephemeral public key) is required. Please reconnect.', - ); + throw new Error('`ephemeralPub` (ephemeral public key) is required. Please reconnect.'); } return ephemeralPub; }; @@ -170,52 +149,28 @@ export const validateKey = (key?: KeyPair) => { }; export const validateActiveWallets = (activeWallets?: ActiveWallets) => { - if ( - !activeWallets || - (activeWallets?.internal?.uid?.equals(EMPTY_WALLET_UID) && - activeWallets?.external?.uid?.equals(EMPTY_WALLET_UID)) - ) { + if (!activeWallets || (activeWallets?.internal?.uid?.equals(EMPTY_WALLET_UID) && activeWallets?.external?.uid?.equals(EMPTY_WALLET_UID))) { throw new Error('No active wallet.'); } return activeWallets; }; -export const validateKvRecords = ( - records?: KVRecords, - fwConstants?: FirmwareConstants, -) => { +export const validateKvRecords = (records?: KVRecords, fwConstants?: FirmwareConstants) => { if (!fwConstants || !fwConstants.kvActionsAllowed) { throw new Error('Unsupported. Please update firmware.'); } else if (typeof records !== 'object' || Object.keys(records).length < 1) { - throw new Error( - 'One or more key-value mapping must be provided in `records` param.', - ); + throw new Error('One or more key-value mapping must be provided in `records` param.'); } else if (Object.keys(records).length > fwConstants.kvActionMaxNum) { - throw new Error( - `Too many keys provided. Please only provide up to ${fwConstants.kvActionMaxNum}.`, - ); + throw new Error(`Too many keys provided. Please only provide up to ${fwConstants.kvActionMaxNum}.`); } return records; }; -export const validateKvRecord = ( - { key, val }: KVRecords, - fwConstants: FirmwareConstants, -) => { - if ( - typeof key !== 'string' || - String(key).length > fwConstants.kvKeyMaxStrSz - ) { - throw new Error( - `Key ${key} too large. Must be <=${fwConstants.kvKeyMaxStrSz} characters.`, - ); - } else if ( - typeof val !== 'string' || - String(val).length > fwConstants.kvValMaxStrSz - ) { - throw new Error( - `Value ${val} too large. Must be <=${fwConstants.kvValMaxStrSz} characters.`, - ); +export const validateKvRecord = ({ key, val }: KVRecords, fwConstants: FirmwareConstants) => { + if (typeof key !== 'string' || String(key).length > fwConstants.kvKeyMaxStrSz) { + throw new Error(`Key ${key} too large. Must be <=${fwConstants.kvKeyMaxStrSz} characters.`); + } else if (typeof val !== 'string' || String(val).length > fwConstants.kvValMaxStrSz) { + throw new Error(`Value ${val} too large. Must be <=${fwConstants.kvValMaxStrSz} characters.`); } else if (String(key).length === 0 || String(val).length === 0) { throw new Error('Keys and values must be >0 characters.'); } else if (!ASCII_REGEX.test(key) || !ASCII_REGEX.test(val)) { diff --git a/packages/sdk/src/types/addKvRecords.ts b/packages/sdk/src/types/addKvRecords.ts index 892f0de9..460ac101 100644 --- a/packages/sdk/src/types/addKvRecords.ts +++ b/packages/sdk/src/types/addKvRecords.ts @@ -7,7 +7,6 @@ export interface AddKvRecordsRequestParams { caseSensitive?: boolean; } -export interface AddKvRecordsRequestFunctionParams - extends AddKvRecordsRequestParams { +export interface AddKvRecordsRequestFunctionParams extends AddKvRecordsRequestParams { client: Client; } diff --git a/packages/sdk/src/types/firmware.ts b/packages/sdk/src/types/firmware.ts index d3b6bab3..f1cb349b 100644 --- a/packages/sdk/src/types/firmware.ts +++ b/packages/sdk/src/types/firmware.ts @@ -41,10 +41,7 @@ export interface FirmwareConstants { extraDataFrameSz: number; extraDataMaxFrames: number; genericSigning: GenericSigningData; - getAddressFlags: [ - typeof EXTERNAL.GET_ADDR_FLAGS.ED25519_PUB, - typeof EXTERNAL.GET_ADDR_FLAGS.SECP256K1_PUB, - ]; + getAddressFlags: [typeof EXTERNAL.GET_ADDR_FLAGS.ED25519_PUB, typeof EXTERNAL.GET_ADDR_FLAGS.SECP256K1_PUB]; kvActionMaxNum: number; kvActionsAllowed: boolean; kvKeyMaxStrSz: number; diff --git a/packages/sdk/src/types/getAddresses.ts b/packages/sdk/src/types/getAddresses.ts index 7ec1bff5..4c7e22cf 100644 --- a/packages/sdk/src/types/getAddresses.ts +++ b/packages/sdk/src/types/getAddresses.ts @@ -7,7 +7,6 @@ export interface GetAddressesRequestParams { iterIdx?: number; } -export interface GetAddressesRequestFunctionParams - extends GetAddressesRequestParams { +export interface GetAddressesRequestFunctionParams extends GetAddressesRequestParams { client: Client; } diff --git a/packages/sdk/src/types/getKvRecords.ts b/packages/sdk/src/types/getKvRecords.ts index f4c1d0e0..e9d0d3bd 100644 --- a/packages/sdk/src/types/getKvRecords.ts +++ b/packages/sdk/src/types/getKvRecords.ts @@ -6,8 +6,7 @@ export interface GetKvRecordsRequestParams { start?: number; } -export interface GetKvRecordsRequestFunctionParams - extends GetKvRecordsRequestParams { +export interface GetKvRecordsRequestFunctionParams extends GetKvRecordsRequestParams { client: Client; } diff --git a/packages/sdk/src/types/removeKvRecords.ts b/packages/sdk/src/types/removeKvRecords.ts index a8cfc747..6b9c7ab5 100644 --- a/packages/sdk/src/types/removeKvRecords.ts +++ b/packages/sdk/src/types/removeKvRecords.ts @@ -5,7 +5,6 @@ export interface RemoveKvRecordsRequestParams { ids?: string[]; } -export interface RemoveKvRecordsRequestFunctionParams - extends RemoveKvRecordsRequestParams { +export interface RemoveKvRecordsRequestFunctionParams extends RemoveKvRecordsRequestParams { client: Client; } diff --git a/packages/sdk/src/types/sign.ts b/packages/sdk/src/types/sign.ts index f9ed2121..f1037309 100644 --- a/packages/sdk/src/types/sign.ts +++ b/packages/sdk/src/types/sign.ts @@ -1,12 +1,4 @@ -import type { - AccessList, - Address, - Hex, - SignedAuthorization, - SignedAuthorizationList, - TypedData, - TypedDataDefinition, -} from 'viem'; +import type { AccessList, Address, Hex, SignedAuthorization, SignedAuthorizationList, TypedData, TypedDataDefinition } from 'viem'; import type { Client } from '../client'; import type { Currency, SigningPath, Wallet } from './client'; import type { FirmwareConstants } from './firmware'; @@ -72,24 +64,11 @@ export type EIP7702AuthListTransactionRequest = BaseTransactionRequest & { }; // Main discriminated union for transaction requests -export type TransactionRequest = - | LegacyTransactionRequest - | EIP2930TransactionRequest - | EIP1559TransactionRequest - | EIP7702AuthTransactionRequest - | EIP7702AuthListTransactionRequest; - -export interface SigningPayload< - TTypedData extends TypedData | Record = TypedData, -> { +export type TransactionRequest = LegacyTransactionRequest | EIP2930TransactionRequest | EIP1559TransactionRequest | EIP7702AuthTransactionRequest | EIP7702AuthListTransactionRequest; + +export interface SigningPayload = TypedData> { signerPath: SigningPath; - payload: - | Uint8Array - | Uint8Array[] - | Buffer - | Buffer[] - | Hex - | EIP712MessagePayload; + payload: Uint8Array | Uint8Array[] | Buffer | Buffer[] | Hex | EIP712MessagePayload; curveType: number; hashType: number; encodingType?: number; @@ -97,18 +76,14 @@ export interface SigningPayload< decoder?: Buffer; } -export interface SignRequestParams< - TTypedData extends TypedData | Record = TypedData, -> { +export interface SignRequestParams = TypedData> { data: SigningPayload | BitcoinSignPayload; currency?: Currency; cachedData?: unknown; nextCode?: Buffer; } -export interface SignRequestFunctionParams< - TTypedData extends TypedData | Record = TypedData, -> extends SignRequestParams { +export interface SignRequestFunctionParams = TypedData> extends SignRequestParams { client: Client; } @@ -178,16 +153,9 @@ export interface DecodeSignResponseParams { } // Align EIP712MessagePayload with Viem's TypedDataDefinition -export interface EIP712MessagePayload< - TTypedData extends TypedData | Record = TypedData, - TPrimaryType extends keyof TTypedData | 'EIP712Domain' = keyof TTypedData, -> { +export interface EIP712MessagePayload = TypedData, TPrimaryType extends keyof TTypedData | 'EIP712Domain' = keyof TTypedData> { types: TTypedData; - domain: TTypedData extends TypedData - ? TypedDataDefinition['domain'] - : Record; + domain: TTypedData extends TypedData ? TypedDataDefinition['domain'] : Record; primaryType: TPrimaryType; - message: TTypedData extends TypedData - ? TypedDataDefinition['message'] - : Record; + message: TTypedData extends TypedData ? TypedDataDefinition['message'] : Record; } diff --git a/packages/sdk/src/util.ts b/packages/sdk/src/util.ts index ee80e6ff..4c14be5d 100644 --- a/packages/sdk/src/util.ts +++ b/packages/sdk/src/util.ts @@ -15,18 +15,9 @@ import { type Hex, parseTransaction } from 'viem'; const EC = elliptic.ec; const { ecdsaRecover } = secp256k1; import { Calldata } from '.'; -import { - BIP_CONSTANTS, - EXTERNAL_NETWORKS_BY_CHAIN_ID_URL, - HARDENED_OFFSET, - NETWORKS_BY_CHAIN_ID, - VERSION_BYTE, -} from './constants'; +import { BIP_CONSTANTS, EXTERNAL_NETWORKS_BY_CHAIN_ID_URL, HARDENED_OFFSET, NETWORKS_BY_CHAIN_ID, VERSION_BYTE } from './constants'; import { LatticeResponseCode, ProtocolConstants } from './protocol'; -import { - isValid4ByteResponse, - isValidBlockExplorerResponse, -} from './shared/validators'; +import { isValid4ByteResponse, isValidBlockExplorerResponse } from './shared/validators'; import type { FirmwareConstants } from './types'; const { COINS, PURPOSES } = BIP_CONSTANTS; @@ -126,37 +117,19 @@ export const toPaddedDER = (sig: any): Buffer => { // TRANSACTION UTILS //-------------------------------------------------- /** @internal */ -export const isValidAssetPath = ( - path: number[], - fwConstants: FirmwareConstants, -): boolean => { - const allowedPurposes = [ - PURPOSES.ETH, - PURPOSES.BTC_LEGACY, - PURPOSES.BTC_WRAPPED_SEGWIT, - PURPOSES.BTC_SEGWIT, - ]; +export const isValidAssetPath = (path: number[], fwConstants: FirmwareConstants): boolean => { + const allowedPurposes = [PURPOSES.ETH, PURPOSES.BTC_LEGACY, PURPOSES.BTC_WRAPPED_SEGWIT, PURPOSES.BTC_SEGWIT]; const allowedCoins = [COINS.ETH, COINS.BTC, COINS.BTC_TESTNET]; // These coin types were given to us by MyCrypto. They should be allowed, but we expect // an Ethereum-type address with these coin types. // These all use SLIP44: https://github.com/satoshilabs/slips/blob/master/slip-0044.md - const allowedMyCryptoCoins = [ - 60, 61, 966, 700, 9006, 9000, 1007, 553, 178, 137, 37310, 108, 40, 889, - 1987, 820, 6060, 1620, 1313114, 76, 246529, 246785, 1001, 227, 916, 464, - 2221, 344, 73799, 246, - ]; + const allowedMyCryptoCoins = [60, 61, 966, 700, 9006, 9000, 1007, 553, 178, 137, 37310, 108, 40, 889, 1987, 820, 6060, 1620, 1313114, 76, 246529, 246785, 1001, 227, 916, 464, 2221, 344, 73799, 246]; // Make sure firmware supports this Bitcoin path const isBitcoin = path[1] === COINS.BTC || path[1] === COINS.BTC_TESTNET; - const isBitcoinNonWrappedSegwit = - isBitcoin && path[0] !== PURPOSES.BTC_WRAPPED_SEGWIT; - if (isBitcoinNonWrappedSegwit && !fwConstants.allowBtcLegacyAndSegwitAddrs) - return false; + const isBitcoinNonWrappedSegwit = isBitcoin && path[0] !== PURPOSES.BTC_WRAPPED_SEGWIT; + if (isBitcoinNonWrappedSegwit && !fwConstants.allowBtcLegacyAndSegwitAddrs) return false; // Make sure this path is otherwise valid - return ( - allowedPurposes.indexOf(path[0]) >= 0 && - (allowedCoins.indexOf(path[1]) >= 0 || - allowedMyCryptoCoins.indexOf(path[1] - HARDENED_OFFSET) > 0) - ); + return allowedPurposes.indexOf(path[0]) >= 0 && (allowedCoins.indexOf(path[1]) >= 0 || allowedMyCryptoCoins.indexOf(path[1] - HARDENED_OFFSET) > 0); }; /** @internal */ @@ -181,23 +154,15 @@ function isBase10NumStr(x: string): boolean { } /** @internal Ensure a param is represented by a buffer */ -export const ensureHexBuffer = ( - x: string | number | bigint | Buffer, - zeroIsNull = true, -): Buffer => { +export const ensureHexBuffer = (x: string | number | bigint | Buffer, zeroIsNull = true): Buffer => { try { const isZeroNumber = typeof x === 'number' && x === 0; const isZeroBigInt = typeof x === 'bigint' && x === 0n; - if (x === null || ((isZeroNumber || isZeroBigInt) && zeroIsNull === true)) - return Buffer.alloc(0); - const isDecimalInput = - typeof x === 'number' || - typeof x === 'bigint' || - (typeof x === 'string' && isBase10NumStr(x)); + if (x === null || ((isZeroNumber || isZeroBigInt) && zeroIsNull === true)) return Buffer.alloc(0); + const isDecimalInput = typeof x === 'number' || typeof x === 'bigint' || (typeof x === 'string' && isBase10NumStr(x)); let hexString: string; if (isDecimalInput) { - const formatted = - typeof x === 'bigint' ? x.toString(10) : (x as string | number); + const formatted = typeof x === 'bigint' ? x.toString(10) : (x as string | number); hexString = new BigNum(formatted).toString(16); } else if (typeof x === 'string' && x.slice(0, 2) === '0x') { hexString = x.slice(2); @@ -210,9 +175,7 @@ export const ensureHexBuffer = ( if (hexString === '00' && !isDecimalInput) return Buffer.alloc(0); return Buffer.from(hexString, 'hex'); } catch (_err) { - throw new Error( - `Cannot convert ${x.toString()} to hex buffer (${(_err as Error).message})`, - ); + throw new Error(`Cannot convert ${x.toString()} to hex buffer (${(_err as Error).message})`); } }; @@ -233,8 +196,7 @@ export const fixLen = (msg: Buffer, length: number): Buffer => { export const aes256_encrypt = (data: Buffer, key: Buffer): Buffer => { const iv = Buffer.from(ProtocolConstants.aesIv); const aesCbc = new aes.ModeOfOperation.cbc(key, iv); - const paddedData = - data.length % 16 === 0 ? data : aes.padding.pkcs7.pad(data); + const paddedData = data.length % 16 === 0 ? data : aes.padding.pkcs7.pad(data); return Buffer.from(aesCbc.encrypt(paddedData)); }; @@ -248,8 +210,7 @@ export const aes256_decrypt = (data: Buffer, key: Buffer): Buffer => { // Decode a DER signature. Returns signature object {r, s } or null if there is an error /** @internal */ export const parseDER = (sigBuf: Buffer) => { - if (sigBuf[0] !== 0x30 || sigBuf[2] !== 0x02) - throw new Error('Failed to decode DER signature'); + if (sigBuf[0] !== 0x30 || sigBuf[2] !== 0x02) throw new Error('Failed to decode DER signature'); let off = 3; const rLen = sigBuf[off]; off++; @@ -278,18 +239,11 @@ export const getP256KeyPairFromPub = (pub: Buffer | string): any => { }; /** @internal */ -export const buildSignerPathBuf = ( - signerPath: number[], - varAddrPathSzAllowed: boolean, -): Buffer => { +export const buildSignerPathBuf = (signerPath: number[], varAddrPathSzAllowed: boolean): Buffer => { const buf = Buffer.alloc(24); let off = 0; - if (varAddrPathSzAllowed && signerPath.length > 5) - throw new Error('Signer path must be <=5 indices.'); - if (!varAddrPathSzAllowed && signerPath.length !== 5) - throw new Error( - 'Your Lattice firmware only supports 5-index derivation paths. Please upgrade.', - ); + if (varAddrPathSzAllowed && signerPath.length > 5) throw new Error('Signer path must be <=5 indices.'); + if (!varAddrPathSzAllowed && signerPath.length !== 5) throw new Error('Your Lattice firmware only supports 5-index derivation paths. Please upgrade.'); buf.writeUInt32LE(signerPath.length, off); off += 4; for (let i = 0; i < 5; i++) { @@ -324,8 +278,7 @@ export const isAsciiStr = (str: string, allowFormatChars = false): boolean => { }; /** @internal Check if a value exists in an object. Only checks first level of keys. */ -export const existsIn = (val: T, obj: { [key: string]: T }): boolean => - Object.keys(obj).some((key) => obj[key] === val); +export const existsIn = (val: T, obj: { [key: string]: T }): boolean => Object.keys(obj).some((key) => obj[key] === val); /** @internal Create a buffer of size `n` and fill it with random data */ export const randomBytes = (n: number): Buffer => { @@ -343,9 +296,7 @@ export const isUInt4 = (n: number) => isInteger(n) && inRange(n, 0, 16); * Fetches an external JSON file containing networks indexed by chain id from a GridPlus repo, and * returns the parsed JSON. */ -async function fetchExternalNetworkForChainId( - chainId: number | string, -): Promise<{ +async function fetchExternalNetworkForChainId(chainId: number | string): Promise<{ [key: string]: { name: string; baseUrl: string; @@ -353,9 +304,7 @@ async function fetchExternalNetworkForChainId( }; }> { try { - const body = await fetch(EXTERNAL_NETWORKS_BY_CHAIN_ID_URL).then((res) => - res.json(), - ); + const body = await fetch(EXTERNAL_NETWORKS_BY_CHAIN_ID_URL).then((res) => res.json()); if (body) { return body[chainId]; } else { @@ -397,10 +346,7 @@ export function selectDefFrom4byteABI(abiData: any[], selector: string) { }) .find((result) => { try { - def = Calldata.EVM.parsers.parseCanonicalName( - selector, - result.text_signature, - ); + def = Calldata.EVM.parsers.parseCanonicalName(selector, result.text_signature); return !!def; } catch (_err) { console.error('Failed to parse canonical name:', _err); @@ -414,10 +360,7 @@ export function selectDefFrom4byteABI(abiData: any[], selector: string) { } } -export async function fetchWithTimeout( - url: string, - options: RequestInit & { timeout?: number }, -): Promise { +export async function fetchWithTimeout(url: string, options: RequestInit & { timeout?: number }): Promise { const { timeout = 8000 } = options; const controller = new AbortController(); const timeoutId = setTimeout(() => controller.abort(), timeout); @@ -429,10 +372,7 @@ export async function fetchWithTimeout( return response; } -async function fetchAndCache( - url: string, - opts?: RequestInit, -): Promise { +async function fetchAndCache(url: string, opts?: RequestInit): Promise { try { if (globalThis.caches && globalThis.Request) { const cache = await caches.open('gp-calldata'); @@ -444,10 +384,7 @@ async function fetchAndCache( const response = await fetch(request, opts); const responseClone = response.clone(); const data = await response.json(); - if ( - response.ok && - (isValidBlockExplorerResponse(data) || isValid4ByteResponse(data)) - ) { + if (response.ok && (isValidBlockExplorerResponse(data) || isValid4ByteResponse(data))) { await cache.put(request, responseClone); return cache.match(request); } @@ -462,10 +399,7 @@ async function fetchAndCache( } } -async function fetchSupportedChainData( - address: string, - supportedChain: number, -) { +async function fetchSupportedChainData(address: string, supportedChain: number) { const url = buildUrlForSupportedChainAndAddress({ address, supportedChain }); return fetchAndCache(url) .then((res) => res.json()) @@ -474,9 +408,7 @@ async function fetchSupportedChainData( try { return JSON.parse(body.result); } catch { - throw new Error( - `Invalid JSON in response: ${body.result.substring(0, 50)}`, - ); + throw new Error(`Invalid JSON in response: ${body.result.substring(0, 50)}`); } } else { throw new Error('Server response was malformed'); @@ -521,10 +453,7 @@ async function postProcessDef(def, calldata) { // value (or for `bytes[]` each underlying value) is of size (4 + 32*n) // it could be nested calldata. We should use that item's selector(s) // to look up nested definition(s). - const nestedCalldata = Calldata.EVM.processors.getNestedCalldata( - def, - calldata, - ); + const nestedCalldata = Calldata.EVM.processors.getNestedCalldata(def, calldata); const nestedDefs = await replaceNestedDefs(nestedCalldata); // Need to recurse before doing the full replacement for await (const [i] of nestedDefs.entries()) { @@ -534,17 +463,11 @@ async function postProcessDef(def, calldata) { if (Array.isArray(nestedDefs[i]) && typeof nestedDefs[i][0] !== 'string') { for await (const [j] of nestedDefs[i].entries()) { if (nestedDefs[i][j] !== null) { - nestedDefs[i][j] = await postProcessDef( - nestedDefs[i][j], - Buffer.from(nestedCalldata[i][j].slice(2), 'hex'), - ); + nestedDefs[i][j] = await postProcessDef(nestedDefs[i][j], Buffer.from(nestedCalldata[i][j].slice(2), 'hex')); } } } else if (nestedDefs[i] !== null) { - nestedDefs[i] = await postProcessDef( - nestedDefs[i], - Buffer.from(nestedCalldata[i].slice(2), 'hex'), - ); + nestedDefs[i] = await postProcessDef(nestedDefs[i], Buffer.from(nestedCalldata[i].slice(2), 'hex')); } } // Replace any nested defs @@ -577,10 +500,7 @@ async function replaceNestedDefs(possNestedDefs) { try { const _nestedSelector = _d.slice(2, 10); const _nestedAbi = await fetch4byteData(_nestedSelector); - const _nestedDef = selectDefFrom4byteABI( - _nestedAbi, - _nestedSelector, - ); + const _nestedDef = selectDefFrom4byteABI(_nestedAbi, _nestedSelector); _nestedDefs.push(_nestedDef); } catch (_err) { console.error('Failed to fetch nested 4byte data:', _err); @@ -620,12 +540,7 @@ async function replaceNestedDefs(possNestedDefs) { /** * Fetches calldata from a remote scanner based on the transaction's `chainId` */ -export async function fetchCalldataDecoder( - _data: Uint8Array | string, - to: string, - _chainId: number | string, - recurse = true, -) { +export async function fetchCalldataDecoder(_data: Uint8Array | string, to: string, _chainId: number | string, recurse = true) { try { // Exit if there is no data. The 2 comes from the 0x prefix, but a later // check will confirm that there are at least 4 bytes of data in the buffer. @@ -644,25 +559,18 @@ export async function fetchCalldataDecoder( } if (data.length < 4) { - throw new Error( - 'Data must contain at least 4 bytes of data to define the selector', - ); + throw new Error('Data must contain at least 4 bytes of data to define the selector'); } const selector = Buffer.from(data.slice(0, 4)).toString('hex'); // Convert the chainId to a number and use it to determine if we can call out to // an etherscan-like explorer for richer data. const chainId = Number(_chainId); const cachedNetwork = NETWORKS_BY_CHAIN_ID[chainId]; - const supportedChain = cachedNetwork - ? cachedNetwork - : await fetchExternalNetworkForChainId(chainId); + const supportedChain = cachedNetwork ? cachedNetwork : await fetchExternalNetworkForChainId(chainId); try { if (supportedChain) { const abi = await fetchSupportedChainData(to, supportedChain); - const parsedAbi = Calldata.EVM.parsers.parseSolidityJSONABI( - selector, - abi, - ); + const parsedAbi = Calldata.EVM.parsers.parseSolidityJSONABI(selector, abi); let def = parsedAbi.def; if (recurse) { def = await postProcessDef(def, data); @@ -697,23 +605,12 @@ export async function fetchCalldataDecoder( * @returns an application secret as a Buffer * @public */ -export const generateAppSecret = ( - deviceId: Buffer | string, - password: Buffer | string, - appName: Buffer | string, -): Buffer => { - const deviceIdBuffer = - typeof deviceId === 'string' ? Buffer.from(deviceId) : deviceId; - const passwordBuffer = - typeof password === 'string' ? Buffer.from(password) : password; - const appNameBuffer = - typeof appName === 'string' ? Buffer.from(appName) : appName; - - const preImage = Buffer.concat([ - deviceIdBuffer, - passwordBuffer, - appNameBuffer, - ]); +export const generateAppSecret = (deviceId: Buffer | string, password: Buffer | string, appName: Buffer | string): Buffer => { + const deviceIdBuffer = typeof deviceId === 'string' ? Buffer.from(deviceId) : deviceId; + const passwordBuffer = typeof password === 'string' ? Buffer.from(password) : password; + const appNameBuffer = typeof appName === 'string' ? Buffer.from(appName) : appName; + + const preImage = Buffer.concat([deviceIdBuffer, passwordBuffer, appNameBuffer]); return Buffer.from(Hash.sha256(preImage)); }; @@ -731,9 +628,7 @@ export const getV = (tx: any, resp: any) => { let useEIP155 = false; if (Buffer.isBuffer(tx) || typeof tx === 'string') { - const txHex = Buffer.isBuffer(tx) - ? (`0x${tx.toString('hex')}` as Hex) - : (tx as Hex); + const txHex = Buffer.isBuffer(tx) ? (`0x${tx.toString('hex')}` as Hex) : (tx as Hex); const txBuf = Buffer.isBuffer(tx) ? tx : Buffer.from(tx.slice(2), 'hex'); hash = Buffer.from(Hash.keccak256(txBuf)); @@ -762,9 +657,7 @@ export const getV = (tx: any, resp: any) => { } catch (err) { console.error('Failed to parse transaction, trying legacy format:', err); try { - const txBufRaw = Buffer.isBuffer(tx) - ? tx - : Buffer.from(tx.slice(2), 'hex'); + const txBufRaw = Buffer.isBuffer(tx) ? tx : Buffer.from(tx.slice(2), 'hex'); const legacyTxArray = RLP.decode(txBufRaw); type = 'legacy'; @@ -780,17 +673,11 @@ export const getV = (tx: any, resp: any) => { } } } else { - throw new Error( - 'Unsupported transaction format. Expected Buffer or hex string.', - ); + throw new Error('Unsupported transaction format. Expected Buffer or hex string.'); } - const rBuf = Buffer.isBuffer(resp.sig.r) - ? resp.sig.r - : Buffer.from(resp.sig.r.slice(2), 'hex'); - const sBuf = Buffer.isBuffer(resp.sig.s) - ? resp.sig.s - : Buffer.from(resp.sig.s.slice(2), 'hex'); + const rBuf = Buffer.isBuffer(resp.sig.r) ? resp.sig.r : Buffer.from(resp.sig.r.slice(2), 'hex'); + const sBuf = Buffer.isBuffer(resp.sig.s) ? resp.sig.s : Buffer.from(resp.sig.s.slice(2), 'hex'); const rs = new Uint8Array(Buffer.concat([rBuf, sBuf])); const pubkeyInput = resp.pubkey; @@ -804,9 +691,7 @@ export const getV = (tx: any, resp: any) => { } else if (pubkeyInput instanceof Uint8Array) { pubkeyBuf = Buffer.from(pubkeyInput); } else if (typeof pubkeyInput === 'string') { - const hex = pubkeyInput.startsWith('0x') - ? pubkeyInput.slice(2) - : pubkeyInput; + const hex = pubkeyInput.startsWith('0x') ? pubkeyInput.slice(2) : pubkeyInput; pubkeyBuf = Buffer.from(hex, 'hex'); } else { pubkeyBuf = Buffer.from(pubkeyInput); @@ -816,8 +701,7 @@ export const getV = (tx: any, resp: any) => { pubkeyBuf = Buffer.concat([Buffer.from([0x04]), pubkeyBuf]); } - const isCompressedPubkey = - pubkeyBuf.length === 33 && (pubkeyBuf[0] === 0x02 || pubkeyBuf[0] === 0x03); + const isCompressedPubkey = pubkeyBuf.length === 33 && (pubkeyBuf[0] === 0x02 || pubkeyBuf[0] === 0x03); const isUncompressedPubkey = pubkeyBuf.length === 65 && pubkeyBuf[0] === 0x04; if (!isCompressedPubkey && !isUncompressedPubkey) { @@ -837,9 +721,7 @@ export const getV = (tx: any, resp: any) => { } else if (pubkeyStr === recovery1Str) { recovery = 1; } else { - throw new Error( - 'Failed to recover V parameter. Bad signature or transaction data.', - ); + throw new Error('Failed to recover V parameter. Bad signature or transaction data.'); } // Use the consolidated v parameter conversion logic @@ -870,23 +752,13 @@ export const getV = (tx: any, resp: any) => { * @param txData - Transaction data containing chainId, useEIP155, and type * @returns The properly formatted v value as Buffer or BN */ -export const convertRecoveryToV = ( - recovery: number, - txData: any = {}, -): Buffer | InstanceType => { +export const convertRecoveryToV = (recovery: number, txData: any = {}): Buffer | InstanceType => { const { chainId, useEIP155, type } = txData; // For typed transactions (EIP-2930, EIP-1559, EIP-7702), we want the recoveryParam (0 or 1) // rather than the `v` value because the `chainId` is already included in the // transaction payload. - if ( - type === 1 || - type === 2 || - type === 4 || - type === 'eip2930' || - type === 'eip1559' || - type === 'eip7702' - ) { + if (type === 1 || type === 2 || type === 4 || type === 'eip2930' || type === 'eip1559' || type === 'eip7702') { return ensureHexBuffer(recovery, true); // 0 or 1, with 0 expected as an empty buffer } else if (!useEIP155 || !chainId) { // For ETH messages and non-EIP155 chains the set should be [27, 28] for `v` @@ -913,27 +785,10 @@ export const convertRecoveryToV = ( * @param publicKey - Expected public key * @returns 0 or 1 for the y-parity value */ -export const getYParity = ( - messageHash: - | Buffer - | Uint8Array - | string - | { messageHash: any; signature: any; publicKey: any } - | any, - signature?: { r: any; s: any } | any, - publicKey?: Buffer | Uint8Array | string, -): number => { +export const getYParity = (messageHash: Buffer | Uint8Array | string | { messageHash: any; signature: any; publicKey: any } | any, signature?: { r: any; s: any } | any, publicKey?: Buffer | Uint8Array | string): number => { // Handle legacy object format for backward compatibility - if ( - typeof messageHash === 'object' && - messageHash && - 'messageHash' in messageHash - ) { - return getYParity( - messageHash.messageHash, - messageHash.signature, - messageHash.publicKey, - ); + if (typeof messageHash === 'object' && messageHash && 'messageHash' in messageHash) { + return getYParity(messageHash.messageHash, messageHash.signature, messageHash.publicKey); } // Handle legacy transaction format for backward compatibility @@ -952,11 +807,7 @@ export const getYParity = ( // Handle transaction objects with getMessageToSign let hash = messageHash; - if ( - typeof messageHash === 'object' && - messageHash && - typeof messageHash.getMessageToSign === 'function' - ) { + if (typeof messageHash === 'object' && messageHash && typeof messageHash.getMessageToSign === 'function') { const type = messageHash._type; if (type !== undefined && type !== null) { // EIP-1559 / EIP-2930 / future typed transactions @@ -988,8 +839,7 @@ export const getYParity = ( const pubkeyBuf = toBuffer(publicKey); // For non-32 byte hashes, hash them (legacy support) - const finalHash = - hashBuf.length === 32 ? hashBuf : Buffer.from(Hash.keccak256(hashBuf)); + const finalHash = hashBuf.length === 32 ? hashBuf : Buffer.from(Hash.keccak256(hashBuf)); // Combine r and s const rs = new Uint8Array(Buffer.concat([rBuf, sBuf])); @@ -1006,9 +856,7 @@ export const getYParity = ( } catch {} } - throw new Error( - 'Failed to recover Y parity. Bad signature or transaction data.', - ); + throw new Error('Failed to recover Y parity. Bad signature or transaction data.'); }; /** @internal */ From 42356dd6051710d47da86d9bc01d64108aaff2df Mon Sep 17 00:00:00 2001 From: netbonus <151201453+netbonus@users.noreply.github.com> Date: Wed, 21 Jan 2026 15:25:06 -0500 Subject: [PATCH 10/14] style: use default 80-char line width --- biome.json | 1 - .../docs/src/components/HomepageFeatures.tsx | 17 +- packages/docs/src/pages/_index.tsx | 10 +- packages/example/src/App.tsx | 21 +- packages/example/src/Lattice.tsx | 11 +- packages/sdk/src/__test__/e2e/api.test.ts | 130 +++-- packages/sdk/src/__test__/e2e/btc.test.ts | 21 +- packages/sdk/src/__test__/e2e/eth.msg.test.ts | 114 +++-- packages/sdk/src/__test__/e2e/general.test.ts | 97 +++- packages/sdk/src/__test__/e2e/kv.test.ts | 32 +- .../src/__test__/e2e/non-exportable.test.ts | 28 +- .../sdk/src/__test__/e2e/signing/bls.test.ts | 73 ++- .../__test__/e2e/signing/determinism.test.ts | 144 +++++- .../__test__/e2e/signing/eip712-vectors.ts | 8 +- .../src/__test__/e2e/signing/evm-tx.test.ts | 8 +- .../e2e/signing/solana/__mocks__/programs.ts | 110 +++-- .../e2e/signing/solana/solana.test.ts | 42 +- .../signing/solana/solana.versioned.test.ts | 37 +- .../__test__/e2e/signing/unformatted.test.ts | 20 +- .../sdk/src/__test__/e2e/signing/vectors.ts | 95 +++- .../__test__/integration/__mocks__/4byte.ts | 9 +- .../integration/__mocks__/etherscan.ts | 12 +- .../integration/__mocks__/handlers.ts | 16 +- .../integration/fetchCalldataDecoder.test.ts | 6 +- .../__test__/unit/__mocks__/decoderData.ts | 27 +- packages/sdk/src/__test__/unit/api.test.ts | 4 +- .../unit/compareEIP7702Serialization.test.ts | 5 +- .../sdk/src/__test__/unit/decoders.test.ts | 23 +- .../sdk/src/__test__/unit/eip7702.test.ts | 5 +- .../sdk/src/__test__/unit/encoders.test.ts | 46 +- .../__test__/unit/ethereum.validate.test.ts | 17 +- .../src/__test__/unit/module.interop.test.ts | 12 +- .../unit/parseGenericSigningResponse.test.ts | 35 +- .../unit/personalSignValidation.test.ts | 40 +- .../unit/selectDefFrom4byteABI.test.ts | 6 +- .../src/__test__/unit/signatureUtils.test.ts | 91 +++- .../sdk/src/__test__/unit/validators.test.ts | 35 +- .../__test__/utils/__test__/builders.test.ts | 6 +- .../utils/__test__/serializers.test.ts | 5 +- packages/sdk/src/__test__/utils/builders.ts | 70 ++- .../sdk/src/__test__/utils/determinism.ts | 6 +- packages/sdk/src/__test__/utils/ethers.ts | 29 +- packages/sdk/src/__test__/utils/helpers.ts | 246 ++++++++-- packages/sdk/src/__test__/utils/runners.ts | 17 +- packages/sdk/src/__test__/utils/setup.ts | 4 +- .../sdk/src/__test__/utils/testConstants.ts | 3 +- .../sdk/src/__test__/utils/testEnvironment.ts | 3 +- .../sdk/src/__test__/utils/testRequest.ts | 15 +- .../sdk/src/__test__/utils/viemComparison.ts | 27 +- packages/sdk/src/api/addressTags.ts | 13 +- packages/sdk/src/api/addresses.ts | 88 +++- packages/sdk/src/api/setup.ts | 4 +- packages/sdk/src/api/signing.ts | 126 ++++- packages/sdk/src/api/state.ts | 4 +- packages/sdk/src/api/utilities.ts | 21 +- packages/sdk/src/bitcoin.ts | 45 +- packages/sdk/src/calldata/evm.ts | 59 ++- packages/sdk/src/calldata/index.ts | 7 +- packages/sdk/src/client.ts | 93 +++- packages/sdk/src/constants.ts | 112 ++++- packages/sdk/src/ethereum.ts | 456 ++++++++++++++---- packages/sdk/src/functions/addKvRecords.ts | 32 +- packages/sdk/src/functions/connect.ts | 28 +- .../sdk/src/functions/fetchActiveWallet.ts | 19 +- packages/sdk/src/functions/fetchDecoder.ts | 16 +- packages/sdk/src/functions/fetchEncData.ts | 44 +- packages/sdk/src/functions/getAddresses.ts | 60 ++- packages/sdk/src/functions/getKvRecords.ts | 41 +- packages/sdk/src/functions/pair.ts | 19 +- packages/sdk/src/functions/removeKvRecords.ts | 23 +- packages/sdk/src/functions/sign.ts | 72 ++- packages/sdk/src/genericSigning.ts | 108 ++++- packages/sdk/src/protocol/latticeConstants.ts | 8 +- packages/sdk/src/protocol/secureMessages.ts | 78 ++- packages/sdk/src/schemas/transaction.ts | 130 +++-- packages/sdk/src/shared/functions.ts | 32 +- packages/sdk/src/shared/predicates.ts | 16 +- packages/sdk/src/shared/utilities.ts | 11 +- packages/sdk/src/shared/validators.ts | 77 ++- packages/sdk/src/types/addKvRecords.ts | 3 +- packages/sdk/src/types/firmware.ts | 5 +- packages/sdk/src/types/getAddresses.ts | 3 +- packages/sdk/src/types/getKvRecords.ts | 3 +- packages/sdk/src/types/removeKvRecords.ts | 3 +- packages/sdk/src/types/sign.ts | 52 +- packages/sdk/src/util.ts | 266 +++++++--- 86 files changed, 3174 insertions(+), 842 deletions(-) diff --git a/biome.json b/biome.json index 097adda7..aed23215 100644 --- a/biome.json +++ b/biome.json @@ -11,7 +11,6 @@ "indentStyle": "space", "indentWidth": 2, "lineEnding": "lf", - "lineWidth": 240, "attributePosition": "auto" }, "linter": { diff --git a/packages/docs/src/components/HomepageFeatures.tsx b/packages/docs/src/components/HomepageFeatures.tsx index 8aea02f5..72309dd8 100644 --- a/packages/docs/src/components/HomepageFeatures.tsx +++ b/packages/docs/src/components/HomepageFeatures.tsx @@ -11,21 +11,32 @@ const FeatureList: FeatureItem[] = [ { title: 'Easy to Use', image: '/img/undraw_docusaurus_mountain.svg', - description: <>Docusaurus was designed from the ground up to be easily installed and used to get your website up and running quickly., + description: ( + <> + Docusaurus was designed from the ground up to be easily installed and + used to get your website up and running quickly. + + ), }, { title: 'Focus on What Matters', image: '/img/undraw_docusaurus_tree.svg', description: ( <> - Docusaurus lets you focus on your docs, and we'll do the chores. Go ahead and move your docs into the docs directory. + Docusaurus lets you focus on your docs, and we'll do the chores. Go + ahead and move your docs into the docs directory. ), }, { title: 'Powered by React', image: '/img/undraw_docusaurus_react.svg', - description: <>Extend or customize your website layout by reusing React. Docusaurus can be extended while reusing the same header and footer., + description: ( + <> + Extend or customize your website layout by reusing React. Docusaurus can + be extended while reusing the same header and footer. + + ), }, ]; diff --git a/packages/docs/src/pages/_index.tsx b/packages/docs/src/pages/_index.tsx index 18527f39..a52585e6 100644 --- a/packages/docs/src/pages/_index.tsx +++ b/packages/docs/src/pages/_index.tsx @@ -18,7 +18,10 @@ function HomepageHeader() {

{siteConfig.title}

{siteConfig.tagline}

- + Getting Started
@@ -31,7 +34,10 @@ export default function Home(): JSX.Element { const { siteConfig } = useDocusaurusContext(); const ExtendedLayout = Layout as React.ComponentType; return ( - +
{/* */}
diff --git a/packages/example/src/App.tsx b/packages/example/src/App.tsx index 92f9c96a..07d3d3e8 100644 --- a/packages/example/src/App.tsx +++ b/packages/example/src/App.tsx @@ -6,7 +6,10 @@ import { Lattice } from './Lattice'; function App() { const [label, setLabel] = useState('No Device'); - const getStoredClient = useCallback(async () => window.localStorage.getItem('storedClient') || '', []); + const getStoredClient = useCallback( + async () => window.localStorage.getItem('storedClient') || '', + [], + ); const setStoredClient = useCallback(async (storedClient: string | null) => { if (!storedClient) return; @@ -60,10 +63,17 @@ function App() { border: '1px solid black', }} > -
+ - +
@@ -76,7 +86,10 @@ function App() { border: '1px solid black', }} > -
+
diff --git a/packages/example/src/Lattice.tsx b/packages/example/src/Lattice.tsx index 5f4200ae..d0f3a9dd 100644 --- a/packages/example/src/Lattice.tsx +++ b/packages/example/src/Lattice.tsx @@ -1,5 +1,14 @@ import { useState } from 'react'; -import { addAddressTags, fetchAddressTags, fetchAddresses, fetchLedgerLiveAddresses, removeAddressTags, sign, signMessage, type AddressTag } from 'gridplus-sdk'; +import { + addAddressTags, + fetchAddressTags, + fetchAddresses, + fetchLedgerLiveAddresses, + removeAddressTags, + sign, + signMessage, + type AddressTag, +} from 'gridplus-sdk'; import { Button } from './Button'; interface LatticeProps { diff --git a/packages/sdk/src/__test__/e2e/api.test.ts b/packages/sdk/src/__test__/e2e/api.test.ts index dfa025d4..a7cc353b 100644 --- a/packages/sdk/src/__test__/e2e/api.test.ts +++ b/packages/sdk/src/__test__/e2e/api.test.ts @@ -3,7 +3,8 @@ vi.mock('../../functions/fetchDecoder.ts', () => ({ })); vi.mock('../../util', async () => { - const actual = await vi.importActual('../../util'); + const actual = + await vi.importActual('../../util'); return { ...actual, fetchCalldataDecoder: vi.fn().mockResolvedValue({ @@ -34,7 +35,14 @@ import { signBtcWrappedSegwitTx, signMessage, } from '../../api'; -import { addAddressTags, fetchAddressTags, fetchLedgerLiveAddresses, removeAddressTags, sign, signSolanaTx } from '../../api/index'; +import { + addAddressTags, + fetchAddressTags, + fetchLedgerLiveAddresses, + removeAddressTags, + sign, + signSolanaTx, +} from '../../api/index'; import { HARDENED_OFFSET } from '../../constants'; import { buildRandomMsg } from '../utils/builders'; import { BTC_PURPOSE_P2SH_P2WPKH, BTC_TESTNET_COIN } from '../utils/helpers'; @@ -52,16 +60,29 @@ describe('API', () => { const btcTxData = { prevOuts: [ { - txHash: '6e78493091f80d89a92ae3152df7fbfbdc44df09cf01a9b76c5113c02eaf2e0f', + txHash: + '6e78493091f80d89a92ae3152df7fbfbdc44df09cf01a9b76c5113c02eaf2e0f', value: 10000, index: 1, - signerPath: [BTC_PURPOSE_P2SH_P2WPKH, BTC_TESTNET_COIN, HARDENED_OFFSET, 0, 0], + signerPath: [ + BTC_PURPOSE_P2SH_P2WPKH, + BTC_TESTNET_COIN, + HARDENED_OFFSET, + 0, + 0, + ], }, ], recipient: 'mhifA1DwiMPHTjSJM8FFSL8ibrzWaBCkVT', value: 1000, fee: 1000, - changePath: [BTC_PURPOSE_P2SH_P2WPKH, BTC_TESTNET_COIN, HARDENED_OFFSET, 1, 0], + changePath: [ + BTC_PURPOSE_P2SH_P2WPKH, + BTC_TESTNET_COIN, + HARDENED_OFFSET, + 1, + 0, + ], }; test('legacy', async () => { await signBtcLegacyTx(btcTxData); @@ -105,8 +126,16 @@ describe('API', () => { }); test('legacy', async () => { - const toHex = (v: bigint | number) => (typeof v === 'bigint' ? `0x${v.toString(16)}` : v); - const rawTx = RLP.encode([txData.nonce, toHex(txData.gasPrice), toHex(txData.gas), txData.to, toHex(txData.value), txData.data]); + const toHex = (v: bigint | number) => + typeof v === 'bigint' ? `0x${v.toString(16)}` : v; + const rawTx = RLP.encode([ + txData.nonce, + toHex(txData.gasPrice), + toHex(txData.gas), + txData.to, + toHex(txData.value), + txData.data, + ]); await sign(rawTx); }); }); @@ -122,9 +151,20 @@ describe('API', () => { describe('address tags', () => { beforeAll(async () => { try { - await Promise.race([fetchAddressTags({ n: 1 }), new Promise((_, reject) => setTimeout(() => reject(new Error('Address tag RPC timed out')), 5000))]); + await Promise.race([ + fetchAddressTags({ n: 1 }), + new Promise((_, reject) => + setTimeout( + () => reject(new Error('Address tag RPC timed out')), + 5000, + ), + ), + ]); } catch (err) { - console.warn('Skipping address tag tests due to connectivity issue:', (err as Error).message); + console.warn( + 'Skipping address tag tests due to connectivity issue:', + (err as Error).message, + ); } }); @@ -207,15 +247,19 @@ describe('API', () => { describe('fetchAddressesByDerivationPath', () => { test('fetch single specific address', async () => { - const addresses = await fetchAddressesByDerivationPath("44'/60'/0'/0/0"); + const addresses = + await fetchAddressesByDerivationPath("44'/60'/0'/0/0"); expect(addresses).toHaveLength(1); expect(addresses[0]).toBeTruthy(); }); test('fetch multiple addresses with wildcard', async () => { - const addresses = await fetchAddressesByDerivationPath("44'/60'/0'/0/X", { - n: 5, - }); + const addresses = await fetchAddressesByDerivationPath( + "44'/60'/0'/0/X", + { + n: 5, + }, + ); expect(addresses).toHaveLength(5); addresses.forEach((address) => { expect(address).toBeTruthy(); @@ -223,10 +267,13 @@ describe('API', () => { }); test('fetch addresses with offset', async () => { - const addresses = await fetchAddressesByDerivationPath("44'/60'/0'/0/X", { - n: 3, - startPathIndex: 10, - }); + const addresses = await fetchAddressesByDerivationPath( + "44'/60'/0'/0/X", + { + n: 3, + startPathIndex: 10, + }, + ); expect(addresses).toHaveLength(3); addresses.forEach((address) => { expect(address).toBeTruthy(); @@ -234,9 +281,12 @@ describe('API', () => { }); test('fetch addresses with lowercase x wildcard', async () => { - const addresses = await fetchAddressesByDerivationPath("44'/60'/0'/0/x", { - n: 2, - }); + const addresses = await fetchAddressesByDerivationPath( + "44'/60'/0'/0/x", + { + n: 2, + }, + ); expect(addresses).toHaveLength(2); addresses.forEach((address) => { expect(address).toBeTruthy(); @@ -244,9 +294,12 @@ describe('API', () => { }); test('fetch addresses with wildcard in middle of path', async () => { - const addresses = await fetchAddressesByDerivationPath("44'/60'/X'/0/0", { - n: 3, - }); + const addresses = await fetchAddressesByDerivationPath( + "44'/60'/X'/0/0", + { + n: 3, + }, + ); expect(addresses).toHaveLength(3); addresses.forEach((address) => { expect(address).toBeTruthy(); @@ -254,9 +307,12 @@ describe('API', () => { }); test('fetch solana addresses with wildcard in middle of path', async () => { - const addresses = await fetchAddressesByDerivationPath("44'/501'/X'/0'", { - n: 1, - }); + const addresses = await fetchAddressesByDerivationPath( + "44'/501'/X'/0'", + { + n: 1, + }, + ); expect(addresses).toHaveLength(1); addresses.forEach((address) => { expect(address).toBeTruthy(); @@ -264,21 +320,29 @@ describe('API', () => { }); test('error on invalid derivation path', async () => { - await expect(fetchAddressesByDerivationPath('invalid/path')).rejects.toThrow(); + await expect( + fetchAddressesByDerivationPath('invalid/path'), + ).rejects.toThrow(); }); test('fetch single address when n=1 with wildcard', async () => { - const addresses = await fetchAddressesByDerivationPath("44'/60'/0'/0/X", { - n: 1, - }); + const addresses = await fetchAddressesByDerivationPath( + "44'/60'/0'/0/X", + { + n: 1, + }, + ); expect(addresses).toHaveLength(1); expect(addresses[0]).toBeTruthy(); }); test('fetch no addresses when n=0', async () => { - const addresses = await fetchAddressesByDerivationPath("44'/60'/0'/0/X", { - n: 0, - }); + const addresses = await fetchAddressesByDerivationPath( + "44'/60'/0'/0/X", + { + n: 0, + }, + ); expect(addresses).toHaveLength(0); }); }); diff --git a/packages/sdk/src/__test__/e2e/btc.test.ts b/packages/sdk/src/__test__/e2e/btc.test.ts index 1cbaede9..a345f787 100644 --- a/packages/sdk/src/__test__/e2e/btc.test.ts +++ b/packages/sdk/src/__test__/e2e/btc.test.ts @@ -16,7 +16,13 @@ import BIP32Factory, { type BIP32Interface } from 'bip32'; import * as ecc from 'tiny-secp256k1'; import type { Client } from '../../client'; import { getPrng, getTestnet } from '../utils/getters'; -import { BTC_PURPOSE_P2PKH, BTC_PURPOSE_P2SH_P2WPKH, BTC_PURPOSE_P2WPKH, setup_btc_sig_test, stripDER } from '../utils/helpers'; +import { + BTC_PURPOSE_P2PKH, + BTC_PURPOSE_P2SH_P2WPKH, + BTC_PURPOSE_P2WPKH, + setup_btc_sig_test, + stripDER, +} from '../utils/helpers'; import { setupClient } from '../utils/setup'; import { TEST_SEED } from '../utils/testConstants'; @@ -53,11 +59,20 @@ async function testSign({ txReq, signingKeys, sigHashes, client }: any) { for (let i = 0; i < len; i++) { const sig = stripDER(tx.sigs?.[i]); const verification = signingKeys[i].verify(sigHashes[i], sig); - expect(verification).toEqualElseLog(true, `Signature validation failed for priv=${signingKeys[i].privateKey.toString('hex')}, ` + `hash=${sigHashes[i].toString('hex')}, sig=${sig.toString('hex')}`); + expect(verification).toEqualElseLog( + true, + `Signature validation failed for priv=${signingKeys[i].privateKey.toString('hex')}, ` + + `hash=${sigHashes[i].toString('hex')}, sig=${sig.toString('hex')}`, + ); } } -async function runTestSet(opts: any, wallet: BIP32Interface | null, inputsSlice: InputObj[], client) { +async function runTestSet( + opts: any, + wallet: BIP32Interface | null, + inputsSlice: InputObj[], + client, +) { expect(wallet).not.toEqualElseLog(null, 'Wallet not available'); if (TEST_TESTNET) { // Testnet + change diff --git a/packages/sdk/src/__test__/e2e/eth.msg.test.ts b/packages/sdk/src/__test__/e2e/eth.msg.test.ts index c87bc286..a827211e 100644 --- a/packages/sdk/src/__test__/e2e/eth.msg.test.ts +++ b/packages/sdk/src/__test__/e2e/eth.msg.test.ts @@ -36,21 +36,37 @@ describe('ETH Messages', () => { const protocol = 'signPersonal'; const msg = '⚠️'; const msg2 = 'ASCII plus ⚠️'; - await expect(client.sign(buildEthMsgReq(msg, protocol))).rejects.toThrow(/Lattice can only display ASCII/); - await expect(client.sign(buildEthMsgReq(msg2, protocol))).rejects.toThrow(/Lattice can only display ASCII/); + await expect(client.sign(buildEthMsgReq(msg, protocol))).rejects.toThrow( + /Lattice can only display ASCII/, + ); + await expect(client.sign(buildEthMsgReq(msg2, protocol))).rejects.toThrow( + /Lattice can only display ASCII/, + ); }); it('Should test ASCII buffers', async () => { - await runEthMsg(buildEthMsgReq(Buffer.from('i am an ascii buffer'), 'signPersonal'), client); - await runEthMsg(buildEthMsgReq(Buffer.from('{\n\ttest: foo\n}'), 'signPersonal'), client); + await runEthMsg( + buildEthMsgReq(Buffer.from('i am an ascii buffer'), 'signPersonal'), + client, + ); + await runEthMsg( + buildEthMsgReq(Buffer.from('{\n\ttest: foo\n}'), 'signPersonal'), + client, + ); }); it('Should test hex buffers', async () => { - await runEthMsg(buildEthMsgReq(Buffer.from('abcdef', 'hex'), 'signPersonal'), client); + await runEthMsg( + buildEthMsgReq(Buffer.from('abcdef', 'hex'), 'signPersonal'), + client, + ); }); it('Should test a message that needs to be prehashed', async () => { - await runEthMsg(buildEthMsgReq(randomBytes(4000), 'signPersonal'), client); + await runEthMsg( + buildEthMsgReq(randomBytes(4000), 'signPersonal'), + client, + ); }); it('Msg: sign_personal boundary conditions and auto-rejected requests', async () => { @@ -59,7 +75,10 @@ describe('ETH Messages', () => { // `personal_sign` requests have a max size smaller than other requests because a header // is displayed in the text region of the screen. The size of this is captured // by `fwConstants.personalSignHeaderSz`. - const maxMsgSz = fwConstants.ethMaxMsgSz + fwConstants.personalSignHeaderSz + fwConstants.extraDataMaxFrames * fwConstants.extraDataFrameSz; + const maxMsgSz = + fwConstants.ethMaxMsgSz + + fwConstants.personalSignHeaderSz + + fwConstants.extraDataMaxFrames * fwConstants.extraDataFrameSz; const maxValid = `0x${randomBytes(maxMsgSz).toString('hex')}`; const minInvalid = `0x${randomBytes(maxMsgSz + 1).toString('hex')}`; const zeroInvalid = '0x'; @@ -71,16 +90,30 @@ describe('ETH Messages', () => { // I guess all this tests is that the first one is shown in plaintext while the second // one (which is too large) gets prehashed. const largeSignPath = [x, HARDENED_OFFSET + 60, x, x, x] as SigningPath; - await runEthMsg(buildEthMsgReq(maxValid, protocol, largeSignPath), client); - await runEthMsg(buildEthMsgReq(minInvalid, protocol, largeSignPath), client); + await runEthMsg( + buildEthMsgReq(maxValid, protocol, largeSignPath), + client, + ); + await runEthMsg( + buildEthMsgReq(minInvalid, protocol, largeSignPath), + client, + ); // Using a zero length payload should auto-reject - await expect(client.sign(buildEthMsgReq(zeroInvalid, protocol))).rejects.toThrow(/Invalid Request/); + await expect( + client.sign(buildEthMsgReq(zeroInvalid, protocol)), + ).rejects.toThrow(/Invalid Request/); }); describe(`Test ${5} random payloads`, () => { for (let i = 0; i < 5; i++) { it(`Payload: ${i}`, async () => { - await runEthMsg(buildEthMsgReq(buildRandomMsg('signPersonal', client), 'signPersonal'), client); + await runEthMsg( + buildEthMsgReq( + buildRandomMsg('signPersonal', client), + 'signPersonal', + ), + client, + ); }); } }); @@ -158,7 +191,8 @@ describe('ETH Messages', () => { side: '1', matchingPolicy: '0x00000000006411739da1c40b106f8511de5d1fac', collection: '0x7a15b36cb834aea88553de69077d3777460d73ac', - tokenId: '5280336779268220421569573059971679349075200194886069432279714075018412552192', + tokenId: + '5280336779268220421569573059971679349075200194886069432279714075018412552192', amount: '1', paymentToken: '0x0000000000000000000000000000000000000000', price: '990000000000000000', @@ -233,7 +267,8 @@ describe('ETH Messages', () => { accountID: 32494, feeTokenID: 0, maxFee: 100, - publicKey: '11413934541425201845815969801249874136651857829494005371571206042985258823663', + publicKey: + '11413934541425201845815969801249874136651857829494005371571206042985258823663', validUntil: 1631655383, nonce: 0, }, @@ -266,7 +301,8 @@ describe('ETH Messages', () => { verifyingContract: '0xf03f457a30e598d5020164a339727ef40f2b8fbc', }, message: { - sender: '0x841fe4876763357975d60da128d8a54bb045d76a64656661756c740000000000', + sender: + '0x841fe4876763357975d60da128d8a54bb045d76a64656661756c740000000000', priceX18: '28898000000000000000000', amount: '-10000000000000000', expiration: '4611687701117784255', @@ -285,17 +321,21 @@ describe('ETH Messages', () => { version: '1', }, message: { - getMakerAmount: '0xf4a215c30000000000000000000000000000000000000000000000000de0b6b3a764000000000000000000000000000000000000000000000000000018fae27693b40000', - getTakerAmount: '0x296637bf0000000000000000000000000000000000000000000000000de0b6b3a764000000000000000000000000000000000000000000000000000018fae27693b40000', + getMakerAmount: + '0xf4a215c30000000000000000000000000000000000000000000000000de0b6b3a764000000000000000000000000000000000000000000000000000018fae27693b40000', + getTakerAmount: + '0x296637bf0000000000000000000000000000000000000000000000000de0b6b3a764000000000000000000000000000000000000000000000000000018fae27693b40000', interaction: '0x', makerAsset: '0x0d500b1d8e8ef31e21c99d1db9a6444d3adf1270', - makerAssetData: '0x23b872dd0000000000000000000000003e3e2ccdd7bae6bbd4a64e8d16ca8842061335eb00000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000de0b6b3a7640000', + makerAssetData: + '0x23b872dd0000000000000000000000003e3e2ccdd7bae6bbd4a64e8d16ca8842061335eb00000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000de0b6b3a7640000', permit: '0x', predicate: '0x961d5b1e000000000000000000000000000000000000000000000000000000000000004000000000000000000000000000000000000000000000000000000000000000a00000000000000000000000000000000000000000000000000000000000000002000000000000000000000000b707d89d29c189421163515c59e42147371d6857000000000000000000000000b707d89d29c189421163515c59e42147371d68570000000000000000000000000000000000000000000000000000000000000002000000000000000000000000000000000000000000000000000000000000004000000000000000000000000000000000000000000000000000000000000000c00000000000000000000000000000000000000000000000000000000000000044cf6fc6e30000000000000000000000003e3e2ccdd7bae6bbd4a64e8d16ca8842061335eb000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000002463592c2b00000000000000000000000000000000000000000000000000000000613e28e500000000000000000000000000000000000000000000000000000000', salt: '885135864076', takerAsset: '0x8f3cf7ad23cd3cadbd9735aff958023239c6a063', - takerAssetData: '0x23b872dd00000000000000000000000000000000000000000000000000000000000000000000000000000000000000003e3e2ccdd7bae6bbd4a64e8d16ca8842061335eb00000000000000000000000000000000000000000000000018fae27693b40000', + takerAssetData: + '0x23b872dd00000000000000000000000000000000000000000000000000000000000000000000000000000000000000003e3e2ccdd7bae6bbd4a64e8d16ca8842061335eb00000000000000000000000000000000000000000000000018fae27693b40000', }, primaryType: 'Order', types: { @@ -640,11 +680,13 @@ describe('ETH Messages', () => { message: { allocations: [ { - reactorKey: '0x6f686d2d64656661756c74000000000000000000000000000000000000000000', + reactorKey: + '0x6f686d2d64656661756c74000000000000000000000000000000000000000000', amount: '1', }, { - reactorKey: '0x6f686d2d64656661756c74000000000000000000000000000000000000000000', + reactorKey: + '0x6f686d2d64656661756c74000000000000000000000000000000000000000000', amount: '2', }, ], @@ -694,11 +736,13 @@ describe('ETH Messages', () => { integer: 56, allocations: [ { - reactorKey: '0x6f686d2d64656661756c74000000000000000000000000000000000000000000', + reactorKey: + '0x6f686d2d64656661756c74000000000000000000000000000000000000000000', amount: '1', }, { - reactorKey: '0x6f686d2d64656661756c74000000000000000000000000000000000000000000', + reactorKey: + '0x6f686d2d64656661756c74000000000000000000000000000000000000000000', amount: '2', }, ], @@ -737,11 +781,13 @@ describe('ETH Messages', () => { message: { allocations: [ { - reactorKey: '0x6f686d2d64656661756c74000000000000000000000000000000000000000000', + reactorKey: + '0x6f686d2d64656661756c74000000000000000000000000000000000000000000', amount: ['1', '2'], }, { - reactorKey: '0x6f686d2d64656661756c74000000000000000000000000000000000000000000', + reactorKey: + '0x6f686d2d64656661756c74000000000000000000000000000000000000000000', amount: ['2', '3'], }, ], @@ -794,7 +840,8 @@ describe('ETH Messages', () => { test: 'hello', allocations: [ { - reactorKey: '0x6f686d2d64656661756c74000000000000000000000000000000000000000000', + reactorKey: + '0x6f686d2d64656661756c74000000000000000000000000000000000000000000', dummy: [ { foo: '0xabcd', @@ -805,7 +852,8 @@ describe('ETH Messages', () => { ], }, { - reactorKey: '0x6f686d2d64656661756c74000000000000000000000000000000000000000000', + reactorKey: + '0x6f686d2d64656661756c74000000000000000000000000000000000000000000', dummy: [ { foo: '0xdeadbeef', @@ -883,9 +931,12 @@ describe('ETH Messages', () => { BYTES16: '0x7ace034ab088fdd434f1e817f32171a0', BYTES20: '0x4ab51f2d5bfdc0f1b96f83358d5f356c98583573', BYTES21: '0x6ecdc19b30c7fa712ba334458d77377b6a586bbab5', - BYTES31: '0x06c21824a98643f96643b3220962f441210b007f4c19dfdf0dea53d097fc28', - BYTES32: '0x59cfcbf35256451756b02fa644d3d0748bd98f5904febf3433e6df19b4df7452', - BYTES: '0x0354b2c449772905b2598a93f5da69962f0444e0a6e2429e8f844f1011446f6fe81815846fb6ebe2d213968d1f8532749735f5702f565db0429b2fe596d295d9c06241389fe97fb2f3b91e1e0f2d978fb26e366737451f1193097bd0a2332e0bfc0cdb631005', + BYTES31: + '0x06c21824a98643f96643b3220962f441210b007f4c19dfdf0dea53d097fc28', + BYTES32: + '0x59cfcbf35256451756b02fa644d3d0748bd98f5904febf3433e6df19b4df7452', + BYTES: + '0x0354b2c449772905b2598a93f5da69962f0444e0a6e2429e8f844f1011446f6fe81815846fb6ebe2d213968d1f8532749735f5702f565db0429b2fe596d295d9c06241389fe97fb2f3b91e1e0f2d978fb26e366737451f1193097bd0a2332e0bfc0cdb631005', STRING: 'I am a string hello there human', BOOL: true, ADDRESS: '0x078a8d6eba928e7ea787ed48f71c5936aed4625d', @@ -1310,7 +1361,10 @@ describe('ETH Messages', () => { describe('test 5 random payloads', () => { for (let i = 0; i < 5; i++) { it(`Payload #${i}`, async () => { - await runEthMsg(buildEthMsgReq(buildRandomMsg('eip712', client), 'eip712'), client); + await runEthMsg( + buildEthMsgReq(buildRandomMsg('eip712', client), 'eip712'), + client, + ); }); } }); diff --git a/packages/sdk/src/__test__/e2e/general.test.ts b/packages/sdk/src/__test__/e2e/general.test.ts index 38f5c0f8..021d6e1f 100644 --- a/packages/sdk/src/__test__/e2e/general.test.ts +++ b/packages/sdk/src/__test__/e2e/general.test.ts @@ -23,7 +23,15 @@ import { LatticeResponseCode, ProtocolConstants } from '../../protocol'; import { randomBytes } from '../../util'; import { buildEthSignRequest } from '../utils/builders'; import { getDeviceId } from '../utils/getters'; -import { BTC_COIN, BTC_PURPOSE_P2PKH, BTC_PURPOSE_P2SH_P2WPKH, BTC_PURPOSE_P2WPKH, BTC_TESTNET_COIN, ETH_COIN, setupTestClient } from '../utils/helpers'; +import { + BTC_COIN, + BTC_PURPOSE_P2PKH, + BTC_PURPOSE_P2SH_P2WPKH, + BTC_PURPOSE_P2WPKH, + BTC_TESTNET_COIN, + ETH_COIN, + setupTestClient, +} from '../utils/helpers'; import { setupClient } from '../utils/setup'; import type { Client } from '../../client'; @@ -172,13 +180,18 @@ describe('General', () => { ctx.skip(); return; } - const { txData, req, maxDataSz, common } = await buildEthSignRequest(client); - await question('Please REJECT the next request if the warning screen displays. Press enter to continue.'); + const { txData, req, maxDataSz, common } = + await buildEthSignRequest(client); + await question( + 'Please REJECT the next request if the warning screen displays. Press enter to continue.', + ); txData.data = randomBytes(maxDataSz); req.data.data = randomBytes(maxDataSz + 1); const tx = createTx(txData, { common }); req.data.payload = tx.getMessageToSign(); - await expect(client.sign(req)).rejects.toThrow(`${ProtocolConstants.responseMsg[LatticeResponseCode.userDeclined]}`); + await expect(client.sign(req)).rejects.toThrow( + `${ProtocolConstants.responseMsg[LatticeResponseCode.userDeclined]}`, + ); }); }); @@ -187,17 +200,30 @@ describe('General', () => { const txData = { prevOuts: [ { - txHash: '6e78493091f80d89a92ae3152df7fbfbdc44df09cf01a9b76c5113c02eaf2e0f', + txHash: + '6e78493091f80d89a92ae3152df7fbfbdc44df09cf01a9b76c5113c02eaf2e0f', value: 10000, index: 1, - signerPath: [BTC_PURPOSE_P2SH_P2WPKH, BTC_TESTNET_COIN, HARDENED_OFFSET, 0, 0], + signerPath: [ + BTC_PURPOSE_P2SH_P2WPKH, + BTC_TESTNET_COIN, + HARDENED_OFFSET, + 0, + 0, + ], }, ], recipient: 'mhifA1DwiMPHTjSJM8FFSL8ibrzWaBCkVT', value: 1000, fee: 1000, // isSegwit: false, // old encoding - changePath: [BTC_PURPOSE_P2SH_P2WPKH, BTC_TESTNET_COIN, HARDENED_OFFSET, 1, 0], + changePath: [ + BTC_PURPOSE_P2SH_P2WPKH, + BTC_TESTNET_COIN, + HARDENED_OFFSET, + 1, + 0, + ], }; const req = { currency: 'BTC' as const, @@ -214,16 +240,29 @@ describe('General', () => { const txData = { prevOuts: [ { - txHash: 'ab8288ef207f11186af98db115aa7120aa36ceb783e8792fb7b2f39c88109a99', + txHash: + 'ab8288ef207f11186af98db115aa7120aa36ceb783e8792fb7b2f39c88109a99', value: 10000, index: 1, - signerPath: [BTC_PURPOSE_P2SH_P2WPKH, BTC_TESTNET_COIN, HARDENED_OFFSET, 0, 0], + signerPath: [ + BTC_PURPOSE_P2SH_P2WPKH, + BTC_TESTNET_COIN, + HARDENED_OFFSET, + 0, + 0, + ], }, ], recipient: '2NGZrVvZG92qGYqzTLjCAewvPZ7JE8S8VxE', value: 1000, fee: 1000, - changePath: [BTC_PURPOSE_P2SH_P2WPKH, BTC_TESTNET_COIN, HARDENED_OFFSET, 1, 0], + changePath: [ + BTC_PURPOSE_P2SH_P2WPKH, + BTC_TESTNET_COIN, + HARDENED_OFFSET, + 1, + 0, + ], }; const req = { currency: 'BTC' as const, @@ -239,16 +278,29 @@ describe('General', () => { const txData = { prevOuts: [ { - txHash: 'f93d0a77f58b4274d84f427d647f1f27e38b4db79fd975691e15109fde7ea06e', + txHash: + 'f93d0a77f58b4274d84f427d647f1f27e38b4db79fd975691e15109fde7ea06e', value: 1802440, index: 1, - signerPath: [BTC_PURPOSE_P2SH_P2WPKH, BTC_TESTNET_COIN, HARDENED_OFFSET, 1, 0], + signerPath: [ + BTC_PURPOSE_P2SH_P2WPKH, + BTC_TESTNET_COIN, + HARDENED_OFFSET, + 1, + 0, + ], }, ], recipient: 'tb1qym0z2a939lefrgw67ep5flhf43dvpg3h4s96tn', value: 1000, fee: 1000, - changePath: [BTC_PURPOSE_P2SH_P2WPKH, BTC_TESTNET_COIN, HARDENED_OFFSET, 1, 0], + changePath: [ + BTC_PURPOSE_P2SH_P2WPKH, + BTC_TESTNET_COIN, + HARDENED_OFFSET, + 1, + 0, + ], }; const req = { currency: 'BTC' as const, @@ -264,17 +316,30 @@ describe('General', () => { const txData = { prevOuts: [ { - txHash: 'b2efdbdd3340d2bc547671ce3993a6f05d70343c07578f9d7f5626fdfc06fa35', + txHash: + 'b2efdbdd3340d2bc547671ce3993a6f05d70343c07578f9d7f5626fdfc06fa35', value: 76800, index: 0, - signerPath: [BTC_PURPOSE_P2WPKH, BTC_TESTNET_COIN, HARDENED_OFFSET, 0, 0], + signerPath: [ + BTC_PURPOSE_P2WPKH, + BTC_TESTNET_COIN, + HARDENED_OFFSET, + 0, + 0, + ], }, ], recipient: '2N4gqWT4oqWL2gz9ps92z9fm2Bg3FUkqG7Q', value: 70000, fee: 4380, isSegwit: true, - changePath: [BTC_PURPOSE_P2WPKH, BTC_TESTNET_COIN, HARDENED_OFFSET, 1, 0], + changePath: [ + BTC_PURPOSE_P2WPKH, + BTC_TESTNET_COIN, + HARDENED_OFFSET, + 1, + 0, + ], }; const req = { currency: 'BTC' as const, diff --git a/packages/sdk/src/__test__/e2e/kv.test.ts b/packages/sdk/src/__test__/e2e/kv.test.ts index 85af4b10..73dd85d3 100644 --- a/packages/sdk/src/__test__/e2e/kv.test.ts +++ b/packages/sdk/src/__test__/e2e/kv.test.ts @@ -46,7 +46,9 @@ describe('key-value', () => { it('Should ask if the user wants to reset state', async () => { let answer = 'Y'; if (process.env.CI !== '1') { - answer = question('Do you want to clear all kv records and start anew? (Y/N) '); + answer = question( + 'Do you want to clear all kv records and start anew? (Y/N) ', + ); } else { answer = 'Y'; } @@ -66,7 +68,9 @@ describe('key-value', () => { } if (lastTotal !== null && total >= lastTotal) { - console.warn('[kv.test] KV cleanup halted to avoid infinite loop (no progress detected).'); + console.warn( + '[kv.test] KV cleanup halted to avoid infinite loop (no progress detected).', + ); break; } @@ -91,7 +95,9 @@ describe('key-value', () => { it('Should make a request to an unknown address', async () => { await client.sign(ETH_REQ as unknown as SignRequestParams).catch((err) => { - expect(err.message).toContain(ProtocolConstants.responseMsg[LatticeResponseCode.userDeclined]); + expect(err.message).toContain( + ProtocolConstants.responseMsg[LatticeResponseCode.userDeclined], + ); }); }); @@ -111,15 +117,23 @@ describe('key-value', () => { it('Should fail to add records with unicode characters', async () => { const badKey = { '0x🔥🦍': 'Muh name' }; const badVal = { UNISWAP_ADDR: 'val🔥🦍' }; - await expect(client.addKvRecords({ records: badKey })).rejects.toThrow('Unicode characters are not supported.'); - await expect(client.addKvRecords({ records: badVal })).rejects.toThrow('Unicode characters are not supported.'); + await expect(client.addKvRecords({ records: badKey })).rejects.toThrow( + 'Unicode characters are not supported.', + ); + await expect(client.addKvRecords({ records: badVal })).rejects.toThrow( + 'Unicode characters are not supported.', + ); }); it('Should fail to add zero length keys and values', async () => { const badKey = { '': 'Muh name' }; const badVal = { UNISWAP_ADDR: '' }; - await expect(client.addKvRecords({ records: badKey })).rejects.toThrow('Keys and values must be >0 characters.'); - await expect(client.addKvRecords({ records: badVal })).rejects.toThrow('Keys and values must be >0 characters.'); + await expect(client.addKvRecords({ records: badKey })).rejects.toThrow( + 'Keys and values must be >0 characters.', + ); + await expect(client.addKvRecords({ records: badVal })).rejects.toThrow( + 'Keys and values must be >0 characters.', + ); }); it('Should fetch the newly created records', async () => { @@ -212,7 +226,9 @@ describe('key-value', () => { it('Should make another request to make sure case sensitivity is enforced', async () => { await client.sign(ETH_REQ as unknown as SignRequestParams).catch((err) => { - expect(err.message).toContain(ProtocolConstants.responseMsg[LatticeResponseCode.userDeclined]); + expect(err.message).toContain( + ProtocolConstants.responseMsg[LatticeResponseCode.userDeclined], + ); }); }); diff --git a/packages/sdk/src/__test__/e2e/non-exportable.test.ts b/packages/sdk/src/__test__/e2e/non-exportable.test.ts index 96301c02..7edd6d13 100644 --- a/packages/sdk/src/__test__/e2e/non-exportable.test.ts +++ b/packages/sdk/src/__test__/e2e/non-exportable.test.ts @@ -47,7 +47,9 @@ describe('Non-Exportable Seed', () => { return; } // NOTE: non-exportable seeds were deprecated from the normal setup pathway in firmware v0.12.0 - const result = await question('Do you have a non-exportable SafeCard seed loaded and wish to continue? (Y/N) '); + const result = await question( + 'Do you have a non-exportable SafeCard seed loaded and wish to continue? (Y/N) ', + ); if (result.toLowerCase() !== 'y') { runTests = false; } @@ -91,7 +93,9 @@ describe('Non-Exportable Seed', () => { }; // Validate that tx sigs are non-uniform const unsignedMsg = tx.getMessageToSign(); - const unsigned = Array.isArray(unsignedMsg) ? RLP.encode(unsignedMsg) : unsignedMsg; + const unsigned = Array.isArray(unsignedMsg) + ? RLP.encode(unsignedMsg) + : unsignedMsg; const tx1Resp = await client.sign(txReq); validateSig(tx1Resp, unsigned); const tx2Resp = await client.sign(txReq); @@ -143,11 +147,21 @@ describe('Non-Exportable Seed', () => { }; // NOTE: This uses the legacy signing pathway, which validates the signature // Once we move this to generic signing, we will need to validate these. - const msg1Resp = await client.sign(msgReq as unknown as SignRequestParams); - const msg2Resp = await client.sign(msgReq as unknown as SignRequestParams); - const msg3Resp = await client.sign(msgReq as unknown as SignRequestParams); - const msg4Resp = await client.sign(msgReq as unknown as SignRequestParams); - const msg5Resp = await client.sign(msgReq as unknown as SignRequestParams); + const msg1Resp = await client.sign( + msgReq as unknown as SignRequestParams, + ); + const msg2Resp = await client.sign( + msgReq as unknown as SignRequestParams, + ); + const msg3Resp = await client.sign( + msgReq as unknown as SignRequestParams, + ); + const msg4Resp = await client.sign( + msgReq as unknown as SignRequestParams, + ); + const msg5Resp = await client.sign( + msgReq as unknown as SignRequestParams, + ); // Check sig 1 expect(getSigStr(msg1Resp)).not.toEqual(getSigStr(msg2Resp)); expect(getSigStr(msg1Resp)).not.toEqual(getSigStr(msg3Resp)); diff --git a/packages/sdk/src/__test__/e2e/signing/bls.test.ts b/packages/sdk/src/__test__/e2e/signing/bls.test.ts index ec424ba0..e37edf40 100644 --- a/packages/sdk/src/__test__/e2e/signing/bls.test.ts +++ b/packages/sdk/src/__test__/e2e/signing/bls.test.ts @@ -15,7 +15,12 @@ * Running with a different mnemonic will cause test failures due to * incorrect key derivations. */ -import { create as createKeystore, decrypt as decryptKeystore, isValidKeystore, verifyPassword } from '@chainsafe/bls-keystore'; +import { + create as createKeystore, + decrypt as decryptKeystore, + isValidKeystore, + verifyPassword, +} from '@chainsafe/bls-keystore'; import { getPublicKey, sign } from '@noble/bls12-381'; import { deriveSeedTree } from 'bls12-381-keygen'; import { question } from 'readline-sync'; @@ -51,21 +56,30 @@ describe('[BLS keys]', () => { // Check if firmware supports BLS (requires >= 0.17.0) const fwVersion = client.getFwVersion(); - const versionStr = fwVersion ? `${fwVersion.major}.${fwVersion.minor}.${fwVersion.fix}` : 'unknown'; + const versionStr = fwVersion + ? `${fwVersion.major}.${fwVersion.minor}.${fwVersion.fix}` + : 'unknown'; console.log(`\n[BLS Test] Firmware version: ${versionStr}`); console.log('[BLS Test] Raw fwVersion:', fwVersion); const fwConstants = client.getFwConstants(); console.log('[BLS Test] getAddressFlags:', fwConstants?.getAddressFlags); - console.log('[BLS Test] BLS12_381_G1_PUB constant:', Constants.GET_ADDR_FLAGS.BLS12_381_G1_PUB); + console.log( + '[BLS Test] BLS12_381_G1_PUB constant:', + Constants.GET_ADDR_FLAGS.BLS12_381_G1_PUB, + ); - supportsBLS = fwConstants?.getAddressFlags?.includes(Constants.GET_ADDR_FLAGS.BLS12_381_G1_PUB as number); + supportsBLS = fwConstants?.getAddressFlags?.includes( + Constants.GET_ADDR_FLAGS.BLS12_381_G1_PUB as number, + ); console.log(`[BLS Test] supportsBLS: ${supportsBLS}\n`); if (!supportsBLS) { - console.warn(`\nSkipping BLS tests: Firmware version ${versionStr} does not support BLS operations.\nBLS support requires firmware version >= 0.17.0\n`); + console.warn( + `\nSkipping BLS tests: Firmware version ${versionStr} does not support BLS operations.\nBLS support requires firmware version >= 0.17.0\n`, + ); } }); @@ -158,9 +172,17 @@ async function testBLSDerivationAndSig(seed, signerPath) { const refPubStr = Buffer.from(refPub).toString('hex'); const refSig = await sign(msg, priv); const refSigStr = Buffer.from(refSig).toString('hex'); - expect(latticePub.toString('hex')).to.equal(refPubStr, 'Deposit public key mismatch'); - expect(latticeSig.pubkey.toString('hex')).to.equal(refPubStr, 'Lattice signature returned wrong pubkey'); - expect(Buffer.from(latticeSig.sig as unknown as Buffer).toString('hex')).to.equal(refSigStr, 'Signature mismatch'); + expect(latticePub.toString('hex')).to.equal( + refPubStr, + 'Deposit public key mismatch', + ); + expect(latticeSig.pubkey.toString('hex')).to.equal( + refPubStr, + 'Lattice signature returned wrong pubkey', + ); + expect( + Buffer.from(latticeSig.sig as unknown as Buffer).toString('hex'), + ).to.equal(refSigStr, 'Signature mismatch'); } async function validateExportedKeystore(seed, path, pw, expKeystoreBuffer) { const exportedKeystore = JSON.parse(expKeystoreBuffer.toString()); @@ -168,18 +190,39 @@ async function validateExportedKeystore(seed, path, pw, expKeystoreBuffer) { const pub = getPublicKey(priv); // Validate the keystore in isolation - expect(isValidKeystore(exportedKeystore)).to.equal(true, 'Exported keystore invalid!'); + expect(isValidKeystore(exportedKeystore)).to.equal( + true, + 'Exported keystore invalid!', + ); const expPwVerified = await verifyPassword(exportedKeystore, pw); - expect(expPwVerified).to.equal(true, `Password could not be verified in exported keystore. Expected "${pw}"`); + expect(expPwVerified).to.equal( + true, + `Password could not be verified in exported keystore. Expected "${pw}"`, + ); const expDec = await decryptKeystore(exportedKeystore, pw); - expect(Buffer.from(expDec).toString('hex')).to.equal(Buffer.from(priv).toString('hex'), 'Exported keystore did not properly encrypt key!'); - expect(exportedKeystore.pubkey).to.equal(Buffer.from(pub).toString('hex'), 'Wrong public key exported from Lattice'); + expect(Buffer.from(expDec).toString('hex')).to.equal( + Buffer.from(priv).toString('hex'), + 'Exported keystore did not properly encrypt key!', + ); + expect(exportedKeystore.pubkey).to.equal( + Buffer.from(pub).toString('hex'), + 'Wrong public key exported from Lattice', + ); // Generate an independent keystore and compare decrypted contents const genKeystore = await createKeystore(pw, priv, pub, getPathStr(path)); - expect(isValidKeystore(genKeystore)).to.equal(true, 'Generated keystore invalid?'); + expect(isValidKeystore(genKeystore)).to.equal( + true, + 'Generated keystore invalid?', + ); const genPwVerified = await verifyPassword(genKeystore, pw); - expect(genPwVerified).to.equal(true, 'Password could not be verified in generated keystore?'); + expect(genPwVerified).to.equal( + true, + 'Password could not be verified in generated keystore?', + ); const genDec = await decryptKeystore(genKeystore, pw); - expect(Buffer.from(expDec).toString('hex')).to.equal(Buffer.from(genDec).toString('hex'), 'Exported encrypted privkey did not match factory test example...'); + expect(Buffer.from(expDec).toString('hex')).to.equal( + Buffer.from(genDec).toString('hex'), + 'Exported encrypted privkey did not match factory test example...', + ); } diff --git a/packages/sdk/src/__test__/e2e/signing/determinism.test.ts b/packages/sdk/src/__test__/e2e/signing/determinism.test.ts index 15f6888d..064950ea 100644 --- a/packages/sdk/src/__test__/e2e/signing/determinism.test.ts +++ b/packages/sdk/src/__test__/e2e/signing/determinism.test.ts @@ -1,8 +1,19 @@ import { HARDENED_OFFSET } from '../../../constants'; import type { SignRequestParams, WalletPath } from '../../../types'; import { randomBytes } from '../../../util'; -import { DEFAULT_SIGNER, buildMsgReq, buildRandomVectors, buildTx, buildTxReq } from '../../utils/builders'; -import { deriveAddress, signEip712JS, signPersonalJS, testUniformSigs } from '../../utils/determinism'; +import { + DEFAULT_SIGNER, + buildMsgReq, + buildRandomVectors, + buildTx, + buildTxReq, +} from '../../utils/builders'; +import { + deriveAddress, + signEip712JS, + signPersonalJS, + testUniformSigs, +} from '../../utils/determinism'; /** * REQUIRED TEST MNEMONIC: * These tests require a SafeCard loaded with the standard test mnemonic: @@ -33,11 +44,29 @@ describe('[Determinism]', () => { }); it('Should validate some Ledger addresses derived from the test seed', async () => { - const path0 = [BTC_PURPOSE_P2PKH, ETH_COIN, HARDENED_OFFSET, 0, 0] as WalletPath; + const path0 = [ + BTC_PURPOSE_P2PKH, + ETH_COIN, + HARDENED_OFFSET, + 0, + 0, + ] as WalletPath; const addr0 = deriveAddress(TEST_SEED, path0); - const path1 = [BTC_PURPOSE_P2PKH, ETH_COIN, HARDENED_OFFSET + 1, 0, 0] as WalletPath; + const path1 = [ + BTC_PURPOSE_P2PKH, + ETH_COIN, + HARDENED_OFFSET + 1, + 0, + 0, + ] as WalletPath; const addr1 = deriveAddress(TEST_SEED, path1); - const path8 = [BTC_PURPOSE_P2PKH, ETH_COIN, HARDENED_OFFSET + 8, 0, 0] as WalletPath; + const path8 = [ + BTC_PURPOSE_P2PKH, + ETH_COIN, + HARDENED_OFFSET + 8, + 0, + 0, + ] as WalletPath; const addr8 = deriveAddress(TEST_SEED, path8); // Fetch these addresses from the Lattice and validate @@ -47,13 +76,22 @@ describe('[Determinism]', () => { n: 1, }; const latAddr0 = await client.getAddresses(req); - expect((latAddr0[0] as string).toLowerCase()).toEqualElseLog(addr0.toLowerCase(), 'Incorrect address 0 fetched. Ensure your SafeCard is loaded with the test mnemonic: "test test test test test test test test test test test junk"'); + expect((latAddr0[0] as string).toLowerCase()).toEqualElseLog( + addr0.toLowerCase(), + 'Incorrect address 0 fetched. Ensure your SafeCard is loaded with the test mnemonic: "test test test test test test test test test test test junk"', + ); req.startPath = path1; const latAddr1 = await client.getAddresses(req); - expect((latAddr1[0] as string).toLowerCase()).toEqualElseLog(addr1.toLowerCase(), 'Incorrect address 1 fetched. Ensure your SafeCard is loaded with the test mnemonic: "test test test test test test test test test test test junk"'); + expect((latAddr1[0] as string).toLowerCase()).toEqualElseLog( + addr1.toLowerCase(), + 'Incorrect address 1 fetched. Ensure your SafeCard is loaded with the test mnemonic: "test test test test test test test test test test test junk"', + ); req.startPath = path8; const latAddr8 = await client.getAddresses(req); - expect((latAddr8[0] as string).toLowerCase()).toEqualElseLog(addr8.toLowerCase(), 'Incorrect address 8 fetched. Ensure your SafeCard is loaded with the test mnemonic: "test test test test test test test test test test test junk"'); + expect((latAddr8[0] as string).toLowerCase()).toEqualElseLog( + addr8.toLowerCase(), + 'Incorrect address 8 fetched. Ensure your SafeCard is loaded with the test mnemonic: "test test test test test test test test test test test junk"', + ); }); }); @@ -108,7 +146,10 @@ describe('[Determinism]', () => { const res = await client.sign(msgReq as unknown as SignRequestParams); const sig = getSigStr(res); const jsSig = signPersonalJS(msgReq.data.payload, msgReq.data.signerPath); - expect(sig).toEqualElseLog(jsSig, 'Lattice sig does not match JS reference'); + expect(sig).toEqualElseLog( + jsSig, + 'Lattice sig does not match JS reference', + ); }); it('Should validate signature from addr1', async () => { @@ -117,7 +158,10 @@ describe('[Determinism]', () => { const res = await client.sign(msgReq as unknown as SignRequestParams); const sig = getSigStr(res); const jsSig = signPersonalJS(msgReq.data.payload, msgReq.data.signerPath); - expect(sig).toEqualElseLog(jsSig, 'Lattice sig does not match JS reference'); + expect(sig).toEqualElseLog( + jsSig, + 'Lattice sig does not match JS reference', + ); }); it('Should validate signature from addr8', async () => { @@ -126,7 +170,10 @@ describe('[Determinism]', () => { const res = await client.sign(msgReq as unknown as SignRequestParams); const sig = getSigStr(res); const jsSig = signPersonalJS(msgReq.data.payload, msgReq.data.signerPath); - expect(sig).toEqualElseLog(jsSig, 'Lattice sig does not match JS reference'); + expect(sig).toEqualElseLog( + jsSig, + 'Lattice sig does not match JS reference', + ); }); }); @@ -137,7 +184,10 @@ describe('[Determinism]', () => { const res = await client.sign(msgReq as unknown as SignRequestParams); const sig = getSigStr(res); const jsSig = signPersonalJS(msgReq.data.payload, msgReq.data.signerPath); - expect(sig).toEqualElseLog(jsSig, 'Lattice sig does not match JS reference'); + expect(sig).toEqualElseLog( + jsSig, + 'Lattice sig does not match JS reference', + ); }); it('Should validate signature from addr1', async () => { @@ -146,7 +196,10 @@ describe('[Determinism]', () => { const res = await client.sign(msgReq as unknown as SignRequestParams); const sig = getSigStr(res); const jsSig = signPersonalJS(msgReq.data.payload, msgReq.data.signerPath); - expect(sig).toEqualElseLog(jsSig, 'Lattice sig does not match JS reference'); + expect(sig).toEqualElseLog( + jsSig, + 'Lattice sig does not match JS reference', + ); }); it('Should validate signature from addr8', async () => { @@ -155,7 +208,10 @@ describe('[Determinism]', () => { const res = await client.sign(msgReq as unknown as SignRequestParams); const sig = getSigStr(res); const jsSig = signPersonalJS(msgReq.data.payload, msgReq.data.signerPath); - expect(sig).toEqualElseLog(jsSig, 'Lattice sig does not match JS reference'); + expect(sig).toEqualElseLog( + jsSig, + 'Lattice sig does not match JS reference', + ); }); }); @@ -166,7 +222,10 @@ describe('[Determinism]', () => { const res = await client.sign(msgReq as unknown as SignRequestParams); const sig = getSigStr(res); const jsSig = signPersonalJS(msgReq.data.payload, msgReq.data.signerPath); - expect(sig).toEqualElseLog(jsSig, 'Lattice sig does not match JS reference'); + expect(sig).toEqualElseLog( + jsSig, + 'Lattice sig does not match JS reference', + ); }); it('Should validate signature from addr1', async () => { @@ -175,7 +234,10 @@ describe('[Determinism]', () => { const res = await client.sign(msgReq as unknown as SignRequestParams); const sig = getSigStr(res); const jsSig = signPersonalJS(msgReq.data.payload, msgReq.data.signerPath); - expect(sig).toEqualElseLog(jsSig, 'Lattice sig does not match JS reference'); + expect(sig).toEqualElseLog( + jsSig, + 'Lattice sig does not match JS reference', + ); }); it('Should validate signature from addr8', async () => { @@ -184,7 +246,10 @@ describe('[Determinism]', () => { const res = await client.sign(msgReq as unknown as SignRequestParams); const sig = getSigStr(res); const jsSig = signPersonalJS(msgReq.data.payload, msgReq.data.signerPath); - expect(sig).toEqualElseLog(jsSig, 'Lattice sig does not match JS reference'); + expect(sig).toEqualElseLog( + jsSig, + 'Lattice sig does not match JS reference', + ); }); }); @@ -235,7 +300,10 @@ describe('[Determinism]', () => { const res = await client.sign(msgReq as unknown as SignRequestParams); const sig = getSigStr(res); const jsSig = signEip712JS(msgReq.data.payload, msgReq.data.signerPath); - expect(sig).toEqualElseLog(jsSig, 'Lattice EIP712 sig does not match JS reference'); + expect(sig).toEqualElseLog( + jsSig, + 'Lattice EIP712 sig does not match JS reference', + ); }); it('Should validate signature from addr1', async () => { @@ -243,7 +311,10 @@ describe('[Determinism]', () => { const res = await client.sign(msgReq as unknown as SignRequestParams); const sig = getSigStr(res); const jsSig = signEip712JS(msgReq.data.payload, msgReq.data.signerPath); - expect(sig).toEqualElseLog(jsSig, 'Lattice EIP712 sig does not match JS reference'); + expect(sig).toEqualElseLog( + jsSig, + 'Lattice EIP712 sig does not match JS reference', + ); }); it('Should validate signature from addr8', async () => { @@ -251,7 +322,10 @@ describe('[Determinism]', () => { const res = await client.sign(msgReq as unknown as SignRequestParams); const sig = getSigStr(res); const jsSig = signEip712JS(msgReq.data.payload, msgReq.data.signerPath); - expect(sig).toEqualElseLog(jsSig, 'Lattice EIP712 sig does not match JS reference'); + expect(sig).toEqualElseLog( + jsSig, + 'Lattice EIP712 sig does not match JS reference', + ); }); }); @@ -292,7 +366,10 @@ describe('[Determinism]', () => { const res = await client.sign(msgReq as unknown as SignRequestParams); const sig = getSigStr(res); const jsSig = signEip712JS(msgReq.data.payload, msgReq.data.signerPath); - expect(sig).toEqualElseLog(jsSig, 'Lattice EIP712 sig does not match JS reference'); + expect(sig).toEqualElseLog( + jsSig, + 'Lattice EIP712 sig does not match JS reference', + ); }); it('Should validate signature from addr1', async () => { @@ -300,7 +377,10 @@ describe('[Determinism]', () => { const res = await client.sign(msgReq as unknown as SignRequestParams); const sig = getSigStr(res); const jsSig = signEip712JS(msgReq.data.payload, msgReq.data.signerPath); - expect(sig).toEqualElseLog(jsSig, 'Lattice EIP712 sig does not match JS reference'); + expect(sig).toEqualElseLog( + jsSig, + 'Lattice EIP712 sig does not match JS reference', + ); }); it('Should validate signature from addr8', async () => { @@ -308,7 +388,10 @@ describe('[Determinism]', () => { const res = await client.sign(msgReq as unknown as SignRequestParams); const sig = getSigStr(res); const jsSig = signEip712JS(msgReq.data.payload, msgReq.data.signerPath); - expect(sig).toEqualElseLog(jsSig, 'Lattice EIP712 sig does not match JS reference'); + expect(sig).toEqualElseLog( + jsSig, + 'Lattice EIP712 sig does not match JS reference', + ); }); }); @@ -349,21 +432,30 @@ describe('[Determinism]', () => { const res = await client.sign(msgReq as unknown as SignRequestParams); const sig = getSigStr(res); const jsSig = signEip712JS(msgReq.data.payload, msgReq.data.signerPath); - expect(sig).toEqualElseLog(jsSig, 'Lattice EIP712 sig does not match JS reference'); + expect(sig).toEqualElseLog( + jsSig, + 'Lattice EIP712 sig does not match JS reference', + ); }); it('Should validate signature from addr1', async () => { msgReq.data.signerPath[2] = HARDENED_OFFSET + 1; const res = await client.sign(msgReq as unknown as SignRequestParams); const sig = getSigStr(res); const jsSig = signEip712JS(msgReq.data.payload, msgReq.data.signerPath); - expect(sig).toEqualElseLog(jsSig, 'Lattice EIP712 sig does not match JS reference'); + expect(sig).toEqualElseLog( + jsSig, + 'Lattice EIP712 sig does not match JS reference', + ); }); it('Should validate signature from addr8', async () => { msgReq.data.signerPath[2] = HARDENED_OFFSET + 8; const res = await client.sign(msgReq as unknown as SignRequestParams); const sig = getSigStr(res); const jsSig = signEip712JS(msgReq.data.payload, msgReq.data.signerPath); - expect(sig).toEqualElseLog(jsSig, 'Lattice EIP712 sig does not match JS reference'); + expect(sig).toEqualElseLog( + jsSig, + 'Lattice EIP712 sig does not match JS reference', + ); }); }); diff --git a/packages/sdk/src/__test__/e2e/signing/eip712-vectors.ts b/packages/sdk/src/__test__/e2e/signing/eip712-vectors.ts index f6e73103..2a06886c 100644 --- a/packages/sdk/src/__test__/e2e/signing/eip712-vectors.ts +++ b/packages/sdk/src/__test__/e2e/signing/eip712-vectors.ts @@ -256,7 +256,9 @@ export const EIP712_MESSAGE_VECTORS: Array<{ }, primaryType: 'Data', message: { - value: BigInt('115792089237316195423570985008687907853269984665640564039457584007913129639935'), + value: BigInt( + '115792089237316195423570985008687907853269984665640564039457584007913129639935', + ), }, }, }, @@ -274,7 +276,9 @@ export const EIP712_MESSAGE_VECTORS: Array<{ }, primaryType: 'Data', message: { - value: BigInt('-57896044618658097711785492504343953926634992332820282019728792003956564819968'), + value: BigInt( + '-57896044618658097711785492504343953926634992332820282019728792003956564819968', + ), }, }, }, diff --git a/packages/sdk/src/__test__/e2e/signing/evm-tx.test.ts b/packages/sdk/src/__test__/e2e/signing/evm-tx.test.ts index 9a7a551b..e5d52bc4 100644 --- a/packages/sdk/src/__test__/e2e/signing/evm-tx.test.ts +++ b/packages/sdk/src/__test__/e2e/signing/evm-tx.test.ts @@ -8,7 +8,13 @@ import { setupClient } from '../../utils/setup'; import { signAndCompareTransaction } from '../../utils/viemComparison'; -import { EDGE_CASE_TEST_VECTORS, EIP1559_TEST_VECTORS, EIP2930_TEST_VECTORS, EIP7702_TEST_VECTORS, LEGACY_VECTORS } from './vectors'; +import { + EDGE_CASE_TEST_VECTORS, + EIP1559_TEST_VECTORS, + EIP2930_TEST_VECTORS, + EIP7702_TEST_VECTORS, + LEGACY_VECTORS, +} from './vectors'; describe('EVM Transaction Signing - Unified Test Suite', () => { beforeAll(async () => { diff --git a/packages/sdk/src/__test__/e2e/signing/solana/__mocks__/programs.ts b/packages/sdk/src/__test__/e2e/signing/solana/__mocks__/programs.ts index f6c57373..f72b1ec9 100644 --- a/packages/sdk/src/__test__/e2e/signing/solana/__mocks__/programs.ts +++ b/packages/sdk/src/__test__/e2e/signing/solana/__mocks__/programs.ts @@ -1,33 +1,87 @@ export const raydiumProgram = Buffer.from([ - 2, 0, 10, 24, 47, 41, 101, 134, 4, 204, 35, 219, 9, 43, 55, 20, 255, 76, 29, 27, 175, 197, 102, 175, 108, 72, 231, 146, 134, 30, 219, 42, 168, 236, 17, 3, 209, 66, 76, 96, 212, 139, 188, 184, 209, 19, 177, 79, 32, 176, 155, 148, 130, 200, - 98, 110, 128, 160, 186, 226, 136, 168, 203, 105, 117, 38, 171, 161, 41, 11, 235, 184, 125, 115, 158, 2, 137, 19, 123, 58, 193, 31, 193, 215, 57, 0, 240, 162, 154, 197, 182, 102, 96, 91, 101, 29, 31, 63, 168, 145, 47, 189, 251, 138, 30, - 226, 174, 123, 162, 139, 147, 54, 101, 251, 231, 142, 230, 173, 232, 212, 153, 225, 58, 113, 24, 53, 97, 26, 89, 169, 83, 10, 48, 190, 92, 28, 22, 83, 66, 168, 232, 41, 135, 46, 21, 208, 110, 217, 210, 243, 1, 237, 247, 237, 183, 71, 1, - 193, 117, 86, 188, 217, 134, 204, 52, 147, 214, 106, 218, 145, 18, 3, 14, 152, 187, 194, 134, 97, 177, 146, 183, 102, 40, 90, 33, 217, 240, 113, 89, 142, 64, 120, 22, 21, 175, 245, 69, 184, 172, 211, 86, 215, 29, 176, 82, 209, 2, 198, - 205, 207, 75, 152, 201, 35, 86, 169, 205, 157, 142, 144, 96, 235, 228, 249, 204, 249, 212, 189, 80, 236, 218, 125, 206, 4, 54, 254, 165, 182, 236, 127, 232, 154, 117, 171, 105, 133, 186, 163, 206, 201, 159, 70, 10, 17, 138, 239, 21, 37, - 109, 139, 81, 59, 76, 57, 145, 209, 124, 73, 151, 15, 83, 117, 205, 87, 153, 166, 158, 188, 141, 157, 153, 230, 131, 244, 4, 118, 170, 60, 182, 39, 25, 110, 87, 174, 54, 186, 81, 129, 162, 212, 79, 70, 190, 180, 232, 143, 72, 102, 119, - 242, 172, 80, 2, 53, 168, 117, 184, 99, 184, 206, 230, 204, 5, 165, 141, 30, 37, 25, 159, 230, 130, 247, 252, 139, 86, 49, 132, 94, 254, 8, 198, 72, 16, 173, 202, 16, 222, 102, 65, 202, 40, 218, 190, 74, 34, 21, 163, 163, 179, 232, 137, - 88, 91, 104, 174, 112, 225, 188, 55, 104, 213, 116, 77, 143, 100, 55, 181, 95, 3, 149, 44, 132, 146, 146, 85, 62, 245, 56, 164, 243, 10, 248, 172, 153, 97, 85, 86, 177, 82, 92, 148, 248, 130, 133, 54, 160, 65, 92, 148, 142, 53, 148, 190, - 85, 136, 146, 82, 19, 186, 209, 204, 116, 25, 155, 69, 186, 207, 152, 45, 54, 94, 70, 191, 239, 106, 17, 161, 122, 157, 199, 83, 30, 23, 42, 103, 226, 74, 230, 111, 113, 173, 56, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, - 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 55, 153, 140, 203, 242, 208, 69, 139, 97, 92, 188, 198, 177, 163, 103, 196, 116, 158, 159, 239, 115, 6, 98, 46, 27, 27, 88, 145, 1, 32, 188, 154, 65, 87, 176, 88, 15, 49, 197, 252, 228, 74, 98, 88, 45, - 188, 249, 215, 142, 231, 89, 67, 160, 132, 163, 147, 179, 80, 54, 141, 34, 137, 147, 8, 75, 217, 73, 196, 54, 2, 195, 63, 32, 119, 144, 237, 22, 163, 82, 76, 161, 185, 151, 92, 241, 33, 162, 169, 12, 255, 236, 125, 248, 182, 138, 205, 95, - 183, 40, 175, 220, 220, 4, 120, 120, 238, 132, 58, 111, 235, 68, 175, 40, 205, 83, 163, 72, 40, 217, 144, 59, 90, 213, 230, 151, 25, 85, 42, 133, 15, 45, 110, 2, 164, 122, 248, 36, 208, 154, 182, 157, 196, 45, 112, 203, 40, 203, 250, 36, - 159, 183, 238, 87, 185, 210, 86, 193, 39, 98, 239, 140, 151, 37, 143, 78, 36, 137, 241, 187, 61, 16, 41, 20, 142, 13, 131, 11, 90, 19, 153, 218, 255, 16, 132, 4, 142, 123, 216, 219, 233, 248, 89, 6, 155, 136, 87, 254, 171, 129, 132, 251, - 104, 127, 99, 70, 24, 192, 53, 218, 196, 57, 220, 26, 235, 59, 85, 152, 160, 240, 0, 0, 0, 0, 1, 6, 167, 213, 23, 25, 44, 92, 81, 33, 140, 201, 76, 61, 74, 241, 127, 88, 218, 238, 8, 155, 161, 253, 68, 227, 219, 217, 138, 0, 0, 0, 0, 6, - 221, 246, 225, 215, 101, 161, 147, 217, 203, 225, 70, 206, 235, 121, 172, 28, 180, 133, 237, 95, 91, 55, 145, 58, 140, 245, 133, 126, 255, 0, 169, 238, 204, 15, 249, 117, 156, 236, 123, 109, 247, 173, 57, 139, 143, 19, 21, 15, 180, 230, - 189, 171, 230, 228, 215, 211, 229, 22, 94, 108, 135, 17, 93, 5, 14, 2, 0, 1, 52, 0, 0, 0, 0, 48, 96, 46, 0, 0, 0, 0, 0, 165, 0, 0, 0, 0, 0, 0, 0, 6, 221, 246, 225, 215, 101, 161, 147, 217, 203, 225, 70, 206, 235, 121, 172, 28, 180, 133, - 237, 95, 91, 55, 145, 58, 140, 245, 133, 126, 255, 0, 169, 23, 4, 1, 21, 0, 22, 1, 1, 20, 7, 0, 9, 0, 15, 14, 23, 22, 0, 17, 18, 23, 10, 16, 7, 6, 13, 2, 19, 12, 11, 3, 4, 8, 5, 18, 1, 9, 0, 17, 9, 64, 66, 15, 0, 0, 0, 0, 0, 89, 241, 0, - 0, 0, 0, 0, 0, 23, 3, 1, 0, 0, 1, 9, + 2, 0, 10, 24, 47, 41, 101, 134, 4, 204, 35, 219, 9, 43, 55, 20, 255, 76, 29, + 27, 175, 197, 102, 175, 108, 72, 231, 146, 134, 30, 219, 42, 168, 236, 17, 3, + 209, 66, 76, 96, 212, 139, 188, 184, 209, 19, 177, 79, 32, 176, 155, 148, 130, + 200, 98, 110, 128, 160, 186, 226, 136, 168, 203, 105, 117, 38, 171, 161, 41, + 11, 235, 184, 125, 115, 158, 2, 137, 19, 123, 58, 193, 31, 193, 215, 57, 0, + 240, 162, 154, 197, 182, 102, 96, 91, 101, 29, 31, 63, 168, 145, 47, 189, 251, + 138, 30, 226, 174, 123, 162, 139, 147, 54, 101, 251, 231, 142, 230, 173, 232, + 212, 153, 225, 58, 113, 24, 53, 97, 26, 89, 169, 83, 10, 48, 190, 92, 28, 22, + 83, 66, 168, 232, 41, 135, 46, 21, 208, 110, 217, 210, 243, 1, 237, 247, 237, + 183, 71, 1, 193, 117, 86, 188, 217, 134, 204, 52, 147, 214, 106, 218, 145, 18, + 3, 14, 152, 187, 194, 134, 97, 177, 146, 183, 102, 40, 90, 33, 217, 240, 113, + 89, 142, 64, 120, 22, 21, 175, 245, 69, 184, 172, 211, 86, 215, 29, 176, 82, + 209, 2, 198, 205, 207, 75, 152, 201, 35, 86, 169, 205, 157, 142, 144, 96, 235, + 228, 249, 204, 249, 212, 189, 80, 236, 218, 125, 206, 4, 54, 254, 165, 182, + 236, 127, 232, 154, 117, 171, 105, 133, 186, 163, 206, 201, 159, 70, 10, 17, + 138, 239, 21, 37, 109, 139, 81, 59, 76, 57, 145, 209, 124, 73, 151, 15, 83, + 117, 205, 87, 153, 166, 158, 188, 141, 157, 153, 230, 131, 244, 4, 118, 170, + 60, 182, 39, 25, 110, 87, 174, 54, 186, 81, 129, 162, 212, 79, 70, 190, 180, + 232, 143, 72, 102, 119, 242, 172, 80, 2, 53, 168, 117, 184, 99, 184, 206, 230, + 204, 5, 165, 141, 30, 37, 25, 159, 230, 130, 247, 252, 139, 86, 49, 132, 94, + 254, 8, 198, 72, 16, 173, 202, 16, 222, 102, 65, 202, 40, 218, 190, 74, 34, + 21, 163, 163, 179, 232, 137, 88, 91, 104, 174, 112, 225, 188, 55, 104, 213, + 116, 77, 143, 100, 55, 181, 95, 3, 149, 44, 132, 146, 146, 85, 62, 245, 56, + 164, 243, 10, 248, 172, 153, 97, 85, 86, 177, 82, 92, 148, 248, 130, 133, 54, + 160, 65, 92, 148, 142, 53, 148, 190, 85, 136, 146, 82, 19, 186, 209, 204, 116, + 25, 155, 69, 186, 207, 152, 45, 54, 94, 70, 191, 239, 106, 17, 161, 122, 157, + 199, 83, 30, 23, 42, 103, 226, 74, 230, 111, 113, 173, 56, 0, 0, 0, 0, 0, 0, + 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, + 55, 153, 140, 203, 242, 208, 69, 139, 97, 92, 188, 198, 177, 163, 103, 196, + 116, 158, 159, 239, 115, 6, 98, 46, 27, 27, 88, 145, 1, 32, 188, 154, 65, 87, + 176, 88, 15, 49, 197, 252, 228, 74, 98, 88, 45, 188, 249, 215, 142, 231, 89, + 67, 160, 132, 163, 147, 179, 80, 54, 141, 34, 137, 147, 8, 75, 217, 73, 196, + 54, 2, 195, 63, 32, 119, 144, 237, 22, 163, 82, 76, 161, 185, 151, 92, 241, + 33, 162, 169, 12, 255, 236, 125, 248, 182, 138, 205, 95, 183, 40, 175, 220, + 220, 4, 120, 120, 238, 132, 58, 111, 235, 68, 175, 40, 205, 83, 163, 72, 40, + 217, 144, 59, 90, 213, 230, 151, 25, 85, 42, 133, 15, 45, 110, 2, 164, 122, + 248, 36, 208, 154, 182, 157, 196, 45, 112, 203, 40, 203, 250, 36, 159, 183, + 238, 87, 185, 210, 86, 193, 39, 98, 239, 140, 151, 37, 143, 78, 36, 137, 241, + 187, 61, 16, 41, 20, 142, 13, 131, 11, 90, 19, 153, 218, 255, 16, 132, 4, 142, + 123, 216, 219, 233, 248, 89, 6, 155, 136, 87, 254, 171, 129, 132, 251, 104, + 127, 99, 70, 24, 192, 53, 218, 196, 57, 220, 26, 235, 59, 85, 152, 160, 240, + 0, 0, 0, 0, 1, 6, 167, 213, 23, 25, 44, 92, 81, 33, 140, 201, 76, 61, 74, 241, + 127, 88, 218, 238, 8, 155, 161, 253, 68, 227, 219, 217, 138, 0, 0, 0, 0, 6, + 221, 246, 225, 215, 101, 161, 147, 217, 203, 225, 70, 206, 235, 121, 172, 28, + 180, 133, 237, 95, 91, 55, 145, 58, 140, 245, 133, 126, 255, 0, 169, 238, 204, + 15, 249, 117, 156, 236, 123, 109, 247, 173, 57, 139, 143, 19, 21, 15, 180, + 230, 189, 171, 230, 228, 215, 211, 229, 22, 94, 108, 135, 17, 93, 5, 14, 2, 0, + 1, 52, 0, 0, 0, 0, 48, 96, 46, 0, 0, 0, 0, 0, 165, 0, 0, 0, 0, 0, 0, 0, 6, + 221, 246, 225, 215, 101, 161, 147, 217, 203, 225, 70, 206, 235, 121, 172, 28, + 180, 133, 237, 95, 91, 55, 145, 58, 140, 245, 133, 126, 255, 0, 169, 23, 4, 1, + 21, 0, 22, 1, 1, 20, 7, 0, 9, 0, 15, 14, 23, 22, 0, 17, 18, 23, 10, 16, 7, 6, + 13, 2, 19, 12, 11, 3, 4, 8, 5, 18, 1, 9, 0, 17, 9, 64, 66, 15, 0, 0, 0, 0, 0, + 89, 241, 0, 0, 0, 0, 0, 0, 23, 3, 1, 0, 0, 1, 9, ]); export const dexlabProgram = Buffer.from([ - 3, 0, 7, 11, 47, 41, 101, 134, 4, 204, 35, 219, 9, 43, 55, 20, 255, 76, 29, 27, 175, 197, 102, 175, 108, 72, 231, 146, 134, 30, 219, 42, 168, 236, 17, 3, 27, 22, 186, 110, 40, 115, 180, 32, 87, 1, 133, 235, 70, 100, 93, 110, 49, 176, 47, - 100, 89, 135, 44, 204, 229, 104, 63, 16, 169, 85, 62, 17, 87, 228, 120, 25, 237, 212, 48, 216, 155, 158, 74, 24, 15, 13, 148, 130, 112, 67, 62, 67, 34, 39, 96, 92, 200, 155, 110, 50, 187, 157, 163, 92, 87, 174, 54, 186, 81, 129, 162, 212, - 79, 70, 190, 180, 232, 143, 72, 102, 119, 242, 172, 80, 2, 53, 168, 117, 184, 99, 184, 206, 230, 204, 5, 165, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 55, 153, 140, 203, 242, 208, 69, - 139, 97, 92, 188, 198, 177, 163, 103, 196, 116, 158, 159, 239, 115, 6, 98, 46, 27, 27, 88, 145, 1, 32, 188, 154, 140, 151, 37, 143, 78, 36, 137, 241, 187, 61, 16, 41, 20, 142, 13, 131, 11, 90, 19, 153, 218, 255, 16, 132, 4, 142, 123, 216, - 219, 233, 248, 89, 198, 250, 122, 243, 190, 219, 173, 58, 61, 101, 243, 106, 171, 201, 116, 49, 177, 187, 228, 194, 210, 246, 224, 228, 124, 166, 2, 3, 69, 47, 93, 97, 6, 155, 136, 87, 254, 171, 129, 132, 251, 104, 127, 99, 70, 24, 192, - 53, 218, 196, 57, 220, 26, 235, 59, 85, 152, 160, 240, 0, 0, 0, 0, 1, 6, 167, 213, 23, 25, 44, 92, 81, 33, 140, 201, 76, 61, 74, 241, 127, 88, 218, 238, 8, 155, 161, 253, 68, 227, 219, 217, 138, 0, 0, 0, 0, 6, 221, 246, 225, 215, 101, - 161, 147, 217, 203, 225, 70, 206, 235, 121, 172, 28, 180, 133, 237, 95, 91, 55, 145, 58, 140, 245, 133, 126, 255, 0, 169, 2, 206, 198, 46, 159, 44, 171, 207, 167, 152, 197, 80, 224, 171, 133, 195, 162, 248, 176, 1, 213, 55, 144, 195, 89, - 153, 30, 36, 158, 74, 236, 222, 5, 4, 2, 0, 1, 52, 0, 0, 0, 0, 48, 96, 46, 0, 0, 0, 0, 0, 165, 0, 0, 0, 0, 0, 0, 0, 6, 221, 246, 225, 215, 101, 161, 147, 217, 203, 225, 70, 206, 235, 121, 172, 28, 180, 133, 237, 95, 91, 55, 145, 58, 140, - 245, 133, 126, 255, 0, 169, 10, 4, 1, 8, 0, 9, 1, 1, 6, 7, 0, 3, 0, 5, 4, 10, 9, 0, 4, 2, 0, 2, 52, 0, 0, 0, 0, 240, 29, 31, 0, 0, 0, 0, 0, 165, 0, 0, 0, 0, 0, 0, 0, 6, 221, 246, 225, 215, 101, 161, 147, 217, 203, 225, 70, 206, 235, 121, - 172, 28, 180, 133, 237, 95, 91, 55, 145, 58, 140, 245, 133, 126, 255, 0, 169, 10, 4, 2, 7, 0, 9, 1, 1, + 3, 0, 7, 11, 47, 41, 101, 134, 4, 204, 35, 219, 9, 43, 55, 20, 255, 76, 29, + 27, 175, 197, 102, 175, 108, 72, 231, 146, 134, 30, 219, 42, 168, 236, 17, 3, + 27, 22, 186, 110, 40, 115, 180, 32, 87, 1, 133, 235, 70, 100, 93, 110, 49, + 176, 47, 100, 89, 135, 44, 204, 229, 104, 63, 16, 169, 85, 62, 17, 87, 228, + 120, 25, 237, 212, 48, 216, 155, 158, 74, 24, 15, 13, 148, 130, 112, 67, 62, + 67, 34, 39, 96, 92, 200, 155, 110, 50, 187, 157, 163, 92, 87, 174, 54, 186, + 81, 129, 162, 212, 79, 70, 190, 180, 232, 143, 72, 102, 119, 242, 172, 80, 2, + 53, 168, 117, 184, 99, 184, 206, 230, 204, 5, 165, 0, 0, 0, 0, 0, 0, 0, 0, 0, + 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 55, 153, + 140, 203, 242, 208, 69, 139, 97, 92, 188, 198, 177, 163, 103, 196, 116, 158, + 159, 239, 115, 6, 98, 46, 27, 27, 88, 145, 1, 32, 188, 154, 140, 151, 37, 143, + 78, 36, 137, 241, 187, 61, 16, 41, 20, 142, 13, 131, 11, 90, 19, 153, 218, + 255, 16, 132, 4, 142, 123, 216, 219, 233, 248, 89, 198, 250, 122, 243, 190, + 219, 173, 58, 61, 101, 243, 106, 171, 201, 116, 49, 177, 187, 228, 194, 210, + 246, 224, 228, 124, 166, 2, 3, 69, 47, 93, 97, 6, 155, 136, 87, 254, 171, 129, + 132, 251, 104, 127, 99, 70, 24, 192, 53, 218, 196, 57, 220, 26, 235, 59, 85, + 152, 160, 240, 0, 0, 0, 0, 1, 6, 167, 213, 23, 25, 44, 92, 81, 33, 140, 201, + 76, 61, 74, 241, 127, 88, 218, 238, 8, 155, 161, 253, 68, 227, 219, 217, 138, + 0, 0, 0, 0, 6, 221, 246, 225, 215, 101, 161, 147, 217, 203, 225, 70, 206, 235, + 121, 172, 28, 180, 133, 237, 95, 91, 55, 145, 58, 140, 245, 133, 126, 255, 0, + 169, 2, 206, 198, 46, 159, 44, 171, 207, 167, 152, 197, 80, 224, 171, 133, + 195, 162, 248, 176, 1, 213, 55, 144, 195, 89, 153, 30, 36, 158, 74, 236, 222, + 5, 4, 2, 0, 1, 52, 0, 0, 0, 0, 48, 96, 46, 0, 0, 0, 0, 0, 165, 0, 0, 0, 0, 0, + 0, 0, 6, 221, 246, 225, 215, 101, 161, 147, 217, 203, 225, 70, 206, 235, 121, + 172, 28, 180, 133, 237, 95, 91, 55, 145, 58, 140, 245, 133, 126, 255, 0, 169, + 10, 4, 1, 8, 0, 9, 1, 1, 6, 7, 0, 3, 0, 5, 4, 10, 9, 0, 4, 2, 0, 2, 52, 0, 0, + 0, 0, 240, 29, 31, 0, 0, 0, 0, 0, 165, 0, 0, 0, 0, 0, 0, 0, 6, 221, 246, 225, + 215, 101, 161, 147, 217, 203, 225, 70, 206, 235, 121, 172, 28, 180, 133, 237, + 95, 91, 55, 145, 58, 140, 245, 133, 126, 255, 0, 169, 10, 4, 2, 7, 0, 9, 1, 1, ]); diff --git a/packages/sdk/src/__test__/e2e/signing/solana/solana.test.ts b/packages/sdk/src/__test__/e2e/signing/solana/solana.test.ts index 550572f7..94a0e596 100644 --- a/packages/sdk/src/__test__/e2e/signing/solana/solana.test.ts +++ b/packages/sdk/src/__test__/e2e/signing/solana/solana.test.ts @@ -6,7 +6,12 @@ * Running with a different mnemonic will cause test failures due to * incorrect key derivations and signature mismatches. */ -import { Keypair as SolanaKeypair, PublicKey as SolanaPublicKey, SystemProgram as SolanaSystemProgram, Transaction as SolanaTransaction } from '@solana/web3.js'; +import { + Keypair as SolanaKeypair, + PublicKey as SolanaPublicKey, + SystemProgram as SolanaSystemProgram, + Transaction as SolanaTransaction, +} from '@solana/web3.js'; import { Constants } from '../../../..'; import { HARDENED_OFFSET } from '../../../../constants'; import { ensureHexBuffer } from '../../../../util'; @@ -20,7 +25,12 @@ import type { Client } from '../../../../client'; //--------------------------------------- // STATE DATA //--------------------------------------- -const DEFAULT_SOLANA_SIGNER_PATH = [HARDENED_OFFSET + 44, HARDENED_OFFSET + 501, HARDENED_OFFSET, HARDENED_OFFSET]; +const DEFAULT_SOLANA_SIGNER_PATH = [ + HARDENED_OFFSET + 44, + HARDENED_OFFSET + 501, + HARDENED_OFFSET, + HARDENED_OFFSET, +]; const prng = getPrng(); describe('[Solana]', () => { @@ -75,16 +85,26 @@ describe('[Solana]', () => { // Generate a pseudorandom blockhash, which is just a public key appearently. const randBuf = prandomBuf(prng, 32, true); - const recentBlockhash = SolanaKeypair.fromSeed(randBuf).publicKey.toBase58(); + const recentBlockhash = + SolanaKeypair.fromSeed(randBuf).publicKey.toBase58(); // Build a transaction and sign it using Solana's JS lib - const txJs = new SolanaTransaction({ recentBlockhash }).add(transfer1, transfer2); + const txJs = new SolanaTransaction({ recentBlockhash }).add( + transfer1, + transfer2, + ); txJs.setSigners(pubA, pubB); - txJs.sign(SolanaKeypair.fromSeed(derivedA.priv), SolanaKeypair.fromSeed(derivedB.priv)); + txJs.sign( + SolanaKeypair.fromSeed(derivedA.priv), + SolanaKeypair.fromSeed(derivedB.priv), + ); const serTxJs = txJs.serialize().toString('hex'); // Build a copy of the transaction and get the serialized payload for signing in firmware. - const txFw = new SolanaTransaction({ recentBlockhash }).add(transfer1, transfer2); + const txFw = new SolanaTransaction({ recentBlockhash }).add( + transfer1, + transfer2, + ); txFw.setSigners(pubA, pubB); // We want to sign the Solana message, not the full transaction const payload = txFw.compileMessage().serialize(); @@ -101,7 +121,10 @@ describe('[Solana]', () => { if (!resp.sig?.r || !resp.sig?.s) { throw new Error('Missing signature components in response'); } - return Buffer.concat([ensureHexBuffer(resp.sig.r as string | Buffer), ensureHexBuffer(resp.sig.s as string | Buffer)]); + return Buffer.concat([ + ensureHexBuffer(resp.sig.r as string | Buffer), + ensureHexBuffer(resp.sig.s as string | Buffer), + ]); }); const sigB = await runGeneric( @@ -114,7 +137,10 @@ describe('[Solana]', () => { if (!resp.sig?.r || !resp.sig?.s) { throw new Error('Missing signature components in response'); } - return Buffer.concat([ensureHexBuffer(resp.sig.r as string | Buffer), ensureHexBuffer(resp.sig.s as string | Buffer)]); + return Buffer.concat([ + ensureHexBuffer(resp.sig.r as string | Buffer), + ensureHexBuffer(resp.sig.s as string | Buffer), + ]); }); txFw.addSignature(pubA, sigA); txFw.addSignature(pubB, sigB); diff --git a/packages/sdk/src/__test__/e2e/signing/solana/solana.versioned.test.ts b/packages/sdk/src/__test__/e2e/signing/solana/solana.versioned.test.ts index 863bb160..249d05c5 100644 --- a/packages/sdk/src/__test__/e2e/signing/solana/solana.versioned.test.ts +++ b/packages/sdk/src/__test__/e2e/signing/solana/solana.versioned.test.ts @@ -6,7 +6,18 @@ * Running with a different mnemonic will cause test failures due to * incorrect key derivations and signature mismatches. */ -import { AddressLookupTableProgram, Connection, Keypair, LAMPORTS_PER_SOL, PublicKey, SystemProgram, Transaction, type TransactionInstruction, TransactionMessage, VersionedTransaction } from '@solana/web3.js'; +import { + AddressLookupTableProgram, + Connection, + Keypair, + LAMPORTS_PER_SOL, + PublicKey, + SystemProgram, + Transaction, + type TransactionInstruction, + TransactionMessage, + VersionedTransaction, +} from '@solana/web3.js'; import { fetchSolanaAddresses, signSolanaTx } from '../../../..'; import { setupClient } from '../../../utils/setup'; @@ -125,9 +136,13 @@ describe('solana.versioned', () => { }); // Create a VersionedTransaction from the serialized data - const versionedTransaction = VersionedTransaction.deserialize(serializedTransaction); + const versionedTransaction = VersionedTransaction.deserialize( + serializedTransaction, + ); - const signedTx = await signSolanaTx(Buffer.from(versionedTransaction.serialize())); + const signedTx = await signSolanaTx( + Buffer.from(versionedTransaction.serialize()), + ); expect(signedTx).toBeTruthy(); }); @@ -135,17 +150,21 @@ describe('solana.versioned', () => { const payer = Keypair.generate(); await requestAirdrop(payer.publicKey, 1); - const [transactionInstruction, pubkey] = await AddressLookupTableProgram.createLookupTable({ - payer: payer.publicKey, - authority: payer.publicKey, - recentSlot: await SOLANA_RPC.getSlot(), - }); + const [transactionInstruction, pubkey] = + await AddressLookupTableProgram.createLookupTable({ + payer: payer.publicKey, + authority: payer.publicKey, + recentSlot: await SOLANA_RPC.getSlot(), + }); await AddressLookupTableProgram.extendLookupTable({ payer: payer.publicKey, authority: payer.publicKey, lookupTable: pubkey, - addresses: [DESTINATION_WALLET_1.publicKey, DESTINATION_WALLET_2.publicKey], + addresses: [ + DESTINATION_WALLET_1.publicKey, + DESTINATION_WALLET_2.publicKey, + ], }); const messageV0 = new TransactionMessage({ diff --git a/packages/sdk/src/__test__/e2e/signing/unformatted.test.ts b/packages/sdk/src/__test__/e2e/signing/unformatted.test.ts index 64e7456a..5ae2bcef 100644 --- a/packages/sdk/src/__test__/e2e/signing/unformatted.test.ts +++ b/packages/sdk/src/__test__/e2e/signing/unformatted.test.ts @@ -9,7 +9,13 @@ import type { Client } from '../../../client'; const prng = getPrng(); const numIter = getNumIter(); -const DEFAULT_SIGNER = [HARDENED_OFFSET + 44, HARDENED_OFFSET + 60, HARDENED_OFFSET, 0, 0]; +const DEFAULT_SIGNER = [ + HARDENED_OFFSET + 44, + HARDENED_OFFSET + 60, + HARDENED_OFFSET, + 0, + 0, +]; describe('[Unformatted]', () => { let client: Client; @@ -30,7 +36,8 @@ describe('[Unformatted]', () => { it('Should test pre-hashed messages', async () => { const fwConstants = client.getFwConstants(); - const { extraDataFrameSz, extraDataMaxFrames, genericSigning } = fwConstants; + const { extraDataFrameSz, extraDataMaxFrames, genericSigning } = + fwConstants; const { baseDataSz } = genericSigning; // Max size that won't be prehashed const maxSz = baseDataSz + extraDataMaxFrames * extraDataFrameSz; @@ -123,7 +130,9 @@ describe('[Unformatted]', () => { hashType: Constants.SIGNING.HASHES.KECCAK256, }, }; - const respLegacy = await client.sign(legacyReq as Parameters[0]); + const respLegacy = await client.sign( + legacyReq as Parameters[0], + ); const genSigR = (respGeneric.sig?.r as Buffer)?.toString('hex') ?? ''; const genSigS = (respGeneric.sig?.s as Buffer)?.toString('hex') ?? ''; @@ -132,7 +141,10 @@ describe('[Unformatted]', () => { const genSig = `${genSigR}${genSigS}`; const legSig = `${legSigR}${legSigS}`; - expect(genSig).toEqualElseLog(legSig, 'Legacy and generic requests produced different sigs.'); + expect(genSig).toEqualElseLog( + legSig, + 'Legacy and generic requests produced different sigs.', + ); }); for (let i = 0; i < numIter; i++) { diff --git a/packages/sdk/src/__test__/e2e/signing/vectors.ts b/packages/sdk/src/__test__/e2e/signing/vectors.ts index 27032852..752df529 100644 --- a/packages/sdk/src/__test__/e2e/signing/vectors.ts +++ b/packages/sdk/src/__test__/e2e/signing/vectors.ts @@ -229,8 +229,11 @@ export const EIP2930_TEST_VECTORS: TestVector[] = [ chainId: 1, accessList: [ { - address: '0xe242e54155b1abc71fc118065270cecaaf8b7768' as `0x${string}`, - storageKeys: ['0x0000000000000000000000000000000000000000000000000000000000000001' as `0x${string}`], + address: + '0xe242e54155b1abc71fc118065270cecaaf8b7768' as `0x${string}`, + storageKeys: [ + '0x0000000000000000000000000000000000000000000000000000000000000001' as `0x${string}`, + ], }, ], }, @@ -249,12 +252,19 @@ export const EIP2930_TEST_VECTORS: TestVector[] = [ chainId: 1, accessList: [ { - address: '0x6B175474E89094C44Da98b954EedeAC495271d0F' as `0x${string}`, // DAI contract - storageKeys: ['0x0000000000000000000000000000000000000000000000000000000000000001' as `0x${string}`, '0x0000000000000000000000000000000000000000000000000000000000000002' as `0x${string}`], + address: + '0x6B175474E89094C44Da98b954EedeAC495271d0F' as `0x${string}`, // DAI contract + storageKeys: [ + '0x0000000000000000000000000000000000000000000000000000000000000001' as `0x${string}`, + '0x0000000000000000000000000000000000000000000000000000000000000002' as `0x${string}`, + ], }, { - address: '0xe242e54155b1abc71fc118065270cecaaf8b7768' as `0x${string}`, // Recipient - storageKeys: ['0x0000000000000000000000000000000000000000000000000000000000000003' as `0x${string}`], + address: + '0xe242e54155b1abc71fc118065270cecaaf8b7768' as `0x${string}`, // Recipient + storageKeys: [ + '0x0000000000000000000000000000000000000000000000000000000000000003' as `0x${string}`, + ], }, ], }, @@ -287,16 +297,27 @@ export const EIP2930_TEST_VECTORS: TestVector[] = [ chainId: 1, accessList: [ { - address: '0x7a250d5630B4cF539739dF2C5dAcb4c659F2488D' as `0x${string}`, // Router - storageKeys: ['0x0000000000000000000000000000000000000000000000000000000000000001' as `0x${string}`, '0x0000000000000000000000000000000000000000000000000000000000000002' as `0x${string}`], + address: + '0x7a250d5630B4cF539739dF2C5dAcb4c659F2488D' as `0x${string}`, // Router + storageKeys: [ + '0x0000000000000000000000000000000000000000000000000000000000000001' as `0x${string}`, + '0x0000000000000000000000000000000000000000000000000000000000000002' as `0x${string}`, + ], }, { - address: '0x6B175474E89094C44Da98b954EedeAC495271d0F' as `0x${string}`, // DAI - storageKeys: ['0x0000000000000000000000000000000000000000000000000000000000000003' as `0x${string}`, '0x0000000000000000000000000000000000000000000000000000000000000004' as `0x${string}`], + address: + '0x6B175474E89094C44Da98b954EedeAC495271d0F' as `0x${string}`, // DAI + storageKeys: [ + '0x0000000000000000000000000000000000000000000000000000000000000003' as `0x${string}`, + '0x0000000000000000000000000000000000000000000000000000000000000004' as `0x${string}`, + ], }, { - address: '0xdAC17F958D2ee523a2206206994597C13D831ec7' as `0x${string}`, // USDT - storageKeys: ['0x0000000000000000000000000000000000000000000000000000000000000005' as `0x${string}`], + address: + '0xdAC17F958D2ee523a2206206994597C13D831ec7' as `0x${string}`, // USDT + storageKeys: [ + '0x0000000000000000000000000000000000000000000000000000000000000005' as `0x${string}`, + ], }, ], }, @@ -325,7 +346,8 @@ export const EIP7702_TEST_VECTORS: TestVector[] = [ authorizationList: [ { chainId: 1, - address: '0x000000004f43c49e93c970e84001853a70923b03' as `0x${string}`, + address: + '0x000000004f43c49e93c970e84001853a70923b03' as `0x${string}`, nonce: 1, yParity: 1, r: '0xc9f7e0af53f516744bc34827bef7236df3123c3a07a601dca75d7698416adc4a' as `0x${string}`, @@ -351,7 +373,8 @@ export const EIP7702_TEST_VECTORS: TestVector[] = [ authorizationList: [ { chainId: 1, - address: '0x000000004f43c49e93c970e84001853a70923b03' as `0x${string}`, + address: + '0x000000004f43c49e93c970e84001853a70923b03' as `0x${string}`, nonce: 1, yParity: 0, r: '0x948c69c40057e9fd4c9bb55506ef764bf80d1bbaf980fe8c09d9d9c0b67d0e49' as `0x${string}`, @@ -377,7 +400,8 @@ export const EIP7702_TEST_VECTORS: TestVector[] = [ authorizationList: [ { chainId: 1, - address: '0x163193c89de836e82bb121bd0dbcaba7e8ba67dc' as `0x${string}`, + address: + '0x163193c89de836e82bb121bd0dbcaba7e8ba67dc' as `0x${string}`, nonce: 4999, yParity: 1, r: '0x806cbbf8a3cfb25b660e03147984ff95725252b6c95aceed91d5c0bfca6ad0d1' as `0x${string}`, @@ -590,7 +614,9 @@ export const EDGE_CASE_TEST_VECTORS: TestVector[] = [ tx: { type: 'eip1559', to: '0xe242e54155b1abc71fc118065270cecaaf8b7768' as `0x${string}`, - value: BigInt('0xffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff'), // Max uint256 + value: BigInt( + '0xffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff', + ), // Max uint256 data: '0x' as `0x${string}`, // Add missing data field nonce: 0, maxFeePerGas: BigInt('20000000000'), // 20 gwei @@ -643,11 +669,15 @@ export const EDGE_CASE_TEST_VECTORS: TestVector[] = [ chainId: 1, accessList: [ { - address: '0xe242e54155b1abc71fc118065270cecaaf8b7768' as `0x${string}`, - storageKeys: ['0x7154f8b310ad6ce97ce3b15e3419d9863865dfe2d8635802f7f4a52a206255a6' as `0x${string}`], + address: + '0xe242e54155b1abc71fc118065270cecaaf8b7768' as `0x${string}`, + storageKeys: [ + '0x7154f8b310ad6ce97ce3b15e3419d9863865dfe2d8635802f7f4a52a206255a6' as `0x${string}`, + ], }, { - address: '0xe0f8ff08ef0242c461da688b8b85e438db724860' as `0x${string}`, + address: + '0xe0f8ff08ef0242c461da688b8b85e438db724860' as `0x${string}`, storageKeys: [], // Empty storage keys }, ], @@ -668,11 +698,15 @@ export const EDGE_CASE_TEST_VECTORS: TestVector[] = [ chainId: 1, accessList: [ { - address: '0xe242e54155b1abc71fc118065270cecaaf8b7768' as `0x${string}`, - storageKeys: ['0x7154f8b310ad6ce97ce3b15e3419d9863865dfe2d8635802f7f4a52a206255a6' as `0x${string}`], + address: + '0xe242e54155b1abc71fc118065270cecaaf8b7768' as `0x${string}`, + storageKeys: [ + '0x7154f8b310ad6ce97ce3b15e3419d9863865dfe2d8635802f7f4a52a206255a6' as `0x${string}`, + ], }, { - address: '0xe0f8ff08ef0242c461da688b8b85e438db724860' as `0x${string}`, + address: + '0xe0f8ff08ef0242c461da688b8b85e438db724860' as `0x${string}`, storageKeys: [], // Empty storage keys }, ], @@ -1018,7 +1052,9 @@ export const ALL_COMPREHENSIVE_VECTORS: TestVector[] = [ * Get vectors by category for targeted testing */ export function getVectorsByCategory(category: string): TestVector[] { - return ALL_COMPREHENSIVE_VECTORS.filter((vector) => vector.category === category); + return ALL_COMPREHENSIVE_VECTORS.filter( + (vector) => vector.category === category, + ); } /** @@ -1030,14 +1066,23 @@ export function getBalancedTestVectors(perType = 3): TestVector[] { const eip2930Vectors = EIP2930_TEST_VECTORS.slice(0, perType); const eip7702Vectors = EIP7702_TEST_VECTORS.slice(0, perType); - return [...legacyVectors, ...eip1559Vectors, ...eip2930Vectors, ...eip7702Vectors]; + return [ + ...legacyVectors, + ...eip1559Vectors, + ...eip2930Vectors, + ...eip7702Vectors, + ]; } /** * Get vectors for boundary testing specifically */ export function getBoundaryTestVectors(): TestVector[] { - return [...BOUNDARY_CONDITION_VECTORS, ...EDGE_CASE_TEST_VECTORS, ...PAYLOAD_SIZE_VECTORS]; + return [ + ...BOUNDARY_CONDITION_VECTORS, + ...EDGE_CASE_TEST_VECTORS, + ...PAYLOAD_SIZE_VECTORS, + ]; } /** diff --git a/packages/sdk/src/__test__/integration/__mocks__/4byte.ts b/packages/sdk/src/__test__/integration/__mocks__/4byte.ts index dea18f81..4bdb39f8 100644 --- a/packages/sdk/src/__test__/integration/__mocks__/4byte.ts +++ b/packages/sdk/src/__test__/integration/__mocks__/4byte.ts @@ -38,7 +38,8 @@ export const fourbyteResponse0x38ed1739 = { created_at: '2020-08-09T08:56:14.110995Z', hex_signature: '0x38ed1739', id: 171806, - text_signature: 'swapExactTokensForTokens(uint256,uint256,address[],address,uint256)', + text_signature: + 'swapExactTokensForTokens(uint256,uint256,address[],address,uint256)', }, ], }; @@ -60,7 +61,8 @@ export const fourbyteResponse0c49ccbe = { { id: 186682, created_at: '2021-05-09T03:48:17.627742Z', - text_signature: 'decreaseLiquidity((uint256,uint128,uint256,uint256,uint256))', + text_signature: + 'decreaseLiquidity((uint256,uint128,uint256,uint256,uint256))', hex_signature: '0x0c49ccbe', bytes_signature: '\\fI̾', }, @@ -84,7 +86,8 @@ export const fourbyteResponse0x6a761202 = { { id: 169422, created_at: '2020-01-28T10:40:07.614936Z', - text_signature: 'execTransaction(address,uint256,bytes,uint8,uint256,uint256,uint256,address,address,bytes)', + text_signature: + 'execTransaction(address,uint256,bytes,uint8,uint256,uint256,uint256,address,address,bytes)', hex_signature: '0x6a761202', bytes_signature: 'jv\\u0012\\u0002', }, diff --git a/packages/sdk/src/__test__/integration/__mocks__/etherscan.ts b/packages/sdk/src/__test__/integration/__mocks__/etherscan.ts index 61f8f422..4cdd2629 100644 --- a/packages/sdk/src/__test__/integration/__mocks__/etherscan.ts +++ b/packages/sdk/src/__test__/integration/__mocks__/etherscan.ts @@ -1447,7 +1447,8 @@ export const etherscanResponse0xc36442b6 = [ type: 'uint256', }, ], - internalType: 'struct INonfungiblePositionManager.DecreaseLiquidityParams', + internalType: + 'struct INonfungiblePositionManager.DecreaseLiquidityParams', name: 'params', type: 'tuple', }, @@ -1535,7 +1536,8 @@ export const etherscanResponse0xc36442b6 = [ type: 'uint256', }, ], - internalType: 'struct INonfungiblePositionManager.IncreaseLiquidityParams', + internalType: + 'struct INonfungiblePositionManager.IncreaseLiquidityParams', name: 'params', type: 'tuple', }, @@ -2526,7 +2528,8 @@ export const etherscanResponse0x06412d7e = [ { internalType: 'uint256', name: 'amount1Min', type: 'uint256' }, { internalType: 'uint256', name: 'deadline', type: 'uint256' }, ], - internalType: 'struct INonfungiblePositionManager.DecreaseLiquidityParams', + internalType: + 'struct INonfungiblePositionManager.DecreaseLiquidityParams', name: 'params', type: 'tuple', }, @@ -2564,7 +2567,8 @@ export const etherscanResponse0x06412d7e = [ { internalType: 'uint256', name: 'amount1Min', type: 'uint256' }, { internalType: 'uint256', name: 'deadline', type: 'uint256' }, ], - internalType: 'struct INonfungiblePositionManager.IncreaseLiquidityParams', + internalType: + 'struct INonfungiblePositionManager.IncreaseLiquidityParams', name: 'params', type: 'tuple', }, diff --git a/packages/sdk/src/__test__/integration/__mocks__/handlers.ts b/packages/sdk/src/__test__/integration/__mocks__/handlers.ts index 4b6a025f..80e26132 100644 --- a/packages/sdk/src/__test__/integration/__mocks__/handlers.ts +++ b/packages/sdk/src/__test__/integration/__mocks__/handlers.ts @@ -1,8 +1,20 @@ import { http, HttpResponse } from 'msw'; -import { fourbyteResponse0c49ccbe, fourbyteResponse0x6a761202, fourbyteResponse0x38ed1739, fourbyteResponse0xa9059cbb, fourbyteResponseac9650d8, fourbyteResponsefc6f7865 } from './4byte'; +import { + fourbyteResponse0c49ccbe, + fourbyteResponse0x6a761202, + fourbyteResponse0x38ed1739, + fourbyteResponse0xa9059cbb, + fourbyteResponseac9650d8, + fourbyteResponsefc6f7865, +} from './4byte'; import addKvRecordsResponse from './addKvRecords.json'; import connectResponse from './connect.json'; -import { etherscanResponse0x06412d7e, etherscanResponse0x7a250d56, etherscanResponse0xa0b86991, etherscanResponse0xc36442b6 } from './etherscan'; +import { + etherscanResponse0x06412d7e, + etherscanResponse0x7a250d56, + etherscanResponse0xa0b86991, + etherscanResponse0xc36442b6, +} from './etherscan'; import fetchActiveWalletResponse from './fetchActiveWallet.json'; import getAddressesResponse from './getAddresses.json'; import getKvRecordsResponse from './getKvRecords.json'; diff --git a/packages/sdk/src/__test__/integration/fetchCalldataDecoder.test.ts b/packages/sdk/src/__test__/integration/fetchCalldataDecoder.test.ts index c466c20b..e5abe6f4 100644 --- a/packages/sdk/src/__test__/integration/fetchCalldataDecoder.test.ts +++ b/packages/sdk/src/__test__/integration/fetchCalldataDecoder.test.ts @@ -33,7 +33,8 @@ describe('fetchCalldataDecoder', () => { }); test('decode proxy calldata', async () => { - const data = '0xa9059cbb0000000000000000000000004ffbf741b0a64e8bd1f9d89fc9b5584cc5227b700000000000000000000000000000000000000000000000000000003052aacdb8'; + const data = + '0xa9059cbb0000000000000000000000004ffbf741b0a64e8bd1f9d89fc9b5584cc5227b700000000000000000000000000000000000000000000000000000003052aacdb8'; const to = '0xa0b86991c6218b36c1d19d4a2e9eb0ce3606eb48'; const decoded = await fetchCalldataDecoder(data, to, '1'); expect(decoded).toMatchSnapshot(); @@ -97,7 +98,8 @@ describe('fetchCalldataDecoder', () => { // TODO: add api key to fix this test test.skip('decode Celo calldata', async () => { - const data = '0xf2fde38b000000000000000000000000b538e8dcd297450bdef46222f3ceb33bb1e921b3'; + const data = + '0xf2fde38b000000000000000000000000b538e8dcd297450bdef46222f3ceb33bb1e921b3'; const to = '0x96d59127ccd1c0e3749e733ee04f0dfbd2f808c8'; const decoded = await fetchCalldataDecoder(data, to, '42220'); expect(decoded).toMatchSnapshot(); diff --git a/packages/sdk/src/__test__/unit/__mocks__/decoderData.ts b/packages/sdk/src/__test__/unit/__mocks__/decoderData.ts index 26c85fc0..faec69c6 100644 --- a/packages/sdk/src/__test__/unit/__mocks__/decoderData.ts +++ b/packages/sdk/src/__test__/unit/__mocks__/decoderData.ts @@ -3,10 +3,18 @@ * For encrypted requests, the results are decrypted. * For each response, the response code and checksum are removed. */ -import { LatticeEncDataSchema, LatticeGetAddressesFlag } from '../../../protocol'; +import { + LatticeEncDataSchema, + LatticeGetAddressesFlag, +} from '../../../protocol'; import { getP256KeyPair } from '../../../util'; -export const clientKeyPair = getP256KeyPair(Buffer.from('3fb53b677f73e4d2b8c89c303f6f6b349f0075ad88ea126cb9f6632085815dca', 'hex')); +export const clientKeyPair = getP256KeyPair( + Buffer.from( + '3fb53b677f73e4d2b8c89c303f6f6b349f0075ad88ea126cb9f6632085815dca', + 'hex', + ), +); export const decoderTestsFwConstants = JSON.parse( '{"extraDataFrameSz":1500,"extraDataMaxFrames":1,"genericSigning":{"baseReqSz":1552,"baseDataSz":1519,"hashTypes":{"NONE":0,"KECCAK256":1,"SHA256":2},"curveTypes":{"SECP256K1":0,"ED25519":1,"BLS12_381_G2":2},"encodingTypes":{"NONE":1,"SOLANA":2,"EVM":4,"ETH_DEPOSIT":5},"calldataDecoding":{"reserved":2895728,"maxSz":1024}},"reqMaxDataSz":1678,"ethMaxGasPrice":20000000000000,"addrFlagsAllowed":true,"ethMaxDataSz":1519,"ethMaxMsgSz":1540,"eip712MaxTypeParams":36,"varAddrPathSzAllowed":true,"eip712Supported":true,"prehashAllowed":true,"ethMsgPreHashAllowed":true,"allowedEthTxTypes":[1,2],"personalSignHeaderSz":72,"kvActionsAllowed":true,"kvKeyMaxStrSz":63,"kvValMaxStrSz":63,"kvActionMaxNum":10,"kvRemoveMaxNum":100,"allowBtcLegacyAndSegwitAddrs":true,"contractDeployKey":"0x08002e0fec8e6acf00835f43c9764f7364fa3f42","abiCategorySz":32,"abiMaxRmv":200,"getAddressFlags":[4,3,5],"maxDecoderBufSz":1600}', @@ -34,7 +42,10 @@ export const signGenericRequest = { encodingType: 4, hashType: 1, omitPubkey: false, - origPayloadBuf: Buffer.from('01f84b01808447868c0082c35094e242e54155b1abc71fc118065270cecaaf8b776885e8d4a51000a417e914679b7e160613be4f8c2d3203d236286d74eb9192f6d6f71b9118a42bb033ccd8e8c0', 'hex'), + origPayloadBuf: Buffer.from( + '01f84b01808447868c0082c35094e242e54155b1abc71fc118065270cecaaf8b776885e8d4a51000a417e914679b7e160613be4f8c2d3203d236286d74eb9192f6d6f71b9118a42bb033ccd8e8c0', + 'hex', + ), }; export const signGenericDecoderData = Buffer.from( '04a50d7d8e5bf6353086dfaff71652a223aa13e02273a2b6bf5a145314b544be1281ac8f78d035874a06b11e3df68e45f7630b2e6ba3be0f51f916fbb6f0a6403930440220640b2c690858ab8d0b9500f9ed64c9aa6b7467b77f1199b061aa96ea780aadaa022048f830f9290dd1b3eaf1922e08a8c992873be1162bd6d5bef681cf911328abe500000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000', @@ -47,7 +58,10 @@ export const signBitcoinRequest = { origData: { prevOuts: [ { - txHash: Buffer.from('b2efdbdd3340d2bc547671ce3993a6f05d70343c07578f9d7f5626fdfc06fa35', 'hex'), + txHash: Buffer.from( + 'b2efdbdd3340d2bc547671ce3993a6f05d70343c07578f9d7f5626fdfc06fa35', + 'hex', + ), value: 76800, index: 0, signerPath: [2147483732, 2147483649, 2147483648, 0, 0], @@ -83,7 +97,10 @@ export const fetchEncryptedDataRequest = { params: { path: [12381, 3600, 0, 0], c: 999, - walletUID: Buffer.from('6ae62c0c96c1e039fc97bfeb7c2428c093fe7f0b6188a434bbac7b652c3e4012', 'hex'), + walletUID: Buffer.from( + '6ae62c0c96c1e039fc97bfeb7c2428c093fe7f0b6188a434bbac7b652c3e4012', + 'hex', + ), }, }; export const fetchEncryptedDataDecoderData = Buffer.from( diff --git a/packages/sdk/src/__test__/unit/api.test.ts b/packages/sdk/src/__test__/unit/api.test.ts index ba8d3a02..99a08dce 100644 --- a/packages/sdk/src/__test__/unit/api.test.ts +++ b/packages/sdk/src/__test__/unit/api.test.ts @@ -52,6 +52,8 @@ describe('parseDerivationPath', () => { }); it('throws error for invalid input', () => { - expect(() => parseDerivationPath('invalid/path')).toThrow('Invalid part in derivation path: invalid'); + expect(() => parseDerivationPath('invalid/path')).toThrow( + 'Invalid part in derivation path: invalid', + ); }); }); diff --git a/packages/sdk/src/__test__/unit/compareEIP7702Serialization.test.ts b/packages/sdk/src/__test__/unit/compareEIP7702Serialization.test.ts index 1856fa33..4eb31b8e 100644 --- a/packages/sdk/src/__test__/unit/compareEIP7702Serialization.test.ts +++ b/packages/sdk/src/__test__/unit/compareEIP7702Serialization.test.ts @@ -1,7 +1,10 @@ import { Hash } from 'ox'; import { parseEther, serializeTransaction, toHex } from 'viem'; import { serializeEIP7702Transaction } from '../../ethereum'; -import type { EIP7702AuthListTransactionRequest as EIP7702AuthListTransaction, EIP7702AuthTransactionRequest as EIP7702AuthTransaction } from '../../types'; +import type { + EIP7702AuthListTransactionRequest as EIP7702AuthListTransaction, + EIP7702AuthTransactionRequest as EIP7702AuthTransaction, +} from '../../types'; describe('EIP7702 Transaction Serialization Comparison', () => { /** diff --git a/packages/sdk/src/__test__/unit/decoders.test.ts b/packages/sdk/src/__test__/unit/decoders.test.ts index 6b2b5a44..9ab692fc 100644 --- a/packages/sdk/src/__test__/unit/decoders.test.ts +++ b/packages/sdk/src/__test__/unit/decoders.test.ts @@ -1,4 +1,10 @@ -import { decodeConnectResponse, decodeFetchEncData, decodeGetAddressesResponse, decodeGetKvRecordsResponse, decodeSignResponse } from '../../functions'; +import { + decodeConnectResponse, + decodeFetchEncData, + decodeGetAddressesResponse, + decodeGetKvRecordsResponse, + decodeSignResponse, +} from '../../functions'; import type { DecodeSignResponseParams } from '../../types'; import { clientKeyPair, @@ -17,11 +23,15 @@ import { describe('decoders', () => { test('connect', () => { - expect(decodeConnectResponse(connectDecoderData, clientKeyPair)).toMatchSnapshot(); + expect( + decodeConnectResponse(connectDecoderData, clientKeyPair), + ).toMatchSnapshot(); }); test('getAddresses', () => { - expect(decodeGetAddressesResponse(getAddressesDecoderData, getAddressesFlag)).toMatchSnapshot(); + expect( + decodeGetAddressesResponse(getAddressesDecoderData, getAddressesFlag), + ).toMatchSnapshot(); }); test('sign - bitcoin', () => { @@ -44,7 +54,12 @@ describe('decoders', () => { }); test('getKvRecords', () => { - expect(decodeGetKvRecordsResponse(getKvRecordsDecoderData, decoderTestsFwConstants)).toMatchSnapshot(); + expect( + decodeGetKvRecordsResponse( + getKvRecordsDecoderData, + decoderTestsFwConstants, + ), + ).toMatchSnapshot(); }); test('fetchEncryptedData', () => { diff --git a/packages/sdk/src/__test__/unit/eip7702.test.ts b/packages/sdk/src/__test__/unit/eip7702.test.ts index 7d596ef7..080f111f 100644 --- a/packages/sdk/src/__test__/unit/eip7702.test.ts +++ b/packages/sdk/src/__test__/unit/eip7702.test.ts @@ -1,7 +1,10 @@ import { Hash } from 'ox'; import { parseEther, toHex } from 'viem'; import { serializeEIP7702Transaction } from '../../ethereum'; -import type { EIP7702AuthListTransactionRequest, EIP7702AuthTransactionRequest } from '../../types'; +import type { + EIP7702AuthListTransactionRequest, + EIP7702AuthTransactionRequest, +} from '../../types'; describe('EIP-7702 Transaction Serialization', () => { /** diff --git a/packages/sdk/src/__test__/unit/encoders.test.ts b/packages/sdk/src/__test__/unit/encoders.test.ts index 1dbab8b0..81ef41b8 100644 --- a/packages/sdk/src/__test__/unit/encoders.test.ts +++ b/packages/sdk/src/__test__/unit/encoders.test.ts @@ -1,8 +1,21 @@ import { EXTERNAL } from '../../constants'; -import { encodeAddKvRecordsRequest, encodeGetAddressesRequest, encodeGetKvRecordsRequest, encodePairRequest, encodeRemoveKvRecordsRequest, encodeSignRequest } from '../../functions'; +import { + encodeAddKvRecordsRequest, + encodeGetAddressesRequest, + encodeGetKvRecordsRequest, + encodePairRequest, + encodeRemoveKvRecordsRequest, + encodeSignRequest, +} from '../../functions'; import { buildTransaction } from '../../shared/functions'; import { getP256KeyPair } from '../../util'; -import { buildFirmwareConstants, buildGetAddressesObject, buildSignObject, buildWallet, getFwVersionsList } from '../utils/builders'; +import { + buildFirmwareConstants, + buildGetAddressesObject, + buildSignObject, + buildWallet, + getFwVersionsList, +} from '../utils/builders'; describe('encoders', () => { let mockRandom: any; @@ -57,19 +70,22 @@ describe('encoders', () => { }); describe('sign', () => { - test.each(getFwVersionsList())('should test sign encoder with firmware v%d.%d.%d', (major, minor, patch) => { - const fwVersion = Buffer.from([patch, minor, major]); - const txObj = buildSignObject(fwVersion); - const tx = buildTransaction(txObj); - const req = { - ...txObj, - ...tx, - wallet: buildWallet(), - }; - const { payload } = encodeSignRequest(req); - const payloadAsString = payload.toString('hex'); - expect(payloadAsString).toMatchSnapshot(); - }); + test.each(getFwVersionsList())( + 'should test sign encoder with firmware v%d.%d.%d', + (major, minor, patch) => { + const fwVersion = Buffer.from([patch, minor, major]); + const txObj = buildSignObject(fwVersion); + const tx = buildTransaction(txObj); + const req = { + ...txObj, + ...tx, + wallet: buildWallet(), + }; + const { payload } = encodeSignRequest(req); + const payloadAsString = payload.toString('hex'); + expect(payloadAsString).toMatchSnapshot(); + }, + ); }); describe('KvRecords', () => { diff --git a/packages/sdk/src/__test__/unit/ethereum.validate.test.ts b/packages/sdk/src/__test__/unit/ethereum.validate.test.ts index 0785c8fe..18c696c9 100644 --- a/packages/sdk/src/__test__/unit/ethereum.validate.test.ts +++ b/packages/sdk/src/__test__/unit/ethereum.validate.test.ts @@ -1,4 +1,9 @@ -import { type MessageTypes, SignTypedDataVersion, TypedDataUtils, type TypedMessage } from '@metamask/eth-sig-util'; +import { + type MessageTypes, + SignTypedDataVersion, + TypedDataUtils, + type TypedMessage, +} from '@metamask/eth-sig-util'; import { ecsign, privateToAddress } from 'ethereumjs-util'; import { mnemonicToAccount } from 'viem/accounts'; import { HARDENED_OFFSET } from '../../constants'; @@ -31,7 +36,10 @@ describe('validateEthereumMsgResponse', () => { if (!hdKey.privateKey) throw new Error('No private key'); const priv = Buffer.from(hdKey.privateKey); const signer = privateToAddress(priv); - const digest = TypedDataUtils.eip712Hash(typedData, SignTypedDataVersion.V4); + const digest = TypedDataUtils.eip712Hash( + typedData, + SignTypedDataVersion.V4, + ); const sig = ecsign(Buffer.from(digest), priv); const fwConstants = buildFirmwareConstants(); const request = ethereum.buildEthereumMsgRequest({ @@ -68,7 +76,10 @@ describe('validateEthereumMsgResponse', () => { if (!hdKey.privateKey) throw new Error('No private key'); const priv = Buffer.from(hdKey.privateKey); const signer = privateToAddress(priv); - const digest = TypedDataUtils.eip712Hash(typedData, SignTypedDataVersion.V4); + const digest = TypedDataUtils.eip712Hash( + typedData, + SignTypedDataVersion.V4, + ); const sig = ecsign(Buffer.from(digest), priv); const result = ethereum.validateEthereumMsgResponse( diff --git a/packages/sdk/src/__test__/unit/module.interop.test.ts b/packages/sdk/src/__test__/unit/module.interop.test.ts index 18025691..98bed751 100644 --- a/packages/sdk/src/__test__/unit/module.interop.test.ts +++ b/packages/sdk/src/__test__/unit/module.interop.test.ts @@ -1,5 +1,11 @@ import { execSync, spawnSync } from 'node:child_process'; -import { existsSync, mkdirSync, mkdtempSync, rmSync, symlinkSync } from 'node:fs'; +import { + existsSync, + mkdirSync, + mkdtempSync, + rmSync, + symlinkSync, +} from 'node:fs'; import os from 'node:os'; import path from 'node:path'; import { fileURLToPath } from 'node:url'; @@ -53,7 +59,9 @@ const runNodeCheck = (args: string[]) => { throw result.error; } if (result.status !== 0) { - throw new Error(`Node command failed (${result.status}):\n${result.stderr || result.stdout}`); + throw new Error( + `Node command failed (${result.status}):\n${result.stderr || result.stdout}`, + ); } }; diff --git a/packages/sdk/src/__test__/unit/parseGenericSigningResponse.test.ts b/packages/sdk/src/__test__/unit/parseGenericSigningResponse.test.ts index da12a4b3..8874c50d 100644 --- a/packages/sdk/src/__test__/unit/parseGenericSigningResponse.test.ts +++ b/packages/sdk/src/__test__/unit/parseGenericSigningResponse.test.ts @@ -34,12 +34,18 @@ describe('parseGenericSigningResponse', () => { const hash = Buffer.from(Hash.keccak256(payload)); // Create a fake signature - const privateKey = Buffer.from('0101010101010101010101010101010101010101010101010101010101010101', 'hex'); + const privateKey = Buffer.from( + '0101010101010101010101010101010101010101010101010101010101010101', + 'hex', + ); const sigObj = secp256k1.ecdsaSign(hash, privateKey); const publicKey = secp256k1.publicKeyCreate(privateKey, false); // Create DER-encoded signature response - const derSig = createDERSignature(Buffer.from(sigObj.signature.slice(0, 32)), Buffer.from(sigObj.signature.slice(32, 64))); + const derSig = createDERSignature( + Buffer.from(sigObj.signature.slice(0, 32)), + Buffer.from(sigObj.signature.slice(32, 64)), + ); // Create mock response buffer const mockResponse = Buffer.concat([ @@ -68,16 +74,25 @@ describe('parseGenericSigningResponse', () => { it('should handle EVM transaction encoding', () => { // Simulate an unsigned legacy transaction - const unsignedTx = Buffer.from('e9808504a817c800825208943535353535353535353535353535353535353535880de0b6b3a764000080', 'hex'); + const unsignedTx = Buffer.from( + 'e9808504a817c800825208943535353535353535353535353535353535353535880de0b6b3a764000080', + 'hex', + ); const hash = Buffer.from(Hash.keccak256(unsignedTx)); // Create a fake signature - const privateKey = Buffer.from('0101010101010101010101010101010101010101010101010101010101010101', 'hex'); + const privateKey = Buffer.from( + '0101010101010101010101010101010101010101010101010101010101010101', + 'hex', + ); const sigObj = secp256k1.ecdsaSign(hash, privateKey); const publicKey = secp256k1.publicKeyCreate(privateKey, false); // Create DER-encoded signature response - const derSig = createDERSignature(Buffer.from(sigObj.signature.slice(0, 32)), Buffer.from(sigObj.signature.slice(32, 64))); + const derSig = createDERSignature( + Buffer.from(sigObj.signature.slice(0, 32)), + Buffer.from(sigObj.signature.slice(32, 64)), + ); // Create mock response buffer const mockResponse = Buffer.concat([ @@ -119,12 +134,18 @@ describe('parseGenericSigningResponse', () => { const hash = Buffer.from(Hash.keccak256(rlpEncoded)); // Create a fake signature - const privateKey = Buffer.from('0101010101010101010101010101010101010101010101010101010101010101', 'hex'); + const privateKey = Buffer.from( + '0101010101010101010101010101010101010101010101010101010101010101', + 'hex', + ); const sigObj = secp256k1.ecdsaSign(hash, privateKey); const publicKey = secp256k1.publicKeyCreate(privateKey, false); // Create DER-encoded signature response - const derSig = createDERSignature(Buffer.from(sigObj.signature.slice(0, 32)), Buffer.from(sigObj.signature.slice(32, 64))); + const derSig = createDERSignature( + Buffer.from(sigObj.signature.slice(0, 32)), + Buffer.from(sigObj.signature.slice(32, 64)), + ); // Create mock response buffer const mockResponse = Buffer.concat([ diff --git a/packages/sdk/src/__test__/unit/personalSignValidation.test.ts b/packages/sdk/src/__test__/unit/personalSignValidation.test.ts index d6d39cb7..ee225c57 100644 --- a/packages/sdk/src/__test__/unit/personalSignValidation.test.ts +++ b/packages/sdk/src/__test__/unit/personalSignValidation.test.ts @@ -12,15 +12,23 @@ describe('Personal Sign Validation - Issue Fix', () => { it('should correctly validate personal message signature', () => { // Create a test private key and derive public key - const privateKey = Buffer.from('0101010101010101010101010101010101010101010101010101010101010101', 'hex'); + const privateKey = Buffer.from( + '0101010101010101010101010101010101010101010101010101010101010101', + 'hex', + ); const publicKey = secp256k1.publicKeyCreate(privateKey, false); // Create a test message const message = Buffer.from('Test message', 'utf8'); // Build personal sign prefix and hash - const prefix = Buffer.from(`\u0019Ethereum Signed Message:\n${message.length.toString()}`, 'utf-8'); - const messageHash = Buffer.from(Hash.keccak256(Buffer.concat([prefix, message]))); + const prefix = Buffer.from( + `\u0019Ethereum Signed Message:\n${message.length.toString()}`, + 'utf-8', + ); + const messageHash = Buffer.from( + Hash.keccak256(Buffer.concat([prefix, message])), + ); // Sign the message const sigObj = secp256k1.ecdsaSign(messageHash, privateKey); @@ -34,7 +42,9 @@ describe('Personal Sign Validation - Issue Fix', () => { // Get the Ethereum address from the public key // This matches what the firmware returns const pubkeyWithoutPrefix = publicKey.slice(1); // Remove 0x04 prefix - const addressBuffer = Buffer.from(Hash.keccak256(pubkeyWithoutPrefix)).slice(-20); + const addressBuffer = Buffer.from( + Hash.keccak256(pubkeyWithoutPrefix), + ).slice(-20); // This is the function that was failing before the fix // It should now correctly add the recovery parameter @@ -45,7 +55,9 @@ describe('Personal Sign Validation - Issue Fix', () => { // Verify the signature has a valid v value (27 or 28) expect(result.v).toBeDefined(); - const vValue = Buffer.isBuffer(result.v) ? result.v.readUInt8(0) : Number(result.v); + const vValue = Buffer.isBuffer(result.v) + ? result.v.readUInt8(0) + : Number(result.v); expect([27, 28]).toContain(vValue); // Verify r and s are buffers of correct length @@ -83,11 +95,19 @@ describe('Personal Sign Validation - Issue Fix', () => { const payloadBuffer = Buffer.from(testPayload.slice(2), 'hex'); // Build personal sign hash - const prefix = Buffer.from(`\u0019Ethereum Signed Message:\n${payloadBuffer.length.toString()}`, 'utf-8'); - const messageHash = Buffer.from(Hash.keccak256(Buffer.concat([prefix, payloadBuffer]))); + const prefix = Buffer.from( + `\u0019Ethereum Signed Message:\n${payloadBuffer.length.toString()}`, + 'utf-8', + ); + const messageHash = Buffer.from( + Hash.keccak256(Buffer.concat([prefix, payloadBuffer])), + ); // Create a valid signature for this message - const privateKey = Buffer.from('0101010101010101010101010101010101010101010101010101010101010101', 'hex'); + const privateKey = Buffer.from( + '0101010101010101010101010101010101010101010101010101010101010101', + 'hex', + ); const publicKey = secp256k1.publicKeyCreate(privateKey, false); const sigObj = secp256k1.ecdsaSign(messageHash, privateKey); @@ -98,7 +118,9 @@ describe('Personal Sign Validation - Issue Fix', () => { // Get address from public key const pubkeyWithoutPrefix = publicKey.slice(1); - const addressBuffer = Buffer.from(Hash.keccak256(pubkeyWithoutPrefix)).slice(-20); + const addressBuffer = Buffer.from( + Hash.keccak256(pubkeyWithoutPrefix), + ).slice(-20); // This should NOT throw with the fix in place expect(() => { diff --git a/packages/sdk/src/__test__/unit/selectDefFrom4byteABI.test.ts b/packages/sdk/src/__test__/unit/selectDefFrom4byteABI.test.ts index 08bb7005..46a79a18 100644 --- a/packages/sdk/src/__test__/unit/selectDefFrom4byteABI.test.ts +++ b/packages/sdk/src/__test__/unit/selectDefFrom4byteABI.test.ts @@ -17,14 +17,16 @@ describe('selectDefFrom4byteAbi', () => { created_at: '2020-08-09T08:56:14.110995Z', hex_signature: '0x38ed1739', id: 171801, - text_signature: 'swapExactTokensForTokens(uint256,uint256,address[],address,uint256)', + text_signature: + 'swapExactTokensForTokens(uint256,uint256,address[],address,uint256)', }, { bytes_signature: '8í9', created_at: '2020-01-09T08:56:14.110995Z', hex_signature: '0x38ed1739', id: 171806, - text_signature: 'swapExactTokensForTokens(uint256,uint256,address[],address,uint256)', + text_signature: + 'swapExactTokensForTokens(uint256,uint256,address[],address,uint256)', }, { bytes_signature: '', diff --git a/packages/sdk/src/__test__/unit/signatureUtils.test.ts b/packages/sdk/src/__test__/unit/signatureUtils.test.ts index 73a2e44b..f1dfbec7 100644 --- a/packages/sdk/src/__test__/unit/signatureUtils.test.ts +++ b/packages/sdk/src/__test__/unit/signatureUtils.test.ts @@ -7,7 +7,10 @@ describe('getYParity', () => { // Helper function to create a valid signature const createValidSignature = (messageHash: Buffer) => { // Create a deterministic private key for testing - const privateKey = Buffer.from('0101010101010101010101010101010101010101010101010101010101010101', 'hex'); + const privateKey = Buffer.from( + '0101010101010101010101010101010101010101010101010101010101010101', + 'hex', + ); // Sign the message const sigObj = secp256k1.ecdsaSign(messageHash, privateKey); @@ -28,7 +31,8 @@ describe('getYParity', () => { describe('Simple signature format', () => { it('should handle simple format with Buffer inputs', () => { const messageHash = randomBytes(32); - const { signature, publicKey, recovery } = createValidSignature(messageHash); + const { signature, publicKey, recovery } = + createValidSignature(messageHash); const yParity = getYParity({ messageHash, @@ -42,7 +46,8 @@ describe('getYParity', () => { it('should handle simple format with hex string inputs', () => { const messageHash = randomBytes(32); - const { signature, publicKey, recovery } = createValidSignature(messageHash); + const { signature, publicKey, recovery } = + createValidSignature(messageHash); const yParity = getYParity({ messageHash: `0x${messageHash.toString('hex')}`, @@ -58,7 +63,10 @@ describe('getYParity', () => { it('should handle simple format with compressed public key', () => { const messageHash = randomBytes(32); - const privateKey = Buffer.from('0101010101010101010101010101010101010101010101010101010101010101', 'hex'); + const privateKey = Buffer.from( + '0101010101010101010101010101010101010101010101010101010101010101', + 'hex', + ); const sigObj = secp256k1.ecdsaSign(messageHash, privateKey); const compressedPubkey = secp256k1.publicKeyCreate(privateKey, true); @@ -77,7 +85,8 @@ describe('getYParity', () => { it('should handle mixed format inputs', () => { const messageHash = randomBytes(32); - const { signature, publicKey, recovery } = createValidSignature(messageHash); + const { signature, publicKey, recovery } = + createValidSignature(messageHash); const yParity = getYParity({ messageHash: messageHash.toString('hex'), // No 0x prefix @@ -129,7 +138,8 @@ describe('getYParity', () => { getMessageToSign: () => messageData, }; - const { signature, publicKey, recovery } = createValidSignature(messageData); + const { signature, publicKey, recovery } = + createValidSignature(messageData); const resp = { sig: signature, @@ -149,7 +159,9 @@ describe('getYParity', () => { const messageHash = new Uint8Array(32); messageHash.fill(42); - const { signature, publicKey, recovery } = createValidSignature(Buffer.from(messageHash)); + const { signature, publicKey, recovery } = createValidSignature( + Buffer.from(messageHash), + ); const resp = { sig: { @@ -172,7 +184,9 @@ describe('getYParity', () => { for (let i = 0; i < 32; i++) { hash[i] = Math.floor(Math.random() * 256); } - const { signature, publicKey, recovery } = createValidSignature(Buffer.from(hash)); + const { signature, publicKey, recovery } = createValidSignature( + Buffer.from(hash), + ); const resp = { sig: signature, @@ -186,7 +200,8 @@ describe('getYParity', () => { it('should hash non-32-byte inputs', () => { const shortData = randomBytes(20); const expectedHash = Buffer.from(Hash.keccak256(shortData)); - const { signature, publicKey, recovery } = createValidSignature(expectedHash); + const { signature, publicKey, recovery } = + createValidSignature(expectedHash); const resp = { sig: signature, @@ -201,19 +216,25 @@ describe('getYParity', () => { describe('Error handling', () => { it('should throw error if legacy format missing response', () => { const tx = randomBytes(32); - expect(() => getYParity(tx)).toThrow('Response with sig and pubkey required for legacy format'); + expect(() => getYParity(tx)).toThrow( + 'Response with sig and pubkey required for legacy format', + ); }); it('should throw error if response missing sig', () => { const tx = randomBytes(32); const resp = { pubkey: randomBytes(65) }; - expect(() => getYParity(tx, resp)).toThrow('Response with sig and pubkey required for legacy format'); + expect(() => getYParity(tx, resp)).toThrow( + 'Response with sig and pubkey required for legacy format', + ); }); it('should throw error if response missing pubkey', () => { const tx = randomBytes(32); const resp = { sig: { r: randomBytes(32), s: randomBytes(32) } }; - expect(() => getYParity(tx, resp)).toThrow('Response with sig and pubkey required for legacy format'); + expect(() => getYParity(tx, resp)).toThrow( + 'Response with sig and pubkey required for legacy format', + ); }); it('should throw error if recovery fails', () => { @@ -227,7 +248,9 @@ describe('getYParity', () => { signature, publicKey, }), - ).toThrow('Failed to recover Y parity. Bad signature or transaction data.'); + ).toThrow( + 'Failed to recover Y parity. Bad signature or transaction data.', + ); }); it('should throw error with invalid signature', () => { @@ -255,10 +278,14 @@ describe('getYParity', () => { const MAGIC = Buffer.from([0x05]); // This would normally use RLP.encode but we'll create a test message - const message = Buffer.concat([MAGIC, Buffer.from('test_rlp_encoded_data', 'utf8')]); + const message = Buffer.concat([ + MAGIC, + Buffer.from('test_rlp_encoded_data', 'utf8'), + ]); const messageHash = Buffer.from(Hash.keccak256(message)); - const { signature, publicKey, recovery } = createValidSignature(messageHash); + const { signature, publicKey, recovery } = + createValidSignature(messageHash); // Test both Buffer format (as returned by device) const yParity1 = getYParity({ @@ -305,8 +332,13 @@ describe('getYParity', () => { // Try multiple messages until we get one with y-parity 1 for (let i = 0; i < 100; i++) { - const messageHash = Buffer.from(Hash.keccak256(Buffer.from(`test message ${i}`))); - const privateKey = Buffer.from('0101010101010101010101010101010101010101010101010101010101010101', 'hex'); + const messageHash = Buffer.from( + Hash.keccak256(Buffer.from(`test message ${i}`)), + ); + const privateKey = Buffer.from( + '0101010101010101010101010101010101010101010101010101010101010101', + 'hex', + ); const sigObj = secp256k1.ecdsaSign(messageHash, privateKey); @@ -337,7 +369,12 @@ describe('getV function', () => { // Helper to create a valid signature const createValidSignature = (messageHash: Buffer, privateKey?: Buffer) => { // Use deterministic key if not provided - const privKey = privateKey || Buffer.from('0101010101010101010101010101010101010101010101010101010101010101', 'hex'); + const privKey = + privateKey || + Buffer.from( + '0101010101010101010101010101010101010101010101010101010101010101', + 'hex', + ); const sigObj = secp256k1.ecdsaSign(messageHash, privKey); const publicKey = secp256k1.publicKeyCreate(privKey, false); @@ -354,7 +391,10 @@ describe('getV function', () => { it('should handle unsigned legacy transaction with valid signature', () => { // A simple unsigned legacy transaction - const unsignedTxRLP = Buffer.from('e9808504a817c800825208943535353535353535353535353535353535353535880de0b6b3a764000080', 'hex'); + const unsignedTxRLP = Buffer.from( + 'e9808504a817c800825208943535353535353535353535353535353535353535880de0b6b3a764000080', + 'hex', + ); // Hash the transaction const hash = Buffer.from(Hash.keccak256(unsignedTxRLP)); @@ -374,8 +414,14 @@ describe('getV function', () => { const mockResp = { sig: { - r: Buffer.from('134f5038e0e6a96741e17a82c8df13e9dc10c3b0e9e956cf7dcf21e1e3b73f9f', 'hex'), - s: Buffer.from('638cf1b1f9dd5e6e8e6b9a8e6e8e6b9a8e6e8e6b9a8e6e8e6b9a8e6e8e6b9a8', 'hex'), + r: Buffer.from( + '134f5038e0e6a96741e17a82c8df13e9dc10c3b0e9e956cf7dcf21e1e3b73f9f', + 'hex', + ), + s: Buffer.from( + '638cf1b1f9dd5e6e8e6b9a8e6e8e6b9a8e6e8e6b9a8e6e8e6b9a8e6e8e6b9a8', + 'hex', + ), }, // This is a fake pubkey, so recovery will fail pubkey: Buffer.from(`04${'1'.repeat(128)}`, 'hex'), @@ -385,7 +431,8 @@ describe('getV function', () => { }); it('should throw error when signature is invalid', () => { - const txHex = '0xe9808504a817c800825208943535353535353535353535353535353535353535880de0b6b3a764000080'; + const txHex = + '0xe9808504a817c800825208943535353535353535353535353535353535353535880de0b6b3a764000080'; const mockResp = { sig: { diff --git a/packages/sdk/src/__test__/unit/validators.test.ts b/packages/sdk/src/__test__/unit/validators.test.ts index 8e93e938..a50978ed 100644 --- a/packages/sdk/src/__test__/unit/validators.test.ts +++ b/packages/sdk/src/__test__/unit/validators.test.ts @@ -1,7 +1,20 @@ import { normalizeToViemTransaction } from '../../ethereum'; -import { validateAddKvRequest, validateConnectRequest, validateGetAddressesRequest, validateGetKvRequest, validateRemoveKvRequest } from '../../functions'; -import { isValid4ByteResponse, isValidBlockExplorerResponse } from '../../shared/validators'; -import { buildGetAddressesObject, buildValidateConnectObject, buildValidateRequestObject } from '../utils/builders'; +import { + validateAddKvRequest, + validateConnectRequest, + validateGetAddressesRequest, + validateGetKvRequest, + validateRemoveKvRequest, +} from '../../functions'; +import { + isValid4ByteResponse, + isValidBlockExplorerResponse, +} from '../../shared/validators'; +import { + buildGetAddressesObject, + buildValidateConnectObject, + buildValidateRequestObject, +} from '../utils/builders'; describe('validators', () => { describe('connect', () => { @@ -29,7 +42,10 @@ describe('validators', () => { test('encodeGetAddressesRequest should throw with invalid startPath', () => { const startPath = [0x80000000 + 44, 0x80000000 + 60, 0, 0, 0, 0, 0]; const fwVersion = Buffer.from([0, 0, 0]); - const testEncodingFunction = () => validateGetAddressesRequest(buildGetAddressesObject({ startPath, fwVersion })); + const testEncodingFunction = () => + validateGetAddressesRequest( + buildGetAddressesObject({ startPath, fwVersion }), + ); expect(testEncodingFunction).toThrowError(); }); }); @@ -76,7 +92,9 @@ describe('validators', () => { test('should throw errors on validation failure', () => { const validateRemoveKvBundle: any = buildValidateRequestObject({}); - expect(() => validateRemoveKvRequest(validateRemoveKvBundle)).toThrowError(); + expect(() => + validateRemoveKvRequest(validateRemoveKvBundle), + ).toThrowError(); }); }); }); @@ -93,7 +111,8 @@ describe('validators', () => { test('should validate as false bad data', () => { const response: any = { - result: 'Max rate limit reached, please use API Key for higher rate limit', + result: + 'Max rate limit reached, please use API Key for higher rate limit', }; expect(isValidBlockExplorerResponse(response)).toBe(false); }); @@ -131,7 +150,9 @@ describe('validators', () => { to: `0x${'1'.repeat(40)}`, value: '1000000000000000000', chainId: 1, - authorizationList: [{ chainId: 1, address: `0x${'2'.repeat(40)}`, nonce: 0 }], + authorizationList: [ + { chainId: 1, address: `0x${'2'.repeat(40)}`, nonce: 0 }, + ], gasPrice: '15000000000', }; diff --git a/packages/sdk/src/__test__/utils/__test__/builders.test.ts b/packages/sdk/src/__test__/utils/__test__/builders.test.ts index 84b30911..4c618103 100644 --- a/packages/sdk/src/__test__/utils/__test__/builders.test.ts +++ b/packages/sdk/src/__test__/utils/__test__/builders.test.ts @@ -1,4 +1,8 @@ -import { buildEvmReq, buildRandomVectors, getFwVersionsList } from '../builders'; +import { + buildEvmReq, + buildRandomVectors, + getFwVersionsList, +} from '../builders'; describe('building', () => { test('should test client', () => { diff --git a/packages/sdk/src/__test__/utils/__test__/serializers.test.ts b/packages/sdk/src/__test__/utils/__test__/serializers.test.ts index b5f29727..d8a99eb4 100644 --- a/packages/sdk/src/__test__/utils/__test__/serializers.test.ts +++ b/packages/sdk/src/__test__/utils/__test__/serializers.test.ts @@ -1,4 +1,7 @@ -import { deserializeObjectWithBuffers, serializeObjectWithBuffers } from '../serializers'; +import { + deserializeObjectWithBuffers, + serializeObjectWithBuffers, +} from '../serializers'; describe('serializers', () => { test('serialize obj', () => { diff --git a/packages/sdk/src/__test__/utils/builders.ts b/packages/sdk/src/__test__/utils/builders.ts index 46fad59f..0e3b8a50 100644 --- a/packages/sdk/src/__test__/utils/builders.ts +++ b/packages/sdk/src/__test__/utils/builders.ts @@ -4,13 +4,22 @@ import { type TypedTransaction, createTx } from '@ethereumjs/tx'; import { generate as randomWords } from 'random-words'; import { Constants } from '../..'; import { Client } from '../../client'; -import { CURRENCIES, HARDENED_OFFSET, getFwVersionConst } from '../../constants'; +import { + CURRENCIES, + HARDENED_OFFSET, + getFwVersionConst, +} from '../../constants'; import type { Currency, SignRequestParams, SigningPath } from '../../types'; import type { FirmwareConstants } from '../../types/firmware'; import { randomBytes } from '../../util'; import { MSG_PAYLOAD_METADATA_SZ } from './constants'; import { getN, getPrng } from './getters'; -import { BTC_PURPOSE_P2PKH, ETH_COIN, buildRandomEip712Object, getTestVectors } from './helpers'; +import { + BTC_PURPOSE_P2PKH, + ETH_COIN, + buildRandomEip712Object, + getTestVectors, +} from './helpers'; const prng = getPrng(); @@ -66,7 +75,10 @@ export const buildFirmwareConstants = (...overrides: any) => { }; export const buildWallet = (overrides?) => ({ - uid: Buffer.from('162b56efe561c12bc93f703dc7026b3ec3d53923270c9259e2b08015fb9defd2', 'hex'), + uid: Buffer.from( + '162b56efe561c12bc93f703dc7026b3ec3d53923270c9259e2b08015fb9defd2', + 'hex', + ), capabilities: 1, external: true, ...overrides, @@ -101,10 +113,14 @@ export const buildSignObject = (fwVersion, overrides?) => { }; export const buildSharedSecret = () => { - return Buffer.from([89, 60, 130, 80, 168, 252, 34, 136, 230, 71, 230, 158, 51, 13, 239, 237, 6, 246, 71, 232, 232, 175, 193, 106, 106, 185, 38, 1, 163, 14, 225, 101]); + return Buffer.from([ + 89, 60, 130, 80, 168, 252, 34, 136, 230, 71, 230, 158, 51, 13, 239, 237, 6, + 246, 71, 232, 232, 175, 193, 106, 106, 185, 38, 1, 163, 14, 225, 101, + ]); }; -export const getNumIter = (n: number | string | undefined = getN()) => (n ? Number.parseInt(`${n}`) : 5); +export const getNumIter = (n: number | string | undefined = getN()) => + n ? Number.parseInt(`${n}`) : 5; /** Generate a bunch of random test vectors using the PRNG */ export const buildRandomVectors = (n: number | string | undefined = getN()) => { @@ -118,7 +134,13 @@ export const buildRandomVectors = (n: number | string | undefined = getN()) => { return RANDOM_VEC; }; -export const DEFAULT_SIGNER = [BTC_PURPOSE_P2PKH, ETH_COIN, HARDENED_OFFSET, 0, 0]; +export const DEFAULT_SIGNER = [ + BTC_PURPOSE_P2PKH, + ETH_COIN, + HARDENED_OFFSET, + 0, + 0, +]; export const buildTx = (data: `0x${string}` = '0xdeadbeef') => { return createTx( @@ -141,7 +163,10 @@ export const buildTx = (data: `0x${string}` = '0xdeadbeef') => { ); }; -export const buildEthSignRequest = async (client: Client, txDataOverrides?: any): Promise => { +export const buildEthSignRequest = async ( + client: Client, + txDataOverrides?: any, +): Promise => { if (client.getFwVersion()?.major === 0 && client.getFwVersion()?.minor < 15) { console.warn('Please update firmware. Skipping ETH signing tests.'); return; @@ -174,7 +199,9 @@ export const buildEthSignRequest = async (client: Client, txDataOverrides?: any) encodingType: Constants.SIGNING.ENCODINGS.EVM, }, }; - const maxDataSz = fwConstants.ethMaxDataSz + fwConstants.extraDataMaxFrames * fwConstants.extraDataFrameSz; + const maxDataSz = + fwConstants.ethMaxDataSz + + fwConstants.extraDataMaxFrames * fwConstants.extraDataFrameSz; return { fwConstants, signerPath, @@ -196,7 +223,10 @@ export const buildTxReq = (tx: TypedTransaction) => ({ }, }); -export const buildMsgReq = (payload = 'hello ethereum', protocol: 'signPersonal' | 'eip712' = 'signPersonal') => ({ +export const buildMsgReq = ( + payload = 'hello ethereum', + protocol: 'signPersonal' | 'eip712' = 'signPersonal', +) => ({ currency: 'ETH_MSG' as const, data: { signerPath: DEFAULT_SIGNER, @@ -250,10 +280,12 @@ export const buildEncDefs = (vectors: any) => { }); // The calldata is already in hex format, we just need to ensure it has 0x prefix - const encDefsCalldata = vectors.canonicalNames.map((_: string, idx: number) => { - const calldata = `0x${idx.toString(16).padStart(8, '0')}`; - return calldata; - }); + const encDefsCalldata = vectors.canonicalNames.map( + (_: string, idx: number) => { + const calldata = `0x${idx.toString(16).padStart(8, '0')}`; + return calldata; + }, + ); return { encDefs, encDefsCalldata }; }; @@ -276,7 +308,17 @@ export function buildRandomMsg(type, client: Client) { } } -export function buildEthMsgReq(payload: any, protocol: 'signPersonal' | 'eip712', signerPath = [BTC_PURPOSE_P2PKH, ETH_COIN, HARDENED_OFFSET, 0, 0] as SigningPath): SignRequestParams { +export function buildEthMsgReq( + payload: any, + protocol: 'signPersonal' | 'eip712', + signerPath = [ + BTC_PURPOSE_P2PKH, + ETH_COIN, + HARDENED_OFFSET, + 0, + 0, + ] as SigningPath, +): SignRequestParams { return { currency: CURRENCIES.ETH_MSG, data: { diff --git a/packages/sdk/src/__test__/utils/determinism.ts b/packages/sdk/src/__test__/utils/determinism.ts index 516c0ec7..236de499 100644 --- a/packages/sdk/src/__test__/utils/determinism.ts +++ b/packages/sdk/src/__test__/utils/determinism.ts @@ -10,7 +10,11 @@ import type { SigningPath } from '../../types'; import { ethPersonalSignMsg, getSigStr } from './helpers'; import { TEST_SEED } from './testConstants'; -export async function testUniformSigs(payload: any, tx: TypedTransaction, client: Client) { +export async function testUniformSigs( + payload: any, + tx: TypedTransaction, + client: Client, +) { const tx1Resp = await client.sign(payload); const tx2Resp = await client.sign(payload); const tx3Resp = await client.sign(payload); diff --git a/packages/sdk/src/__test__/utils/ethers.ts b/packages/sdk/src/__test__/utils/ethers.ts index f2571eb1..a0dbf54f 100644 --- a/packages/sdk/src/__test__/utils/ethers.ts +++ b/packages/sdk/src/__test__/utils/ethers.ts @@ -1,4 +1,13 @@ -const EVM_TYPES = [null, 'address', 'bool', 'uint', 'int', 'bytes', 'string', 'tuple']; +const EVM_TYPES = [ + null, + 'address', + 'bool', + 'uint', + 'int', + 'bytes', + 'string', + 'tuple', +]; export function convertDecoderToEthers(def: unknown[]) { const converted = getConvertedDef(def); @@ -68,25 +77,35 @@ function genTupleData(tupleParam: unknown[]) { const nestedData: unknown[] = []; tupleParam.forEach((nestedParam: unknown) => { const np = nestedParam as { toString: (fmt: string) => string }[]; - nestedData.push(genData(EVM_TYPES[Number.parseInt(np[1].toString('hex'), 16)] ?? '', np)); + nestedData.push( + genData(EVM_TYPES[Number.parseInt(np[1].toString('hex'), 16)] ?? '', np), + ); }); return nestedData; } function genParamData(param: { toString: (fmt: string) => string }[]) { - const evmType = EVM_TYPES[Number.parseInt(param[1].toString('hex'), 16)] ?? ''; + const evmType = + EVM_TYPES[Number.parseInt(param[1].toString('hex'), 16)] ?? ''; const baseData = genData(evmType, param); return getArrayData(param, baseData); } -function getArrayData(param: { toString: (fmt: string) => string }[], baseData: unknown) { +function getArrayData( + param: { toString: (fmt: string) => string }[], + baseData: unknown, +) { let arrayData: unknown[] | undefined; let data: unknown; const arrSzs = param[3] as unknown as { toString: (fmt: string) => string }[]; for (let i = 0; i < arrSzs.length; i++) { // let sz = parseInt(arrSzs[i].toString('hex')); TODO: fix this const dimData: unknown[] = []; - let sz = Number.parseInt((param[3] as unknown as { toString: (fmt: string) => string }[])[i].toString('hex')); + let sz = Number.parseInt( + (param[3] as unknown as { toString: (fmt: string) => string }[])[ + i + ].toString('hex'), + ); if (Number.isNaN(sz)) { sz = 2; //1; } diff --git a/packages/sdk/src/__test__/utils/helpers.ts b/packages/sdk/src/__test__/utils/helpers.ts index fe2d188b..75189c15 100644 --- a/packages/sdk/src/__test__/utils/helpers.ts +++ b/packages/sdk/src/__test__/utils/helpers.ts @@ -5,7 +5,10 @@ import { wordlists } from 'bip39'; import bitcoin, { type Payment } from 'bitcoinjs-lib'; import BN from 'bn.js'; import { ECPairFactory } from 'ecpair'; -import { derivePath as deriveEDKey, getPublicKey as getEDPubkey } from 'ed25519-hd-key'; +import { + derivePath as deriveEDKey, + getPublicKey as getEDPubkey, +} from 'ed25519-hd-key'; import { ec as EC } from 'elliptic'; import { privateToAddress } from 'ethereumjs-util'; import { jsonc } from 'jsonc'; @@ -15,10 +18,20 @@ import * as ecc from 'tiny-secp256k1'; import nacl from 'tweetnacl'; import { Constants } from '../..'; import { Client } from '../../client'; -import { BIP_CONSTANTS, HARDENED_OFFSET, ethMsgProtocol } from '../../constants'; +import { + BIP_CONSTANTS, + HARDENED_OFFSET, + ethMsgProtocol, +} from '../../constants'; import { ProtocolConstants } from '../../protocol'; import { getPathStr } from '../../shared/utilities'; -import { ensureHexBuffer, getV, getYParity, parseDER, randomBytes } from '../../util'; +import { + ensureHexBuffer, + getV, + getYParity, + parseDER, + randomBytes, +} from '../../util'; import { getEnv } from './getters'; import { setStoredClient } from './setup'; @@ -94,14 +107,19 @@ export const getSignatureVBN = (tx: any, resp: any): BN => { // For p2pkh-derived addresses, we use the legacy 44' purpose // For p2wpkh-derived addresse (not yet supported) we will use 84' export const BTC_PURPOSE_P2WPKH = BIP_CONSTANTS.PURPOSES.BTC_SEGWIT; -export const BTC_PURPOSE_P2SH_P2WPKH = BIP_CONSTANTS.PURPOSES.BTC_WRAPPED_SEGWIT; +export const BTC_PURPOSE_P2SH_P2WPKH = + BIP_CONSTANTS.PURPOSES.BTC_WRAPPED_SEGWIT; export const BTC_PURPOSE_P2PKH = BIP_CONSTANTS.PURPOSES.BTC_LEGACY; export const BTC_COIN = BIP_CONSTANTS.COINS.BTC; export const BTC_TESTNET_COIN = BIP_CONSTANTS.COINS.BTC_TESTNET; export const ETH_COIN = BIP_CONSTANTS.COINS.ETH; -export const REUSABLE_KEY = '3fb53b677f73e4d2b8c89c303f6f6b349f0075ad88ea126cb9f6632085815dca'; +export const REUSABLE_KEY = + '3fb53b677f73e4d2b8c89c303f6f6b349f0075ad88ea126cb9f6632085815dca'; -export function setupTestClient(env = getEnv() as any, stateData?: any): Client { +export function setupTestClient( + env = getEnv() as any, + stateData?: any, +): Client { if (stateData) { return new Client({ stateData }); } @@ -166,7 +184,15 @@ export function _get_btc_addr(pubkey, purpose, network) { return obj.address; } -export function _start_tx_builder(wallet, recipient, value, fee, inputs, network, purpose) { +export function _start_tx_builder( + wallet, + recipient, + value, + fee, + inputs, + network, + purpose, +) { const tx = new bitcoin.Transaction(); // Match serialization logic (version 2) used by device and serializer tx.version = 2; @@ -178,7 +204,9 @@ export function _start_tx_builder(wallet, recipient, value, fee, inputs, network const networkIdx = network === bitcoin.networks.testnet ? 1 : 0; const path = buildPath([purpose, harden(networkIdx), harden(0), 1, 0]); const btc_0_change = wallet.derivePath(path); - const btc_0_change_pub = ECPair.fromPublicKey(btc_0_change.publicKey).publicKey; + const btc_0_change_pub = ECPair.fromPublicKey( + btc_0_change.publicKey, + ).publicKey; const changeAddr = _get_btc_addr(btc_0_change_pub, purpose, network); const changeScript = bitcoin.address.toOutputScript(changeAddr, network); tx.addOutput(changeScript, changeValue); @@ -189,7 +217,8 @@ export function _start_tx_builder(wallet, recipient, value, fee, inputs, network inputs.forEach((input) => { const hashLE = Buffer.from(input.hash, 'hex').reverse(); tx.addInput(hashLE, input.idx); - const coin = network === bitcoin.networks.testnet ? BTC_TESTNET_COIN : BTC_COIN; + const coin = + network === bitcoin.networks.testnet ? BTC_TESTNET_COIN : BTC_COIN; const path = buildPath([purpose, coin, harden(0), 0, input.signerIdx]); const keyPair = wallet.derivePath(path); const pubkeyBuf = Buffer.from(keyPair.publicKey); @@ -208,25 +237,68 @@ function _build_sighashes(txb_or_tx, purpose) { const isLegacy = purpose === BTC_PURPOSE_P2PKH; if (txb.inputsMeta) { txb.inputsMeta.forEach((meta, i) => { - hashes.push(isLegacy ? txb.tx.hashForSignature(i, meta.scriptCode, SIGHASH_ALL) : txb.tx.hashForWitnessV0(i, meta.scriptCode, meta.value, SIGHASH_ALL)); + hashes.push( + isLegacy + ? txb.tx.hashForSignature(i, meta.scriptCode, SIGHASH_ALL) + : txb.tx.hashForWitnessV0( + i, + meta.scriptCode, + meta.value, + SIGHASH_ALL, + ), + ); }); } else { // Fallback for prior structure (should not be used) txb.__inputs.forEach((input, i) => { - hashes.push(isLegacy ? txb.__tx.hashForSignature(i, input.signScript, SIGHASH_ALL) : txb.__tx.hashForWitnessV0(i, input.signScript, input.value, SIGHASH_ALL)); + hashes.push( + isLegacy + ? txb.__tx.hashForSignature(i, input.signScript, SIGHASH_ALL) + : txb.__tx.hashForWitnessV0( + i, + input.signScript, + input.value, + SIGHASH_ALL, + ), + ); }); } return hashes; } -function _get_reference_sighashes(wallet, recipient, value, fee, inputs, isTestnet, purpose) { - const network = isTestnet ? bitcoin.networks.testnet : bitcoin.networks.bitcoin; - const built = _start_tx_builder(wallet, recipient, value, fee, inputs, network, purpose); +function _get_reference_sighashes( + wallet, + recipient, + value, + fee, + inputs, + isTestnet, + purpose, +) { + const network = isTestnet + ? bitcoin.networks.testnet + : bitcoin.networks.bitcoin; + const built = _start_tx_builder( + wallet, + recipient, + value, + fee, + inputs, + network, + purpose, + ); // built has shape { tx, inputsMeta } return _build_sighashes(built, purpose); } -function _btc_tx_request_builder(inputs, recipient, value, fee, isTestnet, purpose) { +function _btc_tx_request_builder( + inputs, + recipient, + value, + fee, + isTestnet, + purpose, +) { const currencyIdx = isTestnet ? BTC_TESTNET_COIN : BTC_COIN; const txData = { prevOuts: [] as any[], @@ -264,7 +336,13 @@ export function stripDER(derSig) { function _get_signing_keys(wallet, inputs, isTestnet, purpose) { const currencyIdx = isTestnet ? 1 : 0; return inputs.map((input) => { - const path = buildPath([purpose, harden(currencyIdx), harden(0), 0, input.signerIdx]); + const path = buildPath([ + purpose, + harden(currencyIdx), + harden(0), + 0, + input.signerIdx, + ]); const node = wallet.derivePath(path); const priv = Buffer.from(node.privateKey); const key = secp256k1.keyFromPrivate(priv); @@ -287,7 +365,8 @@ function _generate_btc_address(isTestnet, purpose, rand) { priv.writeUInt32BE(Math.floor(rand.quick() * 2 ** 32), j * 4); } const keyPair = ECPair.fromPrivateKey(priv); - const network = isTestnet === true ? bitcoin.networks.testnet : bitcoin.networks.bitcoin; + const network = + isTestnet === true ? bitcoin.networks.testnet : bitcoin.networks.bitcoin; return _get_btc_addr(keyPair.publicKey, purpose, network); } @@ -296,11 +375,32 @@ export function setup_btc_sig_test(opts, wallet, inputs, rand) { const recipient = _generate_btc_address(isTestnet, recipientPurpose, rand); const sumInputs = _getSumInputs(inputs); const fee = Math.floor(rand.quick() * 50000); - const _value = useChange === true ? Math.floor(rand.quick() * sumInputs) : sumInputs; + const _value = + useChange === true ? Math.floor(rand.quick() * sumInputs) : sumInputs; const value = _value - fee; - const sigHashes = _get_reference_sighashes(wallet, recipient, value, fee, inputs, isTestnet, spenderPurpose); - const signingKeys = _get_signing_keys(wallet, inputs, isTestnet, spenderPurpose); - const txReq = _btc_tx_request_builder(inputs, recipient, value, fee, isTestnet, spenderPurpose); + const sigHashes = _get_reference_sighashes( + wallet, + recipient, + value, + fee, + inputs, + isTestnet, + spenderPurpose, + ); + const signingKeys = _get_signing_keys( + wallet, + inputs, + isTestnet, + spenderPurpose, + ); + const txReq = _btc_tx_request_builder( + inputs, + recipient, + value, + fee, + isTestnet, + spenderPurpose, + ); return { sigHashes, signingKeys, @@ -418,7 +518,9 @@ export const copyBuffer = (x) => { // Convert a set of indices to a human readable bip32 path export const stringifyPath = (parent) => { const convert = (parent) => { - return parent >= HARDENED_OFFSET ? `${parent - HARDENED_OFFSET}'` : `${parent}`; + return parent >= HARDENED_OFFSET + ? `${parent - HARDENED_OFFSET}'` + : `${parent}`; }; if (parent.idx) { // BIP32 style encoding @@ -505,7 +607,8 @@ export const validateBTCAddresses = (resp, jobData, seed, useTestnet?) => { expect(resp.count).toEqual(jobData.count); const wallet = bip32.fromSeed(seed); const path = JSON.parse(JSON.stringify(jobData.path)); - const network = useTestnet === true ? bitcoin.networks.testnet : bitcoin.networks.bitcoin; + const network = + useTestnet === true ? bitcoin.networks.testnet : bitcoin.networks.bitcoin; for (let i = 0; i < jobData.count; i++) { path.idx[jobData.iterIdx] = jobData.path.idx[jobData.iterIdx] + i; // Validate the address @@ -554,7 +657,12 @@ export const validateETHAddresses = (resp, jobData, seed) => { } }; -export const validateDerivedPublicKeys = (pubKeys, firstPath, seed, flag?: number) => { +export const validateDerivedPublicKeys = ( + pubKeys, + firstPath, + seed, + flag?: number, +) => { const wallet = bip32.fromSeed(seed); // We assume the keys were derived in sequential order pubKeys.forEach((pub, i) => { @@ -563,16 +671,23 @@ export const validateDerivedPublicKeys = (pubKeys, firstPath, seed, flag?: numbe if (flag === Constants.GET_ADDR_FLAGS.ED25519_PUB) { // ED25519 requires its own derivation const key = deriveED25519Key(path, seed); - expect(pub.toString('hex')).toEqualElseLog(key.pub.toString('hex'), 'Exported ED25519 pubkey incorrect'); + expect(pub.toString('hex')).toEqualElseLog( + key.pub.toString('hex'), + 'Exported ED25519 pubkey incorrect', + ); } else { // Otherwise this is a SECP256K1 pubkey const priv = wallet.derivePath(getPathStr(path)).privateKey; - expect(pub.toString('hex')).toEqualElseLog(secp256k1.keyFromPrivate(priv).getPublic().encode('hex', false), 'Exported SECP256K1 pubkey incorrect'); + expect(pub.toString('hex')).toEqualElseLog( + secp256k1.keyFromPrivate(priv).getPublic().encode('hex', false), + 'Exported SECP256K1 pubkey incorrect', + ); } }); }; -export const ethPersonalSignMsg = (msg) => `\u0019Ethereum Signed Message:\n${String(msg.length)}${msg}`; +export const ethPersonalSignMsg = (msg) => + `\u0019Ethereum Signed Message:\n${String(msg.length)}${msg}`; //--------------------------------------------------- // Sign Transaction helpers @@ -647,12 +762,18 @@ export const deserializeSignTxJobResult = (res: any) => { _off += 4; o.signerPath.addr = _o.readUInt32LE(_off); _off += 4; - o.pubkey = secp256k1.keyFromPublic(_o.slice(_off, _off + 65).toString('hex'), 'hex'); + o.pubkey = secp256k1.keyFromPublic( + _o.slice(_off, _off + 65).toString('hex'), + 'hex', + ); _off += PK_LEN; // We get back a DER signature in 74 bytes, but not all the bytes are necessarily // used. The second byte contains the DER sig length, so we need to use that. const derLen = _o[_off + 1]; - o.sig = Buffer.from(_o.slice(_off, _off + 2 + derLen).toString('hex'), 'hex'); + o.sig = Buffer.from( + _o.slice(_off, _off + 2 + derLen).toString('hex'), + 'hex', + ); getTxResult.outputs.push(o); } @@ -734,11 +855,14 @@ export const buildRandomEip712Object = (randInt) => { } function getRandomName(upperCase = false, sz = 20) { const name = randStr(sz); - if (upperCase === true) return `${name.slice(0, 1).toUpperCase()}${name.slice(1)}`; + if (upperCase === true) + return `${name.slice(0, 1).toUpperCase()}${name.slice(1)}`; return name; } function getRandomEIP712Type(customTypes: any[] = []) { - const types = Object.keys(customTypes).concat(Object.keys(ethMsgProtocol.TYPED_DATA.typeCodes)); + const types = Object.keys(customTypes).concat( + Object.keys(ethMsgProtocol.TYPED_DATA.typeCodes), + ); return { name: getRandomName(), type: types[randInt(types.length)], @@ -877,15 +1001,25 @@ export const validateGenericSig = (seed, sig, payloadBuf, req, pubkey?) => { r: normalizeSigComponent(sig.r).toString('hex'), s: normalizeSigComponent(sig.s).toString('hex'), }; - expect(key.verify(hash, normalizedSig)).toEqualElseLog(true, 'Signature failed verification.'); + expect(key.verify(hash, normalizedSig)).toEqualElseLog( + true, + 'Signature failed verification.', + ); } else if (curveType === CURVES.ED25519) { if (hashType !== HASHES.NONE) { throw new Error('Bad params'); } const { pub } = deriveED25519Key(signerPath, seed); - const signature = Buffer.concat([normalizeSigComponent(sig.r), normalizeSigComponent(sig.s)]); + const signature = Buffer.concat([ + normalizeSigComponent(sig.r), + normalizeSigComponent(sig.s), + ]); const edPublicKey = pubkey ? normalizeSigComponent(pubkey) : pub; - const isValid = nacl.sign.detached.verify(new Uint8Array(payloadBuf), new Uint8Array(signature), new Uint8Array(edPublicKey)); + const isValid = nacl.sign.detached.verify( + new Uint8Array(payloadBuf), + new Uint8Array(signature), + new Uint8Array(edPublicKey), + ); expect(isValid).toEqualElseLog(true, 'Signature failed verification.'); } else { throw new Error('Bad params'); @@ -935,7 +1069,9 @@ export function toBuffer(data: string | number | Buffer | Uint8Array): Buffer { } if (typeof data === 'string') { const trimmed = data.trim(); - const isHex = trimmed.startsWith('0x') || (/^[0-9a-fA-F]+$/.test(trimmed) && trimmed.length % 2 === 0); + const isHex = + trimmed.startsWith('0x') || + (/^[0-9a-fA-F]+$/.test(trimmed) && trimmed.length % 2 === 0); return isHex ? ensureHexBuffer(trimmed) : Buffer.from(trimmed, 'utf8'); } throw new Error('Unsupported data type'); @@ -945,16 +1081,24 @@ export function toUint8Array(data: Buffer): Uint8Array { return new Uint8Array(data.buffer, data.byteOffset, data.length); } -export function ensureHash32(message: string | number | Buffer | Uint8Array): Uint8Array { +export function ensureHash32( + message: string | number | Buffer | Uint8Array, +): Uint8Array { const msgBuffer = toBuffer(message); - const digest = msgBuffer.length === 32 ? msgBuffer : Buffer.from(Hash.keccak256(msgBuffer)); + const digest = + msgBuffer.length === 32 + ? msgBuffer + : Buffer.from(Hash.keccak256(msgBuffer)); if (digest.length !== 32) { throw new Error('Failed to derive 32-byte hash for signature validation.'); } return toUint8Array(digest); } -export function validateSig(resp: any, message: string | number | Buffer | Uint8Array) { +export function validateSig( + resp: any, + message: string | number | Buffer | Uint8Array, +) { if (!resp.sig?.r || !resp.sig?.s || !resp.pubkey) { throw new Error('Missing signature components'); } @@ -964,11 +1108,20 @@ export function validateSig(resp: any, message: string | number | Buffer | Uint8 const hash = ensureHash32(message); const pubkeyInput = toBuffer(resp.pubkey); - const normalizedPubkey = pubkeyInput.length === 64 ? Buffer.concat([Buffer.from([0x04]), pubkeyInput]) : pubkeyInput; - const isCompressed = normalizedPubkey.length === 33 && (normalizedPubkey[0] === 0x02 || normalizedPubkey[0] === 0x03); + const normalizedPubkey = + pubkeyInput.length === 64 + ? Buffer.concat([Buffer.from([0x04]), pubkeyInput]) + : pubkeyInput; + const isCompressed = + normalizedPubkey.length === 33 && + (normalizedPubkey[0] === 0x02 || normalizedPubkey[0] === 0x03); - const recoveredA = Buffer.from(ecdsaRecover(rs, 0, hash, isCompressed)).toString('hex'); - const recoveredB = Buffer.from(ecdsaRecover(rs, 1, hash, isCompressed)).toString('hex'); + const recoveredA = Buffer.from( + ecdsaRecover(rs, 0, hash, isCompressed), + ).toString('hex'); + const recoveredB = Buffer.from( + ecdsaRecover(rs, 1, hash, isCompressed), + ).toString('hex'); const expected = normalizedPubkey.toString('hex'); if (expected !== recoveredA && expected !== recoveredB) { throw new Error('Signature did not validate.'); @@ -992,10 +1145,15 @@ export const compressPubKey = (pub) => { function _stripTrailingCommas(input: string): string { // Use non-backtracking pattern to avoid ReDoS vulnerability // Unrolled loop for multi-line comments: \/\*[^*]*\*+(?:[^/*][^*]*\*+)*\/ - return input.replace(/,([\s]*(?:\/\/[^\n]*\n[\s]*|\/\*[^*]*\*+(?:[^/*][^*]*\*+)*\/[\s]*)*)([}\]])/g, '$1$2'); + return input.replace( + /,([\s]*(?:\/\/[^\n]*\n[\s]*|\/\*[^*]*\*+(?:[^/*][^*]*\*+)*\/[\s]*)*)([}\]])/g, + '$1$2', + ); } export const getTestVectors = () => { - const raw = readFileSync(`${process.cwd()}/src/__test__/vectors.jsonc`).toString(); + const raw = readFileSync( + `${process.cwd()}/src/__test__/vectors.jsonc`, + ).toString(); return jsonc.parse(_stripTrailingCommas(raw)); }; diff --git a/packages/sdk/src/__test__/utils/runners.ts b/packages/sdk/src/__test__/utils/runners.ts index 86d8d477..1302e5a7 100644 --- a/packages/sdk/src/__test__/utils/runners.ts +++ b/packages/sdk/src/__test__/utils/runners.ts @@ -1,11 +1,18 @@ import type { Client } from '../../client'; import { getEncodedPayload } from '../../genericSigning'; -import type { SigningPayload, SignRequestParams, TestRequestPayload } from '../../types'; +import type { + SigningPayload, + SignRequestParams, + TestRequestPayload, +} from '../../types'; import { parseWalletJobResp, validateGenericSig } from './helpers'; import { TEST_SEED } from './testConstants'; import { testRequest } from './testRequest'; -export async function runTestCase(payload: TestRequestPayload, expectedCode: number) { +export async function runTestCase( + payload: TestRequestPayload, + expectedCode: number, +) { const res = await testRequest(payload); //@ts-expect-error - Accessing private property const fwVersion = payload.client.fwVersion; @@ -21,7 +28,11 @@ export async function runGeneric(request: SignRequestParams, client: Client) { // If no encoding type is specified we encode in hex or ascii const encodingType = data.encodingType || null; const allowedEncodings = client.getFwConstants().genericSigning.encodingTypes; - const { payloadBuf } = getEncodedPayload(data.payload, encodingType, allowedEncodings); + const { payloadBuf } = getEncodedPayload( + data.payload, + encodingType, + allowedEncodings, + ); const seed = TEST_SEED; validateGenericSig(seed, response.sig, payloadBuf, data, response.pubkey); return response; diff --git a/packages/sdk/src/__test__/utils/setup.ts b/packages/sdk/src/__test__/utils/setup.ts index 59b09bb4..4cadb90f 100644 --- a/packages/sdk/src/__test__/utils/setup.ts +++ b/packages/sdk/src/__test__/utils/setup.ts @@ -41,7 +41,9 @@ export async function setupClient() { if (!isPaired) { if (!pairingSecret) { if (process.env.CI) { - throw new Error('Pairing secret is required. If simulator is running, set PAIRING_SECRET environment variable.'); + throw new Error( + 'Pairing secret is required. If simulator is running, set PAIRING_SECRET environment variable.', + ); } pairingSecret = question('Enter pairing secret:'); if (!pairingSecret) { diff --git a/packages/sdk/src/__test__/utils/testConstants.ts b/packages/sdk/src/__test__/utils/testConstants.ts index 41dbd8b5..9c996f73 100644 --- a/packages/sdk/src/__test__/utils/testConstants.ts +++ b/packages/sdk/src/__test__/utils/testConstants.ts @@ -12,7 +12,8 @@ import { mnemonicToSeedSync } from 'bip39'; * This mnemonic is used across multiple test files to ensure consistent * test behavior and deterministic results. */ -export const TEST_MNEMONIC = 'test test test test test test test test test test test junk'; +export const TEST_MNEMONIC = + 'test test test test test test test test test test test junk'; /** * Shared seed derived from TEST_MNEMONIC diff --git a/packages/sdk/src/__test__/utils/testEnvironment.ts b/packages/sdk/src/__test__/utils/testEnvironment.ts index 368518b2..8f23e7c5 100644 --- a/packages/sdk/src/__test__/utils/testEnvironment.ts +++ b/packages/sdk/src/__test__/utils/testEnvironment.ts @@ -6,7 +6,8 @@ expect.extend({ toEqualElseLog(received: unknown, expected: unknown, message?: string) { return { pass: received === expected, - message: () => message ?? `Expected ${String(received)} to equal ${String(expected)}`, + message: () => + message ?? `Expected ${String(received)} to equal ${String(expected)}`, }; }, }); diff --git a/packages/sdk/src/__test__/utils/testRequest.ts b/packages/sdk/src/__test__/utils/testRequest.ts index 0f00be28..8332e70c 100644 --- a/packages/sdk/src/__test__/utils/testRequest.ts +++ b/packages/sdk/src/__test__/utils/testRequest.ts @@ -1,13 +1,22 @@ -import { LatticeSecureEncryptedRequestType, encryptedSecureRequest } from '../../protocol'; +import { + LatticeSecureEncryptedRequestType, + encryptedSecureRequest, +} from '../../protocol'; import type { TestRequestPayload } from '../../types'; /** * `test` takes a data object with a testID and a payload, and sends them to the device. * @category Lattice */ -export const testRequest = async ({ payload, testID, client }: TestRequestPayload) => { +export const testRequest = async ({ + payload, + testID, + client, +}: TestRequestPayload) => { if (!payload) { - throw new Error('First argument must contain `testID` and `payload` fields.'); + throw new Error( + 'First argument must contain `testID` and `payload` fields.', + ); } const sharedSecret = client.sharedSecret; const ephemeralPub = client.ephemeralPub; diff --git a/packages/sdk/src/__test__/utils/viemComparison.ts b/packages/sdk/src/__test__/utils/viemComparison.ts index 0c092e3f..4f5fcab8 100644 --- a/packages/sdk/src/__test__/utils/viemComparison.ts +++ b/packages/sdk/src/__test__/utils/viemComparison.ts @@ -1,4 +1,11 @@ -import { type Address, type Hex, type TransactionSerializable, type TypedDataDefinition, parseTransaction, serializeTransaction } from 'viem'; +import { + type Address, + type Hex, + type TransactionSerializable, + type TypedDataDefinition, + parseTransaction, + serializeTransaction, +} from 'viem'; import { privateKeyToAccount } from 'viem/accounts'; import { sign, signMessage } from '../../api'; import { normalizeLatticeSignature } from '../../ethereum'; @@ -28,7 +35,10 @@ export const getFoundryAccount = () => { export type TestTransaction = TransactionSerializable; // Sign transaction with both Lattice and viem, then compare -export const signAndCompareTransaction = async (tx: TestTransaction, testName: string) => { +export const signAndCompareTransaction = async ( + tx: TestTransaction, + testName: string, +) => { const foundryAccount = getFoundryAccount(); try { @@ -92,14 +102,18 @@ export const signAndCompareTransaction = async (tx: TestTransaction, testName: s // Additional verification: compare signature components // Lattice returns r,s as hex strings with 0x prefix or as Buffer const normalizeSigComponent = (value: string | Buffer) => { - const hexString = typeof value === 'string' ? value : `0x${Buffer.from(value).toString('hex')}`; + const hexString = + typeof value === 'string' + ? value + : `0x${Buffer.from(value).toString('hex')}`; const stripped = hexString.replace(/^0x/, '').toLowerCase(); return `0x${stripped.padStart(64, '0')}`; }; const latticeR = normalizeSigComponent(latticeResult.sig.r); const latticeS = normalizeSigComponent(latticeResult.sig.s); - if (!parsedViemTx.r || !parsedViemTx.s) throw new Error('Missing signature components'); + if (!parsedViemTx.r || !parsedViemTx.s) + throw new Error('Missing signature components'); const viemR = normalizeSigComponent(parsedViemTx.r); const viemS = normalizeSigComponent(parsedViemTx.s); @@ -128,7 +142,10 @@ export type EIP712TestMessage = { }; // Sign EIP-712 typed data with both Lattice and viem, then compare -export const signAndCompareEIP712Message = async (eip712Message: EIP712TestMessage, testName: string) => { +export const signAndCompareEIP712Message = async ( + eip712Message: EIP712TestMessage, + testName: string, +) => { const foundryAccount = getFoundryAccount(); try { diff --git a/packages/sdk/src/api/addressTags.ts b/packages/sdk/src/api/addressTags.ts index cb7b5ad7..4e4ebcf5 100644 --- a/packages/sdk/src/api/addressTags.ts +++ b/packages/sdk/src/api/addressTags.ts @@ -6,7 +6,9 @@ import { queue } from './utilities'; /** * Sends request to the Lattice to add Address Tags. */ -export const addAddressTags = async (tags: [{ [key: string]: string }]): Promise => { +export const addAddressTags = async ( + tags: [{ [key: string]: string }], +): Promise => { // convert an array of objects to an object const records = tags.reduce((acc, tag) => { const key = Object.keys(tag)[0]; @@ -20,7 +22,10 @@ export const addAddressTags = async (tags: [{ [key: string]: string }]): Promise /** * Fetches Address Tags from the Lattice. */ -export const fetchAddressTags = async ({ n = MAX_ADDR, start = 0 }: { n?: number; start?: number } = {}) => { +export const fetchAddressTags = async ({ + n = MAX_ADDR, + start = 0, +}: { n?: number; start?: number } = {}) => { const addressTags: AddressTag[] = []; let remainingToFetch = n; let fetched = start; @@ -45,7 +50,9 @@ export const fetchAddressTags = async ({ n = MAX_ADDR, start = 0 }: { n?: number /** * Removes Address Tags from the Lattice. */ -export const removeAddressTags = async (tags: AddressTag[]): Promise => { +export const removeAddressTags = async ( + tags: AddressTag[], +): Promise => { const ids = tags.map((tag) => `${tag.id}`); return queue((client: Client) => client.removeKvRecords({ ids })); }; diff --git a/packages/sdk/src/api/addresses.ts b/packages/sdk/src/api/addresses.ts index 7fa68ed1..c2414af3 100644 --- a/packages/sdk/src/api/addresses.ts +++ b/packages/sdk/src/api/addresses.ts @@ -17,7 +17,12 @@ import { } from '../constants'; import { LatticeGetAddressesFlag } from '../protocol/latticeConstants'; import type { GetAddressesRequestParams, WalletPath } from '../types'; -import { getFlagFromPath, getStartPath, parseDerivationPathComponents, queue } from './utilities'; +import { + getFlagFromPath, + getStartPath, + parseDerivationPathComponents, + queue, +} from './utilities'; type FetchAddressesParams = { n?: number; @@ -25,7 +30,9 @@ type FetchAddressesParams = { flag?: number; }; -export const fetchAddresses = async (overrides?: Partial) => { +export const fetchAddresses = async ( + overrides?: Partial, +) => { let allAddresses: string[] = []; let totalFetched = 0; const totalToFetch = overrides?.n || MAX_ADDR; @@ -58,9 +65,14 @@ export const fetchAddresses = async (overrides?: Partial => { +export const fetchAddress = async ( + path: number | WalletPath = 0, +): Promise => { return fetchAddresses({ - startPath: typeof path === 'number' ? getStartPath(DEFAULT_ETH_DERIVATION, path) : path, + startPath: + typeof path === 'number' + ? getStartPath(DEFAULT_ETH_DERIVATION, path) + : path, n: 1, }).then((addrs) => addrs[0]); }; @@ -78,12 +90,23 @@ function createFetchBtcAddressesFunction(derivationPath: number[]) { }); }; } -export const fetchBtcLegacyAddresses = createFetchBtcAddressesFunction(BTC_LEGACY_DERIVATION); -export const fetchBtcSegwitAddresses = createFetchBtcAddressesFunction(BTC_SEGWIT_DERIVATION); -export const fetchBtcWrappedSegwitAddresses = createFetchBtcAddressesFunction(BTC_WRAPPED_SEGWIT_DERIVATION); -export const fetchBtcLegacyChangeAddresses = createFetchBtcAddressesFunction(BTC_LEGACY_CHANGE_DERIVATION); -export const fetchBtcSegwitChangeAddresses = createFetchBtcAddressesFunction(BTC_SEGWIT_CHANGE_DERIVATION); -export const fetchBtcWrappedSegwitChangeAddresses = createFetchBtcAddressesFunction(BTC_WRAPPED_SEGWIT_CHANGE_DERIVATION); +export const fetchBtcLegacyAddresses = createFetchBtcAddressesFunction( + BTC_LEGACY_DERIVATION, +); +export const fetchBtcSegwitAddresses = createFetchBtcAddressesFunction( + BTC_SEGWIT_DERIVATION, +); +export const fetchBtcWrappedSegwitAddresses = createFetchBtcAddressesFunction( + BTC_WRAPPED_SEGWIT_DERIVATION, +); +export const fetchBtcLegacyChangeAddresses = createFetchBtcAddressesFunction( + BTC_LEGACY_CHANGE_DERIVATION, +); +export const fetchBtcSegwitChangeAddresses = createFetchBtcAddressesFunction( + BTC_SEGWIT_CHANGE_DERIVATION, +); +export const fetchBtcWrappedSegwitChangeAddresses = + createFetchBtcAddressesFunction(BTC_WRAPPED_SEGWIT_CHANGE_DERIVATION); export const fetchSolanaAddresses = async ( { n, startPathIndex }: FetchAddressesParams = { @@ -110,7 +133,11 @@ export const fetchLedgerLiveAddresses = async ( queue((client) => client .getAddresses({ - startPath: getStartPath(LEDGER_LIVE_DERIVATION, startPathIndex + i, 2), + startPath: getStartPath( + LEDGER_LIVE_DERIVATION, + startPathIndex + i, + 2, + ), n: 1, }) .then((addresses) => addresses.map((address) => `${address}`)), @@ -132,7 +159,11 @@ export const fetchLedgerLegacyAddresses = async ( queue((client) => client .getAddresses({ - startPath: getStartPath(LEDGER_LEGACY_DERIVATION, startPathIndex + i, 3), + startPath: getStartPath( + LEDGER_LEGACY_DERIVATION, + startPathIndex + i, + 3, + ), n: 1, }) .then((addresses) => addresses.map((address) => `${address}`)), @@ -142,12 +173,20 @@ export const fetchLedgerLegacyAddresses = async ( return Promise.all(addresses); }; -export const fetchBip44ChangeAddresses = async ({ n = MAX_ADDR, startPathIndex = 0 }: FetchAddressesParams = {}) => { +export const fetchBip44ChangeAddresses = async ({ + n = MAX_ADDR, + startPathIndex = 0, +}: FetchAddressesParams = {}) => { const addresses = []; for (let i = 0; i < n; i++) { addresses.push( queue((client) => { - const startPath = [44 + HARDENED_OFFSET, 501 + HARDENED_OFFSET, startPathIndex + i + HARDENED_OFFSET, 0 + HARDENED_OFFSET]; + const startPath = [ + 44 + HARDENED_OFFSET, + 501 + HARDENED_OFFSET, + startPathIndex + i + HARDENED_OFFSET, + 0 + HARDENED_OFFSET, + ]; return client .getAddresses({ startPath, @@ -161,11 +200,16 @@ export const fetchBip44ChangeAddresses = async ({ n = MAX_ADDR, startPathIndex = return Promise.all(addresses); }; -export async function fetchAddressesByDerivationPath(path: string, { n = 1, startPathIndex = 0, flag }: FetchAddressesParams = {}): Promise { +export async function fetchAddressesByDerivationPath( + path: string, + { n = 1, startPathIndex = 0, flag }: FetchAddressesParams = {}, +): Promise { const components = path.split('/').filter(Boolean); const parsedPath = parseDerivationPathComponents(components); const _flag = getFlagFromPath(parsedPath); - const wildcardIndex = components.findIndex((part) => part.toLowerCase().includes('x')); + const wildcardIndex = components.findIndex((part) => + part.toLowerCase().includes('x'), + ); if (wildcardIndex === -1) { return queue((client) => @@ -180,7 +224,8 @@ export async function fetchAddressesByDerivationPath(path: string, { n = 1, star const addresses: string[] = []; for (let i = 0; i < n; i++) { const currentPath = [...parsedPath]; - currentPath[wildcardIndex] = currentPath[wildcardIndex] + startPathIndex + i; + currentPath[wildcardIndex] = + currentPath[wildcardIndex] + startPathIndex + i; const result = await queue((client) => client.getAddresses({ @@ -211,9 +256,12 @@ export async function fetchBtcXpub(): Promise { * @returns ypub string */ export async function fetchBtcYpub(): Promise { - const result = await fetchAddressesByDerivationPath(BTC_WRAPPED_SEGWIT_YPUB_PATH, { - flag: LatticeGetAddressesFlag.secp256k1Xpub, - }); + const result = await fetchAddressesByDerivationPath( + BTC_WRAPPED_SEGWIT_YPUB_PATH, + { + flag: LatticeGetAddressesFlag.secp256k1Xpub, + }, + ); return result[0]; } diff --git a/packages/sdk/src/api/setup.ts b/packages/sdk/src/api/setup.ts index f4d555ea..6471528f 100644 --- a/packages/sdk/src/api/setup.ts +++ b/packages/sdk/src/api/setup.ts @@ -50,7 +50,9 @@ export const setup = async (params: SetupParameters): Promise => { setSaveClient(buildSaveClientFn(params.setStoredClient)); if ('deviceId' in params && 'password' in params && 'name' in params) { - const privKey = params.appSecret || Utils.generateAppSecret(params.deviceId, params.password, params.name); + const privKey = + params.appSecret || + Utils.generateAppSecret(params.deviceId, params.password, params.name); const client = new Client({ deviceId: params.deviceId, privKey, diff --git a/packages/sdk/src/api/signing.ts b/packages/sdk/src/api/signing.ts index aaccd57b..fd98777a 100644 --- a/packages/sdk/src/api/signing.ts +++ b/packages/sdk/src/api/signing.ts @@ -1,10 +1,31 @@ import { RLP } from '@ethereumjs/rlp'; import { Hash } from 'ox'; -import { type Address, type Authorization, type Hex, type TransactionSerializable, type TransactionSerializableEIP7702, serializeTransaction } from 'viem'; +import { + type Address, + type Authorization, + type Hex, + type TransactionSerializable, + type TransactionSerializableEIP7702, + serializeTransaction, +} from 'viem'; import { Constants } from '..'; -import { BTC_LEGACY_DERIVATION, BTC_SEGWIT_DERIVATION, BTC_WRAPPED_SEGWIT_DERIVATION, CURRENCIES, DEFAULT_ETH_DERIVATION, SOLANA_DERIVATION } from '../constants'; +import { + BTC_LEGACY_DERIVATION, + BTC_SEGWIT_DERIVATION, + BTC_WRAPPED_SEGWIT_DERIVATION, + CURRENCIES, + DEFAULT_ETH_DERIVATION, + SOLANA_DERIVATION, +} from '../constants'; import { fetchDecoder } from '../functions/fetchDecoder'; -import type { BitcoinSignPayload, EIP712MessagePayload, SignData, SignRequestParams, SigningPayload, TransactionRequest } from '../types'; +import type { + BitcoinSignPayload, + EIP712MessagePayload, + SignData, + SignRequestParams, + SigningPayload, + TransactionRequest, +} from '../types'; import { getYParity } from '../util'; import { isEIP712Payload, queue } from './utilities'; @@ -19,21 +40,38 @@ type AuthorizationRequest = { */ type RawTransaction = Hex | Uint8Array | Buffer; -export const sign = async (transaction: TransactionSerializable | RawTransaction, overrides?: Omit): Promise => { +export const sign = async ( + transaction: TransactionSerializable | RawTransaction, + overrides?: Omit, +): Promise => { const isRaw = isRawTransaction(transaction); - const serializedTx = isRaw ? normalizeRawTransaction(transaction) : serializeTransaction(transaction as TransactionSerializable); + const serializedTx = isRaw + ? normalizeRawTransaction(transaction) + : serializeTransaction(transaction as TransactionSerializable); // Determine the encoding type based on transaction type - let encodingType: typeof Constants.SIGNING.ENCODINGS.EVM | typeof Constants.SIGNING.ENCODINGS.EIP7702_AUTH | typeof Constants.SIGNING.ENCODINGS.EIP7702_AUTH_LIST = Constants.SIGNING.ENCODINGS.EVM; + let encodingType: + | typeof Constants.SIGNING.ENCODINGS.EVM + | typeof Constants.SIGNING.ENCODINGS.EIP7702_AUTH + | typeof Constants.SIGNING.ENCODINGS.EIP7702_AUTH_LIST = + Constants.SIGNING.ENCODINGS.EVM; if (!isRaw && (transaction as TransactionSerializable).type === 'eip7702') { const eip7702Tx = transaction as TransactionSerializableEIP7702; - const hasAuthList = eip7702Tx.authorizationList && eip7702Tx.authorizationList.length > 0; - encodingType = hasAuthList ? Constants.SIGNING.ENCODINGS.EIP7702_AUTH_LIST : Constants.SIGNING.ENCODINGS.EIP7702_AUTH; + const hasAuthList = + eip7702Tx.authorizationList && eip7702Tx.authorizationList.length > 0; + encodingType = hasAuthList + ? Constants.SIGNING.ENCODINGS.EIP7702_AUTH_LIST + : Constants.SIGNING.ENCODINGS.EIP7702_AUTH; } // Only fetch decoder if we have the required fields let decoder: Buffer | undefined; - if (!isRaw && 'data' in (transaction as TransactionSerializable) && 'to' in (transaction as TransactionSerializable) && 'chainId' in (transaction as TransactionSerializable)) { + if ( + !isRaw && + 'data' in (transaction as TransactionSerializable) && + 'to' in (transaction as TransactionSerializable) && + 'chainId' in (transaction as TransactionSerializable) + ) { decoder = await fetchDecoder({ data: (transaction as TransactionSerializable).data, to: (transaction as TransactionSerializable).to, @@ -56,7 +94,15 @@ export const sign = async (transaction: TransactionSerializable | RawTransaction /** * Sign a message with support for EIP-712 typed data and const assertions */ -export function signMessage(payload: string | Uint8Array | Buffer | Buffer[] | EIP712MessagePayload>, overrides?: Omit): Promise { +export function signMessage( + payload: + | string + | Uint8Array + | Buffer + | Buffer[] + | EIP712MessagePayload>, + overrides?: Omit, +): Promise { const basePayload: SigningPayload> = { signerPath: DEFAULT_ETH_DERIVATION, curveType: Constants.SIGNING.CURVES.SECP256K1, @@ -74,8 +120,14 @@ export function signMessage(payload: string | Uint8Array | Buffer | Buffer[] | E return queue((client) => client.sign(tx)); } -function isRawTransaction(value: TransactionSerializable | RawTransaction): value is RawTransaction { - return typeof value === 'string' || value instanceof Uint8Array || Buffer.isBuffer(value); +function isRawTransaction( + value: TransactionSerializable | RawTransaction, +): value is RawTransaction { + return ( + typeof value === 'string' || + value instanceof Uint8Array || + Buffer.isBuffer(value) + ); } function normalizeRawTransaction(tx: RawTransaction): Hex | Buffer { @@ -89,15 +141,26 @@ function normalizeRawTransaction(tx: RawTransaction): Hex | Buffer { * Signs an EIP-7702 authorization to set code for an externally owned account (EOA). * Returns a Viem-compatible authorization object. */ -export const signAuthorization = async (authorization: AuthorizationRequest, overrides?: Omit): Promise => { +export const signAuthorization = async ( + authorization: AuthorizationRequest, + overrides?: Omit, +): Promise => { // EIP-7702 authorization message is: MAGIC || rlp([chain_id, address, nonce]) // MAGIC = 0x05 per EIP-7702 spec const MAGIC = Buffer.from([0x05]); // Handle the address/contractAddress alias - const address = 'address' in authorization ? authorization.address : authorization.contractAddress; + const address = + 'address' in authorization + ? authorization.address + : authorization.contractAddress; - const message = Buffer.concat([MAGIC, Buffer.from(RLP.encode([authorization.chainId, address, authorization.nonce]))]); + const message = Buffer.concat([ + MAGIC, + Buffer.from( + RLP.encode([authorization.chainId, address, authorization.nonce]), + ), + ]); const payload: SigningPayload = { signerPath: DEFAULT_ETH_DERIVATION, @@ -108,7 +171,9 @@ export const signAuthorization = async (authorization: AuthorizationRequest, ove }; // Get the signature with all components - const response = await queue((client) => client.sign({ data: payload, ...overrides })); + const response = await queue((client) => + client.sign({ data: payload, ...overrides }), + ); // Extract signature components if they exist if (response.sig && response.pubkey) { @@ -117,8 +182,12 @@ export const signAuthorization = async (authorization: AuthorizationRequest, ove const yParity = getYParity(messageHash, response.sig, response.pubkey); // Handle both Buffer and string formats for r and s - const rValue = Buffer.isBuffer(response.sig.r) ? `0x${response.sig.r.toString('hex')}` : response.sig.r; - const sValue = Buffer.isBuffer(response.sig.s) ? `0x${response.sig.s.toString('hex')}` : response.sig.s; + const rValue = Buffer.isBuffer(response.sig.r) + ? `0x${response.sig.r.toString('hex')}` + : response.sig.r; + const sValue = Buffer.isBuffer(response.sig.s) + ? `0x${response.sig.s.toString('hex')}` + : response.sig.s; // Create a complete Authorization object with all required signature components const result: Authorization = { @@ -139,7 +208,9 @@ export const signAuthorization = async (authorization: AuthorizationRequest, ove /** * Sign an EIP-7702 transaction using Viem-compatible types */ -export const signAuthorizationList = async (tx: TransactionSerializableEIP7702): Promise => { +export const signAuthorizationList = async ( + tx: TransactionSerializableEIP7702, +): Promise => { const serializedTx = serializeTransaction(tx); const payload: SigningPayload = { @@ -156,7 +227,9 @@ export const signAuthorizationList = async (tx: TransactionSerializableEIP7702): return signedPayload; }; -export const signBtcLegacyTx = async (payload: BitcoinSignPayload): Promise => { +export const signBtcLegacyTx = async ( + payload: BitcoinSignPayload, +): Promise => { const tx = { data: { signerPath: BTC_LEGACY_DERIVATION, @@ -167,7 +240,9 @@ export const signBtcLegacyTx = async (payload: BitcoinSignPayload): Promise client.sign(tx)); }; -export const signBtcSegwitTx = async (payload: BitcoinSignPayload): Promise => { +export const signBtcSegwitTx = async ( + payload: BitcoinSignPayload, +): Promise => { const tx = { data: { signerPath: BTC_SEGWIT_DERIVATION, @@ -178,7 +253,9 @@ export const signBtcSegwitTx = async (payload: BitcoinSignPayload): Promise client.sign(tx)); }; -export const signBtcWrappedSegwitTx = async (payload: BitcoinSignPayload): Promise => { +export const signBtcWrappedSegwitTx = async ( + payload: BitcoinSignPayload, +): Promise => { const tx = { data: { signerPath: BTC_WRAPPED_SEGWIT_DERIVATION, @@ -189,7 +266,10 @@ export const signBtcWrappedSegwitTx = async (payload: BitcoinSignPayload): Promi return queue((client) => client.sign(tx)); }; -export const signSolanaTx = async (payload: Buffer, overrides?: SignRequestParams): Promise => { +export const signSolanaTx = async ( + payload: Buffer, + overrides?: SignRequestParams, +): Promise => { const tx = { data: { signerPath: SOLANA_DERIVATION, diff --git a/packages/sdk/src/api/state.ts b/packages/sdk/src/api/state.ts index 8871a597..22aac567 100644 --- a/packages/sdk/src/api/state.ts +++ b/packages/sdk/src/api/state.ts @@ -2,7 +2,9 @@ import type { Client } from '../client'; export let saveClient: (clientData: string | null) => Promise; -export const setSaveClient = (fn: (clientData: string | null) => Promise) => { +export const setSaveClient = ( + fn: (clientData: string | null) => Promise, +) => { saveClient = fn; }; diff --git a/packages/sdk/src/api/utilities.ts b/packages/sdk/src/api/utilities.ts index fef1146e..20072a67 100644 --- a/packages/sdk/src/api/utilities.ts +++ b/packages/sdk/src/api/utilities.ts @@ -1,6 +1,11 @@ import { Client } from '../client'; import { EXTERNAL, HARDENED_OFFSET } from '../constants'; -import { getFunctionQueue, loadClient, saveClient, setFunctionQueue } from './state'; +import { + getFunctionQueue, + loadClient, + saveClient, + setFunctionQueue, +} from './state'; /** * `queue` is a function that wraps all functional API calls. It limits the number of concurrent @@ -49,7 +54,9 @@ const decodeClientData = (clientData: string) => { return Buffer.from(clientData, 'base64').toString(); }; -export const buildSaveClientFn = (setStoredClient: (clientData: string | null) => Promise) => { +export const buildSaveClientFn = ( + setStoredClient: (clientData: string | null) => Promise, +) => { return async (clientData: string | null) => { if (!clientData) return; const encodedData = encodeClientData(clientData); @@ -81,7 +88,12 @@ export const getStartPath = ( return startPath; }; -export const isEIP712Payload = (payload: any) => typeof payload !== 'string' && 'types' in payload && 'domain' in payload && 'primaryType' in payload && 'message' in payload; +export const isEIP712Payload = (payload: any) => + typeof payload !== 'string' && + 'types' in payload && + 'domain' in payload && + 'primaryType' in payload && + 'message' in payload; export function parseDerivationPath(path: string): number[] { if (!path) return []; @@ -94,7 +106,8 @@ export function parseDerivationPathComponents(components: string[]): number[] { const lowerPart = part.toLowerCase(); if (lowerPart === 'x') return 0; // Wildcard if (lowerPart === "x'") return HARDENED_OFFSET; // Hardened wildcard - if (part.endsWith("'")) return Number.parseInt(part.slice(0, -1)) + HARDENED_OFFSET; + if (part.endsWith("'")) + return Number.parseInt(part.slice(0, -1)) + HARDENED_OFFSET; const val = Number.parseInt(part); if (Number.isNaN(val)) { throw new Error(`Invalid part in derivation path: ${part}`); diff --git a/packages/sdk/src/bitcoin.ts b/packages/sdk/src/bitcoin.ts index e1547fda..190fa812 100644 --- a/packages/sdk/src/bitcoin.ts +++ b/packages/sdk/src/bitcoin.ts @@ -52,7 +52,8 @@ const BTC_SCRIPT_TYPE_P2WPKH_V0 = 0x04; const buildBitcoinTxRequest = (data) => { const { prevOuts, recipient, value, changePath, fee } = data; if (!changePath) throw new Error('No changePath provided.'); - if (changePath.length !== 5) throw new Error('Please provide a full change path.'); + if (changePath.length !== 5) + throw new Error('Please provide a full change path.'); // Serialize the request const payload = Buffer.alloc(59 + 69 * prevOuts.length); let off = 0; @@ -104,7 +105,8 @@ const buildBitcoinTxRequest = (data) => { const scriptType = getScriptType(input); payload.writeUInt8(scriptType, off); off++; - if (!Buffer.isBuffer(input.txHash)) input.txHash = Buffer.from(input.txHash, 'hex'); + if (!Buffer.isBuffer(input.txHash)) + input.txHash = Buffer.from(input.txHash, 'hex'); input.txHash.copy(payload, off); off += input.txHash.length; }); @@ -225,7 +227,9 @@ const getBitcoinAddress = (pubkeyhash, version) => { words.unshift(bech32Version); return bech32.encode(bech32Prefix, words); } else { - return bs58check.encode(Buffer.concat([Buffer.from([version]), pubkeyhash])); + return bs58check.encode( + Buffer.concat([Buffer.from([version]), pubkeyhash]), + ); } }; @@ -234,7 +238,10 @@ const getBitcoinAddress = (pubkeyhash, version) => { function buildRedeemScript(pubkey) { const redeemScript = Buffer.alloc(22); const shaHash = Buffer.from(Hash.sha256(pubkey)); - const pubkeyhash = Buffer.from(ripemd160().update(shaHash).digest('hex'), 'hex'); + const pubkeyhash = Buffer.from( + ripemd160().update(shaHash).digest('hex'), + 'hex', + ); redeemScript.writeUInt8(OP.ZERO, 0); redeemScript.writeUInt8(pubkeyhash.length, 1); pubkeyhash.copy(redeemScript, 2); @@ -283,7 +290,9 @@ function buildLockingScript(address) { case FMT_LEGACY_TESTNET: return buildP2pkhLockingScript(dec.pkh); default: - throw new Error(`Unknown version byte: ${dec.versionByte}. Cannot build BTC transaction.`); + throw new Error( + `Unknown version byte: ${dec.versionByte}. Cannot build BTC transaction.`, + ); } } @@ -400,7 +409,9 @@ function decodeAddress(address) { } // Make sure we decoded if (bech32Dec.words[0] !== 0) { - throw new Error(`Unsupported segwit version: must be 0, got ${bech32Dec.words[0]}`); + throw new Error( + `Unsupported segwit version: must be 0, got ${bech32Dec.words[0]}`, + ); } // Make sure address type is supported. // We currently only support P2WPKH addresses, which bech-32decode to 33 words. @@ -409,7 +420,9 @@ function decodeAddress(address) { // support them either. if (bech32Dec.words.length !== 33) { const isP2wpsh = bech32Dec.words.length === 53; - throw new Error(`Unsupported address${isP2wpsh ? ' (P2WSH not supported)' : ''}: ${address}`); + throw new Error( + `Unsupported address${isP2wpsh ? ' (P2WSH not supported)' : ''}: ${address}`, + ); } pkh = Buffer.from(bech32.fromWords(bech32Dec.words.slice(1))); @@ -432,14 +445,19 @@ function getAddressFormat(path) { return FMT_SEGWIT_NATIVE_V0_TESTNET; } else if (purpose === PURPOSES.BTC_WRAPPED_SEGWIT && coin === COINS.BTC) { return FMT_SEGWIT_WRAPPED; - } else if (purpose === PURPOSES.BTC_WRAPPED_SEGWIT && coin === COINS.BTC_TESTNET) { + } else if ( + purpose === PURPOSES.BTC_WRAPPED_SEGWIT && + coin === COINS.BTC_TESTNET + ) { return FMT_SEGWIT_WRAPPED_TESTNET; } else if (purpose === PURPOSES.BTC_LEGACY && coin === COINS.BTC) { return FMT_LEGACY; } else if (purpose === PURPOSES.BTC_LEGACY && coin === COINS.BTC_TESTNET) { return FMT_LEGACY_TESTNET; } else { - throw new Error('Invalid Bitcoin path provided. Cannot determine address format.'); + throw new Error( + 'Invalid Bitcoin path provided. Cannot determine address format.', + ); } } @@ -456,7 +474,9 @@ function getScriptType(input) { case PURPOSES.BTC_SEGWIT: return BTC_SCRIPT_TYPE_P2WPKH_V0; default: - throw new Error(`Unsupported path purpose (${input.signerPath[0]}): cannot determine BTC script type.`); + throw new Error( + `Unsupported path purpose (${input.signerPath[0]}): cannot determine BTC script type.`, + ); } } @@ -466,7 +486,10 @@ function getScriptType(input) { function needsWitness(inputs) { let w = false; inputs.forEach((input) => { - if (input.signerPath[0] === PURPOSES.BTC_SEGWIT || input.signerPath[0] === PURPOSES.BTC_WRAPPED_SEGWIT) { + if ( + input.signerPath[0] === PURPOSES.BTC_SEGWIT || + input.signerPath[0] === PURPOSES.BTC_WRAPPED_SEGWIT + ) { w = true; } }); diff --git a/packages/sdk/src/calldata/evm.ts b/packages/sdk/src/calldata/evm.ts index 3103c4ce..4e8449b9 100644 --- a/packages/sdk/src/calldata/evm.ts +++ b/packages/sdk/src/calldata/evm.ts @@ -7,7 +7,10 @@ import { decodeAbiParameters, parseAbiParameters } from 'viem'; * @returns Buffer containing RLP-serialized array of calldata info to pass to signing request * @public */ -export const parseSolidityJSONABI = (sig: string, abi: any[]): { def: EVMDef } => { +export const parseSolidityJSONABI = ( + sig: string, + abi: any[], +): { def: EVMDef } => { sig = coerceSig(sig); // Find the first match in the ABI const match = abi @@ -102,11 +105,17 @@ export const getNestedCalldata = (def, calldata) => { if (Array.isArray(paramData)) { paramData.forEach((nestedParamDatum) => { // Ensure nestedParamDatum is a hex string - if (typeof nestedParamDatum !== 'string' || !nestedParamDatum.startsWith('0x')) { + if ( + typeof nestedParamDatum !== 'string' || + !nestedParamDatum.startsWith('0x') + ) { nestedDefIsPossible = false; return; } - const nestedParamDatumBuf = Buffer.from(nestedParamDatum.slice(2), 'hex'); + const nestedParamDatumBuf = Buffer.from( + nestedParamDatum.slice(2), + 'hex', + ); if (!couldBeNestedDef(nestedParamDatumBuf)) { nestedDefIsPossible = false; } @@ -116,7 +125,10 @@ export const getNestedCalldata = (def, calldata) => { } } else if (isBytesItem(defParams[i])) { // Regular `bytes` type - perform size check - if (typeof paramData !== 'string' || !(paramData as string).startsWith('0x')) { + if ( + typeof paramData !== 'string' || + !(paramData as string).startsWith('0x') + ) { nestedDefIsPossible = false; } else { const data = paramData as string; @@ -294,7 +306,8 @@ function parseBasicTypeStr(typeStr: string): EVMParamInfo { if (typeStr.indexOf(t) > -1 && !found) { param.typeIdx = i; param.arraySzs = getArraySzs(typeStr); - const arrStart = param.arraySzs.length > 0 ? typeStr.indexOf('[') : typeStr.length; + const arrStart = + param.arraySzs.length > 0 ? typeStr.indexOf('[') : typeStr.length; const typeStrNum = typeStr.slice(t.length, arrStart); if (Number.parseInt(typeStrNum)) { param.szBytes = Number.parseInt(typeStrNum) / 8; @@ -316,7 +329,12 @@ function parseBasicTypeStr(typeStr: string): EVMParamInfo { * (EVMDef). This function may recurse if there are tuple types. * @internal */ -function parseDef(item, canonicalName = '', def = [], recursed = false): EVMDef { +function parseDef( + item, + canonicalName = '', + def = [], + recursed = false, +): EVMDef { // Function name. Can be an empty string. if (!recursed) { const nameStr = item.name || ''; @@ -331,7 +349,12 @@ function parseDef(item, canonicalName = '', def = [], recursed = false): EVMDef const flatParam = getFlatParam(input); if (input.type.indexOf('tuple') > -1 && input.components) { // For tuples we need to recurse - const recursed = parseDef({ inputs: input.components }, canonicalName, [], true); + const recursed = parseDef( + { inputs: input.components }, + canonicalName, + [], + true, + ); canonicalName = recursed.canonicalName; // Add brackets if this is a tuple array and also add a comma canonicalName += `${input.type.slice(5)},`; @@ -369,7 +392,12 @@ function parseParamDef(def: any[], prefix = ''): any[] { parsedDef[parsedDef.length - 1].push(parseParamDef(param, `${i}-`)); } else { // If this is not tuple info, add the flat param info to the def - parsedDef.push([`#${prefix}${i + 1 - numTuples}`, param.typeIdx, param.szBytes, param.arraySzs]); + parsedDef.push([ + `#${prefix}${i + 1 - numTuples}`, + param.typeIdx, + param.szBytes, + param.arraySzs, + ]); } // Tuple if (param.typeIdx === EVM_TYPES.indexOf('tuple')) { @@ -482,7 +510,8 @@ function getTupleName(name, withArr = true) { } else if (name[i] === ')') { brackets -= 1; } - let canBreak = name[i + 1] === ',' || name[i + 1] === ')' || i === name.length - 1; + let canBreak = + name[i + 1] === ',' || name[i + 1] === ')' || i === name.length - 1; if (!withArr && name[i + 1] === '[') { canBreak = true; } @@ -510,7 +539,17 @@ function isBytesArrItem(param) { } const BAD_CANONICAL_ERR = 'Could not parse canonical function name.'; -const EVM_TYPES = [null, 'address', 'bool', 'uint', 'int', 'bytes', 'string', 'tuple', 'nestedDef']; +const EVM_TYPES = [ + null, + 'address', + 'bool', + 'uint', + 'int', + 'bytes', + 'string', + 'tuple', + 'nestedDef', +]; type EVMParamInfo = { szBytes: number; diff --git a/packages/sdk/src/calldata/index.ts b/packages/sdk/src/calldata/index.ts index c51e31ec..c5d46b8e 100644 --- a/packages/sdk/src/calldata/index.ts +++ b/packages/sdk/src/calldata/index.ts @@ -3,7 +3,12 @@ * calldata decoder info is packed into the request, it is used to decode the calldata in the * request. It is optional. */ -import { getNestedCalldata, parseCanonicalName, parseSolidityJSONABI, replaceNestedDefs } from './evm'; +import { + getNestedCalldata, + parseCanonicalName, + parseSolidityJSONABI, + replaceNestedDefs, +} from './evm'; export const CALLDATA = { EVM: { diff --git a/packages/sdk/src/client.ts b/packages/sdk/src/client.ts index bd76226e..797a6159 100644 --- a/packages/sdk/src/client.ts +++ b/packages/sdk/src/client.ts @@ -1,10 +1,36 @@ import { buildSaveClientFn } from './api/utilities'; -import { BASE_URL, DEFAULT_ACTIVE_WALLETS, EMPTY_WALLET_UID, getFwVersionConst } from './constants'; -import { addKvRecords, connect, fetchActiveWallet, fetchEncData, getAddresses, getKvRecords, pair, removeKvRecords, sign } from './functions/index'; +import { + BASE_URL, + DEFAULT_ACTIVE_WALLETS, + EMPTY_WALLET_UID, + getFwVersionConst, +} from './constants'; +import { + addKvRecords, + connect, + fetchActiveWallet, + fetchEncData, + getAddresses, + getKvRecords, + pair, + removeKvRecords, + sign, +} from './functions/index'; import { buildRetryWrapper } from './shared/functions'; import { getPubKeyBytes } from './shared/utilities'; import { validateEphemeralPub } from './shared/validators'; -import type { ActiveWallets, AddKvRecordsRequestParams, FetchEncDataRequest, GetAddressesRequestParams, GetKvRecordsData, GetKvRecordsRequestParams, KeyPair, RemoveKvRecordsRequestParams, SignData, SignRequestParams } from './types'; +import type { + ActiveWallets, + AddKvRecordsRequestParams, + FetchEncDataRequest, + GetAddressesRequestParams, + GetKvRecordsData, + GetKvRecordsRequestParams, + KeyPair, + RemoveKvRecordsRequestParams, + SignData, + SignRequestParams, +} from './types'; import { getP256KeyPair, getP256KeyPairFromPub, randomBytes } from './util'; /** @@ -90,7 +116,9 @@ export class Client { this.privKey = privKey || randomBytes(32); this.key = getP256KeyPair(this.privKey); this.retryWrapper = buildRetryWrapper(this, this.retryCount); - this.setStoredClient = setStoredClient ? buildSaveClientFn(setStoredClient) : undefined; + this.setStoredClient = setStoredClient + ? buildSaveClientFn(setStoredClient) + : undefined; /** The user may pass in state data to rehydrate a session that was previously cached */ if (stateData) { @@ -130,7 +158,9 @@ export class Client { public get sharedSecret() { // Once every ~256 attempts, we will get a key that starts with a `00` byte, which can lead to // problems initializing AES if we don't force a 32 byte BE buffer. - return Buffer.from(this.key.derive(this.ephemeralPub.getPublic()).toArray('be', 32)); + return Buffer.from( + this.key.derive(this.ephemeralPub.getPublic()).toArray('be', 32), + ); } /** @internal */ @@ -167,7 +197,12 @@ export class Client { * Takes a starting path and a number to get the addresses associated with the active wallet. * @category Lattice */ - public async getAddresses({ startPath, n = 1, flag = 0, iterIdx = 0 }: GetAddressesRequestParams): Promise { + public async getAddresses({ + startPath, + n = 1, + flag = 0, + iterIdx = 0, + }: GetAddressesRequestParams): Promise { return this.retryWrapper(getAddresses, { startPath, n, flag, iterIdx }); } @@ -175,7 +210,12 @@ export class Client { * Builds and sends a request for signing to the Lattice. * @category Lattice */ - public async sign({ data, currency, cachedData, nextCode }: SignRequestParams): Promise { + public async sign({ + data, + currency, + cachedData, + nextCode, + }: SignRequestParams): Promise { return this.retryWrapper(sign, { data, currency, cachedData, nextCode }); } @@ -190,7 +230,11 @@ export class Client { * Takes in a set of key-value records and sends a request to add them to the Lattice. * @category Lattice */ - async addKvRecords({ type = 0, records, caseSensitive = false }: AddKvRecordsRequestParams): Promise { + async addKvRecords({ + type = 0, + records, + caseSensitive = false, + }: AddKvRecordsRequestParams): Promise { return this.retryWrapper(addKvRecords, { type, records, caseSensitive }); } @@ -198,7 +242,11 @@ export class Client { * Fetches a list of key-value records from the Lattice. * @category Lattice */ - public async getKvRecords({ type = 0, n = 1, start = 0 }: GetKvRecordsRequestParams): Promise { + public async getKvRecords({ + type = 0, + n = 1, + start = 0, + }: GetKvRecordsRequestParams): Promise { return this.retryWrapper(getKvRecords, { type, n, start }); } @@ -206,7 +254,10 @@ export class Client { * Takes in an array of ids and sends a request to remove them from the Lattice. * @category Lattice */ - public async removeKvRecords({ type = 0, ids = [] }: RemoveKvRecordsRequestParams): Promise { + public async removeKvRecords({ + type = 0, + ids = [], + }: RemoveKvRecordsRequestParams): Promise { return this.retryWrapper(removeKvRecords, { type, ids }); } @@ -216,15 +267,23 @@ export class Client { * data formatted according to the specified type. * @category Lattice */ - public async fetchEncryptedData(params: FetchEncDataRequest): Promise { + public async fetchEncryptedData( + params: FetchEncDataRequest, + ): Promise { return this.retryWrapper(fetchEncData, params); } /** Get the active wallet */ public getActiveWallet() { - if (this.activeWallets.external.uid && !EMPTY_WALLET_UID.equals(this.activeWallets.external.uid)) { + if ( + this.activeWallets.external.uid && + !EMPTY_WALLET_UID.equals(this.activeWallets.external.uid) + ) { return this.activeWallets.external; - } else if (this.activeWallets.internal.uid && !EMPTY_WALLET_UID.equals(this.activeWallets.internal.uid)) { + } else if ( + this.activeWallets.internal.uid && + !EMPTY_WALLET_UID.equals(this.activeWallets.internal.uid) + ) { return this.activeWallets.internal; } else { return undefined; @@ -358,13 +417,17 @@ export class Client { // Attempt to parse the data const internalWallet = { uid: Buffer.from(unpacked.activeWallets.internal.uid, 'hex'), - name: unpacked.activeWallets.internal.name ? Buffer.from(unpacked.activeWallets.internal.name) : null, + name: unpacked.activeWallets.internal.name + ? Buffer.from(unpacked.activeWallets.internal.name) + : null, capabilities: unpacked.activeWallets.internal.capabilities, external: false, }; const externalWallet = { uid: Buffer.from(unpacked.activeWallets.external.uid, 'hex'), - name: unpacked.activeWallets.external.name ? Buffer.from(unpacked.activeWallets.external.name) : null, + name: unpacked.activeWallets.external.name + ? Buffer.from(unpacked.activeWallets.external.name) + : null, capabilities: unpacked.activeWallets.external.capabilities, external: true, }; diff --git a/packages/sdk/src/constants.ts b/packages/sdk/src/constants.ts index a67d2d81..d29182d9 100644 --- a/packages/sdk/src/constants.ts +++ b/packages/sdk/src/constants.ts @@ -1,5 +1,17 @@ -import { LatticeEncDataSchema, LatticeGetAddressesFlag, LatticeSignBlsDst, LatticeSignCurve, LatticeSignEncoding, LatticeSignHash } from './protocol/latticeConstants'; -import type { ActiveWallets, FirmwareArr, FirmwareConstants, WalletPath } from './types/index.js'; +import { + LatticeEncDataSchema, + LatticeGetAddressesFlag, + LatticeSignBlsDst, + LatticeSignCurve, + LatticeSignEncoding, + LatticeSignHash, +} from './protocol/latticeConstants'; +import type { + ActiveWallets, + FirmwareArr, + FirmwareConstants, + WalletPath, +} from './types/index.js'; /** * Externally exported constants used for building requests @@ -270,7 +282,12 @@ function getFwVersionConst(v: Buffer): FirmwareConstants { }; function gte(v: Buffer, exp: FirmwareArr): boolean { // Note that `v` fields come in as [fix|minor|major] - return v[2] > exp[0] || (v[2] === exp[0] && v[1] > exp[1]) || (v[2] === exp[0] && v[1] === exp[1] && v[0] > exp[2]) || (v[2] === exp[0] && v[1] === exp[1] && v[0] === exp[2]); + return ( + v[2] > exp[0] || + (v[2] === exp[0] && v[1] > exp[1]) || + (v[2] === exp[0] && v[1] === exp[1] && v[0] > exp[2]) || + (v[2] === exp[0] && v[1] === exp[1] && v[0] === exp[2]) + ); } // Very old legacy versions do not give a version number const legacy = v.length === 0; @@ -395,7 +412,10 @@ function getFwVersionConst(v: Buffer): FirmwareConstants { SOLANA: EXTERNAL.SIGNING.ENCODINGS.SOLANA, }; // Supported flags for `getAddresses` - c.getAddressFlags = [EXTERNAL.GET_ADDR_FLAGS.ED25519_PUB, EXTERNAL.GET_ADDR_FLAGS.SECP256K1_PUB]; + c.getAddressFlags = [ + EXTERNAL.GET_ADDR_FLAGS.ED25519_PUB, + EXTERNAL.GET_ADDR_FLAGS.SECP256K1_PUB, + ]; // We updated the max number of params in EIP712 types c.eip712MaxTypeParams = 36; } @@ -426,7 +446,8 @@ function getFwVersionConst(v: Buffer): FirmwareConstants { // V0.17.0 added support for BLS12-381-G1 pubkeys and G2 sigs if (!legacy && gte(v, [0, 17, 0])) { c.getAddressFlags.push(EXTERNAL.GET_ADDR_FLAGS.BLS12_381_G1_PUB); - c.genericSigning.encodingTypes.ETH_DEPOSIT = EXTERNAL.SIGNING.ENCODINGS.ETH_DEPOSIT; + c.genericSigning.encodingTypes.ETH_DEPOSIT = + EXTERNAL.SIGNING.ENCODINGS.ETH_DEPOSIT; } // --- V0.18.X --- @@ -448,7 +469,8 @@ function getFwVersionConst(v: Buffer): FirmwareConstants { const ASCII_REGEX = /^[\u0000-\u007F]+$/; /** @internal */ -const EXTERNAL_NETWORKS_BY_CHAIN_ID_URL = 'https://gridplus.github.io/chains/chains.json'; +const EXTERNAL_NETWORKS_BY_CHAIN_ID_URL = + 'https://gridplus.github.io/chains/chains.json'; /** @internal - Max number of addresses to fetch */ const MAX_ADDR = 10; @@ -502,25 +524,67 @@ export const DEFAULT_ACTIVE_WALLETS: ActiveWallets = { }; /** @internal */ -export const DEFAULT_ETH_DERIVATION: WalletPath = [HARDENED_OFFSET + 44, HARDENED_OFFSET + 60, HARDENED_OFFSET, 0, 0]; +export const DEFAULT_ETH_DERIVATION: WalletPath = [ + HARDENED_OFFSET + 44, + HARDENED_OFFSET + 60, + HARDENED_OFFSET, + 0, + 0, +]; /** @internal */ -export const BTC_LEGACY_DERIVATION = [HARDENED_OFFSET + 44, HARDENED_OFFSET + 0, HARDENED_OFFSET, 0, 0]; +export const BTC_LEGACY_DERIVATION = [ + HARDENED_OFFSET + 44, + HARDENED_OFFSET + 0, + HARDENED_OFFSET, + 0, + 0, +]; /** @internal */ -export const BTC_LEGACY_CHANGE_DERIVATION = [HARDENED_OFFSET + 44, HARDENED_OFFSET + 0, HARDENED_OFFSET, 0, 0]; +export const BTC_LEGACY_CHANGE_DERIVATION = [ + HARDENED_OFFSET + 44, + HARDENED_OFFSET + 0, + HARDENED_OFFSET, + 0, + 0, +]; /** @internal */ -export const BTC_SEGWIT_DERIVATION = [HARDENED_OFFSET + 84, HARDENED_OFFSET, HARDENED_OFFSET, 0, 0]; +export const BTC_SEGWIT_DERIVATION = [ + HARDENED_OFFSET + 84, + HARDENED_OFFSET, + HARDENED_OFFSET, + 0, + 0, +]; /** @internal */ -export const BTC_SEGWIT_CHANGE_DERIVATION = [HARDENED_OFFSET + 84, HARDENED_OFFSET, HARDENED_OFFSET, 1, 0]; +export const BTC_SEGWIT_CHANGE_DERIVATION = [ + HARDENED_OFFSET + 84, + HARDENED_OFFSET, + HARDENED_OFFSET, + 1, + 0, +]; /** @internal */ -export const BTC_WRAPPED_SEGWIT_DERIVATION = [HARDENED_OFFSET + 49, HARDENED_OFFSET, HARDENED_OFFSET, 0, 0]; +export const BTC_WRAPPED_SEGWIT_DERIVATION = [ + HARDENED_OFFSET + 49, + HARDENED_OFFSET, + HARDENED_OFFSET, + 0, + 0, +]; /** @internal */ -export const BTC_WRAPPED_SEGWIT_CHANGE_DERIVATION = [HARDENED_OFFSET + 49, HARDENED_OFFSET, HARDENED_OFFSET, 0, 0]; +export const BTC_WRAPPED_SEGWIT_CHANGE_DERIVATION = [ + HARDENED_OFFSET + 49, + HARDENED_OFFSET, + HARDENED_OFFSET, + 0, + 0, +]; /** * Derivation path for Bitcoin legacy xpub (BIP44). @@ -553,13 +617,29 @@ export const BTC_WRAPPED_SEGWIT_YPUB_PATH = "49'/0'/0'"; export const BTC_SEGWIT_ZPUB_PATH = "84'/0'/0'"; /** @internal */ -export const SOLANA_DERIVATION = [HARDENED_OFFSET + 44, HARDENED_OFFSET + 501, HARDENED_OFFSET, HARDENED_OFFSET]; +export const SOLANA_DERIVATION = [ + HARDENED_OFFSET + 44, + HARDENED_OFFSET + 501, + HARDENED_OFFSET, + HARDENED_OFFSET, +]; /** @internal */ -export const LEDGER_LIVE_DERIVATION = [HARDENED_OFFSET + 49, HARDENED_OFFSET + 60, HARDENED_OFFSET, 0, 0]; +export const LEDGER_LIVE_DERIVATION = [ + HARDENED_OFFSET + 49, + HARDENED_OFFSET + 60, + HARDENED_OFFSET, + 0, + 0, +]; /** @internal */ -export const LEDGER_LEGACY_DERIVATION = [HARDENED_OFFSET + 49, HARDENED_OFFSET + 60, HARDENED_OFFSET, 0]; +export const LEDGER_LEGACY_DERIVATION = [ + HARDENED_OFFSET + 49, + HARDENED_OFFSET + 60, + HARDENED_OFFSET, + 0, +]; export { ASCII_REGEX, diff --git a/packages/sdk/src/ethereum.ts b/packages/sdk/src/ethereum.ts index d1383bad..15f2ae82 100644 --- a/packages/sdk/src/ethereum.ts +++ b/packages/sdk/src/ethereum.ts @@ -7,21 +7,48 @@ import cbor from 'cbor'; import bdec from 'cbor-bigdecimal'; import { Hash } from 'ox'; import secp256k1 from 'secp256k1'; -import { type Hex, type TransactionSerializable, hexToNumber, serializeTransaction } from 'viem'; -import { ASCII_REGEX, EXTERNAL, HANDLE_LARGER_CHAIN_ID, MAX_CHAIN_ID_BYTES, ethMsgProtocol } from './constants'; +import { + type Hex, + type TransactionSerializable, + hexToNumber, + serializeTransaction, +} from 'viem'; +import { + ASCII_REGEX, + EXTERNAL, + HANDLE_LARGER_CHAIN_ID, + MAX_CHAIN_ID_BYTES, + ethMsgProtocol, +} from './constants'; import { buildGenericSigningMsgRequest } from './genericSigning'; import { LatticeSignSchema } from './protocol'; import { type FlexibleTransaction, TransactionSchema } from './schemas'; -import { type FirmwareConstants, type SigningPath, TRANSACTION_TYPE, type TransactionRequest } from './types'; -import { buildSignerPathBuf, convertRecoveryToV, ensureHexBuffer, fixLen, isAsciiStr, splitFrames } from './util'; +import { + type FirmwareConstants, + type SigningPath, + TRANSACTION_TYPE, + type TransactionRequest, +} from './types'; +import { + buildSignerPathBuf, + convertRecoveryToV, + ensureHexBuffer, + fixLen, + isAsciiStr, + splitFrames, +} from './util'; const { ecdsaRecover } = secp256k1; bdec(cbor); const buildEthereumMsgRequest = (input) => { - if (!input.payload || !input.protocol || !input.signerPath) throw new Error('You must provide `payload`, `signerPath`, and `protocol` arguments in the messsage request'); - if (input.signerPath.length > 5 || input.signerPath.length < 2) throw new Error('Please provide a signer path with 2-5 indices'); + if (!input.payload || !input.protocol || !input.signerPath) + throw new Error( + 'You must provide `payload`, `signerPath`, and `protocol` arguments in the messsage request', + ); + if (input.signerPath.length > 5 || input.signerPath.length < 2) + throw new Error('Please provide a signer path with 2-5 indices'); const req = { schema: LatticeSignSchema.ethereumMsg, payload: null, @@ -32,7 +59,10 @@ const buildEthereumMsgRequest = (input) => { case 'signPersonal': return buildPersonalSignRequest(req, input); case 'eip712': - if (!input.fwConstants.eip712Supported) throw new Error('EIP712 is not supported by your Lattice firmware version. Please upgrade.'); + if (!input.fwConstants.eip712Supported) + throw new Error( + 'EIP712 is not supported by your Lattice firmware version. Please upgrade.', + ); return buildEIP712Request(req, input); default: throw new Error('Unsupported protocol'); @@ -45,7 +75,13 @@ const validateEthereumMsgResponse = (res, req) => { if (input.protocol === 'signPersonal') { // NOTE: We are currently hardcoding networkID=1 and useEIP155=false but these // may be configurable in future versions - const hash = prehash ? prehash : Buffer.from(Hash.keccak256(Buffer.concat([get_personal_sign_prefix(msg.length), msg]))); + const hash = prehash + ? prehash + : Buffer.from( + Hash.keccak256( + Buffer.concat([get_personal_sign_prefix(msg.length), msg]), + ), + ); // Get recovery param with a `v` value of [27,28] by setting `useEIP155=false` return addRecoveryParam(hash, sig, signer, { chainId: 1, @@ -56,13 +92,21 @@ const validateEthereumMsgResponse = (res, req) => { // This payload has been parsed with forJSParser=true, converting all numbers // to the format that TypedDataUtils.eip712Hash expects const rawPayloadForHashing = req.validationPayload || req.input.payload; - const payloadForHashing = req.validationPayload ? cloneTypedDataPayload(req.validationPayload) : normalizeTypedDataForHashing(rawPayloadForHashing); - const encoded = TypedDataUtils.eip712Hash(payloadForHashing, SignTypedDataVersion.V4); + const payloadForHashing = req.validationPayload + ? cloneTypedDataPayload(req.validationPayload) + : normalizeTypedDataForHashing(rawPayloadForHashing); + const encoded = TypedDataUtils.eip712Hash( + payloadForHashing, + SignTypedDataVersion.V4, + ); const digest = prehash ? prehash : encoded; // Parse chainId - it could be a number, hex string, decimal string, or bigint - let chainId = input.payload.domain?.chainId || payloadForHashing.domain?.chainId; + let chainId = + input.payload.domain?.chainId || payloadForHashing.domain?.chainId; if (typeof chainId === 'string') { - chainId = chainId.startsWith('0x') ? Number.parseInt(chainId, 16) : Number.parseInt(chainId, 10); + chainId = chainId.startsWith('0x') + ? Number.parseInt(chainId, 16) + : Number.parseInt(chainId, 10); } else if (typeof chainId === 'bigint') { chainId = Number(chainId); } @@ -83,7 +127,10 @@ function normalizeTypedDataForHashing(value: any): any { if (/^0x[0-9a-fA-F]+$/.test(trimmed)) { try { const asBigInt = BigInt(trimmed); - if (asBigInt <= BigInt(Number.MAX_SAFE_INTEGER) && asBigInt >= BigInt(Number.MIN_SAFE_INTEGER)) { + if ( + asBigInt <= BigInt(Number.MAX_SAFE_INTEGER) && + asBigInt >= BigInt(Number.MIN_SAFE_INTEGER) + ) { return Number(asBigInt); } return asBigInt.toString(10); @@ -104,7 +151,14 @@ function normalizeTypedDataForHashing(value: any): any { return Number.isSafeInteger(asNumber) ? asNumber : value.toString(10); } - if (value && typeof value === 'object' && typeof value.toString === 'function' && value.constructor && value.constructor.name === 'BN' && typeof value.toArray === 'function') { + if ( + value && + typeof value === 'object' && + typeof value.toString === 'function' && + value.constructor && + value.constructor.name === 'BN' && + typeof value.toArray === 'function' + ) { const str = value.toString(10); const asNumber = Number(str); return Number.isSafeInteger(asNumber) ? asNumber : str; @@ -153,7 +207,12 @@ function basicTypedDataClone(value: T): T { if (BN.isBigNumber(value)) { return new BN(value) as T; } - if (value && typeof value === 'object' && (value as { constructor?: { name?: string } }).constructor?.name === 'BN' && typeof (value as { clone?: () => unknown }).clone === 'function') { + if ( + value && + typeof value === 'object' && + (value as { constructor?: { name?: string } }).constructor?.name === 'BN' && + typeof (value as { clone?: () => unknown }).clone === 'function' + ) { return (value as unknown as { clone: () => unknown }).clone() as T; } if (value instanceof Date) { @@ -168,20 +227,32 @@ function basicTypedDataClone(value: T): T { type StructuredCloneFn = (value: T, transfer?: unknown) => T; const structuredCloneFn: StructuredCloneFn | null = - typeof globalThis !== 'undefined' && typeof (globalThis as { structuredClone?: unknown }).structuredClone === 'function' ? (globalThis as { structuredClone: StructuredCloneFn }).structuredClone : null; + typeof globalThis !== 'undefined' && + typeof (globalThis as { structuredClone?: unknown }).structuredClone === + 'function' + ? (globalThis as { structuredClone: StructuredCloneFn }).structuredClone + : null; const buildEthereumTxRequest = (data) => { try { let { chainId = 1 } = data; const { signerPath, eip155 = null, fwConstants, type = null } = data; - const { contractDeployKey, extraDataFrameSz, extraDataMaxFrames, prehashAllowed } = fwConstants; + const { + contractDeployKey, + extraDataFrameSz, + extraDataMaxFrames, + prehashAllowed, + } = fwConstants; const EXTRA_DATA_ALLOWED = extraDataFrameSz > 0 && extraDataMaxFrames > 0; const MAX_BASE_DATA_SZ = fwConstants.ethMaxDataSz; const VAR_PATH_SZ = fwConstants.varAddrPathSzAllowed; // Sanity checks: // There are a handful of named chains we allow the user to reference (`chainIds`) // Custom chainIDs should be either numerical or hex strings - if (typeof chainId !== 'number' && isValidChainIdHexNumStr(chainId) === false) { + if ( + typeof chainId !== 'number' && + isValidChainIdHexNumStr(chainId) === false + ) { chainId = chainIds[chainId]; } // If this was not a custom chainID and we cannot find the name of it, exit @@ -191,15 +262,22 @@ const buildEthereumTxRequest = (data) => { // Is this a contract deployment? if (data.to === null && !contractDeployKey) { - throw new Error('Contract deployment not supported. Please update your Lattice firmware.'); + throw new Error( + 'Contract deployment not supported. Please update your Lattice firmware.', + ); } const isDeployment = data.to === null && contractDeployKey; // We support eip1559 and eip2930 types (as well as legacy) - const eip1559IsAllowed = fwConstants.allowedEthTxTypes && fwConstants.allowedEthTxTypes.indexOf(2) > -1; - const eip2930IsAllowed = fwConstants.allowedEthTxTypes && fwConstants.allowedEthTxTypes.indexOf(1) > -1; + const eip1559IsAllowed = + fwConstants.allowedEthTxTypes && + fwConstants.allowedEthTxTypes.indexOf(2) > -1; + const eip2930IsAllowed = + fwConstants.allowedEthTxTypes && + fwConstants.allowedEthTxTypes.indexOf(1) > -1; const isEip1559 = eip1559IsAllowed && (type === 2 || type === 'eip1559'); const isEip2930 = eip2930IsAllowed && (type === 1 || type === 'eip2930'); - if (type !== null && !isEip1559 && !isEip2930) throw new Error('Unsupported Ethereum transaction type'); + if (type !== null && !isEip1559 && !isEip2930) + throw new Error('Unsupported Ethereum transaction type'); // Determine if we should use EIP155 given the chainID. // If we are explicitly told to use eip155, we will use it. Otherwise, // we will look up if the specified chainId is associated with a chain @@ -248,7 +326,10 @@ const buildEthereumTxRequest = (data) => { let maxPriorityFeePerGasBytes: Buffer; let maxFeePerGasBytes: Buffer; if (isEip1559) { - if (!data.maxPriorityFeePerGas) throw new Error('EIP1559 transactions must include `maxPriorityFeePerGas`'); + if (!data.maxPriorityFeePerGas) + throw new Error( + 'EIP1559 transactions must include `maxPriorityFeePerGas`', + ); maxPriorityFeePerGasBytes = ensureHexBuffer(data.maxPriorityFeePerGas); rawTx.push(maxPriorityFeePerGasBytes); maxFeePerGasBytes = ensureHexBuffer(data.maxFeePerGas); @@ -305,7 +386,8 @@ const buildEthereumTxRequest = (data) => { if (useChainIdBuffer(chainId) === true) { chainIdBuf = getChainIdBuf(chainId); chainIdBufSz = chainIdBuf.length; - if (chainIdBufSz > MAX_CHAIN_ID_BYTES) throw new Error('ChainID provided is too large.'); + if (chainIdBufSz > MAX_CHAIN_ID_BYTES) + throw new Error('ChainID provided is too large.'); // Signal to Lattice firmware that it needs to read the chainId from the tx.data buffer txReqPayload.writeUInt8(HANDLE_LARGER_CHAIN_ID, off); off++; @@ -353,8 +435,12 @@ const buildEthereumTxRequest = (data) => { if (isEip1559) { txReqPayload.writeUInt8(2, off); off += 1; // Eip1559 type enum value - if (maxPriorityFeePerGasBytes.length > 8) throw new Error('maxPriorityFeePerGasBytes too large'); - maxPriorityFeePerGasBytes.copy(txReqPayload, off + (8 - maxPriorityFeePerGasBytes.length)); + if (maxPriorityFeePerGasBytes.length > 8) + throw new Error('maxPriorityFeePerGasBytes too large'); + maxPriorityFeePerGasBytes.copy( + txReqPayload, + off + (8 - maxPriorityFeePerGasBytes.length), + ); off += 8; // Skip EIP1559 params } else if (isEip2930) { txReqPayload.writeUInt8(1, off); @@ -382,15 +468,27 @@ const buildEthereumTxRequest = (data) => { if (dataSz > MAX_BASE_DATA_SZ) { // Determine sizes and run through sanity checks const totalSz = dataSz + chainIdExtraSz; - const maxSzAllowed = MAX_BASE_DATA_SZ + extraDataMaxFrames * extraDataFrameSz; + const maxSzAllowed = + MAX_BASE_DATA_SZ + extraDataMaxFrames * extraDataFrameSz; if (prehashAllowed && totalSz > maxSzAllowed) { // If this payload is too large to send, but the Lattice allows a prehashed message, do that - prehash = Buffer.from(Hash.keccak256(get_rlp_encoded_preimage(rawTx, type))); + prehash = Buffer.from( + Hash.keccak256(get_rlp_encoded_preimage(rawTx, type)), + ); } else { - if (!EXTRA_DATA_ALLOWED || (EXTRA_DATA_ALLOWED && totalSz > maxSzAllowed)) throw new Error(`Data field too large (got ${dataBytes.length}; must be <=${maxSzAllowed - chainIdExtraSz} bytes)`); + if ( + !EXTRA_DATA_ALLOWED || + (EXTRA_DATA_ALLOWED && totalSz > maxSzAllowed) + ) + throw new Error( + `Data field too large (got ${dataBytes.length}; must be <=${maxSzAllowed - chainIdExtraSz} bytes)`, + ); // Split overflow data into extraData frames - const frames = splitFrames(dataToCopy.slice(MAX_BASE_DATA_SZ), extraDataFrameSz); + const frames = splitFrames( + dataToCopy.slice(MAX_BASE_DATA_SZ), + extraDataFrameSz, + ); frames.forEach((frame) => { const szLE = Buffer.alloc(4); szLE.writeUInt32LE(frame.length, 0); @@ -400,7 +498,9 @@ const buildEthereumTxRequest = (data) => { } else if (PREHASH_UNSUPPORTED) { // If something is unsupported in firmware but we want to allow such transactions, // we prehash the message here. - prehash = Buffer.from(Hash.keccak256(get_rlp_encoded_preimage(rawTx, type))); + prehash = Buffer.from( + Hash.keccak256(get_rlp_encoded_preimage(rawTx, type)), + ); } // Write the data size (does *NOT* include the chainId buffer, if that exists) @@ -451,7 +551,9 @@ function stripZeros(a) { // and attah the full signature to the end of the transaction payload const buildEthRawTx = (tx, sig, address) => { // RLP-encode the data we sent to the lattice - const hash = Buffer.from(Hash.keccak256(get_rlp_encoded_preimage(tx.rawTx, tx.type))); + const hash = Buffer.from( + Hash.keccak256(get_rlp_encoded_preimage(tx.rawTx, tx.type)), + ); const newSig = addRecoveryParam(hash, sig, address, tx); // Use the signature to generate a new raw transaction payload // Strip the last 3 items and replace them with signature components @@ -462,9 +564,14 @@ const buildEthRawTx = (tx, sig, address) => { newRawTx.push(stripZeros(newSig.r)); newRawTx.push(stripZeros(newSig.s)); const rlpEncoded = Buffer.from(RLP.encode(newRawTx)); - const rlpEncodedWithSig = tx.type ? Buffer.concat([Buffer.from([tx.type]), rlpEncoded]) : rlpEncoded; - - if (tx.type === TRANSACTION_TYPE.EIP7702_AUTH || tx.type === TRANSACTION_TYPE.EIP7702_AUTH_LIST) { + const rlpEncodedWithSig = tx.type + ? Buffer.concat([Buffer.from([tx.type]), rlpEncoded]) + : rlpEncoded; + + if ( + tx.type === TRANSACTION_TYPE.EIP7702_AUTH || + tx.type === TRANSACTION_TYPE.EIP7702_AUTH_LIST + ) { // For EIP-7702 transactions, we return just the hex string return rlpEncodedWithSig.toString('hex'); } @@ -477,8 +584,11 @@ export function addRecoveryParam(hashBuf, sig, address, txData = {}) { try { // Rebuild the keccak256 hash here so we can `ecrecover` const hash = new Uint8Array(hashBuf); - const expectedAddrBuf = Buffer.isBuffer(address) ? address : ensureHexBuffer(address, false); - if (expectedAddrBuf.length !== 20) throw new Error('Invalid signer address provided.'); + const expectedAddrBuf = Buffer.isBuffer(address) + ? address + : ensureHexBuffer(address, false); + if (expectedAddrBuf.length !== 20) + throw new Error('Invalid signer address provided.'); let v = 0; // Fix signature componenet lengths to 32 bytes each const r = fixLen(sig.r, 32); @@ -507,7 +617,9 @@ export function addRecoveryParam(hashBuf, sig, address, txData = {}) { return sig; } else { // If neither is a match, we should return an error - throw new Error(`Invalid Ethereum signature returned. expected=${expectedAddrHex}, recovered=${recoveredAddrs.join(',')}`); + throw new Error( + `Invalid Ethereum signature returned. expected=${expectedAddrHex}, recovered=${recoveredAddrs.join(',')}`, + ); } } catch (err) { if (err instanceof Error) throw err; @@ -519,7 +631,10 @@ export function addRecoveryParam(hashBuf, sig, address, txData = {}) { * Normalize Lattice signature components to viem format. * Handles Buffer v value conversion and yParity vs v for different transaction types. */ -export function normalizeLatticeSignature(latticeResult: any, originalTx: TransactionSerializable) { +export function normalizeLatticeSignature( + latticeResult: any, + originalTx: TransactionSerializable, +) { // Convert Buffer v value to number let vValue: number; if (Buffer.isBuffer(latticeResult.sig.v)) { @@ -578,7 +693,8 @@ export function normalizeLatticeSignature(latticeResult: any, originalTx: Transa } // Convert an RLP-serialized transaction (plus signature) into a transaction hash -const hashTransaction = (serializedTx) => Hash.keccak256(Buffer.from(serializedTx, 'hex')); +const hashTransaction = (serializedTx) => + Hash.keccak256(Buffer.from(serializedTx, 'hex')); // Returns address string given public key buffer function pubToAddrStr(pub) { @@ -688,9 +804,14 @@ function buildPersonalSignRequest(req, input) { if (typeof input.payload === 'string') { if (input.payload.slice(0, 2) === '0x') { payload = ensureHexBuffer(input.payload); - displayHex = false === ASCII_REGEX.test(Buffer.from(input.payload.slice(2), 'hex').toString()); + displayHex = + false === + ASCII_REGEX.test(Buffer.from(input.payload.slice(2), 'hex').toString()); } else { - if (false === isAsciiStr(input.payload)) throw new Error('Currently, the Lattice can only display ASCII strings.'); + if (false === isAsciiStr(input.payload)) + throw new Error( + 'Currently, the Lattice can only display ASCII strings.', + ); payload = Buffer.from(input.payload); } } else if (typeof input.displayHex === 'boolean') { @@ -706,7 +827,8 @@ function buildPersonalSignRequest(req, input) { displayHex = false === ASCII_REGEX.test(input.payload.toString()); } const fwConst = input.fwConstants; - let maxSzAllowed = MAX_BASE_MSG_SZ + fwConst.extraDataMaxFrames * fwConst.extraDataFrameSz; + let maxSzAllowed = + MAX_BASE_MSG_SZ + fwConst.extraDataMaxFrames * fwConst.extraDataFrameSz; if (fwConst.personalSignHeaderSz) { // Account for the personal_sign header string maxSzAllowed -= fwConst.personalSignHeaderSz; @@ -717,7 +839,11 @@ function buildPersonalSignRequest(req, input) { off += 1; req.payload.writeUInt16LE(payload.length, off); off += 2; - const prehash = Buffer.from(Hash.keccak256(Buffer.concat([get_personal_sign_prefix(payload.length), payload]))); + const prehash = Buffer.from( + Hash.keccak256( + Buffer.concat([get_personal_sign_prefix(payload.length), payload]), + ), + ); prehash.copy(req.payload, off); req.prehash = prehash; } else { @@ -737,7 +863,8 @@ function buildPersonalSignRequest(req, input) { } function buildEIP712Request(req, input) { - const { ethMaxMsgSz, varAddrPathSzAllowed, eip712MaxTypeParams } = input.fwConstants; + const { ethMaxMsgSz, varAddrPathSzAllowed, eip712MaxTypeParams } = + input.fwConstants; const { TYPED_DATA } = ethMsgProtocol; const L = 24 + ethMaxMsgSz + 4; let off = 0; @@ -745,14 +872,22 @@ function buildEIP712Request(req, input) { req.payload.writeUInt8(TYPED_DATA.enumIdx, 0); off += 1; // Write the signer path - const signerPathBuf = buildSignerPathBuf(input.signerPath, varAddrPathSzAllowed); + const signerPathBuf = buildSignerPathBuf( + input.signerPath, + varAddrPathSzAllowed, + ); signerPathBuf.copy(req.payload, off); off += signerPathBuf.length; // Parse/clean the EIP712 payload, serialize with CBOR, and write to the payload const data = cloneTypedDataPayload(input.payload); - if (!data.primaryType || !data.types[data.primaryType]) throw new Error('primaryType must be specified and the type must be included.'); - if (!data.message || !data.domain) throw new Error('message and domain must be specified.'); - if (0 > Object.keys(data.types).indexOf('EIP712Domain')) throw new Error('EIP712Domain type must be defined.'); + if (!data.primaryType || !data.types[data.primaryType]) + throw new Error( + 'primaryType must be specified and the type must be included.', + ); + if (!data.message || !data.domain) + throw new Error('message and domain must be specified.'); + if (0 > Object.keys(data.types).indexOf('EIP712Domain')) + throw new Error('EIP712Domain type must be defined.'); // Parse the payload to ensure we have valid EIP712 data types and that // they are encoded such that Lattice firmware can parse them. // We need two different encodings: one to send to the Lattice in a format that plays @@ -761,17 +896,33 @@ function buildEIP712Request(req, input) { // IMPORTANT: Create a new object for the validation payload instead of modifying input.payload // in place, so that validation uses the correctly formatted data const validationPayload = cloneTypedDataPayload(data); - validationPayload.message = parseEIP712Msg(cloneTypedDataPayload(data.message), cloneTypedDataPayload(data.primaryType), cloneTypedDataPayload(data.types), true); - validationPayload.domain = parseEIP712Msg(cloneTypedDataPayload(data.domain), 'EIP712Domain', cloneTypedDataPayload(data.types), true); + validationPayload.message = parseEIP712Msg( + cloneTypedDataPayload(data.message), + cloneTypedDataPayload(data.primaryType), + cloneTypedDataPayload(data.types), + true, + ); + validationPayload.domain = parseEIP712Msg( + cloneTypedDataPayload(data.domain), + 'EIP712Domain', + cloneTypedDataPayload(data.types), + true, + ); // Store the validation payload separately without modifying input.payload req.validationPayload = validationPayload; data.domain = parseEIP712Msg(data.domain, 'EIP712Domain', data.types, false); - data.message = parseEIP712Msg(data.message, data.primaryType, data.types, false); + data.message = parseEIP712Msg( + data.message, + data.primaryType, + data.types, + false, + ); // Now build the message to be sent to the Lattice const payload = Buffer.from(cbor.encode(data)); const fwConst = input.fwConstants; - const maxSzAllowed = ethMaxMsgSz + fwConst.extraDataMaxFrames * fwConst.extraDataFrameSz; + const maxSzAllowed = + ethMaxMsgSz + fwConst.extraDataMaxFrames * fwConst.extraDataFrameSz; // Determine if we need to prehash let shouldPrehash = payload.length > maxSzAllowed; Object.keys(data.types).forEach((k) => { @@ -783,7 +934,10 @@ function buildEIP712Request(req, input) { // If this payload is too large to send, but the Lattice allows a prehashed message, do that req.payload.writeUInt16LE(payload.length, off); off += 2; - const prehash = TypedDataUtils.eip712Hash(req.validationPayload, SignTypedDataVersion.V4); + const prehash = TypedDataUtils.eip712Hash( + req.validationPayload, + SignTypedDataVersion.V4, + ); const prehashBuf = Buffer.from(prehash); prehashBuf.copy(req.payload, off); req.prehash = prehash; @@ -801,17 +955,28 @@ function buildEIP712Request(req, input) { } function getExtraData(payload, input) { - const { ethMaxMsgSz, extraDataFrameSz, extraDataMaxFrames } = input.fwConstants; + const { ethMaxMsgSz, extraDataFrameSz, extraDataMaxFrames } = + input.fwConstants; const MAX_BASE_MSG_SZ = ethMaxMsgSz; const EXTRA_DATA_ALLOWED = extraDataFrameSz > 0 && extraDataMaxFrames > 0; const extraDataPayloads = []; if (payload.length > MAX_BASE_MSG_SZ) { // Determine sizes and run through sanity checks - const maxSzAllowed = MAX_BASE_MSG_SZ + extraDataMaxFrames * extraDataFrameSz; - if (!EXTRA_DATA_ALLOWED) throw new Error(`Your message is ${payload.length} bytes, but can only be a maximum of ${MAX_BASE_MSG_SZ}`); - else if (EXTRA_DATA_ALLOWED && payload.length > maxSzAllowed) throw new Error(`Your message is ${payload.length} bytes, but can only be a maximum of ${maxSzAllowed}`); + const maxSzAllowed = + MAX_BASE_MSG_SZ + extraDataMaxFrames * extraDataFrameSz; + if (!EXTRA_DATA_ALLOWED) + throw new Error( + `Your message is ${payload.length} bytes, but can only be a maximum of ${MAX_BASE_MSG_SZ}`, + ); + else if (EXTRA_DATA_ALLOWED && payload.length > maxSzAllowed) + throw new Error( + `Your message is ${payload.length} bytes, but can only be a maximum of ${maxSzAllowed}`, + ); // Split overflow data into extraData frames - const frames = splitFrames(payload.slice(MAX_BASE_MSG_SZ), extraDataFrameSz); + const frames = splitFrames( + payload.slice(MAX_BASE_MSG_SZ), + extraDataFrameSz, + ); frames.forEach((frame) => { const szLE = Buffer.alloc(4); szLE.writeUInt32LE(frame.length, 0); @@ -825,7 +990,9 @@ function parseEIP712Msg(msg, typeName, types, forJSParser = false) { const type = types[typeName]; type.forEach((item) => { const isArrayType = item.type.indexOf('[') > -1; - const singularType = isArrayType ? item.type.slice(0, item.type.indexOf('[')) : item.type; + const singularType = isArrayType + ? item.type.slice(0, item.type.indexOf('[')) + : item.type; const isCustomType = Object.keys(types).indexOf(singularType) > -1; if (isCustomType && Array.isArray(msg)) { // For custom types we need to jump into the `msg` using the key (name of type) and @@ -834,11 +1001,21 @@ function parseEIP712Msg(msg, typeName, types, forJSParser = false) { // elementary (i.e. non-custom) type. // For arrays, we need to loop through each message item. for (let i = 0; i < msg.length; i++) { - msg[i][item.name] = parseEIP712Msg(msg[i][item.name], singularType, types, forJSParser); + msg[i][item.name] = parseEIP712Msg( + msg[i][item.name], + singularType, + types, + forJSParser, + ); } } else if (isCustomType) { // Not an array means we can jump directly into the sub-struct to convert - msg[item.name] = parseEIP712Msg(msg[item.name], singularType, types, forJSParser); + msg[item.name] = parseEIP712Msg( + msg[item.name], + singularType, + types, + forJSParser, + ); } else if (Array.isArray(msg)) { // If we have an array for this particular type and the type we are parsing // is *not* a custom type, loop through the array elements and convert the types. @@ -848,22 +1025,38 @@ function parseEIP712Msg(msg, typeName, types, forJSParser = false) { // This code is not reachable for custom types so we assume these are arrays of // elementary types. for (let j = 0; j < msg[i][item.name].length; j++) { - msg[i][item.name][j] = parseEIP712Item(msg[i][item.name][j], singularType, forJSParser); + msg[i][item.name][j] = parseEIP712Item( + msg[i][item.name][j], + singularType, + forJSParser, + ); } } else { // Non-arrays parse + replace one value for the elementary type - msg[i][item.name] = parseEIP712Item(msg[i][item.name], singularType, forJSParser); + msg[i][item.name] = parseEIP712Item( + msg[i][item.name], + singularType, + forJSParser, + ); } } } else if (isArrayType) { // If we have an elementary array type and a non-array message level, //loop through the array and parse + replace each item individually. for (let i = 0; i < msg[item.name].length; i++) { - msg[item.name][i] = parseEIP712Item(msg[item.name][i], singularType, forJSParser); + msg[item.name][i] = parseEIP712Item( + msg[item.name][i], + singularType, + forJSParser, + ); } } else { // If this is a singular elementary type, simply parse + replace. - msg[item.name] = parseEIP712Item(msg[item.name], singularType, forJSParser); + msg[item.name] = parseEIP712Item( + msg[item.name], + singularType, + forJSParser, + ); } }); @@ -886,7 +1079,8 @@ function parseEIP712Item(data, type, forJSParser = false) { if (data.length === 0) { data = Buffer.alloc(nBytes); } - if (data.length !== nBytes) throw new Error(`Expected ${type} type, but got ${data.length} bytes`); + if (data.length !== nBytes) + throw new Error(`Expected ${type} type, but got ${data.length} bytes`); if (forJSParser) { // For EIP712 encoding module it's easier to encode hex strings data = `0x${data.toString('hex')}`; @@ -898,12 +1092,19 @@ function parseEIP712Item(data, type, forJSParser = false) { if (data.length === 0) { data = Buffer.alloc(20); } - if (data.length !== 20) throw new Error(`Address type must be 20 bytes, but got ${data.length} bytes`); + if (data.length !== 20) + throw new Error( + `Address type must be 20 bytes, but got ${data.length} bytes`, + ); // For EIP712 encoding module it's easier to encode hex strings if (forJSParser) { data = `0x${data.toString('hex')}`; } - } else if (ethMsgProtocol.TYPED_DATA.typeCodes[type] && type.indexOf('uint') === -1 && type.indexOf('int') > -1) { + } else if ( + ethMsgProtocol.TYPED_DATA.typeCodes[type] && + type.indexOf('uint') === -1 && + type.indexOf('int') > -1 + ) { // Handle signed integers using bignumber.js directly if (forJSParser) { // For EIP712 encoding in this module we need hex strings for signed ints too @@ -921,7 +1122,10 @@ function parseEIP712Item(data, type, forJSParser = false) { // worked (borc is a supposedly "browser compatible" version of cbor) data = new BN(data); } - } else if (ethMsgProtocol.TYPED_DATA.typeCodes[type] && (type.indexOf('uint') > -1 || type.indexOf('int') > -1)) { + } else if ( + ethMsgProtocol.TYPED_DATA.typeCodes[type] && + (type.indexOf('uint') > -1 || type.indexOf('int') > -1) + ) { // For uints, convert to a buffer and do some sanity checking. // Note that we could probably just use bignumber.js directly as we do with // signed ints, but this code is battle tested and we don't want to change it. @@ -947,12 +1151,18 @@ function parseEIP712Item(data, type, forJSParser = false) { } function get_personal_sign_prefix(L) { - return Buffer.from(`\u0019Ethereum Signed Message:\n${L.toString()}`, 'utf-8'); + return Buffer.from( + `\u0019Ethereum Signed Message:\n${L.toString()}`, + 'utf-8', + ); } function get_rlp_encoded_preimage(rawTx, txType) { if (txType) { - return Buffer.concat([Buffer.from([txType]), Buffer.from(RLP.encode(rawTx))]); + return Buffer.concat([ + Buffer.from([txType]), + Buffer.from(RLP.encode(rawTx)), + ]); } else { return Buffer.from(RLP.encode(rawTx)); } @@ -970,7 +1180,9 @@ function get_rlp_encoded_preimage(rawTx, txType) { * hex strings, numbers, bigints). * @returns A `viem`-compatible `TransactionSerializable` object. */ -export const normalizeToViemTransaction = (tx: unknown): TransactionSerializable => { +export const normalizeToViemTransaction = ( + tx: unknown, +): TransactionSerializable => { const parsed = TransactionSchema.parse(tx); return { @@ -983,9 +1195,13 @@ export const normalizeToViemTransaction = (tx: unknown): TransactionSerializable chainId: parsed.chainId, gasPrice: 'gasPrice' in parsed ? parsed.gasPrice : undefined, maxFeePerGas: 'maxFeePerGas' in parsed ? parsed.maxFeePerGas : undefined, - maxPriorityFeePerGas: 'maxPriorityFeePerGas' in parsed ? parsed.maxPriorityFeePerGas : undefined, + maxPriorityFeePerGas: + 'maxPriorityFeePerGas' in parsed + ? parsed.maxPriorityFeePerGas + : undefined, accessList: 'accessList' in parsed ? parsed.accessList : undefined, - authorizationList: 'authorizationList' in parsed ? parsed.authorizationList : undefined, + authorizationList: + 'authorizationList' in parsed ? parsed.authorizationList : undefined, }; }; @@ -993,7 +1209,9 @@ export const normalizeToViemTransaction = (tx: unknown): TransactionSerializable * Convert Ethereum transaction to serialized bytes for generic signing. * Bridge function for firmware v0.15.0+ which removed legacy ETH signing paths. */ -const convertEthereumTransactionToGenericRequest = (req: FlexibleTransaction) => { +const convertEthereumTransactionToGenericRequest = ( + req: FlexibleTransaction, +) => { // Use the unified normalization and serialization pipeline. // 1. Normalize the potentially varied input to a standard viem format. const viemTx = normalizeToViemTransaction(req); @@ -1012,7 +1230,9 @@ type EthereumGenericSigningRequestParams = FlexibleTransaction & { * Build complete generic signing request for Ethereum transactions. * One-step function combining transaction conversion and generic signing setup. */ -export const buildEthereumGenericSigningRequest = (req: EthereumGenericSigningRequestParams) => { +export const buildEthereumGenericSigningRequest = ( + req: EthereumGenericSigningRequestParams, +) => { const { fwConstants, signerPath, ...txData } = req; const payload = convertEthereumTransactionToGenericRequest(txData); @@ -1034,8 +1254,13 @@ export const buildEthereumGenericSigningRequest = (req: EthereumGenericSigningRe * @returns The serialized transaction as a hex string */ export function serializeEIP7702Transaction(tx: TransactionRequest): Hex { - if (tx.type !== TRANSACTION_TYPE.EIP7702_AUTH_LIST && tx.type !== TRANSACTION_TYPE.EIP7702_AUTH) { - throw new Error(`Only EIP-7702 auth transactions (type ${TRANSACTION_TYPE.EIP7702_AUTH}) and auth-list transactions (type ${TRANSACTION_TYPE.EIP7702_AUTH_LIST}) are supported`); + if ( + tx.type !== TRANSACTION_TYPE.EIP7702_AUTH_LIST && + tx.type !== TRANSACTION_TYPE.EIP7702_AUTH + ) { + throw new Error( + `Only EIP-7702 auth transactions (type ${TRANSACTION_TYPE.EIP7702_AUTH}) and auth-list transactions (type ${TRANSACTION_TYPE.EIP7702_AUTH_LIST}) are supported`, + ); } // Type guard to ensure we have an EIP7702 transaction with appropriate authorization data @@ -1043,14 +1268,18 @@ export function serializeEIP7702Transaction(tx: TransactionRequest): Hex { const hasSingleAuth = 'authorization' in tx; if (!hasAuthList && !hasSingleAuth) { - throw new Error('Transaction does not have authorization or authorizationList property'); + throw new Error( + 'Transaction does not have authorization or authorizationList property', + ); } // For type 4 transactions, convert single authorization to array format let authorizationList: any[]; if (tx.type === TRANSACTION_TYPE.EIP7702_AUTH) { if (!hasSingleAuth) { - throw new Error('EIP-7702 auth transaction (type 4) must contain authorization property'); + throw new Error( + 'EIP-7702 auth transaction (type 4) must contain authorization property', + ); } authorizationList = [(tx as any).authorization]; } else { @@ -1058,19 +1287,29 @@ export function serializeEIP7702Transaction(tx: TransactionRequest): Hex { if (hasAuthList) { authorizationList = (tx as any).authorizationList; } else { - throw new Error('EIP-7702 auth list transaction (type 5) must contain authorizationList property'); + throw new Error( + 'EIP-7702 auth list transaction (type 5) must contain authorizationList property', + ); } } // Validate that all required fields exist - if (!authorizationList || !Array.isArray(authorizationList) || authorizationList.length === 0) { - throw new Error('EIP-7702 transaction must contain at least one authorization'); + if ( + !authorizationList || + !Array.isArray(authorizationList) || + authorizationList.length === 0 + ) { + throw new Error( + 'EIP-7702 transaction must contain at least one authorization', + ); } // Validate each authorization authorizationList.forEach((auth, index) => { if (!auth.address) { - throw new Error(`Authorization at index ${index} is missing a contract address`); + throw new Error( + `Authorization at index ${index} is missing a contract address`, + ); } }); @@ -1084,9 +1323,21 @@ export function serializeEIP7702Transaction(tx: TransactionRequest): Hex { type: 'eip7702' as const, chainId: tx.chainId, nonce: tx.nonce, - maxPriorityFeePerGas: typeof tx.maxPriorityFeePerGas === 'string' ? BigInt(tx.maxPriorityFeePerGas) : tx.maxPriorityFeePerGas, - maxFeePerGas: typeof tx.maxFeePerGas === 'string' ? BigInt(tx.maxFeePerGas) : tx.maxFeePerGas, - gas: typeof (tx as any).gas === 'string' ? BigInt((tx as any).gas) : (tx as any).gas || (typeof (tx as any).gasLimit === 'string' ? BigInt((tx as any).gasLimit) : (tx as any).gasLimit), + maxPriorityFeePerGas: + typeof tx.maxPriorityFeePerGas === 'string' + ? BigInt(tx.maxPriorityFeePerGas) + : tx.maxPriorityFeePerGas, + maxFeePerGas: + typeof tx.maxFeePerGas === 'string' + ? BigInt(tx.maxFeePerGas) + : tx.maxFeePerGas, + gas: + typeof (tx as any).gas === 'string' + ? BigInt((tx as any).gas) + : (tx as any).gas || + (typeof (tx as any).gasLimit === 'string' + ? BigInt((tx as any).gasLimit) + : (tx as any).gasLimit), to: tx.to as `0x${string}`, value: typeof tx.value === 'string' ? BigInt(tx.value) : tx.value, data: tx.data || '0x', @@ -1094,10 +1345,17 @@ export function serializeEIP7702Transaction(tx: TransactionRequest): Hex { // Create the Viem-formatted authorization // Ensure proper address handling with 0x prefix const address = auth.address || ''; - const addressStr = typeof address === 'string' ? (address.startsWith('0x') ? address : `0x${address}`) : '0x'; + const addressStr = + typeof address === 'string' + ? address.startsWith('0x') + ? address + : `0x${address}` + : '0x'; if (!addressStr || addressStr === '0x') { - throw new Error(`Authorization at index ${idx} is missing a valid address`); + throw new Error( + `Authorization at index ${idx} is missing a valid address`, + ); } // Handle viem's SignedAuthorization format @@ -1116,7 +1374,16 @@ export function serializeEIP7702Transaction(tx: TransactionRequest): Hex { address: addressStr as `0x${string}`, nonce: BigInt(auth.nonce || 0), signature: { - yParity: typeof auth.yParity === 'number' ? auth.yParity : typeof auth.yParity === 'string' ? (auth.yParity === '0x01' || auth.yParity === '0x1' || auth.yParity === '1' ? 1 : 0) : 0, + yParity: + typeof auth.yParity === 'number' + ? auth.yParity + : typeof auth.yParity === 'string' + ? auth.yParity === '0x01' || + auth.yParity === '0x1' || + auth.yParity === '1' + ? 1 + : 0 + : 0, r: auth.r || '0x0', s: auth.s || '0x0', }, @@ -1129,7 +1396,12 @@ export function serializeEIP7702Transaction(tx: TransactionRequest): Hex { } export const isEip7702Transaction = (tx: TransactionRequest): boolean => { - return typeof tx === 'object' && 'type' in tx && (tx.type === TRANSACTION_TYPE.EIP7702_AUTH_LIST || tx.type === TRANSACTION_TYPE.EIP7702_AUTH); + return ( + typeof tx === 'object' && + 'type' in tx && + (tx.type === TRANSACTION_TYPE.EIP7702_AUTH_LIST || + tx.type === TRANSACTION_TYPE.EIP7702_AUTH) + ); }; export default { diff --git a/packages/sdk/src/functions/addKvRecords.ts b/packages/sdk/src/functions/addKvRecords.ts index 34f7eed2..0ce54783 100644 --- a/packages/sdk/src/functions/addKvRecords.ts +++ b/packages/sdk/src/functions/addKvRecords.ts @@ -1,6 +1,17 @@ -import { LatticeSecureEncryptedRequestType, encryptedSecureRequest } from '../protocol'; -import { validateConnectedClient, validateKvRecord, validateKvRecords } from '../shared/validators'; -import type { AddKvRecordsRequestFunctionParams, FirmwareConstants, KVRecords } from '../types'; +import { + LatticeSecureEncryptedRequestType, + encryptedSecureRequest, +} from '../protocol'; +import { + validateConnectedClient, + validateKvRecord, + validateKvRecords, +} from '../shared/validators'; +import type { + AddKvRecordsRequestFunctionParams, + FirmwareConstants, + KVRecords, +} from '../types'; /** * `addKvRecords` takes in a set of key-value records and sends a request to add them to the @@ -8,8 +19,14 @@ import type { AddKvRecordsRequestFunctionParams, FirmwareConstants, KVRecords } * @category Lattice * @returns A callback with an error or null. */ -export async function addKvRecords({ client, records, type, caseSensitive }: AddKvRecordsRequestFunctionParams): Promise { - const { url, sharedSecret, ephemeralPub, fwConstants } = validateConnectedClient(client); +export async function addKvRecords({ + client, + records, + type, + caseSensitive, +}: AddKvRecordsRequestFunctionParams): Promise { + const { url, sharedSecret, ephemeralPub, fwConstants } = + validateConnectedClient(client); validateAddKvRequest({ records, fwConstants }); // Build the data for this request @@ -60,7 +77,10 @@ export const encodeAddKvRecordsRequest = ({ payload.writeUInt8(Object.keys(records).length, 0); let off = 1; Object.entries(records).forEach(([_key, _val]) => { - const { key, val } = validateKvRecord({ key: _key, val: _val }, fwConstants); + const { key, val } = validateKvRecord( + { key: _key, val: _val }, + fwConstants, + ); // Skip the ID portion. This will get added by firmware. payload.writeUInt32LE(0, off); off += 4; diff --git a/packages/sdk/src/functions/connect.ts b/packages/sdk/src/functions/connect.ts index 10e7ca39..bd03b931 100644 --- a/packages/sdk/src/functions/connect.ts +++ b/packages/sdk/src/functions/connect.ts @@ -1,11 +1,22 @@ import { ProtocolConstants, connectSecureRequest } from '../protocol'; import { doesFetchWalletsOnLoad } from '../shared/predicates'; import { getSharedSecret, parseWallets } from '../shared/utilities'; -import { validateBaseUrl, validateDeviceId, validateKey } from '../shared/validators'; -import type { ActiveWallets, ConnectRequestFunctionParams, KeyPair } from '../types'; +import { + validateBaseUrl, + validateDeviceId, + validateKey, +} from '../shared/validators'; +import type { + ActiveWallets, + ConnectRequestFunctionParams, + KeyPair, +} from '../types'; import { aes256_decrypt, getP256KeyPairFromPub } from '../util'; -export async function connect({ client, id }: ConnectRequestFunctionParams): Promise { +export async function connect({ + client, + id, +}: ConnectRequestFunctionParams): Promise { const { deviceId, key, baseUrl } = validateConnectRequest({ deviceId: id, // @ts-expect-error - private access @@ -22,7 +33,8 @@ export async function connect({ client, id }: ConnectRequestFunctionParams): Pro // Decode response data params. // Response payload data is *not* encrypted. - const { isPaired, fwVersion, activeWallets, ephemeralPub } = await decodeConnectResponse(respPayloadData, key); + const { isPaired, fwVersion, activeWallets, ephemeralPub } = + await decodeConnectResponse(respPayloadData, key); // Update client state with response data @@ -92,7 +104,8 @@ export const decodeConnectResponse = ( ephemeralPub: KeyPair; } => { let off = 0; - const isPaired = response.readUInt8(off) === ProtocolConstants.pairingStatus.paired; + const isPaired = + response.readUInt8(off) === ProtocolConstants.pairingStatus.paired; off++; // If we are already paired, we get the next ephemeral key const pub = response.slice(off, off + 65).toString('hex'); @@ -114,7 +127,10 @@ export const decodeConnectResponse = ( const decWalletData = aes256_decrypt(encWalletData, sharedSecret); // Sanity check to make sure the last part of the decrypted data is empty. The last 2 bytes // are AES padding - if (decWalletData[decWalletData.length - 2] !== 0 || decWalletData[decWalletData.length - 1] !== 0) { + if ( + decWalletData[decWalletData.length - 2] !== 0 || + decWalletData[decWalletData.length - 1] !== 0 + ) { throw new Error('Failed to connect to Lattice.'); } const activeWallets = parseWallets(decWalletData); diff --git a/packages/sdk/src/functions/fetchActiveWallet.ts b/packages/sdk/src/functions/fetchActiveWallet.ts index b414252b..c426bbfc 100644 --- a/packages/sdk/src/functions/fetchActiveWallet.ts +++ b/packages/sdk/src/functions/fetchActiveWallet.ts @@ -1,7 +1,16 @@ import { EMPTY_WALLET_UID } from '../constants'; -import { LatticeSecureEncryptedRequestType, encryptedSecureRequest } from '../protocol'; -import { validateActiveWallets, validateConnectedClient } from '../shared/validators'; -import type { ActiveWallets, FetchActiveWalletRequestFunctionParams } from '../types'; +import { + LatticeSecureEncryptedRequestType, + encryptedSecureRequest, +} from '../protocol'; +import { + validateActiveWallets, + validateConnectedClient, +} from '../shared/validators'; +import type { + ActiveWallets, + FetchActiveWalletRequestFunctionParams, +} from '../types'; /** * Fetch the active wallet in the device. @@ -10,7 +19,9 @@ import type { ActiveWallets, FetchActiveWalletRequestFunctionParams } from '../t * unlocked, the external interface is considered "active" and this will return its {@link Wallet} * data. Otherwise it will return the info for the internal Lattice wallet. */ -export async function fetchActiveWallet({ client }: FetchActiveWalletRequestFunctionParams): Promise { +export async function fetchActiveWallet({ + client, +}: FetchActiveWalletRequestFunctionParams): Promise { const { url, sharedSecret, ephemeralPub } = validateConnectedClient(client); const { decryptedData, newEphemeralPub } = await encryptedSecureRequest({ diff --git a/packages/sdk/src/functions/fetchDecoder.ts b/packages/sdk/src/functions/fetchDecoder.ts index ca206eda..70b4367c 100644 --- a/packages/sdk/src/functions/fetchDecoder.ts +++ b/packages/sdk/src/functions/fetchDecoder.ts @@ -9,15 +9,25 @@ import { fetchCalldataDecoder } from '../util'; * @category Lattice * @returns An object containing the ABI and encoded definition of the contract. */ -export async function fetchDecoder({ data, to, chainId }: TransactionRequest): Promise { +export async function fetchDecoder({ + data, + to, + chainId, +}: TransactionRequest): Promise { try { const client = await getClient(); validateConnectedClient(client); const fwVersion = client.getFwVersion(); - const supportsDecoderRecursion = fwVersion.major > 0 || fwVersion.minor >= 16; + const supportsDecoderRecursion = + fwVersion.major > 0 || fwVersion.minor >= 16; - const { def } = await fetchCalldataDecoder(data, to, chainId, supportsDecoderRecursion); + const { def } = await fetchCalldataDecoder( + data, + to, + chainId, + supportsDecoderRecursion, + ); return def; } catch (error) { diff --git a/packages/sdk/src/functions/fetchEncData.ts b/packages/sdk/src/functions/fetchEncData.ts index 49abae5b..3b71139f 100644 --- a/packages/sdk/src/functions/fetchEncData.ts +++ b/packages/sdk/src/functions/fetchEncData.ts @@ -4,13 +4,27 @@ */ import { v4 as uuidV4 } from 'uuid'; import { EXTERNAL } from '../constants'; -import { LatticeSecureEncryptedRequestType, encryptedSecureRequest } from '../protocol'; +import { + LatticeSecureEncryptedRequestType, + encryptedSecureRequest, +} from '../protocol'; import { getPathStr } from '../shared/utilities'; -import { validateConnectedClient, validateStartPath, validateWallet } from '../shared/validators'; -import type { EIP2335KeyExportData, EIP2335KeyExportReq, FetchEncDataRequestFunctionParams, FirmwareVersion, Wallet } from '../types'; +import { + validateConnectedClient, + validateStartPath, + validateWallet, +} from '../shared/validators'; +import type { + EIP2335KeyExportData, + EIP2335KeyExportReq, + FetchEncDataRequestFunctionParams, + FirmwareVersion, + Wallet, +} from '../types'; const { ENC_DATA } = EXTERNAL; -const ENC_DATA_ERR_STR = 'Unknown encrypted data export type requested. Exiting.'; +const ENC_DATA_ERR_STR = + 'Unknown encrypted data export type requested. Exiting.'; const ENC_DATA_REQ_DATA_SZ = 1025; const ENC_DATA_RESP_SZ = { EIP2335: { @@ -22,8 +36,13 @@ const ENC_DATA_RESP_SZ = { }, } as const; -export async function fetchEncData({ client, schema, params }: FetchEncDataRequestFunctionParams): Promise { - const { url, sharedSecret, ephemeralPub, fwVersion } = validateConnectedClient(client); +export async function fetchEncData({ + client, + schema, + params, +}: FetchEncDataRequestFunctionParams): Promise { + const { url, sharedSecret, ephemeralPub, fwVersion } = + validateConnectedClient(client); const activeWallet = validateWallet(client.getActiveWallet()); validateFetchEncDataRequest({ params }); @@ -71,7 +90,9 @@ export const encodeFetchEncDataRequest = ({ }) => { // Check firmware version if (fwVersion.major < 1 && fwVersion.minor < 17) { - throw new Error('Firmware version >=v0.17.0 is required for encrypted data export.'); + throw new Error( + 'Firmware version >=v0.17.0 is required for encrypted data export.', + ); } // Update params depending on what type of data is being exported if (schema === ENC_DATA.SCHEMAS.BLS_KEYSTORE_EIP2335_PBKDF_V4) { @@ -129,7 +150,9 @@ export const decodeFetchEncData = ({ const dataSz = data.readUInt32LE(off); off += 4; if (dataSz !== expectedSz) { - throw new Error('Invalid data returned from Lattice. Expected EIP2335 data.'); + throw new Error( + 'Invalid data returned from Lattice. Expected EIP2335 data.', + ); } respData.iterations = data.readUInt32LE(off); off += 4; @@ -149,7 +172,10 @@ export const decodeFetchEncData = ({ } }; -const formatEIP2335ExportData = (resp: EIP2335KeyExportData, path: number[]): Buffer => { +const formatEIP2335ExportData = ( + resp: EIP2335KeyExportData, + path: number[], +): Buffer => { try { const { iterations, salt, checksum, iv, cipherText, pubkey } = resp; return Buffer.from( diff --git a/packages/sdk/src/functions/getAddresses.ts b/packages/sdk/src/functions/getAddresses.ts index 245e9ed7..da366d23 100644 --- a/packages/sdk/src/functions/getAddresses.ts +++ b/packages/sdk/src/functions/getAddresses.ts @@ -1,6 +1,21 @@ -import { LatticeGetAddressesFlag, LatticeSecureEncryptedRequestType, ProtocolConstants, encryptedSecureRequest } from '../protocol'; -import { validateConnectedClient, validateIsUInt4, validateNAddresses, validateStartPath, validateWallet } from '../shared/validators'; -import type { FirmwareConstants, GetAddressesRequestFunctionParams, Wallet } from '../types'; +import { + LatticeGetAddressesFlag, + LatticeSecureEncryptedRequestType, + ProtocolConstants, + encryptedSecureRequest, +} from '../protocol'; +import { + validateConnectedClient, + validateIsUInt4, + validateNAddresses, + validateStartPath, + validateWallet, +} from '../shared/validators'; +import type { + FirmwareConstants, + GetAddressesRequestFunctionParams, + Wallet, +} from '../types'; import { isValidAssetPath } from '../util'; /** @@ -9,8 +24,15 @@ import { isValidAssetPath } from '../util'; * @category Lattice * @returns An array of addresses or public keys. */ -export async function getAddresses({ client, startPath: _startPath, n: _n, flag: _flag, iterIdx }: GetAddressesRequestFunctionParams): Promise { - const { url, sharedSecret, ephemeralPub, fwConstants } = validateConnectedClient(client); +export async function getAddresses({ + client, + startPath: _startPath, + n: _n, + flag: _flag, + iterIdx, +}: GetAddressesRequestFunctionParams): Promise { + const { url, sharedSecret, ephemeralPub, fwConstants } = + validateConnectedClient(client); const activeWallet = validateWallet(client.getActiveWallet()); const { startPath, n, flag } = validateGetAddressesRequest({ @@ -75,10 +97,16 @@ export const encodeGetAddressesRequest = ({ iterIdx?: number; }) => { const flags = fwConstants.getAddressFlags || ([] as any[]); - const isPubkeyOnly = flags.indexOf(flag) > -1 && (flag === LatticeGetAddressesFlag.ed25519Pubkey || flag === LatticeGetAddressesFlag.secp256k1Pubkey || flag === LatticeGetAddressesFlag.bls12_381Pubkey); + const isPubkeyOnly = + flags.indexOf(flag) > -1 && + (flag === LatticeGetAddressesFlag.ed25519Pubkey || + flag === LatticeGetAddressesFlag.secp256k1Pubkey || + flag === LatticeGetAddressesFlag.bls12_381Pubkey); const isXpub = flag === LatticeGetAddressesFlag.secp256k1Xpub; if (!isPubkeyOnly && !isXpub && !isValidAssetPath(startPath, fwConstants)) { - throw new Error('Derivation path or flag is not supported. Try updating Lattice firmware.'); + throw new Error( + 'Derivation path or flag is not supported. Try updating Lattice firmware.', + ); } // Ensure path depth is valid (2-5 indices) @@ -127,17 +155,27 @@ export const encodeGetAddressesRequest = ({ * @internal * @return an array of address strings or pubkey buffers */ -export const decodeGetAddressesResponse = (data: Buffer, flag: number): Buffer[] => { +export const decodeGetAddressesResponse = ( + data: Buffer, + flag: number, +): Buffer[] => { let off = 0; - const addressOffset = flag === LatticeGetAddressesFlag.ed25519Pubkey ? 113 : 65; + const addressOffset = + flag === LatticeGetAddressesFlag.ed25519Pubkey ? 113 : 65; // Look for addresses until we reach the end (a 4 byte checksum) const addrs: any[] = []; // Pubkeys are formatted differently in the response - const arePubkeys = flag === LatticeGetAddressesFlag.secp256k1Pubkey || flag === LatticeGetAddressesFlag.ed25519Pubkey || flag === LatticeGetAddressesFlag.bls12_381Pubkey; + const arePubkeys = + flag === LatticeGetAddressesFlag.secp256k1Pubkey || + flag === LatticeGetAddressesFlag.ed25519Pubkey || + flag === LatticeGetAddressesFlag.bls12_381Pubkey; if (arePubkeys) { off += 1; // skip uint8 representing pubkey type } - const respDataLength = ProtocolConstants.msgSizes.secure.data.response.encrypted[LatticeSecureEncryptedRequestType.getAddresses]; + const respDataLength = + ProtocolConstants.msgSizes.secure.data.response.encrypted[ + LatticeSecureEncryptedRequestType.getAddresses + ]; while (off < respDataLength) { if (arePubkeys) { // Pubkeys are shorter and are returned as buffers diff --git a/packages/sdk/src/functions/getKvRecords.ts b/packages/sdk/src/functions/getKvRecords.ts index 657e8b45..4c788592 100644 --- a/packages/sdk/src/functions/getKvRecords.ts +++ b/packages/sdk/src/functions/getKvRecords.ts @@ -1,9 +1,22 @@ -import { LatticeSecureEncryptedRequestType, encryptedSecureRequest } from '../protocol'; +import { + LatticeSecureEncryptedRequestType, + encryptedSecureRequest, +} from '../protocol'; import { validateConnectedClient } from '../shared/validators'; -import type { FirmwareConstants, GetKvRecordsData, GetKvRecordsRequestFunctionParams } from '../types'; +import type { + FirmwareConstants, + GetKvRecordsData, + GetKvRecordsRequestFunctionParams, +} from '../types'; -export async function getKvRecords({ client, type: _type, n: _n, start: _start }: GetKvRecordsRequestFunctionParams): Promise { - const { url, sharedSecret, ephemeralPub, fwConstants } = validateConnectedClient(client); +export async function getKvRecords({ + client, + type: _type, + n: _n, + start: _start, +}: GetKvRecordsRequestFunctionParams): Promise { + const { url, sharedSecret, ephemeralPub, fwConstants } = + validateConnectedClient(client); const { type, n, start } = validateGetKvRequest({ type: _type, @@ -47,7 +60,9 @@ export const validateGetKvRequest = ({ throw new Error('You must request at least one record.'); } if (n > fwConstants.kvActionMaxNum) { - throw new Error(`You may only request up to ${fwConstants.kvActionMaxNum} records at once.`); + throw new Error( + `You may only request up to ${fwConstants.kvActionMaxNum} records at once.`, + ); } if (type !== 0 && !type) { throw new Error('You must specify a type.'); @@ -75,13 +90,20 @@ export const encodeGetKvRecordsRequest = ({ return payload; }; -export const decodeGetKvRecordsResponse = (data: Buffer, fwConstants: FirmwareConstants) => { +export const decodeGetKvRecordsResponse = ( + data: Buffer, + fwConstants: FirmwareConstants, +) => { let off = 0; const nTotal = data.readUInt32BE(off); off += 4; - const nFetched = Number.parseInt(data.slice(off, off + 1).toString('hex'), 16); + const nFetched = Number.parseInt( + data.slice(off, off + 1).toString('hex'), + 16, + ); off += 1; - if (nFetched > fwConstants.kvActionMaxNum) throw new Error('Too many records fetched. Firmware error.'); + if (nFetched > fwConstants.kvActionMaxNum) + throw new Error('Too many records fetched. Firmware error.'); const records: any = []; for (let i = 0; i < nFetched; i++) { const r: any = {}; @@ -89,7 +111,8 @@ export const decodeGetKvRecordsResponse = (data: Buffer, fwConstants: FirmwareCo off += 4; r.type = data.readUInt32BE(off); off += 4; - r.caseSensitive = Number.parseInt(data.slice(off, off + 1).toString('hex'), 16) === 1; + r.caseSensitive = + Number.parseInt(data.slice(off, off + 1).toString('hex'), 16) === 1; off += 1; const keySz = Number.parseInt(data.slice(off, off + 1).toString('hex'), 16); off += 1; diff --git a/packages/sdk/src/functions/pair.ts b/packages/sdk/src/functions/pair.ts index d33ce923..aa119d0a 100644 --- a/packages/sdk/src/functions/pair.ts +++ b/packages/sdk/src/functions/pair.ts @@ -1,4 +1,7 @@ -import { LatticeSecureEncryptedRequestType, encryptedSecureRequest } from '../protocol'; +import { + LatticeSecureEncryptedRequestType, + encryptedSecureRequest, +} from '../protocol'; import { getPubKeyBytes } from '../shared/utilities'; import { validateConnectedClient } from '../shared/validators'; import type { KeyPair, PairRequestParams } from '../types'; @@ -11,8 +14,12 @@ import { generateAppSecret, toPaddedDER } from '../util'; * @category Lattice * @returns The active wallet object. */ -export async function pair({ client, pairingSecret }: PairRequestParams): Promise { - const { url, sharedSecret, ephemeralPub, appName, key } = validateConnectedClient(client); +export async function pair({ + client, + pairingSecret, +}: PairRequestParams): Promise { + const { url, sharedSecret, ephemeralPub, appName, key } = + validateConnectedClient(client); const data = encodePairRequest({ pairingSecret, key, appName }); const { newEphemeralPub } = await encryptedSecureRequest({ @@ -51,7 +58,11 @@ export const encodePairRequest = ({ // (RESP_ERR_PAIR_FAIL) nameBuf.write(appName); } - const hash = generateAppSecret(pubKeyBytes, nameBuf, Buffer.from(pairingSecret)); + const hash = generateAppSecret( + pubKeyBytes, + nameBuf, + Buffer.from(pairingSecret), + ); const sig = key.sign(hash); const derSig = toPaddedDER(sig); const payload = Buffer.concat([nameBuf, derSig]); diff --git a/packages/sdk/src/functions/removeKvRecords.ts b/packages/sdk/src/functions/removeKvRecords.ts index 5a7623d4..b78637eb 100644 --- a/packages/sdk/src/functions/removeKvRecords.ts +++ b/packages/sdk/src/functions/removeKvRecords.ts @@ -1,14 +1,25 @@ -import { LatticeSecureEncryptedRequestType, encryptedSecureRequest } from '../protocol'; +import { + LatticeSecureEncryptedRequestType, + encryptedSecureRequest, +} from '../protocol'; import { validateConnectedClient } from '../shared/validators'; -import type { FirmwareConstants, RemoveKvRecordsRequestFunctionParams } from '../types'; +import type { + FirmwareConstants, + RemoveKvRecordsRequestFunctionParams, +} from '../types'; /** * `removeKvRecords` takes in an array of ids and sends a request to remove them from the Lattice. * @category Lattice * @returns A callback with an error or null. */ -export async function removeKvRecords({ client, type: _type, ids: _ids }: RemoveKvRecordsRequestFunctionParams): Promise { - const { url, sharedSecret, ephemeralPub, fwConstants } = validateConnectedClient(client); +export async function removeKvRecords({ + client, + type: _type, + ids: _ids, +}: RemoveKvRecordsRequestFunctionParams): Promise { + const { url, sharedSecret, ephemeralPub, fwConstants } = + validateConnectedClient(client); const { type, ids } = validateRemoveKvRequest({ fwConstants, @@ -53,7 +64,9 @@ export const validateRemoveKvRequest = ({ throw new Error('You must include one or more `ids` to removed.'); } if (ids.length > fwConstants.kvRemoveMaxNum) { - throw new Error(`Only up to ${fwConstants.kvRemoveMaxNum} records may be removed at once.`); + throw new Error( + `Only up to ${fwConstants.kvRemoveMaxNum} records may be removed at once.`, + ); } if (type !== 0 && !type) { throw new Error('You must specify a type.'); diff --git a/packages/sdk/src/functions/sign.ts b/packages/sdk/src/functions/sign.ts index 76fc9a47..fafbbb3f 100644 --- a/packages/sdk/src/functions/sign.ts +++ b/packages/sdk/src/functions/sign.ts @@ -4,10 +4,22 @@ import bitcoin from '../bitcoin'; import { CURRENCIES } from '../constants'; import ethereum from '../ethereum'; import { parseGenericSigningResponse } from '../genericSigning'; -import { LatticeSecureEncryptedRequestType, LatticeSignSchema, encryptedSecureRequest } from '../protocol'; +import { + LatticeSecureEncryptedRequestType, + LatticeSignSchema, + encryptedSecureRequest, +} from '../protocol'; import { buildTransaction } from '../shared/functions'; import { validateConnectedClient, validateWallet } from '../shared/validators'; -import type { BitcoinSignRequest, DecodeSignResponseParams, EncodeSignRequestParams, SignData, SignRequest, SignRequestFunctionParams, SigningRequestResponse } from '../types'; +import type { + BitcoinSignRequest, + DecodeSignResponseParams, + EncodeSignRequestParams, + SignData, + SignRequest, + SignRequestFunctionParams, + SigningRequestResponse, +} from '../types'; import { parseDER } from '../util'; /** @@ -15,9 +27,16 @@ import { parseDER } from '../util'; * @category Lattice * @returns The response from the device. */ -export async function sign({ client, data, currency, cachedData, nextCode }: SignRequestFunctionParams): Promise { +export async function sign({ + client, + data, + currency, + cachedData, + nextCode, +}: SignRequestFunctionParams): Promise { try { - const { url, sharedSecret, ephemeralPub, fwConstants } = validateConnectedClient(client); + const { url, sharedSecret, ephemeralPub, fwConstants } = + validateConnectedClient(client); const wallet = validateWallet(client.getActiveWallet()); const { requestData, isGeneric } = buildTransaction({ @@ -82,7 +101,13 @@ export async function sign({ client, data, currency, cachedData, nextCode }: Sig } } -export const encodeSignRequest = ({ fwConstants, wallet, requestData, cachedData, nextCode }: EncodeSignRequestParams) => { +export const encodeSignRequest = ({ + fwConstants, + wallet, + requestData, + cachedData, + nextCode, +}: EncodeSignRequestParams) => { let reqPayload: Buffer; let schema: number; let hasExtraPayloads = 0; @@ -96,18 +121,24 @@ export const encodeSignRequest = ({ fwConstants, wallet, requestData, cachedData }; const nextExtraPayload = typedCachedData.extraDataPayloads.shift(); if (!nextExtraPayload) { - throw new Error('No cached extra payload available for multipart sign request.'); + throw new Error( + 'No cached extra payload available for multipart sign request.', + ); } if (typedRequestData.extraDataPayloads) { typedRequestData.extraDataPayloads = typedCachedData.extraDataPayloads; } reqPayload = Buffer.concat([nextCode, nextExtraPayload]); schema = LatticeSignSchema.extraData; - hasExtraPayloads = Number((typedCachedData.extraDataPayloads?.length ?? 0) > 0); + hasExtraPayloads = Number( + (typedCachedData.extraDataPayloads?.length ?? 0) > 0, + ); } else { reqPayload = typedRequestData.payload; schema = typedRequestData.schema; - hasExtraPayloads = Number((typedRequestData.extraDataPayloads?.length ?? 0) > 0); + hasExtraPayloads = Number( + (typedRequestData.extraDataPayloads?.length ?? 0) > 0, + ); } const payload = Buffer.alloc(2 + fwConstants.reqMaxDataSz); @@ -126,17 +157,27 @@ export const encodeSignRequest = ({ fwConstants, wallet, requestData, cachedData return { payload, hasExtraPayloads }; }; -export const decodeSignResponse = ({ data, request, isGeneric, currency }: DecodeSignResponseParams): SignData => { +export const decodeSignResponse = ({ + data, + request, + isGeneric, + currency, +}: DecodeSignResponseParams): SignData => { let off = 0; const derSigLen = 74; // DER signatures are 74 bytes if (currency === CURRENCIES.BTC) { const btcRequest = request as BitcoinSignRequest; const pkhLen = 20; // Pubkeyhashes are 20 bytes const sigsLen = 760; // Up to 10x DER signatures - const changeVersion = bitcoin.getAddressFormat(btcRequest.origData.changePath); + const changeVersion = bitcoin.getAddressFormat( + btcRequest.origData.changePath, + ); const changePubKeyHash = data.slice(off, off + pkhLen); off += pkhLen; - const changeRecipient = bitcoin.getBitcoinAddress(changePubKeyHash, changeVersion); + const changeRecipient = bitcoin.getBitcoinAddress( + changePubKeyHash, + changeVersion, + ); const compressedPubLength = 33; // Size of compressed public key const pubkeys = [] as any[]; const sigs = [] as any[]; @@ -195,7 +236,9 @@ export const decodeSignResponse = ({ data, request, isGeneric, currency }: Decod const serializedTx = bitcoin.serializeTx(preSerializedData); // Generate the transaction hash so the user can look this transaction up later const preImageTxHash = serializedTx; - const txHashPre: Buffer = Buffer.from(Hash.sha256(Buffer.from(preImageTxHash, 'hex'))); + const txHashPre: Buffer = Buffer.from( + Hash.sha256(Buffer.from(preImageTxHash, 'hex')), + ); // Add extra data for debugging/lookup purposes return { tx: serializedTx, @@ -248,7 +291,10 @@ export const decodeSignResponse = ({ data, request, isGeneric, currency }: Decod const sig = parseDER(data.slice(off, off + 2 + data[off + 1])); off += derSigLen; const signer = data.slice(off, off + 20); - const validatedSig = ethereum.validateEthereumMsgResponse({ signer, sig }, request); + const validatedSig = ethereum.validateEthereumMsgResponse( + { signer, sig }, + request, + ); return { sig: { v: BigInt(`0x${validatedSig.v.toString('hex')}`), diff --git a/packages/sdk/src/genericSigning.ts b/packages/sdk/src/genericSigning.ts index 1f18b3d3..baf80d16 100644 --- a/packages/sdk/src/genericSigning.ts +++ b/packages/sdk/src/genericSigning.ts @@ -10,18 +10,57 @@ This payload should be coupled with: * Hash function to use on the message */ import { Hash } from 'ox'; -import { type Hex, type TransactionSerializable, parseTransaction, serializeTransaction } from 'viem'; +import { + type Hex, + type TransactionSerializable, + parseTransaction, + serializeTransaction, +} from 'viem'; // keccak256 now imported from ox via Hash module import { HARDENED_OFFSET } from './constants'; import { Constants } from './index'; import { LatticeSignSchema } from './protocol'; -import { buildSignerPathBuf, existsIn, fixLen, getV, getYParity, parseDER, splitFrames } from './util'; +import { + buildSignerPathBuf, + existsIn, + fixLen, + getV, + getYParity, + parseDER, + splitFrames, +} from './util'; export const buildGenericSigningMsgRequest = (req) => { - const { signerPath, curveType, hashType, encodingType = null, decoder = null, omitPubkey = false, fwConstants, blsDst = Constants.SIGNING.BLS_DST.BLS_DST_NUL } = req; - const { extraDataFrameSz, extraDataMaxFrames, prehashAllowed, genericSigning, varAddrPathSzAllowed } = fwConstants; - const { curveTypes, encodingTypes, hashTypes, baseDataSz, baseReqSz, calldataDecoding } = genericSigning; - const encodedPayload = getEncodedPayload(req.payload, encodingType, encodingTypes); + const { + signerPath, + curveType, + hashType, + encodingType = null, + decoder = null, + omitPubkey = false, + fwConstants, + blsDst = Constants.SIGNING.BLS_DST.BLS_DST_NUL, + } = req; + const { + extraDataFrameSz, + extraDataMaxFrames, + prehashAllowed, + genericSigning, + varAddrPathSzAllowed, + } = fwConstants; + const { + curveTypes, + encodingTypes, + hashTypes, + baseDataSz, + baseReqSz, + calldataDecoding, + } = genericSigning; + const encodedPayload = getEncodedPayload( + req.payload, + encodingType, + encodingTypes, + ); const { encoding } = encodedPayload; let { payloadBuf } = encodedPayload; const origPayloadBuf = payloadBuf; @@ -31,7 +70,12 @@ export const buildGenericSigningMsgRequest = (req) => { // Sanity checks if (!payloadDataSz) { throw new Error('Payload could not be handled.'); - } else if (!genericSigning || !extraDataFrameSz || !extraDataMaxFrames || !prehashAllowed) { + } else if ( + !genericSigning || + !extraDataFrameSz || + !extraDataMaxFrames || + !prehashAllowed + ) { throw new Error('Unsupported. Please update your Lattice firmware.'); } else if (!existsIn(curveType, curveTypes)) { throw new Error('Unsupported curve type.'); @@ -41,11 +85,13 @@ export const buildGenericSigningMsgRequest = (req) => { // If there is a decoder attached to our payload, add it to // the data field of the request. - const hasDecoder = decoder && calldataDecoding && decoder.length <= calldataDecoding.maxSz; + const hasDecoder = + decoder && calldataDecoding && decoder.length <= calldataDecoding.maxSz; // Make sure the payload AND decoder data fits in the firmware buffer. // If it doesn't, we can't include the decoder because the payload will likely // be pre-hashed and the decoder data isn't part of the message to sign. - const decoderFits = hasDecoder && payloadBuf.length + decoder.length <= maxExpandedSz; + const decoderFits = + hasDecoder && payloadBuf.length + decoder.length <= maxExpandedSz; if (hasDecoder && decoderFits) { const decoderBuf = Buffer.alloc(8 + decoder.length); // First write th reserved word @@ -63,7 +109,9 @@ export const buildGenericSigningMsgRequest = (req) => { } signerPath.forEach((idx) => { if (idx < HARDENED_OFFSET) { - throw new Error('Signing on ed25519 requires all signer path indices be hardened.'); + throw new Error( + 'Signing on ed25519 requires all signer path indices be hardened.', + ); } }); } @@ -110,7 +158,9 @@ export const buildGenericSigningMsgRequest = (req) => { // If this payload is too large to send, but the Lattice allows a prehashed message, do that if (hashType === hashTypes.NONE) { // This cannot be done for ED25519 signing, which must sign the full message - throw new Error('Message too large to send and could not be prehashed (hashType=NONE).'); + throw new Error( + 'Message too large to send and could not be prehashed (hashType=NONE).', + ); } else if (hashType === hashTypes.KECCAK256) { prehash = Buffer.from(Hash.keccak256(payloadData)); } else if (hashType === hashTypes.SHA256) { @@ -120,7 +170,10 @@ export const buildGenericSigningMsgRequest = (req) => { } } else { // Split overflow data into extraData frames - const frames = splitFrames(payloadBuf.slice(baseDataSz), extraDataFrameSz); + const frames = splitFrames( + payloadBuf.slice(baseDataSz), + extraDataFrameSz, + ); frames.forEach((frame) => { const szLE = Buffer.alloc(4); szLE.writeUInt32LE(frame.length, 0); @@ -211,7 +264,10 @@ export const parseGenericSigningResponse = (res, off, req) => { const vBn = getV(req.origPayloadBuf, parsed); parsed.sig.v = BigInt(vBn.toString()); populateViemSignedTx(parsed.sig.v, req, parsed); - } else if (req.hashType === Constants.SIGNING.HASHES.KECCAK256 && req.encodingType !== Constants.SIGNING.ENCODINGS.EVM) { + } else if ( + req.hashType === Constants.SIGNING.HASHES.KECCAK256 && + req.encodingType !== Constants.SIGNING.ENCODINGS.EVM + ) { // Generic Keccak256 message - determine if it looks like a transaction let isTransaction = false; @@ -237,7 +293,10 @@ export const parseGenericSigningResponse = (res, off, req) => { parsed.sig.v = BigInt(vBn.toString()); populateViemSignedTx(parsed.sig.v, req, parsed); } catch (err) { - console.error('Failed to get V from transaction, using fallback:', err); + console.error( + 'Failed to get V from transaction, using fallback:', + err, + ); // Fall back to simple recovery if getV fails (e.g., malformed RLP) // Use the correct hash type specified in the request const msgHash = computeMessageHash(req, digestFromResponse); @@ -297,7 +356,11 @@ function computeMessageHash( }, digestFromResponse?: Buffer, ): Buffer { - if (digestFromResponse && digestFromResponse.length === 32 && digestFromResponse.some((byte) => byte !== 0)) { + if ( + digestFromResponse && + digestFromResponse.length === 32 && + digestFromResponse.some((byte) => byte !== 0) + ) { return digestFromResponse; } if (req.hashType === Constants.SIGNING.HASHES.SHA256) { @@ -311,7 +374,11 @@ function computeMessageHash( // Reconstruct a viem-compatible signed transaction string from the raw payload and // recovered signature so consumers can compare or broadcast without extra parsing. -function populateViemSignedTx(sigV: bigint, req: any, parsed: { sig: { r: string; s: string; v?: bigint }; viemTx?: string }) { +function populateViemSignedTx( + sigV: bigint, + req: any, + parsed: { sig: { r: string; s: string; v?: bigint }; viemTx?: string }, +) { if (req.encodingType !== Constants.SIGNING.ENCODINGS.EVM) return; try { @@ -360,7 +427,10 @@ function populateViemSignedTx(sigV: bigint, req: any, parsed: { sig: { r: string s: parsed.sig.s as Hex, }; - parsed.viemTx = serializeTransaction(baseTx as TransactionSerializable, signature as any); + parsed.viemTx = serializeTransaction( + baseTx as TransactionSerializable, + signature as any, + ); } catch (_err) { console.debug('Failed to build viemTx from response', _err); } @@ -372,7 +442,9 @@ export const getEncodedPayload = (payload, encoding, allowedEncodings) => { } // Make sure the encoding type specified is supported by firmware if (!existsIn(encoding, allowedEncodings)) { - throw new Error('Encoding not supported by Lattice firmware. You may want to update.'); + throw new Error( + 'Encoding not supported by Lattice firmware. You may want to update.', + ); } let payloadBuf: Buffer; if (!payload) { diff --git a/packages/sdk/src/protocol/latticeConstants.ts b/packages/sdk/src/protocol/latticeConstants.ts index 2589a17e..041564aa 100644 --- a/packages/sdk/src/protocol/latticeConstants.ts +++ b/packages/sdk/src/protocol/latticeConstants.ts @@ -94,7 +94,10 @@ export const ProtocolConstants = { // message encryption/decryption. This is generally considered // fine because each encryption/decryption uses a unique encryption // secret (derived from the per-message ephemeral key pair). - aesIv: [0x6d, 0x79, 0x73, 0x65, 0x63, 0x72, 0x65, 0x74, 0x70, 0x61, 0x73, 0x73, 0x77, 0x6f, 0x72, 0x64], + aesIv: [ + 0x6d, 0x79, 0x73, 0x65, 0x63, 0x72, 0x65, 0x74, 0x70, 0x61, 0x73, 0x73, + 0x77, 0x6f, 0x72, 0x64, + ], // Constant size of address buffers from the Lattice. // Note that this size also captures public keys returned // by the Lattice (addresses = strings, pubkeys = buffers) @@ -114,7 +117,8 @@ export const ProtocolConstants = { [LatticeResponseCode.userDeclined]: 'Request declined by user', [LatticeResponseCode.pairFailed]: 'Pairing failed', [LatticeResponseCode.pairDisabled]: 'Pairing is currently disabled', - [LatticeResponseCode.permissionDisabled]: 'Automated signing is currently disabled', + [LatticeResponseCode.permissionDisabled]: + 'Automated signing is currently disabled', [LatticeResponseCode.internalError]: 'Device Error', [LatticeResponseCode.gceTimeout]: 'Device Timeout', [LatticeResponseCode.wrongWallet]: 'Active wallet does not match request', diff --git a/packages/sdk/src/protocol/secureMessages.ts b/packages/sdk/src/protocol/secureMessages.ts index 78a0576c..5a7acf59 100644 --- a/packages/sdk/src/protocol/secureMessages.ts +++ b/packages/sdk/src/protocol/secureMessages.ts @@ -1,7 +1,21 @@ import { getEphemeralId, request } from '../shared/functions'; import { validateEphemeralPub } from '../shared/validators'; -import type { DecryptedResponse, KeyPair, LatticeMessageHeader, LatticeSecureConnectRequestPayloadData, LatticeSecureDecryptedResponse, LatticeSecureRequest, LatticeSecureRequestPayload } from '../types'; -import { aes256_decrypt, aes256_encrypt, checksum, getP256KeyPairFromPub, randomBytes } from '../util'; +import type { + DecryptedResponse, + KeyPair, + LatticeMessageHeader, + LatticeSecureConnectRequestPayloadData, + LatticeSecureDecryptedResponse, + LatticeSecureRequest, + LatticeSecureRequestPayload, +} from '../types'; +import { + aes256_decrypt, + aes256_encrypt, + checksum, + getP256KeyPairFromPub, + randomBytes, +} from '../util'; /** * All messages sent to the Lattice from this SDK will be * "secure messages", of which there are two types: @@ -22,7 +36,13 @@ import { aes256_decrypt, aes256_encrypt, checksum, getP256KeyPairFromPub, random * The response to this request will contain a new ephemral public * key, which you will need for the next encrypted request. */ -import { ProtocolConstants as Constants, LatticeMsgType, LatticeProtocolVersion, type LatticeSecureEncryptedRequestType, LatticeSecureMsgType } from './latticeConstants'; +import { + ProtocolConstants as Constants, + LatticeMsgType, + LatticeProtocolVersion, + type LatticeSecureEncryptedRequestType, + LatticeSecureMsgType, +} from './latticeConstants'; const { msgSizes } = Constants; const { secure: szs } = msgSizes; @@ -47,7 +67,11 @@ export async function connectSecureRequest({ pubkey: pubkey, }); const msgId = randomBytes(4); - const msg = serializeSecureRequestMsg(msgId, LatticeSecureMsgType.connect, payloadData); + const msg = serializeSecureRequestMsg( + msgId, + LatticeSecureMsgType.connect, + payloadData, + ); // Send request to the Lattice const resp = await request({ url, payload: msg }); if (resp.length !== szs.payload.response.connect - 1) { @@ -96,7 +120,11 @@ export async function encryptedSecureRequest({ // Serialize the payload data into an encrypted secure // request message. - const msg = serializeSecureRequestMsg(msgId, LatticeSecureMsgType.encrypted, payloadData); + const msg = serializeSecureRequestMsg( + msgId, + LatticeSecureMsgType.encrypted, + payloadData, + ); // Send request to Lattice const resp = await request({ @@ -109,7 +137,10 @@ export async function encryptedSecureRequest({ throw new Error('Wrong Lattice response message size.'); } - const encPayloadData = resp.slice(0, szs.data.response.encrypted.encryptedData); + const encPayloadData = resp.slice( + 0, + szs.data.response.encrypted.encryptedData, + ); // Return decrypted response payload data return decryptEncryptedLatticeResponseData({ @@ -128,20 +159,31 @@ export async function encryptedSecureRequest({ * @param payloadData - Request data * @return {Buffer} Serialized message to be sent to Lattice */ -function serializeSecureRequestMsg(msgId: Buffer, secureRequestType: LatticeSecureMsgType, payloadData: Buffer): Buffer { +function serializeSecureRequestMsg( + msgId: Buffer, + secureRequestType: LatticeSecureMsgType, + payloadData: Buffer, +): Buffer { // Sanity check request data if (msgId.length !== 4) { throw new Error('msgId must be four bytes'); } - if (secureRequestType !== LatticeSecureMsgType.connect && secureRequestType !== LatticeSecureMsgType.encrypted) { + if ( + secureRequestType !== LatticeSecureMsgType.connect && + secureRequestType !== LatticeSecureMsgType.encrypted + ) { throw new Error('Invalid Lattice secure request type'); } // Validate the incoming payload data size. Note that the payload // data is prepended with a secure request type byte, so the // payload data size is one less than the expected size. - const isValidConnectPayloadDataSz = secureRequestType === LatticeSecureMsgType.connect && payloadData.length === szs.payload.request.connect - 1; - const isValidEncryptedPayloadDataSz = secureRequestType === LatticeSecureMsgType.encrypted && payloadData.length === szs.payload.request.encrypted - 1; + const isValidConnectPayloadDataSz = + secureRequestType === LatticeSecureMsgType.connect && + payloadData.length === szs.payload.request.connect - 1; + const isValidEncryptedPayloadDataSz = + secureRequestType === LatticeSecureMsgType.encrypted && + payloadData.length === szs.payload.request.encrypted - 1; // Build payload and size let msgSz = msgSizes.header + msgSizes.checksum; @@ -205,7 +247,9 @@ function serializeSecureRequestMsg(msgId: Buffer, secureRequestType: LatticeSecu * Serialize payload data for a Lattice secure request: connect * @return {Buffer} - 1700 bytes, of which only 65 are used */ -function serializeSecureRequestConnectPayloadData(payloadData: LatticeSecureConnectRequestPayloadData): Buffer { +function serializeSecureRequestConnectPayloadData( + payloadData: LatticeSecureConnectRequestPayloadData, +): Buffer { const serPayloadData = Buffer.alloc(szs.data.request.connect); payloadData.pubkey.copy(serPayloadData, 0); return serPayloadData; @@ -239,7 +283,9 @@ function serializeSecureRequestEncryptedPayloadData({ // Validate the request data size matches the desired request const requestDataSize = szs.data.request.encrypted[requestType]; if (data.length !== requestDataSize) { - throw new Error(`Invalid request datasize (wanted ${requestDataSize}, got ${data.length})`); + throw new Error( + `Invalid request datasize (wanted ${requestDataSize}, got ${data.length})`, + ); } // Build the pre-encrypted data payload, which variable sized and of form: @@ -253,7 +299,10 @@ function serializeSecureRequestEncryptedPayloadData({ // equal to the full message request less the 4-byte ephemeral id. const _encryptedData = Buffer.alloc(szs.data.request.encrypted.encryptedData); preEncryptedData.copy(_encryptedData, 0); - _encryptedData.writeUInt32LE(preEncryptedDataChecksum, preEncryptedData.length); + _encryptedData.writeUInt32LE( + preEncryptedDataChecksum, + preEncryptedData.length, + ); const encryptedData = aes256_encrypt(_encryptedData, sharedSecret); // Calculate ephemeral ID @@ -286,7 +335,8 @@ function decryptEncryptedLatticeResponseData({ // Bulid the object const ephemeralPubSz = 65; // secp256r1 pubkey - const checksumOffset = ephemeralPubSz + szs.data.response.encrypted[requestType]; + const checksumOffset = + ephemeralPubSz + szs.data.response.encrypted[requestType]; const respData: LatticeSecureDecryptedResponse = { ephemeralPub: decData.slice(0, ephemeralPubSz), data: decData.slice(ephemeralPubSz, checksumOffset), diff --git a/packages/sdk/src/schemas/transaction.ts b/packages/sdk/src/schemas/transaction.ts index 20d0a174..63f06eed 100644 --- a/packages/sdk/src/schemas/transaction.ts +++ b/packages/sdk/src/schemas/transaction.ts @@ -4,25 +4,32 @@ import { TRANSACTION_TYPE } from '../types'; // Helper to handle various numeric inputs and convert them to BigInt. // It also validates that the value is not negative. -const toPositiveBigInt = z.union([z.string().regex(/^(0x[0-9a-fA-F]+|[0-9]+)$/, 'Invalid number format'), z.number(), z.bigint()]).transform((val, ctx) => { - try { - const b = typeof val === 'string' && isHex(val) ? hexToBigInt(val) : BigInt(val); - if (b < 0n) { +const toPositiveBigInt = z + .union([ + z.string().regex(/^(0x[0-9a-fA-F]+|[0-9]+)$/, 'Invalid number format'), + z.number(), + z.bigint(), + ]) + .transform((val, ctx) => { + try { + const b = + typeof val === 'string' && isHex(val) ? hexToBigInt(val) : BigInt(val); + if (b < 0n) { + ctx.addIssue({ + code: z.ZodIssueCode.custom, + message: 'Value must be non-negative', + }); + return z.NEVER; + } + return b; + } catch { ctx.addIssue({ code: z.ZodIssueCode.custom, - message: 'Value must be non-negative', + message: 'Invalid numeric value', }); return z.NEVER; } - return b; - } catch { - ctx.addIssue({ - code: z.ZodIssueCode.custom, - message: 'Invalid numeric value', - }); - return z.NEVER; - } -}); + }); // Schema for gas-related fields, ensuring they are non-negative BigInts. const GasValueSchema = toPositiveBigInt.refine((val) => val >= 0n, { @@ -32,7 +39,11 @@ const GasValueSchema = toPositiveBigInt.refine((val) => val >= 0n, { // Schema for chainId, ensuring it's a positive integer. const ChainIdSchema = z .union([z.string(), z.number()]) - .transform((val) => (typeof val === 'string' && isHex(val) ? Number(hexToBigInt(val as Hex)) : Number(val))) + .transform((val) => + typeof val === 'string' && isHex(val) + ? Number(hexToBigInt(val as Hex)) + : Number(val), + ) .refine((val) => Number.isInteger(val) && val > 0, { message: 'Chain ID must be a positive integer', }); @@ -44,43 +55,61 @@ const AddressSchema = z .transform((addr) => getAddress(addr)); // Schema for hex data, ensuring it's a valid hex string. -const DataSchema = z.string().refine(isHex, 'Data must be a valid hex string').default('0x'); +const DataSchema = z + .string() + .refine(isHex, 'Data must be a valid hex string') + .default('0x'); -const NonceSchema = z.union([z.string().regex(/^(0x[0-9a-fA-F]+|[0-9]+)$/, 'Invalid nonce format'), z.number().int().nonnegative(), z.bigint()]).transform((val, ctx) => { - try { - const bigVal = typeof val === 'string' ? (isHex(val as Hex) ? hexToBigInt(val as Hex) : BigInt(val)) : typeof val === 'number' ? BigInt(val) : val; +const NonceSchema = z + .union([ + z.string().regex(/^(0x[0-9a-fA-F]+|[0-9]+)$/, 'Invalid nonce format'), + z.number().int().nonnegative(), + z.bigint(), + ]) + .transform((val, ctx) => { + try { + const bigVal = + typeof val === 'string' + ? isHex(val as Hex) + ? hexToBigInt(val as Hex) + : BigInt(val) + : typeof val === 'number' + ? BigInt(val) + : val; - if (bigVal < 0n) { - ctx.addIssue({ - code: z.ZodIssueCode.custom, - message: 'Nonce must be non-negative', - }); - return z.NEVER; - } + if (bigVal < 0n) { + ctx.addIssue({ + code: z.ZodIssueCode.custom, + message: 'Nonce must be non-negative', + }); + return z.NEVER; + } + + const maxSafe = BigInt(Number.MAX_SAFE_INTEGER); + if (bigVal > maxSafe) { + ctx.addIssue({ + code: z.ZodIssueCode.custom, + message: 'Nonce exceeds JavaScript safe integer range', + }); + return z.NEVER; + } - const maxSafe = BigInt(Number.MAX_SAFE_INTEGER); - if (bigVal > maxSafe) { + return Number(bigVal); + } catch { ctx.addIssue({ code: z.ZodIssueCode.custom, - message: 'Nonce exceeds JavaScript safe integer range', + message: 'Invalid nonce value', }); return z.NEVER; } - - return Number(bigVal); - } catch { - ctx.addIssue({ - code: z.ZodIssueCode.custom, - message: 'Invalid nonce value', - }); - return z.NEVER; - } -}); + }); // Schema for access list entries. const AccessListEntrySchema = z.object({ address: AddressSchema, - storageKeys: z.array(z.string().refine(isHex, 'Storage key must be a hex string')), + storageKeys: z.array( + z.string().refine(isHex, 'Storage key must be a hex string'), + ), }); // Schema for EIP-7702 authorization entries. @@ -107,7 +136,9 @@ const BaseTxSchema = z.object({ // Schema for Legacy (Type 0) transactions. const LegacyTxSchema = BaseTxSchema.extend({ - type: z.union([z.literal('legacy'), z.literal(TRANSACTION_TYPE.LEGACY)]).optional(), + type: z + .union([z.literal('legacy'), z.literal(TRANSACTION_TYPE.LEGACY)]) + .optional(), gasPrice: GasValueSchema, }); @@ -126,7 +157,11 @@ const EIP1559TxSchema = BaseTxSchema.extend({ // Schema for EIP-7702 (Type 4/5) transactions. const EIP7702TxSchema = BaseTxSchema.extend({ - type: z.union([z.literal('eip7702'), z.literal(TRANSACTION_TYPE.EIP7702_AUTH), z.literal(TRANSACTION_TYPE.EIP7702_AUTH_LIST)]), + type: z.union([ + z.literal('eip7702'), + z.literal(TRANSACTION_TYPE.EIP7702_AUTH), + z.literal(TRANSACTION_TYPE.EIP7702_AUTH_LIST), + ]), maxFeePerGas: GasValueSchema, maxPriorityFeePerGas: GasValueSchema, authorizationList: z.array(AuthorizationSchema).min(1), @@ -146,7 +181,9 @@ export const TransactionSchema = z .refine( (val) => { try { - JSON.stringify(val, (_, value) => (typeof value === 'bigint' ? value.toString() : value)); + JSON.stringify(val, (_, value) => + typeof value === 'bigint' ? value.toString() : value, + ); return true; } catch { return false; @@ -180,7 +217,12 @@ export const TransactionSchema = z let type: 'eip7702' | 'eip1559' | 'eip2930' | 'legacy' = 'legacy'; let schema: z.ZodTypeAny = LegacyTxSchema; - if (tx.type === 'eip7702' || tx.type === 4 || tx.type === 5 || hasAuthList) { + if ( + tx.type === 'eip7702' || + tx.type === 4 || + tx.type === 5 || + hasAuthList + ) { type = 'eip7702'; schema = EIP7702TxSchema; } else if (tx.type === 'eip1559' || tx.type === 2 || hasMaxFee) { diff --git a/packages/sdk/src/shared/functions.ts b/packages/sdk/src/shared/functions.ts index 6d4121c3..e03aa25e 100644 --- a/packages/sdk/src/shared/functions.ts +++ b/packages/sdk/src/shared/functions.ts @@ -7,7 +7,12 @@ import { buildGenericSigningMsgRequest } from '../genericSigning'; import type { Currency, FirmwareConstants, RequestParams } from '../types'; import { fetchWithTimeout, parseLattice1Response } from '../util'; import { LatticeResponseError } from './errors'; -import { isDeviceBusy, isInvalidEphemeralId, isWrongWallet, shouldUseEVMLegacyConverter } from './predicates'; +import { + isDeviceBusy, + isInvalidEphemeralId, + isWrongWallet, + shouldUseEVMLegacyConverter, +} from './predicates'; import { validateRequestError } from './validators'; export const buildTransaction = ({ @@ -30,13 +35,19 @@ export const buildTransaction = ({ // general signing requests for newer firmware versions. EIP1559 and EIP155 legacy // requests will convert, but others may not. if (currency === 'ETH' && shouldUseEVMLegacyConverter(fwConstants)) { - console.log('Using the legacy ETH signing path. This will soon be deprecated. ' + 'Please switch to general signing request.'); + console.log( + 'Using the legacy ETH signing path. This will soon be deprecated. ' + + 'Please switch to general signing request.', + ); let payload: Buffer | undefined; try { payload = ethereum.convertEthereumTransactionToGenericRequest(data); } catch (err) { console.error('Failed to convert legacy Ethereum transaction:', err); - throw new Error('Could not convert legacy request. Please switch to a general signing ' + 'request. See gridplus-sdk docs for more information.'); + throw new Error( + 'Could not convert legacy request. Please switch to a general signing ' + + 'request. See gridplus-sdk docs for more information.', + ); } data = { fwConstants, @@ -73,7 +84,11 @@ export const buildTransaction = ({ }; }; -export const request = async ({ url, payload, timeout = 60000 }: RequestParams) => { +export const request = async ({ + url, + payload, + timeout = 60000, +}: RequestParams) => { return fetchWithTimeout(url, { method: 'POST', body: JSON.stringify({ data: payload }), @@ -92,7 +107,9 @@ export const request = async ({ url, payload, timeout = 60000 }: RequestParams) throw new Error(`Error code ${body.status}: ${body.message}`); } - const { data, errorMessage, responseCode } = parseLattice1Response(body.message); + const { data, errorMessage, responseCode } = parseLattice1Response( + body.message, + ); if (errorMessage || responseCode) { throw new LatticeResponseError(responseCode, errorMessage); @@ -157,7 +174,10 @@ export const retryWrapper = async ({ if ((errorMessage || responseCode) && retries) { if (isDeviceBusy(responseCode)) { await sleep(3000); - } else if (isWrongWallet(responseCode) && !client.skipRetryOnWrongWallet) { + } else if ( + isWrongWallet(responseCode) && + !client.skipRetryOnWrongWallet + ) { await client.fetchActiveWallet(); } else if (isInvalidEphemeralId(responseCode)) { await client.connect(client.deviceId); diff --git a/packages/sdk/src/shared/predicates.ts b/packages/sdk/src/shared/predicates.ts index 45afe241..25477720 100644 --- a/packages/sdk/src/shared/predicates.ts +++ b/packages/sdk/src/shared/predicates.ts @@ -2,12 +2,18 @@ import { LatticeResponseCode } from '../protocol'; import type { FirmwareConstants, FirmwareVersion } from '../types'; import { isFWSupported } from './utilities'; -export const isDeviceBusy = (responseCode: number) => responseCode === LatticeResponseCode.deviceBusy || responseCode === LatticeResponseCode.gceTimeout; +export const isDeviceBusy = (responseCode: number) => + responseCode === LatticeResponseCode.deviceBusy || + responseCode === LatticeResponseCode.gceTimeout; -export const isWrongWallet = (responseCode: number) => responseCode === LatticeResponseCode.wrongWallet; +export const isWrongWallet = (responseCode: number) => + responseCode === LatticeResponseCode.wrongWallet; -export const isInvalidEphemeralId = (responseCode: number) => responseCode === LatticeResponseCode.invalidEphemId; +export const isInvalidEphemeralId = (responseCode: number) => + responseCode === LatticeResponseCode.invalidEphemId; -export const doesFetchWalletsOnLoad = (fwVersion: FirmwareVersion) => isFWSupported(fwVersion, { major: 0, minor: 14, fix: 1 }); +export const doesFetchWalletsOnLoad = (fwVersion: FirmwareVersion) => + isFWSupported(fwVersion, { major: 0, minor: 14, fix: 1 }); -export const shouldUseEVMLegacyConverter = (fwConstants: FirmwareConstants) => fwConstants.genericSigning?.encodingTypes?.EVM; +export const shouldUseEVMLegacyConverter = (fwConstants: FirmwareConstants) => + fwConstants.genericSigning?.encodingTypes?.EVM; diff --git a/packages/sdk/src/shared/utilities.ts b/packages/sdk/src/shared/utilities.ts index 1a59617e..08367194 100644 --- a/packages/sdk/src/shared/utilities.ts +++ b/packages/sdk/src/shared/utilities.ts @@ -82,10 +82,17 @@ export const parseWallets = (walletData: any): ActiveWallets => { }; // Determine if a provided firmware version matches or exceeds the current firmware version -export const isFWSupported = (fwVersion: FirmwareVersion, versionSupported: FirmwareVersion): boolean => { +export const isFWSupported = ( + fwVersion: FirmwareVersion, + versionSupported: FirmwareVersion, +): boolean => { const { major, minor, fix } = fwVersion; const { major: _major, minor: _minor, fix: _fix } = versionSupported; - return major > _major || (major >= _major && minor > _minor) || (major >= _major && minor >= _minor && fix >= _fix); + return ( + major > _major || + (major >= _major && minor > _minor) || + (major >= _major && minor >= _minor && fix >= _fix) + ); }; /** diff --git a/packages/sdk/src/shared/validators.ts b/packages/sdk/src/shared/validators.ts index add1607a..5b5a9da9 100644 --- a/packages/sdk/src/shared/validators.ts +++ b/packages/sdk/src/shared/validators.ts @@ -2,7 +2,15 @@ import type { UInt4 } from 'bitwise/types'; import isEmpty from 'lodash/isEmpty.js'; import type { Client } from '../client'; import { ASCII_REGEX, EMPTY_WALLET_UID, MAX_ADDR } from '../constants'; -import type { ActiveWallets, FirmwareConstants, FirmwareVersion, KVRecords, KeyPair, LatticeError, Wallet } from '../types'; +import type { + ActiveWallets, + FirmwareConstants, + FirmwareVersion, + KVRecords, + KeyPair, + LatticeError, + Wallet, +} from '../types'; import { isUInt4 } from '../util'; export const validateIsUInt4 = (n?: number) => { @@ -26,14 +34,17 @@ export const validateStartPath = (startPath?: number[]) => { if (!startPath) { throw new Error('Start path is required'); } - if (startPath.length < 1 || startPath.length > 5) throw new Error('Path must include between 1 and 5 indices'); + if (startPath.length < 1 || startPath.length > 5) + throw new Error('Path must include between 1 and 5 indices'); return startPath; }; export const validateDeviceId = (deviceId?: string) => { if (!deviceId) { - throw new Error('No device ID has been stored. Please connect with your device ID first.'); + throw new Error( + 'No device ID has been stored. Please connect with your device ID first.', + ); } return deviceId; }; @@ -43,7 +54,9 @@ export const validateAppName = (name?: string) => { throw new Error('Name is required.'); } if (name.length < 5 || name.length > 24) { - throw new Error('Invalid length for name provided. Must be 5-24 characters.'); + throw new Error( + 'Invalid length for name provided. Must be 5-24 characters.', + ); } return name; }; @@ -85,7 +98,11 @@ export const validateFwVersion = (fwVersion?: FirmwareVersion) => { if (!fwVersion) { throw new Error('Firmware version does not exist. Please reconnect.'); } - if (typeof fwVersion.fix !== 'number' || typeof fwVersion.minor !== 'number' || typeof fwVersion.major !== 'number') { + if ( + typeof fwVersion.fix !== 'number' || + typeof fwVersion.minor !== 'number' || + typeof fwVersion.major !== 'number' + ) { throw new Error('Firmware version improperly formatted. Please reconnect.'); } return fwVersion; @@ -94,7 +111,9 @@ export const validateFwVersion = (fwVersion?: FirmwareVersion) => { export const validateRequestError = (err: LatticeError) => { const isTimeout = err.code === 'ECONNABORTED' && err.errno === 'ETIME'; if (isTimeout) { - throw new Error('Timeout waiting for device. Please ensure it is connected to the internet and try again in a minute.'); + throw new Error( + 'Timeout waiting for device. Please ensure it is connected to the internet and try again in a minute.', + ); } throw new Error(`Failed to make request to device:\n${err.message}`); }; @@ -129,7 +148,9 @@ export const validateConnectedClient = (client: Client) => { export const validateEphemeralPub = (ephemeralPub?: KeyPair) => { if (!ephemeralPub) { - throw new Error('`ephemeralPub` (ephemeral public key) is required. Please reconnect.'); + throw new Error( + '`ephemeralPub` (ephemeral public key) is required. Please reconnect.', + ); } return ephemeralPub; }; @@ -149,28 +170,52 @@ export const validateKey = (key?: KeyPair) => { }; export const validateActiveWallets = (activeWallets?: ActiveWallets) => { - if (!activeWallets || (activeWallets?.internal?.uid?.equals(EMPTY_WALLET_UID) && activeWallets?.external?.uid?.equals(EMPTY_WALLET_UID))) { + if ( + !activeWallets || + (activeWallets?.internal?.uid?.equals(EMPTY_WALLET_UID) && + activeWallets?.external?.uid?.equals(EMPTY_WALLET_UID)) + ) { throw new Error('No active wallet.'); } return activeWallets; }; -export const validateKvRecords = (records?: KVRecords, fwConstants?: FirmwareConstants) => { +export const validateKvRecords = ( + records?: KVRecords, + fwConstants?: FirmwareConstants, +) => { if (!fwConstants || !fwConstants.kvActionsAllowed) { throw new Error('Unsupported. Please update firmware.'); } else if (typeof records !== 'object' || Object.keys(records).length < 1) { - throw new Error('One or more key-value mapping must be provided in `records` param.'); + throw new Error( + 'One or more key-value mapping must be provided in `records` param.', + ); } else if (Object.keys(records).length > fwConstants.kvActionMaxNum) { - throw new Error(`Too many keys provided. Please only provide up to ${fwConstants.kvActionMaxNum}.`); + throw new Error( + `Too many keys provided. Please only provide up to ${fwConstants.kvActionMaxNum}.`, + ); } return records; }; -export const validateKvRecord = ({ key, val }: KVRecords, fwConstants: FirmwareConstants) => { - if (typeof key !== 'string' || String(key).length > fwConstants.kvKeyMaxStrSz) { - throw new Error(`Key ${key} too large. Must be <=${fwConstants.kvKeyMaxStrSz} characters.`); - } else if (typeof val !== 'string' || String(val).length > fwConstants.kvValMaxStrSz) { - throw new Error(`Value ${val} too large. Must be <=${fwConstants.kvValMaxStrSz} characters.`); +export const validateKvRecord = ( + { key, val }: KVRecords, + fwConstants: FirmwareConstants, +) => { + if ( + typeof key !== 'string' || + String(key).length > fwConstants.kvKeyMaxStrSz + ) { + throw new Error( + `Key ${key} too large. Must be <=${fwConstants.kvKeyMaxStrSz} characters.`, + ); + } else if ( + typeof val !== 'string' || + String(val).length > fwConstants.kvValMaxStrSz + ) { + throw new Error( + `Value ${val} too large. Must be <=${fwConstants.kvValMaxStrSz} characters.`, + ); } else if (String(key).length === 0 || String(val).length === 0) { throw new Error('Keys and values must be >0 characters.'); } else if (!ASCII_REGEX.test(key) || !ASCII_REGEX.test(val)) { diff --git a/packages/sdk/src/types/addKvRecords.ts b/packages/sdk/src/types/addKvRecords.ts index 460ac101..892f0de9 100644 --- a/packages/sdk/src/types/addKvRecords.ts +++ b/packages/sdk/src/types/addKvRecords.ts @@ -7,6 +7,7 @@ export interface AddKvRecordsRequestParams { caseSensitive?: boolean; } -export interface AddKvRecordsRequestFunctionParams extends AddKvRecordsRequestParams { +export interface AddKvRecordsRequestFunctionParams + extends AddKvRecordsRequestParams { client: Client; } diff --git a/packages/sdk/src/types/firmware.ts b/packages/sdk/src/types/firmware.ts index f1cb349b..d3b6bab3 100644 --- a/packages/sdk/src/types/firmware.ts +++ b/packages/sdk/src/types/firmware.ts @@ -41,7 +41,10 @@ export interface FirmwareConstants { extraDataFrameSz: number; extraDataMaxFrames: number; genericSigning: GenericSigningData; - getAddressFlags: [typeof EXTERNAL.GET_ADDR_FLAGS.ED25519_PUB, typeof EXTERNAL.GET_ADDR_FLAGS.SECP256K1_PUB]; + getAddressFlags: [ + typeof EXTERNAL.GET_ADDR_FLAGS.ED25519_PUB, + typeof EXTERNAL.GET_ADDR_FLAGS.SECP256K1_PUB, + ]; kvActionMaxNum: number; kvActionsAllowed: boolean; kvKeyMaxStrSz: number; diff --git a/packages/sdk/src/types/getAddresses.ts b/packages/sdk/src/types/getAddresses.ts index 4c7e22cf..7ec1bff5 100644 --- a/packages/sdk/src/types/getAddresses.ts +++ b/packages/sdk/src/types/getAddresses.ts @@ -7,6 +7,7 @@ export interface GetAddressesRequestParams { iterIdx?: number; } -export interface GetAddressesRequestFunctionParams extends GetAddressesRequestParams { +export interface GetAddressesRequestFunctionParams + extends GetAddressesRequestParams { client: Client; } diff --git a/packages/sdk/src/types/getKvRecords.ts b/packages/sdk/src/types/getKvRecords.ts index e9d0d3bd..f4c1d0e0 100644 --- a/packages/sdk/src/types/getKvRecords.ts +++ b/packages/sdk/src/types/getKvRecords.ts @@ -6,7 +6,8 @@ export interface GetKvRecordsRequestParams { start?: number; } -export interface GetKvRecordsRequestFunctionParams extends GetKvRecordsRequestParams { +export interface GetKvRecordsRequestFunctionParams + extends GetKvRecordsRequestParams { client: Client; } diff --git a/packages/sdk/src/types/removeKvRecords.ts b/packages/sdk/src/types/removeKvRecords.ts index 6b9c7ab5..a8cfc747 100644 --- a/packages/sdk/src/types/removeKvRecords.ts +++ b/packages/sdk/src/types/removeKvRecords.ts @@ -5,6 +5,7 @@ export interface RemoveKvRecordsRequestParams { ids?: string[]; } -export interface RemoveKvRecordsRequestFunctionParams extends RemoveKvRecordsRequestParams { +export interface RemoveKvRecordsRequestFunctionParams + extends RemoveKvRecordsRequestParams { client: Client; } diff --git a/packages/sdk/src/types/sign.ts b/packages/sdk/src/types/sign.ts index f1037309..f9ed2121 100644 --- a/packages/sdk/src/types/sign.ts +++ b/packages/sdk/src/types/sign.ts @@ -1,4 +1,12 @@ -import type { AccessList, Address, Hex, SignedAuthorization, SignedAuthorizationList, TypedData, TypedDataDefinition } from 'viem'; +import type { + AccessList, + Address, + Hex, + SignedAuthorization, + SignedAuthorizationList, + TypedData, + TypedDataDefinition, +} from 'viem'; import type { Client } from '../client'; import type { Currency, SigningPath, Wallet } from './client'; import type { FirmwareConstants } from './firmware'; @@ -64,11 +72,24 @@ export type EIP7702AuthListTransactionRequest = BaseTransactionRequest & { }; // Main discriminated union for transaction requests -export type TransactionRequest = LegacyTransactionRequest | EIP2930TransactionRequest | EIP1559TransactionRequest | EIP7702AuthTransactionRequest | EIP7702AuthListTransactionRequest; - -export interface SigningPayload = TypedData> { +export type TransactionRequest = + | LegacyTransactionRequest + | EIP2930TransactionRequest + | EIP1559TransactionRequest + | EIP7702AuthTransactionRequest + | EIP7702AuthListTransactionRequest; + +export interface SigningPayload< + TTypedData extends TypedData | Record = TypedData, +> { signerPath: SigningPath; - payload: Uint8Array | Uint8Array[] | Buffer | Buffer[] | Hex | EIP712MessagePayload; + payload: + | Uint8Array + | Uint8Array[] + | Buffer + | Buffer[] + | Hex + | EIP712MessagePayload; curveType: number; hashType: number; encodingType?: number; @@ -76,14 +97,18 @@ export interface SigningPayload = TypedData> { +export interface SignRequestParams< + TTypedData extends TypedData | Record = TypedData, +> { data: SigningPayload | BitcoinSignPayload; currency?: Currency; cachedData?: unknown; nextCode?: Buffer; } -export interface SignRequestFunctionParams = TypedData> extends SignRequestParams { +export interface SignRequestFunctionParams< + TTypedData extends TypedData | Record = TypedData, +> extends SignRequestParams { client: Client; } @@ -153,9 +178,16 @@ export interface DecodeSignResponseParams { } // Align EIP712MessagePayload with Viem's TypedDataDefinition -export interface EIP712MessagePayload = TypedData, TPrimaryType extends keyof TTypedData | 'EIP712Domain' = keyof TTypedData> { +export interface EIP712MessagePayload< + TTypedData extends TypedData | Record = TypedData, + TPrimaryType extends keyof TTypedData | 'EIP712Domain' = keyof TTypedData, +> { types: TTypedData; - domain: TTypedData extends TypedData ? TypedDataDefinition['domain'] : Record; + domain: TTypedData extends TypedData + ? TypedDataDefinition['domain'] + : Record; primaryType: TPrimaryType; - message: TTypedData extends TypedData ? TypedDataDefinition['message'] : Record; + message: TTypedData extends TypedData + ? TypedDataDefinition['message'] + : Record; } diff --git a/packages/sdk/src/util.ts b/packages/sdk/src/util.ts index 4c14be5d..ee80e6ff 100644 --- a/packages/sdk/src/util.ts +++ b/packages/sdk/src/util.ts @@ -15,9 +15,18 @@ import { type Hex, parseTransaction } from 'viem'; const EC = elliptic.ec; const { ecdsaRecover } = secp256k1; import { Calldata } from '.'; -import { BIP_CONSTANTS, EXTERNAL_NETWORKS_BY_CHAIN_ID_URL, HARDENED_OFFSET, NETWORKS_BY_CHAIN_ID, VERSION_BYTE } from './constants'; +import { + BIP_CONSTANTS, + EXTERNAL_NETWORKS_BY_CHAIN_ID_URL, + HARDENED_OFFSET, + NETWORKS_BY_CHAIN_ID, + VERSION_BYTE, +} from './constants'; import { LatticeResponseCode, ProtocolConstants } from './protocol'; -import { isValid4ByteResponse, isValidBlockExplorerResponse } from './shared/validators'; +import { + isValid4ByteResponse, + isValidBlockExplorerResponse, +} from './shared/validators'; import type { FirmwareConstants } from './types'; const { COINS, PURPOSES } = BIP_CONSTANTS; @@ -117,19 +126,37 @@ export const toPaddedDER = (sig: any): Buffer => { // TRANSACTION UTILS //-------------------------------------------------- /** @internal */ -export const isValidAssetPath = (path: number[], fwConstants: FirmwareConstants): boolean => { - const allowedPurposes = [PURPOSES.ETH, PURPOSES.BTC_LEGACY, PURPOSES.BTC_WRAPPED_SEGWIT, PURPOSES.BTC_SEGWIT]; +export const isValidAssetPath = ( + path: number[], + fwConstants: FirmwareConstants, +): boolean => { + const allowedPurposes = [ + PURPOSES.ETH, + PURPOSES.BTC_LEGACY, + PURPOSES.BTC_WRAPPED_SEGWIT, + PURPOSES.BTC_SEGWIT, + ]; const allowedCoins = [COINS.ETH, COINS.BTC, COINS.BTC_TESTNET]; // These coin types were given to us by MyCrypto. They should be allowed, but we expect // an Ethereum-type address with these coin types. // These all use SLIP44: https://github.com/satoshilabs/slips/blob/master/slip-0044.md - const allowedMyCryptoCoins = [60, 61, 966, 700, 9006, 9000, 1007, 553, 178, 137, 37310, 108, 40, 889, 1987, 820, 6060, 1620, 1313114, 76, 246529, 246785, 1001, 227, 916, 464, 2221, 344, 73799, 246]; + const allowedMyCryptoCoins = [ + 60, 61, 966, 700, 9006, 9000, 1007, 553, 178, 137, 37310, 108, 40, 889, + 1987, 820, 6060, 1620, 1313114, 76, 246529, 246785, 1001, 227, 916, 464, + 2221, 344, 73799, 246, + ]; // Make sure firmware supports this Bitcoin path const isBitcoin = path[1] === COINS.BTC || path[1] === COINS.BTC_TESTNET; - const isBitcoinNonWrappedSegwit = isBitcoin && path[0] !== PURPOSES.BTC_WRAPPED_SEGWIT; - if (isBitcoinNonWrappedSegwit && !fwConstants.allowBtcLegacyAndSegwitAddrs) return false; + const isBitcoinNonWrappedSegwit = + isBitcoin && path[0] !== PURPOSES.BTC_WRAPPED_SEGWIT; + if (isBitcoinNonWrappedSegwit && !fwConstants.allowBtcLegacyAndSegwitAddrs) + return false; // Make sure this path is otherwise valid - return allowedPurposes.indexOf(path[0]) >= 0 && (allowedCoins.indexOf(path[1]) >= 0 || allowedMyCryptoCoins.indexOf(path[1] - HARDENED_OFFSET) > 0); + return ( + allowedPurposes.indexOf(path[0]) >= 0 && + (allowedCoins.indexOf(path[1]) >= 0 || + allowedMyCryptoCoins.indexOf(path[1] - HARDENED_OFFSET) > 0) + ); }; /** @internal */ @@ -154,15 +181,23 @@ function isBase10NumStr(x: string): boolean { } /** @internal Ensure a param is represented by a buffer */ -export const ensureHexBuffer = (x: string | number | bigint | Buffer, zeroIsNull = true): Buffer => { +export const ensureHexBuffer = ( + x: string | number | bigint | Buffer, + zeroIsNull = true, +): Buffer => { try { const isZeroNumber = typeof x === 'number' && x === 0; const isZeroBigInt = typeof x === 'bigint' && x === 0n; - if (x === null || ((isZeroNumber || isZeroBigInt) && zeroIsNull === true)) return Buffer.alloc(0); - const isDecimalInput = typeof x === 'number' || typeof x === 'bigint' || (typeof x === 'string' && isBase10NumStr(x)); + if (x === null || ((isZeroNumber || isZeroBigInt) && zeroIsNull === true)) + return Buffer.alloc(0); + const isDecimalInput = + typeof x === 'number' || + typeof x === 'bigint' || + (typeof x === 'string' && isBase10NumStr(x)); let hexString: string; if (isDecimalInput) { - const formatted = typeof x === 'bigint' ? x.toString(10) : (x as string | number); + const formatted = + typeof x === 'bigint' ? x.toString(10) : (x as string | number); hexString = new BigNum(formatted).toString(16); } else if (typeof x === 'string' && x.slice(0, 2) === '0x') { hexString = x.slice(2); @@ -175,7 +210,9 @@ export const ensureHexBuffer = (x: string | number | bigint | Buffer, zeroIsNull if (hexString === '00' && !isDecimalInput) return Buffer.alloc(0); return Buffer.from(hexString, 'hex'); } catch (_err) { - throw new Error(`Cannot convert ${x.toString()} to hex buffer (${(_err as Error).message})`); + throw new Error( + `Cannot convert ${x.toString()} to hex buffer (${(_err as Error).message})`, + ); } }; @@ -196,7 +233,8 @@ export const fixLen = (msg: Buffer, length: number): Buffer => { export const aes256_encrypt = (data: Buffer, key: Buffer): Buffer => { const iv = Buffer.from(ProtocolConstants.aesIv); const aesCbc = new aes.ModeOfOperation.cbc(key, iv); - const paddedData = data.length % 16 === 0 ? data : aes.padding.pkcs7.pad(data); + const paddedData = + data.length % 16 === 0 ? data : aes.padding.pkcs7.pad(data); return Buffer.from(aesCbc.encrypt(paddedData)); }; @@ -210,7 +248,8 @@ export const aes256_decrypt = (data: Buffer, key: Buffer): Buffer => { // Decode a DER signature. Returns signature object {r, s } or null if there is an error /** @internal */ export const parseDER = (sigBuf: Buffer) => { - if (sigBuf[0] !== 0x30 || sigBuf[2] !== 0x02) throw new Error('Failed to decode DER signature'); + if (sigBuf[0] !== 0x30 || sigBuf[2] !== 0x02) + throw new Error('Failed to decode DER signature'); let off = 3; const rLen = sigBuf[off]; off++; @@ -239,11 +278,18 @@ export const getP256KeyPairFromPub = (pub: Buffer | string): any => { }; /** @internal */ -export const buildSignerPathBuf = (signerPath: number[], varAddrPathSzAllowed: boolean): Buffer => { +export const buildSignerPathBuf = ( + signerPath: number[], + varAddrPathSzAllowed: boolean, +): Buffer => { const buf = Buffer.alloc(24); let off = 0; - if (varAddrPathSzAllowed && signerPath.length > 5) throw new Error('Signer path must be <=5 indices.'); - if (!varAddrPathSzAllowed && signerPath.length !== 5) throw new Error('Your Lattice firmware only supports 5-index derivation paths. Please upgrade.'); + if (varAddrPathSzAllowed && signerPath.length > 5) + throw new Error('Signer path must be <=5 indices.'); + if (!varAddrPathSzAllowed && signerPath.length !== 5) + throw new Error( + 'Your Lattice firmware only supports 5-index derivation paths. Please upgrade.', + ); buf.writeUInt32LE(signerPath.length, off); off += 4; for (let i = 0; i < 5; i++) { @@ -278,7 +324,8 @@ export const isAsciiStr = (str: string, allowFormatChars = false): boolean => { }; /** @internal Check if a value exists in an object. Only checks first level of keys. */ -export const existsIn = (val: T, obj: { [key: string]: T }): boolean => Object.keys(obj).some((key) => obj[key] === val); +export const existsIn = (val: T, obj: { [key: string]: T }): boolean => + Object.keys(obj).some((key) => obj[key] === val); /** @internal Create a buffer of size `n` and fill it with random data */ export const randomBytes = (n: number): Buffer => { @@ -296,7 +343,9 @@ export const isUInt4 = (n: number) => isInteger(n) && inRange(n, 0, 16); * Fetches an external JSON file containing networks indexed by chain id from a GridPlus repo, and * returns the parsed JSON. */ -async function fetchExternalNetworkForChainId(chainId: number | string): Promise<{ +async function fetchExternalNetworkForChainId( + chainId: number | string, +): Promise<{ [key: string]: { name: string; baseUrl: string; @@ -304,7 +353,9 @@ async function fetchExternalNetworkForChainId(chainId: number | string): Promise }; }> { try { - const body = await fetch(EXTERNAL_NETWORKS_BY_CHAIN_ID_URL).then((res) => res.json()); + const body = await fetch(EXTERNAL_NETWORKS_BY_CHAIN_ID_URL).then((res) => + res.json(), + ); if (body) { return body[chainId]; } else { @@ -346,7 +397,10 @@ export function selectDefFrom4byteABI(abiData: any[], selector: string) { }) .find((result) => { try { - def = Calldata.EVM.parsers.parseCanonicalName(selector, result.text_signature); + def = Calldata.EVM.parsers.parseCanonicalName( + selector, + result.text_signature, + ); return !!def; } catch (_err) { console.error('Failed to parse canonical name:', _err); @@ -360,7 +414,10 @@ export function selectDefFrom4byteABI(abiData: any[], selector: string) { } } -export async function fetchWithTimeout(url: string, options: RequestInit & { timeout?: number }): Promise { +export async function fetchWithTimeout( + url: string, + options: RequestInit & { timeout?: number }, +): Promise { const { timeout = 8000 } = options; const controller = new AbortController(); const timeoutId = setTimeout(() => controller.abort(), timeout); @@ -372,7 +429,10 @@ export async function fetchWithTimeout(url: string, options: RequestInit & { tim return response; } -async function fetchAndCache(url: string, opts?: RequestInit): Promise { +async function fetchAndCache( + url: string, + opts?: RequestInit, +): Promise { try { if (globalThis.caches && globalThis.Request) { const cache = await caches.open('gp-calldata'); @@ -384,7 +444,10 @@ async function fetchAndCache(url: string, opts?: RequestInit): Promise const response = await fetch(request, opts); const responseClone = response.clone(); const data = await response.json(); - if (response.ok && (isValidBlockExplorerResponse(data) || isValid4ByteResponse(data))) { + if ( + response.ok && + (isValidBlockExplorerResponse(data) || isValid4ByteResponse(data)) + ) { await cache.put(request, responseClone); return cache.match(request); } @@ -399,7 +462,10 @@ async function fetchAndCache(url: string, opts?: RequestInit): Promise } } -async function fetchSupportedChainData(address: string, supportedChain: number) { +async function fetchSupportedChainData( + address: string, + supportedChain: number, +) { const url = buildUrlForSupportedChainAndAddress({ address, supportedChain }); return fetchAndCache(url) .then((res) => res.json()) @@ -408,7 +474,9 @@ async function fetchSupportedChainData(address: string, supportedChain: number) try { return JSON.parse(body.result); } catch { - throw new Error(`Invalid JSON in response: ${body.result.substring(0, 50)}`); + throw new Error( + `Invalid JSON in response: ${body.result.substring(0, 50)}`, + ); } } else { throw new Error('Server response was malformed'); @@ -453,7 +521,10 @@ async function postProcessDef(def, calldata) { // value (or for `bytes[]` each underlying value) is of size (4 + 32*n) // it could be nested calldata. We should use that item's selector(s) // to look up nested definition(s). - const nestedCalldata = Calldata.EVM.processors.getNestedCalldata(def, calldata); + const nestedCalldata = Calldata.EVM.processors.getNestedCalldata( + def, + calldata, + ); const nestedDefs = await replaceNestedDefs(nestedCalldata); // Need to recurse before doing the full replacement for await (const [i] of nestedDefs.entries()) { @@ -463,11 +534,17 @@ async function postProcessDef(def, calldata) { if (Array.isArray(nestedDefs[i]) && typeof nestedDefs[i][0] !== 'string') { for await (const [j] of nestedDefs[i].entries()) { if (nestedDefs[i][j] !== null) { - nestedDefs[i][j] = await postProcessDef(nestedDefs[i][j], Buffer.from(nestedCalldata[i][j].slice(2), 'hex')); + nestedDefs[i][j] = await postProcessDef( + nestedDefs[i][j], + Buffer.from(nestedCalldata[i][j].slice(2), 'hex'), + ); } } } else if (nestedDefs[i] !== null) { - nestedDefs[i] = await postProcessDef(nestedDefs[i], Buffer.from(nestedCalldata[i].slice(2), 'hex')); + nestedDefs[i] = await postProcessDef( + nestedDefs[i], + Buffer.from(nestedCalldata[i].slice(2), 'hex'), + ); } } // Replace any nested defs @@ -500,7 +577,10 @@ async function replaceNestedDefs(possNestedDefs) { try { const _nestedSelector = _d.slice(2, 10); const _nestedAbi = await fetch4byteData(_nestedSelector); - const _nestedDef = selectDefFrom4byteABI(_nestedAbi, _nestedSelector); + const _nestedDef = selectDefFrom4byteABI( + _nestedAbi, + _nestedSelector, + ); _nestedDefs.push(_nestedDef); } catch (_err) { console.error('Failed to fetch nested 4byte data:', _err); @@ -540,7 +620,12 @@ async function replaceNestedDefs(possNestedDefs) { /** * Fetches calldata from a remote scanner based on the transaction's `chainId` */ -export async function fetchCalldataDecoder(_data: Uint8Array | string, to: string, _chainId: number | string, recurse = true) { +export async function fetchCalldataDecoder( + _data: Uint8Array | string, + to: string, + _chainId: number | string, + recurse = true, +) { try { // Exit if there is no data. The 2 comes from the 0x prefix, but a later // check will confirm that there are at least 4 bytes of data in the buffer. @@ -559,18 +644,25 @@ export async function fetchCalldataDecoder(_data: Uint8Array | string, to: strin } if (data.length < 4) { - throw new Error('Data must contain at least 4 bytes of data to define the selector'); + throw new Error( + 'Data must contain at least 4 bytes of data to define the selector', + ); } const selector = Buffer.from(data.slice(0, 4)).toString('hex'); // Convert the chainId to a number and use it to determine if we can call out to // an etherscan-like explorer for richer data. const chainId = Number(_chainId); const cachedNetwork = NETWORKS_BY_CHAIN_ID[chainId]; - const supportedChain = cachedNetwork ? cachedNetwork : await fetchExternalNetworkForChainId(chainId); + const supportedChain = cachedNetwork + ? cachedNetwork + : await fetchExternalNetworkForChainId(chainId); try { if (supportedChain) { const abi = await fetchSupportedChainData(to, supportedChain); - const parsedAbi = Calldata.EVM.parsers.parseSolidityJSONABI(selector, abi); + const parsedAbi = Calldata.EVM.parsers.parseSolidityJSONABI( + selector, + abi, + ); let def = parsedAbi.def; if (recurse) { def = await postProcessDef(def, data); @@ -605,12 +697,23 @@ export async function fetchCalldataDecoder(_data: Uint8Array | string, to: strin * @returns an application secret as a Buffer * @public */ -export const generateAppSecret = (deviceId: Buffer | string, password: Buffer | string, appName: Buffer | string): Buffer => { - const deviceIdBuffer = typeof deviceId === 'string' ? Buffer.from(deviceId) : deviceId; - const passwordBuffer = typeof password === 'string' ? Buffer.from(password) : password; - const appNameBuffer = typeof appName === 'string' ? Buffer.from(appName) : appName; - - const preImage = Buffer.concat([deviceIdBuffer, passwordBuffer, appNameBuffer]); +export const generateAppSecret = ( + deviceId: Buffer | string, + password: Buffer | string, + appName: Buffer | string, +): Buffer => { + const deviceIdBuffer = + typeof deviceId === 'string' ? Buffer.from(deviceId) : deviceId; + const passwordBuffer = + typeof password === 'string' ? Buffer.from(password) : password; + const appNameBuffer = + typeof appName === 'string' ? Buffer.from(appName) : appName; + + const preImage = Buffer.concat([ + deviceIdBuffer, + passwordBuffer, + appNameBuffer, + ]); return Buffer.from(Hash.sha256(preImage)); }; @@ -628,7 +731,9 @@ export const getV = (tx: any, resp: any) => { let useEIP155 = false; if (Buffer.isBuffer(tx) || typeof tx === 'string') { - const txHex = Buffer.isBuffer(tx) ? (`0x${tx.toString('hex')}` as Hex) : (tx as Hex); + const txHex = Buffer.isBuffer(tx) + ? (`0x${tx.toString('hex')}` as Hex) + : (tx as Hex); const txBuf = Buffer.isBuffer(tx) ? tx : Buffer.from(tx.slice(2), 'hex'); hash = Buffer.from(Hash.keccak256(txBuf)); @@ -657,7 +762,9 @@ export const getV = (tx: any, resp: any) => { } catch (err) { console.error('Failed to parse transaction, trying legacy format:', err); try { - const txBufRaw = Buffer.isBuffer(tx) ? tx : Buffer.from(tx.slice(2), 'hex'); + const txBufRaw = Buffer.isBuffer(tx) + ? tx + : Buffer.from(tx.slice(2), 'hex'); const legacyTxArray = RLP.decode(txBufRaw); type = 'legacy'; @@ -673,11 +780,17 @@ export const getV = (tx: any, resp: any) => { } } } else { - throw new Error('Unsupported transaction format. Expected Buffer or hex string.'); + throw new Error( + 'Unsupported transaction format. Expected Buffer or hex string.', + ); } - const rBuf = Buffer.isBuffer(resp.sig.r) ? resp.sig.r : Buffer.from(resp.sig.r.slice(2), 'hex'); - const sBuf = Buffer.isBuffer(resp.sig.s) ? resp.sig.s : Buffer.from(resp.sig.s.slice(2), 'hex'); + const rBuf = Buffer.isBuffer(resp.sig.r) + ? resp.sig.r + : Buffer.from(resp.sig.r.slice(2), 'hex'); + const sBuf = Buffer.isBuffer(resp.sig.s) + ? resp.sig.s + : Buffer.from(resp.sig.s.slice(2), 'hex'); const rs = new Uint8Array(Buffer.concat([rBuf, sBuf])); const pubkeyInput = resp.pubkey; @@ -691,7 +804,9 @@ export const getV = (tx: any, resp: any) => { } else if (pubkeyInput instanceof Uint8Array) { pubkeyBuf = Buffer.from(pubkeyInput); } else if (typeof pubkeyInput === 'string') { - const hex = pubkeyInput.startsWith('0x') ? pubkeyInput.slice(2) : pubkeyInput; + const hex = pubkeyInput.startsWith('0x') + ? pubkeyInput.slice(2) + : pubkeyInput; pubkeyBuf = Buffer.from(hex, 'hex'); } else { pubkeyBuf = Buffer.from(pubkeyInput); @@ -701,7 +816,8 @@ export const getV = (tx: any, resp: any) => { pubkeyBuf = Buffer.concat([Buffer.from([0x04]), pubkeyBuf]); } - const isCompressedPubkey = pubkeyBuf.length === 33 && (pubkeyBuf[0] === 0x02 || pubkeyBuf[0] === 0x03); + const isCompressedPubkey = + pubkeyBuf.length === 33 && (pubkeyBuf[0] === 0x02 || pubkeyBuf[0] === 0x03); const isUncompressedPubkey = pubkeyBuf.length === 65 && pubkeyBuf[0] === 0x04; if (!isCompressedPubkey && !isUncompressedPubkey) { @@ -721,7 +837,9 @@ export const getV = (tx: any, resp: any) => { } else if (pubkeyStr === recovery1Str) { recovery = 1; } else { - throw new Error('Failed to recover V parameter. Bad signature or transaction data.'); + throw new Error( + 'Failed to recover V parameter. Bad signature or transaction data.', + ); } // Use the consolidated v parameter conversion logic @@ -752,13 +870,23 @@ export const getV = (tx: any, resp: any) => { * @param txData - Transaction data containing chainId, useEIP155, and type * @returns The properly formatted v value as Buffer or BN */ -export const convertRecoveryToV = (recovery: number, txData: any = {}): Buffer | InstanceType => { +export const convertRecoveryToV = ( + recovery: number, + txData: any = {}, +): Buffer | InstanceType => { const { chainId, useEIP155, type } = txData; // For typed transactions (EIP-2930, EIP-1559, EIP-7702), we want the recoveryParam (0 or 1) // rather than the `v` value because the `chainId` is already included in the // transaction payload. - if (type === 1 || type === 2 || type === 4 || type === 'eip2930' || type === 'eip1559' || type === 'eip7702') { + if ( + type === 1 || + type === 2 || + type === 4 || + type === 'eip2930' || + type === 'eip1559' || + type === 'eip7702' + ) { return ensureHexBuffer(recovery, true); // 0 or 1, with 0 expected as an empty buffer } else if (!useEIP155 || !chainId) { // For ETH messages and non-EIP155 chains the set should be [27, 28] for `v` @@ -785,10 +913,27 @@ export const convertRecoveryToV = (recovery: number, txData: any = {}): Buffer | * @param publicKey - Expected public key * @returns 0 or 1 for the y-parity value */ -export const getYParity = (messageHash: Buffer | Uint8Array | string | { messageHash: any; signature: any; publicKey: any } | any, signature?: { r: any; s: any } | any, publicKey?: Buffer | Uint8Array | string): number => { +export const getYParity = ( + messageHash: + | Buffer + | Uint8Array + | string + | { messageHash: any; signature: any; publicKey: any } + | any, + signature?: { r: any; s: any } | any, + publicKey?: Buffer | Uint8Array | string, +): number => { // Handle legacy object format for backward compatibility - if (typeof messageHash === 'object' && messageHash && 'messageHash' in messageHash) { - return getYParity(messageHash.messageHash, messageHash.signature, messageHash.publicKey); + if ( + typeof messageHash === 'object' && + messageHash && + 'messageHash' in messageHash + ) { + return getYParity( + messageHash.messageHash, + messageHash.signature, + messageHash.publicKey, + ); } // Handle legacy transaction format for backward compatibility @@ -807,7 +952,11 @@ export const getYParity = (messageHash: Buffer | Uint8Array | string | { message // Handle transaction objects with getMessageToSign let hash = messageHash; - if (typeof messageHash === 'object' && messageHash && typeof messageHash.getMessageToSign === 'function') { + if ( + typeof messageHash === 'object' && + messageHash && + typeof messageHash.getMessageToSign === 'function' + ) { const type = messageHash._type; if (type !== undefined && type !== null) { // EIP-1559 / EIP-2930 / future typed transactions @@ -839,7 +988,8 @@ export const getYParity = (messageHash: Buffer | Uint8Array | string | { message const pubkeyBuf = toBuffer(publicKey); // For non-32 byte hashes, hash them (legacy support) - const finalHash = hashBuf.length === 32 ? hashBuf : Buffer.from(Hash.keccak256(hashBuf)); + const finalHash = + hashBuf.length === 32 ? hashBuf : Buffer.from(Hash.keccak256(hashBuf)); // Combine r and s const rs = new Uint8Array(Buffer.concat([rBuf, sBuf])); @@ -856,7 +1006,9 @@ export const getYParity = (messageHash: Buffer | Uint8Array | string | { message } catch {} } - throw new Error('Failed to recover Y parity. Bad signature or transaction data.'); + throw new Error( + 'Failed to recover Y parity. Bad signature or transaction data.', + ); }; /** @internal */ From 21785cc83424576d73b24c8024d39e1d556225e3 Mon Sep 17 00:00:00 2001 From: netbonus <151201453+netbonus@users.noreply.github.com> Date: Wed, 21 Jan 2026 16:46:26 -0500 Subject: [PATCH 11/14] chore: merge dev and add @gridplus/btc package - Add @gridplus/btc package with Biome linting - Add btc xpub utilities to SDK - Update root scripts to include @gridplus/btc in CI - Add @types/node to docs package - Fix e2e args passthrough in CI --- .github/workflows/build-test.yml | 24 +- package.json | 11 +- packages/btc/biome.json | 8 + packages/btc/package.json | 46 ++++ .../btc/src/__test__/unit/network.test.ts | 51 ++++ .../__test__/unit/provider/blockbook.test.ts | 152 ++++++++++++ .../btc/src/__test__/unit/slip132.test.ts | 85 +++++++ packages/btc/src/__test__/unit/tx.test.ts | 111 +++++++++ packages/btc/src/__test__/unit/wallet.test.ts | 108 +++++++++ packages/btc/src/constants.ts | 31 +++ packages/btc/src/index.ts | 67 ++++++ packages/btc/src/network.ts | 78 +++++++ packages/btc/src/provider/blockbook.ts | 193 +++++++++++++++ packages/btc/src/provider/index.ts | 2 + packages/btc/src/provider/types.ts | 115 +++++++++ packages/btc/src/slip132.ts | 110 +++++++++ packages/btc/src/tx.ts | 219 ++++++++++++++++++ packages/btc/src/types.ts | 80 +++++++ packages/btc/src/wallet.ts | 196 ++++++++++++++++ packages/btc/tsconfig.json | 24 ++ packages/btc/tsup.config.ts | 31 +++ packages/btc/vitest.config.ts | 7 + packages/docs/package.json | 1 + packages/sdk/package.json | 1 + .../sdk/src/__test__/e2e/btc-xpub.test.ts | 26 +++ .../sdk/src/__test__/integration/btc.test.ts | 49 ++++ .../sdk/src/__test__/unit/btc/xpub.test.ts | 62 +++++ packages/sdk/src/btc/index.ts | 59 +++++ packages/sdk/src/btc/xpub.ts | 119 ++++++++++ packages/sdk/src/index.ts | 1 + pnpm-lock.yaml | 40 ++++ 31 files changed, 2091 insertions(+), 16 deletions(-) create mode 100644 packages/btc/biome.json create mode 100644 packages/btc/package.json create mode 100644 packages/btc/src/__test__/unit/network.test.ts create mode 100644 packages/btc/src/__test__/unit/provider/blockbook.test.ts create mode 100644 packages/btc/src/__test__/unit/slip132.test.ts create mode 100644 packages/btc/src/__test__/unit/tx.test.ts create mode 100644 packages/btc/src/__test__/unit/wallet.test.ts create mode 100644 packages/btc/src/constants.ts create mode 100644 packages/btc/src/index.ts create mode 100644 packages/btc/src/network.ts create mode 100644 packages/btc/src/provider/blockbook.ts create mode 100644 packages/btc/src/provider/index.ts create mode 100644 packages/btc/src/provider/types.ts create mode 100644 packages/btc/src/slip132.ts create mode 100644 packages/btc/src/tx.ts create mode 100644 packages/btc/src/types.ts create mode 100644 packages/btc/src/wallet.ts create mode 100644 packages/btc/tsconfig.json create mode 100644 packages/btc/tsup.config.ts create mode 100644 packages/btc/vitest.config.ts create mode 100644 packages/sdk/src/__test__/e2e/btc-xpub.test.ts create mode 100644 packages/sdk/src/__test__/integration/btc.test.ts create mode 100644 packages/sdk/src/__test__/unit/btc/xpub.test.ts create mode 100644 packages/sdk/src/btc/index.ts create mode 100644 packages/sdk/src/btc/xpub.ts diff --git a/.github/workflows/build-test.yml b/.github/workflows/build-test.yml index 4ec8b44b..247cfe2e 100644 --- a/.github/workflows/build-test.yml +++ b/.github/workflows/build-test.yml @@ -38,16 +38,16 @@ jobs: run: pnpm install --frozen-lockfile - name: Lint - run: pnpm turbo run lint + run: pnpm lint - name: Type check - run: pnpm turbo run typecheck + run: pnpm typecheck - name: Test - run: pnpm turbo run test + run: pnpm test - name: Build - run: pnpm turbo run build + run: pnpm build - name: Release a nightly build if: env.INTERNAL_EVENT == 'true' @@ -111,19 +111,21 @@ jobs: PAIRING_SECRET: '12345678' ENC_PW: '12345678' APP_NAME: 'lattice-manager' - run: pnpm run e2e --reporter=basic + run: pnpm run e2e -- --reporter=basic - name: Show simulator logs on failure if: failure() && env.INTERNAL_EVENT == 'true' - working-directory: lattice-simulator run: | - echo "=== Simulator logs ===" - cat simulator.log || echo "No simulator logs found" + if [ -d lattice-simulator ]; then + echo "=== Simulator logs ===" + cat lattice-simulator/simulator.log || echo "No simulator logs found" + else + echo "Simulator directory not found, skipping logs" + fi - name: Stop simulator if: always() && env.INTERNAL_EVENT == 'true' - working-directory: lattice-simulator run: | - if [ -f simulator.pid ]; then - kill $(cat simulator.pid) || true + if [ -f lattice-simulator/simulator.pid ]; then + kill $(cat lattice-simulator/simulator.pid) || true fi diff --git a/package.json b/package.json index 061c6916..d26cc23c 100644 --- a/package.json +++ b/package.json @@ -3,11 +3,12 @@ "private": true, "packageManager": "pnpm@10.6.2", "scripts": { - "build": "turbo run build --filter=gridplus-sdk", - "test": "turbo run test --filter=gridplus-sdk", - "lint": "turbo run lint --filter=gridplus-sdk", - "lint:fix": "turbo run lint:fix --filter=gridplus-sdk", - "typecheck": "turbo run typecheck --filter=gridplus-sdk", + "build": "turbo run build --filter=gridplus-sdk --filter=@gridplus/btc", + "test": "turbo run test --filter=gridplus-sdk --filter=@gridplus/btc", + "test-unit": "turbo run test-unit --filter=gridplus-sdk --filter=@gridplus/btc", + "lint": "turbo run lint --filter=gridplus-sdk --filter=@gridplus/btc", + "lint:fix": "turbo run lint:fix --filter=gridplus-sdk --filter=@gridplus/btc", + "typecheck": "turbo run typecheck --filter=gridplus-sdk --filter=@gridplus/btc", "e2e": "turbo run e2e --filter=gridplus-sdk", "docs:build": "pnpm --filter gridplus-sdk-docs run build", "docs:start": "pnpm --filter gridplus-sdk-docs run start" diff --git a/packages/btc/biome.json b/packages/btc/biome.json new file mode 100644 index 00000000..c365284a --- /dev/null +++ b/packages/btc/biome.json @@ -0,0 +1,8 @@ +{ + "$schema": "https://biomejs.dev/schemas/1.9.4/schema.json", + "extends": ["../../biome.json"], + "files": { + "include": ["src/**/*.ts"], + "ignore": ["**/dist/**", "**/node_modules/**"] + } +} diff --git a/packages/btc/package.json b/packages/btc/package.json new file mode 100644 index 00000000..aa25d38e --- /dev/null +++ b/packages/btc/package.json @@ -0,0 +1,46 @@ +{ + "name": "@gridplus/btc", + "version": "0.1.0", + "type": "module", + "description": "BTC utilities for GridPlus SDK - SLIP-132, network inference, transaction building, wallet utilities", + "scripts": { + "build": "tsup", + "test": "vitest run ./src/__test__/unit", + "lint": "biome check src", + "lint:fix": "biome check --write src", + "typecheck": "tsc --noEmit" + }, + "files": [ + "dist" + ], + "main": "./dist/index.cjs", + "module": "./dist/index.mjs", + "exports": { + ".": { + "types": "./dist/index.d.ts", + "import": "./dist/index.mjs", + "require": "./dist/index.cjs" + }, + "./package.json": "./package.json" + }, + "types": "./dist/index.d.ts", + "repository": { + "type": "git", + "url": "https://github.com/GridPlus/gridplus-sdk.git", + "directory": "packages/btc" + }, + "dependencies": { + "bs58check": "^4.0.0" + }, + "devDependencies": { + "@biomejs/biome": "^1.9.0", + "@types/node": "^24.10.4", + "tsup": "^8.5.0", + "typescript": "^5.9.2", + "vitest": "3.2.4" + }, + "license": "MIT", + "engines": { + "node": ">=20" + } +} diff --git a/packages/btc/src/__test__/unit/network.test.ts b/packages/btc/src/__test__/unit/network.test.ts new file mode 100644 index 00000000..f90b39d0 --- /dev/null +++ b/packages/btc/src/__test__/unit/network.test.ts @@ -0,0 +1,51 @@ +import bs58check from 'bs58check'; +import { SLIP132_VERSION_BYTES } from '../../constants'; +import { + inferFromXpub, + getCoinType, + getNetworkFromCoinType, + isTestnet, +} from '../../network'; + +const TEST_XPUB = + 'xpub661MyMwAqRbcFtXgS5sYJABqqG9YLmC4Q1Rdap9gSE8NqtwybGhePY2gZ29ESFjqJoCu1Rupje8YtGqsefD265TMg7usUDFdp6W1EGMcet8'; +const toVersion = (xpub: string, version: number) => { + const decoded = bs58check.decode(xpub); + const converted = new Uint8Array(decoded.length); + const view = new DataView(converted.buffer); + view.setUint32(0, version, false); + converted.set(decoded.subarray(4), 4); + return bs58check.encode(converted); +}; + +const TEST_TPUB = toVersion(TEST_XPUB, SLIP132_VERSION_BYTES.tpub.public); + +describe('btc/network', () => { + it('infers mainnet from xpub', () => { + expect(inferFromXpub(TEST_XPUB)).toBe('mainnet'); + }); + + it('infers testnet from tpub', () => { + expect(inferFromXpub(TEST_TPUB)).toBe('testnet'); + }); + + it('returns coin type for mainnet', () => { + expect(getCoinType('mainnet')).toBe(0); + }); + + it('returns coin type for testnet/regtest', () => { + expect(getCoinType('testnet')).toBe(1); + expect(getCoinType('regtest')).toBe(1); + }); + + it('returns network from coin type', () => { + expect(getNetworkFromCoinType(0)).toBe('mainnet'); + expect(getNetworkFromCoinType(1)).toBe('testnet'); + }); + + it('identifies testnet-like networks', () => { + expect(isTestnet('mainnet')).toBe(false); + expect(isTestnet('testnet')).toBe(true); + expect(isTestnet('regtest')).toBe(true); + }); +}); diff --git a/packages/btc/src/__test__/unit/provider/blockbook.test.ts b/packages/btc/src/__test__/unit/provider/blockbook.test.ts new file mode 100644 index 00000000..8852c212 --- /dev/null +++ b/packages/btc/src/__test__/unit/provider/blockbook.test.ts @@ -0,0 +1,152 @@ +import { + BlockbookProvider, + createBlockbookProvider, +} from '../../../provider/blockbook'; + +const buildResponse = (data: unknown, ok = true, status = 200) => ({ + ok, + status, + json: async () => data, + text: async () => JSON.stringify(data), +}); + +describe('btc/provider/blockbook', () => { + const fetchMock = vi.fn(); + + beforeEach(() => { + fetchMock.mockReset(); + vi.stubGlobal('fetch', fetchMock); + }); + + afterEach(() => { + vi.unstubAllGlobals(); + vi.restoreAllMocks(); + }); + + it('uses baseUrl without trailing slash', async () => { + const provider = new BlockbookProvider({ baseUrl: 'https://example.com/' }); + fetchMock.mockResolvedValueOnce( + buildResponse({ + address: 'xpub', + balance: '0', + totalReceived: '0', + totalSent: '0', + unconfirmedBalance: '0', + unconfirmedTxs: 0, + txs: 0, + }), + ); + + await provider.getSummary('xpub123'); + expect(fetchMock).toHaveBeenCalledWith( + 'https://example.com/api/v2/xpub/xpub123?details=basic', + expect.any(Object), + ); + }); + + it('fetches summary data', async () => { + const provider = createBlockbookProvider({ + baseUrl: 'https://example.com', + }); + fetchMock.mockResolvedValueOnce( + buildResponse({ + address: 'xpub', + balance: '100', + totalReceived: '150', + totalSent: '50', + unconfirmedBalance: '0', + unconfirmedTxs: 0, + txs: 2, + }), + ); + + const summary = await provider.getSummary('xpub123'); + expect(summary.balance).toBe('100'); + }); + + it('fetches transactions with paging', async () => { + const provider = new BlockbookProvider({ baseUrl: 'https://example.com' }); + fetchMock.mockResolvedValueOnce( + buildResponse({ + transactions: [ + { + txid: 'tx1', + version: 1, + vin: [], + vout: [], + blockHeight: 1, + confirmations: 1, + blockTime: 0, + value: '0', + valueIn: '0', + fees: '0', + }, + ], + }), + ); + + const txs = await provider.getTransactions('xpub123', { + page: 2, + pageSize: 10, + }); + expect(fetchMock.mock.calls[0][0]).toContain('page=2'); + expect(fetchMock.mock.calls[0][0]).toContain('pageSize=10'); + expect(txs).toHaveLength(1); + }); + + it('fetches UTXOs', async () => { + const provider = new BlockbookProvider({ baseUrl: 'https://example.com' }); + fetchMock.mockResolvedValueOnce( + buildResponse([ + { + txid: 'tx1', + vout: 0, + value: '1000', + height: 1, + confirmations: 1, + }, + ]), + ); + + const utxos = await provider.getUtxos('xpub123'); + expect(utxos).toHaveLength(1); + }); + + it('broadcasts a raw transaction', async () => { + const provider = new BlockbookProvider({ baseUrl: 'https://example.com' }); + fetchMock.mockResolvedValueOnce(buildResponse({ result: 'txid123' })); + + const txid = await provider.broadcast('rawtx'); + expect(txid).toBe('txid123'); + }); + + it('throws on non-ok responses', async () => { + const provider = new BlockbookProvider({ baseUrl: 'https://example.com' }); + fetchMock.mockResolvedValueOnce( + buildResponse({ error: 'bad' }, false, 500), + ); + + await expect(provider.getSummary('xpub123')).rejects.toThrow( + 'Blockbook request failed: 500', + ); + }); + + it('fetches fee rates with conversions', async () => { + const provider = new BlockbookProvider({ baseUrl: 'https://example.com' }); + fetchMock.mockImplementation((url: string) => { + if (url.includes('/estimatefee/2')) { + return Promise.resolve(buildResponse({ result: '0.00002' })); + } + if (url.includes('/estimatefee/6')) { + return Promise.resolve(buildResponse({ result: '0.00003' })); + } + if (url.includes('/estimatefee/12')) { + return Promise.resolve(buildResponse({ result: '0.00001' })); + } + return Promise.resolve(buildResponse({})); + }); + + const fees = await provider.getFeeRates(); + expect(fees).toEqual({ fast: 2, medium: 3, slow: 1 }); + }); +}); diff --git a/packages/btc/src/__test__/unit/slip132.test.ts b/packages/btc/src/__test__/unit/slip132.test.ts new file mode 100644 index 00000000..351f8756 --- /dev/null +++ b/packages/btc/src/__test__/unit/slip132.test.ts @@ -0,0 +1,85 @@ +import bs58check from 'bs58check'; +import { SLIP132_VERSION_BYTES } from '../../constants'; +import { + getPrefix, + normalize, + format, + inferPurpose, + getVersionBytes, +} from '../../slip132'; + +const TEST_XPUB = + 'xpub661MyMwAqRbcFtXgS5sYJABqqG9YLmC4Q1Rdap9gSE8NqtwybGhePY2gZ29ESFjqJoCu1Rupje8YtGqsefD265TMg7usUDFdp6W1EGMcet8'; +const toVersion = (xpub: string, version: number) => { + const decoded = bs58check.decode(xpub); + const converted = new Uint8Array(decoded.length); + const view = new DataView(converted.buffer); + view.setUint32(0, version, false); + converted.set(decoded.subarray(4), 4); + return bs58check.encode(converted); +}; + +const TEST_TPUB = toVersion(TEST_XPUB, SLIP132_VERSION_BYTES.tpub.public); + +describe('btc/slip132', () => { + describe('getPrefix', () => { + it('identifies xpub prefix', () => { + expect(getPrefix(TEST_XPUB)).toBe('xpub'); + }); + + it('identifies tpub prefix', () => { + expect(getPrefix(TEST_TPUB)).toBe('tpub'); + }); + + it('throws for invalid xpub', () => { + expect(() => getPrefix('invalid')).toThrow(); + }); + }); + + describe('getVersionBytes', () => { + it('extracts version bytes from xpub', () => { + expect(getVersionBytes(TEST_XPUB)).toBe(0x0488b21e); + }); + }); + + describe('normalize', () => { + it('normalizes xpub to xpub (no change)', () => { + const result = normalize(TEST_XPUB); + expect(result.startsWith('xpub')).toBe(true); + }); + + it('normalizes ypub to xpub', () => { + const ypub = format(TEST_XPUB, 49, 'mainnet'); + const result = normalize(ypub); + expect(result.startsWith('xpub')).toBe(true); + }); + }); + + describe('format', () => { + it('formats xpub to ypub for purpose 49', () => { + const result = format(TEST_XPUB, 49, 'mainnet'); + expect(result.startsWith('ypub')).toBe(true); + }); + + it('formats xpub to zpub for purpose 84', () => { + const result = format(TEST_XPUB, 84, 'mainnet'); + expect(result.startsWith('zpub')).toBe(true); + }); + + it('formats to testnet prefix for testnet network', () => { + const result = format(TEST_XPUB, 44, 'testnet'); + expect(result.startsWith('tpub')).toBe(true); + }); + }); + + describe('inferPurpose', () => { + it('infers purpose 44 from xpub', () => { + expect(inferPurpose(TEST_XPUB)).toBe(44); + }); + + it('infers purpose 84 from zpub', () => { + const zpub = format(TEST_XPUB, 84, 'mainnet'); + expect(inferPurpose(zpub)).toBe(84); + }); + }); +}); diff --git a/packages/btc/src/__test__/unit/tx.test.ts b/packages/btc/src/__test__/unit/tx.test.ts new file mode 100644 index 00000000..4e4b8e3e --- /dev/null +++ b/packages/btc/src/__test__/unit/tx.test.ts @@ -0,0 +1,111 @@ +import type { WalletUtxo } from '../../types'; +import { buildTxReq, estimateFee } from '../../tx'; +import { HARDENED_OFFSET } from '../../constants'; + +const utxos: WalletUtxo[] = [ + { + txid: 'tx1', + vout: 0, + value: 100000, + confirmations: 6, + address: 'bc1qtest', + path: [HARDENED_OFFSET + 84, HARDENED_OFFSET, HARDENED_OFFSET, 0, 0], + scriptType: 'p2wpkh', + }, +]; + +describe('btc/tx', () => { + it('estimates fee for a standard segwit tx', () => { + const fee = estimateFee(1, 10, 'p2wpkh'); + expect(fee).toBe(1420); + }); + + it('builds a tx request with change', () => { + const result = buildTxReq({ + utxos, + recipient: 'bc1qrecipient', + value: 50000, + feeRate: 10, + purpose: 84, + changeIndex: 5, + }); + + expect(result.prevOuts).toHaveLength(1); + expect(result.fee).toBe(1420); + expect(result.changeValue).toBe(48580); + expect(result.changePath).toEqual([ + HARDENED_OFFSET + 84, + HARDENED_OFFSET, + HARDENED_OFFSET, + 1, + 5, + ]); + }); + + it('selects the largest UTXO first', () => { + const result = buildTxReq({ + utxos: [ + { ...utxos[0], txid: 'small', value: 20000 }, + { ...utxos[0], txid: 'large', value: 80000 }, + ], + recipient: 'bc1qrecipient', + value: 50000, + feeRate: 10, + purpose: 84, + changeIndex: 0, + }); + + expect(result.prevOuts).toHaveLength(1); + expect(result.prevOuts[0].txHash).toBe('large'); + }); + + it('throws for empty UTXO set', () => { + expect(() => + buildTxReq({ + utxos: [], + recipient: 'bc1qrecipient', + value: 50000, + feeRate: 10, + purpose: 84, + changeIndex: 0, + }), + ).toThrow('No UTXOs available'); + }); + + it('throws for invalid value or fee rate', () => { + expect(() => + buildTxReq({ + utxos, + recipient: 'bc1qrecipient', + value: 0, + feeRate: 10, + purpose: 84, + changeIndex: 0, + }), + ).toThrow('Value must be positive'); + + expect(() => + buildTxReq({ + utxos, + recipient: 'bc1qrecipient', + value: 1000, + feeRate: 0, + purpose: 84, + changeIndex: 0, + }), + ).toThrow('Fee rate must be positive'); + }); + + it('throws when funds are insufficient', () => { + expect(() => + buildTxReq({ + utxos, + recipient: 'bc1qrecipient', + value: 99000, + feeRate: 10, + purpose: 84, + changeIndex: 0, + }), + ).toThrow('Insufficient funds'); + }); +}); diff --git a/packages/btc/src/__test__/unit/wallet.test.ts b/packages/btc/src/__test__/unit/wallet.test.ts new file mode 100644 index 00000000..c7443b10 --- /dev/null +++ b/packages/btc/src/__test__/unit/wallet.test.ts @@ -0,0 +1,108 @@ +import type { BtcProvider } from '../../provider/types'; +import { getSnapshot, getSummary } from '../../wallet'; +import { HARDENED_OFFSET } from '../../constants'; + +const provider: BtcProvider = { + getSummary: vi.fn(), + getTransactions: vi.fn(), + getUtxos: vi.fn(), + broadcast: vi.fn(), + getFeeRates: vi.fn(), +}; + +describe('btc/wallet', () => { + beforeEach(() => { + vi.resetAllMocks(); + }); + + it('returns wallet summary with parsed values', async () => { + (provider.getSummary as any).mockResolvedValue({ + address: 'xpub', + balance: '100000', + totalReceived: '150000', + totalSent: '50000', + unconfirmedBalance: '0', + unconfirmedTxs: 0, + txs: 3, + }); + (provider.getUtxos as any).mockResolvedValue([ + { + txid: 'tx1', + vout: 0, + value: '50000', + height: 1, + confirmations: 6, + }, + ]); + + const summary = await getSummary({ xpub: 'xpub', provider }); + expect(summary.balance).toBe(100000); + expect(summary.txCount).toBe(3); + expect(summary.utxoCount).toBe(1); + }); + + it('builds a wallet snapshot with address info', async () => { + (provider.getSummary as any).mockResolvedValue({ + address: 'xpub', + balance: '100000', + totalReceived: '150000', + totalSent: '50000', + unconfirmedBalance: '0', + unconfirmedTxs: 0, + txs: 3, + tokens: [ + { + type: 'XPUBAddress', + name: 'bc1qreceive', + path: "m/84'/0'/0'/0/3", + transfers: 1, + decimals: 8, + balance: '0', + totalReceived: '0', + totalSent: '0', + }, + { + type: 'XPUBAddress', + name: 'bc1qchange', + path: "m/84'/0'/0'/1/1", + transfers: 1, + decimals: 8, + balance: '0', + totalReceived: '0', + totalSent: '0', + }, + ], + }); + (provider.getUtxos as any).mockResolvedValue([ + { + txid: 'tx1', + vout: 0, + value: '50000', + height: 1, + confirmations: 6, + address: 'bc1qutxo', + path: "m/84'/0'/0'/0/0", + }, + ]); + + const snapshot = await getSnapshot({ + xpub: 'xpub', + provider, + purpose: 84, + }); + + expect(snapshot.utxos).toHaveLength(1); + expect(snapshot.utxos[0].scriptType).toBe('p2wpkh'); + expect(snapshot.utxos[0].path).toEqual([ + HARDENED_OFFSET + 84, + HARDENED_OFFSET + 0, + HARDENED_OFFSET + 0, + 0, + 0, + ]); + expect(snapshot.addresses.receiving).toEqual(['bc1qreceive']); + expect(snapshot.addresses.change).toEqual(['bc1qchange']); + expect(snapshot.nextReceiveIndex).toBe(4); + expect(snapshot.nextChangeIndex).toBe(2); + }); +}); diff --git a/packages/btc/src/constants.ts b/packages/btc/src/constants.ts new file mode 100644 index 00000000..01ffab78 --- /dev/null +++ b/packages/btc/src/constants.ts @@ -0,0 +1,31 @@ +/** BIP32 hardened offset (2^31) */ +export const HARDENED_OFFSET = 0x80000000; + +export const SLIP132_VERSION_BYTES = { + // Mainnet + xpub: { public: 0x0488b21e, private: 0x0488ade4 }, // BIP44 - Legacy (P2PKH) + ypub: { public: 0x049d7cb2, private: 0x049d7878 }, // BIP49 - Wrapped SegWit (P2SH-P2WPKH) + zpub: { public: 0x04b24746, private: 0x04b2430c }, // BIP84 - Native SegWit (P2WPKH) + + // Testnet + tpub: { public: 0x043587cf, private: 0x04358394 }, // BIP44 - Legacy (P2PKH) + upub: { public: 0x044a5262, private: 0x044a4e28 }, // BIP49 - Wrapped SegWit (P2SH-P2WPKH) + vpub: { public: 0x045f1cf6, private: 0x045f18bc }, // BIP84 - Native SegWit (P2WPKH) +} as const; + +export const BTC_PURPOSES = { + LEGACY: 44, // BIP44 - P2PKH + WRAPPED: 49, // BIP49 - P2SH-P2WPKH + NATIVE: 84, // BIP84 - P2WPKH +} as const; + +export const BTC_COIN_TYPES = { + MAINNET: 0, + TESTNET: 1, +} as const; + +export const BTC_NETWORKS = { + MAINNET: 'mainnet', + TESTNET: 'testnet', + REGTEST: 'regtest', +} as const; diff --git a/packages/btc/src/index.ts b/packages/btc/src/index.ts new file mode 100644 index 00000000..efb3db9a --- /dev/null +++ b/packages/btc/src/index.ts @@ -0,0 +1,67 @@ +// Constants +export { + HARDENED_OFFSET, + SLIP132_VERSION_BYTES, + BTC_PURPOSES, + BTC_COIN_TYPES, + BTC_NETWORKS, +} from './constants'; + +// Types +export type { + BtcPurpose, + BtcCoinType, + BtcNetwork, + XpubPrefix, + ScriptType, + XpubOptions, + XpubsOptions, + PreviousOutput, + WalletUtxo, + WalletSummary, + WalletSnapshot, + TxBuildInput, + TxBuildResult, +} from './types'; + +// SLIP-132 utilities +export * as slip132 from './slip132'; +export { + getVersionBytes, + getPrefix, + normalize, + format, + inferPurpose, +} from './slip132'; + +// Network utilities +export * as network from './network'; +export { + inferFromXpub, + getCoinType, + getNetworkFromCoinType, + isTestnet, +} from './network'; + +// Transaction building +export { buildTxReq, estimateFee } from './tx'; + +// Wallet utilities +export { getSummary, getSnapshot } from './wallet'; +export type { WalletOptions } from './wallet'; + +// Provider +export * as provider from './provider'; +export { + createBlockbookProvider, + BlockbookProvider, +} from './provider/blockbook'; +export type { + BtcProvider, + BlockbookProviderConfig, + BlockbookUtxo, + BlockbookTransaction, + BlockbookSummary, + FeeRates, + PagingOptions, +} from './provider/types'; diff --git a/packages/btc/src/network.ts b/packages/btc/src/network.ts new file mode 100644 index 00000000..77847b40 --- /dev/null +++ b/packages/btc/src/network.ts @@ -0,0 +1,78 @@ +import { + BTC_COIN_TYPES, + BTC_NETWORKS, + SLIP132_VERSION_BYTES, +} from './constants'; +import type { BtcCoinType, BtcNetwork } from './types'; +import { getVersionBytes } from './slip132'; + +/** + * Infer the network (mainnet/testnet) from an extended public key. + */ +export function inferFromXpub(xpub: string): BtcNetwork { + const version = getVersionBytes(xpub); + + const mainnetVersions = new Set([ + SLIP132_VERSION_BYTES.xpub.public, + SLIP132_VERSION_BYTES.ypub.public, + SLIP132_VERSION_BYTES.zpub.public, + ]); + if (mainnetVersions.has(version)) { + return BTC_NETWORKS.MAINNET; + } + + const testnetVersions = new Set([ + SLIP132_VERSION_BYTES.tpub.public, + SLIP132_VERSION_BYTES.upub.public, + SLIP132_VERSION_BYTES.vpub.public, + ]); + if (testnetVersions.has(version)) { + return BTC_NETWORKS.TESTNET; + } + + throw new Error(`Unknown xpub version: 0x${version.toString(16)}`); +} + +/** + * Get the BIP44 coin type for a given network. + */ +export function getCoinType(network: BtcNetwork): BtcCoinType; +export function getCoinType(override: BtcCoinType): BtcCoinType; +export function getCoinType( + networkOrOverride: BtcNetwork | BtcCoinType, +): BtcCoinType { + if (typeof networkOrOverride === 'number') { + return networkOrOverride; + } + + switch (networkOrOverride) { + case BTC_NETWORKS.MAINNET: + return BTC_COIN_TYPES.MAINNET; + case BTC_NETWORKS.TESTNET: + case BTC_NETWORKS.REGTEST: + return BTC_COIN_TYPES.TESTNET; + default: + throw new Error(`Unknown network: ${networkOrOverride}`); + } +} + +/** + * Get network name from coin type. + */ +export function getNetworkFromCoinType(coinType: BtcCoinType): BtcNetwork { + switch (coinType) { + case BTC_COIN_TYPES.MAINNET: + return BTC_NETWORKS.MAINNET; + case BTC_COIN_TYPES.TESTNET: + return BTC_NETWORKS.TESTNET; + default: + throw new Error(`Unknown coin type: ${coinType}`); + } +} + +/** + * Check if a network is testnet-like (testnet or regtest). + */ +export function isTestnet(network: BtcNetwork): boolean { + return network === BTC_NETWORKS.TESTNET || network === BTC_NETWORKS.REGTEST; +} diff --git a/packages/btc/src/provider/blockbook.ts b/packages/btc/src/provider/blockbook.ts new file mode 100644 index 00000000..23defda6 --- /dev/null +++ b/packages/btc/src/provider/blockbook.ts @@ -0,0 +1,193 @@ +import type { + BtcProvider, + BlockbookProviderConfig, + BlockbookSummary, + BlockbookTransaction, + BlockbookUtxo, + BlockbookBroadcastResponse, + BlockbookFeeEstimateResponse, + FeeRates, + PagingOptions, +} from './types'; + +/** + * Type guard for xpub response with transactions. + * The response is an object that may contain a transactions array. + */ +function isXpubWithTransactionsResponse( + value: unknown, +): value is { transactions?: BlockbookTransaction[] } { + if (typeof value !== 'object' || value === null) { + return false; + } + const obj = value as Record; + if ('transactions' in obj && obj.transactions !== undefined) { + return Array.isArray(obj.transactions); + } + return true; +} + +/** + * Type guard for BlockbookBroadcastResponse + */ +function isBroadcastResponse( + value: unknown, +): value is BlockbookBroadcastResponse { + return ( + typeof value === 'object' && + value !== null && + 'result' in value && + typeof (value as BlockbookBroadcastResponse).result === 'string' + ); +} + +/** + * Type guard for BlockbookFeeEstimateResponse + */ +function isFeeEstimateResponse( + value: unknown, +): value is BlockbookFeeEstimateResponse { + return ( + typeof value === 'object' && + value !== null && + 'result' in value && + typeof (value as BlockbookFeeEstimateResponse).result === 'string' + ); +} + +const DEFAULT_BLOCKBOOK_URLS = { + mainnet: 'https://btc1.trezor.io', + testnet: 'https://tbtc1.trezor.io', +} as const; + +/** + * Blockbook provider for Bitcoin chain data. + */ +export class BlockbookProvider implements BtcProvider { + private baseUrl: string; + + constructor(config?: BlockbookProviderConfig) { + if (config?.baseUrl) { + this.baseUrl = config.baseUrl.replace(/\/$/, ''); + } else { + const network = config?.network ?? 'mainnet'; + this.baseUrl = DEFAULT_BLOCKBOOK_URLS[network]; + } + } + + /** + * Get account summary for an xpub. + */ + async getSummary(xpub: string): Promise { + const response = await this.fetch(`/api/v2/xpub/${xpub}?details=basic`); + return response as BlockbookSummary; + } + + /** + * Get transaction history for an xpub. + */ + async getTransactions( + xpub: string, + options: PagingOptions = {}, + ): Promise { + const { page = 1, pageSize = 50 } = options; + const params = new URLSearchParams({ + details: 'txs', + page: String(page), + pageSize: String(pageSize), + }); + + const response = await this.fetch(`/api/v2/xpub/${xpub}?${params}`); + if (!isXpubWithTransactionsResponse(response)) { + throw new Error('Invalid response from Blockbook xpub endpoint'); + } + return response.transactions ?? []; + } + + /** + * Get unspent transaction outputs for an xpub. + */ + async getUtxos(xpub: string): Promise { + const response = await this.fetch(`/api/v2/utxo/${xpub}`); + return response as BlockbookUtxo[]; + } + + /** + * Broadcast a signed transaction. + */ + async broadcast(rawTx: string): Promise { + const response = await this.fetch('/api/v2/sendtx/', { + method: 'POST', + body: rawTx, + }); + + if (isBroadcastResponse(response)) { + return response.result; + } + throw new Error('Unexpected broadcast response format from Blockbook'); + } + + /** + * Get current fee rate estimates. + * Blockbook returns estimates for different confirmation targets. + */ + async getFeeRates(): Promise { + const [fast, medium, slow] = await Promise.all([ + this.fetchFeeEstimate(2), + this.fetchFeeEstimate(6), + this.fetchFeeEstimate(12), + ]); + + return { + fast: Math.ceil(fast), + medium: Math.ceil(medium), + slow: Math.ceil(slow), + }; + } + + private async fetchFeeEstimate(blocks: number): Promise { + const response = await this.fetch(`/api/v2/estimatefee/${blocks}`); + if (!isFeeEstimateResponse(response)) { + throw new Error( + `Fee estimation failed: invalid response format for ${blocks} blocks`, + ); + } + const btcPerKb = Number.parseFloat(response.result); + if (Number.isNaN(btcPerKb) || btcPerKb <= 0) { + throw new Error( + `Fee estimation failed: invalid fee rate "${response.result}" for ${blocks} blocks`, + ); + } + return btcPerKb * 100000; + } + + private async fetch( + path: string, + options: RequestInit = {}, + ): Promise { + const url = `${this.baseUrl}${path}`; + const response = await fetch(url, { + ...options, + headers: { + 'Content-Type': 'application/json', + ...options.headers, + }, + }); + + if (!response.ok) { + const text = await response.text(); + throw new Error(`Blockbook request failed: ${response.status} ${text}`); + } + + return response.json(); + } +} + +/** + * Create a Blockbook provider instance. + */ +export function createBlockbookProvider( + config?: BlockbookProviderConfig, +): BtcProvider { + return new BlockbookProvider(config); +} diff --git a/packages/btc/src/provider/index.ts b/packages/btc/src/provider/index.ts new file mode 100644 index 00000000..49a2737a --- /dev/null +++ b/packages/btc/src/provider/index.ts @@ -0,0 +1,2 @@ +export * from './types'; +export * from './blockbook'; diff --git a/packages/btc/src/provider/types.ts b/packages/btc/src/provider/types.ts new file mode 100644 index 00000000..85baf044 --- /dev/null +++ b/packages/btc/src/provider/types.ts @@ -0,0 +1,115 @@ +/** UTXO as returned by Blockbook */ +export interface BlockbookUtxo { + txid: string; + vout: number; + value: string; + height: number; + confirmations: number; + address?: string; + path?: string; +} + +/** Transaction as returned by Blockbook */ +export interface BlockbookTransaction { + txid: string; + version: number; + vin: Array<{ + txid: string; + vout: number; + sequence: number; + addresses: string[]; + value: string; + }>; + vout: Array<{ + value: string; + n: number; + addresses: string[]; + isOwn?: boolean; + }>; + blockHeight: number; + confirmations: number; + blockTime: number; + value: string; + valueIn: string; + fees: string; +} + +/** Address/Xpub summary from Blockbook */ +export interface BlockbookSummary { + address: string; + balance: string; + totalReceived: string; + totalSent: string; + unconfirmedBalance: string; + unconfirmedTxs: number; + txs: number; + usedTokens?: number; + tokens?: Array<{ + type: string; + name: string; + path: string; + transfers: number; + decimals: number; + balance: string; + totalReceived: string; + totalSent: string; + }>; +} + +/** Paging options for transaction queries */ +export interface PagingOptions { + page?: number; + pageSize?: number; + from?: number; + to?: number; +} + +/** Fee rate estimates */ +export interface FeeRates { + fast: number; // sats/vbyte - ~10 min confirmation + medium: number; // sats/vbyte - ~30 min confirmation + slow: number; // sats/vbyte - ~60 min confirmation + economy?: number; // sats/vbyte - lowest priority +} + +/** BTC Provider interface - abstraction over chain data sources */ +export interface BtcProvider { + /** Get account summary for an xpub */ + getSummary(xpub: string): Promise; + + /** Get transaction history for an xpub */ + getTransactions( + xpub: string, + options?: PagingOptions, + ): Promise; + + /** Get unspent transaction outputs for an xpub */ + getUtxos(xpub: string): Promise; + + /** Broadcast a signed transaction */ + broadcast(rawTx: string): Promise; + + /** Get current fee rate estimates */ + getFeeRates(): Promise; +} + +/** Provider configuration */ +export interface BlockbookProviderConfig { + baseUrl?: string; + network?: 'mainnet' | 'testnet'; +} + +/** Response from xpub endpoint with transaction details */ +export interface BlockbookXpubResponse extends BlockbookSummary { + transactions?: BlockbookTransaction[]; +} + +/** Response from broadcast endpoint */ +export interface BlockbookBroadcastResponse { + result: string; +} + +/** Response from fee estimate endpoint */ +export interface BlockbookFeeEstimateResponse { + result: string; +} diff --git a/packages/btc/src/slip132.ts b/packages/btc/src/slip132.ts new file mode 100644 index 00000000..5e30456b --- /dev/null +++ b/packages/btc/src/slip132.ts @@ -0,0 +1,110 @@ +import bs58check from 'bs58check'; +import { SLIP132_VERSION_BYTES, BTC_PURPOSES } from './constants'; +import type { BtcNetwork, BtcPurpose, XpubPrefix } from './types'; + +/** + * Get the 4-byte version prefix from an extended public key. + */ +export function getVersionBytes(xpub: string): number { + const decoded = bs58check.decode(xpub); + const view = new DataView( + decoded.buffer, + decoded.byteOffset, + decoded.byteLength, + ); + return view.getUint32(0, false); // false = big-endian +} + +/** + * Get the prefix string (xpub, ypub, zpub, etc.) from an extended public key. + */ +export function getPrefix(xpub: string): XpubPrefix { + const version = getVersionBytes(xpub); + for (const [prefix, bytes] of Object.entries(SLIP132_VERSION_BYTES)) { + if (bytes.public === version) { + return prefix as XpubPrefix; + } + } + throw new Error(`Unknown xpub version: 0x${version.toString(16)}`); +} + +/** + * Normalize any extended public key to standard xpub/tpub format. + * This strips SLIP-132 encoding (ypub/zpub/upub/vpub) back to base format. + */ +export function normalize(xpub: string): string { + const prefix = getPrefix(xpub); + const isTestnet = ['tpub', 'upub', 'vpub'].includes(prefix); + const targetVersion = isTestnet + ? SLIP132_VERSION_BYTES.tpub.public + : SLIP132_VERSION_BYTES.xpub.public; + + return convertVersion(xpub, targetVersion); +} + +/** + * Format an extended public key with the appropriate SLIP-132 prefix + * based on purpose and network. + */ +export function format( + xpub: string, + purpose: BtcPurpose, + network: BtcNetwork = 'mainnet', +): string { + const isTestnet = network !== 'mainnet'; + + let targetVersion: number; + switch (purpose) { + case BTC_PURPOSES.LEGACY: + targetVersion = isTestnet + ? SLIP132_VERSION_BYTES.tpub.public + : SLIP132_VERSION_BYTES.xpub.public; + break; + case BTC_PURPOSES.WRAPPED: + targetVersion = isTestnet + ? SLIP132_VERSION_BYTES.upub.public + : SLIP132_VERSION_BYTES.ypub.public; + break; + case BTC_PURPOSES.NATIVE: + targetVersion = isTestnet + ? SLIP132_VERSION_BYTES.vpub.public + : SLIP132_VERSION_BYTES.zpub.public; + break; + default: + throw new Error(`Unknown purpose: ${purpose}`); + } + + return convertVersion(xpub, targetVersion); +} + +/** + * Convert an extended public key to a different version. + */ +function convertVersion(xpub: string, targetVersion: number): string { + const decoded = bs58check.decode(xpub); + const converted = new Uint8Array(decoded.length); + const view = new DataView(converted.buffer); + view.setUint32(0, targetVersion, false); // false = big-endian + converted.set(decoded.subarray(4), 4); // copy bytes after version + return bs58check.encode(converted); +} + +/** + * Infer the BTC purpose from an extended public key prefix. + */ +export function inferPurpose(xpub: string): BtcPurpose { + const prefix = getPrefix(xpub); + switch (prefix) { + case 'xpub': + case 'tpub': + return BTC_PURPOSES.LEGACY; + case 'ypub': + case 'upub': + return BTC_PURPOSES.WRAPPED; + case 'zpub': + case 'vpub': + return BTC_PURPOSES.NATIVE; + default: + throw new Error(`Cannot infer purpose from prefix: ${prefix}`); + } +} diff --git a/packages/btc/src/tx.ts b/packages/btc/src/tx.ts new file mode 100644 index 00000000..13409d26 --- /dev/null +++ b/packages/btc/src/tx.ts @@ -0,0 +1,219 @@ +import { HARDENED_OFFSET, BTC_COIN_TYPES } from './constants'; +import type { + TxBuildInput, + TxBuildResult, + WalletUtxo, + BtcPurpose, + BtcCoinType, + ScriptType, +} from './types'; + +const VBYTE_SIZES = { + P2PKH_INPUT: 148, + P2SH_P2WPKH_INPUT: 91, + P2WPKH_INPUT: 68, + P2PKH_OUTPUT: 34, + P2SH_OUTPUT: 32, + P2WPKH_OUTPUT: 31, + VERSION: 4, + LOCKTIME: 4, + SEGWIT_MARKER: 2, + INPUT_COUNT: 1, + OUTPUT_COUNT: 1, +} as const; + +/** + * Get output size based on script type. + */ +function getOutputSize(scriptType: ScriptType): number { + switch (scriptType) { + case 'p2pkh': + return VBYTE_SIZES.P2PKH_OUTPUT; + case 'p2sh-p2wpkh': + return VBYTE_SIZES.P2SH_OUTPUT; + case 'p2wpkh': + return VBYTE_SIZES.P2WPKH_OUTPUT; + } +} + +/** + * Estimate transaction size in virtual bytes. + */ +function estimateTxVbytes( + inputCount: number, + outputCount: number, + inputType: ScriptType, +): number { + let inputSize: number; + switch (inputType) { + case 'p2pkh': + inputSize = VBYTE_SIZES.P2PKH_INPUT; + break; + case 'p2sh-p2wpkh': + inputSize = VBYTE_SIZES.P2SH_P2WPKH_INPUT; + break; + case 'p2wpkh': + inputSize = VBYTE_SIZES.P2WPKH_INPUT; + break; + } + + const overhead = + VBYTE_SIZES.VERSION + + VBYTE_SIZES.LOCKTIME + + VBYTE_SIZES.INPUT_COUNT + + VBYTE_SIZES.OUTPUT_COUNT; + + const hasSegwit = inputType !== 'p2pkh'; + const segwitOverhead = hasSegwit ? VBYTE_SIZES.SEGWIT_MARKER : 0; + + const outputSize = getOutputSize(inputType); + + return ( + overhead + + segwitOverhead + + inputCount * inputSize + + outputCount * outputSize + ); +} + +/** + * Select UTXOs for a transaction using simple largest-first strategy. + */ +function selectUtxos( + utxos: WalletUtxo[], + targetValue: number, + feeRate: number, +): { selected: WalletUtxo[]; fee: number } { + const sorted = [...utxos].sort((a, b) => b.value - a.value); + + const selected: WalletUtxo[] = []; + let totalValue = 0; + + for (const utxo of sorted) { + selected.push(utxo); + totalValue += utxo.value; + + const vbytes = estimateTxVbytes(selected.length, 2, selected[0].scriptType); + const estimatedFee = Math.ceil(vbytes * feeRate); + + if (totalValue >= targetValue + estimatedFee) { + return { selected, fee: estimatedFee }; + } + } + + throw new Error( + `Insufficient funds: have ${totalValue} sats, need ${targetValue} + fees`, + ); +} + +/** + * Extract account index from a derivation path. + * Path format: [purpose', coinType', account', change, index] + */ +function extractAccountFromPath(path: number[]): number { + if (path.length < 3) { + throw new Error('Invalid derivation path: too short to extract account'); + } + return path[2]; +} + +/** + * Build derivation path for change address. + */ +function buildChangePath( + purpose: BtcPurpose, + coinType: BtcCoinType, + account: number, + changeIndex: number, +): number[] { + return [ + purpose + HARDENED_OFFSET, + coinType + HARDENED_OFFSET, + account, + 1, + changeIndex, + ]; +} + +/** + * Build a transaction request from wallet UTXOs. + * + * @param input - Transaction building parameters + * @returns Transaction request ready for signing + * + * @example + * const txReq = buildTxReq({ + * utxos: walletSnapshot.utxos, + * recipient: 'bc1q...', + * value: 50000, + * feeRate: 10, + * purpose: 84, + * changeIndex: 5, + * }); + */ +export function buildTxReq(input: TxBuildInput): TxBuildResult { + const { + utxos, + recipient, + value, + feeRate, + purpose, + coinType = BTC_COIN_TYPES.MAINNET, + changeIndex, + } = input; + + if (utxos.length === 0) { + throw new Error('No UTXOs available'); + } + + if (value <= 0) { + throw new Error('Value must be positive'); + } + + if (feeRate <= 0) { + throw new Error('Fee rate must be positive'); + } + + const { selected, fee } = selectUtxos(utxos, value, feeRate); + + const totalInput = selected.reduce((sum, utxo) => sum + utxo.value, 0); + const changeValue = totalInput - value - fee; + + if (changeValue < 0) { + throw new Error( + `Insufficient funds: need ${value + fee} sats, have ${totalInput} sats`, + ); + } + + const prevOuts = selected.map((utxo) => ({ + txHash: utxo.txid, + value: utxo.value, + index: utxo.vout, + signerPath: utxo.path, + })); + + const account = extractAccountFromPath(selected[0].path); + const changePath = buildChangePath(purpose, coinType, account, changeIndex); + + return { + prevOuts, + recipient, + value, + fee, + changePath, + changeValue, + totalInput, + }; +} + +/** + * Estimate fee for a transaction. + */ +export function estimateFee( + utxoCount: number, + feeRate: number, + inputType: ScriptType = 'p2wpkh', +): number { + const vbytes = estimateTxVbytes(utxoCount, 2, inputType); + return Math.ceil(vbytes * feeRate); +} diff --git a/packages/btc/src/types.ts b/packages/btc/src/types.ts new file mode 100644 index 00000000..c3395451 --- /dev/null +++ b/packages/btc/src/types.ts @@ -0,0 +1,80 @@ +export type BtcPurpose = 44 | 49 | 84; +export type BtcCoinType = 0 | 1; +export type BtcNetwork = 'mainnet' | 'testnet' | 'regtest'; +export type XpubPrefix = 'xpub' | 'ypub' | 'zpub' | 'tpub' | 'upub' | 'vpub'; +export type ScriptType = 'p2pkh' | 'p2sh-p2wpkh' | 'p2wpkh'; + +export interface XpubOptions { + purpose: BtcPurpose; + coinType?: BtcCoinType; + account?: number; +} + +export interface XpubsOptions { + purposes: BtcPurpose[]; + coinType?: BtcCoinType; + account?: number; +} + +/** UTXO input for transaction signing (compatible with Lattice SDK) */ +export interface PreviousOutput { + txHash: string; + value: number; + index: number; + signerPath: number[]; +} + +/** Wallet UTXO with derivation info */ +export interface WalletUtxo { + txid: string; + vout: number; + value: number; // satoshis + confirmations: number; + address: string; + path: number[]; // derivation path + scriptType: ScriptType; +} + +/** Wallet summary */ +export interface WalletSummary { + balance: number; // satoshis + unconfirmedBalance: number; // satoshis + totalReceived: number; // satoshis + totalSent: number; // satoshis + txCount: number; + utxoCount: number; +} + +/** Full wallet snapshot for transaction building */ +export interface WalletSnapshot { + summary: WalletSummary; + utxos: WalletUtxo[]; + addresses: { + receiving: string[]; + change: string[]; + }; + nextReceiveIndex: number; + nextChangeIndex: number; +} + +/** Input for transaction building */ +export interface TxBuildInput { + utxos: WalletUtxo[]; + recipient: string; + value: number; // satoshis to send + feeRate: number; // sats/vbyte + purpose: BtcPurpose; + coinType?: BtcCoinType; + changeIndex: number; // index for change address +} + +/** Result of transaction building */ +export interface TxBuildResult { + prevOuts: PreviousOutput[]; + recipient: string; + value: number; + fee: number; + changePath: number[]; + changeValue: number; + totalInput: number; +} diff --git a/packages/btc/src/wallet.ts b/packages/btc/src/wallet.ts new file mode 100644 index 00000000..845f4e40 --- /dev/null +++ b/packages/btc/src/wallet.ts @@ -0,0 +1,196 @@ +import type { + WalletSummary, + WalletSnapshot, + WalletUtxo, + BtcPurpose, + ScriptType, +} from './types'; +import type { BtcProvider, BlockbookUtxo } from './provider/types'; +import { inferPurpose } from './slip132'; +import { HARDENED_OFFSET } from './constants'; + +/** + * Safely parse a string to an integer, throwing a descriptive error if invalid. + * + * @param value - The string value to parse + * @param fieldName - The name of the field for error messages + * @returns The parsed integer + * @throws Error if the value cannot be parsed to a valid integer + */ +function safeParseInt(value: string, fieldName: string): number { + const parsed = Number.parseInt(value, 10); + if (Number.isNaN(parsed)) { + throw new Error( + `Invalid ${fieldName}: expected numeric string but received "${value}"`, + ); + } + return parsed; +} + +export interface WalletOptions { + xpub: string; + purpose?: BtcPurpose; + provider: BtcProvider; +} + +/** + * Parse a BIP32 path string to array format. + */ +function parsePath(pathStr: string): number[] { + return pathStr + .replace('m/', '') + .split('/') + .map((part) => { + const isHardened = part.endsWith("'") || part.endsWith('h'); + const indexStr = part.replace(/['h]/g, ''); + const index = safeParseInt(indexStr, `path component "${part}"`); + return isHardened ? index + HARDENED_OFFSET : index; + }); +} + +/** + * Determine script type from purpose. + */ +function getScriptType(purpose: BtcPurpose): ScriptType { + switch (purpose) { + case 44: + return 'p2pkh'; + case 49: + return 'p2sh-p2wpkh'; + case 84: + return 'p2wpkh'; + default: + throw new Error(`Unknown purpose: ${purpose}`); + } +} + +/** + * Convert Blockbook UTXO to wallet UTXO with derivation info. + */ +function toWalletUtxo(utxo: BlockbookUtxo, purpose: BtcPurpose): WalletUtxo { + return { + txid: utxo.txid, + vout: utxo.vout, + value: safeParseInt(utxo.value, 'utxo.value'), + confirmations: utxo.confirmations, + address: utxo.address ?? '', + path: utxo.path ? parsePath(utxo.path) : [], + scriptType: getScriptType(purpose), + }; +} + +/** + * Get wallet summary from an xpub. + * + * @param options - Wallet options + * @returns Wallet summary with balance info + * + * @example + * const summary = await getSummary({ + * xpub: 'zpub...', + * provider: createBlockbookProvider(), + * }); + * console.log(`Balance: ${summary.balance} sats`); + */ +export async function getSummary( + options: WalletOptions, +): Promise { + const { xpub, provider } = options; + + const blockbookSummary = await provider.getSummary(xpub); + const utxos = await provider.getUtxos(xpub); + + return { + balance: safeParseInt(blockbookSummary.balance, 'balance'), + unconfirmedBalance: safeParseInt( + blockbookSummary.unconfirmedBalance, + 'unconfirmedBalance', + ), + totalReceived: safeParseInt( + blockbookSummary.totalReceived, + 'totalReceived', + ), + totalSent: safeParseInt(blockbookSummary.totalSent, 'totalSent'), + txCount: blockbookSummary.txs, + utxoCount: utxos.length, + }; +} + +/** + * Get full wallet snapshot for transaction building. + * + * @param options - Wallet options + * @returns Complete wallet snapshot with UTXOs and address info + * + * @example + * const snapshot = await getSnapshot({ + * xpub: 'zpub...', + * provider: createBlockbookProvider(), + * }); + * const txReq = buildTxReq({ utxos: snapshot.utxos, ... }); + */ +export async function getSnapshot( + options: WalletOptions, +): Promise { + const { xpub, provider } = options; + const purpose = options.purpose ?? inferPurpose(xpub); + + const [blockbookSummary, blockbookUtxos] = await Promise.all([ + provider.getSummary(xpub), + provider.getUtxos(xpub), + ]); + + const utxos = blockbookUtxos.map((utxo) => toWalletUtxo(utxo, purpose)); + + const tokens = blockbookSummary.tokens ?? []; + let nextReceiveIndex = 0; + let nextChangeIndex = 0; + const receivingAddresses: string[] = []; + const changeAddresses: string[] = []; + + for (const token of tokens) { + if (token.path) { + const parts = token.path.split('/'); + const isChange = parts[parts.length - 2] === '1'; + const indexPart = parts[parts.length - 1]; + const index = safeParseInt( + indexPart, + `address index in path "${token.path}"`, + ); + + if (isChange) { + changeAddresses.push(token.name); + nextChangeIndex = Math.max(nextChangeIndex, index + 1); + } else { + receivingAddresses.push(token.name); + nextReceiveIndex = Math.max(nextReceiveIndex, index + 1); + } + } + } + + const summary: WalletSummary = { + balance: safeParseInt(blockbookSummary.balance, 'balance'), + unconfirmedBalance: safeParseInt( + blockbookSummary.unconfirmedBalance, + 'unconfirmedBalance', + ), + totalReceived: safeParseInt( + blockbookSummary.totalReceived, + 'totalReceived', + ), + totalSent: safeParseInt(blockbookSummary.totalSent, 'totalSent'), + txCount: blockbookSummary.txs, + utxoCount: utxos.length, + }; + + return { + summary, + utxos, + addresses: { + receiving: receivingAddresses, + change: changeAddresses, + }, + nextReceiveIndex, + nextChangeIndex, + }; +} diff --git a/packages/btc/tsconfig.json b/packages/btc/tsconfig.json new file mode 100644 index 00000000..c3e95802 --- /dev/null +++ b/packages/btc/tsconfig.json @@ -0,0 +1,24 @@ +{ + "compilerOptions": { + "allowSyntheticDefaultImports": true, + "declaration": true, + "declarationMap": true, + "esModuleInterop": true, + "forceConsistentCasingInFileNames": true, + "isolatedModules": true, + "lib": ["ES2022", "DOM"], + "module": "ES2022", + "moduleResolution": "Bundler", + "outDir": "./dist", + "resolveJsonModule": true, + "rootDir": "./src", + "skipDefaultLibCheck": true, + "skipLibCheck": true, + "sourceMap": true, + "strict": true, + "target": "ES2022", + "types": ["node", "vitest", "vitest/globals"] + }, + "exclude": ["node_modules"], + "include": ["src"] +} diff --git a/packages/btc/tsup.config.ts b/packages/btc/tsup.config.ts new file mode 100644 index 00000000..12a41f8d --- /dev/null +++ b/packages/btc/tsup.config.ts @@ -0,0 +1,31 @@ +import { readFileSync } from 'node:fs'; +import { dirname, resolve } from 'node:path'; +import { fileURLToPath } from 'node:url'; +import { defineConfig } from 'tsup'; + +const __dirname = dirname(fileURLToPath(import.meta.url)); +const pkg = JSON.parse( + readFileSync(resolve(__dirname, 'package.json'), 'utf-8'), +); + +const external = Object.keys({ + ...(pkg.dependencies ?? {}), + ...(pkg.peerDependencies ?? {}), +}); + +export default defineConfig({ + entry: ['src/index.ts'], + outDir: './dist', + format: ['esm', 'cjs'], + target: 'node20', + sourcemap: true, + clean: true, + bundle: true, + dts: true, + silent: true, + outExtension: ({ format }) => ({ + js: format === 'esm' ? '.mjs' : '.cjs', + }), + external, + tsconfig: './tsconfig.json', +}); diff --git a/packages/btc/vitest.config.ts b/packages/btc/vitest.config.ts new file mode 100644 index 00000000..7382f40e --- /dev/null +++ b/packages/btc/vitest.config.ts @@ -0,0 +1,7 @@ +import { defineConfig } from 'vitest/config'; + +export default defineConfig({ + test: { + globals: true, + }, +}); diff --git a/packages/docs/package.json b/packages/docs/package.json index e56aa9e1..bfca7e64 100644 --- a/packages/docs/package.json +++ b/packages/docs/package.json @@ -28,6 +28,7 @@ "devDependencies": { "@docusaurus/module-type-aliases": "^3.4.0", "@tsconfig/docusaurus": "^2.0.3", + "@types/node": "^22.0.0", "docusaurus-plugin-typedoc": "~1.0.1", "typedoc": "~0.25.13", "typedoc-plugin-markdown": "~4.0.1", diff --git a/packages/sdk/package.json b/packages/sdk/package.json index b5144170..5f007ee6 100644 --- a/packages/sdk/package.json +++ b/packages/sdk/package.json @@ -54,6 +54,7 @@ "url": "https://github.com/GridPlus/gridplus-sdk.git" }, "dependencies": { + "@gridplus/btc": "workspace:*", "@ethereumjs/common": "^10.0.0", "@ethereumjs/rlp": "^10.0.0", "@ethereumjs/tx": "^10.0.0", diff --git a/packages/sdk/src/__test__/e2e/btc-xpub.test.ts b/packages/sdk/src/__test__/e2e/btc-xpub.test.ts new file mode 100644 index 00000000..11f3c055 --- /dev/null +++ b/packages/sdk/src/__test__/e2e/btc-xpub.test.ts @@ -0,0 +1,26 @@ +import { btc } from '../../index'; +import { setupClient } from '../utils/setup'; + +describe('BTC Xpub E2E', () => { + beforeAll(async () => { + await setupClient(); + }); + + it('fetches zpub with getXpub', async () => { + const zpub = await btc.getXpub({ purpose: 84 }); + expect(zpub).toBeTruthy(); + expect(zpub.startsWith('zpub')).toBe(true); + }); + + it('fetches all xpubs with getAllXpubs', async () => { + const xpubs = await btc.getAllXpubs(); + expect(xpubs.xpub.startsWith('xpub')).toBe(true); + expect(xpubs.ypub.startsWith('ypub')).toBe(true); + expect(xpubs.zpub.startsWith('zpub')).toBe(true); + }); + + it('fetches testnet xpub', async () => { + const tpub = await btc.getXpub({ purpose: 44, coinType: 1 }); + expect(tpub.startsWith('tpub')).toBe(true); + }); +}); diff --git a/packages/sdk/src/__test__/integration/btc.test.ts b/packages/sdk/src/__test__/integration/btc.test.ts new file mode 100644 index 00000000..6536127c --- /dev/null +++ b/packages/sdk/src/__test__/integration/btc.test.ts @@ -0,0 +1,49 @@ +import { btc } from '../../index'; + +describe('BTC Integration', () => { + describe('slip132 + network integration', () => { + it('formats and infers network correctly', () => { + const xpub = + 'xpub661MyMwAqRbcFtXgS5sYJABqqG9YLmC4Q1Rdap9gSE8NqtwybGhePY2gZ29ESFjqJoCu1Rupje8YtGqsefD265TMg7usUDFdp6W1EGMcet8'; + + const zpub = btc.slip132.format(xpub, 84, 'mainnet'); + expect(zpub.startsWith('zpub')).toBe(true); + + const network = btc.network.inferFromXpub(zpub); + expect(network).toBe('mainnet'); + + const purpose = btc.slip132.inferPurpose(zpub); + expect(purpose).toBe(84); + }); + }); + + describe('tx + wallet integration', () => { + it('builds transaction from wallet UTXOs', () => { + const utxos: btc.WalletUtxo[] = [ + { + txid: 'abc123', + vout: 0, + value: 100000, + confirmations: 6, + address: 'bc1q...', + path: [0x80000054, 0x80000000, 0x80000000, 0, 0], + scriptType: 'p2wpkh', + }, + ]; + + const result = btc.buildTxReq({ + utxos, + recipient: 'bc1qtest...', + value: 50000, + feeRate: 10, + purpose: 84, + changeIndex: 0, + }); + + expect(result.value).toBe(50000); + expect(result.prevOuts).toHaveLength(1); + expect(result.changeValue).toBeGreaterThan(0); + expect(result.fee).toBeGreaterThan(0); + }); + }); +}); diff --git a/packages/sdk/src/__test__/unit/btc/xpub.test.ts b/packages/sdk/src/__test__/unit/btc/xpub.test.ts new file mode 100644 index 00000000..c3d5168c --- /dev/null +++ b/packages/sdk/src/__test__/unit/btc/xpub.test.ts @@ -0,0 +1,62 @@ +vi.mock('../../../api/utilities', () => ({ + queue: vi.fn(), +})); + +import { BTC_COIN_TYPES, BTC_PURPOSES } from '@gridplus/btc'; +import { getAllXpubs, getXpub, getXpubs } from '../../../btc/xpub'; +import { queue } from '../../../api/utilities'; + +const TEST_XPUB = + 'xpub661MyMwAqRbcFtXgS5sYJABqqG9YLmC4Q1Rdap9gSE8NqtwybGhePY2gZ29ESFjqJoCu1Rupje8YtGqsefD265TMg7usUDFdp6W1EGMcet8'; + +const mockQueue = vi.mocked(queue); + +describe('btc/xpub', () => { + beforeEach(() => { + mockQueue.mockResolvedValue([TEST_XPUB]); + }); + + it('returns legacy xpub for purpose 44', async () => { + const result = await getXpub({ purpose: 44 }); + expect(result.startsWith('xpub')).toBe(true); + }); + + it('returns ypub for purpose 49', async () => { + const result = await getXpub({ purpose: 49 }); + expect(result.startsWith('ypub')).toBe(true); + }); + + it('returns zpub for purpose 84', async () => { + const result = await getXpub({ purpose: 84 }); + expect(result.startsWith('zpub')).toBe(true); + }); + + it('returns tpub for testnet coin type', async () => { + const result = await getXpub({ + purpose: BTC_PURPOSES.LEGACY, + coinType: BTC_COIN_TYPES.TESTNET, + }); + expect(result.startsWith('tpub')).toBe(true); + }); + + it('throws when no xpub is returned', async () => { + mockQueue.mockResolvedValueOnce([]); + await expect(getXpub({ purpose: 44 })).rejects.toThrow( + 'Failed to fetch xpub from device', + ); + }); + + it('fetches multiple xpubs by purpose', async () => { + const results = await getXpubs({ purposes: [44, 49, 84] }); + expect(results.get(44)?.startsWith('xpub')).toBe(true); + expect(results.get(49)?.startsWith('ypub')).toBe(true); + expect(results.get(84)?.startsWith('zpub')).toBe(true); + }); + + it('fetches all standard xpubs', async () => { + const results = await getAllXpubs(); + expect(results.xpub.startsWith('xpub')).toBe(true); + expect(results.ypub.startsWith('ypub')).toBe(true); + expect(results.zpub.startsWith('zpub')).toBe(true); + }); +}); diff --git a/packages/sdk/src/btc/index.ts b/packages/sdk/src/btc/index.ts new file mode 100644 index 00000000..c6fce03a --- /dev/null +++ b/packages/sdk/src/btc/index.ts @@ -0,0 +1,59 @@ +// Re-export everything from @gridplus/btc +export { + // Constants + HARDENED_OFFSET, + SLIP132_VERSION_BYTES, + BTC_PURPOSES, + BTC_COIN_TYPES, + BTC_NETWORKS, + // SLIP-132 utilities + slip132, + getVersionBytes, + getPrefix, + normalize, + format, + inferPurpose, + // Network utilities + network, + inferFromXpub, + getCoinType, + getNetworkFromCoinType, + isTestnet, + // Transaction building + buildTxReq, + estimateFee, + // Wallet utilities + getSummary, + getSnapshot, + // Provider + provider, + createBlockbookProvider, + BlockbookProvider, +} from '@gridplus/btc'; + +export type { + BtcPurpose, + BtcCoinType, + BtcNetwork, + XpubPrefix, + ScriptType, + XpubOptions, + XpubsOptions, + PreviousOutput, + WalletUtxo, + WalletSummary, + WalletSnapshot, + WalletOptions, + TxBuildInput, + TxBuildResult, + BtcProvider, + BlockbookProviderConfig, + BlockbookUtxo, + BlockbookTransaction, + BlockbookSummary, + FeeRates, + PagingOptions, +} from '@gridplus/btc'; + +// Lattice-specific xpub fetching (requires SDK dependencies) +export { getXpub, getXpubs, getAllXpubs } from './xpub'; diff --git a/packages/sdk/src/btc/xpub.ts b/packages/sdk/src/btc/xpub.ts new file mode 100644 index 00000000..d5ed9a98 --- /dev/null +++ b/packages/sdk/src/btc/xpub.ts @@ -0,0 +1,119 @@ +import { HARDENED_OFFSET } from '../constants'; +import { LatticeGetAddressesFlag } from '../protocol/latticeConstants'; +import { queue } from '../api/utilities'; +import { + BTC_PURPOSES, + BTC_COIN_TYPES, + format, + type BtcPurpose, + type BtcCoinType, + type XpubOptions, + type XpubsOptions, +} from '@gridplus/btc'; + +/** + * Build the derivation path for fetching an xpub. + * Path format: m/purpose'/coinType'/account' + */ +function buildXpubPath( + purpose: BtcPurpose, + coinType: BtcCoinType, + account: number, +): number[] { + return [ + purpose + HARDENED_OFFSET, + coinType + HARDENED_OFFSET, + account + HARDENED_OFFSET, + ]; +} + +/** + * Fetch a single extended public key from the Lattice device. + * + * @param options - Configuration for which xpub to fetch + * @param options.purpose - BIP purpose (44=legacy, 49=wrapped segwit, 84=native segwit) + * @param options.coinType - Coin type (0=mainnet, 1=testnet). Defaults to 0 (mainnet) + * @param options.account - Account index. Defaults to 0 + * @returns Extended public key formatted with appropriate SLIP-132 prefix + * + * @example + * // Fetch native segwit xpub (zpub) for mainnet + * const zpub = await getXpub({ purpose: 84 }); + * + * @example + * // Fetch legacy xpub for testnet account 1 + * const tpub = await getXpub({ purpose: 44, coinType: 1, account: 1 }); + */ +export async function getXpub(options: XpubOptions): Promise { + const { purpose, coinType = BTC_COIN_TYPES.MAINNET, account = 0 } = options; + + const startPath = buildXpubPath(purpose, coinType, account); + const network = coinType === BTC_COIN_TYPES.TESTNET ? 'testnet' : 'mainnet'; + + const result = await queue((client) => + client.getAddresses({ + startPath, + n: 1, + flag: LatticeGetAddressesFlag.secp256k1Xpub, + }), + ); + + if (!result || result.length === 0) { + throw new Error('Failed to fetch xpub from device'); + } + + return format(result[0], purpose, network); +} + +/** + * Fetch multiple extended public keys from the Lattice device. + * + * @param options - Configuration for which xpubs to fetch + * @param options.purposes - Array of BIP purposes to fetch + * @param options.coinType - Coin type (0=mainnet, 1=testnet). Defaults to 0 (mainnet) + * @param options.account - Account index. Defaults to 0 + * @returns Map of purpose to extended public key + * + * @example + * // Fetch all three xpub types for mainnet + * const xpubs = await getXpubs({ purposes: [44, 49, 84] }); + * console.log(xpubs.get(84)); // zpub... + */ +export async function getXpubs( + options: XpubsOptions, +): Promise> { + const { purposes, coinType = BTC_COIN_TYPES.MAINNET, account = 0 } = options; + + const results = new Map(); + + for (const purpose of purposes) { + const xpub = await getXpub({ purpose, coinType, account }); + results.set(purpose, xpub); + } + + return results; +} + +/** + * Convenience function to fetch all standard xpubs (legacy, wrapped, native). + */ +export async function getAllXpubs( + coinType: BtcCoinType = BTC_COIN_TYPES.MAINNET, + account = 0, +): Promise<{ xpub: string; ypub: string; zpub: string }> { + const xpubs = await getXpubs({ + purposes: [BTC_PURPOSES.LEGACY, BTC_PURPOSES.WRAPPED, BTC_PURPOSES.NATIVE], + coinType, + account, + }); + + const xpub = xpubs.get(BTC_PURPOSES.LEGACY); + const ypub = xpubs.get(BTC_PURPOSES.WRAPPED); + const zpub = xpubs.get(BTC_PURPOSES.NATIVE); + + if (!xpub || !ypub || !zpub) { + throw new Error('Failed to fetch all xpubs'); + } + + return { xpub, ypub, zpub }; +} diff --git a/packages/sdk/src/index.ts b/packages/sdk/src/index.ts index 8dfc3e0d..53feb6e8 100644 --- a/packages/sdk/src/index.ts +++ b/packages/sdk/src/index.ts @@ -3,3 +3,4 @@ export { Client } from './client'; export { EXTERNAL as Constants } from './constants'; export { EXTERNAL as Utils } from './util'; export * from './api'; +export * as btc from './btc'; diff --git a/pnpm-lock.yaml b/pnpm-lock.yaml index e69233a5..3442ba99 100644 --- a/pnpm-lock.yaml +++ b/pnpm-lock.yaml @@ -15,6 +15,28 @@ importers: specifier: ^2.3.0 version: 2.7.4 + packages/btc: + dependencies: + bs58check: + specifier: ^4.0.0 + version: 4.0.0 + devDependencies: + '@biomejs/biome': + specifier: ^1.9.0 + version: 1.9.4 + '@types/node': + specifier: ^24.10.4 + version: 24.10.4 + tsup: + specifier: ^8.5.0 + version: 8.5.1(@microsoft/api-extractor@7.55.2(@types/node@24.10.4))(jiti@1.21.7)(postcss@8.5.6)(tsx@4.21.0)(typescript@5.9.3) + typescript: + specifier: ^5.9.2 + version: 5.9.3 + vitest: + specifier: 3.2.4 + version: 3.2.4(@types/debug@4.1.12)(@types/node@24.10.4)(jiti@1.21.7)(msw@2.12.7(@types/node@24.10.4)(typescript@5.9.3))(terser@5.44.1)(tsx@4.21.0) + packages/docs: dependencies: '@docusaurus/core': @@ -51,6 +73,9 @@ importers: '@tsconfig/docusaurus': specifier: ^2.0.3 version: 2.0.7 + '@types/node': + specifier: ^22.0.0 + version: 22.19.7 docusaurus-plugin-typedoc: specifier: ~1.0.1 version: 1.0.5(typedoc-plugin-markdown@4.0.3(typedoc@0.25.13(typescript@5.9.3))) @@ -115,6 +140,9 @@ importers: '@ethereumjs/tx': specifier: ^10.0.0 version: 10.1.0 + '@gridplus/btc': + specifier: workspace:* + version: link:../btc '@metamask/eth-sig-util': specifier: ^8.2.0 version: 8.2.0 @@ -2588,6 +2616,9 @@ packages: '@types/node@17.0.45': resolution: {integrity: sha512-w+tIMs3rq2afQdsPJlODhoUEKzFP1ayaoyl1CcnwtIlsVe7K7bA1NGm4s3PraqTLlXnbIN84zuBlxBWo1u9BLw==} + '@types/node@22.19.7': + resolution: {integrity: sha512-MciR4AKGHWl7xwxkBa6xUGxQJ4VBOmPTF7sL+iGzuahOFaO0jHCsuEfS80pan1ef4gWId1oWOweIhrDEYLuaOw==} + '@types/node@24.10.4': resolution: {integrity: sha512-vnDVpYPMzs4wunl27jHrfmwojOGKya0xyM3sH+UE5iv5uPS6vX7UIoh6m+vQc5LGBq52HBKPIn/zcSZVzeDEZg==} @@ -7457,6 +7488,9 @@ packages: unbzip2-stream@1.4.3: resolution: {integrity: sha512-mlExGW4w71ebDJviH16lQLtZS32VKqsSfk80GCfUlwT/4/hNRFsoscrF/c++9xinkMzECL1uL9DDwXqFWkruPg==} + undici-types@6.21.0: + resolution: {integrity: sha512-iwDZqg0QAGrg9Rav5H4n0M64c3mkR59cJ6wQp+7C4nI0gsmExaedaYLNO44eT4AtBBwjbTiGPMlt2Md0T9H9JQ==} + undici-types@7.16.0: resolution: {integrity: sha512-Zz+aZWSj8LE6zoxD+xrjh4VfkIG8Ya6LvYkZqtUQGJPZjYl53ypCaUwWqo7eI0x66KBGeRo+mlBEkMSeSZ38Nw==} @@ -11032,6 +11066,10 @@ snapshots: '@types/node@17.0.45': {} + '@types/node@22.19.7': + dependencies: + undici-types: 6.21.0 + '@types/node@24.10.4': dependencies: undici-types: 7.16.0 @@ -17039,6 +17077,8 @@ snapshots: through: 2.3.8 optional: true + undici-types@6.21.0: {} + undici-types@7.16.0: {} unicode-canonical-property-names-ecmascript@2.0.1: {} From 2e8672e03116c699f2cb23a411086ca0239fcc7c Mon Sep 17 00:00:00 2001 From: netbonus <151201453+netbonus@users.noreply.github.com> Date: Wed, 21 Jan 2026 16:49:09 -0500 Subject: [PATCH 12/14] ci: trigger PR check From 906d205e9f5e7e980faddd00023e067a31d73043 Mon Sep 17 00:00:00 2001 From: netbonus <151201453+netbonus@users.noreply.github.com> Date: Wed, 21 Jan 2026 18:07:30 -0500 Subject: [PATCH 13/14] ci: retrigger checks From 18f0ce2b0ecaa7f40b8a23b8cddeb63dc25103b9 Mon Sep 17 00:00:00 2001 From: netbonus <151201453+netbonus@users.noreply.github.com> Date: Thu, 22 Jan 2026 06:18:29 -0500 Subject: [PATCH 14/14] fix(ci): pass environment variables to e2e tests via Turborepo Turborepo sandboxes environment variables by default. Add passThroughEnv to the e2e task to allow CI-defined variables (DEVICE_ID, PASSWORD, etc.) to reach the test runner. --- turbo.json | 13 ++++++++++++- 1 file changed, 12 insertions(+), 1 deletion(-) diff --git a/turbo.json b/turbo.json index 5ca17f2e..201a23cc 100644 --- a/turbo.json +++ b/turbo.json @@ -21,7 +21,18 @@ }, "e2e": { "dependsOn": ["build"], - "cache": false + "cache": false, + "passThroughEnv": [ + "CI", + "DEBUG", + "DEBUG_SIGNING", + "DEVICE_ID", + "PASSWORD", + "PAIRING_SECRET", + "ENC_PW", + "APP_NAME", + "baseUrl" + ] } } }