@gibs/bridge-client 1.12.0 → 1.13.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/LICENSE ADDED
@@ -0,0 +1,15 @@
1
+ ISC License
2
+
3
+ Copyright (c) 2026 Gibs Finance
4
+
5
+ Permission to use, copy, modify, and/or distribute this software for any
6
+ purpose with or without fee is hereby granted, provided that the above
7
+ copyright notice and this permission notice appear in all copies.
8
+
9
+ THE SOFTWARE IS PROVIDED "AS IS" AND THE AUTHOR DISCLAIMS ALL WARRANTIES WITH
10
+ REGARD TO THIS SOFTWARE INCLUDING ALL IMPLIED WARRANTIES OF MERCHANTABILITY
11
+ AND FITNESS. IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR ANY SPECIAL, DIRECT,
12
+ INDIRECT, OR CONSEQUENTIAL DAMAGES OR ANY DAMAGES WHATSOEVER RESULTING FROM
13
+ LOSS OF USE, DATA OR PROFITS, WHETHER IN AN ACTION OF CONTRACT, NEGLIGENCE OR
14
+ OTHER TORTIOUS ACTION, ARISING OUT OF OR IN CONNECTION WITH THE USE OR
15
+ PERFORMANCE OF THIS SOFTWARE.
package/README.md ADDED
@@ -0,0 +1,53 @@
1
+ # @gibs/bridge-client
2
+
3
+ Browser-side endpoints, public clients, and caching for a Gibs Finance bridge interface. It has no framework dependency.
4
+
5
+ It sits between `@gibs/bridge-sdk`, which prices and builds transfers but does no input or output, and an application that renders them.
6
+
7
+ ## Install
8
+
9
+ ```sh
10
+ npm install @gibs/bridge-client viem
11
+ ```
12
+
13
+ `viem` is a peer dependency. The supported range is `>=2.37.0 <3`.
14
+
15
+ ## Requirements
16
+
17
+ - Node 20 or later, or a modern browser.
18
+ - The package is ESM only. Use `import`, not `require`.
19
+ - `@gibs/bridge-sdk` is a dependency, and it reaches TON code. A browser bundle needs a global `Buffer`. Install the `buffer` package and set `globalThis.Buffer` before you import this package. Node has it already.
20
+
21
+ ## Example
22
+
23
+ ```ts
24
+ import { clientFromChain } from '@gibs/bridge-client/client'
25
+ import { chainLogoUrl } from '@gibs/bridge-client/image'
26
+ import { isSupportedEvmChain } from '@gibs/bridge-client/networks'
27
+
28
+ const pulsechain = 369
29
+
30
+ if (isSupportedEvmChain(pulsechain)) {
31
+ const client = clientFromChain(pulsechain)
32
+ const blockNumber = await client.getBlockNumber()
33
+ console.log(blockNumber, chainLogoUrl({ chainId: pulsechain, displayPx: 32 }))
34
+ }
35
+ ```
36
+
37
+ ## Subpath exports
38
+
39
+ Import from a subpath so that your bundler loads only what you use.
40
+
41
+ | Subpath | Purpose |
42
+ | --- | --- |
43
+ | `@gibs/bridge-client/client` | `clientFromChain`, a cached viem public client per chain |
44
+ | `@gibs/bridge-client/endpoints` | Per-chain RPC endpoint lists, with storage and subscription |
45
+ | `@gibs/bridge-client/networks` | The supported viem chains |
46
+ | `@gibs/bridge-client/image` | Chain logo urls |
47
+ | `@gibs/bridge-client/token-metadata` | Token metadata reads and caching |
48
+ | `@gibs/bridge-client/chain-read` | Allowance reads |
49
+ | `@gibs/bridge-client/cache` | The `Cache` class |
50
+
51
+ ## License
52
+
53
+ ISC
@@ -0,0 +1,18 @@
1
+ /** One chain, carrying only what this application reads. */
2
+ export type ChainTableEntry = {
3
+ readonly id: number;
4
+ readonly name: string;
5
+ readonly nativeCurrency: {
6
+ readonly name: string;
7
+ readonly symbol: string;
8
+ readonly decimals: number;
9
+ };
10
+ readonly http: readonly string[];
11
+ readonly explorer: string | null;
12
+ /** Whether this is a test network, which a production build must not offer. */
13
+ readonly isTestnet: boolean;
14
+ /** The chain's Multicall3 deployment, or null where it has none. */
15
+ readonly multicall3: string | null;
16
+ };
17
+ /** Every supported chain, the bridge's own first. */
18
+ export declare const chainTable: readonly ChainTableEntry[];
@@ -0,0 +1,89 @@
1
+ // GENERATED by scripts/generate-chain-table.mjs — do not edit by hand.
2
+ //
3
+ // The Ethereum Virtual Machine chains LI.FI or Relay will quote, plus the
4
+ // bridge's own, with the fields a public client needs. Regenerate when either
5
+ // service adds a chain; the generator's own doc comment explains why this is a
6
+ // table rather than viem imports.
7
+ //
8
+ // Chains those services list that viem does not ship, and which are therefore
9
+ // absent here: 1672, 4663, 5042, 42018, 69000, 97477, 685689, 3586256, 5064014.
10
+ // They have no endpoint to seed from, so naming them would produce a chain the
11
+ // picker offers and no read can reach.
12
+ /** Every supported chain, the bridge's own first. */
13
+ export const chainTable = [
14
+ { id: 1, name: "Ethereum", nativeCurrency: { "name": "Ether", "symbol": "ETH", "decimals": 18 }, http: ["https://eth.merkle.io"], explorer: "https://etherscan.io", isTestnet: false, multicall3: "0xca11bde05977b3631167028862be2a173976ca11" },
15
+ { id: 369, name: "PulseChain", nativeCurrency: { "name": "Pulse", "symbol": "PLS", "decimals": 18 }, http: ["https://rpc.pulsechain.com"], explorer: "https://ipfs.scan.pulsechain.com", isTestnet: false, multicall3: "0xca11bde05977b3631167028862be2a173976ca11" },
16
+ { id: 11155111, name: "Sepolia", nativeCurrency: { "name": "Sepolia Ether", "symbol": "ETH", "decimals": 18 }, http: ["https://11155111.rpc.thirdweb.com"], explorer: "https://sepolia.etherscan.io", isTestnet: true, multicall3: "0xca11bde05977b3631167028862be2a173976ca11" },
17
+ { id: 943, name: "PulseChain V4", nativeCurrency: { "name": "V4 Pulse", "symbol": "v4PLS", "decimals": 18 }, http: ["https://rpc.v4.testnet.pulsechain.com"], explorer: "https://scan.v4.testnet.pulsechain.com", isTestnet: true, multicall3: "0xca11bde05977b3631167028862be2a173976ca11" },
18
+ { id: 56, name: "BNB Smart Chain", nativeCurrency: { "decimals": 18, "name": "BNB", "symbol": "BNB" }, http: ["https://56.rpc.thirdweb.com"], explorer: "https://bscscan.com", isTestnet: false, multicall3: "0xca11bde05977b3631167028862be2a173976ca11" },
19
+ { id: 42161, name: "Arbitrum One", nativeCurrency: { "name": "Ether", "symbol": "ETH", "decimals": 18 }, http: ["https://arb1.arbitrum.io/rpc"], explorer: "https://arbiscan.io", isTestnet: false, multicall3: "0xca11bde05977b3631167028862be2a173976ca11" },
20
+ { id: 8453, name: "Base", nativeCurrency: { "name": "Ether", "symbol": "ETH", "decimals": 18 }, http: ["https://mainnet-preconf.base.org"], explorer: "https://basescan.org", isTestnet: false, multicall3: "0xca11bde05977b3631167028862be2a173976ca11" },
21
+ { id: 81457, name: "Blast", nativeCurrency: { "decimals": 18, "name": "Ether", "symbol": "ETH" }, http: ["https://rpc.blast.io"], explorer: "https://blastscan.io", isTestnet: false, multicall3: "0xcA11bde05977b3631167028862bE2a173976CA11" },
22
+ { id: 43114, name: "Avalanche", nativeCurrency: { "decimals": 18, "name": "Avalanche", "symbol": "AVAX" }, http: ["https://api.avax.network/ext/bc/C/rpc"], explorer: "https://snowtrace.io", isTestnet: false, multicall3: "0xca11bde05977b3631167028862be2a173976ca11" },
23
+ { id: 137, name: "Polygon", nativeCurrency: { "name": "POL", "symbol": "POL", "decimals": 18 }, http: ["https://polygon.drpc.org"], explorer: "https://polygonscan.com", isTestnet: false, multicall3: "0xca11bde05977b3631167028862be2a173976ca11" },
24
+ { id: 534352, name: "Scroll", nativeCurrency: { "name": "Ether", "symbol": "ETH", "decimals": 18 }, http: ["https://rpc.scroll.io"], explorer: "https://scrollscan.com", isTestnet: false, multicall3: "0xca11bde05977b3631167028862be2a173976ca11" },
25
+ { id: 10, name: "OP Mainnet", nativeCurrency: { "name": "Ether", "symbol": "ETH", "decimals": 18 }, http: ["https://mainnet.optimism.io"], explorer: "https://optimistic.etherscan.io", isTestnet: false, multicall3: "0xca11bde05977b3631167028862be2a173976ca11" },
26
+ { id: 59144, name: "Linea Mainnet", nativeCurrency: { "name": "Linea Ether", "symbol": "ETH", "decimals": 18 }, http: ["https://rpc.linea.build"], explorer: "https://lineascan.build", isTestnet: false, multicall3: "0xcA11bde05977b3631167028862bE2a173976CA11" },
27
+ { id: 324, name: "ZKsync Era", nativeCurrency: { "decimals": 18, "name": "Ether", "symbol": "ETH" }, http: ["https://mainnet.era.zksync.io"], explorer: "https://explorer.zksync.io/", isTestnet: false, multicall3: "0xF9cda624FBC7e059355ce98a31693d299FACd963" },
28
+ { id: 100, name: "Gnosis", nativeCurrency: { "decimals": 18, "name": "xDAI", "symbol": "XDAI" }, http: ["https://rpc.gnosischain.com"], explorer: "https://gnosisscan.io", isTestnet: false, multicall3: "0xca11bde05977b3631167028862be2a173976ca11" },
29
+ { id: 122, name: "Fuse", nativeCurrency: { "name": "Fuse", "symbol": "FUSE", "decimals": 18 }, http: ["https://rpc.fuse.io"], explorer: "https://explorer.fuse.io", isTestnet: false, multicall3: "0xca11bde05977b3631167028862be2a173976ca11" },
30
+ { id: 288, name: "Boba Network", nativeCurrency: { "decimals": 18, "name": "Ether", "symbol": "ETH" }, http: ["https://mainnet.boba.network"], explorer: "https://bobascan.com", isTestnet: false, multicall3: "0xca11bde05977b3631167028862be2a173976ca11" },
31
+ { id: 130, name: "Unichain", nativeCurrency: { "name": "Ether", "symbol": "ETH", "decimals": 18 }, http: ["https://mainnet.unichain.org/"], explorer: "https://uniscan.xyz", isTestnet: false, multicall3: "0xca11bde05977b3631167028862be2a173976ca11" },
32
+ { id: 1329, name: "Sei Network", nativeCurrency: { "name": "Sei", "symbol": "SEI", "decimals": 18 }, http: ["https://evm-rpc.sei-apis.com/"], explorer: "https://seiscan.io", isTestnet: false, multicall3: "0xcA11bde05977b3631167028862bE2a173976CA11" },
33
+ { id: 13371, name: "Immutable zkEVM", nativeCurrency: { "decimals": 18, "name": "Immutable Coin", "symbol": "IMX" }, http: ["https://rpc.immutable.com"], explorer: "https://explorer.immutable.com", isTestnet: false, multicall3: "0x236bdA4589e44e6850f5aC6a74BfCa398a86c6c0" },
34
+ { id: 146, name: "Sonic", nativeCurrency: { "decimals": 18, "name": "Sonic", "symbol": "S" }, http: ["https://rpc.soniclabs.com"], explorer: "https://sonicscan.org", isTestnet: false, multicall3: "0xca11bde05977b3631167028862be2a173976ca11" },
35
+ { id: 1625, name: "Gravity Alpha Mainnet", nativeCurrency: { "name": "G", "symbol": "G", "decimals": 18 }, http: ["https://rpc.gravity.xyz"], explorer: "https://explorer.gravity.xyz", isTestnet: false, multicall3: "0xf8ac4BEB2F75d2cFFb588c63251347fdD629B92c" },
36
+ { id: 25, name: "Cronos Mainnet", nativeCurrency: { "decimals": 18, "name": "Cronos", "symbol": "CRO" }, http: ["https://evm.cronos.org"], explorer: "https://explorer.cronos.org", isTestnet: false, multicall3: "0xcA11bde05977b3631167028862bE2a173976CA11" },
37
+ { id: 252, name: "Fraxtal", nativeCurrency: { "name": "Frax", "symbol": "FRAX", "decimals": 18 }, http: ["https://rpc.frax.com"], explorer: "https://fraxscan.com", isTestnet: false, multicall3: "0xca11bde05977b3631167028862be2a173976ca11" },
38
+ { id: 2741, name: "Abstract", nativeCurrency: { "decimals": 18, "name": "ETH", "symbol": "ETH" }, http: ["https://api.mainnet.abs.xyz"], explorer: "https://abscan.org", isTestnet: false, multicall3: "0xAa4De41dba0Ca5dCBb288b7cC6b708F3aaC759E7" },
39
+ { id: 30, name: "Rootstock Mainnet", nativeCurrency: { "decimals": 18, "name": "Rootstock Bitcoin", "symbol": "RBTC" }, http: ["https://public-node.rsk.co"], explorer: "https://explorer.rsk.co", isTestnet: false, multicall3: "0xcA11bde05977b3631167028862bE2a173976CA11" },
40
+ { id: 42220, name: "Celo", nativeCurrency: { "decimals": 18, "name": "CELO", "symbol": "CELO" }, http: ["https://forno.celo.org"], explorer: "https://celoscan.io", isTestnet: false, multicall3: "0xcA11bde05977b3631167028862bE2a173976CA11" },
41
+ { id: 480, name: "World Chain", nativeCurrency: { "name": "Ether", "symbol": "ETH", "decimals": 18 }, http: ["https://worldchain-mainnet.g.alchemy.com/public"], explorer: "https://worldscan.org", isTestnet: false, multicall3: "0xca11bde05977b3631167028862be2a173976ca11" },
42
+ { id: 5000, name: "Mantle", nativeCurrency: { "decimals": 18, "name": "MNT", "symbol": "MNT" }, http: ["https://rpc.mantle.xyz"], explorer: "https://mantlescan.xyz/", isTestnet: false, multicall3: "0xcA11bde05977b3631167028862bE2a173976CA11" },
43
+ { id: 80094, name: "Berachain", nativeCurrency: { "decimals": 18, "name": "BERA Token", "symbol": "BERA" }, http: ["https://rpc.berachain.com"], explorer: "https://berascan.com", isTestnet: false, multicall3: "0xcA11bde05977b3631167028862bE2a173976CA11" },
44
+ { id: 14, name: "Flare Mainnet", nativeCurrency: { "decimals": 18, "name": "Flare", "symbol": "FLR" }, http: ["https://flare-api.flare.network/ext/C/rpc"], explorer: "https://flare-explorer.flare.network", isTestnet: false, multicall3: "0xcA11bde05977b3631167028862bE2a173976CA11" },
45
+ { id: 40, name: "Telos", nativeCurrency: { "decimals": 18, "name": "Telos", "symbol": "TLOS" }, http: ["https://rpc.telos.net"], explorer: "https://www.teloscan.io/", isTestnet: false, multicall3: "0xcA11bde05977b3631167028862bE2a173976CA11" },
46
+ { id: 50, name: "XDC Network", nativeCurrency: { "decimals": 18, "name": "XDC", "symbol": "XDC" }, http: ["https://rpc.xdcrpc.com"], explorer: "https://xdcscan.com", isTestnet: false, multicall3: "0x0B1795ccA8E4eC4df02346a082df54D437F8D9aF" },
47
+ { id: 88, name: "Viction", nativeCurrency: { "name": "Viction", "symbol": "VIC", "decimals": 18 }, http: ["https://rpc.viction.xyz"], explorer: "https://vicscan.xyz", isTestnet: false, multicall3: null },
48
+ { id: 143, name: "Monad", nativeCurrency: { "name": "Monad", "symbol": "MON", "decimals": 18 }, http: ["https://rpc.monad.xyz", "https://rpc1.monad.xyz"], explorer: "https://monadvision.com", isTestnet: false, multicall3: "0xcA11bde05977b3631167028862bE2a173976CA11" },
49
+ { id: 169, name: "Manta Pacific Mainnet", nativeCurrency: { "decimals": 18, "name": "ETH", "symbol": "ETH" }, http: ["https://pacific-rpc.manta.network/http"], explorer: "https://pacific-explorer.manta.network", isTestnet: false, multicall3: "0xcA11bde05977b3631167028862bE2a173976CA11" },
50
+ { id: 196, name: "X Layer Mainnet", nativeCurrency: { "decimals": 18, "name": "OKB", "symbol": "OKB" }, http: ["https://rpc.xlayer.tech"], explorer: "https://www.oklink.com/xlayer", isTestnet: false, multicall3: "0xcA11bde05977b3631167028862bE2a173976CA11" },
51
+ { id: 204, name: "opBNB", nativeCurrency: { "name": "BNB", "symbol": "BNB", "decimals": 18 }, http: ["https://opbnb-mainnet-rpc.bnbchain.org"], explorer: "https://opbnb.bscscan.com", isTestnet: false, multicall3: "0xcA11bde05977b3631167028862bE2a173976CA11" },
52
+ { id: 232, name: "Lens", nativeCurrency: { "name": "GHO", "symbol": "GHO", "decimals": 18 }, http: ["https://rpc.lens.xyz"], explorer: "https://explorer.lens.xyz", isTestnet: false, multicall3: null },
53
+ { id: 360, name: "Shape", nativeCurrency: { "name": "Ether", "symbol": "ETH", "decimals": 18 }, http: ["https://mainnet.shape.network"], explorer: "https://shapescan.xyz", isTestnet: false, multicall3: "0xca11bde05977b3631167028862be2a173976ca11" },
54
+ { id: 747, name: "Flow EVM Mainnet", nativeCurrency: { "decimals": 18, "name": "Flow", "symbol": "FLOW" }, http: ["https://mainnet.evm.nodes.onflow.org"], explorer: "https://evm.flowscan.io", isTestnet: false, multicall3: "0xca11bde05977b3631167028862be2a173976ca11" },
55
+ { id: 988, name: "Stable Mainnet", nativeCurrency: { "name": "USDT0", "symbol": "USDT0", "decimals": 18 }, http: ["https://rpc.stable.xyz"], explorer: "https://stablescan.xyz", isTestnet: false, multicall3: "0xcA11bde05977b3631167028862bE2a173976CA11" },
56
+ { id: 999, name: "Zora Goerli Testnet", nativeCurrency: { "decimals": 18, "name": "Zora Goerli", "symbol": "ETH" }, http: ["https://testnet.rpc.zora.energy"], explorer: "https://testnet.explorer.zora.energy", isTestnet: true, multicall3: "0xcA11bde05977b3631167028862bE2a173976CA11" },
57
+ { id: 1088, name: "Metis", nativeCurrency: { "decimals": 18, "name": "Metis", "symbol": "METIS" }, http: ["https://metis.rpc.hypersync.xyz", "https://metis-pokt.nodies.app", "https://api.blockeden.xyz/metis/67nCBdZQSH9z3YqDDjdm", "https://metis-andromeda.rpc.thirdweb.com", "https://metis-andromeda.gateway.tenderly.co", "https://metis.api.onfinality.io/public", "https://andromeda.metis.io/?owner=1088", "https://metis-mainnet.public.blastapi.io"], explorer: "https://explorer.metis.io", isTestnet: false, multicall3: "0xca11bde05977b3631167028862be2a173976ca11" },
58
+ { id: 1135, name: "Lisk", nativeCurrency: { "decimals": 18, "name": "Ether", "symbol": "ETH" }, http: ["https://rpc.api.lisk.com"], explorer: "https://blockscout.lisk.com", isTestnet: false, multicall3: "0xA9d71E1dd7ca26F26e656E66d6AA81ed7f745bf0" },
59
+ { id: 1337, name: "Tempo", nativeCurrency: { "name": "USD", "symbol": "USD", "decimals": 6 }, http: ["http://localhost:8545"], explorer: null, isTestnet: false, multicall3: null },
60
+ { id: 1480, name: "Vana", nativeCurrency: { "decimals": 18, "name": "Vana", "symbol": "VANA" }, http: ["https://rpc.vana.org/"], explorer: "https://vanascan.io", isTestnet: false, multicall3: "0xD8d2dFca27E8797fd779F8547166A2d3B29d360E" },
61
+ { id: 1776, name: "Injective", nativeCurrency: { "decimals": 18, "name": "Injective", "symbol": "INJ" }, http: ["https://sentry.evm-rpc.injective.network"], explorer: "https://blockscout.injective.network", isTestnet: false, multicall3: null },
62
+ { id: 1868, name: "Soneium Mainnet", nativeCurrency: { "name": "Ether", "symbol": "ETH", "decimals": 18 }, http: ["https://rpc.soneium.org"], explorer: "https://soneium.blockscout.com", isTestnet: false, multicall3: "0xcA11bde05977b3631167028862bE2a173976CA11" },
63
+ { id: 2020, name: "Ronin", nativeCurrency: { "name": "RON", "symbol": "RON", "decimals": 18 }, http: ["https://api.roninchain.com/rpc"], explorer: "https://app.roninchain.com", isTestnet: false, multicall3: "0xca11bde05977b3631167028862be2a173976ca11" },
64
+ { id: 2818, name: "Morph", nativeCurrency: { "decimals": 18, "name": "Ether", "symbol": "ETH" }, http: ["https://rpc.morphl2.io"], explorer: "https://explorer.morphl2.io", isTestnet: false, multicall3: "0xcA11bde05977b3631167028862bE2a173976CA11" },
65
+ { id: 4217, name: "Tempo Mainnet", nativeCurrency: { "name": "USD", "symbol": "USD", "decimals": 6 }, http: ["https://rpc.presto.tempo.xyz"], explorer: "https://explore.mainnet.tempo.xyz", isTestnet: false, multicall3: null },
66
+ { id: 4326, name: "MegaETH", nativeCurrency: { "name": "Ether", "symbol": "ETH", "decimals": 18 }, http: ["https://mainnet.megaeth.com/rpc"], explorer: "https://mega.etherscan.io", isTestnet: false, multicall3: "0xcA11bde05977b3631167028862bE2a173976CA11" },
67
+ { id: 5031, name: "Somnia", nativeCurrency: { "name": "Somnia", "symbol": "SOMI", "decimals": 18 }, http: ["https://api.infra.mainnet.somnia.network"], explorer: "https://explorer.somnia.network", isTestnet: false, multicall3: null },
68
+ { id: 5330, name: "Superseed", nativeCurrency: { "name": "Ether", "symbol": "ETH", "decimals": 18 }, http: ["https://mainnet.superseed.xyz"], explorer: "https://explorer.superseed.xyz", isTestnet: false, multicall3: "0xcA11bde05977b3631167028862bE2a173976CA11" },
69
+ { id: 8217, name: "Klaytn", nativeCurrency: { "decimals": 18, "name": "Klaytn", "symbol": "KLAY" }, http: ["https://public-en-cypress.klaytn.net"], explorer: "https://scope.klaytn.com", isTestnet: false, multicall3: "0xcA11bde05977b3631167028862bE2a173976CA11" },
70
+ { id: 8333, name: "B3", nativeCurrency: { "name": "Ether", "symbol": "ETH", "decimals": 18 }, http: ["https://mainnet-rpc.b3.fun/http"], explorer: "https://explorer.b3.fun", isTestnet: false, multicall3: "0xca11bde05977b3631167028862be2a173976ca11" },
71
+ { id: 9745, name: "Plasma", nativeCurrency: { "name": "Plasma", "symbol": "XPL", "decimals": 18 }, http: ["https://rpc.plasma.to"], explorer: "https://plasmascan.to", isTestnet: false, multicall3: "0xcA11bde05977b3631167028862bE2a173976CA11" },
72
+ { id: 16661, name: "0G Mainnet", nativeCurrency: { "name": "0G", "symbol": "0G", "decimals": 18 }, http: ["https://evmrpc.0g.ai"], explorer: "https://chainscan.0g.ai", isTestnet: false, multicall3: null },
73
+ { id: 33139, name: "ApeChain", nativeCurrency: { "name": "ApeCoin", "symbol": "APE", "decimals": 18 }, http: ["https://rpc.apechain.com/http"], explorer: "https://apescan.io", isTestnet: false, multicall3: "0xcA11bde05977b3631167028862bE2a173976CA11" },
74
+ { id: 34443, name: "Mode Mainnet", nativeCurrency: { "name": "Ether", "symbol": "ETH", "decimals": 18 }, http: ["https://mainnet.mode.network"], explorer: "https://modescan.io", isTestnet: false, multicall3: "0xca11bde05977b3631167028862be2a173976ca11" },
75
+ { id: 42170, name: "Arbitrum Nova", nativeCurrency: { "name": "Ether", "symbol": "ETH", "decimals": 18 }, http: ["https://nova.arbitrum.io/rpc"], explorer: "https://nova.arbiscan.io", isTestnet: false, multicall3: "0xca11bde05977b3631167028862be2a173976ca11" },
76
+ { id: 42793, name: "Etherlink", nativeCurrency: { "decimals": 18, "name": "Tez", "symbol": "XTZ" }, http: ["https://node.mainnet.etherlink.com"], explorer: "https://explorer.etherlink.com", isTestnet: false, multicall3: "0xcA11bde05977b3631167028862bE2a173976CA11" },
77
+ { id: 43111, name: "Hemi", nativeCurrency: { "name": "Ether", "symbol": "ETH", "decimals": 18 }, http: ["https://rpc.hemi.network/rpc"], explorer: "https://explorer.hemi.xyz", isTestnet: false, multicall3: null },
78
+ { id: 43419, name: "Gunz Mainnet", nativeCurrency: { "name": "GUN", "symbol": "GUN", "decimals": 18 }, http: ["https://rpc.gunzchain.io/ext/bc/2M47TxWHGnhNtq6pM5zPXdATBtuqubxn5EPFgFmEawCQr9WFML/rpc"], explorer: "https://gunzscan.io/", isTestnet: false, multicall3: "0xcA11bde05977b3631167028862bE2a173976CA11" },
79
+ { id: 48900, name: "Zircuit Mainnet", nativeCurrency: { "decimals": 18, "name": "Ether", "symbol": "ETH" }, http: ["https://mainnet.zircuit.com", "https://zircuit1-mainnet.liquify.com", "https://zircuit1-mainnet.p2pify.com", "https://zircuit-mainnet.drpc.org"], explorer: "https://explorer.zircuit.com", isTestnet: false, multicall3: "0xcA11bde05977b3631167028862bE2a173976CA11" },
80
+ { id: 57073, name: "Ink", nativeCurrency: { "name": "Ether", "symbol": "ETH", "decimals": 18 }, http: ["https://rpc-gel.inkonchain.com", "https://rpc-qnd.inkonchain.com"], explorer: "https://explorer.inkonchain.com", isTestnet: false, multicall3: "0xcA11bde05977b3631167028862bE2a173976CA11" },
81
+ { id: 60808, name: "BOB", nativeCurrency: { "decimals": 18, "name": "ETH", "symbol": "ETH" }, http: ["https://rpc.gobob.xyz"], explorer: "https://explorer.gobob.xyz", isTestnet: false, multicall3: "0xcA11bde05977b3631167028862bE2a173976CA11" },
82
+ { id: 84532, name: "Base Sepolia", nativeCurrency: { "name": "Sepolia Ether", "symbol": "ETH", "decimals": 18 }, http: ["https://sepolia-preconf.base.org"], explorer: "https://sepolia.basescan.org", isTestnet: true, multicall3: "0xca11bde05977b3631167028862be2a173976ca11" },
83
+ { id: 98866, name: "Plume", nativeCurrency: { "name": "Plume", "symbol": "PLUME", "decimals": 18 }, http: ["https://rpc.plume.org"], explorer: "https://explorer.plume.org", isTestnet: false, multicall3: "0xcA11bde05977b3631167028862bE2a173976CA11" },
84
+ { id: 421614, name: "Arbitrum Sepolia", nativeCurrency: { "name": "Arbitrum Sepolia Ether", "symbol": "ETH", "decimals": 18 }, http: ["https://sepolia-rollup.arbitrum.io/rpc"], explorer: "https://sepolia.arbiscan.io", isTestnet: true, multicall3: "0xca11bde05977b3631167028862be2a173976ca11" },
85
+ { id: 747474, name: "Katana", nativeCurrency: { "name": "Ether", "symbol": "ETH", "decimals": 18 }, http: ["https://rpc.katana.network"], explorer: "https://katanascan.com", isTestnet: false, multicall3: null },
86
+ { id: 5042002, name: "Arc Testnet", nativeCurrency: { "name": "USDC", "symbol": "USDC", "decimals": 18 }, http: ["https://rpc.testnet.arc.network", "https://rpc.quicknode.testnet.arc.network", "https://rpc.blockdaemon.testnet.arc.network"], explorer: "https://testnet.arcscan.app", isTestnet: true, multicall3: "0xcA11bde05977b3631167028862bE2a173976CA11" },
87
+ { id: 7777777, name: "Zora", nativeCurrency: { "decimals": 18, "name": "Ether", "symbol": "ETH" }, http: ["https://rpc.zora.energy"], explorer: "https://explorer.zora.energy", isTestnet: false, multicall3: "0xcA11bde05977b3631167028862bE2a173976CA11" },
88
+ { id: 11155420, name: "OP Sepolia", nativeCurrency: { "name": "Sepolia Ether", "symbol": "ETH", "decimals": 18 }, http: ["https://sepolia.optimism.io"], explorer: "https://optimism-sepolia.blockscout.com", isTestnet: true, multicall3: "0xca11bde05977b3631167028862be2a173976ca11" },
89
+ ];
package/dist/client.d.ts CHANGED
@@ -1,13 +1,19 @@
1
- import type { PublicClient } from 'viem';
1
+ import type { HttpTransportConfig, PublicClient } from 'viem';
2
2
  /**
3
- * The request-batching setting for a chain's client: `false` to disable batching
4
- * for chains that reject batch bodies (see {@link chainsWithoutRpcBatching}), or
5
- * `undefined` to use the transport's default batching.
3
+ * The request-batching setting for a chain's client.
4
+ *
5
+ * `false` for chains whose nodes reject a batch body at all (see
6
+ * {@link chainsWithoutRpcBatching}). Otherwise batching stays on, capped at
7
+ * {@link MAX_SAME_METHOD_CALLS_PER_SECOND} so no single post can exceed the
8
+ * proxy's per-method budget on its own and lose every call in it.
9
+ *
10
+ * The `wait` of ten milliseconds is viem's own default, restated here because
11
+ * naming `batchSize` alone would otherwise drop it.
6
12
  *
7
13
  * @param chainId - the chain to resolve the batch setting for
8
- * @returns `false` when batching must be disabled, otherwise `undefined`
14
+ * @returns `false` when batching must be disabled, otherwise the batch config
9
15
  */
10
- export declare const rpcBatchSetting: (chainId: number) => false | undefined;
16
+ export declare const rpcBatchSetting: (chainId: number) => HttpTransportConfig["batch"];
11
17
  /**
12
18
  * Returns a viem {@link PublicClient} for a chain, reusing the cached instance
13
19
  * while the chain's endpoint list is unchanged. The cache key is derived from the
package/dist/client.js CHANGED
@@ -1,4 +1,6 @@
1
- import { chainKey, clientCache, clientFromChain as buildClient } from '@gibs/common/client';
1
+ import { chainsMetadata } from '@gibs/bridge-sdk/chains';
2
+ import { toChain } from '@gibs/bridge-sdk/config';
3
+ import { chainKey, clientCache, clientFromChain as buildClient, MAX_SAME_METHOD_CALLS_PER_SECOND, } from '@gibs/common/client';
2
4
  import * as _ from 'lodash-es';
3
5
  import { bsc } from 'viem/chains';
4
6
  import * as endpoints from './endpoints.js';
@@ -13,14 +15,22 @@ import { evmChainsById } from './networks.js';
13
15
  */
14
16
  const chainsWithoutRpcBatching = new Set([bsc.id]);
15
17
  /**
16
- * The request-batching setting for a chain's client: `false` to disable batching
17
- * for chains that reject batch bodies (see {@link chainsWithoutRpcBatching}), or
18
- * `undefined` to use the transport's default batching.
18
+ * The request-batching setting for a chain's client.
19
+ *
20
+ * `false` for chains whose nodes reject a batch body at all (see
21
+ * {@link chainsWithoutRpcBatching}). Otherwise batching stays on, capped at
22
+ * {@link MAX_SAME_METHOD_CALLS_PER_SECOND} so no single post can exceed the
23
+ * proxy's per-method budget on its own and lose every call in it.
24
+ *
25
+ * The `wait` of ten milliseconds is viem's own default, restated here because
26
+ * naming `batchSize` alone would otherwise drop it.
19
27
  *
20
28
  * @param chainId - the chain to resolve the batch setting for
21
- * @returns `false` when batching must be disabled, otherwise `undefined`
29
+ * @returns `false` when batching must be disabled, otherwise the batch config
22
30
  */
23
- export const rpcBatchSetting = (chainId) => chainsWithoutRpcBatching.has(chainId) ? false : undefined;
31
+ export const rpcBatchSetting = (chainId) => chainsWithoutRpcBatching.has(chainId)
32
+ ? false
33
+ : { batchSize: MAX_SAME_METHOD_CALLS_PER_SECOND, wait: 10 };
24
34
  /**
25
35
  * A chain's own default endpoints, used when the editable table has no entry.
26
36
  *
@@ -31,6 +41,23 @@ const defaultUrlsFor = (chainId) => {
31
41
  if (!chainId) {
32
42
  throw new Error('chainId is required');
33
43
  }
44
+ // The APPLICATION's metadata first, viem's chain object only for the chains it
45
+ // does not override.
46
+ //
47
+ // These are two different answers to the same question, and reading only the
48
+ // second is why demoting a rate-limited Sepolia endpoint in `chainsMetadata`
49
+ // changed nothing: the seed built from that map, while THIS function — which
50
+ // decides what a client actually talks to when the stored table is empty —
51
+ // returned viem's own default and went on hitting the demoted host first.
52
+ //
53
+ // The bridge chains are overridden precisely because viem's defaults are not
54
+ // the ones this application wants; the wider list (Arbitrum, Base, and the
55
+ // rest) has no override and correctly falls through.
56
+ const overridden = chainsMetadata[toChain(chainId)];
57
+ const overriddenHttp = overridden?.rpcUrls?.default?.http;
58
+ if (overriddenHttp?.length) {
59
+ return [...overriddenHttp];
60
+ }
34
61
  const chain = evmChainsById.get(chainId);
35
62
  if (!chain) {
36
63
  return [];
@@ -31,7 +31,13 @@ export type EndpointStorage = {
31
31
  * rename silently reverts every one of them to the defaults on the next load.
32
32
  */
33
33
  export declare const ENDPOINTS_STORAGE_KEY = "rpcs";
34
- /** The bridge's production chains. */
34
+ /**
35
+ * Every chain the application can reach, the bridge's own first.
36
+ *
37
+ * The bridge's three keep taking their urls from `chainsMetadata`, which is
38
+ * where the product's own node choices live and which must win over viem's
39
+ * generic default for them.
40
+ */
35
41
  export declare const productionSeed: EndpointEntry[];
36
42
  /** The bridge's test chains, for a build that is not production. */
37
43
  export declare const testnetSeed: EndpointEntry[];
package/dist/endpoints.js CHANGED
@@ -1,5 +1,6 @@
1
1
  import { chainsMetadata } from '@gibs/bridge-sdk/chains';
2
2
  import { Chains, toChain } from '@gibs/bridge-sdk/config';
3
+ import { chainTable } from './chain-table.js';
3
4
  import * as _ from 'lodash-es';
4
5
  /**
5
6
  * The storage key holding the endpoint table.
@@ -8,10 +9,53 @@ import * as _ from 'lodash-es';
8
9
  * rename silently reverts every one of them to the defaults on the next load.
9
10
  */
10
11
  export const ENDPOINTS_STORAGE_KEY = 'rpcs';
11
- /** Builds a seed table from the bridge metadata for the given chains. */
12
+ /**
13
+ * EVERY SUPPORTED CHAIN GETS AN ENTRY, and the entry comes from viem.
14
+ *
15
+ * This used to seed three chains — PulseChain, Ethereum and Binance Smart Chain
16
+ * — while the aggregators quote routes from seventy-odd. A chain absent from
17
+ * this table is not refused; `clientFromChain` falls through to viem's own
18
+ * public nodes for it. So every read on an aggregator origin went to a node
19
+ * nobody chose, which is where the rate limiting came from, and the endpoint
20
+ * editor could not show the chain at all: a reader whose Base node was throttled
21
+ * had no way to replace it, because Base was not in the list they were editing.
22
+ *
23
+ * Seeding from `chainTable` fixes both at once. The urls are viem's own
24
+ * defaults, which is what the three seeded chains always used — the change is
25
+ * the breadth, not the source.
26
+ */
12
27
  const seedFor = (chains) => chains.map((chain) => [Number(chain), [...chainsMetadata[chain].rpcUrls.default.http]]);
13
- /** The bridge's production chains. */
14
- export const productionSeed = seedFor([Chains.PLS, Chains.ETH, Chains.BNB]);
28
+ /** The bridge's own chains, kept first so a picker built from this reads well. */
29
+ const BRIDGE_CHAIN_IDS = [
30
+ Number(Chains.PLS),
31
+ Number(Chains.ETH),
32
+ Number(Chains.BNB),
33
+ ];
34
+ /**
35
+ * Every chain the application can reach, the bridge's own first.
36
+ *
37
+ * The bridge's three keep taking their urls from `chainsMetadata`, which is
38
+ * where the product's own node choices live and which must win over viem's
39
+ * generic default for them.
40
+ */
41
+ export const productionSeed = [
42
+ ...seedFor([Chains.PLS, Chains.ETH, Chains.BNB]),
43
+ ...chainTable
44
+ .filter((entry) =>
45
+ // The bridge's own three are seeded above, from `chainsMetadata`, and
46
+ // must not be seeded twice. That map is where the product's node
47
+ // choices live — demoting a rate-limited host there has to take effect,
48
+ // and a table entry added after it would silently win.
49
+ !BRIDGE_CHAIN_IDS.includes(entry.id) &&
50
+ // A PRODUCTION BUILD PROMISES NOT TO OFFER A TEST NETWORK, and the
51
+ // endpoint table is one of the places that promise can be broken: the
52
+ // chains seeded here are the chains the endpoint editor lists. The
53
+ // table carries test networks deliberately, because a client still has
54
+ // to be buildable for them on a testnet build; this is where they are
55
+ // held back.
56
+ !entry.isTestnet)
57
+ .map((entry) => [entry.id, [...entry.http]]),
58
+ ];
15
59
  /** The bridge's test chains, for a build that is not production. */
16
60
  export const testnetSeed = seedFor([Chains.V4PLS, Chains.SEP]);
17
61
  /** The seed the next load starts from. */
package/dist/image.d.ts CHANGED
@@ -1,14 +1,16 @@
1
1
  /**
2
2
  * Chain-logo resolution, in one place.
3
3
  *
4
- * Two image services are involved and neither covers every chain a route can
5
- * start from, which is why this is a module rather than an inline template
6
- * string: gib.show keys on real chain ids, while the non-Ethereum origins reach
7
- * us under Relay's own pseudo identifiers (Solana, Bitcoin, Tron, The Open
8
- * Network, Eclipse). Asking gib.show for one of those returns a 404, and a 404
9
- * in an `<img src>` is drawn by the browser as a broken-image glyph — so the
10
- * mistake surfaces as a visual blemish on the token pill rather than as an
11
- * error anyone would go looking for.
4
+ * gib.show serves almost every chain a route can start from — including the
5
+ * non-Ethereum ones — but it keys them by a CAIP-2 style identifier rather than
6
+ * by Relay's invented decimal ids, so the two have to be mapped. Only Eclipse
7
+ * and Hyperliquid are genuinely absent from it, and those alone fall back to
8
+ * Relay's own icon service.
9
+ *
10
+ * This is a module rather than an inline template string because getting the
11
+ * key wrong is invisible: a 404 in an `<img src>` is drawn by the browser as a
12
+ * broken-image glyph, so the mistake surfaces as a blemish on a token pill
13
+ * rather than as an error anyone would go looking for.
12
14
  */
13
15
  /**
14
16
  * Relay's public chain-icon service, keyed on Relay's own chain identifiers —
@@ -16,31 +18,51 @@
16
18
  *
17
19
  * The `light` variant is the full-colour brand mark (the pair is light/dark
18
20
  * BACKGROUND, not a monochrome inversion), so one variant serves both of our
19
- * themes. Not every id is populated — The Open Network currently answers 403 —
20
- * which is why callers must tolerate a badge that fails to load.
21
+ * themes. Not every id is populated — The Open Network answers 403, which is
22
+ * what the bundled asset used to exist for — so callers must still tolerate a
23
+ * badge that fails to load. Only Eclipse and Hyperliquid reach this now.
21
24
  *
22
25
  * @param chainId - a Relay chain identifier
23
26
  * @returns the icon url
24
27
  */
25
28
  export declare const relayChainIconUrl: (chainId: number) => string;
26
29
  /**
27
- * Resolves the logo url for a chain, choosing the service that can actually
28
- * serve it.
30
+ * Resolves the logo url for a chain, choosing the service that can serve it.
29
31
  *
30
- * A chain we bundle the mark for wins outright ({@link bundledChainLogoUri}).
31
- * Otherwise Ethereum-Virtual-Machine chains go to gib.show, honouring the
32
- * configured image root (so a self-hosted deployment is respected) rather than
33
- * hard-coding the public host, and everything else is a Relay identifier and
34
- * goes to Relay.
32
+ * A chain gib.show knows under its own identifier wins outright
33
+ * ({@link gibShowChainIdentifier}). Otherwise an Ethereum-Virtual-Machine chain
34
+ * goes to gib.show by number, honouring the configured image root so a
35
+ * self-hosted deployment is respected rather than hard-coding the public host.
36
+ * What remains is Eclipse and Hyperliquid, which gib.show does not carry, and
37
+ * they go to Relay.
35
38
  *
36
39
  * @param chainId - the decimal chain id, Relay's pseudo identifiers included
37
40
  * @param displayPx - the CSS pixel size the logo renders at; when supplied,
38
41
  * gib.show is asked for twice that in webp per its retina guidance. Ignored
39
- * for a Relay-served icon and for a bundled asset, neither of which takes
40
- * sizing parameters.
42
+ * for a Relay-served icon and for an identifier-keyed mark, neither of which
43
+ * takes sizing parameters.
41
44
  * @returns an image url for the chain
42
45
  */
43
46
  export declare const chainLogoUrl: ({ chainId, displayPx, }: {
44
47
  chainId: number;
45
48
  displayPx?: number;
46
49
  }) => string;
50
+ /**
51
+ * The gib.show sources for a chain's NATIVE COIN, best first.
52
+ *
53
+ * The service serves the first source that resolves: the coin as this chain
54
+ * keys it, then the coin as its home chain keys it, then the chain's own mark.
55
+ * The last is an honest fallback rather than a mistake — for a chain's own coin
56
+ * the two marks are the same picture.
57
+ *
58
+ * @param chainId - the chain the coin is being shown on
59
+ * @returns the ordered source list, for {@link imageLinks.images}
60
+ */
61
+ export declare const nativeCoinImageSources: (chainId: number) => string[];
62
+ /**
63
+ * Resolves the picture of a chain's native coin, as opposed to of the chain.
64
+ *
65
+ * @param chainId - the chain the coin is being shown on
66
+ * @returns an image url
67
+ */
68
+ export declare const nativeCoinLogoUrl: (chainId: number) => string;
package/dist/image.js CHANGED
@@ -1,16 +1,20 @@
1
1
  import * as imageLinks from '@gibs/bridge-sdk/image-links';
2
2
  import { ecosystemByChainId, relayChainIds } from '@gibs/bridge-sdk/relay-quote';
3
+ import { zeroAddress } from 'viem';
4
+ import { chainTable } from './chain-table.js';
3
5
  /**
4
6
  * Chain-logo resolution, in one place.
5
7
  *
6
- * Two image services are involved and neither covers every chain a route can
7
- * start from, which is why this is a module rather than an inline template
8
- * string: gib.show keys on real chain ids, while the non-Ethereum origins reach
9
- * us under Relay's own pseudo identifiers (Solana, Bitcoin, Tron, The Open
10
- * Network, Eclipse). Asking gib.show for one of those returns a 404, and a 404
11
- * in an `<img src>` is drawn by the browser as a broken-image glyph — so the
12
- * mistake surfaces as a visual blemish on the token pill rather than as an
13
- * error anyone would go looking for.
8
+ * gib.show serves almost every chain a route can start from — including the
9
+ * non-Ethereum ones — but it keys them by a CAIP-2 style identifier rather than
10
+ * by Relay's invented decimal ids, so the two have to be mapped. Only Eclipse
11
+ * and Hyperliquid are genuinely absent from it, and those alone fall back to
12
+ * Relay's own icon service.
13
+ *
14
+ * This is a module rather than an inline template string because getting the
15
+ * key wrong is invisible: a 404 in an `<img src>` is drawn by the browser as a
16
+ * broken-image glyph, so the mistake surfaces as a blemish on a token pill
17
+ * rather than as an error anyone would go looking for.
14
18
  */
15
19
  /**
16
20
  * Relay's public chain-icon service, keyed on Relay's own chain identifiers —
@@ -18,59 +22,73 @@ import { ecosystemByChainId, relayChainIds } from '@gibs/bridge-sdk/relay-quote'
18
22
  *
19
23
  * The `light` variant is the full-colour brand mark (the pair is light/dark
20
24
  * BACKGROUND, not a monochrome inversion), so one variant serves both of our
21
- * themes. Not every id is populated — The Open Network currently answers 403 —
22
- * which is why callers must tolerate a badge that fails to load.
25
+ * themes. Not every id is populated — The Open Network answers 403, which is
26
+ * what the bundled asset used to exist for — so callers must still tolerate a
27
+ * badge that fails to load. Only Eclipse and Hyperliquid reach this now.
23
28
  *
24
29
  * @param chainId - a Relay chain identifier
25
30
  * @returns the icon url
26
31
  */
27
32
  export const relayChainIconUrl = (chainId) => `https://assets.relay.link/icons/${chainId}/light.png`;
28
33
  /**
29
- * Chains we ship the mark for ourselves, because neither service will serve it.
30
- *
31
- * The Open Network is the only entry today: gib.show does not know Relay's
32
- * pseudo id, and Relay's own service answers 403 for it — the one chain its
33
- * authority does not actually cover. Between the two, the badge had no source at
34
- * all and rendered as nothing via the hide-on-error path, which reads as a
35
- * missing chain rather than a missing image.
36
- *
37
- * The paths are RELATIVE (no leading slash): the reference deployment publishes
38
- * to the InterPlanetary File System, where a leading slash resolves against the
39
- * gateway root instead of the deployed directory.
40
- *
41
- * A CONSUMER CONTRACT: this package names the asset but cannot ship it. An
42
- * application using these urls must serve a file at each path from its own
43
- * static root, or the badge silently renders as nothing. The application-side
44
- * check is `ui/src/lib/tokens/bundledChainLogo.test.ts`.
45
- *
46
- * This is checked BEFORE either service, so an entry here is the last word — the
47
- * point is to stop depending on a service that has already been observed to fail
48
- * for the chain.
34
+ * gib.show's own key for a chain that Relay reaches us under a pseudo id.
35
+ *
36
+ * THE SERVICE WAS NEVER MISSING THESE CHAINS — WE WERE ASKING BY THE WRONG KEY.
37
+ * gib.show keys every chain by a CAIP-2 style identifier: `eip155-1`,
38
+ * `solana-501`, `bip122-0`. Relay invented its own decimal ids for the
39
+ * non-Ethereum origins (Solana is 792703809, Bitcoin 8253038), and asking
40
+ * gib.show for one of those 404s — which read as "gib.show does not serve
41
+ * Solana" and sent every non-Ethereum badge to a second service. It serves them
42
+ * all, under its own key.
43
+ *
44
+ * Each entry below was verified twice: the url returns 200 with a real PNG, and
45
+ * the picture was LOOKED AT and is the chain's genuine mark. A 200 alone proves
46
+ * nothing here — `/image/999` returns 200 and hands back a completely different
47
+ * chain's logo, which is why an identifier is only listed once someone has seen
48
+ * what comes back.
49
+ *
50
+ * ECLIPSE AND HYPERLIQUID ARE ABSENT ON PURPOSE. Neither appears anywhere in
51
+ * gib.show's network list under any key, so they keep Relay's service. Guessing
52
+ * a plausible identifier for them would be worse than falling back: Hyperliquid
53
+ * in particular collides with an unrelated chain at id 999, so a guess renders
54
+ * the wrong company's logo rather than none at all.
55
+ *
56
+ * The pay-by-hand chains (Zcash and, later, its siblings) are NOT listed by
57
+ * hand here. `bridge-sdk`'s `image-links.ts` is the one place that key is
58
+ * declared — see its `pseudoChainGibShowIdentifier` — and this table spreads
59
+ * that map in, so a pay-by-hand chain added there needs no matching edit here.
49
60
  */
50
- const bundledChainLogoUri = {
51
- [relayChainIds.ton]: 'images/networks/ton.png',
61
+ const gibShowChainIdentifier = {
62
+ [relayChainIds.solana]: 'solana-501',
63
+ [relayChainIds.bitcoin]: 'bip122-0',
64
+ [relayChainIds.tron]: 'tvm-195',
65
+ [relayChainIds.ton]: 'ton-607',
66
+ ...imageLinks.pseudoChainGibShowIdentifier,
52
67
  };
53
68
  /**
54
- * Resolves the logo url for a chain, choosing the service that can actually
55
- * serve it.
69
+ * Resolves the logo url for a chain, choosing the service that can serve it.
56
70
  *
57
- * A chain we bundle the mark for wins outright ({@link bundledChainLogoUri}).
58
- * Otherwise Ethereum-Virtual-Machine chains go to gib.show, honouring the
59
- * configured image root (so a self-hosted deployment is respected) rather than
60
- * hard-coding the public host, and everything else is a Relay identifier and
61
- * goes to Relay.
71
+ * A chain gib.show knows under its own identifier wins outright
72
+ * ({@link gibShowChainIdentifier}). Otherwise an Ethereum-Virtual-Machine chain
73
+ * goes to gib.show by number, honouring the configured image root so a
74
+ * self-hosted deployment is respected rather than hard-coding the public host.
75
+ * What remains is Eclipse and Hyperliquid, which gib.show does not carry, and
76
+ * they go to Relay.
62
77
  *
63
78
  * @param chainId - the decimal chain id, Relay's pseudo identifiers included
64
79
  * @param displayPx - the CSS pixel size the logo renders at; when supplied,
65
80
  * gib.show is asked for twice that in webp per its retina guidance. Ignored
66
- * for a Relay-served icon and for a bundled asset, neither of which takes
67
- * sizing parameters.
81
+ * for a Relay-served icon and for an identifier-keyed mark, neither of which
82
+ * takes sizing parameters.
68
83
  * @returns an image url for the chain
69
84
  */
70
85
  export const chainLogoUrl = ({ chainId, displayPx, }) => {
71
- const bundled = bundledChainLogoUri[chainId];
72
- if (bundled !== undefined) {
73
- return bundled;
86
+ const identifier = gibShowChainIdentifier[chainId];
87
+ if (identifier !== undefined) {
88
+ // gib.show serves this chain, just not under Relay's number. No sizing
89
+ // parameters: these are fixed-size marks rather than the resizable images
90
+ // the numeric endpoint serves.
91
+ return `${imageLinks.getImageRoot()}/image/${identifier}`;
74
92
  }
75
93
  if (ecosystemByChainId(chainId) !== 'evm') {
76
94
  return relayChainIconUrl(chainId);
@@ -81,3 +99,58 @@ export const chainLogoUrl = ({ chainId, displayPx, }) => {
81
99
  }
82
100
  return `${base}?w=${displayPx * 2}&h=${displayPx * 2}&format=webp`;
83
101
  };
102
+ /**
103
+ * The chain a native coin actually belongs to, found by its symbol.
104
+ *
105
+ * WHY THIS IS NEEDED. gib.show serves a coin's own picture under
106
+ * `<chain>/<zero address>`, and it carries that picture only for the chain the
107
+ * coin is native to — not for every chain the coin has been bridged onto. Ether
108
+ * on Base is the case that exposed it: `8453/<zero address>` is a 404, so the
109
+ * lookup fell through to the chain mark and drew Base's blue disc where the
110
+ * ether diamond belongs. The same picture is one source away under `1/<zero
111
+ * address>`, and gib.show's multi-source lookup will cross chains to reach it.
112
+ *
113
+ * READ FROM THE TABLE, NOT FROM A LIST WRITTEN HERE. The generated chain table
114
+ * orders the bridge's own chains first and the rest in the order the product
115
+ * chose, so the EARLIEST entry carrying a symbol is the chain that coin came
116
+ * from: ether resolves to Ethereum ahead of the dozen chains that also call
117
+ * their coin ETH, and the mapping stays right as the table grows. The table is
118
+ * reversed before the map is built because a later `set` wins — reversing makes
119
+ * the earliest entry the last one written.
120
+ *
121
+ * A coin whose home chain has no dedicated picture — Binance Coin, Polygon
122
+ * Ecosystem Token, Avalanche — loses nothing. Their home chain IS the chain
123
+ * they are asked about, so the list collapses to what it already was, and for
124
+ * a chain's own coin the chain mark is the coin mark.
125
+ */
126
+ const homeChainOfSymbol = new Map([...chainTable].reverse().map((entry) => [entry.nativeCurrency.symbol, entry.id]));
127
+ /** Every chain's own native symbol, for resolving a coin from its chain alone. */
128
+ const symbolOfChain = new Map(chainTable.map((entry) => [entry.id, entry.nativeCurrency.symbol]));
129
+ /**
130
+ * The gib.show sources for a chain's NATIVE COIN, best first.
131
+ *
132
+ * The service serves the first source that resolves: the coin as this chain
133
+ * keys it, then the coin as its home chain keys it, then the chain's own mark.
134
+ * The last is an honest fallback rather than a mistake — for a chain's own coin
135
+ * the two marks are the same picture.
136
+ *
137
+ * @param chainId - the chain the coin is being shown on
138
+ * @returns the ordered source list, for {@link imageLinks.images}
139
+ */
140
+ export const nativeCoinImageSources = (chainId) => {
141
+ const sources = [`${chainId}/${zeroAddress}`];
142
+ const symbol = symbolOfChain.get(chainId);
143
+ const home = symbol === undefined ? undefined : homeChainOfSymbol.get(symbol);
144
+ if (home !== undefined && home !== chainId) {
145
+ sources.push(`${home}/${zeroAddress}`);
146
+ }
147
+ sources.push(`${chainId}/`);
148
+ return sources;
149
+ };
150
+ /**
151
+ * Resolves the picture of a chain's native coin, as opposed to of the chain.
152
+ *
153
+ * @param chainId - the chain the coin is being shown on
154
+ * @returns an image url
155
+ */
156
+ export const nativeCoinLogoUrl = (chainId) => imageLinks.images(nativeCoinImageSources(chainId));
package/dist/networks.js CHANGED
@@ -1,10 +1,47 @@
1
- // NAMED imports, deliberately, and never `import * as chains from 'viem/chains'`.
2
- // A namespace import that is read dynamically (`Object.values`, or indexing by a
3
- // computed key) defeats tree-shaking outright: all ~700 chain definitions viem
4
- // ships get emitted to answer a question about the few dozen below, which
5
- // measured 255 kB of an application entry chunk. Naming each one keeps only what
6
- // is named.
7
- import { abstract, arbitrum, aurora, avalanche, base, berachain, blast, boba, bsc, celo, cronos, cronoszkEVM, fantom, fraxtal, fuse, gnosis, gravity, immutableZkEvm, linea, mainnet, mantle, optimism, polygon, polygonZkEvm, pulsechain, pulsechainV4, rootstock, scroll, sei, sepolia, sonic, taiko, unichain, worldchain, zksync, } from 'viem/chains';
1
+ import { registerChainNames } from '@gibs/bridge-sdk/chain-names';
2
+ import { chainTable } from './chain-table.js';
3
+ /**
4
+ * Turns one row of the generated table into the shape viem wants.
5
+ *
6
+ * NAMED VIEM IMPORTS USED TO LIVE HERE — 37 of them, each pulling that chain's
7
+ * whole definition including fee logic, formatters and contract addresses this
8
+ * application never reads. That cost about 3.7 kB compressed for a third of the
9
+ * chains the aggregators quote; naming all 73 would have cost about 7.3 kB
10
+ * against roughly 7.8 kB of headroom under the first-load budget. The generated
11
+ * table carries the same 73 in 2.6 kB, so the whole supported set now weighs
12
+ * less than the third of it that shipped before.
13
+ *
14
+ * A namespace import is still never used, for the reason the old comment here
15
+ * gave: `Object.values(chains)` defeats tree-shaking and emitted all ~674
16
+ * definitions, measured at 255 kB. The generator does that read in Node, at
17
+ * build time, where it costs a reader nothing.
18
+ *
19
+ * `blockExplorers` is omitted rather than faked when a chain has no explorer —
20
+ * viem treats the field as optional and an empty url would render a dead link.
21
+ */
22
+ const toViemChain = (entry) => ({
23
+ id: entry.id,
24
+ name: entry.name,
25
+ nativeCurrency: entry.nativeCurrency,
26
+ rpcUrls: { default: { http: [...entry.http] } },
27
+ ...(entry.explorer === null
28
+ ? {}
29
+ : { blockExplorers: { default: { name: 'Explorer', url: entry.explorer } } }),
30
+ // WITHOUT THIS, NOTHING CAN BATCH A READ. viem's `multicall` action reads the
31
+ // Multicall3 address off the chain and throws `ChainDoesNotSupportContract`
32
+ // when it is absent, so every chain built here refused — including
33
+ // PulseChain, whose token-metadata fallback failed with exactly that.
34
+ //
35
+ // It went missing when this table replaced viem's own chain objects to keep
36
+ // 255 kB of unused per-chain data out of the build. The table kept the four
37
+ // fields a TRANSPORT needs and dropped the one field an ACTION needs.
38
+ //
39
+ // Read per chain, never assumed: the canonical `0xcA11bde0…` covers most, but
40
+ // there are 8 distinct deployments across the chains listed here.
41
+ ...(entry.multicall3 === null
42
+ ? {}
43
+ : { contracts: { multicall3: { address: entry.multicall3 } } }),
44
+ });
8
45
  /**
9
46
  * Every Ethereum-Virtual-Machine chain this client can build a public client for.
10
47
  *
@@ -18,43 +55,7 @@ import { abstract, arbitrum, aurora, avalanche, base, berachain, blast, boba, bs
18
55
  * for them. Anything wallet-facing that needs them keys off the aggregator's
19
56
  * chain identifiers instead.
20
57
  */
21
- export const evmChains = [
22
- mainnet,
23
- pulsechain,
24
- sepolia,
25
- pulsechainV4,
26
- bsc,
27
- arbitrum,
28
- base,
29
- blast,
30
- avalanche,
31
- polygon,
32
- scroll,
33
- optimism,
34
- linea,
35
- zksync,
36
- polygonZkEvm,
37
- gnosis,
38
- fantom,
39
- fuse,
40
- boba,
41
- unichain,
42
- aurora,
43
- sei,
44
- immutableZkEvm,
45
- sonic,
46
- gravity,
47
- taiko,
48
- cronos,
49
- cronoszkEVM,
50
- fraxtal,
51
- abstract,
52
- rootstock,
53
- celo,
54
- worldchain,
55
- mantle,
56
- berachain,
57
- ];
58
+ export const evmChains = chainTable.map(toViemChain);
58
59
  /**
59
60
  * Lookup from a chain id to its viem chain definition, for supported chains only.
60
61
  *
@@ -63,6 +64,12 @@ export const evmChains = [
63
64
  * numeric-versus-string mismatch reads as "chain not supported".
64
65
  */
65
66
  export const evmChainsById = new Map(evmChains.map((chain) => [chain.id, chain]));
67
+ // Hand these names to the SDK's display-name table so ONE function answers
68
+ // "what is this chain called" everywhere. Done at module load, before anything
69
+ // renders, because a name that arrives late is a name that changes under the
70
+ // reader — which is precisely the flicker this removes. The SDK's own entries
71
+ // win on conflict, so the product's chosen spellings are not overwritten.
72
+ registerChainNames(evmChains.map((chain) => [chain.id, chain.name]));
66
73
  /**
67
74
  * Whether this client can reach a chain over Ethereum-style remote procedure calls.
68
75
  *
@@ -1,8 +1,15 @@
1
1
  import { nativeTokenName, nativeTokenSymbol, toChain } from '@gibs/bridge-sdk/config';
2
+ import { isNativeAsset } from '@gibs/bridge-sdk/ecosystems';
2
3
  import * as _ from 'lodash-es';
3
- import { erc20Abi, erc20Abi_bytes32, hexToString, zeroAddress } from 'viem';
4
+ import { erc20Abi, erc20Abi_bytes32, hexToString } from 'viem';
4
5
  import { clientFromChain } from './client.js';
5
6
  import { evmChainsById } from './networks.js';
7
+ /**
8
+ * How many tokens one multicall asks about. Results come back flat, so each
9
+ * loop below MUST pair a result triple with the address chunk that produced it
10
+ * — see the primary loop for what indexing the flat array cost.
11
+ */
12
+ const TOKENS_PER_MULTICALL = 100;
6
13
  const bytes32Whitelist = new Set();
7
14
  // In-memory store for token metadata (loaded once, no persistence)
8
15
  const tokenMetadataStore = new Map();
@@ -81,7 +88,11 @@ async function batchFetchTokenMetadata(chainId, tokenAddresses) {
81
88
  const nativeTokens = [];
82
89
  const erc20Tokens = [];
83
90
  tokenAddresses.forEach((address) => {
84
- if (address === zeroAddress || address.toLowerCase() === zeroAddress.toLowerCase()) {
91
+ // The registry's rule, not a hand-written one. Two spellings of the same
92
+ // comparison sat here — an exact match OR a lower-cased one — which is what
93
+ // a duplicated rule looks like once nobody is sure which half is load
94
+ // bearing.
95
+ if (isNativeAsset(address, 'evm')) {
85
96
  nativeTokens.push(address);
86
97
  }
87
98
  else {
@@ -99,7 +110,7 @@ async function batchFetchTokenMetadata(chainId, tokenAddresses) {
99
110
  return results;
100
111
  }
101
112
  // console.log(`Processing ${erc20Tokens.length} ERC20 tokens for chain ${chainId}:`, erc20Tokens)
102
- const addressChunks = _.chunk(erc20Tokens, 100);
113
+ const addressChunks = _.chunk(erc20Tokens, TOKENS_PER_MULTICALL);
103
114
  const methods = ['symbol', 'name', 'decimals'];
104
115
  const addressChunksToCallChunks = (addressChunks, abi) => addressChunks.map((chunk) => chunk.map((address) => methods.map((functionName) => ({
105
116
  functionName,
@@ -111,16 +122,23 @@ async function batchFetchTokenMetadata(chainId, tokenAddresses) {
111
122
  // Try with standard ERC20 ABI first
112
123
  const chunkedCalls = addressChunksToCallChunks(addressChunks, (address) => bytes32Whitelist.has(getStoreKey(chainId, address)) ? erc20Abi_bytes32 : erc20Abi);
113
124
  const fallback = [];
114
- for (const chunk of chunkedCalls) {
115
- // console.log(`Executing multicall for chain ${chainId} with ${_.flatten(chunk).length} contracts`)
125
+ for (const [chunkIndex, chunk] of chunkedCalls.entries()) {
126
+ // The addresses THIS chunk asked about. Indexing the flat `erc20Tokens`
127
+ // here instead was a silent data-corruption bug: `index` restarts at 0 for
128
+ // every chunk, so with more than TOKENS_PER_MULTICALL tokens on one chain,
129
+ // the second chunk's results were stored under the FIRST chunk's addresses
130
+ // — every token past the first 100 got another token's name, symbol and
131
+ // decimals. A wrong `decimals` misprints every amount rendered for that
132
+ // token. Reading from the same partition that built the calls makes the
133
+ // pairing structural rather than arithmetic.
134
+ const chunkAddresses = addressChunks[chunkIndex];
116
135
  try {
117
136
  const batchResults = await client.multicall({
118
137
  allowFailure: true,
119
138
  contracts: _.flatten(chunk),
120
139
  });
121
- // console.log(`Multicall results for chain ${chainId}:`, batchResults.length, 'results')
122
140
  _.chunk(batchResults, 3).forEach((batch, index) => {
123
- const tokenAddress = erc20Tokens[index];
141
+ const tokenAddress = chunkAddresses[index];
124
142
  const symbol = batch[0].result;
125
143
  const name = batch[1].result;
126
144
  const decimals = Number(batch[2].result);
@@ -136,17 +154,21 @@ async function batchFetchTokenMetadata(chainId, tokenAddresses) {
136
154
  }
137
155
  catch (error) {
138
156
  console.error(`Multicall failed for chain ${chainId}:`, error);
139
- // Add all tokens to fallback if multicall fails completely
140
- fallback.push(...erc20Tokens);
157
+ // Only THIS chunk's addresses. Pushing every token meant one failing
158
+ // chunk re-queued the whole set through the bytes32 fallback, including
159
+ // the tokens that had just succeeded.
160
+ fallback.push(...chunkAddresses);
141
161
  }
142
162
  }
143
163
  if (fallback.length === 0) {
144
164
  return results;
145
165
  }
146
166
  console.log(`Processing ${fallback.length} tokens in fallback with bytes32 ABI for chain ${chainId}:`, fallback);
147
- const fallbackAddressChunks = _.chunk(fallback, 100);
167
+ const fallbackAddressChunks = _.chunk(fallback, TOKENS_PER_MULTICALL);
148
168
  const fallbackChunkedCalls = addressChunksToCallChunks(fallbackAddressChunks, () => erc20Abi_bytes32);
149
- for (const chunk of fallbackChunkedCalls) {
169
+ for (const [chunkIndex, chunk] of fallbackChunkedCalls.entries()) {
170
+ // Same pairing rule as the primary loop above.
171
+ const chunkAddresses = fallbackAddressChunks[chunkIndex];
150
172
  try {
151
173
  console.log(`Executing fallback multicall for chain ${chainId} with ${_.flatten(chunk).length} contracts`);
152
174
  const batchResults = await client.multicall({
@@ -155,7 +177,7 @@ async function batchFetchTokenMetadata(chainId, tokenAddresses) {
155
177
  });
156
178
  console.log(`Fallback multicall results for chain ${chainId}:`, batchResults.length, 'results');
157
179
  _.chunk(batchResults, 3).forEach((batch, index) => {
158
- const tokenAddress = fallback[index];
180
+ const tokenAddress = chunkAddresses[index];
159
181
  const symbol = batch[0];
160
182
  const name = batch[1];
161
183
  const decimals = Number(batch[2]);
package/package.json CHANGED
@@ -1,13 +1,32 @@
1
1
  {
2
2
  "name": "@gibs/bridge-client",
3
- "version": "1.12.0",
4
- "description": "Browser-side data loading, caching and endpoint resolution for the Gibs Finance bridge",
3
+ "version": "1.13.0",
4
+ "description": "Browser-side endpoints, clients, and caching for a Gibs Finance bridge interface.",
5
+ "keywords": [
6
+ "ethereum",
7
+ "pulsechain",
8
+ "bridge",
9
+ "viem",
10
+ "browser"
11
+ ],
12
+ "license": "ISC",
13
+ "author": "Gibs Finance",
14
+ "homepage": "https://gibs.finance",
15
+ "repository": {
16
+ "type": "git",
17
+ "url": "git+https://github.com/gibsfinance/bridge.git",
18
+ "directory": "packages/bridge-client"
19
+ },
20
+ "bugs": {
21
+ "url": "https://github.com/gibsfinance/bridge/issues"
22
+ },
23
+ "engines": {
24
+ "node": ">=20"
25
+ },
5
26
  "type": "module",
27
+ "sideEffects": false,
6
28
  "main": "./dist/index.js",
7
29
  "types": "./dist/index.d.ts",
8
- "files": [
9
- "dist"
10
- ],
11
30
  "exports": {
12
31
  ".": {
13
32
  "gibs-source": "./src/index.ts",
@@ -50,27 +69,35 @@
50
69
  "default": "./dist/token-metadata.js"
51
70
  }
52
71
  },
53
- "publishConfig": {
54
- "access": "public"
55
- },
72
+ "files": [
73
+ "dist",
74
+ "!dist/tsconfig.tsbuildinfo",
75
+ "!dist/**/*.test-d.*",
76
+ "!dist/test-utils",
77
+ "LICENSE"
78
+ ],
56
79
  "scripts": {
80
+ "prepublishOnly": "npx tsc",
57
81
  "build": "npx tsc",
58
82
  "test": "vitest run",
59
83
  "test:watch": "vitest"
60
84
  },
61
- "keywords": [],
62
- "author": "",
63
- "license": "ISC",
85
+ "dependencies": {
86
+ "@gibs/bridge-sdk": "^1.13.0",
87
+ "@gibs/common": "^1.13.0",
88
+ "lodash-es": "^4.17.21"
89
+ },
90
+ "peerDependencies": {
91
+ "viem": ">=2.37.0 <3"
92
+ },
64
93
  "devDependencies": {
65
94
  "@types/lodash-es": "^4.17.12",
66
95
  "@types/node": "^24",
67
96
  "typescript": "~5.7.2",
97
+ "viem": "^2.46.3",
68
98
  "vitest": "^3.1.1"
69
99
  },
70
- "dependencies": {
71
- "@gibs/bridge-sdk": "^1.12.0",
72
- "@gibs/common": "^1.12.0",
73
- "lodash-es": "^4.17.21",
74
- "viem": "^2.46.3"
100
+ "publishConfig": {
101
+ "access": "public"
75
102
  }
76
103
  }