@anywalletkit/extension-kit 0.4.0 → 0.5.1

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
package/README.md CHANGED
@@ -41,7 +41,7 @@ pnpm add @anywalletkit/extension-kit @anywalletkit/core @anywalletkit/keyring
41
41
  | Permissions | `PermissionStore`, `CHAIN_ACCOUNT_KEYS` | per-origin accounts (EVM, Solana, TON, Tron, Bitcoin, NEAR) and chain; `grant` adds accounts to one origin |
42
42
  | Approvals | `ApprovalQueue`, `ReviewDeclined`, `QueueLimitError`, `APPROVALS_MAX_PENDING_PER_ORIGIN`, `APPROVALS_MAX_PENDING_PER_SITE`, `APPROVALS_MAX_PENDING_TOTAL` | one approval window at a time; at most 3 pending requests per origin, 5 per site (registrable domain), 20 in all, one pending `connect`; a per-site sheet rate with a doubling cooldown; `declineOrigin` / `declineSite`; `exempt` for the wallet's own pages; transport notes (`setVia`) for WalletConnect peers |
43
43
  | Limits | `siteOf`, `MULTI_LABEL_SUFFIXES`, `TokenBucket` | a conservative registrable domain (eTLD+1, no full Public Suffix List: the last two labels, three under known multi-label and hosting suffixes) and a keyed token bucket, for per-site limits |
44
- | Signing | `classifySignRequest`, `prepareTransaction`, `createSignHandler`, `messageText`, `revealHidden`, `gasLimitExcessive` | connected-address and chain checks, EIP-7702 delegations from sites refused, nonce / gas / fees from the wallet's RPC, a site's gas limit checked against the estimate (`gasCheck`), invisible / bidi characters escaped as `⟨U+XXXX⟩` (`hiddenChars`), a personal_sign of a raw 32-byte hash refused unless `allowBlindHash` (then `blindHash`) |
44
+ | Signing | `classifySignRequest`, `prepareTransaction`, `createSignHandler`, `messageText`, `revealHidden`, `gasLimitExcessive` | connected-address and chain checks, EIP-7702 delegations from sites refused, nonce / gas / fees from the wallet's RPC, a site's gas limit checked against the estimate (`gasCheck`), invisible / bidi characters escaped as `⟨U+XXXX⟩` (`hiddenChars`), a personal_sign of exactly 32 signed bytes marked `blindHash` for a danger screen: the hex form (`0x` + 64 hex) refused unless `allowBlindHash`, text whose UTF-8 is 32 bytes always shown, never refused (`blindHashForm: 'hex' \| 'text'`) |
45
45
  | Networks | `NetworksTable`, `DEFAULT_NETWORKS`, `parseAddChainParams`, Solana / TON / Tron endpoints | EVM networks with public RPC failover lists; `wallet_addEthereumChain` only for catalog chains, https only, `eth_chainId` verified, site-supplied RPCs stamped `custom` |
46
46
  | Review gate | `ReviewGate`, `evmCallMatcher`, `chainTxMatcher` | after one sheet reviewed a whole plan, exactly those operations pass once within a TTL |
47
47
  | Swaps | `quoteRoutes`, `reviewSwapPlan`, `executionFloor`, `SwapsStore`, `SwapTracker`, `parseSwapId` | progressive quotes over swap2 providers, one review sheet per plan (a gasless order is shown and matched by its EIP-712 hash), pre-approvals bound to the reviewing account (`meta.account`), pending swaps resumed across worker restarts |
package/dist/index.cjs CHANGED
@@ -2049,6 +2049,58 @@ var permissionDescriptor = (origin, accounts) => ({
2049
2049
  date: Date.now()
2050
2050
  });
2051
2051
 
2052
+ // src/personal-sign.ts
2053
+ var HEX = /^0x([0-9a-fA-F]{2})*$/;
2054
+ var BLIND_HASH_MAX_CHARS = 66;
2055
+ var hexBytes = (value) => Uint8Array.from(value.slice(2).match(/../g) ?? [], (b) => Number.parseInt(b, 16));
2056
+ function codePoint(s, i) {
2057
+ const c = s.charCodeAt(i);
2058
+ if (c < 55296 || c > 57343) return c;
2059
+ const lo = c <= 56319 && i + 1 < s.length ? s.charCodeAt(i + 1) : 0;
2060
+ return lo >= 56320 && lo <= 57343 ? 65536 + (c - 55296 << 10) + (lo - 56320) : 65533;
2061
+ }
2062
+ function utf8Length(s) {
2063
+ let n = 0;
2064
+ for (let i = 0; i < s.length; i++) {
2065
+ const c = codePoint(s, i);
2066
+ if (c > 65535) {
2067
+ n += 4;
2068
+ i++;
2069
+ } else n += c < 128 ? 1 : c < 2048 ? 2 : 3;
2070
+ }
2071
+ return n;
2072
+ }
2073
+ function utf8Bytes(s) {
2074
+ const out = new Uint8Array(utf8Length(s));
2075
+ let o = 0;
2076
+ for (let i = 0; i < s.length; i++) {
2077
+ const c = codePoint(s, i);
2078
+ if (c < 128) out[o++] = c;
2079
+ else if (c < 2048) {
2080
+ out[o++] = 192 | c >> 6;
2081
+ out[o++] = 128 | c & 63;
2082
+ } else if (c < 65536) {
2083
+ out[o++] = 224 | c >> 12;
2084
+ out[o++] = 128 | c >> 6 & 63;
2085
+ out[o++] = 128 | c & 63;
2086
+ } else {
2087
+ out[o++] = 240 | c >> 18;
2088
+ out[o++] = 128 | c >> 12 & 63;
2089
+ out[o++] = 128 | c >> 6 & 63;
2090
+ out[o++] = 128 | c & 63;
2091
+ i++;
2092
+ }
2093
+ }
2094
+ return out;
2095
+ }
2096
+ function personalSignInput(message) {
2097
+ return HEX.test(message) ? hexBytes(message) : message;
2098
+ }
2099
+ function personalSignBytes(message) {
2100
+ const input = personalSignInput(message);
2101
+ return typeof input === "string" ? utf8Bytes(input) : input;
2102
+ }
2103
+
2052
2104
  // src/siwe.ts
2053
2105
  var SIWE_FORBIDDEN_CHARS = /[\u061c\u200b-\u200f\u202a-\u202e\u2060-\u2064\u2066-\u2069\ufeff]/;
2054
2106
  var HEADER_LINE = /^(?<domain>[^\n]+?) wants you to sign in with your Ethereum account:(?:\n|$)/;
@@ -2177,7 +2229,6 @@ var safeNonce = (n) => {
2177
2229
  };
2178
2230
  var MAX_FEE_REASKS = 3;
2179
2231
  var ADDRESS = /^0x[0-9a-fA-F]{40}$/;
2180
- var HEX = /^0x([0-9a-fA-F]{2})*$/;
2181
2232
  var lc = (s) => s.toLowerCase();
2182
2233
  var INVISIBLE = /[\p{Cf}\u2028\u2029\u115f\u1160\u3164\uffa0\x00-\x08\x0b\x0c\x0e-\x1f\x7f-\x9f]/gu;
2183
2234
  function revealHidden(text) {
@@ -2213,7 +2264,6 @@ function decodedMessage(value) {
2213
2264
  }
2214
2265
  var HIDDEN_CONTROL = /[\x00-\x08\x0b\x0c\x0e-\x1f\x7f-\x9f]/;
2215
2266
  var err = (code, message) => ({ code, message });
2216
- var BLIND_HASH = /^0x[0-9a-fA-F]{64}$/;
2217
2267
  function classifySignRequest(input) {
2218
2268
  const { method, params } = input;
2219
2269
  const owned = new Set(input.accounts.map(lc));
@@ -2224,15 +2274,16 @@ function classifySignRequest(input) {
2224
2274
  if (ours(msg) && !ours(addr)) [msg, addr] = [addr, msg];
2225
2275
  if (!ours(addr)) return err(RELAY_ERRORS.unauthorized, "personal_sign for an address this wallet does not hold.");
2226
2276
  if (typeof msg !== "string") return err(RELAY_ERRORS.invalidParams, "personal_sign expects a message string.");
2227
- const blindHash = BLIND_HASH.test(msg);
2228
- if (blindHash && !input.allowBlindHash) return err(RELAY_ERRORS.unsupportedMethod, 'This site asks you to sign a raw 32-byte hash (blind signing). It can be a swap order or a transfer (e.g. CoW Protocol) paying someone else, so this wallet refuses it. "Allow blind hash signatures" in Options turns on a review for such requests.');
2277
+ const thirtyTwo = msg.length <= BLIND_HASH_MAX_CHARS && personalSignBytes(msg).length === 32;
2278
+ const blindHashForm = !thirtyTwo ? void 0 : HEX.test(msg) ? "hex" : "text";
2279
+ if (blindHashForm === "hex" && !input.allowBlindHash) return err(RELAY_ERRORS.unsupportedMethod, 'This site asks you to sign a raw 32-byte hash (blind signing). It can be a swap order or a transfer (e.g. CoW Protocol) paying someone else, so this wallet refuses it. "Allow blind hash signatures" in Options turns on a review for such requests.');
2229
2280
  const shown = shownMessage(msg);
2230
2281
  const text = shown.text;
2231
2282
  const signed = decodedMessage(msg) ?? text;
2232
2283
  const siwe = input.origin ? describeSiwe(signed, { origin: input.origin, account: checksum(addr), chainId: input.network.chainId, ...input.via ? { via: input.via } : {}, ...input.now !== void 0 ? { now: input.now } : {} }) : null;
2233
2284
  if (siwe && HIDDEN_CONTROL.test(signed)) return err(RELAY_ERRORS.invalidParams, "This sign-in message contains hidden control characters \u2014 refused.");
2234
2285
  if (siwe && SIWE_FORBIDDEN_CHARS.test(signed)) return err(RELAY_ERRORS.invalidParams, "This sign-in message contains invisible or text-direction characters \u2014 refused.");
2235
- return { kind: "sign-message", method, account: checksum(addr), text, raw: msg, ...siwe ? { siwe } : {}, ...shown.hidden ? { hiddenChars: true } : {}, ...blindHash ? { blindHash: true } : {} };
2286
+ return { kind: "sign-message", method, account: checksum(addr), text, raw: msg, ...siwe ? { siwe } : {}, ...shown.hidden ? { hiddenChars: true } : {}, ...blindHashForm ? { blindHash: true, blindHashForm } : {} };
2236
2287
  }
2237
2288
  if (method === "eth_signTypedData_v4" || method === "eth_signTypedData_v3" || method === "eth_signTypedData") {
2238
2289
  const [addr, data] = params;
@@ -2396,7 +2447,7 @@ function createSignHandler(deps) {
2396
2447
  try {
2397
2448
  switch (info.kind) {
2398
2449
  case "sign-message":
2399
- return await handle.signer.signMessage(HEX.test(info.raw) ? Uint8Array.from(info.raw.slice(2).match(/../g) ?? [], (b) => Number.parseInt(b, 16)) : info.raw);
2450
+ return await handle.signer.signMessage(personalSignInput(info.raw));
2400
2451
  case "sign-typed-data":
2401
2452
  return await handle.signer.signTypedData(info.typedData);
2402
2453
  }