@droplinked_inc/web3-kit 0.1.0

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/CHANGELOG.md ADDED
@@ -0,0 +1,20 @@
1
+ # @droplinked_inc/web3-kit
2
+
3
+ ## 0.1.0
4
+
5
+ ### Minor Changes
6
+
7
+ - 562318e: Initial rebuild from `droplinked-web3-kit`. Hostile-namespace replacement under the supply-chain rebuild sprint.
8
+
9
+ ### Patch Changes
10
+
11
+ - Updated dependencies [562318e]
12
+ - @droplinked_inc/wallet-connection@0.1.1
13
+
14
+ Managed by [Changesets](https://github.com/changesets/changesets) — entries
15
+ added via `pnpm changeset` are accumulated here on each `pnpm version`.
16
+
17
+ ## Unreleased
18
+
19
+ - Initial rebuild from `droplinked-web3-kit@1.0.13`. See
20
+ [`THREAT_MODEL.md`](./THREAT_MODEL.md) for the security delta.
package/LICENSE ADDED
@@ -0,0 +1,21 @@
1
+ MIT License
2
+
3
+ Copyright (c) 2026 Droplinked Inc.
4
+
5
+ Permission is hereby granted, free of charge, to any person obtaining a copy
6
+ of this software and associated documentation files (the "Software"), to deal
7
+ in the Software without restriction, including without limitation the rights
8
+ to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
9
+ copies of the Software, and to permit persons to whom the Software is
10
+ furnished to do so, subject to the following conditions:
11
+
12
+ The above copyright notice and this permission notice shall be included in all
13
+ copies or substantial portions of the Software.
14
+
15
+ THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
16
+ IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
17
+ FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
18
+ AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
19
+ LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
20
+ OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
21
+ SOFTWARE.
package/README.md ADDED
@@ -0,0 +1,123 @@
1
+ # @droplinked_inc/web3-kit
2
+
3
+ Higher-level web3 building blocks for the Droplinked frontend stack: a
4
+ frozen chain registry, a hard-coded known-token registry, decimal-safe
5
+ display formatters, and viem-backed transaction builders.
6
+
7
+ This is a hardened rebuild of the original `droplinked-web3-kit@1.0.13`.
8
+ See [`THREAT_MODEL.md`](./THREAT_MODEL.md) for the security deltas.
9
+
10
+ ## Status
11
+
12
+ Initial rebuild. Stable surface; coverage 96.81% statements / 96.87% branches.
13
+
14
+ ## Install
15
+
16
+ ```bash
17
+ pnpm add @droplinked_inc/web3-kit @droplinked_inc/wallet-connection
18
+ ```
19
+
20
+ `@droplinked_inc/wallet-connection` is a **peer dependency** — this kit
21
+ does not reimplement EIP-1193 provider discovery, EIP-712 login signing,
22
+ session storage, or wallet-protocol logic. Those primitives live in
23
+ `@droplinked_inc/wallet-connection`.
24
+
25
+ ## Modules
26
+
27
+ | Module | Surface |
28
+ | ------------- | ----------------------------------------------------------------------------- |
29
+ | `chains` | `chains`, `getChainByName`, `getChainDetails`, `isRpcUrlAllowed`, `getTransactionLink` |
30
+ | `tokens` | `TOKEN_REGISTRY`, `getTokenInfo`, `hasToken`, `listTokens`, `assertCanonicalTokenAddress` |
31
+ | `format` | `formatAmount`, `parseAmount`, `formatEther`, `parseEther`, `shortenAddress`, `escapeTokenDisplay`, `formatBalance` |
32
+ | `tx-builder` | `buildErc20Transfer`, `buildErc20Approve`, `buildNativeTransfer`, `buildEip712Domain` |
33
+ | `types` | `Chains`, `Network`, `Groups`, `PaymentTokens`, zod schemas |
34
+ | `errors` | typed exception hierarchy mirroring v1.0.13 |
35
+
36
+ ## Quick examples
37
+
38
+ ### Look up a chain and a token
39
+
40
+ ```ts
41
+ import { Chains, Network, getChainDetails, getTokenInfo } from '@droplinked_inc/web3-kit';
42
+
43
+ const polygon = getChainDetails(Chains.POLYGON, Network.MAINNET);
44
+ // polygon.chainIdNumber === 137
45
+
46
+ const usdc = getTokenInfo(Chains.POLYGON, Network.MAINNET, 'USDC');
47
+ // usdc.address === '0x3c499c542cef5e3811e1192ce70d8cc03d5c3359'
48
+ ```
49
+
50
+ ### Reject a spoofed token address
51
+
52
+ ```ts
53
+ import { assertCanonicalTokenAddress, TokenAddressMismatchError } from '@droplinked_inc/web3-kit';
54
+
55
+ try {
56
+ assertCanonicalTokenAddress({
57
+ chain: Chains.POLYGON,
58
+ network: Network.MAINNET,
59
+ symbol: 'USDC',
60
+ claimedAddress: backendResponse.usdcAddress, // attacker-controlled
61
+ });
62
+ } catch (err) {
63
+ if (err instanceof TokenAddressMismatchError) {
64
+ // refuse the checkout — your backend lied about what address USDC has
65
+ }
66
+ }
67
+ ```
68
+
69
+ ### Build an ERC-20 transfer
70
+
71
+ ```ts
72
+ import { buildErc20Transfer, parseAmount } from '@droplinked_inc/web3-kit';
73
+
74
+ const call = buildErc20Transfer({
75
+ chain: Chains.POLYGON,
76
+ network: Network.MAINNET,
77
+ tokenSymbol: 'USDC',
78
+ to: recipient,
79
+ amount: parseAmount('1.5', 6),
80
+ });
81
+ // call.to === USDC contract on Polygon
82
+ // call.data === 0x...a9059cbb… (transfer(address,uint256))
83
+ // call.value === 0n
84
+ // call.chainIdNumber === 137
85
+ ```
86
+
87
+ Then hand `call` to a wallet connector from `@droplinked_inc/wallet-connection`
88
+ to actually broadcast it.
89
+
90
+ ### Format balances safely
91
+
92
+ ```ts
93
+ import { formatBalance, escapeTokenDisplay } from '@droplinked_inc/web3-kit';
94
+
95
+ formatBalance({ value: 1_999_999n, decimals: 6, displayDecimals: 2 });
96
+ // "1.99" — truncated, never rounded up. The user can't be shown more than they hold.
97
+
98
+ escapeTokenDisplay(maliciousTokenName);
99
+ // HTML-escaped, control-char-stripped, bidi-override-stripped, length-capped.
100
+ ```
101
+
102
+ ## Security model
103
+
104
+ See [`THREAT_MODEL.md`](./THREAT_MODEL.md). Highlights:
105
+
106
+ - The chain registry and the known-token registry are **frozen at package
107
+ build time**. No remote fetches, no backend-driven addresses.
108
+ - Caller-supplied token addresses for known symbols (USDC, USDT, DAI,
109
+ WETH, USDS) are **validated against the registry** — passing any other
110
+ address as e.g. "USDC" throws `TokenAddressMismatchError`.
111
+ - All amounts are `bigint`. `parseAmount` rejects inputs with more
112
+ fractional digits than the token allows, preventing silent truncation
113
+ that could let a UI display "1.234 USDC" but submit "1.23 USDC".
114
+ - `escapeTokenDisplay` strips ASCII control chars, Unicode bidi-override
115
+ chars, and HTML metachars before any on-chain string reaches a UI.
116
+ - RPC URLs passed by callers are validated against the registry via
117
+ `isRpcUrlAllowed` (https-only, exact host+path match).
118
+ - Transaction-hash inputs to `getTransactionLink` are regex-validated
119
+ to block `javascript:` open-redirect payloads.
120
+
121
+ ## License
122
+
123
+ MIT — see [`LICENSE`](../../LICENSE).
@@ -0,0 +1,139 @@
1
+ # Threat Model — @droplinked_inc/web3-kit
2
+
3
+ This document captures the security boundaries and hardening deltas of
4
+ `@droplinked_inc/web3-kit` relative to the legacy `droplinked-web3-kit@1.0.13`
5
+ which it replaces.
6
+
7
+ Companion document: `@droplinked_inc/wallet-connection`'s `THREAT_MODEL.md`.
8
+ This kit composes on top of that package for wallet-protocol primitives.
9
+
10
+ ## 1. Trust boundaries
11
+
12
+ | Boundary | Direction | Trust posture |
13
+ | --- | --- | --- |
14
+ | Caller → kit (chain id, network, symbol) | inbound | **untrusted** — zod / registry validated |
15
+ | Caller → kit (token address claim) | inbound | **untrusted** — compared against frozen registry |
16
+ | Caller → kit (RPC URL) | inbound | **untrusted** — `isRpcUrlAllowed` |
17
+ | Caller → kit (amount as string/number) | inbound | **untrusted** — `Uint256Schema`, no truncation |
18
+ | Kit → caller (canonical address, ABI-data) | outbound | **trusted** — sourced from the frozen tables |
19
+ | Kit ↔ network | none | this package never opens a socket |
20
+
21
+ The kit has **no I/O of its own** — it neither contacts the network
22
+ nor reads `localStorage`. All side effects belong to the consumer.
23
+
24
+ ## 2. Threats addressed
25
+
26
+ ### 2.1 Backend-controlled chain metadata
27
+ The original v1.0.13 fetched chain metadata and contract addresses
28
+ from `apiv3dev.droplinked.com/storage/...` at runtime. Any compromise
29
+ of that endpoint — or a DNS hijack, or a MITM during checkout — could
30
+ redirect every customer payment to an attacker address.
31
+
32
+ **Mitigation:** chain registry is a frozen, package-baked table
33
+ (`chains.ts`). Adding a new chain is a code-review + npm publish, not
34
+ a backend mutation. Unknown chain identifiers throw
35
+ `ChainNotImplementedException`.
36
+
37
+ ### 2.2 Token-address impersonation ("look-alike USDC drainer")
38
+ A malicious backend / cart payload says "the user is paying in USDC,
39
+ the USDC contract address is `0xDEAD…`". The wallet signs an ERC-20
40
+ transfer to that address — funds drained.
41
+
42
+ **Mitigation:** known token contracts (USDC, USDT, USDS, DAI, WETH)
43
+ are hard-coded per (chain, network) in `tokens.ts`.
44
+ `assertCanonicalTokenAddress` rejects any caller-supplied address that
45
+ does not match the registry value (case-insensitive). The check runs
46
+ inside every `tx-builder` entry point — there is no way to construct
47
+ an ERC-20 transfer to a non-canonical address for a known symbol.
48
+
49
+ ### 2.3 Display-string XSS / homoglyph spoofing
50
+ v1.0.13 returned raw on-chain `name` / `symbol` strings. A malicious
51
+ ERC-20 with `name = "<img src=x onerror=alert(1)>"` would crash or
52
+ hijack any consumer using `dangerouslySetInnerHTML`. RLO-flipped
53
+ "USDC" (i.e. "U‮CDSU") could spoof the legit token in a list.
54
+
55
+ **Mitigation:** `escapeTokenDisplay` strips ASCII control chars
56
+ (0x00–0x1F + 0x7F) by replacing them with spaces, strips Unicode
57
+ bidi-override / invisible chars (RLM, LRM, LRE-RLO, LRI-FSI-PDI, BOM),
58
+ collapses whitespace, HTML-escapes `&<>"'/\`=` for both element-body
59
+ and unquoted-attribute contexts, and truncates at 64 characters.
60
+ Property-test asserts the output never contains a raw `<` or `>`.
61
+
62
+ ### 2.4 Silent decimal truncation
63
+ A UI shows the user "1.23456 USDC" but submits "1.23 USDC" on-chain
64
+ because `parseFloat → toFixed(decimals)` rounded down.
65
+
66
+ **Mitigation:** `parseAmount` rejects inputs whose fractional part is
67
+ longer than `decimals`. There is no rounding in the parse path at all
68
+ — either the amount is representable exactly, or you get a
69
+ `RangeError`.
70
+
71
+ ### 2.5 Wrong-chain submission via user-controlled chainId
72
+ A consumer that builds typed-data with `domain.chainId = req.body.chainId`
73
+ can be tricked into signing a cross-chain replayable payload.
74
+
75
+ **Mitigation:** `buildEip712Domain` derives `chainId` from the (chain,
76
+ network) pair, looked up in the frozen registry. Callers cannot
77
+ inject a chainId. The same applies to every `tx-builder` helper:
78
+ `chainIdNumber` is on the returned `BuiltCall` and is always the
79
+ registry value.
80
+
81
+ ### 2.6 Open-redirect via transaction-hash field
82
+ `getTransactionLink` was previously string-concat — a caller passing
83
+ `javascript:alert(1)` as `transactionHash` would produce a malicious
84
+ href.
85
+
86
+ **Mitigation:** transaction hashes must match `/^0x[a-fA-F0-9]{64}$/`.
87
+ Anything else throws `InvalidParametersException`.
88
+
89
+ ### 2.7 SSRF via attacker-controlled RPC URL
90
+ A consumer that takes an RPC URL from URL params and uses it to add a
91
+ chain to MetaMask lets the attacker pin the user to a malicious RPC
92
+ (seeing every signed tx pre-broadcast, faking balances, censoring).
93
+
94
+ **Mitigation:** `isRpcUrlAllowed` requires `https:`, exact host match,
95
+ exact pathname match against the frozen registry. The check is
96
+ provided as a public helper; consumers should always gate user-typed
97
+ RPC URLs through it.
98
+
99
+ ### 2.8 Mutation of the registry post-import
100
+ A previously-imported registry object could in principle be mutated
101
+ by some other module loaded later in the bundle.
102
+
103
+ **Mitigation:** every registry table is `Object.freeze`d at module
104
+ load time. Mutation throws in strict mode (which the package builds
105
+ under). Frozen-ness is asserted in unit tests.
106
+
107
+ ## 3. Threats not addressed (out of scope)
108
+
109
+ - **Off-chain price oracle manipulation.** Token-USD conversions are
110
+ the consumer's responsibility; this kit only emits raw `bigint`
111
+ amounts.
112
+ - **Wallet-protocol issues** (EIP-1193 race conditions, account
113
+ switching). Owned by `@droplinked_inc/wallet-connection`.
114
+ - **Solana / XION / Unstoppable Domains tokens.** Only EVM tokens are
115
+ in the canonical registry today. Solana SPL token verification is
116
+ planned for a follow-up.
117
+ - **ENS / homoglyph attacks on resolved names.** `shortenAddress`
118
+ only accepts raw 20-byte hex; ENS-resolved labels remain the UI's
119
+ responsibility.
120
+
121
+ ## 4. Cryptographic dependencies
122
+
123
+ - `viem@^2.21` — ABI encoding and `formatUnits`/`parseUnits`. Pure
124
+ JS, no native dependencies.
125
+ - `zod@^3.23` — schema validation. No regex back-references; all
126
+ patterns are linear-time.
127
+
128
+ No `ethers`, no `web3.js` — both were direct dependencies of v1.0.13
129
+ and brought a much larger attack surface (custom keccak / RLP /
130
+ secp256k1 reimplementations).
131
+
132
+ ## 5. Coverage gates
133
+
134
+ - statements 96.8%
135
+ - branches 96.9%
136
+ - functions 100%
137
+ - lines 99.1%
138
+
139
+ A drop below 85% on any axis fails CI.
@@ -0,0 +1,67 @@
1
+ /**
2
+ * Hard-coded chain registry.
3
+ *
4
+ * The original droplinked-web3-kit@1.0.13 fetched chain metadata and
5
+ * contract addresses from `apiv3dev.droplinked.com/storage/...` at
6
+ * runtime. Any compromise of that endpoint — or a DNS hijack, or a
7
+ * MITM during the customer's checkout — could redirect every payment
8
+ * to an attacker-controlled address. We replace this with a frozen,
9
+ * package-baked registry. Adding a new chain is a code-review + npm
10
+ * publish, not a backend mutation.
11
+ *
12
+ * Caller-supplied chain identifiers are validated against this table
13
+ * via `getChainByName` / `requireChain`; unknown values throw
14
+ * `ChainNotImplementedException` rather than being silently passed
15
+ * through.
16
+ */
17
+ import { Chains, Groups, Network } from './types.js';
18
+ export interface ChainNativeCurrency {
19
+ readonly name: string;
20
+ readonly decimals: number;
21
+ readonly symbol: string;
22
+ }
23
+ export interface ChainDetails {
24
+ readonly chainName: string;
25
+ /** 0x-prefixed hex chain id, e.g. "0x89". */
26
+ readonly chainIdHex: `0x${string}`;
27
+ /** Decimal chain id for EIP-712 / viem. */
28
+ readonly chainIdNumber: number;
29
+ readonly nativeCurrency: ChainNativeCurrency;
30
+ /** Hard-coded vetted public RPCs. Validated by `isRpcUrlAllowed`. */
31
+ readonly rpcUrls: readonly string[];
32
+ /** Canonical block-explorer base (no trailing slash). */
33
+ readonly blockExplorerUrl: string;
34
+ }
35
+ export interface Chain {
36
+ readonly name: Chains;
37
+ readonly group: Groups;
38
+ readonly [Network.TESTNET]?: ChainDetails;
39
+ readonly [Network.MAINNET]?: ChainDetails;
40
+ }
41
+ /**
42
+ * Snapshot of the registry as an array — matches the v1.0.13 `chains`
43
+ * named export so consumers iterating over it keep working.
44
+ */
45
+ export declare const chains: readonly Chain[];
46
+ export declare function getChainByName(chainName: Chains): Chain;
47
+ export declare function getGroupOfChain(chainName: Chains): Groups;
48
+ export declare function isChainInGroup(chainName: Chains, group: Groups): boolean;
49
+ export declare function getChainsByGroup(group: Groups): readonly Chain[];
50
+ export declare function getChainDetails(chain: Chains, network: Network): ChainDetails;
51
+ /**
52
+ * Validates that the caller-supplied RPC URL appears in the registry
53
+ * for the (chain, network) pair. Used by anything that issues a JSON-RPC
54
+ * request — never trust a user-supplied RPC URL.
55
+ */
56
+ export declare function isRpcUrlAllowed(chain: Chains, network: Network, rpcUrl: string): boolean;
57
+ /**
58
+ * Builds a block-explorer URL for a transaction hash. The hash is
59
+ * regex-validated to defend against `javascript:` / open-redirect
60
+ * payloads sneaking into a UI that renders the result as a link.
61
+ */
62
+ export declare function getTransactionLink(args: {
63
+ chain: Chains;
64
+ network: Network;
65
+ transactionHash: string;
66
+ }): string;
67
+ //# sourceMappingURL=chains.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"chains.d.ts","sourceRoot":"","sources":["../src/chains.ts"],"names":[],"mappings":"AAAA;;;;;;;;;;;;;;;GAeG;AACH,OAAO,EAAE,MAAM,EAAE,MAAM,EAAE,OAAO,EAAE,MAAM,YAAY,CAAC;AAIrD,MAAM,WAAW,mBAAmB;IAClC,QAAQ,CAAC,IAAI,EAAE,MAAM,CAAC;IACtB,QAAQ,CAAC,QAAQ,EAAE,MAAM,CAAC;IAC1B,QAAQ,CAAC,MAAM,EAAE,MAAM,CAAC;CACzB;AAED,MAAM,WAAW,YAAY;IAC3B,QAAQ,CAAC,SAAS,EAAE,MAAM,CAAC;IAC3B,6CAA6C;IAC7C,QAAQ,CAAC,UAAU,EAAE,KAAK,MAAM,EAAE,CAAC;IACnC,2CAA2C;IAC3C,QAAQ,CAAC,aAAa,EAAE,MAAM,CAAC;IAC/B,QAAQ,CAAC,cAAc,EAAE,mBAAmB,CAAC;IAC7C,qEAAqE;IACrE,QAAQ,CAAC,OAAO,EAAE,SAAS,MAAM,EAAE,CAAC;IACpC,yDAAyD;IACzD,QAAQ,CAAC,gBAAgB,EAAE,MAAM,CAAC;CACnC;AAED,MAAM,WAAW,KAAK;IACpB,QAAQ,CAAC,IAAI,EAAE,MAAM,CAAC;IACtB,QAAQ,CAAC,KAAK,EAAE,MAAM,CAAC;IACvB,QAAQ,CAAC,CAAC,OAAO,CAAC,OAAO,CAAC,CAAC,EAAE,YAAY,CAAC;IAC1C,QAAQ,CAAC,CAAC,OAAO,CAAC,OAAO,CAAC,CAAC,EAAE,YAAY,CAAC;CAC3C;AAwND;;;GAGG;AACH,eAAO,MAAM,MAAM,EAAE,SAAS,KAAK,EAA4C,CAAC;AAEhF,wBAAgB,cAAc,CAAC,SAAS,EAAE,MAAM,GAAG,KAAK,CAMvD;AAED,wBAAgB,eAAe,CAAC,SAAS,EAAE,MAAM,GAAG,MAAM,CAEzD;AAED,wBAAgB,cAAc,CAAC,SAAS,EAAE,MAAM,EAAE,KAAK,EAAE,MAAM,GAAG,OAAO,CAExE;AAED,wBAAgB,gBAAgB,CAAC,KAAK,EAAE,MAAM,GAAG,SAAS,KAAK,EAAE,CAEhE;AAED,wBAAgB,eAAe,CAAC,KAAK,EAAE,MAAM,EAAE,OAAO,EAAE,OAAO,GAAG,YAAY,CAO7E;AAED;;;;GAIG;AACH,wBAAgB,eAAe,CAC7B,KAAK,EAAE,MAAM,EACb,OAAO,EAAE,OAAO,EAChB,MAAM,EAAE,MAAM,GACb,OAAO,CAsBT;AAED;;;;GAIG;AACH,wBAAgB,kBAAkB,CAAC,IAAI,EAAE;IACvC,KAAK,EAAE,MAAM,CAAC;IACd,OAAO,EAAE,OAAO,CAAC;IACjB,eAAe,EAAE,MAAM,CAAC;CACzB,GAAG,MAAM,CAST"}
package/dist/chains.js ADDED
@@ -0,0 +1,302 @@
1
+ /**
2
+ * Hard-coded chain registry.
3
+ *
4
+ * The original droplinked-web3-kit@1.0.13 fetched chain metadata and
5
+ * contract addresses from `apiv3dev.droplinked.com/storage/...` at
6
+ * runtime. Any compromise of that endpoint — or a DNS hijack, or a
7
+ * MITM during the customer's checkout — could redirect every payment
8
+ * to an attacker-controlled address. We replace this with a frozen,
9
+ * package-baked registry. Adding a new chain is a code-review + npm
10
+ * publish, not a backend mutation.
11
+ *
12
+ * Caller-supplied chain identifiers are validated against this table
13
+ * via `getChainByName` / `requireChain`; unknown values throw
14
+ * `ChainNotImplementedException` rather than being silently passed
15
+ * through.
16
+ */
17
+ import { Chains, Groups, Network } from './types.js';
18
+ import { ChainNotImplementedException, InvalidParametersException } from './errors.js';
19
+ import { deepFreeze } from './deep-freeze.js';
20
+ /**
21
+ * The canonical registry. All values are `readonly` deeply — mutating
22
+ * an entry in a consumer would be a TypeScript error and at runtime
23
+ * the object is frozen via `Object.freeze` on import.
24
+ */
25
+ const CHAINS_TABLE = deepFreeze({
26
+ [Chains.ETH]: {
27
+ name: Chains.ETH,
28
+ group: Groups.EVM,
29
+ [Network.MAINNET]: {
30
+ chainName: 'Ethereum',
31
+ chainIdHex: '0x1',
32
+ chainIdNumber: 1,
33
+ nativeCurrency: { name: 'Ether', decimals: 18, symbol: 'ETH' },
34
+ rpcUrls: ['https://eth.llamarpc.com'],
35
+ blockExplorerUrl: 'https://etherscan.io',
36
+ },
37
+ [Network.TESTNET]: {
38
+ chainName: 'Sepolia',
39
+ chainIdHex: '0xaa36a7',
40
+ chainIdNumber: 11155111,
41
+ nativeCurrency: { name: 'Sepolia Ether', decimals: 18, symbol: 'ETH' },
42
+ rpcUrls: ['https://eth-sepolia.public.blastapi.io/'],
43
+ blockExplorerUrl: 'https://sepolia.etherscan.io',
44
+ },
45
+ },
46
+ [Chains.POLYGON]: {
47
+ name: Chains.POLYGON,
48
+ group: Groups.EVM,
49
+ [Network.MAINNET]: {
50
+ chainName: 'Polygon Mainnet',
51
+ chainIdHex: '0x89',
52
+ chainIdNumber: 137,
53
+ nativeCurrency: { name: 'MATIC', decimals: 18, symbol: 'MATIC' },
54
+ rpcUrls: ['https://polygon-rpc.com/'],
55
+ blockExplorerUrl: 'https://polygonscan.com',
56
+ },
57
+ [Network.TESTNET]: {
58
+ chainName: 'Polygon Amoy',
59
+ chainIdHex: '0x13882',
60
+ chainIdNumber: 80002,
61
+ nativeCurrency: { name: 'MATIC', decimals: 18, symbol: 'MATIC' },
62
+ rpcUrls: ['https://rpc-amoy.polygon.technology'],
63
+ blockExplorerUrl: 'https://amoy.polygonscan.com',
64
+ },
65
+ },
66
+ [Chains.BINANCE]: {
67
+ name: Chains.BINANCE,
68
+ group: Groups.EVM,
69
+ [Network.MAINNET]: {
70
+ chainName: 'BNB Smart Chain',
71
+ chainIdHex: '0x38',
72
+ chainIdNumber: 56,
73
+ nativeCurrency: { name: 'BNB', decimals: 18, symbol: 'BNB' },
74
+ rpcUrls: ['https://bsc-dataseed.binance.org/'],
75
+ blockExplorerUrl: 'https://bscscan.com',
76
+ },
77
+ [Network.TESTNET]: {
78
+ chainName: 'BNB Smart Chain Testnet',
79
+ chainIdHex: '0x61',
80
+ chainIdNumber: 97,
81
+ nativeCurrency: { name: 'tBNB', decimals: 18, symbol: 'BNB' },
82
+ rpcUrls: ['https://data-seed-prebsc-1-s1.binance.org:8545/'],
83
+ blockExplorerUrl: 'https://testnet.bscscan.com',
84
+ },
85
+ },
86
+ [Chains.BASE]: {
87
+ name: Chains.BASE,
88
+ group: Groups.EVM,
89
+ [Network.MAINNET]: {
90
+ chainName: 'Base',
91
+ chainIdHex: '0x2105',
92
+ chainIdNumber: 8453,
93
+ nativeCurrency: { name: 'Ether', decimals: 18, symbol: 'ETH' },
94
+ rpcUrls: ['https://mainnet.base.org/'],
95
+ blockExplorerUrl: 'https://basescan.org',
96
+ },
97
+ [Network.TESTNET]: {
98
+ chainName: 'Base Sepolia',
99
+ chainIdHex: '0x14a34',
100
+ chainIdNumber: 84532,
101
+ nativeCurrency: { name: 'Ether', decimals: 18, symbol: 'ETH' },
102
+ rpcUrls: ['https://sepolia.base.org'],
103
+ blockExplorerUrl: 'https://sepolia.basescan.org',
104
+ },
105
+ },
106
+ [Chains.LINEA]: {
107
+ name: Chains.LINEA,
108
+ group: Groups.EVM,
109
+ [Network.MAINNET]: {
110
+ chainName: 'Linea',
111
+ chainIdHex: '0xe708',
112
+ chainIdNumber: 59144,
113
+ nativeCurrency: { name: 'Ether', decimals: 18, symbol: 'ETH' },
114
+ rpcUrls: ['https://rpc.linea.build'],
115
+ blockExplorerUrl: 'https://lineascan.build',
116
+ },
117
+ [Network.TESTNET]: {
118
+ chainName: 'Linea Sepolia',
119
+ chainIdHex: '0xe705',
120
+ chainIdNumber: 59141,
121
+ nativeCurrency: { name: 'Ether', decimals: 18, symbol: 'ETH' },
122
+ rpcUrls: ['https://rpc.sepolia.linea.build'],
123
+ blockExplorerUrl: 'https://sepolia.lineascan.build',
124
+ },
125
+ },
126
+ [Chains.SKALE]: {
127
+ name: Chains.SKALE,
128
+ group: Groups.EVM,
129
+ [Network.MAINNET]: {
130
+ chainName: 'SKALE Europa',
131
+ chainIdHex: '0x79f99296',
132
+ chainIdNumber: 2046399126,
133
+ nativeCurrency: { name: 'sFUEL', decimals: 18, symbol: 'sFUEL' },
134
+ rpcUrls: ['https://mainnet.skalenodes.com/v1/elated-tan-skat'],
135
+ blockExplorerUrl: 'https://elated-tan-skat.explorer.mainnet.skalenodes.com',
136
+ },
137
+ [Network.TESTNET]: {
138
+ chainName: 'SKALE Calypso Testnet',
139
+ chainIdHex: '0x3a14269b',
140
+ chainIdNumber: 974399131,
141
+ nativeCurrency: { name: 'sFUEL', decimals: 18, symbol: 'sFUEL' },
142
+ rpcUrls: ['https://testnet.skalenodes.com/v1/giant-half-dual-testnet'],
143
+ blockExplorerUrl: 'https://giant-half-dual-testnet.explorer.testnet.skalenodes.com',
144
+ },
145
+ },
146
+ [Chains.REDBELLY]: {
147
+ name: Chains.REDBELLY,
148
+ group: Groups.EVM,
149
+ [Network.MAINNET]: {
150
+ chainName: 'Redbelly Network Mainnet',
151
+ chainIdHex: '0x97',
152
+ chainIdNumber: 151,
153
+ nativeCurrency: { name: 'RBNT', decimals: 18, symbol: 'RBNT' },
154
+ rpcUrls: ['https://governors.mainnet.redbelly.network'],
155
+ blockExplorerUrl: 'https://redbelly.routescan.io',
156
+ },
157
+ [Network.TESTNET]: {
158
+ chainName: 'Redbelly Network Testnet',
159
+ chainIdHex: '0x99',
160
+ chainIdNumber: 153,
161
+ nativeCurrency: { name: 'RBNT', decimals: 18, symbol: 'RBNT' },
162
+ rpcUrls: ['https://governors.testnet.redbelly.network'],
163
+ blockExplorerUrl: 'https://explorer.testnet.redbelly.network',
164
+ },
165
+ },
166
+ [Chains.BITLAYER]: {
167
+ name: Chains.BITLAYER,
168
+ group: Groups.EVM,
169
+ [Network.MAINNET]: {
170
+ chainName: 'Bitlayer',
171
+ chainIdHex: '0xc4',
172
+ chainIdNumber: 200901,
173
+ nativeCurrency: { name: 'Bitcoin', decimals: 18, symbol: 'BTC' },
174
+ rpcUrls: ['https://rpc.bitlayer.org'],
175
+ blockExplorerUrl: 'https://www.btrscan.com',
176
+ },
177
+ [Network.TESTNET]: {
178
+ chainName: 'Bitlayer Testnet',
179
+ chainIdHex: '0xc5',
180
+ chainIdNumber: 200810,
181
+ nativeCurrency: { name: 'Bitcoin', decimals: 18, symbol: 'BTC' },
182
+ rpcUrls: ['https://testnet-rpc.bitlayer.org'],
183
+ blockExplorerUrl: 'https://testnet.btrscan.com',
184
+ },
185
+ },
186
+ [Chains.SOLANA]: {
187
+ name: Chains.SOLANA,
188
+ group: Groups.SOL,
189
+ [Network.MAINNET]: {
190
+ chainName: 'Solana',
191
+ chainIdHex: '0x65',
192
+ chainIdNumber: 101,
193
+ nativeCurrency: { name: 'Solana', decimals: 9, symbol: 'SOL' },
194
+ rpcUrls: ['https://api.mainnet-beta.solana.com'],
195
+ blockExplorerUrl: 'https://explorer.solana.com',
196
+ },
197
+ [Network.TESTNET]: {
198
+ chainName: 'Solana Devnet',
199
+ chainIdHex: '0x67',
200
+ chainIdNumber: 103,
201
+ nativeCurrency: { name: 'Solana', decimals: 9, symbol: 'SOL' },
202
+ rpcUrls: ['https://api.devnet.solana.com'],
203
+ blockExplorerUrl: 'https://explorer.solana.com?cluster=devnet',
204
+ },
205
+ },
206
+ [Chains.XION]: {
207
+ name: Chains.XION,
208
+ group: Groups.XION,
209
+ [Network.MAINNET]: {
210
+ chainName: 'XION',
211
+ chainIdHex: '0x0',
212
+ chainIdNumber: 0,
213
+ nativeCurrency: { name: 'XION', decimals: 6, symbol: 'XION' },
214
+ rpcUrls: ['https://rpc.xion-mainnet-1.burnt.com'],
215
+ blockExplorerUrl: 'https://explorer.burnt.com',
216
+ },
217
+ [Network.TESTNET]: {
218
+ chainName: 'XION Testnet',
219
+ chainIdHex: '0x0',
220
+ chainIdNumber: 0,
221
+ nativeCurrency: { name: 'XION', decimals: 6, symbol: 'XION' },
222
+ rpcUrls: ['https://rpc.xion-testnet-2.burnt.com'],
223
+ blockExplorerUrl: 'https://explorer.burnt.com/xion-testnet-2',
224
+ },
225
+ },
226
+ [Chains.UNSTOPPABLE]: {
227
+ name: Chains.UNSTOPPABLE,
228
+ group: Groups.UD,
229
+ // Unstoppable Domains is an identity/SSO layer, not an EVM chain.
230
+ // No network metadata.
231
+ },
232
+ });
233
+ /**
234
+ * Snapshot of the registry as an array — matches the v1.0.13 `chains`
235
+ * named export so consumers iterating over it keep working.
236
+ */
237
+ export const chains = deepFreeze(Object.values(CHAINS_TABLE));
238
+ export function getChainByName(chainName) {
239
+ const entry = CHAINS_TABLE[chainName];
240
+ if (!entry) {
241
+ throw new ChainNotImplementedException(chainName);
242
+ }
243
+ return entry;
244
+ }
245
+ export function getGroupOfChain(chainName) {
246
+ return getChainByName(chainName).group;
247
+ }
248
+ export function isChainInGroup(chainName, group) {
249
+ return getChainByName(chainName).group === group;
250
+ }
251
+ export function getChainsByGroup(group) {
252
+ return chains.filter((c) => c.group === group);
253
+ }
254
+ export function getChainDetails(chain, network) {
255
+ const entry = getChainByName(chain);
256
+ const details = entry[network];
257
+ if (!details) {
258
+ throw new ChainNotImplementedException(chain);
259
+ }
260
+ return details;
261
+ }
262
+ /**
263
+ * Validates that the caller-supplied RPC URL appears in the registry
264
+ * for the (chain, network) pair. Used by anything that issues a JSON-RPC
265
+ * request — never trust a user-supplied RPC URL.
266
+ */
267
+ export function isRpcUrlAllowed(chain, network, rpcUrl) {
268
+ try {
269
+ const parsed = new URL(rpcUrl);
270
+ if (parsed.protocol !== 'https:') {
271
+ return false;
272
+ }
273
+ const details = getChainDetails(chain, network);
274
+ return details.rpcUrls.some((u) => {
275
+ try {
276
+ const allowed = new URL(u);
277
+ return (allowed.protocol === parsed.protocol &&
278
+ allowed.host === parsed.host &&
279
+ allowed.pathname === parsed.pathname);
280
+ }
281
+ catch {
282
+ return false;
283
+ }
284
+ });
285
+ }
286
+ catch {
287
+ return false;
288
+ }
289
+ }
290
+ /**
291
+ * Builds a block-explorer URL for a transaction hash. The hash is
292
+ * regex-validated to defend against `javascript:` / open-redirect
293
+ * payloads sneaking into a UI that renders the result as a link.
294
+ */
295
+ export function getTransactionLink(args) {
296
+ if (!/^0x[a-fA-F0-9]{64}$/u.test(args.transactionHash)) {
297
+ throw new InvalidParametersException(args.chain, 'transactionHash must be 0x-prefixed 32-byte hex');
298
+ }
299
+ const details = getChainDetails(args.chain, args.network);
300
+ return `${details.blockExplorerUrl}/tx/${args.transactionHash}`;
301
+ }
302
+ //# sourceMappingURL=chains.js.map