@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 +20 -0
- package/LICENSE +21 -0
- package/README.md +123 -0
- package/THREAT_MODEL.md +139 -0
- package/dist/chains.d.ts +67 -0
- package/dist/chains.d.ts.map +1 -0
- package/dist/chains.js +302 -0
- package/dist/chains.js.map +1 -0
- package/dist/deep-freeze.d.ts +17 -0
- package/dist/deep-freeze.d.ts.map +1 -0
- package/dist/deep-freeze.js +40 -0
- package/dist/deep-freeze.js.map +1 -0
- package/dist/errors.d.ts +96 -0
- package/dist/errors.d.ts.map +1 -0
- package/dist/errors.js +157 -0
- package/dist/errors.js.map +1 -0
- package/dist/format.d.ts +48 -0
- package/dist/format.d.ts.map +1 -0
- package/dist/format.js +0 -0
- package/dist/format.js.map +1 -0
- package/dist/index.d.ts +18 -0
- package/dist/index.d.ts.map +1 -0
- package/dist/index.js +24 -0
- package/dist/index.js.map +1 -0
- package/dist/tokens.d.ts +60 -0
- package/dist/tokens.d.ts.map +1 -0
- package/dist/tokens.js +215 -0
- package/dist/tokens.js.map +1 -0
- package/dist/tx-builder.d.ts +76 -0
- package/dist/tx-builder.d.ts.map +1 -0
- package/dist/tx-builder.js +116 -0
- package/dist/tx-builder.js.map +1 -0
- package/dist/types.d.ts +155 -0
- package/dist/types.d.ts.map +1 -0
- package/dist/types.js +140 -0
- package/dist/types.js.map +1 -0
- package/package.json +55 -0
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).
|
package/THREAT_MODEL.md
ADDED
|
@@ -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. "UCDSU") 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.
|
package/dist/chains.d.ts
ADDED
|
@@ -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
|