@berachain/berajs 0.2.9 → 0.2.10
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/dist/actions/clients/exports.d.ts +77 -1
- package/dist/actions/clients/exports.mjs +13 -4
- package/dist/actions/exports.d.ts +59 -27
- package/dist/actions/exports.mjs +23 -23
- package/dist/actions/governance/exports.mjs +3 -3
- package/dist/actions/server/exports.mjs +4 -4
- package/dist/{chunk-4Z4AK6SH.mjs → chunk-3JJLQ2JX.mjs} +3 -3
- package/dist/{chunk-EXIUPSFN.mjs → chunk-7YVNSDXG.mjs} +2 -2
- package/dist/{chunk-WXXOISTU.mjs → chunk-AUOPN6NK.mjs} +1 -1
- package/dist/{chunk-75M6TF7M.mjs → chunk-DQRH5VE3.mjs} +1 -1
- package/dist/{chunk-CDFWPU2R.mjs → chunk-E7YFXBBQ.mjs} +0 -124
- package/dist/{chunk-AFN4CVD3.mjs → chunk-GUURQAME.mjs} +1 -1
- package/dist/{chunk-KQUMKB66.mjs → chunk-GY6B3PD5.mjs} +1 -1
- package/dist/{chunk-HSSJKHZ4.mjs → chunk-HYDP32P6.mjs} +3 -3
- package/dist/{chunk-NPBQLVL3.mjs → chunk-IXIBY5FP.mjs} +2 -2
- package/dist/{chunk-J5I45WGQ.mjs → chunk-KHXJDYA4.mjs} +7 -0
- package/dist/chunk-P5WXXULM.mjs +54 -0
- package/dist/{chunk-QJIXTYTZ.mjs → chunk-QBBOWFMH.mjs} +105 -30
- package/dist/{chunk-FFB5LFDW.mjs → chunk-QVHEM4BG.mjs} +2 -2
- package/dist/{chunk-3EARVV7K.mjs → chunk-WNBWX23Q.mjs} +17 -5
- package/dist/chunk-Y6THHG77.mjs +126 -0
- package/dist/{chunk-XIYN6AL6.mjs → chunk-ZLTMIFCZ.mjs} +10 -5
- package/dist/contexts/exports.mjs +8 -8
- package/dist/errors/exports.mjs +5 -5
- package/dist/{getValidatorQueuedOperatorAddress-Dw5KN5sh.d.ts → getValidatorQueuedOperatorAddress-DphU3qhE.d.ts} +1 -1
- package/dist/hooks/exports.d.ts +17 -18
- package/dist/hooks/exports.mjs +161 -132
- package/dist/hooks/governance/exports.mjs +4 -4
- package/dist/{pol.d-CqPA9K6m.d.ts → pol.d-Dw5SQcRX.d.ts} +15 -4
- package/dist/types/exports.d.ts +1 -1
- package/dist/utils/exports.mjs +11 -9
- package/package.json +4 -4
- package/src/actions/clients/exports.ts +3 -0
- package/src/actions/clients/fetchBeep.ts +34 -0
- package/src/actions/clients/fetchOpenApi.ts +93 -0
- package/src/actions/clients/fetchOpenApi.unit.test.ts +223 -0
- package/src/actions/clients/fetchRailwayBackend.ts +34 -0
- package/src/actions/enso/getEnsoUserTokensWithBalances.ts +18 -0
- package/src/actions/exports.ts +1 -0
- package/src/actions/honey/getPythLatestPrices.ts +7 -0
- package/src/actions/pol/__tests__/rewardVaults.integration.test.ts +1 -1
- package/src/actions/pol/getAutoclaimedIncentives.ts +21 -10
- package/src/actions/pol/getAutoclaimedIncentivesTxHash.ts +41 -0
- package/src/actions/pol/getBgtIncentiveDistributorPaused.ts +5 -12
- package/src/actions/pol/getEarnedStakedBeraVault.ts +20 -16
- package/src/actions/pol/getRewardVaults.ts +4 -4
- package/src/actions/pol/getStakingDailyAssets.ts +18 -14
- package/src/actions/validators/utils/getValidatorBoostApy.ts +1 -1
- package/src/errors/RequestError.ts +12 -3
- package/src/errors/RequestError.unit.test.ts +55 -0
- package/src/errors/errorMap.ts +8 -0
- package/src/hooks/exports.ts +1 -0
- package/src/hooks/pol/useAutoclaimedIncentives.ts +1 -10
- package/src/hooks/pol/useAutoclaimedIncentivesTxHash.ts +45 -0
- package/src/hooks/validators/useValidator.ts +4 -8
- package/src/types/bribe-boost.d.ts +14 -3
- package/src/utils/polyfillAbortSignalAny.ts +53 -0
- package/src/utils/polyfillAbortSignalAny.unit.test.ts +81 -0
- /package/dist/{exports-BcUTGFUb.d.ts → getApolloClient-BcUTGFUb.d.ts} +0 -0
|
@@ -0,0 +1,41 @@
|
|
|
1
|
+
import type { Address } from "viem";
|
|
2
|
+
|
|
3
|
+
import { RequestError } from "../../errors/RequestError";
|
|
4
|
+
import type { AutoclaimedIncentivesTxHashResponse } from "../../types/bribe-boost";
|
|
5
|
+
import { beraFetchJson } from "../../utils/beraFetch";
|
|
6
|
+
import { parseBaseArgs } from "../../utils/parseBaseArgs";
|
|
7
|
+
|
|
8
|
+
/**
|
|
9
|
+
* Companion to `getAutoclaimedIncentives` that fetches the on-chain tx hash
|
|
10
|
+
* for each autoclaimed token. Same nested shape as `/autoclaimed`, but each
|
|
11
|
+
* token leaf is `{ txhash }` where the hash may be absent or empty while the
|
|
12
|
+
* bot is still in flight.
|
|
13
|
+
*
|
|
14
|
+
* Returns `null` on 404 (bot has not run yet for this wallet).
|
|
15
|
+
*/
|
|
16
|
+
export async function getAutoclaimedIncentivesTxHash({
|
|
17
|
+
account,
|
|
18
|
+
...args
|
|
19
|
+
}: {
|
|
20
|
+
account: Address;
|
|
21
|
+
} & BeraJS.BaseFunctionArgs): Promise<AutoclaimedIncentivesTxHashResponse | null> {
|
|
22
|
+
const { config } = parseBaseArgs(args);
|
|
23
|
+
|
|
24
|
+
try {
|
|
25
|
+
return await beraFetchJson<AutoclaimedIncentivesTxHashResponse>(
|
|
26
|
+
{
|
|
27
|
+
url: `${
|
|
28
|
+
config.pol.bribeBoostApi
|
|
29
|
+
}/api/v1/wallets/${account.toLowerCase()}/autoclaimed/txhash`,
|
|
30
|
+
name: "pol-autoclaimed-incentives-txhash",
|
|
31
|
+
type: "rest",
|
|
32
|
+
},
|
|
33
|
+
{ cache: "no-store" },
|
|
34
|
+
);
|
|
35
|
+
} catch (error) {
|
|
36
|
+
if (error instanceof RequestError && error.statusCode === 404) {
|
|
37
|
+
return null;
|
|
38
|
+
}
|
|
39
|
+
throw error;
|
|
40
|
+
}
|
|
41
|
+
}
|
|
@@ -9,8 +9,6 @@ import { parseBaseArgs } from "../../utils/parseBaseArgs";
|
|
|
9
9
|
* Reads `paused()` on the BGT incentive distributor. Used to gate the
|
|
10
10
|
* autoclaim banner — when `true`, users can no longer self-claim and the
|
|
11
11
|
* bot is responsible for distributing incentives on their behalf.
|
|
12
|
-
*
|
|
13
|
-
* Fails closed: any read error returns `false` so the banner stays hidden.
|
|
14
12
|
*/
|
|
15
13
|
export async function getBgtIncentiveDistributorPaused({
|
|
16
14
|
publicClient,
|
|
@@ -22,14 +20,9 @@ export async function getBgtIncentiveDistributorPaused({
|
|
|
22
20
|
|
|
23
21
|
const { config } = parseBaseArgs(args);
|
|
24
22
|
|
|
25
|
-
|
|
26
|
-
|
|
27
|
-
|
|
28
|
-
|
|
29
|
-
|
|
30
|
-
});
|
|
31
|
-
return paused;
|
|
32
|
-
} catch {
|
|
33
|
-
return false;
|
|
34
|
-
}
|
|
23
|
+
return publicClient.readContract({
|
|
24
|
+
address: config.pol.bgtIncentiveDistributor,
|
|
25
|
+
abi: bgtIncentiveDistributorAbi,
|
|
26
|
+
functionName: "paused",
|
|
27
|
+
});
|
|
35
28
|
}
|
|
@@ -1,11 +1,8 @@
|
|
|
1
1
|
import type { Address } from "viem";
|
|
2
2
|
|
|
3
|
-
import { getUriFromLink } from "@berachain/config";
|
|
4
|
-
|
|
5
|
-
import type { VaultEarningResponse } from "@berachain/graphql/api";
|
|
6
|
-
|
|
7
|
-
import { beraFetchJson } from "../../utils/beraFetch";
|
|
8
3
|
import { parseBaseArgs } from "../../utils/parseBaseArgs";
|
|
4
|
+
import { fetchBeep } from "../clients/fetchBeep";
|
|
5
|
+
import { fetchRailwayBackend } from "../clients/fetchRailwayBackend";
|
|
9
6
|
|
|
10
7
|
type GetEarnedStakedBeraVaultArgs = {
|
|
11
8
|
/**
|
|
@@ -21,17 +18,24 @@ export async function getEarnedStakedBeraVault({
|
|
|
21
18
|
address,
|
|
22
19
|
account,
|
|
23
20
|
...args
|
|
24
|
-
}: BeraJS.BaseFunctionArgs &
|
|
25
|
-
GetEarnedStakedBeraVaultArgs): Promise<VaultEarningResponse> {
|
|
21
|
+
}: BeraJS.BaseFunctionArgs & GetEarnedStakedBeraVaultArgs) {
|
|
26
22
|
const { config } = parseBaseArgs(args);
|
|
27
23
|
|
|
28
|
-
|
|
29
|
-
|
|
30
|
-
|
|
31
|
-
|
|
32
|
-
|
|
33
|
-
|
|
34
|
-
|
|
35
|
-
|
|
36
|
-
|
|
24
|
+
// TODO(beep): remove this entire block once `beep` is deployed on every
|
|
25
|
+
// chain. It is the only legacy-Railway-specific code left here; deleting it
|
|
26
|
+
// leaves the `beep` path below as the sole implementation.
|
|
27
|
+
if (!config.beep) {
|
|
28
|
+
return fetchRailwayBackend(
|
|
29
|
+
"/vaults/{vault}/earnings/{owner}",
|
|
30
|
+
{ path: { vault: address, owner: account } },
|
|
31
|
+
args,
|
|
32
|
+
);
|
|
33
|
+
}
|
|
34
|
+
|
|
35
|
+
// beep backend (next-gen), typed against `@berachain/graphql/beep`.
|
|
36
|
+
return fetchBeep(
|
|
37
|
+
"/v0/stake/{vault}/earnings/{owner}",
|
|
38
|
+
{ path: { vault: address, owner: account } },
|
|
39
|
+
args,
|
|
40
|
+
);
|
|
37
41
|
}
|
|
@@ -87,13 +87,13 @@ export async function getRewardVaults({
|
|
|
87
87
|
...vault,
|
|
88
88
|
dynamicData: {
|
|
89
89
|
...vault.dynamicData,
|
|
90
|
-
|
|
91
|
-
|
|
92
|
-
|
|
90
|
+
allTimeRewards: vault.dynamicData?.allTimeRewards ?? "0",
|
|
91
|
+
rewardCapturePercentage:
|
|
92
|
+
vault.dynamicData?.rewardCapturePercentage ?? 0,
|
|
93
93
|
activeIncentivesValueUsd: totalIncentiveInUsdc.toString(),
|
|
94
94
|
activeIncentivesRateUsd:
|
|
95
95
|
vault.dynamicData?.activeIncentivesRateUsd ?? "0",
|
|
96
|
-
|
|
96
|
+
rewardCapturePerBlock: vault.dynamicData?.rewardCapturePerBlock ?? 0,
|
|
97
97
|
},
|
|
98
98
|
activeIncentives: incentivesArray,
|
|
99
99
|
} satisfies typeof vault;
|
|
@@ -1,11 +1,8 @@
|
|
|
1
1
|
import type { Address } from "viem";
|
|
2
2
|
|
|
3
|
-
import { getUriFromLink } from "@berachain/config";
|
|
4
|
-
|
|
5
|
-
import type { VaultStatsByDayResponse } from "@berachain/graphql/api";
|
|
6
|
-
|
|
7
|
-
import { beraFetchJson } from "../../utils/beraFetch";
|
|
8
3
|
import { parseBaseArgs } from "../../utils/parseBaseArgs";
|
|
4
|
+
import { fetchBeep } from "../clients/fetchBeep";
|
|
5
|
+
import { fetchRailwayBackend } from "../clients/fetchRailwayBackend";
|
|
9
6
|
|
|
10
7
|
export async function getStakingDailyAssets({
|
|
11
8
|
address,
|
|
@@ -13,15 +10,22 @@ export async function getStakingDailyAssets({
|
|
|
13
10
|
}: {
|
|
14
11
|
address: Address;
|
|
15
12
|
range: 30 | 60 | 90;
|
|
16
|
-
})
|
|
13
|
+
}) {
|
|
17
14
|
const { config } = parseBaseArgs({});
|
|
18
|
-
|
|
19
|
-
|
|
20
|
-
|
|
21
|
-
|
|
22
|
-
|
|
23
|
-
|
|
24
|
-
|
|
25
|
-
|
|
15
|
+
|
|
16
|
+
// TODO(beep): remove this entire block once `beep` is deployed on every
|
|
17
|
+
// chain. It is the only legacy-Railway-specific code left here; deleting it
|
|
18
|
+
// leaves the `beep` path below as the sole implementation.
|
|
19
|
+
if (!config.beep) {
|
|
20
|
+
return fetchRailwayBackend("/vaults/{vault}/stats-by-day", {
|
|
21
|
+
path: { vault: address },
|
|
22
|
+
query: { days: `${range}` },
|
|
23
|
+
});
|
|
24
|
+
}
|
|
25
|
+
|
|
26
|
+
// beep backend (next-gen), typed against `@berachain/graphql/beep`.
|
|
27
|
+
return fetchBeep("/v0/stake/{vault}/stats-by-day", {
|
|
28
|
+
path: { vault: address },
|
|
29
|
+
query: { days: `${range}` },
|
|
26
30
|
});
|
|
27
31
|
}
|
|
@@ -51,7 +51,7 @@ export function getValidatorBoostApy({
|
|
|
51
51
|
|
|
52
52
|
const boostApy =
|
|
53
53
|
(returnPerBgt *
|
|
54
|
-
Number(validator.dynamicData?.
|
|
54
|
+
Number(validator.dynamicData?.lastDayDistributedRewards ?? 0) *
|
|
55
55
|
365) /
|
|
56
56
|
(validatorActiveBoostAmount * bgtPrice);
|
|
57
57
|
|
|
@@ -1,6 +1,7 @@
|
|
|
1
1
|
import type { ErrorLike } from "@apollo/client";
|
|
2
2
|
|
|
3
3
|
import { getUriFromLink } from "@berachain/config";
|
|
4
|
+
import { bepolia } from "@berachain/config/bepolia";
|
|
4
5
|
import { mainnet } from "@berachain/config/mainnet";
|
|
5
6
|
|
|
6
7
|
import { BeraError, type IBeraErrorArgs } from "./BeraError";
|
|
@@ -33,12 +34,16 @@ export class RequestError extends BeraError {
|
|
|
33
34
|
*/
|
|
34
35
|
private queryRichfulDomains = [
|
|
35
36
|
mainnet.bex.aggregatorsProxyUrl,
|
|
36
|
-
|
|
37
|
+
// covers both /aggregator/quote and /aggregator/transaction?quoteId=...
|
|
38
|
+
// (the transaction endpoint's quoteId would otherwise fragment grouping)
|
|
39
|
+
"https://api.fly.trade/aggregator",
|
|
37
40
|
"https://open-api.openocean.finance/v4/bera/swap",
|
|
38
41
|
// vercel internal proxy
|
|
39
42
|
"/api/aggregators?aggregator",
|
|
40
43
|
// bonder endpoints have date-based query params (e.g. ?start=2025-11-28)
|
|
41
44
|
mainnet.backend,
|
|
45
|
+
// beep (next-gen backend) on testnet — same date-based bonder query params
|
|
46
|
+
bepolia.beep,
|
|
42
47
|
];
|
|
43
48
|
|
|
44
49
|
statusCode: number | undefined;
|
|
@@ -208,14 +213,18 @@ export class RequestError extends BeraError {
|
|
|
208
213
|
// - have a network error
|
|
209
214
|
// - have CORS errors
|
|
210
215
|
// the list is empirically collected from sentry error logs.
|
|
216
|
+
// The trailing "(host)" suffix is optional: some browsers/transports omit
|
|
217
|
+
// it (e.g. bare "Failed to fetch" / "Load failed"). We normalize all of
|
|
218
|
+
// them to a single NETWORK_ERROR reason so the variants group together,
|
|
219
|
+
// falling back to the endpoint url when no host is present in the message.
|
|
211
220
|
const failedToFetchMatch = message.match(
|
|
212
|
-
/^(?:NetworkError when attempting to fetch resource\.|Failed to fetch|Load failed) \(([a-z0-9-.]+)\)
|
|
221
|
+
/^(?:NetworkError when attempting to fetch resource\.|Failed to fetch|Load failed)(?: \(([a-z0-9-.]+)\))?$/,
|
|
213
222
|
);
|
|
214
223
|
if (failedToFetchMatch) {
|
|
215
224
|
return {
|
|
216
225
|
reason: RequestError.REASON_MAP.NETWORK_ERROR,
|
|
217
226
|
message: message,
|
|
218
|
-
url: failedToFetchMatch[1],
|
|
227
|
+
url: failedToFetchMatch[1] ?? this.endpoint.url,
|
|
219
228
|
};
|
|
220
229
|
}
|
|
221
230
|
}
|
|
@@ -0,0 +1,55 @@
|
|
|
1
|
+
import { describe, expect, it } from "vitest";
|
|
2
|
+
|
|
3
|
+
import { RequestError } from "./RequestError.js";
|
|
4
|
+
|
|
5
|
+
describe("RequestError endpoint normalization (grouping)", () => {
|
|
6
|
+
it("strips query params from the fly.trade transaction endpoint so quoteId no longer fragments groups", () => {
|
|
7
|
+
const base = "https://api.fly.trade/aggregator/transaction";
|
|
8
|
+
const a = new RequestError({
|
|
9
|
+
response: undefined,
|
|
10
|
+
endpoint: {
|
|
11
|
+
url: `${base}?quoteId=aaaa-1111&estimateGas=false`,
|
|
12
|
+
type: "rest",
|
|
13
|
+
},
|
|
14
|
+
});
|
|
15
|
+
const b = new RequestError({
|
|
16
|
+
response: undefined,
|
|
17
|
+
endpoint: {
|
|
18
|
+
url: `${base}?quoteId=bbbb-2222&estimateGas=false`,
|
|
19
|
+
type: "rest",
|
|
20
|
+
},
|
|
21
|
+
});
|
|
22
|
+
|
|
23
|
+
expect(a.endpoint.url).toBe(base);
|
|
24
|
+
expect(b.endpoint.url).toBe(base);
|
|
25
|
+
expect(a.endpoint.url).toBe(b.endpoint.url);
|
|
26
|
+
});
|
|
27
|
+
});
|
|
28
|
+
|
|
29
|
+
describe("RequestError network-error normalization (grouping)", () => {
|
|
30
|
+
it.each([
|
|
31
|
+
"Failed to fetch",
|
|
32
|
+
"Load failed",
|
|
33
|
+
"NetworkError when attempting to fetch resource.",
|
|
34
|
+
"Failed to fetch (api.berachain.com)",
|
|
35
|
+
])("normalizes %s to NETWORK_ERROR reason", (message) => {
|
|
36
|
+
const err = new RequestError({
|
|
37
|
+
response: undefined,
|
|
38
|
+
cause: new TypeError(message),
|
|
39
|
+
endpoint: { url: "https://api.berachain.com", type: "graphql" },
|
|
40
|
+
});
|
|
41
|
+
|
|
42
|
+
expect(err.reason).toBe(RequestError.REASON_MAP.NETWORK_ERROR);
|
|
43
|
+
expect(err.level).toBe("warning");
|
|
44
|
+
});
|
|
45
|
+
|
|
46
|
+
it("does not treat unrelated messages as network errors", () => {
|
|
47
|
+
const err = new RequestError({
|
|
48
|
+
response: undefined,
|
|
49
|
+
cause: new TypeError("Cannot read properties of undefined"),
|
|
50
|
+
endpoint: { url: "https://api.berachain.com", type: "graphql" },
|
|
51
|
+
});
|
|
52
|
+
|
|
53
|
+
expect(err.reason).not.toBe(RequestError.REASON_MAP.NETWORK_ERROR);
|
|
54
|
+
});
|
|
55
|
+
});
|
package/src/errors/errorMap.ts
CHANGED
|
@@ -89,6 +89,14 @@ export const errorMsgMap = {
|
|
|
89
89
|
errorMSG:
|
|
90
90
|
"Nonce is too low. Your wallet might have unconfirmed transactions.",
|
|
91
91
|
},
|
|
92
|
+
REPLACEMENT_UNDERPRICED: {
|
|
93
|
+
// Transient resubmission issue (gas bump too low). Not function-specific —
|
|
94
|
+
// normalizing the raw RPC string to this key collapses the per-function
|
|
95
|
+
// Sentry groups (functionName is still kept as a tag).
|
|
96
|
+
keywords: ["replacement transaction underpriced"],
|
|
97
|
+
errorMSG:
|
|
98
|
+
"Your wallet tried to replace a pending transaction without raising the gas enough. Please wait for the previous transaction to finish, or try again.",
|
|
99
|
+
},
|
|
92
100
|
GAS_PRICE: {
|
|
93
101
|
keywords: ["gasLimit"],
|
|
94
102
|
errorMSG:
|
package/src/hooks/exports.ts
CHANGED
|
@@ -49,6 +49,7 @@ export * from "./honey/useIsBasketModeEnabled";
|
|
|
49
49
|
export * from "./honey/usePythLatestPrices";
|
|
50
50
|
export * from "./perps/usePythUpdateFee";
|
|
51
51
|
export { useAutoclaimedIncentives } from "./pol/useAutoclaimedIncentives";
|
|
52
|
+
export { useAutoclaimedIncentivesTxHash } from "./pol/useAutoclaimedIncentivesTxHash";
|
|
52
53
|
export * from "./pol/useBgtAprSimulation";
|
|
53
54
|
export { useBgtUnstakedBalance } from "./pol/useBgtUnstakedBalance";
|
|
54
55
|
export { useClaimableFees } from "./pol/useClaimableFees";
|
|
@@ -3,7 +3,6 @@ import useSWR from "swr";
|
|
|
3
3
|
import { useBeraWallet } from "@berachain/wagmi/hooks";
|
|
4
4
|
|
|
5
5
|
import { getAutoclaimedIncentives } from "../../actions/pol/getAutoclaimedIncentives";
|
|
6
|
-
import { BeraError } from "../../errors/BeraError";
|
|
7
6
|
import type { AutoclaimedIncentivesResponse } from "../../types/bribe-boost";
|
|
8
7
|
import type { DefaultHookReturnType } from "../../types/global";
|
|
9
8
|
|
|
@@ -26,15 +25,7 @@ export function useAutoclaimedIncentives({
|
|
|
26
25
|
|
|
27
26
|
const swrResponse = useSWR(
|
|
28
27
|
QUERY_KEY,
|
|
29
|
-
async ([, account]) => {
|
|
30
|
-
if (!account) {
|
|
31
|
-
throw new BeraError({
|
|
32
|
-
message: "useAutoclaimedIncentives needs a logged in account",
|
|
33
|
-
level: "error",
|
|
34
|
-
});
|
|
35
|
-
}
|
|
36
|
-
return await getAutoclaimedIncentives({ account });
|
|
37
|
-
},
|
|
28
|
+
async ([, account]) => getAutoclaimedIncentives({ account }),
|
|
38
29
|
{
|
|
39
30
|
revalidateOnFocus: false,
|
|
40
31
|
revalidateOnReconnect: false,
|
|
@@ -0,0 +1,45 @@
|
|
|
1
|
+
import useSWR from "swr";
|
|
2
|
+
|
|
3
|
+
import { useBeraWallet } from "@berachain/wagmi/hooks";
|
|
4
|
+
|
|
5
|
+
import { getAutoclaimedIncentivesTxHash } from "../../actions/pol/getAutoclaimedIncentivesTxHash";
|
|
6
|
+
import { POLLING } from "../../enum/polling";
|
|
7
|
+
import type { AutoclaimedIncentivesTxHashResponse } from "../../types/bribe-boost";
|
|
8
|
+
import type { DefaultHookReturnType } from "../../types/global";
|
|
9
|
+
|
|
10
|
+
interface UseAutoclaimedIncentivesTxHashArgs {
|
|
11
|
+
// Hard gate, identical to `useAutoclaimedIncentives` — only flip true once
|
|
12
|
+
// both `!isBgtPolRewardToken` and `bgtIncentiveDistributor.paused()` hold.
|
|
13
|
+
enabled: boolean;
|
|
14
|
+
}
|
|
15
|
+
|
|
16
|
+
export function useAutoclaimedIncentivesTxHash({
|
|
17
|
+
enabled,
|
|
18
|
+
}: UseAutoclaimedIncentivesTxHashArgs): DefaultHookReturnType<AutoclaimedIncentivesTxHashResponse | null> {
|
|
19
|
+
const { address: account } = useBeraWallet();
|
|
20
|
+
|
|
21
|
+
const QUERY_KEY =
|
|
22
|
+
enabled && account
|
|
23
|
+
? (["useAutoclaimedIncentivesTxHash", account] as const)
|
|
24
|
+
: null;
|
|
25
|
+
|
|
26
|
+
const swrResponse = useSWR(
|
|
27
|
+
QUERY_KEY,
|
|
28
|
+
async ([, account]) => getAutoclaimedIncentivesTxHash({ account }),
|
|
29
|
+
{
|
|
30
|
+
// txhashes flip from absent → present as the bot processes each token,
|
|
31
|
+
// so we need to actually poll. Cadence matches the bot's tx cadence
|
|
32
|
+
// (minutes, not seconds) and lets the user watch pending → claimed
|
|
33
|
+
// transitions without reloading the page.
|
|
34
|
+
refreshInterval: POLLING.NORMAL,
|
|
35
|
+
revalidateOnFocus: true,
|
|
36
|
+
revalidateOnReconnect: true,
|
|
37
|
+
revalidateIfStale: false,
|
|
38
|
+
},
|
|
39
|
+
);
|
|
40
|
+
|
|
41
|
+
return {
|
|
42
|
+
...swrResponse,
|
|
43
|
+
refresh: () => swrResponse?.mutate?.(),
|
|
44
|
+
};
|
|
45
|
+
}
|
|
@@ -40,12 +40,8 @@ export function useValidator(
|
|
|
40
40
|
"",
|
|
41
41
|
queuedBoostAmount:
|
|
42
42
|
indexerValidator?.dynamicData?.queuedBoostAmount ?? "",
|
|
43
|
-
|
|
44
|
-
indexerValidator?.dynamicData?.
|
|
45
|
-
usersQueuedBoostCount:
|
|
46
|
-
indexerValidator?.dynamicData?.usersQueuedBoostCount ?? 0,
|
|
47
|
-
allTimeDistributedBGTAmount:
|
|
48
|
-
indexerValidator?.dynamicData?.allTimeDistributedBGTAmount ??
|
|
43
|
+
allTimeDistributedRewards:
|
|
44
|
+
indexerValidator?.dynamicData?.allTimeDistributedRewards ??
|
|
49
45
|
"0",
|
|
50
46
|
rewardRate:
|
|
51
47
|
onChainValidator?.dynamicData?.rewardRate ??
|
|
@@ -53,8 +49,8 @@ export function useValidator(
|
|
|
53
49
|
"",
|
|
54
50
|
stakedBeraAmount:
|
|
55
51
|
indexerValidator?.dynamicData?.stakedBeraAmount ?? "",
|
|
56
|
-
|
|
57
|
-
indexerValidator?.dynamicData?.
|
|
52
|
+
lastDayDistributedRewards:
|
|
53
|
+
indexerValidator?.dynamicData?.lastDayDistributedRewards ??
|
|
58
54
|
"",
|
|
59
55
|
lastDayProposedBlockCount:
|
|
60
56
|
indexerValidator?.dynamicData?.lastDayProposedBlockCount ?? 0,
|
|
@@ -33,11 +33,22 @@ export interface BribeBoostRewardProof {
|
|
|
33
33
|
}
|
|
34
34
|
|
|
35
35
|
// Response shape of GET /api/v1/wallets/:wallet/autoclaimed:
|
|
36
|
-
// { [wallet]: { [validatorPubkey]: { [tokenAddress]:
|
|
37
|
-
//
|
|
36
|
+
// { [wallet]: { [validatorPubkey]: { [tokenAddress]: { amount } } } }
|
|
37
|
+
// `amount` is a stringified bigint in token base units. The backend now
|
|
38
|
+
// aggregates across validators under a single fake pubkey (`0x000…000`),
|
|
39
|
+
// so consumers can collapse the validator dimension blindly.
|
|
38
40
|
export type AutoclaimedIncentivesResponse = Record<
|
|
39
41
|
Address,
|
|
40
|
-
Record<Address, Record<Address, string>>
|
|
42
|
+
Record<Address, Record<Address, { amount: string }>>
|
|
43
|
+
>;
|
|
44
|
+
|
|
45
|
+
// Response shape of GET /api/v1/wallets/:wallet/autoclaimed/txhash: same
|
|
46
|
+
// nesting, but each token leaf is `{ txhash }`. Tokens still pending are
|
|
47
|
+
// simply absent — when a token from `/autoclaimed` has no entry here, the
|
|
48
|
+
// bot hasn't distributed it yet.
|
|
49
|
+
export type AutoclaimedIncentivesTxHashResponse = Record<
|
|
50
|
+
Address,
|
|
51
|
+
Record<Address, Record<Address, { txhash: string }>>
|
|
41
52
|
>;
|
|
42
53
|
|
|
43
54
|
export interface BribeBoostValidatorApr {
|
|
@@ -0,0 +1,53 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Polyfill for `AbortSignal.any`.
|
|
3
|
+
*
|
|
4
|
+
* `AbortSignal.any` ships in Chrome 116+, Safari 17.4+, Firefox 124+. Older
|
|
5
|
+
* environments — notably Android WebViews bundled with old Chromium (e.g.
|
|
6
|
+
* Chrome Mobile WebView 114) — lack it, and third-party libraries that call it
|
|
7
|
+
* (e.g. @pythnetwork/hermes-client) throw "AbortSignal.any is not a function".
|
|
8
|
+
*
|
|
9
|
+
* This installs a spec-compatible-enough fallback: it returns a controller's
|
|
10
|
+
* signal that aborts as soon as any of the input signals abort (propagating the
|
|
11
|
+
* abort reason). It is idempotent and a no-op when the native API exists.
|
|
12
|
+
*/
|
|
13
|
+
export function polyfillAbortSignalAny(): void {
|
|
14
|
+
if (typeof AbortSignal === "undefined") return;
|
|
15
|
+
if (typeof (AbortSignal as { any?: unknown }).any === "function") return;
|
|
16
|
+
|
|
17
|
+
(
|
|
18
|
+
AbortSignal as unknown as { any: (signals: AbortSignal[]) => AbortSignal }
|
|
19
|
+
).any = (signals: AbortSignal[]): AbortSignal => {
|
|
20
|
+
const controller = new AbortController();
|
|
21
|
+
|
|
22
|
+
// Phase 1: if any signal is already aborted, short-circuit before attaching
|
|
23
|
+
// any listeners (attaching to the others would leave dangling listeners
|
|
24
|
+
// that never fire — the leak the WeakRef/FinalizationRegistry versions guard
|
|
25
|
+
// against; checking first avoids it without that machinery).
|
|
26
|
+
for (const signal of signals) {
|
|
27
|
+
if (signal.aborted) {
|
|
28
|
+
controller.abort(signal.reason);
|
|
29
|
+
return controller.signal;
|
|
30
|
+
}
|
|
31
|
+
}
|
|
32
|
+
|
|
33
|
+
// Phase 2: forward the first abort, then detach every listener so none
|
|
34
|
+
// outlive the combined signal. Safe for our short-lived usage (per-request
|
|
35
|
+
// fetch signals that settle and are collected together).
|
|
36
|
+
const listeners: Array<() => void> = [];
|
|
37
|
+
const cleanup = () => {
|
|
38
|
+
for (let i = 0; i < signals.length; i++) {
|
|
39
|
+
signals[i].removeEventListener("abort", listeners[i]);
|
|
40
|
+
}
|
|
41
|
+
};
|
|
42
|
+
for (const signal of signals) {
|
|
43
|
+
const onAbort = () => {
|
|
44
|
+
if (!controller.signal.aborted) controller.abort(signal.reason);
|
|
45
|
+
cleanup();
|
|
46
|
+
};
|
|
47
|
+
listeners.push(onAbort);
|
|
48
|
+
signal.addEventListener("abort", onAbort);
|
|
49
|
+
}
|
|
50
|
+
|
|
51
|
+
return controller.signal;
|
|
52
|
+
};
|
|
53
|
+
}
|
|
@@ -0,0 +1,81 @@
|
|
|
1
|
+
import { afterEach, describe, expect, it } from "vitest";
|
|
2
|
+
|
|
3
|
+
import { polyfillAbortSignalAny } from "./polyfillAbortSignalAny.js";
|
|
4
|
+
|
|
5
|
+
describe("polyfillAbortSignalAny", () => {
|
|
6
|
+
const original = (AbortSignal as { any?: unknown }).any;
|
|
7
|
+
|
|
8
|
+
afterEach(() => {
|
|
9
|
+
// restore whatever the runtime had (native or undefined)
|
|
10
|
+
(AbortSignal as { any?: unknown }).any = original;
|
|
11
|
+
});
|
|
12
|
+
|
|
13
|
+
it("does not overwrite a native AbortSignal.any", () => {
|
|
14
|
+
const native = () => new AbortController().signal;
|
|
15
|
+
(AbortSignal as { any?: unknown }).any = native;
|
|
16
|
+
polyfillAbortSignalAny();
|
|
17
|
+
expect((AbortSignal as { any?: unknown }).any).toBe(native);
|
|
18
|
+
});
|
|
19
|
+
|
|
20
|
+
it("installs a fallback when AbortSignal.any is missing", () => {
|
|
21
|
+
(AbortSignal as { any?: unknown }).any = undefined;
|
|
22
|
+
polyfillAbortSignalAny();
|
|
23
|
+
expect(typeof (AbortSignal as { any?: unknown }).any).toBe("function");
|
|
24
|
+
});
|
|
25
|
+
|
|
26
|
+
it("aborts the combined signal when one input aborts", () => {
|
|
27
|
+
(AbortSignal as { any?: unknown }).any = undefined;
|
|
28
|
+
polyfillAbortSignalAny();
|
|
29
|
+
|
|
30
|
+
const a = new AbortController();
|
|
31
|
+
const b = new AbortController();
|
|
32
|
+
const combined = (
|
|
33
|
+
AbortSignal as unknown as { any: (s: AbortSignal[]) => AbortSignal }
|
|
34
|
+
).any([a.signal, b.signal]);
|
|
35
|
+
|
|
36
|
+
expect(combined.aborted).toBe(false);
|
|
37
|
+
b.abort("boom");
|
|
38
|
+
expect(combined.aborted).toBe(true);
|
|
39
|
+
expect(combined.reason).toBe("boom");
|
|
40
|
+
});
|
|
41
|
+
|
|
42
|
+
it("removes listeners from all inputs once the combined signal aborts", () => {
|
|
43
|
+
(AbortSignal as { any?: unknown }).any = undefined;
|
|
44
|
+
polyfillAbortSignalAny();
|
|
45
|
+
|
|
46
|
+
const a = new AbortController();
|
|
47
|
+
const b = new AbortController();
|
|
48
|
+
const removed: AbortSignal[] = [];
|
|
49
|
+
for (const c of [a, b]) {
|
|
50
|
+
const orig = c.signal.removeEventListener.bind(c.signal);
|
|
51
|
+
c.signal.removeEventListener = ((...args: Parameters<typeof orig>) => {
|
|
52
|
+
removed.push(c.signal);
|
|
53
|
+
return orig(...args);
|
|
54
|
+
}) as typeof orig;
|
|
55
|
+
}
|
|
56
|
+
|
|
57
|
+
(AbortSignal as unknown as { any: (s: AbortSignal[]) => AbortSignal }).any([
|
|
58
|
+
a.signal,
|
|
59
|
+
b.signal,
|
|
60
|
+
]);
|
|
61
|
+
|
|
62
|
+
a.abort("x");
|
|
63
|
+
// both inputs should have had their abort listener detached, not just the firing one
|
|
64
|
+
expect(removed).toContain(a.signal);
|
|
65
|
+
expect(removed).toContain(b.signal);
|
|
66
|
+
});
|
|
67
|
+
|
|
68
|
+
it("is already aborted if an input is already aborted", () => {
|
|
69
|
+
(AbortSignal as { any?: unknown }).any = undefined;
|
|
70
|
+
polyfillAbortSignalAny();
|
|
71
|
+
|
|
72
|
+
const a = new AbortController();
|
|
73
|
+
a.abort("early");
|
|
74
|
+
const combined = (
|
|
75
|
+
AbortSignal as unknown as { any: (s: AbortSignal[]) => AbortSignal }
|
|
76
|
+
).any([a.signal]);
|
|
77
|
+
|
|
78
|
+
expect(combined.aborted).toBe(true);
|
|
79
|
+
expect(combined.reason).toBe("early");
|
|
80
|
+
});
|
|
81
|
+
});
|
|
File without changes
|