@quicknode/sdk 2.6.0 → 3.0.0-alpha.11

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.
Files changed (47) hide show
  1. package/README.md +212 -46
  2. package/browser.js +9 -0
  3. package/errors.js +121 -0
  4. package/index.d.ts +2286 -650
  5. package/index.darwin-arm64.node +0 -0
  6. package/index.js +328 -0
  7. package/index.linux-arm64-gnu.node +0 -0
  8. package/index.linux-arm64-musl.node +0 -0
  9. package/index.linux-x64-gnu.node +0 -0
  10. package/index.linux-x64-musl.node +0 -0
  11. package/package.json +48 -54
  12. package/sdk.d.ts +389 -0
  13. package/sdk.js +88 -0
  14. package/sdk.mjs +34 -0
  15. package/cjs/index.js +0 -542
  16. package/cjs/package.json +0 -3
  17. package/esm/client/client.js +0 -9
  18. package/esm/client/index.js +0 -2
  19. package/esm/core/addOns/nftTokenV2/actions.js +0 -107
  20. package/esm/core/addOns/nftTokenV2/types/qn_fetchNFTCollectionDetails.js +0 -10
  21. package/esm/core/addOns/nftTokenV2/types/qn_fetchNFTs.js +0 -13
  22. package/esm/core/addOns/nftTokenV2/types/qn_fetchNFTsByCollection.js +0 -13
  23. package/esm/core/addOns/nftTokenV2/types/qn_getTokenMetadataByContractAddress.js +0 -10
  24. package/esm/core/addOns/nftTokenV2/types/qn_getTokenMetadataBySymbol.js +0 -11
  25. package/esm/core/addOns/nftTokenV2/types/qn_getTransactionsByAddress.js +0 -18
  26. package/esm/core/addOns/nftTokenV2/types/qn_getTransfersByNFT.js +0 -12
  27. package/esm/core/addOns/nftTokenV2/types/qn_getWalletTokenBalance.js +0 -12
  28. package/esm/core/addOns/nftTokenV2/types/qn_getWalletTokenTransactions.js +0 -14
  29. package/esm/core/addOns/nftTokenV2/types/qn_verifyNFTsOwner.js +0 -11
  30. package/esm/core/addOns/shared/helpers.js +0 -10
  31. package/esm/core/chains.js +0 -118
  32. package/esm/core/core.js +0 -30
  33. package/esm/core/index.d.ts +0 -558
  34. package/esm/core/index.js +0 -2
  35. package/esm/index.js +0 -11
  36. package/esm/lib/constants.js +0 -4
  37. package/esm/lib/errors/QNChainNotSupported.js +0 -7
  38. package/esm/lib/errors/QNInputValidationError.js +0 -10
  39. package/esm/lib/errors/QNInvalidEnpointUrl.js +0 -7
  40. package/esm/lib/helpers/getClientHeaders.js +0 -11
  41. package/esm/lib/helpers/globalFetch.js +0 -13
  42. package/esm/lib/validation/ValidateInput.js +0 -16
  43. package/esm/lib/validation/validators.js +0 -15
  44. package/esm/package.json +0 -4
  45. package/esm/solana/index.d.ts +0 -76
  46. package/esm/solana/index.js +0 -2
  47. package/esm/solana/solana.js +0 -111
package/README.md CHANGED
@@ -1,80 +1,246 @@
1
- # QuickNode SDK
1
+ # Quicknode SDK
2
2
 
3
- A SDK from [QuickNode](https://www.quicknode.com/) making it easy for developers to interact with QuickNode's services.
3
+ A unified SDK for building on Quicknode.
4
4
 
5
- QuickNode's SDK is a JavaScript and TypeScript framework-agnostic library that supports both CommonJS and ES module systems.
5
+ Rust SDK with Python, Node.js, and Ruby bindings.
6
6
 
7
- > :grey_question: We want to hear from you! Please take a few minutes to fill out our [QuickNode SDK feedback form](https://forms.gle/vWFXDDjEUySjWUof6) and let us know what you currently think about the SDK. This helps us further improve the SDK.
7
+ ## Table of Contents
8
8
 
9
- [![Coverage Status](https://coveralls.io/repos/github/quiknode-labs/qn-oss/badge.svg?branch=main)](https://coveralls.io/github/quiknode-labs/qn-oss?branch=main)
10
- [![npm](https://img.shields.io/npm/dm/@quicknode/sdk)](https://www.npmjs.com/package/@quicknode/sdk)
11
- [![npm](https://img.shields.io/npm/v/@quicknode/sdk?color=g)](https://www.npmjs.com/package/@quicknode/sdk)
12
- ![Maintenance](https://img.shields.io/maintenance/yes/2023?color=g)
13
- [![License](https://img.shields.io/npm/l/@quicknode/sdk?color=g)](https://github.com/quiknode-labs/qn-oss/blob/main/LICENSE.txt)
14
- [![GitHub issues](https://img.shields.io/github/issues-raw/quiknode-labs/qn-oss?color=g)](https://github.com/quiknode-labs/qn-oss/issues)
15
- [![Discord](https://img.shields.io/discord/880505845090250794?color=g)](https://discord.gg/DkdgEqE)
9
+ - [Per-language docs](#per-language-docs)
10
+ - [Project Structure](#project-structure)
11
+ - [Installation](#installation)
12
+ - [Development](#development)
13
+ - [Prerequisites](#prerequisites)
14
+ - [Build Commands](#build-commands)
15
+ - [Testing](#testing)
16
+ - [Examples](#examples)
17
+ - [Releasing](#releasing)
18
+ - [Rust crate only (crates.io)](#rust-crate-only-cratesio)
19
+ - [All bindings together (Python / Node / Ruby)](#all-bindings-together-python--node--ruby)
20
+ - [npm publish (`@quicknode/sdk`)](#npm-publish-quicknodesdk)
21
+ - [PyPI publish (`quicknode-sdk`)](#pypi-publish-quicknode-sdk)
22
+ - [License](#license)
16
23
 
17
- <br>
24
+ ## Per-language docs
18
25
 
19
- ## Getting Started
26
+ API reference, configuration, and error handling for each language live next to the package — those are also the docs that render on each package listing.
20
27
 
21
- ### Installation
28
+ - **Rust** — [`crates/core/README.md`](crates/core/README.md) (`quicknode-sdk` on crates.io)
29
+ - **Python** — [`python/README.md`](python/README.md) (`quicknode-sdk` on PyPI)
30
+ - **Node.js** — [`npm/README.md`](npm/README.md) (`@quicknode/sdk` on npm)
31
+ - **Ruby** — [`ruby/README.md`](ruby/README.md) (`quicknode_sdk` on RubyGems)
22
32
 
23
- - Requires Node.js v16 or higher
24
- - `npm install @quicknode/sdk` or `yarn add @quicknode/sdk`
33
+ This file covers project structure, install index, and how to develop and release the SDK.
25
34
 
26
- <br>
35
+ ## Project Structure
27
36
 
28
- ### Quickstart
37
+ ```
38
+ sdk/
39
+ ├── crates/
40
+ │ ├── core/ # Pure Rust business logic
41
+ │ ├── python/ # PyO3 bindings
42
+ │ ├── node/ # napi-rs bindings
43
+ │ └── ruby/ # magnus bindings
44
+ ├── python/sdk/ # Python package with type hints
45
+ ├── npm/ # Node.js package with TypeScript types
46
+ ├── ruby/ # Ruby package
47
+ └── pyproject.toml # maturin build config
48
+ ```
49
+
50
+ ## Installation
51
+
52
+ | Language | Install |
53
+ |---|---|
54
+ | Rust | `cargo add quicknode-sdk` — see [`crates/core/README.md`](crates/core/README.md) |
55
+ | Python | `uv add quicknode-sdk` — see [`python/README.md`](python/README.md) |
56
+ | Node.js | `npm install @quicknode/sdk` — see [`npm/README.md`](npm/README.md) |
57
+ | Ruby | `gem install quicknode_sdk` — see [`ruby/README.md`](ruby/README.md) |
58
+
59
+ ## Development
60
+
61
+ ### Prerequisites
62
+
63
+ - Rust (stable)
64
+ - Python 3.8+ with [uv](https://docs.astral.sh/uv/)
65
+ - Node.js 18+
66
+ - Ruby 3.0+
67
+ - [just](https://github.com/casey/just)
68
+
69
+ ### Build Commands
70
+
71
+ Use the commands in the `Justfile` for the setup and build commands.
72
+
73
+ ```bash
74
+ # Core library
75
+ cargo check
76
+ cargo test -p quicknode-sdk
77
+
78
+ # Python (from project root)
79
+ just python-setup-env
80
+ just python-build
81
+
82
+ # Node.js (from npm/)
83
+ just node-build
84
+
85
+ # Ruby
86
+ just ruby-build
87
+
88
+ # Rust
89
+ cargo build -p quicknode-sdk
90
+ ```
91
+
92
+ ### Testing
93
+
94
+ ```bash
95
+ just test
96
+ ```
97
+
98
+ Runs the Rust unit tests for `quicknode-sdk` using [wiremock](https://github.com/LukeMathWalker/wiremock-rs) to mock HTTP responses — no API key required.
99
+
100
+ ### Examples
101
+
102
+ ```bash
103
+ # Rust
104
+ QN_SDK__API_KEY=replaceme cargo run --example admin -p quicknode-sdk --features rust
105
+
106
+ # Python
107
+ QN_SDK__API_KEY=replaceme uv run python/examples/admin.py
108
+ QN_SDK__API_KEY=replaceme uv run python/examples/streams.py
29
109
 
30
- ```ts
31
- import Core from '@quicknode/sdk/core';
110
+ # Node.js
111
+ cd npm && QN_SDK__API_KEY=replaceme npx tsx examples/admin.ts
112
+ cd npm && QN_SDK__API_KEY=replaceme npx tsx examples/streams.ts
32
113
 
33
- const core = new Core({ endpointUrl: 'replaceme' });
114
+ # Ruby (build first, then run)
115
+ just ruby-build
116
+ QN_SDK__API_KEY=replaceme ruby ruby/examples/admin.rb
117
+ QN_SDK__API_KEY=replaceme ruby ruby/examples/admin_e2e.rb
118
+ QN_SDK__API_KEY=replaceme ruby ruby/examples/streams.rb
119
+ ```
120
+
121
+ ### Releasing
122
+
123
+ The Rust crate (`quicknode-sdk` on crates.io) versions independently from the Python, Node, and Ruby bindings. Its version lives in `crates/core/Cargo.toml`; the bindings share the workspace version in the root `Cargo.toml`.
124
+
125
+ #### Rust crate only (crates.io)
126
+
127
+ ```bash
128
+ # 1. Bump the version in crates/core/Cargo.toml (e.g. 0.1.0 → 0.1.0-alpha.5)
129
+ # Pre-release identifiers use SemVer 2.0 syntax: MAJOR.MINOR.PATCH-<id>.<N>
130
+ # Examples: 0.1.0-alpha.4, 0.2.0-beta.1, 0.2.0-rc.1
131
+
132
+ # 2. Commit and push
133
+ git commit -am "chore: release quicknode-sdk 0.1.0-alpha.5"
134
+ git push
34
135
 
35
- const blockNumber = core.client.getBlockNumber().then((response) => console.log(response));
136
+ # 3. Validate the tarball (no upload)
137
+ cargo publish -p quicknode-sdk --dry-run
138
+
139
+ # 4. Publish (requires `cargo login` with a crates.io token)
140
+ cargo publish -p quicknode-sdk
36
141
  ```
37
142
 
38
- Full example app implementation [here](https://github.com/quiknode-labs/qn-oss/tree/main/packages/apps/examples/sdk-api)
143
+ The first publish claims the `quicknode-sdk` name permanently. Published versions are immutable — you cannot overwrite or delete them (only `cargo yank`, which hides but doesn't remove).
144
+
145
+ #### All bindings together (Python / Node / Ruby)
146
+
147
+ macOS (Apple Silicon) artifacts are built locally rather than on GitHub Actions to avoid the ~10× runner cost. Linux artifacts are built by CI when a GitHub release is published.
148
+
149
+ 1. **Bump versions and commit:**
150
+ ```bash
151
+ just release 0.2.0
152
+ git push
153
+ ```
154
+
155
+ 2. **Create the GitHub release** via the GitHub UI:
156
+ - **Releases → Draft a new release**.
157
+ - **Choose a tag** → type `v0.2.0` → **Create new tag on publish**.
158
+ - Target branch: `main`.
159
+ - Fill in title and notes (or click **Generate release notes**).
160
+ - Click **Publish release**.
161
+
162
+ This creates + pushes the tag and triggers `.github/workflows/release.yml`, which builds Linux artifacts and attaches them to the release.
163
+
164
+ 3. **Build macOS arm64 artifacts locally and append them to the release:**
165
+ ```bash
166
+ just macos-build-and-publish 0.2.0
167
+ ```
168
+
169
+ Step 3 requires the [`gh` CLI](https://cli.github.com/) authenticated to the repo. Intel macOS (`x86_64-apple-darwin`) is not shipped — users on Intel Macs install from source.
170
+
171
+ `just release` does **not** bump the Rust crate version (that's managed separately in `crates/core/Cargo.toml`). If you want the Rust crate to move in lockstep with a binding release, bump it manually in the same commit.
172
+
173
+ #### npm publish (`@quicknode/sdk`)
174
+
175
+ The Node package is published to npm as `@quicknode/sdk`. During the 3.x pre-release period, publishes use the `next` dist-tag so `npm install @quicknode/sdk` continues to resolve to the legacy 2.x release while `npm install @quicknode/sdk@next` pulls the rewrite.
176
+
177
+ The npm publish uses [napi-rs's multi-package layout](https://napi.rs/docs/deep-dive/release): one main package plus per-platform sub-packages (`@quicknode/sdk-linux-x64-gnu`, `@quicknode/sdk-darwin-arm64`, etc.) declared as `optionalDependencies`. Publishing is triggered manually via a GitHub Actions workflow so the macOS binary (built locally) can be uploaded to the GitHub release before publish.
178
+
179
+ Anyone with permission to run the `Publish npm` workflow in this repo can cut a release.
180
+
181
+ **Note on versions:** the git tag tracks the overall project version (e.g. `v0.1.0-alpha.5`) and is set in `crates/core/Cargo.toml` / the root `Cargo.toml`. The npm package version is set independently in `npm/package.json` (e.g. `3.0.0-alpha.5`) to stay compatible with the pre-existing `@quicknode/sdk` 2.x series on npm. The two versions do not need to match.
182
+
183
+ **Per-release flow:**
184
+
185
+ 1. **Bump the npm version** in `npm/package.json` (e.g. `3.0.0-alpha.4` → `3.0.0-alpha.5`), commit, and push to `main`. (Bump the overall project version in `just release <version>` as part of the normal release flow above — this sets the git tag.)
39
186
 
40
- <br>
187
+ 2. **Create the GitHub release** via the GitHub UI:
188
+ - Go to **Releases → Draft a new release**.
189
+ - Click **Choose a tag**, type the new tag (e.g. `v0.1.0-alpha.5`), and select **Create new tag on publish**.
190
+ - Target branch: `main`.
191
+ - Fill in the title and release notes (or click **Generate release notes**).
192
+ - Click **Publish release**.
41
193
 
42
- ## Documentation
194
+ Publishing the release creates + pushes the tag, which triggers `.github/workflows/release.yml`. CI builds the Linux `.node` artifacts and attaches them to the release you just created.
43
195
 
44
- Please see [the official QuickNode SDK documentation](https://www.quicknode.com/docs/quicknode-sdk/getting-started) for the full documentation of SDK functions.
196
+ 3. **Wait for `release.yml` to finish.** Confirm the four Linux `index.*.node` artifacts are attached to the release.
45
197
 
46
- <br>
198
+ 4. **Build and upload the macOS arm64 binary** locally (Apple Silicon Mac required):
199
+ ```bash
200
+ just node-build
201
+ gh release upload v0.1.0-alpha.5 npm/index.darwin-arm64.node
202
+ ```
47
203
 
48
- ## Contributing corner
204
+ 5. **Trigger the publish workflow.** From the GitHub UI: **Actions → Publish npm → Run workflow**, then enter the git tag (`v0.1.0-alpha.5`) and npm dist-tag (`next`). Or via CLI:
205
+ ```bash
206
+ gh workflow run publish-npm.yml -f tag=v0.1.0-alpha.5 -f npm_tag=next
207
+ ```
49
208
 
50
- ### Issues
209
+ 6. **Verify.**
210
+ ```bash
211
+ npm view @quicknode/sdk dist-tags
212
+ # Expected: next: 3.0.0-alpha.5, latest: 2.6.0 (unchanged)
213
+ ```
51
214
 
52
- Please submit any issues or feature requests as an [issue in Github](https://github.com/quiknode-labs/qn-oss/issues).
215
+ Users can install the pre-release with `npm install @quicknode/sdk@next`.
53
216
 
54
- <br>
217
+ #### PyPI publish (`quicknode-sdk`)
55
218
 
56
- ### Development
219
+ The Python package is published to PyPI as `quicknode-sdk`. Wheels and the sdist are built by `release.yml` on every GitHub release and attached as artifacts; the `Publish PyPI` workflow downloads them from a release tag and uploads to PyPI via `twine`.
57
220
 
58
- We recommend using the example application to develop
221
+ **Version format:** PyPI uses PEP 440, so pre-releases are written without a hyphen. `0.1.0-alpha.6` → `0.1.0a6` in `pyproject.toml`.
59
222
 
60
- 1. In `qn-oss` monorepo root directory, run `yarn install`
61
- 1. cd `packages/apps/examples/sdk-api` from `qn-oss` monorepo root
62
- 1. `cp .env.example .env` and add api key
63
- 1. `nx serve apps-examples-sdk-api`
64
- 1. Then you can send requests to the API, for example: `curl http://localhost:3333/api/nftsByAddress/0xbc08dadccc79c00587d7e6a75bb68ff5fd30f9e0`
223
+ **Per-release flow:**
65
224
 
66
- <br>
225
+ 1. **Bump the version** in `pyproject.toml` (e.g. `0.1.0a6` → `0.1.0a7`) and run `uv lock` to refresh `uv.lock`. Commit and push.
67
226
 
68
- ### Running tests
227
+ 2. **Create the GitHub release** as described in the main Releasing section — this triggers `release.yml`, which builds the Linux wheels and sdist and attaches them to the release as `quicknode_sdk-*.whl` and `quicknode_sdk-*.tar.gz`.
69
228
 
70
- Run `nx test libs-sdk` to execute the tests via [Jest](https://jestjs.io).
229
+ 3. **Wait for `release.yml` to finish.** Confirm 16 wheels (4 Python versions × 4 Linux targets) and 1 sdist are attached to the release.
71
230
 
72
- API responses are recorded using [polly.js](https://github.com/Netflix/pollyjs). You can re-record live requests by passing in an API key, copy `.env.test.example` to `.env.test` and fill out with your API key and re-record tests `nx test libs-sdk -- -u`
231
+ 4. **Trigger the publish workflow.** From the GitHub UI: **Actions Publish PyPI Run workflow**, then enter the git tag. Or via CLI:
232
+ ```bash
233
+ gh workflow run publish-pypi.yml -f tag=v0.1.0-alpha.6
234
+ ```
73
235
 
74
- <br>
236
+ 5. **Verify.**
237
+ ```bash
238
+ pip install quicknode-sdk==0.1.0a6
239
+ python -c "import sdk; print(sdk.QuicknodeSdk)"
240
+ ```
75
241
 
76
- ### Running linting
242
+ **First publish:** the repo secret `PYPI_API_TOKEN` must be set. Project-scoped tokens only work after the project exists on PyPI, so the first upload needs a user-scoped token; rotate to a project-scoped token after.
77
243
 
78
- Run `nx lint libs-sdk` to execute the lint via [ESLint](https://eslint.org/).
244
+ ## License
79
245
 
80
- <br>
246
+ MIT
package/browser.js ADDED
@@ -0,0 +1,9 @@
1
+ // Native Node.js bindings (.node files compiled from Rust) cannot run in
2
+ // browser environments — they are binary extensions that require the Node.js
3
+ // runtime and OS-level dynamic linking. Bundlers (Webpack, Vite, etc.) that
4
+ // encounter this package in a browser target will load this shim instead,
5
+ // producing a clear error rather than a cryptic binary load failure.
6
+ throw new Error(
7
+ '@quicknode/sdk does not support browser environments. ' +
8
+ 'This package requires Node.js.'
9
+ );
package/errors.js ADDED
@@ -0,0 +1,121 @@
1
+ // Typed error classes. The Rust binding throws a plain napi Error whose
2
+ // message is tagged "[<kind>|<status>|<body_len>]<msg>\x1f<body>"; parseAndRethrow
3
+ // decodes that and throws an instance of the matching subclass below.
4
+
5
+ class QuicknodeError extends Error {
6
+ constructor(message) {
7
+ super(message);
8
+ this.name = "QuicknodeError";
9
+ }
10
+ }
11
+
12
+ class ConfigError extends QuicknodeError {
13
+ constructor(message) {
14
+ super(message);
15
+ this.name = "ConfigError";
16
+ }
17
+ }
18
+
19
+ class HttpError extends QuicknodeError {
20
+ constructor(message) {
21
+ super(message);
22
+ this.name = "HttpError";
23
+ }
24
+ }
25
+
26
+ class TimeoutError extends HttpError {
27
+ constructor(message) {
28
+ super(message);
29
+ this.name = "TimeoutError";
30
+ }
31
+ }
32
+
33
+ class ConnectionError extends HttpError {
34
+ constructor(message) {
35
+ super(message);
36
+ this.name = "ConnectionError";
37
+ }
38
+ }
39
+
40
+ class ApiError extends QuicknodeError {
41
+ constructor(message, status, body) {
42
+ super(message);
43
+ this.name = "ApiError";
44
+ this.status = status;
45
+ this.body = body;
46
+ }
47
+ }
48
+
49
+ class DecodeError extends QuicknodeError {
50
+ constructor(message, body) {
51
+ super(message);
52
+ this.name = "DecodeError";
53
+ this.body = body;
54
+ }
55
+ }
56
+
57
+ const TAG_RE = /^\[(Config|Http|Timeout|Connect|Api|Decode)\|([^|]+)\|([^\]]+)\](.*)$/s;
58
+
59
+ function fromNapiError(err) {
60
+ if (!(err instanceof Error)) return err;
61
+ const m = err.message.match(TAG_RE);
62
+ if (!m) return err;
63
+ const [, kind, statusStr, bodyLenStr, rest] = m;
64
+ // rest = "<msg>\x1f<body>". Use body_len (byte length from Rust) to split
65
+ // deterministically — the body may itself contain \x1f, and Api messages
66
+ // embed the body in msg, so scanning for the first separator is unsafe.
67
+ let msg = rest;
68
+ let body = "";
69
+ if (bodyLenStr !== "-") {
70
+ const bodyLen = Number(bodyLenStr);
71
+ const bodyBytes = Buffer.from(rest, "utf8");
72
+ const bodyStart = bodyBytes.length - bodyLen;
73
+ if (bodyStart >= 1 && bodyBytes[bodyStart - 1] === 0x1f) {
74
+ msg = bodyBytes.slice(0, bodyStart - 1).toString("utf8");
75
+ body = bodyBytes.slice(bodyStart).toString("utf8");
76
+ }
77
+ }
78
+ switch (kind) {
79
+ case "Config": return new ConfigError(msg);
80
+ case "Timeout": return new TimeoutError(msg);
81
+ case "Connect": return new ConnectionError(msg);
82
+ case "Http": return new HttpError(msg);
83
+ case "Api": return new ApiError(msg, Number(statusStr), body);
84
+ case "Decode": return new DecodeError(msg, body);
85
+ default: return err;
86
+ }
87
+ }
88
+
89
+ // Wraps an object's methods so thrown napi errors get retagged as typed
90
+ // subclasses. Handles both sync throws and rejected promises.
91
+ function wrapClient(client) {
92
+ return new Proxy(client, {
93
+ get(target, prop) {
94
+ const val = target[prop];
95
+ if (typeof val !== "function") return val;
96
+ return function (...args) {
97
+ try {
98
+ const result = val.apply(target, args);
99
+ if (result && typeof result.then === "function") {
100
+ return result.catch((e) => { throw fromNapiError(e); });
101
+ }
102
+ return result;
103
+ } catch (e) {
104
+ throw fromNapiError(e);
105
+ }
106
+ };
107
+ },
108
+ });
109
+ }
110
+
111
+ module.exports = {
112
+ QuicknodeError,
113
+ ConfigError,
114
+ HttpError,
115
+ TimeoutError,
116
+ ConnectionError,
117
+ ApiError,
118
+ DecodeError,
119
+ fromNapiError,
120
+ wrapClient,
121
+ };