@gibs/bridge-client 1.12.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.
@@ -0,0 +1,9 @@
1
+ export declare class Cache {
2
+ protected ttl: number;
3
+ cache: Map<string, {
4
+ time: number;
5
+ value: unknown;
6
+ }>;
7
+ constructor(ttl?: number);
8
+ clearStale(): number;
9
+ }
package/dist/cache.js ADDED
@@ -0,0 +1,17 @@
1
+ export class Cache {
2
+ ttl;
3
+ cache = new Map();
4
+ constructor(ttl = 1000 * 60) {
5
+ this.ttl = ttl;
6
+ }
7
+ clearStale() {
8
+ const now = Date.now();
9
+ for (const k of this.cache.keys()) {
10
+ const { time } = this.cache.get(k);
11
+ if (now - time > this.ttl) {
12
+ this.cache.delete(k);
13
+ }
14
+ }
15
+ return now;
16
+ }
17
+ }
@@ -0,0 +1,27 @@
1
+ import { type Hex } from 'viem';
2
+ /** Inputs identifying an ERC-20 allowance to read: which token, owner, and spender on which chain. */
3
+ export type ApprovalParameters = {
4
+ /** The ERC-20 token contract address. */
5
+ token: Hex;
6
+ /** The address allowed to spend the owner's tokens. */
7
+ spender: Hex;
8
+ /** The chain the token lives on. */
9
+ chainId: number;
10
+ /** The token owner whose allowance is being read. */
11
+ account: Hex;
12
+ };
13
+ /**
14
+ * Reads an ERC-20 allowance for the given owner/spender pair on a chain.
15
+ *
16
+ * The native asset (`zeroAddress`) has no allowance concept, so it returns
17
+ * `maxUint256` (always sufficient). A missing or malformed owner address throws,
18
+ * matching the Svelte source — callers that may lack an account must guard first.
19
+ *
20
+ * Ported verbatim from `chain-read.svelte.ts`; the only change is sourcing
21
+ * {@link clientFromChain} from the framework-agnostic `wallet/clientFromChain`
22
+ * module instead of the Svelte `input.svelte` rune store.
23
+ *
24
+ * @param params The token, spender, chain, and owner to read.
25
+ * @returns The allowance in token base units.
26
+ */
27
+ export declare const checkAllowance: ({ token, spender, chainId, account }: ApprovalParameters) => Promise<bigint>;
@@ -0,0 +1,31 @@
1
+ import { erc20Abi, isAddress, maxUint256, zeroAddress } from 'viem';
2
+ import { clientFromChain } from './client.js';
3
+ /**
4
+ * Reads an ERC-20 allowance for the given owner/spender pair on a chain.
5
+ *
6
+ * The native asset (`zeroAddress`) has no allowance concept, so it returns
7
+ * `maxUint256` (always sufficient). A missing or malformed owner address throws,
8
+ * matching the Svelte source — callers that may lack an account must guard first.
9
+ *
10
+ * Ported verbatim from `chain-read.svelte.ts`; the only change is sourcing
11
+ * {@link clientFromChain} from the framework-agnostic `wallet/clientFromChain`
12
+ * module instead of the Svelte `input.svelte` rune store.
13
+ *
14
+ * @param params The token, spender, chain, and owner to read.
15
+ * @returns The allowance in token base units.
16
+ */
17
+ export const checkAllowance = async ({ token, spender, chainId, account }) => {
18
+ if (token === zeroAddress) {
19
+ return maxUint256;
20
+ }
21
+ const client = clientFromChain(chainId);
22
+ if (!account || !isAddress(account)) {
23
+ throw new Error('No account');
24
+ }
25
+ return await client.readContract({
26
+ address: token,
27
+ abi: erc20Abi,
28
+ functionName: 'allowance',
29
+ args: [account, spender],
30
+ });
31
+ };
@@ -0,0 +1,27 @@
1
+ import type { PublicClient } from 'viem';
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.
6
+ *
7
+ * @param chainId - the chain to resolve the batch setting for
8
+ * @returns `false` when batching must be disabled, otherwise `undefined`
9
+ */
10
+ export declare const rpcBatchSetting: (chainId: number) => false | undefined;
11
+ /**
12
+ * Returns a viem {@link PublicClient} for a chain, reusing the cached instance
13
+ * while the chain's endpoint list is unchanged. The cache key is derived from the
14
+ * chain id plus its urls, so editing the list yields a fresh client and lets the
15
+ * previous transport be garbage collected.
16
+ *
17
+ * The url source is the user-editable endpoint table; when it has no entry — or
18
+ * an EMPTY one — the chain's own defaults are used instead. The empty case is not
19
+ * hypothetical: the endpoint editor lets a user delete every entry for a chain,
20
+ * and an empty array is truthy, so a `||` fallback here quietly built a client
21
+ * with no transports at all and every read on that chain failed until the list
22
+ * was restored.
23
+ *
24
+ * @param chainId - the chain to build a client for
25
+ * @returns the cached or freshly built public client
26
+ */
27
+ export declare const clientFromChain: (chainId: number) => PublicClient;
package/dist/client.js ADDED
@@ -0,0 +1,78 @@
1
+ import { chainKey, clientCache, clientFromChain as buildClient } from '@gibs/common/client';
2
+ import * as _ from 'lodash-es';
3
+ import { bsc } from 'viem/chains';
4
+ import * as endpoints from './endpoints.js';
5
+ import { evmChainsById } from './networks.js';
6
+ /**
7
+ * Chains whose public nodes reject BATCH remote procedure call requests (an
8
+ * array-wrapped body). viem otherwise wraps even a single request in an array
9
+ * when batching is on, which those nodes refuse — so every call fails and is
10
+ * retried, burning through requests. Their clients are built with batching
11
+ * disabled so requests go out as plain objects the node accepts. Binance Smart
12
+ * Chain's `publicnode` endpoint is the known offender.
13
+ */
14
+ const chainsWithoutRpcBatching = new Set([bsc.id]);
15
+ /**
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.
19
+ *
20
+ * @param chainId - the chain to resolve the batch setting for
21
+ * @returns `false` when batching must be disabled, otherwise `undefined`
22
+ */
23
+ export const rpcBatchSetting = (chainId) => chainsWithoutRpcBatching.has(chainId) ? false : undefined;
24
+ /**
25
+ * A chain's own default endpoints, used when the editable table has no entry.
26
+ *
27
+ * @param chainId - the chain to look up
28
+ * @returns the chain's default endpoints (possibly empty)
29
+ */
30
+ const defaultUrlsFor = (chainId) => {
31
+ if (!chainId) {
32
+ throw new Error('chainId is required');
33
+ }
34
+ const chain = evmChainsById.get(chainId);
35
+ if (!chain) {
36
+ return [];
37
+ }
38
+ const { http } = chain.rpcUrls.default;
39
+ return [...(http ?? [])];
40
+ };
41
+ /**
42
+ * Returns a viem {@link PublicClient} for a chain, reusing the cached instance
43
+ * while the chain's endpoint list is unchanged. The cache key is derived from the
44
+ * chain id plus its urls, so editing the list yields a fresh client and lets the
45
+ * previous transport be garbage collected.
46
+ *
47
+ * The url source is the user-editable endpoint table; when it has no entry — or
48
+ * an EMPTY one — the chain's own defaults are used instead. The empty case is not
49
+ * hypothetical: the endpoint editor lets a user delete every entry for a chain,
50
+ * and an empty array is truthy, so a `||` fallback here quietly built a client
51
+ * with no transports at all and every read on that chain failed until the list
52
+ * was restored.
53
+ *
54
+ * @param chainId - the chain to build a client for
55
+ * @returns the cached or freshly built public client
56
+ */
57
+ export const clientFromChain = (chainId) => {
58
+ const stored = endpoints.get(chainId);
59
+ const urls = _.compact(stored?.length ? stored : defaultUrlsFor(chainId));
60
+ const key = chainKey(chainId, urls);
61
+ const existing = clientCache.get(chainId);
62
+ if (existing && existing.key === key) {
63
+ return existing.client;
64
+ }
65
+ const chain = evmChainsById.get(chainId);
66
+ if (!chain) {
67
+ // Previously this searched every chain viem ships and asserted a hit with a
68
+ // `!`. That answered for chains this application does not support, and did
69
+ // so badly: there are no endpoints for them, so the client was built with
70
+ // none and silently fell back to viem's own public node — or, for a chain
71
+ // viem had never heard of, died on an unreadable `undefined.id` several
72
+ // frames away. Both are worse than saying so here.
73
+ throw new Error(`Chain ${chainId} is not one of the supported networks.`);
74
+ }
75
+ const client = buildClient({ chain, urls, batch: rpcBatchSetting(chainId) });
76
+ clientCache.set(chainId, { key, client });
77
+ return client;
78
+ };
@@ -0,0 +1,99 @@
1
+ /**
2
+ * The user-editable table of remote procedure call endpoints, per chain.
3
+ *
4
+ * This is the single source every read in the application resolves through, so
5
+ * that replacing a blocked node in one place moves every balance read, every
6
+ * receipt poll and every contract call at once.
7
+ *
8
+ * It is a plain external store — `subscribe` plus `getSnapshot` — rather than a
9
+ * hook, so it can be consumed by React through `useSyncExternalStore`, by another
10
+ * framework, or by nothing at all. The React binding lives with the application;
11
+ * nothing here imports a rendering library.
12
+ */
13
+ /** One chain's identifier paired with its endpoint list. */
14
+ export type EndpointEntry = [chainId: number, urls: string[]];
15
+ /**
16
+ * The minimal shape of browser storage this module needs.
17
+ *
18
+ * Injectable rather than reaching for `localStorage` directly: the store then
19
+ * works under a test runner, on a server, and in a host that keeps preferences
20
+ * somewhere other than the browser — and a test can assert what was persisted
21
+ * without installing a global.
22
+ */
23
+ export type EndpointStorage = {
24
+ getItem(key: string): string | null;
25
+ setItem(key: string, value: string): void;
26
+ };
27
+ /**
28
+ * The storage key holding the endpoint table.
29
+ *
30
+ * The literal must not change: users have their own nodes saved under it, and a
31
+ * rename silently reverts every one of them to the defaults on the next load.
32
+ */
33
+ export declare const ENDPOINTS_STORAGE_KEY = "rpcs";
34
+ /** The bridge's production chains. */
35
+ export declare const productionSeed: EndpointEntry[];
36
+ /** The bridge's test chains, for a build that is not production. */
37
+ export declare const testnetSeed: EndpointEntry[];
38
+ /**
39
+ * Replaces the seed table and the persistence target.
40
+ *
41
+ * Both fields are optional so a host can override one without restating the
42
+ * other. The loaded table is discarded either way, so the next read rebuilds
43
+ * from the new seed merged with whatever is stored — which means this is safe to
44
+ * call after something has already read, not only at startup.
45
+ *
46
+ * @param options.seed - the entries to start from when storage holds nothing
47
+ * @param options.storage - where the table is persisted, or null for nowhere
48
+ */
49
+ export declare const configureEndpoints: ({ seed: nextSeed, storage: nextStorage, }: {
50
+ seed?: EndpointEntry[];
51
+ storage?: EndpointStorage | null;
52
+ }) => void;
53
+ /** Serializes one chain's list the way it is persisted: `chainId,url,url`. */
54
+ export declare const key: (chain: number, list: string[]) => string;
55
+ /**
56
+ * Parses a stored blob back into a table, merged onto the seeded entries.
57
+ *
58
+ * @param entry - the persisted blob, or null when nothing is stored
59
+ * @returns the merged table, or null when there was nothing to parse
60
+ */
61
+ export declare const parse: (entry: string | null) => Map<number, string[]> | null;
62
+ /** Serializes every entry, one chain per line. */
63
+ export declare const stringify: () => string;
64
+ /**
65
+ * Subscribes to changes in the endpoint table.
66
+ *
67
+ * @param listener - called after any mutation
68
+ * @returns the unsubscribe function
69
+ */
70
+ export declare const subscribe: (listener: () => void) => (() => void);
71
+ /**
72
+ * The current table.
73
+ *
74
+ * The identity is stable until something changes it — every mutating accessor
75
+ * replaces the map rather than mutating in place — which is what lets a caller
76
+ * use this directly as a `useSyncExternalStore` snapshot without re-rendering
77
+ * forever.
78
+ */
79
+ export declare const getSnapshot: () => Map<number, string[]>;
80
+ /** The url list for a chain, or null when it has no entry. */
81
+ export declare const get: (chain: number) => string[] | null;
82
+ /** All `[chainId, urls]` entries as a fresh array. */
83
+ export declare const entries: () => [number, string[]][];
84
+ /** Replaces a chain's url list, persists it, and notifies subscribers. */
85
+ export declare const setEndpoints: (chain: number, list: string[]) => void;
86
+ /** Replaces the url at index `i`; a no-op when the value is unchanged. */
87
+ export declare const update: (chain: number, i: number, url: string) => void;
88
+ /** Removes the url at index `i`. */
89
+ export declare const remove: (chain: number, i: number) => void;
90
+ /**
91
+ * Applies `addition` to a chain's url list and stores the result, keeping only
92
+ * the first item if it is empty (the leading "add new" slot) and dropping every
93
+ * other empty. A no-op when the filtered list is unchanged.
94
+ */
95
+ export declare const add: (chain: number, addition?: (urls: string[]) => string[]) => void;
96
+ /** Whether `list` already contains the chain's canonical default endpoint. */
97
+ export declare const hasDefault: (chain: number, list: string[]) => boolean;
98
+ /** Appends the chain's canonical default endpoints back onto its list. */
99
+ export declare const restoreDefault: (chain: number) => void;
@@ -0,0 +1,175 @@
1
+ import { chainsMetadata } from '@gibs/bridge-sdk/chains';
2
+ import { Chains, toChain } from '@gibs/bridge-sdk/config';
3
+ import * as _ from 'lodash-es';
4
+ /**
5
+ * The storage key holding the endpoint table.
6
+ *
7
+ * The literal must not change: users have their own nodes saved under it, and a
8
+ * rename silently reverts every one of them to the defaults on the next load.
9
+ */
10
+ export const ENDPOINTS_STORAGE_KEY = 'rpcs';
11
+ /** Builds a seed table from the bridge metadata for the given chains. */
12
+ 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]);
15
+ /** The bridge's test chains, for a build that is not production. */
16
+ export const testnetSeed = seedFor([Chains.V4PLS, Chains.SEP]);
17
+ /** The seed the next load starts from. */
18
+ let seed = productionSeed;
19
+ /** Where the table is persisted, or null when there is nowhere. */
20
+ let storage = typeof localStorage === 'undefined' ? null : localStorage;
21
+ /** The live table, or null while it has not been loaded yet. */
22
+ let value = null;
23
+ const listeners = new Set();
24
+ const emit = () => {
25
+ for (const listener of listeners) {
26
+ listener();
27
+ }
28
+ };
29
+ /**
30
+ * Replaces the seed table and the persistence target.
31
+ *
32
+ * Both fields are optional so a host can override one without restating the
33
+ * other. The loaded table is discarded either way, so the next read rebuilds
34
+ * from the new seed merged with whatever is stored — which means this is safe to
35
+ * call after something has already read, not only at startup.
36
+ *
37
+ * @param options.seed - the entries to start from when storage holds nothing
38
+ * @param options.storage - where the table is persisted, or null for nowhere
39
+ */
40
+ export const configureEndpoints = ({ seed: nextSeed, storage: nextStorage, }) => {
41
+ if (nextSeed !== undefined) {
42
+ seed = nextSeed;
43
+ }
44
+ if (nextStorage !== undefined) {
45
+ storage = nextStorage;
46
+ }
47
+ value = null;
48
+ emit();
49
+ };
50
+ /** Serializes one chain's list the way it is persisted: `chainId,url,url`. */
51
+ export const key = (chain, list) => `${chain},${list.join(',')}`;
52
+ /**
53
+ * Parses a stored blob back into a table, merged onto the seeded entries.
54
+ *
55
+ * @param entry - the persisted blob, or null when nothing is stored
56
+ * @returns the merged table, or null when there was nothing to parse
57
+ */
58
+ export const parse = (entry) => {
59
+ if (!entry) {
60
+ return null;
61
+ }
62
+ const stored = entry.split('\n').map((row) => {
63
+ const [chain, ...list] = row.split(',');
64
+ return [Number(chain), _.compact(list)];
65
+ });
66
+ return new Map([...seed, ...stored]);
67
+ };
68
+ /**
69
+ * Reads the persisted table, or null when there is nothing usable to read.
70
+ *
71
+ * Any failure falls back to the seeded defaults rather than propagating: a
72
+ * corrupt or unreadable entry must not be the reason the application cannot
73
+ * reach a chain at all.
74
+ */
75
+ const readStored = () => {
76
+ if (!storage) {
77
+ return null;
78
+ }
79
+ try {
80
+ return parse(storage.getItem(ENDPOINTS_STORAGE_KEY));
81
+ }
82
+ catch (error) {
83
+ console.error('unable to read the stored endpoint list', error);
84
+ return null;
85
+ }
86
+ };
87
+ /** The table, loaded from storage the first time it is asked for. */
88
+ const loaded = () => {
89
+ if (!value) {
90
+ value = readStored() ?? new Map(seed);
91
+ }
92
+ return value;
93
+ };
94
+ /** Serializes every entry, one chain per line. */
95
+ export const stringify = () => [...loaded().entries()].map(([chainId, list]) => key(chainId, list)).join('\n');
96
+ /**
97
+ * Subscribes to changes in the endpoint table.
98
+ *
99
+ * @param listener - called after any mutation
100
+ * @returns the unsubscribe function
101
+ */
102
+ export const subscribe = (listener) => {
103
+ listeners.add(listener);
104
+ return () => {
105
+ listeners.delete(listener);
106
+ };
107
+ };
108
+ /**
109
+ * The current table.
110
+ *
111
+ * The identity is stable until something changes it — every mutating accessor
112
+ * replaces the map rather than mutating in place — which is what lets a caller
113
+ * use this directly as a `useSyncExternalStore` snapshot without re-rendering
114
+ * forever.
115
+ */
116
+ export const getSnapshot = () => loaded();
117
+ /** The url list for a chain, or null when it has no entry. */
118
+ export const get = (chain) => loaded().get(chain) ?? null;
119
+ /** All `[chainId, urls]` entries as a fresh array. */
120
+ export const entries = () => [...loaded().entries()];
121
+ /** Replaces a chain's url list, persists it, and notifies subscribers. */
122
+ export const setEndpoints = (chain, list) => {
123
+ const next = new Map(loaded());
124
+ next.set(chain, list);
125
+ value = next;
126
+ storage?.setItem(ENDPOINTS_STORAGE_KEY, stringify());
127
+ emit();
128
+ };
129
+ /** Replaces the url at index `i`; a no-op when the value is unchanged. */
130
+ export const update = (chain, i, url) => {
131
+ const list = get(chain) ?? [];
132
+ if (list[i] === url) {
133
+ return;
134
+ }
135
+ const next = list.slice(0);
136
+ next[i] = url;
137
+ setEndpoints(chain, next);
138
+ };
139
+ /** Removes the url at index `i`. */
140
+ export const remove = (chain, i) => {
141
+ const next = (get(chain) ?? []).slice(0);
142
+ next.splice(i, 1);
143
+ setEndpoints(chain, next);
144
+ };
145
+ /**
146
+ * Applies `addition` to a chain's url list and stores the result, keeping only
147
+ * the first item if it is empty (the leading "add new" slot) and dropping every
148
+ * other empty. A no-op when the filtered list is unchanged.
149
+ */
150
+ export const add = (chain, addition = (urls) => [''].concat(urls)) => {
151
+ const before = get(chain) ?? [];
152
+ const list = addition(before.slice(0));
153
+ const filteredList = list.filter((val, i) => {
154
+ // only the first item in the list may be empty
155
+ if (!val && !i) {
156
+ return true;
157
+ }
158
+ return !!val;
159
+ });
160
+ if (_.isEqual(before, filteredList)) {
161
+ return;
162
+ }
163
+ setEndpoints(chain, filteredList);
164
+ };
165
+ /** Whether `list` already contains the chain's canonical default endpoint. */
166
+ export const hasDefault = (chain, list) => {
167
+ const defaultUrl = chainsMetadata[toChain(chain)].rpcUrls.default.http[0];
168
+ if (!defaultUrl)
169
+ return false;
170
+ return list.includes(defaultUrl);
171
+ };
172
+ /** Appends the chain's canonical default endpoints back onto its list. */
173
+ export const restoreDefault = (chain) => {
174
+ add(chain, (l) => l.concat(chainsMetadata[toChain(chain)].rpcUrls.default.http));
175
+ };
@@ -0,0 +1,46 @@
1
+ /**
2
+ * Chain-logo resolution, in one place.
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.
12
+ */
13
+ /**
14
+ * Relay's public chain-icon service, keyed on Relay's own chain identifiers —
15
+ * the authority for the ids it invented.
16
+ *
17
+ * The `light` variant is the full-colour brand mark (the pair is light/dark
18
+ * 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
+ *
22
+ * @param chainId - a Relay chain identifier
23
+ * @returns the icon url
24
+ */
25
+ export declare const relayChainIconUrl: (chainId: number) => string;
26
+ /**
27
+ * Resolves the logo url for a chain, choosing the service that can actually
28
+ * serve it.
29
+ *
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.
35
+ *
36
+ * @param chainId - the decimal chain id, Relay's pseudo identifiers included
37
+ * @param displayPx - the CSS pixel size the logo renders at; when supplied,
38
+ * 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.
41
+ * @returns an image url for the chain
42
+ */
43
+ export declare const chainLogoUrl: ({ chainId, displayPx, }: {
44
+ chainId: number;
45
+ displayPx?: number;
46
+ }) => string;
package/dist/image.js ADDED
@@ -0,0 +1,83 @@
1
+ import * as imageLinks from '@gibs/bridge-sdk/image-links';
2
+ import { ecosystemByChainId, relayChainIds } from '@gibs/bridge-sdk/relay-quote';
3
+ /**
4
+ * Chain-logo resolution, in one place.
5
+ *
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.
14
+ */
15
+ /**
16
+ * Relay's public chain-icon service, keyed on Relay's own chain identifiers —
17
+ * the authority for the ids it invented.
18
+ *
19
+ * The `light` variant is the full-colour brand mark (the pair is light/dark
20
+ * 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.
23
+ *
24
+ * @param chainId - a Relay chain identifier
25
+ * @returns the icon url
26
+ */
27
+ export const relayChainIconUrl = (chainId) => `https://assets.relay.link/icons/${chainId}/light.png`;
28
+ /**
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.
49
+ */
50
+ const bundledChainLogoUri = {
51
+ [relayChainIds.ton]: 'images/networks/ton.png',
52
+ };
53
+ /**
54
+ * Resolves the logo url for a chain, choosing the service that can actually
55
+ * serve it.
56
+ *
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.
62
+ *
63
+ * @param chainId - the decimal chain id, Relay's pseudo identifiers included
64
+ * @param displayPx - the CSS pixel size the logo renders at; when supplied,
65
+ * 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.
68
+ * @returns an image url for the chain
69
+ */
70
+ export const chainLogoUrl = ({ chainId, displayPx, }) => {
71
+ const bundled = bundledChainLogoUri[chainId];
72
+ if (bundled !== undefined) {
73
+ return bundled;
74
+ }
75
+ if (ecosystemByChainId(chainId) !== 'evm') {
76
+ return relayChainIconUrl(chainId);
77
+ }
78
+ const base = imageLinks.network(chainId);
79
+ if (displayPx === undefined) {
80
+ return base;
81
+ }
82
+ return `${base}?w=${displayPx * 2}&h=${displayPx * 2}&format=webp`;
83
+ };
@@ -0,0 +1,22 @@
1
+ /**
2
+ * Browser-side machinery for talking to the chains the bridge supports.
3
+ *
4
+ * Everything here is framework-free. It is the layer between `@gibs/bridge-sdk`,
5
+ * which knows about pathways, quotes and fees but performs no input or output,
6
+ * and an application, which renders. Endpoint resolution, public clients, chain
7
+ * reads, token metadata, caching and logo resolution all live here so that a
8
+ * second application does not have to rebuild them — and so that the rules they
9
+ * encode (which node answers for a chain, which image service knows a chain's
10
+ * mark) have exactly one home.
11
+ *
12
+ * Subpath exports are the intended way in — `@gibs/bridge-client/client`,
13
+ * `/endpoints`, `/image` and so on — so that importing one does not pull the
14
+ * rest. This barrel exists for consumers that would rather not think about it.
15
+ */
16
+ export { Cache } from './cache.js';
17
+ export { checkAllowance, type ApprovalParameters } from './chain-read.js';
18
+ export { clientFromChain, rpcBatchSetting } from './client.js';
19
+ export { add, configureEndpoints, ENDPOINTS_STORAGE_KEY, entries, get, getSnapshot, hasDefault, key, parse, productionSeed, remove, restoreDefault, setEndpoints, stringify, subscribe, testnetSeed, update, type EndpointEntry, type EndpointStorage, } from './endpoints.js';
20
+ export { chainLogoUrl, relayChainIconUrl } from './image.js';
21
+ export { evmChains, evmChainsById, isSupportedEvmChain } from './networks.js';
22
+ export { getBridgeTokenMetadata, getStoreKey, getStoreSize, getTokenAddressFromBridge, getTokenChainIdFromBridge, getTokenMetadata, loadTokenMetadata, parseChainScopedAddress, parseTokenKey, type ChainScopedAddress, } from './token-metadata.js';
package/dist/index.js ADDED
@@ -0,0 +1,22 @@
1
+ /**
2
+ * Browser-side machinery for talking to the chains the bridge supports.
3
+ *
4
+ * Everything here is framework-free. It is the layer between `@gibs/bridge-sdk`,
5
+ * which knows about pathways, quotes and fees but performs no input or output,
6
+ * and an application, which renders. Endpoint resolution, public clients, chain
7
+ * reads, token metadata, caching and logo resolution all live here so that a
8
+ * second application does not have to rebuild them — and so that the rules they
9
+ * encode (which node answers for a chain, which image service knows a chain's
10
+ * mark) have exactly one home.
11
+ *
12
+ * Subpath exports are the intended way in — `@gibs/bridge-client/client`,
13
+ * `/endpoints`, `/image` and so on — so that importing one does not pull the
14
+ * rest. This barrel exists for consumers that would rather not think about it.
15
+ */
16
+ export { Cache } from './cache.js';
17
+ export { checkAllowance } from './chain-read.js';
18
+ export { clientFromChain, rpcBatchSetting } from './client.js';
19
+ export { add, configureEndpoints, ENDPOINTS_STORAGE_KEY, entries, get, getSnapshot, hasDefault, key, parse, productionSeed, remove, restoreDefault, setEndpoints, stringify, subscribe, testnetSeed, update, } from './endpoints.js';
20
+ export { chainLogoUrl, relayChainIconUrl } from './image.js';
21
+ export { evmChains, evmChainsById, isSupportedEvmChain } from './networks.js';
22
+ export { getBridgeTokenMetadata, getStoreKey, getStoreSize, getTokenAddressFromBridge, getTokenChainIdFromBridge, getTokenMetadata, loadTokenMetadata, parseChainScopedAddress, parseTokenKey, } from './token-metadata.js';
@@ -0,0 +1,30 @@
1
+ import type { Chain } from 'viem';
2
+ /**
3
+ * Every Ethereum-Virtual-Machine chain this client can build a public client for.
4
+ *
5
+ * The leading entries are the bridge's own chains; the rest are the routing
6
+ * destinations reachable through the aggregators. Ordering is preserved because
7
+ * chain pickers render it directly.
8
+ *
9
+ * Solana and Bitcoin are deliberately absent. They are supported ORIGINS, but
10
+ * they are not Ethereum-style chains: they are keyed by a genesis hash and by
11
+ * their own string rather than by a number, and nothing here can build a client
12
+ * for them. Anything wallet-facing that needs them keys off the aggregator's
13
+ * chain identifiers instead.
14
+ */
15
+ export declare const evmChains: readonly Chain[];
16
+ /**
17
+ * Lookup from a chain id to its viem chain definition, for supported chains only.
18
+ *
19
+ * Keyed as `string | number` because callers hold ids that have been through a
20
+ * url, a wallet, or a quote response, and a lookup that silently misses on a
21
+ * numeric-versus-string mismatch reads as "chain not supported".
22
+ */
23
+ export declare const evmChainsById: Map<string | number, Chain>;
24
+ /**
25
+ * Whether this client can reach a chain over Ethereum-style remote procedure calls.
26
+ *
27
+ * @param chainId - the chain id to test
28
+ * @returns true when the chain has a definition here
29
+ */
30
+ export declare const isSupportedEvmChain: (chainId: number) => boolean;
@@ -0,0 +1,72 @@
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';
8
+ /**
9
+ * Every Ethereum-Virtual-Machine chain this client can build a public client for.
10
+ *
11
+ * The leading entries are the bridge's own chains; the rest are the routing
12
+ * destinations reachable through the aggregators. Ordering is preserved because
13
+ * chain pickers render it directly.
14
+ *
15
+ * Solana and Bitcoin are deliberately absent. They are supported ORIGINS, but
16
+ * they are not Ethereum-style chains: they are keyed by a genesis hash and by
17
+ * their own string rather than by a number, and nothing here can build a client
18
+ * for them. Anything wallet-facing that needs them keys off the aggregator's
19
+ * chain identifiers instead.
20
+ */
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
+ /**
59
+ * Lookup from a chain id to its viem chain definition, for supported chains only.
60
+ *
61
+ * Keyed as `string | number` because callers hold ids that have been through a
62
+ * url, a wallet, or a quote response, and a lookup that silently misses on a
63
+ * numeric-versus-string mismatch reads as "chain not supported".
64
+ */
65
+ export const evmChainsById = new Map(evmChains.map((chain) => [chain.id, chain]));
66
+ /**
67
+ * Whether this client can reach a chain over Ethereum-style remote procedure calls.
68
+ *
69
+ * @param chainId - the chain id to test
70
+ * @returns true when the chain has a definition here
71
+ */
72
+ export const isSupportedEvmChain = (chainId) => evmChainsById.has(chainId);
@@ -0,0 +1,21 @@
1
+ import type { TokenMetadata } from '@gibs/bridge-sdk/types';
2
+ import type { Hex } from 'viem';
3
+ export type ChainScopedAddress = `${number}:${Hex}`;
4
+ export declare function getStoreKey(chainId: number, tokenAddress: string): ChainScopedAddress;
5
+ export declare function parseChainScopedAddress(key: ChainScopedAddress): {
6
+ chainId: number;
7
+ address: Hex;
8
+ };
9
+ export declare function parseTokenKey(key: string): {
10
+ chainId: number;
11
+ address: Hex;
12
+ };
13
+ export declare function getTokenMetadata(chainId: number, tokenAddress: string): TokenMetadata | null;
14
+ export declare function loadTokenMetadata(tokens: Array<{
15
+ chainId: number;
16
+ address: Hex;
17
+ }>): Promise<void>;
18
+ export declare function getStoreSize(): number;
19
+ export declare function getTokenAddressFromBridge(bridge: any): string;
20
+ export declare function getTokenChainIdFromBridge(bridge: any): number;
21
+ export declare function getBridgeTokenMetadata(bridge: any): TokenMetadata | null;
@@ -0,0 +1,245 @@
1
+ import { nativeTokenName, nativeTokenSymbol, toChain } from '@gibs/bridge-sdk/config';
2
+ import * as _ from 'lodash-es';
3
+ import { erc20Abi, erc20Abi_bytes32, hexToString, zeroAddress } from 'viem';
4
+ import { clientFromChain } from './client.js';
5
+ import { evmChainsById } from './networks.js';
6
+ const bytes32Whitelist = new Set();
7
+ // In-memory store for token metadata (loaded once, no persistence)
8
+ const tokenMetadataStore = new Map();
9
+ // Generate store key
10
+ export function getStoreKey(chainId, tokenAddress) {
11
+ return `${chainId}:${tokenAddress.toLowerCase()}`;
12
+ }
13
+ // Parse a ChainScopedAddress into its components
14
+ export function parseChainScopedAddress(key) {
15
+ const [chainIdStr, address] = key.split(':');
16
+ const chainId = parseInt(chainIdStr ?? '', 10);
17
+ if (isNaN(chainId)) {
18
+ throw new Error(`Invalid chain ID in ChainScopedAddress: ${chainIdStr}`);
19
+ }
20
+ if (!address || !address.startsWith('0x')) {
21
+ throw new Error(`Invalid address in ChainScopedAddress: ${address}`);
22
+ }
23
+ return {
24
+ chainId,
25
+ address: address,
26
+ };
27
+ }
28
+ // Parse a string key (chainId:address format) into its components with proper types
29
+ export function parseTokenKey(key) {
30
+ const [chainIdStr, address] = key.split(':');
31
+ const chainId = parseInt(chainIdStr ?? '', 10);
32
+ if (isNaN(chainId)) {
33
+ throw new Error(`Invalid chain ID in token key: ${chainIdStr}`);
34
+ }
35
+ if (!address) {
36
+ throw new Error(`Invalid address in token key: ${address}`);
37
+ }
38
+ // Ensure address is properly formatted as Hex
39
+ const hexAddress = address.startsWith('0x') ? address : `0x${address}`;
40
+ return {
41
+ chainId,
42
+ address: hexAddress,
43
+ };
44
+ }
45
+ // Get native token metadata
46
+ function getNativeTokenMetadata(chainId) {
47
+ const chain = evmChainsById.get(chainId);
48
+ // Safely get native token info from SDK
49
+ let name = chain?.nativeCurrency?.name || 'Ether';
50
+ let symbol = chain?.nativeCurrency?.symbol || 'ETH';
51
+ const decimals = chain?.nativeCurrency?.decimals || 18;
52
+ // Try to get more specific info from SDK if available
53
+ try {
54
+ const chainKey = toChain(chainId);
55
+ const sdkName = nativeTokenName[chainKey];
56
+ const sdkSymbol = nativeTokenSymbol[chainKey];
57
+ if (sdkName) {
58
+ name = sdkName;
59
+ }
60
+ if (sdkSymbol) {
61
+ symbol = sdkSymbol;
62
+ }
63
+ }
64
+ catch {
65
+ // Fallback to viem chain info
66
+ }
67
+ return { name, symbol, decimals };
68
+ }
69
+ // Batch fetch token metadata for multiple tokens on the same chain
70
+ async function batchFetchTokenMetadata(chainId, tokenAddresses) {
71
+ const results = new Map();
72
+ if (tokenAddresses.length === 0)
73
+ return results;
74
+ // Get the chain and client
75
+ const chain = evmChainsById.get(chainId);
76
+ if (!chain) {
77
+ throw new Error(`Chain not found for chainId: ${chainId}`);
78
+ }
79
+ const client = clientFromChain(chainId);
80
+ // Separate native tokens from ERC20 tokens
81
+ const nativeTokens = [];
82
+ const erc20Tokens = [];
83
+ tokenAddresses.forEach((address) => {
84
+ if (address === zeroAddress || address.toLowerCase() === zeroAddress.toLowerCase()) {
85
+ nativeTokens.push(address);
86
+ }
87
+ else {
88
+ erc20Tokens.push(address);
89
+ }
90
+ });
91
+ // Handle native tokens
92
+ const nativeMetadata = getNativeTokenMetadata(chainId);
93
+ nativeTokens.forEach((address) => {
94
+ results.set(address.toLowerCase(), nativeMetadata);
95
+ });
96
+ // Handle ERC20 tokens with batch multicall
97
+ if (erc20Tokens.length === 0) {
98
+ // console.log(`No ERC20 tokens to process for chain ${chainId}`)
99
+ return results;
100
+ }
101
+ // console.log(`Processing ${erc20Tokens.length} ERC20 tokens for chain ${chainId}:`, erc20Tokens)
102
+ const addressChunks = _.chunk(erc20Tokens, 100);
103
+ const methods = ['symbol', 'name', 'decimals'];
104
+ const addressChunksToCallChunks = (addressChunks, abi) => addressChunks.map((chunk) => chunk.map((address) => methods.map((functionName) => ({
105
+ functionName,
106
+ address,
107
+ abi: abi(address),
108
+ allowFailure: true,
109
+ args: [],
110
+ }))));
111
+ // Try with standard ERC20 ABI first
112
+ const chunkedCalls = addressChunksToCallChunks(addressChunks, (address) => bytes32Whitelist.has(getStoreKey(chainId, address)) ? erc20Abi_bytes32 : erc20Abi);
113
+ const fallback = [];
114
+ for (const chunk of chunkedCalls) {
115
+ // console.log(`Executing multicall for chain ${chainId} with ${_.flatten(chunk).length} contracts`)
116
+ try {
117
+ const batchResults = await client.multicall({
118
+ allowFailure: true,
119
+ contracts: _.flatten(chunk),
120
+ });
121
+ // console.log(`Multicall results for chain ${chainId}:`, batchResults.length, 'results')
122
+ _.chunk(batchResults, 3).forEach((batch, index) => {
123
+ const tokenAddress = erc20Tokens[index];
124
+ const symbol = batch[0].result;
125
+ const name = batch[1].result;
126
+ const decimals = Number(batch[2].result);
127
+ // console.log(`Token ${tokenAddress} results:`, { symbol, name, decimals, errors: [batch[0].error, batch[1].error, batch[2].error] })
128
+ if (name && symbol && !isNaN(decimals)) {
129
+ results.set(tokenAddress.toLowerCase(), { name, symbol, decimals });
130
+ }
131
+ else {
132
+ console.log(`Adding ${tokenAddress} to fallback due to missing data`);
133
+ fallback.push(tokenAddress);
134
+ }
135
+ });
136
+ }
137
+ catch (error) {
138
+ console.error(`Multicall failed for chain ${chainId}:`, error);
139
+ // Add all tokens to fallback if multicall fails completely
140
+ fallback.push(...erc20Tokens);
141
+ }
142
+ }
143
+ if (fallback.length === 0) {
144
+ return results;
145
+ }
146
+ console.log(`Processing ${fallback.length} tokens in fallback with bytes32 ABI for chain ${chainId}:`, fallback);
147
+ const fallbackAddressChunks = _.chunk(fallback, 100);
148
+ const fallbackChunkedCalls = addressChunksToCallChunks(fallbackAddressChunks, () => erc20Abi_bytes32);
149
+ for (const chunk of fallbackChunkedCalls) {
150
+ try {
151
+ console.log(`Executing fallback multicall for chain ${chainId} with ${_.flatten(chunk).length} contracts`);
152
+ const batchResults = await client.multicall({
153
+ allowFailure: false,
154
+ contracts: _.flatten(chunk),
155
+ });
156
+ console.log(`Fallback multicall results for chain ${chainId}:`, batchResults.length, 'results');
157
+ _.chunk(batchResults, 3).forEach((batch, index) => {
158
+ const tokenAddress = fallback[index];
159
+ const symbol = batch[0];
160
+ const name = batch[1];
161
+ const decimals = Number(batch[2]);
162
+ const symbolString = hexToString(symbol);
163
+ const nameString = hexToString(name);
164
+ console.log(`Fallback token ${tokenAddress} results:`, {
165
+ symbolString,
166
+ nameString,
167
+ decimals,
168
+ });
169
+ results.set(tokenAddress.toLowerCase(), {
170
+ name: nameString,
171
+ symbol: symbolString,
172
+ decimals,
173
+ });
174
+ // Add to bytes32 whitelist for future optimization
175
+ bytes32Whitelist.add(getStoreKey(chainId, tokenAddress));
176
+ });
177
+ }
178
+ catch (error) {
179
+ console.error(`Fallback multicall failed for chain ${chainId}:`, error);
180
+ }
181
+ }
182
+ return results;
183
+ }
184
+ // Get token metadata synchronously (from store only)
185
+ export function getTokenMetadata(chainId, tokenAddress) {
186
+ const storeKey = getStoreKey(chainId, tokenAddress);
187
+ return tokenMetadataStore.get(storeKey) || null;
188
+ }
189
+ // Load token metadata for multiple tokens (called once on page load)
190
+ export async function loadTokenMetadata(tokens) {
191
+ // Group tokens by chain ID for batching
192
+ const tokensByChain = new Map();
193
+ tokens.forEach(({ chainId, address }) => {
194
+ if (!tokensByChain.has(chainId)) {
195
+ tokensByChain.set(chainId, []);
196
+ }
197
+ tokensByChain.get(chainId).push(address);
198
+ });
199
+ // Batch fetch for each chain
200
+ const batchPromises = Array.from(tokensByChain.entries()).map(async ([chainId, addresses]) => {
201
+ try {
202
+ // console.log(`Loading token metadata for chain ${chainId} (${addresses.length} tokens):`, addresses)
203
+ const results = await batchFetchTokenMetadata(chainId, addresses);
204
+ // console.log(`Loaded ${results.size} token metadata results for chain ${chainId}:`, Array.from(results.entries()))
205
+ // Store the results
206
+ results.forEach((metadata, address) => {
207
+ if (metadata) {
208
+ const storeKey = getStoreKey(chainId, address);
209
+ tokenMetadataStore.set(storeKey, metadata);
210
+ // console.log(`Stored metadata for ${storeKey}:`, metadata)
211
+ }
212
+ });
213
+ }
214
+ catch (error) {
215
+ console.error(`Failed to load tokens for chain ${chainId}:`, error);
216
+ }
217
+ });
218
+ await Promise.allSettled(batchPromises);
219
+ }
220
+ // Get store size
221
+ export function getStoreSize() {
222
+ return tokenMetadataStore.size;
223
+ }
224
+ // Utility functions for bridge transactions
225
+ export function getTokenAddressFromBridge(bridge) {
226
+ return (bridge.originationToken?.address ||
227
+ bridge.destinationToken?.address ||
228
+ bridge.originationTokenAddress ||
229
+ bridge.destinationTokenAddress ||
230
+ '');
231
+ }
232
+ export function getTokenChainIdFromBridge(bridge) {
233
+ return Number(bridge.originationToken?.chainId ||
234
+ bridge.destinationToken?.chainId ||
235
+ bridge.originationTokenChainId ||
236
+ bridge.destinationTokenChainId ||
237
+ 1);
238
+ }
239
+ export function getBridgeTokenMetadata(bridge) {
240
+ const address = getTokenAddressFromBridge(bridge);
241
+ const chainId = getTokenChainIdFromBridge(bridge);
242
+ if (!address)
243
+ return null;
244
+ return getTokenMetadata(chainId, address);
245
+ }
package/package.json ADDED
@@ -0,0 +1,76 @@
1
+ {
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",
5
+ "type": "module",
6
+ "main": "./dist/index.js",
7
+ "types": "./dist/index.d.ts",
8
+ "files": [
9
+ "dist"
10
+ ],
11
+ "exports": {
12
+ ".": {
13
+ "gibs-source": "./src/index.ts",
14
+ "types": "./dist/index.d.ts",
15
+ "default": "./dist/index.js"
16
+ },
17
+ "./cache": {
18
+ "gibs-source": "./src/cache.ts",
19
+ "types": "./dist/cache.d.ts",
20
+ "default": "./dist/cache.js"
21
+ },
22
+ "./image": {
23
+ "gibs-source": "./src/image.ts",
24
+ "types": "./dist/image.d.ts",
25
+ "default": "./dist/image.js"
26
+ },
27
+ "./networks": {
28
+ "gibs-source": "./src/networks.ts",
29
+ "types": "./dist/networks.d.ts",
30
+ "default": "./dist/networks.js"
31
+ },
32
+ "./endpoints": {
33
+ "gibs-source": "./src/endpoints.ts",
34
+ "types": "./dist/endpoints.d.ts",
35
+ "default": "./dist/endpoints.js"
36
+ },
37
+ "./client": {
38
+ "gibs-source": "./src/client.ts",
39
+ "types": "./dist/client.d.ts",
40
+ "default": "./dist/client.js"
41
+ },
42
+ "./chain-read": {
43
+ "gibs-source": "./src/chain-read.ts",
44
+ "types": "./dist/chain-read.d.ts",
45
+ "default": "./dist/chain-read.js"
46
+ },
47
+ "./token-metadata": {
48
+ "gibs-source": "./src/token-metadata.ts",
49
+ "types": "./dist/token-metadata.d.ts",
50
+ "default": "./dist/token-metadata.js"
51
+ }
52
+ },
53
+ "publishConfig": {
54
+ "access": "public"
55
+ },
56
+ "scripts": {
57
+ "build": "npx tsc",
58
+ "test": "vitest run",
59
+ "test:watch": "vitest"
60
+ },
61
+ "keywords": [],
62
+ "author": "",
63
+ "license": "ISC",
64
+ "devDependencies": {
65
+ "@types/lodash-es": "^4.17.12",
66
+ "@types/node": "^24",
67
+ "typescript": "~5.7.2",
68
+ "vitest": "^3.1.1"
69
+ },
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"
75
+ }
76
+ }