@gibs/bridge-indexer 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.
@@ -0,0 +1 @@
1
+ export {};
@@ -0,0 +1,24 @@
1
+ /**
2
+ * Reads a Gibs Finance bridge indexer.
3
+ *
4
+ * WHAT THIS PACKAGE IS FOR. The bridge's quote maths, chain metadata, ABIs and
5
+ * transaction building have been publishable for a long time; the ability to
6
+ * ask what happened to a transfer has not. That half lived inside the interface
7
+ * — and, independently, inside a second, older interface — so anybody building
8
+ * on the bridge could construct a crossing and then had no supported way to
9
+ * find out whether it landed. This package is that missing half.
10
+ *
11
+ * WHAT IT DOES NOT DO. It reads. Nothing here signs, broadcasts, or holds a
12
+ * wallet, and nothing here renders. A transfer's status arrives as data — a
13
+ * record, a count of affirmations, a delivery flag — and the words a person
14
+ * reads are the consumer's to choose, because a library cannot own somebody
15
+ * else's message catalogue.
16
+ *
17
+ * Point it somewhere with {@link setIndexerEndpoint} before the first query; it
18
+ * defaults to the next indexer (https://next-indexer.gibs.finance).
19
+ */
20
+ export { DEFAULT_INDEXER_ENDPOINT, indexerClient, indexerEndpoint, setIndexerEndpoint } from './endpoint.js';
21
+ export { bridgeStatuses, statusList, statusToIndex, type BridgeStatus, type ContinuedLiveBridgeStatusParams, type LiveBridgeStatusParams, } from './bridge-status.js';
22
+ export { buildBridgeFilter, fetchBridgeTransactions, fetchDeliveredMessageHashes, forgetDeliveredMessageHashes, loadTokenMetadataForBridges, peekDeliveredMessageHashes, type BridgeData, type BridgeFilterMode, type DeliveredMessageHashes, type FeeData, type LoadBridgesParams, } from './transfers.js';
23
+ export { liveBridgeStatusStageOne, liveBridgeStatusStageTwo } from './live-status.js';
24
+ export type { UserRequest, UserRequestFilter } from './graphql.js';
package/dist/index.js ADDED
@@ -0,0 +1,23 @@
1
+ /**
2
+ * Reads a Gibs Finance bridge indexer.
3
+ *
4
+ * WHAT THIS PACKAGE IS FOR. The bridge's quote maths, chain metadata, ABIs and
5
+ * transaction building have been publishable for a long time; the ability to
6
+ * ask what happened to a transfer has not. That half lived inside the interface
7
+ * — and, independently, inside a second, older interface — so anybody building
8
+ * on the bridge could construct a crossing and then had no supported way to
9
+ * find out whether it landed. This package is that missing half.
10
+ *
11
+ * WHAT IT DOES NOT DO. It reads. Nothing here signs, broadcasts, or holds a
12
+ * wallet, and nothing here renders. A transfer's status arrives as data — a
13
+ * record, a count of affirmations, a delivery flag — and the words a person
14
+ * reads are the consumer's to choose, because a library cannot own somebody
15
+ * else's message catalogue.
16
+ *
17
+ * Point it somewhere with {@link setIndexerEndpoint} before the first query; it
18
+ * defaults to the next indexer (https://next-indexer.gibs.finance).
19
+ */
20
+ export { DEFAULT_INDEXER_ENDPOINT, indexerClient, indexerEndpoint, setIndexerEndpoint } from './endpoint.js';
21
+ export { bridgeStatuses, statusList, statusToIndex, } from './bridge-status.js';
22
+ export { buildBridgeFilter, fetchBridgeTransactions, fetchDeliveredMessageHashes, forgetDeliveredMessageHashes, loadTokenMetadataForBridges, peekDeliveredMessageHashes, } from './transfers.js';
23
+ export { liveBridgeStatusStageOne, liveBridgeStatusStageTwo } from './live-status.js';
@@ -0,0 +1,18 @@
1
+ import { type ContinuedLiveBridgeStatusParams, type LiveBridgeStatusParams } from './bridge-status.js';
2
+ /** Clears the in-memory indexer cache. Exported for test isolation. */
3
+ export declare const _clearLiveBridgeStatusCache: () => void;
4
+ /**
5
+ * Stage one of the live bridge-status pipeline. Reads the origination receipt
6
+ * to decide between SUBMITTED (not yet mined) and MINED (receipt available).
7
+ * @param params - the hash, ticker block, and bridge key to inspect
8
+ * @returns the params extended with the resolved status (and receipt when mined)
9
+ */
10
+ export declare const liveBridgeStatusStageOne: (params: LiveBridgeStatusParams) => Promise<ContinuedLiveBridgeStatusParams>;
11
+ /**
12
+ * Stage two of the live bridge-status pipeline. Waits for finalization of the
13
+ * origination receipt, then reads the indexer to advance the status through
14
+ * FINALIZED, VALIDATING, and AFFIRMED.
15
+ * @param params - the stage-one output, or null when the pipeline was cleared
16
+ * @returns the params advanced to its next status, or unchanged when not ready
17
+ */
18
+ export declare const liveBridgeStatusStageTwo: (params: ContinuedLiveBridgeStatusParams | null) => Promise<ContinuedLiveBridgeStatusParams | null>;
@@ -0,0 +1,178 @@
1
+ import { gql } from 'graphql-request';
2
+ import { indexerClient } from './endpoint.js';
3
+ import { clientFromChain } from '@gibs/bridge-client/client';
4
+ import { bridgeStatuses, statusToIndex, } from './bridge-status.js';
5
+ import { Cache } from '@gibs/bridge-client/cache';
6
+ /** Single shared indexer client, mirroring the construction in history.ts. */
7
+ /**
8
+ * Every request here goes through {@link indexerClient}, which is a FUNCTION
9
+ * rather than a held client: a consumer configures the address during startup,
10
+ * and a client captured at module-evaluation time would have frozen the default
11
+ * before they got the chance.
12
+ */
13
+ const client = () => indexerClient();
14
+ const liveBridgeStatusQuery = gql `
15
+ query LiveBridgeStatus($hash: String!) {
16
+ userRequests(where: { transactionHash: $hash }) {
17
+ items {
18
+ messageId
19
+ confirmedSignatures
20
+ finishedSigning
21
+ requiredSignatures {
22
+ value
23
+ }
24
+ delivery {
25
+ transactionHash
26
+ }
27
+ feeDirector {
28
+ feeType
29
+ }
30
+ type
31
+ encodedData
32
+ signatures
33
+ destinationAmbAddress
34
+ }
35
+ }
36
+ }
37
+ `;
38
+ /** Time-to-live cache that collapses identical indexer reads within five seconds. */
39
+ const gqlCache = new Cache(5 * 1_000);
40
+ /** Clears the in-memory indexer cache. Exported for test isolation. */
41
+ export const _clearLiveBridgeStatusCache = () => {
42
+ gqlCache.cache.clear();
43
+ };
44
+ /**
45
+ * Requests the live bridge status for an origination transaction hash, reusing
46
+ * an in-flight or recently resolved promise within the cache time-to-live so
47
+ * repeated polls of the same hash collapse to a single network read.
48
+ * @param hash - the origination transaction hash to look up
49
+ * @returns the indexer's user-request payload for the hash
50
+ */
51
+ const requestBridgeStatus = (hash) => {
52
+ const key = JSON.stringify(['live-bridge-status', hash]);
53
+ const now = gqlCache.clearStale();
54
+ const cached = gqlCache.cache.get(key);
55
+ if (cached) {
56
+ return cached.value;
57
+ }
58
+ const result = client().request(liveBridgeStatusQuery, { hash });
59
+ gqlCache.cache.set(key, { time: now, value: result });
60
+ return result;
61
+ };
62
+ /**
63
+ * Stage one of the live bridge-status pipeline. Reads the origination receipt
64
+ * to decide between SUBMITTED (not yet mined) and MINED (receipt available).
65
+ * @param params - the hash, ticker block, and bridge key to inspect
66
+ * @returns the params extended with the resolved status (and receipt when mined)
67
+ */
68
+ export const liveBridgeStatusStageOne = async (params) => {
69
+ const { hash, bridgeKey } = params;
70
+ const [, fromChain] = bridgeKey;
71
+ const client = clientFromChain(Number(fromChain));
72
+ const receipt = await client
73
+ .getTransactionReceipt({
74
+ hash,
75
+ })
76
+ .catch(() => null);
77
+ if (!receipt) {
78
+ // transaction has not yet been mined — getTransaction may throw if the node
79
+ // hasn't propagated it yet (mempool lag), so we catch and still return SUBMITTED
80
+ await Promise.all([
81
+ client
82
+ .getTransaction({ hash })
83
+ .then((transaction) => {
84
+ client.getBlock({ blockTag: 'latest' }).then((latest) => {
85
+ console.log('transaction has not yet been mined', hash, transaction?.maxFeePerGas ?? transaction?.gasPrice, latest.baseFeePerGas);
86
+ });
87
+ })
88
+ .catch(() => {
89
+ // transaction not yet visible to this node — normal during mempool propagation
90
+ }),
91
+ ]);
92
+ return {
93
+ ...params,
94
+ status: bridgeStatuses.SUBMITTED,
95
+ };
96
+ }
97
+ return {
98
+ ...params,
99
+ status: bridgeStatuses.MINED,
100
+ receipt,
101
+ };
102
+ };
103
+ /**
104
+ * Stage two of the live bridge-status pipeline. Waits for finalization of the
105
+ * origination receipt, then reads the indexer to advance the status through
106
+ * FINALIZED, VALIDATING, and AFFIRMED.
107
+ * @param params - the stage-one output, or null when the pipeline was cleared
108
+ * @returns the params advanced to its next status, or unchanged when not ready
109
+ */
110
+ export const liveBridgeStatusStageTwo = async (params) => {
111
+ if (!params)
112
+ return null;
113
+ const { status, receipt, bridgeKey } = params;
114
+ if (statusToIndex(status) < statusToIndex(bridgeStatuses.MINED)) {
115
+ return params;
116
+ }
117
+ const client = clientFromChain(Number(bridgeKey[1]));
118
+ const finalizedBlock = await client.getBlock({ blockTag: 'finalized' });
119
+ params.finalizedBlock = finalizedBlock;
120
+ if (finalizedBlock.number < receipt.blockNumber) {
121
+ console.log('transaction has been mined, not yet finalized hash=%o current=%o finalized=%o receipt=%o', receipt?.transactionHash, params.ticker.number, finalizedBlock.number, receipt.blockNumber);
122
+ return params;
123
+ }
124
+ const result = await requestBridgeStatus(receipt.transactionHash);
125
+ const userRequest = result.userRequests.items?.[0];
126
+ if (!userRequest?.messageId)
127
+ return params;
128
+ const count = Number(userRequest.confirmedSignatures ?? 0);
129
+ const requiredSignatures = userRequest.requiredSignatures?.value != null
130
+ ? Number(userRequest.requiredSignatures.value)
131
+ : null;
132
+ const deliveryType = userRequest.feeDirector?.feeType ?? null;
133
+ if (userRequest.delivery?.transactionHash) {
134
+ // AFFIRMED here means DELIVERED — a delivery transaction hash exists. This
135
+ // is the canonical meaning: it matches the on-chain event that produces
136
+ // it (`HomeAMB:AffirmationCompleted`). `bridge-history-item-utils.ts`
137
+ // once used AFFIRMED for the opposite fact (signed but not delivered);
138
+ // that collision is fixed there in favor of THIS meaning — see the
139
+ // comment on `createBridgeStatusFromHistory` and docs/transfer-states.md
140
+ // section 1. Do not reintroduce a second meaning for this value.
141
+ return {
142
+ ...params,
143
+ messageId: userRequest.messageId,
144
+ status: bridgeStatuses.AFFIRMED,
145
+ deliveredHash: userRequest.delivery.transactionHash,
146
+ count,
147
+ requiredSignatures,
148
+ deliveryType,
149
+ };
150
+ }
151
+ if (userRequest.finishedSigning) {
152
+ return {
153
+ ...params,
154
+ messageId: userRequest.messageId,
155
+ status: bridgeStatuses.VALIDATING,
156
+ count,
157
+ requiredSignatures,
158
+ deliveryType,
159
+ // Only attached once the request is actually releasable (finished
160
+ // signing, not yet delivered) — the one lifecycle point a manual
161
+ // release calldata could be built and broadcast.
162
+ release: {
163
+ requestType: userRequest.type,
164
+ encodedData: userRequest.encodedData,
165
+ signatures: userRequest.signatures ?? [],
166
+ destinationAmbAddress: userRequest.destinationAmbAddress,
167
+ },
168
+ };
169
+ }
170
+ return {
171
+ ...params,
172
+ messageId: userRequest.messageId,
173
+ status: bridgeStatuses.FINALIZED,
174
+ count,
175
+ requiredSignatures,
176
+ deliveryType,
177
+ };
178
+ };
@@ -0,0 +1,147 @@
1
+ import type { TokenMetadata } from '@gibs/bridge-sdk/types';
2
+ import { type Hex } from 'viem';
3
+ import type { UserRequest, UserRequestFilter } from './graphql.js';
4
+ export interface FeeData {
5
+ tokenAddress: Hex;
6
+ feeManagerContract: {
7
+ chainId: string;
8
+ address: Hex;
9
+ omnibridgeAddress: Hex;
10
+ };
11
+ feeUpdate: {
12
+ feeType: Hex;
13
+ fee: string;
14
+ };
15
+ }
16
+ export interface BridgeData {
17
+ userRequests: UserRequest[];
18
+ tokenMetadata: Map<string, TokenMetadata>;
19
+ feeData: FeeData[];
20
+ pageInfo?: {
21
+ hasNextPage: boolean;
22
+ hasPreviousPage: boolean;
23
+ startCursor?: string | null;
24
+ endCursor?: string | null;
25
+ };
26
+ totalCount?: number;
27
+ /**
28
+ * How many bridges this account released FOR SOMEONE ELSE the archive could
29
+ * not be asked about, because the fold-in list is capped at
30
+ * {@link DELIVERED_HASHES_LIMIT}.
31
+ *
32
+ * Reported so the interface can SAY SO. The cap used to reach a
33
+ * `console.warn` and nothing else, so a delivery account whose released
34
+ * bridges run into the thousands was shown a page footer that read as the
35
+ * whole archive. A count that is silently short is worse than one that is
36
+ * absent: the reader trusts it.
37
+ */
38
+ releasedForOthersOmitted: number;
39
+ }
40
+ /**
41
+ * Loads token metadata for unique token/chain combinations found in a set of bridges.
42
+ * Exported for independent testability.
43
+ */
44
+ export declare function loadTokenMetadataForBridges(bridges: UserRequest[]): Promise<Map<string, TokenMetadata>>;
45
+ /**
46
+ * Which part of the archive a browse asks for.
47
+ *
48
+ * `pending` and `completed` are a genuine partition of every request, so
49
+ * switching always changes what is on screen. `all` asks for both and is
50
+ * coherent only BECAUSE of that partition — it carries no `delivered` filter at
51
+ * all rather than a third value of one.
52
+ */
53
+ export type BridgeFilterMode = 'pending' | 'completed' | 'all';
54
+ export type LoadBridgesParams = {
55
+ address: Hex | null | undefined;
56
+ hash?: Hex | null | undefined;
57
+ limit?: number;
58
+ after?: string;
59
+ before?: string;
60
+ filterMode?: BridgeFilterMode;
61
+ /**
62
+ * Chain ids whose transfers must not appear, sorted. Omit or leave empty to
63
+ * ask for every network.
64
+ *
65
+ * IT IS THE INDEXER THAT APPLIES THIS, not the browser, and that is the whole
66
+ * reason it is here. A network filter applied to the page after it arrives
67
+ * would draw three rows under a count of ten while thousands more matched on
68
+ * pages the reader never reached — a silently capped list reads as the whole
69
+ * answer.
70
+ */
71
+ hiddenChainIds?: readonly number[];
72
+ };
73
+ /**
74
+ * Builds the GraphQL filter for user request queries from bridge loading params.
75
+ * Pure function — no side effects, exported for independent testability.
76
+ */
77
+ export declare function buildBridgeFilter(params: LoadBridgesParams, deliveredMessageHashes?: Hex[]): UserRequestFilter | undefined;
78
+ /**
79
+ * What one account has released on behalf of other people, as the archive query
80
+ * needs it: the hashes to fold in, and how many did not fit.
81
+ */
82
+ export interface DeliveredMessageHashes {
83
+ /** The lowercased message hashes, newest first, at most {@link DELIVERED_HASHES_LIMIT}. */
84
+ readonly hashes: Hex[];
85
+ /** How many further released bridges the cap left out. Zero when none. */
86
+ readonly omitted: number;
87
+ }
88
+ /**
89
+ * Drops one account's cached delivered-hash list, so the next history fetch
90
+ * resolves it again.
91
+ *
92
+ * Called when the reader's own transaction is mined. A release the READER just
93
+ * made is the one change this list can undergo while they are watching it, and
94
+ * making them wait out the cache to see it would defeat the burst poll that
95
+ * exists for exactly that moment.
96
+ *
97
+ * @param address - the account whose list to forget. No address forgets nothing.
98
+ */
99
+ export declare const forgetDeliveredMessageHashes: (address: Hex | null | undefined) => void;
100
+ /** Clears every cached delivered-hash list. Exported for test isolation. */
101
+ export declare const _clearDeliveredHashesCache: () => void;
102
+ /**
103
+ * One account's delivered-hash list IF it is already resolved and still fresh,
104
+ * without starting a fetch for it.
105
+ *
106
+ * WHY LOOKING IS A DIFFERENT QUESTION FROM ASKING. `fetchBridgeTransactions`
107
+ * needs to know whether waiting for this list is free before it decides whether
108
+ * to run the archive query alongside it or after it. A cached list costs
109
+ * nothing to wait for, so the archive query can be built widened straight away;
110
+ * an unresolved one costs a whole round trip, and that is the round trip the
111
+ * page used to sit through before it asked for a single transfer.
112
+ *
113
+ * A STALE ENTRY READS AS ABSENT. `fetchDeliveredMessageHashes` sweeps expired
114
+ * entries on its way past, so an entry can outlive its window between sweeps.
115
+ * Returning one would hand the caller a list it believes is current.
116
+ *
117
+ * @param address - the account to look up. No address has no list.
118
+ * @returns the resolved list's promise, or null when there is no fresh one.
119
+ */
120
+ export declare const peekDeliveredMessageHashes: (address: Hex | null | undefined) => Promise<DeliveredMessageHashes> | null;
121
+ /**
122
+ * Fetches the message hashes of bridges this account RELEASED on behalf of others —
123
+ * the on-chain `deliverer` (the account that submitted the release transaction).
124
+ * Those bridges never match the history query's from/to scope, so without this they
125
+ * are invisible in the account's history.
126
+ *
127
+ * Ponder stores hex columns lowercase but does not normalize the query side, so the
128
+ * deliverer is queried lowercased to match (the same normalization the from/to scope
129
+ * relies on). Returns lowercased message hashes; nothing when no address.
130
+ *
131
+ * The answer is cached per account for {@link DELIVERED_HASHES_CACHE_TTL}. See
132
+ * that constant for why: this is the query that was reading hundreds of records
133
+ * in front of every ten-row page.
134
+ *
135
+ * Resilient by design: a failure here returns an empty list rather than throwing, so
136
+ * a deliveries-query problem degrades history to the from/to scope instead of blanking
137
+ * the whole panel. A failure is NEVER cached — a reader whose network blipped once
138
+ * would otherwise lose their released bridges for the full minute.
139
+ */
140
+ export declare function fetchDeliveredMessageHashes(address: Hex | null | undefined): Promise<DeliveredMessageHashes>;
141
+ /** Clears the in-memory bridge cache. Exported for test isolation. */
142
+ export declare const _clearBridgeCache: () => void;
143
+ /**
144
+ * Inner fetching logic for loadBridgeTransactions.
145
+ * Exported so it can be tested independently of the Svelte loading store wrapper.
146
+ */
147
+ export declare function fetchBridgeTransactions(rawParams: LoadBridgesParams, controller: AbortController): Promise<BridgeData | null>;