@proton/wrap-constants 0.3.0 → 0.3.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/dist/index.mjs CHANGED
@@ -1,6 +1,9 @@
1
1
  import * as z from "zod";
2
2
  import cloneDeep from "lodash.clonedeep";
3
3
  import BN from "bignumber.js";
4
+ import { base32nopad, base58, base58xrp, bech32, bech32m, createBase58check } from "@scure/base";
5
+ import { sha256 } from "@noble/hashes/sha2";
6
+ import { keccak_256 } from "@noble/hashes/sha3";
4
7
  // src/coins.ts
5
8
  let Chains = /* @__PURE__ */ function(Chains) {
6
9
  Chains["BitcoinMainnet"] = "Bitcoin";
@@ -2272,4 +2275,292 @@ const applyDiscountToFee = (fee, staked) => {
2272
2275
  else if (staked >= 1e5) return fee * .66;
2273
2276
  else return fee;
2274
2277
  };
2275
- export { Chains, ExtendedAssetParser, FEE_FACTOR, Graph, ILink, INode, ITopics, Networks, Providers, Status, TOPICS, Type, WRAP_AUTH_ACTION, WRAP_CONTRACT, accountParser, accountRamDeltaParser, actParser, actionTraceParser, addressParser, applyDiscountToFee, authorizationParser, authorizationsParser, baseActionParser, baseActionSerializedParser, bloksRatesParser, chronicleTransactionParser, compute_liquidity, compute_transfer, compute_transfer_curve, createAbstractAuthParser, dailyParser, dbHistoryTransactionParser, dbMarketParser, depositBodyParser, depthParser, exchangeRateParser, exchangeRatesParser, explorerLinkByChain, fillType, generateAddressBodyParser, generateAddressResponse, generateAuthActionsBaseParser, generateAuthParser, getDailyResponseParser, getDepthParser, getDepthResponseParser, getHistoryActionsParser, getHistoryActionsResponseParser, getHistoryTransactionResponseParser, getHistoryTransfersParser, getHistoryTransfersResponseParser, getLeaderboardParser, getLeaderboardResponseParser, getMarketsResponseParser, getOhlcvParser, getOhlcvResponseParser, getOpenOrdersBalancesResponseParser, getOpenOrdersParser, getOpenOrdersResponseParser, getOrderHistoryParser, getOrderHistoryResponseParser, getOrderLifecycleParser, getOrderLifecycleResponseParser, getReferralTotalsParser, getReferralTotalsResponseParser, getReferralsParser, getReferralsResponseParser, getSyncResponseParser, getTradeHistoryParser, getTradeHistoryResponseParser, getTradesParser, getTradesResponseParser, historyActionParser, historyPartialParser, historyTransactionParser, historyTransferParser, initializeCoins, initializeConstants, isInputInvalid, isSameToken, leaderboardParser, logStatus, loginAccountParamsV3Parser, marketOpenOrdersBalanceParser, marketParser, median, metalTransactionParser, ohlcvParser, openOrderParser, orderHistoryParser, orderLogParser, orderSide, orderType, otcOrderResponseParser, otcQuoteBodyParser, otcQuoteResponseParser, postSerializeOrderParser, postSerializeOrderResponseParser, postSubmitOrderParser, postSubmitOrderResponseParser, protonExplorerTxUrl, rateParser, receiptParser, refferalParser, refferalTotalParser, similarChains, singleActionTransactionParser, sortDirectionEnum, syncParser, traceParser, tradeLogParser, tradeParser, webauthActionParser, withdrawBodyBaseAction, withdrawBodyParser, withdrawHistoryMetalResponseParser, withdrawHistoryMetalSwapParser, withdrawMetalResponseParser, withdrawalFeeQuoteParser, wrapParser, wrapSetConfParser, wrapStartParser };
2278
+ // src/validation/address.ts
2279
+ /**
2280
+ * Withdrawal destination validation for the bridge (MFE-855).
2281
+ *
2282
+ * The address and memo end up inside the XPR transfer memo as
2283
+ * `coin:network:address[?memo=...]:fee`, which the bridge server splits on ':'.
2284
+ * So beyond per-chain format checks, these validators also reject characters
2285
+ * that would corrupt that structure.
2286
+ *
2287
+ * Result levels:
2288
+ * - 'full' : the chain's address format was fully checked, including its
2289
+ * checksum where the format has one. Formats without a checksum
2290
+ * (Solana keys, EOS names, Hedera ids, single-case EVM) cannot
2291
+ * catch every typo even at this level.
2292
+ * - 'basic' : only the shared safety rules (and partial format checks) ran,
2293
+ * because no complete validator exists for this chain/format.
2294
+ */
2295
+ const MAX_ADDRESS_LENGTH = 128;
2296
+ const MAX_DEFAULT_MEMO_BYTES = 256;
2297
+ const FULL = {
2298
+ valid: true,
2299
+ level: "full"
2300
+ };
2301
+ const BASIC = {
2302
+ valid: true,
2303
+ level: "basic"
2304
+ };
2305
+ const fail = (reason) => ({
2306
+ valid: false,
2307
+ reason
2308
+ });
2309
+ const PRINTABLE_ASCII = /^[\x21-\x7e]+$/;
2310
+ const sharedAddressChecks = (address) => {
2311
+ if (typeof address !== "string" || address.length === 0) return fail("Address is required.");
2312
+ if (address.length > 128) return fail("Address is too long.");
2313
+ if (!PRINTABLE_ASCII.test(address)) return fail("Address contains spaces or unsupported characters.");
2314
+ if (address.includes(":")) return fail("Address must not contain \":\". If you pasted a payment link, paste only the address.");
2315
+ if (address.includes("?")) return fail("Address must not contain \"?\". Use the memo field for memos or tags.");
2316
+ return null;
2317
+ };
2318
+ const tryDecode = (fn) => {
2319
+ try {
2320
+ return fn();
2321
+ } catch {
2322
+ return;
2323
+ }
2324
+ };
2325
+ const bytesEqual = (a, b) => a.length === b.length && a.every((v, i) => v === b[i]);
2326
+ const allZero = (a) => a.every((v) => v === 0);
2327
+ const UTXO_PARAMS = {
2328
+ ["Bitcoin"]: {
2329
+ name: "Bitcoin",
2330
+ segwitHrp: "bc",
2331
+ base58Versions: [0, 5]
2332
+ },
2333
+ ["Bitcoin (Testnet)"]: {
2334
+ name: "Bitcoin testnet",
2335
+ segwitHrp: "tb",
2336
+ base58Versions: [111, 196]
2337
+ },
2338
+ ["Litecoin"]: {
2339
+ name: "Litecoin",
2340
+ segwitHrp: "ltc",
2341
+ base58Versions: [
2342
+ 48,
2343
+ 50,
2344
+ 5
2345
+ ]
2346
+ },
2347
+ ["Litecoin (Testnet)"]: {
2348
+ name: "Litecoin testnet",
2349
+ segwitHrp: "tltc",
2350
+ base58Versions: [
2351
+ 111,
2352
+ 58,
2353
+ 196
2354
+ ]
2355
+ },
2356
+ ["Dogecoin"]: {
2357
+ name: "Dogecoin",
2358
+ base58Versions: [30, 22]
2359
+ }
2360
+ };
2361
+ const base58check = createBase58check(sha256);
2362
+ const BECH32_CHARSET = "qpzry9x8gf2tvdw0s3jn54khce6mua7l";
2363
+ const validateSegwit = (address, hrp, name) => {
2364
+ if (address.length > 90) return fail(`Not a valid ${name} address.`);
2365
+ const lower = address.toLowerCase();
2366
+ const version = BECH32_CHARSET.indexOf(lower.charAt(hrp.length + 1));
2367
+ if (version < 0) return fail(`Not a valid ${name} address.`);
2368
+ const decoded = tryDecode(() => (version === 0 ? bech32 : bech32m).decode(address, 90));
2369
+ if (!decoded || decoded.prefix !== hrp) return fail(`Not a valid ${name} address (checksum or network mismatch).`);
2370
+ const program = tryDecode(() => bech32.fromWords(decoded.words.slice(1)));
2371
+ if (!program) return fail(`Not a valid ${name} address.`);
2372
+ if (version === 0 && program.length !== 20 && program.length !== 32) return fail(`Not a valid ${name} address.`);
2373
+ if (version === 1 && program.length !== 32) return fail(`Not a valid ${name} address.`);
2374
+ if (version > 1) return fail(`Unsupported ${name} address version.`);
2375
+ return FULL;
2376
+ };
2377
+ const validateUtxo = (address, params) => {
2378
+ const { name, segwitHrp, base58Versions } = params;
2379
+ if (segwitHrp && address.toLowerCase().startsWith(`${segwitHrp}1`)) return validateSegwit(address, segwitHrp, name);
2380
+ const payload = tryDecode(() => base58check.decode(address));
2381
+ if (!payload || payload.length !== 21 || !base58Versions.includes(payload[0])) return fail(`Not a valid ${name} address (checksum or network mismatch).`);
2382
+ return FULL;
2383
+ };
2384
+ const EVM_CHAINS = /* @__PURE__ */ new Set([
2385
+ "Ethereum",
2386
+ "Ethereum (Sepolia)",
2387
+ "Binance (BEP20)",
2388
+ "Polygon",
2389
+ "Avalanche C-Chain",
2390
+ "Metal C-Chain",
2391
+ "Metal C-Chain (Testnet)",
2392
+ "Metal L2",
2393
+ "Metal L2 (Testnet)"
2394
+ ]);
2395
+ const toHex = (bytes) => Array.from(bytes, (b) => b.toString(16).padStart(2, "0")).join("");
2396
+ const validateHex40 = (hex) => {
2397
+ if (!/^[0-9a-fA-F]{40}$/.test(hex)) return fail("Address must be 0x followed by 40 hexadecimal characters.");
2398
+ if (/^0{40}$/.test(hex)) return fail("Withdrawing to the zero address would burn the funds.");
2399
+ const lower = hex.toLowerCase();
2400
+ if (hex === lower || hex === hex.toUpperCase()) return FULL;
2401
+ const hash = toHex(keccak_256(new TextEncoder().encode(lower)));
2402
+ for (let i = 0; i < 40; i++) {
2403
+ const c = hex[i];
2404
+ if (c >= "0" && c <= "9") continue;
2405
+ const shouldBeUpper = parseInt(hash[i], 16) >= 8;
2406
+ if (c === c.toUpperCase() !== shouldBeUpper) return fail("Address checksum (EIP-55 capitalization) is invalid. Check for a typo.");
2407
+ }
2408
+ return FULL;
2409
+ };
2410
+ const validateEvm = (address) => address.startsWith("0x") ? validateHex40(address.slice(2)) : fail("Address must start with 0x.");
2411
+ const validateXdc = (address) => {
2412
+ if (address.startsWith("xdc") || address.startsWith("0x")) return validateHex40(address.slice(address.startsWith("xdc") ? 3 : 2));
2413
+ return fail("Address must start with xdc or 0x.");
2414
+ };
2415
+ const crc16xmodem = (data) => {
2416
+ let crc = 0;
2417
+ for (const byte of data) {
2418
+ crc ^= byte << 8;
2419
+ for (let i = 0; i < 8; i++) crc = crc & 32768 ? (crc << 1 ^ 4129) & 65535 : crc << 1 & 65535;
2420
+ }
2421
+ return crc;
2422
+ };
2423
+ const STELLAR_KINDS = {
2424
+ G: {
2425
+ length: 56,
2426
+ version: 48,
2427
+ bytes: 35
2428
+ },
2429
+ M: {
2430
+ length: 69,
2431
+ version: 96,
2432
+ bytes: 43
2433
+ }
2434
+ };
2435
+ const validateStellar = (address) => {
2436
+ const kind = STELLAR_KINDS[address[0]];
2437
+ if (!kind || address.length !== kind.length || !/^[A-Z2-7]+$/.test(address)) return fail("Not a valid Stellar address (should start with G and be 56 characters).");
2438
+ const data = tryDecode(() => base32nopad.decode(address));
2439
+ if (!data || data.length !== kind.bytes || data[0] !== kind.version) return fail("Not a valid Stellar address.");
2440
+ const body = data.slice(0, -2);
2441
+ const crc = crc16xmodem(body);
2442
+ if (data[data.length - 2] !== (crc & 255) || data[data.length - 1] !== crc >> 8) return fail("Stellar address checksum is invalid. Check for a typo.");
2443
+ return FULL;
2444
+ };
2445
+ const XRP_BLACKHOLES = [/* @__PURE__ */ new Uint8Array(20), Uint8Array.from([.../* @__PURE__ */ new Uint8Array(19), 1])];
2446
+ const validateXrp = (address) => {
2447
+ if (address.startsWith("X") || address.startsWith("T")) return fail("X-addresses are not supported. Use the classic r-address and put the destination tag in the memo field.");
2448
+ if (!address.startsWith("r") || address.length < 25 || address.length > 35) return fail("Not a valid XRP address.");
2449
+ const data = tryDecode(() => base58xrp.decode(address));
2450
+ if (!data || data.length !== 25 || data[0] !== 0) return fail("Not a valid XRP address.");
2451
+ const checksum = sha256(sha256(data.slice(0, 21))).slice(0, 4);
2452
+ if (!bytesEqual(checksum, data.slice(21))) return fail("XRP address checksum is invalid. Check for a typo.");
2453
+ const accountId = data.slice(1, 21);
2454
+ if (XRP_BLACKHOLES.some((b) => bytesEqual(b, accountId))) return fail("This XRP address is unspendable (black hole).");
2455
+ return FULL;
2456
+ };
2457
+ const validateSolana = (address) => {
2458
+ if (address.length < 32 || address.length > 44) return fail("Not a valid Solana address.");
2459
+ const key = tryDecode(() => base58.decode(address));
2460
+ if (!key || key.length !== 32) return fail("Not a valid Solana address.");
2461
+ if (allZero(key)) return fail("This is the Solana system program, not a wallet address.");
2462
+ return FULL;
2463
+ };
2464
+ const validateCardano = (address) => {
2465
+ const lower = address.toLowerCase();
2466
+ if (lower.startsWith("addr_test1")) return fail("This is a Cardano testnet address.");
2467
+ if (lower.startsWith("stake")) return fail("This is a Cardano stake (reward) address, not a payment address.");
2468
+ if (lower.startsWith("addr1")) {
2469
+ const decoded = tryDecode(() => bech32.decode(address, 1023));
2470
+ const data = decoded && tryDecode(() => bech32.fromWords(decoded.words));
2471
+ if (!decoded || decoded.prefix !== "addr" || !data || data.length === 0) return fail("Not a valid Cardano address (checksum failed). Check for a typo.");
2472
+ const type = data[0] >> 4;
2473
+ if ((data[0] & 15) !== 1) return fail("This Cardano address is not for mainnet.");
2474
+ return (type <= 3 ? data.length === 57 : type <= 5 ? data.length > 29 : type <= 7 ? data.length === 29 : false) ? FULL : fail("Not a valid Cardano payment address.");
2475
+ }
2476
+ if ((address.startsWith("Ae2") || address.startsWith("DdzFF")) && tryDecode(() => base58.decode(address))) return BASIC;
2477
+ return fail("Not a valid Cardano address.");
2478
+ };
2479
+ const validateEos = (address) => /^[a-z1-5.]{1,12}$/.test(address) && !address.endsWith(".") ? FULL : fail("EOS account names are 1-12 characters: a-z, 1-5 and \".\" (not at the end).");
2480
+ const validateHedera = (address) => {
2481
+ const match = /^0\.0\.(\d{1,19})(-[a-z]{5})?$/.exec(address);
2482
+ if (!match) return fail("Hedera account ids look like 0.0.12345.");
2483
+ return match[2] ? BASIC : FULL;
2484
+ };
2485
+ const VALIDATORS = {
2486
+ ["XDC Network"]: validateXdc,
2487
+ ["Stellar"]: validateStellar,
2488
+ ["Stellar (Testnet)"]: validateStellar,
2489
+ ["Ripple"]: validateXrp,
2490
+ ["Solana"]: validateSolana,
2491
+ ["Cardano"]: validateCardano,
2492
+ ["EOS"]: validateEos,
2493
+ ["EOS (Jungle Testnet)"]: validateEos,
2494
+ ["Hedera (Mainnet)"]: validateHedera,
2495
+ ["Hedera (Testnet)"]: validateHedera
2496
+ };
2497
+ /**
2498
+ * Validate a withdrawal destination address for the given bridge chain
2499
+ * (a `Chains` enum value, i.e. `wallet.chain`). Expects already-trimmed input.
2500
+ * Pure and synchronous, so it is safe to call on every keystroke.
2501
+ */
2502
+ const validateWithdrawAddress = (chain, address) => {
2503
+ const sharedFailure = sharedAddressChecks(address);
2504
+ if (sharedFailure) return sharedFailure;
2505
+ const utxo = UTXO_PARAMS[chain];
2506
+ if (utxo) return validateUtxo(address, utxo);
2507
+ if (EVM_CHAINS.has(chain)) return validateEvm(address);
2508
+ const validator = VALIDATORS[chain];
2509
+ return validator ? validator(address) : BASIC;
2510
+ };
2511
+ const MEMO_BYTE_LIMITS = {
2512
+ ["Stellar"]: 28,
2513
+ ["Stellar (Testnet)"]: 28,
2514
+ ["Hedera (Mainnet)"]: 100,
2515
+ ["Hedera (Testnet)"]: 100
2516
+ };
2517
+ /**
2518
+ * Validate an optional withdrawal memo / destination tag. The memo is appended
2519
+ * to the address as `?memo=...` inside the ':'-delimited bridge memo, so
2520
+ * delimiter and URL-structure characters are rejected on every chain.
2521
+ */
2522
+ const validateWithdrawMemo = (chain, memo) => {
2523
+ if (memo === void 0 || memo === "") return { valid: true };
2524
+ if (typeof memo !== "string") return fail("Memo must be text.");
2525
+ if (/\p{Cc}/u.test(memo)) return fail("Memo contains unsupported characters.");
2526
+ if (/[:?&#=]/.test(memo)) return fail("Memo must not contain : ? & # or =.");
2527
+ if (chain === "Ripple") return /^\d{1,10}$/.test(memo) && Number(memo) <= 4294967295 ? { valid: true } : fail("XRP destination tags must be a whole number between 0 and 4294967295.");
2528
+ const limit = MEMO_BYTE_LIMITS[chain] ?? MAX_DEFAULT_MEMO_BYTES;
2529
+ if (new TextEncoder().encode(memo).length > limit) return fail(`Memo must be at most ${limit} bytes.`);
2530
+ return { valid: true };
2531
+ };
2532
+ // src/validation/withdrawError.ts
2533
+ /**
2534
+ * Turn a failed bridge withdrawal request into a message that is safe to show
2535
+ * to the user (MFE-855).
2536
+ *
2537
+ * Only 4xx responses are treated as user-facing: they describe a problem with
2538
+ * the request (for example an invalid address passed through from Proton Swap).
2539
+ * 5xx responses, network failures and anything unrecognised get a generic
2540
+ * message, so server internals such as stack traces are never displayed and
2541
+ * the raw "AxiosError: Request failed with status code 500" is never shown.
2542
+ *
2543
+ * Duck-typed so it has no axios dependency.
2544
+ */
2545
+ const GENERIC_WITHDRAW_ERROR = "Withdrawal failed. Please check the amount and address and try again.";
2546
+ const MAX_WITHDRAW_ERROR_LENGTH = 200;
2547
+ const sanitize = (raw) => {
2548
+ const cleaned = raw.replace(/[\p{Cc}\s]+/gu, " ").trim();
2549
+ return cleaned.length > 200 ? `${cleaned.slice(0, 199)}…` : cleaned;
2550
+ };
2551
+ const messageFromBody = (data) => {
2552
+ if (typeof data === "string") return data;
2553
+ if (data && typeof data === "object") {
2554
+ const { message, error } = data;
2555
+ if (typeof message === "string") return message;
2556
+ if (typeof error === "string") return error;
2557
+ }
2558
+ };
2559
+ const withdrawErrorMessage = (error) => {
2560
+ const response = error?.response;
2561
+ const status = response?.status;
2562
+ if (typeof status !== "number" || status < 400 || status >= 500) return GENERIC_WITHDRAW_ERROR;
2563
+ const raw = messageFromBody(response?.data);
2564
+ return (raw === void 0 ? "" : sanitize(raw)) || "Withdrawal failed. Please check the amount and address and try again.";
2565
+ };
2566
+ export { Chains, ExtendedAssetParser, FEE_FACTOR, GENERIC_WITHDRAW_ERROR, Graph, ILink, INode, ITopics, MAX_ADDRESS_LENGTH, MAX_WITHDRAW_ERROR_LENGTH, Networks, Providers, Status, TOPICS, Type, WRAP_AUTH_ACTION, WRAP_CONTRACT, accountParser, accountRamDeltaParser, actParser, actionTraceParser, addressParser, applyDiscountToFee, authorizationParser, authorizationsParser, baseActionParser, baseActionSerializedParser, bloksRatesParser, chronicleTransactionParser, compute_liquidity, compute_transfer, compute_transfer_curve, createAbstractAuthParser, dailyParser, dbHistoryTransactionParser, dbMarketParser, depositBodyParser, depthParser, exchangeRateParser, exchangeRatesParser, explorerLinkByChain, fillType, generateAddressBodyParser, generateAddressResponse, generateAuthActionsBaseParser, generateAuthParser, getDailyResponseParser, getDepthParser, getDepthResponseParser, getHistoryActionsParser, getHistoryActionsResponseParser, getHistoryTransactionResponseParser, getHistoryTransfersParser, getHistoryTransfersResponseParser, getLeaderboardParser, getLeaderboardResponseParser, getMarketsResponseParser, getOhlcvParser, getOhlcvResponseParser, getOpenOrdersBalancesResponseParser, getOpenOrdersParser, getOpenOrdersResponseParser, getOrderHistoryParser, getOrderHistoryResponseParser, getOrderLifecycleParser, getOrderLifecycleResponseParser, getReferralTotalsParser, getReferralTotalsResponseParser, getReferralsParser, getReferralsResponseParser, getSyncResponseParser, getTradeHistoryParser, getTradeHistoryResponseParser, getTradesParser, getTradesResponseParser, historyActionParser, historyPartialParser, historyTransactionParser, historyTransferParser, initializeCoins, initializeConstants, isInputInvalid, isSameToken, leaderboardParser, logStatus, loginAccountParamsV3Parser, marketOpenOrdersBalanceParser, marketParser, median, metalTransactionParser, ohlcvParser, openOrderParser, orderHistoryParser, orderLogParser, orderSide, orderType, otcOrderResponseParser, otcQuoteBodyParser, otcQuoteResponseParser, postSerializeOrderParser, postSerializeOrderResponseParser, postSubmitOrderParser, postSubmitOrderResponseParser, protonExplorerTxUrl, rateParser, receiptParser, refferalParser, refferalTotalParser, similarChains, singleActionTransactionParser, sortDirectionEnum, syncParser, traceParser, tradeLogParser, tradeParser, validateWithdrawAddress, validateWithdrawMemo, webauthActionParser, withdrawBodyBaseAction, withdrawBodyParser, withdrawErrorMessage, withdrawHistoryMetalResponseParser, withdrawHistoryMetalSwapParser, withdrawMetalResponseParser, withdrawalFeeQuoteParser, wrapParser, wrapSetConfParser, wrapStartParser };
package/package.json CHANGED
@@ -1,7 +1,8 @@
1
1
  {
2
2
  "name": "@proton/wrap-constants",
3
+ "description": "Shared constants, coin metadata, and API parsers for MetalX",
3
4
  "author": "Metallicus",
4
- "module": "dist/index.mjs",
5
+ "module": "./dist/index.mjs",
5
6
  "type": "module",
6
7
  "exports": {
7
8
  ".": {
@@ -18,29 +19,32 @@
18
19
  "publishConfig": {
19
20
  "access": "public"
20
21
  },
21
- "version": "0.3.0",
22
+ "version": "0.3.1",
22
23
  "license": "MIT",
23
- "main": "dist/index.cjs",
24
- "typings": "dist/index.d.cts",
24
+ "main": "./dist/index.cjs",
25
+ "typings": "./dist/index.d.cts",
25
26
  "files": [
26
- "dist",
27
- "src"
27
+ "dist"
28
28
  ],
29
29
  "engines": {
30
30
  "node": ">=20"
31
31
  },
32
32
  "devDependencies": {
33
- "@bloks/numbers": "^27.0.0",
34
33
  "@eslint/js": "^10.0.1",
35
34
  "@size-limit/preset-small-lib": "^14.3.0",
36
35
  "@types/lodash.clonedeep": "^4.5.6",
36
+ "@vitest/coverage-v8": "^5.1.0",
37
37
  "eslint": "^10.11.0",
38
38
  "size-limit": "^14.3.0",
39
39
  "tsdown": "0.22.14",
40
40
  "typescript": "~6.0.0",
41
- "typescript-eslint": "^8.70.1"
41
+ "typescript-eslint": "^8.70.1",
42
+ "vitest": "^5.1.0"
42
43
  },
43
44
  "dependencies": {
45
+ "@bloks/numbers": "^27.0.0",
46
+ "@noble/hashes": "^1.8.0",
47
+ "@scure/base": "^1.2.6",
44
48
  "bignumber.js": "^9.3.1",
45
49
  "lodash.clonedeep": "^4.5.0",
46
50
  "zod": "^4.6.7"
@@ -50,6 +54,8 @@
50
54
  "dev": "tsdown src/index.ts --watch",
51
55
  "size": "size-limit",
52
56
  "analyze": "size-limit --why",
53
- "lint": "eslint ."
57
+ "lint": "eslint .",
58
+ "test": "vitest run",
59
+ "test:coverage": "vitest run --coverage"
54
60
  }
55
61
  }