diff --git a/wallet/entrypoints/background.ts b/wallet/entrypoints/background.ts index 20efd2c..a12ffae 100644 --- a/wallet/entrypoints/background.ts +++ b/wallet/entrypoints/background.ts @@ -9,8 +9,10 @@ * browser restart / extension reload. We do NOT "encrypt the seed with a key stored * beside it" (that adds surface for zero gain). Threat model documented in README. */ -import { createPublicClient, http, parseEther, formatEther } from "viem"; +import { createPublicClient, http, parseEther, formatEther, type TypedDataDefinition } from "viem"; import { Keyring } from "../src/keyring/keyring"; +import { parseTypedData } from "../src/provider/typed-data"; +import { WorkerOperator, NETWORKS, type MinimalPublicClient } from "lightnode-sdk"; import { encryptVault, decryptVault, type EncryptedVault } from "../src/keyring/vault"; import { chainById, lightchainMainnet } from "../src/rpc/chains"; import { type BgMessage, type WalletOp, type JsonRpcRequest, RpcError } from "../src/provider/protocol"; @@ -96,6 +98,23 @@ async function handleWalletOp(op: WalletOp): Promise { const wei = await publicClient().getBalance({ address: op.address as `0x${string}` }); return { wei: wei.toString(), lcai: formatEther(wei) }; } + case "workerStatus": { + // Read-only worker registry/stake lookup via the SDK. No key needed; we + // return only number/bool fields (chrome.runtime can't structured-clone bigint). + const wo = new WorkerOperator(NETWORKS.mainnet, { + publicClient: publicClient() as unknown as MinimalPublicClient, + workerAddress: op.address as `0x${string}`, + }); + const s = await wo.status(); + return { + registered: s.registered, + belowFloor: s.belowFloor, + stakeLcai: s.stakeLcai, + minStakeLcai: Number(s.minStakeWei) / 1e18, + headroomLcai: s.headroomLcai, + claimableLcai: s.claimableLcai, + }; + } case "send": { const kr = await restore(); const acct = kr?.accountFor(op.from); @@ -185,6 +204,20 @@ async function fulfilApproved(request: JsonRpcRequest, origin: string): Promise< if (!acct) throw RpcError.unauthorized; return signAndSend(acct.account, tx.to, tx.value ? BigInt(tx.value) : 0n); } + if (request.method === "eth_signTypedData_v4") { + const [address, json] = request.params as [string, string]; + const acct = kr.accountFor(address); + if (!acct) throw RpcError.unauthorized; + const td = parseTypedData(json); + if (!td?.domain || !td.primaryType || !td.types || td.message === undefined) throw RpcError.invalidParams; + // Bind the signature to our chains - never sign typed data aimed elsewhere (review H5). + const chainId = td.domain.chainId != null ? Number(td.domain.chainId) : undefined; + if (chainId !== 9200 && chainId !== 8200) throw { code: 4901, message: "Typed data targets a different chain" }; + const types = { ...(td.types as Record) }; + delete types.EIP712Domain; // viem derives the domain type itself + const def = { domain: td.domain, types, primaryType: td.primaryType, message: td.message } as unknown as TypedDataDefinition; + return acct.account.signTypedData(def); + } throw RpcError.unsupported; } diff --git a/wallet/entrypoints/popup/App.tsx b/wallet/entrypoints/popup/App.tsx index abd7761..933ba7c 100644 --- a/wallet/entrypoints/popup/App.tsx +++ b/wallet/entrypoints/popup/App.tsx @@ -1,6 +1,10 @@ import { useCallback, useEffect, useState } from "react"; import { createMnemonic, isValidMnemonic } from "../../src/keyring/mnemonic"; -import { wallet, type WalletState, type PendingRequest } from "./wallet-api"; +import { decodeDangerousCall, type Severity } from "../../src/provider/decode-call"; +import { summarizeTypedData } from "../../src/provider/typed-data"; +import { wallet, type WalletState, type PendingRequest, type WorkerStatusView } from "./wallet-api"; + +const SEVERITY_CLASS: Record = { info: "muted", warn: "warn", danger: "warn" }; const EXPLORER = "https://mainnet.lightscan.app"; const short = (a: string) => `${a.slice(0, 6)}…${a.slice(-4)}`; @@ -156,14 +160,45 @@ function WalletHome({ state, onChange }: { state: WalletState; onChange: () => v

{short(address)} view

+
-

LightChain superpowers

-

Worker staking + monitoring, encrypted AI inference, and DAO intelligence connect through the lightnode SDK. Manage them at lightnode.

+

More superpowers

+

Encrypted AI inference, DAO intelligence, and the Ethereum bridge connect through the lightnode SDK and land next. Explore them at lightnode.

); } +function WorkerPanel({ address }: { address: string }) { + const [s, setS] = useState("loading"); + useEffect(() => { + setS("loading"); + wallet({ type: "workerStatus", address }).then(setS).catch(() => setS("error")); + }, [address]); + if (s === "loading") return

Worker status

Checking the registry…

; + if (s === "error") return

Worker status

Could not reach the worker registry. Try again later.

; + if (!s.registered) { + return ( +
+

Worker status

+

This address is not a registered LightChain worker. Run a worker →

+
+ ); + } + const fmt = (n: number) => n.toLocaleString(undefined, { maximumFractionDigits: 2 }); + return ( +
+

Worker

registered
+
Stake{fmt(s.stakeLcai)} LCAI
+
Min stake{fmt(s.minStakeLcai)}
+
Headroom{fmt(s.headroomLcai)}
+ {s.claimableLcai > 0 &&
Claimable{fmt(s.claimableLcai)} LCAI
} + {s.belowFloor &&

Below the stake floor - top up to keep earning. Manage at lightnode.

} + Manage worker → +
+ ); +} + function SendForm({ from, onSent }: { from: string; onSent: () => void }) { const [to, setTo] = useState(""); const [amount, setAmount] = useState(""); @@ -234,18 +269,40 @@ function labelFor(method: string): string { if (method === "eth_requestAccounts") return "Connect this site to your wallet"; if (method === "personal_sign") return "Sign a message"; if (method === "eth_sendTransaction") return "Send a transaction"; + if (method === "eth_signTypedData_v4") return "Sign typed data (EIP-712)"; return method; } function RequestDetail({ req }: { req: PendingRequest }) { if (req.method === "eth_sendTransaction") { const tx = (req.params?.[0] ?? {}) as { to?: string; value?: string; data?: string }; - const hasData = tx.data && tx.data !== "0x"; + const decoded = decodeDangerousCall(tx.data as `0x${string}` | undefined); return (

to: {tx.to ?? "(contract creation)"}

value: {tx.value ? Number(BigInt(tx.value)) / 1e18 : 0} LCAI

- {hasData &&

This is a contract interaction (data {String(tx.data).slice(0, 12)}…). Only approve if you trust this site - contract calls can move tokens.

} + {decoded.kind !== "empty" && ( +

+ {decoded.label}. {decoded.detail} +

+ )} +
+ ); + } + if (req.method === "eth_signTypedData_v4") { + const s = summarizeTypedData(req.params?.[1], [9200, 8200]); + return ( +
+ {s.error ? ( +

{s.error} Reject unless you trust this site.

+ ) : ( + <> +

type: {s.primaryType}{s.domainName ? ` · ${s.domainName}` : ""}

+ {s.verifyingContract &&

contract: {s.verifyingContract}

} + {!s.chainIdOk &&

Domain chain ({s.chainId ?? "?"}) is not LightChain - reject unless you are sure.

} + {s.warning &&

{s.warning}

} + + )}
); } diff --git a/wallet/entrypoints/popup/wallet-api.ts b/wallet/entrypoints/popup/wallet-api.ts index f734c30..7d05bd2 100644 --- a/wallet/entrypoints/popup/wallet-api.ts +++ b/wallet/entrypoints/popup/wallet-api.ts @@ -19,3 +19,11 @@ export interface PendingRequest { origin: string; params?: unknown[]; } +export interface WorkerStatusView { + registered: boolean; + belowFloor: boolean; + stakeLcai: number; + minStakeLcai: number; + headroomLcai: number; + claimableLcai: number; +} diff --git a/wallet/package-lock.json b/wallet/package-lock.json index fbeeaf0..5c8359c 100644 --- a/wallet/package-lock.json +++ b/wallet/package-lock.json @@ -12,6 +12,7 @@ "@noble/hashes": "^1.6.1", "@scure/bip32": "^1.6.2", "@scure/bip39": "^1.5.4", + "lightnode-sdk": "file:../sdk", "react": "^18.3.1", "react-dom": "^18.3.1", "viem": "^2.21.0" @@ -26,6 +27,22 @@ "wxt": "^0.19.13" } }, + "../sdk": { + "name": "lightnode-sdk", + "version": "0.18.3", + "license": "MIT", + "dependencies": { + "@noble/ciphers": "^1.0.0", + "@noble/curves": "^1.0.0", + "viem": ">=2" + }, + "bin": { + "lightnode": "dist/cli.js" + }, + "engines": { + "node": ">=18" + } + }, "node_modules/@1natsu/wait-element": { "version": "4.2.0", "resolved": "https://registry.npmjs.org/@1natsu/wait-element/-/wait-element-4.2.0.tgz", @@ -313,9 +330,9 @@ } }, "node_modules/@esbuild/aix-ppc64": { - "version": "0.25.12", - "resolved": "https://registry.npmjs.org/@esbuild/aix-ppc64/-/aix-ppc64-0.25.12.tgz", - "integrity": "sha512-Hhmwd6CInZ3dwpuGTF8fJG6yoWmsToE+vYgD4nytZVxcu1ulHpUQRAB1UJ8+N1Am3Mz4+xOByoQoSZf4D+CpkA==", + "version": "0.28.0", + "resolved": "https://registry.npmjs.org/@esbuild/aix-ppc64/-/aix-ppc64-0.28.0.tgz", + "integrity": "sha512-lhRUCeuOyJQURhTxl4WkpFTjIsbDayJHih5kZC1giwE+MhIzAb7mEsQMqMf18rHLsrb5qI1tafG20mLxEWcWlA==", "cpu": [ "ppc64" ], @@ -325,14 +342,15 @@ "os": [ "aix" ], + "peer": true, "engines": { "node": ">=18" } }, "node_modules/@esbuild/android-arm": { - "version": "0.25.12", - "resolved": "https://registry.npmjs.org/@esbuild/android-arm/-/android-arm-0.25.12.tgz", - "integrity": "sha512-VJ+sKvNA/GE7Ccacc9Cha7bpS8nyzVv0jdVgwNDaR4gDMC/2TTRc33Ip8qrNYUcpkOHUT5OZ0bUcNNVZQ9RLlg==", + "version": "0.28.0", + "resolved": "https://registry.npmjs.org/@esbuild/android-arm/-/android-arm-0.28.0.tgz", + "integrity": "sha512-wqh0ByljabXLKHeWXYLqoJ5jKC4XBaw6Hk08OfMrCRd2nP2ZQ5eleDZC41XHyCNgktBGYMbqnrJKq/K/lzPMSQ==", "cpu": [ "arm" ], @@ -342,14 +360,15 @@ "os": [ "android" ], + "peer": true, "engines": { "node": ">=18" } }, "node_modules/@esbuild/android-arm64": { - "version": "0.25.12", - "resolved": "https://registry.npmjs.org/@esbuild/android-arm64/-/android-arm64-0.25.12.tgz", - "integrity": "sha512-6AAmLG7zwD1Z159jCKPvAxZd4y/VTO0VkprYy+3N2FtJ8+BQWFXU+OxARIwA46c5tdD9SsKGZ/1ocqBS/gAKHg==", + "version": "0.28.0", + "resolved": "https://registry.npmjs.org/@esbuild/android-arm64/-/android-arm64-0.28.0.tgz", + "integrity": "sha512-+WzIXQOSaGs33tLEgYPYe/yQHf0WTU0X42Jca3y8NWMbUVhp7rUnw+vAsRC/QiDrdD31IszMrZy+qwPOPjd+rw==", "cpu": [ "arm64" ], @@ -359,14 +378,15 @@ "os": [ "android" ], + "peer": true, "engines": { "node": ">=18" } }, "node_modules/@esbuild/android-x64": { - "version": "0.25.12", - "resolved": "https://registry.npmjs.org/@esbuild/android-x64/-/android-x64-0.25.12.tgz", - "integrity": "sha512-5jbb+2hhDHx5phYR2By8GTWEzn6I9UqR11Kwf22iKbNpYrsmRB18aX/9ivc5cabcUiAT/wM+YIZ6SG9QO6a8kg==", + "version": "0.28.0", + "resolved": "https://registry.npmjs.org/@esbuild/android-x64/-/android-x64-0.28.0.tgz", + "integrity": "sha512-+VJggoaKhk2VNNqVL7f6S189UzShHC/mR9EE8rDdSkdpN0KflSwWY/gWjDrNxxisg8Fp1ZCD9jLMo4m0OUfeUA==", "cpu": [ "x64" ], @@ -376,14 +396,15 @@ "os": [ "android" ], + "peer": true, "engines": { "node": ">=18" } }, "node_modules/@esbuild/darwin-arm64": { - "version": "0.25.12", - "resolved": "https://registry.npmjs.org/@esbuild/darwin-arm64/-/darwin-arm64-0.25.12.tgz", - "integrity": "sha512-N3zl+lxHCifgIlcMUP5016ESkeQjLj/959RxxNYIthIg+CQHInujFuXeWbWMgnTo4cp5XVHqFPmpyu9J65C1Yg==", + "version": "0.28.0", + "resolved": "https://registry.npmjs.org/@esbuild/darwin-arm64/-/darwin-arm64-0.28.0.tgz", + "integrity": "sha512-0T+A9WZm+bZ84nZBtk1ckYsOvyA3x7e2Acj1KdVfV4/2tdG4fzUp91YHx+GArWLtwqp77pBXVCPn2We7Letr0Q==", "cpu": [ "arm64" ], @@ -393,14 +414,15 @@ "os": [ "darwin" ], + "peer": true, "engines": { "node": ">=18" } }, "node_modules/@esbuild/darwin-x64": { - "version": "0.25.12", - "resolved": "https://registry.npmjs.org/@esbuild/darwin-x64/-/darwin-x64-0.25.12.tgz", - "integrity": "sha512-HQ9ka4Kx21qHXwtlTUVbKJOAnmG1ipXhdWTmNXiPzPfWKpXqASVcWdnf2bnL73wgjNrFXAa3yYvBSd9pzfEIpA==", + "version": "0.28.0", + "resolved": "https://registry.npmjs.org/@esbuild/darwin-x64/-/darwin-x64-0.28.0.tgz", + "integrity": "sha512-fyzLm/DLDl/84OCfp2f/XQ4flmORsjU7VKt8HLjvIXChJoFFOIL6pLJPH4Yhd1n1gGFF9mPwtlN5Wf82DZs+LQ==", "cpu": [ "x64" ], @@ -410,14 +432,15 @@ "os": [ "darwin" ], + "peer": true, "engines": { "node": ">=18" } }, "node_modules/@esbuild/freebsd-arm64": { - "version": "0.25.12", - "resolved": "https://registry.npmjs.org/@esbuild/freebsd-arm64/-/freebsd-arm64-0.25.12.tgz", - "integrity": "sha512-gA0Bx759+7Jve03K1S0vkOu5Lg/85dou3EseOGUes8flVOGxbhDDh/iZaoek11Y8mtyKPGF3vP8XhnkDEAmzeg==", + "version": "0.28.0", + "resolved": "https://registry.npmjs.org/@esbuild/freebsd-arm64/-/freebsd-arm64-0.28.0.tgz", + "integrity": "sha512-l9GeW5UZBT9k9brBYI+0WDffcRxgHQD8ShN2Ur4xWq/NFzUKm3k5lsH4PdaRgb2w7mI9u61nr2gI2mLI27Nh3Q==", "cpu": [ "arm64" ], @@ -427,14 +450,15 @@ "os": [ "freebsd" ], + "peer": true, "engines": { "node": ">=18" } }, "node_modules/@esbuild/freebsd-x64": { - "version": "0.25.12", - "resolved": "https://registry.npmjs.org/@esbuild/freebsd-x64/-/freebsd-x64-0.25.12.tgz", - "integrity": "sha512-TGbO26Yw2xsHzxtbVFGEXBFH0FRAP7gtcPE7P5yP7wGy7cXK2oO7RyOhL5NLiqTlBh47XhmIUXuGciXEqYFfBQ==", + "version": "0.28.0", + "resolved": "https://registry.npmjs.org/@esbuild/freebsd-x64/-/freebsd-x64-0.28.0.tgz", + "integrity": "sha512-BXoQai/A0wPO6Es3yFJ7APCiKGc1tdAEOgeTNy3SsB491S3aHn4S4r3e976eUnPdU+NbdtmBuLncYir2tMU9Nw==", "cpu": [ "x64" ], @@ -444,14 +468,15 @@ "os": [ "freebsd" ], + "peer": true, "engines": { "node": ">=18" } }, "node_modules/@esbuild/linux-arm": { - "version": "0.25.12", - "resolved": "https://registry.npmjs.org/@esbuild/linux-arm/-/linux-arm-0.25.12.tgz", - "integrity": "sha512-lPDGyC1JPDou8kGcywY0YILzWlhhnRjdof3UlcoqYmS9El818LLfJJc3PXXgZHrHCAKs/Z2SeZtDJr5MrkxtOw==", + "version": "0.28.0", + "resolved": "https://registry.npmjs.org/@esbuild/linux-arm/-/linux-arm-0.28.0.tgz", + "integrity": "sha512-CjaaREJagqJp7iTaNQjjidaNbCKYcd4IDkzbwwxtSvjI7NZm79qiHc8HqciMddQ6CKvJT6aBd8lO9kN/ZudLlw==", "cpu": [ "arm" ], @@ -461,14 +486,15 @@ "os": [ "linux" ], + "peer": true, "engines": { "node": ">=18" } }, "node_modules/@esbuild/linux-arm64": { - "version": "0.25.12", - "resolved": "https://registry.npmjs.org/@esbuild/linux-arm64/-/linux-arm64-0.25.12.tgz", - "integrity": "sha512-8bwX7a8FghIgrupcxb4aUmYDLp8pX06rGh5HqDT7bB+8Rdells6mHvrFHHW2JAOPZUbnjUpKTLg6ECyzvas2AQ==", + "version": "0.28.0", + "resolved": "https://registry.npmjs.org/@esbuild/linux-arm64/-/linux-arm64-0.28.0.tgz", + "integrity": "sha512-RVyzfb3FWsGA55n6WY0MEIEPURL1FcbhFE6BffZEMEekfCzCIMtB5yyDcFnVbTnwk+CLAgTujmV/Lgvih56W+A==", "cpu": [ "arm64" ], @@ -478,14 +504,15 @@ "os": [ "linux" ], + "peer": true, "engines": { "node": ">=18" } }, "node_modules/@esbuild/linux-ia32": { - "version": "0.25.12", - "resolved": "https://registry.npmjs.org/@esbuild/linux-ia32/-/linux-ia32-0.25.12.tgz", - "integrity": "sha512-0y9KrdVnbMM2/vG8KfU0byhUN+EFCny9+8g202gYqSSVMonbsCfLjUO+rCci7pM0WBEtz+oK/PIwHkzxkyharA==", + "version": "0.28.0", + "resolved": "https://registry.npmjs.org/@esbuild/linux-ia32/-/linux-ia32-0.28.0.tgz", + "integrity": "sha512-KBnSTt1kxl9x70q+ydterVdl+Cn0H18ngRMRCEQfrbqdUuntQQ0LoMZv47uB97NljZFzY6HcfqEZ2SAyIUTQBQ==", "cpu": [ "ia32" ], @@ -495,14 +522,15 @@ "os": [ "linux" ], + "peer": true, "engines": { "node": ">=18" } }, "node_modules/@esbuild/linux-loong64": { - "version": "0.25.12", - "resolved": "https://registry.npmjs.org/@esbuild/linux-loong64/-/linux-loong64-0.25.12.tgz", - "integrity": "sha512-h///Lr5a9rib/v1GGqXVGzjL4TMvVTv+s1DPoxQdz7l/AYv6LDSxdIwzxkrPW438oUXiDtwM10o9PmwS/6Z0Ng==", + "version": "0.28.0", + "resolved": "https://registry.npmjs.org/@esbuild/linux-loong64/-/linux-loong64-0.28.0.tgz", + "integrity": "sha512-zpSlUce1mnxzgBADvxKXX5sl8aYQHo2ezvMNI8I0lbblJtp8V4odlm3Yzlj7gPyt3T8ReksE6bK+pT3WD+aJRg==", "cpu": [ "loong64" ], @@ -512,14 +540,15 @@ "os": [ "linux" ], + "peer": true, "engines": { "node": ">=18" } }, "node_modules/@esbuild/linux-mips64el": { - "version": "0.25.12", - "resolved": "https://registry.npmjs.org/@esbuild/linux-mips64el/-/linux-mips64el-0.25.12.tgz", - "integrity": "sha512-iyRrM1Pzy9GFMDLsXn1iHUm18nhKnNMWscjmp4+hpafcZjrr2WbT//d20xaGljXDBYHqRcl8HnxbX6uaA/eGVw==", + "version": "0.28.0", + "resolved": "https://registry.npmjs.org/@esbuild/linux-mips64el/-/linux-mips64el-0.28.0.tgz", + "integrity": "sha512-2jIfP6mmjkdmeTlsX/9vmdmhBmKADrWqN7zcdtHIeNSCH1SqIoNI63cYsjQR8J+wGa4Y5izRcSHSm8K3QWmk3w==", "cpu": [ "mips64el" ], @@ -529,14 +558,15 @@ "os": [ "linux" ], + "peer": true, "engines": { "node": ">=18" } }, "node_modules/@esbuild/linux-ppc64": { - "version": "0.25.12", - "resolved": "https://registry.npmjs.org/@esbuild/linux-ppc64/-/linux-ppc64-0.25.12.tgz", - "integrity": "sha512-9meM/lRXxMi5PSUqEXRCtVjEZBGwB7P/D4yT8UG/mwIdze2aV4Vo6U5gD3+RsoHXKkHCfSxZKzmDssVlRj1QQA==", + "version": "0.28.0", + "resolved": "https://registry.npmjs.org/@esbuild/linux-ppc64/-/linux-ppc64-0.28.0.tgz", + "integrity": "sha512-bc0FE9wWeC0WBm49IQMPSPILRocGTQt3j5KPCA8os6VprfuJ7KD+5PzESSrJ6GmPIPJK965ZJHTUlSA6GNYEhg==", "cpu": [ "ppc64" ], @@ -546,14 +576,15 @@ "os": [ "linux" ], + "peer": true, "engines": { "node": ">=18" } }, "node_modules/@esbuild/linux-riscv64": { - "version": "0.25.12", - "resolved": "https://registry.npmjs.org/@esbuild/linux-riscv64/-/linux-riscv64-0.25.12.tgz", - "integrity": "sha512-Zr7KR4hgKUpWAwb1f3o5ygT04MzqVrGEGXGLnj15YQDJErYu/BGg+wmFlIDOdJp0PmB0lLvxFIOXZgFRrdjR0w==", + "version": "0.28.0", + "resolved": "https://registry.npmjs.org/@esbuild/linux-riscv64/-/linux-riscv64-0.28.0.tgz", + "integrity": "sha512-SQPZOwoTTT/HXFXQJG/vBX8sOFagGqvZyXcgLA3NhIqcBv1BJU1d46c0rGcrij2B56Z2rNiSLaZOYW5cUk7yLQ==", "cpu": [ "riscv64" ], @@ -563,14 +594,15 @@ "os": [ "linux" ], + "peer": true, "engines": { "node": ">=18" } }, "node_modules/@esbuild/linux-s390x": { - "version": "0.25.12", - "resolved": "https://registry.npmjs.org/@esbuild/linux-s390x/-/linux-s390x-0.25.12.tgz", - "integrity": "sha512-MsKncOcgTNvdtiISc/jZs/Zf8d0cl/t3gYWX8J9ubBnVOwlk65UIEEvgBORTiljloIWnBzLs4qhzPkJcitIzIg==", + "version": "0.28.0", + "resolved": "https://registry.npmjs.org/@esbuild/linux-s390x/-/linux-s390x-0.28.0.tgz", + "integrity": "sha512-SCfR0HN8CEEjnYnySJTd2cw0k9OHB/YFzt5zgJEwa+wL/T/raGWYMBqwDNAC6dqFKmJYZoQBRfHjgwLHGSrn3Q==", "cpu": [ "s390x" ], @@ -580,14 +612,15 @@ "os": [ "linux" ], + "peer": true, "engines": { "node": ">=18" } }, "node_modules/@esbuild/linux-x64": { - "version": "0.25.12", - "resolved": "https://registry.npmjs.org/@esbuild/linux-x64/-/linux-x64-0.25.12.tgz", - "integrity": "sha512-uqZMTLr/zR/ed4jIGnwSLkaHmPjOjJvnm6TVVitAa08SLS9Z0VM8wIRx7gWbJB5/J54YuIMInDquWyYvQLZkgw==", + "version": "0.28.0", + "resolved": "https://registry.npmjs.org/@esbuild/linux-x64/-/linux-x64-0.28.0.tgz", + "integrity": "sha512-us0dSb9iFxIi8srnpl931Nvs65it/Jd2a2K3qs7fz2WfGPHqzfzZTfec7oxZJRNPXPnNYZtanmRc4AL/JwVzHQ==", "cpu": [ "x64" ], @@ -597,14 +630,15 @@ "os": [ "linux" ], + "peer": true, "engines": { "node": ">=18" } }, "node_modules/@esbuild/netbsd-arm64": { - "version": "0.25.12", - "resolved": "https://registry.npmjs.org/@esbuild/netbsd-arm64/-/netbsd-arm64-0.25.12.tgz", - "integrity": "sha512-xXwcTq4GhRM7J9A8Gv5boanHhRa/Q9KLVmcyXHCTaM4wKfIpWkdXiMog/KsnxzJ0A1+nD+zoecuzqPmCRyBGjg==", + "version": "0.28.0", + "resolved": "https://registry.npmjs.org/@esbuild/netbsd-arm64/-/netbsd-arm64-0.28.0.tgz", + "integrity": "sha512-CR/RYotgtCKwtftMwJlUU7xCVNg3lMYZ0RzTmAHSfLCXw3NtZtNpswLEj/Kkf6kEL3Gw+BpOekRX0BYCtklhUw==", "cpu": [ "arm64" ], @@ -614,14 +648,15 @@ "os": [ "netbsd" ], + "peer": true, "engines": { "node": ">=18" } }, "node_modules/@esbuild/netbsd-x64": { - "version": "0.25.12", - "resolved": "https://registry.npmjs.org/@esbuild/netbsd-x64/-/netbsd-x64-0.25.12.tgz", - "integrity": "sha512-Ld5pTlzPy3YwGec4OuHh1aCVCRvOXdH8DgRjfDy/oumVovmuSzWfnSJg+VtakB9Cm0gxNO9BzWkj6mtO1FMXkQ==", + "version": "0.28.0", + "resolved": "https://registry.npmjs.org/@esbuild/netbsd-x64/-/netbsd-x64-0.28.0.tgz", + "integrity": "sha512-nU1yhmYutL+fQ71Kxnhg8uEOdC0pwEW9entHykTgEbna2pw2dkbFSMeqjjyHZoCmt8SBkOSvV+yNmm94aUrrqw==", "cpu": [ "x64" ], @@ -631,14 +666,15 @@ "os": [ "netbsd" ], + "peer": true, "engines": { "node": ">=18" } }, "node_modules/@esbuild/openbsd-arm64": { - "version": "0.25.12", - "resolved": "https://registry.npmjs.org/@esbuild/openbsd-arm64/-/openbsd-arm64-0.25.12.tgz", - "integrity": "sha512-fF96T6KsBo/pkQI950FARU9apGNTSlZGsv1jZBAlcLL1MLjLNIWPBkj5NlSz8aAzYKg+eNqknrUJ24QBybeR5A==", + "version": "0.28.0", + "resolved": "https://registry.npmjs.org/@esbuild/openbsd-arm64/-/openbsd-arm64-0.28.0.tgz", + "integrity": "sha512-cXb5vApOsRsxsEl4mcZ1XY3D4DzcoMxR/nnc4IyqYs0rTI8ZKmW6kyyg+11Z8yvgMfAEldKzP7AdP64HnSC/6g==", "cpu": [ "arm64" ], @@ -648,14 +684,15 @@ "os": [ "openbsd" ], + "peer": true, "engines": { "node": ">=18" } }, "node_modules/@esbuild/openbsd-x64": { - "version": "0.25.12", - "resolved": "https://registry.npmjs.org/@esbuild/openbsd-x64/-/openbsd-x64-0.25.12.tgz", - "integrity": "sha512-MZyXUkZHjQxUvzK7rN8DJ3SRmrVrke8ZyRusHlP+kuwqTcfWLyqMOE3sScPPyeIXN/mDJIfGXvcMqCgYKekoQw==", + "version": "0.28.0", + "resolved": "https://registry.npmjs.org/@esbuild/openbsd-x64/-/openbsd-x64-0.28.0.tgz", + "integrity": "sha512-8wZM2qqtv9UP3mzy7HiGYNH/zjTA355mpeuA+859TyR+e+Tc08IHYpLJuMsfpDJwoLo1ikIJI8jC3GFjnRClzA==", "cpu": [ "x64" ], @@ -665,14 +702,15 @@ "os": [ "openbsd" ], + "peer": true, "engines": { "node": ">=18" } }, "node_modules/@esbuild/openharmony-arm64": { - "version": "0.25.12", - "resolved": "https://registry.npmjs.org/@esbuild/openharmony-arm64/-/openharmony-arm64-0.25.12.tgz", - "integrity": "sha512-rm0YWsqUSRrjncSXGA7Zv78Nbnw4XL6/dzr20cyrQf7ZmRcsovpcRBdhD43Nuk3y7XIoW2OxMVvwuRvk9XdASg==", + "version": "0.28.0", + "resolved": "https://registry.npmjs.org/@esbuild/openharmony-arm64/-/openharmony-arm64-0.28.0.tgz", + "integrity": "sha512-FLGfyizszcef5C3YtoyQDACyg95+dndv79i2EekILBofh5wpCa1KuBqOWKrEHZg3zrL3t5ouE5jgr94vA+Wb2w==", "cpu": [ "arm64" ], @@ -682,14 +720,15 @@ "os": [ "openharmony" ], + "peer": true, "engines": { "node": ">=18" } }, "node_modules/@esbuild/sunos-x64": { - "version": "0.25.12", - "resolved": "https://registry.npmjs.org/@esbuild/sunos-x64/-/sunos-x64-0.25.12.tgz", - "integrity": "sha512-3wGSCDyuTHQUzt0nV7bocDy72r2lI33QL3gkDNGkod22EsYl04sMf0qLb8luNKTOmgF/eDEDP5BFNwoBKH441w==", + "version": "0.28.0", + "resolved": "https://registry.npmjs.org/@esbuild/sunos-x64/-/sunos-x64-0.28.0.tgz", + "integrity": "sha512-1ZgjUoEdHZZl/YlV76TSCz9Hqj9h9YmMGAgAPYd+q4SicWNX3G5GCyx9uhQWSLcbvPW8Ni7lj4gDa1T40akdlw==", "cpu": [ "x64" ], @@ -699,14 +738,15 @@ "os": [ "sunos" ], + "peer": true, "engines": { "node": ">=18" } }, "node_modules/@esbuild/win32-arm64": { - "version": "0.25.12", - "resolved": "https://registry.npmjs.org/@esbuild/win32-arm64/-/win32-arm64-0.25.12.tgz", - "integrity": "sha512-rMmLrur64A7+DKlnSuwqUdRKyd3UE7oPJZmnljqEptesKM8wx9J8gx5u0+9Pq0fQQW8vqeKebwNXdfOyP+8Bsg==", + "version": "0.28.0", + "resolved": "https://registry.npmjs.org/@esbuild/win32-arm64/-/win32-arm64-0.28.0.tgz", + "integrity": "sha512-Q9StnDmQ/enxnpxCCLSg0oo4+34B9TdXpuyPeTedN/6+iXBJ4J+zwfQI28u/Jl40nOYAxGoNi7mFP40RUtkmUA==", "cpu": [ "arm64" ], @@ -716,14 +756,15 @@ "os": [ "win32" ], + "peer": true, "engines": { "node": ">=18" } }, "node_modules/@esbuild/win32-ia32": { - "version": "0.25.12", - "resolved": "https://registry.npmjs.org/@esbuild/win32-ia32/-/win32-ia32-0.25.12.tgz", - "integrity": "sha512-HkqnmmBoCbCwxUKKNPBixiWDGCpQGVsrQfJoVGYLPT41XWF8lHuE5N6WhVia2n4o5QK5M4tYr21827fNhi4byQ==", + "version": "0.28.0", + "resolved": "https://registry.npmjs.org/@esbuild/win32-ia32/-/win32-ia32-0.28.0.tgz", + "integrity": "sha512-zF3ag/gfiCe6U2iczcRzSYJKH1DCI+ByzSENHlM2FcDbEeo5Zd2C86Aq0tKUYAJJ1obRP84ymxIAksZUcdztHA==", "cpu": [ "ia32" ], @@ -733,14 +774,15 @@ "os": [ "win32" ], + "peer": true, "engines": { "node": ">=18" } }, "node_modules/@esbuild/win32-x64": { - "version": "0.25.12", - "resolved": "https://registry.npmjs.org/@esbuild/win32-x64/-/win32-x64-0.25.12.tgz", - "integrity": "sha512-alJC0uCZpTFrSL0CCDjcgleBXPnCrEAhTBILpeAp7M/OFgoqtAetfBzX0xM00MUsVVPpVjlPuMbREqnZCXaTnA==", + "version": "0.28.0", + "resolved": "https://registry.npmjs.org/@esbuild/win32-x64/-/win32-x64-0.28.0.tgz", + "integrity": "sha512-pEl1bO9mfAmIC+tW5btTmrKaujg3zGtUmWNdCw/xs70FBjwAL3o9OEKNHvNmnyylD6ubxUERiEhdsL0xBQ9efw==", "cpu": [ "x64" ], @@ -750,6 +792,7 @@ "os": [ "win32" ], + "peer": true, "engines": { "node": ">=18" } @@ -3155,12 +3198,14 @@ "license": "MIT" }, "node_modules/esbuild": { - "version": "0.25.12", - "resolved": "https://registry.npmjs.org/esbuild/-/esbuild-0.25.12.tgz", - "integrity": "sha512-bbPBYYrtZbkt6Os6FiTLCTFxvq4tt3JKall1vRwshA3fdVztsLAatFaZobhkBC8/BrPetoa0oksYoKXoG4ryJg==", + "version": "0.28.0", + "resolved": "https://registry.npmjs.org/esbuild/-/esbuild-0.28.0.tgz", + "integrity": "sha512-sNR9MHpXSUV/XB4zmsFKN+QgVG82Cc7+/aaxJ8Adi8hyOac+EXptIp45QBPaVyX3N70664wRbTcLTOemCAnyqw==", "dev": true, "hasInstallScript": true, "license": "MIT", + "optional": true, + "peer": true, "bin": { "esbuild": "bin/esbuild" }, @@ -3168,32 +3213,32 @@ "node": ">=18" }, "optionalDependencies": { - "@esbuild/aix-ppc64": "0.25.12", - "@esbuild/android-arm": "0.25.12", - "@esbuild/android-arm64": "0.25.12", - "@esbuild/android-x64": "0.25.12", - "@esbuild/darwin-arm64": "0.25.12", - "@esbuild/darwin-x64": "0.25.12", - "@esbuild/freebsd-arm64": "0.25.12", - "@esbuild/freebsd-x64": "0.25.12", - "@esbuild/linux-arm": "0.25.12", - "@esbuild/linux-arm64": "0.25.12", - "@esbuild/linux-ia32": "0.25.12", - "@esbuild/linux-loong64": "0.25.12", - "@esbuild/linux-mips64el": "0.25.12", - "@esbuild/linux-ppc64": "0.25.12", - "@esbuild/linux-riscv64": "0.25.12", - "@esbuild/linux-s390x": "0.25.12", - "@esbuild/linux-x64": "0.25.12", - "@esbuild/netbsd-arm64": "0.25.12", - "@esbuild/netbsd-x64": "0.25.12", - "@esbuild/openbsd-arm64": "0.25.12", - "@esbuild/openbsd-x64": "0.25.12", - "@esbuild/openharmony-arm64": "0.25.12", - "@esbuild/sunos-x64": "0.25.12", - "@esbuild/win32-arm64": "0.25.12", - "@esbuild/win32-ia32": "0.25.12", - "@esbuild/win32-x64": "0.25.12" + "@esbuild/aix-ppc64": "0.28.0", + "@esbuild/android-arm": "0.28.0", + "@esbuild/android-arm64": "0.28.0", + "@esbuild/android-x64": "0.28.0", + "@esbuild/darwin-arm64": "0.28.0", + "@esbuild/darwin-x64": "0.28.0", + "@esbuild/freebsd-arm64": "0.28.0", + "@esbuild/freebsd-x64": "0.28.0", + "@esbuild/linux-arm": "0.28.0", + "@esbuild/linux-arm64": "0.28.0", + "@esbuild/linux-ia32": "0.28.0", + "@esbuild/linux-loong64": "0.28.0", + "@esbuild/linux-mips64el": "0.28.0", + "@esbuild/linux-ppc64": "0.28.0", + "@esbuild/linux-riscv64": "0.28.0", + "@esbuild/linux-s390x": "0.28.0", + "@esbuild/linux-x64": "0.28.0", + "@esbuild/netbsd-arm64": "0.28.0", + "@esbuild/netbsd-x64": "0.28.0", + "@esbuild/openbsd-arm64": "0.28.0", + "@esbuild/openbsd-x64": "0.28.0", + "@esbuild/openharmony-arm64": "0.28.0", + "@esbuild/sunos-x64": "0.28.0", + "@esbuild/win32-arm64": "0.28.0", + "@esbuild/win32-ia32": "0.28.0", + "@esbuild/win32-x64": "0.28.0" } }, "node_modules/escalade": { @@ -4474,6 +4519,10 @@ "url": "https://opencollective.com/parcel" } }, + "node_modules/lightnode-sdk": { + "resolved": "../sdk", + "link": true + }, "node_modules/lines-and-columns": { "version": "2.0.4", "resolved": "https://registry.npmjs.org/lines-and-columns/-/lines-and-columns-2.0.4.tgz", @@ -8104,15 +8153,499 @@ } } }, - "node_modules/wxt/node_modules/@types/chrome": { - "version": "0.0.280", - "resolved": "https://registry.npmjs.org/@types/chrome/-/chrome-0.0.280.tgz", - "integrity": "sha512-AotSmZrL9bcZDDmSI1D9dE7PGbhOur5L0cKxXd7IqbVizQWCY4gcvupPUVsQ4FfDj3V2tt/iOpomT9EY0s+w1g==", + "node_modules/wxt/node_modules/@esbuild/aix-ppc64": { + "version": "0.25.12", + "resolved": "https://registry.npmjs.org/@esbuild/aix-ppc64/-/aix-ppc64-0.25.12.tgz", + "integrity": "sha512-Hhmwd6CInZ3dwpuGTF8fJG6yoWmsToE+vYgD4nytZVxcu1ulHpUQRAB1UJ8+N1Am3Mz4+xOByoQoSZf4D+CpkA==", + "cpu": [ + "ppc64" + ], "dev": true, "license": "MIT", - "dependencies": { - "@types/filesystem": "*", - "@types/har-format": "*" + "optional": true, + "os": [ + "aix" + ], + "engines": { + "node": ">=18" + } + }, + "node_modules/wxt/node_modules/@esbuild/android-arm": { + "version": "0.25.12", + "resolved": "https://registry.npmjs.org/@esbuild/android-arm/-/android-arm-0.25.12.tgz", + "integrity": "sha512-VJ+sKvNA/GE7Ccacc9Cha7bpS8nyzVv0jdVgwNDaR4gDMC/2TTRc33Ip8qrNYUcpkOHUT5OZ0bUcNNVZQ9RLlg==", + "cpu": [ + "arm" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "android" + ], + "engines": { + "node": ">=18" + } + }, + "node_modules/wxt/node_modules/@esbuild/android-arm64": { + "version": "0.25.12", + "resolved": "https://registry.npmjs.org/@esbuild/android-arm64/-/android-arm64-0.25.12.tgz", + "integrity": "sha512-6AAmLG7zwD1Z159jCKPvAxZd4y/VTO0VkprYy+3N2FtJ8+BQWFXU+OxARIwA46c5tdD9SsKGZ/1ocqBS/gAKHg==", + "cpu": [ + "arm64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "android" + ], + "engines": { + "node": ">=18" + } + }, + "node_modules/wxt/node_modules/@esbuild/android-x64": { + "version": "0.25.12", + "resolved": "https://registry.npmjs.org/@esbuild/android-x64/-/android-x64-0.25.12.tgz", + "integrity": "sha512-5jbb+2hhDHx5phYR2By8GTWEzn6I9UqR11Kwf22iKbNpYrsmRB18aX/9ivc5cabcUiAT/wM+YIZ6SG9QO6a8kg==", + "cpu": [ + "x64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "android" + ], + "engines": { + "node": ">=18" + } + }, + "node_modules/wxt/node_modules/@esbuild/darwin-arm64": { + "version": "0.25.12", + "resolved": "https://registry.npmjs.org/@esbuild/darwin-arm64/-/darwin-arm64-0.25.12.tgz", + "integrity": "sha512-N3zl+lxHCifgIlcMUP5016ESkeQjLj/959RxxNYIthIg+CQHInujFuXeWbWMgnTo4cp5XVHqFPmpyu9J65C1Yg==", + "cpu": [ + "arm64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "darwin" + ], + "engines": { + "node": ">=18" + } + }, + "node_modules/wxt/node_modules/@esbuild/darwin-x64": { + "version": "0.25.12", + "resolved": "https://registry.npmjs.org/@esbuild/darwin-x64/-/darwin-x64-0.25.12.tgz", + "integrity": "sha512-HQ9ka4Kx21qHXwtlTUVbKJOAnmG1ipXhdWTmNXiPzPfWKpXqASVcWdnf2bnL73wgjNrFXAa3yYvBSd9pzfEIpA==", + "cpu": [ + "x64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "darwin" + ], + "engines": { + "node": ">=18" + } + }, + "node_modules/wxt/node_modules/@esbuild/freebsd-arm64": { + "version": "0.25.12", + "resolved": "https://registry.npmjs.org/@esbuild/freebsd-arm64/-/freebsd-arm64-0.25.12.tgz", + "integrity": "sha512-gA0Bx759+7Jve03K1S0vkOu5Lg/85dou3EseOGUes8flVOGxbhDDh/iZaoek11Y8mtyKPGF3vP8XhnkDEAmzeg==", + "cpu": [ + "arm64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "freebsd" + ], + "engines": { + "node": ">=18" + } + }, + "node_modules/wxt/node_modules/@esbuild/freebsd-x64": { + "version": "0.25.12", + "resolved": "https://registry.npmjs.org/@esbuild/freebsd-x64/-/freebsd-x64-0.25.12.tgz", + "integrity": "sha512-TGbO26Yw2xsHzxtbVFGEXBFH0FRAP7gtcPE7P5yP7wGy7cXK2oO7RyOhL5NLiqTlBh47XhmIUXuGciXEqYFfBQ==", + "cpu": [ + "x64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "freebsd" + ], + "engines": { + "node": ">=18" + } + }, + "node_modules/wxt/node_modules/@esbuild/linux-arm": { + "version": "0.25.12", + "resolved": "https://registry.npmjs.org/@esbuild/linux-arm/-/linux-arm-0.25.12.tgz", + "integrity": "sha512-lPDGyC1JPDou8kGcywY0YILzWlhhnRjdof3UlcoqYmS9El818LLfJJc3PXXgZHrHCAKs/Z2SeZtDJr5MrkxtOw==", + "cpu": [ + "arm" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "linux" + ], + "engines": { + "node": ">=18" + } + }, + "node_modules/wxt/node_modules/@esbuild/linux-arm64": { + "version": "0.25.12", + "resolved": "https://registry.npmjs.org/@esbuild/linux-arm64/-/linux-arm64-0.25.12.tgz", + "integrity": "sha512-8bwX7a8FghIgrupcxb4aUmYDLp8pX06rGh5HqDT7bB+8Rdells6mHvrFHHW2JAOPZUbnjUpKTLg6ECyzvas2AQ==", + "cpu": [ + "arm64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "linux" + ], + "engines": { + "node": ">=18" + } + }, + "node_modules/wxt/node_modules/@esbuild/linux-ia32": { + "version": "0.25.12", + "resolved": "https://registry.npmjs.org/@esbuild/linux-ia32/-/linux-ia32-0.25.12.tgz", + "integrity": "sha512-0y9KrdVnbMM2/vG8KfU0byhUN+EFCny9+8g202gYqSSVMonbsCfLjUO+rCci7pM0WBEtz+oK/PIwHkzxkyharA==", + "cpu": [ + "ia32" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "linux" + ], + "engines": { + "node": ">=18" + } + }, + "node_modules/wxt/node_modules/@esbuild/linux-loong64": { + "version": "0.25.12", + "resolved": "https://registry.npmjs.org/@esbuild/linux-loong64/-/linux-loong64-0.25.12.tgz", + "integrity": "sha512-h///Lr5a9rib/v1GGqXVGzjL4TMvVTv+s1DPoxQdz7l/AYv6LDSxdIwzxkrPW438oUXiDtwM10o9PmwS/6Z0Ng==", + "cpu": [ + "loong64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "linux" + ], + "engines": { + "node": ">=18" + } + }, + "node_modules/wxt/node_modules/@esbuild/linux-mips64el": { + "version": "0.25.12", + "resolved": "https://registry.npmjs.org/@esbuild/linux-mips64el/-/linux-mips64el-0.25.12.tgz", + "integrity": "sha512-iyRrM1Pzy9GFMDLsXn1iHUm18nhKnNMWscjmp4+hpafcZjrr2WbT//d20xaGljXDBYHqRcl8HnxbX6uaA/eGVw==", + "cpu": [ + "mips64el" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "linux" + ], + "engines": { + "node": ">=18" + } + }, + "node_modules/wxt/node_modules/@esbuild/linux-ppc64": { + "version": "0.25.12", + "resolved": "https://registry.npmjs.org/@esbuild/linux-ppc64/-/linux-ppc64-0.25.12.tgz", + "integrity": "sha512-9meM/lRXxMi5PSUqEXRCtVjEZBGwB7P/D4yT8UG/mwIdze2aV4Vo6U5gD3+RsoHXKkHCfSxZKzmDssVlRj1QQA==", + "cpu": [ + "ppc64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "linux" + ], + "engines": { + "node": ">=18" + } + }, + "node_modules/wxt/node_modules/@esbuild/linux-riscv64": { + "version": "0.25.12", + "resolved": "https://registry.npmjs.org/@esbuild/linux-riscv64/-/linux-riscv64-0.25.12.tgz", + "integrity": "sha512-Zr7KR4hgKUpWAwb1f3o5ygT04MzqVrGEGXGLnj15YQDJErYu/BGg+wmFlIDOdJp0PmB0lLvxFIOXZgFRrdjR0w==", + "cpu": [ + "riscv64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "linux" + ], + "engines": { + "node": ">=18" + } + }, + "node_modules/wxt/node_modules/@esbuild/linux-s390x": { + "version": "0.25.12", + "resolved": "https://registry.npmjs.org/@esbuild/linux-s390x/-/linux-s390x-0.25.12.tgz", + "integrity": "sha512-MsKncOcgTNvdtiISc/jZs/Zf8d0cl/t3gYWX8J9ubBnVOwlk65UIEEvgBORTiljloIWnBzLs4qhzPkJcitIzIg==", + "cpu": [ + "s390x" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "linux" + ], + "engines": { + "node": ">=18" + } + }, + "node_modules/wxt/node_modules/@esbuild/linux-x64": { + "version": "0.25.12", + "resolved": "https://registry.npmjs.org/@esbuild/linux-x64/-/linux-x64-0.25.12.tgz", + "integrity": "sha512-uqZMTLr/zR/ed4jIGnwSLkaHmPjOjJvnm6TVVitAa08SLS9Z0VM8wIRx7gWbJB5/J54YuIMInDquWyYvQLZkgw==", + "cpu": [ + "x64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "linux" + ], + "engines": { + "node": ">=18" + } + }, + "node_modules/wxt/node_modules/@esbuild/netbsd-arm64": { + "version": "0.25.12", + "resolved": "https://registry.npmjs.org/@esbuild/netbsd-arm64/-/netbsd-arm64-0.25.12.tgz", + "integrity": "sha512-xXwcTq4GhRM7J9A8Gv5boanHhRa/Q9KLVmcyXHCTaM4wKfIpWkdXiMog/KsnxzJ0A1+nD+zoecuzqPmCRyBGjg==", + "cpu": [ + "arm64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "netbsd" + ], + "engines": { + "node": ">=18" + } + }, + "node_modules/wxt/node_modules/@esbuild/netbsd-x64": { + "version": "0.25.12", + "resolved": "https://registry.npmjs.org/@esbuild/netbsd-x64/-/netbsd-x64-0.25.12.tgz", + "integrity": "sha512-Ld5pTlzPy3YwGec4OuHh1aCVCRvOXdH8DgRjfDy/oumVovmuSzWfnSJg+VtakB9Cm0gxNO9BzWkj6mtO1FMXkQ==", + "cpu": [ + "x64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "netbsd" + ], + "engines": { + "node": ">=18" + } + }, + "node_modules/wxt/node_modules/@esbuild/openbsd-arm64": { + "version": "0.25.12", + "resolved": "https://registry.npmjs.org/@esbuild/openbsd-arm64/-/openbsd-arm64-0.25.12.tgz", + "integrity": "sha512-fF96T6KsBo/pkQI950FARU9apGNTSlZGsv1jZBAlcLL1MLjLNIWPBkj5NlSz8aAzYKg+eNqknrUJ24QBybeR5A==", + "cpu": [ + "arm64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "openbsd" + ], + "engines": { + "node": ">=18" + } + }, + "node_modules/wxt/node_modules/@esbuild/openbsd-x64": { + "version": "0.25.12", + "resolved": "https://registry.npmjs.org/@esbuild/openbsd-x64/-/openbsd-x64-0.25.12.tgz", + "integrity": "sha512-MZyXUkZHjQxUvzK7rN8DJ3SRmrVrke8ZyRusHlP+kuwqTcfWLyqMOE3sScPPyeIXN/mDJIfGXvcMqCgYKekoQw==", + "cpu": [ + "x64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "openbsd" + ], + "engines": { + "node": ">=18" + } + }, + "node_modules/wxt/node_modules/@esbuild/openharmony-arm64": { + "version": "0.25.12", + "resolved": "https://registry.npmjs.org/@esbuild/openharmony-arm64/-/openharmony-arm64-0.25.12.tgz", + "integrity": "sha512-rm0YWsqUSRrjncSXGA7Zv78Nbnw4XL6/dzr20cyrQf7ZmRcsovpcRBdhD43Nuk3y7XIoW2OxMVvwuRvk9XdASg==", + "cpu": [ + "arm64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "openharmony" + ], + "engines": { + "node": ">=18" + } + }, + "node_modules/wxt/node_modules/@esbuild/sunos-x64": { + "version": "0.25.12", + "resolved": "https://registry.npmjs.org/@esbuild/sunos-x64/-/sunos-x64-0.25.12.tgz", + "integrity": "sha512-3wGSCDyuTHQUzt0nV7bocDy72r2lI33QL3gkDNGkod22EsYl04sMf0qLb8luNKTOmgF/eDEDP5BFNwoBKH441w==", + "cpu": [ + "x64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "sunos" + ], + "engines": { + "node": ">=18" + } + }, + "node_modules/wxt/node_modules/@esbuild/win32-arm64": { + "version": "0.25.12", + "resolved": "https://registry.npmjs.org/@esbuild/win32-arm64/-/win32-arm64-0.25.12.tgz", + "integrity": "sha512-rMmLrur64A7+DKlnSuwqUdRKyd3UE7oPJZmnljqEptesKM8wx9J8gx5u0+9Pq0fQQW8vqeKebwNXdfOyP+8Bsg==", + "cpu": [ + "arm64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "win32" + ], + "engines": { + "node": ">=18" + } + }, + "node_modules/wxt/node_modules/@esbuild/win32-ia32": { + "version": "0.25.12", + "resolved": "https://registry.npmjs.org/@esbuild/win32-ia32/-/win32-ia32-0.25.12.tgz", + "integrity": "sha512-HkqnmmBoCbCwxUKKNPBixiWDGCpQGVsrQfJoVGYLPT41XWF8lHuE5N6WhVia2n4o5QK5M4tYr21827fNhi4byQ==", + "cpu": [ + "ia32" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "win32" + ], + "engines": { + "node": ">=18" + } + }, + "node_modules/wxt/node_modules/@esbuild/win32-x64": { + "version": "0.25.12", + "resolved": "https://registry.npmjs.org/@esbuild/win32-x64/-/win32-x64-0.25.12.tgz", + "integrity": "sha512-alJC0uCZpTFrSL0CCDjcgleBXPnCrEAhTBILpeAp7M/OFgoqtAetfBzX0xM00MUsVVPpVjlPuMbREqnZCXaTnA==", + "cpu": [ + "x64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "win32" + ], + "engines": { + "node": ">=18" + } + }, + "node_modules/wxt/node_modules/@types/chrome": { + "version": "0.0.280", + "resolved": "https://registry.npmjs.org/@types/chrome/-/chrome-0.0.280.tgz", + "integrity": "sha512-AotSmZrL9bcZDDmSI1D9dE7PGbhOur5L0cKxXd7IqbVizQWCY4gcvupPUVsQ4FfDj3V2tt/iOpomT9EY0s+w1g==", + "dev": true, + "license": "MIT", + "dependencies": { + "@types/filesystem": "*", + "@types/har-format": "*" + } + }, + "node_modules/wxt/node_modules/esbuild": { + "version": "0.25.12", + "resolved": "https://registry.npmjs.org/esbuild/-/esbuild-0.25.12.tgz", + "integrity": "sha512-bbPBYYrtZbkt6Os6FiTLCTFxvq4tt3JKall1vRwshA3fdVztsLAatFaZobhkBC8/BrPetoa0oksYoKXoG4ryJg==", + "dev": true, + "hasInstallScript": true, + "license": "MIT", + "bin": { + "esbuild": "bin/esbuild" + }, + "engines": { + "node": ">=18" + }, + "optionalDependencies": { + "@esbuild/aix-ppc64": "0.25.12", + "@esbuild/android-arm": "0.25.12", + "@esbuild/android-arm64": "0.25.12", + "@esbuild/android-x64": "0.25.12", + "@esbuild/darwin-arm64": "0.25.12", + "@esbuild/darwin-x64": "0.25.12", + "@esbuild/freebsd-arm64": "0.25.12", + "@esbuild/freebsd-x64": "0.25.12", + "@esbuild/linux-arm": "0.25.12", + "@esbuild/linux-arm64": "0.25.12", + "@esbuild/linux-ia32": "0.25.12", + "@esbuild/linux-loong64": "0.25.12", + "@esbuild/linux-mips64el": "0.25.12", + "@esbuild/linux-ppc64": "0.25.12", + "@esbuild/linux-riscv64": "0.25.12", + "@esbuild/linux-s390x": "0.25.12", + "@esbuild/linux-x64": "0.25.12", + "@esbuild/netbsd-arm64": "0.25.12", + "@esbuild/netbsd-x64": "0.25.12", + "@esbuild/openbsd-arm64": "0.25.12", + "@esbuild/openbsd-x64": "0.25.12", + "@esbuild/openharmony-arm64": "0.25.12", + "@esbuild/sunos-x64": "0.25.12", + "@esbuild/win32-arm64": "0.25.12", + "@esbuild/win32-ia32": "0.25.12", + "@esbuild/win32-x64": "0.25.12" } }, "node_modules/wxt/node_modules/vite": { diff --git a/wallet/package.json b/wallet/package.json index ee9daba..ecb8066 100644 --- a/wallet/package.json +++ b/wallet/package.json @@ -16,6 +16,7 @@ "@noble/hashes": "^1.6.1", "@scure/bip32": "^1.6.2", "@scure/bip39": "^1.5.4", + "lightnode-sdk": "file:../sdk", "react": "^18.3.1", "react-dom": "^18.3.1", "viem": "^2.21.0" diff --git a/wallet/src/provider/decode-call.test.ts b/wallet/src/provider/decode-call.test.ts new file mode 100644 index 0000000..818c6b0 --- /dev/null +++ b/wallet/src/provider/decode-call.test.ts @@ -0,0 +1,67 @@ +import { describe, it, expect } from "vitest"; +import { encodeFunctionData, maxUint256, type Hex } from "viem"; +import { decodeDangerousCall } from "./decode-call"; +import { summarizeTypedData } from "./typed-data"; + +const SPENDER = "0x1111111111111111111111111111111111111111"; +const TO = "0x2222222222222222222222222222222222222222"; + +const erc20 = [ + { type: "function", name: "approve", inputs: [{ name: "spender", type: "address" }, { name: "value", type: "uint256" }] }, + { type: "function", name: "transfer", inputs: [{ name: "to", type: "address" }, { name: "value", type: "uint256" }] }, + { type: "function", name: "setApprovalForAll", inputs: [{ name: "operator", type: "address" }, { name: "approved", type: "bool" }] }, + { type: "function", name: "safeTransferFrom", inputs: [{ name: "from", type: "address" }, { name: "to", type: "address" }, { name: "id", type: "uint256" }, { name: "data", type: "bytes" }] }, +] as const; +const data = (name: string, args: readonly unknown[]): Hex => encodeFunctionData({ abi: erc20, functionName: name, args }); + +describe("decodeDangerousCall", () => { + it("treats no data as a native transfer", () => { + expect(decodeDangerousCall("0x").kind).toBe("empty"); + expect(decodeDangerousCall(undefined).kind).toBe("empty"); + }); + it("flags an UNLIMITED approve as danger", () => { + const d = decodeDangerousCall(data("approve", [SPENDER, maxUint256])); + expect(d.kind).toBe("approve"); + expect(d.severity).toBe("danger"); + expect(d.unlimited).toBe(true); + expect(d.detail).toContain("UNLIMITED"); + }); + it("reports a bounded approve without the unlimited flag", () => { + const d = decodeDangerousCall(data("approve", [SPENDER, 1000n])); + expect(d.unlimited).toBe(false); + expect(d.severity).toBe("danger"); + expect(d.spender?.toLowerCase()).toBe(SPENDER); + }); + it("hard-flags setApprovalForAll(true) and softens revoke", () => { + expect(decodeDangerousCall(data("setApprovalForAll", [SPENDER, true])).severity).toBe("danger"); + expect(decodeDangerousCall(data("setApprovalForAll", [SPENDER, false])).severity).toBe("info"); + }); + it("classifies transfer + 4-arg safeTransferFrom (review fix) as transfers", () => { + expect(decodeDangerousCall(data("transfer", [TO, 5n])).kind).toBe("transfer"); + const st = decodeDangerousCall(data("safeTransferFrom", [SPENDER, TO, 7n, "0x"])); + expect(st.kind).toBe("transferFrom"); + expect(st.recipient?.toLowerCase()).toBe(TO); + }); + it("falls back to a guarded 'unknown' for unrecognized selectors", () => { + expect(decodeDangerousCall("0xdeadbeef00000000").kind).toBe("unknown"); + }); +}); + +describe("summarizeTypedData", () => { + const base = { domain: { name: "Uniswap", chainId: 9200, verifyingContract: SPENDER }, primaryType: "Mail", message: {} }; + it("accepts a payload whose chainId is allowed", () => { + const s = summarizeTypedData(JSON.stringify(base), [9200, 8200]); + expect(s.chainIdOk).toBe(true); + expect(s.domainName).toBe("Uniswap"); + expect(s.verifyingContract).toBe(SPENDER); + }); + it("rejects a mismatched chainId", () => { + expect(summarizeTypedData(JSON.stringify({ ...base, domain: { ...base.domain, chainId: 1 } }), [9200]).chainIdOk).toBe(false); + }); + it("warns on Permit primary types", () => { + expect(summarizeTypedData(JSON.stringify({ ...base, primaryType: "Permit" }), [9200]).warning).toBeTruthy(); + }); + it("handles malformed input safely", () => { + expect(summarizeTypedData("{not json", [9200]).error).toBeTruthy(); + }); +}); diff --git a/wallet/src/provider/decode-call.ts b/wallet/src/provider/decode-call.ts new file mode 100644 index 0000000..54c93af --- /dev/null +++ b/wallet/src/provider/decode-call.ts @@ -0,0 +1,111 @@ +/** + * Decode an eth_sendTransaction `data` field into a human-facing danger summary + * so the approval popup never blind-signs. Pure + unit-tested (no DOM). Covers + * the ERC-20/721/1155 + Permit methods that drain wallets; everything else is + * surfaced as "unrecognized contract call, only approve if you trust this site". + */ +import { decodeFunctionData, getAddress, type Hex } from "viem"; + +export type Severity = "info" | "warn" | "danger"; +export type DangerKind = + | "approve" + | "increaseAllowance" + | "setApprovalForAll" + | "permit" + | "transfer" + | "transferFrom" + | "unknown" + | "empty"; + +export interface DecodedCall { + kind: DangerKind; + label: string; + severity: Severity; + detail: string; + unlimited?: boolean; + spender?: string; + recipient?: string; +} + +// >= 2^255 is the standard "effectively unlimited" heuristic (catches MAX_UINT256 +// and the 2^255 sentinel dapps use). +const UNLIMITED = 1n << 255n; + +// Both safeTransferFrom overloads (3-arg 0x42842e0e and 4-arg-with-bytes 0xb88d4fde) +// are included so marketplace transfers don't fall through to "unknown". +const DANGEROUS_ABI = [ + { type: "function", name: "approve", inputs: [{ name: "spender", type: "address" }, { name: "value", type: "uint256" }] }, + { type: "function", name: "increaseAllowance", inputs: [{ name: "spender", type: "address" }, { name: "addedValue", type: "uint256" }] }, + { type: "function", name: "setApprovalForAll", inputs: [{ name: "operator", type: "address" }, { name: "approved", type: "bool" }] }, + { type: "function", name: "transfer", inputs: [{ name: "to", type: "address" }, { name: "value", type: "uint256" }] }, + { type: "function", name: "transferFrom", inputs: [{ name: "from", type: "address" }, { name: "to", type: "address" }, { name: "value", type: "uint256" }] }, + { type: "function", name: "safeTransferFrom", inputs: [{ name: "from", type: "address" }, { name: "to", type: "address" }, { name: "id", type: "uint256" }] }, + { type: "function", name: "safeTransferFrom", inputs: [{ name: "from", type: "address" }, { name: "to", type: "address" }, { name: "id", type: "uint256" }, { name: "data", type: "bytes" }] }, + { type: "function", name: "permit", inputs: [{ name: "owner", type: "address" }, { name: "spender", type: "address" }, { name: "value", type: "uint256" }, { name: "deadline", type: "uint256" }, { name: "v", type: "uint8" }, { name: "r", type: "bytes32" }, { name: "s", type: "bytes32" }] }, +] as const; + +const EMPTY: DecodedCall = { kind: "empty", label: "Native transfer", severity: "info", detail: "No contract call data." }; +const UNKNOWN: DecodedCall = { kind: "unknown", label: "Contract interaction", severity: "warn", detail: "Unrecognized contract call. Only approve if you trust this site - it can move tokens." }; + +const short = (a: string) => `${a.slice(0, 6)}…${a.slice(-4)}`; +const addr = (v: unknown): string => { + try { + return getAddress(String(v)); + } catch { + return String(v); + } +}; +const big = (v: unknown): bigint => { + try { + return BigInt(v as bigint); + } catch { + return 0n; + } +}; + +export function decodeDangerousCall(data: Hex | undefined | null): DecodedCall { + if (!data || data === "0x" || data.length < 10) return EMPTY; + try { + const { functionName, args } = decodeFunctionData({ abi: DANGEROUS_ABI, data }); + return summarize(functionName, (args ?? []) as readonly unknown[]); + } catch { + return UNKNOWN; + } +} + +function summarize(fn: string, args: readonly unknown[]): DecodedCall { + if (fn === "approve") return spendApproval("approve", "Token spending approval", addr(args[0]), big(args[1])); + if (fn === "increaseAllowance") return spendApproval("increaseAllowance", "Increase token allowance", addr(args[0]), big(args[1])); + if (fn === "permit") return spendApproval("permit", "Gasless spending permit", addr(args[1]), big(args[2])); + if (fn === "setApprovalForAll") return setApprovalAll(addr(args[0]), Boolean(args[1])); + if (fn === "transfer") return { kind: "transfer", label: "Token transfer", severity: "warn", detail: `Sends tokens to ${short(addr(args[0]))}.`, recipient: addr(args[0]) }; + if (fn === "transferFrom" || fn === "safeTransferFrom") return { kind: "transferFrom", label: "Token transfer", severity: "warn", detail: `Moves tokens to ${short(addr(args[1]))}.`, recipient: addr(args[1]) }; + return UNKNOWN; +} + +function spendApproval(kind: DangerKind, label: string, spender: string, value: bigint): DecodedCall { + const unlimited = value >= UNLIMITED; + return { + kind, + label, + severity: "danger", + spender, + unlimited, + detail: unlimited + ? `UNLIMITED: ${short(spender)} could spend ALL of this token, now and later.` + : `Lets ${short(spender)} spend up to ${value.toString()} raw units.`, + }; +} + +function setApprovalAll(operator: string, approved: boolean): DecodedCall { + return { + kind: "setApprovalForAll", + label: "Approve ALL NFTs", + severity: approved ? "danger" : "info", + spender: operator, + unlimited: approved, + detail: approved + ? `Grants ${short(operator)} control of EVERY NFT in this collection you own.` + : `Revokes ${short(operator)}'s approval for this collection.`, + }; +} diff --git a/wallet/src/provider/protocol.ts b/wallet/src/provider/protocol.ts index ecbceba..4076618 100644 --- a/wallet/src/provider/protocol.ts +++ b/wallet/src/provider/protocol.ts @@ -37,6 +37,7 @@ export type WalletOp = | { type: "lock" } | { type: "addAccount" } | { type: "getBalance"; address: string } + | { type: "workerStatus"; address: string } | { type: "send"; from: string; to: string; valueWei: string } | { type: "listPending" } | { type: "resolvePending"; id: string; approved: boolean }; diff --git a/wallet/src/provider/typed-data.ts b/wallet/src/provider/typed-data.ts new file mode 100644 index 0000000..bab8287 --- /dev/null +++ b/wallet/src/provider/typed-data.ts @@ -0,0 +1,50 @@ +/** + * Parse + validate an EIP-712 payload for the approval popup. EIP-712 phishing + * spoofs domain.name / verifyingContract while keeping chainId correct, so we + * surface the full domain and hard-warn on Permit / order-style primary types. + */ +export interface TypedDataSummary { + chainIdOk: boolean; + chainId?: number; + primaryType: string; + domainName?: string; + verifyingContract?: string; + warning?: string; + error?: string; +} + +interface RawTypedData { + domain?: { name?: string; chainId?: number | string; verifyingContract?: string }; + primaryType?: string; + message?: unknown; + types?: Record; +} + +const RISKY_PRIMARY = /permit|order|approv/i; + +export function summarizeTypedData(payload: unknown, allowedChainIds: number[]): TypedDataSummary { + const td = parseTypedData(payload); + if (!td) return { chainIdOk: false, primaryType: "?", error: "Could not parse the typed-data payload." }; + const chainId = td.domain?.chainId != null ? Number(td.domain.chainId) : undefined; + const primaryType = td.primaryType ?? "?"; + return { + chainId, + chainIdOk: chainId != null && allowedChainIds.includes(chainId), + primaryType, + domainName: td.domain?.name, + verifyingContract: td.domain?.verifyingContract, + warning: RISKY_PRIMARY.test(primaryType) + ? "This authorizes spending/an order via signature (no transaction needed). Verify the contract." + : undefined, + }; +} + +export function parseTypedData(payload: unknown): RawTypedData | null { + try { + const obj = typeof payload === "string" ? JSON.parse(payload) : payload; + if (!obj || typeof obj !== "object") return null; + return obj as RawTypedData; + } catch { + return null; + } +}