solana-resilience-kit 1.0.1 → 1.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/README.md CHANGED
@@ -3,6 +3,7 @@
3
3
  [![npm version](https://img.shields.io/npm/v/solana-resilience-kit.svg)](https://www.npmjs.com/package/solana-resilience-kit)
4
4
  [![npm downloads](https://img.shields.io/npm/dm/solana-resilience-kit.svg)](https://www.npmjs.com/package/solana-resilience-kit)
5
5
  [![types included](https://img.shields.io/npm/types/solana-resilience-kit.svg)](https://www.npmjs.com/package/solana-resilience-kit)
6
+ [![coverage ≥90% (CI-enforced)](https://img.shields.io/badge/coverage-%E2%89%A590%25%20CI--gated-brightgreen)](./.github/workflows/ci.yml)
6
7
  [![license: MIT](https://img.shields.io/npm/l/solana-resilience-kit.svg)](./LICENSE)
7
8
 
8
9
  A vendor-neutral, **client-side resilience and observability layer for Solana dApps**, built on `@solana/kit` (web3.js v2). It unifies the reliability work that is today either left as a do-it-yourself recipe by the official SDK or locked inside a single provider: health-aware multi-RPC failover, a correct transaction send/confirm state machine, simulate-based fee/CU estimation, Jito/MEV routing with automatic RPC fallback, and standardized OpenTelemetry/Datadog telemetry — behind one clean API that works on top of any set of providers.
@@ -99,7 +100,7 @@ The pool exposes a real `@solana/kit` `RpcTransport`, so callers build a normal
99
100
  npm install solana-resilience-kit @solana/kit
100
101
  ```
101
102
 
102
- Requires Node ≥ 20. The package is ESM-only and ships compiled JS with type declarations. `@solana/kit` is a peer of your app and is used directly in the API surface.
103
+ Requires Node ≥ 20. The package is ESM-only and ships compiled JS with type declarations. **`@solana/kit` is a required peer dependency** (`^6.9.0`): install it alongside so your app and the SDK resolve to a *single* kit instance — this keeps kit's branded types (`Address`, `Signature`, `Base64EncodedWireTransaction`, …) compatible across the boundary. `@opentelemetry/api` is an **optional** peer, needed only if you use `OtelMetrics` (and is already pulled in transitively by the OpenTelemetry SDK packages — see [Wiring observability to OpenTelemetry / Datadog](#wiring-observability-to-opentelemetry--datadog)).
103
104
 
104
105
  ## Quickstart
105
106
 
@@ -143,13 +144,14 @@ The package ships an executable, `solana-resilience-diagnose`, built on the same
143
144
  `Diagnostics` core (`src/cli/diagnose.ts`). It answers the two questions an
144
145
  operator asks when a Solana dApp misbehaves — *which of my providers is healthy
145
146
  and freshest?* and *did this transaction land, expire, or is it still pending?* —
146
- without writing any code. Run it with `npx` (no install) or from a dependency's
147
- `node_modules/.bin`:
147
+ without writing any code. Run it zero-install with `npx`, or once the package
148
+ is a dependency — call `solana-resilience-diagnose` directly (it is on your
149
+ `node_modules/.bin`):
148
150
 
149
151
  ```bash
150
152
  # Probe provider health across one or more endpoints (reuses the pool's own
151
153
  # slot-freshness ranking, so "freshest" matches what routing would pick):
152
- npx solana-resilience-diagnose probe \
154
+ npx -p solana-resilience-kit solana-resilience-diagnose probe \
153
155
  --rpc https://api.mainnet-beta.solana.com \
154
156
  --rpc https://my-backup.rpc
155
157
  ```
@@ -167,7 +169,7 @@ Freshest: https://api.mainnet-beta.solana.com · 1/2 healthy.
167
169
  # Explain a transaction's outcome point-in-time (no polling loop): it compares
168
170
  # the current signature status and block height against lastValidBlockHeight —
169
171
  # the canonical Solana rule — and never re-signs.
170
- npx solana-resilience-diagnose explain \
172
+ npx -p solana-resilience-kit solana-resilience-diagnose explain \
171
173
  --rpc https://api.mainnet-beta.solana.com \
172
174
  --sig 5xRe...your-signature \
173
175
  --lvbh 287654321
@@ -192,6 +194,63 @@ expired transaction) · `2` a usage error. Run with no command, `help`, or
192
194
  `--help` to print usage. The argv parser is a pure, network-free function and is
193
195
  unit-tested in isolation (`test/cli/argv.test.ts`).
194
196
 
197
+ ## Wiring observability to OpenTelemetry / Datadog
198
+
199
+ The library depends on **only `@opentelemetry/api`** — `OtelMetrics` writes to the
200
+ *global* OpenTelemetry meter, which is a **no-op until your app registers a real
201
+ `MeterProvider`** with a reader + exporter. The OTel SDK and OTLP exporter are
202
+ your application's dependencies, not the SDK's, so you choose the backend. The
203
+ ~10 lines that make exports real:
204
+
205
+ ```ts
206
+ import { metrics } from "@opentelemetry/api";
207
+ import { MeterProvider, PeriodicExportingMetricReader } from "@opentelemetry/sdk-metrics";
208
+ import { OTLPMetricExporter } from "@opentelemetry/exporter-metrics-otlp-http";
209
+ import { OtelMetrics, ResilientRpcPool } from "solana-resilience-kit";
210
+
211
+ // 1. Register a working MeterProvider — without this, OtelMetrics is inert.
212
+ metrics.setGlobalMeterProvider(
213
+ new MeterProvider({
214
+ readers: [
215
+ new PeriodicExportingMetricReader({
216
+ // Reads OTEL_EXPORTER_OTLP_ENDPOINT, e.g. an OTel Collector or the
217
+ // Datadog Agent's OTLP intake (http://localhost:4318).
218
+ exporter: new OTLPMetricExporter(),
219
+ exportIntervalMillis: 10_000,
220
+ }),
221
+ ],
222
+ }),
223
+ );
224
+
225
+ // 2. Hand OtelMetrics to the SDK — now every signal below is exported.
226
+ const pool = new ResilientRpcPool({ endpoints, metrics: new OtelMetrics({ serviceName: "my-dapp" }) });
227
+ ```
228
+
229
+ Install the exporter side in your app (devtime/runtime, not in this library):
230
+
231
+ ```bash
232
+ npm install @opentelemetry/sdk-metrics @opentelemetry/exporter-metrics-otlp-http
233
+ ```
234
+
235
+ **For Datadog**, point `OTEL_EXPORTER_OTLP_ENDPOINT` at the Datadog Agent's OTLP
236
+ endpoint (enable OTLP ingestion in the Agent); no separate Collector needed.
237
+
238
+ The SDK emits a small, fixed set of client-side instruments:
239
+
240
+ | Instrument | Type | Attributes | Emitted when |
241
+ |---|---|---|---|
242
+ | `rpc.request.latency_ms` | histogram | `endpoint`, `method`, `ok` | every RPC request attempt (per endpoint) |
243
+ | `rpc.request.failures` | counter | `endpoint`, `method` | a request attempt fails |
244
+ | `rpc.rate_limited` | counter | `endpoint` | an attempt is rejected with HTTP 429 |
245
+ | `tx.rebroadcasts` | counter | `signature` | the sender rebroadcasts the signed transaction |
246
+ | `tx.landings` | counter | `signature`, `outcome`, `slots` | a transaction reaches a terminal outcome (`confirmed` / `expired`) |
247
+ | `rpc.endpoint.slot` | gauge | `endpoint` | a `getSlot` response is observed (slot-lag dashboards) |
248
+
249
+ A runnable end-to-end demo is in [`examples/otel-setup.ts`](./examples/otel-setup.ts):
250
+ `npm run example:otel` wires `OtelMetrics` into a pool + sender, drives simulated
251
+ sends against the harness, and exports all six instruments — with a console
252
+ exporter attached so you see every data point even without a collector running.
253
+
195
254
  ## Testing your own code against the fault harness
196
255
 
197
256
  The deterministic Solana cluster simulator the SDK is tested with is shipped as a
@@ -238,7 +297,7 @@ npm run test:cov # coverage with the thresholds enforced
238
297
  npm run typecheck # tsc --noEmit
239
298
  ```
240
299
 
241
- Coverage thresholds (`vitest.config.ts`) are **lines 90 / functions 90 / branches 85 / statements 90**, and the suite passes them. A fully reproducible Docker environment is available via the [`Makefile`](./Makefile):
300
+ Coverage thresholds (`vitest.config.ts`) are **lines 90 / functions 90 / branches 85 / statements 90**, and the suite passes them. **CI enforces this gate on every push and PR** — the `docker compose run --rm cov` step in [`ci.yml`](./.github/workflows/ci.yml) runs `npm run test:cov`, which exits non-zero (failing the build) if coverage drops below those thresholds. A fully reproducible Docker environment is available via the [`Makefile`](./Makefile):
242
301
 
243
302
  ```bash
244
303
  make verify # typecheck + always-green harness/metrics tests, in Docker
package/dist/cli/index.js CHANGED
@@ -19,6 +19,7 @@
19
19
  * tx) · 2 a usage error.
20
20
  */
21
21
  import { createSolanaRpc } from "@solana/kit";
22
+ import { realpathSync } from "node:fs";
22
23
  import { pathToFileURL } from "node:url";
23
24
  import { SdkError } from "../errors.js";
24
25
  import { Diagnostics } from "./diagnose.js";
@@ -211,8 +212,11 @@ export async function run(argv, deps = {}) {
211
212
  return result.status === "expired" ? 1 : 0;
212
213
  }
213
214
  /* v8 ignore start -- process bootstrap; only runs when invoked as the binary */
215
+ // Resolve symlinks: npm installs the bin as a symlink in node_modules/.bin, so
216
+ // process.argv[1] is that link while import.meta.url is the real dist file —
217
+ // comparing realpaths is what makes the binary fire when run by name.
214
218
  const entry = process.argv[1];
215
- if (entry !== undefined && import.meta.url === pathToFileURL(entry).href) {
219
+ if (entry !== undefined && import.meta.url === pathToFileURL(realpathSync(entry)).href) {
216
220
  run(process.argv.slice(2)).then((code) => {
217
221
  process.exitCode = code;
218
222
  }, (err) => {
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "solana-resilience-kit",
3
- "version": "1.0.1",
3
+ "version": "1.1.0",
4
4
  "description": "Vendor-neutral, client-side resilience and observability layer for Solana dApps, built on @solana/kit (web3.js v2).",
5
5
  "type": "module",
6
6
  "license": "MIT",
@@ -58,14 +58,24 @@
58
58
  "test:cov": "vitest run --coverage",
59
59
  "typecheck": "tsc --noEmit",
60
60
  "docs:api": "typedoc",
61
+ "example:otel": "tsx examples/otel-setup.ts",
61
62
  "prepublishOnly": "npm run typecheck && npm test && npm run build"
62
63
  },
63
- "dependencies": {
64
+ "peerDependencies": {
64
65
  "@opentelemetry/api": "^1.9.0",
65
66
  "@solana/kit": "^6.9.0"
66
67
  },
68
+ "peerDependenciesMeta": {
69
+ "@opentelemetry/api": {
70
+ "optional": true
71
+ }
72
+ },
67
73
  "devDependencies": {
74
+ "@opentelemetry/api": "^1.9.0",
75
+ "@opentelemetry/exporter-metrics-otlp-http": "^0.219.0",
76
+ "@opentelemetry/sdk-metrics": "^2.8.0",
68
77
  "@solana-program/system": "^0.12.2",
78
+ "@solana/kit": "^6.9.0",
69
79
  "@types/node": "^22.19.21",
70
80
  "@vitest/coverage-v8": "^4.1.9",
71
81
  "tsx": "^4.22.4",