From 6f27c74b70c5e83358872a71a89df6491e7e97da Mon Sep 17 00:00:00 2001 From: Al Date: Thu, 5 Oct 2023 13:57:53 +0200 Subject: [PATCH 1/2] feat: adding support for interacting with dispenser api on testnet via ensureFunded (#144) * feat: initial implementation of ensureFunded with dispenser api * chore: minor tweaks * docs: updating docs * chore: updating recommended test runner; fixing tests * chore: disabling jest auto running behaviour * feat: adding dispenser api client; refining ensureFunded BREAKING CHANGE: Adding new client class for interacting with TestNet Dispenser API; Changing output type of ensureFunded method * docs: updating docs * chore: restoring recommended plugin * chore: refactoring docs * chore: fixing tests * chore: updating docs * chore: addressing pr comments --- .github/pull_request_template.md | 5 + .github/workflows/issue_labelled.yml | 2 +- .prettierignore | 4 + .vscode/settings.json | 6 +- docs/capabilities/dispenser-client.md | 57 +++++ docs/capabilities/transfer.md | 13 +- docs/code/README.md | 2 + ...penser_client.DispenserApiTestnetClient.md | 221 ++++++++++++++++++ ..._client.DispenserApiTestnetClientParams.md | 32 +++ ..._dispenser_client.DispenserFundResponse.md | 32 +++ ...dispenser_client.DispenserLimitResponse.md | 21 ++ .../types_transfer.AlgoTransferParams.md | 10 +- .../types_transfer.EnsureFundedParams.md | 14 +- .../types_transfer.EnsureFundedReturnType.md | 36 +++ .../types_transfer.TransferAssetParams.md | 14 +- docs/code/modules/index.md | 66 +++++- docs/code/modules/types_dispenser_client.md | 15 ++ .../modules/types_dispenser_client_spec.md | 3 + docs/code/modules/types_transfer.md | 1 + package-lock.json | 26 +++ package.json | 4 +- src/account.spec.ts | 12 +- src/dispenser-client.ts | 21 ++ src/index.ts | 1 + src/network-client.ts | 5 +- src/transfer.spec.ts | 88 +++++-- src/transfer.ts | 123 +++++++--- src/types/dispenser-client.spec.ts | 70 ++++++ src/types/dispenser-client.ts | 179 ++++++++++++++ src/types/transfer.ts | 10 +- src/util.ts | 20 ++ 31 files changed, 1022 insertions(+), 91 deletions(-) create mode 100644 .github/pull_request_template.md create mode 100644 docs/capabilities/dispenser-client.md create mode 100644 docs/code/classes/types_dispenser_client.DispenserApiTestnetClient.md create mode 100644 docs/code/interfaces/types_dispenser_client.DispenserApiTestnetClientParams.md create mode 100644 docs/code/interfaces/types_dispenser_client.DispenserFundResponse.md create mode 100644 docs/code/interfaces/types_dispenser_client.DispenserLimitResponse.md create mode 100644 docs/code/interfaces/types_transfer.EnsureFundedReturnType.md create mode 100644 docs/code/modules/types_dispenser_client.md create mode 100644 docs/code/modules/types_dispenser_client_spec.md create mode 100644 src/dispenser-client.ts create mode 100644 src/types/dispenser-client.spec.ts create mode 100644 src/types/dispenser-client.ts diff --git a/.github/pull_request_template.md b/.github/pull_request_template.md new file mode 100644 index 00000000..fabab730 --- /dev/null +++ b/.github/pull_request_template.md @@ -0,0 +1,5 @@ +## Proposed Changes + +- +- +- diff --git a/.github/workflows/issue_labelled.yml b/.github/workflows/issue_labelled.yml index d8689d11..950ea3ea 100644 --- a/.github/workflows/issue_labelled.yml +++ b/.github/workflows/issue_labelled.yml @@ -1,4 +1,4 @@ -name: Create Zendesk ticket when an issue is labelled with makerx +name: Create Zendesk ticket when an issue is labelled with makerx on: issues: types: [labeled] diff --git a/.prettierignore b/.prettierignore index dbda6ae9..72717bb3 100644 --- a/.prettierignore +++ b/.prettierignore @@ -10,3 +10,7 @@ coverage **/generated/types.ts # don't format ide files .idea +# don't format auto generated code docs +docs/code +# don't format test contract artifacts +tests/example-contracts/**/*.json diff --git a/.vscode/settings.json b/.vscode/settings.json index 2703f452..9a608b58 100644 --- a/.vscode/settings.json +++ b/.vscode/settings.json @@ -6,10 +6,6 @@ "source.organizeImports": true }, "jest.jestCommandLine": "npm run test --", - "jest.autoRun": { - "watch": false, - "onSave": "test-file", - "onStartup": ["all-tests"] - }, + "jest.autoRun": {}, "dotenv.enableAutocloaking": false } diff --git a/docs/capabilities/dispenser-client.md b/docs/capabilities/dispenser-client.md new file mode 100644 index 00000000..594d3ff8 --- /dev/null +++ b/docs/capabilities/dispenser-client.md @@ -0,0 +1,57 @@ +# TestNet Dispenser Client + +The TestNet Dispenser Client is a utility for interacting with the AlgoKit TestNet Dispenser API. It provides methods to fund an account, register a refund for a transaction, and get the current limit for an account. + +## Creating a Dispenser Client + +To create a Dispenser Client, you need to provide an authorization token. This can be done in two ways: + +1. Pass the token directly to the client constructor as `authToken`. +2. Set the token as an environment variable `ALGOKIT_DISPENSER_ACCESS_TOKEN` (see [docs](https://github.com/algorandfoundation/algokit/blob/main/docs/testnet_api.md#error-handling) on how to obtain the token). + +If both methods are used, the constructor argument takes precedence. + +```ts +import * as algokit from '@algorandfoundation/algokit-utils' + +// Using constructor argument +const client = algokit.getDispenserApiTestnetClient({ authToken: 'your_auth_token' }) + +// Using environment variable +process.env['ALGOKIT_DISPENSER_ACCESS_TOKEN'] = 'your_auth_token' +const client = algokit.getDispenserApiTestnetClient() +``` + +## Funding an Account + +To fund an account with Algos from the dispenser API, use the `fund` method. This method requires the receiver's address, the amount to be funded, and the asset ID. + +```ts +const response = await client.fund('receiver_address', 1000) +``` + +The `fund` method returns a `DispenserFundResponse` object, which contains the transaction ID (`txId`) and the amount funded. + +## Registering a Refund + +To register a refund for a transaction with the dispenser API, use the `refund` method. This method requires the transaction ID of the refund transaction. + +```ts +await client.refund('transaction_id') +``` + +> Keep in mind, to perform a refund you need to perform a payment transaction yourself first by sending funds back to TestNet Dispenser, then you can invoke this refund endpoint and pass the txn_id of your refund txn. You can obtain dispenser address by inspecting the sender field of any issued fund transaction initiated via [fund](#funding-an-account). + +## Getting Current Limit + +To get the current limit for an account with Algos from the dispenser API, use the `getLimit` method. This method requires the account address. + +```ts +const response = await client.getLimit() +``` + +The `limit` method returns a `DispenserLimitResponse` object, which contains the current limit amount. + +## Error Handling + +If an error occurs while making a request to the dispenser API, an exception will be raised with a message indicating the type of error. Refer to [Error Handling docs](https://github.com/algorandfoundation/algokit/blob/main/docs/testnet_api.md#error-handling) for details on how you can handle each individual error `code`. diff --git a/docs/capabilities/transfer.md b/docs/capabilities/transfer.md index 0eab9069..0cc433b4 100644 --- a/docs/capabilities/transfer.md +++ b/docs/capabilities/transfer.md @@ -17,18 +17,17 @@ The key function to facilitate Algo transfers is `algokit.transferAlgos(transfer ## `ensureFunded` -The ability to automatically fund an account to have a minimum amount of disposable ALGOs to spend is incredibly useful for automation and deployment scripts. The function to facilitate this is `algokit.ensureFunded(funding, algod, kmd?)`, which returns a [`SendTransactionResult`](./transaction.md#sendtransactionresult) (or undefined if it didn't need to send a transaction) and takes a [`EnsureFundedParams`](../code/interfaces/types_transfer.EnsureFundedParams.md): +The `ensureFunded` function automatically funds an account to maintain a minimum amount of disposable ALGOs. This is particularly useful for automation and deployment scripts. The function is defined as `algokit.ensureFunded(funding, algod, kmd?)` and returns a [`EnsureFundedReturnType`](../code/interfaces/types_transfer.EnsureFundedReturnType.md) if a transaction was needed, or `undefined` if no transaction was required. The function takes a [`EnsureFundedParams`](../code/interfaces/types_transfer.EnsureFundedParams.md) object as an argument: - All properties in [`SendTransactionParams`](./transaction.md#sendtransactionparams) - `accountToFund: SendTransactionFrom | string` - The account that is to be funded -- `fundingSource?: SendTransactionFrom` - The account that is the source of funds, if not specified then it will use the [dispenser](./account.md#dispenser) -- `minSpendingBalance: AlgoAmount` - The minimum balance of ALGOs that the account should have available to spend (i.e. on top of minimum balance requirement) -- `minFundingIncrement?: AlgoAmount` - When issuing a funding amount, the minimum amount to transfer (avoids many small transfers if this gets called often on an active account) -- `amount: AlgoAmount` - The [amount](./amount.md) of ALGOs to send +- `fundingSource?: SendTransactionFrom | DispenserApiTestnetClient` - The account that is the source of funds or a dispenser API client. If not specified, it will use the [dispenser](./account.md#dispenser) +- `minSpendingBalance: AlgoAmount` - The minimum balance of ALGOs that the account should have available to spend (i.e., on top of the minimum balance requirement) +- `minFundingIncrement?: AlgoAmount` - When issuing a funding amount, the minimum amount to transfer. This avoids many small transfers if this function gets called often on an active account - `transactionParams?: SuggestedParams` - The optional [transaction parameters](./transaction.md#transaction-params) - `note?: TransactionNote` - The [transaction note](./transaction.md#transaction-notes) -The function calls Algod to find the current balance and minimum balance requirement, gets the difference between those two numbers and checks to see if it's more than the `minSpendingBalance` and if so then it will send the difference, or the `minFundingIncrement` if that is specified. +The function calls Algod to find the current balance and minimum balance requirement, calculates the difference between those two numbers, and checks to see if it's more than the `minSpendingBalance`. If so, it will send the difference, or the `minFundingIncrement` if that is specified. If the `fundingSource` is an instance of `DispenserApiTestnetClient`, the function will use the dispenser API to fund the account. Refer to [algokit-cli documentation](https://github.com/algorandfoundation/algokit-cli/blob/main/docs/features/dispenser.md#ci-access-token) for details on obtaining an access token for AlgoKit TestNet Dispenser API. ## `transferAsset` @@ -48,3 +47,5 @@ The key function to facilitate asset transfers is `transferAsset(transfer, algod If you want to programmtically send funds then you will often need a "dispenser" account that has a store of ALGOs that can be sent and a private key available for that dispenser account. There is a standard AlgoKit Utils function to get access to a [dispenser account](./account.md#accounts): [`algokit.getDispenserAccount(algod, kmd?)`](../code/modules/index.md#getdispenseraccount). When running against [LocalNet](https://github.com/algorandfoundation/algokit-cli/blob/main/docs/features/localnet.md), the dispenser account can be automatically determined using the [Kmd API](https://developer.algorand.org/docs/rest-apis/kmd). When running against other networks like TestNet or MainNet the mnemonic (and optionally sender address if it's been rekeyed) of the dispenser account can be provided via environment variables (`process.env.DISPENSER_MNEMONIC` and optionally `process.env.DISPENSER_SENDER` if rekeyed). + +> Please note that this does not refer to the [AlgoKit TestNet Dispenser API](./dispenser-client.md) which is a separate abstraction that can be used to fund accounts on TestNet via dedicated API service. diff --git a/docs/code/README.md b/docs/code/README.md index 3dd222a6..bee54a1a 100644 --- a/docs/code/README.md +++ b/docs/code/README.md @@ -17,6 +17,8 @@ - [types/app-client.spec](modules/types_app_client_spec.md) - [types/app-spec](modules/types_app_spec.md) - [types/config](modules/types_config.md) +- [types/dispenser-client](modules/types_dispenser_client.md) +- [types/dispenser-client.spec](modules/types_dispenser_client_spec.md) - [types/indexer](modules/types_indexer.md) - [types/logging](modules/types_logging.md) - [types/logic-error](modules/types_logic_error.md) diff --git a/docs/code/classes/types_dispenser_client.DispenserApiTestnetClient.md b/docs/code/classes/types_dispenser_client.DispenserApiTestnetClient.md new file mode 100644 index 00000000..f81fd841 --- /dev/null +++ b/docs/code/classes/types_dispenser_client.DispenserApiTestnetClient.md @@ -0,0 +1,221 @@ +[@algorandfoundation/algokit-utils](../README.md) / [types/dispenser-client](../modules/types_dispenser_client.md) / DispenserApiTestnetClient + +# Class: DispenserApiTestnetClient + +[types/dispenser-client](../modules/types_dispenser_client.md).DispenserApiTestnetClient + +`DispenserApiTestnetClient` is a class that provides methods to interact with the [Algorand TestNet Dispenser API](https://github.com/algorandfoundation/algokit/blob/main/docs/testnet_api.md). +It allows you to fund an address with Algos, refund a transaction, and get the funding limit for the Algo asset. + +The class requires an authentication token and a request timeout to be initialized. The authentication token can be provided +either directly as a parameter or through an `ALGOKIT_DISPENSER_ACCESS_TOKEN` environment variable. If neither is provided, an error is thrown. + +The request timeout can be provided as a parameter. If not provided, a default value is used. + +**`Method`** + +fund - Sends a funding request to the dispenser API to fund the specified address with the given amount of Algo. + +**`Method`** + +refund - Sends a refund request to the dispenser API for the specified refundTxnId. + +**`Method`** + +limit - Sends a request to the dispenser API to get the funding limit for the Algo asset. + +**`Example`** + +```typescript +const client = new DispenserApiTestnetClient({ authToken: 'your_auth_token', requestTimeout: 30 }); +const fundResponse = await client.fund('your_address', 100); +const limitResponse = await client.getLimit(); +await client.refund('your_transaction_id'); +``` + +**`Throws`** + +If neither the environment variable 'ALGOKIT_DISPENSER_ACCESS_TOKEN' nor the authToken parameter were provided. + +## Table of contents + +### Constructors + +- [constructor](types_dispenser_client.DispenserApiTestnetClient.md#constructor) + +### Properties + +- [\_authToken](types_dispenser_client.DispenserApiTestnetClient.md#_authtoken) +- [\_requestTimeout](types_dispenser_client.DispenserApiTestnetClient.md#_requesttimeout) + +### Accessors + +- [authToken](types_dispenser_client.DispenserApiTestnetClient.md#authtoken) +- [requestTimeout](types_dispenser_client.DispenserApiTestnetClient.md#requesttimeout) + +### Methods + +- [fund](types_dispenser_client.DispenserApiTestnetClient.md#fund) +- [getLimit](types_dispenser_client.DispenserApiTestnetClient.md#getlimit) +- [processDispenserRequest](types_dispenser_client.DispenserApiTestnetClient.md#processdispenserrequest) +- [refund](types_dispenser_client.DispenserApiTestnetClient.md#refund) + +## Constructors + +### constructor + +• **new DispenserApiTestnetClient**(`params`) + +#### Parameters + +| Name | Type | +| :------ | :------ | +| `params` | ``null`` \| [`DispenserApiTestnetClientParams`](../interfaces/types_dispenser_client.DispenserApiTestnetClientParams.md) | + +#### Defined in + +[src/types/dispenser-client.ts:61](https://github.com/algorandfoundation/algokit-utils-ts/blob/main/src/types/dispenser-client.ts#L61) + +## Properties + +### \_authToken + +• `Private` **\_authToken**: `string` + +#### Defined in + +[src/types/dispenser-client.ts:58](https://github.com/algorandfoundation/algokit-utils-ts/blob/main/src/types/dispenser-client.ts#L58) + +___ + +### \_requestTimeout + +• `Private` **\_requestTimeout**: `number` + +#### Defined in + +[src/types/dispenser-client.ts:59](https://github.com/algorandfoundation/algokit-utils-ts/blob/main/src/types/dispenser-client.ts#L59) + +## Accessors + +### authToken + +• `get` **authToken**(): `string` + +The authentication token used for API requests. + +#### Returns + +`string` + +#### Defined in + +[src/types/dispenser-client.ts:77](https://github.com/algorandfoundation/algokit-utils-ts/blob/main/src/types/dispenser-client.ts#L77) + +___ + +### requestTimeout + +• `get` **requestTimeout**(): `number` + +The timeout for API requests, in seconds. + +#### Returns + +`number` + +#### Defined in + +[src/types/dispenser-client.ts:81](https://github.com/algorandfoundation/algokit-utils-ts/blob/main/src/types/dispenser-client.ts#L81) + +## Methods + +### fund + +▸ **fund**(`address`, `amount`): `Promise`<[`DispenserFundResponse`](../interfaces/types_dispenser_client.DispenserFundResponse.md)\> + +Sends a funding request to the dispenser API to fund the specified address with the given amount of Algo. + +#### Parameters + +| Name | Type | Description | +| :------ | :------ | :------ | +| `address` | `string` | The address to fund. | +| `amount` | `number` | The amount of Algo to fund. | + +#### Returns + +`Promise`<[`DispenserFundResponse`](../interfaces/types_dispenser_client.DispenserFundResponse.md)\> + +DispenserFundResponse: An object containing the transaction ID and funded amount. + +#### Defined in + +[src/types/dispenser-client.ts:142](https://github.com/algorandfoundation/algokit-utils-ts/blob/main/src/types/dispenser-client.ts#L142) + +___ + +### getLimit + +▸ **getLimit**(): `Promise`<[`DispenserLimitResponse`](../interfaces/types_dispenser_client.DispenserLimitResponse.md)\> + +Sends a request to the dispenser API to get the funding limit for the Algo asset. + +#### Returns + +`Promise`<[`DispenserLimitResponse`](../interfaces/types_dispenser_client.DispenserLimitResponse.md)\> + +DispenserLimitResponse: An object containing the funding limit amount. + +#### Defined in + +[src/types/dispenser-client.ts:168](https://github.com/algorandfoundation/algokit-utils-ts/blob/main/src/types/dispenser-client.ts#L168) + +___ + +### processDispenserRequest + +▸ `Private` **processDispenserRequest**(`authToken`, `urlSuffix`, `body?`, `method?`): `Promise`<`Response`\> + +Processes a dispenser API request. + +#### Parameters + +| Name | Type | Default value | Description | +| :------ | :------ | :------ | :------ | +| `authToken` | `string` | `undefined` | The authentication token. | +| `urlSuffix` | `string` | `undefined` | The URL suffix for the API request. | +| `body` | ``null`` \| `Record`<`string`, `string` \| `number`\> | `null` | The request body. | +| `method` | `string` | `'POST'` | The HTTP method. | + +#### Returns + +`Promise`<`Response`\> + +The API response. + +#### Defined in + +[src/types/dispenser-client.ts:95](https://github.com/algorandfoundation/algokit-utils-ts/blob/main/src/types/dispenser-client.ts#L95) + +___ + +### refund + +▸ **refund**(`refundTxnId`): `Promise`<`void`\> + +Sends a refund request to the dispenser API for the specified refundTxnId. + +#### Parameters + +| Name | Type | Description | +| :------ | :------ | :------ | +| `refundTxnId` | `string` | The transaction ID to refund. | + +#### Returns + +`Promise`<`void`\> + +#### Defined in + +[src/types/dispenser-client.ts:159](https://github.com/algorandfoundation/algokit-utils-ts/blob/main/src/types/dispenser-client.ts#L159) diff --git a/docs/code/interfaces/types_dispenser_client.DispenserApiTestnetClientParams.md b/docs/code/interfaces/types_dispenser_client.DispenserApiTestnetClientParams.md new file mode 100644 index 00000000..68ce405b --- /dev/null +++ b/docs/code/interfaces/types_dispenser_client.DispenserApiTestnetClientParams.md @@ -0,0 +1,32 @@ +[@algorandfoundation/algokit-utils](../README.md) / [types/dispenser-client](../modules/types_dispenser_client.md) / DispenserApiTestnetClientParams + +# Interface: DispenserApiTestnetClientParams + +[types/dispenser-client](../modules/types_dispenser_client.md).DispenserApiTestnetClientParams + +## Table of contents + +### Properties + +- [authToken](types_dispenser_client.DispenserApiTestnetClientParams.md#authtoken) +- [requestTimeout](types_dispenser_client.DispenserApiTestnetClientParams.md#requesttimeout) + +## Properties + +### authToken + +• **authToken**: `string` + +#### Defined in + +[src/types/dispenser-client.ts:27](https://github.com/algorandfoundation/algokit-utils-ts/blob/main/src/types/dispenser-client.ts#L27) + +___ + +### requestTimeout + +• **requestTimeout**: ``null`` \| `number` + +#### Defined in + +[src/types/dispenser-client.ts:28](https://github.com/algorandfoundation/algokit-utils-ts/blob/main/src/types/dispenser-client.ts#L28) diff --git a/docs/code/interfaces/types_dispenser_client.DispenserFundResponse.md b/docs/code/interfaces/types_dispenser_client.DispenserFundResponse.md new file mode 100644 index 00000000..f85c8e6f --- /dev/null +++ b/docs/code/interfaces/types_dispenser_client.DispenserFundResponse.md @@ -0,0 +1,32 @@ +[@algorandfoundation/algokit-utils](../README.md) / [types/dispenser-client](../modules/types_dispenser_client.md) / DispenserFundResponse + +# Interface: DispenserFundResponse + +[types/dispenser-client](../modules/types_dispenser_client.md).DispenserFundResponse + +## Table of contents + +### Properties + +- [amount](types_dispenser_client.DispenserFundResponse.md#amount) +- [txId](types_dispenser_client.DispenserFundResponse.md#txid) + +## Properties + +### amount + +• **amount**: `number` + +#### Defined in + +[src/types/dispenser-client.ts:19](https://github.com/algorandfoundation/algokit-utils-ts/blob/main/src/types/dispenser-client.ts#L19) + +___ + +### txId + +• **txId**: `string` + +#### Defined in + +[src/types/dispenser-client.ts:18](https://github.com/algorandfoundation/algokit-utils-ts/blob/main/src/types/dispenser-client.ts#L18) diff --git a/docs/code/interfaces/types_dispenser_client.DispenserLimitResponse.md b/docs/code/interfaces/types_dispenser_client.DispenserLimitResponse.md new file mode 100644 index 00000000..6e286f73 --- /dev/null +++ b/docs/code/interfaces/types_dispenser_client.DispenserLimitResponse.md @@ -0,0 +1,21 @@ +[@algorandfoundation/algokit-utils](../README.md) / [types/dispenser-client](../modules/types_dispenser_client.md) / DispenserLimitResponse + +# Interface: DispenserLimitResponse + +[types/dispenser-client](../modules/types_dispenser_client.md).DispenserLimitResponse + +## Table of contents + +### Properties + +- [amount](types_dispenser_client.DispenserLimitResponse.md#amount) + +## Properties + +### amount + +• **amount**: `number` + +#### Defined in + +[src/types/dispenser-client.ts:23](https://github.com/algorandfoundation/algokit-utils-ts/blob/main/src/types/dispenser-client.ts#L23) diff --git a/docs/code/interfaces/types_transfer.AlgoTransferParams.md b/docs/code/interfaces/types_transfer.AlgoTransferParams.md index f0002787..1a9f60ca 100644 --- a/docs/code/interfaces/types_transfer.AlgoTransferParams.md +++ b/docs/code/interfaces/types_transfer.AlgoTransferParams.md @@ -39,7 +39,7 @@ The amount to send #### Defined in -[src/types/transfer.ts:12](https://github.com/algorandfoundation/algokit-utils-ts/blob/main/src/types/transfer.ts#L12) +[src/types/transfer.ts:13](https://github.com/algorandfoundation/algokit-utils-ts/blob/main/src/types/transfer.ts#L13) ___ @@ -83,7 +83,7 @@ The account that will send the ALGOs #### Defined in -[src/types/transfer.ts:8](https://github.com/algorandfoundation/algokit-utils-ts/blob/main/src/types/transfer.ts#L8) +[src/types/transfer.ts:9](https://github.com/algorandfoundation/algokit-utils-ts/blob/main/src/types/transfer.ts#L9) ___ @@ -127,7 +127,7 @@ The (optional) transaction note #### Defined in -[src/types/transfer.ts:16](https://github.com/algorandfoundation/algokit-utils-ts/blob/main/src/types/transfer.ts#L16) +[src/types/transfer.ts:17](https://github.com/algorandfoundation/algokit-utils-ts/blob/main/src/types/transfer.ts#L17) ___ @@ -188,7 +188,7 @@ The account / account address that will receive the ALGOs #### Defined in -[src/types/transfer.ts:10](https://github.com/algorandfoundation/algokit-utils-ts/blob/main/src/types/transfer.ts#L10) +[src/types/transfer.ts:11](https://github.com/algorandfoundation/algokit-utils-ts/blob/main/src/types/transfer.ts#L11) ___ @@ -200,4 +200,4 @@ Optional transaction parameters #### Defined in -[src/types/transfer.ts:14](https://github.com/algorandfoundation/algokit-utils-ts/blob/main/src/types/transfer.ts#L14) +[src/types/transfer.ts:15](https://github.com/algorandfoundation/algokit-utils-ts/blob/main/src/types/transfer.ts#L15) diff --git a/docs/code/interfaces/types_transfer.EnsureFundedParams.md b/docs/code/interfaces/types_transfer.EnsureFundedParams.md index 6cd194a3..ba3f4e95 100644 --- a/docs/code/interfaces/types_transfer.EnsureFundedParams.md +++ b/docs/code/interfaces/types_transfer.EnsureFundedParams.md @@ -40,7 +40,7 @@ The account to fund #### Defined in -[src/types/transfer.ts:22](https://github.com/algorandfoundation/algokit-utils-ts/blob/main/src/types/transfer.ts#L22) +[src/types/transfer.ts:23](https://github.com/algorandfoundation/algokit-utils-ts/blob/main/src/types/transfer.ts#L23) ___ @@ -78,13 +78,13 @@ ___ ### fundingSource -• `Optional` **fundingSource**: [`SendTransactionFrom`](../modules/types_transaction.md#sendtransactionfrom) +• `Optional` **fundingSource**: [`SendTransactionFrom`](../modules/types_transaction.md#sendtransactionfrom) \| [`DispenserApiTestnetClient`](../classes/types_dispenser_client.DispenserApiTestnetClient.md) The account to use as a funding source, will default to using the dispenser account returned by `algokit.getDispenserAccount` #### Defined in -[src/types/transfer.ts:24](https://github.com/algorandfoundation/algokit-utils-ts/blob/main/src/types/transfer.ts#L24) +[src/types/transfer.ts:25](https://github.com/algorandfoundation/algokit-utils-ts/blob/main/src/types/transfer.ts#L25) ___ @@ -128,7 +128,7 @@ When issuing a funding amount, the minimum amount to transfer (avoids many small #### Defined in -[src/types/transfer.ts:28](https://github.com/algorandfoundation/algokit-utils-ts/blob/main/src/types/transfer.ts#L28) +[src/types/transfer.ts:29](https://github.com/algorandfoundation/algokit-utils-ts/blob/main/src/types/transfer.ts#L29) ___ @@ -140,7 +140,7 @@ The minimum balance of ALGOs that the account should have available to spend (i. #### Defined in -[src/types/transfer.ts:26](https://github.com/algorandfoundation/algokit-utils-ts/blob/main/src/types/transfer.ts#L26) +[src/types/transfer.ts:27](https://github.com/algorandfoundation/algokit-utils-ts/blob/main/src/types/transfer.ts#L27) ___ @@ -152,7 +152,7 @@ The (optional) transaction note, default: "Funding account to meet minimum requi #### Defined in -[src/types/transfer.ts:32](https://github.com/algorandfoundation/algokit-utils-ts/blob/main/src/types/transfer.ts#L32) +[src/types/transfer.ts:33](https://github.com/algorandfoundation/algokit-utils-ts/blob/main/src/types/transfer.ts#L33) ___ @@ -213,4 +213,4 @@ Optional transaction parameters #### Defined in -[src/types/transfer.ts:30](https://github.com/algorandfoundation/algokit-utils-ts/blob/main/src/types/transfer.ts#L30) +[src/types/transfer.ts:31](https://github.com/algorandfoundation/algokit-utils-ts/blob/main/src/types/transfer.ts#L31) diff --git a/docs/code/interfaces/types_transfer.EnsureFundedReturnType.md b/docs/code/interfaces/types_transfer.EnsureFundedReturnType.md new file mode 100644 index 00000000..c5f14ecd --- /dev/null +++ b/docs/code/interfaces/types_transfer.EnsureFundedReturnType.md @@ -0,0 +1,36 @@ +[@algorandfoundation/algokit-utils](../README.md) / [types/transfer](../modules/types_transfer.md) / EnsureFundedReturnType + +# Interface: EnsureFundedReturnType + +[types/transfer](../modules/types_transfer.md).EnsureFundedReturnType + +## Table of contents + +### Properties + +- [amount](types_transfer.EnsureFundedReturnType.md#amount) +- [transactionId](types_transfer.EnsureFundedReturnType.md#transactionid) + +## Properties + +### amount + +• **amount**: `number` + +The response if the transaction was sent and waited for + +#### Defined in + +[src/types/transfer.ts:58](https://github.com/algorandfoundation/algokit-utils-ts/blob/main/src/types/transfer.ts#L58) + +___ + +### transactionId + +• **transactionId**: `string` + +The transaction + +#### Defined in + +[src/types/transfer.ts:56](https://github.com/algorandfoundation/algokit-utils-ts/blob/main/src/types/transfer.ts#L56) diff --git a/docs/code/interfaces/types_transfer.TransferAssetParams.md b/docs/code/interfaces/types_transfer.TransferAssetParams.md index a0ba708f..ab5c0735 100644 --- a/docs/code/interfaces/types_transfer.TransferAssetParams.md +++ b/docs/code/interfaces/types_transfer.TransferAssetParams.md @@ -41,7 +41,7 @@ The amount to send as the smallest divisible unit value #### Defined in -[src/types/transfer.ts:44](https://github.com/algorandfoundation/algokit-utils-ts/blob/main/src/types/transfer.ts#L44) +[src/types/transfer.ts:45](https://github.com/algorandfoundation/algokit-utils-ts/blob/main/src/types/transfer.ts#L45) ___ @@ -53,7 +53,7 @@ The asset id that will be transfered #### Defined in -[src/types/transfer.ts:42](https://github.com/algorandfoundation/algokit-utils-ts/blob/main/src/types/transfer.ts#L42) +[src/types/transfer.ts:43](https://github.com/algorandfoundation/algokit-utils-ts/blob/main/src/types/transfer.ts#L43) ___ @@ -81,7 +81,7 @@ An address of a target account from which to perform a clawback operation. Pleas #### Defined in -[src/types/transfer.ts:48](https://github.com/algorandfoundation/algokit-utils-ts/blob/main/src/types/transfer.ts#L48) +[src/types/transfer.ts:49](https://github.com/algorandfoundation/algokit-utils-ts/blob/main/src/types/transfer.ts#L49) ___ @@ -109,7 +109,7 @@ The account that will send the asset #### Defined in -[src/types/transfer.ts:38](https://github.com/algorandfoundation/algokit-utils-ts/blob/main/src/types/transfer.ts#L38) +[src/types/transfer.ts:39](https://github.com/algorandfoundation/algokit-utils-ts/blob/main/src/types/transfer.ts#L39) ___ @@ -153,7 +153,7 @@ The (optional) transaction note #### Defined in -[src/types/transfer.ts:50](https://github.com/algorandfoundation/algokit-utils-ts/blob/main/src/types/transfer.ts#L50) +[src/types/transfer.ts:51](https://github.com/algorandfoundation/algokit-utils-ts/blob/main/src/types/transfer.ts#L51) ___ @@ -214,7 +214,7 @@ The account / account address that will receive the asset #### Defined in -[src/types/transfer.ts:40](https://github.com/algorandfoundation/algokit-utils-ts/blob/main/src/types/transfer.ts#L40) +[src/types/transfer.ts:41](https://github.com/algorandfoundation/algokit-utils-ts/blob/main/src/types/transfer.ts#L41) ___ @@ -226,4 +226,4 @@ Optional transaction parameters #### Defined in -[src/types/transfer.ts:46](https://github.com/algorandfoundation/algokit-utils-ts/blob/main/src/types/transfer.ts#L46) +[src/types/transfer.ts:47](https://github.com/algorandfoundation/algokit-utils-ts/blob/main/src/types/transfer.ts#L47) diff --git a/docs/code/modules/index.md b/docs/code/modules/index.md index 64016df9..73162f5a 100644 --- a/docs/code/modules/index.md +++ b/docs/code/modules/index.md @@ -53,6 +53,7 @@ - [getCreatorAppsByName](index.md#getcreatorappsbyname) - [getDefaultLocalNetConfig](index.md#getdefaultlocalnetconfig) - [getDispenserAccount](index.md#getdispenseraccount) +- [getDispenserApiTestnetClient](index.md#getdispenserapitestnetclient) - [getIndexerConfigFromEnvironment](index.md#getindexerconfigfromenvironment) - [getKmdWalletAccount](index.md#getkmdwalletaccount) - [getLocalNetDispenserAccount](index.md#getlocalnetdispenseraccount) @@ -102,7 +103,7 @@ The AlgoKit config. To update it use the configure method. #### Defined in -[src/index.ts:15](https://github.com/algorandfoundation/algokit-utils-ts/blob/main/src/index.ts#L15) +[src/index.ts:16](https://github.com/algorandfoundation/algokit-utils-ts/blob/main/src/index.ts#L16) ## Functions @@ -351,29 +352,36 @@ ___ ### ensureFunded -▸ **ensureFunded**(`funding`, `algod`, `kmd?`): `Promise`<[`SendTransactionResult`](../interfaces/types_transaction.SendTransactionResult.md) \| `undefined`\> +▸ **ensureFunded**<`T`\>(`funding`, `algod`, `kmd?`): `Promise`<[`EnsureFundedReturnType`](../interfaces/types_transfer.EnsureFundedReturnType.md) \| `undefined`\> Funds a given account using a funding source such that it has a certain amount of algos free to spend (accounting for ALGOs locked in minimum balance requirement). https://developer.algorand.org/docs/get-details/accounts/#minimum-balance +#### Type parameters + +| Name | Type | +| :------ | :------ | +| `T` | extends [`EnsureFundedParams`](../interfaces/types_transfer.EnsureFundedParams.md) | + #### Parameters | Name | Type | Description | | :------ | :------ | :------ | -| `funding` | [`EnsureFundedParams`](../interfaces/types_transfer.EnsureFundedParams.md) | The funding configuration | -| `algod` | `default` | An algod client | -| `kmd?` | `default` | An optional kmd client | +| `funding` | `T` | The funding configuration of type `EnsureFundedParams`, including the account to fund, minimum spending balance, and optional parameters. If you set `useDispenserApi` to true, you must also set `ALGOKIT_DISPENSER_ACCESS_TOKEN` in your environment variables. | +| `algod` | `default` | An instance of the Algodv2 client. | +| `kmd?` | `default` | An optional instance of the Kmd client. | #### Returns -`Promise`<[`SendTransactionResult`](../interfaces/types_transaction.SendTransactionResult.md) \| `undefined`\> +`Promise`<[`EnsureFundedReturnType`](../interfaces/types_transfer.EnsureFundedReturnType.md) \| `undefined`\> -undefined if nothing was needed or the transaction send result +- `EnsureFundedReturnType` if funds were transferred. +- `undefined` if no funds were needed. #### Defined in -[src/transfer.ts:48](https://github.com/algorandfoundation/algokit-utils-ts/blob/main/src/transfer.ts#L48) +[src/transfer.ts:114](https://github.com/algorandfoundation/algokit-utils-ts/blob/main/src/transfer.ts#L114) ___ @@ -1391,6 +1399,42 @@ If running on LocalNet then it will return the default dispenser account automat ___ +### getDispenserApiTestnetClient + +▸ **getDispenserApiTestnetClient**(`params?`): [`DispenserApiTestnetClient`](../classes/types_dispenser_client.DispenserApiTestnetClient.md) + +Create a new DispenserApiTestnetClient instance. +Refer to [docs](https://github.com/algorandfoundation/algokit/blob/main/docs/testnet_api.md) on guidance to obtain an access token. + +#### Parameters + +| Name | Type | Default value | Description | +| :------ | :------ | :------ | :------ | +| `params` | ``null`` \| [`DispenserApiTestnetClientParams`](../interfaces/types_dispenser_client.DispenserApiTestnetClientParams.md) | `null` | An object containing parameters for the DispenserApiTestnetClient class. Or null if you want the client to load the access token from the environment variable `ALGOKIT_DISPENSER_ACCESS_TOKEN`. | + +#### Returns + +[`DispenserApiTestnetClient`](../classes/types_dispenser_client.DispenserApiTestnetClient.md) + +An instance of the DispenserApiTestnetClient class. + +**`Example`** + +```ts +const client = algokit.getDispenserApiTestnetClient( + { + authToken: 'your_auth_token', + requestTimeout: 15, + } +) +``` + +#### Defined in + +[src/dispenser-client.ts:19](https://github.com/algorandfoundation/algokit-utils-ts/blob/main/src/dispenser-client.ts#L19) + +___ + ### getIndexerConfigFromEnvironment ▸ **getIndexerConfigFromEnvironment**(): [`AlgoClientConfig`](../interfaces/types_network_client.AlgoClientConfig.md) @@ -1639,7 +1683,7 @@ ___ #### Defined in -[src/network-client.ts:193](https://github.com/algorandfoundation/algokit-utils-ts/blob/main/src/network-client.ts#L193) +[src/network-client.ts:194](https://github.com/algorandfoundation/algokit-utils-ts/blob/main/src/network-client.ts#L194) ___ @@ -2297,7 +2341,7 @@ await algokit.transferAlgos({ from, to, amount: algokit.algos(1) }, algod) #### Defined in -[src/transfer.ts:18](https://github.com/algorandfoundation/algokit-utils-ts/blob/main/src/transfer.ts#L18) +[src/transfer.ts:82](https://github.com/algorandfoundation/algokit-utils-ts/blob/main/src/transfer.ts#L82) ___ @@ -2329,7 +2373,7 @@ await algokit.transferAsset({ from, to, assetId, amount }, algod) #### Defined in -[src/transfer.ts:94](https://github.com/algorandfoundation/algokit-utils-ts/blob/main/src/transfer.ts#L94) +[src/transfer.ts:165](https://github.com/algorandfoundation/algokit-utils-ts/blob/main/src/transfer.ts#L165) ___ diff --git a/docs/code/modules/types_dispenser_client.md b/docs/code/modules/types_dispenser_client.md new file mode 100644 index 00000000..2ae89639 --- /dev/null +++ b/docs/code/modules/types_dispenser_client.md @@ -0,0 +1,15 @@ +[@algorandfoundation/algokit-utils](../README.md) / types/dispenser-client + +# Module: types/dispenser-client + +## Table of contents + +### Classes + +- [DispenserApiTestnetClient](../classes/types_dispenser_client.DispenserApiTestnetClient.md) + +### Interfaces + +- [DispenserApiTestnetClientParams](../interfaces/types_dispenser_client.DispenserApiTestnetClientParams.md) +- [DispenserFundResponse](../interfaces/types_dispenser_client.DispenserFundResponse.md) +- [DispenserLimitResponse](../interfaces/types_dispenser_client.DispenserLimitResponse.md) diff --git a/docs/code/modules/types_dispenser_client_spec.md b/docs/code/modules/types_dispenser_client_spec.md new file mode 100644 index 00000000..1da387b6 --- /dev/null +++ b/docs/code/modules/types_dispenser_client_spec.md @@ -0,0 +1,3 @@ +[@algorandfoundation/algokit-utils](../README.md) / types/dispenser-client.spec + +# Module: types/dispenser-client.spec diff --git a/docs/code/modules/types_transfer.md b/docs/code/modules/types_transfer.md index a1de97c9..f96d2c1a 100644 --- a/docs/code/modules/types_transfer.md +++ b/docs/code/modules/types_transfer.md @@ -8,4 +8,5 @@ - [AlgoTransferParams](../interfaces/types_transfer.AlgoTransferParams.md) - [EnsureFundedParams](../interfaces/types_transfer.EnsureFundedParams.md) +- [EnsureFundedReturnType](../interfaces/types_transfer.EnsureFundedReturnType.md) - [TransferAssetParams](../interfaces/types_transfer.TransferAssetParams.md) diff --git a/package-lock.json b/package-lock.json index 2a07b230..5ac663bd 100644 --- a/package-lock.json +++ b/package-lock.json @@ -27,6 +27,7 @@ "copyfiles": "^2.4.1", "dotenv-cli": "^7.3.0", "eslint": "^8.47.0", + "jest-fetch-mock": "^3.0.3", "npm-run-all": "^4.1.5", "prettier": "^3.0.2", "rimraf": "^5.0.1", @@ -6713,6 +6714,25 @@ "node": "^14.15.0 || ^16.10.0 || >=18.0.0" } }, + "node_modules/jest-fetch-mock": { + "version": "3.0.3", + "resolved": "https://registry.npmjs.org/jest-fetch-mock/-/jest-fetch-mock-3.0.3.tgz", + "integrity": "sha512-Ux1nWprtLrdrH4XwE7O7InRY6psIi3GOsqNESJgMJ+M5cv4A8Lh7SN9d2V2kKRZ8ebAfcd1LNyZguAOb6JiDqw==", + "dev": true, + "dependencies": { + "cross-fetch": "^3.0.4", + "promise-polyfill": "^8.1.3" + } + }, + "node_modules/jest-fetch-mock/node_modules/cross-fetch": { + "version": "3.1.8", + "resolved": "https://registry.npmjs.org/cross-fetch/-/cross-fetch-3.1.8.tgz", + "integrity": "sha512-cvA+JwZoU0Xq+h6WkMvAUqPEYy92Obet6UdKLfW60qn99ftItKjB5T+BkyWOFWe2pUyfQ+IJHmpOTznqk1M6Kg==", + "dev": true, + "dependencies": { + "node-fetch": "^2.6.12" + } + }, "node_modules/jest-get-type": { "version": "29.4.3", "resolved": "https://registry.npmjs.org/jest-get-type/-/jest-get-type-29.4.3.tgz", @@ -11742,6 +11762,12 @@ "integrity": "sha512-3ouUOpQhtgrbOa17J7+uxOTpITYWaGP7/AhoR3+A+/1e9skrzelGi/dXzEYyvbxubEF6Wn2ypscTKiKJFFn1ag==", "dev": true }, + "node_modules/promise-polyfill": { + "version": "8.3.0", + "resolved": "https://registry.npmjs.org/promise-polyfill/-/promise-polyfill-8.3.0.tgz", + "integrity": "sha512-H5oELycFml5yto/atYqmjyigJoAo3+OXwolYiH7OfQuYlAqhxNvTfiNMbV9hsC6Yp83yE5r2KTVmtrG6R9i6Pg==", + "dev": true + }, "node_modules/prompts": { "version": "2.4.2", "resolved": "https://registry.npmjs.org/prompts/-/prompts-2.4.2.tgz", diff --git a/package.json b/package.json index 63724f02..3f5c6b92 100644 --- a/package.json +++ b/package.json @@ -69,7 +69,8 @@ "format": "prettier --write .", "commit-lint": "commitlint --edit -o", "semantic-release": "semantic-release", - "generate:code-docs": "typedoc" + "generate:code-docs": "typedoc", + "pre-commit": "run-s check-types lint:fix audit format test generate:code-docs" }, "overrides": { "semver": "7.5.2", @@ -97,6 +98,7 @@ "copyfiles": "^2.4.1", "dotenv-cli": "^7.3.0", "eslint": "^8.47.0", + "jest-fetch-mock": "^3.0.3", "npm-run-all": "^4.1.5", "prettier": "^3.0.2", "rimraf": "^5.0.1", diff --git a/src/account.spec.ts b/src/account.spec.ts index a3c42fbc..00cd0f69 100644 --- a/src/account.spec.ts +++ b/src/account.spec.ts @@ -7,7 +7,7 @@ import { algorandFixture } from './testing' describe('account', () => { const localnet = algorandFixture() - beforeEach(localnet.beforeEach, 10_000) + beforeEach(localnet.beforeEach, 10e6) test('New account is retrieved and funded', async () => { const { algod, kmd } = localnet.context @@ -16,7 +16,7 @@ describe('account', () => { const accountInfo = await algod.accountInformation(account.addr).do() expect(accountInfo['amount']).toBeGreaterThan(0) - }, 10_000) + }, 10e6) test('Same account is subsequently retrieved', async () => { const { algod, kmd } = localnet.context @@ -28,7 +28,7 @@ describe('account', () => { expect(account).not.toBe(account2) expect(account.addr).toBe(account2.addr) expect(account.sk).toEqual(account2.sk) - }, 10_000) + }, 10e6) test('Environment is used in preference to kmd', async () => { const { algod, kmd } = localnet.context @@ -42,7 +42,7 @@ describe('account', () => { expect(account).not.toBe(account2) expect(account.addr).toBe(account2.addr) expect(account.sk).toEqual(account2.sk) - }, 10_000) + }, 10e6) test('Deprecated signature 1 still works', async () => { const { algod, kmd } = localnet.context @@ -51,7 +51,7 @@ describe('account', () => { const accountInfo = await algod.accountInformation(account.addr).do() expect(accountInfo['amount']).toBeGreaterThan(0) - }, 10_000) + }, 10e6) test('Deprecated signature 2 still works', async () => { const { algod, kmd } = localnet.context @@ -61,5 +61,5 @@ describe('account', () => { const accountInfo = await algod.accountInformation(account.addr).do() expect(accountInfo['amount']).toBeGreaterThan(0) - }, 10_000) + }, 10e6) }) diff --git a/src/dispenser-client.ts b/src/dispenser-client.ts new file mode 100644 index 00000000..87a2c1eb --- /dev/null +++ b/src/dispenser-client.ts @@ -0,0 +1,21 @@ +import { DispenserApiTestnetClient, DispenserApiTestnetClientParams } from './types/dispenser-client' + +/** + * Create a new DispenserApiTestnetClient instance. + * Refer to [docs](https://github.com/algorandfoundation/algokit/blob/main/docs/testnet_api.md) on guidance to obtain an access token. + * + * @param params An object containing parameters for the DispenserApiTestnetClient class. + * Or null if you want the client to load the access token from the environment variable `ALGOKIT_DISPENSER_ACCESS_TOKEN`. + * @example + * const client = algokit.getDispenserApiTestnetClient( + * { + * authToken: 'your_auth_token', + * requestTimeout: 15, + * } + * ) + * + * @returns An instance of the DispenserApiTestnetClient class. + */ +export function getDispenserApiTestnetClient(params: DispenserApiTestnetClientParams | null = null) { + return new DispenserApiTestnetClient(params) +} diff --git a/src/index.ts b/src/index.ts index b314e505..d129363c 100644 --- a/src/index.ts +++ b/src/index.ts @@ -10,6 +10,7 @@ export * from './localnet' export * from './network-client' export * from './transaction' export * from './transfer' +export * from './dispenser-client' /** The AlgoKit config. To update it use the configure method. */ export const Config = new UpdatableConfig() diff --git a/src/network-client.ts b/src/network-client.ts index 295fea08..9e9d9ed5 100644 --- a/src/network-client.ts +++ b/src/network-client.ts @@ -188,11 +188,12 @@ export function getAlgoKmdClient(config?: AlgoClientConfig): Kmd { export async function isTestNet(algod: Algodv2): Promise { const params = await algod.getTransactionParams().do() - return params.genesisID === 'testnet-v1' + return ['testnet-v1.0', 'testnet-v1', 'testnet'].includes(params.genesisID) } + export async function isMainNet(algod: Algodv2): Promise { const params = await algod.getTransactionParams().do() - return params.genesisID === 'mainnet-v1' + return ['mainnet-v1.0', 'mainnet-v1', 'mainnet'].includes(params.genesisID) } export { isLocalNet } from './localnet' diff --git a/src/transfer.spec.ts b/src/transfer.spec.ts index 7b204416..e476d4ea 100644 --- a/src/transfer.spec.ts +++ b/src/transfer.spec.ts @@ -1,13 +1,25 @@ import { describe, test } from '@jest/globals' import algosdk, { TransactionType } from 'algosdk' +import fetchMock, { enableFetchMocks } from 'jest-fetch-mock' import invariant from 'tiny-invariant' import * as algokit from './' import { algorandFixture } from './testing' import { ensureFundsAndOptIn, generateTestAsset, optIn } from './testing/asset' +enableFetchMocks() describe('transfer', () => { const localnet = algorandFixture() - beforeEach(localnet.beforeEach, 10_000) + const env = process.env + + beforeEach(async () => { + jest.resetModules() + process.env = { ...env } + await localnet.beforeEach() + }, 10_000) + + afterEach(() => { + process.env = env + }) test('Transfer Algo is sent and waited for', async () => { const { algod, testAccount } = localnet.context @@ -24,6 +36,7 @@ describe('transfer', () => { ) const accountInfo = await algod.accountInformation(secondAccount.addr).do() + expect(transaction).toBeInstanceOf(algosdk.Transaction) expect(transaction.type).toBe(TransactionType.pay) expect(confirmation?.txn.txn.type).toBe('pay') @@ -218,21 +231,20 @@ describe('transfer', () => { const accountInfo = await algod.accountInformation(secondAccount.addr).do() invariant(result) - const { transaction, confirmation } = result - expect(transaction.type).toBe(TransactionType.pay) - expect(confirmation?.txn.txn.type).toBe('pay') + expect(result.transactionId).toBeDefined() + expect(result.amount).toBeDefined() + const txnInfo = await algod.pendingTransactionInformation(result.transactionId).do() + + expect(txnInfo.txn.txn.type).toBe('pay') - expect(transaction.amount).toBe(100_001) - expect(confirmation?.txn.txn.amt).toBe(100_001) + expect(result.amount).toBe(100_001) + expect(txnInfo.txn.txn.amt).toBe(100_001) expect(accountInfo['amount']).toBe(100_001) - expect(algosdk.encodeAddress(transaction.from.publicKey)).toBe(testAccount.addr) // eslint-disable-next-line @typescript-eslint/no-non-null-assertion - expect(algosdk.encodeAddress(confirmation!.txn.txn.snd)).toBe(testAccount.addr) - - expect(algosdk.encodeAddress(transaction.to.publicKey)).toBe(secondAccount.addr) + expect(algosdk.encodeAddress(txnInfo.txn.txn.snd)).toBe(testAccount.addr) // eslint-disable-next-line @typescript-eslint/no-non-null-assertion - expect(algosdk.encodeAddress(confirmation!.txn.txn.rcv!)).toBe(secondAccount.addr) + expect(algosdk.encodeAddress(txnInfo.txn.txn.rcv!)).toBe(secondAccount.addr) }) test('ensureFunded respects minimum funding increment', async () => { @@ -270,9 +282,59 @@ describe('transfer', () => { ) invariant(result) - const { transaction } = result - expect(algosdk.encodeAddress(transaction.from.publicKey)).toBe(dispenser.addr) + const txnInfo = await algod.pendingTransactionInformation(result.transactionId).do() + const resultReceiver = algosdk.encodeAddress(txnInfo.txn.txn.snd) + expect(resultReceiver).toBe(dispenser.addr) const accountInfo = await algod.accountInformation(secondAccount.addr).do() expect(accountInfo['amount']).toBe(1_000_000) }) + + test('ensureFunded uses dispenser api with access token sucessfully', async () => { + process.env.ALGOKIT_DISPENSER_ACCESS_TOKEN = 'dummy_token' + + // Mock the fetch response + fetchMock.mockResponseOnce(JSON.stringify({ txID: 'dummy_tx_id', amount: 200_000 })) + + const algodClient = algokit.getAlgoClient(algokit.getAlgoNodeConfig('testnet', 'algod')) + const dispenserClient = algokit.getDispenserApiTestnetClient() + + const accountToFund = algosdk.generateAccount() + + const result = await algokit.ensureFunded( + { + accountToFund: accountToFund, + minSpendingBalance: algokit.algos(100), + minFundingIncrement: algokit.algos(0.1), + fundingSource: dispenserClient, + }, + algodClient, + ) + + invariant(result) + expect(result.transactionId).toBeDefined() + expect(result.amount).toBe(200_000) + }) + + test('ensureFunded uses dispenser api and fails with rejected response', async () => { + process.env.ALGOKIT_DISPENSER_ACCESS_TOKEN = 'dummy_token' + + // Mock the fetch response + fetchMock.mockRejectOnce(new Error('dummy_error')) + + const algodClient = algokit.getAlgoClient(algokit.getAlgoNodeConfig('testnet', 'algod')) + const dispenserClient = algokit.getDispenserApiTestnetClient() + const accountToFund = algosdk.generateAccount() + + await expect( + algokit.ensureFunded( + { + accountToFund: accountToFund, + minSpendingBalance: algokit.algos(100), + minFundingIncrement: algokit.algos(1), + fundingSource: dispenserClient, + }, + algodClient, + ), + ).rejects.toThrowErrorMatchingInlineSnapshot('"dummy_error"') + }) }) diff --git a/src/transfer.ts b/src/transfer.ts index 497e63b4..58561a4f 100644 --- a/src/transfer.ts +++ b/src/transfer.ts @@ -1,8 +1,72 @@ import algosdk, { Algodv2, Kmd } from 'algosdk' import { Config, getDispenserAccount, microAlgos } from './' +import { isTestNet } from './network-client' import { encodeTransactionNote, getSenderAddress, getTransactionParams, sendTransaction } from './transaction' -import { SendTransactionResult } from './types/transaction' -import { AlgoTransferParams, EnsureFundedParams, TransferAssetParams } from './types/transfer' +import { AlgoAmount } from './types/amount' +import { DispenserApiTestnetClient } from './types/dispenser-client' +import { SendTransactionResult, TransactionNote } from './types/transaction' +import { AlgoTransferParams, EnsureFundedParams, EnsureFundedReturnType, TransferAssetParams } from './types/transfer' +import { calculateFundAmount } from './util' + +async function fundUsingDispenserApi( + dispenserClient: DispenserApiTestnetClient, + addressToFund: string, + fundAmount: number, +): Promise { + const response = await dispenserClient.fund(addressToFund, fundAmount) + return { transactionId: response.txId, amount: response.amount } +} + +async function fundUsingTransfer({ + algod, + addressToFund, + funding, + fundAmount, + transactionParams, + sendParams, + note, + kmd, +}: { + algod: Algodv2 + addressToFund: string + funding: EnsureFundedParams + fundAmount: number + transactionParams: algosdk.SuggestedParams | undefined + sendParams: { + skipSending?: boolean | undefined + skipWaiting?: boolean | undefined + atc?: algosdk.AtomicTransactionComposer | undefined + suppressLog?: boolean | undefined + fee?: AlgoAmount | undefined + maxFee?: AlgoAmount | undefined + maxRoundsToWaitForConfirmation?: number | undefined + } + note: TransactionNote + kmd?: Kmd +}): Promise { + if (funding.fundingSource instanceof DispenserApiTestnetClient) { + throw new Error('Dispenser API client is not supported in this context.') + } + + const from = funding.fundingSource ?? (await getDispenserAccount(algod, kmd)) + const amount = microAlgos(Math.max(fundAmount, funding.minFundingIncrement?.microAlgos ?? 0)) + const response = await transferAlgos( + { + from, + to: addressToFund, + note: note ?? 'Funding account to meet minimum requirement', + amount: amount, + transactionParams: transactionParams, + ...sendParams, + }, + algod, + ) + + return { + transactionId: response.transaction.txID(), + amount: Number(response.transaction.amount), + } +} /** * Transfer ALGOs between two accounts. @@ -40,12 +104,18 @@ export async function transferAlgos(transfer: AlgoTransferParams, algod: Algodv2 * * https://developer.algorand.org/docs/get-details/accounts/#minimum-balance * - * @param funding The funding configuration - * @param algod An algod client - * @param kmd An optional kmd client - * @returns undefined if nothing was needed or the transaction send result + * @param funding The funding configuration of type `EnsureFundedParams`, including the account to fund, minimum spending balance, and optional parameters. If you set `useDispenserApi` to true, you must also set `ALGOKIT_DISPENSER_ACCESS_TOKEN` in your environment variables. + * @param algod An instance of the Algodv2 client. + * @param kmd An optional instance of the Kmd client. + * @returns + * - `EnsureFundedReturnType` if funds were transferred. + * - `undefined` if no funds were needed. */ -export async function ensureFunded(funding: EnsureFundedParams, algod: Algodv2, kmd?: Kmd): Promise { +export async function ensureFunded( + funding: T, + algod: Algodv2, + kmd?: Kmd, +): Promise { const { accountToFund, fundingSource, minSpendingBalance, minFundingIncrement, transactionParams, note, ...sendParams } = funding const addressToFund = typeof accountToFund === 'string' ? accountToFund : getSenderAddress(accountToFund) @@ -55,26 +125,27 @@ export async function ensureFunded(funding: EnsureFundedParams, algod: Algodv2, const minimumBalanceRequirement = microAlgos(Number(accountInfo['min-balance'])) const currentSpendingBalance = microAlgos(balance - minimumBalanceRequirement.microAlgos) - if (minSpendingBalance > currentSpendingBalance) { - const from = fundingSource ?? (await getDispenserAccount(algod, kmd)) - const minFundAmount = microAlgos(minSpendingBalance.microAlgos - currentSpendingBalance.microAlgos) - const fundAmount = microAlgos(Math.max(minFundAmount.microAlgos, minFundingIncrement?.microAlgos ?? 0)) - Config.getLogger(sendParams.suppressLog).info( - `Funding ${addressToFund} ${fundAmount} from ${getSenderAddress( - from, - )} to reach minimum spend amount of ${minSpendingBalance} (balance = ${balance}, min_balance_req = ${minimumBalanceRequirement})`, - ) - return await transferAlgos( - { - from, - to: addressToFund, - note: note ?? 'Funding account to meet minimum requirement', - amount: fundAmount, + const fundAmount = calculateFundAmount( + minSpendingBalance.microAlgos, + currentSpendingBalance.microAlgos, + minFundingIncrement?.microAlgos ?? 0, + ) + + if (fundAmount !== null) { + if ((await isTestNet(algod)) && fundingSource instanceof DispenserApiTestnetClient) { + return fundUsingDispenserApi(fundingSource, addressToFund, fundAmount) as Promise + } else { + return fundUsingTransfer({ + algod, + addressToFund, + funding, + fundAmount, transactionParams, - ...sendParams, - }, - algod, - ) + sendParams, + note, + kmd, + }) as Promise + } } return undefined diff --git a/src/types/dispenser-client.spec.ts b/src/types/dispenser-client.spec.ts new file mode 100644 index 00000000..b625adfe --- /dev/null +++ b/src/types/dispenser-client.spec.ts @@ -0,0 +1,70 @@ +import fetchMock, { enableFetchMocks } from 'jest-fetch-mock' +import { DispenserApiTestnetClient } from './dispenser-client' +enableFetchMocks() + +describe('DispenserApiTestnetClient', () => { + const env = process.env + + beforeEach(async () => { + jest.resetModules() + process.env = { ...env } + }) + + afterEach(() => { + process.env = env + }) + it('should fund account with algos with auth token', async () => { + const mockResponse = { txID: 'dummy_tx_id', amount: 1 } + fetchMock.mockResponseOnce(JSON.stringify(mockResponse)) + + const dispenserClient = new DispenserApiTestnetClient({ authToken: 'dummy_auth_token', requestTimeout: null }) + const address = 'dummy_address' + const amount = 1 + + const response = await dispenserClient.fund(address, amount) + expect(response.txId).toEqual('dummy_tx_id') + expect(response.amount).toEqual(1) + }) + + it('should register refund with auth token', async () => { + fetchMock.mockResponseOnce(JSON.stringify({})) + + const dispenserClient = new DispenserApiTestnetClient({ authToken: 'dummy_auth_token', requestTimeout: null }) + const refundTxnId = 'dummy_txn_id' + + await dispenserClient.refund(refundTxnId) + expect(fetchMock.mock.calls.length).toEqual(1) + const methodArgs = fetchMock.mock.calls[0] + expect(methodArgs.length).toEqual(2) + const requestParams = methodArgs[1] + expect(requestParams?.method).toEqual('POST') + expect((requestParams?.headers as Record)?.Authorization).toEqual(`Bearer ${dispenserClient.authToken}`) + expect(JSON.parse(requestParams?.body as string)).toEqual({ refundTransactionID: refundTxnId }) + }) + + it('should get limit with auth token', async () => { + const amount = 10000000 + const mockResponse = { amount: amount } + fetchMock.mockResponseOnce(JSON.stringify(mockResponse)) + + const dispenserClient = new DispenserApiTestnetClient({ authToken: 'dummy_auth_token', requestTimeout: null }) + const response = await dispenserClient.getLimit() + expect(response.amount).toEqual(amount) + }) + + it('should throw error when no auth token provided', () => { + expect(() => new DispenserApiTestnetClient(null)).toThrow() + }) + + it('should init with environment variable', () => { + process.env.ALGOKIT_DISPENSER_ACCESS_TOKEN = 'dummy_token' + const client = new DispenserApiTestnetClient(null) + expect(client.authToken).toEqual('dummy_token') + }) + + it('should init with argument over environment variable', () => { + process.env.ALGOKIT_DISPENSER_ACCESS_TOKEN = 'dummy_token' + const client = new DispenserApiTestnetClient({ authToken: 'test_value_2', requestTimeout: null }) + expect(client.authToken).toEqual('test_value_2') + }) +}) diff --git a/src/types/dispenser-client.ts b/src/types/dispenser-client.ts new file mode 100644 index 00000000..d95bb11c --- /dev/null +++ b/src/types/dispenser-client.ts @@ -0,0 +1,179 @@ +const baseUrl = 'https://api.dispenser.algorandfoundation.tools' +const dispenserRequestTimeout = 15 +const dispenserAccessTokenKey = 'ALGOKIT_DISPENSER_ACCESS_TOKEN' + +enum DispenserAssetName { + Algo = 0, +} + +const dispenserAssets = { + [DispenserAssetName.Algo]: { + assetId: 0, + decimals: 6, + description: 'Algo', + }, +} + +export interface DispenserFundResponse { + txId: string + amount: number +} + +export interface DispenserLimitResponse { + amount: number +} + +export interface DispenserApiTestnetClientParams { + authToken: string + requestTimeout: number | null +} + +/** + * `DispenserApiTestnetClient` is a class that provides methods to interact with the [Algorand TestNet Dispenser API](https://github.com/algorandfoundation/algokit/blob/main/docs/testnet_api.md). + * It allows you to fund an address with Algos, refund a transaction, and get the funding limit for the Algo asset. + * + * The class requires an authentication token and a request timeout to be initialized. The authentication token can be provided + * either directly as a parameter or through an `ALGOKIT_DISPENSER_ACCESS_TOKEN` environment variable. If neither is provided, an error is thrown. + * + * The request timeout can be provided as a parameter. If not provided, a default value is used. + * + * @property {string} authToken - The authentication token used for API requests. + * @property {number} requestTimeout - The timeout for API requests, in seconds. + * + * @method fund - Sends a funding request to the dispenser API to fund the specified address with the given amount of Algo. + * @method refund - Sends a refund request to the dispenser API for the specified refundTxnId. + * @method limit - Sends a request to the dispenser API to get the funding limit for the Algo asset. + * + * @example + * ```typescript + * const client = new DispenserApiTestnetClient({ authToken: 'your_auth_token', requestTimeout: 30 }); + * const fundResponse = await client.fund('your_address', 100); + * const limitResponse = await client.getLimit(); + * await client.refund('your_transaction_id'); + * ``` + * + * @throws {Error} If neither the environment variable 'ALGOKIT_DISPENSER_ACCESS_TOKEN' nor the authToken parameter were provided. + */ +export class DispenserApiTestnetClient { + private _authToken: string + private _requestTimeout: number + + constructor(params: DispenserApiTestnetClientParams | null) { + const authTokenFromEnv = process.env[dispenserAccessTokenKey] + + if (params?.authToken) { + this._authToken = params.authToken + } else if (authTokenFromEnv) { + this._authToken = authTokenFromEnv + } else { + throw new Error( + `Can't init AlgoKit TestNet Dispenser API client because neither environment variable ${dispenserAccessTokenKey} or the authToken were provided.`, + ) + } + + this._requestTimeout = params?.requestTimeout || dispenserRequestTimeout + } + + get authToken(): string { + return this._authToken + } + + get requestTimeout(): number { + return this._requestTimeout + } + + /** + * Processes a dispenser API request. + * + * @param authToken - The authentication token. + * @param urlSuffix - The URL suffix for the API request. + * @param body - The request body. + * @param method - The HTTP method. + * + * @returns The API response. + */ + private async processDispenserRequest( + authToken: string, + urlSuffix: string, + body: Record | null = null, + method = 'POST', + ): Promise { + const headers = { Authorization: `Bearer ${authToken}` } + + const requestArgs: RequestInit = { + method: method, + headers: headers, + signal: AbortSignal.timeout(this.requestTimeout * 1000), + } + + if (body) { + requestArgs.body = JSON.stringify(body) + } + + const response = await fetch(`${baseUrl}/${urlSuffix}`, requestArgs) + if (!response.ok) { + let error_message = `Error processing dispenser API request: ${response.status}` + let error_response = null + try { + error_response = await response.json() + } catch (err) { + // suppress exception + } + + if (error_response && error_response.code) { + error_message = error_response.code + } else if (response.status === 400) { + error_message = (await response.json()).message + } + + throw new Error(error_message) + } + return response + } + + /** + * Sends a funding request to the dispenser API to fund the specified address with the given amount of Algo. + * + * @param address - The address to fund. + * @param amount - The amount of Algo to fund. + * + * @returns DispenserFundResponse: An object containing the transaction ID and funded amount. + */ + async fund(address: string, amount: number): Promise { + const response = await this.processDispenserRequest( + this.authToken, + `fund/${dispenserAssets[DispenserAssetName.Algo].assetId}`, + { receiver: address, amount: amount, assetID: dispenserAssets[DispenserAssetName.Algo].assetId }, + 'POST', + ) + + const content = await response.json() + return { txId: content.txID, amount: content.amount } + } + + /** + * Sends a refund request to the dispenser API for the specified refundTxnId. + * + * @param refundTxnId - The transaction ID to refund. + */ + async refund(refundTxnId: string): Promise { + await this.processDispenserRequest(this.authToken, 'refund', { refundTransactionID: refundTxnId }, 'POST') + } + + /** + * Sends a request to the dispenser API to get the funding limit for the Algo asset. + * + * @returns DispenserLimitResponse: An object containing the funding limit amount. + */ + async getLimit(): Promise { + const response = await this.processDispenserRequest( + this.authToken, + `fund/${dispenserAssets[DispenserAssetName.Algo].assetId}/limit`, + null, + 'GET', + ) + const content = await response.json() + + return { amount: content.amount } + } +} diff --git a/src/types/transfer.ts b/src/types/transfer.ts index 470344bd..633d5311 100644 --- a/src/types/transfer.ts +++ b/src/types/transfer.ts @@ -1,5 +1,6 @@ import { SuggestedParams } from 'algosdk' import { AlgoAmount } from './amount' +import { DispenserApiTestnetClient } from './dispenser-client' import { SendTransactionFrom, SendTransactionParams, TransactionNote } from './transaction' /** Parameters for `transferAlgos` call. */ @@ -21,7 +22,7 @@ export interface EnsureFundedParams extends SendTransactionParams { /** The account to fund */ accountToFund: SendTransactionFrom | string /** The account to use as a funding source, will default to using the dispenser account returned by `algokit.getDispenserAccount` */ - fundingSource?: SendTransactionFrom + fundingSource?: SendTransactionFrom | DispenserApiTestnetClient /** The minimum balance of ALGOs that the account should have available to spend (i.e. on top of minimum balance requirement) */ minSpendingBalance: AlgoAmount /** When issuing a funding amount, the minimum amount to transfer (avoids many small transfers if this gets called often on an active account) */ @@ -49,3 +50,10 @@ export interface TransferAssetParams extends SendTransactionParams { /** The (optional) transaction note */ note?: TransactionNote } + +export interface EnsureFundedReturnType { + /** The transaction */ + transactionId: string + /** The response if the transaction was sent and waited for */ + amount: number +} diff --git a/src/util.ts b/src/util.ts index 86cf75ae..752f8dd5 100644 --- a/src/util.ts +++ b/src/util.ts @@ -20,3 +20,23 @@ export const toNumber = (value: number | bigint) => { } export class UnsafeConversionError extends Error {} + +/** + * Calculates the amount of funds to add to a wallet to bring it up to the minimum spending balance. + * @param minSpendingBalance The minimum spending balance for the wallet + * @param currentSpendingBalance The current spending balance for the wallet + * @param minFundingIncrement The minimum amount of funds that can be added to the wallet + * @returns The amount of funds to add to the wallet or null if the wallet is already above the minimum spending balance + */ +export const calculateFundAmount = ( + minSpendingBalance: number, + currentSpendingBalance: number, + minFundingIncrement: number, +): number | null => { + if (minSpendingBalance > currentSpendingBalance) { + const minFundAmount = minSpendingBalance - currentSpendingBalance + return Math.max(minFundAmount, minFundingIncrement) + } else { + return null + } +} From 03d3212a5ae51dcf62e5e893fe2c89cc40d100df Mon Sep 17 00:00:00 2001 From: Al Date: Thu, 5 Oct 2023 18:14:06 +0200 Subject: [PATCH 2/2] chore: renaming class (#145) * chore: renaming class * chore: fixing tests --- docs/capabilities/dispenser-client.md | 4 +- docs/capabilities/transfer.md | 4 +- ...enser_client.TestNetDispenserApiClient.md} | 50 ++++++------- ..._client.DispenserApiTestnetClientParams.md | 32 -------- ..._dispenser_client.DispenserFundResponse.md | 4 +- ...dispenser_client.DispenserLimitResponse.md | 2 +- ..._client.TestNetDispenserApiClientParams.md | 32 ++++++++ .../types_transfer.EnsureFundedParams.md | 2 +- docs/code/modules/index.md | 74 +++++++++---------- docs/code/modules/types_dispenser_client.md | 4 +- src/dispenser-client.ts | 14 ++-- src/transfer.spec.ts | 22 +++--- src/transfer.ts | 8 +- src/types/dispenser-client.spec.ts | 39 ++++++---- src/types/dispenser-client.ts | 12 +-- src/types/transfer.ts | 4 +- tests/setup.ts | 6 -- 17 files changed, 161 insertions(+), 152 deletions(-) rename docs/code/classes/{types_dispenser_client.DispenserApiTestnetClient.md => types_dispenser_client.TestNetDispenserApiClient.md} (74%) delete mode 100644 docs/code/interfaces/types_dispenser_client.DispenserApiTestnetClientParams.md create mode 100644 docs/code/interfaces/types_dispenser_client.TestNetDispenserApiClientParams.md diff --git a/docs/capabilities/dispenser-client.md b/docs/capabilities/dispenser-client.md index 594d3ff8..07c60882 100644 --- a/docs/capabilities/dispenser-client.md +++ b/docs/capabilities/dispenser-client.md @@ -15,11 +15,11 @@ If both methods are used, the constructor argument takes precedence. import * as algokit from '@algorandfoundation/algokit-utils' // Using constructor argument -const client = algokit.getDispenserApiTestnetClient({ authToken: 'your_auth_token' }) +const client = algokit.getTestNetDispenserApiClient({ authToken: 'your_auth_token' }) // Using environment variable process.env['ALGOKIT_DISPENSER_ACCESS_TOKEN'] = 'your_auth_token' -const client = algokit.getDispenserApiTestnetClient() +const client = algokit.getTestNetDispenserApiClient() ``` ## Funding an Account diff --git a/docs/capabilities/transfer.md b/docs/capabilities/transfer.md index 0cc433b4..bd3be8fc 100644 --- a/docs/capabilities/transfer.md +++ b/docs/capabilities/transfer.md @@ -21,13 +21,13 @@ The `ensureFunded` function automatically funds an account to maintain a minimum - All properties in [`SendTransactionParams`](./transaction.md#sendtransactionparams) - `accountToFund: SendTransactionFrom | string` - The account that is to be funded -- `fundingSource?: SendTransactionFrom | DispenserApiTestnetClient` - The account that is the source of funds or a dispenser API client. If not specified, it will use the [dispenser](./account.md#dispenser) +- `fundingSource?: SendTransactionFrom | TestNetDispenserApiClient` - The account that is the source of funds or a dispenser API client. If not specified, it will use the [dispenser](./account.md#dispenser) - `minSpendingBalance: AlgoAmount` - The minimum balance of ALGOs that the account should have available to spend (i.e., on top of the minimum balance requirement) - `minFundingIncrement?: AlgoAmount` - When issuing a funding amount, the minimum amount to transfer. This avoids many small transfers if this function gets called often on an active account - `transactionParams?: SuggestedParams` - The optional [transaction parameters](./transaction.md#transaction-params) - `note?: TransactionNote` - The [transaction note](./transaction.md#transaction-notes) -The function calls Algod to find the current balance and minimum balance requirement, calculates the difference between those two numbers, and checks to see if it's more than the `minSpendingBalance`. If so, it will send the difference, or the `minFundingIncrement` if that is specified. If the `fundingSource` is an instance of `DispenserApiTestnetClient`, the function will use the dispenser API to fund the account. Refer to [algokit-cli documentation](https://github.com/algorandfoundation/algokit-cli/blob/main/docs/features/dispenser.md#ci-access-token) for details on obtaining an access token for AlgoKit TestNet Dispenser API. +The function calls Algod to find the current balance and minimum balance requirement, calculates the difference between those two numbers, and checks to see if it's more than the `minSpendingBalance`. If so, it will send the difference, or the `minFundingIncrement` if that is specified. If the `fundingSource` is an instance of `TestNetDispenserApiClient`, the function will use the dispenser API to fund the account. Refer to [algokit-cli documentation](https://github.com/algorandfoundation/algokit-cli/blob/main/docs/features/dispenser.md#ci-access-token) for details on obtaining an access token for AlgoKit TestNet Dispenser API. ## `transferAsset` diff --git a/docs/code/classes/types_dispenser_client.DispenserApiTestnetClient.md b/docs/code/classes/types_dispenser_client.TestNetDispenserApiClient.md similarity index 74% rename from docs/code/classes/types_dispenser_client.DispenserApiTestnetClient.md rename to docs/code/classes/types_dispenser_client.TestNetDispenserApiClient.md index f81fd841..fc053f5d 100644 --- a/docs/code/classes/types_dispenser_client.DispenserApiTestnetClient.md +++ b/docs/code/classes/types_dispenser_client.TestNetDispenserApiClient.md @@ -1,10 +1,10 @@ -[@algorandfoundation/algokit-utils](../README.md) / [types/dispenser-client](../modules/types_dispenser_client.md) / DispenserApiTestnetClient +[@algorandfoundation/algokit-utils](../README.md) / [types/dispenser-client](../modules/types_dispenser_client.md) / TestNetDispenserApiClient -# Class: DispenserApiTestnetClient +# Class: TestNetDispenserApiClient -[types/dispenser-client](../modules/types_dispenser_client.md).DispenserApiTestnetClient +[types/dispenser-client](../modules/types_dispenser_client.md).TestNetDispenserApiClient -`DispenserApiTestnetClient` is a class that provides methods to interact with the [Algorand TestNet Dispenser API](https://github.com/algorandfoundation/algokit/blob/main/docs/testnet_api.md). +`TestNetDispenserApiClient` is a class that provides methods to interact with the [Algorand TestNet Dispenser API](https://github.com/algorandfoundation/algokit/blob/main/docs/testnet_api.md). It allows you to fund an address with Algos, refund a transaction, and get the funding limit for the Algo asset. The class requires an authentication token and a request timeout to be initialized. The authentication token can be provided @@ -27,7 +27,7 @@ limit - Sends a request to the dispenser API to get the funding limit for the Al **`Example`** ```typescript -const client = new DispenserApiTestnetClient({ authToken: 'your_auth_token', requestTimeout: 30 }); +const client = new TestNetDispenserApiClient({ authToken: 'your_auth_token', requestTimeout: 30 }); const fundResponse = await client.fund('your_address', 100); const limitResponse = await client.getLimit(); await client.refund('your_transaction_id'); @@ -41,40 +41,40 @@ If neither the environment variable 'ALGOKIT_DISPENSER_ACCESS_TOKEN' nor the aut ### Constructors -- [constructor](types_dispenser_client.DispenserApiTestnetClient.md#constructor) +- [constructor](types_dispenser_client.TestNetDispenserApiClient.md#constructor) ### Properties -- [\_authToken](types_dispenser_client.DispenserApiTestnetClient.md#_authtoken) -- [\_requestTimeout](types_dispenser_client.DispenserApiTestnetClient.md#_requesttimeout) +- [\_authToken](types_dispenser_client.TestNetDispenserApiClient.md#_authtoken) +- [\_requestTimeout](types_dispenser_client.TestNetDispenserApiClient.md#_requesttimeout) ### Accessors -- [authToken](types_dispenser_client.DispenserApiTestnetClient.md#authtoken) -- [requestTimeout](types_dispenser_client.DispenserApiTestnetClient.md#requesttimeout) +- [authToken](types_dispenser_client.TestNetDispenserApiClient.md#authtoken) +- [requestTimeout](types_dispenser_client.TestNetDispenserApiClient.md#requesttimeout) ### Methods -- [fund](types_dispenser_client.DispenserApiTestnetClient.md#fund) -- [getLimit](types_dispenser_client.DispenserApiTestnetClient.md#getlimit) -- [processDispenserRequest](types_dispenser_client.DispenserApiTestnetClient.md#processdispenserrequest) -- [refund](types_dispenser_client.DispenserApiTestnetClient.md#refund) +- [fund](types_dispenser_client.TestNetDispenserApiClient.md#fund) +- [getLimit](types_dispenser_client.TestNetDispenserApiClient.md#getlimit) +- [processDispenserRequest](types_dispenser_client.TestNetDispenserApiClient.md#processdispenserrequest) +- [refund](types_dispenser_client.TestNetDispenserApiClient.md#refund) ## Constructors ### constructor -• **new DispenserApiTestnetClient**(`params`) +• **new TestNetDispenserApiClient**(`params`) #### Parameters | Name | Type | | :------ | :------ | -| `params` | ``null`` \| [`DispenserApiTestnetClientParams`](../interfaces/types_dispenser_client.DispenserApiTestnetClientParams.md) | +| `params` | ``null`` \| [`TestNetDispenserApiClientParams`](../interfaces/types_dispenser_client.TestNetDispenserApiClientParams.md) | #### Defined in -[src/types/dispenser-client.ts:61](https://github.com/algorandfoundation/algokit-utils-ts/blob/main/src/types/dispenser-client.ts#L61) +[src/types/dispenser-client.ts:63](https://github.com/algorandfoundation/algokit-utils-ts/blob/main/src/types/dispenser-client.ts#L63) ## Properties @@ -84,7 +84,7 @@ If neither the environment variable 'ALGOKIT_DISPENSER_ACCESS_TOKEN' nor the aut #### Defined in -[src/types/dispenser-client.ts:58](https://github.com/algorandfoundation/algokit-utils-ts/blob/main/src/types/dispenser-client.ts#L58) +[src/types/dispenser-client.ts:60](https://github.com/algorandfoundation/algokit-utils-ts/blob/main/src/types/dispenser-client.ts#L60) ___ @@ -94,7 +94,7 @@ ___ #### Defined in -[src/types/dispenser-client.ts:59](https://github.com/algorandfoundation/algokit-utils-ts/blob/main/src/types/dispenser-client.ts#L59) +[src/types/dispenser-client.ts:61](https://github.com/algorandfoundation/algokit-utils-ts/blob/main/src/types/dispenser-client.ts#L61) ## Accessors @@ -110,7 +110,7 @@ The authentication token used for API requests. #### Defined in -[src/types/dispenser-client.ts:77](https://github.com/algorandfoundation/algokit-utils-ts/blob/main/src/types/dispenser-client.ts#L77) +[src/types/dispenser-client.ts:79](https://github.com/algorandfoundation/algokit-utils-ts/blob/main/src/types/dispenser-client.ts#L79) ___ @@ -126,7 +126,7 @@ The timeout for API requests, in seconds. #### Defined in -[src/types/dispenser-client.ts:81](https://github.com/algorandfoundation/algokit-utils-ts/blob/main/src/types/dispenser-client.ts#L81) +[src/types/dispenser-client.ts:83](https://github.com/algorandfoundation/algokit-utils-ts/blob/main/src/types/dispenser-client.ts#L83) ## Methods @@ -151,7 +151,7 @@ DispenserFundResponse: An object containing the transaction ID and funded amount #### Defined in -[src/types/dispenser-client.ts:142](https://github.com/algorandfoundation/algokit-utils-ts/blob/main/src/types/dispenser-client.ts#L142) +[src/types/dispenser-client.ts:144](https://github.com/algorandfoundation/algokit-utils-ts/blob/main/src/types/dispenser-client.ts#L144) ___ @@ -169,7 +169,7 @@ DispenserLimitResponse: An object containing the funding limit amount. #### Defined in -[src/types/dispenser-client.ts:168](https://github.com/algorandfoundation/algokit-utils-ts/blob/main/src/types/dispenser-client.ts#L168) +[src/types/dispenser-client.ts:170](https://github.com/algorandfoundation/algokit-utils-ts/blob/main/src/types/dispenser-client.ts#L170) ___ @@ -196,7 +196,7 @@ The API response. #### Defined in -[src/types/dispenser-client.ts:95](https://github.com/algorandfoundation/algokit-utils-ts/blob/main/src/types/dispenser-client.ts#L95) +[src/types/dispenser-client.ts:97](https://github.com/algorandfoundation/algokit-utils-ts/blob/main/src/types/dispenser-client.ts#L97) ___ @@ -218,4 +218,4 @@ Sends a refund request to the dispenser API for the specified refundTxnId. #### Defined in -[src/types/dispenser-client.ts:159](https://github.com/algorandfoundation/algokit-utils-ts/blob/main/src/types/dispenser-client.ts#L159) +[src/types/dispenser-client.ts:161](https://github.com/algorandfoundation/algokit-utils-ts/blob/main/src/types/dispenser-client.ts#L161) diff --git a/docs/code/interfaces/types_dispenser_client.DispenserApiTestnetClientParams.md b/docs/code/interfaces/types_dispenser_client.DispenserApiTestnetClientParams.md deleted file mode 100644 index 68ce405b..00000000 --- a/docs/code/interfaces/types_dispenser_client.DispenserApiTestnetClientParams.md +++ /dev/null @@ -1,32 +0,0 @@ -[@algorandfoundation/algokit-utils](../README.md) / [types/dispenser-client](../modules/types_dispenser_client.md) / DispenserApiTestnetClientParams - -# Interface: DispenserApiTestnetClientParams - -[types/dispenser-client](../modules/types_dispenser_client.md).DispenserApiTestnetClientParams - -## Table of contents - -### Properties - -- [authToken](types_dispenser_client.DispenserApiTestnetClientParams.md#authtoken) -- [requestTimeout](types_dispenser_client.DispenserApiTestnetClientParams.md#requesttimeout) - -## Properties - -### authToken - -• **authToken**: `string` - -#### Defined in - -[src/types/dispenser-client.ts:27](https://github.com/algorandfoundation/algokit-utils-ts/blob/main/src/types/dispenser-client.ts#L27) - -___ - -### requestTimeout - -• **requestTimeout**: ``null`` \| `number` - -#### Defined in - -[src/types/dispenser-client.ts:28](https://github.com/algorandfoundation/algokit-utils-ts/blob/main/src/types/dispenser-client.ts#L28) diff --git a/docs/code/interfaces/types_dispenser_client.DispenserFundResponse.md b/docs/code/interfaces/types_dispenser_client.DispenserFundResponse.md index f85c8e6f..fac713db 100644 --- a/docs/code/interfaces/types_dispenser_client.DispenserFundResponse.md +++ b/docs/code/interfaces/types_dispenser_client.DispenserFundResponse.md @@ -19,7 +19,7 @@ #### Defined in -[src/types/dispenser-client.ts:19](https://github.com/algorandfoundation/algokit-utils-ts/blob/main/src/types/dispenser-client.ts#L19) +[src/types/dispenser-client.ts:21](https://github.com/algorandfoundation/algokit-utils-ts/blob/main/src/types/dispenser-client.ts#L21) ___ @@ -29,4 +29,4 @@ ___ #### Defined in -[src/types/dispenser-client.ts:18](https://github.com/algorandfoundation/algokit-utils-ts/blob/main/src/types/dispenser-client.ts#L18) +[src/types/dispenser-client.ts:20](https://github.com/algorandfoundation/algokit-utils-ts/blob/main/src/types/dispenser-client.ts#L20) diff --git a/docs/code/interfaces/types_dispenser_client.DispenserLimitResponse.md b/docs/code/interfaces/types_dispenser_client.DispenserLimitResponse.md index 6e286f73..5aaa95d9 100644 --- a/docs/code/interfaces/types_dispenser_client.DispenserLimitResponse.md +++ b/docs/code/interfaces/types_dispenser_client.DispenserLimitResponse.md @@ -18,4 +18,4 @@ #### Defined in -[src/types/dispenser-client.ts:23](https://github.com/algorandfoundation/algokit-utils-ts/blob/main/src/types/dispenser-client.ts#L23) +[src/types/dispenser-client.ts:25](https://github.com/algorandfoundation/algokit-utils-ts/blob/main/src/types/dispenser-client.ts#L25) diff --git a/docs/code/interfaces/types_dispenser_client.TestNetDispenserApiClientParams.md b/docs/code/interfaces/types_dispenser_client.TestNetDispenserApiClientParams.md new file mode 100644 index 00000000..de317c91 --- /dev/null +++ b/docs/code/interfaces/types_dispenser_client.TestNetDispenserApiClientParams.md @@ -0,0 +1,32 @@ +[@algorandfoundation/algokit-utils](../README.md) / [types/dispenser-client](../modules/types_dispenser_client.md) / TestNetDispenserApiClientParams + +# Interface: TestNetDispenserApiClientParams + +[types/dispenser-client](../modules/types_dispenser_client.md).TestNetDispenserApiClientParams + +## Table of contents + +### Properties + +- [authToken](types_dispenser_client.TestNetDispenserApiClientParams.md#authtoken) +- [requestTimeout](types_dispenser_client.TestNetDispenserApiClientParams.md#requesttimeout) + +## Properties + +### authToken + +• **authToken**: `string` + +#### Defined in + +[src/types/dispenser-client.ts:29](https://github.com/algorandfoundation/algokit-utils-ts/blob/main/src/types/dispenser-client.ts#L29) + +___ + +### requestTimeout + +• **requestTimeout**: ``null`` \| `number` + +#### Defined in + +[src/types/dispenser-client.ts:30](https://github.com/algorandfoundation/algokit-utils-ts/blob/main/src/types/dispenser-client.ts#L30) diff --git a/docs/code/interfaces/types_transfer.EnsureFundedParams.md b/docs/code/interfaces/types_transfer.EnsureFundedParams.md index ba3f4e95..581f877e 100644 --- a/docs/code/interfaces/types_transfer.EnsureFundedParams.md +++ b/docs/code/interfaces/types_transfer.EnsureFundedParams.md @@ -78,7 +78,7 @@ ___ ### fundingSource -• `Optional` **fundingSource**: [`SendTransactionFrom`](../modules/types_transaction.md#sendtransactionfrom) \| [`DispenserApiTestnetClient`](../classes/types_dispenser_client.DispenserApiTestnetClient.md) +• `Optional` **fundingSource**: [`SendTransactionFrom`](../modules/types_transaction.md#sendtransactionfrom) \| [`TestNetDispenserApiClient`](../classes/types_dispenser_client.TestNetDispenserApiClient.md) The account to use as a funding source, will default to using the dispenser account returned by `algokit.getDispenserAccount` diff --git a/docs/code/modules/index.md b/docs/code/modules/index.md index 73162f5a..f230c56b 100644 --- a/docs/code/modules/index.md +++ b/docs/code/modules/index.md @@ -53,13 +53,13 @@ - [getCreatorAppsByName](index.md#getcreatorappsbyname) - [getDefaultLocalNetConfig](index.md#getdefaultlocalnetconfig) - [getDispenserAccount](index.md#getdispenseraccount) -- [getDispenserApiTestnetClient](index.md#getdispenserapitestnetclient) - [getIndexerConfigFromEnvironment](index.md#getindexerconfigfromenvironment) - [getKmdWalletAccount](index.md#getkmdwalletaccount) - [getLocalNetDispenserAccount](index.md#getlocalnetdispenseraccount) - [getOrCreateKmdWalletAccount](index.md#getorcreatekmdwalletaccount) - [getSenderAddress](index.md#getsenderaddress) - [getSenderTransactionSigner](index.md#getsendertransactionsigner) +- [getTestNetDispenserApiClient](index.md#gettestnetdispenserapiclient) - [getTransactionParams](index.md#gettransactionparams) - [getTransactionWithSigner](index.md#gettransactionwithsigner) - [isLocalNet](index.md#islocalnet) @@ -1399,42 +1399,6 @@ If running on LocalNet then it will return the default dispenser account automat ___ -### getDispenserApiTestnetClient - -▸ **getDispenserApiTestnetClient**(`params?`): [`DispenserApiTestnetClient`](../classes/types_dispenser_client.DispenserApiTestnetClient.md) - -Create a new DispenserApiTestnetClient instance. -Refer to [docs](https://github.com/algorandfoundation/algokit/blob/main/docs/testnet_api.md) on guidance to obtain an access token. - -#### Parameters - -| Name | Type | Default value | Description | -| :------ | :------ | :------ | :------ | -| `params` | ``null`` \| [`DispenserApiTestnetClientParams`](../interfaces/types_dispenser_client.DispenserApiTestnetClientParams.md) | `null` | An object containing parameters for the DispenserApiTestnetClient class. Or null if you want the client to load the access token from the environment variable `ALGOKIT_DISPENSER_ACCESS_TOKEN`. | - -#### Returns - -[`DispenserApiTestnetClient`](../classes/types_dispenser_client.DispenserApiTestnetClient.md) - -An instance of the DispenserApiTestnetClient class. - -**`Example`** - -```ts -const client = algokit.getDispenserApiTestnetClient( - { - authToken: 'your_auth_token', - requestTimeout: 15, - } -) -``` - -#### Defined in - -[src/dispenser-client.ts:19](https://github.com/algorandfoundation/algokit-utils-ts/blob/main/src/dispenser-client.ts#L19) - -___ - ### getIndexerConfigFromEnvironment ▸ **getIndexerConfigFromEnvironment**(): [`AlgoClientConfig`](../interfaces/types_network_client.AlgoClientConfig.md) @@ -1594,6 +1558,42 @@ A transaction signer ___ +### getTestNetDispenserApiClient + +▸ **getTestNetDispenserApiClient**(`params?`): [`TestNetDispenserApiClient`](../classes/types_dispenser_client.TestNetDispenserApiClient.md) + +Create a new TestNetDispenserApiClient instance. +Refer to [docs](https://github.com/algorandfoundation/algokit/blob/main/docs/testnet_api.md) on guidance to obtain an access token. + +#### Parameters + +| Name | Type | Default value | Description | +| :------ | :------ | :------ | :------ | +| `params` | ``null`` \| [`TestNetDispenserApiClientParams`](../interfaces/types_dispenser_client.TestNetDispenserApiClientParams.md) | `null` | An object containing parameters for the TestNetDispenserApiClient class. Or null if you want the client to load the access token from the environment variable `ALGOKIT_DISPENSER_ACCESS_TOKEN`. | + +#### Returns + +[`TestNetDispenserApiClient`](../classes/types_dispenser_client.TestNetDispenserApiClient.md) + +An instance of the TestNetDispenserApiClient class. + +**`Example`** + +```ts +const client = algokit.getTestNetDispenserApiClient( + { + authToken: 'your_auth_token', + requestTimeout: 15, + } +) +``` + +#### Defined in + +[src/dispenser-client.ts:19](https://github.com/algorandfoundation/algokit-utils-ts/blob/main/src/dispenser-client.ts#L19) + +___ + ### getTransactionParams ▸ **getTransactionParams**(`params`, `algod`): `Promise`<`SuggestedParamsWithMinFee` \| { `fee`: `number` ; `firstRound`: `number` ; `flatFee?`: `boolean` ; `genesisHash`: `string` ; `genesisID`: `string` ; `lastRound`: `number` }\> diff --git a/docs/code/modules/types_dispenser_client.md b/docs/code/modules/types_dispenser_client.md index 2ae89639..014fde5a 100644 --- a/docs/code/modules/types_dispenser_client.md +++ b/docs/code/modules/types_dispenser_client.md @@ -6,10 +6,10 @@ ### Classes -- [DispenserApiTestnetClient](../classes/types_dispenser_client.DispenserApiTestnetClient.md) +- [TestNetDispenserApiClient](../classes/types_dispenser_client.TestNetDispenserApiClient.md) ### Interfaces -- [DispenserApiTestnetClientParams](../interfaces/types_dispenser_client.DispenserApiTestnetClientParams.md) - [DispenserFundResponse](../interfaces/types_dispenser_client.DispenserFundResponse.md) - [DispenserLimitResponse](../interfaces/types_dispenser_client.DispenserLimitResponse.md) +- [TestNetDispenserApiClientParams](../interfaces/types_dispenser_client.TestNetDispenserApiClientParams.md) diff --git a/src/dispenser-client.ts b/src/dispenser-client.ts index 87a2c1eb..ef227d16 100644 --- a/src/dispenser-client.ts +++ b/src/dispenser-client.ts @@ -1,21 +1,21 @@ -import { DispenserApiTestnetClient, DispenserApiTestnetClientParams } from './types/dispenser-client' +import { TestNetDispenserApiClient, TestNetDispenserApiClientParams } from './types/dispenser-client' /** - * Create a new DispenserApiTestnetClient instance. + * Create a new TestNetDispenserApiClient instance. * Refer to [docs](https://github.com/algorandfoundation/algokit/blob/main/docs/testnet_api.md) on guidance to obtain an access token. * - * @param params An object containing parameters for the DispenserApiTestnetClient class. + * @param params An object containing parameters for the TestNetDispenserApiClient class. * Or null if you want the client to load the access token from the environment variable `ALGOKIT_DISPENSER_ACCESS_TOKEN`. * @example - * const client = algokit.getDispenserApiTestnetClient( + * const client = algokit.getTestNetDispenserApiClient( * { * authToken: 'your_auth_token', * requestTimeout: 15, * } * ) * - * @returns An instance of the DispenserApiTestnetClient class. + * @returns An instance of the TestNetDispenserApiClient class. */ -export function getDispenserApiTestnetClient(params: DispenserApiTestnetClientParams | null = null) { - return new DispenserApiTestnetClient(params) +export function getTestNetDispenserApiClient(params: TestNetDispenserApiClientParams | null = null) { + return new TestNetDispenserApiClient(params) } diff --git a/src/transfer.spec.ts b/src/transfer.spec.ts index e476d4ea..93f69ef2 100644 --- a/src/transfer.spec.ts +++ b/src/transfer.spec.ts @@ -1,11 +1,9 @@ import { describe, test } from '@jest/globals' import algosdk, { TransactionType } from 'algosdk' -import fetchMock, { enableFetchMocks } from 'jest-fetch-mock' import invariant from 'tiny-invariant' import * as algokit from './' import { algorandFixture } from './testing' import { ensureFundsAndOptIn, generateTestAsset, optIn } from './testing/asset' -enableFetchMocks() describe('transfer', () => { const localnet = algorandFixture() @@ -292,11 +290,13 @@ describe('transfer', () => { test('ensureFunded uses dispenser api with access token sucessfully', async () => { process.env.ALGOKIT_DISPENSER_ACCESS_TOKEN = 'dummy_token' - // Mock the fetch response - fetchMock.mockResponseOnce(JSON.stringify({ txID: 'dummy_tx_id', amount: 200_000 })) - const algodClient = algokit.getAlgoClient(algokit.getAlgoNodeConfig('testnet', 'algod')) - const dispenserClient = algokit.getDispenserApiTestnetClient() + const dispenserClient = algokit.getTestNetDispenserApiClient() + Object.assign(dispenserClient, { + fund: jest.fn().mockImplementation(() => { + return Promise.resolve({ txId: 'dummy_tx_id', amount: 200_000 }) + }), + }) const accountToFund = algosdk.generateAccount() @@ -318,11 +318,13 @@ describe('transfer', () => { test('ensureFunded uses dispenser api and fails with rejected response', async () => { process.env.ALGOKIT_DISPENSER_ACCESS_TOKEN = 'dummy_token' - // Mock the fetch response - fetchMock.mockRejectOnce(new Error('dummy_error')) - const algodClient = algokit.getAlgoClient(algokit.getAlgoNodeConfig('testnet', 'algod')) - const dispenserClient = algokit.getDispenserApiTestnetClient() + const dispenserClient = algokit.getTestNetDispenserApiClient() + Object.assign(dispenserClient, { + fund: jest.fn().mockImplementation(() => { + return Promise.reject(new Error('dummy_error')) + }), + }) const accountToFund = algosdk.generateAccount() await expect( diff --git a/src/transfer.ts b/src/transfer.ts index 58561a4f..08b403df 100644 --- a/src/transfer.ts +++ b/src/transfer.ts @@ -3,13 +3,13 @@ import { Config, getDispenserAccount, microAlgos } from './' import { isTestNet } from './network-client' import { encodeTransactionNote, getSenderAddress, getTransactionParams, sendTransaction } from './transaction' import { AlgoAmount } from './types/amount' -import { DispenserApiTestnetClient } from './types/dispenser-client' +import { TestNetDispenserApiClient } from './types/dispenser-client' import { SendTransactionResult, TransactionNote } from './types/transaction' import { AlgoTransferParams, EnsureFundedParams, EnsureFundedReturnType, TransferAssetParams } from './types/transfer' import { calculateFundAmount } from './util' async function fundUsingDispenserApi( - dispenserClient: DispenserApiTestnetClient, + dispenserClient: TestNetDispenserApiClient, addressToFund: string, fundAmount: number, ): Promise { @@ -44,7 +44,7 @@ async function fundUsingTransfer({ note: TransactionNote kmd?: Kmd }): Promise { - if (funding.fundingSource instanceof DispenserApiTestnetClient) { + if (funding.fundingSource instanceof TestNetDispenserApiClient) { throw new Error('Dispenser API client is not supported in this context.') } @@ -132,7 +132,7 @@ export async function ensureFunded( ) if (fundAmount !== null) { - if ((await isTestNet(algod)) && fundingSource instanceof DispenserApiTestnetClient) { + if ((await isTestNet(algod)) && fundingSource instanceof TestNetDispenserApiClient) { return fundUsingDispenserApi(fundingSource, addressToFund, fundAmount) as Promise } else { return fundUsingTransfer({ diff --git a/src/types/dispenser-client.spec.ts b/src/types/dispenser-client.spec.ts index b625adfe..d468f822 100644 --- a/src/types/dispenser-client.spec.ts +++ b/src/types/dispenser-client.spec.ts @@ -1,12 +1,12 @@ -import fetchMock, { enableFetchMocks } from 'jest-fetch-mock' -import { DispenserApiTestnetClient } from './dispenser-client' -enableFetchMocks() +jest.mock('cross-fetch') +import crossFetch from 'cross-fetch' +import { TestNetDispenserApiClient } from './dispenser-client' -describe('DispenserApiTestnetClient', () => { +describe('TestNetDispenserApiClient', () => { const env = process.env beforeEach(async () => { - jest.resetModules() + jest.resetAllMocks() process.env = { ...env } }) @@ -15,9 +15,12 @@ describe('DispenserApiTestnetClient', () => { }) it('should fund account with algos with auth token', async () => { const mockResponse = { txID: 'dummy_tx_id', amount: 1 } - fetchMock.mockResponseOnce(JSON.stringify(mockResponse)) + jest.mocked(crossFetch).mockResolvedValueOnce({ + json: () => Promise.resolve(mockResponse), + ok: true, + } as Response) - const dispenserClient = new DispenserApiTestnetClient({ authToken: 'dummy_auth_token', requestTimeout: null }) + const dispenserClient = new TestNetDispenserApiClient({ authToken: 'dummy_auth_token', requestTimeout: null }) const address = 'dummy_address' const amount = 1 @@ -27,9 +30,13 @@ describe('DispenserApiTestnetClient', () => { }) it('should register refund with auth token', async () => { - fetchMock.mockResponseOnce(JSON.stringify({})) + const fetchMock = jest.mocked(crossFetch) + fetchMock.mockResolvedValueOnce({ + json: () => Promise.resolve({}), + ok: true, + } as Response) - const dispenserClient = new DispenserApiTestnetClient({ authToken: 'dummy_auth_token', requestTimeout: null }) + const dispenserClient = new TestNetDispenserApiClient({ authToken: 'dummy_auth_token', requestTimeout: null }) const refundTxnId = 'dummy_txn_id' await dispenserClient.refund(refundTxnId) @@ -45,26 +52,30 @@ describe('DispenserApiTestnetClient', () => { it('should get limit with auth token', async () => { const amount = 10000000 const mockResponse = { amount: amount } - fetchMock.mockResponseOnce(JSON.stringify(mockResponse)) + const fetchMock = jest.mocked(crossFetch) + fetchMock.mockResolvedValueOnce({ + json: () => Promise.resolve(mockResponse), + ok: true, + } as Response) - const dispenserClient = new DispenserApiTestnetClient({ authToken: 'dummy_auth_token', requestTimeout: null }) + const dispenserClient = new TestNetDispenserApiClient({ authToken: 'dummy_auth_token', requestTimeout: null }) const response = await dispenserClient.getLimit() expect(response.amount).toEqual(amount) }) it('should throw error when no auth token provided', () => { - expect(() => new DispenserApiTestnetClient(null)).toThrow() + expect(() => new TestNetDispenserApiClient(null)).toThrow() }) it('should init with environment variable', () => { process.env.ALGOKIT_DISPENSER_ACCESS_TOKEN = 'dummy_token' - const client = new DispenserApiTestnetClient(null) + const client = new TestNetDispenserApiClient(null) expect(client.authToken).toEqual('dummy_token') }) it('should init with argument over environment variable', () => { process.env.ALGOKIT_DISPENSER_ACCESS_TOKEN = 'dummy_token' - const client = new DispenserApiTestnetClient({ authToken: 'test_value_2', requestTimeout: null }) + const client = new TestNetDispenserApiClient({ authToken: 'test_value_2', requestTimeout: null }) expect(client.authToken).toEqual('test_value_2') }) }) diff --git a/src/types/dispenser-client.ts b/src/types/dispenser-client.ts index d95bb11c..0c6f7588 100644 --- a/src/types/dispenser-client.ts +++ b/src/types/dispenser-client.ts @@ -1,3 +1,5 @@ +import fetch from 'cross-fetch' + const baseUrl = 'https://api.dispenser.algorandfoundation.tools' const dispenserRequestTimeout = 15 const dispenserAccessTokenKey = 'ALGOKIT_DISPENSER_ACCESS_TOKEN' @@ -23,13 +25,13 @@ export interface DispenserLimitResponse { amount: number } -export interface DispenserApiTestnetClientParams { +export interface TestNetDispenserApiClientParams { authToken: string requestTimeout: number | null } /** - * `DispenserApiTestnetClient` is a class that provides methods to interact with the [Algorand TestNet Dispenser API](https://github.com/algorandfoundation/algokit/blob/main/docs/testnet_api.md). + * `TestNetDispenserApiClient` is a class that provides methods to interact with the [Algorand TestNet Dispenser API](https://github.com/algorandfoundation/algokit/blob/main/docs/testnet_api.md). * It allows you to fund an address with Algos, refund a transaction, and get the funding limit for the Algo asset. * * The class requires an authentication token and a request timeout to be initialized. The authentication token can be provided @@ -46,7 +48,7 @@ export interface DispenserApiTestnetClientParams { * * @example * ```typescript - * const client = new DispenserApiTestnetClient({ authToken: 'your_auth_token', requestTimeout: 30 }); + * const client = new TestNetDispenserApiClient({ authToken: 'your_auth_token', requestTimeout: 30 }); * const fundResponse = await client.fund('your_address', 100); * const limitResponse = await client.getLimit(); * await client.refund('your_transaction_id'); @@ -54,11 +56,11 @@ export interface DispenserApiTestnetClientParams { * * @throws {Error} If neither the environment variable 'ALGOKIT_DISPENSER_ACCESS_TOKEN' nor the authToken parameter were provided. */ -export class DispenserApiTestnetClient { +export class TestNetDispenserApiClient { private _authToken: string private _requestTimeout: number - constructor(params: DispenserApiTestnetClientParams | null) { + constructor(params: TestNetDispenserApiClientParams | null) { const authTokenFromEnv = process.env[dispenserAccessTokenKey] if (params?.authToken) { diff --git a/src/types/transfer.ts b/src/types/transfer.ts index 633d5311..9a251584 100644 --- a/src/types/transfer.ts +++ b/src/types/transfer.ts @@ -1,6 +1,6 @@ import { SuggestedParams } from 'algosdk' import { AlgoAmount } from './amount' -import { DispenserApiTestnetClient } from './dispenser-client' +import { TestNetDispenserApiClient } from './dispenser-client' import { SendTransactionFrom, SendTransactionParams, TransactionNote } from './transaction' /** Parameters for `transferAlgos` call. */ @@ -22,7 +22,7 @@ export interface EnsureFundedParams extends SendTransactionParams { /** The account to fund */ accountToFund: SendTransactionFrom | string /** The account to use as a funding source, will default to using the dispenser account returned by `algokit.getDispenserAccount` */ - fundingSource?: SendTransactionFrom | DispenserApiTestnetClient + fundingSource?: SendTransactionFrom | TestNetDispenserApiClient /** The minimum balance of ALGOs that the account should have available to spend (i.e. on top of minimum balance requirement) */ minSpendingBalance: AlgoAmount /** When issuing a funding amount, the minimum amount to transfer (avoids many small transfers if this gets called often on an active account) */ diff --git a/tests/setup.ts b/tests/setup.ts index bc0322cd..e69de29b 100644 --- a/tests/setup.ts +++ b/tests/setup.ts @@ -1,6 +0,0 @@ -import { Config } from '../src/' -import { consoleLogger } from '../src/types/logging' - -Config.configure({ - logger: consoleLogger, -})