@cubee_ee/sdk 0.1.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/LICENSE +21 -0
- package/README.md +110 -0
- package/dist/clients/AdminClient.d.ts +61 -0
- package/dist/clients/AdminClient.d.ts.map +1 -0
- package/dist/clients/AdminClient.js +196 -0
- package/dist/clients/AdminClient.js.map +1 -0
- package/dist/clients/CubeBackendClient.d.ts +67 -0
- package/dist/clients/CubeBackendClient.d.ts.map +1 -0
- package/dist/clients/CubeBackendClient.js +126 -0
- package/dist/clients/CubeBackendClient.js.map +1 -0
- package/dist/clients/CubicPoolClient.d.ts +73 -0
- package/dist/clients/CubicPoolClient.d.ts.map +1 -0
- package/dist/clients/CubicPoolClient.js +425 -0
- package/dist/clients/CubicPoolClient.js.map +1 -0
- package/dist/clients/PoolFactoryClient.d.ts +45 -0
- package/dist/clients/PoolFactoryClient.d.ts.map +1 -0
- package/dist/clients/PoolFactoryClient.js +83 -0
- package/dist/clients/PoolFactoryClient.js.map +1 -0
- package/dist/clients/RpcClient.d.ts +28 -0
- package/dist/clients/RpcClient.d.ts.map +1 -0
- package/dist/clients/RpcClient.js +58 -0
- package/dist/clients/RpcClient.js.map +1 -0
- package/dist/clients/SingleTokenDepositClient.d.ts +45 -0
- package/dist/clients/SingleTokenDepositClient.d.ts.map +1 -0
- package/dist/clients/SingleTokenDepositClient.js +63 -0
- package/dist/clients/SingleTokenDepositClient.js.map +1 -0
- package/dist/clients/index.d.ts +8 -0
- package/dist/clients/index.d.ts.map +1 -0
- package/dist/clients/index.js +24 -0
- package/dist/clients/index.js.map +1 -0
- package/dist/clients/tx-builders.d.ts +32 -0
- package/dist/clients/tx-builders.d.ts.map +1 -0
- package/dist/clients/tx-builders.js +398 -0
- package/dist/clients/tx-builders.js.map +1 -0
- package/dist/config/index.d.ts +60 -0
- package/dist/config/index.d.ts.map +1 -0
- package/dist/config/index.js +59 -0
- package/dist/config/index.js.map +1 -0
- package/dist/config/networks.d.ts +10 -0
- package/dist/config/networks.d.ts.map +1 -0
- package/dist/config/networks.js +31 -0
- package/dist/config/networks.js.map +1 -0
- package/dist/config/tokens.d.ts +19 -0
- package/dist/config/tokens.d.ts.map +1 -0
- package/dist/config/tokens.js +43 -0
- package/dist/config/tokens.js.map +1 -0
- package/dist/idl/cubic_pool.json +2497 -0
- package/dist/idl/index.d.ts +975 -0
- package/dist/idl/index.d.ts.map +1 -0
- package/dist/idl/index.js +18 -0
- package/dist/idl/index.js.map +1 -0
- package/dist/idl/protocol_admin.json +1816 -0
- package/dist/idl/single_token_liquidity.json +745 -0
- package/dist/index.d.ts +48 -0
- package/dist/index.d.ts.map +1 -0
- package/dist/index.js +136 -0
- package/dist/index.js.map +1 -0
- package/dist/math/cubicMath.d.ts +40 -0
- package/dist/math/cubicMath.d.ts.map +1 -0
- package/dist/math/cubicMath.js +75 -0
- package/dist/math/cubicMath.js.map +1 -0
- package/dist/math/fixedPoint.d.ts +14 -0
- package/dist/math/fixedPoint.d.ts.map +1 -0
- package/dist/math/fixedPoint.js +46 -0
- package/dist/math/fixedPoint.js.map +1 -0
- package/dist/math/index.d.ts +7 -0
- package/dist/math/index.d.ts.map +1 -0
- package/dist/math/index.js +23 -0
- package/dist/math/index.js.map +1 -0
- package/dist/math/logExp.d.ts +15 -0
- package/dist/math/logExp.d.ts.map +1 -0
- package/dist/math/logExp.js +91 -0
- package/dist/math/logExp.js.map +1 -0
- package/dist/math/singleToken.d.ts +53 -0
- package/dist/math/singleToken.d.ts.map +1 -0
- package/dist/math/singleToken.js +185 -0
- package/dist/math/singleToken.js.map +1 -0
- package/dist/math/slippage.d.ts +24 -0
- package/dist/math/slippage.d.ts.map +1 -0
- package/dist/math/slippage.js +54 -0
- package/dist/math/slippage.js.map +1 -0
- package/dist/math/weightedMath.d.ts +18 -0
- package/dist/math/weightedMath.d.ts.map +1 -0
- package/dist/math/weightedMath.js +45 -0
- package/dist/math/weightedMath.js.map +1 -0
- package/dist/parsers/borsh.d.ts +24 -0
- package/dist/parsers/borsh.d.ts.map +1 -0
- package/dist/parsers/borsh.js +80 -0
- package/dist/parsers/borsh.js.map +1 -0
- package/dist/parsers/events.d.ts +3 -0
- package/dist/parsers/events.d.ts.map +1 -0
- package/dist/parsers/events.js +172 -0
- package/dist/parsers/events.js.map +1 -0
- package/dist/parsers/index.d.ts +5 -0
- package/dist/parsers/index.d.ts.map +1 -0
- package/dist/parsers/index.js +21 -0
- package/dist/parsers/index.js.map +1 -0
- package/dist/parsers/mintAccount.d.ts +22 -0
- package/dist/parsers/mintAccount.d.ts.map +1 -0
- package/dist/parsers/mintAccount.js +22 -0
- package/dist/parsers/mintAccount.js.map +1 -0
- package/dist/parsers/poolAccount.d.ts +32 -0
- package/dist/parsers/poolAccount.d.ts.map +1 -0
- package/dist/parsers/poolAccount.js +88 -0
- package/dist/parsers/poolAccount.js.map +1 -0
- package/dist/types/events.d.ts +82 -0
- package/dist/types/events.d.ts.map +1 -0
- package/dist/types/events.js +3 -0
- package/dist/types/events.js.map +1 -0
- package/dist/types/index.d.ts +5 -0
- package/dist/types/index.d.ts.map +1 -0
- package/dist/types/index.js +21 -0
- package/dist/types/index.js.map +1 -0
- package/dist/types/pool.d.ts +66 -0
- package/dist/types/pool.d.ts.map +1 -0
- package/dist/types/pool.js +3 -0
- package/dist/types/pool.js.map +1 -0
- package/dist/types/result.d.ts +23 -0
- package/dist/types/result.d.ts.map +1 -0
- package/dist/types/result.js +11 -0
- package/dist/types/result.js.map +1 -0
- package/dist/types/tx.d.ts +80 -0
- package/dist/types/tx.d.ts.map +1 -0
- package/dist/types/tx.js +3 -0
- package/dist/types/tx.js.map +1 -0
- package/dist/utils/errors.d.ts +8 -0
- package/dist/utils/errors.d.ts.map +1 -0
- package/dist/utils/errors.js +83 -0
- package/dist/utils/errors.js.map +1 -0
- package/dist/utils/index.d.ts +4 -0
- package/dist/utils/index.d.ts.map +1 -0
- package/dist/utils/index.js +20 -0
- package/dist/utils/index.js.map +1 -0
- package/dist/utils/pda.d.ts +8 -0
- package/dist/utils/pda.d.ts.map +1 -0
- package/dist/utils/pda.js +27 -0
- package/dist/utils/pda.js.map +1 -0
- package/dist/utils/retry.d.ts +22 -0
- package/dist/utils/retry.d.ts.map +1 -0
- package/dist/utils/retry.js +62 -0
- package/dist/utils/retry.js.map +1 -0
- package/package.json +54 -0
- package/src/clients/AdminClient.ts +254 -0
- package/src/clients/CubeBackendClient.ts +193 -0
- package/src/clients/CubicPoolClient.ts +492 -0
- package/src/clients/PoolFactoryClient.ts +102 -0
- package/src/clients/RpcClient.ts +78 -0
- package/src/clients/SingleTokenDepositClient.ts +91 -0
- package/src/clients/index.ts +7 -0
- package/src/clients/tx-builders.ts +538 -0
- package/src/config/index.ts +82 -0
- package/src/config/networks.ts +49 -0
- package/src/config/tokens.ts +52 -0
- package/src/idl/cubic_pool.json +2497 -0
- package/src/idl/index.ts +13 -0
- package/src/idl/protocol_admin.json +1816 -0
- package/src/idl/single_token_liquidity.json +745 -0
- package/src/index.ts +154 -0
- package/src/math/cubicMath.ts +89 -0
- package/src/math/fixedPoint.ts +39 -0
- package/src/math/index.ts +6 -0
- package/src/math/logExp.ts +82 -0
- package/src/math/singleToken.ts +250 -0
- package/src/math/slippage.ts +49 -0
- package/src/math/weightedMath.ts +48 -0
- package/src/parsers/borsh.ts +80 -0
- package/src/parsers/events.ts +172 -0
- package/src/parsers/index.ts +4 -0
- package/src/parsers/mintAccount.ts +37 -0
- package/src/parsers/poolAccount.ts +113 -0
- package/src/types/events.ts +100 -0
- package/src/types/index.ts +4 -0
- package/src/types/pool.ts +64 -0
- package/src/types/result.ts +45 -0
- package/src/types/tx.ts +87 -0
- package/src/utils/errors.ts +78 -0
- package/src/utils/index.ts +3 -0
- package/src/utils/pda.ts +58 -0
- package/src/utils/retry.ts +85 -0
package/src/types/tx.ts
ADDED
|
@@ -0,0 +1,87 @@
|
|
|
1
|
+
import { PublicKey, TransactionInstruction } from "@solana/web3.js";
|
|
2
|
+
import BN from "bn.js";
|
|
3
|
+
|
|
4
|
+
export interface SwapParams {
|
|
5
|
+
user: PublicKey;
|
|
6
|
+
tokenInIndex: number;
|
|
7
|
+
tokenOutIndex: number;
|
|
8
|
+
amountIn: BN;
|
|
9
|
+
/** Hundredths-bps; omit to use SDK config default. */
|
|
10
|
+
slippageHundredthsBps?: number;
|
|
11
|
+
/** Optional explicit minimum; overrides slippage-derived value. */
|
|
12
|
+
minAmountOut?: BN;
|
|
13
|
+
}
|
|
14
|
+
|
|
15
|
+
export interface SwapQuote {
|
|
16
|
+
tokenInIndex: number;
|
|
17
|
+
tokenOutIndex: number;
|
|
18
|
+
amountIn: BN;
|
|
19
|
+
amountOut: BN;
|
|
20
|
+
/** Spot-based upper bound on amountOut; useful for price-impact UI. */
|
|
21
|
+
spotOut: BN;
|
|
22
|
+
/** Absolute price impact in hundredths of basis point. */
|
|
23
|
+
priceImpactHbps: number;
|
|
24
|
+
feeAmount: BN;
|
|
25
|
+
protocolFeeAmount: BN;
|
|
26
|
+
/** Minimum amount_out to pass to the swap ix given the quoted slippage. */
|
|
27
|
+
minAmountOut: BN;
|
|
28
|
+
}
|
|
29
|
+
|
|
30
|
+
export interface AddLiquidityParams {
|
|
31
|
+
user: PublicKey;
|
|
32
|
+
tokenAmounts: BN[];
|
|
33
|
+
minimumBptAmount?: BN;
|
|
34
|
+
}
|
|
35
|
+
|
|
36
|
+
export interface RemoveLiquidityParams {
|
|
37
|
+
user: PublicKey;
|
|
38
|
+
bptAmount: BN;
|
|
39
|
+
minimumTokenAmounts?: BN[];
|
|
40
|
+
}
|
|
41
|
+
|
|
42
|
+
export interface SingleTokenDepositParams {
|
|
43
|
+
user: PublicKey;
|
|
44
|
+
tokenInIndex: number;
|
|
45
|
+
amountIn: BN;
|
|
46
|
+
slippageHundredthsBps?: number;
|
|
47
|
+
minimumBptAmount?: BN;
|
|
48
|
+
}
|
|
49
|
+
|
|
50
|
+
export interface SingleTokenDepositQuote {
|
|
51
|
+
tokenInIndex: number;
|
|
52
|
+
amountIn: BN;
|
|
53
|
+
/** Per-token allocations (sum = amountIn). */
|
|
54
|
+
allocations: BN[];
|
|
55
|
+
/** Per-leg expected swap out. `0` for sidelined tokens. */
|
|
56
|
+
expectedOuts: BN[];
|
|
57
|
+
/** Per-leg min_out derived from slippage. */
|
|
58
|
+
minOuts: BN[];
|
|
59
|
+
/** Amounts the helper will pass to add_liquidity after proportional capping. */
|
|
60
|
+
depositedAmounts: BN[];
|
|
61
|
+
/** Helper-held excess returned to the user after add_liquidity. */
|
|
62
|
+
refundAmounts: BN[];
|
|
63
|
+
/** Projected BPT to receive (ballpark, pre-CPI). */
|
|
64
|
+
estimatedBpt: BN;
|
|
65
|
+
/** Indices of tokens excluded from the deposit (actBal == 0). */
|
|
66
|
+
sidelinedTokenIndices: number[];
|
|
67
|
+
}
|
|
68
|
+
|
|
69
|
+
export interface BuiltTx {
|
|
70
|
+
instructions: TransactionInstruction[];
|
|
71
|
+
/** Accounts that should sign (user is implicit). */
|
|
72
|
+
extraSigners?: PublicKey[];
|
|
73
|
+
/** Suggested CU limit — caller decides whether to prepend ComputeBudget ix. */
|
|
74
|
+
suggestedCuLimit: number;
|
|
75
|
+
}
|
|
76
|
+
|
|
77
|
+
export interface DeployPoolParams {
|
|
78
|
+
payer: PublicKey;
|
|
79
|
+
configKey: PublicKey;
|
|
80
|
+
poolId: BN;
|
|
81
|
+
tokens: PublicKey[];
|
|
82
|
+
weightsBps: number[];
|
|
83
|
+
virtualBalances: BN[];
|
|
84
|
+
swapFeeRate: number;
|
|
85
|
+
/** SPL Token program to use for the BPT mint (classic SPL Token recommended). */
|
|
86
|
+
bptTokenProgram?: PublicKey;
|
|
87
|
+
}
|
|
@@ -0,0 +1,78 @@
|
|
|
1
|
+
import { SdkError, SdkErrorCode } from "../types/result";
|
|
2
|
+
|
|
3
|
+
const CONTRACT_ERROR_MAP: Record<number, { code: SdkErrorCode; message: string }> = {
|
|
4
|
+
// cubic-pool error codes (see programs/cubic-pool/src/errors.rs)
|
|
5
|
+
6000: { code: "invalid_input", message: "Invalid token count" },
|
|
6
|
+
6001: { code: "invalid_input", message: "Invalid token index" },
|
|
7
|
+
6002: { code: "invalid_input", message: "Invalid weights — must sum to 10000" },
|
|
8
|
+
6003: { code: "invalid_input", message: "Invalid virtual balances" },
|
|
9
|
+
6004: { code: "insufficient_funds", message: "Insufficient pool liquidity" },
|
|
10
|
+
6005: { code: "slippage_exceeded", message: "Slippage tolerance exceeded" },
|
|
11
|
+
6006: { code: "invalid_input", message: "Invalid token amounts" },
|
|
12
|
+
6007: { code: "slippage_exceeded", message: "BPT received is below the minimum requested" },
|
|
13
|
+
6009: { code: "invalid_input", message: "Swap fee rate exceeds the allowed maximum" },
|
|
14
|
+
6011: { code: "math_overflow", message: "Math overflow" },
|
|
15
|
+
6012: { code: "math_overflow", message: "Math underflow" },
|
|
16
|
+
6022: { code: "pool_disabled", message: "Pool is disabled" },
|
|
17
|
+
6023: { code: "swaps_disabled", message: "Swaps are disabled on this pool" },
|
|
18
|
+
6025: { code: "invalid_input", message: "Zero amount" },
|
|
19
|
+
};
|
|
20
|
+
|
|
21
|
+
/**
|
|
22
|
+
* Convert a raw error (possibly from Anchor, web3.js, or fetch) into a
|
|
23
|
+
* cleanly-typed SdkError. Used by safeCall wrappers so callers always
|
|
24
|
+
* get structured errors, never exceptions.
|
|
25
|
+
*/
|
|
26
|
+
export function toSdkError(cause: unknown): SdkError {
|
|
27
|
+
// Anchor-encoded program errors
|
|
28
|
+
const msg = extractMessage(cause);
|
|
29
|
+
const code = extractErrorCode(msg);
|
|
30
|
+
if (code !== null && CONTRACT_ERROR_MAP[code]) {
|
|
31
|
+
return {
|
|
32
|
+
code: CONTRACT_ERROR_MAP[code].code,
|
|
33
|
+
humanMessage: CONTRACT_ERROR_MAP[code].message,
|
|
34
|
+
cause,
|
|
35
|
+
};
|
|
36
|
+
}
|
|
37
|
+
// Common network strings
|
|
38
|
+
if (/timed? ?out|ETIMEDOUT/i.test(msg)) {
|
|
39
|
+
return { code: "rpc_timeout", humanMessage: "RPC timed out", cause };
|
|
40
|
+
}
|
|
41
|
+
if (/429|rate limit/i.test(msg)) {
|
|
42
|
+
return { code: "rpc_rate_limited", humanMessage: "RPC rate-limited — slow down", cause };
|
|
43
|
+
}
|
|
44
|
+
if (/fetch failed|ECONNREFUSED|ENOTFOUND/i.test(msg)) {
|
|
45
|
+
return { code: "rpc_unavailable", humanMessage: "RPC endpoint unreachable", cause };
|
|
46
|
+
}
|
|
47
|
+
if (/Account does not exist|account not found/i.test(msg)) {
|
|
48
|
+
return { code: "account_not_found", humanMessage: "Account does not exist on-chain", cause };
|
|
49
|
+
}
|
|
50
|
+
if (/insufficient funds/i.test(msg)) {
|
|
51
|
+
return { code: "insufficient_funds", humanMessage: "Insufficient funds for this operation", cause };
|
|
52
|
+
}
|
|
53
|
+
return { code: "unknown", humanMessage: msg || "Unknown error", cause };
|
|
54
|
+
}
|
|
55
|
+
|
|
56
|
+
function extractMessage(e: unknown): string {
|
|
57
|
+
if (!e) return "";
|
|
58
|
+
if (typeof e === "string") return e;
|
|
59
|
+
if (e instanceof Error) return e.message;
|
|
60
|
+
if (typeof e === "object" && e !== null && "message" in e) {
|
|
61
|
+
return String((e as { message?: string }).message ?? "");
|
|
62
|
+
}
|
|
63
|
+
try {
|
|
64
|
+
return JSON.stringify(e);
|
|
65
|
+
} catch {
|
|
66
|
+
return String(e);
|
|
67
|
+
}
|
|
68
|
+
}
|
|
69
|
+
|
|
70
|
+
function extractErrorCode(msg: string): number | null {
|
|
71
|
+
// Anchor error format: "custom program error: 0x1774"
|
|
72
|
+
const hex = msg.match(/custom program error: 0x([0-9a-fA-F]+)/);
|
|
73
|
+
if (hex) return parseInt(hex[1], 16);
|
|
74
|
+
// Anchor SDK format: "Error Number: 6007"
|
|
75
|
+
const dec = msg.match(/Error Number:\s*(\d+)/);
|
|
76
|
+
if (dec) return parseInt(dec[1], 10);
|
|
77
|
+
return null;
|
|
78
|
+
}
|
package/src/utils/pda.ts
ADDED
|
@@ -0,0 +1,58 @@
|
|
|
1
|
+
import { PublicKey } from "@solana/web3.js";
|
|
2
|
+
import BN from "bn.js";
|
|
3
|
+
import { ASSOCIATED_TOKEN_PROGRAM_ID } from "@solana/spl-token";
|
|
4
|
+
import {
|
|
5
|
+
BPT_MINT_SEED,
|
|
6
|
+
CUBIC_POOL_SEED,
|
|
7
|
+
STLD_HELPER_SEED,
|
|
8
|
+
TREASURY_SEED,
|
|
9
|
+
} from "../config";
|
|
10
|
+
|
|
11
|
+
export function derivePoolPda(
|
|
12
|
+
programId: PublicKey,
|
|
13
|
+
config: PublicKey,
|
|
14
|
+
poolId: BN
|
|
15
|
+
): [PublicKey, number] {
|
|
16
|
+
return PublicKey.findProgramAddressSync(
|
|
17
|
+
[CUBIC_POOL_SEED, config.toBuffer(), poolId.toArrayLike(Buffer, "le", 8)],
|
|
18
|
+
programId
|
|
19
|
+
);
|
|
20
|
+
}
|
|
21
|
+
|
|
22
|
+
export function deriveBptMint(
|
|
23
|
+
cubicPoolProgramId: PublicKey,
|
|
24
|
+
pool: PublicKey
|
|
25
|
+
): [PublicKey, number] {
|
|
26
|
+
return PublicKey.findProgramAddressSync(
|
|
27
|
+
[BPT_MINT_SEED, pool.toBuffer()],
|
|
28
|
+
cubicPoolProgramId
|
|
29
|
+
);
|
|
30
|
+
}
|
|
31
|
+
|
|
32
|
+
export function deriveHelperPda(
|
|
33
|
+
stldProgramId: PublicKey,
|
|
34
|
+
pool: PublicKey
|
|
35
|
+
): [PublicKey, number] {
|
|
36
|
+
return PublicKey.findProgramAddressSync(
|
|
37
|
+
[STLD_HELPER_SEED, pool.toBuffer()],
|
|
38
|
+
stldProgramId
|
|
39
|
+
);
|
|
40
|
+
}
|
|
41
|
+
|
|
42
|
+
export function deriveTreasuryPda(
|
|
43
|
+
protocolFeesProgramId: PublicKey
|
|
44
|
+
): [PublicKey, number] {
|
|
45
|
+
return PublicKey.findProgramAddressSync([TREASURY_SEED], protocolFeesProgramId);
|
|
46
|
+
}
|
|
47
|
+
|
|
48
|
+
export function deriveAta(
|
|
49
|
+
owner: PublicKey,
|
|
50
|
+
mint: PublicKey,
|
|
51
|
+
tokenProgram: PublicKey
|
|
52
|
+
): PublicKey {
|
|
53
|
+
const [ata] = PublicKey.findProgramAddressSync(
|
|
54
|
+
[owner.toBuffer(), tokenProgram.toBuffer(), mint.toBuffer()],
|
|
55
|
+
ASSOCIATED_TOKEN_PROGRAM_ID
|
|
56
|
+
);
|
|
57
|
+
return ata;
|
|
58
|
+
}
|
|
@@ -0,0 +1,85 @@
|
|
|
1
|
+
import { SdkResult, err, ok } from "../types/result";
|
|
2
|
+
import { toSdkError } from "./errors";
|
|
3
|
+
|
|
4
|
+
export interface RetryOptions {
|
|
5
|
+
/** Number of attempts total (1 = no retry). */
|
|
6
|
+
attempts?: number;
|
|
7
|
+
/** Per-attempt timeout (ms). */
|
|
8
|
+
timeoutMs?: number;
|
|
9
|
+
/** Backoff schedule (ms) between attempts. */
|
|
10
|
+
backoffMs?: number[];
|
|
11
|
+
/** Additional predicate: return `false` to stop retrying early. */
|
|
12
|
+
shouldRetry?: (error: unknown) => boolean;
|
|
13
|
+
}
|
|
14
|
+
|
|
15
|
+
const DEFAULT_BACKOFF_MS = [200, 500, 1500];
|
|
16
|
+
|
|
17
|
+
/**
|
|
18
|
+
* Retry-wrapped helper. Returns a SdkResult<T>. Never throws. Use for any
|
|
19
|
+
* I/O: RPC calls, backend fetches, CPI simulations.
|
|
20
|
+
*
|
|
21
|
+
* ```ts
|
|
22
|
+
* const res = await safeCall(() => connection.getAccountInfo(pk), { attempts: 3 });
|
|
23
|
+
* if (!res.ok) showToast(res.error.humanMessage);
|
|
24
|
+
* ```
|
|
25
|
+
*/
|
|
26
|
+
export async function safeCall<T>(
|
|
27
|
+
operation: () => Promise<T>,
|
|
28
|
+
opts: RetryOptions = {}
|
|
29
|
+
): Promise<SdkResult<T>> {
|
|
30
|
+
const attempts = opts.attempts ?? 3;
|
|
31
|
+
const backoff = opts.backoffMs ?? DEFAULT_BACKOFF_MS;
|
|
32
|
+
const timeoutMs = opts.timeoutMs ?? 15_000;
|
|
33
|
+
const shouldRetry = opts.shouldRetry ?? defaultShouldRetry;
|
|
34
|
+
|
|
35
|
+
let lastError: unknown;
|
|
36
|
+
for (let attempt = 0; attempt < attempts; attempt++) {
|
|
37
|
+
try {
|
|
38
|
+
const raced = await withTimeout(operation(), timeoutMs);
|
|
39
|
+
return ok(raced);
|
|
40
|
+
} catch (e) {
|
|
41
|
+
lastError = e;
|
|
42
|
+
if (!shouldRetry(e)) break;
|
|
43
|
+
if (attempt < attempts - 1) {
|
|
44
|
+
const delayIdx = Math.min(attempt, backoff.length - 1);
|
|
45
|
+
await sleep(backoff[delayIdx]);
|
|
46
|
+
}
|
|
47
|
+
}
|
|
48
|
+
}
|
|
49
|
+
return err(
|
|
50
|
+
toSdkError(lastError).code,
|
|
51
|
+
toSdkError(lastError).humanMessage,
|
|
52
|
+
lastError
|
|
53
|
+
);
|
|
54
|
+
}
|
|
55
|
+
|
|
56
|
+
function defaultShouldRetry(e: unknown): boolean {
|
|
57
|
+
const err = toSdkError(e);
|
|
58
|
+
// Retry on transient network / rate-limit errors, stop on structural ones.
|
|
59
|
+
return (
|
|
60
|
+
err.code === "rpc_timeout" ||
|
|
61
|
+
err.code === "rpc_unavailable" ||
|
|
62
|
+
err.code === "rpc_rate_limited" ||
|
|
63
|
+
err.code === "backend_unavailable"
|
|
64
|
+
);
|
|
65
|
+
}
|
|
66
|
+
|
|
67
|
+
function sleep(ms: number): Promise<void> {
|
|
68
|
+
return new Promise((r) => setTimeout(r, ms));
|
|
69
|
+
}
|
|
70
|
+
|
|
71
|
+
function withTimeout<T>(p: Promise<T>, ms: number): Promise<T> {
|
|
72
|
+
return new Promise((resolve, reject) => {
|
|
73
|
+
const h = setTimeout(() => reject(new Error(`timed out after ${ms}ms`)), ms);
|
|
74
|
+
p.then(
|
|
75
|
+
(v) => {
|
|
76
|
+
clearTimeout(h);
|
|
77
|
+
resolve(v);
|
|
78
|
+
},
|
|
79
|
+
(e) => {
|
|
80
|
+
clearTimeout(h);
|
|
81
|
+
reject(e);
|
|
82
|
+
}
|
|
83
|
+
);
|
|
84
|
+
});
|
|
85
|
+
}
|