From 4b7939b38dd20ab304633707ee9a9e6b851d1c76 Mon Sep 17 00:00:00 2001 From: Chad Ostrowski <221614+chadoh@users.noreply.github.com> Date: Wed, 15 May 2024 16:40:05 -0400 Subject: [PATCH] feat!: export `ContractClient` et al. in `contract`; rename `SorobanRpc` to `rpc` (#962) MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit * Appease ESLint: This includes some fixes and updates to eslint configuration to make it work as expected - Extended `airbnb-typescript/base` to get it to stop yelling at me about importing files without file extension. Saw this recommended as the fix on StackOverflow [[1]]. And it makes sense to me that if we are extending Airbnb's lint rules and using TypeScript, we probably want their TypeScript-specific lint rules, too. - Added the `eslint-plugin-jsdoc` plugin because the old `valid-jsdoc` rule we were using has been deprecated [[2]], and this plugin is the new way. Previously we had `valid-jsdoc: 1` (with some extra customization), and my guess is that extending `plugin:jsdoc/recommended` (plus some customization) is roughly equivalent. - Researched [[3]] whether JSDoc `@param`-style docs or TSDoc-style `/** inline param docs */` work better. TSDoc work better. So disabled `jsdoc/require-param`. - Remove mostly-duplicate `src/soroban/.eslintrc.js`, which had only one setting different from `src/.eslintrc.js` Note that `src/contract_client` is now perhaps the only directory of code to pass our ESLint settings 🤔 [1]: https://stackoverflow.com/a/67610259/249801 [2]: https://eslint.org/docs/latest/rules/valid-jsdoc [3]: https://github.com/stellar/js-stellar-sdk/pull/962#discussion_r1596950321 * New exports `contract` and `rpc`: - `SorobanRpc` now also exported as `rpc` - New main export `contract`. This allows us to import it the usual way, instead of needing to do things like import { ContractClient } from "stellar-sdk/lib/contract_client" which doesn't work in the browser (because `lib`) - `ContractSpec` now available at `contract.Spec` - Also includes other supporting classes, functions, and types: - `contract.AssembledTransaction` - `contract.ClientOptions` - etc - These are also available at matching entrypoints, if your node and TypeScript configuration support the `exports` declaration: import { Client, ClientOptions, AssembledTransaction, } from 'stellar-sdk/contract' This also attempts to document exported modules. The JSDoc-style comments in `src/index.ts` don't actually show up when you import these things. We can figure this out later. Docs here https://jsdoc.app/howto-es2015-modules. In the meantime, these are still useful notes that we can use later. --------- Co-authored-by: George --- .eslintrc.js | 7 +- CHANGELOG.md | 71 ++++- README.md | 2 +- package.json | 17 ++ src/.eslintrc.js | 26 +- .../assembled_transaction.ts | 256 ++++++++++-------- src/contract/basic_node_signer.ts | 27 ++ src/{contract_client => contract}/client.ts | 53 ++-- src/contract/index.ts | 7 + .../result.ts => contract/rust_result.ts} | 43 ++- .../sent_transaction.ts | 89 +++--- src/{contract_spec.ts => contract/spec.ts} | 95 +++---- src/{contract_client => contract}/types.ts | 35 ++- src/{contract_client => contract}/utils.ts | 40 ++- src/contract_client/basic_node_signer.ts | 21 -- src/contract_client/index.ts | 6 - src/index.ts | 31 ++- src/{soroban => rpc}/api.ts | 0 src/{soroban => rpc}/axios.ts | 0 src/{soroban => rpc}/browser.ts | 0 src/rpc/index.ts | 13 + src/{soroban => rpc}/jsonrpc.ts | 0 src/{soroban => rpc}/parsers.ts | 0 src/{soroban => rpc}/server.ts | 0 src/{soroban => rpc}/transaction.ts | 0 src/{soroban => rpc}/utils.ts | 0 src/rust_types/index.ts | 1 - src/soroban/.eslintrc.js | 42 --- src/soroban/index.ts | 13 - .../src/test-contract-client-constructor.js | 33 +-- test/e2e/src/test-custom-types.js | 7 +- test/e2e/src/test-hello-world.js | 1 - test/e2e/src/test-swap.js | 7 +- test/e2e/src/util.js | 22 +- test/unit/server/soroban/get_account_test.js | 2 +- .../server/soroban/get_contract_data_test.js | 2 +- test/unit/server/soroban/get_events_test.js | 6 +- test/unit/server/soroban/get_health_test.js | 2 +- .../server/soroban/get_latest_ledger_test.js | 2 +- .../server/soroban/get_ledger_entries_test.js | 2 +- test/unit/server/soroban/get_network_test.js | 2 +- .../server/soroban/get_transaction_test.js | 2 +- .../server/soroban/request_airdrop_test.js | 2 +- .../server/soroban/send_transaction_test.js | 2 +- .../soroban/simulate_transaction_test.js | 14 +- test/unit/spec/contract_spec.ts | 14 +- test/unit/transaction_test.js | 14 +- yarn.lock | 73 ++++- 48 files changed, 661 insertions(+), 443 deletions(-) rename src/{contract_client => contract}/assembled_transaction.ts (77%) create mode 100644 src/contract/basic_node_signer.ts rename src/{contract_client => contract}/client.ts (66%) create mode 100644 src/contract/index.ts rename src/{rust_types/result.ts => contract/rust_result.ts} (85%) rename src/{contract_client => contract}/sent_transaction.ts (68%) rename src/{contract_spec.ts => contract/spec.ts} (96%) rename src/{contract_client => contract}/types.ts (82%) rename src/{contract_client => contract}/utils.ts (68%) delete mode 100644 src/contract_client/basic_node_signer.ts delete mode 100644 src/contract_client/index.ts rename src/{soroban => rpc}/api.ts (100%) rename src/{soroban => rpc}/axios.ts (100%) rename src/{soroban => rpc}/browser.ts (100%) create mode 100644 src/rpc/index.ts rename src/{soroban => rpc}/jsonrpc.ts (100%) rename src/{soroban => rpc}/parsers.ts (100%) rename src/{soroban => rpc}/server.ts (100%) rename src/{soroban => rpc}/transaction.ts (100%) rename src/{soroban => rpc}/utils.ts (100%) delete mode 100644 src/rust_types/index.ts delete mode 100644 src/soroban/.eslintrc.js delete mode 100644 src/soroban/index.ts diff --git a/.eslintrc.js b/.eslintrc.js index 2b366f290..16fdb5cab 100644 --- a/.eslintrc.js +++ b/.eslintrc.js @@ -2,9 +2,12 @@ module.exports = { env: { es6: true, }, - extends: ["airbnb-base", "prettier"], + extends: [ + "airbnb-base", + "prettier", + "plugin:jsdoc/recommended", + ], plugins: ["@babel", "prettier", "prefer-import"], - parser: "@typescript-eslint/parser", rules: { "node/no-unpublished-require": 0, }, diff --git a/CHANGELOG.md b/CHANGELOG.md index 4f303959d..cf2c9e45f 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -6,10 +6,75 @@ A breaking change will get clearly marked in this log. ## Unreleased +### Breaking Changes + +- `ContractClient` functionality previously added in [v11.3.0](https://github.com/stellar/js-stellar-sdk/releases/tag/v11.3.0) was exported in a non-standard way. You can now import it as any other stellar-sdk module. + + ```diff + - import { ContractClient } from '@stellar/stellar-sdk/lib/contract_client' + + import { contract } from '@stellar/stellar-sdk' + + const { Client } = contract + ``` + + Note that this top-level `contract` export is a container for ContractClient and related functionality. The ContractClient class is now available at `contract.Client`, as shown. Further note that there is a capitalized `Contract` export as well, which comes [from stellar-base](https://github.com/stellar/js-stellar-base/blob/b96281b9b3f94af23a913f93bdb62477f5434ccc/src/contract.js#L6-L19). You can remember which is which because capital-C `Contract` is a class, whereas lowercase-c `contract` is a container/module with a bunch of classes, functions, and types. + + Additionally, this is available from the `/contract` entrypoint, if your version of Node [and TypeScript](https://stackoverflow.com/a/70020984/249801) support [the `exports` declaration](https://nodejs.org/api/packages.html#exports). Finally, some of its exports have been renamed. + + ```diff + import { + - ContractClient, + + Client, + AssembledTransaction, + - ContractClientOptions, + + ClientOptions, + SentTransaction, + -} from '@stellar/stellar-sdk/lib/contract_client' + +} from '@stellar/stellar-sdk/contract' + ``` + + +- The `ContractSpec` class is now nested under the `contract` module, and has been renamed to `Spec`. + + ```diff + -import { ContractSpec } from '@stellar/stellar-sdk' + +import { contract } from '@stellar/stellar-sdk' + +const { Spec } = contract + ``` + + Alternatively, you can import this from the `contract` entrypoint, if your version of Node [and TypeScript](https://stackoverflow.com/a/70020984/249801) support [the `exports` declaration](https://nodejs.org/api/packages.html#exports). + + ```diff + -import { ContractSpec } from '@stellar/stellar-sdk' + +import { Spec } from '@stellar/stellar-sdk/contract' + ``` + +- Previously, `AssembledTransaction.signAndSend()` would return a `SentTransaction` even if the transaction never finalized. That is, if it successfully sent the transaction to the network, but the transaction was still `status: 'PENDING'`, then it would `console.error` an error message, but return the indeterminate transaction anyhow. + + It now throws a `SentTransaction.Errors.TransactionStillPending` error with that error message instead. + +### Deprecated + +- `SorobanRpc` module is now also exported as `rpc`. You can import it with either name for now, but `SorobanRpc` will be removed in a future release. + + ```diff + import { SorobanRpc } from '@stellar/stellar-sdk' + +// OR + +import { rpc } from '@stellar/stellar-sdk' + ``` + + You can also now import it at the `/rpc` entrypoint, if your version of Node [and TypeScript](https://stackoverflow.com/a/70020984/249801) support [the `exports` declaration](https://nodejs.org/api/packages.html#exports). + + ```diff + -import { SorobanRpc } from '@stellar/stellar-sdk' + -const { Api } = SorobanRpc + +import { Api } from '@stellar/stellar-sdk/rpc' + ``` + ### Added -* Added a from method in `ContractClient` which takes the `ContractClientOptions` and instantiates the `ContractClient` by utilizing the `contractId` to retrieve the contract wasm from the blockchain. The custom section is then extracted and used to create a `ContractSpec` which is then used to create the client. -* Similarly adds `fromWasm` and `fromWasmHash` methods in `ContractClient` which can be used to initialize a `ContractClient` if you already have the wasm bytes or the wasm hash along with the `ContractClientOptions`. -* Added `getContractWasmByContractId` and `getContractWasmByHash` methods in `Server` which can be used to retrieve the wasm bytecode of a contract via its `contractId` and wasm hash respectively. + +* Added a `from` method in `contract.Client` which takes the `ClientOptions` and instantiates the `Client` by utilizing the `contractId` to retrieve the contract wasm from the blockchain. The custom section is then extracted and used to create a `contract.Spec` which is then used to create the client. +* Similarly adds `fromWasm` and `fromWasmHash` methods in `Client` which can be used to initialize a `Client` if you already have the wasm bytes or the wasm hash along with the `ClientOptions`. +* Added `getContractWasmByContractId` and `getContractWasmByHash` methods in `rpc.Server` which can be used to retrieve the wasm bytecode of a contract via its `contractId` and wasm hash respectively. ## [v12.0.0-rc.2](https://github.com/stellar/js-stellar-sdk/compare/v11.3.0...v12.0.0-rc.2) diff --git a/README.md b/README.md index 6edbabf81..c0d36aeee 100644 --- a/README.md +++ b/README.md @@ -96,7 +96,7 @@ The usage documentation for this library lives in a handful of places: You can also refer to: * the [documentation](https://developers.stellar.org/network/horizon) for the Horizon REST API (if using the `Horizon` module) and - * the [documentation](https://soroban.stellar.org/docs/reference/rpc) for Soroban RPC's API (if using the `SorobanRpc` module) + * the [documentation](https://soroban.stellar.org/docs/reference/rpc) for Soroban RPC's API (if using the `rpc` module) ### Usage with React-Native diff --git a/package.json b/package.json index 412d408d8..d31f2a319 100644 --- a/package.json +++ b/package.json @@ -23,6 +23,21 @@ "/lib", "/dist" ], + "exports": { + ".": { + "browser": "./dist/stellar-sdk.min.js", + "types": "./lib/index.d.ts", + "default": "./lib/index.js" + }, + "./contract": { + "types": "./lib/contract/index.d.ts", + "default": "./lib/contract/index.js" + }, + "./rpc": { + "types": "./lib/rpc/index.d.ts", + "default": "./lib/rpc/index.js" + } + }, "scripts": { "build": "cross-env NODE_ENV=development yarn _build", "build:prod": "cross-env NODE_ENV=production yarn _build", @@ -109,8 +124,10 @@ "dotenv": "^16.4.5", "eslint": "^8.57.0", "eslint-config-airbnb-base": "^15.0.0", + "eslint-config-airbnb-typescript": "^18.0.0", "eslint-config-prettier": "^9.0.0", "eslint-plugin-import": "^2.29.1", + "eslint-plugin-jsdoc": "^48.2.4", "eslint-plugin-node": "^11.1.0", "eslint-plugin-prefer-import": "^0.0.1", "eslint-plugin-prettier": "^5.1.2", diff --git a/src/.eslintrc.js b/src/.eslintrc.js index 7ceb3272d..97b3cc934 100644 --- a/src/.eslintrc.js +++ b/src/.eslintrc.js @@ -1,6 +1,13 @@ module.exports = { - env: { - es6: true, + extends: [ + "airbnb-base", + "airbnb-typescript/base", + "prettier", + "plugin:jsdoc/recommended", + ], + parserOptions: { + parser: "@typescript-eslint/parser", + project: "./config/tsconfig.json", }, rules: { // OFF @@ -10,6 +17,8 @@ module.exports = { camelcase: 0, "class-methods-use-this": 0, "linebreak-style": 0, + "jsdoc/require-returns": 0, + "jsdoc/require-param": 0, "new-cap": 0, "no-param-reassign": 0, "no-underscore-dangle": 0, @@ -18,19 +27,12 @@ module.exports = { "lines-between-class-members": 0, // WARN - "prefer-import/prefer-import-over-require": [1], + "arrow-body-style": 1, "no-console": ["warn", { allow: ["assert"] }], "no-debugger": 1, - "no-unused-vars": 1, - "arrow-body-style": 1, - "valid-jsdoc": [ - 1, - { - requireReturnDescription: false, - }, - ], - "prefer-const": 1, "object-shorthand": 1, + "prefer-const": 1, + "prefer-import/prefer-import-over-require": [1], "require-await": 1, // ERROR diff --git a/src/contract_client/assembled_transaction.ts b/src/contract/assembled_transaction.ts similarity index 77% rename from src/contract_client/assembled_transaction.ts rename to src/contract/assembled_transaction.ts index 120f1bffd..1f46824b9 100644 --- a/src/contract_client/assembled_transaction.ts +++ b/src/contract/assembled_transaction.ts @@ -1,40 +1,47 @@ -import type { - AssembledTransactionOptions, - ContractClientOptions, - Tx, - XDR_BASE64, -} from "./types"; +/* disable max-classes rule, because extending error shouldn't count! */ +/* eslint max-classes-per-file: 0 */ import { Account, BASE_FEE, Contract, Operation, - SorobanRpc, StrKey, TransactionBuilder, authorizeEntry, xdr, -} from ".."; -import { Err } from "../rust_types"; +} from "@stellar/stellar-base"; +import type { + AssembledTransactionOptions, + ClientOptions, + MethodOptions, + Tx, + XDR_BASE64, +} from "./types"; +import { Server } from "../rpc/server"; +import { Api } from "../rpc/api"; +import { assembleTransaction } from "../rpc/transaction"; +import type { Client } from "./client"; +import { Err } from "./rust_result"; import { DEFAULT_TIMEOUT, contractErrorPattern, - implementsToString + implementsToString, } from "./utils"; import { SentTransaction } from "./sent_transaction"; -export const NULL_ACCOUNT = "GAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAWHF" +export const NULL_ACCOUNT = + "GAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAWHF"; /** - * The main workhorse of {@link ContractClient}. This class is used to wrap a + * The main workhorse of {@link Client}. This class is used to wrap a * transaction-under-construction and provide high-level interfaces to the most * common workflows, while still providing access to low-level stellar-sdk * transaction manipulation. * * Most of the time, you will not construct an `AssembledTransaction` directly, - * but instead receive one as the return value of a `ContractClient` method. If + * but instead receive one as the return value of a `Client` method. If * you're familiar with the libraries generated by soroban-cli's `contract - * bindings typescript` command, these also wraps `ContractClient` and return + * bindings typescript` command, these also wraps `Client` and return * `AssembledTransaction` instances. * * Let's look at examples of how to use `AssembledTransaction` for a variety of @@ -48,20 +55,29 @@ export const NULL_ACCOUNT = "GAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA * ```ts * const { result } = await AssembledTransaction.build({ * method: 'myReadMethod', - * args: spec.funcArgsToScVals('myReadMethod', { args: 'for', my: 'method', ... }), + * args: spec.funcArgsToScVals('myReadMethod', { + * args: 'for', + * my: 'method', + * ... + * }), * contractId: 'C123…', * networkPassphrase: '…', * rpcUrl: 'https://…', - * publicKey: Keypair.random().publicKey(), // keypairs are irrelevant, for simulation-only read calls - * parseResultXdr: (result: xdr.ScVal) => spec.funcResToNative('myReadMethod', result), + * publicKey: undefined, // irrelevant, for simulation-only read calls + * parseResultXdr: (result: xdr.ScVal) => + * spec.funcResToNative('myReadMethod', result), * }) * ``` * * While that looks pretty complicated, most of the time you will use this in - * conjunction with {@link ContractClient}, which simplifies it to: + * conjunction with {@link Client}, which simplifies it to: * * ```ts - * const { result } = await client.myReadMethod({ args: 'for', my: 'method', ... }) + * const { result } = await client.myReadMethod({ + * args: 'for', + * my: 'method', + * ... + * }) * ``` * * # 2. Simple write call @@ -70,11 +86,15 @@ export const NULL_ACCOUNT = "GAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA * further manipulation, only one more step is needed: * * ```ts - * const assembledTx = await client.myWriteMethod({ args: 'for', my: 'method', ... }) + * const assembledTx = await client.myWriteMethod({ + * args: 'for', + * my: 'method', + * ... + * }) * const sentTx = await assembledTx.signAndSend() * ``` * - * Here we're assuming that you're using a {@link ContractClient}, rather than + * Here we're assuming that you're using a {@link Client}, rather than * constructing `AssembledTransaction`'s directly. * * Note that `sentTx`, the return value of `signAndSend`, is a @@ -98,7 +118,7 @@ export const NULL_ACCOUNT = "GAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA * * If you need more control over the transaction before simulating it, you can * set various {@link MethodOptions} when constructing your - * `AssembledTransaction`. With a {@link ContractClient}, this is passed as a + * `AssembledTransaction`. With a {@link Client}, this is passed as a * second object after the arguments (or the only object, if the method takes * no arguments): * @@ -124,7 +144,8 @@ export const NULL_ACCOUNT = "GAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA * await tx.simulate() * ``` * - * If you need to inspect the simulation later, you can access it with `tx.simulation`. + * If you need to inspect the simulation later, you can access it with + * `tx.simulation`. * * # 4. Multi-auth workflows * @@ -204,7 +225,7 @@ export const NULL_ACCOUNT = "GAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA * ``` * * Under the hood, this uses `signAuthEntry`, which you either need to inject - * during initial construction of the `ContractClient`/`AssembledTransaction`, + * during initial construction of the `Client`/`AssembledTransaction`, * or which you can pass directly to `signAuthEntries`. * * Now Bob can again serialize the transaction and send back to Alice, where @@ -230,12 +251,14 @@ export class AssembledTransaction { * ``` */ public raw?: TransactionBuilder; + /** * The Transaction as it was built with `raw.build()` right before * simulation. Once this is set, modifying `raw` will have no effect unless * you call `tx.simulate()` again. */ public built?: Tx; + /** * The result of the transaction simulation. This is set after the first call * to `simulate`. It is difficult to serialize and deserialize, so it is not @@ -243,34 +266,39 @@ export class AssembledTransaction { * cached, serializable access to the data needed by AssembledTransaction * logic. */ - public simulation?: SorobanRpc.Api.SimulateTransactionResponse; + public simulation?: Api.SimulateTransactionResponse; + /** * Cached simulation result. This is set after the first call to - * {@link simulationData}, and is used to facilitate serialization and - * deserialization of the AssembledTransaction. + * {@link AssembledTransaction#simulationData}, and is used to facilitate + * serialization and deserialization of the AssembledTransaction. * - * Most of the time, if you need this data, you can call `tx.simulation.result`. + * Most of the time, if you need this data, you can call + * `tx.simulation.result`. * * If you need access to this data after a transaction has been serialized * and then deserialized, you can call `simulationData.result`. */ - private simulationResult?: SorobanRpc.Api.SimulateHostFunctionResult; + private simulationResult?: Api.SimulateHostFunctionResult; + /** * Cached simulation transaction data. This is set after the first call to - * {@link simulationData}, and is used to facilitate serialization and - * deserialization of the AssembledTransaction. + * {@link AssembledTransaction#simulationData}, and is used to facilitate + * serialization and deserialization of the AssembledTransaction. * - * Most of the time, if you need this data, you can call `simulation.transactionData`. + * Most of the time, if you need this data, you can call + * `simulation.transactionData`. * * If you need access to this data after a transaction has been serialized * and then deserialized, you can call `simulationData.transactionData`. */ private simulationTransactionData?: xdr.SorobanTransactionData; + /** * The Soroban server to use for all RPC calls. This is constructed from the * `rpcUrl` in the options. */ - private server: SorobanRpc.Server; + private server: Server; /** * A list of the most important errors that various AssembledTransaction @@ -278,14 +306,14 @@ export class AssembledTransaction { * logic. */ static Errors = { - ExpiredState: class ExpiredStateError extends Error {}, - NeedsMoreSignatures: class NeedsMoreSignaturesError extends Error {}, - NoSignatureNeeded: class NoSignatureNeededError extends Error {}, - NoUnsignedNonInvokerAuthEntries: class NoUnsignedNonInvokerAuthEntriesError extends Error {}, - NoSigner: class NoSignerError extends Error {}, - NotYetSimulated: class NotYetSimulatedError extends Error {}, - FakeAccount: class FakeAccountError extends Error {}, - } + ExpiredState: class ExpiredStateError extends Error { }, + NeedsMoreSignatures: class NeedsMoreSignaturesError extends Error { }, + NoSignatureNeeded: class NoSignatureNeededError extends Error { }, + NoUnsignedNonInvokerAuthEntries: class NoUnsignedNonInvokerAuthEntriesError extends Error { }, + NoSigner: class NoSignerError extends Error { }, + NotYetSimulated: class NotYetSimulatedError extends Error { }, + FakeAccount: class FakeAccountError extends Error { }, + }; /** * Serialize the AssembledTransaction to a JSON string. This is useful for @@ -319,26 +347,26 @@ export class AssembledTransaction { retval: XDR_BASE64; }; simulationTransactionData: XDR_BASE64; - } + }, ): AssembledTransaction { const txn = new AssembledTransaction(options); - txn.built = TransactionBuilder.fromXDR(tx, options.networkPassphrase) as Tx + txn.built = TransactionBuilder.fromXDR(tx, options.networkPassphrase) as Tx; txn.simulationResult = { auth: simulationResult.auth.map((a) => - xdr.SorobanAuthorizationEntry.fromXDR(a, "base64") + xdr.SorobanAuthorizationEntry.fromXDR(a, "base64"), ), retval: xdr.ScVal.fromXDR(simulationResult.retval, "base64"), }; txn.simulationTransactionData = xdr.SorobanTransactionData.fromXDR( simulationTransactionData, - "base64" + "base64", ); return txn; } private constructor(public options: AssembledTransactionOptions) { this.options.simulate = this.options.simulate ?? true; - this.server = new SorobanRpc.Server(this.options.rpcUrl, { + this.server = new Server(this.options.rpcUrl, { allowHttp: this.options.allowHttp ?? false, }); } @@ -362,7 +390,7 @@ export class AssembledTransaction { * }) */ static async build( - options: AssembledTransactionOptions + options: AssembledTransactionOptions, ): Promise> { const tx = new AssembledTransaction(options); const contract = new Contract(options.contractId); @@ -386,18 +414,18 @@ export class AssembledTransaction { simulate = async (): Promise => { if (!this.raw) { throw new Error( - 'Transaction has not yet been assembled; ' + - 'call `AssembledTransaction.build` first.' + "Transaction has not yet been assembled; " + + "call `AssembledTransaction.build` first.", ); } this.built = this.raw.build(); this.simulation = await this.server.simulateTransaction(this.built); - if (SorobanRpc.Api.isSimulationSuccess(this.simulation)) { - this.built = SorobanRpc.assembleTransaction( + if (Api.isSimulationSuccess(this.simulation)) { + this.built = assembleTransaction( this.built, - this.simulation + this.simulation, ).build(); } @@ -405,7 +433,7 @@ export class AssembledTransaction { }; get simulationData(): { - result: SorobanRpc.Api.SimulateHostFunctionResult; + result: Api.SimulateHostFunctionResult; transactionData: xdr.SorobanTransactionData; } { if (this.simulationResult && this.simulationTransactionData) { @@ -416,19 +444,21 @@ export class AssembledTransaction { } const simulation = this.simulation!; if (!simulation) { - throw new AssembledTransaction.Errors.NotYetSimulated("Transaction has not yet been simulated"); + throw new AssembledTransaction.Errors.NotYetSimulated( + "Transaction has not yet been simulated", + ); } - if (SorobanRpc.Api.isSimulationError(simulation)) { + if (Api.isSimulationError(simulation)) { throw new Error(`Transaction simulation failed: "${simulation.error}"`); } - if (SorobanRpc.Api.isSimulationRestore(simulation)) { + if (Api.isSimulationRestore(simulation)) { throw new AssembledTransaction.Errors.ExpiredState( `You need to restore some contract state before you can invoke this method. ${JSON.stringify( simulation, null, - 2 - )}` + 2, + )}`, ); } @@ -437,8 +467,8 @@ export class AssembledTransaction { `Expected an invocation simulation, but got no 'result' field. Simulation: ${JSON.stringify( simulation, null, - 2 - )}` + 2, + )}`, ); } @@ -457,9 +487,9 @@ export class AssembledTransaction { return this.options.parseResultXdr(this.simulationData.result.retval); } catch (e) { if (!implementsToString(e)) throw e; - let err = this.parseError(e.toString()); + const err = this.parseError(e.toString()); if (err) return err as T; - throw e; + throw e; // eslint-disable-line } } @@ -467,8 +497,8 @@ export class AssembledTransaction { if (!this.options.errorTypes) return undefined; const match = errorMessage.match(contractErrorPattern); if (!match) return undefined; - let i = parseInt(match[1], 10); - let err = this.options.errorTypes[i]; + const i = parseInt(match[1], 10); + const err = this.options.errorTypes[i]; if (!err) return undefined; return new Err(err); } @@ -485,13 +515,13 @@ export class AssembledTransaction { signTransaction = this.options.signTransaction, }: { /** - * If `true`, sign and send the transaction even if it is a read call. + * If `true`, sign and send the transaction even if it is a read call */ force?: boolean; /** * You must provide this here if you did not provide one before */ - signTransaction?: ContractClientOptions["signTransaction"]; + signTransaction?: ClientOptions["signTransaction"]; } = {}): Promise> => { if (!this.built) { throw new Error("Transaction has not yet been simulated"); @@ -500,31 +530,32 @@ export class AssembledTransaction { if (!force && this.isReadCall) { throw new AssembledTransaction.Errors.NoSignatureNeeded( "This is a read call. It requires no signature or sending. " + - "Use `force: true` to sign and send anyway." + "Use `force: true` to sign and send anyway.", ); } if (!signTransaction) { throw new AssembledTransaction.Errors.NoSigner( "You must provide a signTransaction function, either when calling " + - "`signAndSend` or when initializing your ContractClient" + "`signAndSend` or when initializing your Client", ); } - if ((await this.needsNonInvokerSigningBy()).length) { + if (this.needsNonInvokerSigningBy().length) { throw new AssembledTransaction.Errors.NeedsMoreSignatures( "Transaction requires more signatures. " + - "See `needsNonInvokerSigningBy` for details." + "See `needsNonInvokerSigningBy` for details.", ); } - const typeChecked: AssembledTransaction = this - return await SentTransaction.init(signTransaction, typeChecked); + const typeChecked: AssembledTransaction = this; + const sent = await SentTransaction.init(signTransaction, typeChecked); + return sent; }; private getStorageExpiration = async () => { const entryRes = await this.server.getLedgerEntries( - new Contract(this.options.contractId).getFootprint() + new Contract(this.options.contractId).getFootprint(), ); if ( !entryRes.entries || @@ -551,17 +582,18 @@ export class AssembledTransaction { * One at a time, for each public key in this array, you will need to * serialize this transaction with `toJSON`, send to the owner of that key, * deserialize the transaction with `txFromJson`, and call - * {@link signAuthEntries}. Then re-serialize and send to the next account - * in this list. + * {@link AssembledTransaction#signAuthEntries}. Then re-serialize and send to + * the next account in this list. */ - needsNonInvokerSigningBy = async ({ + needsNonInvokerSigningBy = ({ includeAlreadySigned = false, }: { /** - * Whether or not to include auth entries that have already been signed. Default: false + * Whether or not to include auth entries that have already been signed. + * Default: false */ includeAlreadySigned?: boolean; - } = {}): Promise => { + } = {}): string[] => { if (!this.built) { throw new Error("Transaction has not yet been simulated"); } @@ -572,8 +604,8 @@ export class AssembledTransaction { if (!("operations" in this.built)) { throw new Error( `Unexpected Transaction type; no operations: ${JSON.stringify( - this.built - )}` + this.built, + )}`, ); } const rawInvokeHostFunctionOp = this.built @@ -585,34 +617,35 @@ export class AssembledTransaction { .filter( (entry) => entry.credentials().switch() === - xdr.SorobanCredentialsType.sorobanCredentialsAddress() && + xdr.SorobanCredentialsType.sorobanCredentialsAddress() && (includeAlreadySigned || entry.credentials().address().signature().switch().name === - "scvVoid") + "scvVoid"), ) .map((entry) => StrKey.encodeEd25519PublicKey( - entry.credentials().address().address().accountId().ed25519() - ) - ) + entry.credentials().address().address().accountId().ed25519(), + ), + ), ), ]; }; /** - * If {@link needsNonInvokerSigningBy} returns a non-empty list, you can serialize - * the transaction with `toJSON`, send it to the owner of one of the public keys - * in the map, deserialize with `txFromJSON`, and call this method on their - * machine. Internally, this will use `signAuthEntry` function from connected - * `wallet` for each. + * If {@link AssembledTransaction#needsNonInvokerSigningBy} returns a + * non-empty list, you can serialize the transaction with `toJSON`, send it to + * the owner of one of the public keys in the map, deserialize with + * `txFromJSON`, and call this method on their machine. Internally, this will + * use `signAuthEntry` function from connected `wallet` for each. * * Then, re-serialize the transaction and either send to the next * `needsNonInvokerSigningBy` owner, or send it back to the original account - * who simulated the transaction so they can {@link sign} the transaction - * envelope and {@link send} it to the network. + * who simulated the transaction so they can {@link AssembledTransaction#sign} + * the transaction envelope and {@link AssembledTransaction#send} it to the + * network. * - * Sending to all `needsNonInvokerSigningBy` owners in parallel is not currently - * supported! + * Sending to all `needsNonInvokerSigningBy` owners in parallel is not + * currently supported! */ signAuthEntries = async ({ expiration = this.getStorageExpiration(), @@ -625,7 +658,7 @@ export class AssembledTransaction { * contract's current `persistent` storage expiration date/ledger * number/block. */ - expiration?: number | Promise + expiration?: number | Promise; /** * Sign all auth entries for this account. Default: the account that * constructed the transaction @@ -633,29 +666,29 @@ export class AssembledTransaction { publicKey?: string; /** * You must provide this here if you did not provide one before. Default: - * the `signAuthEntry` function from the `ContractClient` options. Must + * the `signAuthEntry` function from the `Client` options. Must * sign things as the given `publicKey`. */ - signAuthEntry?: ContractClientOptions["signAuthEntry"]; + signAuthEntry?: ClientOptions["signAuthEntry"]; } = {}): Promise => { if (!this.built) throw new Error("Transaction has not yet been assembled or simulated"); - const needsNonInvokerSigningBy = await this.needsNonInvokerSigningBy(); + const needsNonInvokerSigningBy = this.needsNonInvokerSigningBy(); if (!needsNonInvokerSigningBy) { throw new AssembledTransaction.Errors.NoUnsignedNonInvokerAuthEntries( - "No unsigned non-invoker auth entries; maybe you already signed?" + "No unsigned non-invoker auth entries; maybe you already signed?", ); } - if (needsNonInvokerSigningBy.indexOf(publicKey ?? '') === -1) { + if (needsNonInvokerSigningBy.indexOf(publicKey ?? "") === -1) { throw new AssembledTransaction.Errors.NoSignatureNeeded( - `No auth entries for public key "${publicKey}"` + `No auth entries for public key "${publicKey}"`, ); } if (!signAuthEntry) { throw new AssembledTransaction.Errors.NoSigner( - 'You must provide `signAuthEntry` when calling `signAuthEntries`, ' + - 'or when constructing the `ContractClient` or `AssembledTransaction`' + "You must provide `signAuthEntry` when calling `signAuthEntries`, " + + "or when constructing the `Client` or `AssembledTransaction`", ); } @@ -664,6 +697,7 @@ export class AssembledTransaction { const authEntries = rawInvokeHostFunctionOp.auth ?? []; + // eslint-disable-next-line no-restricted-syntax for (const [i, entry] of authEntries.entries()) { if ( entry.credentials().switch() !== @@ -672,25 +706,23 @@ export class AssembledTransaction { // if the invoker/source account, then the entry doesn't need explicit // signature, since the tx envelope is already signed by the source // account, so only check for sorobanCredentialsAddress - continue; + continue; // eslint-disable-line no-continue } const pk = StrKey.encodeEd25519PublicKey( - entry.credentials().address().address().accountId().ed25519() + entry.credentials().address().address().accountId().ed25519(), ); // this auth entry needs to be signed by a different account // (or maybe already was!) - if (pk !== publicKey) continue; + if (pk !== publicKey) continue; // eslint-disable-line no-continue + // eslint-disable-next-line no-await-in-loop authEntries[i] = await authorizeEntry( entry, async (preimage) => - Buffer.from( - await signAuthEntry(preimage.toXDR("base64")), - "base64" - ), - await expiration, - this.options.networkPassphrase + Buffer.from(await signAuthEntry(preimage.toXDR("base64")), "base64"), + await expiration, // eslint-disable-line no-await-in-loop + this.options.networkPassphrase, ); } }; diff --git a/src/contract/basic_node_signer.ts b/src/contract/basic_node_signer.ts new file mode 100644 index 000000000..9e50327cd --- /dev/null +++ b/src/contract/basic_node_signer.ts @@ -0,0 +1,27 @@ +import { Keypair, TransactionBuilder, hash } from "@stellar/stellar-base"; +import type { AssembledTransaction } from "./assembled_transaction"; +import type { Client } from "./client"; + +/** + * For use with {@link Client} and {@link AssembledTransaction}. + * Implements `signTransaction` and `signAuthEntry` with signatures expected by + * those classes. This is useful for testing and maybe some simple Node + * applications. Feel free to use this as a starting point for your own + * Wallet/TransactionSigner implementation. + */ +export const basicNodeSigner = ( + /** {@link Keypair} to use to sign the transaction or auth entry */ + keypair: Keypair, + /** passphrase of network to sign for */ + networkPassphrase: string, +) => ({ + // eslint-disable-next-line require-await + signTransaction: async (tx: string) => { + const t = TransactionBuilder.fromXDR(tx, networkPassphrase); + t.sign(keypair); + return t.toXDR(); + }, + // eslint-disable-next-line require-await + signAuthEntry: async (entryXdr: string): Promise => + keypair.sign(hash(Buffer.from(entryXdr, "base64"))).toString("base64"), +}); diff --git a/src/contract_client/client.ts b/src/contract/client.ts similarity index 66% rename from src/contract_client/client.ts rename to src/contract/client.ts index 37c0b724d..e0c298b4a 100644 --- a/src/contract_client/client.ts +++ b/src/contract/client.ts @@ -1,10 +1,11 @@ -import { ContractSpec, xdr } from ".."; -import { Server } from '../soroban'; +import { xdr } from "@stellar/stellar-base"; +import { Spec } from "./spec"; +import { Server } from '../rpc'; import { AssembledTransaction } from "./assembled_transaction"; -import type { ContractClientOptions, MethodOptions } from "./types"; +import type { ClientOptions, MethodOptions } from "./types"; import { processSpecEntryStream } from './utils'; -export class ContractClient { +export class Client { /** * Generate a class from the contract spec that where each contract method * gets included with an identical name. @@ -14,8 +15,10 @@ export class ContractClient { * transaction. */ constructor( - public readonly spec: ContractSpec, - public readonly options: ContractClientOptions, + /** {@link Spec} to construct a Client for */ + public readonly spec: Spec, + /** see {@link ClientOptions} */ + public readonly options: ClientOptions, ) { this.spec.funcs().forEach((xdrFn) => { const method = xdrFn.name().toString(); @@ -33,7 +36,7 @@ export class ContractClient { ...acc, [curr.value()]: { message: curr.doc().toString() }, }), - {} as Pick, + {} as Pick, ), parseResultXdr: (result: xdr.ScVal) => spec.funcResToNative(method, result), @@ -48,19 +51,19 @@ export class ContractClient { } /** - * Generates a ContractClient instance from the provided ContractClientOptions and the contract's wasm hash. + * Generates a Client instance from the provided ClientOptions and the contract's wasm hash. * The wasmHash can be provided in either hex or base64 format. * * @param wasmHash The hash of the contract's wasm binary, in either hex or base64 format. - * @param options The ContractClientOptions object containing the necessary configuration, including the rpcUrl. + * @param options The ClientOptions object containing the necessary configuration, including the rpcUrl. * @param format The format of the provided wasmHash, either "hex" or "base64". Defaults to "hex". - * @returns A Promise that resolves to a ContractClient instance. + * @returns A Promise that resolves to a Client instance. * @throws {TypeError} If the provided options object does not contain an rpcUrl. */ - static async fromWasmHash(wasmHash: Buffer | string, - options: ContractClientOptions, + static async fromWasmHash(wasmHash: Buffer | string, + options: ClientOptions, format: "hex" | "base64" = "hex" - ): Promise { + ): Promise { if (!options || !options.rpcUrl) { throw new TypeError('options must contain rpcUrl'); } @@ -68,18 +71,18 @@ export class ContractClient { const serverOpts: Server.Options = { allowHttp }; const server = new Server(rpcUrl, serverOpts); const wasm = await server.getContractWasmByHash(wasmHash, format); - return ContractClient.fromWasm(wasm, options); + return Client.fromWasm(wasm, options); } /** - * Generates a ContractClient instance from the provided ContractClientOptions and the contract's wasm binary. + * Generates a Client instance from the provided ClientOptions and the contract's wasm binary. * * @param wasm The contract's wasm binary as a Buffer. - * @param options The ContractClientOptions object containing the necessary configuration. - * @returns A Promise that resolves to a ContractClient instance. + * @param options The ClientOptions object containing the necessary configuration. + * @returns A Promise that resolves to a Client instance. * @throws {Error} If the contract spec cannot be obtained from the provided wasm binary. */ - static async fromWasm(wasm: Buffer, options: ContractClientOptions): Promise { + static async fromWasm(wasm: Buffer, options: ClientOptions): Promise { const wasmModule = await WebAssembly.compile(wasm); const xdrSections = WebAssembly.Module.customSections(wasmModule, "contractspecv0"); if (xdrSections.length === 0) { @@ -87,18 +90,18 @@ export class ContractClient { } const bufferSection = Buffer.from(xdrSections[0]); const specEntryArray = processSpecEntryStream(bufferSection); - const spec = new ContractSpec(specEntryArray); - return new ContractClient(spec, options); + const spec = new Spec(specEntryArray); + return new Client(spec, options); } /** - * Generates a ContractClient instance from the provided ContractClientOptions, which must include the contractId and rpcUrl. + * Generates a Client instance from the provided ClientOptions, which must include the contractId and rpcUrl. * - * @param options The ContractClientOptions object containing the necessary configuration, including the contractId and rpcUrl. - * @returns A Promise that resolves to a ContractClient instance. + * @param options The ClientOptions object containing the necessary configuration, including the contractId and rpcUrl. + * @returns A Promise that resolves to a Client instance. * @throws {TypeError} If the provided options object does not contain both rpcUrl and contractId. */ - static async from(options: ContractClientOptions): Promise { + static async from(options: ClientOptions): Promise { if (!options || !options.rpcUrl || !options.contractId) { throw new TypeError('options must contain rpcUrl and contractId'); } @@ -106,7 +109,7 @@ export class ContractClient { const serverOpts: Server.Options = { allowHttp }; const server = new Server(rpcUrl, serverOpts); const wasm = await server.getContractWasmByContractId(contractId); - return ContractClient.fromWasm(wasm, options); + return Client.fromWasm(wasm, options); } txFromJSON = (json: string): AssembledTransaction => { diff --git a/src/contract/index.ts b/src/contract/index.ts new file mode 100644 index 000000000..8b9e1dc5e --- /dev/null +++ b/src/contract/index.ts @@ -0,0 +1,7 @@ +export * from "./assembled_transaction"; +export * from "./basic_node_signer"; +export * from "./client"; +export * from "./rust_result"; +export * from "./sent_transaction"; +export * from "./spec"; +export * from "./types"; diff --git a/src/rust_types/result.ts b/src/contract/rust_result.ts similarity index 85% rename from src/rust_types/result.ts rename to src/contract/rust_result.ts index 8a47de4f4..b3c160851 100644 --- a/src/rust_types/result.ts +++ b/src/contract/rust_result.ts @@ -1,3 +1,6 @@ +/* disable max-classes rule, because extending error shouldn't count! */ +/* eslint max-classes-per-file: 0 */ + /** * A minimal implementation of Rust's `Result` type. Used for contract * methods that return Results, to maintain their distinction from methods @@ -52,10 +55,22 @@ export interface ErrorMessage { */ export class Ok implements Result { constructor(readonly value: T) {} - unwrapErr(): never { throw new Error("No error") } - unwrap() { return this.value } - isOk() { return true } - isErr() { return false } + + unwrapErr(): never { + throw new Error("No error"); + } + + unwrap() { + return this.value; + } + + isOk() { + return true; + } + + isErr() { + return false; + } } /** @@ -65,8 +80,20 @@ export class Ok implements Result { */ export class Err implements Result { constructor(readonly error: E) {} - unwrapErr() { return this.error } - unwrap(): never { throw new Error(this.error.message) } - isOk() { return false } - isErr() { return true } + + unwrapErr() { + return this.error; + } + + unwrap(): never { + throw new Error(this.error.message); + } + + isOk() { + return false; + } + + isErr() { + return true; + } } diff --git a/src/contract_client/sent_transaction.ts b/src/contract/sent_transaction.ts similarity index 68% rename from src/contract_client/sent_transaction.ts rename to src/contract/sent_transaction.ts index a1276f747..d6f275d78 100644 --- a/src/contract_client/sent_transaction.ts +++ b/src/contract/sent_transaction.ts @@ -1,7 +1,11 @@ -import type { ContractClientOptions, Tx } from "./types"; -import { SorobanDataBuilder, SorobanRpc, TransactionBuilder } from ".."; +/* disable max-classes rule, because extending error shouldn't count! */ +/* eslint max-classes-per-file: 0 */ +import { SorobanDataBuilder, TransactionBuilder } from "@stellar/stellar-base"; +import type { ClientOptions, MethodOptions, Tx } from "./types"; +import { Server } from "../rpc/server" +import { Api } from "../rpc/api" import { DEFAULT_TIMEOUT, withExponentialBackoff } from "./utils"; -import { AssembledTransaction } from "./assembled_transaction"; +import type { AssembledTransaction } from "./assembled_transaction"; /** * A transaction that has been sent to the Soroban network. This happens in two steps: @@ -18,33 +22,38 @@ import { AssembledTransaction } from "./assembled_transaction"; * `getTransactionResponse`. */ export class SentTransaction { - public server: SorobanRpc.Server; + public server: Server; + public signed?: Tx; + /** * The result of calling `sendTransaction` to broadcast the transaction to the * network. */ - public sendTransactionResponse?: SorobanRpc.Api.SendTransactionResponse; + public sendTransactionResponse?: Api.SendTransactionResponse; + /** * If `sendTransaction` completes successfully (which means it has `status: 'PENDING'`), * then `getTransaction` will be called in a loop for * {@link MethodOptions.timeoutInSeconds} seconds. This array contains all * the results of those calls. */ - public getTransactionResponseAll?: SorobanRpc.Api.GetTransactionResponse[]; + public getTransactionResponseAll?: Api.GetTransactionResponse[]; + /** * The most recent result of calling `getTransaction`, from the * `getTransactionResponseAll` array. */ - public getTransactionResponse?: SorobanRpc.Api.GetTransactionResponse; + public getTransactionResponse?: Api.GetTransactionResponse; static Errors = { - SendFailed: class SendFailedError extends Error {}, - SendResultOnly: class SendResultOnlyError extends Error {}, + SendFailed: class SendFailedError extends Error { }, + SendResultOnly: class SendResultOnlyError extends Error { }, + TransactionStillPending: class TransactionStillPendingError extends Error { }, }; constructor( - public signTransaction: ContractClientOptions["signTransaction"], + public signTransaction: ClientOptions["signTransaction"], public assembled: AssembledTransaction, ) { if (!signTransaction) { @@ -52,7 +61,7 @@ export class SentTransaction { "You must provide a `signTransaction` function to send a transaction", ); } - this.server = new SorobanRpc.Server(this.assembled.options.rpcUrl, { + this.server = new Server(this.assembled.options.rpcUrl, { allowHttp: this.assembled.options.allowHttp ?? false, }); } @@ -62,20 +71,26 @@ export class SentTransaction { * a `signTransaction` function. This will also send the transaction to the * network. */ - static init = async ( - signTransaction: ContractClientOptions["signTransaction"], - assembled: AssembledTransaction, - ): Promise> => { + static init = async ( + /** More info in {@link MethodOptions} */ + signTransaction: ClientOptions["signTransaction"], + /** {@link AssembledTransaction} from which this SentTransaction was initialized */ + assembled: AssembledTransaction, + ): Promise> => { const tx = new SentTransaction(signTransaction, assembled); - return await tx.send(); + const sent = await tx.send(); + return sent; }; private send = async (): Promise => { - const timeoutInSeconds = this.assembled.options.timeoutInSeconds ?? DEFAULT_TIMEOUT + const timeoutInSeconds = + this.assembled.options.timeoutInSeconds ?? DEFAULT_TIMEOUT; this.assembled.built = TransactionBuilder.cloneFrom(this.assembled.built!, { fee: this.assembled.built!.fee, timebounds: undefined, - sorobanData: new SorobanDataBuilder(this.assembled.simulationData.transactionData.toXDR()).build() + sorobanData: new SorobanDataBuilder( + this.assembled.simulationData.transactionData.toXDR(), + ).build(), }) .setTimeout(timeoutInSeconds) .build(); @@ -99,8 +114,11 @@ export class SentTransaction { if (this.sendTransactionResponse.status !== "PENDING") { throw new SentTransaction.Errors.SendFailed( - "Sending the transaction to the network failed!\n" + - JSON.stringify(this.sendTransactionResponse, null, 2), + `Sending the transaction to the network failed!\n${JSON.stringify( + this.sendTransactionResponse, + null, + 2, + )}`, ); } @@ -108,7 +126,7 @@ export class SentTransaction { this.getTransactionResponseAll = await withExponentialBackoff( () => this.server.getTransaction(hash), - (resp) => resp.status === SorobanRpc.Api.GetTransactionStatus.NOT_FOUND, + (resp) => resp.status === Api.GetTransactionStatus.NOT_FOUND, timeoutInSeconds, ); @@ -116,21 +134,21 @@ export class SentTransaction { this.getTransactionResponseAll[this.getTransactionResponseAll.length - 1]; if ( this.getTransactionResponse.status === - SorobanRpc.Api.GetTransactionStatus.NOT_FOUND + Api.GetTransactionStatus.NOT_FOUND ) { - console.error( + throw new SentTransaction.Errors.TransactionStillPending( `Waited ${timeoutInSeconds} seconds for transaction to complete, but it did not. ` + - `Returning anyway. Check the transaction status manually. ` + - `Sent transaction: ${JSON.stringify( - this.sendTransactionResponse, - null, - 2, - )}\n` + - `All attempts to get the result: ${JSON.stringify( - this.getTransactionResponseAll, - null, - 2, - )}`, + `Returning anyway. Check the transaction status manually. ` + + `Sent transaction: ${JSON.stringify( + this.sendTransactionResponse, + null, + 2, + )}\n` + + `All attempts to get the result: ${JSON.stringify( + this.getTransactionResponseAll, + null, + 2, + )}`, ); } @@ -147,7 +165,8 @@ export class SentTransaction { ); } - // if "returnValue" not present, the transaction failed; return without parsing the result + // if "returnValue" not present, the transaction failed; return without + // parsing the result throw new Error("Transaction failed! Cannot parse result."); } diff --git a/src/contract_spec.ts b/src/contract/spec.ts similarity index 96% rename from src/contract_spec.ts rename to src/contract/spec.ts index 91add10fb..937875fbb 100644 --- a/src/contract_spec.ts +++ b/src/contract/spec.ts @@ -1,4 +1,4 @@ -import { JSONSchema7, JSONSchema7Definition } from "json-schema"; +import type { JSONSchema7, JSONSchema7Definition } from "json-schema"; import { ScIntType, XdrLargeInt, @@ -6,8 +6,8 @@ import { Address, Contract, scValToBigInt, -} from "."; -import { Ok } from "./rust_types"; +} from "@stellar/stellar-base" +import { Ok } from "./rust_result" export interface Union { tag: string; @@ -48,7 +48,7 @@ function readObj(args: object, input: xdr.ScSpecFunctionInputV0): any { * console.log(result); // {success: true} * ``` */ -export class ContractSpec { +export class Spec { public entries: xdr.ScSpecEntry[] = []; /** @@ -65,7 +65,7 @@ export class ContractSpec { let entry = entries[0]; if (typeof entry === "string") { this.entries = (entries as string[]).map((s) => - xdr.ScSpecEntry.fromXDR(s, "base64") + xdr.ScSpecEntry.fromXDR(s, "base64"), ); } else { this.entries = entries as xdr.ScSpecEntry[]; @@ -83,7 +83,7 @@ export class ContractSpec { .filter( (entry) => entry.switch().value === - xdr.ScSpecEntryKind.scSpecEntryFunctionV0().value + xdr.ScSpecEntryKind.scSpecEntryFunctionV0().value, ) .map((entry) => entry.functionV0()); } @@ -164,9 +164,7 @@ export class ContractSpec { } let output = outputs[0]; if (output.switch().value === xdr.ScSpecType.scSpecTypeResult().value) { - return new Ok( - this.scValToNative(val, output.result().okType()) - ); + return new Ok(this.scValToNative(val, output.result().okType())); } return this.scValToNative(val, output); } @@ -181,7 +179,7 @@ export class ContractSpec { */ findEntry(name: string): xdr.ScSpecEntry { let entry = this.entries.find( - (entry) => entry.value().name().toString() === name + (entry) => entry.value().name().toString() === name, ); if (!entry) { throw new Error(`no such entry: ${name}`); @@ -220,7 +218,7 @@ export class ContractSpec { return xdr.ScVal.scvVoid(); default: throw new TypeError( - `Type ${ty} was not void, but value was null` + `Type ${ty} was not void, but value was null`, ); } } @@ -232,7 +230,7 @@ export class ContractSpec { if (val instanceof Address) { if (ty.switch().value !== xdr.ScSpecType.scSpecTypeAddress().value) { throw new TypeError( - `Type ${ty} was not address, but value was Address` + `Type ${ty} was not address, but value was Address`, ); } return val.toScVal(); @@ -241,7 +239,7 @@ export class ContractSpec { if (val instanceof Contract) { if (ty.switch().value !== xdr.ScSpecType.scSpecTypeAddress().value) { throw new TypeError( - `Type ${ty} was not address, but value was Address` + `Type ${ty} was not address, but value was Address`, ); } return val.address().toScVal(); @@ -254,7 +252,7 @@ export class ContractSpec { let bytes_n = ty.bytesN(); if (copy.length !== bytes_n.n()) { throw new TypeError( - `expected ${bytes_n.n()} bytes, but got ${copy.length}` + `expected ${bytes_n.n()} bytes, but got ${copy.length}`, ); } //@ts-ignore @@ -265,7 +263,7 @@ export class ContractSpec { return xdr.ScVal.scvBytes(copy); default: throw new TypeError( - `invalid type (${ty}) specified for Bytes and BytesN` + `invalid type (${ty}) specified for Bytes and BytesN`, ); } } @@ -275,7 +273,7 @@ export class ContractSpec { let vec = ty.vec(); let elementType = vec.elementType(); return xdr.ScVal.scvVec( - val.map((v) => this.nativeToScVal(v, elementType)) + val.map((v) => this.nativeToScVal(v, elementType)), ); } case xdr.ScSpecType.scSpecTypeTuple().value: { @@ -283,11 +281,11 @@ export class ContractSpec { let valTypes = tup.valueTypes(); if (val.length !== valTypes.length) { throw new TypeError( - `Tuple expects ${valTypes.length} values, but ${val.length} were provided` + `Tuple expects ${valTypes.length} values, but ${val.length} were provided`, ); } return xdr.ScVal.scvVec( - val.map((v, i) => this.nativeToScVal(v, valTypes[i])) + val.map((v, i) => this.nativeToScVal(v, valTypes[i])), ); } case xdr.ScSpecType.scSpecTypeMap().value: { @@ -299,13 +297,13 @@ export class ContractSpec { let key = this.nativeToScVal(entry[0], keyType); let val = this.nativeToScVal(entry[1], valueType); return new xdr.ScMapEntry({ key, val }); - }) + }), ); } default: throw new TypeError( - `Type ${ty} was not vec, but value was Array` + `Type ${ty} was not vec, but value was Array`, ); } } @@ -330,14 +328,13 @@ export class ContractSpec { if ((val.constructor?.name ?? "") !== "Object") { throw new TypeError( - `cannot interpret ${ - val.constructor?.name - } value as ScVal (${JSON.stringify(val)})` + `cannot interpret ${val.constructor?.name + } value as ScVal (${JSON.stringify(val)})`, ); } throw new TypeError( - `Received object ${val} did not match the provided type ${ty}` + `Received object ${val} did not match the provided type ${ty}`, ); } @@ -380,7 +377,7 @@ export class ContractSpec { return xdr.ScVal.scvVoid(); default: throw new TypeError( - `Type ${ty} was not void, but value was undefined` + `Type ${ty} was not void, but value was undefined`, ); } } @@ -399,7 +396,7 @@ export class ContractSpec { case xdr.ScSpecEntryKind.scSpecEntryUdtEnumV0(): if (typeof val !== "number") { throw new TypeError( - `expected number for enum ${name}, but got ${typeof val}` + `expected number for enum ${name}, but got ${typeof val}`, ); } return this.nativeToEnum(val as number, entry.udtEnumV0()); @@ -414,7 +411,7 @@ export class ContractSpec { private nativeToUnion( val: Union, - union_: xdr.ScSpecUdtUnionV0 + union_: xdr.ScSpecUdtUnionV0, ): xdr.ScVal { let entry_name = val.tag; let case_ = union_.cases().find((entry) => { @@ -434,11 +431,11 @@ export class ContractSpec { if (Array.isArray(val.values)) { if (val.values.length != types.length) { throw new TypeError( - `union ${union_} expects ${types.length} values, but got ${val.values.length}` + `union ${union_} expects ${types.length} values, but got ${val.values.length}`, ); } let scvals = val.values.map((v, i) => - this.nativeToScVal(v, types[i]) + this.nativeToScVal(v, types[i]), ); scvals.unshift(key); return xdr.ScVal.scvVec(scvals); @@ -455,11 +452,11 @@ export class ContractSpec { if (fields.some(isNumeric)) { if (!fields.every(isNumeric)) { throw new Error( - "mixed numeric and non-numeric field names are not allowed" + "mixed numeric and non-numeric field names are not allowed", ); } return xdr.ScVal.scvVec( - fields.map((_, i) => this.nativeToScVal(val[i], fields[i].type())) + fields.map((_, i) => this.nativeToScVal(val[i], fields[i].type())), ); } return xdr.ScVal.scvMap( @@ -469,7 +466,7 @@ export class ContractSpec { key: this.nativeToScVal(name, xdr.ScSpecTypeDef.scSpecTypeSymbol()), val: this.nativeToScVal(val[name], field.type()), }); - }) + }), ); } @@ -531,13 +528,13 @@ export class ContractSpec { if (value == xdr.ScSpecType.scSpecTypeVec().value) { let vec = typeDef.vec(); return (scv.vec() ?? []).map((elm) => - this.scValToNative(elm, vec.elementType()) + this.scValToNative(elm, vec.elementType()), ) as T; } else if (value == xdr.ScSpecType.scSpecTypeTuple().value) { let tuple = typeDef.tuple(); let valTypes = tuple.valueTypes(); return (scv.vec() ?? []).map((elm, i) => - this.scValToNative(elm, valTypes[i]) + this.scValToNative(elm, valTypes[i]), ) as T; } throw new TypeError(`Type ${typeDef} was not vec, but ${scv} is`); @@ -562,8 +559,8 @@ export class ContractSpec { `ScSpecType ${t.name} was not map, but ${JSON.stringify( scv, null, - 2 - )} is` + 2, + )} is`, ); } @@ -581,9 +578,8 @@ export class ContractSpec { value !== xdr.ScSpecType.scSpecTypeSymbol().value ) { throw new Error( - `ScSpecType ${ - t.name - } was not string or symbol, but ${JSON.stringify(scv, null, 2)} is` + `ScSpecType ${t.name + } was not string or symbol, but ${JSON.stringify(scv, null, 2)} is`, ); } return scv.value()?.toString() as T; @@ -600,8 +596,8 @@ export class ContractSpec { `failed to convert ${JSON.stringify( scv, null, - 2 - )} to native type from type ${t.name}` + 2, + )} to native type from type ${t.name}`, ); } } @@ -617,7 +613,7 @@ export class ContractSpec { return this.unionToNative(scv, entry.udtUnionV0()); default: throw new Error( - `failed to parse udt ${udt.name().toString()}: ${entry}` + `failed to parse udt ${udt.name().toString()}: ${entry}`, ); } } @@ -629,7 +625,7 @@ export class ContractSpec { } if (vec.length === 0 && udt.cases.length !== 0) { throw new Error( - `${val} has length 0, but the there are at least one case in the union` + `${val} has length 0, but the there are at least one case in the union`, ); } let name = vec[0].sym().toString(); @@ -639,7 +635,7 @@ export class ContractSpec { let entry = udt.cases().find(findCase(name)); if (!entry) { throw new Error( - `failed to find entry ${name} in union {udt.name().toString()}` + `failed to find entry ${name} in union {udt.name().toString()}`, ); } let res: Union = { tag: name }; @@ -667,7 +663,7 @@ export class ContractSpec { let field = fields[i]; res[field.name().toString()] = this.scValToNative( entry.val(), - field.type() + field.type(), ); }); return res; @@ -692,7 +688,7 @@ export class ContractSpec { .filter( (entry) => entry.switch().value === - xdr.ScSpecEntryKind.scSpecEntryUdtErrorEnumV0().value + xdr.ScSpecEntryKind.scSpecEntryUdtErrorEnumV0().value, ) .flatMap((entry) => (entry.value() as xdr.ScSpecUdtErrorEnumV0).cases()); } @@ -1012,7 +1008,7 @@ function structToJsonSchema(udt: xdr.ScSpecUdtStructV0): object { if (fields.some(isNumeric)) { if (!fields.every(isNumeric)) { throw new Error( - "mixed numeric and non-numeric field names are not allowed" + "mixed numeric and non-numeric field names are not allowed", ); } let items = fields.map((_, i) => typeRef(fields[i].type())); @@ -1035,7 +1031,7 @@ function structToJsonSchema(udt: xdr.ScSpecUdtStructV0): object { } function args_and_required( - input: { type: () => xdr.ScSpecTypeDef; name: () => string | Buffer }[] + input: { type: () => xdr.ScSpecTypeDef; name: () => string | Buffer }[], ): { properties: object; required?: string[] } { let properties: any = {}; let required: string[] = []; @@ -1116,7 +1112,7 @@ function unionToJsonSchema(udt: xdr.ScSpecUdtUnionV0): any { tag: title, values: { type: "array", - items: c.type().map(typeRef) + items: c.type().map(typeRef), }, }, required: ["tag", "values"], @@ -1156,4 +1152,3 @@ function enumToJsonSchema(udt: xdr.ScSpecUdtEnumV0): any { } return res; } - diff --git a/src/contract_client/types.ts b/src/contract/types.ts similarity index 82% rename from src/contract_client/types.ts rename to src/contract/types.ts index f80b524d6..8b92276c2 100644 --- a/src/contract_client/types.ts +++ b/src/contract/types.ts @@ -1,11 +1,9 @@ -import { - BASE_FEE, - Memo, - MemoType, - Operation, - Transaction, - xdr, -} from ".."; +/* disable PascalCase naming convention, to avoid breaking change */ +/* eslint-disable @typescript-eslint/naming-convention */ +import { BASE_FEE, Memo, MemoType, Operation, Transaction, xdr } from "@stellar/stellar-base"; +import type { Client } from "./client"; +import type { AssembledTransaction } from "./assembled_transaction"; +import { DEFAULT_TIMEOUT } from "./utils"; export type XDR_BASE64 = string; export type u32 = number; @@ -25,7 +23,7 @@ export type Duration = bigint; */ export type Tx = Transaction, Operation[]>; -export type ContractClientOptions = { +export type ClientOptions = { /** * The public key of the account that will send this transaction. You can * override this for specific methods later, like @@ -49,7 +47,7 @@ export type ContractClientOptions = { network?: string; networkPassphrase?: string; accountToSign?: string; - } + }, ) => Promise; /** * A function to sign a specific auth entry for a transaction, using the @@ -64,7 +62,7 @@ export type ContractClientOptions = { entryXdr: XDR_BASE64, opts?: { accountToSign?: string; - } + }, ) => Promise; contractId: string; networkPassphrase: string; @@ -75,15 +73,15 @@ export type ContractClientOptions = { */ allowHttp?: boolean; /** - * This gets filled in automatically from the ContractSpec if you use - * {@link ContractClient.generate} to create your ContractClient. + * This gets filled in automatically from the ContractSpec when you + * instantiate a {@link Client}. * * Background: If the contract you're calling uses the `#[contracterror]` * macro to create an `Error` enum, then those errors get included in the * on-chain XDR that also describes your contract's methods. Each error will * have a specific number. * - * A ContractClient makes method calls with an {@link AssembledTransaction}. + * A Client makes method calls with an {@link AssembledTransaction}. * When one of these method calls encounters an error, `AssembledTransaction` * will first attempt to parse the error as an "official" `contracterror` * error, by using this passed-in `errorTypes` object. See @@ -101,20 +99,21 @@ export type MethodOptions = { fee?: string; /** - * The maximum amount of time to wait for the transaction to complete. Default: {@link DEFAULT_TIMEOUT} + * The maximum amount of time to wait for the transaction to complete. + * Default: {@link DEFAULT_TIMEOUT} */ timeoutInSeconds?: number; /** - * Whether to automatically simulate the transaction when constructing the AssembledTransaction. Default: true + * Whether to automatically simulate the transaction when constructing the + * AssembledTransaction. Default: true */ simulate?: boolean; }; export type AssembledTransactionOptions = MethodOptions & - ContractClientOptions & { + ClientOptions & { method: string; args?: any[]; parseResultXdr: (xdr: xdr.ScVal) => T; }; - diff --git a/src/contract_client/utils.ts b/src/contract/utils.ts similarity index 68% rename from src/contract_client/utils.ts rename to src/contract/utils.ts index 8364940a3..44e7b8b02 100644 --- a/src/contract_client/utils.ts +++ b/src/contract/utils.ts @@ -1,4 +1,5 @@ -import { xdr, cereal } from ".."; +import { xdr, cereal } from "@stellar/stellar-base"; +import type { AssembledTransaction } from "./assembled_transaction"; /** * The default timeout for waiting for a transaction to be included in a block. @@ -6,15 +7,20 @@ import { xdr, cereal } from ".."; export const DEFAULT_TIMEOUT = 5 * 60; /** - * Keep calling a `fn` for `timeoutInSeconds` seconds, if `keepWaitingIf` is true. - * Returns an array of all attempts to call the function. + * Keep calling a `fn` for `timeoutInSeconds` seconds, if `keepWaitingIf` is + * true. Returns an array of all attempts to call the function. */ export async function withExponentialBackoff( + /** Function to call repeatedly */ fn: (previousFailure?: T) => Promise, + /** Condition to check when deciding whether or not to call `fn` again */ keepWaitingIf: (result: T) => boolean, + /** How long to wait between the first and second call */ timeoutInSeconds: number, + /** What to multiply `timeoutInSeconds` by, each subsequent attempt */ exponentialFactor = 1.5, - verbose = false + /** Whether to log extra info */ + verbose = false, ): Promise { const attempts: T[] = []; @@ -30,31 +36,34 @@ export async function withExponentialBackoff( Date.now() < waitUntil && keepWaitingIf(attempts[attempts.length - 1]) ) { - count++; + count += 1; // Wait a beat if (verbose) { + // eslint-disable-next-line no-console console.info( - `Waiting ${waitTime}ms before trying again (bringing the total wait time to ${totalWaitTime}ms so far, of total ${ - timeoutInSeconds * 1000 - }ms)` + `Waiting ${waitTime}ms before trying again (bringing the total wait time to ${totalWaitTime}ms so far, of total ${timeoutInSeconds * 1000 + }ms)`, ); } + // eslint-disable-next-line await new Promise((res) => setTimeout(res, waitTime)); // Exponential backoff - waitTime = waitTime * exponentialFactor; + waitTime *= exponentialFactor; if (new Date(Date.now() + waitTime).valueOf() > waitUntil) { waitTime = waitUntil - Date.now(); if (verbose) { + // eslint-disable-next-line no-console console.info(`was gonna wait too long; new waitTime: ${waitTime}ms`); } } totalWaitTime = waitTime + totalWaitTime; // Try again + // eslint-disable-next-line no-await-in-loop attempts.push(await fn(attempts[attempts.length - 1])); if (verbose && keepWaitingIf(attempts[attempts.length - 1])) { + // eslint-disable-next-line no-console console.info( - `${count}. Called ${fn}; ${ - attempts.length + `${count}. Called ${fn}; ${attempts.length } prev attempts. Most recent: ${JSON.stringify( attempts[attempts.length - 1], null, @@ -72,14 +81,17 @@ export async function withExponentialBackoff( * errors get included in the on-chain XDR that also describes your contract's * methods. Each error will have a specific number. This Regular Expression * matches these "expected error types" that a contract may throw, and helps - * @{link AssembledTransaction} parse these errors. + * {@link AssembledTransaction} parse these errors. */ export const contractErrorPattern = /Error\(Contract, #(\d+)\)/; /** * A TypeScript type guard that checks if an object has a `toString` method. */ -export function implementsToString(obj: unknown): obj is { toString(): string } { +export function implementsToString( + /** some object that may or may not have a `toString` method */ + obj: unknown, +): obj is { toString(): string } { return typeof obj === "object" && obj !== null && "toString" in obj; } @@ -89,7 +101,7 @@ export function implementsToString(obj: unknown): obj is { toString(): string } export function processSpecEntryStream(buffer: Buffer) { const reader = new cereal.XdrReader(buffer); const res: xdr.ScSpecEntry[] = []; - while (!reader.eof){ + while (!reader.eof) { // @ts-ignore res.push(xdr.ScSpecEntry.read(reader)); } diff --git a/src/contract_client/basic_node_signer.ts b/src/contract_client/basic_node_signer.ts deleted file mode 100644 index 52fbd46c5..000000000 --- a/src/contract_client/basic_node_signer.ts +++ /dev/null @@ -1,21 +0,0 @@ -import { Keypair, TransactionBuilder, hash } from '..' - -/** - * For use with {@link ContractClient} and {@link AssembledTransaction}. - * Implements `signTransaction` and `signAuthEntry` with signatures expected by - * those classes. This is useful for testing and maybe some simple Node - * applications. Feel free to use this as a starting point for your own - * Wallet/TransactionSigner implementation. - */ -export const basicNodeSigner = (keypair: Keypair, networkPassphrase: string) => ({ - signTransaction: async (tx: string) => { - const t = TransactionBuilder.fromXDR(tx, networkPassphrase); - t.sign(keypair); - return t.toXDR(); - }, - signAuthEntry: async (entryXdr: string): Promise => { - return keypair - .sign(hash(Buffer.from(entryXdr, "base64"))) - .toString('base64') - } -}) diff --git a/src/contract_client/index.ts b/src/contract_client/index.ts deleted file mode 100644 index 53ac515b2..000000000 --- a/src/contract_client/index.ts +++ /dev/null @@ -1,6 +0,0 @@ -export * from './assembled_transaction' -export * from './basic_node_signer' -export * from './client' -export * from './sent_transaction' -export * from './types' -export * from './utils' diff --git a/src/index.ts b/src/index.ts index ec315bd0c..83dfde51f 100644 --- a/src/index.ts +++ b/src/index.ts @@ -16,9 +16,34 @@ export * as Friendbot from './friendbot'; // Horizon-related classes to expose export * as Horizon from './horizon'; -// Soroban RPC-related classes to expose -export * as SorobanRpc from './soroban'; -export * from './contract_spec'; +/** + * Tools for interacting with the Soroban RPC server, such as `Server`, + * `assembleTransaction`, and the `Api` types. You can import these from the + * `/rpc` entrypoint, if your version of Node and your TypeScript configuration + * allow it: + * + * ```ts + * import { Server } from '@stellar/stellar-sdk/rpc'; + * ``` + */ +export * as rpc from './rpc'; + +/** + * @deprecated Use `rpc` instead + */ +export * as SorobanRpc from './rpc'; + +/** + * Tools for interacting with smart contracts, such as `Client`, `Spec`, and + * `AssembledTransaction`. You can import these from the `/contract` + * entrypoint, if your version of Node and your TypeScript configuration allow + * it: + * + * ```ts + * import { Client } from '@stellar/stellar-sdk/contract'; + * ``` + */ +export * as contract from './contract' // expose classes and functions from stellar-base export * from '@stellar/stellar-base'; diff --git a/src/soroban/api.ts b/src/rpc/api.ts similarity index 100% rename from src/soroban/api.ts rename to src/rpc/api.ts diff --git a/src/soroban/axios.ts b/src/rpc/axios.ts similarity index 100% rename from src/soroban/axios.ts rename to src/rpc/axios.ts diff --git a/src/soroban/browser.ts b/src/rpc/browser.ts similarity index 100% rename from src/soroban/browser.ts rename to src/rpc/browser.ts diff --git a/src/rpc/index.ts b/src/rpc/index.ts new file mode 100644 index 000000000..99ee2ba2a --- /dev/null +++ b/src/rpc/index.ts @@ -0,0 +1,13 @@ +// tslint:disable-next-line: no-reference +/// + +// Expose all types +export * from "./api"; + +// soroban-client classes to expose +export { Server, Durability } from "./server"; +export { default as AxiosClient } from "./axios"; +export { parseRawSimulation, parseRawEvents } from "./parsers"; +export * from "./transaction"; + +export default module.exports; diff --git a/src/soroban/jsonrpc.ts b/src/rpc/jsonrpc.ts similarity index 100% rename from src/soroban/jsonrpc.ts rename to src/rpc/jsonrpc.ts diff --git a/src/soroban/parsers.ts b/src/rpc/parsers.ts similarity index 100% rename from src/soroban/parsers.ts rename to src/rpc/parsers.ts diff --git a/src/soroban/server.ts b/src/rpc/server.ts similarity index 100% rename from src/soroban/server.ts rename to src/rpc/server.ts diff --git a/src/soroban/transaction.ts b/src/rpc/transaction.ts similarity index 100% rename from src/soroban/transaction.ts rename to src/rpc/transaction.ts diff --git a/src/soroban/utils.ts b/src/rpc/utils.ts similarity index 100% rename from src/soroban/utils.ts rename to src/rpc/utils.ts diff --git a/src/rust_types/index.ts b/src/rust_types/index.ts deleted file mode 100644 index bb162e4c8..000000000 --- a/src/rust_types/index.ts +++ /dev/null @@ -1 +0,0 @@ -export * from "./result"; diff --git a/src/soroban/.eslintrc.js b/src/soroban/.eslintrc.js deleted file mode 100644 index 9d226df13..000000000 --- a/src/soroban/.eslintrc.js +++ /dev/null @@ -1,42 +0,0 @@ -module.exports = { - env: { - es6: true - }, - parser: '@babel/eslint-parser', - extends: ['airbnb-base', 'prettier'], - plugins: ['prettier', 'prefer-import'], - rules: { - // OFF - 'import/prefer-default-export': 0, - 'node/no-unsupported-features/es-syntax': 0, - 'node/no-unsupported-features/es-builtins': 0, - camelcase: 0, - 'class-methods-use-this': 0, - 'linebreak-style': 0, - 'new-cap': 0, - 'no-param-reassign': 0, - 'no-underscore-dangle': 0, - 'no-use-before-define': 0, - 'prefer-destructuring': 0, - 'lines-between-class-members': 0, - - // WARN - 'prefer-import/prefer-import-over-require': [1], - 'no-console': ['warn', { allow: ['assert'] }], - 'no-debugger': 1, - 'no-unused-vars': 1, - 'arrow-body-style': 1, - 'valid-jsdoc': [ - 1, - { - requireReturnDescription: false - } - ], - 'prefer-const': 1, - 'object-shorthand': 1, - 'require-await': 1, - - // ERROR - 'no-unused-expressions': [2, { allowTaggedTemplates: true }] - } -}; diff --git a/src/soroban/index.ts b/src/soroban/index.ts deleted file mode 100644 index 36e296ee1..000000000 --- a/src/soroban/index.ts +++ /dev/null @@ -1,13 +0,0 @@ -// tslint:disable-next-line: no-reference -/// - -// Expose all types -export * from './api'; - -// soroban-client classes to expose -export { Server, Durability } from './server'; -export { default as AxiosClient } from './axios'; -export { parseRawSimulation, parseRawEvents } from './parsers'; -export * from './transaction'; - -export default module.exports; diff --git a/test/e2e/src/test-contract-client-constructor.js b/test/e2e/src/test-contract-client-constructor.js index 018b370db..83211941c 100644 --- a/test/e2e/src/test-contract-client-constructor.js +++ b/test/e2e/src/test-contract-client-constructor.js @@ -1,14 +1,7 @@ const test = require('ava') const { spawnSync } = require('node:child_process') -const { Address } = require('../../..') const { contracts, networkPassphrase, rpcUrl, friendbotUrl } = require('./util') -const { ContractSpec } = require('../../..') -const { Keypair } = require('../../..') - -const { - ContractClient, - basicNodeSigner, -} = require('../../../lib/contract_client') +const { Address, contract, Keypair } = require('../../..') async function generateFundedKeypair() { const keypair = Keypair.random() @@ -17,7 +10,7 @@ async function generateFundedKeypair() { }; /** - * Generates a ContractClient for the contract with the given name. + * Generates a Client for the contract with the given name. * Also generates a new account to use as as the keypair of this contract. This * account is funded by friendbot. You can pass in an account to re-use the * same account with multiple contract clients. @@ -25,21 +18,21 @@ async function generateFundedKeypair() { * By default, will re-deploy the contract every time. Pass in the same * `contractId` again if you want to re-use the a contract instance. */ -async function clientFromConstructor(contract, { keypair = generateFundedKeypair(), contractId } = {}) { - if (!contracts[contract]) { +async function clientFromConstructor(name, { keypair = generateFundedKeypair(), contractId } = {}) { + if (!contracts[name]) { throw new Error( - `Contract ${contract} not found. ` + + `Contract ${name} not found. ` + `Pick one of: ${Object.keys(contracts).join(", ")}` ) } keypair = await keypair // eslint-disable-line no-param-reassign - const wallet = basicNodeSigner(keypair, networkPassphrase) + const wallet = contract.basicNodeSigner(keypair, networkPassphrase) - const {path} = contracts[contract]; + const {path} = contracts[name]; const xdr = JSON.parse(spawnSync("./target/bin/soroban", ["contract", "inspect", "--wasm", path, "--output", "xdr-base64-array"], { shell: true, encoding: "utf8" }).stdout.trim()) - const spec = new ContractSpec(xdr); - let wasmHash = contracts[contract].hash; + const spec = new contract.Spec(xdr); + let wasmHash = contracts[name].hash; if (!wasmHash) { wasmHash = spawnSync("./target/bin/soroban", ["contract", "install", "--wasm", path], { shell: true, encoding: "utf8" }).stdout.trim() } @@ -54,7 +47,7 @@ async function clientFromConstructor(contract, { keypair = generateFundedKeypair wasmHash, ], { shell: true, encoding: "utf8" }).stdout.trim(); - const client = new ContractClient(spec, { + const client = new contract.Client(spec, { networkPassphrase, contractId, rpcUrl, @@ -70,11 +63,11 @@ async function clientFromConstructor(contract, { keypair = generateFundedKeypair } /** - * Generates a ContractClient given the contractId using the from method. + * Generates a Client given the contractId using the from method. */ async function clientForFromTest(contractId, publicKey, keypair) { keypair = await keypair; // eslint-disable-line no-param-reassign - const wallet = basicNodeSigner(keypair, networkPassphrase); + const wallet = contract.basicNodeSigner(keypair, networkPassphrase); const options = { networkPassphrase, contractId, @@ -83,7 +76,7 @@ async function clientForFromTest(contractId, publicKey, keypair) { publicKey, ...wallet, }; - return ContractClient.from(options); + return contract.Client.from(options); } test.before(async t => { diff --git a/test/e2e/src/test-custom-types.js b/test/e2e/src/test-custom-types.js index fd511e3a3..63094187b 100644 --- a/test/e2e/src/test-custom-types.js +++ b/test/e2e/src/test-custom-types.js @@ -1,6 +1,5 @@ const test = require('ava') -const { Address } = require('../../..') -const { Ok, Err } = require('../../../lib/rust_types') +const { Address, contract } = require('../../..') const { clientFor } = require('./util') test.before(async t => { @@ -29,11 +28,11 @@ test('woid', async t => { test('u32_fail_on_even', async t => { t.deepEqual( (await t.context.client.u32_fail_on_even({ u32_: 1 })).result, - new Ok(1) + new contract.Ok(1) ) t.deepEqual( (await t.context.client.u32_fail_on_even({ u32_: 2 })).result, - new Err({ message: "Please provide an odd number" }) + new contract.Err({ message: "Please provide an odd number" }) ) }) diff --git a/test/e2e/src/test-hello-world.js b/test/e2e/src/test-hello-world.js index a489f1692..fbd927be3 100644 --- a/test/e2e/src/test-hello-world.js +++ b/test/e2e/src/test-hello-world.js @@ -1,6 +1,5 @@ const test = require('ava') const { clientFor } = require('./util') -const { Keypair } = require('../../..') test("hello", async (t) => { const { client } = await clientFor('helloWorld') diff --git a/test/e2e/src/test-swap.js b/test/e2e/src/test-swap.js index 802ed0d73..885debda9 100644 --- a/test/e2e/src/test-swap.js +++ b/test/e2e/src/test-swap.js @@ -1,6 +1,5 @@ const test = require('ava') -const { SorobanRpc } = require('../../..') -const { AssembledTransaction } = require('../../../lib/contract_client') +const { contract, rpc } = require('../../..') const { clientFor, generateFundedKeypair } = require('./util') const amountAToSwap = 2n @@ -52,7 +51,7 @@ test('calling `signAndSend()` too soon throws descriptive error', async t => { min_b_for_a: amountBToSwap, }) const error = await t.throwsAsync(tx.signAndSend()) - t.true(error instanceof AssembledTransaction.Errors.NeedsMoreSignatures, `error is not of type 'NeedsMoreSignaturesError'; instead it is of type '${error?.constructor.name}'`) + t.true(error instanceof contract.AssembledTransaction.Errors.NeedsMoreSignatures, `error is not of type 'NeedsMoreSignaturesError'; instead it is of type '${error?.constructor.name}'`) if (error) t.regex(error.message, /needsNonInvokerSigningBy/) }) @@ -111,7 +110,7 @@ test('alice swaps bob 10 A for 1 B', async t => { ) t.is( result.getTransactionResponse.status, - SorobanRpc.Api.GetTransactionStatus.SUCCESS, + rpc.Api.GetTransactionStatus.SUCCESS, `tx failed: ${JSON.stringify(result.getTransactionResponse, null, 2)}` ) diff --git a/test/e2e/src/util.js b/test/e2e/src/util.js index 85add1c78..c70995de2 100644 --- a/test/e2e/src/util.js +++ b/test/e2e/src/util.js @@ -1,9 +1,5 @@ const { spawnSync } = require('node:child_process') -const { Keypair } = require('../../..') -const { - ContractClient, - basicNodeSigner, -} = require('../../../lib/contract_client') +const { contract, Keypair } = require('../../..') const contracts = { customTypes: { @@ -40,7 +36,7 @@ async function generateFundedKeypair() { module.exports.generateFundedKeypair = generateFundedKeypair /** - * Generates a ContractClient for the contract with the given name. + * Generates a Client for the contract with the given name. * Also generates a new account to use as as the keypair of this contract. This * account is funded by friendbot. You can pass in an account to re-use the * same account with multiple contract clients. @@ -48,20 +44,20 @@ module.exports.generateFundedKeypair = generateFundedKeypair * By default, will re-deploy the contract every time. Pass in the same * `contractId` again if you want to re-use the a contract instance. */ -async function clientFor(contract, { keypair = generateFundedKeypair(), contractId } = {}) { - if (!contracts[contract]) { +async function clientFor(name, { keypair = generateFundedKeypair(), contractId } = {}) { + if (!contracts[name]) { throw new Error( - `Contract ${contract} not found. ` + + `Contract ${name} not found. ` + `Pick one of: ${Object.keys(contracts).join(", ")}` ) } keypair = await keypair // eslint-disable-line no-param-reassign - const wallet = basicNodeSigner(keypair, networkPassphrase) + const wallet = contract.basicNodeSigner(keypair, networkPassphrase) - let wasmHash = contracts[contract].hash; + let wasmHash = contracts[name].hash; if (!wasmHash) { - wasmHash = spawnSync("./target/bin/soroban", ["contract", "install", "--wasm", contracts[contract].path], { shell: true, encoding: "utf8" }).stdout.trim() + wasmHash = spawnSync("./target/bin/soroban", ["contract", "install", "--wasm", contracts[name].path], { shell: true, encoding: "utf8" }).stdout.trim() } // TODO: do this with js-stellar-sdk, instead of shelling out to the CLI @@ -74,7 +70,7 @@ async function clientFor(contract, { keypair = generateFundedKeypair(), contract wasmHash, ], { shell: true, encoding: "utf8" }).stdout.trim(); - const client = await ContractClient.fromWasmHash(wasmHash, { + const client = await contract.Client.fromWasmHash(wasmHash, { networkPassphrase, contractId, rpcUrl, diff --git a/test/unit/server/soroban/get_account_test.js b/test/unit/server/soroban/get_account_test.js index b6781f76c..4372a39c9 100644 --- a/test/unit/server/soroban/get_account_test.js +++ b/test/unit/server/soroban/get_account_test.js @@ -1,5 +1,5 @@ const { Account, Keypair, StrKey, hash, xdr } = StellarSdk; -const { Server, AxiosClient } = StellarSdk.SorobanRpc; +const { Server, AxiosClient } = StellarSdk.rpc; describe("Server#getAccount", function () { beforeEach(function () { diff --git a/test/unit/server/soroban/get_contract_data_test.js b/test/unit/server/soroban/get_contract_data_test.js index 1fb004a61..8b2996664 100644 --- a/test/unit/server/soroban/get_contract_data_test.js +++ b/test/unit/server/soroban/get_contract_data_test.js @@ -1,5 +1,5 @@ const { Address, xdr, nativeToScVal, hash } = StellarSdk; -const { Server, AxiosClient, Durability } = StellarSdk.SorobanRpc; +const { Server, AxiosClient, Durability } = StellarSdk.rpc; describe("Server#getContractData", function () { beforeEach(function () { diff --git a/test/unit/server/soroban/get_events_test.js b/test/unit/server/soroban/get_events_test.js index 8b3f4f8e7..6861f887a 100644 --- a/test/unit/server/soroban/get_events_test.js +++ b/test/unit/server/soroban/get_events_test.js @@ -1,5 +1,5 @@ -const { nativeToScVal, SorobanRpc } = StellarSdk; -const { Server, AxiosClient } = StellarSdk.SorobanRpc; +const { nativeToScVal, rpc } = StellarSdk; +const { Server, AxiosClient } = StellarSdk.rpc; describe("Server#getEvents", function () { beforeEach(function () { @@ -225,7 +225,7 @@ function setupMock(axiosMock, params, result) { } function parseEvents(result) { - return SorobanRpc.parseRawEvents(result); + return rpc.parseRawEvents(result); } const contractId = "CA3D5KRYM6CB7OWQ6TWYRR3Z4T7GNZLKERYNZGGA5SOAOPIFY6YQGAXE"; diff --git a/test/unit/server/soroban/get_health_test.js b/test/unit/server/soroban/get_health_test.js index 917a945f6..4682cbf67 100644 --- a/test/unit/server/soroban/get_health_test.js +++ b/test/unit/server/soroban/get_health_test.js @@ -1,4 +1,4 @@ -const { Server, AxiosClient } = StellarSdk.SorobanRpc; +const { Server, AxiosClient } = StellarSdk.rpc; describe("Server#getHealth", function () { beforeEach(function () { diff --git a/test/unit/server/soroban/get_latest_ledger_test.js b/test/unit/server/soroban/get_latest_ledger_test.js index 6aa048033..fecf1a0ec 100644 --- a/test/unit/server/soroban/get_latest_ledger_test.js +++ b/test/unit/server/soroban/get_latest_ledger_test.js @@ -1,4 +1,4 @@ -const { Server, AxiosClient } = StellarSdk.SorobanRpc; +const { Server, AxiosClient } = StellarSdk.rpc; describe("Server#getLatestLedger", function () { beforeEach(function () { diff --git a/test/unit/server/soroban/get_ledger_entries_test.js b/test/unit/server/soroban/get_ledger_entries_test.js index cf11dc5f3..a550d62f9 100644 --- a/test/unit/server/soroban/get_ledger_entries_test.js +++ b/test/unit/server/soroban/get_ledger_entries_test.js @@ -1,5 +1,5 @@ const { xdr, nativeToScVal, Durability, hash } = StellarSdk; -const { Server, AxiosClient } = StellarSdk.SorobanRpc; +const { Server, AxiosClient } = StellarSdk.rpc; describe("Server#getLedgerEntries", function () { const address = "CCJZ5DGASBWQXR5MPFCJXMBI333XE5U3FSJTNQU7RIKE3P5GN2K2WYD5"; diff --git a/test/unit/server/soroban/get_network_test.js b/test/unit/server/soroban/get_network_test.js index a5b4e05a7..fd36476f2 100644 --- a/test/unit/server/soroban/get_network_test.js +++ b/test/unit/server/soroban/get_network_test.js @@ -1,4 +1,4 @@ -const { Server, AxiosClient } = StellarSdk.SorobanRpc; +const { Server, AxiosClient } = StellarSdk.rpc; describe("Server#getNetwork", function () { beforeEach(function () { diff --git a/test/unit/server/soroban/get_transaction_test.js b/test/unit/server/soroban/get_transaction_test.js index a63b7aa21..52b6c807a 100644 --- a/test/unit/server/soroban/get_transaction_test.js +++ b/test/unit/server/soroban/get_transaction_test.js @@ -6,7 +6,7 @@ const { nativeToScVal, XdrLargeInt, } = StellarSdk; -const { Server, AxiosClient } = StellarSdk.SorobanRpc; +const { Server, AxiosClient } = StellarSdk.rpc; describe("Server#getTransaction", function () { let keypair = Keypair.random(); diff --git a/test/unit/server/soroban/request_airdrop_test.js b/test/unit/server/soroban/request_airdrop_test.js index 74e0c6486..2465a981b 100644 --- a/test/unit/server/soroban/request_airdrop_test.js +++ b/test/unit/server/soroban/request_airdrop_test.js @@ -1,5 +1,5 @@ const { Account, Keypair, StrKey, Networks, xdr, hash } = StellarSdk; -const { Server, AxiosClient } = StellarSdk.SorobanRpc; +const { Server, AxiosClient } = StellarSdk.rpc; describe("Server#requestAirdrop", function () { function accountLedgerEntryData(accountId, sequence) { diff --git a/test/unit/server/soroban/send_transaction_test.js b/test/unit/server/soroban/send_transaction_test.js index be235e8bb..08f35a402 100644 --- a/test/unit/server/soroban/send_transaction_test.js +++ b/test/unit/server/soroban/send_transaction_test.js @@ -1,5 +1,5 @@ const { xdr } = StellarSdk; -const { Server, AxiosClient } = StellarSdk.SorobanRpc; +const { Server, AxiosClient } = StellarSdk.rpc; describe("Server#sendTransaction", function () { let keypair = StellarSdk.Keypair.random(); diff --git a/test/unit/server/soroban/simulate_transaction_test.js b/test/unit/server/soroban/simulate_transaction_test.js index f1c2bacbc..e35322e2d 100644 --- a/test/unit/server/soroban/simulate_transaction_test.js +++ b/test/unit/server/soroban/simulate_transaction_test.js @@ -2,13 +2,13 @@ const { Account, Keypair, Networks, - SorobanRpc, + rpc, SorobanDataBuilder, authorizeInvocation, authorizeEntry, xdr, } = StellarSdk; -const { Server, AxiosClient, parseRawSimulation } = StellarSdk.SorobanRpc; +const { Server, AxiosClient, parseRawSimulation } = StellarSdk.rpc; const randomSecret = Keypair.random().secret(); @@ -134,7 +134,7 @@ describe("Server#simulateTransaction", async function (done) { const parsed = parseRawSimulation(simResponse); expect(parsed).to.deep.equal(parsedCopy); - expect(SorobanRpc.Api.isSimulationSuccess(parsed)).to.be.true; + expect(rpc.Api.isSimulationSuccess(parsed)).to.be.true; }); it("works with no auth", async function () { @@ -146,7 +146,7 @@ describe("Server#simulateTransaction", async function (done) { const parsed = parseRawSimulation(simResponse); expect(parsed).to.be.deep.equal(parsedCopy); - expect(SorobanRpc.Api.isSimulationSuccess(parsed)).to.be.true; + expect(rpc.Api.isSimulationSuccess(parsed)).to.be.true; }); }); @@ -160,7 +160,7 @@ describe("Server#simulateTransaction", async function (done) { }; const parsed = parseRawSimulation(simResponse); - expect(SorobanRpc.Api.isSimulationRestore(parsed)).to.be.true; + expect(rpc.Api.isSimulationRestore(parsed)).to.be.true; expect(parsed).to.be.deep.equal(expected); }, ); @@ -180,7 +180,7 @@ describe("Server#simulateTransaction", async function (done) { const parsed = parseRawSimulation(simResponse); expect(parsed).to.be.deep.equal(expected); - expect(SorobanRpc.Api.isSimulationError(parsed)).to.be.true; + expect(rpc.Api.isSimulationError(parsed)).to.be.true; }); xit("simulates fee bump transactions"); @@ -302,7 +302,7 @@ describe("works with real responses", function () { }; it("parses the schema", function () { - expect(SorobanRpc.Api.isSimulationRaw(schema)).to.be.true; + expect(rpc.Api.isSimulationRaw(schema)).to.be.true; const parsed = parseRawSimulation(schema); diff --git a/test/unit/spec/contract_spec.ts b/test/unit/spec/contract_spec.ts index 861a7c968..5ea7de000 100644 --- a/test/unit/spec/contract_spec.ts +++ b/test/unit/spec/contract_spec.ts @@ -1,4 +1,4 @@ -import { xdr, Address, ContractSpec, Keypair } from "../../../lib"; +import { xdr, Address, contract, Keypair } from "../../../lib"; import { JSONSchemaFaker } from "json-schema-faker"; import spec from "../spec.json"; @@ -6,7 +6,7 @@ import { expect } from "chai"; const publicKey = "GCBVOLOM32I7OD5TWZQCIXCXML3TK56MDY7ZMTAILIBQHHKPCVU42XYW"; const addr = Address.fromString(publicKey); -let SPEC: ContractSpec; +let SPEC: contract.Spec; JSONSchemaFaker.format("address", () => { let keypair = Keypair.random(); @@ -14,11 +14,11 @@ JSONSchemaFaker.format("address", () => { }); before(() => { - SPEC = new ContractSpec(spec); + SPEC = new contract.Spec(spec); }); it("throws if no entries", () => { - expect(() => new ContractSpec([])).to.throw( + expect(() => new contract.Spec([])).to.throw( /Contract spec must have at least one entry/i ); }); @@ -36,7 +36,7 @@ describe("Can round trip custom types", function () { } async function jsonSchema_roundtrip( - spec: ContractSpec, + spec: contract.Spec, funcName: string, num: number = 100 ) { @@ -70,7 +70,7 @@ describe("Can round trip custom types", function () { } describe("Json Schema", () => { - SPEC = new ContractSpec(spec); + SPEC = new contract.Spec(spec); let names = SPEC.funcs().map((f) => f.name().toString()); const banned = ["strukt_hel", "not", "woid", "val", "multi_args"]; names @@ -259,7 +259,7 @@ describe.skip("Print contract spec", function () { describe("parsing and building ScVals", function () { it("Can parse entries", function () { - let spec = new ContractSpec([GIGA_MAP, func]); + let spec = new contract.Spec([GIGA_MAP, func]); let fn = spec.findEntry("giga_map"); let gigaMap = spec.findEntry("GigaMap"); expect(gigaMap).deep.equal(GIGA_MAP); diff --git a/test/unit/transaction_test.js b/test/unit/transaction_test.js index d5a1b3633..4ee16f7f2 100644 --- a/test/unit/transaction_test.js +++ b/test/unit/transaction_test.js @@ -1,4 +1,4 @@ -const { xdr, SorobanRpc } = StellarSdk; +const { xdr, rpc } = StellarSdk; describe("assembleTransaction", () => { xit("works with keybump transactions"); @@ -80,7 +80,7 @@ describe("assembleTransaction", () => { it("simulate updates the tx data from simulation response", () => { const txn = singleContractFnTransaction(); - const result = SorobanRpc.assembleTransaction( + const result = rpc.assembleTransaction( txn, simulationResponse, ).build(); @@ -97,7 +97,7 @@ describe("assembleTransaction", () => { it("simulate adds the auth to the host function in tx operation", () => { const txn = singleContractFnTransaction(); - const result = SorobanRpc.assembleTransaction( + const result = rpc.assembleTransaction( txn, simulationResponse, ).build(); @@ -141,7 +141,7 @@ describe("assembleTransaction", () => { const txn = singleContractFnTransaction(); let simulateResp = JSON.parse(JSON.stringify(simulationResponse)); simulateResp.results[0].auth = null; - const result = SorobanRpc.assembleTransaction(txn, simulateResp).build(); + const result = rpc.assembleTransaction(txn, simulateResp).build(); expect( result @@ -170,7 +170,7 @@ describe("assembleTransaction", () => { .build(); expect(() => { - SorobanRpc.assembleTransaction(txn, { + rpc.assembleTransaction(txn, { transactionData: {}, events: [], minResourceFee: "0", @@ -198,7 +198,7 @@ describe("assembleTransaction", () => { .addOperation(op) .build(); - const tx = SorobanRpc.assembleTransaction( + const tx = rpc.assembleTransaction( txn, simulationResponse, ).build(); @@ -208,7 +208,7 @@ describe("assembleTransaction", () => { it("doesn't overwrite auth if it's present", function () { const txn = singleContractFnTransaction([fnAuth, fnAuth, fnAuth]); - const tx = SorobanRpc.assembleTransaction( + const tx = rpc.assembleTransaction( txn, simulationResponse, ).build(); diff --git a/yarn.lock b/yarn.lock index dedf29d84..d9f863397 100644 --- a/yarn.lock +++ b/yarn.lock @@ -1132,6 +1132,18 @@ resolved "https://registry.yarnpkg.com/@discoveryjs/json-ext/-/json-ext-0.5.7.tgz#1d572bfbbe14b7704e0ba0f39b74815b84870d70" integrity sha512-dBVuXR082gk3jsFp7Rd/JI4kytwGHecnCoTtXFb7DB6CNHp4rg5k1bhg0nWdLGLnOV71lmDzGQaLMy8iPLY0pw== +"@es-joy/jsdoccomment@~0.43.0": + version "0.43.0" + resolved "https://registry.yarnpkg.com/@es-joy/jsdoccomment/-/jsdoccomment-0.43.0.tgz#35c295cadd0a939d1a3a6cd1548f66ec76d38870" + integrity sha512-Q1CnsQrytI3TlCB1IVWXWeqUIPGVEKGaE7IbVdt13Nq/3i0JESAkQQERrfiQkmlpijl+++qyqPgaS31Bvc1jRQ== + dependencies: + "@types/eslint" "^8.56.5" + "@types/estree" "^1.0.5" + "@typescript-eslint/types" "^7.2.0" + comment-parser "1.4.1" + esquery "^1.5.0" + jsdoc-type-pratt-parser "~4.0.0" + "@eslint-community/eslint-utils@^4.2.0", "@eslint-community/eslint-utils@^4.4.0": version "4.4.0" resolved "https://registry.yarnpkg.com/@eslint-community/eslint-utils/-/eslint-utils-4.4.0.tgz#a23514e8fb9af1269d5f7788aa556798d61c6b59" @@ -1665,7 +1677,7 @@ resolved "https://registry.yarnpkg.com/@typescript-eslint/types/-/types-6.21.0.tgz#205724c5123a8fef7ecd195075fa6e85bac3436d" integrity sha512-1kFmZ1rOm5epu9NZEZm1kckCDGj5UJEf7P1kliH4LKu/RkwpsfqqGmY2OOcUs18lSlQBKLDYBOGxRVtrMN5lpg== -"@typescript-eslint/types@7.8.0": +"@typescript-eslint/types@7.8.0", "@typescript-eslint/types@^7.2.0": version "7.8.0" resolved "https://registry.yarnpkg.com/@typescript-eslint/types/-/types-7.8.0.tgz#1fd2577b3ad883b769546e2d1ef379f929a7091d" integrity sha512-wf0peJ+ZGlcH+2ZS23aJbOv+ztjeeP8uQ9GgwMJGVLx/Nj9CJt17GWgWWoSmoRVKAX2X+7fzEnAjxdvK2gqCLw== @@ -2042,6 +2054,11 @@ archy@^1.0.0: resolved "https://registry.yarnpkg.com/archy/-/archy-1.0.0.tgz#f9c8c13757cc1dd7bc379ac77b2c62a5c2868c40" integrity sha512-Xg+9RwCg/0p32teKdGMPTPnVXKD0w3DfHnFTficozsAgsvq2XenPJq/MYpzzQ/v8zrOyJn6Ds39VA4JIDwFfqw== +are-docs-informative@^0.0.2: + version "0.0.2" + resolved "https://registry.yarnpkg.com/are-docs-informative/-/are-docs-informative-0.0.2.tgz#387f0e93f5d45280373d387a59d34c96db321963" + integrity sha512-ixiS0nLNNG5jNQzgZJNoUpBKdo9yTYZMGJ+QgT2jmjR7G7+QHRCc4v6LQ3NgE7EBJq+o0ams3waJwkrlBom8Ig== + are-we-there-yet@~1.1.2: version "1.1.7" resolved "https://registry.yarnpkg.com/are-we-there-yet/-/are-we-there-yet-1.1.7.tgz#b15474a932adab4ff8a50d9adfa7e4e926f21146" @@ -2533,6 +2550,11 @@ buffer@^6.0.3: base64-js "^1.3.1" ieee754 "^1.2.1" +builtin-modules@^3.3.0: + version "3.3.0" + resolved "https://registry.yarnpkg.com/builtin-modules/-/builtin-modules-3.3.0.tgz#cae62812b89801e9656336e46223e030386be7b6" + integrity sha512-zhaCDicdLuWN5UbN5IMnFqNMhNfo919sH85y2/ea+5Yg9TsTkeZxpL+JLbp6cgYFS4sRLp3YV4S6yDuqVWHYOw== + builtin-status-codes@^3.0.0: version "3.0.0" resolved "https://registry.yarnpkg.com/builtin-status-codes/-/builtin-status-codes-3.0.0.tgz#85982878e21b98e1c66425e03d0174788f569ee8" @@ -2948,6 +2970,11 @@ commander@~2.1.0: resolved "https://registry.yarnpkg.com/commander/-/commander-2.1.0.tgz#d121bbae860d9992a3d517ba96f56588e47c6781" integrity sha512-J2wnb6TKniXNOtoHS8TSrG9IOQluPrsmyAJ8oCUJOBmv+uLBCyPYAZkD2jFvw2DCzIXNnISIM01NIvr35TkBMQ== +comment-parser@1.4.1: + version "1.4.1" + resolved "https://registry.yarnpkg.com/comment-parser/-/comment-parser-1.4.1.tgz#bdafead37961ac079be11eb7ec65c4d021eaf9cc" + integrity sha512-buhp5kePrmda3vhc5B9t7pUQXAb2Tnd0qgpkIhPhkHXxJpiPJ11H0ZEU0oBpJ2QztSbzG/ZxMj/CHsYJqRHmyg== + common-path-prefix@^3.0.0: version "3.0.0" resolved "https://registry.yarnpkg.com/common-path-prefix/-/common-path-prefix-3.0.0.tgz#7d007a7e07c58c4b4d5f433131a19141b29f11e0" @@ -3634,6 +3661,13 @@ eslint-config-airbnb-base@^15.0.0: object.entries "^1.1.5" semver "^6.3.0" +eslint-config-airbnb-typescript@^18.0.0: + version "18.0.0" + resolved "https://registry.yarnpkg.com/eslint-config-airbnb-typescript/-/eslint-config-airbnb-typescript-18.0.0.tgz#b1646db4134858d704b1d2bee47e1d72c180315f" + integrity sha512-oc+Lxzgzsu8FQyFVa4QFaVKiitTYiiW3frB9KYW5OWdPrqFc7FzxgB20hP4cHMlr+MBzGcLl3jnCOVOydL9mIg== + dependencies: + eslint-config-airbnb-base "^15.0.0" + eslint-config-prettier@^9.0.0: version "9.1.0" resolved "https://registry.yarnpkg.com/eslint-config-prettier/-/eslint-config-prettier-9.1.0.tgz#31af3d94578645966c082fcb71a5846d3c94867f" @@ -3686,6 +3720,21 @@ eslint-plugin-import@^2.29.1: semver "^6.3.1" tsconfig-paths "^3.15.0" +eslint-plugin-jsdoc@^48.2.4: + version "48.2.4" + resolved "https://registry.yarnpkg.com/eslint-plugin-jsdoc/-/eslint-plugin-jsdoc-48.2.4.tgz#0b6972faa9e5de89a08f1b0bcdc30e70a9cad736" + integrity sha512-3ebvVgCJFy06gpmuS2ynz13uh9iFSzZ1C1dDkgcSAqVVg82zlORKMk2fvjq708pAO6bwfs5YLttknFEbaoDiGw== + dependencies: + "@es-joy/jsdoccomment" "~0.43.0" + are-docs-informative "^0.0.2" + comment-parser "1.4.1" + debug "^4.3.4" + escape-string-regexp "^4.0.0" + esquery "^1.5.0" + is-builtin-module "^3.2.1" + semver "^7.6.0" + spdx-expression-parse "^4.0.0" + eslint-plugin-node@^11.1.0: version "11.1.0" resolved "https://registry.yarnpkg.com/eslint-plugin-node/-/eslint-plugin-node-11.1.0.tgz#c95544416ee4ada26740a30474eefc5402dc671d" @@ -3818,7 +3867,7 @@ esprima@^4.0.0: resolved "https://registry.yarnpkg.com/esprima/-/esprima-4.0.1.tgz#13b04cdb3e6c5d19df91ab6987a8695619b0aa71" integrity sha512-eGuFFw7Upda+g4p+QHvnW0RyTX/SVeJBDM/gCtMARO0cLuT2HcEKnTPvhjV6aGeqrCB/sbNop0Kszm0jsaWU4A== -esquery@^1.4.2: +esquery@^1.4.2, esquery@^1.5.0: version "1.5.0" resolved "https://registry.yarnpkg.com/esquery/-/esquery-1.5.0.tgz#6ce17738de8577694edd7361c57182ac8cb0db0b" integrity sha512-YQLXUplAwJgCydQ78IMJywZCceoqk1oH01OERdSAJc/7U2AylwjhSCLDEtqwg811idIS/9fIU5GjG73IgjKMVg== @@ -4701,6 +4750,13 @@ is-buffer@^2.0.5: resolved "https://registry.yarnpkg.com/is-buffer/-/is-buffer-2.0.5.tgz#ebc252e400d22ff8d77fa09888821a24a658c191" integrity sha512-i2R6zNFDwgEHJyQUtJEk0XFi1i0dPFn/oqjK3/vPCcDeJvW5NQ83V8QbicfF1SupOaB0h8ntgBC2YiE7dfyctQ== +is-builtin-module@^3.2.1: + version "3.2.1" + resolved "https://registry.yarnpkg.com/is-builtin-module/-/is-builtin-module-3.2.1.tgz#f03271717d8654cfcaf07ab0463faa3571581169" + integrity sha512-BSLE3HnV2syZ0FK0iMA/yUGplUeMmNz4AW5fnTunbCIqZi4vG3WjJT9FHMy5D69xmAYBHXQhJdALdpwVxV501A== + dependencies: + builtin-modules "^3.3.0" + is-callable@^1.1.3, is-callable@^1.1.4, is-callable@^1.2.7: version "1.2.7" resolved "https://registry.yarnpkg.com/is-callable/-/is-callable-1.2.7.tgz#3bc2a85ea742d9e36205dcacdd72ca1fdc51b055" @@ -5093,6 +5149,11 @@ jsbn@~0.1.0: resolved "https://registry.yarnpkg.com/jsbn/-/jsbn-0.1.1.tgz#a5e654c2e5a2deb5f201d96cefbca80c0ef2f513" integrity sha512-UVU9dibq2JcFWxQPA6KCqj5O42VOmAY3zQUfEKxU0KpTGXwNoCjkX1e13eHNvw/xPynt6pU0rZ1htjWTNTSXsg== +jsdoc-type-pratt-parser@~4.0.0: + version "4.0.0" + resolved "https://registry.yarnpkg.com/jsdoc-type-pratt-parser/-/jsdoc-type-pratt-parser-4.0.0.tgz#136f0571a99c184d84ec84662c45c29ceff71114" + integrity sha512-YtOli5Cmzy3q4dP26GraSOeAhqecewG04hoO8DY56CH4KJ9Fvv5qKWUCCo3HZob7esJQHCv6/+bnTy72xZZaVQ== + jsdoc@^4.0.2: version "4.0.3" resolved "https://registry.yarnpkg.com/jsdoc/-/jsdoc-4.0.3.tgz#bfee86c6a82f6823e12b5e8be698fd99ae46c061" @@ -7113,6 +7174,14 @@ spdx-expression-parse@^3.0.0: spdx-exceptions "^2.1.0" spdx-license-ids "^3.0.0" +spdx-expression-parse@^4.0.0: + version "4.0.0" + resolved "https://registry.yarnpkg.com/spdx-expression-parse/-/spdx-expression-parse-4.0.0.tgz#a23af9f3132115465dac215c099303e4ceac5794" + integrity sha512-Clya5JIij/7C6bRR22+tnGXbc4VKlibKSVj2iHvVeX5iMW7s1SIQlqu699JkODJJIhh/pUu8L0/VLh8xflD+LQ== + dependencies: + spdx-exceptions "^2.1.0" + spdx-license-ids "^3.0.0" + spdx-license-ids@^3.0.0: version "3.0.17" resolved "https://registry.yarnpkg.com/spdx-license-ids/-/spdx-license-ids-3.0.17.tgz#887da8aa73218e51a1d917502d79863161a93f9c"