From dcfd5ca3f6514dd9e7c5e6b92d1585ea25c56d3f Mon Sep 17 00:00:00 2001 From: Teddy Lo Date: Mon, 8 Apr 2024 11:23:39 -0400 Subject: [PATCH] Release v0.5.0 --- .eslintrc.json | 90 + .github/workflows/release.yaml | 27 + .gitignore | 39 + LICENSE | 201 + README.md | 159 + docs/images/banner.svg | 41 + docs/openapi/orchestration.swagger.json | 1493 +++++++ docs/openapi/rewards.swagger.json | 617 +++ examples/cosmos/list-rewards.ts | 14 + examples/cosmos/list-stakes.ts | 37 + .../ethereum/create-and-process-workflow.ts | 173 + examples/ethereum/create-workflow.ts | 51 + examples/ethereum/list-rewards.ts | 15 + examples/ethereum/list-stakes.ts | 38 + examples/example.ts | 15 + .../solana/create-and-process-workflow.ts | 188 + examples/solana/create-workflow.ts | 51 + examples/solana/list-rewards.ts | 37 + package-lock.json | 3607 +++++++++++++++++ package.json | 46 + src/auth/index.ts | 176 + src/client/protocols/cosmos-staking.ts | 47 + src/client/protocols/ethereum-kiln-staking.ts | 153 + src/client/protocols/solana-staking.ts | 149 + src/client/staking-client.ts | 292 ++ .../staking/orchestration/v1/action.pb.ts | 16 + .../staking/orchestration/v1/api.pb.ts | 42 + .../staking/orchestration/v1/common.pb.ts | 9 + .../orchestration/v1/ethereum_kiln.pb.ts | 52 + .../staking/orchestration/v1/network.pb.ts | 16 + .../staking/orchestration/v1/protocol.pb.ts | 15 + .../staking/orchestration/v1/solana.pb.ts | 75 + .../orchestration/v1/staking_context.pb.ts | 33 + .../orchestration/v1/staking_target.pb.ts | 42 + .../staking/orchestration/v1/workflow.pb.ts | 124 + .../coinbase/staking/rewards/v1/common.pb.ts | 11 + .../staking/rewards/v1/protocol.pb.ts | 8 + .../coinbase/staking/rewards/v1/reward.pb.ts | 62 + .../staking/rewards/v1/reward_service.pb.ts | 20 + .../coinbase/staking/rewards/v1/stake.pb.ts | 75 + src/gen/fetch.pb.ts | 341 ++ src/gen/google/protobuf/timestamp.pb.ts | 9 + src/index.ts | 5 + src/signers/ethereum-signer.ts | 26 + src/signers/index.ts | 2 + src/signers/solana-signer.ts | 26 + src/signers/txsigner.ts | 21 + src/utils/date.ts | 10 + tsconfig.json | 32 + 49 files changed, 8828 insertions(+) create mode 100644 .eslintrc.json create mode 100644 .github/workflows/release.yaml create mode 100644 .gitignore create mode 100644 LICENSE create mode 100644 README.md create mode 100644 docs/images/banner.svg create mode 100644 docs/openapi/orchestration.swagger.json create mode 100644 docs/openapi/rewards.swagger.json create mode 100644 examples/cosmos/list-rewards.ts create mode 100644 examples/cosmos/list-stakes.ts create mode 100644 examples/ethereum/create-and-process-workflow.ts create mode 100644 examples/ethereum/create-workflow.ts create mode 100644 examples/ethereum/list-rewards.ts create mode 100644 examples/ethereum/list-stakes.ts create mode 100644 examples/example.ts create mode 100644 examples/solana/create-and-process-workflow.ts create mode 100644 examples/solana/create-workflow.ts create mode 100644 examples/solana/list-rewards.ts create mode 100644 package-lock.json create mode 100644 package.json create mode 100644 src/auth/index.ts create mode 100644 src/client/protocols/cosmos-staking.ts create mode 100644 src/client/protocols/ethereum-kiln-staking.ts create mode 100644 src/client/protocols/solana-staking.ts create mode 100644 src/client/staking-client.ts create mode 100644 src/gen/coinbase/staking/orchestration/v1/action.pb.ts create mode 100644 src/gen/coinbase/staking/orchestration/v1/api.pb.ts create mode 100644 src/gen/coinbase/staking/orchestration/v1/common.pb.ts create mode 100644 src/gen/coinbase/staking/orchestration/v1/ethereum_kiln.pb.ts create mode 100644 src/gen/coinbase/staking/orchestration/v1/network.pb.ts create mode 100644 src/gen/coinbase/staking/orchestration/v1/protocol.pb.ts create mode 100644 src/gen/coinbase/staking/orchestration/v1/solana.pb.ts create mode 100644 src/gen/coinbase/staking/orchestration/v1/staking_context.pb.ts create mode 100644 src/gen/coinbase/staking/orchestration/v1/staking_target.pb.ts create mode 100644 src/gen/coinbase/staking/orchestration/v1/workflow.pb.ts create mode 100644 src/gen/coinbase/staking/rewards/v1/common.pb.ts create mode 100644 src/gen/coinbase/staking/rewards/v1/protocol.pb.ts create mode 100644 src/gen/coinbase/staking/rewards/v1/reward.pb.ts create mode 100644 src/gen/coinbase/staking/rewards/v1/reward_service.pb.ts create mode 100644 src/gen/coinbase/staking/rewards/v1/stake.pb.ts create mode 100644 src/gen/fetch.pb.ts create mode 100644 src/gen/google/protobuf/timestamp.pb.ts create mode 100644 src/index.ts create mode 100644 src/signers/ethereum-signer.ts create mode 100644 src/signers/index.ts create mode 100644 src/signers/solana-signer.ts create mode 100644 src/signers/txsigner.ts create mode 100644 src/utils/date.ts create mode 100644 tsconfig.json diff --git a/.eslintrc.json b/.eslintrc.json new file mode 100644 index 0000000..938d5f0 --- /dev/null +++ b/.eslintrc.json @@ -0,0 +1,90 @@ +{ + "env": { + "es6": true, + "node": true + }, + "extends": [ + "prettier", + "eslint:recommended", + "plugin:@typescript-eslint/eslint-recommended", + "plugin:@typescript-eslint/recommended", + // NOTE: This needs to be last, per documentation: + // https://github.com/prettier/eslint-plugin-prettier?tab=readme-ov-file#configuration-legacy-eslintrc + "plugin:prettier/recommended" + ], + "globals": { + "Atomics": "readonly", + "SharedArrayBuffer": "readonly" + }, + "parser": "@typescript-eslint/parser", + "parserOptions": { + "ecmaVersion": 2018, + "sourceType": "module" + }, + "plugins": [ + "@typescript-eslint", + "filename-rules", + "prettier" + ], + "rules": { + "filename-rules/match": [ + 2, + "kebab-case" + ], + "no-unused-vars": "warn", + "no-constant-condition": "warn", + "no-empty": "warn", + "consistent-return": "error", + "@typescript-eslint/no-empty-function": "warn", + "prettier/prettier": [ + "error", + { + "singleQuote": true + }, + { + "trailingComma": "none" + } + ], + "prefer-const": "off", + // NOTE: We can't enable TS compilation to enforce function return types + // Source: https://github.com/Microsoft/TypeScript/issues/18529 + // So instead we enforce it with linting + "@typescript-eslint/explicit-function-return-type": "error", + "object-curly-spacing": [ + "error", + "always" + ], + "padding-line-between-statements": [ + "error", + { + "blankLine": "always", + "prev": [ + "const", + "let", + "var" + ], + "next": "*" + }, + { + "blankLine": "any", + "prev": [ + "const", + "let", + "var" + ], + "next": [ + "const", + "let", + "var" + ] + } + ], + "lines-between-class-members": [ + "error", + "always", + { + "exceptAfterSingleLine": true + } + ] + } +} diff --git a/.github/workflows/release.yaml b/.github/workflows/release.yaml new file mode 100644 index 0000000..d201051 --- /dev/null +++ b/.github/workflows/release.yaml @@ -0,0 +1,27 @@ +name: Version 🔖 + +on: + release: + types: [created] + +concurrency: ${{ github.workflow }}-${{ github.ref }} + +jobs: + version: + name: Release + runs-on: ubuntu-latest + environment: release + permissions: + contents: read + id-token: write + steps: + - uses: actions/checkout@v3 + - uses: actions/setup-node@v3 + with: + node-version: '18.x' + registry-url: 'https://registry.npmjs.org' + - run: npm install -g npm@^9.5.0 + - run: npm ci + - run: npm publish --provenance --access public + env: + NODE_AUTH_TOKEN: ${{ secrets.NPM_TOKEN }} diff --git a/.gitignore b/.gitignore new file mode 100644 index 0000000..737fe95 --- /dev/null +++ b/.gitignore @@ -0,0 +1,39 @@ +# IDE/Tools +.docker +.idea +# vim +.*.sw? + +# Local +.DS_Store + +# Binaries for programs and plugins +*.exe +*.exe~ +*.dll +*.so +*.dylib + +# Test binary, built with `go test -c` +*.test + +# Output of the go coverage tool, specifically when used with LiteIDE +*.out + +# Dependency directories (remove the comment below to include it) +vendor/ + +# Cloud API key +.coinbase_cloud_api_key*.json + +# Example binary +staking-client-* + +# environment variables +.env +.envrc + +# Output directory +dist/ + +node_modules/ diff --git a/LICENSE b/LICENSE new file mode 100644 index 0000000..ef3e6bf --- /dev/null +++ b/LICENSE @@ -0,0 +1,201 @@ + Apache License + Version 2.0, January 2004 + http://www.apache.org/licenses/ + + TERMS AND CONDITIONS FOR USE, REPRODUCTION, AND DISTRIBUTION + + 1. Definitions. + + "License" shall mean the terms and conditions for use, reproduction, + and distribution as defined by Sections 1 through 9 of this document. + + "Licensor" shall mean the copyright owner or entity authorized by + the copyright owner that is granting the License. + + "Legal Entity" shall mean the union of the acting entity and all + other entities that control, are controlled by, or are under common + control with that entity. For the purposes of this definition, + "control" means (i) the power, direct or indirect, to cause the + direction or management of such entity, whether by contract or + otherwise, or (ii) ownership of fifty percent (50%) or more of the + outstanding shares, or (iii) beneficial ownership of such entity. + + "You" (or "Your") shall mean an individual or Legal Entity + exercising permissions granted by this License. + + "Source" form shall mean the preferred form for making modifications, + including but not limited to software source code, documentation + source, and configuration files. + + "Object" form shall mean any form resulting from mechanical + transformation or translation of a Source form, including but + not limited to compiled object code, generated documentation, + and conversions to other media types. + + "Work" shall mean the work of authorship, whether in Source or + Object form, made available under the License, as indicated by a + copyright notice that is included in or attached to the work + (an example is provided in the Appendix below). + + "Derivative Works" shall mean any work, whether in Source or Object + form, that is based on (or derived from) the Work and for which the + editorial revisions, annotations, elaborations, or other modifications + represent, as a whole, an original work of authorship. For the purposes + of this License, Derivative Works shall not include works that remain + separable from, or merely link (or bind by name) to the interfaces of, + the Work and Derivative Works thereof. + + "Contribution" shall mean any work of authorship, including + the original version of the Work and any modifications or additions + to that Work or Derivative Works thereof, that is intentionally + submitted to Licensor for inclusion in the Work by the copyright owner + or by an individual or Legal Entity authorized to submit on behalf of + the copyright owner. For the purposes of this definition, "submitted" + means any form of electronic, verbal, or written communication sent + to the Licensor or its representatives, including but not limited to + communication on electronic mailing lists, source code control systems, + and issue tracking systems that are managed by, or on behalf of, the + Licensor for the purpose of discussing and improving the Work, but + excluding communication that is conspicuously marked or otherwise + designated in writing by the copyright owner as "Not a Contribution." + + "Contributor" shall mean Licensor and any individual or Legal Entity + on behalf of whom a Contribution has been received by Licensor and + subsequently incorporated within the Work. + + 2. Grant of Copyright License. Subject to the terms and conditions of + this License, each Contributor hereby grants to You a perpetual, + worldwide, non-exclusive, no-charge, royalty-free, irrevocable + copyright license to reproduce, prepare Derivative Works of, + publicly display, publicly perform, sublicense, and distribute the + Work and such Derivative Works in Source or Object form. + + 3. Grant of Patent License. Subject to the terms and conditions of + this License, each Contributor hereby grants to You a perpetual, + worldwide, non-exclusive, no-charge, royalty-free, irrevocable + (except as stated in this section) patent license to make, have made, + use, offer to sell, sell, import, and otherwise transfer the Work, + where such license applies only to those patent claims licensable + by such Contributor that are necessarily infringed by their + Contribution(s) alone or by combination of their Contribution(s) + with the Work to which such Contribution(s) was submitted. If You + institute patent litigation against any entity (including a + cross-claim or counterclaim in a lawsuit) alleging that the Work + or a Contribution incorporated within the Work constitutes direct + or contributory patent infringement, then any patent licenses + granted to You under this License for that Work shall terminate + as of the date such litigation is filed. + + 4. Redistribution. You may reproduce and distribute copies of the + Work or Derivative Works thereof in any medium, with or without + modifications, and in Source or Object form, provided that You + meet the following conditions: + + (a) You must give any other recipients of the Work or + Derivative Works a copy of this License; and + + (b) You must cause any modified files to carry prominent notices + stating that You changed the files; and + + (c) You must retain, in the Source form of any Derivative Works + that You distribute, all copyright, patent, trademark, and + attribution notices from the Source form of the Work, + excluding those notices that do not pertain to any part of + the Derivative Works; and + + (d) If the Work includes a "NOTICE" text file as part of its + distribution, then any Derivative Works that You distribute must + include a readable copy of the attribution notices contained + within such NOTICE file, excluding those notices that do not + pertain to any part of the Derivative Works, in at least one + of the following places: within a NOTICE text file distributed + as part of the Derivative Works; within the Source form or + documentation, if provided along with the Derivative Works; or, + within a display generated by the Derivative Works, if and + wherever such third-party notices normally appear. The contents + of the NOTICE file are for informational purposes only and + do not modify the License. You may add Your own attribution + notices within Derivative Works that You distribute, alongside + or as an addendum to the NOTICE text from the Work, provided + that such additional attribution notices cannot be construed + as modifying the License. + + You may add Your own copyright statement to Your modifications and + may provide additional or different license terms and conditions + for use, reproduction, or distribution of Your modifications, or + for any such Derivative Works as a whole, provided Your use, + reproduction, and distribution of the Work otherwise complies with + the conditions stated in this License. + + 5. Submission of Contributions. Unless You explicitly state otherwise, + any Contribution intentionally submitted for inclusion in the Work + by You to the Licensor shall be under the terms and conditions of + this License, without any additional terms or conditions. + Notwithstanding the above, nothing herein shall supersede or modify + the terms of any separate license agreement you may have executed + with Licensor regarding such Contributions. + + 6. Trademarks. This License does not grant permission to use the trade + names, trademarks, service marks, or product names of the Licensor, + except as required for reasonable and customary use in describing the + origin of the Work and reproducing the content of the NOTICE file. + + 7. Disclaimer of Warranty. Unless required by applicable law or + agreed to in writing, Licensor provides the Work (and each + Contributor provides its Contributions) on an "AS IS" BASIS, + WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or + implied, including, without limitation, any warranties or conditions + of TITLE, NON-INFRINGEMENT, MERCHANTABILITY, or FITNESS FOR A + PARTICULAR PURPOSE. You are solely responsible for determining the + appropriateness of using or redistributing the Work and assume any + risks associated with Your exercise of permissions under this License. + + 8. Limitation of Liability. In no event and under no legal theory, + whether in tort (including negligence), contract, or otherwise, + unless required by applicable law (such as deliberate and grossly + negligent acts) or agreed to in writing, shall any Contributor be + liable to You for damages, including any direct, indirect, special, + incidental, or consequential damages of any character arising as a + result of this License or out of the use or inability to use the + Work (including but not limited to damages for loss of goodwill, + work stoppage, computer failure or malfunction, or any and all + other commercial damages or losses), even if such Contributor + has been advised of the possibility of such damages. + + 9. Accepting Warranty or Additional Liability. While redistributing + the Work or Derivative Works thereof, You may choose to offer, + and charge a fee for, acceptance of support, warranty, indemnity, + or other liability obligations and/or rights consistent with this + License. However, in accepting such obligations, You may act only + on Your own behalf and on Your sole responsibility, not on behalf + of any other Contributor, and only if You agree to indemnify, + defend, and hold each Contributor harmless for any liability + incurred by, or claims asserted against, such Contributor by reason + of your accepting any such warranty or additional liability. + + END OF TERMS AND CONDITIONS + + APPENDIX: How to apply the Apache License to your work. + + To apply the Apache License to your work, attach the following + boilerplate notice, with the fields enclosed by brackets "[]" + replaced with your own identifying information. (Don't include + the brackets!) The text should be enclosed in the appropriate + comment syntax for the file format. We also recommend that a + file or class name and description of purpose be included on the + same "printed page" as the copyright notice for easier + identification within third-party archives. + + Copyright (c) 2018-2024 Coinbase, Inc. + + Licensed under the Apache License, Version 2.0 (the "License"); + you may not use this file except in compliance with the License. + You may obtain a copy of the License at + + http://www.apache.org/licenses/LICENSE-2.0 + + Unless required by applicable law or agreed to in writing, software + distributed under the License is distributed on an "AS IS" BASIS, + WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + See the License for the specific language governing permissions and + limitations under the License. diff --git a/README.md b/README.md new file mode 100644 index 0000000..250c122 --- /dev/null +++ b/README.md @@ -0,0 +1,159 @@ +Coinbase Staking API + +# [Coinbase Staking API](https://github.com/coinbase/staking-client-library-ts) + +> Programmatic access to Coinbase's best-in-class staking infrastructure and services. :large_blue_circle: + +[![npm version](https://badge.fury.io/js/@coinbase%2Fstaking-client-library-ts.svg)](https://badge.fury.io/js/@coinbase%2Fstaking-client-library-ts) [![Current version](https://img.shields.io/github/tag/coinbase/staking-client-library-ts?color=3498DB&label=version)](https://github.com/coinbase/staking-client-library-ts/releases) [![GitHub contributors](https://img.shields.io/github/contributors/coinbase/staking-client-library-ts?color=3498DB)](https://github.com/coinbase/staking-client-library-ts/graphs/contributors) [![GitHub Stars](https://img.shields.io/github/stars/coinbase/staking-client-library-ts.svg?color=3498DB)](https://github.com/coinbase/staking-client-library-ts/stargazers) [![GitHub](https://img.shields.io/github/license/coinbase/staking-client-library-ts?color=3498DB)](https://github.com/coinbase/staking-client-library-ts/blob/main/LICENSE) + +## Overview + +`staking-client-library-ts` is the Typescript SDK for the **Coinbase Staking API** :large_blue_circle:. + +The Coinbase Staking API empowers developers to deliver a fully-featured staking experience in their Web2 apps, wallets, or dApps using *one common interface* across protocols. + +A traditional infrastructure-heavy staking integration can take months. Coinbase's Staking API enables onboarding within hours :sparkles:. + +## Quick Start + +Prerequisite: [Node 18.12+](https://nodejs.org/en/blog/release/v18.12.0) + +1. Install this package: `npm install @coinbase/staking-client-library-ts` +2. Create and download an API key from the [Cloud Platform](https://portal.cloud.coinbase.com/access/api). +3. Place the key named `.coinbase_cloud_api_key.json` at the root of this repository. +4. Run one of the code samples [below](#stake-partial-eth-💠) or any of our [provided examples](./examples/) :rocket:. + +### Stake Partial ETH :diamond_shape_with_a_dot_inside: + +This code sample creates an ETH staking workflow. View the full code sample [here](examples/ethereum/create-workflow.ts) + +
+ Code Sample + +```typescript +// examples/ethereum/create-workflow.ts +import { StakingClient } from "@coinbase/staking-client-library-ts"; + +const client = new StakingClient(); + +client.Ethereum.stake( + 'your-project-id', // replace with your project id + 'holesky', + true, + 'your-wallet-address', // replace with your wallet address + '0xA55416de5DE61A0AC1aa8970a280E04388B1dE4b', + '123', +) + .then((workflow) => { + console.log('Workflow created %s', workflow.name); + }) + .catch(() => { + throw new Error(`Error creating workflow`); + }); +``` + +
+ +
+ Output + + ```text + Workflow created: projects/62376b2f-3f24-42c9-9025-d576a3c06d6f/workflows/ffbf9b45-c57b-49cb-a4d5-fdab66d8cb25 + ``` + +
+ +### View Ethereum Rewards :moneybag: + +This code sample returns rewards for an Ethereum validator address. View the full code sample [here](examples/ethereum/list-rewards.ts). + +
+ Code Sample + +```typescript +import { StakingClient } from "@coinbase/staking-client-library-ts"; + +// Defines which address and rewards we want to see +const address: string = + '0xac53512c39d0081ca4437c285305eb423f474e6153693c12fbba4a3df78bcaa3422b31d800c5bea71c1b017168a60474'; +const filter: string = `address='${address}' AND period_end_time > '2024-02-25T00:00:00Z' AND period_end_time < '2024-02-27T00:00:00Z'`; + +const client = new StakingClient(); + +// Loops through rewards array and prints each reward +client.Ethereum.listRewards(filter).then((resp) => { + resp.rewards!.forEach((reward) => { + console.log(JSON.stringify(reward, null, 2)); + }); +}); +``` + +
+ +
+ Output + + ```json + { + "address": "0xac53512c39d0081ca4437c285305eb423f474e6153693c12fbba4a3df78bcaa3422b31d800c5bea71c1b017168a60474", + "date": "2024-02-25", + "aggregationUnit": "DAY", + "periodStartTime": "2024-02-25T00:00:00Z", + "periodEndTime": "2024-02-25T23:59:59Z", + "totalEarnedNativeUnit": { + "amount": "0.002183619", + "exp": "18", + "ticker": "ETH", + "rawNumeric": "2183619000000000" + }, + "totalEarnedUsd": [ + { + "source": "COINBASE_EXCHANGE", + "conversionTime": "2024-02-26T00:09:00Z", + "amount": { + "amount": "6.79", + "exp": "2", + "ticker": "USD", + "rawNumeric": "679" + }, + "conversionPrice": "3105.780029" + } + ], + "endingBalance": null, + "protocol": "ethereum" + } + { + "address": "0xac53512c39d0081ca4437c285305eb423f474e6153693c12fbba4a3df78bcaa3422b31d800c5bea71c1b017168a60474", + "date": "2024-02-26", + "aggregationUnit": "DAY", + "periodStartTime": "2024-02-26T00:00:00Z", + "periodEndTime": "2024-02-26T23:59:59Z", + "totalEarnedNativeUnit": { + "amount": "0.002182946", + "exp": "18", + "ticker": "ETH", + "rawNumeric": "2182946000000000" + }, + "totalEarnedUsd": [ + { + "source": "COINBASE_EXCHANGE", + "conversionTime": "2024-02-27T00:09:00Z", + "amount": { + "amount": "6.94", + "exp": "2", + "ticker": "USD", + "rawNumeric": "694" + }, + "conversionPrice": "3178.889893" + } + ], + "endingBalance": null, + "protocol": "ethereum" + } + ``` + +
+ +## Documentation + +There are numerous examples in the [`examples directory`](./examples) to help get you started. For even more, refer to our [documentation website](https://docs.cloud.coinbase.com/) for detailed definitions, API specifications, integration guides, and more! diff --git a/docs/images/banner.svg b/docs/images/banner.svg new file mode 100644 index 0000000..af98a42 --- /dev/null +++ b/docs/images/banner.svg @@ -0,0 +1,41 @@ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + diff --git a/docs/openapi/orchestration.swagger.json b/docs/openapi/orchestration.swagger.json new file mode 100644 index 0000000..39d3a86 --- /dev/null +++ b/docs/openapi/orchestration.swagger.json @@ -0,0 +1,1493 @@ +{ + "swagger": "2.0", + "info": { + "title": "Orchestration Service", + "description": "Service that can power non-custodial staking experiences for your users.", + "version": "v1" + }, + "tags": [ + { + "name": "Protocol", + "description": "Protocols details" + }, + { + "name": "Network", + "description": "Networks details" + }, + { + "name": "Action", + "description": "Actions details" + }, + { + "name": "StakingTarget", + "description": "Staking targets details" + }, + { + "name": "StakingContext", + "description": "Staking context details" + }, + { + "name": "Workflow", + "description": "Workflow management details" + }, + { + "name": "StakingService" + } + ], + "host": "api.developer.coinbase.com", + "basePath": "/staking", + "schemes": [ + "https" + ], + "consumes": [ + "application/json" + ], + "produces": [ + "application/json" + ], + "paths": { + "/v1/protocols": { + "get": { + "summary": "List supported protocols", + "description": "List supported protocols", + "operationId": "listProtocols", + "responses": { + "200": { + "description": "OK", + "schema": { + "$ref": "#/definitions/v1ListProtocolsResponse" + } + }, + "400": { + "description": "The request attempted has invalid parameters", + "schema": { + "$ref": "#/definitions/rpcStatus" + } + }, + "401": { + "description": "Returned if authentication information is invalid", + "schema": { + "$ref": "#/definitions/rpcStatus" + } + }, + "403": { + "description": "Returned when a user does not have permission to the resource.", + "schema": { + "$ref": "#/definitions/rpcStatus" + } + }, + "404": { + "description": "Returned when a resource is not found.", + "schema": { + "$ref": "#/definitions/rpcStatus" + } + }, + "429": { + "description": "Returned when a resource limit has been reached.", + "schema": { + "$ref": "#/definitions/rpcStatus" + } + }, + "500": { + "description": "Returned when an internal server error happens.", + "schema": { + "$ref": "#/definitions/rpcStatus" + } + }, + "default": { + "description": "An unexpected error response.", + "schema": { + "$ref": "#/definitions/rpcStatus" + } + } + }, + "tags": [ + "Protocol" + ] + } + }, + "/v1/viewStakingContext:view": { + "get": { + "summary": "Returns point-in-time context of staking data for an address", + "description": "Returns point-in-time context of staking data for an address", + "operationId": "ViewStakingContext", + "responses": { + "200": { + "description": "OK", + "schema": { + "$ref": "#/definitions/v1ViewStakingContextResponse" + } + }, + "400": { + "description": "The request attempted has invalid parameters", + "schema": { + "$ref": "#/definitions/rpcStatus" + } + }, + "401": { + "description": "Returned if authentication information is invalid", + "schema": { + "$ref": "#/definitions/rpcStatus" + } + }, + "403": { + "description": "Returned when a user does not have permission to the resource.", + "schema": { + "$ref": "#/definitions/rpcStatus" + } + }, + "404": { + "description": "Returned when a resource is not found.", + "schema": { + "$ref": "#/definitions/rpcStatus" + } + }, + "429": { + "description": "Returned when a resource limit has been reached.", + "schema": { + "$ref": "#/definitions/rpcStatus" + } + }, + "500": { + "description": "Returned when an internal server error happens.", + "schema": { + "$ref": "#/definitions/rpcStatus" + } + }, + "default": { + "description": "An unexpected error response.", + "schema": { + "$ref": "#/definitions/rpcStatus" + } + } + }, + "parameters": [ + { + "name": "address", + "description": "The address to fetch staking context for.", + "in": "query", + "required": true, + "type": "string" + }, + { + "name": "network", + "description": "The network to fetch staking context for.", + "in": "query", + "required": true, + "type": "string" + }, + { + "name": "ethereumKilnStakingContextParameters.integratorContractAddress", + "description": "Integrator contract address.", + "in": "query", + "required": false, + "type": "string" + } + ], + "tags": [ + "StakingContext" + ] + } + }, + "/v1/{name}": { + "get": { + "summary": "Get workflow", + "operationId": "getWorkflow", + "responses": { + "200": { + "description": "OK", + "schema": { + "$ref": "#/definitions/v1Workflow" + } + }, + "400": { + "description": "The request attempted has invalid parameters", + "schema": { + "$ref": "#/definitions/rpcStatus" + } + }, + "401": { + "description": "Returned if authentication information is invalid", + "schema": { + "$ref": "#/definitions/rpcStatus" + } + }, + "403": { + "description": "Returned when a user does not have permission to the resource.", + "schema": { + "$ref": "#/definitions/rpcStatus" + } + }, + "404": { + "description": "Returned when a resource is not found.", + "schema": { + "$ref": "#/definitions/rpcStatus" + } + }, + "429": { + "description": "Returned when a resource limit has been reached.", + "schema": { + "$ref": "#/definitions/rpcStatus" + } + }, + "500": { + "description": "Returned when an internal server error happens.", + "schema": { + "$ref": "#/definitions/rpcStatus" + } + }, + "default": { + "description": "An unexpected error response.", + "schema": { + "$ref": "#/definitions/rpcStatus" + } + } + }, + "parameters": [ + { + "name": "name", + "description": "The resource name of the workflow.\nFormat: projects/{project}/workflows/{workflow}", + "in": "path", + "required": true, + "type": "string", + "pattern": "projects/[^/]+/workflows/[^/]+" + } + ], + "tags": [ + "Workflow" + ] + } + }, + "/v1/{name}/step": { + "post": { + "summary": "Perform the next step in a workflow", + "description": "Perform the next step in a workflow", + "operationId": "updateWorkflow", + "responses": { + "200": { + "description": "OK", + "schema": { + "$ref": "#/definitions/v1Workflow" + } + }, + "400": { + "description": "The request attempted has invalid parameters", + "schema": { + "$ref": "#/definitions/rpcStatus" + } + }, + "401": { + "description": "Returned if authentication information is invalid", + "schema": { + "$ref": "#/definitions/rpcStatus" + } + }, + "403": { + "description": "Returned when a user does not have permission to the resource.", + "schema": { + "$ref": "#/definitions/rpcStatus" + } + }, + "404": { + "description": "Returned when a resource is not found.", + "schema": { + "$ref": "#/definitions/rpcStatus" + } + }, + "429": { + "description": "Returned when a resource limit has been reached.", + "schema": { + "$ref": "#/definitions/rpcStatus" + } + }, + "500": { + "description": "Returned when an internal server error happens.", + "schema": { + "$ref": "#/definitions/rpcStatus" + } + }, + "default": { + "description": "An unexpected error response.", + "schema": { + "$ref": "#/definitions/rpcStatus" + } + } + }, + "parameters": [ + { + "name": "name", + "description": "The resource name of the workflow.\nFormat: projects/{project}/workflows/{workflow}", + "in": "path", + "required": true, + "type": "string", + "pattern": "projects/[^/]+/workflows/[^/]+" + }, + { + "name": "body", + "in": "body", + "required": true, + "schema": { + "$ref": "#/definitions/StakingServicePerformWorkflowStepBody" + } + } + ], + "tags": [ + "Workflow" + ] + } + }, + "/v1/{parent}/actions": { + "get": { + "summary": "List supported actions", + "description": "List supported actions", + "operationId": "listActions", + "responses": { + "200": { + "description": "OK", + "schema": { + "$ref": "#/definitions/v1ListActionsResponse" + } + }, + "400": { + "description": "The request attempted has invalid parameters", + "schema": { + "$ref": "#/definitions/rpcStatus" + } + }, + "401": { + "description": "Returned if authentication information is invalid", + "schema": { + "$ref": "#/definitions/rpcStatus" + } + }, + "403": { + "description": "Returned when a user does not have permission to the resource.", + "schema": { + "$ref": "#/definitions/rpcStatus" + } + }, + "404": { + "description": "Returned when a resource is not found.", + "schema": { + "$ref": "#/definitions/rpcStatus" + } + }, + "429": { + "description": "Returned when a resource limit has been reached.", + "schema": { + "$ref": "#/definitions/rpcStatus" + } + }, + "500": { + "description": "Returned when an internal server error happens.", + "schema": { + "$ref": "#/definitions/rpcStatus" + } + }, + "default": { + "description": "An unexpected error response.", + "schema": { + "$ref": "#/definitions/rpcStatus" + } + } + }, + "parameters": [ + { + "name": "parent", + "description": "The resource name of the parent that owns\nthe collection of actions.\nFormat: protocols/{protocol}/networks/{network}", + "in": "path", + "required": true, + "type": "string", + "pattern": "protocols/[^/]+/networks/[^/]+" + } + ], + "tags": [ + "Action" + ] + } + }, + "/v1/{parent}/networks": { + "get": { + "summary": "List supported networks", + "description": "List supported networks", + "operationId": "listNetworks", + "responses": { + "200": { + "description": "OK", + "schema": { + "$ref": "#/definitions/v1ListNetworksResponse" + } + }, + "400": { + "description": "The request attempted has invalid parameters", + "schema": { + "$ref": "#/definitions/rpcStatus" + } + }, + "401": { + "description": "Returned if authentication information is invalid", + "schema": { + "$ref": "#/definitions/rpcStatus" + } + }, + "403": { + "description": "Returned when a user does not have permission to the resource.", + "schema": { + "$ref": "#/definitions/rpcStatus" + } + }, + "404": { + "description": "Returned when a resource is not found.", + "schema": { + "$ref": "#/definitions/rpcStatus" + } + }, + "429": { + "description": "Returned when a resource limit has been reached.", + "schema": { + "$ref": "#/definitions/rpcStatus" + } + }, + "500": { + "description": "Returned when an internal server error happens.", + "schema": { + "$ref": "#/definitions/rpcStatus" + } + }, + "default": { + "description": "An unexpected error response.", + "schema": { + "$ref": "#/definitions/rpcStatus" + } + } + }, + "parameters": [ + { + "name": "parent", + "description": "The resource name of the parent that owns\nthe collection of networks.\nFormat: protocols/{protocol}", + "in": "path", + "required": true, + "type": "string", + "pattern": "protocols/[^/]+" + } + ], + "tags": [ + "Network" + ] + } + }, + "/v1/{parent}/stakingTargets": { + "get": { + "summary": "List supported staking targets", + "description": "List supported staking targets", + "operationId": "listStakingTargets", + "responses": { + "200": { + "description": "OK", + "schema": { + "$ref": "#/definitions/v1ListStakingTargetsResponse" + } + }, + "400": { + "description": "The request attempted has invalid parameters", + "schema": { + "$ref": "#/definitions/rpcStatus" + } + }, + "401": { + "description": "Returned if authentication information is invalid", + "schema": { + "$ref": "#/definitions/rpcStatus" + } + }, + "403": { + "description": "Returned when a user does not have permission to the resource.", + "schema": { + "$ref": "#/definitions/rpcStatus" + } + }, + "404": { + "description": "Returned when a resource is not found.", + "schema": { + "$ref": "#/definitions/rpcStatus" + } + }, + "429": { + "description": "Returned when a resource limit has been reached.", + "schema": { + "$ref": "#/definitions/rpcStatus" + } + }, + "500": { + "description": "Returned when an internal server error happens.", + "schema": { + "$ref": "#/definitions/rpcStatus" + } + }, + "default": { + "description": "An unexpected error response.", + "schema": { + "$ref": "#/definitions/rpcStatus" + } + } + }, + "parameters": [ + { + "name": "parent", + "description": "The resource name of the parent that owns\nthe collection of staking targets.\nFormat: protocols/{protocol}/networks/{network}", + "in": "path", + "required": true, + "type": "string", + "pattern": "protocols/[^/]+/networks/[^/]+" + }, + { + "name": "pageSize", + "description": "The maximum number of staking targets to return. The service may\nreturn fewer than this value.\n\nIf unspecified, 100 staking targets will be returned.\nThe maximum value is 1000; values over 1000 will be floored to 1000.", + "in": "query", + "required": false, + "type": "integer", + "format": "int32" + }, + { + "name": "pageToken", + "description": "A page token as part of the response of a previous call.\nProvide this to retrieve the next page.\n\nWhen paginating, all other parameters must match the previous\nrequest to list resources.", + "in": "query", + "required": false, + "type": "string" + } + ], + "tags": [ + "StakingTarget" + ] + } + }, + "/v1/{parent}/workflows": { + "get": { + "summary": "List supported workflows", + "operationId": "listWorkflows", + "responses": { + "200": { + "description": "OK", + "schema": { + "$ref": "#/definitions/v1ListWorkflowsResponse" + } + }, + "400": { + "description": "The request attempted has invalid parameters", + "schema": { + "$ref": "#/definitions/rpcStatus" + } + }, + "401": { + "description": "Returned if authentication information is invalid", + "schema": { + "$ref": "#/definitions/rpcStatus" + } + }, + "403": { + "description": "Returned when a user does not have permission to the resource.", + "schema": { + "$ref": "#/definitions/rpcStatus" + } + }, + "404": { + "description": "Returned when a resource is not found.", + "schema": { + "$ref": "#/definitions/rpcStatus" + } + }, + "429": { + "description": "Returned when a resource limit has been reached.", + "schema": { + "$ref": "#/definitions/rpcStatus" + } + }, + "500": { + "description": "Returned when an internal server error happens.", + "schema": { + "$ref": "#/definitions/rpcStatus" + } + }, + "default": { + "description": "An unexpected error response.", + "schema": { + "$ref": "#/definitions/rpcStatus" + } + } + }, + "parameters": [ + { + "name": "parent", + "description": "The resource name of the parent that owns\nthe collection of networks.\nFormat: projects/{project}", + "in": "path", + "required": true, + "type": "string", + "pattern": "projects/[^/]+" + }, + { + "name": "filter", + "description": "[AIP-160](https://google.aip.dev/160) filter\nSupported fields:\n- string action: \"stake\", \"unstake\"\n- string protocol: \"ethereum_kiln\"\n- string network: \"holesky\", \"mainnet\"", + "in": "query", + "required": false, + "type": "string" + }, + { + "name": "pageSize", + "description": "The maximum number of workflows to return. The service may\nreturn fewer than this value.\n\nIf unspecified, 100 workflows will be returned.\nThe maximum value is 1000; values over 1000 will be floored to 1000.", + "in": "query", + "required": false, + "type": "integer", + "format": "int32" + }, + { + "name": "pageToken", + "description": "A page token as part of the response of a previous call.\nProvide this to retrieve the next page.\n\nWhen paginating, all other parameters must match the previous\nrequest to list resources.", + "in": "query", + "required": false, + "type": "string" + } + ], + "tags": [ + "Workflow" + ] + }, + "post": { + "summary": "Create workflow", + "operationId": "createWorkflow", + "responses": { + "200": { + "description": "OK", + "schema": { + "$ref": "#/definitions/v1Workflow" + } + }, + "400": { + "description": "The request attempted has invalid parameters", + "schema": { + "$ref": "#/definitions/rpcStatus" + } + }, + "401": { + "description": "Returned if authentication information is invalid", + "schema": { + "$ref": "#/definitions/rpcStatus" + } + }, + "403": { + "description": "Returned when a user does not have permission to the resource.", + "schema": { + "$ref": "#/definitions/rpcStatus" + } + }, + "404": { + "description": "Returned when a resource is not found.", + "schema": { + "$ref": "#/definitions/rpcStatus" + } + }, + "429": { + "description": "Returned when a resource limit has been reached.", + "schema": { + "$ref": "#/definitions/rpcStatus" + } + }, + "500": { + "description": "Returned when an internal server error happens.", + "schema": { + "$ref": "#/definitions/rpcStatus" + } + }, + "default": { + "description": "An unexpected error response.", + "schema": { + "$ref": "#/definitions/rpcStatus" + } + } + }, + "parameters": [ + { + "name": "parent", + "description": "The resource name of the parent that owns\nthe workflow.\nFormat: projects/{project}", + "in": "path", + "required": true, + "type": "string", + "pattern": "projects/[^/]+" + }, + { + "name": "workflow", + "description": "The workflow to create.", + "in": "body", + "required": true, + "schema": { + "$ref": "#/definitions/v1Workflow", + "required": [ + "workflow" + ] + } + } + ], + "tags": [ + "Workflow" + ] + } + } + }, + "definitions": { + "StakeAccountBalanceState": { + "type": "string", + "enum": [ + "BALANCE_STATE_UNSPECIFIED", + "BALANCE_STATE_INACTIVE", + "BALANCE_STATE_ACTIVATING", + "BALANCE_STATE_ACTIVE", + "BALANCE_STATE_DEACTIVATING" + ], + "default": "BALANCE_STATE_UNSPECIFIED", + "description": "Represents the different states a stake account balance can have.\nUsed to check to see if stake is actively earning rewards or ready to be withdrawn.\n\n - BALANCE_STATE_UNSPECIFIED: The balance is not known.\n - BALANCE_STATE_INACTIVE: The balance is not actively staking.\n - BALANCE_STATE_ACTIVATING: The balance is in a warm up period and will activate in the next epoch.\n - BALANCE_STATE_ACTIVE: The balance is actively staking and earning rewards.\n - BALANCE_STATE_DEACTIVATING: The balance is in a cool down period and will be deactivated in the next epoch." + }, + "StakingServicePerformWorkflowStepBody": { + "type": "object", + "properties": { + "step": { + "type": "integer", + "format": "int32", + "description": "The index of the step to be performed." + }, + "data": { + "type": "string", + "description": "Transaction metadata. This is either the signed transaction or transaction hash depending on the workflow's broadcast method." + } + }, + "description": "The request message for PerformWorkflowStep.", + "required": [ + "step", + "data" + ] + }, + "WaitStepOutputWaitUnit": { + "type": "string", + "enum": [ + "WAIT_UNIT_UNSPECIFIED", + "WAIT_UNIT_SECONDS", + "WAIT_UNIT_BLOCKS", + "WAIT_UNIT_EPOCHS", + "WAIT_UNIT_CHECKPOINTS" + ], + "default": "WAIT_UNIT_UNSPECIFIED", + "description": "The unit of wait time.\n\n - WAIT_UNIT_UNSPECIFIED: Unspecified wait time.\n - WAIT_UNIT_SECONDS: Wait time measured in seconds.\n - WAIT_UNIT_BLOCKS: Wait time measured in blocks.\n - WAIT_UNIT_EPOCHS: Wait time measured in epochs.\n - WAIT_UNIT_CHECKPOINTS: Wait time measured in checkpoints." + }, + "protobufAny": { + "type": "object", + "properties": { + "@type": { + "type": "string" + } + }, + "additionalProperties": {} + }, + "rpcStatus": { + "type": "object", + "properties": { + "code": { + "type": "integer", + "format": "int32" + }, + "message": { + "type": "string" + }, + "details": { + "type": "array", + "items": { + "type": "object", + "$ref": "#/definitions/protobufAny" + } + } + } + }, + "v1Action": { + "type": "object", + "properties": { + "name": { + "type": "string", + "title": "The resource name of the Action.\nFormat: protocols/{protocolName}/networks/{networkName}/actions/{actionName}\nEx: protocols/ethereum_kiln/networks/holesky/validators/stake" + } + }, + "description": "An Action resource represents an action you may take on a network (e.g. stake, unstake)." + }, + "v1Amount": { + "type": "object", + "properties": { + "value": { + "type": "string", + "description": "The total value of the token." + }, + "currency": { + "type": "string", + "description": "The currency of the token" + } + }, + "description": "The amount of a token you wish to perform an action\nwith." + }, + "v1Contract": { + "type": "object", + "properties": { + "name": { + "type": "string", + "title": "The resource name of the Contract Address.\nFormat: protocols/{protocolName}/networks/{networkName}/stakingTargets/{contractName}\nEx: protocols/ethereum_kiln/networks/holesky/stakingTargets/0xA55416de5DE61A0AC1aa8970a280E04388B1dE4b" + }, + "address": { + "type": "string", + "description": "The contract address you may submit actions to." + } + }, + "description": "A Contract resource, which represents an active contract\nfor the given protocol network which you can submit an action\nto." + }, + "v1EthereumKilnClaimStakeParameters": { + "type": "object", + "properties": { + "stakerAddress": { + "type": "string", + "description": "The address you wish to claim stake for." + }, + "integratorContractAddress": { + "type": "string", + "title": "The address of the integrator contract" + } + }, + "description": "The parameters required for the claim stake action on Ethereum Kiln.", + "title": "EthereumKiln: Claim Stake Parameters", + "required": [ + "stakerAddress", + "integratorContractAddress" + ] + }, + "v1EthereumKilnStakeParameters": { + "type": "object", + "properties": { + "stakerAddress": { + "type": "string", + "description": "The address you wish to stake from." + }, + "integratorContractAddress": { + "type": "string", + "description": "The address of the integrator contract." + }, + "amount": { + "$ref": "#/definitions/v1Amount", + "description": "The amount of Ethereum to stake in wei." + } + }, + "description": "The parameters required for the stake action on Ethereum Kiln.", + "title": "EthereumKiln: Stake Parameters", + "required": [ + "stakerAddress", + "integratorContractAddress", + "amount" + ] + }, + "v1EthereumKilnStakingContextDetails": { + "type": "object", + "properties": { + "ethereumBalance": { + "$ref": "#/definitions/v1Amount", + "description": "The Ethereum balance of the address.\nThis can be used to gate the stake action to make sure the requested stake amount\nis less than ethereum_balance." + }, + "integratorShareBalance": { + "$ref": "#/definitions/v1Amount", + "description": "The number of integrator shares owned by the address." + }, + "integratorShareUnderlyingBalance": { + "$ref": "#/definitions/v1Amount", + "title": "The total Ethereum you can exchange for your integrator shares.\nThis can be used to gate the unstake action to make sure the requested unstake amount\nis less than integrator_share_underlying_balance" + }, + "totalExitableEth": { + "$ref": "#/definitions/v1Amount", + "description": "The total amount of Ethereum you can redeem for all non-claimed vPool shares.\nThis along with the condition total_shares_pending_exit == fulfillable_share_count\ncan be used to gate the claim_stake action." + }, + "totalSharesPendingExit": { + "$ref": "#/definitions/v1Amount", + "description": "The number of vPool shares are eligible to receive now or at a later point in time." + }, + "fulfillableShareCount": { + "$ref": "#/definitions/v1Amount", + "description": "The number of vPool shares you are able to claim now." + } + }, + "description": "The protocol specific details for an Ethereum Kiln staking context.", + "title": "EthereumKiln: Staking context details" + }, + "v1EthereumKilnStakingContextParameters": { + "type": "object", + "properties": { + "integratorContractAddress": { + "type": "string", + "description": "Integrator contract address." + } + }, + "description": "The protocol specific parameters required for fetching a staking context.", + "title": "EthereumKiln: Staking Context Parameters" + }, + "v1EthereumKilnStakingParameters": { + "type": "object", + "properties": { + "stakeParameters": { + "$ref": "#/definitions/v1EthereumKilnStakeParameters", + "description": "The parameters for stake action on Ethereum Kiln." + }, + "unstakeParameters": { + "$ref": "#/definitions/v1EthereumKilnUnstakeParameters", + "description": "The parameters for unstake action on Ethereum Kiln." + }, + "claimStakeParameters": { + "$ref": "#/definitions/v1EthereumKilnClaimStakeParameters", + "description": "The parameters for claim stake action on Ethereum Kiln." + } + }, + "description": "The parameters needed for staking on Ethereum via Kiln.", + "title": "EthereumKiln: Staking Parameters" + }, + "v1EthereumKilnUnstakeParameters": { + "type": "object", + "properties": { + "stakerAddress": { + "type": "string", + "description": "The address you wish to unstake from." + }, + "integratorContractAddress": { + "type": "string", + "description": "The address of the integrator contract." + }, + "amount": { + "$ref": "#/definitions/v1Amount", + "description": "The amount of Ethereum to unstake in wei." + } + }, + "description": "The parameters required for the unstake action on Ethereum Kiln.", + "title": "EthereumKiln: Unstake Parameters", + "required": [ + "stakerAddress", + "integratorContractAddress", + "amount" + ] + }, + "v1ListActionsResponse": { + "type": "object", + "properties": { + "actions": { + "type": "array", + "items": { + "type": "object", + "$ref": "#/definitions/v1Action" + }, + "description": "The list of actions." + } + }, + "description": "The response message for ListActions." + }, + "v1ListNetworksResponse": { + "type": "object", + "properties": { + "networks": { + "type": "array", + "items": { + "type": "object", + "$ref": "#/definitions/v1Network" + }, + "description": "The list of networks." + } + }, + "description": "The response message for ListNetworks." + }, + "v1ListProtocolsResponse": { + "type": "object", + "properties": { + "protocols": { + "type": "array", + "items": { + "type": "object", + "$ref": "#/definitions/v1Protocol" + }, + "description": "The list of protocols." + } + }, + "description": "The response message for ListProtocols." + }, + "v1ListStakingTargetsResponse": { + "type": "object", + "properties": { + "stakingTargets": { + "type": "array", + "items": { + "type": "object", + "$ref": "#/definitions/v1StakingTarget" + }, + "description": "The list of staking targets." + }, + "nextPageToken": { + "type": "string", + "description": "A token which can be provided as `page_token` to retrieve the next page.\nIf this field is omitted, there are no additional pages." + } + }, + "description": "The response message for ListStakingTargets." + }, + "v1ListWorkflowsResponse": { + "type": "object", + "properties": { + "workflows": { + "type": "array", + "items": { + "type": "object", + "$ref": "#/definitions/v1Workflow" + }, + "description": "The list of workflows." + }, + "nextPageToken": { + "type": "string", + "description": "A token which can be provided as `page_token` to retrieve the next page.\nIf this field is omitted, there are no additional pages." + } + }, + "description": "The response message for ListWorkflows." + }, + "v1Network": { + "type": "object", + "properties": { + "name": { + "type": "string", + "title": "The resource name of the Network.\nFormat: protocols/{protocolName}/networks/{networkName}\nEx: protocols/ethereum_kiln/networks/holesky" + } + }, + "description": "A Network resource represents a blockchain network e.g. mainnet, testnet, etc." + }, + "v1PriorityFee": { + "type": "object", + "properties": { + "computeUnitLimit": { + "type": "string", + "format": "int64", + "description": "The maximum number of compute units a transaction is allowed to consume." + }, + "unitPrice": { + "type": "string", + "format": "int64", + "description": "The price to pay per compute unit." + } + }, + "description": "A prioritization fee that can be added to a Solana transaction." + }, + "v1Protocol": { + "type": "object", + "properties": { + "name": { + "type": "string", + "title": "The resource name of the Protocol.\nFormat: protocols/{protocolName}\nEx: protocols/ethereum_kiln" + } + }, + "description": "A Protocol resource (e.g. ethereum_kiln, solana etc.)." + }, + "v1SolanaClaimStakeParameters": { + "type": "object", + "properties": { + "walletAddress": { + "type": "string", + "description": "The address which is the signing authority to claim stake." + }, + "stakeAccountAddress": { + "type": "string", + "description": "The address of the stake account to claim stake from." + }, + "priorityFee": { + "$ref": "#/definitions/v1PriorityFee", + "description": "The option to set a priority fee for the transaction." + } + }, + "description": "The parameters required to perform a claim stake operation on Solana.", + "title": "Solana: Claim Stake Parameters" + }, + "v1SolanaStakeParameters": { + "type": "object", + "properties": { + "walletAddress": { + "type": "string", + "description": "The address where the funds are coming from to stake." + }, + "validatorAddress": { + "type": "string", + "description": "The address of the validator." + }, + "amount": { + "$ref": "#/definitions/v1Amount", + "title": "The amount of Solana to stake in lamports. (1 lamport = 0.000000001 SOL)" + }, + "priorityFee": { + "$ref": "#/definitions/v1PriorityFee", + "description": "The option to set a priority fee for the transaction." + } + }, + "description": "The parameters required to perform a stake operation on Solana.", + "title": "Solana: Stake Parameters" + }, + "v1SolanaStakingContextDetails": { + "type": "object", + "properties": { + "balance": { + "$ref": "#/definitions/v1Amount", + "description": "The total balance of the main wallet address (system account).\nUsed to check the balance for any future staking or transaction to send." + }, + "currentEpoch": { + "type": "string", + "format": "int64", + "description": "The current epoch that the Solana blockchain is in.\nUsed as a frame of reference for future stake activations and deactivations." + }, + "epochCompletionPercentage": { + "type": "string", + "description": "How much of the epoch has passed as a percentage.\nUsed to inform how much time is left before a stake is activated or deactivated." + }, + "stakeAccounts": { + "type": "array", + "items": { + "type": "object", + "$ref": "#/definitions/v1StakeAccount" + }, + "description": "The list of staking accounts that are linked to the main wallet address (system account).\nUsed to check for statuses and balances of all stake accounts related to the main wallet address that\nthey're linked to." + } + }, + "description": "The protocol specific details for a Solana staking context.", + "title": "Solana: Staking Context Details" + }, + "v1SolanaStakingContextParameters": { + "type": "object", + "description": "The protocol specific parameters required for fetching a staking context.", + "title": "Solana: Staking Context Parameters" + }, + "v1SolanaStakingParameters": { + "type": "object", + "properties": { + "stakeParameters": { + "$ref": "#/definitions/v1SolanaStakeParameters", + "description": "The parameters for stake action on Solana." + }, + "unstakeParameters": { + "$ref": "#/definitions/v1SolanaUnstakeParameters", + "description": "The parameters for unstake action on Solana." + }, + "claimStakeParameters": { + "$ref": "#/definitions/v1SolanaClaimStakeParameters", + "description": "The parameters for claim stake action on Solana." + } + }, + "description": "The parameters needed for staking on Solana.", + "title": "Solana: Staking Parameters" + }, + "v1SolanaUnstakeParameters": { + "type": "object", + "properties": { + "walletAddress": { + "type": "string", + "description": "The address which is the signing authority to unstake." + }, + "stakeAccountAddress": { + "type": "string", + "description": "The address of the stake account to unstake from." + }, + "amount": { + "$ref": "#/definitions/v1Amount", + "title": "The amount of Solana to unstake in lamports. (1 lamport = 0.000000001 SOL)" + }, + "priorityFee": { + "$ref": "#/definitions/v1PriorityFee", + "description": "The option to set a priority fee for the transaction." + } + }, + "description": "The parameters required to perform a unstake operation on Solana.", + "title": "Solana: Unstake Parameters" + }, + "v1StakeAccount": { + "type": "object", + "properties": { + "address": { + "type": "string", + "description": "The address of the stake account.\nUsed to hold the staked funds transferred over from the main wallet." + }, + "bondedStake": { + "$ref": "#/definitions/v1Amount", + "description": "The bonded balance in lamports on the stake account (rent is not included in bonded amount).\nUsed to check the amount that is currently staked." + }, + "rentReserve": { + "$ref": "#/definitions/v1Amount", + "description": "The rent amount for the stake account in lamports.\nUsed to highlight the amount used as the rent to maintain the address on the Solana blockchain." + }, + "balance": { + "$ref": "#/definitions/v1Amount", + "description": "The total balance on the address in lamports.\nUsed to check the total balance for the stake account." + }, + "balanceState": { + "$ref": "#/definitions/StakeAccountBalanceState", + "description": "The balance state of the stake account.\nUsed to show what state the currently staked funds are in.", + "readOnly": true + }, + "validator": { + "type": "string", + "description": "The validator (vote account) that the stake account is assigned to stake to.\nUsed to show where the staked funds are staked to." + } + }, + "description": "The balance information for a stake account." + }, + "v1StakingTarget": { + "type": "object", + "properties": { + "validator": { + "$ref": "#/definitions/v1Validator", + "description": "A validator to stake to." + }, + "contract": { + "$ref": "#/definitions/v1Contract", + "description": "A contract to send a staking action to." + } + }, + "description": "A Staking Target represents a destination that you perform an action on related to staking." + }, + "v1TxStepOutput": { + "type": "object", + "properties": { + "unsignedTx": { + "type": "string", + "description": "The unsigned transaction which was signed in order to be broadcasted.", + "readOnly": true + }, + "signedTx": { + "type": "string", + "description": "The signed transaction which was asked to be broadcasted.", + "readOnly": true + }, + "txHash": { + "type": "string", + "description": "The hash of the broadcasted transaction.", + "readOnly": true + }, + "state": { + "$ref": "#/definitions/v1TxStepOutputState", + "description": "The state of the transaction step.", + "readOnly": true + }, + "errorMessage": { + "type": "string", + "description": "The error message if the transaction step failed.", + "readOnly": true + } + }, + "description": "The details of a transaction being constructed and broadcasted to the network." + }, + "v1TxStepOutputState": { + "type": "string", + "enum": [ + "STATE_UNSPECIFIED", + "STATE_NOT_CONSTRUCTED", + "STATE_CONSTRUCTED", + "STATE_PENDING_SIGNING", + "STATE_SIGNED", + "STATE_BROADCASTING", + "STATE_CONFIRMING", + "STATE_CONFIRMED", + "STATE_FINALIZED", + "STATE_FAILED", + "STATE_SUCCESS", + "STATE_PENDING_EXT_BROADCAST" + ], + "default": "STATE_UNSPECIFIED", + "description": "State defines an enumeration of states for a staking transaction.\n\n - STATE_UNSPECIFIED: Unspecified transaction state, this is for backwards compatibility.\n - STATE_NOT_CONSTRUCTED: Tx has not yet been constructed in the backend.\n - STATE_CONSTRUCTED: Tx construction is over in the backend.\n - STATE_PENDING_SIGNING: Tx is waiting to be signed.\n - STATE_SIGNED: Tx has been signed and returned to the backend.\n - STATE_BROADCASTING: Tx is being broadcasted to the network.\n - STATE_CONFIRMING: Tx is waiting for confirmation.\n - STATE_CONFIRMED: Tx has been confirmed to be included in a block.\n - STATE_FINALIZED: Tx has been finalized.\n - STATE_FAILED: Tx construction or broadcasting failed.\n - STATE_SUCCESS: Tx has been successfully executed.\n - STATE_PENDING_EXT_BROADCAST: Tx is waiting to be externally broadcasted by the customer." + }, + "v1Validator": { + "type": "object", + "properties": { + "name": { + "type": "string", + "title": "The resource name of the Validator.\nFormat: protocols/{protocolName}/networks/{networkName}/stakingTargets/{validatorName}\nEx: protocols/solana/networks/testnet/stakingTargets/GkqYQysEGmuL6V2AJoNnWZUz2ZBGWhzQXsJiXm2CLKAN" + }, + "address": { + "type": "string", + "description": "The public address of the validator." + }, + "commissionRate": { + "type": "number", + "format": "float", + "title": "The rate of commission for the validator" + } + }, + "description": "A Validator resource represents an active validator for the given protocol network." + }, + "v1ViewStakingContextResponse": { + "type": "object", + "properties": { + "address": { + "type": "string", + "description": "The address you are getting a staking context for." + }, + "ethereumKilnStakingContextDetails": { + "$ref": "#/definitions/v1EthereumKilnStakingContextDetails", + "description": "EthereumKiln staking context details." + }, + "solanaStakingContextDetails": { + "$ref": "#/definitions/v1SolanaStakingContextDetails", + "description": "Solana staking context details." + } + }, + "description": "The response message for the ViewStakingContext request.", + "required": [ + "address", + "ethereumKilnStakingContextDetails", + "solanaStakingContextDetails" + ] + }, + "v1WaitStepOutput": { + "type": "object", + "properties": { + "start": { + "type": "string", + "format": "int64", + "description": "The beginning of wait period.", + "readOnly": true + }, + "current": { + "type": "string", + "format": "int64", + "description": "The current wait progress.", + "readOnly": true + }, + "target": { + "type": "string", + "format": "int64", + "description": "The target wait end point.", + "readOnly": true + }, + "unit": { + "$ref": "#/definitions/WaitStepOutputWaitUnit", + "description": "The wait unit (like checkpoint, block, epoch etc).", + "readOnly": true + }, + "state": { + "$ref": "#/definitions/v1WaitStepOutputState", + "description": "The state of the wait step.", + "readOnly": true + } + }, + "description": "The output details of a step where we wait for some kind of on-chain activity to finish like reaching a certain checkpoint, epoch or block." + }, + "v1WaitStepOutputState": { + "type": "string", + "enum": [ + "STATE_UNSPECIFIED", + "STATE_NOT_STARTED", + "STATE_IN_PROGRESS", + "STATE_COMPLETED" + ], + "default": "STATE_UNSPECIFIED", + "description": "WaitStepState defines an enumeration of states for a wait step.\n\n - STATE_UNSPECIFIED: Unspecified wait step state.\n - STATE_NOT_STARTED: Wait step has not started.\n - STATE_IN_PROGRESS: Wait step is in-progress.\n - STATE_COMPLETED: Wait step completed." + }, + "v1Workflow": { + "type": "object", + "properties": { + "name": { + "type": "string", + "title": "The resource name of the workflow.\nFormat: projects/{projectUUID}/workflows/{workflowUUID}\nEx: projects/ 123e4567-e89b-12d3-a456-426614174000/workflows/123e4567-e89b-12d3-a456-426614174000", + "readOnly": true + }, + "action": { + "type": "string", + "title": "The resource name of the action being\nperformed.\nFormat: protocols/{protocol}/networks/{network}/actions/{action}" + }, + "solanaStakingParameters": { + "$ref": "#/definitions/v1SolanaStakingParameters", + "description": "Solana staking parameters." + }, + "ethereumKilnStakingParameters": { + "$ref": "#/definitions/v1EthereumKilnStakingParameters", + "description": "EthereumKiln staking parameters." + }, + "state": { + "$ref": "#/definitions/v1WorkflowState", + "description": "The current state of the workflow.", + "readOnly": true + }, + "currentStepId": { + "type": "integer", + "format": "int32", + "description": "The index of the current step.", + "readOnly": true + }, + "steps": { + "type": "array", + "items": { + "type": "object", + "$ref": "#/definitions/v1WorkflowStep" + }, + "description": "The list of steps for this workflow.", + "readOnly": true + }, + "createTime": { + "type": "string", + "format": "date-time", + "description": "The timestamp the workflow was created.", + "readOnly": true + }, + "updateTime": { + "type": "string", + "format": "date-time", + "description": "The timestamp the workflow was last updated.", + "readOnly": true + }, + "skipBroadcast": { + "type": "boolean", + "description": "Flag to skip tx broadcast to network on behalf of the user. Use this flag if you instead prefer to broadcast signed txs on your own." + }, + "completeTime": { + "type": "string", + "format": "date-time", + "description": "The timestamp the workflow completed.", + "readOnly": true + } + }, + "description": "A Workflow resource.", + "required": [ + "action", + "solanaStakingParameters", + "ethereumKilnStakingParameters" + ] + }, + "v1WorkflowState": { + "type": "string", + "enum": [ + "STATE_UNSPECIFIED", + "STATE_IN_PROGRESS", + "STATE_WAITING_FOR_SIGNING", + "STATE_COMPLETED", + "STATE_FAILED", + "STATE_WAITING_FOR_EXT_BROADCAST" + ], + "default": "STATE_UNSPECIFIED", + "description": "Example flow: A workflow with skip_broadcast = true leading to a successful completion.\n IN_PROGRESS -\u003e WAITING_FOR_EXT_BROADCAST -\u003e IN_PROGRESS -\u003e COMPLETED\n Example flow: A workflow with skip_broadcast = false leading to a successful completion.\n IN_PROGRESS -\u003e WAITING_FOR_SIGNING -\u003e IN_PROGRESS -\u003e COMPLETED\n Example flow: A workflow with skip_broadcast = false leading to a failure.\n IN_PROGRESS -\u003e WAITING_FOR_SIGNING -\u003e IN_PROGRESS -\u003e FAILED\n\n - STATE_UNSPECIFIED: Unspecified workflow state, this is for backwards compatibility.\n - STATE_IN_PROGRESS: In Progress represents a workflow that is currently in progress.\n - STATE_WAITING_FOR_SIGNING: Waiting for signing represents the workflow is waiting on the consumer to sign and return the corresponding signed tx.\n - STATE_COMPLETED: Completed represents the workflow has completed.\n - STATE_FAILED: Failed represents the workflow has failed.\n - STATE_WAITING_FOR_EXT_BROADCAST: Waiting for external broadcast represents the workflow is waiting for the customer to broadcast a tx and return its corresponding tx hash.", + "title": "The state of a workflow" + }, + "v1WorkflowStep": { + "type": "object", + "properties": { + "name": { + "type": "string", + "description": "The human readable name of the step.", + "readOnly": true + }, + "txStepOutput": { + "$ref": "#/definitions/v1TxStepOutput", + "description": "The tx step output (e.g. transaction metadata such as unsigned tx, signed tx etc).", + "readOnly": true + }, + "waitStepOutput": { + "$ref": "#/definitions/v1WaitStepOutput", + "description": "The waiting details for any kind like how many checkpoints away for unbonding etc.", + "readOnly": true + } + }, + "description": "The information for a step in the workflow.", + "title": "The information for a step in the workflow" + } + } +} diff --git a/docs/openapi/rewards.swagger.json b/docs/openapi/rewards.swagger.json new file mode 100644 index 0000000..a1ae811 --- /dev/null +++ b/docs/openapi/rewards.swagger.json @@ -0,0 +1,617 @@ +{ + "swagger": "2.0", + "info": { + "title": "Coinbase Rewards API", + "description": "API that delivers crypto-forward onchain staking-related rewards data", + "version": "v1" + }, + "tags": [ + { + "name": "Reward", + "description": "A high-level view of an address's rewards aggregated over some period of time (ex: over an Epoch)." + }, + { + "name": "Stake", + "description": "A snapshot of an address's staking-related balance at a particular point in time. Feature coming soon." + }, + { + "name": "RewardService" + } + ], + "host": "api.developer.coinbase.com", + "basePath": "/rewards", + "schemes": [ + "https" + ], + "consumes": [ + "application/json" + ], + "produces": [ + "application/json" + ], + "paths": { + "/v1/{name}": { + "get": { + "summary": "Returns a staking balance", + "description": "Returns a specific staking balance for an address on the specific protocol at a particular point in time.", + "operationId": "RewardService_GetStake", + "responses": { + "200": { + "description": "OK", + "schema": { + "$ref": "#/definitions/v1Stake" + } + }, + "400": { + "description": "The request attempted has invalid parameters", + "schema": { + "example": { + "code": 3, + "message": "Invalid stake ID. \u003cRemediation assistance here\u003e.", + "details": [] + } + } + }, + "401": { + "description": "Returned if authentication information is invalid", + "schema": { + "example": "Unauthorized" + } + }, + "500": { + "description": "Returned when an internal server error happens.", + "schema": { + "example": { + "code": 3, + "message": "Internal server error.", + "details": [] + } + } + }, + "default": { + "description": "An unexpected error response.", + "schema": { + "$ref": "#/definitions/rpcStatus" + } + } + }, + "parameters": [ + { + "name": "name", + "description": "The resource name of the stake to retrieve.\nFormat: protocols/{protocol}/stakes/{stake}", + "in": "path", + "required": true, + "type": "string", + "pattern": "protocols/[^/]+/stakes/[^/]+" + } + ], + "tags": [ + "Stake" + ] + } + }, + "/v1/{parent}/rewards": { + "get": { + "summary": "List and filter rewards", + "description": "Lists onchain rewards of an address for a specific protocol, with optional filters for time range, aggregation period, and more.", + "operationId": "RewardService_ListRewards", + "responses": { + "200": { + "description": "OK", + "schema": { + "example": { + "rewards": [ + { + "address": "beefKGBWeSpHzYBHZXwp5So7wdQGX6mu4ZHCsH3uTar", + "epoch": "533", + "aggregationUnit": "epoch", + "periodStartTime": null, + "periodEndTime": "2023-11-16T00:13:44Z", + "totalEarnedNativeUnit": { + "amount": "224.7098145", + "exp": "9", + "ticker": "SOL", + "rawNumeric": "224709814509" + }, + "totalEarnedUsd": null, + "endingBalance": null, + "protocol": "solana" + }, + { + "address": "beefKGBWeSpHzYBHZXwp5So7wdQGX6mu4ZHCsH3uTar", + "epoch": "532", + "aggregationUnit": "epoch", + "periodStartTime": null, + "periodEndTime": "2023-11-13T19:38:36Z", + "totalEarnedNativeUnit": { + "amount": "225.0794241", + "exp": "9", + "ticker": "SOL", + "rawNumeric": "225079424094" + }, + "totalEarnedUsd": null, + "endingBalance": null, + "protocol": "solana" + } + ], + "nextPageToken": "VAql-wtdiJWkWII9bJBDnE9oEc-8IlgU0DtKbxSDtBg=:1:1700241277" + } + } + }, + "400": { + "description": "The request attempted has invalid parameters", + "schema": { + "example": { + "code": 3, + "message": "Filter validation failed. \u003cRemediation assistance here\u003e.", + "details": [] + } + } + }, + "401": { + "description": "Returned if authentication information is invalid", + "schema": { + "example": "Unauthorized" + } + }, + "500": { + "description": "Returned when an internal server error happens.", + "schema": { + "example": { + "code": 3, + "message": "Internal server error.", + "details": [] + } + } + }, + "default": { + "description": "An unexpected error response.", + "schema": { + "$ref": "#/definitions/rpcStatus" + } + } + }, + "parameters": [ + { + "name": "parent", + "description": "The protocol that the rewards were earned on.\nThe response will only include rewards for the protocol specified here.", + "in": "path", + "required": true, + "type": "string", + "pattern": "protocols/[^/]+" + }, + { + "name": "pageSize", + "description": "The maximum number of items to return. Maximum size of this value is 500.\nIf user supplies a value \u003e 500, the API will truncate to 500.", + "in": "query", + "required": false, + "type": "integer", + "format": "int32" + }, + { + "name": "pageToken", + "description": "A page token as part of the response of a previous call.\nProvide this to retrieve the next page.\n\nWhen paginating, all other parameters must match the previous\nrequest to list resources correctly.", + "in": "query", + "required": false, + "type": "string" + }, + { + "name": "filter", + "description": "[AIP-160](https://google.aip.dev/160) format compliant filter. Supported protocols are 'ethereum', 'solana', and 'cosmos'.\nSupplying other protocols will return an error.\n* **Ethereum**:\n - Fields:\n - `address` - A ethereum validator public key.\n - `epoch` - An ethereum epoch. Supports epoch comparisons (ex: `epoch \u003e= 1000 AND epoch \u003c= 2000`).\n - Example(s):\n - `\"address='0xac53512c39d0081ca4437c285305eb423f474e6153693c12fbba4a3df78bcaa3422b31d800c5bea71c1b017168a60474' AND epoch \u003e= 234640 AND epoch \u003c 234645\"`\n\n* **Solana**:\n - Fields:\n - `address` - A solana validator or delegator address.\n - `epoch` - A solana epoch. Supports epoch comparisons (ex: `epoch \u003e= 1000 AND epoch \u003c= 2000`).\n - Example(s):\n - `\"address='beefKGBWeSpHzYBHZXwp5So7wdQGX6mu4ZHCsH3uTar' AND epoch \u003e= 440 AND epoch \u003c 450\"`\n\n* **Cosmos**:\n - Fields:\n - `address` - A cosmos validator or delegator address (ex: `cosmosvaloper1c4k24jzduc365kywrsvf5ujz4ya6mwympnc4en` and `cosmos1c4k24jzduc365kywrsvf5ujz4ya6mwymy8vq4q`)\n - `date` - A date in format 'YYYY-MM-DD'. Supports multiple comparisons (ex: `date \u003e= '2023-07-01' AND date \u003c= '2023-07-31'`).\n - Example(s):\n - `address='cosmos1mfduj0qax6ut8rd6cfc4j0ds06z0mwlhrljhqh' AND date = '2023-10-16'`", + "in": "query", + "required": false, + "type": "string" + } + ], + "tags": [ + "Reward" + ] + } + }, + "/v1/{parent}/stakes": { + "get": { + "summary": "List and filter staking balances", + "description": "Lists staking balance of a protocol, with optional filters for time range and address.", + "operationId": "RewardService_ListStakes", + "responses": { + "200": { + "description": "OK", + "schema": { + "$ref": "#/definitions/v1ListStakesResponse" + } + }, + "400": { + "description": "The request attempted has invalid parameters", + "schema": { + "example": { + "code": 3, + "message": "Filter validation failed. \u003cRemediation assistance here\u003e.", + "details": [] + } + } + }, + "401": { + "description": "Returned if authentication information is invalid", + "schema": { + "example": "Unauthorized" + } + }, + "500": { + "description": "Returned when an internal server error happens.", + "schema": { + "example": { + "code": 3, + "message": "Internal server error.", + "details": [] + } + } + }, + "default": { + "description": "An unexpected error response.", + "schema": { + "$ref": "#/definitions/rpcStatus" + } + } + }, + "parameters": [ + { + "name": "parent", + "description": "The protocol that the staking balance exists on.\nThe response will only include staking balances for the protocol specified here.", + "in": "path", + "required": true, + "type": "string", + "pattern": "protocols/[^/]+" + }, + { + "name": "pageSize", + "description": "The maximum number of items to return.", + "in": "query", + "required": false, + "type": "integer", + "format": "int32" + }, + { + "name": "pageToken", + "description": "A page token as part of the response of a previous call.\nProvide this to retrieve the next page.\n\nWhen paginating, all other parameters must match the previous\nrequest to list resources.", + "in": "query", + "required": false, + "type": "string" + }, + { + "name": "filter", + "description": "[AIP-160](https://google.aip.dev/160) filter", + "in": "query", + "required": false, + "type": "string" + } + ], + "tags": [ + "Stake" + ] + } + } + }, + "definitions": { + "RewardRateCalculationMethods": { + "type": "string", + "enum": [ + "CALCULATION_METHODS_UNSPECIFIED", + "SOLO_STAKER", + "POOLED_STAKER", + "EPOCH_AUTO_COMPOUNDING", + "NO_AUTO_COMPOUNDING" + ], + "default": "CALCULATION_METHODS_UNSPECIFIED", + "description": "Representing the different methods of calculating yield.\n\n - CALCULATION_METHODS_UNSPECIFIED: Calculation method is unknown or unspecified.\n - SOLO_STAKER: A single Ethereum validator acting in isolation is currently not able to compound earned rewards because\nEthereum only allows validators to stake 32 ETH precisely.\nThis percentage yield is assuming that the rewards never compound, mimicking the behavior of a solo staker.\n - POOLED_STAKER: A pool of Ethereum validators of sufficient size is able to compound rewards almost immediately.\nThis percentage yield is assuming rewards compound immediately, mimicking the behavior of a sufficiently large pool.\n - EPOCH_AUTO_COMPOUNDING: A Solana delegator's staking rewards are staked (and therefore auto-compound) when rewards are paid out between epochs.\nThis percentage yield is assuming the rewards are auto-compounded on that schedule, mimicking a Solana delegator.\n - NO_AUTO_COMPOUNDING: A Solana validator's rewards accumulate in a separate account from the validator's active stake.\nThis percentage yield is assuming the rewards are not auto-compounded at any point, mimicking a Solana validator who never staked their rewards." + }, + "StakeDelegation": { + "type": "object", + "properties": { + "address": { + "type": "string", + "title": "Address associated to the delegation" + }, + "amount": { + "$ref": "#/definitions/v1AssetAmount", + "title": "Amount of delegation received or given" + }, + "commissionRate": { + "type": "string", + "title": "Commission rate for delegation" + } + }, + "description": "A single delegation from one address to another." + }, + "USDValueSource": { + "type": "string", + "enum": [ + "SOURCE_UNSPECIFIED", + "COINBASE_EXCHANGE" + ], + "default": "SOURCE_UNSPECIFIED", + "description": "The source of the USD price conversion.\n\n - SOURCE_UNSPECIFIED: The USD value source is unknown or unspecified.\n - COINBASE_EXCHANGE: The USD value source is the Coinbase exchange." + }, + "protobufAny": { + "type": "object", + "properties": { + "@type": { + "type": "string" + } + }, + "additionalProperties": {} + }, + "rpcStatus": { + "type": "object", + "properties": { + "code": { + "type": "integer", + "format": "int32" + }, + "message": { + "type": "string" + }, + "details": { + "type": "array", + "items": { + "type": "object", + "$ref": "#/definitions/protobufAny" + } + } + } + }, + "v1AggregationUnit": { + "type": "string", + "enum": [ + "AGGREGATION_UNIT_UNSPECIFIED", + "EPOCH", + "DAY" + ], + "default": "AGGREGATION_UNIT_UNSPECIFIED", + "description": "The unit of time that the reward events were aggregated by.\n\n - AGGREGATION_UNIT_UNSPECIFIED: Aggregation unit is unknown or unspecified.\n - EPOCH: Indicates the rewards are aggregated by epoch. This means there will be a 'epoch' field displaying the epoch on this resource.\n - DAY: Indicates the rewards are aggregated by day. This means there will be a 'date' field displaying the date on this resource." + }, + "v1AssetAmount": { + "type": "object", + "properties": { + "amount": { + "type": "string", + "title": "The amount of the asset in the most common denomination.\nEx: ETH (converted from gwei)\nEx: USD (converted from fractional pennies)", + "readOnly": true + }, + "exp": { + "type": "string", + "description": "The number of decimals needed to convert from the raw numeric value to the most\ncommon denomination.", + "readOnly": true + }, + "ticker": { + "type": "string", + "description": "The ticker of this asset (ex: USD, ETH, SOL).", + "readOnly": true + }, + "rawNumeric": { + "type": "string", + "description": "The raw, unadulterated numeric value.\nEx: Wei (in Ethereum) and Lamports (in Solana).", + "readOnly": true + } + }, + "description": "Amount encapsulation for a given asset." + }, + "v1ListRewardsResponse": { + "type": "object", + "properties": { + "rewards": { + "type": "array", + "items": { + "type": "object", + "$ref": "#/definitions/v1Reward" + }, + "description": "The rewards returned in this response.", + "readOnly": true + }, + "nextPageToken": { + "type": "string", + "description": "The page token the user must use in the next request if the next page is desired.", + "readOnly": true + } + }, + "description": "The response message for ListRewards." + }, + "v1ListStakesResponse": { + "type": "object", + "properties": { + "stakes": { + "type": "array", + "items": { + "type": "object", + "$ref": "#/definitions/v1Stake" + }, + "description": "The staking balances returned in this response.", + "readOnly": true + }, + "nextPageToken": { + "type": "string", + "description": "The page token the user must use in the next request if the next page is desired.", + "readOnly": true + } + }, + "description": "The response message for ListStakes." + }, + "v1ParticipantType": { + "type": "string", + "enum": [ + "PARTICIPANT_TYPE_UNSPECIFIED", + "DELEGATOR", + "VALIDATOR" + ], + "default": "PARTICIPANT_TYPE_UNSPECIFIED", + "description": "The participant type of a staking-related address.\n\n - PARTICIPANT_TYPE_UNSPECIFIED: The participant type is unknown.\n - DELEGATOR: Used when the onchain participant type is a delegator\n(i.e. someone who delegates the responsibilities of validating blocks to another address in return for a share of the rewards).\n - VALIDATOR: Used when the onchain participant type is a validator\n(i.e. an address that is directly responsible for performing validation of blocks)." + }, + "v1Reward": { + "type": "object", + "properties": { + "address": { + "type": "string", + "description": "The address that earned this reward.", + "readOnly": true + }, + "epoch": { + "type": "string", + "format": "int64", + "description": "A unique identifier for the consensus-cycle of the blockchain.", + "readOnly": true + }, + "date": { + "type": "string", + "description": "The date of the reward in format 'YYYY-MM-DD' in UTC.", + "readOnly": true + }, + "aggregationUnit": { + "$ref": "#/definitions/v1AggregationUnit", + "description": "The unit of time that the reward events were rolled up by.\nCan be either \"epoch\" or \"daily\".", + "readOnly": true + }, + "periodStartTime": { + "type": "string", + "format": "date-time", + "description": "The starting time of this reward period. Returned when querying by epoch.\nTimestamps are in UTC, conforming to the RFC-3339 spec (e.g. 2023-11-13T19:38:36Z). UTC offsets are not currently supported.\nField currently unavailable. Coming soon.", + "readOnly": true + }, + "periodEndTime": { + "type": "string", + "format": "date-time", + "description": "The ending time of this reward period. Returned when querying by epoch.\nTimestamps are in UTC, conforming to the RFC-3339 spec (e.g. 2023-11-13T19:38:36Z). UTC offsets are not currently supported.", + "readOnly": true + }, + "totalEarnedNativeUnit": { + "$ref": "#/definitions/v1AssetAmount", + "description": "The amount earned in this time period in the native unit of the protocol.", + "readOnly": true + }, + "totalEarnedUsd": { + "type": "array", + "items": { + "type": "object", + "$ref": "#/definitions/v1USDValue" + }, + "description": "The amount earned in this time period in USD. Calculated by getting each individual reward of this\ntime period and summing the USD value of each individual component. USD value is calculate at\nthe time each component was earned.", + "readOnly": true + }, + "endingBalance": { + "$ref": "#/definitions/v1Stake", + "description": "A snapshot of the staking balance the end of this period.\nField currently unavailable. Coming soon.", + "readOnly": true + }, + "protocol": { + "type": "string", + "description": "The protocol on which this reward was earned.", + "readOnly": true + } + }, + "description": "Rewards earned within a particular period of time." + }, + "v1RewardRate": { + "type": "object", + "properties": { + "percentage": { + "type": "string", + "description": "The percentage rate of rewards calculation. Will include two digits after the decimal (ex: 3.05).", + "readOnly": true + }, + "calculatedTime": { + "type": "string", + "format": "date-time", + "description": "The time at which this yield calculation was calculated.\nTimestamps are in UTC, conforming to the RFC-3339 spec (e.g. 2023-11-13T19:38:36Z). UTC offsets are not currently supported.", + "readOnly": true + }, + "calculationMethod": { + "$ref": "#/definitions/RewardRateCalculationMethods", + "description": "The method used to calculate this yield. This could include information about which\nrewards we're including in the calculation, how we're estimating the compounding period, etc.", + "readOnly": true + } + }, + "description": "Reward yield calculation at a given point in time." + }, + "v1Stake": { + "type": "object", + "properties": { + "address": { + "type": "string", + "description": "The address of the staking balance.", + "readOnly": true + }, + "evaluationTime": { + "type": "string", + "format": "date-time", + "description": "The time at which this balance was evaluated.\nTimestamps are in UTC, conforming to the RFC-3339 spec (e.g. 2023-11-13T19:38:36Z). UTC offsets are not currently supported.", + "readOnly": true + }, + "bondedStake": { + "$ref": "#/definitions/v1AssetAmount", + "description": "The total amount of stake that is actively earning rewards to this address.\nIncludes any delegated stake and self-stake.\nFor delegators, this would be only the amount delegated to a validator in most cases.\nOnly includes stake that is *actively contributing to rewards and can't be reduced\nwithout affecting the rewards dynamics*.\n\nPending inactive stake is included.\nPending active stake is not included.", + "readOnly": true + }, + "totalDelegationReceived": { + "$ref": "#/definitions/v1AssetAmount", + "description": "The amount of stake that this address receives from other addresses.\nFor most delegators, this will be 0.", + "readOnly": true + }, + "delegationsReceived": { + "$ref": "#/definitions/StakeDelegation", + "title": "The list of individual delegations this address has received from other addresses", + "readOnly": true + }, + "delegationsGiven": { + "$ref": "#/definitions/StakeDelegation", + "description": "The amount that this address stakes to another address.", + "readOnly": true + }, + "rewardRateCalculations": { + "type": "array", + "items": { + "type": "object", + "$ref": "#/definitions/v1RewardRate" + }, + "description": "An estimated yield of this address.", + "readOnly": true + }, + "participantType": { + "$ref": "#/definitions/v1ParticipantType", + "description": "The participant type at the time of evaluation (i.e. validator, delegator).", + "readOnly": true + }, + "protocol": { + "type": "string", + "description": "The protocol on which this staking balance exists.", + "readOnly": true + }, + "unbondedBalance": { + "$ref": "#/definitions/v1AssetAmount", + "description": "The amount of stake that is not actively earning rewards to this address.\nThis amount includes any native token balance that is under the domain and control of the address in question,\nbut is not actively staked.\n\nPending active stake would be included here.", + "readOnly": true + } + }, + "description": "The representation of a staking activity at a particular point in time." + }, + "v1USDValue": { + "type": "object", + "properties": { + "source": { + "$ref": "#/definitions/USDValueSource", + "description": "The source of the USD price conversion. Could be internal to Coinbase, and external source, or any other source.", + "readOnly": true + }, + "conversionTime": { + "type": "string", + "format": "date-time", + "description": "The timestamp at which the USD value was sourced to convert the value into USD.\nThis value is as close to the time the reward was earned as possible.\nTimestamps are in UTC, conforming to the RFC-3339 spec (e.g. 2023-11-13T19:38:36Z). UTC offsets are not currently supported.", + "readOnly": true + }, + "amount": { + "$ref": "#/definitions/v1AssetAmount", + "description": "The USD value of the reward at the conversion time..", + "readOnly": true + } + }, + "description": "Information regarding the USD value of a reward, with necessary context and metadata." + } + } +} diff --git a/examples/cosmos/list-rewards.ts b/examples/cosmos/list-rewards.ts new file mode 100644 index 0000000..2be233c --- /dev/null +++ b/examples/cosmos/list-rewards.ts @@ -0,0 +1,14 @@ +import { StakingClient } from '../../src/client/staking-client'; + +// Defines which address and rewards we want to see +const address: string = 'cosmosvaloper1c4k24jzduc365kywrsvf5ujz4ya6mwympnc4en'; +const filter: string = `address='${address}' AND period_end_time > '2024-03-25T00:00:00Z' AND period_end_time < '2024-03-27T00:00:00Z'`; + +const client = new StakingClient(); + +// Loops through rewards array and prints each reward +client.Cosmos.listRewards(filter).then((resp) => { + resp.rewards!.forEach((reward) => { + console.log(JSON.stringify(reward, null, 2)); + }); +}); diff --git a/examples/cosmos/list-stakes.ts b/examples/cosmos/list-stakes.ts new file mode 100644 index 0000000..e51903b --- /dev/null +++ b/examples/cosmos/list-stakes.ts @@ -0,0 +1,37 @@ +import { StakingClient } from '../../src/client/staking-client'; + +// TODO: Replace address as per your requirement. +const address: string = 'cosmosvaloper1c4k24jzduc365kywrsvf5ujz4ya6mwympnc4en'; + +const client = new StakingClient(); + +async function listStakes(): Promise { + if (address === '') { + throw new Error('Please set the address variable in this file'); + } + + const filter: string = `address='${address}'`; + + try { + // List cosmos staking balances + let resp = await client.Cosmos.listStakes(filter); + + let count = 0; + + // Loop through staked balance array and print each balance + resp.stakes!.forEach((stake) => { + count++; + const marshaledStake = JSON.stringify(stake); + + console.log(`[${count}] Stake details: ${marshaledStake}`); + }); + } catch (error) { + if (error instanceof Error) { + throw new Error(`Error listing staking balances: ${error.message}`); + } + } +} + +listStakes().catch((error) => { + console.error('Error listing cosmos staking balances: ', error.message); +}); diff --git a/examples/ethereum/create-and-process-workflow.ts b/examples/ethereum/create-and-process-workflow.ts new file mode 100644 index 0000000..d5a671f --- /dev/null +++ b/examples/ethereum/create-and-process-workflow.ts @@ -0,0 +1,173 @@ +import { TxSignerFactory } from '../../src/signers'; +import { + StakingClient, + workflowHasFinished, + workflowWaitingForSigning, + workflowWaitingForExternalBroadcast, + isTxStepOutput, + isWaitStepOutput, +} from '../../src/client/staking-client'; +import { Workflow } from '../../src/gen/coinbase/staking/orchestration/v1/workflow.pb'; +import { calculateTimeDifference } from '../../src/utils/date'; + +const projectId: string = ''; // replace with your project id +const privateKey: string = ''; // replace with your private key +const stakerAddress: string = ''; // replace with your staker address +const integrationAddress: string = '0xA55416de5DE61A0AC1aa8970a280E04388B1dE4b'; // replace with your integration address +const amount: string = '123'; // replace with your amount +const network: string = 'holesky'; // replace with your network + +const client = new StakingClient(); + +const signer = TxSignerFactory.getSigner('ethereum'); + +async function stakePartialEth(): Promise { + if (projectId === '' || privateKey === '' || stakerAddress === '') { + throw new Error( + 'Please set the projectId, privateKey and stakerAddress variables in this file', + ); + } + + let unsignedTx = ''; + let workflow: Workflow = {} as Workflow; + let currentStepId: number | undefined; + let workflowId: string; + + try { + // Create a new eth kiln stake workflow + workflow = await client.Ethereum.stake( + projectId, + network, + false, + stakerAddress, + integrationAddress, + amount, + ); + + workflowId = workflow.name?.split('/').pop() || ''; + if (workflowId == null || workflowId === '') { + throw new Error('Unexpected workflow state. workflowId is null'); + } + + currentStepId = workflow.currentStepId; + if (currentStepId == null) { + throw new Error('Unexpected workflow state. currentStepId is null'); + } + + console.log('Workflow created %s ...', workflow.name); + } catch (error) { + if (error instanceof Error) { + throw new Error(`Error creating workflow: ${error.message}`); + } + throw new Error(`Error creating workflow`); + } + + // Loop until the workflow has reached an end state. + // eslint-disable-next-line no-constant-condition + while (true) { + // Every second, get the latest workflow state. + // If the workflow is waiting for signing, sign the unsigned tx and return back the signed tx. + // If the workflow is waiting for external broadcast, sign and broadcast the unsigned tx externally and return back the tx hash via the PerformWorkflowStep API. + // Note: In this example, we just log this message as the wallet provider needs to implement this logic. + try { + workflow = await client.getWorkflow(projectId, workflowId); + } catch (error) { + // TODO: add retry logic for network errors + if (error instanceof Error) { + throw new Error(`Error creating workflow: ${error.message}`); + } + } + + await printWorkflowProgressDetails(workflow); + + if (workflowWaitingForSigning(workflow)) { + unsignedTx = + workflow.steps![currentStepId].txStepOutput?.unsignedTx || ''; + if (unsignedTx === '') { + console.log('Waiting for unsigned tx to be available ...'); + await new Promise((resolve) => setTimeout(resolve, 1000)); // sleep for 1 second + continue; + } + + console.log('Signing unsigned tx %s ...', unsignedTx); + const signedTx = await signer.signTransaction(privateKey, unsignedTx); + + console.log('Returning back signed tx %s ...', signedTx); + + workflow = await client.performWorkflowStep( + projectId, + workflowId, + currentStepId, + signedTx, + ); + } else if (workflowWaitingForExternalBroadcast(workflow)) { + console.log( + 'Please sign and broadcast this unsigned tx %s externally and return back the tx hash via the PerformWorkflowStep API ...', + unsignedTx, + ); + break; + } else if (workflowHasFinished(workflow)) { + console.log('Workflow completed with state %s ...', workflow.state); + break; + } + + await new Promise((resolve) => setTimeout(resolve, 1000)); // sleep for 1 second + } +} + +async function printWorkflowProgressDetails(workflow: Workflow): Promise { + if (workflow.steps == null || workflow.steps.length === 0) { + console.log('Waiting for steps to be created ...'); + await new Promise((resolve) => setTimeout(resolve, 1000)); // sleep for 1 second + return; + } + + const currentStepId = workflow.currentStepId; + + if (currentStepId == null) { + return; + } + + const step = workflow.steps[currentStepId]; + + let stepDetails = ''; + + if (isTxStepOutput(step)) { + stepDetails = `state: ${step.txStepOutput?.state} tx hash: ${step.txStepOutput?.txHash}`; + } else if (isWaitStepOutput(step)) { + stepDetails = `state: ${step.waitStepOutput?.state}} current: ${step.waitStepOutput?.current}} target: ${step.waitStepOutput?.target}`; + } else { + throw new Error('Encountered unexpected workflow step type'); + } + + const runtime = calculateTimeDifference( + workflow.createTime, + workflow.updateTime, + ); + + if (workflowHasFinished(workflow)) { + console.log( + 'Workflow reached end state - step name: %s %s workflow state: %s runtime: %d seconds', + step.name, + stepDetails, + workflow.state, + runtime, + ); + } else { + console.log( + 'Waiting for workflow to finish - step name: %s %s workflow state: %s runtime: %d seconds', + step.name, + stepDetails, + workflow.state, + runtime, + ); + } +} + +stakePartialEth() + .then(() => { + console.log('Done staking eth'); + }) + .catch((error) => { + console.error('Error staking eth: ', error.message); + }); diff --git a/examples/ethereum/create-workflow.ts b/examples/ethereum/create-workflow.ts new file mode 100644 index 0000000..d1595d0 --- /dev/null +++ b/examples/ethereum/create-workflow.ts @@ -0,0 +1,51 @@ +import { StakingClient } from '../../src/client/staking-client'; +import { Workflow } from '../../src/gen/coinbase/staking/orchestration/v1/workflow.pb'; + +const projectId: string = ''; // replace with your project id +const stakerAddress: string = ''; // replace with your staker address +const integrationAddress: string = '0xA55416de5DE61A0AC1aa8970a280E04388B1dE4b'; // replace with your integration address +const amount: string = '123'; // replace with your amount +const network: string = 'holesky'; // replace with your network + +const client = new StakingClient(); + +async function stakePartialEth(): Promise { + if (projectId === '' || stakerAddress === '') { + throw new Error( + 'Please set the projectId and stakerAddress variables in this file', + ); + } + + let workflow: Workflow = {} as Workflow; + + try { + // Create a new eth kiln stake workflow + workflow = await client.Ethereum.stake( + projectId, + network, + true, + stakerAddress, + integrationAddress, + amount, + ); + + console.log('Workflow created %s ...', workflow.name); + } catch (error) { + let errorMessage = ''; + + if (error instanceof Error) { + errorMessage = error.message; + } + throw new Error(`Error creating workflow: ${errorMessage}`); + } +} + +stakePartialEth() + .then(() => { + console.log('Done creating eth staking workflow'); + }) + .catch((error) => { + if (error instanceof Error) { + console.error('Error creating eth staking workflow: ', error.message); + } + }); diff --git a/examples/ethereum/list-rewards.ts b/examples/ethereum/list-rewards.ts new file mode 100644 index 0000000..c45ec0b --- /dev/null +++ b/examples/ethereum/list-rewards.ts @@ -0,0 +1,15 @@ +import { StakingClient } from '../../src/client/staking-client'; + +// Defines which address and rewards we want to see +const address: string = + '0xac53512c39d0081ca4437c285305eb423f474e6153693c12fbba4a3df78bcaa3422b31d800c5bea71c1b017168a60474'; +const filter: string = `address='${address}' AND period_end_time > '2024-02-25T00:00:00Z' AND period_end_time < '2024-02-27T00:00:00Z'`; + +const client = new StakingClient(); + +// Loops through rewards array and prints each reward +client.Ethereum.listRewards(filter).then((resp) => { + resp.rewards!.forEach((reward) => { + console.log(JSON.stringify(reward, null, 2)); + }); +}); diff --git a/examples/ethereum/list-stakes.ts b/examples/ethereum/list-stakes.ts new file mode 100644 index 0000000..1c9ee0d --- /dev/null +++ b/examples/ethereum/list-stakes.ts @@ -0,0 +1,38 @@ +import { StakingClient } from '../../src/client/staking-client'; + +// TODO: Replace address as per your requirement. +const address: string = + '0xac53512c39d0081ca4437c285305eb423f474e6153693c12fbba4a3df78bcaa3422b31d800c5bea71c1b017168a60474'; + +const client = new StakingClient(); + +async function listStakes(): Promise { + if (address === '') { + throw new Error('Please set the address variable in this file'); + } + + const filter: string = `address='${address}'`; + + try { + // List ethereum staking balances + let resp = await client.Ethereum.listStakes(filter); + + let count = 0; + + // Loop through staked balance array and print each balance + resp.stakes!.forEach((stake) => { + count++; + const marshaledStake = JSON.stringify(stake); + + console.log(`[${count}] Stake details: ${marshaledStake}`); + }); + } catch (error) { + if (error instanceof Error) { + throw new Error(`Error listing staking balances: ${error.message}`); + } + } +} + +listStakes().catch((error) => { + console.error('Error listing ethereum staking balances: ', error.message); +}); diff --git a/examples/example.ts b/examples/example.ts new file mode 100644 index 0000000..3a161db --- /dev/null +++ b/examples/example.ts @@ -0,0 +1,15 @@ +import { StakingClient } from '../src/client/staking-client'; + +const client = new StakingClient(); + +client.listProtocols().then((response) => { + console.log(response); +}); + +client.listNetworks('ethereum_kiln').then((response) => { + console.log(response); +}); + +client.listActions('ethereum_kiln', 'holesky').then((response) => { + console.log(response); +}); diff --git a/examples/solana/create-and-process-workflow.ts b/examples/solana/create-and-process-workflow.ts new file mode 100644 index 0000000..31632a1 --- /dev/null +++ b/examples/solana/create-and-process-workflow.ts @@ -0,0 +1,188 @@ +import { TxSignerFactory } from '../../src/signers'; +import { + StakingClient, + workflowHasFinished, + workflowWaitingForSigning, + workflowWaitingForExternalBroadcast, + isTxStepOutput, + isWaitStepOutput, +} from '../../src/client/staking-client'; +import { Workflow } from '../../src/gen/coinbase/staking/orchestration/v1/workflow.pb'; +import { calculateTimeDifference } from '../../src/utils/date'; + +const projectId: string = ''; // replace with your project id +const privateKey: string = ''; // replace with your private key +const walletAddress: string = ''; // replace with your wallet address +const validatorAddress: string = 'beefKGBWeSpHzYBHZXwp5So7wdQGX6mu4ZHCsH3uTar'; // replace with your validator address +const amount: string = '100000000'; // replace with your amount. For solana it should be >= 0.1 SOL +const network: string = 'mainnet'; // replace with your network + +const client = new StakingClient(); + +const signer = TxSignerFactory.getSigner('solana'); + +async function stakeSolana(): Promise { + if (projectId === '' || walletAddress === '') { + throw new Error( + 'Please set the projectId and stakerAddress variables in this file', + ); + } + + let unsignedTx = ''; + let workflow: Workflow = {} as Workflow; + let currentStepId: number | undefined; + let workflowId: string; + + try { + // Create a new solana stake workflow + workflow = await client.Solana.stake( + projectId, + network, + true, + walletAddress, + validatorAddress, + amount, + ); + + workflowId = workflow.name?.split('/').pop() || ''; + if (workflowId == null || workflowId === '') { + throw new Error('Unexpected workflow state. workflowId is null'); + } + + currentStepId = workflow.currentStepId; + if (currentStepId == null) { + throw new Error('Unexpected workflow state. currentStepId is null'); + } + + console.log('Workflow created %s ...', workflow.name); + } catch (error) { + if (error instanceof Error) { + throw new Error(`Error creating workflow: ${error.message}`); + } + + const msg = JSON.stringify(error); + + throw new Error(`Error creating workflow ${msg}`); + } + + // Loop until the workflow has reached an end state. + // eslint-disable-next-line no-constant-condition + while (true) { + // Every second, get the latest workflow state. + // If the workflow is waiting for signing, sign the unsigned tx and return back the signed tx. + // If the workflow is waiting for external broadcast, sign and broadcast the unsigned tx externally and return back the tx hash via the PerformWorkflowStep API. + // Note: In this example, we just log this message as the wallet provider needs to implement this logic. + try { + workflow = await client.getWorkflow(projectId, workflowId); + } catch (error) { + // TODO: add retry logic for network errors + if (error instanceof Error) { + throw new Error(`Error getting workflow: ${error.message}`); + } + } + + await printWorkflowProgressDetails(workflow); + + if (workflowWaitingForSigning(workflow)) { + unsignedTx = + workflow.steps![currentStepId].txStepOutput?.unsignedTx || ''; + if (unsignedTx === '') { + console.log('Waiting for unsigned tx to be available ...'); + await new Promise((resolve) => setTimeout(resolve, 1000)); // sleep for 1 second + continue; + } + + console.log('Signing unsigned tx %s ...', unsignedTx); + const signedTx = await signer.signTransaction(privateKey, unsignedTx); + + console.log('Returning back signed tx %s ...', signedTx); + + workflow = await client.performWorkflowStep( + projectId, + workflowId, + currentStepId, + signedTx, + ); + } else if (workflowWaitingForExternalBroadcast(workflow)) { + unsignedTx = + workflow.steps![currentStepId].txStepOutput?.unsignedTx || ''; + if (unsignedTx === '') { + console.log('Waiting for unsigned tx to be available ...'); + await new Promise((resolve) => setTimeout(resolve, 1000)); // sleep for 1 second + continue; + } + + console.log('Signing unsigned tx %s ...', unsignedTx); + + const signedTx = await signer.signTransaction(privateKey, unsignedTx); + + console.log( + 'Please broadcast this signed tx %s externally and return back the tx hash via the PerformWorkflowStep API ...', + signedTx, + ); + break; + } else if (workflowHasFinished(workflow)) { + console.log('Workflow completed with state %s ...', workflow.state); + break; + } + + await new Promise((resolve) => setTimeout(resolve, 1000)); // sleep for 1 second + } +} + +async function printWorkflowProgressDetails(workflow: Workflow): Promise { + if (workflow.steps == null || workflow.steps.length === 0) { + console.log('Waiting for steps to be created ...'); + await new Promise((resolve) => setTimeout(resolve, 1000)); // sleep for 1 second + return; + } + + const currentStepId = workflow.currentStepId; + + if (currentStepId == null) { + return; + } + + const step = workflow.steps[currentStepId]; + + let stepDetails = ''; + + if (isTxStepOutput(step)) { + stepDetails = `state: ${step.txStepOutput?.state} tx hash: ${step.txStepOutput?.txHash}`; + } else if (isWaitStepOutput(step)) { + stepDetails = `state: ${step.waitStepOutput?.state}} current: ${step.waitStepOutput?.current}} target: ${step.waitStepOutput?.target}`; + } else { + throw new Error('Encountered unexpected workflow step type'); + } + + const runtime = calculateTimeDifference( + workflow.createTime, + workflow.updateTime, + ); + + if (workflowHasFinished(workflow)) { + console.log( + 'Workflow reached end state - step name: %s %s workflow state: %s runtime: %d seconds', + step.name, + stepDetails, + workflow.state, + runtime, + ); + } else { + console.log( + 'Waiting for workflow to finish - step name: %s %s workflow state: %s runtime: %d seconds', + step.name, + stepDetails, + workflow.state, + runtime, + ); + } +} + +stakeSolana() + .then(() => { + console.log('Done staking sol'); + }) + .catch((error) => { + console.error('Error staking sol: ', error.message); + }); diff --git a/examples/solana/create-workflow.ts b/examples/solana/create-workflow.ts new file mode 100644 index 0000000..d38d807 --- /dev/null +++ b/examples/solana/create-workflow.ts @@ -0,0 +1,51 @@ +import { StakingClient } from '../../src/client/staking-client'; +import { Workflow } from '../../src/gen/coinbase/staking/orchestration/v1/workflow.pb'; + +const projectId: string = ''; // replace with your project id +const walletAddress: string = ''; // replace with your wallet address +const validatorAddress: string = 'beefKGBWeSpHzYBHZXwp5So7wdQGX6mu4ZHCsH3uTar'; // replace with your validator address +const amount: string = '100000000'; // replace with your amount. For solana it should be >= 0.1 SOL +const network: string = 'mainnet'; // replace with your network + +const client = new StakingClient(); + +async function stakeSolana(): Promise { + if (projectId === '' || walletAddress === '') { + throw new Error( + 'Please set the projectId and stakerAddress variables in this file', + ); + } + + let workflow: Workflow = {} as Workflow; + + try { + // Create a new solana stake workflow + workflow = await client.Solana.stake( + projectId, + network, + true, + walletAddress, + validatorAddress, + amount, + ); + + console.log('Workflow created %s ...', workflow.name); + } catch (error) { + let errorMessage = ''; + + if (error instanceof Error) { + errorMessage = error.message; + } + throw new Error(`Error creating workflow: ${errorMessage}`); + } +} + +stakeSolana() + .then(() => { + console.log('Done creating sol staking workflow'); + }) + .catch((error) => { + if (error instanceof Error) { + console.error('Error creating sol staking workflow: ', error.message); + } + }); diff --git a/examples/solana/list-rewards.ts b/examples/solana/list-rewards.ts new file mode 100644 index 0000000..6a66aa0 --- /dev/null +++ b/examples/solana/list-rewards.ts @@ -0,0 +1,37 @@ +import { StakingClient } from '../../src/client/staking-client'; + +// TODO: Replace address as per your requirement. +const address: string = 'beefKGBWeSpHzYBHZXwp5So7wdQGX6mu4ZHCsH3uTar'; + +const client = new StakingClient(); + +async function listRewards(): Promise { + if (address === '') { + throw new Error('Please set the address variable in this file'); + } + + const filter: string = `address='${address}'`; + + try { + // List solana rewards + let resp = await client.Solana.listRewards(filter); + + let count = 0; + + // Loop through rewards array and print each reward + resp.rewards!.forEach((reward) => { + count++; + const marshaledReward = JSON.stringify(reward); + + console.log(`[${count}] Reward details: ${marshaledReward}`); + }); + } catch (error) { + if (error instanceof Error) { + throw new Error(`Error listing solana rewards: ${error.message}`); + } + } +} + +listRewards().catch((error) => { + console.error('Error listing solana rewards: ', error.message); +}); diff --git a/package-lock.json b/package-lock.json new file mode 100644 index 0000000..079e0be --- /dev/null +++ b/package-lock.json @@ -0,0 +1,3607 @@ +{ + "name": "@coinbase/staking-client-library-ts", + "version": "0.5.1", + "lockfileVersion": 3, + "requires": true, + "packages": { + "": { + "name": "@coinbase/staking-client-library-ts", + "version": "0.5.1", + "license": "Apache-2.0", + "dependencies": { + "@ethereumjs/tx": "^5.1.0", + "@solana/web3.js": "^1.91.3", + "bs58": "^5.0.0", + "node-jose": "^2.2.0" + }, + "devDependencies": { + "@types/node": "^20.11.0", + "@types/node-jose": "^1.1.10", + "@typescript-eslint/eslint-plugin": "^6.7.0", + "@typescript-eslint/parser": "^6.7.0", + "eslint": "^8.49.0", + "eslint-config-prettier": "^9.0.0", + "eslint-plugin-filename-rules": "^1.3.1", + "eslint-plugin-node": "^11.1.0", + "eslint-plugin-prettier": "^5.1.3", + "eslint-plugin-unicorn": "^51.0.1", + "prettier": "^3.0.3", + "prettier-eslint": "^16.0.0", + "rimraf": "^3.0.2", + "typescript": "^5.2.2" + } + }, + "node_modules/@aashutoshrathi/word-wrap": { + "version": "1.2.6", + "resolved": "https://registry.npmjs.org/@aashutoshrathi/word-wrap/-/word-wrap-1.2.6.tgz", + "integrity": "sha512-1Yjs2SvM8TflER/OD3cOjhWWOZb58A2t7wpE2S9XfBYTiIl+XFhQG2bjy4Pu1I+EAlCNUzRDYDdFwFYUKvXcIA==", + "dev": true, + "engines": { + "node": ">=0.10.0" + } + }, + "node_modules/@babel/code-frame": { + "version": "7.24.2", + "resolved": "https://registry.npmjs.org/@babel/code-frame/-/code-frame-7.24.2.tgz", + "integrity": "sha512-y5+tLQyV8pg3fsiln67BVLD1P13Eg4lh5RW9mF0zUuvLrv9uIQ4MCL+CRT+FTsBlBjcIan6PGsLcBN0m3ClUyQ==", + "dev": true, + "dependencies": { + "@babel/highlight": "^7.24.2", + "picocolors": "^1.0.0" + }, + "engines": { + "node": ">=6.9.0" + } + }, + "node_modules/@babel/helper-validator-identifier": { + "version": "7.22.20", + "resolved": "https://registry.npmjs.org/@babel/helper-validator-identifier/-/helper-validator-identifier-7.22.20.tgz", + "integrity": "sha512-Y4OZ+ytlatR8AI+8KZfKuL5urKp7qey08ha31L8b3BwewJAoJamTzyvxPR/5D+KkdJCGPq/+8TukHBlY10FX9A==", + "dev": true, + "engines": { + "node": ">=6.9.0" + } + }, + "node_modules/@babel/highlight": { + "version": "7.24.2", + "resolved": "https://registry.npmjs.org/@babel/highlight/-/highlight-7.24.2.tgz", + "integrity": "sha512-Yac1ao4flkTxTteCDZLEvdxg2fZfz1v8M4QpaGypq/WPDqg3ijHYbDfs+LG5hvzSoqaSZ9/Z9lKSP3CjZjv+pA==", + "dev": true, + "dependencies": { + "@babel/helper-validator-identifier": "^7.22.20", + "chalk": "^2.4.2", + "js-tokens": "^4.0.0", + "picocolors": "^1.0.0" + }, + "engines": { + "node": ">=6.9.0" + } + }, + "node_modules/@babel/highlight/node_modules/ansi-styles": { + "version": "3.2.1", + "resolved": "https://registry.npmjs.org/ansi-styles/-/ansi-styles-3.2.1.tgz", + "integrity": "sha512-VT0ZI6kZRdTh8YyJw3SMbYm/u+NqfsAxEpWO0Pf9sq8/e94WxxOpPKx9FR1FlyCtOVDNOQ+8ntlqFxiRc+r5qA==", + "dev": true, + "dependencies": { + "color-convert": "^1.9.0" + }, + "engines": { + "node": ">=4" + } + }, + "node_modules/@babel/highlight/node_modules/chalk": { + "version": "2.4.2", + "resolved": "https://registry.npmjs.org/chalk/-/chalk-2.4.2.tgz", + "integrity": "sha512-Mti+f9lpJNcwF4tWV8/OrTTtF1gZi+f8FqlyAdouralcFWFQWF2+NgCHShjkCb+IFBLq9buZwE1xckQU4peSuQ==", + "dev": true, + "dependencies": { + "ansi-styles": "^3.2.1", + "escape-string-regexp": "^1.0.5", + "supports-color": "^5.3.0" + }, + "engines": { + "node": ">=4" + } + }, + "node_modules/@babel/highlight/node_modules/color-convert": { + "version": "1.9.3", + "resolved": "https://registry.npmjs.org/color-convert/-/color-convert-1.9.3.tgz", + "integrity": "sha512-QfAUtd+vFdAtFQcC8CCyYt1fYWxSqAiK2cSD6zDB8N3cpsEBAvRxp9zOGg6G/SHHJYAT88/az/IuDGALsNVbGg==", + "dev": true, + "dependencies": { + "color-name": "1.1.3" + } + }, + "node_modules/@babel/highlight/node_modules/color-name": { + "version": "1.1.3", + "resolved": "https://registry.npmjs.org/color-name/-/color-name-1.1.3.tgz", + "integrity": "sha512-72fSenhMw2HZMTVHeCA9KCmpEIbzWiQsjN+BHcBbS9vr1mtt+vJjPdksIBNUmKAW8TFUDPJK5SUU3QhE9NEXDw==", + "dev": true + }, + "node_modules/@babel/highlight/node_modules/escape-string-regexp": { + "version": "1.0.5", + "resolved": "https://registry.npmjs.org/escape-string-regexp/-/escape-string-regexp-1.0.5.tgz", + "integrity": "sha512-vbRorB5FUQWvla16U8R/qgaFIya2qGzwDrNmCZuYKrbdSUMG6I1ZCGQRefkRVhuOkIGVne7BQ35DSfo1qvJqFg==", + "dev": true, + "engines": { + "node": ">=0.8.0" + } + }, + "node_modules/@babel/highlight/node_modules/has-flag": { + "version": "3.0.0", + "resolved": "https://registry.npmjs.org/has-flag/-/has-flag-3.0.0.tgz", + "integrity": "sha512-sKJf1+ceQBr4SMkvQnBDNDtf4TXpVhVGateu0t918bl30FnbE2m4vNLX+VWe/dpjlb+HugGYzW7uQXH98HPEYw==", + "dev": true, + "engines": { + "node": ">=4" + } + }, + "node_modules/@babel/highlight/node_modules/supports-color": { + "version": "5.5.0", + "resolved": "https://registry.npmjs.org/supports-color/-/supports-color-5.5.0.tgz", + "integrity": "sha512-QjVjwdXIt408MIiAqCX4oUKsgU2EqAGzs2Ppkm4aQYbjm+ZEWEcW4SfFNTr4uMNZma0ey4f5lgLrkB0aX0QMow==", + "dev": true, + "dependencies": { + "has-flag": "^3.0.0" + }, + "engines": { + "node": ">=4" + } + }, + "node_modules/@babel/runtime": { + "version": "7.24.1", + "resolved": "https://registry.npmjs.org/@babel/runtime/-/runtime-7.24.1.tgz", + "integrity": "sha512-+BIznRzyqBf+2wCTxcKE3wDjfGeCoVE61KSHGpkzqrLi8qxqFwBeUFyId2cxkTmm55fzDGnm0+yCxaxygrLUnQ==", + "dependencies": { + "regenerator-runtime": "^0.14.0" + }, + "engines": { + "node": ">=6.9.0" + } + }, + "node_modules/@eslint-community/eslint-utils": { + "version": "4.4.0", + "resolved": "https://registry.npmjs.org/@eslint-community/eslint-utils/-/eslint-utils-4.4.0.tgz", + "integrity": "sha512-1/sA4dwrzBAyeUoQ6oxahHKmrZvsnLCg4RfxW3ZFGGmQkSNQPFNLV9CUEFQP1x9EYXHTo5p6xdhZM1Ne9p/AfA==", + "dev": true, + "dependencies": { + "eslint-visitor-keys": "^3.3.0" + }, + "engines": { + "node": "^12.22.0 || ^14.17.0 || >=16.0.0" + }, + "peerDependencies": { + "eslint": "^6.0.0 || ^7.0.0 || >=8.0.0" + } + }, + "node_modules/@eslint-community/regexpp": { + "version": "4.8.0", + "resolved": "https://registry.npmjs.org/@eslint-community/regexpp/-/regexpp-4.8.0.tgz", + "integrity": "sha512-JylOEEzDiOryeUnFbQz+oViCXS0KsvR1mvHkoMiu5+UiBvy+RYX7tzlIIIEstF/gVa2tj9AQXk3dgnxv6KxhFg==", + "dev": true, + "engines": { + "node": "^12.0.0 || ^14.0.0 || >=16.0.0" + } + }, + "node_modules/@eslint/eslintrc": { + "version": "2.1.4", + "resolved": "https://registry.npmjs.org/@eslint/eslintrc/-/eslintrc-2.1.4.tgz", + "integrity": "sha512-269Z39MS6wVJtsoUl10L60WdkhJVdPG24Q4eZTH3nnF6lpvSShEK3wQjDX9JRWAUPvPh7COouPpU9IrqaZFvtQ==", + "dev": true, + "dependencies": { + "ajv": "^6.12.4", + "debug": "^4.3.2", + "espree": "^9.6.0", + "globals": "^13.19.0", + "ignore": "^5.2.0", + "import-fresh": "^3.2.1", + "js-yaml": "^4.1.0", + "minimatch": "^3.1.2", + "strip-json-comments": "^3.1.1" + }, + "engines": { + "node": "^12.22.0 || ^14.17.0 || >=16.0.0" + }, + "funding": { + "url": "https://opencollective.com/eslint" + } + }, + "node_modules/@eslint/js": { + "version": "8.57.0", + "resolved": "https://registry.npmjs.org/@eslint/js/-/js-8.57.0.tgz", + "integrity": "sha512-Ys+3g2TaW7gADOJzPt83SJtCDhMjndcDMFVQ/Tj9iA1BfJzFKD9mAUXT3OenpuPHbI6P/myECxRJrofUsDx/5g==", + "dev": true, + "engines": { + "node": "^12.22.0 || ^14.17.0 || >=16.0.0" + } + }, + "node_modules/@ethereumjs/common": { + "version": "4.1.0", + "resolved": "https://registry.npmjs.org/@ethereumjs/common/-/common-4.1.0.tgz", + "integrity": "sha512-XWdQvUjlQHVwh4uGEPFKHpsic69GOsMXEhlHrggS5ju/+2zAmmlz6B25TkCCymeElC9DUp13tH5Tc25Iuvtlcg==", + "dependencies": { + "@ethereumjs/util": "^9.0.1", + "crc": "^4.3.2" + } + }, + "node_modules/@ethereumjs/rlp": { + "version": "5.0.1", + "resolved": "https://registry.npmjs.org/@ethereumjs/rlp/-/rlp-5.0.1.tgz", + "integrity": "sha512-Ab/Hfzz+T9Zl+65Nkg+9xAmwKPLicsnQ4NW49pgvJp9ovefuic95cgOS9CbPc9izIEgsqm1UitV0uNveCvud9w==", + "bin": { + "rlp": "bin/rlp.cjs" + }, + "engines": { + "node": ">=18" + } + }, + "node_modules/@ethereumjs/tx": { + "version": "5.1.0", + "resolved": "https://registry.npmjs.org/@ethereumjs/tx/-/tx-5.1.0.tgz", + "integrity": "sha512-VUhw2+4yXArJZRWhPjmZFrN4WUjUo0qUZUszVpW2KzsGlqCFf67kwJcH9Rca5eS0CRHjr2qHJLpvYOjNuaXVdA==", + "dependencies": { + "@ethereumjs/common": "^4.1.0", + "@ethereumjs/rlp": "^5.0.1", + "@ethereumjs/util": "^9.0.1", + "ethereum-cryptography": "^2.1.2" + }, + "engines": { + "node": ">=18" + }, + "peerDependencies": { + "c-kzg": "^2.1.2" + }, + "peerDependenciesMeta": { + "c-kzg": { + "optional": true + } + } + }, + "node_modules/@ethereumjs/util": { + "version": "9.0.1", + "resolved": "https://registry.npmjs.org/@ethereumjs/util/-/util-9.0.1.tgz", + "integrity": "sha512-NdFFEzCc3H1sYkNnnySwLg6owdQMhjUc2jfuDyx8Xv162WSluCnnSKouKOSG3njGNEyy2I9NmF8zTRDwuqpZWA==", + "dependencies": { + "@ethereumjs/rlp": "^5.0.1", + "ethereum-cryptography": "^2.1.2" + }, + "engines": { + "node": ">=18" + }, + "peerDependencies": { + "c-kzg": "^2.1.2" + }, + "peerDependenciesMeta": { + "c-kzg": { + "optional": true + } + } + }, + "node_modules/@humanwhocodes/config-array": { + "version": "0.11.14", + "resolved": "https://registry.npmjs.org/@humanwhocodes/config-array/-/config-array-0.11.14.tgz", + "integrity": "sha512-3T8LkOmg45BV5FICb15QQMsyUSWrQ8AygVfC7ZG32zOalnqrilm018ZVCw0eapXux8FtA33q8PSRSstjee3jSg==", + "dev": true, + "dependencies": { + "@humanwhocodes/object-schema": "^2.0.2", + "debug": "^4.3.1", + "minimatch": "^3.0.5" + }, + "engines": { + "node": ">=10.10.0" + } + }, + "node_modules/@humanwhocodes/module-importer": { + "version": "1.0.1", + "resolved": "https://registry.npmjs.org/@humanwhocodes/module-importer/-/module-importer-1.0.1.tgz", + "integrity": "sha512-bxveV4V8v5Yb4ncFTT3rPSgZBOpCkjfK0y4oVVVJwIuDVBRMDXrPyXRL988i5ap9m9bnyEEjWfm5WkBmtffLfA==", + "dev": true, + "engines": { + "node": ">=12.22" + }, + "funding": { + "type": "github", + "url": "https://github.com/sponsors/nzakas" + } + }, + "node_modules/@humanwhocodes/object-schema": { + "version": "2.0.2", + "resolved": "https://registry.npmjs.org/@humanwhocodes/object-schema/-/object-schema-2.0.2.tgz", + "integrity": "sha512-6EwiSjwWYP7pTckG6I5eyFANjPhmPjUX9JRLUSfNPC7FX7zK9gyZAfUEaECL6ALTpGX5AjnBq3C9XmVWPitNpw==", + "dev": true + }, + "node_modules/@jest/schemas": { + "version": "29.6.3", + "resolved": "https://registry.npmjs.org/@jest/schemas/-/schemas-29.6.3.tgz", + "integrity": "sha512-mo5j5X+jIZmJQveBKeS/clAueipV7KgiX1vMgCxam1RNYiqE1w62n0/tJJnHtjW8ZHcQco5gY85jA3mi0L+nSA==", + "dev": true, + "dependencies": { + "@sinclair/typebox": "^0.27.8" + }, + "engines": { + "node": "^14.15.0 || ^16.10.0 || >=18.0.0" + } + }, + "node_modules/@noble/curves": { + "version": "1.1.0", + "resolved": "https://registry.npmjs.org/@noble/curves/-/curves-1.1.0.tgz", + "integrity": "sha512-091oBExgENk/kGj3AZmtBDMpxQPDtxQABR2B9lb1JbVTs6ytdzZNwvhxQ4MWasRNEzlbEH8jCWFCwhF/Obj5AA==", + "dependencies": { + "@noble/hashes": "1.3.1" + }, + "funding": { + "url": "https://paulmillr.com/funding/" + } + }, + "node_modules/@noble/hashes": { + "version": "1.3.1", + "resolved": "https://registry.npmjs.org/@noble/hashes/-/hashes-1.3.1.tgz", + "integrity": "sha512-EbqwksQwz9xDRGfDST86whPBgM65E0OH/pCgqW0GBVzO22bNE+NuIbeTb714+IfSjU3aRk47EUvXIb5bTsenKA==", + "engines": { + "node": ">= 16" + }, + "funding": { + "url": "https://paulmillr.com/funding/" + } + }, + "node_modules/@nodelib/fs.scandir": { + "version": "2.1.5", + "resolved": "https://registry.npmjs.org/@nodelib/fs.scandir/-/fs.scandir-2.1.5.tgz", + "integrity": "sha512-vq24Bq3ym5HEQm2NKCr3yXDwjc7vTsEThRDnkp2DK9p1uqLR+DHurm/NOTo0KG7HYHU7eppKZj3MyqYuMBf62g==", + "dev": true, + "dependencies": { + "@nodelib/fs.stat": "2.0.5", + "run-parallel": "^1.1.9" + }, + "engines": { + "node": ">= 8" + } + }, + "node_modules/@nodelib/fs.stat": { + "version": "2.0.5", + "resolved": "https://registry.npmjs.org/@nodelib/fs.stat/-/fs.stat-2.0.5.tgz", + "integrity": "sha512-RkhPPp2zrqDAQA/2jNhnztcPAlv64XdhIp7a7454A5ovI7Bukxgt7MX7udwAu3zg1DcpPU0rz3VV1SeaqvY4+A==", + "dev": true, + "engines": { + "node": ">= 8" + } + }, + "node_modules/@nodelib/fs.walk": { + "version": "1.2.8", + "resolved": "https://registry.npmjs.org/@nodelib/fs.walk/-/fs.walk-1.2.8.tgz", + "integrity": "sha512-oGB+UxlgWcgQkgwo8GcEGwemoTFt3FIO9ababBmaGwXIoBKZ+GTy0pP185beGg7Llih/NSHSV2XAs1lnznocSg==", + "dev": true, + "dependencies": { + "@nodelib/fs.scandir": "2.1.5", + "fastq": "^1.6.0" + }, + "engines": { + "node": ">= 8" + } + }, + "node_modules/@pkgr/core": { + "version": "0.1.1", + "resolved": "https://registry.npmjs.org/@pkgr/core/-/core-0.1.1.tgz", + "integrity": "sha512-cq8o4cWH0ibXh9VGi5P20Tu9XF/0fFXl9EUinr9QfTM7a7p0oTA4iJRCQWppXR1Pg8dSM0UCItCkPwsk9qWWYA==", + "dev": true, + "engines": { + "node": "^12.20.0 || ^14.18.0 || >=16.0.0" + }, + "funding": { + "url": "https://opencollective.com/unts" + } + }, + "node_modules/@scure/base": { + "version": "1.1.5", + "resolved": "https://registry.npmjs.org/@scure/base/-/base-1.1.5.tgz", + "integrity": "sha512-Brj9FiG2W1MRQSTB212YVPRrcbjkv48FoZi/u4l/zds/ieRrqsh7aUf6CLwkAq61oKXr/ZlTzlY66gLIj3TFTQ==", + "funding": { + "url": "https://paulmillr.com/funding/" + } + }, + "node_modules/@scure/bip32": { + "version": "1.3.1", + "resolved": "https://registry.npmjs.org/@scure/bip32/-/bip32-1.3.1.tgz", + "integrity": "sha512-osvveYtyzdEVbt3OfwwXFr4P2iVBL5u1Q3q4ONBfDY/UpOuXmOlbgwc1xECEboY8wIays8Yt6onaWMUdUbfl0A==", + "dependencies": { + "@noble/curves": "~1.1.0", + "@noble/hashes": "~1.3.1", + "@scure/base": "~1.1.0" + }, + "funding": { + "url": "https://paulmillr.com/funding/" + } + }, + "node_modules/@scure/bip39": { + "version": "1.2.1", + "resolved": "https://registry.npmjs.org/@scure/bip39/-/bip39-1.2.1.tgz", + "integrity": "sha512-Z3/Fsz1yr904dduJD0NpiyRHhRYHdcnyh73FZWiV+/qhWi83wNJ3NWolYqCEN+ZWsUz2TWwajJggcRE9r1zUYg==", + "dependencies": { + "@noble/hashes": "~1.3.0", + "@scure/base": "~1.1.0" + }, + "funding": { + "url": "https://paulmillr.com/funding/" + } + }, + "node_modules/@sinclair/typebox": { + "version": "0.27.8", + "resolved": "https://registry.npmjs.org/@sinclair/typebox/-/typebox-0.27.8.tgz", + "integrity": "sha512-+Fj43pSMwJs4KRrH/938Uf+uAELIgVBmQzg/q1YG10djyfA3TnrU8N8XzqCh/okZdszqBQTZf96idMfE5lnwTA==", + "dev": true + }, + "node_modules/@solana/buffer-layout": { + "version": "4.0.1", + "resolved": "https://registry.npmjs.org/@solana/buffer-layout/-/buffer-layout-4.0.1.tgz", + "integrity": "sha512-E1ImOIAD1tBZFRdjeM4/pzTiTApC0AOBGwyAMS4fwIodCWArzJ3DWdoh8cKxeFM2fElkxBh2Aqts1BPC373rHA==", + "dependencies": { + "buffer": "~6.0.3" + }, + "engines": { + "node": ">=5.10" + } + }, + "node_modules/@solana/web3.js": { + "version": "1.91.3", + "resolved": "https://registry.npmjs.org/@solana/web3.js/-/web3.js-1.91.3.tgz", + "integrity": "sha512-Z6FZyW8SWm7RXW5ZSyr1kmpR+eH/F4DhgxV4WPaq5AbAAMnCiiGm36Jb7ACHFXtWzq1a24hBkJ1wnVANjsmdPA==", + "dependencies": { + "@babel/runtime": "^7.23.4", + "@noble/curves": "^1.2.0", + "@noble/hashes": "^1.3.3", + "@solana/buffer-layout": "^4.0.1", + "agentkeepalive": "^4.5.0", + "bigint-buffer": "^1.1.5", + "bn.js": "^5.2.1", + "borsh": "^0.7.0", + "bs58": "^4.0.1", + "buffer": "6.0.3", + "fast-stable-stringify": "^1.0.0", + "jayson": "^4.1.0", + "node-fetch": "^2.7.0", + "rpc-websockets": "^7.5.1", + "superstruct": "^0.14.2" + } + }, + "node_modules/@solana/web3.js/node_modules/@noble/curves": { + "version": "1.4.0", + "resolved": "https://registry.npmjs.org/@noble/curves/-/curves-1.4.0.tgz", + "integrity": "sha512-p+4cb332SFCrReJkCYe8Xzm0OWi4Jji5jVdIZRL/PmacmDkFNw6MrrV+gGpiPxLHbV+zKFRywUWbaseT+tZRXg==", + "dependencies": { + "@noble/hashes": "1.4.0" + }, + "funding": { + "url": "https://paulmillr.com/funding/" + } + }, + "node_modules/@solana/web3.js/node_modules/@noble/hashes": { + "version": "1.4.0", + "resolved": "https://registry.npmjs.org/@noble/hashes/-/hashes-1.4.0.tgz", + "integrity": "sha512-V1JJ1WTRUqHHrOSh597hURcMqVKVGL/ea3kv0gSnEdsEZ0/+VyPghM1lMNGc00z7CIQorSvbKpuJkxvuHbvdbg==", + "engines": { + "node": ">= 16" + }, + "funding": { + "url": "https://paulmillr.com/funding/" + } + }, + "node_modules/@solana/web3.js/node_modules/base-x": { + "version": "3.0.9", + "resolved": "https://registry.npmjs.org/base-x/-/base-x-3.0.9.tgz", + "integrity": "sha512-H7JU6iBHTal1gp56aKoaa//YUxEaAOUiydvrV/pILqIHXTtqxSkATOnDA2u+jZ/61sD+L/412+7kzXRtWukhpQ==", + "dependencies": { + "safe-buffer": "^5.0.1" + } + }, + "node_modules/@solana/web3.js/node_modules/bs58": { + "version": "4.0.1", + "resolved": "https://registry.npmjs.org/bs58/-/bs58-4.0.1.tgz", + "integrity": "sha512-Ok3Wdf5vOIlBrgCvTq96gBkJw+JUEzdBgyaza5HLtPm7yTHkjRy8+JzNyHF7BHa0bNWOQIp3m5YF0nnFcOIKLw==", + "dependencies": { + "base-x": "^3.0.2" + } + }, + "node_modules/@types/connect": { + "version": "3.4.38", + "resolved": "https://registry.npmjs.org/@types/connect/-/connect-3.4.38.tgz", + "integrity": "sha512-K6uROf1LD88uDQqJCktA4yzL1YYAK6NgfsI0v/mTgyPKWsX1CnJ0XPSDhViejru1GcRkLWb8RlzFYJRqGUbaug==", + "dependencies": { + "@types/node": "*" + } + }, + "node_modules/@types/json-schema": { + "version": "7.0.12", + "resolved": "https://registry.npmjs.org/@types/json-schema/-/json-schema-7.0.12.tgz", + "integrity": "sha512-Hr5Jfhc9eYOQNPYO5WLDq/n4jqijdHNlDXjuAQkkt+mWdQR+XJToOHrsD4cPaMXpn6KO7y2+wM8AZEs8VpBLVA==", + "dev": true + }, + "node_modules/@types/node": { + "version": "20.11.30", + "resolved": "https://registry.npmjs.org/@types/node/-/node-20.11.30.tgz", + "integrity": "sha512-dHM6ZxwlmuZaRmUPfv1p+KrdD1Dci04FbdEm/9wEMouFqxYoFl5aMkt0VMAUtYRQDyYvD41WJLukhq/ha3YuTw==", + "dependencies": { + "undici-types": "~5.26.4" + } + }, + "node_modules/@types/node-jose": { + "version": "1.1.10", + "resolved": "https://registry.npmjs.org/@types/node-jose/-/node-jose-1.1.10.tgz", + "integrity": "sha512-7L0ucJTugW4x/sYpQ+c5IudAwr0pFuxDVnZLpHKWpff7p1lVa3wTuNvnrzFBNeLojE+UY0cVCwNGXLxXsMIrzw==", + "dev": true, + "dependencies": { + "@types/node": "*" + } + }, + "node_modules/@types/normalize-package-data": { + "version": "2.4.4", + "resolved": "https://registry.npmjs.org/@types/normalize-package-data/-/normalize-package-data-2.4.4.tgz", + "integrity": "sha512-37i+OaWTh9qeK4LSHPsyRC7NahnGotNuZvjLSgcPzblpHB3rrCJxAOgI5gCdKm7coonsaX1Of0ILiTcnZjbfxA==", + "dev": true + }, + "node_modules/@types/semver": { + "version": "7.5.1", + "resolved": "https://registry.npmjs.org/@types/semver/-/semver-7.5.1.tgz", + "integrity": "sha512-cJRQXpObxfNKkFAZbJl2yjWtJCqELQIdShsogr1d2MilP8dKD9TE/nEKHkJgUNHdGKCQaf9HbIynuV2csLGVLg==", + "dev": true + }, + "node_modules/@types/ws": { + "version": "7.4.7", + "resolved": "https://registry.npmjs.org/@types/ws/-/ws-7.4.7.tgz", + "integrity": "sha512-JQbbmxZTZehdc2iszGKs5oC3NFnjeay7mtAWrdt7qNtAVK0g19muApzAy4bm9byz79xa2ZnO/BOBC2R8RC5Lww==", + "dependencies": { + "@types/node": "*" + } + }, + "node_modules/@typescript-eslint/eslint-plugin": { + "version": "6.7.0", + "resolved": "https://registry.npmjs.org/@typescript-eslint/eslint-plugin/-/eslint-plugin-6.7.0.tgz", + "integrity": "sha512-gUqtknHm0TDs1LhY12K2NA3Rmlmp88jK9Tx8vGZMfHeNMLE3GH2e9TRub+y+SOjuYgtOmok+wt1AyDPZqxbNag==", + "dev": true, + "dependencies": { + "@eslint-community/regexpp": "^4.5.1", + "@typescript-eslint/scope-manager": "6.7.0", + "@typescript-eslint/type-utils": "6.7.0", + "@typescript-eslint/utils": "6.7.0", + "@typescript-eslint/visitor-keys": "6.7.0", + "debug": "^4.3.4", + "graphemer": "^1.4.0", + "ignore": "^5.2.4", + "natural-compare": "^1.4.0", + "semver": "^7.5.4", + "ts-api-utils": "^1.0.1" + }, + "engines": { + "node": "^16.0.0 || >=18.0.0" + }, + "funding": { + "type": "opencollective", + "url": "https://opencollective.com/typescript-eslint" + }, + "peerDependencies": { + "@typescript-eslint/parser": "^6.0.0 || ^6.0.0-alpha", + "eslint": "^7.0.0 || ^8.0.0" + }, + "peerDependenciesMeta": { + "typescript": { + "optional": true + } + } + }, + "node_modules/@typescript-eslint/parser": { + "version": "6.21.0", + "resolved": "https://registry.npmjs.org/@typescript-eslint/parser/-/parser-6.21.0.tgz", + "integrity": "sha512-tbsV1jPne5CkFQCgPBcDOt30ItF7aJoZL997JSF7MhGQqOeT3svWRYxiqlfA5RUdlHN6Fi+EI9bxqbdyAUZjYQ==", + "dev": true, + "dependencies": { + "@typescript-eslint/scope-manager": "6.21.0", + "@typescript-eslint/types": "6.21.0", + "@typescript-eslint/typescript-estree": "6.21.0", + "@typescript-eslint/visitor-keys": "6.21.0", + "debug": "^4.3.4" + }, + "engines": { + "node": "^16.0.0 || >=18.0.0" + }, + "funding": { + "type": "opencollective", + "url": "https://opencollective.com/typescript-eslint" + }, + "peerDependencies": { + "eslint": "^7.0.0 || ^8.0.0" + }, + "peerDependenciesMeta": { + "typescript": { + "optional": true + } + } + }, + "node_modules/@typescript-eslint/parser/node_modules/@typescript-eslint/scope-manager": { + "version": "6.21.0", + "resolved": "https://registry.npmjs.org/@typescript-eslint/scope-manager/-/scope-manager-6.21.0.tgz", + "integrity": "sha512-OwLUIWZJry80O99zvqXVEioyniJMa+d2GrqpUTqi5/v5D5rOrppJVBPa0yKCblcigC0/aYAzxxqQ1B+DS2RYsg==", + "dev": true, + "dependencies": { + "@typescript-eslint/types": "6.21.0", + "@typescript-eslint/visitor-keys": "6.21.0" + }, + "engines": { + "node": "^16.0.0 || >=18.0.0" + }, + "funding": { + "type": "opencollective", + "url": "https://opencollective.com/typescript-eslint" + } + }, + "node_modules/@typescript-eslint/parser/node_modules/@typescript-eslint/types": { + "version": "6.21.0", + "resolved": "https://registry.npmjs.org/@typescript-eslint/types/-/types-6.21.0.tgz", + "integrity": "sha512-1kFmZ1rOm5epu9NZEZm1kckCDGj5UJEf7P1kliH4LKu/RkwpsfqqGmY2OOcUs18lSlQBKLDYBOGxRVtrMN5lpg==", + "dev": true, + "engines": { + "node": "^16.0.0 || >=18.0.0" + }, + "funding": { + "type": "opencollective", + "url": "https://opencollective.com/typescript-eslint" + } + }, + "node_modules/@typescript-eslint/parser/node_modules/@typescript-eslint/typescript-estree": { + "version": "6.21.0", + "resolved": "https://registry.npmjs.org/@typescript-eslint/typescript-estree/-/typescript-estree-6.21.0.tgz", + "integrity": "sha512-6npJTkZcO+y2/kr+z0hc4HwNfrrP4kNYh57ek7yCNlrBjWQ1Y0OS7jiZTkgumrvkX5HkEKXFZkkdFNkaW2wmUQ==", + "dev": true, + "dependencies": { + "@typescript-eslint/types": "6.21.0", + "@typescript-eslint/visitor-keys": "6.21.0", + "debug": "^4.3.4", + "globby": "^11.1.0", + "is-glob": "^4.0.3", + "minimatch": "9.0.3", + "semver": "^7.5.4", + "ts-api-utils": "^1.0.1" + }, + "engines": { + "node": "^16.0.0 || >=18.0.0" + }, + "funding": { + "type": "opencollective", + "url": "https://opencollective.com/typescript-eslint" + }, + "peerDependenciesMeta": { + "typescript": { + "optional": true + } + } + }, + "node_modules/@typescript-eslint/parser/node_modules/@typescript-eslint/visitor-keys": { + "version": "6.21.0", + "resolved": "https://registry.npmjs.org/@typescript-eslint/visitor-keys/-/visitor-keys-6.21.0.tgz", + "integrity": "sha512-JJtkDduxLi9bivAB+cYOVMtbkqdPOhZ+ZI5LC47MIRrDV4Yn2o+ZnW10Nkmr28xRpSpdJ6Sm42Hjf2+REYXm0A==", + "dev": true, + "dependencies": { + "@typescript-eslint/types": "6.21.0", + "eslint-visitor-keys": "^3.4.1" + }, + "engines": { + "node": "^16.0.0 || >=18.0.0" + }, + "funding": { + "type": "opencollective", + "url": "https://opencollective.com/typescript-eslint" + } + }, + "node_modules/@typescript-eslint/parser/node_modules/brace-expansion": { + "version": "2.0.1", + "resolved": "https://registry.npmjs.org/brace-expansion/-/brace-expansion-2.0.1.tgz", + "integrity": "sha512-XnAIvQ8eM+kC6aULx6wuQiwVsnzsi9d3WxzV3FpWTGA19F621kwdbsAcFKXgKUHZWsy+mY6iL1sHTxWEFCytDA==", + "dev": true, + "dependencies": { + "balanced-match": "^1.0.0" + } + }, + "node_modules/@typescript-eslint/parser/node_modules/minimatch": { + "version": "9.0.3", + "resolved": "https://registry.npmjs.org/minimatch/-/minimatch-9.0.3.tgz", + "integrity": "sha512-RHiac9mvaRw0x3AYRgDC1CxAP7HTcNrrECeA8YYJeWnpo+2Q5CegtZjaotWTWxDG3UeGA1coE05iH1mPjT/2mg==", + "dev": true, + "dependencies": { + "brace-expansion": "^2.0.1" + }, + "engines": { + "node": ">=16 || 14 >=14.17" + }, + "funding": { + "url": "https://github.com/sponsors/isaacs" + } + }, + "node_modules/@typescript-eslint/scope-manager": { + "version": "6.7.0", + "resolved": "https://registry.npmjs.org/@typescript-eslint/scope-manager/-/scope-manager-6.7.0.tgz", + "integrity": "sha512-lAT1Uau20lQyjoLUQ5FUMSX/dS07qux9rYd5FGzKz/Kf8W8ccuvMyldb8hadHdK/qOI7aikvQWqulnEq2nCEYA==", + "dev": true, + "dependencies": { + "@typescript-eslint/types": "6.7.0", + "@typescript-eslint/visitor-keys": "6.7.0" + }, + "engines": { + "node": "^16.0.0 || >=18.0.0" + }, + "funding": { + "type": "opencollective", + "url": "https://opencollective.com/typescript-eslint" + } + }, + "node_modules/@typescript-eslint/type-utils": { + "version": "6.7.0", + "resolved": "https://registry.npmjs.org/@typescript-eslint/type-utils/-/type-utils-6.7.0.tgz", + "integrity": "sha512-f/QabJgDAlpSz3qduCyQT0Fw7hHpmhOzY/Rv6zO3yO+HVIdPfIWhrQoAyG+uZVtWAIS85zAyzgAFfyEr+MgBpg==", + "dev": true, + "dependencies": { + "@typescript-eslint/typescript-estree": "6.7.0", + "@typescript-eslint/utils": "6.7.0", + "debug": "^4.3.4", + "ts-api-utils": "^1.0.1" + }, + "engines": { + "node": "^16.0.0 || >=18.0.0" + }, + "funding": { + "type": "opencollective", + "url": "https://opencollective.com/typescript-eslint" + }, + "peerDependencies": { + "eslint": "^7.0.0 || ^8.0.0" + }, + "peerDependenciesMeta": { + "typescript": { + "optional": true + } + } + }, + "node_modules/@typescript-eslint/types": { + "version": "6.7.0", + "resolved": "https://registry.npmjs.org/@typescript-eslint/types/-/types-6.7.0.tgz", + "integrity": "sha512-ihPfvOp7pOcN/ysoj0RpBPOx3HQTJTrIN8UZK+WFd3/iDeFHHqeyYxa4hQk4rMhsz9H9mXpR61IzwlBVGXtl9Q==", + "dev": true, + "engines": { + "node": "^16.0.0 || >=18.0.0" + }, + "funding": { + "type": "opencollective", + "url": "https://opencollective.com/typescript-eslint" + } + }, + "node_modules/@typescript-eslint/typescript-estree": { + "version": "6.7.0", + "resolved": "https://registry.npmjs.org/@typescript-eslint/typescript-estree/-/typescript-estree-6.7.0.tgz", + "integrity": "sha512-dPvkXj3n6e9yd/0LfojNU8VMUGHWiLuBZvbM6V6QYD+2qxqInE7J+J/ieY2iGwR9ivf/R/haWGkIj04WVUeiSQ==", + "dev": true, + "dependencies": { + "@typescript-eslint/types": "6.7.0", + "@typescript-eslint/visitor-keys": "6.7.0", + "debug": "^4.3.4", + "globby": "^11.1.0", + "is-glob": "^4.0.3", + "semver": "^7.5.4", + "ts-api-utils": "^1.0.1" + }, + "engines": { + "node": "^16.0.0 || >=18.0.0" + }, + "funding": { + "type": "opencollective", + "url": "https://opencollective.com/typescript-eslint" + }, + "peerDependenciesMeta": { + "typescript": { + "optional": true + } + } + }, + "node_modules/@typescript-eslint/utils": { + "version": "6.7.0", + "resolved": "https://registry.npmjs.org/@typescript-eslint/utils/-/utils-6.7.0.tgz", + "integrity": "sha512-MfCq3cM0vh2slSikQYqK2Gq52gvOhe57vD2RM3V4gQRZYX4rDPnKLu5p6cm89+LJiGlwEXU8hkYxhqqEC/V3qA==", + "dev": true, + "dependencies": { + "@eslint-community/eslint-utils": "^4.4.0", + "@types/json-schema": "^7.0.12", + "@types/semver": "^7.5.0", + "@typescript-eslint/scope-manager": "6.7.0", + "@typescript-eslint/types": "6.7.0", + "@typescript-eslint/typescript-estree": "6.7.0", + "semver": "^7.5.4" + }, + "engines": { + "node": "^16.0.0 || >=18.0.0" + }, + "funding": { + "type": "opencollective", + "url": "https://opencollective.com/typescript-eslint" + }, + "peerDependencies": { + "eslint": "^7.0.0 || ^8.0.0" + } + }, + "node_modules/@typescript-eslint/visitor-keys": { + "version": "6.7.0", + "resolved": "https://registry.npmjs.org/@typescript-eslint/visitor-keys/-/visitor-keys-6.7.0.tgz", + "integrity": "sha512-/C1RVgKFDmGMcVGeD8HjKv2bd72oI1KxQDeY8uc66gw9R0OK0eMq48cA+jv9/2Ag6cdrsUGySm1yzYmfz0hxwQ==", + "dev": true, + "dependencies": { + "@typescript-eslint/types": "6.7.0", + "eslint-visitor-keys": "^3.4.1" + }, + "engines": { + "node": "^16.0.0 || >=18.0.0" + }, + "funding": { + "type": "opencollective", + "url": "https://opencollective.com/typescript-eslint" + } + }, + "node_modules/@ungap/structured-clone": { + "version": "1.2.0", + "resolved": "https://registry.npmjs.org/@ungap/structured-clone/-/structured-clone-1.2.0.tgz", + "integrity": "sha512-zuVdFrMJiuCDQUMCzQaD6KL28MjnqqN8XnAqiEq9PNm/hCPTSGfrXCOfwj1ow4LFb/tNymJPwsNbVePc1xFqrQ==", + "dev": true + }, + "node_modules/acorn": { + "version": "8.11.3", + "resolved": "https://registry.npmjs.org/acorn/-/acorn-8.11.3.tgz", + "integrity": "sha512-Y9rRfJG5jcKOE0CLisYbojUjIrIEE7AGMzA/Sm4BslANhbS+cDMpgBdcPT91oJ7OuJ9hYJBx59RjbhxVnrF8Xg==", + "dev": true, + "bin": { + "acorn": "bin/acorn" + }, + "engines": { + "node": ">=0.4.0" + } + }, + "node_modules/acorn-jsx": { + "version": "5.3.2", + "resolved": "https://registry.npmjs.org/acorn-jsx/-/acorn-jsx-5.3.2.tgz", + "integrity": "sha512-rq9s+JNhf0IChjtDXxllJ7g41oZk5SlXtp0LHwyA5cejwn7vKmKp4pPri6YEePv2PU65sAsegbXtIinmDFDXgQ==", + "dev": true, + "peerDependencies": { + "acorn": "^6.0.0 || ^7.0.0 || ^8.0.0" + } + }, + "node_modules/agentkeepalive": { + "version": "4.5.0", + "resolved": "https://registry.npmjs.org/agentkeepalive/-/agentkeepalive-4.5.0.tgz", + "integrity": "sha512-5GG/5IbQQpC9FpkRGsSvZI5QYeSCzlJHdpBQntCsuTOxhKD8lqKhrleg2Yi7yvMIf82Ycmmqln9U8V9qwEiJew==", + "dependencies": { + "humanize-ms": "^1.2.1" + }, + "engines": { + "node": ">= 8.0.0" + } + }, + "node_modules/ajv": { + "version": "6.12.6", + "resolved": "https://registry.npmjs.org/ajv/-/ajv-6.12.6.tgz", + "integrity": "sha512-j3fVLgvTo527anyYyJOGTYJbG+vnnQYvE0m5mmkc1TK+nxAppkCLMIL0aZ4dblVCNoGShhm+kzE4ZUykBoMg4g==", + "dev": true, + "dependencies": { + "fast-deep-equal": "^3.1.1", + "fast-json-stable-stringify": "^2.0.0", + "json-schema-traverse": "^0.4.1", + "uri-js": "^4.2.2" + }, + "funding": { + "type": "github", + "url": "https://github.com/sponsors/epoberezkin" + } + }, + "node_modules/ansi-regex": { + "version": "5.0.1", + "resolved": "https://registry.npmjs.org/ansi-regex/-/ansi-regex-5.0.1.tgz", + "integrity": "sha512-quJQXlTSUGL2LH9SUXo8VwsY4soanhgo6LNSm84E1LBcE8s3O0wpdiRzyR9z/ZZJMlMWv37qOOb9pdJlMUEKFQ==", + "dev": true, + "engines": { + "node": ">=8" + } + }, + "node_modules/ansi-styles": { + "version": "4.3.0", + "resolved": "https://registry.npmjs.org/ansi-styles/-/ansi-styles-4.3.0.tgz", + "integrity": "sha512-zbB9rCJAT1rbjiVDb2hqKFHNYLxgtk8NURxZ3IZwD3F6NtxbXZQCnnSi1Lkx+IDohdPlFp222wVALIheZJQSEg==", + "dev": true, + "dependencies": { + "color-convert": "^2.0.1" + }, + "engines": { + "node": ">=8" + }, + "funding": { + "url": "https://github.com/chalk/ansi-styles?sponsor=1" + } + }, + "node_modules/argparse": { + "version": "2.0.1", + "resolved": "https://registry.npmjs.org/argparse/-/argparse-2.0.1.tgz", + "integrity": "sha512-8+9WqebbFzpX9OR+Wa6O29asIogeRMzcGtAINdpMHHyAg10f05aSFVBbcEqGf/PXw1EjAZ+q2/bEBg3DvurK3Q==", + "dev": true + }, + "node_modules/array-union": { + "version": "2.1.0", + "resolved": "https://registry.npmjs.org/array-union/-/array-union-2.1.0.tgz", + "integrity": "sha512-HGyxoOTYUyCM6stUe6EJgnd4EoewAI7zMdfqO+kGjnlZmBDz/cR5pf8r/cR4Wq60sL/p0IkcjUEEPwS3GFrIyw==", + "dev": true, + "engines": { + "node": ">=8" + } + }, + "node_modules/balanced-match": { + "version": "1.0.2", + "resolved": "https://registry.npmjs.org/balanced-match/-/balanced-match-1.0.2.tgz", + "integrity": "sha512-3oSeUO0TMV67hN1AmbXsK4yaqU7tjiHlbxRDZOpH0KW9+CeX4bRAaX0Anxt0tx2MrpRpWwQaPwIlISEJhYU5Pw==", + "dev": true + }, + "node_modules/base-x": { + "version": "4.0.0", + "resolved": "https://registry.npmjs.org/base-x/-/base-x-4.0.0.tgz", + "integrity": "sha512-FuwxlW4H5kh37X/oW59pwTzzTKRzfrrQwhmyspRM7swOEZcHtDZSCt45U6oKgtuFE+WYPblePMVIPR4RZrh/hw==" + }, + "node_modules/base64-js": { + "version": "1.5.1", + "resolved": "https://registry.npmjs.org/base64-js/-/base64-js-1.5.1.tgz", + "integrity": "sha512-AKpaYlHn8t4SVbOHCy+b5+KKgvR4vrsD8vbvrbiQJps7fKDTkjkDry6ji0rUJjC0kzbNePLwzxq8iypo41qeWA==", + "funding": [ + { + "type": "github", + "url": "https://github.com/sponsors/feross" + }, + { + "type": "patreon", + "url": "https://www.patreon.com/feross" + }, + { + "type": "consulting", + "url": "https://feross.org/support" + } + ] + }, + "node_modules/base64url": { + "version": "3.0.1", + "resolved": "https://registry.npmjs.org/base64url/-/base64url-3.0.1.tgz", + "integrity": "sha512-ir1UPr3dkwexU7FdV8qBBbNDRUhMmIekYMFZfi+C/sLNnRESKPl23nB9b2pltqfOQNnGzsDdId90AEtG5tCx4A==", + "engines": { + "node": ">=6.0.0" + } + }, + "node_modules/bigint-buffer": { + "version": "1.1.5", + "resolved": "https://registry.npmjs.org/bigint-buffer/-/bigint-buffer-1.1.5.tgz", + "integrity": "sha512-trfYco6AoZ+rKhKnxA0hgX0HAbVP/s808/EuDSe2JDzUnCp/xAsli35Orvk67UrTEcwuxZqYZDmfA2RXJgxVvA==", + "hasInstallScript": true, + "dependencies": { + "bindings": "^1.3.0" + }, + "engines": { + "node": ">= 10.0.0" + } + }, + "node_modules/bindings": { + "version": "1.5.0", + "resolved": "https://registry.npmjs.org/bindings/-/bindings-1.5.0.tgz", + "integrity": "sha512-p2q/t/mhvuOj/UeLlV6566GD/guowlr0hHxClI0W9m7MWYkL1F0hLo+0Aexs9HSPCtR1SXQ0TD3MMKrXZajbiQ==", + "dependencies": { + "file-uri-to-path": "1.0.0" + } + }, + "node_modules/bn.js": { + "version": "5.2.1", + "resolved": "https://registry.npmjs.org/bn.js/-/bn.js-5.2.1.tgz", + "integrity": "sha512-eXRvHzWyYPBuB4NBy0cmYQjGitUrtqwbvlzP3G6VFnNRbsZQIxQ10PbKKHt8gZ/HW/D/747aDl+QkDqg3KQLMQ==" + }, + "node_modules/borsh": { + "version": "0.7.0", + "resolved": "https://registry.npmjs.org/borsh/-/borsh-0.7.0.tgz", + "integrity": "sha512-CLCsZGIBCFnPtkNnieW/a8wmreDmfUtjU2m9yHrzPXIlNbqVs0AQrSatSG6vdNYUqdc83tkQi2eHfF98ubzQLA==", + "dependencies": { + "bn.js": "^5.2.0", + "bs58": "^4.0.0", + "text-encoding-utf-8": "^1.0.2" + } + }, + "node_modules/borsh/node_modules/base-x": { + "version": "3.0.9", + "resolved": "https://registry.npmjs.org/base-x/-/base-x-3.0.9.tgz", + "integrity": "sha512-H7JU6iBHTal1gp56aKoaa//YUxEaAOUiydvrV/pILqIHXTtqxSkATOnDA2u+jZ/61sD+L/412+7kzXRtWukhpQ==", + "dependencies": { + "safe-buffer": "^5.0.1" + } + }, + "node_modules/borsh/node_modules/bs58": { + "version": "4.0.1", + "resolved": "https://registry.npmjs.org/bs58/-/bs58-4.0.1.tgz", + "integrity": "sha512-Ok3Wdf5vOIlBrgCvTq96gBkJw+JUEzdBgyaza5HLtPm7yTHkjRy8+JzNyHF7BHa0bNWOQIp3m5YF0nnFcOIKLw==", + "dependencies": { + "base-x": "^3.0.2" + } + }, + "node_modules/brace-expansion": { + "version": "1.1.11", + "resolved": "https://registry.npmjs.org/brace-expansion/-/brace-expansion-1.1.11.tgz", + "integrity": "sha512-iCuPHDFgrHX7H2vEI/5xpz07zSHB00TpugqhmYtVmMO6518mCuRMoOYFldEBl0g187ufozdaHgWKcYFb61qGiA==", + "dev": true, + "dependencies": { + "balanced-match": "^1.0.0", + "concat-map": "0.0.1" + } + }, + "node_modules/braces": { + "version": "3.0.2", + "resolved": "https://registry.npmjs.org/braces/-/braces-3.0.2.tgz", + "integrity": "sha512-b8um+L1RzM3WDSzvhm6gIz1yfTbBt6YTlcEKAvsmqCZZFw46z626lVj9j1yEPW33H5H+lBQpZMP1k8l+78Ha0A==", + "dev": true, + "dependencies": { + "fill-range": "^7.0.1" + }, + "engines": { + "node": ">=8" + } + }, + "node_modules/browserslist": { + "version": "4.23.0", + "resolved": "https://registry.npmjs.org/browserslist/-/browserslist-4.23.0.tgz", + "integrity": "sha512-QW8HiM1shhT2GuzkvklfjcKDiWFXHOeFCIA/huJPwHsslwcydgk7X+z2zXpEijP98UCY7HbubZt5J2Zgvf0CaQ==", + "dev": true, + "funding": [ + { + "type": "opencollective", + "url": "https://opencollective.com/browserslist" + }, + { + "type": "tidelift", + "url": "https://tidelift.com/funding/github/npm/browserslist" + }, + { + "type": "github", + "url": "https://github.com/sponsors/ai" + } + ], + "dependencies": { + "caniuse-lite": "^1.0.30001587", + "electron-to-chromium": "^1.4.668", + "node-releases": "^2.0.14", + "update-browserslist-db": "^1.0.13" + }, + "bin": { + "browserslist": "cli.js" + }, + "engines": { + "node": "^6 || ^7 || ^8 || ^9 || ^10 || ^11 || ^12 || >=13.7" + } + }, + "node_modules/bs58": { + "version": "5.0.0", + "resolved": "https://registry.npmjs.org/bs58/-/bs58-5.0.0.tgz", + "integrity": "sha512-r+ihvQJvahgYT50JD05dyJNKlmmSlMoOGwn1lCcEzanPglg7TxYjioQUYehQ9mAR/+hOSd2jRc/Z2y5UxBymvQ==", + "dependencies": { + "base-x": "^4.0.0" + } + }, + "node_modules/buffer": { + "version": "6.0.3", + "resolved": "https://registry.npmjs.org/buffer/-/buffer-6.0.3.tgz", + "integrity": "sha512-FTiCpNxtwiZZHEZbcbTIcZjERVICn9yq/pDFkTl95/AxzD1naBctN7YO68riM/gLSDY7sdrMby8hofADYuuqOA==", + "funding": [ + { + "type": "github", + "url": "https://github.com/sponsors/feross" + }, + { + "type": "patreon", + "url": "https://www.patreon.com/feross" + }, + { + "type": "consulting", + "url": "https://feross.org/support" + } + ], + "dependencies": { + "base64-js": "^1.3.1", + "ieee754": "^1.2.1" + } + }, + "node_modules/bufferutil": { + "version": "4.0.8", + "resolved": "https://registry.npmjs.org/bufferutil/-/bufferutil-4.0.8.tgz", + "integrity": "sha512-4T53u4PdgsXqKaIctwF8ifXlRTTmEPJ8iEPWFdGZvcf7sbwYo6FKFEX9eNNAnzFZ7EzJAQ3CJeOtCRA4rDp7Pw==", + "hasInstallScript": true, + "optional": true, + "dependencies": { + "node-gyp-build": "^4.3.0" + }, + "engines": { + "node": ">=6.14.2" + } + }, + "node_modules/builtin-modules": { + "version": "3.3.0", + "resolved": "https://registry.npmjs.org/builtin-modules/-/builtin-modules-3.3.0.tgz", + "integrity": "sha512-zhaCDicdLuWN5UbN5IMnFqNMhNfo919sH85y2/ea+5Yg9TsTkeZxpL+JLbp6cgYFS4sRLp3YV4S6yDuqVWHYOw==", + "dev": true, + "engines": { + "node": ">=6" + }, + "funding": { + "url": "https://github.com/sponsors/sindresorhus" + } + }, + "node_modules/callsites": { + "version": "3.1.0", + "resolved": "https://registry.npmjs.org/callsites/-/callsites-3.1.0.tgz", + "integrity": "sha512-P8BjAsXvZS+VIDUI11hHCQEv74YT67YUi5JJFNWIqL235sBmjX4+qx9Muvls5ivyNENctx46xQLQ3aTuE7ssaQ==", + "dev": true, + "engines": { + "node": ">=6" + } + }, + "node_modules/caniuse-lite": { + "version": "1.0.30001600", + "resolved": "https://registry.npmjs.org/caniuse-lite/-/caniuse-lite-1.0.30001600.tgz", + "integrity": "sha512-+2S9/2JFhYmYaDpZvo0lKkfvuKIglrx68MwOBqMGHhQsNkLjB5xtc/TGoEPs+MxjSyN/72qer2g97nzR641mOQ==", + "dev": true, + "funding": [ + { + "type": "opencollective", + "url": "https://opencollective.com/browserslist" + }, + { + "type": "tidelift", + "url": "https://tidelift.com/funding/github/npm/caniuse-lite" + }, + { + "type": "github", + "url": "https://github.com/sponsors/ai" + } + ] + }, + "node_modules/chalk": { + "version": "4.1.2", + "resolved": "https://registry.npmjs.org/chalk/-/chalk-4.1.2.tgz", + "integrity": "sha512-oKnbhFyRIXpUuez8iBMmyEa4nbj4IOQyuhc/wy9kY7/WVPcwIO9VA668Pu8RkO7+0G76SLROeyw9CpQ061i4mA==", + "dev": true, + "dependencies": { + "ansi-styles": "^4.1.0", + "supports-color": "^7.1.0" + }, + "engines": { + "node": ">=10" + }, + "funding": { + "url": "https://github.com/chalk/chalk?sponsor=1" + } + }, + "node_modules/ci-info": { + "version": "4.0.0", + "resolved": "https://registry.npmjs.org/ci-info/-/ci-info-4.0.0.tgz", + "integrity": "sha512-TdHqgGf9odd8SXNuxtUBVx8Nv+qZOejE6qyqiy5NtbYYQOeFa6zmHkxlPzmaLxWWHsU6nJmB7AETdVPi+2NBUg==", + "dev": true, + "funding": [ + { + "type": "github", + "url": "https://github.com/sponsors/sibiraj-s" + } + ], + "engines": { + "node": ">=8" + } + }, + "node_modules/clean-regexp": { + "version": "1.0.0", + "resolved": "https://registry.npmjs.org/clean-regexp/-/clean-regexp-1.0.0.tgz", + "integrity": "sha512-GfisEZEJvzKrmGWkvfhgzcz/BllN1USeqD2V6tg14OAOgaCD2Z/PUEuxnAZ/nPvmaHRG7a8y77p1T/IRQ4D1Hw==", + "dev": true, + "dependencies": { + "escape-string-regexp": "^1.0.5" + }, + "engines": { + "node": ">=4" + } + }, + "node_modules/clean-regexp/node_modules/escape-string-regexp": { + "version": "1.0.5", + "resolved": "https://registry.npmjs.org/escape-string-regexp/-/escape-string-regexp-1.0.5.tgz", + "integrity": "sha512-vbRorB5FUQWvla16U8R/qgaFIya2qGzwDrNmCZuYKrbdSUMG6I1ZCGQRefkRVhuOkIGVne7BQ35DSfo1qvJqFg==", + "dev": true, + "engines": { + "node": ">=0.8.0" + } + }, + "node_modules/color-convert": { + "version": "2.0.1", + "resolved": "https://registry.npmjs.org/color-convert/-/color-convert-2.0.1.tgz", + "integrity": "sha512-RRECPsj7iu/xb5oKYcsFHSppFNnsj/52OVTRKb4zP5onXwVF3zVmmToNcOfGC+CRDpfK/U584fMg38ZHCaElKQ==", + "dev": true, + "dependencies": { + "color-name": "~1.1.4" + }, + "engines": { + "node": ">=7.0.0" + } + }, + "node_modules/color-name": { + "version": "1.1.4", + "resolved": "https://registry.npmjs.org/color-name/-/color-name-1.1.4.tgz", + "integrity": "sha512-dOy+3AuW3a2wNbZHIuMZpTcgjGuLU/uBL/ubcZF9OXbDo8ff4O8yVp5Bf0efS8uEoYo5q4Fx7dY9OgQGXgAsQA==", + "dev": true + }, + "node_modules/commander": { + "version": "2.20.3", + "resolved": "https://registry.npmjs.org/commander/-/commander-2.20.3.tgz", + "integrity": "sha512-GpVkmM8vF2vQUkj2LvZmD35JxeJOLCwJ9cUkugyk2nuhbv3+mJvpLYYt+0+USMxE+oj+ey/lJEnhZw75x/OMcQ==" + }, + "node_modules/common-tags": { + "version": "1.8.2", + "resolved": "https://registry.npmjs.org/common-tags/-/common-tags-1.8.2.tgz", + "integrity": "sha512-gk/Z852D2Wtb//0I+kRFNKKE9dIIVirjoqPoA1wJU+XePVXZfGeBpk45+A1rKO4Q43prqWBNY/MiIeRLbPWUaA==", + "dev": true, + "engines": { + "node": ">=4.0.0" + } + }, + "node_modules/concat-map": { + "version": "0.0.1", + "resolved": "https://registry.npmjs.org/concat-map/-/concat-map-0.0.1.tgz", + "integrity": "sha512-/Srv4dswyQNBfohGpz9o6Yb3Gz3SrUDqBH5rTuhGR7ahtlbYKnVxw2bCFMRljaA7EXHaXZ8wsHdodFvbkhKmqg==", + "dev": true + }, + "node_modules/core-js-compat": { + "version": "3.36.1", + "resolved": "https://registry.npmjs.org/core-js-compat/-/core-js-compat-3.36.1.tgz", + "integrity": "sha512-Dk997v9ZCt3X/npqzyGdTlq6t7lDBhZwGvV94PKzDArjp7BTRm7WlDAXYd/OWdeFHO8OChQYRJNJvUCqCbrtKA==", + "dev": true, + "dependencies": { + "browserslist": "^4.23.0" + }, + "funding": { + "type": "opencollective", + "url": "https://opencollective.com/core-js" + } + }, + "node_modules/crc": { + "version": "4.3.2", + "resolved": "https://registry.npmjs.org/crc/-/crc-4.3.2.tgz", + "integrity": "sha512-uGDHf4KLLh2zsHa8D8hIQ1H/HtFQhyHrc0uhHBcoKGol/Xnb+MPYfUMw7cvON6ze/GUESTudKayDcJC5HnJv1A==", + "engines": { + "node": ">=12" + }, + "peerDependencies": { + "buffer": ">=6.0.3" + }, + "peerDependenciesMeta": { + "buffer": { + "optional": true + } + } + }, + "node_modules/cross-spawn": { + "version": "7.0.3", + "resolved": "https://registry.npmjs.org/cross-spawn/-/cross-spawn-7.0.3.tgz", + "integrity": "sha512-iRDPJKUPVEND7dHPO8rkbOnPpyDygcDFtWjpeWNCgy8WP2rXcxXL8TskReQl6OrB2G7+UJrags1q15Fudc7G6w==", + "dev": true, + "dependencies": { + "path-key": "^3.1.0", + "shebang-command": "^2.0.0", + "which": "^2.0.1" + }, + "engines": { + "node": ">= 8" + } + }, + "node_modules/debug": { + "version": "4.3.4", + "resolved": "https://registry.npmjs.org/debug/-/debug-4.3.4.tgz", + "integrity": "sha512-PRWFHuSU3eDtQJPvnNY7Jcket1j0t5OuOsFzPPzsekD52Zl8qUfFIPEiswXqIvHWGVHOgX+7G/vCNNhehwxfkQ==", + "dev": true, + "dependencies": { + "ms": "2.1.2" + }, + "engines": { + "node": ">=6.0" + }, + "peerDependenciesMeta": { + "supports-color": { + "optional": true + } + } + }, + "node_modules/deep-is": { + "version": "0.1.4", + "resolved": "https://registry.npmjs.org/deep-is/-/deep-is-0.1.4.tgz", + "integrity": "sha512-oIPzksmTg4/MriiaYGO+okXDT7ztn/w3Eptv/+gSIdMdKsJo0u4CfYNFJPy+4SKMuCqGw2wxnA+URMg3t8a/bQ==", + "dev": true + }, + "node_modules/delay": { + "version": "5.0.0", + "resolved": "https://registry.npmjs.org/delay/-/delay-5.0.0.tgz", + "integrity": "sha512-ReEBKkIfe4ya47wlPYf/gu5ib6yUG0/Aez0JQZQz94kiWtRQvZIQbTiehsnwHvLSWJnQdhVeqYue7Id1dKr0qw==", + "engines": { + "node": ">=10" + }, + "funding": { + "url": "https://github.com/sponsors/sindresorhus" + } + }, + "node_modules/dir-glob": { + "version": "3.0.1", + "resolved": "https://registry.npmjs.org/dir-glob/-/dir-glob-3.0.1.tgz", + "integrity": "sha512-WkrWp9GR4KXfKGYzOLmTuGVi1UWFfws377n9cc55/tb6DuqyF6pcQ5AbiHEshaDpY9v6oaSr2XCDidGmMwdzIA==", + "dev": true, + "dependencies": { + "path-type": "^4.0.0" + }, + "engines": { + "node": ">=8" + } + }, + "node_modules/dlv": { + "version": "1.1.3", + "resolved": "https://registry.npmjs.org/dlv/-/dlv-1.1.3.tgz", + "integrity": "sha512-+HlytyjlPKnIG8XuRG8WvmBP8xs8P71y+SKKS6ZXWoEgLuePxtDoUEiH7WkdePWrQ5JBpE6aoVqfZfJUQkjXwA==", + "dev": true + }, + "node_modules/doctrine": { + "version": "3.0.0", + "resolved": "https://registry.npmjs.org/doctrine/-/doctrine-3.0.0.tgz", + "integrity": "sha512-yS+Q5i3hBf7GBkd4KG8a7eBNNWNGLTaEwwYWUijIYM7zrlYDM0BFXHjjPWlWZ1Rg7UaddZeIDmi9jF3HmqiQ2w==", + "dev": true, + "dependencies": { + "esutils": "^2.0.2" + }, + "engines": { + "node": ">=6.0.0" + } + }, + "node_modules/electron-to-chromium": { + "version": "1.4.721", + "resolved": "https://registry.npmjs.org/electron-to-chromium/-/electron-to-chromium-1.4.721.tgz", + "integrity": "sha512-k1x2r6foI8iJOp+1qTxbbrrWMsOiHkzGBYwYigaq+apO1FSqtn44KTo3Sy69qt7CRr7149zTcsDvH7MUKsOuIQ==", + "dev": true + }, + "node_modules/error-ex": { + "version": "1.3.2", + "resolved": "https://registry.npmjs.org/error-ex/-/error-ex-1.3.2.tgz", + "integrity": "sha512-7dFHNmqeFSEt2ZBsCriorKnn3Z2pj+fd9kmI6QoWw4//DL+icEBfc0U7qJCisqrTsKTjw4fNFy2pW9OqStD84g==", + "dev": true, + "dependencies": { + "is-arrayish": "^0.2.1" + } + }, + "node_modules/es6-promise": { + "version": "4.2.8", + "resolved": "https://registry.npmjs.org/es6-promise/-/es6-promise-4.2.8.tgz", + "integrity": "sha512-HJDGx5daxeIvxdBxvG2cb9g4tEvwIk3i8+nhX0yGrYmZUzbkdg8QbDevheDB8gd0//uPj4c1EQua8Q+MViT0/w==" + }, + "node_modules/es6-promisify": { + "version": "5.0.0", + "resolved": "https://registry.npmjs.org/es6-promisify/-/es6-promisify-5.0.0.tgz", + "integrity": "sha512-C+d6UdsYDk0lMebHNR4S2NybQMMngAOnOwYBQjTOiv0MkoJMP0Myw2mgpDLBcpfCmRLxyFqYhS/CfOENq4SJhQ==", + "dependencies": { + "es6-promise": "^4.0.3" + } + }, + "node_modules/escalade": { + "version": "3.1.2", + "resolved": "https://registry.npmjs.org/escalade/-/escalade-3.1.2.tgz", + "integrity": "sha512-ErCHMCae19vR8vQGe50xIsVomy19rg6gFu3+r3jkEO46suLMWBksvVyoGgQV+jOfl84ZSOSlmv6Gxa89PmTGmA==", + "dev": true, + "engines": { + "node": ">=6" + } + }, + "node_modules/escape-string-regexp": { + "version": "4.0.0", + "resolved": "https://registry.npmjs.org/escape-string-regexp/-/escape-string-regexp-4.0.0.tgz", + "integrity": "sha512-TtpcNJ3XAzx3Gq8sWRzJaVajRs0uVxA2YAkdb1jm2YkPz4G6egUFAyA3n5vtEIZefPk5Wa4UXbKuS5fKkJWdgA==", + "dev": true, + "engines": { + "node": ">=10" + }, + "funding": { + "url": "https://github.com/sponsors/sindresorhus" + } + }, + "node_modules/eslint": { + "version": "8.57.0", + "resolved": "https://registry.npmjs.org/eslint/-/eslint-8.57.0.tgz", + "integrity": "sha512-dZ6+mexnaTIbSBZWgou51U6OmzIhYM2VcNdtiTtI7qPNZm35Akpr0f6vtw3w1Kmn5PYo+tZVfh13WrhpS6oLqQ==", + "dev": true, + "dependencies": { + "@eslint-community/eslint-utils": "^4.2.0", + "@eslint-community/regexpp": "^4.6.1", + "@eslint/eslintrc": "^2.1.4", + "@eslint/js": "8.57.0", + "@humanwhocodes/config-array": "^0.11.14", + "@humanwhocodes/module-importer": "^1.0.1", + "@nodelib/fs.walk": "^1.2.8", + "@ungap/structured-clone": "^1.2.0", + "ajv": "^6.12.4", + "chalk": "^4.0.0", + "cross-spawn": "^7.0.2", + "debug": "^4.3.2", + "doctrine": "^3.0.0", + "escape-string-regexp": "^4.0.0", + "eslint-scope": "^7.2.2", + "eslint-visitor-keys": "^3.4.3", + "espree": "^9.6.1", + "esquery": "^1.4.2", + "esutils": "^2.0.2", + "fast-deep-equal": "^3.1.3", + "file-entry-cache": "^6.0.1", + "find-up": "^5.0.0", + "glob-parent": "^6.0.2", + "globals": "^13.19.0", + "graphemer": "^1.4.0", + "ignore": "^5.2.0", + "imurmurhash": "^0.1.4", + "is-glob": "^4.0.0", + "is-path-inside": "^3.0.3", + "js-yaml": "^4.1.0", + "json-stable-stringify-without-jsonify": "^1.0.1", + "levn": "^0.4.1", + "lodash.merge": "^4.6.2", + "minimatch": "^3.1.2", + "natural-compare": "^1.4.0", + "optionator": "^0.9.3", + "strip-ansi": "^6.0.1", + "text-table": "^0.2.0" + }, + "bin": { + "eslint": "bin/eslint.js" + }, + "engines": { + "node": "^12.22.0 || ^14.17.0 || >=16.0.0" + }, + "funding": { + "url": "https://opencollective.com/eslint" + } + }, + "node_modules/eslint-config-prettier": { + "version": "9.0.0", + "resolved": "https://registry.npmjs.org/eslint-config-prettier/-/eslint-config-prettier-9.0.0.tgz", + "integrity": "sha512-IcJsTkJae2S35pRsRAwoCE+925rJJStOdkKnLVgtE+tEpqU0EVVM7OqrwxqgptKdX29NUwC82I5pXsGFIgSevw==", + "dev": true, + "bin": { + "eslint-config-prettier": "bin/cli.js" + }, + "peerDependencies": { + "eslint": ">=7.0.0" + } + }, + "node_modules/eslint-plugin-es": { + "version": "3.0.1", + "resolved": "https://registry.npmjs.org/eslint-plugin-es/-/eslint-plugin-es-3.0.1.tgz", + "integrity": "sha512-GUmAsJaN4Fc7Gbtl8uOBlayo2DqhwWvEzykMHSCZHU3XdJ+NSzzZcVhXh3VxX5icqQ+oQdIEawXX8xkR3mIFmQ==", + "dev": true, + "dependencies": { + "eslint-utils": "^2.0.0", + "regexpp": "^3.0.0" + }, + "engines": { + "node": ">=8.10.0" + }, + "funding": { + "url": "https://github.com/sponsors/mysticatea" + }, + "peerDependencies": { + "eslint": ">=4.19.1" + } + }, + "node_modules/eslint-plugin-filename-rules": { + "version": "1.3.1", + "resolved": "https://registry.npmjs.org/eslint-plugin-filename-rules/-/eslint-plugin-filename-rules-1.3.1.tgz", + "integrity": "sha512-kBMxGFvK3QrRBHMurhFSNa+PFdszezVtBV6egg39TDzlj6D4jL3Xx6oyNjm5xE4C+TdQUBzWwymHJHBPyxOreA==", + "dev": true, + "engines": { + "node": ">=6.0.0" + } + }, + "node_modules/eslint-plugin-node": { + "version": "11.1.0", + "resolved": "https://registry.npmjs.org/eslint-plugin-node/-/eslint-plugin-node-11.1.0.tgz", + "integrity": "sha512-oUwtPJ1W0SKD0Tr+wqu92c5xuCeQqB3hSCHasn/ZgjFdA9iDGNkNf2Zi9ztY7X+hNuMib23LNGRm6+uN+KLE3g==", + "dev": true, + "dependencies": { + "eslint-plugin-es": "^3.0.0", + "eslint-utils": "^2.0.0", + "ignore": "^5.1.1", + "minimatch": "^3.0.4", + "resolve": "^1.10.1", + "semver": "^6.1.0" + }, + "engines": { + "node": ">=8.10.0" + }, + "peerDependencies": { + "eslint": ">=5.16.0" + } + }, + "node_modules/eslint-plugin-node/node_modules/semver": { + "version": "6.3.1", + "resolved": "https://registry.npmjs.org/semver/-/semver-6.3.1.tgz", + "integrity": "sha512-BR7VvDCVHO+q2xBEWskxS6DJE1qRnb7DxzUrogb71CWoSficBxYsiAGd+Kl0mmq/MprG9yArRkyrQxTO6XjMzA==", + "dev": true, + "bin": { + "semver": "bin/semver.js" + } + }, + "node_modules/eslint-plugin-prettier": { + "version": "5.1.3", + "resolved": "https://registry.npmjs.org/eslint-plugin-prettier/-/eslint-plugin-prettier-5.1.3.tgz", + "integrity": "sha512-C9GCVAs4Eq7ZC/XFQHITLiHJxQngdtraXaM+LoUFoFp/lHNl2Zn8f3WQbe9HvTBBQ9YnKFB0/2Ajdqwo5D1EAw==", + "dev": true, + "dependencies": { + "prettier-linter-helpers": "^1.0.0", + "synckit": "^0.8.6" + }, + "engines": { + "node": "^14.18.0 || >=16.0.0" + }, + "funding": { + "url": "https://opencollective.com/eslint-plugin-prettier" + }, + "peerDependencies": { + "@types/eslint": ">=8.0.0", + "eslint": ">=8.0.0", + "eslint-config-prettier": "*", + "prettier": ">=3.0.0" + }, + "peerDependenciesMeta": { + "@types/eslint": { + "optional": true + }, + "eslint-config-prettier": { + "optional": true + } + } + }, + "node_modules/eslint-plugin-unicorn": { + "version": "51.0.1", + "resolved": "https://registry.npmjs.org/eslint-plugin-unicorn/-/eslint-plugin-unicorn-51.0.1.tgz", + "integrity": "sha512-MuR/+9VuB0fydoI0nIn2RDA5WISRn4AsJyNSaNKLVwie9/ONvQhxOBbkfSICBPnzKrB77Fh6CZZXjgTt/4Latw==", + "dev": true, + "dependencies": { + "@babel/helper-validator-identifier": "^7.22.20", + "@eslint-community/eslint-utils": "^4.4.0", + "@eslint/eslintrc": "^2.1.4", + "ci-info": "^4.0.0", + "clean-regexp": "^1.0.0", + "core-js-compat": "^3.34.0", + "esquery": "^1.5.0", + "indent-string": "^4.0.0", + "is-builtin-module": "^3.2.1", + "jsesc": "^3.0.2", + "pluralize": "^8.0.0", + "read-pkg-up": "^7.0.1", + "regexp-tree": "^0.1.27", + "regjsparser": "^0.10.0", + "semver": "^7.5.4", + "strip-indent": "^3.0.0" + }, + "engines": { + "node": ">=16" + }, + "funding": { + "url": "https://github.com/sindresorhus/eslint-plugin-unicorn?sponsor=1" + }, + "peerDependencies": { + "eslint": ">=8.56.0" + } + }, + "node_modules/eslint-scope": { + "version": "7.2.2", + "resolved": "https://registry.npmjs.org/eslint-scope/-/eslint-scope-7.2.2.tgz", + "integrity": "sha512-dOt21O7lTMhDM+X9mB4GX+DZrZtCUJPL/wlcTqxyrx5IvO0IYtILdtrQGQp+8n5S0gwSVmOf9NQrjMOgfQZlIg==", + "dev": true, + "dependencies": { + "esrecurse": "^4.3.0", + "estraverse": "^5.2.0" + }, + "engines": { + "node": "^12.22.0 || ^14.17.0 || >=16.0.0" + }, + "funding": { + "url": "https://opencollective.com/eslint" + } + }, + "node_modules/eslint-utils": { + "version": "2.1.0", + "resolved": "https://registry.npmjs.org/eslint-utils/-/eslint-utils-2.1.0.tgz", + "integrity": "sha512-w94dQYoauyvlDc43XnGB8lU3Zt713vNChgt4EWwhXAP2XkBvndfxF0AgIqKOOasjPIPzj9JqgwkwbCYD0/V3Zg==", + "dev": true, + "dependencies": { + "eslint-visitor-keys": "^1.1.0" + }, + "engines": { + "node": ">=6" + }, + "funding": { + "url": "https://github.com/sponsors/mysticatea" + } + }, + "node_modules/eslint-utils/node_modules/eslint-visitor-keys": { + "version": "1.3.0", + "resolved": "https://registry.npmjs.org/eslint-visitor-keys/-/eslint-visitor-keys-1.3.0.tgz", + "integrity": "sha512-6J72N8UNa462wa/KFODt/PJ3IU60SDpC3QXC1Hjc1BXXpfL2C9R5+AU7jhe0F6GREqVMh4Juu+NY7xn+6dipUQ==", + "dev": true, + "engines": { + "node": ">=4" + } + }, + "node_modules/eslint-visitor-keys": { + "version": "3.4.3", + "resolved": "https://registry.npmjs.org/eslint-visitor-keys/-/eslint-visitor-keys-3.4.3.tgz", + "integrity": "sha512-wpc+LXeiyiisxPlEkUzU6svyS1frIO3Mgxj1fdy7Pm8Ygzguax2N3Fa/D/ag1WqbOprdI+uY6wMUl8/a2G+iag==", + "dev": true, + "engines": { + "node": "^12.22.0 || ^14.17.0 || >=16.0.0" + }, + "funding": { + "url": "https://opencollective.com/eslint" + } + }, + "node_modules/espree": { + "version": "9.6.1", + "resolved": "https://registry.npmjs.org/espree/-/espree-9.6.1.tgz", + "integrity": "sha512-oruZaFkjorTpF32kDSI5/75ViwGeZginGGy2NoOSg3Q9bnwlnmDm4HLnkl0RE3n+njDXR037aY1+x58Z/zFdwQ==", + "dev": true, + "dependencies": { + "acorn": "^8.9.0", + "acorn-jsx": "^5.3.2", + "eslint-visitor-keys": "^3.4.1" + }, + "engines": { + "node": "^12.22.0 || ^14.17.0 || >=16.0.0" + }, + "funding": { + "url": "https://opencollective.com/eslint" + } + }, + "node_modules/esquery": { + "version": "1.5.0", + "resolved": "https://registry.npmjs.org/esquery/-/esquery-1.5.0.tgz", + "integrity": "sha512-YQLXUplAwJgCydQ78IMJywZCceoqk1oH01OERdSAJc/7U2AylwjhSCLDEtqwg811idIS/9fIU5GjG73IgjKMVg==", + "dev": true, + "dependencies": { + "estraverse": "^5.1.0" + }, + "engines": { + "node": ">=0.10" + } + }, + "node_modules/esrecurse": { + "version": "4.3.0", + "resolved": "https://registry.npmjs.org/esrecurse/-/esrecurse-4.3.0.tgz", + "integrity": "sha512-KmfKL3b6G+RXvP8N1vr3Tq1kL/oCFgn2NYXEtqP8/L3pKapUA4G8cFVaoF3SU323CD4XypR/ffioHmkti6/Tag==", + "dev": true, + "dependencies": { + "estraverse": "^5.2.0" + }, + "engines": { + "node": ">=4.0" + } + }, + "node_modules/estraverse": { + "version": "5.3.0", + "resolved": "https://registry.npmjs.org/estraverse/-/estraverse-5.3.0.tgz", + "integrity": "sha512-MMdARuVEQziNTeJD8DgMqmhwR11BRQ/cBP+pLtYdSTnf3MIO8fFeiINEbX36ZdNlfU/7A9f3gUw49B3oQsvwBA==", + "dev": true, + "engines": { + "node": ">=4.0" + } + }, + "node_modules/esutils": { + "version": "2.0.3", + "resolved": "https://registry.npmjs.org/esutils/-/esutils-2.0.3.tgz", + "integrity": "sha512-kVscqXk4OCp68SZ0dkgEKVi6/8ij300KBWTJq32P/dYeWTSwK41WyTxalN1eRmA5Z9UU/LX9D7FWSmV9SAYx6g==", + "dev": true, + "engines": { + "node": ">=0.10.0" + } + }, + "node_modules/ethereum-cryptography": { + "version": "2.1.2", + "resolved": "https://registry.npmjs.org/ethereum-cryptography/-/ethereum-cryptography-2.1.2.tgz", + "integrity": "sha512-Z5Ba0T0ImZ8fqXrJbpHcbpAvIswRte2wGNR/KePnu8GbbvgJ47lMxT/ZZPG6i9Jaht4azPDop4HaM00J0J59ug==", + "dependencies": { + "@noble/curves": "1.1.0", + "@noble/hashes": "1.3.1", + "@scure/bip32": "1.3.1", + "@scure/bip39": "1.2.1" + } + }, + "node_modules/eventemitter3": { + "version": "4.0.7", + "resolved": "https://registry.npmjs.org/eventemitter3/-/eventemitter3-4.0.7.tgz", + "integrity": "sha512-8guHBZCwKnFhYdHr2ysuRWErTwhoN2X8XELRlrRwpmfeY2jjuUN4taQMsULKUVo1K4DvZl+0pgfyoysHxvmvEw==" + }, + "node_modules/eyes": { + "version": "0.1.8", + "resolved": "https://registry.npmjs.org/eyes/-/eyes-0.1.8.tgz", + "integrity": "sha512-GipyPsXO1anza0AOZdy69Im7hGFCNB7Y/NGjDlZGJ3GJJLtwNSb2vrzYrTYJRrRloVx7pl+bhUaTB8yiccPvFQ==", + "engines": { + "node": "> 0.1.90" + } + }, + "node_modules/fast-deep-equal": { + "version": "3.1.3", + "resolved": "https://registry.npmjs.org/fast-deep-equal/-/fast-deep-equal-3.1.3.tgz", + "integrity": "sha512-f3qQ9oQy9j2AhBe/H9VC91wLmKBCCU/gDOnKNAYG5hswO7BLKj09Hc5HYNz9cGI++xlpDCIgDaitVs03ATR84Q==", + "dev": true + }, + "node_modules/fast-diff": { + "version": "1.3.0", + "resolved": "https://registry.npmjs.org/fast-diff/-/fast-diff-1.3.0.tgz", + "integrity": "sha512-VxPP4NqbUjj6MaAOafWeUn2cXWLcCtljklUtZf0Ind4XQ+QPtmA0b18zZy0jIQx+ExRVCR/ZQpBmik5lXshNsw==", + "dev": true + }, + "node_modules/fast-glob": { + "version": "3.3.1", + "resolved": "https://registry.npmjs.org/fast-glob/-/fast-glob-3.3.1.tgz", + "integrity": "sha512-kNFPyjhh5cKjrUltxs+wFx+ZkbRaxxmZ+X0ZU31SOsxCEtP9VPgtq2teZw1DebupL5GmDaNQ6yKMMVcM41iqDg==", + "dev": true, + "dependencies": { + "@nodelib/fs.stat": "^2.0.2", + "@nodelib/fs.walk": "^1.2.3", + "glob-parent": "^5.1.2", + "merge2": "^1.3.0", + "micromatch": "^4.0.4" + }, + "engines": { + "node": ">=8.6.0" + } + }, + "node_modules/fast-glob/node_modules/glob-parent": { + "version": "5.1.2", + "resolved": "https://registry.npmjs.org/glob-parent/-/glob-parent-5.1.2.tgz", + "integrity": "sha512-AOIgSQCepiJYwP3ARnGx+5VnTu2HBYdzbGP45eLw1vr3zB3vZLeyed1sC9hnbcOc9/SrMyM5RPQrkGz4aS9Zow==", + "dev": true, + "dependencies": { + "is-glob": "^4.0.1" + }, + "engines": { + "node": ">= 6" + } + }, + "node_modules/fast-json-stable-stringify": { + "version": "2.1.0", + "resolved": "https://registry.npmjs.org/fast-json-stable-stringify/-/fast-json-stable-stringify-2.1.0.tgz", + "integrity": "sha512-lhd/wF+Lk98HZoTCtlVraHtfh5XYijIjalXck7saUtuanSDyLMxnHhSXEDJqHxD7msR8D0uCmqlkwjCV8xvwHw==", + "dev": true + }, + "node_modules/fast-levenshtein": { + "version": "2.0.6", + "resolved": "https://registry.npmjs.org/fast-levenshtein/-/fast-levenshtein-2.0.6.tgz", + "integrity": "sha512-DCXu6Ifhqcks7TZKY3Hxp3y6qphY5SJZmrWMDrKcERSOXWQdMhU9Ig/PYrzyw/ul9jOIyh0N4M0tbC5hodg8dw==", + "dev": true + }, + "node_modules/fast-stable-stringify": { + "version": "1.0.0", + "resolved": "https://registry.npmjs.org/fast-stable-stringify/-/fast-stable-stringify-1.0.0.tgz", + "integrity": "sha512-wpYMUmFu5f00Sm0cj2pfivpmawLZ0NKdviQ4w9zJeR8JVtOpOxHmLaJuj0vxvGqMJQWyP/COUkF75/57OKyRag==" + }, + "node_modules/fastq": { + "version": "1.15.0", + "resolved": "https://registry.npmjs.org/fastq/-/fastq-1.15.0.tgz", + "integrity": "sha512-wBrocU2LCXXa+lWBt8RoIRD89Fi8OdABODa/kEnyeyjS5aZO5/GNvI5sEINADqP/h8M29UHTHUb53sUu5Ihqdw==", + "dev": true, + "dependencies": { + "reusify": "^1.0.4" + } + }, + "node_modules/file-entry-cache": { + "version": "6.0.1", + "resolved": "https://registry.npmjs.org/file-entry-cache/-/file-entry-cache-6.0.1.tgz", + "integrity": "sha512-7Gps/XWymbLk2QLYK4NzpMOrYjMhdIxXuIvy2QBsLE6ljuodKvdkWs/cpyJJ3CVIVpH0Oi1Hvg1ovbMzLdFBBg==", + "dev": true, + "dependencies": { + "flat-cache": "^3.0.4" + }, + "engines": { + "node": "^10.12.0 || >=12.0.0" + } + }, + "node_modules/file-uri-to-path": { + "version": "1.0.0", + "resolved": "https://registry.npmjs.org/file-uri-to-path/-/file-uri-to-path-1.0.0.tgz", + "integrity": "sha512-0Zt+s3L7Vf1biwWZ29aARiVYLx7iMGnEUl9x33fbB/j3jR81u/O2LbqK+Bm1CDSNDKVtJ/YjwY7TUd5SkeLQLw==" + }, + "node_modules/fill-range": { + "version": "7.0.1", + "resolved": "https://registry.npmjs.org/fill-range/-/fill-range-7.0.1.tgz", + "integrity": "sha512-qOo9F+dMUmC2Lcb4BbVvnKJxTPjCm+RRpe4gDuGrzkL7mEVl/djYSu2OdQ2Pa302N4oqkSg9ir6jaLWJ2USVpQ==", + "dev": true, + "dependencies": { + "to-regex-range": "^5.0.1" + }, + "engines": { + "node": ">=8" + } + }, + "node_modules/find-up": { + "version": "5.0.0", + "resolved": "https://registry.npmjs.org/find-up/-/find-up-5.0.0.tgz", + "integrity": "sha512-78/PXT1wlLLDgTzDs7sjq9hzz0vXD+zn+7wypEe4fXQxCmdmqfGsEPQxmiCSQI3ajFV91bVSsvNtrJRiW6nGng==", + "dev": true, + "dependencies": { + "locate-path": "^6.0.0", + "path-exists": "^4.0.0" + }, + "engines": { + "node": ">=10" + }, + "funding": { + "url": "https://github.com/sponsors/sindresorhus" + } + }, + "node_modules/flat-cache": { + "version": "3.1.0", + "resolved": "https://registry.npmjs.org/flat-cache/-/flat-cache-3.1.0.tgz", + "integrity": "sha512-OHx4Qwrrt0E4jEIcI5/Xb+f+QmJYNj2rrK8wiIdQOIrB9WrrJL8cjZvXdXuBTkkEwEqLycb5BeZDV1o2i9bTew==", + "dev": true, + "dependencies": { + "flatted": "^3.2.7", + "keyv": "^4.5.3", + "rimraf": "^3.0.2" + }, + "engines": { + "node": ">=12.0.0" + } + }, + "node_modules/flatted": { + "version": "3.2.7", + "resolved": "https://registry.npmjs.org/flatted/-/flatted-3.2.7.tgz", + "integrity": "sha512-5nqDSxl8nn5BSNxyR3n4I6eDmbolI6WT+QqR547RwxQapgjQBmtktdP+HTBb/a/zLsbzERTONyUB5pefh5TtjQ==", + "dev": true + }, + "node_modules/fs.realpath": { + "version": "1.0.0", + "resolved": "https://registry.npmjs.org/fs.realpath/-/fs.realpath-1.0.0.tgz", + "integrity": "sha512-OO0pH2lK6a0hZnAdau5ItzHPI6pUlvI7jMVnxUQRtw4owF2wk8lOSabtGDCTP4Ggrg2MbGnWO9X8K1t4+fGMDw==", + "dev": true + }, + "node_modules/function-bind": { + "version": "1.1.2", + "resolved": "https://registry.npmjs.org/function-bind/-/function-bind-1.1.2.tgz", + "integrity": "sha512-7XHNxH7qX9xG5mIwxkhumTox/MIRNcOgDrxWsMt2pAr23WHp6MrRlN7FBSFpCpr+oVO0F744iUgR82nJMfG2SA==", + "dev": true, + "funding": { + "url": "https://github.com/sponsors/ljharb" + } + }, + "node_modules/glob": { + "version": "7.2.3", + "resolved": "https://registry.npmjs.org/glob/-/glob-7.2.3.tgz", + "integrity": "sha512-nFR0zLpU2YCaRxwoCJvL6UvCH2JFyFVIvwTLsIf21AuHlMskA1hhTdk+LlYJtOlYt9v6dvszD2BGRqBL+iQK9Q==", + "dev": true, + "dependencies": { + "fs.realpath": "^1.0.0", + "inflight": "^1.0.4", + "inherits": "2", + "minimatch": "^3.1.1", + "once": "^1.3.0", + "path-is-absolute": "^1.0.0" + }, + "engines": { + "node": "*" + }, + "funding": { + "url": "https://github.com/sponsors/isaacs" + } + }, + "node_modules/glob-parent": { + "version": "6.0.2", + "resolved": "https://registry.npmjs.org/glob-parent/-/glob-parent-6.0.2.tgz", + "integrity": "sha512-XxwI8EOhVQgWp6iDL+3b0r86f4d6AX6zSU55HfB4ydCEuXLXc5FcYeOu+nnGftS4TEju/11rt4KJPTMgbfmv4A==", + "dev": true, + "dependencies": { + "is-glob": "^4.0.3" + }, + "engines": { + "node": ">=10.13.0" + } + }, + "node_modules/globals": { + "version": "13.24.0", + "resolved": "https://registry.npmjs.org/globals/-/globals-13.24.0.tgz", + "integrity": "sha512-AhO5QUcj8llrbG09iWhPU2B204J1xnPeL8kQmVorSsy+Sjj1sk8gIyh6cUocGmH4L0UuhAJy+hJMRA4mgA4mFQ==", + "dev": true, + "dependencies": { + "type-fest": "^0.20.2" + }, + "engines": { + "node": ">=8" + }, + "funding": { + "url": "https://github.com/sponsors/sindresorhus" + } + }, + "node_modules/globby": { + "version": "11.1.0", + "resolved": "https://registry.npmjs.org/globby/-/globby-11.1.0.tgz", + "integrity": "sha512-jhIXaOzy1sb8IyocaruWSn1TjmnBVs8Ayhcy83rmxNJ8q2uWKCAj3CnJY+KpGSXCueAPc0i05kVvVKtP1t9S3g==", + "dev": true, + "dependencies": { + "array-union": "^2.1.0", + "dir-glob": "^3.0.1", + "fast-glob": "^3.2.9", + "ignore": "^5.2.0", + "merge2": "^1.4.1", + "slash": "^3.0.0" + }, + "engines": { + "node": ">=10" + }, + "funding": { + "url": "https://github.com/sponsors/sindresorhus" + } + }, + "node_modules/graphemer": { + "version": "1.4.0", + "resolved": "https://registry.npmjs.org/graphemer/-/graphemer-1.4.0.tgz", + "integrity": "sha512-EtKwoO6kxCL9WO5xipiHTZlSzBm7WLT627TqC/uVRd0HKmq8NXyebnNYxDoBi7wt8eTWrUrKXCOVaFq9x1kgag==", + "dev": true + }, + "node_modules/has-ansi": { + "version": "2.0.0", + "resolved": "https://registry.npmjs.org/has-ansi/-/has-ansi-2.0.0.tgz", + "integrity": "sha512-C8vBJ8DwUCx19vhm7urhTuUsr4/IyP6l4VzNQDv+ryHQObW3TTTp9yB68WpYgRe2bbaGuZ/se74IqFeVnMnLZg==", + "dev": true, + "dependencies": { + "ansi-regex": "^2.0.0" + }, + "engines": { + "node": ">=0.10.0" + } + }, + "node_modules/has-ansi/node_modules/ansi-regex": { + "version": "2.1.1", + "resolved": "https://registry.npmjs.org/ansi-regex/-/ansi-regex-2.1.1.tgz", + "integrity": "sha512-TIGnTpdo+E3+pCyAluZvtED5p5wCqLdezCyhPZzKPcxvFplEt4i+W7OONCKgeZFT3+y5NZZfOOS/Bdcanm1MYA==", + "dev": true, + "engines": { + "node": ">=0.10.0" + } + }, + "node_modules/has-flag": { + "version": "4.0.0", + "resolved": "https://registry.npmjs.org/has-flag/-/has-flag-4.0.0.tgz", + "integrity": "sha512-EykJT/Q1KjTWctppgIAgfSO0tKVuZUjhgMr17kqTumMl6Afv3EISleU7qZUzoXDFTAHTDC4NOoG/ZxU3EvlMPQ==", + "dev": true, + "engines": { + "node": ">=8" + } + }, + "node_modules/hasown": { + "version": "2.0.2", + "resolved": "https://registry.npmjs.org/hasown/-/hasown-2.0.2.tgz", + "integrity": "sha512-0hJU9SCPvmMzIBdZFqNPXWa6dqh7WdH0cII9y+CyS8rG3nL48Bclra9HmKhVVUHyPWNH5Y7xDwAB7bfgSjkUMQ==", + "dev": true, + "dependencies": { + "function-bind": "^1.1.2" + }, + "engines": { + "node": ">= 0.4" + } + }, + "node_modules/hosted-git-info": { + "version": "2.8.9", + "resolved": "https://registry.npmjs.org/hosted-git-info/-/hosted-git-info-2.8.9.tgz", + "integrity": "sha512-mxIDAb9Lsm6DoOJ7xH+5+X4y1LU/4Hi50L9C5sIswK3JzULS4bwk1FvjdBgvYR4bzT4tuUQiC15FE2f5HbLvYw==", + "dev": true + }, + "node_modules/humanize-ms": { + "version": "1.2.1", + "resolved": "https://registry.npmjs.org/humanize-ms/-/humanize-ms-1.2.1.tgz", + "integrity": "sha512-Fl70vYtsAFb/C06PTS9dZBo7ihau+Tu/DNCk/OyHhea07S+aeMWpFFkUaXRa8fI+ScZbEI8dfSxwY7gxZ9SAVQ==", + "dependencies": { + "ms": "^2.0.0" + } + }, + "node_modules/ieee754": { + "version": "1.2.1", + "resolved": "https://registry.npmjs.org/ieee754/-/ieee754-1.2.1.tgz", + "integrity": "sha512-dcyqhDvX1C46lXZcVqCpK+FtMRQVdIMN6/Df5js2zouUsqG7I6sFxitIC+7KYK29KdXOLHdu9zL4sFnoVQnqaA==", + "funding": [ + { + "type": "github", + "url": "https://github.com/sponsors/feross" + }, + { + "type": "patreon", + "url": "https://www.patreon.com/feross" + }, + { + "type": "consulting", + "url": "https://feross.org/support" + } + ] + }, + "node_modules/ignore": { + "version": "5.2.4", + "resolved": "https://registry.npmjs.org/ignore/-/ignore-5.2.4.tgz", + "integrity": "sha512-MAb38BcSbH0eHNBxn7ql2NH/kX33OkB3lZ1BNdh7ENeRChHTYsTvWrMubiIAMNS2llXEEgZ1MUOBtXChP3kaFQ==", + "dev": true, + "engines": { + "node": ">= 4" + } + }, + "node_modules/import-fresh": { + "version": "3.3.0", + "resolved": "https://registry.npmjs.org/import-fresh/-/import-fresh-3.3.0.tgz", + "integrity": "sha512-veYYhQa+D1QBKznvhUHxb8faxlrwUnxseDAbAp457E0wLNio2bOSKnjYDhMj+YiAq61xrMGhQk9iXVk5FzgQMw==", + "dev": true, + "dependencies": { + "parent-module": "^1.0.0", + "resolve-from": "^4.0.0" + }, + "engines": { + "node": ">=6" + }, + "funding": { + "url": "https://github.com/sponsors/sindresorhus" + } + }, + "node_modules/imurmurhash": { + "version": "0.1.4", + "resolved": "https://registry.npmjs.org/imurmurhash/-/imurmurhash-0.1.4.tgz", + "integrity": "sha512-JmXMZ6wuvDmLiHEml9ykzqO6lwFbof0GG4IkcGaENdCRDDmMVnny7s5HsIgHCbaq0w2MyPhDqkhTUgS2LU2PHA==", + "dev": true, + "engines": { + "node": ">=0.8.19" + } + }, + "node_modules/indent-string": { + "version": "4.0.0", + "resolved": "https://registry.npmjs.org/indent-string/-/indent-string-4.0.0.tgz", + "integrity": "sha512-EdDDZu4A2OyIK7Lr/2zG+w5jmbuk1DVBnEwREQvBzspBJkCEbRa8GxU1lghYcaGJCnRWibjDXlq779X1/y5xwg==", + "dev": true, + "engines": { + "node": ">=8" + } + }, + "node_modules/inflight": { + "version": "1.0.6", + "resolved": "https://registry.npmjs.org/inflight/-/inflight-1.0.6.tgz", + "integrity": "sha512-k92I/b08q4wvFscXCLvqfsHCrjrF7yiXsQuIVvVE7N82W3+aqpzuUdBbfhWcy/FZR3/4IgflMgKLOsvPDrGCJA==", + "dev": true, + "dependencies": { + "once": "^1.3.0", + "wrappy": "1" + } + }, + "node_modules/inherits": { + "version": "2.0.4", + "resolved": "https://registry.npmjs.org/inherits/-/inherits-2.0.4.tgz", + "integrity": "sha512-k/vGaX4/Yla3WzyMCvTQOXYeIHvqOKtnqBduzTHpzpQZzAskKMhZ2K+EnBiSM9zGSoIFeMpXKxa4dYeZIQqewQ==", + "dev": true + }, + "node_modules/is-arrayish": { + "version": "0.2.1", + "resolved": "https://registry.npmjs.org/is-arrayish/-/is-arrayish-0.2.1.tgz", + "integrity": "sha512-zz06S8t0ozoDXMG+ube26zeCTNXcKIPJZJi8hBrF4idCLms4CG9QtK7qBl1boi5ODzFpjswb5JPmHCbMpjaYzg==", + "dev": true + }, + "node_modules/is-builtin-module": { + "version": "3.2.1", + "resolved": "https://registry.npmjs.org/is-builtin-module/-/is-builtin-module-3.2.1.tgz", + "integrity": "sha512-BSLE3HnV2syZ0FK0iMA/yUGplUeMmNz4AW5fnTunbCIqZi4vG3WjJT9FHMy5D69xmAYBHXQhJdALdpwVxV501A==", + "dev": true, + "dependencies": { + "builtin-modules": "^3.3.0" + }, + "engines": { + "node": ">=6" + }, + "funding": { + "url": "https://github.com/sponsors/sindresorhus" + } + }, + "node_modules/is-core-module": { + "version": "2.13.1", + "resolved": "https://registry.npmjs.org/is-core-module/-/is-core-module-2.13.1.tgz", + "integrity": "sha512-hHrIjvZsftOsvKSn2TRYl63zvxsgE0K+0mYMoH6gD4omR5IWB2KynivBQczo3+wF1cCkjzvptnI9Q0sPU66ilw==", + "dev": true, + "dependencies": { + "hasown": "^2.0.0" + }, + "funding": { + "url": "https://github.com/sponsors/ljharb" + } + }, + "node_modules/is-extglob": { + "version": "2.1.1", + "resolved": "https://registry.npmjs.org/is-extglob/-/is-extglob-2.1.1.tgz", + "integrity": "sha512-SbKbANkN603Vi4jEZv49LeVJMn4yGwsbzZworEoyEiutsN3nJYdbO36zfhGJ6QEDpOZIFkDtnq5JRxmvl3jsoQ==", + "dev": true, + "engines": { + "node": ">=0.10.0" + } + }, + "node_modules/is-glob": { + "version": "4.0.3", + "resolved": "https://registry.npmjs.org/is-glob/-/is-glob-4.0.3.tgz", + "integrity": "sha512-xelSayHH36ZgE7ZWhli7pW34hNbNl8Ojv5KVmkJD4hBdD3th8Tfk9vYasLM+mXWOZhFkgZfxhLSnrwRr4elSSg==", + "dev": true, + "dependencies": { + "is-extglob": "^2.1.1" + }, + "engines": { + "node": ">=0.10.0" + } + }, + "node_modules/is-number": { + "version": "7.0.0", + "resolved": "https://registry.npmjs.org/is-number/-/is-number-7.0.0.tgz", + "integrity": "sha512-41Cifkg6e8TylSpdtTpeLVMqvSBEVzTttHvERD741+pnZ8ANv0004MRL43QKPDlK9cGvNp6NZWZUBlbGXYxxng==", + "dev": true, + "engines": { + "node": ">=0.12.0" + } + }, + "node_modules/is-path-inside": { + "version": "3.0.3", + "resolved": "https://registry.npmjs.org/is-path-inside/-/is-path-inside-3.0.3.tgz", + "integrity": "sha512-Fd4gABb+ycGAmKou8eMftCupSir5lRxqf4aD/vd0cD2qc4HL07OjCeuHMr8Ro4CoMaeCKDB0/ECBOVWjTwUvPQ==", + "dev": true, + "engines": { + "node": ">=8" + } + }, + "node_modules/isexe": { + "version": "2.0.0", + "resolved": "https://registry.npmjs.org/isexe/-/isexe-2.0.0.tgz", + "integrity": "sha512-RHxMLp9lnKHGHRng9QFhRCMbYAcVpn69smSGcq3f36xjgVVWThj4qqLbTLlq7Ssj8B+fIQ1EuCEGI2lKsyQeIw==", + "dev": true + }, + "node_modules/isomorphic-ws": { + "version": "4.0.1", + "resolved": "https://registry.npmjs.org/isomorphic-ws/-/isomorphic-ws-4.0.1.tgz", + "integrity": "sha512-BhBvN2MBpWTaSHdWRb/bwdZJ1WaehQ2L1KngkCkfLUGF0mAWAT1sQUQacEmQ0jXkFw/czDXPNQSL5u2/Krsz1w==", + "peerDependencies": { + "ws": "*" + } + }, + "node_modules/jayson": { + "version": "4.1.0", + "resolved": "https://registry.npmjs.org/jayson/-/jayson-4.1.0.tgz", + "integrity": "sha512-R6JlbyLN53Mjku329XoRT2zJAE6ZgOQ8f91ucYdMCD4nkGCF9kZSrcGXpHIU4jeKj58zUZke2p+cdQchU7Ly7A==", + "dependencies": { + "@types/connect": "^3.4.33", + "@types/node": "^12.12.54", + "@types/ws": "^7.4.4", + "commander": "^2.20.3", + "delay": "^5.0.0", + "es6-promisify": "^5.0.0", + "eyes": "^0.1.8", + "isomorphic-ws": "^4.0.1", + "json-stringify-safe": "^5.0.1", + "JSONStream": "^1.3.5", + "uuid": "^8.3.2", + "ws": "^7.4.5" + }, + "bin": { + "jayson": "bin/jayson.js" + }, + "engines": { + "node": ">=8" + } + }, + "node_modules/jayson/node_modules/@types/node": { + "version": "12.20.55", + "resolved": "https://registry.npmjs.org/@types/node/-/node-12.20.55.tgz", + "integrity": "sha512-J8xLz7q2OFulZ2cyGTLE1TbbZcjpno7FaN6zdJNrgAdrJ+DZzh/uFR6YrTb4C+nXakvud8Q4+rbhoIWlYQbUFQ==" + }, + "node_modules/jayson/node_modules/uuid": { + "version": "8.3.2", + "resolved": "https://registry.npmjs.org/uuid/-/uuid-8.3.2.tgz", + "integrity": "sha512-+NYs2QeMWy+GWFOEm9xnn6HCDp0l7QBD7ml8zLUmJ+93Q5NF0NocErnwkTkXVFNiX3/fpC6afS8Dhb/gz7R7eg==", + "bin": { + "uuid": "dist/bin/uuid" + } + }, + "node_modules/js-tokens": { + "version": "4.0.0", + "resolved": "https://registry.npmjs.org/js-tokens/-/js-tokens-4.0.0.tgz", + "integrity": "sha512-RdJUflcE3cUzKiMqQgsCu06FPu9UdIJO0beYbPhHN4k6apgJtifcoCtT9bcxOpYBtpD2kCM6Sbzg4CausW/PKQ==", + "dev": true + }, + "node_modules/js-yaml": { + "version": "4.1.0", + "resolved": "https://registry.npmjs.org/js-yaml/-/js-yaml-4.1.0.tgz", + "integrity": "sha512-wpxZs9NoxZaJESJGIZTyDEaYpl0FKSA+FB9aJiyemKhMwkxQg63h4T1KJgUGHpTqPDNRcmmYLugrRjJlBtWvRA==", + "dev": true, + "dependencies": { + "argparse": "^2.0.1" + }, + "bin": { + "js-yaml": "bin/js-yaml.js" + } + }, + "node_modules/jsesc": { + "version": "3.0.2", + "resolved": "https://registry.npmjs.org/jsesc/-/jsesc-3.0.2.tgz", + "integrity": "sha512-xKqzzWXDttJuOcawBt4KnKHHIf5oQ/Cxax+0PWFG+DFDgHNAdi+TXECADI+RYiFUMmx8792xsMbbgXj4CwnP4g==", + "dev": true, + "bin": { + "jsesc": "bin/jsesc" + }, + "engines": { + "node": ">=6" + } + }, + "node_modules/json-buffer": { + "version": "3.0.1", + "resolved": "https://registry.npmjs.org/json-buffer/-/json-buffer-3.0.1.tgz", + "integrity": "sha512-4bV5BfR2mqfQTJm+V5tPPdf+ZpuhiIvTuAB5g8kcrXOZpTT/QwwVRWBywX1ozr6lEuPdbHxwaJlm9G6mI2sfSQ==", + "dev": true + }, + "node_modules/json-parse-even-better-errors": { + "version": "2.3.1", + "resolved": "https://registry.npmjs.org/json-parse-even-better-errors/-/json-parse-even-better-errors-2.3.1.tgz", + "integrity": "sha512-xyFwyhro/JEof6Ghe2iz2NcXoj2sloNsWr/XsERDK/oiPCfaNhl5ONfp+jQdAZRQQ0IJWNzH9zIZF7li91kh2w==", + "dev": true + }, + "node_modules/json-schema-traverse": { + "version": "0.4.1", + "resolved": "https://registry.npmjs.org/json-schema-traverse/-/json-schema-traverse-0.4.1.tgz", + "integrity": "sha512-xbbCH5dCYU5T8LcEhhuh7HJ88HXuW3qsI3Y0zOZFKfZEHcpWiHU/Jxzk629Brsab/mMiHQti9wMP+845RPe3Vg==", + "dev": true + }, + "node_modules/json-stable-stringify-without-jsonify": { + "version": "1.0.1", + "resolved": "https://registry.npmjs.org/json-stable-stringify-without-jsonify/-/json-stable-stringify-without-jsonify-1.0.1.tgz", + "integrity": "sha512-Bdboy+l7tA3OGW6FjyFHWkP5LuByj1Tk33Ljyq0axyzdk9//JSi2u3fP1QSmd1KNwq6VOKYGlAu87CisVir6Pw==", + "dev": true + }, + "node_modules/json-stringify-safe": { + "version": "5.0.1", + "resolved": "https://registry.npmjs.org/json-stringify-safe/-/json-stringify-safe-5.0.1.tgz", + "integrity": "sha512-ZClg6AaYvamvYEE82d3Iyd3vSSIjQ+odgjaTzRuO3s7toCdFKczob2i0zCh7JE8kWn17yvAWhUVxvqGwUalsRA==" + }, + "node_modules/jsonparse": { + "version": "1.3.1", + "resolved": "https://registry.npmjs.org/jsonparse/-/jsonparse-1.3.1.tgz", + "integrity": "sha512-POQXvpdL69+CluYsillJ7SUhKvytYjW9vG/GKpnf+xP8UWgYEM/RaMzHHofbALDiKbbP1W8UEYmgGl39WkPZsg==", + "engines": [ + "node >= 0.2.0" + ] + }, + "node_modules/JSONStream": { + "version": "1.3.5", + "resolved": "https://registry.npmjs.org/JSONStream/-/JSONStream-1.3.5.tgz", + "integrity": "sha512-E+iruNOY8VV9s4JEbe1aNEm6MiszPRr/UfcHMz0TQh1BXSxHK+ASV1R6W4HpjBhSeS+54PIsAMCBmwD06LLsqQ==", + "dependencies": { + "jsonparse": "^1.2.0", + "through": ">=2.2.7 <3" + }, + "bin": { + "JSONStream": "bin.js" + }, + "engines": { + "node": "*" + } + }, + "node_modules/keyv": { + "version": "4.5.3", + "resolved": "https://registry.npmjs.org/keyv/-/keyv-4.5.3.tgz", + "integrity": "sha512-QCiSav9WaX1PgETJ+SpNnx2PRRapJ/oRSXM4VO5OGYGSjrxbKPVFVhB3l2OCbLCk329N8qyAtsJjSjvVBWzEug==", + "dev": true, + "dependencies": { + "json-buffer": "3.0.1" + } + }, + "node_modules/levn": { + "version": "0.4.1", + "resolved": "https://registry.npmjs.org/levn/-/levn-0.4.1.tgz", + "integrity": "sha512-+bT2uH4E5LGE7h/n3evcS/sQlJXCpIp6ym8OWJ5eV6+67Dsql/LaaT7qJBAt2rzfoa/5QBGBhxDix1dMt2kQKQ==", + "dev": true, + "dependencies": { + "prelude-ls": "^1.2.1", + "type-check": "~0.4.0" + }, + "engines": { + "node": ">= 0.8.0" + } + }, + "node_modules/lines-and-columns": { + "version": "1.2.4", + "resolved": "https://registry.npmjs.org/lines-and-columns/-/lines-and-columns-1.2.4.tgz", + "integrity": "sha512-7ylylesZQ/PV29jhEDl3Ufjo6ZX7gCqJr5F7PKrqc93v7fzSymt1BpwEU8nAUXs8qzzvqhbjhK5QZg6Mt/HkBg==", + "dev": true + }, + "node_modules/locate-path": { + "version": "6.0.0", + "resolved": "https://registry.npmjs.org/locate-path/-/locate-path-6.0.0.tgz", + "integrity": "sha512-iPZK6eYjbxRu3uB4/WZ3EsEIMJFMqAoopl3R+zuq0UjcAm/MO6KCweDgPfP3elTztoKP3KtnVHxTn2NHBSDVUw==", + "dev": true, + "dependencies": { + "p-locate": "^5.0.0" + }, + "engines": { + "node": ">=10" + }, + "funding": { + "url": "https://github.com/sponsors/sindresorhus" + } + }, + "node_modules/lodash": { + "version": "4.17.21", + "resolved": "https://registry.npmjs.org/lodash/-/lodash-4.17.21.tgz", + "integrity": "sha512-v2kDEe57lecTulaDIuNTPy3Ry4gLGJ6Z1O3vE1krgXZNrsQ+LFTGHVxVjcXPs17LhbZVGedAJv8XZ1tvj5FvSg==" + }, + "node_modules/lodash.merge": { + "version": "4.6.2", + "resolved": "https://registry.npmjs.org/lodash.merge/-/lodash.merge-4.6.2.tgz", + "integrity": "sha512-0KpjqXRVvrYyCsX1swR/XTK0va6VQkQM6MNo7PqW77ByjAhoARA8EfrP1N4+KlKj8YS0ZUCtRT/YUuhyYDujIQ==", + "dev": true + }, + "node_modules/loglevel": { + "version": "1.9.1", + "resolved": "https://registry.npmjs.org/loglevel/-/loglevel-1.9.1.tgz", + "integrity": "sha512-hP3I3kCrDIMuRwAwHltphhDM1r8i55H33GgqjXbrisuJhF4kRhW1dNuxsRklp4bXl8DSdLaNLuiL4A/LWRfxvg==", + "dev": true, + "engines": { + "node": ">= 0.6.0" + }, + "funding": { + "type": "tidelift", + "url": "https://tidelift.com/funding/github/npm/loglevel" + } + }, + "node_modules/loglevel-colored-level-prefix": { + "version": "1.0.0", + "resolved": "https://registry.npmjs.org/loglevel-colored-level-prefix/-/loglevel-colored-level-prefix-1.0.0.tgz", + "integrity": "sha512-u45Wcxxc+SdAlh4yeF/uKlC1SPUPCy0gullSNKXod5I4bmifzk+Q4lSLExNEVn19tGaJipbZ4V4jbFn79/6mVA==", + "dev": true, + "dependencies": { + "chalk": "^1.1.3", + "loglevel": "^1.4.1" + } + }, + "node_modules/loglevel-colored-level-prefix/node_modules/ansi-regex": { + "version": "2.1.1", + "resolved": "https://registry.npmjs.org/ansi-regex/-/ansi-regex-2.1.1.tgz", + "integrity": "sha512-TIGnTpdo+E3+pCyAluZvtED5p5wCqLdezCyhPZzKPcxvFplEt4i+W7OONCKgeZFT3+y5NZZfOOS/Bdcanm1MYA==", + "dev": true, + "engines": { + "node": ">=0.10.0" + } + }, + "node_modules/loglevel-colored-level-prefix/node_modules/ansi-styles": { + "version": "2.2.1", + "resolved": "https://registry.npmjs.org/ansi-styles/-/ansi-styles-2.2.1.tgz", + "integrity": "sha512-kmCevFghRiWM7HB5zTPULl4r9bVFSWjz62MhqizDGUrq2NWuNMQyuv4tHHoKJHs69M/MF64lEcHdYIocrdWQYA==", + "dev": true, + "engines": { + "node": ">=0.10.0" + } + }, + "node_modules/loglevel-colored-level-prefix/node_modules/chalk": { + "version": "1.1.3", + "resolved": "https://registry.npmjs.org/chalk/-/chalk-1.1.3.tgz", + "integrity": "sha512-U3lRVLMSlsCfjqYPbLyVv11M9CPW4I728d6TCKMAOJueEeB9/8o+eSsMnxPJD+Q+K909sdESg7C+tIkoH6on1A==", + "dev": true, + "dependencies": { + "ansi-styles": "^2.2.1", + "escape-string-regexp": "^1.0.2", + "has-ansi": "^2.0.0", + "strip-ansi": "^3.0.0", + "supports-color": "^2.0.0" + }, + "engines": { + "node": ">=0.10.0" + } + }, + "node_modules/loglevel-colored-level-prefix/node_modules/escape-string-regexp": { + "version": "1.0.5", + "resolved": "https://registry.npmjs.org/escape-string-regexp/-/escape-string-regexp-1.0.5.tgz", + "integrity": "sha512-vbRorB5FUQWvla16U8R/qgaFIya2qGzwDrNmCZuYKrbdSUMG6I1ZCGQRefkRVhuOkIGVne7BQ35DSfo1qvJqFg==", + "dev": true, + "engines": { + "node": ">=0.8.0" + } + }, + "node_modules/loglevel-colored-level-prefix/node_modules/strip-ansi": { + "version": "3.0.1", + "resolved": "https://registry.npmjs.org/strip-ansi/-/strip-ansi-3.0.1.tgz", + "integrity": "sha512-VhumSSbBqDTP8p2ZLKj40UjBCV4+v8bUSEpUb4KjRgWk9pbqGF4REFj6KEagidb2f/M6AzC0EmFyDNGaw9OCzg==", + "dev": true, + "dependencies": { + "ansi-regex": "^2.0.0" + }, + "engines": { + "node": ">=0.10.0" + } + }, + "node_modules/loglevel-colored-level-prefix/node_modules/supports-color": { + "version": "2.0.0", + "resolved": "https://registry.npmjs.org/supports-color/-/supports-color-2.0.0.tgz", + "integrity": "sha512-KKNVtd6pCYgPIKU4cp2733HWYCpplQhddZLBUryaAHou723x+FRzQ5Df824Fj+IyyuiQTRoub4SnIFfIcrp70g==", + "dev": true, + "engines": { + "node": ">=0.8.0" + } + }, + "node_modules/long": { + "version": "5.2.3", + "resolved": "https://registry.npmjs.org/long/-/long-5.2.3.tgz", + "integrity": "sha512-lcHwpNoggQTObv5apGNCTdJrO69eHOZMi4BNC+rTLER8iHAqGrUVeLh/irVIM7zTw2bOXA8T6uNPeujwOLg/2Q==" + }, + "node_modules/lru-cache": { + "version": "6.0.0", + "resolved": "https://registry.npmjs.org/lru-cache/-/lru-cache-6.0.0.tgz", + "integrity": "sha512-Jo6dJ04CmSjuznwJSS3pUeWmd/H0ffTlkXXgwZi+eq1UCmqQwCh+eLsYOYCwY991i2Fah4h1BEMCx4qThGbsiA==", + "dev": true, + "dependencies": { + "yallist": "^4.0.0" + }, + "engines": { + "node": ">=10" + } + }, + "node_modules/merge2": { + "version": "1.4.1", + "resolved": "https://registry.npmjs.org/merge2/-/merge2-1.4.1.tgz", + "integrity": "sha512-8q7VEgMJW4J8tcfVPy8g09NcQwZdbwFEqhe/WZkoIzjn/3TGDwtOCYtXGxA3O8tPzpczCCDgv+P2P5y00ZJOOg==", + "dev": true, + "engines": { + "node": ">= 8" + } + }, + "node_modules/micromatch": { + "version": "4.0.5", + "resolved": "https://registry.npmjs.org/micromatch/-/micromatch-4.0.5.tgz", + "integrity": "sha512-DMy+ERcEW2q8Z2Po+WNXuw3c5YaUSFjAO5GsJqfEl7UjvtIuFKO6ZrKvcItdy98dwFI2N1tg3zNIdKaQT+aNdA==", + "dev": true, + "dependencies": { + "braces": "^3.0.2", + "picomatch": "^2.3.1" + }, + "engines": { + "node": ">=8.6" + } + }, + "node_modules/min-indent": { + "version": "1.0.1", + "resolved": "https://registry.npmjs.org/min-indent/-/min-indent-1.0.1.tgz", + "integrity": "sha512-I9jwMn07Sy/IwOj3zVkVik2JTvgpaykDZEigL6Rx6N9LbMywwUSMtxET+7lVoDLLd3O3IXwJwvuuns8UB/HeAg==", + "dev": true, + "engines": { + "node": ">=4" + } + }, + "node_modules/minimatch": { + "version": "3.1.2", + "resolved": "https://registry.npmjs.org/minimatch/-/minimatch-3.1.2.tgz", + "integrity": "sha512-J7p63hRiAjw1NDEww1W7i37+ByIrOWO5XQQAzZ3VOcL0PNybwpfmV/N05zFAzwQ9USyEcX6t3UO+K5aqBQOIHw==", + "dev": true, + "dependencies": { + "brace-expansion": "^1.1.7" + }, + "engines": { + "node": "*" + } + }, + "node_modules/ms": { + "version": "2.1.2", + "resolved": "https://registry.npmjs.org/ms/-/ms-2.1.2.tgz", + "integrity": "sha512-sGkPx+VjMtmA6MX27oA4FBFELFCZZ4S4XqeGOXCv68tT+jb3vk/RyaKWP0PTKyWtmLSM0b+adUTEvbs1PEaH2w==" + }, + "node_modules/natural-compare": { + "version": "1.4.0", + "resolved": "https://registry.npmjs.org/natural-compare/-/natural-compare-1.4.0.tgz", + "integrity": "sha512-OWND8ei3VtNC9h7V60qff3SVobHr996CTwgxubgyQYEpg290h9J0buyECNNJexkFm5sOajh5G116RYA1c8ZMSw==", + "dev": true + }, + "node_modules/node-fetch": { + "version": "2.7.0", + "resolved": "https://registry.npmjs.org/node-fetch/-/node-fetch-2.7.0.tgz", + "integrity": "sha512-c4FRfUm/dbcWZ7U+1Wq0AwCyFL+3nt2bEw05wfxSz+DWpWsitgmSgYmy2dQdWyKC1694ELPqMs/YzUSNozLt8A==", + "dependencies": { + "whatwg-url": "^5.0.0" + }, + "engines": { + "node": "4.x || >=6.0.0" + }, + "peerDependencies": { + "encoding": "^0.1.0" + }, + "peerDependenciesMeta": { + "encoding": { + "optional": true + } + } + }, + "node_modules/node-forge": { + "version": "1.3.1", + "resolved": "https://registry.npmjs.org/node-forge/-/node-forge-1.3.1.tgz", + "integrity": "sha512-dPEtOeMvF9VMcYV/1Wb8CPoVAXtp6MKMlcbAt4ddqmGqUJ6fQZFXkNZNkNlfevtNkGtaSoXf/vNNNSvgrdXwtA==", + "engines": { + "node": ">= 6.13.0" + } + }, + "node_modules/node-gyp-build": { + "version": "4.8.0", + "resolved": "https://registry.npmjs.org/node-gyp-build/-/node-gyp-build-4.8.0.tgz", + "integrity": "sha512-u6fs2AEUljNho3EYTJNBfImO5QTo/J/1Etd+NVdCj7qWKUSN/bSLkZwhDv7I+w/MSC6qJ4cknepkAYykDdK8og==", + "optional": true, + "bin": { + "node-gyp-build": "bin.js", + "node-gyp-build-optional": "optional.js", + "node-gyp-build-test": "build-test.js" + } + }, + "node_modules/node-jose": { + "version": "2.2.0", + "resolved": "https://registry.npmjs.org/node-jose/-/node-jose-2.2.0.tgz", + "integrity": "sha512-XPCvJRr94SjLrSIm4pbYHKLEaOsDvJCpyFw/6V/KK/IXmyZ6SFBzAUDO9HQf4DB/nTEFcRGH87mNciOP23kFjw==", + "dependencies": { + "base64url": "^3.0.1", + "buffer": "^6.0.3", + "es6-promise": "^4.2.8", + "lodash": "^4.17.21", + "long": "^5.2.0", + "node-forge": "^1.2.1", + "pako": "^2.0.4", + "process": "^0.11.10", + "uuid": "^9.0.0" + } + }, + "node_modules/node-releases": { + "version": "2.0.14", + "resolved": "https://registry.npmjs.org/node-releases/-/node-releases-2.0.14.tgz", + "integrity": "sha512-y10wOWt8yZpqXmOgRo77WaHEmhYQYGNA6y421PKsKYWEK8aW+cqAphborZDhqfyKrbZEN92CN1X2KbafY2s7Yw==", + "dev": true + }, + "node_modules/normalize-package-data": { + "version": "2.5.0", + "resolved": "https://registry.npmjs.org/normalize-package-data/-/normalize-package-data-2.5.0.tgz", + "integrity": "sha512-/5CMN3T0R4XTj4DcGaexo+roZSdSFW/0AOOTROrjxzCG1wrWXEsGbRKevjlIL+ZDE4sZlJr5ED4YW0yqmkK+eA==", + "dev": true, + "dependencies": { + "hosted-git-info": "^2.1.4", + "resolve": "^1.10.0", + "semver": "2 || 3 || 4 || 5", + "validate-npm-package-license": "^3.0.1" + } + }, + "node_modules/normalize-package-data/node_modules/semver": { + "version": "5.7.2", + "resolved": "https://registry.npmjs.org/semver/-/semver-5.7.2.tgz", + "integrity": "sha512-cBznnQ9KjJqU67B52RMC65CMarK2600WFnbkcaiwWq3xy/5haFJlshgnpjovMVJ+Hff49d8GEn0b87C5pDQ10g==", + "dev": true, + "bin": { + "semver": "bin/semver" + } + }, + "node_modules/once": { + "version": "1.4.0", + "resolved": "https://registry.npmjs.org/once/-/once-1.4.0.tgz", + "integrity": "sha512-lNaJgI+2Q5URQBkccEKHTQOPaXdUxnZZElQTZY0MFUAuaEqe1E+Nyvgdz/aIyNi6Z9MzO5dv1H8n58/GELp3+w==", + "dev": true, + "dependencies": { + "wrappy": "1" + } + }, + "node_modules/optionator": { + "version": "0.9.3", + "resolved": "https://registry.npmjs.org/optionator/-/optionator-0.9.3.tgz", + "integrity": "sha512-JjCoypp+jKn1ttEFExxhetCKeJt9zhAgAve5FXHixTvFDW/5aEktX9bufBKLRRMdU7bNtpLfcGu94B3cdEJgjg==", + "dev": true, + "dependencies": { + "@aashutoshrathi/word-wrap": "^1.2.3", + "deep-is": "^0.1.3", + "fast-levenshtein": "^2.0.6", + "levn": "^0.4.1", + "prelude-ls": "^1.2.1", + "type-check": "^0.4.0" + }, + "engines": { + "node": ">= 0.8.0" + } + }, + "node_modules/p-limit": { + "version": "3.1.0", + "resolved": "https://registry.npmjs.org/p-limit/-/p-limit-3.1.0.tgz", + "integrity": "sha512-TYOanM3wGwNGsZN2cVTYPArw454xnXj5qmWF1bEoAc4+cU/ol7GVh7odevjp1FNHduHc3KZMcFduxU5Xc6uJRQ==", + "dev": true, + "dependencies": { + "yocto-queue": "^0.1.0" + }, + "engines": { + "node": ">=10" + }, + "funding": { + "url": "https://github.com/sponsors/sindresorhus" + } + }, + "node_modules/p-locate": { + "version": "5.0.0", + "resolved": "https://registry.npmjs.org/p-locate/-/p-locate-5.0.0.tgz", + "integrity": "sha512-LaNjtRWUBY++zB5nE/NwcaoMylSPk+S+ZHNB1TzdbMJMny6dynpAGt7X/tl/QYq3TIeE6nxHppbo2LGymrG5Pw==", + "dev": true, + "dependencies": { + "p-limit": "^3.0.2" + }, + "engines": { + "node": ">=10" + }, + "funding": { + "url": "https://github.com/sponsors/sindresorhus" + } + }, + "node_modules/p-try": { + "version": "2.2.0", + "resolved": "https://registry.npmjs.org/p-try/-/p-try-2.2.0.tgz", + "integrity": "sha512-R4nPAVTAU0B9D35/Gk3uJf/7XYbQcyohSKdvAxIRSNghFl4e71hVoGnBNQz9cWaXxO2I10KTC+3jMdvvoKw6dQ==", + "dev": true, + "engines": { + "node": ">=6" + } + }, + "node_modules/pako": { + "version": "2.1.0", + "resolved": "https://registry.npmjs.org/pako/-/pako-2.1.0.tgz", + "integrity": "sha512-w+eufiZ1WuJYgPXbV/PO3NCMEc3xqylkKHzp8bxp1uW4qaSNQUkwmLLEc3kKsfz8lpV1F8Ht3U1Cm+9Srog2ug==" + }, + "node_modules/parent-module": { + "version": "1.0.1", + "resolved": "https://registry.npmjs.org/parent-module/-/parent-module-1.0.1.tgz", + "integrity": "sha512-GQ2EWRpQV8/o+Aw8YqtfZZPfNRWZYkbidE9k5rpl/hC3vtHHBfGm2Ifi6qWV+coDGkrUKZAxE3Lot5kcsRlh+g==", + "dev": true, + "dependencies": { + "callsites": "^3.0.0" + }, + "engines": { + "node": ">=6" + } + }, + "node_modules/parse-json": { + "version": "5.2.0", + "resolved": "https://registry.npmjs.org/parse-json/-/parse-json-5.2.0.tgz", + "integrity": "sha512-ayCKvm/phCGxOkYRSCM82iDwct8/EonSEgCSxWxD7ve6jHggsFl4fZVQBPRNgQoKiuV/odhFrGzQXZwbifC8Rg==", + "dev": true, + "dependencies": { + "@babel/code-frame": "^7.0.0", + "error-ex": "^1.3.1", + "json-parse-even-better-errors": "^2.3.0", + "lines-and-columns": "^1.1.6" + }, + "engines": { + "node": ">=8" + }, + "funding": { + "url": "https://github.com/sponsors/sindresorhus" + } + }, + "node_modules/path-exists": { + "version": "4.0.0", + "resolved": "https://registry.npmjs.org/path-exists/-/path-exists-4.0.0.tgz", + "integrity": "sha512-ak9Qy5Q7jYb2Wwcey5Fpvg2KoAc/ZIhLSLOSBmRmygPsGwkVVt0fZa0qrtMz+m6tJTAHfZQ8FnmB4MG4LWy7/w==", + "dev": true, + "engines": { + "node": ">=8" + } + }, + "node_modules/path-is-absolute": { + "version": "1.0.1", + "resolved": "https://registry.npmjs.org/path-is-absolute/-/path-is-absolute-1.0.1.tgz", + "integrity": "sha512-AVbw3UJ2e9bq64vSaS9Am0fje1Pa8pbGqTTsmXfaIiMpnr5DlDhfJOuLj9Sf95ZPVDAUerDfEk88MPmPe7UCQg==", + "dev": true, + "engines": { + "node": ">=0.10.0" + } + }, + "node_modules/path-key": { + "version": "3.1.1", + "resolved": "https://registry.npmjs.org/path-key/-/path-key-3.1.1.tgz", + "integrity": "sha512-ojmeN0qd+y0jszEtoY48r0Peq5dwMEkIlCOu6Q5f41lfkswXuKtYrhgoTpLnyIcHm24Uhqx+5Tqm2InSwLhE6Q==", + "dev": true, + "engines": { + "node": ">=8" + } + }, + "node_modules/path-parse": { + "version": "1.0.7", + "resolved": "https://registry.npmjs.org/path-parse/-/path-parse-1.0.7.tgz", + "integrity": "sha512-LDJzPVEEEPR+y48z93A0Ed0yXb8pAByGWo/k5YYdYgpY2/2EsOsksJrq7lOHxryrVOn1ejG6oAp8ahvOIQD8sw==", + "dev": true + }, + "node_modules/path-type": { + "version": "4.0.0", + "resolved": "https://registry.npmjs.org/path-type/-/path-type-4.0.0.tgz", + "integrity": "sha512-gDKb8aZMDeD/tZWs9P6+q0J9Mwkdl6xMV8TjnGP3qJVJ06bdMgkbBlLU8IdfOsIsFz2BW1rNVT3XuNEl8zPAvw==", + "dev": true, + "engines": { + "node": ">=8" + } + }, + "node_modules/picocolors": { + "version": "1.0.0", + "resolved": "https://registry.npmjs.org/picocolors/-/picocolors-1.0.0.tgz", + "integrity": "sha512-1fygroTLlHu66zi26VoTDv8yRgm0Fccecssto+MhsZ0D/DGW2sm8E8AjW7NU5VVTRt5GxbeZ5qBuJr+HyLYkjQ==", + "dev": true + }, + "node_modules/picomatch": { + "version": "2.3.1", + "resolved": "https://registry.npmjs.org/picomatch/-/picomatch-2.3.1.tgz", + "integrity": "sha512-JU3teHTNjmE2VCGFzuY8EXzCDVwEqB2a8fsIvwaStHhAWJEeVd1o1QD80CU6+ZdEXXSLbSsuLwJjkCBWqRQUVA==", + "dev": true, + "engines": { + "node": ">=8.6" + }, + "funding": { + "url": "https://github.com/sponsors/jonschlinkert" + } + }, + "node_modules/pluralize": { + "version": "8.0.0", + "resolved": "https://registry.npmjs.org/pluralize/-/pluralize-8.0.0.tgz", + "integrity": "sha512-Nc3IT5yHzflTfbjgqWcCPpo7DaKy4FnpB0l/zCAW0Tc7jxAiuqSxHasntB3D7887LSrA93kDJ9IXovxJYxyLCA==", + "dev": true, + "engines": { + "node": ">=4" + } + }, + "node_modules/prelude-ls": { + "version": "1.2.1", + "resolved": "https://registry.npmjs.org/prelude-ls/-/prelude-ls-1.2.1.tgz", + "integrity": "sha512-vkcDPrRZo1QZLbn5RLGPpg/WmIQ65qoWWhcGKf/b5eplkkarX0m9z8ppCat4mlOqUsWpyNuYgO3VRyrYHSzX5g==", + "dev": true, + "engines": { + "node": ">= 0.8.0" + } + }, + "node_modules/prettier": { + "version": "3.0.3", + "resolved": "https://registry.npmjs.org/prettier/-/prettier-3.0.3.tgz", + "integrity": "sha512-L/4pUDMxcNa8R/EthV08Zt42WBO4h1rarVtK0K+QJG0X187OLo7l699jWw0GKuwzkPQ//jMFA/8Xm6Fh3J/DAg==", + "dev": true, + "bin": { + "prettier": "bin/prettier.cjs" + }, + "engines": { + "node": ">=14" + }, + "funding": { + "url": "https://github.com/prettier/prettier?sponsor=1" + } + }, + "node_modules/prettier-eslint": { + "version": "16.3.0", + "resolved": "https://registry.npmjs.org/prettier-eslint/-/prettier-eslint-16.3.0.tgz", + "integrity": "sha512-Lh102TIFCr11PJKUMQ2kwNmxGhTsv/KzUg9QYF2Gkw259g/kPgndZDWavk7/ycbRvj2oz4BPZ1gCU8bhfZH/Xg==", + "dev": true, + "dependencies": { + "@typescript-eslint/parser": "^6.7.5", + "common-tags": "^1.4.0", + "dlv": "^1.1.0", + "eslint": "^8.7.0", + "indent-string": "^4.0.0", + "lodash.merge": "^4.6.0", + "loglevel-colored-level-prefix": "^1.0.0", + "prettier": "^3.0.1", + "pretty-format": "^29.7.0", + "require-relative": "^0.8.7", + "typescript": "^5.2.2", + "vue-eslint-parser": "^9.1.0" + }, + "engines": { + "node": ">=16.10.0" + }, + "peerDependencies": { + "prettier-plugin-svelte": "^3.0.0", + "svelte-eslint-parser": "*" + }, + "peerDependenciesMeta": { + "prettier-plugin-svelte": { + "optional": true + }, + "svelte-eslint-parser": { + "optional": true + } + } + }, + "node_modules/prettier-linter-helpers": { + "version": "1.0.0", + "resolved": "https://registry.npmjs.org/prettier-linter-helpers/-/prettier-linter-helpers-1.0.0.tgz", + "integrity": "sha512-GbK2cP9nraSSUF9N2XwUwqfzlAFlMNYYl+ShE/V+H8a9uNl/oUqB1w2EL54Jh0OlyRSd8RfWYJ3coVS4TROP2w==", + "dev": true, + "dependencies": { + "fast-diff": "^1.1.2" + }, + "engines": { + "node": ">=6.0.0" + } + }, + "node_modules/pretty-format": { + "version": "29.7.0", + "resolved": "https://registry.npmjs.org/pretty-format/-/pretty-format-29.7.0.tgz", + "integrity": "sha512-Pdlw/oPxN+aXdmM9R00JVC9WVFoCLTKJvDVLgmJ+qAffBMxsV85l/Lu7sNx4zSzPyoL2euImuEwHhOXdEgNFZQ==", + "dev": true, + "dependencies": { + "@jest/schemas": "^29.6.3", + "ansi-styles": "^5.0.0", + "react-is": "^18.0.0" + }, + "engines": { + "node": "^14.15.0 || ^16.10.0 || >=18.0.0" + } + }, + "node_modules/pretty-format/node_modules/ansi-styles": { + "version": "5.2.0", + "resolved": "https://registry.npmjs.org/ansi-styles/-/ansi-styles-5.2.0.tgz", + "integrity": "sha512-Cxwpt2SfTzTtXcfOlzGEee8O+c+MmUgGrNiBcXnuWxuFJHe6a5Hz7qwhwe5OgaSYI0IJvkLqWX1ASG+cJOkEiA==", + "dev": true, + "engines": { + "node": ">=10" + }, + "funding": { + "url": "https://github.com/chalk/ansi-styles?sponsor=1" + } + }, + "node_modules/process": { + "version": "0.11.10", + "resolved": "https://registry.npmjs.org/process/-/process-0.11.10.tgz", + "integrity": "sha512-cdGef/drWFoydD1JsMzuFf8100nZl+GT+yacc2bEced5f9Rjk4z+WtFUTBu9PhOi9j/jfmBPu0mMEY4wIdAF8A==", + "engines": { + "node": ">= 0.6.0" + } + }, + "node_modules/punycode": { + "version": "2.3.1", + "resolved": "https://registry.npmjs.org/punycode/-/punycode-2.3.1.tgz", + "integrity": "sha512-vYt7UD1U9Wg6138shLtLOvdAu+8DsC/ilFtEVHcH+wydcSpNE20AfSOduf6MkRFahL5FY7X1oU7nKVZFtfq8Fg==", + "dev": true, + "engines": { + "node": ">=6" + } + }, + "node_modules/queue-microtask": { + "version": "1.2.3", + "resolved": "https://registry.npmjs.org/queue-microtask/-/queue-microtask-1.2.3.tgz", + "integrity": "sha512-NuaNSa6flKT5JaSYQzJok04JzTL1CA6aGhv5rfLW3PgqA+M2ChpZQnAC8h8i4ZFkBS8X5RqkDBHA7r4hej3K9A==", + "dev": true, + "funding": [ + { + "type": "github", + "url": "https://github.com/sponsors/feross" + }, + { + "type": "patreon", + "url": "https://www.patreon.com/feross" + }, + { + "type": "consulting", + "url": "https://feross.org/support" + } + ] + }, + "node_modules/react-is": { + "version": "18.2.0", + "resolved": "https://registry.npmjs.org/react-is/-/react-is-18.2.0.tgz", + "integrity": "sha512-xWGDIW6x921xtzPkhiULtthJHoJvBbF3q26fzloPCK0hsvxtPVelvftw3zjbHWSkR2km9Z+4uxbDDK/6Zw9B8w==", + "dev": true + }, + "node_modules/read-pkg": { + "version": "5.2.0", + "resolved": "https://registry.npmjs.org/read-pkg/-/read-pkg-5.2.0.tgz", + "integrity": "sha512-Ug69mNOpfvKDAc2Q8DRpMjjzdtrnv9HcSMX+4VsZxD1aZ6ZzrIE7rlzXBtWTyhULSMKg076AW6WR5iZpD0JiOg==", + "dev": true, + "dependencies": { + "@types/normalize-package-data": "^2.4.0", + "normalize-package-data": "^2.5.0", + "parse-json": "^5.0.0", + "type-fest": "^0.6.0" + }, + "engines": { + "node": ">=8" + } + }, + "node_modules/read-pkg-up": { + "version": "7.0.1", + "resolved": "https://registry.npmjs.org/read-pkg-up/-/read-pkg-up-7.0.1.tgz", + "integrity": "sha512-zK0TB7Xd6JpCLmlLmufqykGE+/TlOePD6qKClNW7hHDKFh/J7/7gCWGR7joEQEW1bKq3a3yUZSObOoWLFQ4ohg==", + "dev": true, + "dependencies": { + "find-up": "^4.1.0", + "read-pkg": "^5.2.0", + "type-fest": "^0.8.1" + }, + "engines": { + "node": ">=8" + }, + "funding": { + "url": "https://github.com/sponsors/sindresorhus" + } + }, + "node_modules/read-pkg-up/node_modules/find-up": { + "version": "4.1.0", + "resolved": "https://registry.npmjs.org/find-up/-/find-up-4.1.0.tgz", + "integrity": "sha512-PpOwAdQ/YlXQ2vj8a3h8IipDuYRi3wceVQQGYWxNINccq40Anw7BlsEXCMbt1Zt+OLA6Fq9suIpIWD0OsnISlw==", + "dev": true, + "dependencies": { + "locate-path": "^5.0.0", + "path-exists": "^4.0.0" + }, + "engines": { + "node": ">=8" + } + }, + "node_modules/read-pkg-up/node_modules/locate-path": { + "version": "5.0.0", + "resolved": "https://registry.npmjs.org/locate-path/-/locate-path-5.0.0.tgz", + "integrity": "sha512-t7hw9pI+WvuwNJXwk5zVHpyhIqzg2qTlklJOf0mVxGSbe3Fp2VieZcduNYjaLDoy6p9uGpQEGWG87WpMKlNq8g==", + "dev": true, + "dependencies": { + "p-locate": "^4.1.0" + }, + "engines": { + "node": ">=8" + } + }, + "node_modules/read-pkg-up/node_modules/p-limit": { + "version": "2.3.0", + "resolved": "https://registry.npmjs.org/p-limit/-/p-limit-2.3.0.tgz", + "integrity": "sha512-//88mFWSJx8lxCzwdAABTJL2MyWB12+eIY7MDL2SqLmAkeKU9qxRvWuSyTjm3FUmpBEMuFfckAIqEaVGUDxb6w==", + "dev": true, + "dependencies": { + "p-try": "^2.0.0" + }, + "engines": { + "node": ">=6" + }, + "funding": { + "url": "https://github.com/sponsors/sindresorhus" + } + }, + "node_modules/read-pkg-up/node_modules/p-locate": { + "version": "4.1.0", + "resolved": "https://registry.npmjs.org/p-locate/-/p-locate-4.1.0.tgz", + "integrity": "sha512-R79ZZ/0wAxKGu3oYMlz8jy/kbhsNrS7SKZ7PxEHBgJ5+F2mtFW2fK2cOtBh1cHYkQsbzFV7I+EoRKe6Yt0oK7A==", + "dev": true, + "dependencies": { + "p-limit": "^2.2.0" + }, + "engines": { + "node": ">=8" + } + }, + "node_modules/read-pkg-up/node_modules/type-fest": { + "version": "0.8.1", + "resolved": "https://registry.npmjs.org/type-fest/-/type-fest-0.8.1.tgz", + "integrity": "sha512-4dbzIzqvjtgiM5rw1k5rEHtBANKmdudhGyBEajN01fEyhaAIhsoKNy6y7+IN93IfpFtwY9iqi7kD+xwKhQsNJA==", + "dev": true, + "engines": { + "node": ">=8" + } + }, + "node_modules/read-pkg/node_modules/type-fest": { + "version": "0.6.0", + "resolved": "https://registry.npmjs.org/type-fest/-/type-fest-0.6.0.tgz", + "integrity": "sha512-q+MB8nYR1KDLrgr4G5yemftpMC7/QLqVndBmEEdqzmNj5dcFOO4Oo8qlwZE3ULT3+Zim1F8Kq4cBnikNhlCMlg==", + "dev": true, + "engines": { + "node": ">=8" + } + }, + "node_modules/regenerator-runtime": { + "version": "0.14.1", + "resolved": "https://registry.npmjs.org/regenerator-runtime/-/regenerator-runtime-0.14.1.tgz", + "integrity": "sha512-dYnhHh0nJoMfnkZs6GmmhFknAGRrLznOu5nc9ML+EJxGvrx6H7teuevqVqCuPcPK//3eDrrjQhehXVx9cnkGdw==" + }, + "node_modules/regexp-tree": { + "version": "0.1.27", + "resolved": "https://registry.npmjs.org/regexp-tree/-/regexp-tree-0.1.27.tgz", + "integrity": "sha512-iETxpjK6YoRWJG5o6hXLwvjYAoW+FEZn9os0PD/b6AP6xQwsa/Y7lCVgIixBbUPMfhu+i2LtdeAqVTgGlQarfA==", + "dev": true, + "bin": { + "regexp-tree": "bin/regexp-tree" + } + }, + "node_modules/regexpp": { + "version": "3.2.0", + "resolved": "https://registry.npmjs.org/regexpp/-/regexpp-3.2.0.tgz", + "integrity": "sha512-pq2bWo9mVD43nbts2wGv17XLiNLya+GklZ8kaDLV2Z08gDCsGpnKn9BFMepvWuHCbyVvY7J5o5+BVvoQbmlJLg==", + "dev": true, + "engines": { + "node": ">=8" + }, + "funding": { + "url": "https://github.com/sponsors/mysticatea" + } + }, + "node_modules/regjsparser": { + "version": "0.10.0", + "resolved": "https://registry.npmjs.org/regjsparser/-/regjsparser-0.10.0.tgz", + "integrity": "sha512-qx+xQGZVsy55CH0a1hiVwHmqjLryfh7wQyF5HO07XJ9f7dQMY/gPQHhlyDkIzJKC+x2fUCpCcUODUUUFrm7SHA==", + "dev": true, + "dependencies": { + "jsesc": "~0.5.0" + }, + "bin": { + "regjsparser": "bin/parser" + } + }, + "node_modules/regjsparser/node_modules/jsesc": { + "version": "0.5.0", + "resolved": "https://registry.npmjs.org/jsesc/-/jsesc-0.5.0.tgz", + "integrity": "sha512-uZz5UnB7u4T9LvwmFqXii7pZSouaRPorGs5who1Ip7VO0wxanFvBL7GkM6dTHlgX+jhBApRetaWpnDabOeTcnA==", + "dev": true, + "bin": { + "jsesc": "bin/jsesc" + } + }, + "node_modules/require-relative": { + "version": "0.8.7", + "resolved": "https://registry.npmjs.org/require-relative/-/require-relative-0.8.7.tgz", + "integrity": "sha512-AKGr4qvHiryxRb19m3PsLRGuKVAbJLUD7E6eOaHkfKhwc+vSgVOCY5xNvm9EkolBKTOf0GrQAZKLimOCz81Khg==", + "dev": true + }, + "node_modules/resolve": { + "version": "1.22.8", + "resolved": "https://registry.npmjs.org/resolve/-/resolve-1.22.8.tgz", + "integrity": "sha512-oKWePCxqpd6FlLvGV1VU0x7bkPmmCNolxzjMf4NczoDnQcIWrAF+cPtZn5i6n+RfD2d9i0tzpKnG6Yk168yIyw==", + "dev": true, + "dependencies": { + "is-core-module": "^2.13.0", + "path-parse": "^1.0.7", + "supports-preserve-symlinks-flag": "^1.0.0" + }, + "bin": { + "resolve": "bin/resolve" + }, + "funding": { + "url": "https://github.com/sponsors/ljharb" + } + }, + "node_modules/resolve-from": { + "version": "4.0.0", + "resolved": "https://registry.npmjs.org/resolve-from/-/resolve-from-4.0.0.tgz", + "integrity": "sha512-pb/MYmXstAkysRFx8piNI1tGFNQIFA3vkE3Gq4EuA1dF6gHp/+vgZqsCGJapvy8N3Q+4o7FwvquPJcnZ7RYy4g==", + "dev": true, + "engines": { + "node": ">=4" + } + }, + "node_modules/reusify": { + "version": "1.0.4", + "resolved": "https://registry.npmjs.org/reusify/-/reusify-1.0.4.tgz", + "integrity": "sha512-U9nH88a3fc/ekCF1l0/UP1IosiuIjyTh7hBvXVMHYgVcfGvt897Xguj2UOLDeI5BG2m7/uwyaLVT6fbtCwTyzw==", + "dev": true, + "engines": { + "iojs": ">=1.0.0", + "node": ">=0.10.0" + } + }, + "node_modules/rimraf": { + "version": "3.0.2", + "resolved": "https://registry.npmjs.org/rimraf/-/rimraf-3.0.2.tgz", + "integrity": "sha512-JZkJMZkAGFFPP2YqXZXPbMlMBgsxzE8ILs4lMIX/2o0L9UBw9O/Y3o6wFw/i9YLapcUJWwqbi3kdxIPdC62TIA==", + "dev": true, + "dependencies": { + "glob": "^7.1.3" + }, + "bin": { + "rimraf": "bin.js" + }, + "funding": { + "url": "https://github.com/sponsors/isaacs" + } + }, + "node_modules/rpc-websockets": { + "version": "7.9.0", + "resolved": "https://registry.npmjs.org/rpc-websockets/-/rpc-websockets-7.9.0.tgz", + "integrity": "sha512-DwKewQz1IUA5wfLvgM8wDpPRcr+nWSxuFxx5CbrI2z/MyyZ4nXLM86TvIA+cI1ZAdqC8JIBR1mZR55dzaLU+Hw==", + "dependencies": { + "@babel/runtime": "^7.17.2", + "eventemitter3": "^4.0.7", + "uuid": "^8.3.2", + "ws": "^8.5.0" + }, + "funding": { + "type": "paypal", + "url": "https://paypal.me/kozjak" + }, + "optionalDependencies": { + "bufferutil": "^4.0.1", + "utf-8-validate": "^5.0.2" + } + }, + "node_modules/rpc-websockets/node_modules/uuid": { + "version": "8.3.2", + "resolved": "https://registry.npmjs.org/uuid/-/uuid-8.3.2.tgz", + "integrity": "sha512-+NYs2QeMWy+GWFOEm9xnn6HCDp0l7QBD7ml8zLUmJ+93Q5NF0NocErnwkTkXVFNiX3/fpC6afS8Dhb/gz7R7eg==", + "bin": { + "uuid": "dist/bin/uuid" + } + }, + "node_modules/rpc-websockets/node_modules/ws": { + "version": "8.16.0", + "resolved": "https://registry.npmjs.org/ws/-/ws-8.16.0.tgz", + "integrity": "sha512-HS0c//TP7Ina87TfiPUz1rQzMhHrl/SG2guqRcTOIUYD2q8uhUdNHZYJUaQ8aTGPzCh+c6oawMKW35nFl1dxyQ==", + "engines": { + "node": ">=10.0.0" + }, + "peerDependencies": { + "bufferutil": "^4.0.1", + "utf-8-validate": ">=5.0.2" + }, + "peerDependenciesMeta": { + "bufferutil": { + "optional": true + }, + "utf-8-validate": { + "optional": true + } + } + }, + "node_modules/run-parallel": { + "version": "1.2.0", + "resolved": "https://registry.npmjs.org/run-parallel/-/run-parallel-1.2.0.tgz", + "integrity": "sha512-5l4VyZR86LZ/lDxZTR6jqL8AFE2S0IFLMP26AbjsLVADxHdhB/c0GUsH+y39UfCi3dzz8OlQuPmnaJOMoDHQBA==", + "dev": true, + "funding": [ + { + "type": "github", + "url": "https://github.com/sponsors/feross" + }, + { + "type": "patreon", + "url": "https://www.patreon.com/feross" + }, + { + "type": "consulting", + "url": "https://feross.org/support" + } + ], + "dependencies": { + "queue-microtask": "^1.2.2" + } + }, + "node_modules/safe-buffer": { + "version": "5.2.1", + "resolved": "https://registry.npmjs.org/safe-buffer/-/safe-buffer-5.2.1.tgz", + "integrity": "sha512-rp3So07KcdmmKbGvgaNxQSJr7bGVSVk5S9Eq1F+ppbRo70+YeaDxkw5Dd8NPN+GD6bjnYm2VuPuCXmpuYvmCXQ==", + "funding": [ + { + "type": "github", + "url": "https://github.com/sponsors/feross" + }, + { + "type": "patreon", + "url": "https://www.patreon.com/feross" + }, + { + "type": "consulting", + "url": "https://feross.org/support" + } + ] + }, + "node_modules/semver": { + "version": "7.5.4", + "resolved": "https://registry.npmjs.org/semver/-/semver-7.5.4.tgz", + "integrity": "sha512-1bCSESV6Pv+i21Hvpxp3Dx+pSD8lIPt8uVjRrxAUt/nbswYc+tK6Y2btiULjd4+fnq15PX+nqQDC7Oft7WkwcA==", + "dev": true, + "dependencies": { + "lru-cache": "^6.0.0" + }, + "bin": { + "semver": "bin/semver.js" + }, + "engines": { + "node": ">=10" + } + }, + "node_modules/shebang-command": { + "version": "2.0.0", + "resolved": "https://registry.npmjs.org/shebang-command/-/shebang-command-2.0.0.tgz", + "integrity": "sha512-kHxr2zZpYtdmrN1qDjrrX/Z1rR1kG8Dx+gkpK1G4eXmvXswmcE1hTWBWYUzlraYw1/yZp6YuDY77YtvbN0dmDA==", + "dev": true, + "dependencies": { + "shebang-regex": "^3.0.0" + }, + "engines": { + "node": ">=8" + } + }, + "node_modules/shebang-regex": { + "version": "3.0.0", + "resolved": "https://registry.npmjs.org/shebang-regex/-/shebang-regex-3.0.0.tgz", + "integrity": "sha512-7++dFhtcx3353uBaq8DDR4NuxBetBzC7ZQOhmTQInHEd6bSrXdiEyzCvG07Z44UYdLShWUyXt5M/yhz8ekcb1A==", + "dev": true, + "engines": { + "node": ">=8" + } + }, + "node_modules/slash": { + "version": "3.0.0", + "resolved": "https://registry.npmjs.org/slash/-/slash-3.0.0.tgz", + "integrity": "sha512-g9Q1haeby36OSStwb4ntCGGGaKsaVSjQ68fBxoQcutl5fS1vuY18H3wSt3jFyFtrkx+Kz0V1G85A4MyAdDMi2Q==", + "dev": true, + "engines": { + "node": ">=8" + } + }, + "node_modules/spdx-correct": { + "version": "3.2.0", + "resolved": "https://registry.npmjs.org/spdx-correct/-/spdx-correct-3.2.0.tgz", + "integrity": "sha512-kN9dJbvnySHULIluDHy32WHRUu3Og7B9sbY7tsFLctQkIqnMh3hErYgdMjTYuqmcXX+lK5T1lnUt3G7zNswmZA==", + "dev": true, + "dependencies": { + "spdx-expression-parse": "^3.0.0", + "spdx-license-ids": "^3.0.0" + } + }, + "node_modules/spdx-exceptions": { + "version": "2.5.0", + "resolved": "https://registry.npmjs.org/spdx-exceptions/-/spdx-exceptions-2.5.0.tgz", + "integrity": "sha512-PiU42r+xO4UbUS1buo3LPJkjlO7430Xn5SVAhdpzzsPHsjbYVflnnFdATgabnLude+Cqu25p6N+g2lw/PFsa4w==", + "dev": true + }, + "node_modules/spdx-expression-parse": { + "version": "3.0.1", + "resolved": "https://registry.npmjs.org/spdx-expression-parse/-/spdx-expression-parse-3.0.1.tgz", + "integrity": "sha512-cbqHunsQWnJNE6KhVSMsMeH5H/L9EpymbzqTQ3uLwNCLZ1Q481oWaofqH7nO6V07xlXwY6PhQdQ2IedWx/ZK4Q==", + "dev": true, + "dependencies": { + "spdx-exceptions": "^2.1.0", + "spdx-license-ids": "^3.0.0" + } + }, + "node_modules/spdx-license-ids": { + "version": "3.0.17", + "resolved": "https://registry.npmjs.org/spdx-license-ids/-/spdx-license-ids-3.0.17.tgz", + "integrity": "sha512-sh8PWc/ftMqAAdFiBu6Fy6JUOYjqDJBJvIhpfDMyHrr0Rbp5liZqd4TjtQ/RgfLjKFZb+LMx5hpml5qOWy0qvg==", + "dev": true + }, + "node_modules/strip-ansi": { + "version": "6.0.1", + "resolved": "https://registry.npmjs.org/strip-ansi/-/strip-ansi-6.0.1.tgz", + "integrity": "sha512-Y38VPSHcqkFrCpFnQ9vuSXmquuv5oXOKpGeT6aGrr3o3Gc9AlVa6JBfUSOCnbxGGZF+/0ooI7KrPuUSztUdU5A==", + "dev": true, + "dependencies": { + "ansi-regex": "^5.0.1" + }, + "engines": { + "node": ">=8" + } + }, + "node_modules/strip-indent": { + "version": "3.0.0", + "resolved": "https://registry.npmjs.org/strip-indent/-/strip-indent-3.0.0.tgz", + "integrity": "sha512-laJTa3Jb+VQpaC6DseHhF7dXVqHTfJPCRDaEbid/drOhgitgYku/letMUqOXFoWV0zIIUbjpdH2t+tYj4bQMRQ==", + "dev": true, + "dependencies": { + "min-indent": "^1.0.0" + }, + "engines": { + "node": ">=8" + } + }, + "node_modules/strip-json-comments": { + "version": "3.1.1", + "resolved": "https://registry.npmjs.org/strip-json-comments/-/strip-json-comments-3.1.1.tgz", + "integrity": "sha512-6fPc+R4ihwqP6N/aIv2f1gMH8lOVtWQHoqC4yK6oSDVVocumAsfCqjkXnqiYMhmMwS/mEHLp7Vehlt3ql6lEig==", + "dev": true, + "engines": { + "node": ">=8" + }, + "funding": { + "url": "https://github.com/sponsors/sindresorhus" + } + }, + "node_modules/superstruct": { + "version": "0.14.2", + "resolved": "https://registry.npmjs.org/superstruct/-/superstruct-0.14.2.tgz", + "integrity": "sha512-nPewA6m9mR3d6k7WkZ8N8zpTWfenFH3q9pA2PkuiZxINr9DKB2+40wEQf0ixn8VaGuJ78AB6iWOtStI+/4FKZQ==" + }, + "node_modules/supports-color": { + "version": "7.2.0", + "resolved": "https://registry.npmjs.org/supports-color/-/supports-color-7.2.0.tgz", + "integrity": "sha512-qpCAvRl9stuOHveKsn7HncJRvv501qIacKzQlO/+Lwxc9+0q2wLyv4Dfvt80/DPn2pqOBsJdDiogXGR9+OvwRw==", + "dev": true, + "dependencies": { + "has-flag": "^4.0.0" + }, + "engines": { + "node": ">=8" + } + }, + "node_modules/supports-preserve-symlinks-flag": { + "version": "1.0.0", + "resolved": "https://registry.npmjs.org/supports-preserve-symlinks-flag/-/supports-preserve-symlinks-flag-1.0.0.tgz", + "integrity": "sha512-ot0WnXS9fgdkgIcePe6RHNk1WA8+muPa6cSjeR3V8K27q9BB1rTE3R1p7Hv0z1ZyAc8s6Vvv8DIyWf681MAt0w==", + "dev": true, + "engines": { + "node": ">= 0.4" + }, + "funding": { + "url": "https://github.com/sponsors/ljharb" + } + }, + "node_modules/synckit": { + "version": "0.8.8", + "resolved": "https://registry.npmjs.org/synckit/-/synckit-0.8.8.tgz", + "integrity": "sha512-HwOKAP7Wc5aRGYdKH+dw0PRRpbO841v2DENBtjnR5HFWoiNByAl7vrx3p0G/rCyYXQsrxqtX48TImFtPcIHSpQ==", + "dev": true, + "dependencies": { + "@pkgr/core": "^0.1.0", + "tslib": "^2.6.2" + }, + "engines": { + "node": "^14.18.0 || >=16.0.0" + }, + "funding": { + "url": "https://opencollective.com/unts" + } + }, + "node_modules/text-encoding-utf-8": { + "version": "1.0.2", + "resolved": "https://registry.npmjs.org/text-encoding-utf-8/-/text-encoding-utf-8-1.0.2.tgz", + "integrity": "sha512-8bw4MY9WjdsD2aMtO0OzOCY3pXGYNx2d2FfHRVUKkiCPDWjKuOlhLVASS+pD7VkLTVjW268LYJHwsnPFlBpbAg==" + }, + "node_modules/text-table": { + "version": "0.2.0", + "resolved": "https://registry.npmjs.org/text-table/-/text-table-0.2.0.tgz", + "integrity": "sha512-N+8UisAXDGk8PFXP4HAzVR9nbfmVJ3zYLAWiTIoqC5v5isinhr+r5uaO8+7r3BMfuNIufIsA7RdpVgacC2cSpw==", + "dev": true + }, + "node_modules/through": { + "version": "2.3.8", + "resolved": "https://registry.npmjs.org/through/-/through-2.3.8.tgz", + "integrity": "sha512-w89qg7PI8wAdvX60bMDP+bFoD5Dvhm9oLheFp5O4a2QF0cSBGsBX4qZmadPMvVqlLJBBci+WqGGOAPvcDeNSVg==" + }, + "node_modules/to-regex-range": { + "version": "5.0.1", + "resolved": "https://registry.npmjs.org/to-regex-range/-/to-regex-range-5.0.1.tgz", + "integrity": "sha512-65P7iz6X5yEr1cwcgvQxbbIw7Uk3gOy5dIdtZ4rDveLqhrdJP+Li/Hx6tyK0NEb+2GCyneCMJiGqrADCSNk8sQ==", + "dev": true, + "dependencies": { + "is-number": "^7.0.0" + }, + "engines": { + "node": ">=8.0" + } + }, + "node_modules/tr46": { + "version": "0.0.3", + "resolved": "https://registry.npmjs.org/tr46/-/tr46-0.0.3.tgz", + "integrity": "sha512-N3WMsuqV66lT30CrXNbEjx4GEwlow3v6rr4mCcv6prnfwhS01rkgyFdjPNBYd9br7LpXV1+Emh01fHnq2Gdgrw==" + }, + "node_modules/ts-api-utils": { + "version": "1.0.3", + "resolved": "https://registry.npmjs.org/ts-api-utils/-/ts-api-utils-1.0.3.tgz", + "integrity": "sha512-wNMeqtMz5NtwpT/UZGY5alT+VoKdSsOOP/kqHFcUW1P/VRhH2wJ48+DN2WwUliNbQ976ETwDL0Ifd2VVvgonvg==", + "dev": true, + "engines": { + "node": ">=16.13.0" + }, + "peerDependencies": { + "typescript": ">=4.2.0" + } + }, + "node_modules/tslib": { + "version": "2.6.2", + "resolved": "https://registry.npmjs.org/tslib/-/tslib-2.6.2.tgz", + "integrity": "sha512-AEYxH93jGFPn/a2iVAwW87VuUIkR1FVUKB77NwMF7nBTDkDrrT/Hpt/IrCJ0QXhW27jTBDcf5ZY7w6RiqTMw2Q==", + "dev": true + }, + "node_modules/type-check": { + "version": "0.4.0", + "resolved": "https://registry.npmjs.org/type-check/-/type-check-0.4.0.tgz", + "integrity": "sha512-XleUoc9uwGXqjWwXaUTZAmzMcFZ5858QA2vvx1Ur5xIcixXIP+8LnFDgRplU30us6teqdlskFfu+ae4K79Ooew==", + "dev": true, + "dependencies": { + "prelude-ls": "^1.2.1" + }, + "engines": { + "node": ">= 0.8.0" + } + }, + "node_modules/type-fest": { + "version": "0.20.2", + "resolved": "https://registry.npmjs.org/type-fest/-/type-fest-0.20.2.tgz", + "integrity": "sha512-Ne+eE4r0/iWnpAxD852z3A+N0Bt5RN//NjJwRd2VFHEmrywxf5vsZlh4R6lixl6B+wz/8d+maTSAkN1FIkI3LQ==", + "dev": true, + "engines": { + "node": ">=10" + }, + "funding": { + "url": "https://github.com/sponsors/sindresorhus" + } + }, + "node_modules/typescript": { + "version": "5.2.2", + "resolved": "https://registry.npmjs.org/typescript/-/typescript-5.2.2.tgz", + "integrity": "sha512-mI4WrpHsbCIcwT9cF4FZvr80QUeKvsUsUvKDoR+X/7XHQH98xYD8YHZg7ANtz2GtZt/CBq2QJ0thkGJMHfqc1w==", + "dev": true, + "bin": { + "tsc": "bin/tsc", + "tsserver": "bin/tsserver" + }, + "engines": { + "node": ">=14.17" + } + }, + "node_modules/undici-types": { + "version": "5.26.5", + "resolved": "https://registry.npmjs.org/undici-types/-/undici-types-5.26.5.tgz", + "integrity": "sha512-JlCMO+ehdEIKqlFxk6IfVoAUVmgz7cU7zD/h9XZ0qzeosSHmUJVOzSQvvYSYWXkFXC+IfLKSIffhv0sVZup6pA==" + }, + "node_modules/update-browserslist-db": { + "version": "1.0.13", + "resolved": "https://registry.npmjs.org/update-browserslist-db/-/update-browserslist-db-1.0.13.tgz", + "integrity": "sha512-xebP81SNcPuNpPP3uzeW1NYXxI3rxyJzF3pD6sH4jE7o/IX+WtSpwnVU+qIsDPyk0d3hmFQ7mjqc6AtV604hbg==", + "dev": true, + "funding": [ + { + "type": "opencollective", + "url": "https://opencollective.com/browserslist" + }, + { + "type": "tidelift", + "url": "https://tidelift.com/funding/github/npm/browserslist" + }, + { + "type": "github", + "url": "https://github.com/sponsors/ai" + } + ], + "dependencies": { + "escalade": "^3.1.1", + "picocolors": "^1.0.0" + }, + "bin": { + "update-browserslist-db": "cli.js" + }, + "peerDependencies": { + "browserslist": ">= 4.21.0" + } + }, + "node_modules/uri-js": { + "version": "4.4.1", + "resolved": "https://registry.npmjs.org/uri-js/-/uri-js-4.4.1.tgz", + "integrity": "sha512-7rKUyy33Q1yc98pQ1DAmLtwX109F7TIfWlW1Ydo8Wl1ii1SeHieeh0HHfPeL2fMXK6z0s8ecKs9frCuLJvndBg==", + "dev": true, + "dependencies": { + "punycode": "^2.1.0" + } + }, + "node_modules/utf-8-validate": { + "version": "5.0.10", + "resolved": "https://registry.npmjs.org/utf-8-validate/-/utf-8-validate-5.0.10.tgz", + "integrity": "sha512-Z6czzLq4u8fPOyx7TU6X3dvUZVvoJmxSQ+IcrlmagKhilxlhZgxPK6C5Jqbkw1IDUmFTM+cz9QDnnLTwDz/2gQ==", + "hasInstallScript": true, + "optional": true, + "dependencies": { + "node-gyp-build": "^4.3.0" + }, + "engines": { + "node": ">=6.14.2" + } + }, + "node_modules/uuid": { + "version": "9.0.0", + "resolved": "https://registry.npmjs.org/uuid/-/uuid-9.0.0.tgz", + "integrity": "sha512-MXcSTerfPa4uqyzStbRoTgt5XIe3x5+42+q1sDuy3R5MDk66URdLMOZe5aPX/SQd+kuYAh0FdP/pO28IkQyTeg==", + "bin": { + "uuid": "dist/bin/uuid" + } + }, + "node_modules/validate-npm-package-license": { + "version": "3.0.4", + "resolved": "https://registry.npmjs.org/validate-npm-package-license/-/validate-npm-package-license-3.0.4.tgz", + "integrity": "sha512-DpKm2Ui/xN7/HQKCtpZxoRWBhZ9Z0kqtygG8XCgNQ8ZlDnxuQmWhj566j8fN4Cu3/JmbhsDo7fcAJq4s9h27Ew==", + "dev": true, + "dependencies": { + "spdx-correct": "^3.0.0", + "spdx-expression-parse": "^3.0.0" + } + }, + "node_modules/vue-eslint-parser": { + "version": "9.4.2", + "resolved": "https://registry.npmjs.org/vue-eslint-parser/-/vue-eslint-parser-9.4.2.tgz", + "integrity": "sha512-Ry9oiGmCAK91HrKMtCrKFWmSFWvYkpGglCeFAIqDdr9zdXmMMpJOmUJS7WWsW7fX81h6mwHmUZCQQ1E0PkSwYQ==", + "dev": true, + "dependencies": { + "debug": "^4.3.4", + "eslint-scope": "^7.1.1", + "eslint-visitor-keys": "^3.3.0", + "espree": "^9.3.1", + "esquery": "^1.4.0", + "lodash": "^4.17.21", + "semver": "^7.3.6" + }, + "engines": { + "node": "^14.17.0 || >=16.0.0" + }, + "funding": { + "url": "https://github.com/sponsors/mysticatea" + }, + "peerDependencies": { + "eslint": ">=6.0.0" + } + }, + "node_modules/webidl-conversions": { + "version": "3.0.1", + "resolved": "https://registry.npmjs.org/webidl-conversions/-/webidl-conversions-3.0.1.tgz", + "integrity": "sha512-2JAn3z8AR6rjK8Sm8orRC0h/bcl/DqL7tRPdGZ4I1CjdF+EaMLmYxBHyXuKL849eucPFhvBoxMsflfOb8kxaeQ==" + }, + "node_modules/whatwg-url": { + "version": "5.0.0", + "resolved": "https://registry.npmjs.org/whatwg-url/-/whatwg-url-5.0.0.tgz", + "integrity": "sha512-saE57nupxk6v3HY35+jzBwYa0rKSy0XR8JSxZPwgLr7ys0IBzhGviA1/TUGJLmSVqs8pb9AnvICXEuOHLprYTw==", + "dependencies": { + "tr46": "~0.0.3", + "webidl-conversions": "^3.0.0" + } + }, + "node_modules/which": { + "version": "2.0.2", + "resolved": "https://registry.npmjs.org/which/-/which-2.0.2.tgz", + "integrity": "sha512-BLI3Tl1TW3Pvl70l3yq3Y64i+awpwXqsGBYWkkqMtnbXgrMD+yj7rhW0kuEDxzJaYXGjEW5ogapKNMEKNMjibA==", + "dev": true, + "dependencies": { + "isexe": "^2.0.0" + }, + "bin": { + "node-which": "bin/node-which" + }, + "engines": { + "node": ">= 8" + } + }, + "node_modules/wrappy": { + "version": "1.0.2", + "resolved": "https://registry.npmjs.org/wrappy/-/wrappy-1.0.2.tgz", + "integrity": "sha512-l4Sp/DRseor9wL6EvV2+TuQn63dMkPjZ/sp9XkghTEbV9KlPS1xUsZ3u7/IQO4wxtcFB4bgpQPRcR3QCvezPcQ==", + "dev": true + }, + "node_modules/ws": { + "version": "7.5.9", + "resolved": "https://registry.npmjs.org/ws/-/ws-7.5.9.tgz", + "integrity": "sha512-F+P9Jil7UiSKSkppIiD94dN07AwvFixvLIj1Og1Rl9GGMuNipJnV9JzjD6XuqmAeiswGvUmNLjr5cFuXwNS77Q==", + "engines": { + "node": ">=8.3.0" + }, + "peerDependencies": { + "bufferutil": "^4.0.1", + "utf-8-validate": "^5.0.2" + }, + "peerDependenciesMeta": { + "bufferutil": { + "optional": true + }, + "utf-8-validate": { + "optional": true + } + } + }, + "node_modules/yallist": { + "version": "4.0.0", + "resolved": "https://registry.npmjs.org/yallist/-/yallist-4.0.0.tgz", + "integrity": "sha512-3wdGidZyq5PB084XLES5TpOSRA3wjXAlIWMhum2kRcv/41Sn2emQ0dycQW4uZXLejwKvg6EsvbdlVL+FYEct7A==", + "dev": true + }, + "node_modules/yocto-queue": { + "version": "0.1.0", + "resolved": "https://registry.npmjs.org/yocto-queue/-/yocto-queue-0.1.0.tgz", + "integrity": "sha512-rVksvsnNCdJ/ohGc6xgPwyN8eheCxsiLM8mxuE/t/mOVqJewPuO1miLpTHQiRgTKCLexL4MeAFVagts7HmNZ2Q==", + "dev": true, + "engines": { + "node": ">=10" + }, + "funding": { + "url": "https://github.com/sponsors/sindresorhus" + } + } + } +} diff --git a/package.json b/package.json new file mode 100644 index 0000000..cd46379 --- /dev/null +++ b/package.json @@ -0,0 +1,46 @@ +{ + "name": "@coinbase/staking-client-library-ts", + "version": "0.5.1", + "description": "Coinbase Staking API Typescript Library", + "repository": "https://github.com/coinbase/staking-client-library-ts.git", + "license": "Apache-2.0", + "scripts": { + "clean": "rimraf ./dist", + "build": "npm run clean && tsc", + "prepare": "npm run build", + "lint": "eslint . --ext .ts --ignore-pattern '/dist/*/*'", + "lint-fix": "eslint . --ext .ts --fix --ignore-pattern '/dist/*/*'" + }, + "publishConfig": { + "access": "public", + "provenance": true + }, + "files": [ + "src/", + "dist/" + ], + "dependencies": { + "@ethereumjs/tx": "^5.1.0", + "@solana/web3.js": "^1.91.3", + "bs58": "^5.0.0", + "node-jose": "^2.2.0" + }, + "devDependencies": { + "@types/node": "^20.11.0", + "@types/node-jose": "^1.1.10", + "@typescript-eslint/eslint-plugin": "^6.7.0", + "@typescript-eslint/parser": "^6.7.0", + "eslint": "^8.49.0", + "eslint-config-prettier": "^9.0.0", + "eslint-plugin-filename-rules": "^1.3.1", + "eslint-plugin-node": "^11.1.0", + "eslint-plugin-prettier": "^5.1.3", + "eslint-plugin-unicorn": "^51.0.1", + "prettier": "^3.0.3", + "prettier-eslint": "^16.0.0", + "rimraf": "^3.0.2", + "typescript": "^5.2.2" + }, + "main": "dist/index.js", + "types": "dist/index.d.ts" +} diff --git a/src/auth/index.ts b/src/auth/index.ts new file mode 100644 index 0000000..3b22df7 --- /dev/null +++ b/src/auth/index.ts @@ -0,0 +1,176 @@ +import { readFileSync } from 'fs'; +import { JWK, JWS } from 'node-jose'; + +const legacyPemHeader = '-----BEGIN ECDSA Private Key-----'; +const legacyPemFooter = '-----END ECDSA Private Key-----'; +const pemHeader = '-----BEGIN EC PRIVATE KEY-----'; +const pemFooter = '-----END EC PRIVATE KEY-----'; + +/** + * Build a JWT for the specified service and URI. + * @param service The name of the service. + * @param uri The URI for which the JWT is to be generated. + * @returns The generated JWT. + */ +export const buildJWT = async ( + url: string, + method = 'GET', +): Promise => { + const keyFile = readFileSync('.coinbase_cloud_api_key.json', { + encoding: 'utf8', + }); + const apiKey: APIKey = JSON.parse(keyFile); + + const pemPrivateKey = extractPemKey(apiKey.privateKey); + let privateKey: JWK.Key; + + try { + privateKey = await JWK.asKey(pemPrivateKey, 'pem'); + if (privateKey.kty !== 'EC') { + throw new Error('Not an EC private key'); + } + } catch (error) { + throw new Error(`jwt: Could not decode or parse private key. ${error}`); + } + + const header = { + alg: 'ES256', + kid: apiKey.name, + typ: 'JWT', + nonce: nonce(), + }; + + const audience = getAudience(url); + const uri = `${method} ${url.substring(8)}`; + + const claims: APIKeyClaims = { + sub: apiKey.name, + iss: 'coinbase-cloud', + nbf: Math.floor(Date.now() / 1000), + exp: Math.floor(Date.now() / 1000) + 60, // +1 minute + aud: [audience], + uri, + }; + + const payload = Buffer.from(JSON.stringify(claims)).toString('utf8'); + + try { + const result = await JWS.createSign( + { format: 'compact', fields: header }, + privateKey, + ) + .update(payload) + .final(); + + return result as unknown as string; + } catch (err) { + throw new Error(`jwt: Failed to sign JWT. ${err}`); + } +}; + +/** + * Represents the API key details. + */ +interface APIKey { + /** The name of the API key. */ + name: string; + /** The private key string. */ + privateKey: string; +} + +/** + * Represents the claims included in the JWT. + */ +interface APIKeyClaims { + /** Audience of the JWT. */ + aud: string[]; + /** Subject of the JWT. Typically the identifier of the API key. */ + sub: string; + /** Expiry time of the JWT in seconds. */ + exp: number; + /** Time before which the JWT is not valid in seconds. */ + nbf: number; + /** Issuer of the JWT. */ + iss: string; + /** URI claim for the JWT. */ + uri: string; +} + +/** + * Extracts the PEM key from the given string. + * @param privateKeyString The string for the private key from which to extract the PEM key. + * @returns The extracted PEM key body. + */ +const extractPemKey = (privateKeyString: string): string => { + // Remove all newline characters + privateKeyString = privateKeyString.replace(/\n/g, ''); + + // If the string starts with the standard PEM header and footer, return as is. + if ( + privateKeyString.startsWith(pemHeader) && + privateKeyString.endsWith(pemFooter) + ) { + return privateKeyString; + } + + // If the string starts with the legacy header and footer, replace them. + const regex = new RegExp( + `^${legacyPemHeader}([\\s\\S]+?)${legacyPemFooter}$`, + ); + + const match = privateKeyString.match(regex); + + if (match && match[1]) { + return pemHeader + match[1].trim() + pemFooter; + } + + // The string does not match any of the expected formats. + throw new Error('wrong format of API private key'); +}; + +/** + * Generates a nonce of 16 numeric characters. + * @returns The generated nonce. + */ +const nonce = (): string => { + const range = '0123456789'; + let result = ''; + + for (let i = 0; i < 16; i++) { + result += range.charAt(Math.floor(Math.random() * range.length)); + } + + return result; +}; + +const getAudience = (url: string): string => { + if (url.indexOf('staking') > -1) { + return 'staking'; + } else if (url.indexOf('rewards') > -1) { + return 'rewards-reporting'; + } else { + return 'unknown'; + } +}; + +export const customFetch = async ( + input: RequestInfo | URL, + init?: RequestInit | undefined, +): Promise => { + // remove query parameters + let url = input.toString(); + + if (url.indexOf('?') > -1) { + url = url.substring(0, url.indexOf('?')); + } + const token = await buildJWT(url, init?.method); + const params = { + ...init, + headers: { + ...init?.headers, + Authorization: `Bearer ${token}`, + }, + }; + + return fetch(input, params); +}; diff --git a/src/client/protocols/cosmos-staking.ts b/src/client/protocols/cosmos-staking.ts new file mode 100644 index 0000000..d1f9197 --- /dev/null +++ b/src/client/protocols/cosmos-staking.ts @@ -0,0 +1,47 @@ +import { StakingClient } from '../staking-client'; +import { + ListRewardsRequest, + ListRewardsResponse, +} from '../../gen/coinbase/staking/rewards/v1/reward.pb'; +import { + ListStakesRequest, + ListStakesResponse, +} from '../../gen/coinbase/staking/rewards/v1/stake.pb'; + +export class Cosmos { + private parent: StakingClient; + + constructor(parent: StakingClient) { + this.parent = parent; + } + + async listRewards( + filter: string, + pageSize: number = 100, + pageToken?: string, + ): Promise { + const req: ListRewardsRequest = { + parent: 'protocols/cosmos', + filter: filter, + pageSize: pageSize, + pageToken: pageToken, + }; + + return this.parent.listRewards('cosmos', req); + } + + async listStakes( + filter: string, + pageSize: number = 100, + pageToken?: string, + ): Promise { + const req: ListStakesRequest = { + parent: 'protocols/cosmos', + filter: filter, + pageSize: pageSize, + pageToken: pageToken, + }; + + return this.parent.listStakes('cosmos', req); + } +} diff --git a/src/client/protocols/ethereum-kiln-staking.ts b/src/client/protocols/ethereum-kiln-staking.ts new file mode 100644 index 0000000..5ca7f65 --- /dev/null +++ b/src/client/protocols/ethereum-kiln-staking.ts @@ -0,0 +1,153 @@ +import { StakingClient } from '../staking-client'; +import { + CreateWorkflowRequest, + Workflow, +} from '../../gen/coinbase/staking/orchestration/v1/workflow.pb'; +import { + ViewStakingContextRequest, + ViewStakingContextResponse, +} from '../../gen/coinbase/staking/orchestration/v1/staking_context.pb'; +import { + ListRewardsRequest, + ListRewardsResponse, +} from '../../gen/coinbase/staking/rewards/v1/reward.pb'; +import { + ListStakesRequest, + ListStakesResponse, +} from '../../gen/coinbase/staking/rewards/v1/stake.pb'; + +export class Ethereum { + private parent: StakingClient; + + constructor(parent: StakingClient) { + this.parent = parent; + } + + async stake( + projectId: string, + network: string, + skipBroadcast: boolean = false, + stakerAddress: string, + integratorContractAddress: string, + amount: string, + ): Promise { + const req: CreateWorkflowRequest = { + parent: `projects/${projectId}`, + workflow: { + action: `protocols/ethereum_kiln/networks/${network}/actions/stake`, + skipBroadcast: skipBroadcast, + ethereumKilnStakingParameters: { + stakeParameters: { + stakerAddress: stakerAddress, + integratorContractAddress: integratorContractAddress, + amount: { + value: amount, + currency: 'ETH', + }, + }, + }, + }, + }; + + return this.parent.createWorkflow(projectId, req); + } + + async unstake( + projectId: string, + network: string, + skipBroadcast: boolean = false, + stakerAddress: string, + integratorContractAddress: string, + amount: string, + ): Promise { + const req: CreateWorkflowRequest = { + parent: `projects/${projectId}`, + workflow: { + action: `protocols/ethereum_kiln/networks/${network}/actions/unstake`, + skipBroadcast: skipBroadcast, + ethereumKilnStakingParameters: { + unstakeParameters: { + stakerAddress: stakerAddress, + integratorContractAddress: integratorContractAddress, + amount: { + value: amount, + currency: 'ETH', + }, + }, + }, + }, + }; + + return this.parent.createWorkflow(projectId, req); + } + + async claimStake( + projectId: string, + network: string, + skipBroadcast: boolean = false, + stakerAddress: string, + integratorContractAddress: string, + ): Promise { + const req: CreateWorkflowRequest = { + parent: `projects/${projectId}`, + workflow: { + action: `protocols/ethereum_kiln/networks/${network}/actions/claim_stake`, + skipBroadcast: skipBroadcast, + ethereumKilnStakingParameters: { + claimStakeParameters: { + stakerAddress: stakerAddress, + integratorContractAddress: integratorContractAddress, + }, + }, + }, + }; + + return this.parent.createWorkflow(projectId, req); + } + + async viewStakingContext( + address: string, + network: string, + integratorContractAddress: string, + ): Promise { + const req: ViewStakingContextRequest = { + address: address, + network: `protocols/ethereum_kiln/networks/${network}`, + ethereumKilnStakingContextParameters: { + integratorContractAddress: integratorContractAddress, + }, + }; + + return this.parent.viewStakingContext(req); + } + + async listRewards( + filter: string, + pageSize: number = 100, + pageToken?: string, + ): Promise { + const req: ListRewardsRequest = { + parent: 'protocols/ethereum', + filter: filter, + pageSize: pageSize, + pageToken: pageToken, + }; + + return this.parent.listRewards('ethereum', req); + } + + async listStakes( + filter: string, + pageSize: number = 100, + pageToken?: string, + ): Promise { + const req: ListStakesRequest = { + parent: 'protocols/ethereum', + filter: filter, + pageSize: pageSize, + pageToken: pageToken, + }; + + return this.parent.listStakes('ethereum', req); + } +} diff --git a/src/client/protocols/solana-staking.ts b/src/client/protocols/solana-staking.ts new file mode 100644 index 0000000..ca2dfc9 --- /dev/null +++ b/src/client/protocols/solana-staking.ts @@ -0,0 +1,149 @@ +import { StakingClient } from '../staking-client'; +import { + CreateWorkflowRequest, + Workflow, +} from '../../gen/coinbase/staking/orchestration/v1/workflow.pb'; +import { + ViewStakingContextRequest, + ViewStakingContextResponse, +} from '../../gen/coinbase/staking/orchestration/v1/staking_context.pb'; +import { + ListRewardsRequest, + ListRewardsResponse, +} from '../../gen/coinbase/staking/rewards/v1/reward.pb'; +import { + ListStakesRequest, + ListStakesResponse, +} from '../../gen/coinbase/staking/rewards/v1/stake.pb'; + +export class Solana { + private parent: StakingClient; + + constructor(parent: StakingClient) { + this.parent = parent; + } + + async stake( + projectId: string, + network: string, + skipBroadcast: boolean = false, + walletAddress: string, + validatorAddress: string, + amount: string, + ): Promise { + const req: CreateWorkflowRequest = { + parent: `projects/${projectId}`, + workflow: { + action: `protocols/solana/networks/${network}/actions/stake`, + skipBroadcast: skipBroadcast, + solanaStakingParameters: { + stakeParameters: { + walletAddress: walletAddress, + validatorAddress: validatorAddress, + amount: { + value: amount, + currency: 'SOL', + }, + }, + }, + }, + }; + + return this.parent.createWorkflow(projectId, req); + } + + async unstake( + projectId: string, + network: string, + skipBroadcast: boolean = false, + walletAddress: string, + stakeAccountAddress: string, + amount: string, + ): Promise { + const req: CreateWorkflowRequest = { + parent: `projects/${projectId}`, + workflow: { + action: `protocols/solana/networks/${network}/actions/unstake`, + skipBroadcast: skipBroadcast, + solanaStakingParameters: { + unstakeParameters: { + walletAddress: walletAddress, + stakeAccountAddress: stakeAccountAddress, + amount: { + value: amount, + currency: 'SOL', + }, + }, + }, + }, + }; + + return this.parent.createWorkflow(projectId, req); + } + + async claimStake( + projectId: string, + network: string, + skipBroadcast: boolean = false, + walletAddress: string, + stakeAccountAddress: string, + ): Promise { + const req: CreateWorkflowRequest = { + parent: `projects/${projectId}`, + workflow: { + action: `protocols/solana/networks/${network}/actions/claim_stake`, + skipBroadcast: skipBroadcast, + solanaStakingParameters: { + claimStakeParameters: { + walletAddress: walletAddress, + stakeAccountAddress: stakeAccountAddress, + }, + }, + }, + }; + + return this.parent.createWorkflow(projectId, req); + } + + async viewStakingContext( + address: string, + network: string, + ): Promise { + const req: ViewStakingContextRequest = { + address: address, + network: `protocols/solana/networks/${network}`, + }; + + return this.parent.viewStakingContext(req); + } + + async listRewards( + filter: string, + pageSize: number = 100, + pageToken?: string, + ): Promise { + const req: ListRewardsRequest = { + parent: 'protocols/solana', + filter: filter, + pageSize: pageSize, + pageToken: pageToken, + }; + + return this.parent.listRewards('solana', req); + } + + async listStakes( + filter: string, + pageSize: number = 100, + pageToken?: string, + ): Promise { + const req: ListStakesRequest = { + parent: 'protocols/solana', + filter: filter, + pageSize: pageSize, + pageToken: pageToken, + }; + + return this.parent.listStakes('solana', req); + } +} diff --git a/src/client/staking-client.ts b/src/client/staking-client.ts new file mode 100644 index 0000000..b5579e0 --- /dev/null +++ b/src/client/staking-client.ts @@ -0,0 +1,292 @@ +import { StakingService } from '../gen/coinbase/staking/orchestration/v1/api.pb'; +import { RewardService } from '../gen/coinbase/staking/rewards/v1/reward_service.pb'; +import { + ListProtocolsRequest, + ListProtocolsResponse, +} from '../gen/coinbase/staking/orchestration/v1/protocol.pb'; +import { + ListNetworksRequest, + ListNetworksResponse, +} from '../gen/coinbase/staking/orchestration/v1/network.pb'; +import { + ListActionsRequest, + ListActionsResponse, +} from '../gen/coinbase/staking/orchestration/v1/action.pb'; +import * as fm from '../gen/fetch.pb'; +import { buildJWT } from '../auth'; +import { + ViewStakingContextRequest, + ViewStakingContextResponse, +} from '../gen/coinbase/staking/orchestration/v1/staking_context.pb'; +import { + CreateWorkflowRequest, + GetWorkflowRequest, + ListWorkflowsRequest, + ListWorkflowsResponse, + PerformWorkflowStepRequest, + Workflow, + WorkflowState, + WorkflowStep, + TxStepOutput, + WaitStepOutput, +} from '../gen/coinbase/staking/orchestration/v1/workflow.pb'; +import { + ListRewardsRequest, + ListRewardsResponse, +} from '../gen/coinbase/staking/rewards/v1/reward.pb'; +import { + ListStakesRequest, + ListStakesResponse, +} from '../gen/coinbase/staking/rewards/v1/stake.pb'; + +import { Ethereum } from './protocols/ethereum-kiln-staking'; +import { Solana } from './protocols/solana-staking'; +import { Cosmos } from './protocols/cosmos-staking'; + +const DEFAULT_URL = 'https://api.developer.coinbase.com/staking'; + +export class StakingClient { + readonly baseURL: string; + readonly Ethereum: Ethereum; + readonly Solana: Solana; + readonly Cosmos: Cosmos; + + constructor(baseURL?: string) { + if (baseURL) { + this.baseURL = baseURL; + } else { + this.baseURL = DEFAULT_URL; + } + + this.Ethereum = new Ethereum(this); + this.Solana = new Solana(this); + this.Cosmos = new Cosmos(this); + } + + // List protocols supported by Staking API + async listProtocols(): Promise { + const path: string = '/v1/protocols'; + const method: string = 'GET'; + const url: string = this.baseURL + '/orchestration'; + + // Generate the JWT token and get the auth details as a initReq object. + const initReq = await getAuthDetails(url, path, method); + + const req: ListProtocolsRequest = {}; + + return StakingService.ListProtocols(req, initReq); + } + + // List networks supported by Staking API for a given protocol. + async listNetworks(protocol: string): Promise { + const parent: string = `protocols/${protocol}`; + const path: string = `/v1/${parent}/networks`; + const method: string = 'GET'; + const url: string = this.baseURL + '/orchestration'; + + // Generate the JWT token and get the auth details as a initReq object. + const initReq = await getAuthDetails(url, path, method); + + const req: ListNetworksRequest = { + parent: parent, + }; + + return StakingService.ListNetworks(req, initReq); + } + + // List actions supported by Staking API for a given protocol and network. + async listActions( + protocol: string, + network: string, + ): Promise { + const parent: string = `protocols/${protocol}/networks/${network}`; + const path: string = `/v1/${parent}/actions`; + const method: string = 'GET'; + const url: string = this.baseURL + '/orchestration'; + + // Generate the JWT token and get the auth details as a initReq object. + const initReq = await getAuthDetails(url, path, method); + + const req: ListActionsRequest = { + parent: parent, + }; + + return StakingService.ListActions(req, initReq); + } + + // Returns point-in-time context of staking data for an address. This function takes the entire req object as input. + // Use the protocol-specific helper functions like Ethereum.ViewStakingContext to view protocol and network + // specific staking context. + async viewStakingContext( + req: ViewStakingContextRequest, + ): Promise { + const path: string = '/v1/viewStakingContext:view'; + const method: string = 'GET'; + const url: string = this.baseURL + '/orchestration'; + + // Generate the JWT token and get the auth details as a initReq object. + const initReq = await getAuthDetails(url, path, method); + + return StakingService.ViewStakingContext(req, initReq); + } + + // Create a workflow under a given project. This function takes the entire req object as input. + // Use the protocol-specific helper functions like Ethereum.Stake to create a protocol and action specific workflow. + async createWorkflow( + projectId: string, + req: CreateWorkflowRequest, + ): Promise { + const parent: string = `projects/${projectId}`; + const path: string = `/v1/${parent}/workflows`; + const method: string = 'POST'; + const url: string = this.baseURL + '/orchestration'; + + // Generate the JWT token and get the auth details as a initReq object. + const initReq = await getAuthDetails(url, path, method); + + return StakingService.CreateWorkflow(req, initReq); + } + + // Get a workflow given its project and workflow id. + async getWorkflow(projectId: string, workflowId: string): Promise { + const parent: string = `projects/${projectId}`; + const name: string = `${parent}/workflows/${workflowId}`; + const path: string = `/v1/${name}`; + const method: string = 'GET'; + const url: string = this.baseURL + '/orchestration'; + + // Generate the JWT token and get the auth details as a initReq object. + const initReq = await getAuthDetails(url, path, method); + + const req: GetWorkflowRequest = { + name: name, + }; + + return StakingService.GetWorkflow(req, initReq); + } + + // Return back a signed tx or a broadcasted tx hash for a given workflow and step number. + async performWorkflowStep( + projectId: string, + workflowId: string, + stepIndex: number, + data: string, + ): Promise { + const parent: string = `projects/${projectId}`; + const name: string = `${parent}/workflows/${workflowId}`; + const path: string = `/v1/${name}/step`; + const method: string = 'POST'; + const url: string = this.baseURL + '/orchestration'; + + // Generate the JWT token and get the auth details as a initReq object. + const initReq = await getAuthDetails(url, path, method); + + const req: PerformWorkflowStepRequest = { + name: name, + step: stepIndex, + data, + }; + + return StakingService.PerformWorkflowStep(req, initReq); + } + + // List workflows for a given project. + async listWorkflows( + project: string, + pageSize: number = 100, + filter: string = '', + ): Promise { + const parent: string = `projects/${project}`; + const path: string = `/v1/${parent}/workflows`; + const method: string = 'GET'; + const url: string = this.baseURL + '/orchestration'; + + // Generate the JWT token and get the auth details as a initReq object. + const initReq = await getAuthDetails(url, path, method); + + const req: ListWorkflowsRequest = { + parent: parent, + pageSize: pageSize, + filter: filter, + }; + + return StakingService.ListWorkflows(req, initReq); + } + + // List onchain rewards of an address for a specific protocol, with optional filters for time range, aggregation period, and more. + async listRewards( + protocol: string, + req: ListRewardsRequest, + ): Promise { + const parent: string = `protocols/${protocol}`; + const path: string = `/v1/${parent}/rewards`; + const method: string = 'GET'; + const url: string = this.baseURL + '/rewards'; + + // Generate the JWT token and get the auth details as a initReq object. + const initReq = await getAuthDetails(url, path, method); + + return RewardService.ListRewards(req, initReq); + } + + // List staking activities for a given protocol. + async listStakes( + protocol: string, + req: ListStakesRequest, + ): Promise { + const parent: string = `protocols/${protocol}`; + const path: string = `/v1/${parent}/stakes`; + const method: string = 'GET'; + const url: string = this.baseURL + '/rewards'; + + // Generate the JWT token and get the auth details as a initReq object. + const initReq = await getAuthDetails(url, path, method); + + return RewardService.ListStakes(req, initReq); + } +} + +export function workflowHasFinished(workflow: Workflow): boolean { + return ( + workflow.state === WorkflowState.STATE_COMPLETED || + workflow.state === WorkflowState.STATE_FAILED + ); +} + +export function workflowWaitingForSigning(workflow: Workflow): boolean { + return workflow.state === WorkflowState.STATE_WAITING_FOR_SIGNING; +} + +export function workflowWaitingForExternalBroadcast( + workflow: Workflow, +): boolean { + return workflow.state === WorkflowState.STATE_WAITING_FOR_EXT_BROADCAST; +} + +export function isTxStepOutput( + step: WorkflowStep, +): step is WorkflowStep & { txStepOutput: TxStepOutput } { + return (step as WorkflowStep).txStepOutput !== undefined; +} + +export function isWaitStepOutput( + step: WorkflowStep, +): step is WorkflowStep & { waitStepOutput: WaitStepOutput } { + return (step as WorkflowStep).waitStepOutput !== undefined; +} + +async function getAuthDetails( + url: string, + path: string, + method: string, +): Promise { + // Generate the JWT token + const token = await buildJWT(url + path, method); + + return { + pathPrefix: url, + headers: { + Authorization: `Bearer ${token}`, + }, + }; +} diff --git a/src/gen/coinbase/staking/orchestration/v1/action.pb.ts b/src/gen/coinbase/staking/orchestration/v1/action.pb.ts new file mode 100644 index 0000000..23514f2 --- /dev/null +++ b/src/gen/coinbase/staking/orchestration/v1/action.pb.ts @@ -0,0 +1,16 @@ +/* eslint-disable */ +// @ts-nocheck +/* +* This file is a generated Typescript file for GRPC Gateway, DO NOT MODIFY +*/ +export type Action = { + name?: string +} + +export type ListActionsRequest = { + parent?: string +} + +export type ListActionsResponse = { + actions?: Action[] +} \ No newline at end of file diff --git a/src/gen/coinbase/staking/orchestration/v1/api.pb.ts b/src/gen/coinbase/staking/orchestration/v1/api.pb.ts new file mode 100644 index 0000000..5f524a3 --- /dev/null +++ b/src/gen/coinbase/staking/orchestration/v1/api.pb.ts @@ -0,0 +1,42 @@ +/* eslint-disable */ +// @ts-nocheck +/* +* This file is a generated Typescript file for GRPC Gateway, DO NOT MODIFY +*/ + +import * as fm from "../../../../fetch.pb" +import * as CoinbaseStakingOrchestrationV1Action from "./action.pb" +import * as CoinbaseStakingOrchestrationV1Network from "./network.pb" +import * as CoinbaseStakingOrchestrationV1Protocol from "./protocol.pb" +import * as CoinbaseStakingOrchestrationV1Staking_context from "./staking_context.pb" +import * as CoinbaseStakingOrchestrationV1Staking_target from "./staking_target.pb" +import * as CoinbaseStakingOrchestrationV1Workflow from "./workflow.pb" +export class StakingService { + static ListProtocols(req: CoinbaseStakingOrchestrationV1Protocol.ListProtocolsRequest, initReq?: fm.InitReq): Promise { + return fm.fetchReq(`/v1/protocols?${fm.renderURLSearchParams(req, [])}`, {...initReq, method: "GET"}) + } + static ListNetworks(req: CoinbaseStakingOrchestrationV1Network.ListNetworksRequest, initReq?: fm.InitReq): Promise { + return fm.fetchReq(`/v1/${req["parent"]}/networks?${fm.renderURLSearchParams(req, ["parent"])}`, {...initReq, method: "GET"}) + } + static ListStakingTargets(req: CoinbaseStakingOrchestrationV1Staking_target.ListStakingTargetsRequest, initReq?: fm.InitReq): Promise { + return fm.fetchReq(`/v1/${req["parent"]}/stakingTargets?${fm.renderURLSearchParams(req, ["parent"])}`, {...initReq, method: "GET"}) + } + static ListActions(req: CoinbaseStakingOrchestrationV1Action.ListActionsRequest, initReq?: fm.InitReq): Promise { + return fm.fetchReq(`/v1/${req["parent"]}/actions?${fm.renderURLSearchParams(req, ["parent"])}`, {...initReq, method: "GET"}) + } + static CreateWorkflow(req: CoinbaseStakingOrchestrationV1Workflow.CreateWorkflowRequest, initReq?: fm.InitReq): Promise { + return fm.fetchReq(`/v1/${req["parent"]}/workflows`, {...initReq, method: "POST", body: JSON.stringify(req["workflow"], fm.replacer)}) + } + static GetWorkflow(req: CoinbaseStakingOrchestrationV1Workflow.GetWorkflowRequest, initReq?: fm.InitReq): Promise { + return fm.fetchReq(`/v1/${req["name"]}?${fm.renderURLSearchParams(req, ["name"])}`, {...initReq, method: "GET"}) + } + static ListWorkflows(req: CoinbaseStakingOrchestrationV1Workflow.ListWorkflowsRequest, initReq?: fm.InitReq): Promise { + return fm.fetchReq(`/v1/${req["parent"]}/workflows?${fm.renderURLSearchParams(req, ["parent"])}`, {...initReq, method: "GET"}) + } + static PerformWorkflowStep(req: CoinbaseStakingOrchestrationV1Workflow.PerformWorkflowStepRequest, initReq?: fm.InitReq): Promise { + return fm.fetchReq(`/v1/${req["name"]}/step`, {...initReq, method: "POST", body: JSON.stringify(req, fm.replacer)}) + } + static ViewStakingContext(req: CoinbaseStakingOrchestrationV1Staking_context.ViewStakingContextRequest, initReq?: fm.InitReq): Promise { + return fm.fetchReq(`/v1/viewStakingContext:view?${fm.renderURLSearchParams(req, [])}`, {...initReq, method: "GET"}) + } +} \ No newline at end of file diff --git a/src/gen/coinbase/staking/orchestration/v1/common.pb.ts b/src/gen/coinbase/staking/orchestration/v1/common.pb.ts new file mode 100644 index 0000000..32edc2b --- /dev/null +++ b/src/gen/coinbase/staking/orchestration/v1/common.pb.ts @@ -0,0 +1,9 @@ +/* eslint-disable */ +// @ts-nocheck +/* +* This file is a generated Typescript file for GRPC Gateway, DO NOT MODIFY +*/ +export type Amount = { + value?: string + currency?: string +} \ No newline at end of file diff --git a/src/gen/coinbase/staking/orchestration/v1/ethereum_kiln.pb.ts b/src/gen/coinbase/staking/orchestration/v1/ethereum_kiln.pb.ts new file mode 100644 index 0000000..7ef1123 --- /dev/null +++ b/src/gen/coinbase/staking/orchestration/v1/ethereum_kiln.pb.ts @@ -0,0 +1,52 @@ +/* eslint-disable */ +// @ts-nocheck +/* +* This file is a generated Typescript file for GRPC Gateway, DO NOT MODIFY +*/ + +import * as CoinbaseStakingOrchestrationV1Common from "./common.pb" + +type Absent = { [k in Exclude]?: undefined }; +type OneOf = + | { [k in keyof T]?: undefined } + | ( + keyof T extends infer K ? + (K extends string & keyof T ? { [k in K]: T[K] } & Absent + : never) + : never); +export type EthereumKilnStakeParameters = { + stakerAddress?: string + integratorContractAddress?: string + amount?: CoinbaseStakingOrchestrationV1Common.Amount +} + +export type EthereumKilnUnstakeParameters = { + stakerAddress?: string + integratorContractAddress?: string + amount?: CoinbaseStakingOrchestrationV1Common.Amount +} + +export type EthereumKilnClaimStakeParameters = { + stakerAddress?: string + integratorContractAddress?: string +} + + +type BaseEthereumKilnStakingParameters = { +} + +export type EthereumKilnStakingParameters = BaseEthereumKilnStakingParameters + & OneOf<{ stakeParameters: EthereumKilnStakeParameters; unstakeParameters: EthereumKilnUnstakeParameters; claimStakeParameters: EthereumKilnClaimStakeParameters }> + +export type EthereumKilnStakingContextParameters = { + integratorContractAddress?: string +} + +export type EthereumKilnStakingContextDetails = { + ethereumBalance?: CoinbaseStakingOrchestrationV1Common.Amount + integratorShareBalance?: CoinbaseStakingOrchestrationV1Common.Amount + integratorShareUnderlyingBalance?: CoinbaseStakingOrchestrationV1Common.Amount + totalExitableEth?: CoinbaseStakingOrchestrationV1Common.Amount + totalSharesPendingExit?: CoinbaseStakingOrchestrationV1Common.Amount + fulfillableShareCount?: CoinbaseStakingOrchestrationV1Common.Amount +} \ No newline at end of file diff --git a/src/gen/coinbase/staking/orchestration/v1/network.pb.ts b/src/gen/coinbase/staking/orchestration/v1/network.pb.ts new file mode 100644 index 0000000..cee02ce --- /dev/null +++ b/src/gen/coinbase/staking/orchestration/v1/network.pb.ts @@ -0,0 +1,16 @@ +/* eslint-disable */ +// @ts-nocheck +/* +* This file is a generated Typescript file for GRPC Gateway, DO NOT MODIFY +*/ +export type Network = { + name?: string +} + +export type ListNetworksRequest = { + parent?: string +} + +export type ListNetworksResponse = { + networks?: Network[] +} \ No newline at end of file diff --git a/src/gen/coinbase/staking/orchestration/v1/protocol.pb.ts b/src/gen/coinbase/staking/orchestration/v1/protocol.pb.ts new file mode 100644 index 0000000..7c3c80b --- /dev/null +++ b/src/gen/coinbase/staking/orchestration/v1/protocol.pb.ts @@ -0,0 +1,15 @@ +/* eslint-disable */ +// @ts-nocheck +/* +* This file is a generated Typescript file for GRPC Gateway, DO NOT MODIFY +*/ +export type Protocol = { + name?: string +} + +export type ListProtocolsRequest = { +} + +export type ListProtocolsResponse = { + protocols?: Protocol[] +} \ No newline at end of file diff --git a/src/gen/coinbase/staking/orchestration/v1/solana.pb.ts b/src/gen/coinbase/staking/orchestration/v1/solana.pb.ts new file mode 100644 index 0000000..ca9d084 --- /dev/null +++ b/src/gen/coinbase/staking/orchestration/v1/solana.pb.ts @@ -0,0 +1,75 @@ +/* eslint-disable */ +// @ts-nocheck +/* +* This file is a generated Typescript file for GRPC Gateway, DO NOT MODIFY +*/ + +import * as CoinbaseStakingOrchestrationV1Common from "./common.pb" + +type Absent = { [k in Exclude]?: undefined }; +type OneOf = + | { [k in keyof T]?: undefined } + | ( + keyof T extends infer K ? + (K extends string & keyof T ? { [k in K]: T[K] } & Absent + : never) + : never); + +export enum StakeAccountBalanceState { + BALANCE_STATE_UNSPECIFIED = "BALANCE_STATE_UNSPECIFIED", + BALANCE_STATE_INACTIVE = "BALANCE_STATE_INACTIVE", + BALANCE_STATE_ACTIVATING = "BALANCE_STATE_ACTIVATING", + BALANCE_STATE_ACTIVE = "BALANCE_STATE_ACTIVE", + BALANCE_STATE_DEACTIVATING = "BALANCE_STATE_DEACTIVATING", +} + +export type PriorityFee = { + computeUnitLimit?: string + unitPrice?: string +} + +export type SolanaStakeParameters = { + walletAddress?: string + validatorAddress?: string + amount?: CoinbaseStakingOrchestrationV1Common.Amount + priorityFee?: PriorityFee +} + +export type SolanaUnstakeParameters = { + walletAddress?: string + stakeAccountAddress?: string + amount?: CoinbaseStakingOrchestrationV1Common.Amount + priorityFee?: PriorityFee +} + +export type SolanaClaimStakeParameters = { + walletAddress?: string + stakeAccountAddress?: string + priorityFee?: PriorityFee +} + +export type SolanaStakingContextParameters = { +} + +export type SolanaStakingContextDetails = { + balance?: CoinbaseStakingOrchestrationV1Common.Amount + currentEpoch?: string + epochCompletionPercentage?: string + stakeAccounts?: StakeAccount[] +} + +export type StakeAccount = { + address?: string + bondedStake?: CoinbaseStakingOrchestrationV1Common.Amount + rentReserve?: CoinbaseStakingOrchestrationV1Common.Amount + balance?: CoinbaseStakingOrchestrationV1Common.Amount + balanceState?: StakeAccountBalanceState + validator?: string +} + + +type BaseSolanaStakingParameters = { +} + +export type SolanaStakingParameters = BaseSolanaStakingParameters + & OneOf<{ stakeParameters: SolanaStakeParameters; unstakeParameters: SolanaUnstakeParameters; claimStakeParameters: SolanaClaimStakeParameters }> \ No newline at end of file diff --git a/src/gen/coinbase/staking/orchestration/v1/staking_context.pb.ts b/src/gen/coinbase/staking/orchestration/v1/staking_context.pb.ts new file mode 100644 index 0000000..6242db3 --- /dev/null +++ b/src/gen/coinbase/staking/orchestration/v1/staking_context.pb.ts @@ -0,0 +1,33 @@ +/* eslint-disable */ +// @ts-nocheck +/* +* This file is a generated Typescript file for GRPC Gateway, DO NOT MODIFY +*/ + +import * as CoinbaseStakingOrchestrationV1Ethereum_kiln from "./ethereum_kiln.pb" +import * as CoinbaseStakingOrchestrationV1Solana from "./solana.pb" + +type Absent = { [k in Exclude]?: undefined }; +type OneOf = + | { [k in keyof T]?: undefined } + | ( + keyof T extends infer K ? + (K extends string & keyof T ? { [k in K]: T[K] } & Absent + : never) + : never); + +type BaseViewStakingContextRequest = { + address?: string + network?: string +} + +export type ViewStakingContextRequest = BaseViewStakingContextRequest + & OneOf<{ ethereumKilnStakingContextParameters: CoinbaseStakingOrchestrationV1Ethereum_kiln.EthereumKilnStakingContextParameters; solanaStakingContextParameters: CoinbaseStakingOrchestrationV1Solana.SolanaStakingContextParameters }> + + +type BaseViewStakingContextResponse = { + address?: string +} + +export type ViewStakingContextResponse = BaseViewStakingContextResponse + & OneOf<{ ethereumKilnStakingContextDetails: CoinbaseStakingOrchestrationV1Ethereum_kiln.EthereumKilnStakingContextDetails; solanaStakingContextDetails: CoinbaseStakingOrchestrationV1Solana.SolanaStakingContextDetails }> \ No newline at end of file diff --git a/src/gen/coinbase/staking/orchestration/v1/staking_target.pb.ts b/src/gen/coinbase/staking/orchestration/v1/staking_target.pb.ts new file mode 100644 index 0000000..80196ac --- /dev/null +++ b/src/gen/coinbase/staking/orchestration/v1/staking_target.pb.ts @@ -0,0 +1,42 @@ +/* eslint-disable */ +// @ts-nocheck +/* +* This file is a generated Typescript file for GRPC Gateway, DO NOT MODIFY +*/ + +type Absent = { [k in Exclude]?: undefined }; +type OneOf = + | { [k in keyof T]?: undefined } + | ( + keyof T extends infer K ? + (K extends string & keyof T ? { [k in K]: T[K] } & Absent + : never) + : never); +export type Validator = { + name?: string + address?: string + commissionRate?: number +} + +export type Contract = { + name?: string + address?: string +} + + +type BaseStakingTarget = { +} + +export type StakingTarget = BaseStakingTarget + & OneOf<{ validator: Validator; contract: Contract }> + +export type ListStakingTargetsRequest = { + parent?: string + pageSize?: number + pageToken?: string +} + +export type ListStakingTargetsResponse = { + stakingTargets?: StakingTarget[] + nextPageToken?: string +} \ No newline at end of file diff --git a/src/gen/coinbase/staking/orchestration/v1/workflow.pb.ts b/src/gen/coinbase/staking/orchestration/v1/workflow.pb.ts new file mode 100644 index 0000000..cc6549e --- /dev/null +++ b/src/gen/coinbase/staking/orchestration/v1/workflow.pb.ts @@ -0,0 +1,124 @@ +/* eslint-disable */ +// @ts-nocheck +/* +* This file is a generated Typescript file for GRPC Gateway, DO NOT MODIFY +*/ + +import * as GoogleProtobufTimestamp from "../../../../google/protobuf/timestamp.pb" +import * as CoinbaseStakingOrchestrationV1Ethereum_kiln from "./ethereum_kiln.pb" +import * as CoinbaseStakingOrchestrationV1Solana from "./solana.pb" + +type Absent = { [k in Exclude]?: undefined }; +type OneOf = + | { [k in keyof T]?: undefined } + | ( + keyof T extends infer K ? + (K extends string & keyof T ? { [k in K]: T[K] } & Absent + : never) + : never); + +export enum TxStepOutputState { + STATE_UNSPECIFIED = "STATE_UNSPECIFIED", + STATE_NOT_CONSTRUCTED = "STATE_NOT_CONSTRUCTED", + STATE_CONSTRUCTED = "STATE_CONSTRUCTED", + STATE_PENDING_SIGNING = "STATE_PENDING_SIGNING", + STATE_SIGNED = "STATE_SIGNED", + STATE_BROADCASTING = "STATE_BROADCASTING", + STATE_CONFIRMING = "STATE_CONFIRMING", + STATE_CONFIRMED = "STATE_CONFIRMED", + STATE_FINALIZED = "STATE_FINALIZED", + STATE_FAILED = "STATE_FAILED", + STATE_SUCCESS = "STATE_SUCCESS", + STATE_PENDING_EXT_BROADCAST = "STATE_PENDING_EXT_BROADCAST", +} + +export enum WaitStepOutputWaitUnit { + WAIT_UNIT_UNSPECIFIED = "WAIT_UNIT_UNSPECIFIED", + WAIT_UNIT_SECONDS = "WAIT_UNIT_SECONDS", + WAIT_UNIT_BLOCKS = "WAIT_UNIT_BLOCKS", + WAIT_UNIT_EPOCHS = "WAIT_UNIT_EPOCHS", + WAIT_UNIT_CHECKPOINTS = "WAIT_UNIT_CHECKPOINTS", +} + +export enum WaitStepOutputState { + STATE_UNSPECIFIED = "STATE_UNSPECIFIED", + STATE_NOT_STARTED = "STATE_NOT_STARTED", + STATE_IN_PROGRESS = "STATE_IN_PROGRESS", + STATE_COMPLETED = "STATE_COMPLETED", +} + +export enum WorkflowState { + STATE_UNSPECIFIED = "STATE_UNSPECIFIED", + STATE_IN_PROGRESS = "STATE_IN_PROGRESS", + STATE_WAITING_FOR_SIGNING = "STATE_WAITING_FOR_SIGNING", + STATE_COMPLETED = "STATE_COMPLETED", + STATE_FAILED = "STATE_FAILED", + STATE_WAITING_FOR_EXT_BROADCAST = "STATE_WAITING_FOR_EXT_BROADCAST", +} + +export type TxStepOutput = { + unsignedTx?: string + signedTx?: string + txHash?: string + state?: TxStepOutputState + errorMessage?: string +} + +export type WaitStepOutput = { + start?: string + current?: string + target?: string + unit?: WaitStepOutputWaitUnit + state?: WaitStepOutputState +} + + +type BaseWorkflowStep = { + name?: string +} + +export type WorkflowStep = BaseWorkflowStep + & OneOf<{ txStepOutput: TxStepOutput; waitStepOutput: WaitStepOutput }> + + +type BaseWorkflow = { + name?: string + action?: string + state?: WorkflowState + currentStepId?: number + steps?: WorkflowStep[] + createTime?: GoogleProtobufTimestamp.Timestamp + updateTime?: GoogleProtobufTimestamp.Timestamp + skipBroadcast?: boolean + completeTime?: GoogleProtobufTimestamp.Timestamp +} + +export type Workflow = BaseWorkflow + & OneOf<{ solanaStakingParameters: CoinbaseStakingOrchestrationV1Solana.SolanaStakingParameters; ethereumKilnStakingParameters: CoinbaseStakingOrchestrationV1Ethereum_kiln.EthereumKilnStakingParameters }> + +export type CreateWorkflowRequest = { + parent?: string + workflow?: Workflow +} + +export type GetWorkflowRequest = { + name?: string +} + +export type ListWorkflowsRequest = { + parent?: string + filter?: string + pageSize?: number + pageToken?: string +} + +export type ListWorkflowsResponse = { + workflows?: Workflow[] + nextPageToken?: string +} + +export type PerformWorkflowStepRequest = { + name?: string + step?: number + data?: string +} \ No newline at end of file diff --git a/src/gen/coinbase/staking/rewards/v1/common.pb.ts b/src/gen/coinbase/staking/rewards/v1/common.pb.ts new file mode 100644 index 0000000..64c5b9b --- /dev/null +++ b/src/gen/coinbase/staking/rewards/v1/common.pb.ts @@ -0,0 +1,11 @@ +/* eslint-disable */ +// @ts-nocheck +/* +* This file is a generated Typescript file for GRPC Gateway, DO NOT MODIFY +*/ +export type AssetAmount = { + amount?: string + exp?: string + ticker?: string + rawNumeric?: string +} \ No newline at end of file diff --git a/src/gen/coinbase/staking/rewards/v1/protocol.pb.ts b/src/gen/coinbase/staking/rewards/v1/protocol.pb.ts new file mode 100644 index 0000000..2730aab --- /dev/null +++ b/src/gen/coinbase/staking/rewards/v1/protocol.pb.ts @@ -0,0 +1,8 @@ +/* eslint-disable */ +// @ts-nocheck +/* +* This file is a generated Typescript file for GRPC Gateway, DO NOT MODIFY +*/ +export type Protocol = { + name?: string +} \ No newline at end of file diff --git a/src/gen/coinbase/staking/rewards/v1/reward.pb.ts b/src/gen/coinbase/staking/rewards/v1/reward.pb.ts new file mode 100644 index 0000000..8f6d426 --- /dev/null +++ b/src/gen/coinbase/staking/rewards/v1/reward.pb.ts @@ -0,0 +1,62 @@ +/* eslint-disable */ +// @ts-nocheck +/* +* This file is a generated Typescript file for GRPC Gateway, DO NOT MODIFY +*/ + +import * as GoogleProtobufTimestamp from "../../../../google/protobuf/timestamp.pb" +import * as CoinbaseStakingRewardsV1Common from "./common.pb" +import * as CoinbaseStakingRewardsV1Stake from "./stake.pb" + +type Absent = { [k in Exclude]?: undefined }; +type OneOf = + | { [k in keyof T]?: undefined } + | ( + keyof T extends infer K ? + (K extends string & keyof T ? { [k in K]: T[K] } & Absent + : never) + : never); + +export enum AggregationUnit { + AGGREGATION_UNIT_UNSPECIFIED = "AGGREGATION_UNIT_UNSPECIFIED", + EPOCH = "EPOCH", + DAY = "DAY", +} + +export enum USDValueSource { + SOURCE_UNSPECIFIED = "SOURCE_UNSPECIFIED", + COINBASE_EXCHANGE = "COINBASE_EXCHANGE", +} + + +type BaseReward = { + address?: string + aggregationUnit?: AggregationUnit + periodStartTime?: GoogleProtobufTimestamp.Timestamp + periodEndTime?: GoogleProtobufTimestamp.Timestamp + totalEarnedNativeUnit?: CoinbaseStakingRewardsV1Common.AssetAmount + totalEarnedUsd?: USDValue[] + endingBalance?: CoinbaseStakingRewardsV1Stake.Stake + protocol?: string +} + +export type Reward = BaseReward + & OneOf<{ epoch: string; date: string }> + +export type USDValue = { + source?: USDValueSource + conversionTime?: GoogleProtobufTimestamp.Timestamp + amount?: CoinbaseStakingRewardsV1Common.AssetAmount +} + +export type ListRewardsRequest = { + parent?: string + pageSize?: number + pageToken?: string + filter?: string +} + +export type ListRewardsResponse = { + rewards?: Reward[] + nextPageToken?: string +} \ No newline at end of file diff --git a/src/gen/coinbase/staking/rewards/v1/reward_service.pb.ts b/src/gen/coinbase/staking/rewards/v1/reward_service.pb.ts new file mode 100644 index 0000000..f58b880 --- /dev/null +++ b/src/gen/coinbase/staking/rewards/v1/reward_service.pb.ts @@ -0,0 +1,20 @@ +/* eslint-disable */ +// @ts-nocheck +/* +* This file is a generated Typescript file for GRPC Gateway, DO NOT MODIFY +*/ + +import * as fm from "../../../../fetch.pb" +import * as CoinbaseStakingRewardsV1Reward from "./reward.pb" +import * as CoinbaseStakingRewardsV1Stake from "./stake.pb" +export class RewardService { + static ListRewards(req: CoinbaseStakingRewardsV1Reward.ListRewardsRequest, initReq?: fm.InitReq): Promise { + return fm.fetchReq(`/v1/${req["parent"]}/rewards?${fm.renderURLSearchParams(req, ["parent"])}`, {...initReq, method: "GET"}) + } + static GetStake(req: CoinbaseStakingRewardsV1Stake.GetStakeRequest, initReq?: fm.InitReq): Promise { + return fm.fetchReq(`/v1/${req["nameprotocolsstakes"]}?${fm.renderURLSearchParams(req, ["nameprotocolsstakes"])}`, {...initReq, method: "GET"}) + } + static ListStakes(req: CoinbaseStakingRewardsV1Stake.ListStakesRequest, initReq?: fm.InitReq): Promise { + return fm.fetchReq(`/v1/${req["parent"]}/stakes?${fm.renderURLSearchParams(req, ["parent"])}`, {...initReq, method: "GET"}) + } +} \ No newline at end of file diff --git a/src/gen/coinbase/staking/rewards/v1/stake.pb.ts b/src/gen/coinbase/staking/rewards/v1/stake.pb.ts new file mode 100644 index 0000000..de6c259 --- /dev/null +++ b/src/gen/coinbase/staking/rewards/v1/stake.pb.ts @@ -0,0 +1,75 @@ +/* eslint-disable */ +// @ts-nocheck +/* +* This file is a generated Typescript file for GRPC Gateway, DO NOT MODIFY +*/ + +import * as GoogleProtobufTimestamp from "../../../../google/protobuf/timestamp.pb" +import * as CoinbaseStakingRewardsV1Common from "./common.pb" + +type Absent = { [k in Exclude]?: undefined }; +type OneOf = + | { [k in keyof T]?: undefined } + | ( + keyof T extends infer K ? + (K extends string & keyof T ? { [k in K]: T[K] } & Absent + : never) + : never); + +export enum ParticipantType { + PARTICIPANT_TYPE_UNSPECIFIED = "PARTICIPANT_TYPE_UNSPECIFIED", + DELEGATOR = "DELEGATOR", + VALIDATOR = "VALIDATOR", +} + +export enum RewardRateCalculationMethods { + CALCULATION_METHODS_UNSPECIFIED = "CALCULATION_METHODS_UNSPECIFIED", + SOLO_STAKER = "SOLO_STAKER", + POOLED_STAKER = "POOLED_STAKER", + EPOCH_AUTO_COMPOUNDING = "EPOCH_AUTO_COMPOUNDING", + NO_AUTO_COMPOUNDING = "NO_AUTO_COMPOUNDING", +} + +export type StakeDelegation = { + address?: string + amount?: CoinbaseStakingRewardsV1Common.AssetAmount + commissionRate?: string +} + + +type BaseStake = { + address?: string + evaluationTime?: GoogleProtobufTimestamp.Timestamp + bondedStake?: CoinbaseStakingRewardsV1Common.AssetAmount + totalDelegationReceived?: CoinbaseStakingRewardsV1Common.AssetAmount + rewardRateCalculations?: RewardRate[] + participantType?: ParticipantType + protocol?: string + unbondedBalance?: CoinbaseStakingRewardsV1Common.AssetAmount +} + +export type Stake = BaseStake + & OneOf<{ delegationsReceived: StakeDelegation }> + & OneOf<{ delegationsGiven: StakeDelegation }> + +export type RewardRate = { + percentage?: string + calculatedTime?: GoogleProtobufTimestamp.Timestamp + calculationMethod?: RewardRateCalculationMethods +} + +export type ListStakesRequest = { + parent?: string + pageSize?: number + pageToken?: string + filter?: string +} + +export type ListStakesResponse = { + stakes?: Stake[] + nextPageToken?: string +} + +export type GetStakeRequest = { + name?: string +} \ No newline at end of file diff --git a/src/gen/fetch.pb.ts b/src/gen/fetch.pb.ts new file mode 100644 index 0000000..8273636 --- /dev/null +++ b/src/gen/fetch.pb.ts @@ -0,0 +1,341 @@ +/* eslint-disable */ +// @ts-nocheck +/* +* This file is a generated Typescript file for GRPC Gateway, DO NOT MODIFY +*/ + +/** + * base64 encoder and decoder + * Copied and adapted from https://github.com/protobufjs/protobuf.js/blob/master/lib/base64/index.js + */ +// Base64 encoding table +const b64 = new Array(64); + +// Base64 decoding table +const s64 = new Array(123); + +// 65..90, 97..122, 48..57, 43, 47 +for (let i = 0; i < 64;) + s64[b64[i] = i < 26 ? i + 65 : i < 52 ? i + 71 : i < 62 ? i - 4 : i - 59 | 43] = i++; + +export function b64Encode(buffer: Uint8Array, start: number, end: number): string { + let parts: string[] = null; + const chunk = []; + let i = 0, // output index + j = 0, // goto index + t; // temporary + while (start < end) { + const b = buffer[start++]; + switch (j) { + case 0: + chunk[i++] = b64[b >> 2]; + t = (b & 3) << 4; + j = 1; + break; + case 1: + chunk[i++] = b64[t | b >> 4]; + t = (b & 15) << 2; + j = 2; + break; + case 2: + chunk[i++] = b64[t | b >> 6]; + chunk[i++] = b64[b & 63]; + j = 0; + break; + } + if (i > 8191) { + (parts || (parts = [])).push(String.fromCharCode.apply(String, chunk)); + i = 0; + } + } + if (j) { + chunk[i++] = b64[t]; + chunk[i++] = 61; + if (j === 1) + chunk[i++] = 61; + } + if (parts) { + if (i) + parts.push(String.fromCharCode.apply(String, chunk.slice(0, i))); + return parts.join(""); + } + return String.fromCharCode.apply(String, chunk.slice(0, i)); +} + +const invalidEncoding = "invalid encoding"; + +export function b64Decode(s: string): Uint8Array { + const buffer = []; + let offset = 0; + let j = 0, // goto index + t; // temporary + for (let i = 0; i < s.length;) { + let c = s.charCodeAt(i++); + if (c === 61 && j > 1) + break; + if ((c = s64[c]) === undefined) + throw Error(invalidEncoding); + switch (j) { + case 0: + t = c; + j = 1; + break; + case 1: + buffer[offset++] = t << 2 | (c & 48) >> 4; + t = c; + j = 2; + break; + case 2: + buffer[offset++] = (t & 15) << 4 | (c & 60) >> 2; + t = c; + j = 3; + break; + case 3: + buffer[offset++] = (t & 3) << 6 | c; + j = 0; + break; + } + } + if (j === 1) + throw Error(invalidEncoding); + return new Uint8Array(buffer); +} + +function b64Test(s: string): boolean { + return /^(?:[A-Za-z0-9+/]{4})*(?:[A-Za-z0-9+/]{2}==|[A-Za-z0-9+/]{3}=)?$/.test(s); +} + +export interface InitReq extends RequestInit { + pathPrefix?: string +} + +export function replacer(key: any, value: any): any { + if(value && value.constructor === Uint8Array) { + return b64Encode(value, 0, value.length); + } + + return value; +} + +export function fetchReq(path: string, init?: InitReq): Promise { + const {pathPrefix, ...req} = init || {} + + const url = pathPrefix ? `${pathPrefix}${path}` : path + + return fetch(url, req).then(r => r.json().then((body: O) => { + if (!r.ok) { throw body; } + return body; + })) as Promise +} + +// NotifyStreamEntityArrival is a callback that will be called on streaming entity arrival +export type NotifyStreamEntityArrival = (resp: T) => void + +/** + * fetchStreamingRequest is able to handle grpc-gateway server side streaming call + * it takes NotifyStreamEntityArrival that lets users respond to entity arrival during the call + * all entities will be returned as an array after the call finishes. + **/ +export async function fetchStreamingRequest(path: string, callback?: NotifyStreamEntityArrival, init?: InitReq) { + const {pathPrefix, ...req} = init || {} + const url = pathPrefix ?`${pathPrefix}${path}` : path + const result = await fetch(url, req) + // needs to use the .ok to check the status of HTTP status code + // http other than 200 will not throw an error, instead the .ok will become false. + // see https://developer.mozilla.org/en-US/docs/Web/API/Fetch_API/Using_Fetch# + if (!result.ok) { + const resp = await result.json() + const errMsg = resp.error && resp.error.message ? resp.error.message : "" + throw new Error(errMsg) + } + + if (!result.body) { + throw new Error("response doesnt have a body") + } + + await result.body + .pipeThrough(new TextDecoderStream()) + .pipeThrough(getNewLineDelimitedJSONDecodingStream()) + .pipeTo(getNotifyEntityArrivalSink((e: R) => { + if (callback) { + callback(e) + } + })) + + // wait for the streaming to finish and return the success respond + return +} + +/** + * JSONStringStreamController represents the transform controller that's able to transform the incoming + * new line delimited json content stream into entities and able to push the entity to the down stream + */ +interface JSONStringStreamController extends TransformStreamDefaultController { + buf?: string + pos?: number + enqueue: (s: T) => void +} + +/** + * getNewLineDelimitedJSONDecodingStream returns a TransformStream that's able to handle new line delimited json stream content into parsed entities + */ +function getNewLineDelimitedJSONDecodingStream(): TransformStream { + return new TransformStream({ + start(controller: JSONStringStreamController) { + controller.buf = '' + controller.pos = 0 + }, + + transform(chunk: string, controller: JSONStringStreamController) { + if (controller.buf === undefined) { + controller.buf = '' + } + if (controller.pos === undefined) { + controller.pos = 0 + } + controller.buf += chunk + while (controller.pos < controller.buf.length) { + if (controller.buf[controller.pos] === '\n') { + const line = controller.buf.substring(0, controller.pos) + const response = JSON.parse(line) + controller.enqueue(response.result) + controller.buf = controller.buf.substring(controller.pos + 1) + controller.pos = 0 + } else { + ++controller.pos + } + } + } + }) + +} + +/** + * getNotifyEntityArrivalSink takes the NotifyStreamEntityArrival callback and return + * a sink that will call the callback on entity arrival + * @param notifyCallback + */ +function getNotifyEntityArrivalSink(notifyCallback: NotifyStreamEntityArrival) { + return new WritableStream({ + write(entity: T) { + notifyCallback(entity) + } + }) +} + +type Primitive = string | boolean | number; +type RequestPayload = Record; +type FlattenedRequestPayload = Record>; + +/** + * Checks if given value is a plain object + * Logic copied and adapted from below source: + * https://github.com/char0n/ramda-adjunct/blob/master/src/isPlainObj.js + * @param {unknown} value + * @return {boolean} + */ +function isPlainObject(value: unknown): boolean { + const isObject = + Object.prototype.toString.call(value).slice(8, -1) === "Object"; + const isObjLike = value !== null && isObject; + + if (!isObjLike || !isObject) { + return false; + } + + const proto = Object.getPrototypeOf(value); + + const hasObjectConstructor = + typeof proto === "object" && + proto.constructor === Object.prototype.constructor; + + return hasObjectConstructor; +} + +/** + * Checks if given value is of a primitive type + * @param {unknown} value + * @return {boolean} + */ +function isPrimitive(value: unknown): boolean { + return ["string", "number", "boolean"].some(t => typeof value === t); +} + +/** + * Checks if given primitive is zero-value + * @param {Primitive} value + * @return {boolean} + */ +function isZeroValuePrimitive(value: Primitive): boolean { + return value === false || value === 0 || value === ""; +} + +/** + * Flattens a deeply nested request payload and returns an object + * with only primitive values and non-empty array of primitive values + * as per https://github.com/googleapis/googleapis/blob/master/google/api/http.proto + * @param {RequestPayload} requestPayload + * @param {String} path + * @return {FlattenedRequestPayload>} + */ +function flattenRequestPayload( + requestPayload: T, + path: string = "" +): FlattenedRequestPayload { + return Object.keys(requestPayload).reduce( + (acc: T, key: string): T => { + const value = requestPayload[key]; + const newPath = path ? [path, key].join(".") : key; + + const isNonEmptyPrimitiveArray = + Array.isArray(value) && + value.every(v => isPrimitive(v)) && + value.length > 0; + + const isNonZeroValuePrimitive = + isPrimitive(value) && !isZeroValuePrimitive(value as Primitive); + + let objectToMerge = {}; + + if (isPlainObject(value)) { + objectToMerge = flattenRequestPayload(value as RequestPayload, newPath); + } else if (isNonZeroValuePrimitive || isNonEmptyPrimitiveArray) { + objectToMerge = { [newPath]: value }; + } + + return { ...acc, ...objectToMerge }; + }, + {} as T + ) as FlattenedRequestPayload; +} + +/** + * Renders a deeply nested request payload into a string of URL search + * parameters by first flattening the request payload and then removing keys + * which are already present in the URL path. + * @param {RequestPayload} requestPayload + * @param {string[]} urlPathParams + * @return {string} + */ +export function renderURLSearchParams( + requestPayload: T, + urlPathParams: string[] = [] +): string { + const flattenedRequestPayload = flattenRequestPayload(requestPayload); + + const urlSearchParams = Object.keys(flattenedRequestPayload).reduce( + (acc: string[][], key: string): string[][] => { + // key should not be present in the url path as a parameter + const value = flattenedRequestPayload[key]; + if (urlPathParams.find(f => f === key)) { + return acc; + } + return Array.isArray(value) + ? [...acc, ...value.map(m => [key, m.toString()])] + : (acc = [...acc, [key, value.toString()]]); + }, + [] as string[][] + ); + + return new URLSearchParams(urlSearchParams).toString(); +} \ No newline at end of file diff --git a/src/gen/google/protobuf/timestamp.pb.ts b/src/gen/google/protobuf/timestamp.pb.ts new file mode 100644 index 0000000..283bbd1 --- /dev/null +++ b/src/gen/google/protobuf/timestamp.pb.ts @@ -0,0 +1,9 @@ +/* eslint-disable */ +// @ts-nocheck +/* +* This file is a generated Typescript file for GRPC Gateway, DO NOT MODIFY +*/ +export type Timestamp = { + seconds?: string + nanos?: number +} \ No newline at end of file diff --git a/src/index.ts b/src/index.ts new file mode 100644 index 0000000..870ba28 --- /dev/null +++ b/src/index.ts @@ -0,0 +1,5 @@ +import * as Utils from './utils/date'; + +export { Utils }; + +export { StakingClient } from './client/staking-client'; diff --git a/src/signers/ethereum-signer.ts b/src/signers/ethereum-signer.ts new file mode 100644 index 0000000..e834378 --- /dev/null +++ b/src/signers/ethereum-signer.ts @@ -0,0 +1,26 @@ +import { TransactionFactory } from '@ethereumjs/tx'; +import { bytesToHex } from '@ethereumjs/util'; +import { TxSigner } from './txsigner'; + +export class EthereumTransactionSigner implements TxSigner { + async signTransaction( + privateKey: string, + unsignedTx: string, + ): Promise { + let transaction = TransactionFactory.fromSerializedData( + Buffer.from(unsignedTx, 'hex'), + ); + + const decodedPrivateKey = Buffer.from(privateKey, 'hex'); + + let signedTx = transaction.sign(decodedPrivateKey); + + const verifiedSignature = signedTx.verifySignature(); + + if (!verifiedSignature) { + throw new Error('Produced an invalid signature!'); + } + + return bytesToHex(signedTx.serialize()); + } +} diff --git a/src/signers/index.ts b/src/signers/index.ts new file mode 100644 index 0000000..b7949a4 --- /dev/null +++ b/src/signers/index.ts @@ -0,0 +1,2 @@ +export * from './ethereum-signer'; +export * from './txsigner'; diff --git a/src/signers/solana-signer.ts b/src/signers/solana-signer.ts new file mode 100644 index 0000000..d088575 --- /dev/null +++ b/src/signers/solana-signer.ts @@ -0,0 +1,26 @@ +import { Transaction, Account } from '@solana/web3.js'; +import * as bs58 from 'bs58'; +import { TxSigner } from './txsigner'; + +const TRANSACTION_SERIALIZE_CONFIG = { + requireAllSignatures: false, + verifySignatures: true, +}; + +export class SolanaTransactionSigner implements TxSigner { + async signTransaction( + privateKey: string, + unsignedTx: string, + ): Promise { + const txn = bs58.decode(unsignedTx); + const tx = Transaction.from(txn); + + const secretKey = bs58.decode(privateKey); + + const sender = new Account(secretKey); + + tx.sign(sender); + + return bs58.encode(tx.serialize(TRANSACTION_SERIALIZE_CONFIG)); + } +} diff --git a/src/signers/txsigner.ts b/src/signers/txsigner.ts new file mode 100644 index 0000000..e438511 --- /dev/null +++ b/src/signers/txsigner.ts @@ -0,0 +1,21 @@ +import { EthereumTransactionSigner } from './ethereum-signer'; +import { SolanaTransactionSigner } from './solana-signer'; + +export interface TxSigner { + // eslint-disable-next-line no-unused-vars + signTransaction(privateKey: string, unsignedTx: string): Promise; +} + +export class TxSignerFactory { + static getSigner(protocol: string): TxSigner { + switch (protocol) { + case 'ethereum': + return new EthereumTransactionSigner(); + case 'solana': + return new SolanaTransactionSigner(); + // other cases for additional protocols + default: + throw new Error('Unsupported protocol'); + } + } +} diff --git a/src/utils/date.ts b/src/utils/date.ts new file mode 100644 index 0000000..1acec6c --- /dev/null +++ b/src/utils/date.ts @@ -0,0 +1,10 @@ +// calculateTimeDifference is a function that takes two timestamps and returns the difference in seconds. +export function calculateTimeDifference( + timestamp1: string, + timestamp2: string, +): number { + const date1 = new Date(timestamp1); + const date2 = new Date(timestamp2); + + return Math.abs(date1.getTime() - date2.getTime()) / 1000; +} diff --git a/tsconfig.json b/tsconfig.json new file mode 100644 index 0000000..1dc7e70 --- /dev/null +++ b/tsconfig.json @@ -0,0 +1,32 @@ +{ + "compilerOptions": { + "target": "es6", /* Set the JavaScript language version for emitted JavaScript and include compatible library declarations. */ + "module": "CommonJS", /* Specify what module code is generated. */ + "outDir": "./dist", + "rootDir": "./src", + "sourceMap": true, + "declaration": true, + "esModuleInterop": true, /* Emit additional JavaScript to ease support for importing CommonJS modules. This enables 'allowSyntheticDefaultImports' for type compatibility. */ + "forceConsistentCasingInFileNames": true, /* Ensure that casing is correct in imports. */ + "strict": true, /* Enable all strict type-checking options. */ + "moduleResolution": "node", + "noImplicitAny": true, + "strictNullChecks": true, + "alwaysStrict": true, + "noUnusedLocals": true, + "noUnusedParameters": true, + "noImplicitReturns": true, + "noFallthroughCasesInSwitch": true, + "skipLibCheck": true, /* Skip type checking all .d.ts files. */ + "types": [ + "node" + ] + }, + "include": [ + "src/**/*.ts" + ], + "exclude": [ + "node_modules", + "openapi" + ] +}