diff --git a/.changeset/README.md b/.changeset/README.md new file mode 100644 index 000000000..ce8813d3d --- /dev/null +++ b/.changeset/README.md @@ -0,0 +1,8 @@ +# Changesets + +Hello and welcome! This folder has been automatically generated by `@changesets/cli`, a build tool that works +with multi-package repos, or single-package repos to help you version and publish your code. You can +find the full documentation for it [in our repository](https://github.com/changesets/changesets) + +We have a quick list of common questions to get you started engaging with this project in +[our documentation](https://github.com/changesets/changesets/blob/main/docs/common-questions.md) \ No newline at end of file diff --git a/.changeset/config.json b/.changeset/config.json new file mode 100644 index 000000000..8c3a4e925 --- /dev/null +++ b/.changeset/config.json @@ -0,0 +1,19 @@ +{ + "$schema": "https://unpkg.com/@changesets/config@3.1.1/schema.json", + "changelog": "@changesets/cli/changelog", + "commit": false, + "fixed": [ + [ + "@hashgraph/stablecoin-npm-contracts", + "@hashgraph/stablecoin-npm-backend", + "@hashgraph/stablecoin-npm-sdk", + "@hashgraph/stablecoin-npm-cli", + "@hashgraph/stablecoin-dapp" + ] + ], + "linked": [], + "access": "restricted", + "baseBranch": "main", + "updateInternalDependencies": "patch", + "ignore": [] +} diff --git a/.github/PULL_REQUEST_TEMPLATE/pull_request_template.md b/.github/PULL_REQUEST_TEMPLATE/pull_request_template.md index 5e9573614..79a69f18a 100644 --- a/.github/PULL_REQUEST_TEMPLATE/pull_request_template.md +++ b/.github/PULL_REQUEST_TEMPLATE/pull_request_template.md @@ -38,4 +38,5 @@ Please describe the tests that you ran to verify your changes. Provide instructi - **Effective Tests** (I have added tests that prove my fix is effective or that my feature works) ✔️ - **Local Test Pass** (New and existing unit tests pass locally with my changes) ✅ - **Dependency Updates** (Any dependent changes have been merged and published in downstream modules) 🔄 +- **Changeset** (I have added a changeset file via `npm run changeset`, or applied a bypass label if not applicable: `no-changeset`, `docs-only`, `chore`, `hotfix`) 📦 - **Spellcheck** (I have checked my code and corrected any misspellings) 📝 diff --git a/.github/actions/create-env-file/action.yaml b/.github/actions/create-env-file/action.yaml index 77c87300a..ac6a55b41 100644 --- a/.github/actions/create-env-file/action.yaml +++ b/.github/actions/create-env-file/action.yaml @@ -24,9 +24,19 @@ runs: API_RESPONSE_1=$(curl -s -H "Authorization: Bearer ${{ inputs.api-access-token-1 }}" -H "User-Agent: AppName/1.2.3" https://portal.hedera.com/api/account) API_RESPONSE_2=$(curl -s -H "Authorization: Bearer ${{ inputs.api-access-token-2 }}" -H "User-Agent: AppName/1.2.3" https://portal.hedera.com/api/account) + # Validate API responses are valid JSON before parsing + if ! echo "$API_RESPONSE_1" | jq . > /dev/null 2>&1; then + echo "Error: API call 1 did not return valid JSON. Check that api-access-token-1 is set and valid." + exit 1 + fi + if ! echo "$API_RESPONSE_2" | jq . > /dev/null 2>&1; then + echo "Error: API call 2 did not return valid JSON. Check that api-access-token-2 is set and valid." + exit 1 + fi + # Extract private keys in DER - TESTNET_PRIVATE_KEY_0_DER=$(echo $API_RESPONSE_1 | jq -r '.accounts[1].privateKey') - TESTNET_PRIVATE_KEY_1_DER=$(echo $API_RESPONSE_2 | jq -r '.accounts[1].privateKey') + TESTNET_PRIVATE_KEY_0_DER=$(echo "$API_RESPONSE_1" | jq -r '.accounts[1].privateKey') + TESTNET_PRIVATE_KEY_1_DER=$(echo "$API_RESPONSE_2" | jq -r '.accounts[1].privateKey') # Remove DER header and add 0x prefix TESTNET_PRIVATE_KEY_0="0x$(echo $TESTNET_PRIVATE_KEY_0_DER | tail -c 65)" # Strip first 26 bytes (52 hex characters) @@ -41,24 +51,39 @@ runs: shell: sh run: | # Making API call to fetch secrets - API_RESPONSE_1=$(curl -s -H "Authorization: Bearer ${{ inputs.api-access-token-1 }}" -H "User-Agent: AppName/1.2.3" https://portal.hedera.com/api/account) - API_RESPONSE_2=$(curl -s -H "Authorization: Bearer ${{ inputs.api-access-token-2 }}" -H "User-Agent: AppName/1.2.3" https://portal.hedera.com/api/account) + API_RESPONSE_1=$(curl -s -w "\n%{http_code}" -H "Authorization: Bearer ${{ inputs.api-access-token-1 }}" -H "User-Agent: AppName/1.2.3" https://portal.hedera.com/api/account) + HTTP_STATUS_1=$(echo "$API_RESPONSE_1" | tail -n1) + API_RESPONSE_1=$(echo "$API_RESPONSE_1" | sed '$d') + + API_RESPONSE_2=$(curl -s -w "\n%{http_code}" -H "Authorization: Bearer ${{ inputs.api-access-token-2 }}" -H "User-Agent: AppName/1.2.3" https://portal.hedera.com/api/account) + HTTP_STATUS_2=$(echo "$API_RESPONSE_2" | tail -n1) + API_RESPONSE_2=$(echo "$API_RESPONSE_2" | sed '$d') + + # Validate API responses are valid JSON before parsing + if ! echo "$API_RESPONSE_1" | jq . > /dev/null 2>&1; then + echo "Error: API call 1 failed (HTTP $HTTP_STATUS_1). Check that api-access-token-1 is set and valid." + exit 1 + fi + if ! echo "$API_RESPONSE_2" | jq . > /dev/null 2>&1; then + echo "Error: API call 2 failed (HTTP $HTTP_STATUS_2). Check that api-access-token-2 is set and valid." + exit 1 + fi # Extract account details from API_RESPONSE_1 - CLIENT_PRIVATE_KEY_ECDSA=$(echo $API_RESPONSE_1 | jq -r '.accounts[1].privateKey') - CLIENT_PUBLIC_KEY_ECDSA=$(echo $API_RESPONSE_1 | jq -r '.accounts[1].publicKey') - CLIENT_ACCOUNT_ID_ECDSA=$(echo $API_RESPONSE_1 | jq -r '.accounts[1].accountNum') - CLIENT_PRIVATE_KEY_ED25519=$(echo $API_RESPONSE_1 | jq -r '.accounts[0].privateKey') - CLIENT_PUBLIC_KEY_ED25519=$(echo $API_RESPONSE_1 | jq -r '.accounts[0].publicKey') - CLIENT_ACCOUNT_ID_ED25519=$(echo $API_RESPONSE_1 | jq -r '.accounts[0].accountNum') + CLIENT_PRIVATE_KEY_ECDSA=$(echo "$API_RESPONSE_1" | jq -r '.accounts[1].privateKey') + CLIENT_PUBLIC_KEY_ECDSA=$(echo "$API_RESPONSE_1" | jq -r '.accounts[1].publicKey') + CLIENT_ACCOUNT_ID_ECDSA=$(echo "$API_RESPONSE_1" | jq -r '.accounts[1].accountNum') + CLIENT_PRIVATE_KEY_ED25519=$(echo "$API_RESPONSE_1" | jq -r '.accounts[0].privateKey') + CLIENT_PUBLIC_KEY_ED25519=$(echo "$API_RESPONSE_1" | jq -r '.accounts[0].publicKey') + CLIENT_ACCOUNT_ID_ED25519=$(echo "$API_RESPONSE_1" | jq -r '.accounts[0].accountNum') # Extract account details from API_RESPONSE_2 - CLIENT_PRIVATE_KEY_ECDSA_2=$(echo $API_RESPONSE_2 | jq -r '.accounts[1].privateKey') - CLIENT_PUBLIC_KEY_ECDSA_2=$(echo $API_RESPONSE_2 | jq -r '.accounts[1].publicKey') - CLIENT_ACCOUNT_ID_ECDSA_2=$(echo $API_RESPONSE_2 | jq -r '.accounts[1].accountNum') - CLIENT_PRIVATE_KEY_ED25519_2=$(echo $API_RESPONSE_2 | jq -r '.accounts[0].privateKey') - CLIENT_PUBLIC_KEY_ED25519_2=$(echo $API_RESPONSE_2 | jq -r '.accounts[0].publicKey') - CLIENT_ACCOUNT_ID_ED25519_2=$(echo $API_RESPONSE_2 | jq -r '.accounts[0].accountNum') + CLIENT_PRIVATE_KEY_ECDSA_2=$(echo "$API_RESPONSE_2" | jq -r '.accounts[1].privateKey') + CLIENT_PUBLIC_KEY_ECDSA_2=$(echo "$API_RESPONSE_2" | jq -r '.accounts[1].publicKey') + CLIENT_ACCOUNT_ID_ECDSA_2=$(echo "$API_RESPONSE_2" | jq -r '.accounts[1].accountNum') + CLIENT_PRIVATE_KEY_ED25519_2=$(echo "$API_RESPONSE_2" | jq -r '.accounts[0].privateKey') + CLIENT_PUBLIC_KEY_ED25519_2=$(echo "$API_RESPONSE_2" | jq -r '.accounts[0].publicKey') + CLIENT_ACCOUNT_ID_ED25519_2=$(echo "$API_RESPONSE_2" | jq -r '.accounts[0].accountNum') # Fetch EVM addresses from Mirror Node API CLIENT_EVM_ADDRESS_ECDSA=$(curl -s https://testnet.mirrornode.hedera.com/api/v1/accounts/0.0.$CLIENT_ACCOUNT_ID_ECDSA | jq -r '.evm_address') diff --git a/.github/workflows/changeset.yaml b/.github/workflows/changeset.yaml new file mode 100644 index 000000000..973d354b7 --- /dev/null +++ b/.github/workflows/changeset.yaml @@ -0,0 +1,124 @@ +name: "000: [FLOW] Changeset Check" + +on: + pull_request: + branches: + - develop + types: + - opened + - synchronize + - reopened + - labeled # checks for bypass labels (no-changeset, docs-only, hotfix, chore) + - unlabeled + +defaults: + run: + shell: bash + +permissions: + contents: read + pull-requests: read + +jobs: + check-changeset: + name: Validate Changeset Required + runs-on: token-studio-linux-medium + timeout-minutes: 5 + + steps: + - name: Harden Runner + uses: step-security/harden-runner@5ef0c079ce82195b2a36a210272d6b661572d83e # v2.14.2 + with: + egress-policy: audit + + - name: Checkout repository + uses: actions/checkout@08c6903cd8c0fde910a37f88322edcfb5dd907a8 # v5.0.0 + with: + fetch-depth: 0 + # Ensure we have the head branch for comparison + ref: ${{ github.head_ref }} + + - name: Fetch base branch + run: | + echo "Base branch: ${{ github.base_ref }}" + git fetch origin ${{ github.base_ref }}:${{ github.base_ref }} + git branch -a + + - name: Setup NodeJS Environment + uses: actions/setup-node@49933ea5288caeca8642d1e84afbd3f7d6820020 # v4.4.0 + with: + node-version: 22.20.0 + + - name: Check for bypass labels + id: bypass + env: + GH_TOKEN: ${{ github.token }} + run: | + LABELS=$(gh pr view ${{ github.event.number }} --json labels --jq '.labels[].name' || echo "") + if echo "${LABELS}" | grep -E "(no-changeset|docs-only|hotfix|chore)" > /dev/null; then + echo "bypass=true" >> "${GITHUB_OUTPUT}" + echo "✅ Found bypass label. Skipping changeset check." + else + echo "bypass=false" >> "${GITHUB_OUTPUT}" + echo "🔍 No bypass labels found. Changeset check required." + fi + + - name: Install dependencies + if: ${{ steps.bypass.outputs.bypass == 'false' }} + run: npm ci + + - name: Check changeset status + if: ${{ steps.bypass.outputs.bypass == 'false' }} + env: + BASE_BRANCH: ${{ github.base_ref }} + run: | + echo "🔍 Checking for NEW changesets in this PR..." + echo "Comparing HEAD against base branch: ${BASE_BRANCH}" + + NEW_CHANGESETS=$(git diff "${BASE_BRANCH}...HEAD" --name-only --diff-filter=A | grep "^\.changeset/.*\.md$" | grep -v "README.md" || true) + NEW_CHANGESET_COUNT=$(echo "${NEW_CHANGESETS}" | grep -c "\.md$" || true) + + echo "Files changed in PR:" + git diff "${BASE_BRANCH}...HEAD" --name-only --diff-filter=A | head -10 + echo "" + echo "NEW changeset files in this PR: ${NEW_CHANGESET_COUNT}" + if [[ -n "${NEW_CHANGESETS}" ]]; then + echo "Found NEW changesets:" + echo "${NEW_CHANGESETS}" + fi + + if [[ "${NEW_CHANGESET_COUNT}" -gt 0 ]]; then + echo "✅ Changeset validation passed - found NEW changeset files in PR" + else + echo "" + echo "❌ NEW CHANGESET REQUIRED" + echo "" + echo "This PR requires a NEW changeset to document the changes." + echo "We found no new .changeset/*.md files introduced by this PR." + echo "" + echo "To create a changeset:" + echo "1. Run: npm run changeset" + echo "2. Select the packages that changed" + echo "3. Choose the change type (patch/minor/major)" + echo "4. Write a description of your changes" + echo "5. Commit the generated .changeset/*.md file" + echo "" + echo "To bypass this check (for docs/chore changes only):" + echo "Add one of these labels to the PR:" + echo "- no-changeset: For pure documentation or config changes" + echo "- docs-only: For documentation-only changes" + echo "- chore: For build system or dependency updates" + echo "- hotfix: For emergency fixes" + echo "" + echo "More info: https://github.com/changesets/changesets/blob/main/docs/intro-to-using-changesets.md" + exit 1 + fi + + - name: Success summary + if: ${{ always() }} + run: | + if [[ "${{ steps.bypass.outputs.bypass }}" == "true" ]]; then + echo "✅ Changeset check bypassed due to label" + else + echo "✅ Changeset validation completed successfully" + fi diff --git a/README.md b/README.md index e18309c47..2e73c06e2 100644 --- a/README.md +++ b/README.md @@ -269,10 +269,21 @@ npm run prettier # Format all modules npm run prettier:check # Check formatting without changes ``` +### Changesets + +PRs targeting the `develop` branch must include a changeset file documenting the change. To create one: + +```bash +npm run changeset # Interactive prompt: select packages and bump type +``` + +This generates a `.changeset/*.md` file that must be committed with your changes. If your PR does not require a changeset (documentation, chores, hotfixes), add one of the following labels to bypass the check: `no-changeset`, `docs-only`, `chore`, `hotfix`. + ## Continuous Integration The project uses separate GitHub Actions workflows for each module: +- **Changeset Check** (`.github/workflows/changeset.yaml`): Enforces that PRs to `develop` include a changeset file; can be bypassed with labels (`no-changeset`, `docs-only`, `hotfix`, `chore`) - **Contracts Tests** (`.github/workflows/test-contracts.yaml`): Runs when contract files change - **SDK Tests** (`.github/workflows/test-sdk.yaml`): Runs when SDK files change - **Backend Tests** (`.github/workflows/test-backend.yaml`): Runs when backend files change diff --git a/apps/backend/package.json b/apps/backend/package.json index 8f5232f9f..0bf5d6938 100644 --- a/apps/backend/package.json +++ b/apps/backend/package.json @@ -1,6 +1,6 @@ { "name": "@hashgraph/stablecoin-npm-backend", - "version": "4.2.0", + "version": "4.3.0", "description": "", "author": "", "license": "Apache-2.0", @@ -19,7 +19,7 @@ "test:cov": "npx jest --coverage", "test:debug": "node --inspect-brk -r tsconfig-paths/register -r ts-node/register node_modules/.bin/jest --runInBand", "test:e2e": "npx jest --config ./test/jest-e2e.json", - "test:ci": "npx jest --ci --runInBand", + "test:ci": "npx jest --ci --runInBand --forceExit", "clear-cache": "npx jest --clearCache", "clean:modules": "rimraf node_modules", "deleteAllTransactions": "ts-node ./src/scripts/deleteAllDBTransactions.ts", diff --git a/apps/backend/src/jobs/autoSubmit.service.ts b/apps/backend/src/jobs/autoSubmit.service.ts index 1330ad11a..3b55ebd3c 100644 --- a/apps/backend/src/jobs/autoSubmit.service.ts +++ b/apps/backend/src/jobs/autoSubmit.service.ts @@ -24,12 +24,13 @@ import TransactionService from '../transaction/transaction.service'; import { TransactionStatus } from '../transaction/status.enum'; import { Transaction, - Client, PublicKey, TransactionResponse, TransactionReceipt, Status, + Client, } from '@hiero-ledger/sdk'; +import { buildHederaClient } from '../utils/clientFactory'; import { GetTransactionsResponseDto } from '../transaction/dto/get-transactions-response.dto'; import { hexToUint8Array } from '../utils/utils'; import { LoggerService } from '../logger/logger.service.js'; @@ -142,7 +143,7 @@ export default class AutoSubmitService { async submit(transaction: GetTransactionsResponseDto): Promise { try { - const client: Client = Client.forName(transaction.network); + const client = buildHederaClient(transaction.network, transaction.consensus_nodes); let deserializedTransaction = Transaction.fromBytes( hexToUint8Array(transaction.transaction_message), diff --git a/apps/backend/src/transaction/dto/create-transaction-request.dto.ts b/apps/backend/src/transaction/dto/create-transaction-request.dto.ts index 01c505923..32c008107 100644 --- a/apps/backend/src/transaction/dto/create-transaction-request.dto.ts +++ b/apps/backend/src/transaction/dto/create-transaction-request.dto.ts @@ -26,15 +26,28 @@ import { IsIn, IsInt, IsNotEmpty, + IsOptional, IsString, Matches, Min, + ValidateNested, } from 'class-validator'; +import { Type } from 'class-transformer'; import { hederaIdRegex, hexRegex } from '../../common/regexp'; import { RemoveHexPrefix } from '../../common/decorators/transform-hexPrefix.decorator'; import { Network } from '../network.enum'; import { Transform } from 'class-transformer'; +export class ConsensusNodeDto { + @IsString() + @IsNotEmpty() + url: string; + + @IsString() + @IsNotEmpty() + nodeId: string; +} + export class CreateTransactionRequestDto { @ApiProperty({ description: 'The message to be signed by the keys', @@ -112,6 +125,17 @@ export class CreateTransactionRequestDto { ) network: Network; + @ApiProperty({ + description: + 'Consensus nodes for custom networks (required when network is "custom")', + required: false, + }) + @IsOptional() + @IsArray() + @ValidateNested({ each: true }) + @Type(() => ConsensusNodeDto) + consensus_nodes?: ConsensusNodeDto[]; + @ApiProperty({ description: 'The start date of the transaction in ISO 8601 format', example: '2023-08-01T12:00:00Z', @@ -129,6 +153,7 @@ export class CreateTransactionRequestDto { threshold: number, network: Network, start_date: string, + consensus_nodes?: ConsensusNodeDto[], ) { this.transaction_message = transaction_message; this.description = description; @@ -137,5 +162,6 @@ export class CreateTransactionRequestDto { this.threshold = threshold; this.network = network; this.start_date = start_date; + this.consensus_nodes = consensus_nodes; } } diff --git a/apps/backend/src/transaction/dto/get-transactions-response.dto.ts b/apps/backend/src/transaction/dto/get-transactions-response.dto.ts index 05867a803..3447f76c5 100644 --- a/apps/backend/src/transaction/dto/get-transactions-response.dto.ts +++ b/apps/backend/src/transaction/dto/get-transactions-response.dto.ts @@ -30,6 +30,7 @@ export class GetTransactionsResponseDto { network: string; hedera_account_id: string; start_date: string; + consensus_nodes: { url: string; nodeId: string }[] | null; constructor( id: string, @@ -43,6 +44,7 @@ export class GetTransactionsResponseDto { network: string, hedera_account_id: string, start_date: string, + consensus_nodes: { url: string; nodeId: string }[] | null, ) { this.id = id; this.transaction_message = transaction_message; @@ -55,5 +57,6 @@ export class GetTransactionsResponseDto { this.network = network; this.hedera_account_id = hedera_account_id; this.start_date = start_date; + this.consensus_nodes = consensus_nodes; } } diff --git a/apps/backend/src/transaction/network.enum.ts b/apps/backend/src/transaction/network.enum.ts index 1bc6908a4..6b89e0250 100644 --- a/apps/backend/src/transaction/network.enum.ts +++ b/apps/backend/src/transaction/network.enum.ts @@ -2,4 +2,5 @@ export enum Network { MAINNET = 'mainnet', TESTNET = 'testnet', PREVIEWNET = 'previewnet', + CUSTOM = 'custom', } diff --git a/apps/backend/src/transaction/transaction.entity.ts b/apps/backend/src/transaction/transaction.entity.ts index ed133b38d..2b4e6179d 100644 --- a/apps/backend/src/transaction/transaction.entity.ts +++ b/apps/backend/src/transaction/transaction.entity.ts @@ -1,5 +1,4 @@ import { Column, Entity, PrimaryGeneratedColumn } from 'typeorm'; -import { Network } from './network.enum'; import { TransactionStatus } from './status.enum'; @Entity() @@ -46,12 +45,11 @@ export default class Transaction { @Column() threshold: number; - @Column({ - type: 'enum', - enum: Network, - nullable: false, - }) - network: Network; + @Column({ type: 'varchar', nullable: false }) + network: string; + + @Column({ type: 'jsonb', nullable: true }) + consensus_nodes: { url: string; nodeId: string }[] | null; @Column({ type: 'timestamp with time zone', diff --git a/apps/backend/src/transaction/transaction.service.ts b/apps/backend/src/transaction/transaction.service.ts index 218856a5f..855b08b80 100644 --- a/apps/backend/src/transaction/transaction.service.ts +++ b/apps/backend/src/transaction/transaction.service.ts @@ -44,7 +44,8 @@ import { } from '../common/exceptions/domain-exceptions'; import { TransactionStatus } from './status.enum'; import { Network } from './network.enum'; -import { Client, Transaction as TransactionSdk } from '@hiero-ledger/sdk'; +import { Transaction as TransactionSdk } from '@hiero-ledger/sdk'; +import { buildHederaClient } from '../utils/clientFactory'; @Injectable() export default class TransactionService { @@ -97,7 +98,7 @@ export default class TransactionService { const deserializedTransaction = TransactionSdk.fromBytes( hexToUint8Array(transaction.transaction_message), - ).freezeWith(Client.forName(transaction.network)); + ).freezeWith(buildHederaClient(transaction.network, transaction.consensus_nodes)); if ( !verifySignature( @@ -251,6 +252,7 @@ export default class TransactionService { transaction.network, transaction.hedera_account_id, transaction.start_date.toUTCString(), + transaction.consensus_nodes ?? null, ); } } diff --git a/apps/backend/src/utils/clientFactory.ts b/apps/backend/src/utils/clientFactory.ts new file mode 100644 index 000000000..3d257f76a --- /dev/null +++ b/apps/backend/src/utils/clientFactory.ts @@ -0,0 +1,44 @@ +/* + * + * Hedera Stablecoin SDK + * + * Copyright (C) 2023 Hedera Hashgraph, LLC + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + * + */ + +import { Client } from '@hiero-ledger/sdk'; + +export function buildHederaClient( + network: string, + consensusNodes?: { url: string; nodeId: string }[] | null, +): Client { + switch (network) { + case 'mainnet': + return Client.forMainnet(); + case 'testnet': + return Client.forTestnet(); + case 'previewnet': + return Client.forPreviewnet(); + default: { + if (!consensusNodes?.length) + throw new Error( + `Network '${network}' requires consensus_nodes to be provided`, + ); + return Client.forNetwork( + Object.fromEntries(consensusNodes.map((n) => [n.url, n.nodeId])), + ); + } + } +} diff --git a/apps/backend/test/transaction/transaction.controller.spec.ts b/apps/backend/test/transaction/transaction.controller.spec.ts index 8bcb341f9..10fd123ae 100644 --- a/apps/backend/test/transaction/transaction.controller.spec.ts +++ b/apps/backend/test/transaction/transaction.controller.spec.ts @@ -333,6 +333,7 @@ describe('Transaction Controller Test', () => { DEFAULT.network, DEFAULT.hedera_account_id, DEFAULT.start_date.toDateString(), + null, ), ), ); @@ -349,6 +350,7 @@ describe('Transaction Controller Test', () => { DEFAULT.network, DEFAULT.hedera_account_id, DEFAULT.start_date.toDateString(), + null, ); //* 🎬 Act ⬇ const result = await controller.getTransactionById( @@ -381,6 +383,7 @@ function createMockGetAllByPublicKeyTxServiceResult( pendingTransaction.network, pendingTransaction.hedera_account_id, pendingTransaction.start_date.toDateString(), + null, ); return new Pagination( [transactionResponse, transactionResponse], diff --git a/apps/backend/test/transaction/transaction.mock.ts b/apps/backend/test/transaction/transaction.mock.ts index 5f8656e62..f8ade3291 100644 --- a/apps/backend/test/transaction/transaction.mock.ts +++ b/apps/backend/test/transaction/transaction.mock.ts @@ -119,6 +119,7 @@ export default class TransactionMock extends Transaction { signatures: TransactionMock.txPending0().signatures, network: TransactionMock.txPending0().network, start_date: TransactionMock.txPending0().start_date.toDateString(), + consensus_nodes: null, }; static txPending1(command: Partial = {}) { diff --git a/apps/backend/test/transaction/transaction.service.spec.ts b/apps/backend/test/transaction/transaction.service.spec.ts index 62d3cb880..569902cc5 100644 --- a/apps/backend/test/transaction/transaction.service.spec.ts +++ b/apps/backend/test/transaction/transaction.service.spec.ts @@ -25,6 +25,7 @@ import { Repository } from 'typeorm'; import TransactionService from '../../src/transaction/transaction.service'; import Transaction from '../../src/transaction/transaction.entity'; import { SignTransactionRequestDto } from '../../src/transaction/dto/sign-transaction-request.dto'; +import { CreateTransactionRequestDto } from '../../src/transaction/dto/create-transaction-request.dto'; import TransactionMock, { DEFAULT } from './transaction.mock'; import { LoggerService } from '../../src/logger/logger.service'; import { TransactionStatus } from '../../src/transaction/status.enum'; @@ -88,7 +89,7 @@ describe('Transaction Service Test', () => { threshold: pendingTransaction.threshold, network: pendingTransaction.network, start_date: pendingTransaction.start_date.toDateString(), - }; + } as CreateTransactionRequestDto; const expected = TransactionMock.txPending0(); //* 🎬 Act ⬇ @@ -118,7 +119,7 @@ describe('Transaction Service Test', () => { threshold: pendingTransaction.threshold, network: pendingTransaction.network, start_date: pendingTransaction.start_date.toDateString(), - }; + } as CreateTransactionRequestDto; //* 🎬 Act ⬇ const transaction = await service.create(createTransactionDto); @@ -143,7 +144,7 @@ describe('Transaction Service Test', () => { threshold: new_threshold, network: pendingTransaction.network, start_date: pendingTransaction.start_date.toDateString(), - }; + } as CreateTransactionRequestDto; //* 🎬 Act ⬇ const transaction = await service.create(createTransactionDto); @@ -167,7 +168,7 @@ describe('Transaction Service Test', () => { threshold: pendingTransaction.threshold, network: pendingTransaction.network, start_date: pendingTransaction.start_date.toDateString(), - }; + } as CreateTransactionRequestDto; const expected = TransactionMock.txPending0({ threshold: createTransactionDto.key_list.length, diff --git a/apps/cli/package.json b/apps/cli/package.json index 0bd7b9fbc..1956e5007 100644 --- a/apps/cli/package.json +++ b/apps/cli/package.json @@ -1,6 +1,6 @@ { "name": "@hashgraph/stablecoin-npm-cli", - "version": "4.2.0", + "version": "4.3.0", "description": "CLI for Hedera Stablecoin", "main": "./build/src/index.js", "bin": { diff --git a/apps/docs/package.json b/apps/docs/package.json index 2abbd0227..46e9c3883 100644 --- a/apps/docs/package.json +++ b/apps/docs/package.json @@ -22,8 +22,8 @@ "@mdx-js/react": "3.1.1", "clsx": "2.1.1", "prism-react-renderer": "2.4.1", - "react": "19.2.3", - "react-dom": "19.2.3" + "react": "18.3.1", + "react-dom": "18.3.1" }, "devDependencies": { "@docusaurus/module-type-aliases": "3.9.2", diff --git a/apps/docs/sidebars.ts b/apps/docs/sidebars.ts index 706f7d17d..428505595 100644 --- a/apps/docs/sidebars.ts +++ b/apps/docs/sidebars.ts @@ -76,6 +76,17 @@ const sidebars: SidebarsConfig = { "references/security", ], }, + { + type: "category", + label: "References", + items: [ + "references/intro", + "references/migration", + "references/deployed-addresses", + "references/troubleshooting", + "references/security", + ], + }, ], }; diff --git a/apps/web/package.json b/apps/web/package.json index e7a2ac730..a77f3ddf8 100644 --- a/apps/web/package.json +++ b/apps/web/package.json @@ -1,6 +1,6 @@ { "name": "@hashgraph/stablecoin-dapp", - "version": "4.2.0", + "version": "4.3.0", "files": [ "build/" ], @@ -137,11 +137,6 @@ "/src/test/setupTests.tsx" ], "moduleNameMapper": { - "^react$": "/node_modules/react", - "^react/jsx-runtime$": "/node_modules/react/jsx-runtime", - "^react/jsx-dev-runtime$": "/node_modules/react/jsx-dev-runtime", - "^react-dom$": "/node_modules/react-dom", - "^react-dom/(.*)$": "/node_modules/react-dom/$1", "^(\\.{1,2}/.*)\\.(m)?js$": "$1", "axios": "/../../node_modules/axios/dist/node/axios.cjs", "blade": "/src/mocks/blade-sdk-mock.js", diff --git a/documentation/client/configuration.md b/documentation/client/configuration.md index bcb6b3023..70b11eb5a 100644 --- a/documentation/client/configuration.md +++ b/documentation/client/configuration.md @@ -17,7 +17,7 @@ npm run start:wizard ## Manual YAML Parameters -If you prefer to bypass the wizard, you must manually create a `hsca-config.yaml` file (using this [sample file (hsca-config.sample.yaml)](https://github.com/hashgraph/stablecoin-studio/blob/main/apps/cli/hsca-config.sample.yaml) as a template). +If you prefer to bypass the wizard, you must manually create a `hsca-config.yaml` file (using this [sample file (hsca-config.sample.yaml)](../../apps/cli/hsca-config.sample.yaml) as a template). ```bash cd path/to/cli/ diff --git a/package-lock.json b/package-lock.json index a910727b5..c81c94ea8 100644 --- a/package-lock.json +++ b/package-lock.json @@ -1,12 +1,12 @@ { "name": "@hashgraph/hedera-stable-coin", - "version": "4.2.0", + "version": "4.3.0", "lockfileVersion": 3, "requires": true, "packages": { "": { "name": "@hashgraph/hedera-stable-coin", - "version": "4.2.0", + "version": "4.3.0", "license": "Apache-2.0", "workspaces": [ "packages/*", @@ -16,16 +16,19 @@ "husky": "9.1.7" }, "devDependencies": { + "@changesets/cli": "2.29.8", "@commitlint/cli": "19.8.0", "@commitlint/config-conventional": "19.8.0", "concurrently": "7.6.0", "dotenv": "16.4.7", + "react": "18.3.1", + "react-dom": "18.3.1", "rimraf": "4.4.1" } }, "apps/backend": { "name": "@hashgraph/stablecoin-npm-backend", - "version": "4.2.0", + "version": "4.3.0", "license": "Apache-2.0", "dependencies": { "@hiero-ledger/sdk": "2.79.0", @@ -154,7 +157,7 @@ }, "apps/cli": { "name": "@hashgraph/stablecoin-npm-cli", - "version": "4.2.0", + "version": "4.3.0", "license": "Apache-2.0", "dependencies": { "@hashgraph/stablecoin-npm-sdk": "*", @@ -274,8 +277,8 @@ "@mdx-js/react": "3.1.1", "clsx": "2.1.1", "prism-react-renderer": "2.4.1", - "react": "19.2.3", - "react-dom": "19.2.3" + "react": "18.3.1", + "react-dom": "18.3.1" }, "devDependencies": { "@docusaurus/module-type-aliases": "3.9.2", @@ -303,7 +306,7 @@ }, "apps/web": { "name": "@hashgraph/stablecoin-dapp", - "version": "4.2.0", + "version": "4.3.0", "dependencies": { "@chakra-ui/icons": "2.2.4", "@chakra-ui/react": "2.6.1", @@ -3094,18 +3097,6 @@ "url": "https://github.com/prettier/prettier?sponsor=1" } }, - "apps/web/node_modules/react": { - "version": "18.3.1", - "resolved": "https://registry.npmjs.org/react/-/react-18.3.1.tgz", - "integrity": "sha512-wS+hAgJShR0KhEvPJArfuPVN1+Hz1t0Y6n5jLrGQbkb4urgPE/0Rve+1kMB1v/oWgHgm4WIcV+i7F2pTVj+2iQ==", - "license": "MIT", - "dependencies": { - "loose-envify": "^1.1.0" - }, - "engines": { - "node": ">=0.10.0" - } - }, "apps/web/node_modules/react-app-rewired": { "version": "2.2.1", "resolved": "https://registry.npmjs.org/react-app-rewired/-/react-app-rewired-2.2.1.tgz", @@ -3150,19 +3141,6 @@ "react-dom": "^16.9.0 || ^17 || ^18" } }, - "apps/web/node_modules/react-dom": { - "version": "18.3.1", - "resolved": "https://registry.npmjs.org/react-dom/-/react-dom-18.3.1.tgz", - "integrity": "sha512-5m4nQKp+rZRb09LNH59GM4BxTh9251/ylbKIbpe7TpGxfJ+9kv6BLkLBXIjjspbgbnIBNqlI23tRnTWT0snUIw==", - "license": "MIT", - "dependencies": { - "loose-envify": "^1.1.0", - "scheduler": "^0.23.2" - }, - "peerDependencies": { - "react": "^18.3.1" - } - }, "apps/web/node_modules/react-fast-compare": { "version": "3.2.1", "resolved": "https://registry.npmjs.org/react-fast-compare/-/react-fast-compare-3.2.1.tgz", @@ -8223,6 +8201,534 @@ "lodash.mergewith": "4.6.2" } }, + "node_modules/@changesets/apply-release-plan": { + "version": "7.1.0", + "resolved": "https://registry.npmjs.org/@changesets/apply-release-plan/-/apply-release-plan-7.1.0.tgz", + "integrity": "sha512-yq8ML3YS7koKQ/9bk1PqO0HMzApIFNwjlwCnwFEXMzNe8NpzeeYYKCmnhWJGkN8g7E51MnWaSbqRcTcdIxUgnQ==", + "dev": true, + "license": "MIT", + "dependencies": { + "@changesets/config": "^3.1.3", + "@changesets/get-version-range-type": "^0.4.0", + "@changesets/git": "^3.0.4", + "@changesets/should-skip-package": "^0.1.2", + "@changesets/types": "^6.1.0", + "@manypkg/get-packages": "^1.1.3", + "detect-indent": "^6.0.0", + "fs-extra": "^7.0.1", + "lodash.startcase": "^4.4.0", + "outdent": "^0.5.0", + "prettier": "^2.7.1", + "resolve-from": "^5.0.0", + "semver": "^7.5.3" + } + }, + "node_modules/@changesets/apply-release-plan/node_modules/fs-extra": { + "version": "7.0.1", + "resolved": "https://registry.npmjs.org/fs-extra/-/fs-extra-7.0.1.tgz", + "integrity": "sha512-YJDaCJZEnBmcbw13fvdAM9AwNOJwOzrE4pqMqBq5nFiEqXUqHwlK4B+3pUw6JNvfSPtX05xFHtYy/1ni01eGCw==", + "dev": true, + "license": "MIT", + "dependencies": { + "graceful-fs": "^4.1.2", + "jsonfile": "^4.0.0", + "universalify": "^0.1.0" + }, + "engines": { + "node": ">=6 <7 || >=8" + } + }, + "node_modules/@changesets/apply-release-plan/node_modules/jsonfile": { + "version": "4.0.0", + "resolved": "https://registry.npmjs.org/jsonfile/-/jsonfile-4.0.0.tgz", + "integrity": "sha512-m6F1R3z8jjlf2imQHS2Qez5sjKWQzbuuhuJ/FKYFRZvPE3PuHcSMVZzfsLhGVOkfd20obL5SWEBew5ShlquNxg==", + "dev": true, + "license": "MIT", + "optionalDependencies": { + "graceful-fs": "^4.1.6" + } + }, + "node_modules/@changesets/apply-release-plan/node_modules/prettier": { + "version": "2.8.8", + "resolved": "https://registry.npmjs.org/prettier/-/prettier-2.8.8.tgz", + "integrity": "sha512-tdN8qQGvNjw4CHbY+XXk0JgCXn9QiF21a55rBe5LJAU+kDyC4WQn4+awm2Xfk2lQMk5fKup9XgzTZtGkjBdP9Q==", + "dev": true, + "license": "MIT", + "bin": { + "prettier": "bin-prettier.js" + }, + "engines": { + "node": ">=10.13.0" + }, + "funding": { + "url": "https://github.com/prettier/prettier?sponsor=1" + } + }, + "node_modules/@changesets/apply-release-plan/node_modules/universalify": { + "version": "0.1.2", + "resolved": "https://registry.npmjs.org/universalify/-/universalify-0.1.2.tgz", + "integrity": "sha512-rBJeI5CXAlmy1pV+617WB9J63U6XcazHHF2f2dbJix4XzpUF0RS3Zbj0FGIOCAva5P/d/GBOYaACQ1w+0azUkg==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">= 4.0.0" + } + }, + "node_modules/@changesets/assemble-release-plan": { + "version": "6.0.9", + "resolved": "https://registry.npmjs.org/@changesets/assemble-release-plan/-/assemble-release-plan-6.0.9.tgz", + "integrity": "sha512-tPgeeqCHIwNo8sypKlS3gOPmsS3wP0zHt67JDuL20P4QcXiw/O4Hl7oXiuLnP9yg+rXLQ2sScdV1Kkzde61iSQ==", + "dev": true, + "license": "MIT", + "dependencies": { + "@changesets/errors": "^0.2.0", + "@changesets/get-dependents-graph": "^2.1.3", + "@changesets/should-skip-package": "^0.1.2", + "@changesets/types": "^6.1.0", + "@manypkg/get-packages": "^1.1.3", + "semver": "^7.5.3" + } + }, + "node_modules/@changesets/changelog-git": { + "version": "0.2.1", + "resolved": "https://registry.npmjs.org/@changesets/changelog-git/-/changelog-git-0.2.1.tgz", + "integrity": "sha512-x/xEleCFLH28c3bQeQIyeZf8lFXyDFVn1SgcBiR2Tw/r4IAWlk1fzxCEZ6NxQAjF2Nwtczoen3OA2qR+UawQ8Q==", + "dev": true, + "license": "MIT", + "dependencies": { + "@changesets/types": "^6.1.0" + } + }, + "node_modules/@changesets/cli": { + "version": "2.29.8", + "resolved": "https://registry.npmjs.org/@changesets/cli/-/cli-2.29.8.tgz", + "integrity": "sha512-1weuGZpP63YWUYjay/E84qqwcnt5yJMM0tep10Up7Q5cS/DGe2IZ0Uj3HNMxGhCINZuR7aO9WBMdKnPit5ZDPA==", + "dev": true, + "license": "MIT", + "dependencies": { + "@changesets/apply-release-plan": "^7.0.14", + "@changesets/assemble-release-plan": "^6.0.9", + "@changesets/changelog-git": "^0.2.1", + "@changesets/config": "^3.1.2", + "@changesets/errors": "^0.2.0", + "@changesets/get-dependents-graph": "^2.1.3", + "@changesets/get-release-plan": "^4.0.14", + "@changesets/git": "^3.0.4", + "@changesets/logger": "^0.1.1", + "@changesets/pre": "^2.0.2", + "@changesets/read": "^0.6.6", + "@changesets/should-skip-package": "^0.1.2", + "@changesets/types": "^6.1.0", + "@changesets/write": "^0.4.0", + "@inquirer/external-editor": "^1.0.2", + "@manypkg/get-packages": "^1.1.3", + "ansi-colors": "^4.1.3", + "ci-info": "^3.7.0", + "enquirer": "^2.4.1", + "fs-extra": "^7.0.1", + "mri": "^1.2.0", + "p-limit": "^2.2.0", + "package-manager-detector": "^0.2.0", + "picocolors": "^1.1.0", + "resolve-from": "^5.0.0", + "semver": "^7.5.3", + "spawndamnit": "^3.0.1", + "term-size": "^2.1.0" + }, + "bin": { + "changeset": "bin.js" + } + }, + "node_modules/@changesets/cli/node_modules/ci-info": { + "version": "3.9.0", + "resolved": "https://registry.npmjs.org/ci-info/-/ci-info-3.9.0.tgz", + "integrity": "sha512-NIxF55hv4nSqQswkAeiOi1r83xy8JldOFDTWiug55KBu9Jnblncd2U6ViHmYgHf01TPZS77NJBhBMKdWj9HQMQ==", + "dev": true, + "funding": [ + { + "type": "github", + "url": "https://github.com/sponsors/sibiraj-s" + } + ], + "license": "MIT", + "engines": { + "node": ">=8" + } + }, + "node_modules/@changesets/cli/node_modules/fs-extra": { + "version": "7.0.1", + "resolved": "https://registry.npmjs.org/fs-extra/-/fs-extra-7.0.1.tgz", + "integrity": "sha512-YJDaCJZEnBmcbw13fvdAM9AwNOJwOzrE4pqMqBq5nFiEqXUqHwlK4B+3pUw6JNvfSPtX05xFHtYy/1ni01eGCw==", + "dev": true, + "license": "MIT", + "dependencies": { + "graceful-fs": "^4.1.2", + "jsonfile": "^4.0.0", + "universalify": "^0.1.0" + }, + "engines": { + "node": ">=6 <7 || >=8" + } + }, + "node_modules/@changesets/cli/node_modules/jsonfile": { + "version": "4.0.0", + "resolved": "https://registry.npmjs.org/jsonfile/-/jsonfile-4.0.0.tgz", + "integrity": "sha512-m6F1R3z8jjlf2imQHS2Qez5sjKWQzbuuhuJ/FKYFRZvPE3PuHcSMVZzfsLhGVOkfd20obL5SWEBew5ShlquNxg==", + "dev": true, + "license": "MIT", + "optionalDependencies": { + "graceful-fs": "^4.1.6" + } + }, + "node_modules/@changesets/cli/node_modules/p-limit": { + "version": "2.3.0", + "resolved": "https://registry.npmjs.org/p-limit/-/p-limit-2.3.0.tgz", + "integrity": "sha512-//88mFWSJx8lxCzwdAABTJL2MyWB12+eIY7MDL2SqLmAkeKU9qxRvWuSyTjm3FUmpBEMuFfckAIqEaVGUDxb6w==", + "dev": true, + "license": "MIT", + "dependencies": { + "p-try": "^2.0.0" + }, + "engines": { + "node": ">=6" + }, + "funding": { + "url": "https://github.com/sponsors/sindresorhus" + } + }, + "node_modules/@changesets/cli/node_modules/package-manager-detector": { + "version": "0.2.11", + "resolved": "https://registry.npmjs.org/package-manager-detector/-/package-manager-detector-0.2.11.tgz", + "integrity": "sha512-BEnLolu+yuz22S56CU1SUKq3XC3PkwD5wv4ikR4MfGvnRVcmzXR9DwSlW2fEamyTPyXHomBJRzgapeuBvRNzJQ==", + "dev": true, + "license": "MIT", + "dependencies": { + "quansync": "^0.2.7" + } + }, + "node_modules/@changesets/cli/node_modules/universalify": { + "version": "0.1.2", + "resolved": "https://registry.npmjs.org/universalify/-/universalify-0.1.2.tgz", + "integrity": "sha512-rBJeI5CXAlmy1pV+617WB9J63U6XcazHHF2f2dbJix4XzpUF0RS3Zbj0FGIOCAva5P/d/GBOYaACQ1w+0azUkg==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">= 4.0.0" + } + }, + "node_modules/@changesets/config": { + "version": "3.1.3", + "resolved": "https://registry.npmjs.org/@changesets/config/-/config-3.1.3.tgz", + "integrity": "sha512-vnXjcey8YgBn2L1OPWd3ORs0bGC4LoYcK/ubpgvzNVr53JXV5GiTVj7fWdMRsoKUH7hhhMAQnsJUqLr21EncNw==", + "dev": true, + "license": "MIT", + "dependencies": { + "@changesets/errors": "^0.2.0", + "@changesets/get-dependents-graph": "^2.1.3", + "@changesets/logger": "^0.1.1", + "@changesets/should-skip-package": "^0.1.2", + "@changesets/types": "^6.1.0", + "@manypkg/get-packages": "^1.1.3", + "fs-extra": "^7.0.1", + "micromatch": "^4.0.8" + } + }, + "node_modules/@changesets/config/node_modules/fs-extra": { + "version": "7.0.1", + "resolved": "https://registry.npmjs.org/fs-extra/-/fs-extra-7.0.1.tgz", + "integrity": "sha512-YJDaCJZEnBmcbw13fvdAM9AwNOJwOzrE4pqMqBq5nFiEqXUqHwlK4B+3pUw6JNvfSPtX05xFHtYy/1ni01eGCw==", + "dev": true, + "license": "MIT", + "dependencies": { + "graceful-fs": "^4.1.2", + "jsonfile": "^4.0.0", + "universalify": "^0.1.0" + }, + "engines": { + "node": ">=6 <7 || >=8" + } + }, + "node_modules/@changesets/config/node_modules/jsonfile": { + "version": "4.0.0", + "resolved": "https://registry.npmjs.org/jsonfile/-/jsonfile-4.0.0.tgz", + "integrity": "sha512-m6F1R3z8jjlf2imQHS2Qez5sjKWQzbuuhuJ/FKYFRZvPE3PuHcSMVZzfsLhGVOkfd20obL5SWEBew5ShlquNxg==", + "dev": true, + "license": "MIT", + "optionalDependencies": { + "graceful-fs": "^4.1.6" + } + }, + "node_modules/@changesets/config/node_modules/universalify": { + "version": "0.1.2", + "resolved": "https://registry.npmjs.org/universalify/-/universalify-0.1.2.tgz", + "integrity": "sha512-rBJeI5CXAlmy1pV+617WB9J63U6XcazHHF2f2dbJix4XzpUF0RS3Zbj0FGIOCAva5P/d/GBOYaACQ1w+0azUkg==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">= 4.0.0" + } + }, + "node_modules/@changesets/errors": { + "version": "0.2.0", + "resolved": "https://registry.npmjs.org/@changesets/errors/-/errors-0.2.0.tgz", + "integrity": "sha512-6BLOQUscTpZeGljvyQXlWOItQyU71kCdGz7Pi8H8zdw6BI0g3m43iL4xKUVPWtG+qrrL9DTjpdn8eYuCQSRpow==", + "dev": true, + "license": "MIT", + "dependencies": { + "extendable-error": "^0.1.5" + } + }, + "node_modules/@changesets/get-dependents-graph": { + "version": "2.1.3", + "resolved": "https://registry.npmjs.org/@changesets/get-dependents-graph/-/get-dependents-graph-2.1.3.tgz", + "integrity": "sha512-gphr+v0mv2I3Oxt19VdWRRUxq3sseyUpX9DaHpTUmLj92Y10AGy+XOtV+kbM6L/fDcpx7/ISDFK6T8A/P3lOdQ==", + "dev": true, + "license": "MIT", + "dependencies": { + "@changesets/types": "^6.1.0", + "@manypkg/get-packages": "^1.1.3", + "picocolors": "^1.1.0", + "semver": "^7.5.3" + } + }, + "node_modules/@changesets/get-release-plan": { + "version": "4.0.15", + "resolved": "https://registry.npmjs.org/@changesets/get-release-plan/-/get-release-plan-4.0.15.tgz", + "integrity": "sha512-Q04ZaRPuEVZtA+auOYgFaVQQSA98dXiVe/yFaZfY7hoSmQICHGvP0TF4u3EDNHWmmCS4ekA/XSpKlSM2PyTS2g==", + "dev": true, + "license": "MIT", + "dependencies": { + "@changesets/assemble-release-plan": "^6.0.9", + "@changesets/config": "^3.1.3", + "@changesets/pre": "^2.0.2", + "@changesets/read": "^0.6.7", + "@changesets/types": "^6.1.0", + "@manypkg/get-packages": "^1.1.3" + } + }, + "node_modules/@changesets/get-version-range-type": { + "version": "0.4.0", + "resolved": "https://registry.npmjs.org/@changesets/get-version-range-type/-/get-version-range-type-0.4.0.tgz", + "integrity": "sha512-hwawtob9DryoGTpixy1D3ZXbGgJu1Rhr+ySH2PvTLHvkZuQ7sRT4oQwMh0hbqZH1weAooedEjRsbrWcGLCeyVQ==", + "dev": true, + "license": "MIT" + }, + "node_modules/@changesets/git": { + "version": "3.0.4", + "resolved": "https://registry.npmjs.org/@changesets/git/-/git-3.0.4.tgz", + "integrity": "sha512-BXANzRFkX+XcC1q/d27NKvlJ1yf7PSAgi8JG6dt8EfbHFHi4neau7mufcSca5zRhwOL8j9s6EqsxmT+s+/E6Sw==", + "dev": true, + "license": "MIT", + "dependencies": { + "@changesets/errors": "^0.2.0", + "@manypkg/get-packages": "^1.1.3", + "is-subdir": "^1.1.1", + "micromatch": "^4.0.8", + "spawndamnit": "^3.0.1" + } + }, + "node_modules/@changesets/logger": { + "version": "0.1.1", + "resolved": "https://registry.npmjs.org/@changesets/logger/-/logger-0.1.1.tgz", + "integrity": "sha512-OQtR36ZlnuTxKqoW4Sv6x5YIhOmClRd5pWsjZsddYxpWs517R0HkyiefQPIytCVh4ZcC5x9XaG8KTdd5iRQUfg==", + "dev": true, + "license": "MIT", + "dependencies": { + "picocolors": "^1.1.0" + } + }, + "node_modules/@changesets/parse": { + "version": "0.4.3", + "resolved": "https://registry.npmjs.org/@changesets/parse/-/parse-0.4.3.tgz", + "integrity": "sha512-ZDmNc53+dXdWEv7fqIUSgRQOLYoUom5Z40gmLgmATmYR9NbL6FJJHwakcCpzaeCy+1D0m0n7mT4jj2B/MQPl7A==", + "dev": true, + "license": "MIT", + "dependencies": { + "@changesets/types": "^6.1.0", + "js-yaml": "^4.1.1" + } + }, + "node_modules/@changesets/pre": { + "version": "2.0.2", + "resolved": "https://registry.npmjs.org/@changesets/pre/-/pre-2.0.2.tgz", + "integrity": "sha512-HaL/gEyFVvkf9KFg6484wR9s0qjAXlZ8qWPDkTyKF6+zqjBe/I2mygg3MbpZ++hdi0ToqNUF8cjj7fBy0dg8Ug==", + "dev": true, + "license": "MIT", + "dependencies": { + "@changesets/errors": "^0.2.0", + "@changesets/types": "^6.1.0", + "@manypkg/get-packages": "^1.1.3", + "fs-extra": "^7.0.1" + } + }, + "node_modules/@changesets/pre/node_modules/fs-extra": { + "version": "7.0.1", + "resolved": "https://registry.npmjs.org/fs-extra/-/fs-extra-7.0.1.tgz", + "integrity": "sha512-YJDaCJZEnBmcbw13fvdAM9AwNOJwOzrE4pqMqBq5nFiEqXUqHwlK4B+3pUw6JNvfSPtX05xFHtYy/1ni01eGCw==", + "dev": true, + "license": "MIT", + "dependencies": { + "graceful-fs": "^4.1.2", + "jsonfile": "^4.0.0", + "universalify": "^0.1.0" + }, + "engines": { + "node": ">=6 <7 || >=8" + } + }, + "node_modules/@changesets/pre/node_modules/jsonfile": { + "version": "4.0.0", + "resolved": "https://registry.npmjs.org/jsonfile/-/jsonfile-4.0.0.tgz", + "integrity": "sha512-m6F1R3z8jjlf2imQHS2Qez5sjKWQzbuuhuJ/FKYFRZvPE3PuHcSMVZzfsLhGVOkfd20obL5SWEBew5ShlquNxg==", + "dev": true, + "license": "MIT", + "optionalDependencies": { + "graceful-fs": "^4.1.6" + } + }, + "node_modules/@changesets/pre/node_modules/universalify": { + "version": "0.1.2", + "resolved": "https://registry.npmjs.org/universalify/-/universalify-0.1.2.tgz", + "integrity": "sha512-rBJeI5CXAlmy1pV+617WB9J63U6XcazHHF2f2dbJix4XzpUF0RS3Zbj0FGIOCAva5P/d/GBOYaACQ1w+0azUkg==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">= 4.0.0" + } + }, + "node_modules/@changesets/read": { + "version": "0.6.7", + "resolved": "https://registry.npmjs.org/@changesets/read/-/read-0.6.7.tgz", + "integrity": "sha512-D1G4AUYGrBEk8vj8MGwf75k9GpN6XL3wg8i42P2jZZwFLXnlr2Pn7r9yuQNbaMCarP7ZQWNJbV6XLeysAIMhTA==", + "dev": true, + "license": "MIT", + "dependencies": { + "@changesets/git": "^3.0.4", + "@changesets/logger": "^0.1.1", + "@changesets/parse": "^0.4.3", + "@changesets/types": "^6.1.0", + "fs-extra": "^7.0.1", + "p-filter": "^2.1.0", + "picocolors": "^1.1.0" + } + }, + "node_modules/@changesets/read/node_modules/fs-extra": { + "version": "7.0.1", + "resolved": "https://registry.npmjs.org/fs-extra/-/fs-extra-7.0.1.tgz", + "integrity": "sha512-YJDaCJZEnBmcbw13fvdAM9AwNOJwOzrE4pqMqBq5nFiEqXUqHwlK4B+3pUw6JNvfSPtX05xFHtYy/1ni01eGCw==", + "dev": true, + "license": "MIT", + "dependencies": { + "graceful-fs": "^4.1.2", + "jsonfile": "^4.0.0", + "universalify": "^0.1.0" + }, + "engines": { + "node": ">=6 <7 || >=8" + } + }, + "node_modules/@changesets/read/node_modules/jsonfile": { + "version": "4.0.0", + "resolved": "https://registry.npmjs.org/jsonfile/-/jsonfile-4.0.0.tgz", + "integrity": "sha512-m6F1R3z8jjlf2imQHS2Qez5sjKWQzbuuhuJ/FKYFRZvPE3PuHcSMVZzfsLhGVOkfd20obL5SWEBew5ShlquNxg==", + "dev": true, + "license": "MIT", + "optionalDependencies": { + "graceful-fs": "^4.1.6" + } + }, + "node_modules/@changesets/read/node_modules/universalify": { + "version": "0.1.2", + "resolved": "https://registry.npmjs.org/universalify/-/universalify-0.1.2.tgz", + "integrity": "sha512-rBJeI5CXAlmy1pV+617WB9J63U6XcazHHF2f2dbJix4XzpUF0RS3Zbj0FGIOCAva5P/d/GBOYaACQ1w+0azUkg==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">= 4.0.0" + } + }, + "node_modules/@changesets/should-skip-package": { + "version": "0.1.2", + "resolved": "https://registry.npmjs.org/@changesets/should-skip-package/-/should-skip-package-0.1.2.tgz", + "integrity": "sha512-qAK/WrqWLNCP22UDdBTMPH5f41elVDlsNyat180A33dWxuUDyNpg6fPi/FyTZwRriVjg0L8gnjJn2F9XAoF0qw==", + "dev": true, + "license": "MIT", + "dependencies": { + "@changesets/types": "^6.1.0", + "@manypkg/get-packages": "^1.1.3" + } + }, + "node_modules/@changesets/types": { + "version": "6.1.0", + "resolved": "https://registry.npmjs.org/@changesets/types/-/types-6.1.0.tgz", + "integrity": "sha512-rKQcJ+o1nKNgeoYRHKOS07tAMNd3YSN0uHaJOZYjBAgxfV7TUE7JE+z4BzZdQwb5hKaYbayKN5KrYV7ODb2rAA==", + "dev": true, + "license": "MIT" + }, + "node_modules/@changesets/write": { + "version": "0.4.0", + "resolved": "https://registry.npmjs.org/@changesets/write/-/write-0.4.0.tgz", + "integrity": "sha512-CdTLvIOPiCNuH71pyDu3rA+Q0n65cmAbXnwWH84rKGiFumFzkmHNT8KHTMEchcxN+Kl8I54xGUhJ7l3E7X396Q==", + "dev": true, + "license": "MIT", + "dependencies": { + "@changesets/types": "^6.1.0", + "fs-extra": "^7.0.1", + "human-id": "^4.1.1", + "prettier": "^2.7.1" + } + }, + "node_modules/@changesets/write/node_modules/fs-extra": { + "version": "7.0.1", + "resolved": "https://registry.npmjs.org/fs-extra/-/fs-extra-7.0.1.tgz", + "integrity": "sha512-YJDaCJZEnBmcbw13fvdAM9AwNOJwOzrE4pqMqBq5nFiEqXUqHwlK4B+3pUw6JNvfSPtX05xFHtYy/1ni01eGCw==", + "dev": true, + "license": "MIT", + "dependencies": { + "graceful-fs": "^4.1.2", + "jsonfile": "^4.0.0", + "universalify": "^0.1.0" + }, + "engines": { + "node": ">=6 <7 || >=8" + } + }, + "node_modules/@changesets/write/node_modules/jsonfile": { + "version": "4.0.0", + "resolved": "https://registry.npmjs.org/jsonfile/-/jsonfile-4.0.0.tgz", + "integrity": "sha512-m6F1R3z8jjlf2imQHS2Qez5sjKWQzbuuhuJ/FKYFRZvPE3PuHcSMVZzfsLhGVOkfd20obL5SWEBew5ShlquNxg==", + "dev": true, + "license": "MIT", + "optionalDependencies": { + "graceful-fs": "^4.1.6" + } + }, + "node_modules/@changesets/write/node_modules/prettier": { + "version": "2.8.8", + "resolved": "https://registry.npmjs.org/prettier/-/prettier-2.8.8.tgz", + "integrity": "sha512-tdN8qQGvNjw4CHbY+XXk0JgCXn9QiF21a55rBe5LJAU+kDyC4WQn4+awm2Xfk2lQMk5fKup9XgzTZtGkjBdP9Q==", + "dev": true, + "license": "MIT", + "bin": { + "prettier": "bin-prettier.js" + }, + "engines": { + "node": ">=10.13.0" + }, + "funding": { + "url": "https://github.com/prettier/prettier?sponsor=1" + } + }, + "node_modules/@changesets/write/node_modules/universalify": { + "version": "0.1.2", + "resolved": "https://registry.npmjs.org/universalify/-/universalify-0.1.2.tgz", + "integrity": "sha512-rBJeI5CXAlmy1pV+617WB9J63U6XcazHHF2f2dbJix4XzpUF0RS3Zbj0FGIOCAva5P/d/GBOYaACQ1w+0azUkg==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">= 4.0.0" + } + }, "node_modules/@chevrotain/cst-dts-gen": { "version": "11.1.2", "resolved": "https://registry.npmjs.org/@chevrotain/cst-dts-gen/-/cst-dts-gen-11.1.2.tgz", @@ -14530,6 +15036,16 @@ "url": "https://github.com/chalk/ansi-styles?sponsor=1" } }, + "node_modules/@hiero-ledger/cryptography/node_modules/react": { + "version": "19.2.4", + "resolved": "https://registry.npmjs.org/react/-/react-19.2.4.tgz", + "integrity": "sha512-9nfp2hYpCwOjAN+8TZFGhtWEwgvWHXqESH8qT89AT/lWklpLON22Lc8pEtnpsZz7VmawabSU0gCjnj8aC0euHQ==", + "license": "MIT", + "peer": true, + "engines": { + "node": ">=0.10.0" + } + }, "node_modules/@hiero-ledger/cryptography/node_modules/react-native": { "version": "0.84.1", "resolved": "https://registry.npmjs.org/react-native/-/react-native-0.84.1.tgz", @@ -14612,6 +15128,16 @@ "node": ">=8" } }, + "node_modules/@hiero-ledger/cryptography/node_modules/react-refresh": { + "version": "0.14.2", + "resolved": "https://registry.npmjs.org/react-refresh/-/react-refresh-0.14.2.tgz", + "integrity": "sha512-jCvmsr+1IUSMUyzOkRcvnVbX3ZYC6g9TDrDbFuFmRDq7PD4yaGbLKNQL6k2jnArV8hjYxh7hVhAZB6s9HDGpZA==", + "license": "MIT", + "peer": true, + "engines": { + "node": ">=0.10.0" + } + }, "node_modules/@hiero-ledger/cryptography/node_modules/scheduler": { "version": "0.27.0", "resolved": "https://registry.npmjs.org/scheduler/-/scheduler-0.27.0.tgz", @@ -14807,6 +15333,16 @@ "url": "https://github.com/chalk/ansi-styles?sponsor=1" } }, + "node_modules/@hiero-ledger/sdk/node_modules/react": { + "version": "19.2.4", + "resolved": "https://registry.npmjs.org/react/-/react-19.2.4.tgz", + "integrity": "sha512-9nfp2hYpCwOjAN+8TZFGhtWEwgvWHXqESH8qT89AT/lWklpLON22Lc8pEtnpsZz7VmawabSU0gCjnj8aC0euHQ==", + "license": "MIT", + "peer": true, + "engines": { + "node": ">=0.10.0" + } + }, "node_modules/@hiero-ledger/sdk/node_modules/react-native": { "version": "0.84.1", "resolved": "https://registry.npmjs.org/react-native/-/react-native-0.84.1.tgz", @@ -14888,6 +15424,16 @@ "node": ">=8" } }, + "node_modules/@hiero-ledger/sdk/node_modules/react-refresh": { + "version": "0.14.2", + "resolved": "https://registry.npmjs.org/react-refresh/-/react-refresh-0.14.2.tgz", + "integrity": "sha512-jCvmsr+1IUSMUyzOkRcvnVbX3ZYC6g9TDrDbFuFmRDq7PD4yaGbLKNQL6k2jnArV8hjYxh7hVhAZB6s9HDGpZA==", + "license": "MIT", + "peer": true, + "engines": { + "node": ">=0.10.0" + } + }, "node_modules/@hiero-ledger/sdk/node_modules/scheduler": { "version": "0.27.0", "resolved": "https://registry.npmjs.org/scheduler/-/scheduler-0.27.0.tgz", @@ -16375,6 +16921,184 @@ "node": ">=8" } }, + "node_modules/@manypkg/find-root": { + "version": "1.1.0", + "resolved": "https://registry.npmjs.org/@manypkg/find-root/-/find-root-1.1.0.tgz", + "integrity": "sha512-mki5uBvhHzO8kYYix/WRy2WX8S3B5wdVSc9D6KcU5lQNglP2yt58/VfLuAK49glRXChosY8ap2oJ1qgma3GUVA==", + "dev": true, + "license": "MIT", + "dependencies": { + "@babel/runtime": "^7.5.5", + "@types/node": "^12.7.1", + "find-up": "^4.1.0", + "fs-extra": "^8.1.0" + } + }, + "node_modules/@manypkg/find-root/node_modules/@types/node": { + "version": "12.20.55", + "resolved": "https://registry.npmjs.org/@types/node/-/node-12.20.55.tgz", + "integrity": "sha512-J8xLz7q2OFulZ2cyGTLE1TbbZcjpno7FaN6zdJNrgAdrJ+DZzh/uFR6YrTb4C+nXakvud8Q4+rbhoIWlYQbUFQ==", + "dev": true, + "license": "MIT" + }, + "node_modules/@manypkg/find-root/node_modules/find-up": { + "version": "4.1.0", + "resolved": "https://registry.npmjs.org/find-up/-/find-up-4.1.0.tgz", + "integrity": "sha512-PpOwAdQ/YlXQ2vj8a3h8IipDuYRi3wceVQQGYWxNINccq40Anw7BlsEXCMbt1Zt+OLA6Fq9suIpIWD0OsnISlw==", + "dev": true, + "license": "MIT", + "dependencies": { + "locate-path": "^5.0.0", + "path-exists": "^4.0.0" + }, + "engines": { + "node": ">=8" + } + }, + "node_modules/@manypkg/find-root/node_modules/fs-extra": { + "version": "8.1.0", + "resolved": "https://registry.npmjs.org/fs-extra/-/fs-extra-8.1.0.tgz", + "integrity": "sha512-yhlQgA6mnOJUKOsRUFsgJdQCvkKhcz8tlZG5HBQfReYZy46OwLcY+Zia0mtdHsOo9y/hP+CxMN0TU9QxoOtG4g==", + "dev": true, + "license": "MIT", + "dependencies": { + "graceful-fs": "^4.2.0", + "jsonfile": "^4.0.0", + "universalify": "^0.1.0" + }, + "engines": { + "node": ">=6 <7 || >=8" + } + }, + "node_modules/@manypkg/find-root/node_modules/jsonfile": { + "version": "4.0.0", + "resolved": "https://registry.npmjs.org/jsonfile/-/jsonfile-4.0.0.tgz", + "integrity": "sha512-m6F1R3z8jjlf2imQHS2Qez5sjKWQzbuuhuJ/FKYFRZvPE3PuHcSMVZzfsLhGVOkfd20obL5SWEBew5ShlquNxg==", + "dev": true, + "license": "MIT", + "optionalDependencies": { + "graceful-fs": "^4.1.6" + } + }, + "node_modules/@manypkg/find-root/node_modules/locate-path": { + "version": "5.0.0", + "resolved": "https://registry.npmjs.org/locate-path/-/locate-path-5.0.0.tgz", + "integrity": "sha512-t7hw9pI+WvuwNJXwk5zVHpyhIqzg2qTlklJOf0mVxGSbe3Fp2VieZcduNYjaLDoy6p9uGpQEGWG87WpMKlNq8g==", + "dev": true, + "license": "MIT", + "dependencies": { + "p-locate": "^4.1.0" + }, + "engines": { + "node": ">=8" + } + }, + "node_modules/@manypkg/find-root/node_modules/p-limit": { + "version": "2.3.0", + "resolved": "https://registry.npmjs.org/p-limit/-/p-limit-2.3.0.tgz", + "integrity": "sha512-//88mFWSJx8lxCzwdAABTJL2MyWB12+eIY7MDL2SqLmAkeKU9qxRvWuSyTjm3FUmpBEMuFfckAIqEaVGUDxb6w==", + "dev": true, + "license": "MIT", + "dependencies": { + "p-try": "^2.0.0" + }, + "engines": { + "node": ">=6" + }, + "funding": { + "url": "https://github.com/sponsors/sindresorhus" + } + }, + "node_modules/@manypkg/find-root/node_modules/p-locate": { + "version": "4.1.0", + "resolved": "https://registry.npmjs.org/p-locate/-/p-locate-4.1.0.tgz", + "integrity": "sha512-R79ZZ/0wAxKGu3oYMlz8jy/kbhsNrS7SKZ7PxEHBgJ5+F2mtFW2fK2cOtBh1cHYkQsbzFV7I+EoRKe6Yt0oK7A==", + "dev": true, + "license": "MIT", + "dependencies": { + "p-limit": "^2.2.0" + }, + "engines": { + "node": ">=8" + } + }, + "node_modules/@manypkg/find-root/node_modules/path-exists": { + "version": "4.0.0", + "resolved": "https://registry.npmjs.org/path-exists/-/path-exists-4.0.0.tgz", + "integrity": "sha512-ak9Qy5Q7jYb2Wwcey5Fpvg2KoAc/ZIhLSLOSBmRmygPsGwkVVt0fZa0qrtMz+m6tJTAHfZQ8FnmB4MG4LWy7/w==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">=8" + } + }, + "node_modules/@manypkg/find-root/node_modules/universalify": { + "version": "0.1.2", + "resolved": "https://registry.npmjs.org/universalify/-/universalify-0.1.2.tgz", + "integrity": "sha512-rBJeI5CXAlmy1pV+617WB9J63U6XcazHHF2f2dbJix4XzpUF0RS3Zbj0FGIOCAva5P/d/GBOYaACQ1w+0azUkg==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">= 4.0.0" + } + }, + "node_modules/@manypkg/get-packages": { + "version": "1.1.3", + "resolved": "https://registry.npmjs.org/@manypkg/get-packages/-/get-packages-1.1.3.tgz", + "integrity": "sha512-fo+QhuU3qE/2TQMQmbVMqaQ6EWbMhi4ABWP+O4AM1NqPBuy0OrApV5LO6BrrgnhtAHS2NH6RrVk9OL181tTi8A==", + "dev": true, + "license": "MIT", + "dependencies": { + "@babel/runtime": "^7.5.5", + "@changesets/types": "^4.0.1", + "@manypkg/find-root": "^1.1.0", + "fs-extra": "^8.1.0", + "globby": "^11.0.0", + "read-yaml-file": "^1.1.0" + } + }, + "node_modules/@manypkg/get-packages/node_modules/@changesets/types": { + "version": "4.1.0", + "resolved": "https://registry.npmjs.org/@changesets/types/-/types-4.1.0.tgz", + "integrity": "sha512-LDQvVDv5Kb50ny2s25Fhm3d9QSZimsoUGBsUioj6MC3qbMUCuC8GPIvk/M6IvXx3lYhAs0lwWUQLb+VIEUCECw==", + "dev": true, + "license": "MIT" + }, + "node_modules/@manypkg/get-packages/node_modules/fs-extra": { + "version": "8.1.0", + "resolved": "https://registry.npmjs.org/fs-extra/-/fs-extra-8.1.0.tgz", + "integrity": "sha512-yhlQgA6mnOJUKOsRUFsgJdQCvkKhcz8tlZG5HBQfReYZy46OwLcY+Zia0mtdHsOo9y/hP+CxMN0TU9QxoOtG4g==", + "dev": true, + "license": "MIT", + "dependencies": { + "graceful-fs": "^4.2.0", + "jsonfile": "^4.0.0", + "universalify": "^0.1.0" + }, + "engines": { + "node": ">=6 <7 || >=8" + } + }, + "node_modules/@manypkg/get-packages/node_modules/jsonfile": { + "version": "4.0.0", + "resolved": "https://registry.npmjs.org/jsonfile/-/jsonfile-4.0.0.tgz", + "integrity": "sha512-m6F1R3z8jjlf2imQHS2Qez5sjKWQzbuuhuJ/FKYFRZvPE3PuHcSMVZzfsLhGVOkfd20obL5SWEBew5ShlquNxg==", + "dev": true, + "license": "MIT", + "optionalDependencies": { + "graceful-fs": "^4.1.6" + } + }, + "node_modules/@manypkg/get-packages/node_modules/universalify": { + "version": "0.1.2", + "resolved": "https://registry.npmjs.org/universalify/-/universalify-0.1.2.tgz", + "integrity": "sha512-rBJeI5CXAlmy1pV+617WB9J63U6XcazHHF2f2dbJix4XzpUF0RS3Zbj0FGIOCAva5P/d/GBOYaACQ1w+0azUkg==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">= 4.0.0" + } + }, "node_modules/@mdx-js/mdx": { "version": "3.1.1", "resolved": "https://registry.npmjs.org/@mdx-js/mdx/-/mdx-3.1.1.tgz", @@ -23782,33 +24506,6 @@ "react-popper": "^2.2.5" } }, - "node_modules/@types/react-datepicker/node_modules/react": { - "version": "18.3.1", - "resolved": "https://registry.npmjs.org/react/-/react-18.3.1.tgz", - "integrity": "sha512-wS+hAgJShR0KhEvPJArfuPVN1+Hz1t0Y6n5jLrGQbkb4urgPE/0Rve+1kMB1v/oWgHgm4WIcV+i7F2pTVj+2iQ==", - "license": "MIT", - "peer": true, - "dependencies": { - "loose-envify": "^1.1.0" - }, - "engines": { - "node": ">=0.10.0" - } - }, - "node_modules/@types/react-datepicker/node_modules/react-dom": { - "version": "18.3.1", - "resolved": "https://registry.npmjs.org/react-dom/-/react-dom-18.3.1.tgz", - "integrity": "sha512-5m4nQKp+rZRb09LNH59GM4BxTh9251/ylbKIbpe7TpGxfJ+9kv6BLkLBXIjjspbgbnIBNqlI23tRnTWT0snUIw==", - "license": "MIT", - "peer": true, - "dependencies": { - "loose-envify": "^1.1.0", - "scheduler": "^0.23.2" - }, - "peerDependencies": { - "react": "^18.3.1" - } - }, "node_modules/@types/react-datepicker/node_modules/react-popper": { "version": "2.3.0", "resolved": "https://registry.npmjs.org/react-popper/-/react-popper-2.3.0.tgz", @@ -24737,22 +25434,6 @@ } } }, - "node_modules/@typescript-eslint/typescript-estree/node_modules/minimatch": { - "version": "9.0.9", - "resolved": "https://registry.npmjs.org/minimatch/-/minimatch-9.0.9.tgz", - "integrity": "sha512-OBwBN9AL4dqmETlpS2zasx+vTeWclWzkblfZk7KTA5j3jeOONz/tRCnZomUyvNg83wL5Zv9Ss6HMJXAgL8R2Yg==", - "dev": true, - "license": "ISC", - "dependencies": { - "brace-expansion": "^2.0.1" - }, - "engines": { - "node": ">=16 || 14 >=14.17" - }, - "funding": { - "url": "https://github.com/sponsors/isaacs" - } - }, "node_modules/@typescript-eslint/utils": { "version": "6.21.0", "resolved": "https://registry.npmjs.org/@typescript-eslint/utils/-/utils-6.21.0.tgz", @@ -25542,19 +26223,6 @@ "license": "MIT", "peer": true }, - "node_modules/@walletconnect/modal-core/node_modules/react": { - "version": "18.3.1", - "resolved": "https://registry.npmjs.org/react/-/react-18.3.1.tgz", - "integrity": "sha512-wS+hAgJShR0KhEvPJArfuPVN1+Hz1t0Y6n5jLrGQbkb4urgPE/0Rve+1kMB1v/oWgHgm4WIcV+i7F2pTVj+2iQ==", - "license": "MIT", - "peer": true, - "dependencies": { - "loose-envify": "^1.1.0" - }, - "engines": { - "node": ">=0.10.0" - } - }, "node_modules/@walletconnect/modal-core/node_modules/use-sync-external-store": { "version": "1.2.0", "resolved": "https://registry.npmjs.org/use-sync-external-store/-/use-sync-external-store-1.2.0.tgz", @@ -28800,6 +29468,19 @@ "integrity": "sha512-LcknSilhIGatDAsY1ak2I8VtGaHNhgMSYVxFrGLXv+xLHytaKZKcaUJJUE7qmBr7h33o5YQwP55pMI0xmkpJwg==", "license": "MIT" }, + "node_modules/better-path-resolve": { + "version": "1.0.0", + "resolved": "https://registry.npmjs.org/better-path-resolve/-/better-path-resolve-1.0.0.tgz", + "integrity": "sha512-pbnl5XzGBdrFU/wT4jqmJVPn2B6UHPBOhzMQkY/SPUPB6QtUXtmBHBIwCbXJol93mOpGMnQyP/+BB19q04xj7g==", + "dev": true, + "license": "MIT", + "dependencies": { + "is-windows": "^1.0.0" + }, + "engines": { + "node": ">=4" + } + }, "node_modules/bfj": { "version": "9.1.3", "resolved": "https://registry.npmjs.org/bfj/-/bfj-9.1.3.tgz", @@ -33846,6 +34527,16 @@ "integrity": "sha512-53rsFbGdwMwlF7qvCt0ypLM5V5/Mbl0szB7GPN8y9NCcbknYOeVVXdrXEq+90IwAfrrzt6Hd+u2E2ntakICU8w==", "license": "MIT" }, + "node_modules/detect-indent": { + "version": "6.1.0", + "resolved": "https://registry.npmjs.org/detect-indent/-/detect-indent-6.1.0.tgz", + "integrity": "sha512-reYkTUJAZb9gUuZ2RvVCNhVHdg62RHnJ7WJl8ftMi4diZ6NWlciOzQN88pUhSELEwflJht4oQDv0F0BMlwaYtA==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">=8" + } + }, "node_modules/detect-newline": { "version": "3.1.0", "resolved": "https://registry.npmjs.org/detect-newline/-/detect-newline-3.1.0.tgz", @@ -34400,16 +35091,6 @@ "node": ">= 0.8" } }, - "node_modules/encoding": { - "version": "0.1.13", - "resolved": "https://registry.npmjs.org/encoding/-/encoding-0.1.13.tgz", - "integrity": "sha512-ETBauow1T35Y/WZMkio9jiM0Z5xjHHmJ4XmjZOq1l/dXz3lr2sRn87nJy20RupqSh1F2m3HHPSp8ShIPQJrJ3A==", - "license": "MIT", - "optional": true, - "dependencies": { - "iconv-lite": "^0.6.2" - } - }, "node_modules/encoding-sniffer": { "version": "0.2.1", "resolved": "https://registry.npmjs.org/encoding-sniffer/-/encoding-sniffer-0.2.1.tgz", @@ -34448,19 +35129,6 @@ "node": ">=18" } }, - "node_modules/encoding/node_modules/iconv-lite": { - "version": "0.6.3", - "resolved": "https://registry.npmjs.org/iconv-lite/-/iconv-lite-0.6.3.tgz", - "integrity": "sha512-4fCk79wshMdzMp2rH06qWrJE4iolqLhCUH+OiuIgU++RB0+94NlDL81atO7GX55uUKueo0txHNtvEyI6D7WdMw==", - "license": "MIT", - "optional": true, - "dependencies": { - "safer-buffer": ">= 2.1.2 < 3.0.0" - }, - "engines": { - "node": ">=0.10.0" - } - }, "node_modules/end-of-stream": { "version": "1.4.5", "resolved": "https://registry.npmjs.org/end-of-stream/-/end-of-stream-1.4.5.tgz", @@ -37025,6 +37693,13 @@ "node": ">=0.10.0" } }, + "node_modules/extendable-error": { + "version": "0.1.7", + "resolved": "https://registry.npmjs.org/extendable-error/-/extendable-error-0.1.7.tgz", + "integrity": "sha512-UOiS2in6/Q0FK0R0q6UY9vYpQ21mr/Qn1KOnte7vsACuNJf514WvCCUHSRCPcgjPT2bAhNIJdlE6bVap1GKmeg==", + "dev": true, + "license": "MIT" + }, "node_modules/extension-port-stream": { "version": "2.1.1", "resolved": "https://registry.npmjs.org/extension-port-stream/-/extension-port-stream-2.1.1.tgz", @@ -40291,6 +40966,16 @@ "node": ">= 6" } }, + "node_modules/human-id": { + "version": "4.1.3", + "resolved": "https://registry.npmjs.org/human-id/-/human-id-4.1.3.tgz", + "integrity": "sha512-tsYlhAYpjCKa//8rXZ9DqKEawhPoSytweBC2eNvcaDK+57RZLHGqNs3PZTQO6yekLFSuvA6AlnAfrw1uBvtb+Q==", + "dev": true, + "license": "MIT", + "bin": { + "human-id": "dist/cli.js" + } + }, "node_modules/human-signals": { "version": "2.1.0", "resolved": "https://registry.npmjs.org/human-signals/-/human-signals-2.1.0.tgz", @@ -41517,6 +42202,19 @@ "url": "https://github.com/sponsors/ljharb" } }, + "node_modules/is-subdir": { + "version": "1.2.0", + "resolved": "https://registry.npmjs.org/is-subdir/-/is-subdir-1.2.0.tgz", + "integrity": "sha512-2AT6j+gXe/1ueqbW6fLZJiIw3F8iXGJtt0yDrZaBhAZEG1raiTxKWU+IPqMCzQAXOUCKdA4UDMgacKH25XG2Cw==", + "dev": true, + "license": "MIT", + "dependencies": { + "better-path-resolve": "1.0.0" + }, + "engines": { + "node": ">=4" + } + }, "node_modules/is-symbol": { "version": "1.1.1", "resolved": "https://registry.npmjs.org/is-symbol/-/is-symbol-1.1.1.tgz", @@ -41623,6 +42321,16 @@ "url": "https://github.com/sponsors/ljharb" } }, + "node_modules/is-windows": { + "version": "1.0.2", + "resolved": "https://registry.npmjs.org/is-windows/-/is-windows-1.0.2.tgz", + "integrity": "sha512-eXK1UInq2bPmjyX6e3VHIzMLobc4J94i4AWn+Hpq3OU5KkrRC96OAcR3PRJ/pGu6m8TRnBHP9dkXQVsT/COVIA==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">=0.10.0" + } + }, "node_modules/is-wsl": { "version": "2.2.0", "resolved": "https://registry.npmjs.org/is-wsl/-/is-wsl-2.2.0.tgz", @@ -47012,7 +47720,16 @@ "resolved": "https://registry.npmjs.org/hermes-estree/-/hermes-estree-0.33.3.tgz", "integrity": "sha512-6kzYZHCk8Fy1Uc+t3HGYyJn3OL4aeqKLTyina4UFtWl8I0kSL7OmKThaiX+Uh2f8nGw3mo4Ifxg0M5Zk3/Oeqg==", "license": "MIT", - "peer": true + "peer": true, + "dependencies": { + "queue": "6.0.2" + }, + "bin": { + "image-size": "bin/image-size.js" + }, + "engines": { + "node": ">=16.x" + } }, "node_modules/metro/node_modules/hermes-parser": { "version": "0.33.3", @@ -49599,6 +50316,16 @@ "license": "MIT", "peer": true }, + "node_modules/mri": { + "version": "1.2.0", + "resolved": "https://registry.npmjs.org/mri/-/mri-1.2.0.tgz", + "integrity": "sha512-tzzskb3bG8LvYGFF/mDTpq3jpI6Q9wc3LEmBaghu+DdCssd1FakN7Bc0hVNmEyGq1bq3RgfkCb3cmQLpNPOroA==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">=4" + } + }, "node_modules/mrmime": { "version": "2.0.1", "resolved": "https://registry.npmjs.org/mrmime/-/mrmime-2.0.1.tgz", @@ -50620,6 +51347,13 @@ "node": ">=0.10.0" } }, + "node_modules/outdent": { + "version": "0.5.0", + "resolved": "https://registry.npmjs.org/outdent/-/outdent-0.5.0.tgz", + "integrity": "sha512-/jHxFIzoMXdqPzTaCpFzAAWhpkSjZPF4Vsn6jAfNpmbH/ymsmd7Qc6VE9BGn0L6YMj6uwpQLxCECpus4ukKS9Q==", + "dev": true, + "license": "MIT" + }, "node_modules/own-keys": { "version": "1.0.1", "resolved": "https://registry.npmjs.org/own-keys/-/own-keys-1.0.1.tgz", @@ -50763,6 +51497,29 @@ "node": ">=12.20" } }, + "node_modules/p-filter": { + "version": "2.1.0", + "resolved": "https://registry.npmjs.org/p-filter/-/p-filter-2.1.0.tgz", + "integrity": "sha512-ZBxxZ5sL2HghephhpGAQdoskxplTwr7ICaehZwLIlfL6acuVgZPm8yBNuRAFBGEqtD/hmUeq9eqLg2ys9Xr/yw==", + "dev": true, + "license": "MIT", + "dependencies": { + "p-map": "^2.0.0" + }, + "engines": { + "node": ">=8" + } + }, + "node_modules/p-filter/node_modules/p-map": { + "version": "2.1.0", + "resolved": "https://registry.npmjs.org/p-map/-/p-map-2.1.0.tgz", + "integrity": "sha512-y3b8Kpd8OAN444hxfBbFfj1FY/RjtTd8tzYwhUqNYXx0fXx2iX4maP4Qr6qhIKbQXI02wTLAda4fYUbDagTUFw==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">=6" + } + }, "node_modules/p-finally": { "version": "1.0.0", "resolved": "https://registry.npmjs.org/p-finally/-/p-finally-1.0.0.tgz", @@ -53862,6 +54619,23 @@ "url": "https://github.com/sponsors/ljharb" } }, + "node_modules/quansync": { + "version": "0.2.11", + "resolved": "https://registry.npmjs.org/quansync/-/quansync-0.2.11.tgz", + "integrity": "sha512-AifT7QEbW9Nri4tAwR5M/uzpBuqfZf+zwaEM/QkzEjj7NBuFD2rBuy0K3dE+8wltbezDV7JMA0WfnCPYRSYbXA==", + "dev": true, + "funding": [ + { + "type": "individual", + "url": "https://github.com/sponsors/antfu" + }, + { + "type": "individual", + "url": "https://github.com/sponsors/sxzz" + } + ], + "license": "MIT" + }, "node_modules/query-string": { "version": "7.1.3", "resolved": "https://registry.npmjs.org/query-string/-/query-string-7.1.3.tgz", @@ -54024,10 +54798,13 @@ } }, "node_modules/react": { - "version": "19.2.3", - "resolved": "https://registry.npmjs.org/react/-/react-19.2.3.tgz", - "integrity": "sha512-Ku/hhYbVjOQnXDZFv2+RibmLFGwFdeeKHFcOTlrt7xplBnya5OGn/hIRDsqDiSUcfORsDC7MPxwork8jBwsIWA==", + "version": "18.3.1", + "resolved": "https://registry.npmjs.org/react/-/react-18.3.1.tgz", + "integrity": "sha512-wS+hAgJShR0KhEvPJArfuPVN1+Hz1t0Y6n5jLrGQbkb4urgPE/0Rve+1kMB1v/oWgHgm4WIcV+i7F2pTVj+2iQ==", "license": "MIT", + "dependencies": { + "loose-envify": "^1.1.0" + }, "engines": { "node": ">=0.10.0" } @@ -54402,23 +55179,18 @@ } }, "node_modules/react-dom": { - "version": "19.2.3", - "resolved": "https://registry.npmjs.org/react-dom/-/react-dom-19.2.3.tgz", - "integrity": "sha512-yELu4WmLPw5Mr/lmeEpox5rw3RETacE++JgHqQzd2dg+YbJuat3jH4ingc+WPZhxaoFzdv9y33G+F7Nl5O0GBg==", + "version": "18.3.1", + "resolved": "https://registry.npmjs.org/react-dom/-/react-dom-18.3.1.tgz", + "integrity": "sha512-5m4nQKp+rZRb09LNH59GM4BxTh9251/ylbKIbpe7TpGxfJ+9kv6BLkLBXIjjspbgbnIBNqlI23tRnTWT0snUIw==", "license": "MIT", "dependencies": { - "scheduler": "^0.27.0" + "loose-envify": "^1.1.0", + "scheduler": "^0.23.2" }, "peerDependencies": { - "react": "^19.2.3" + "react": "^18.3.1" } }, - "node_modules/react-dom/node_modules/scheduler": { - "version": "0.27.0", - "resolved": "https://registry.npmjs.org/scheduler/-/scheduler-0.27.0.tgz", - "integrity": "sha512-eNv+WrVbKu1f3vbYJT/xtiF5syA5HPIMtf9IgY/nKg0sWqzAUEvqY/xm7OcZc/qafLx/iO9FgOmeSAp4v5ti/Q==", - "license": "MIT" - }, "node_modules/react-error-overlay": { "version": "6.1.0", "resolved": "https://registry.npmjs.org/react-error-overlay/-/react-error-overlay-6.1.0.tgz", @@ -54732,6 +55504,56 @@ "node": ">=0.10.0" } }, + "node_modules/read-yaml-file": { + "version": "1.1.0", + "resolved": "https://registry.npmjs.org/read-yaml-file/-/read-yaml-file-1.1.0.tgz", + "integrity": "sha512-VIMnQi/Z4HT2Fxuwg5KrY174U1VdUIASQVWXXyqtNRtxSr9IYkn1rsI6Tb6HsrHCmB7gVpNwX6JxPTHcH6IoTA==", + "dev": true, + "license": "MIT", + "dependencies": { + "graceful-fs": "^4.1.5", + "js-yaml": "^3.6.1", + "pify": "^4.0.1", + "strip-bom": "^3.0.0" + }, + "engines": { + "node": ">=6" + } + }, + "node_modules/read-yaml-file/node_modules/argparse": { + "version": "1.0.10", + "resolved": "https://registry.npmjs.org/argparse/-/argparse-1.0.10.tgz", + "integrity": "sha512-o5Roy6tNG4SL/FOkCAN6RzjiakZS25RLYFrcMttJqbdd8BWrnA+fGz57iN5Pb06pvBGvl5gQ0B48dJlslXvoTg==", + "dev": true, + "license": "MIT", + "dependencies": { + "sprintf-js": "~1.0.2" + } + }, + "node_modules/read-yaml-file/node_modules/js-yaml": { + "version": "3.14.2", + "resolved": "https://registry.npmjs.org/js-yaml/-/js-yaml-3.14.2.tgz", + "integrity": "sha512-PMSmkqxr106Xa156c2M265Z+FTrPl+oxd/rgOQy2tijQeK5TxQ43psO1ZCwhVOSdnn+RzkzlRz/eY4BgJBYVpg==", + "dev": true, + "license": "MIT", + "dependencies": { + "argparse": "^1.0.7", + "esprima": "^4.0.0" + }, + "bin": { + "js-yaml": "bin/js-yaml.js" + } + }, + "node_modules/read-yaml-file/node_modules/strip-bom": { + "version": "3.0.0", + "resolved": "https://registry.npmjs.org/strip-bom/-/strip-bom-3.0.0.tgz", + "integrity": "sha512-vavAMRXOgBVNF6nyEEmL3DBK19iRpDcoIwW+swQ+CbGiu7lju6t+JklA1MHweoWtadgt4ISVUsXLyDq34ddcwA==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">=4" + } + }, "node_modules/readable-stream": { "version": "2.3.8", "resolved": "https://registry.npmjs.org/readable-stream/-/readable-stream-2.3.8.tgz", @@ -57721,6 +58543,30 @@ "integrity": "sha512-zC8zGoGkmc8J9ndvml8Xksr1Amk9qBujgbF0JAIWO7kXr43w0h/0GJNM/Vustixu+YE8N/MTrQ7N31FvHUACxQ==", "dev": true }, + "node_modules/spawndamnit": { + "version": "3.0.1", + "resolved": "https://registry.npmjs.org/spawndamnit/-/spawndamnit-3.0.1.tgz", + "integrity": "sha512-MmnduQUuHCoFckZoWnXsTg7JaiLBJrKFj9UI2MbRPGaJeVpsLcVBu6P/IGZovziM/YBsellCmsprgNA+w0CzVg==", + "dev": true, + "license": "SEE LICENSE IN LICENSE", + "dependencies": { + "cross-spawn": "^7.0.5", + "signal-exit": "^4.0.1" + } + }, + "node_modules/spawndamnit/node_modules/signal-exit": { + "version": "4.1.0", + "resolved": "https://registry.npmjs.org/signal-exit/-/signal-exit-4.1.0.tgz", + "integrity": "sha512-bzyZ1e88w9O1iNJbKnOlvYTrWPDl46O1bG0D3XInv+9tkPrxrN8jUUTiFlDkkmKWgn1M6CfIA13SuGqOa9Korw==", + "dev": true, + "license": "ISC", + "engines": { + "node": ">=14" + }, + "funding": { + "url": "https://github.com/sponsors/isaacs" + } + }, "node_modules/spdy": { "version": "4.0.2", "resolved": "https://registry.npmjs.org/spdy/-/spdy-4.0.2.tgz", @@ -59172,6 +60018,19 @@ "node": ">=8" } }, + "node_modules/term-size": { + "version": "2.2.1", + "resolved": "https://registry.npmjs.org/term-size/-/term-size-2.2.1.tgz", + "integrity": "sha512-wK0Ri4fOGjv/XPy8SBHZChl8CM7uMc5VML7SqiQ0zG7+J5Vr+RMQDoHa2CNT6KHUnTGIXH34UDMkPzAUyapBZg==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">=8" + }, + "funding": { + "url": "https://github.com/sponsors/sindresorhus" + } + }, "node_modules/terminal-link": { "version": "2.1.1", "resolved": "https://registry.npmjs.org/terminal-link/-/terminal-link-2.1.1.tgz", @@ -63322,7 +64181,7 @@ }, "packages/contracts": { "name": "@hashgraph/stablecoin-npm-contracts", - "version": "4.2.0", + "version": "4.3.0", "license": "Apache-2.0", "dependencies": { "commit-msg": "0.2.3" @@ -63384,7 +64243,7 @@ }, "packages/sdk": { "name": "@hashgraph/stablecoin-npm-sdk", - "version": "4.2.0", + "version": "4.3.0", "license": "Apache-2.0", "dependencies": { "@hashgraph/cryptography": "1.4.3", diff --git a/package.json b/package.json index f0f2da1d6..190e0f2cc 100644 --- a/package.json +++ b/package.json @@ -1,6 +1,6 @@ { "name": "@hashgraph/hedera-stable-coin", - "version": "4.2.0", + "version": "4.3.0", "private": true, "description": "Stablecoin studio", "keywords": [ @@ -64,16 +64,21 @@ "commitlint": "commitlint --edit" }, "devDependencies": { + "@changesets/cli": "2.29.8", "@commitlint/cli": "19.8.0", "@commitlint/config-conventional": "19.8.0", "concurrently": "7.6.0", "dotenv": "16.4.7", + "react": "18.3.1", + "react-dom": "18.3.1", "rimraf": "4.4.1" }, "dependencies": { "husky": "9.1.7" }, "overrides": { + "react": "18.3.1", + "react-dom": "18.3.1", "react-refresh": "0.11.0", "webpack": "5.104.1", "bfj": "9.1.3", @@ -102,4 +107,4 @@ "packages/*", "apps/*" ] -} \ No newline at end of file +} diff --git a/packages/contracts/.prettierignore b/packages/contracts/.prettierignore index 39490ecd3..5ef9a43b9 100644 --- a/packages/contracts/.prettierignore +++ b/packages/contracts/.prettierignore @@ -7,7 +7,7 @@ node_modules/ package.json typechain-types/ .gitlab-ci.yml -.env.sample +.env.example build package.json *.md \ No newline at end of file diff --git a/packages/contracts/package.json b/packages/contracts/package.json index b49a093c3..4fdb04634 100644 --- a/packages/contracts/package.json +++ b/packages/contracts/package.json @@ -1,6 +1,6 @@ { "name": "@hashgraph/stablecoin-npm-contracts", - "version": "4.2.0", + "version": "4.3.0", "description": "", "main": "./build/typechain-types/index.js", "module": "./build/typechain-types/index.js", @@ -16,6 +16,10 @@ "import": "./build/typechain-types/index.js", "require": "./build/typechain-types/index.js" }, + "./typechain-types/factories/contracts": { + "import": "./build/typechain-types/factories/contracts/index.js", + "require": "./build/typechain-types/factories/contracts/index.js" + }, "./typechain-types/*": { "import": "./build/typechain-types/*", "require": "./build/typechain-types/*" diff --git a/packages/sdk/__mocks__/fireblocks-sdk.js b/packages/sdk/__mocks__/fireblocks-sdk.js new file mode 100644 index 000000000..510e4c75f --- /dev/null +++ b/packages/sdk/__mocks__/fireblocks-sdk.js @@ -0,0 +1,2 @@ +// __mocks__/fireblocks-sdk.js +module.exports = {}; diff --git a/packages/sdk/example/.env.sample b/packages/sdk/example/.env.example similarity index 94% rename from packages/sdk/example/.env.sample rename to packages/sdk/example/.env.example index e89869a1a..dcafa8d21 100644 --- a/packages/sdk/example/.env.sample +++ b/packages/sdk/example/.env.example @@ -6,4 +6,4 @@ FACTORY_ADDRESS='0.0.XXXX' # Optional: provide an existing token ID to skip creation in testExternalEVM TOKEN_ID='' # Optional: provide an existing token ID to skip creation in testExternalHedera -TOKEN_ID_HEDERA='' \ No newline at end of file +TOKEN_ID_HEDERA='' diff --git a/packages/sdk/example/ts/burn.ts b/packages/sdk/example/ts/burn.ts new file mode 100644 index 000000000..19dda032b --- /dev/null +++ b/packages/sdk/example/ts/burn.ts @@ -0,0 +1,166 @@ +import { + Network, + InitializationRequest, + CreateRequest, + ConnectRequest, + SupportedWallets, + TokenSupplyType, + StableCoin, + AssociateTokenRequest, + GetStableCoinDetailsRequest, + BurnRequest, + BigDecimal, +} from '@hashgraph/stablecoin-npm-sdk'; +import assert from 'node:assert'; + +// Load environment variables from .env file +console.log('__dirname:', __dirname); +require('dotenv').config({ path: __dirname + '/../../.env' }); + +const mirrorNodeConfig = { + name: 'Testnet Mirror Node', + network: 'testnet', + baseUrl: 'https://testnet.mirrornode.hedera.com/api/v1/', + apiKey: '', + headerName: '', + selected: true, +}; +const RPCNodeConfig = { + name: 'HashIO', + network: 'testnet', + baseUrl: 'https://testnet.hashio.io/api', + apiKey: '', + headerName: '', + selected: true, +}; + +const main = async () => { + // Initialize the network and connect to the Hedera testnet + await Network.init( + new InitializationRequest({ + network: 'testnet', + mirrorNode: mirrorNodeConfig, + rpcNode: RPCNodeConfig, + configuration: { + factoryAddress: process.env.FACTORY_ADDRESS!, + resolverAddress: process.env.RESOLVER_ADDRESS!, + }, + }), + ); + + // Define the account details for connecting to the network + const account = { + accountId: process.env.MY_ACCOUNT_ID!, + privateKey: { + key: process.env.MY_PRIVATE_KEY!, + type: 'ED25519', + }, + }; + + // Connect to the network using the provided account + await Network.connect( + new ConnectRequest({ + account: account, + network: 'testnet', + mirrorNode: mirrorNodeConfig, + rpcNode: RPCNodeConfig, + wallet: SupportedWallets.CLIENT, + }), + ); + + // Create a new stablecoin with the specified parameters + const request = new CreateRequest({ + name: 'test', + symbol: 'test', + decimals: 6, + initialSupply: '1000', + freezeKey: { + key: 'null', + type: 'null', + }, + kycKey: { + key: 'null', + type: 'null', + }, + wipeKey: { + key: 'null', + type: 'null', + }, + pauseKey: { + key: 'null', + type: 'null', + }, + feeScheduleKey: { + key: 'null', + type: 'null', + }, + supplyType: TokenSupplyType.INFINITE, + createReserve: false, + grantKYCToOriginalSender: true, + burnRoleAccount: account.accountId.toString(), + wipeRoleAccount: account.accountId.toString(), + rescueRoleAccount: account.accountId.toString(), + pauseRoleAccount: account.accountId.toString(), + freezeRoleAccount: account.accountId.toString(), + deleteRoleAccount: account.accountId.toString(), + kycRoleAccount: account.accountId.toString(), + cashInRoleAccount: account.accountId.toString(), + feeRoleAccount: account.accountId.toString(), + cashInRoleAllowance: '10', + proxyOwnerAccount: account.accountId.toString(), + configId: + '0x0000000000000000000000000000000000000000000000000000000000000002', + configVersion: 1, + }); + + // Create the stablecoin and log the result + const stableCoin = (await StableCoin.create(request)) as { coin: any; reserve: any }; + console.log('StableCoin created:', stableCoin); + + // Associate the stablecoin with the account + await StableCoin.associate( + new AssociateTokenRequest({ + targetId: account.accountId.toString(), + tokenId: stableCoin?.coin?.tokenId?.toString()!, + }), + ); + + //Get the token info before burn + const tokenInfo = await StableCoin.getInfo( + new GetStableCoinDetailsRequest({ + id: stableCoin?.coin?.tokenId?.toString()!, + }), + ); + + // Perform burn operation + await StableCoin.burn( + new BurnRequest({ + amount: '10', + tokenId: stableCoin?.coin?.tokenId?.toString()!, + }), + ); + + await new Promise((resolve) => setTimeout(resolve, 5000)); + + //After burn, get the token info again + const tokenInfoAfterBurn = await StableCoin.getInfo( + new GetStableCoinDetailsRequest({ + id: stableCoin?.coin?.tokenId?.toString()!, + }), + ); + const final = + tokenInfo.totalSupply!.toBigInt() - new BigDecimal('10', 6).toBigInt(); + + assert( + tokenInfoAfterBurn.totalSupply!.toBigInt().toString() === + final.toString(), + 'Burn operation failed: totalSupply mismatch', + ); + process.exit(0); +}; + +try { + main(); +} catch (error) { + console.error(error); +} diff --git a/packages/sdk/example/ts/createDFNSMultisigAccount.ts b/packages/sdk/example/ts/createDFNSMultisigAccount.ts new file mode 100644 index 000000000..227a85a4f --- /dev/null +++ b/packages/sdk/example/ts/createDFNSMultisigAccount.ts @@ -0,0 +1,84 @@ +/** + * Creates a Hedera multisig account whose KeyList includes the DFNS wallet + * public key (+ optionally the deployer key as a co-signer). + * + * The account is created with a 1-of-2 threshold so DFNS alone can sign, + * but the deployer can also sign independently (useful for recovery). + * Change `threshold` to 2 if you need both keys to sign every transaction. + * + * HOW TO RUN: + * npm run create-dfns-multisig-account + * + * After it prints the new account ID, set it in your .env: + * DFNS_MULTISIG_ACCOUNT_ID=0.0.XXXX + */ + +import { + AccountCreateTransaction, + AccountId, + Client, + Hbar, + KeyList, + PrivateKey, + PublicKey, +} from '@hiero-ledger/sdk'; + +require('dotenv').config({ path: __dirname + '/../../.env' }); + +const DEPLOYER_ACCOUNT_ID = process.env.MY_ACCOUNT_ID!; +const DEPLOYER_PRIVATE_KEY = process.env.MY_PRIVATE_KEY_ECDSA!; +const DFNS_WALLET_PUBLIC_KEY = process.env.DFNS_WALLET_PUBLIC_KEY!; + +const CONSENSUS_NODE_URL = process.env.CONSENSUS_NODE_URL ?? '34.94.106.61:50211'; +const CONSENSUS_NODE_ID = process.env.CONSENSUS_NODE_ID ?? '0.0.3'; + +// How many keys must sign: 1 = DFNS alone is enough, 2 = both must sign. +const THRESHOLD = 1; + +async function main(): Promise { + if (!DEPLOYER_ACCOUNT_ID || !DEPLOYER_PRIVATE_KEY) { + throw new Error('MY_ACCOUNT_ID and MY_PRIVATE_KEY_ECDSA must be set in .env'); + } + if (!DFNS_WALLET_PUBLIC_KEY) { + throw new Error('DFNS_WALLET_PUBLIC_KEY must be set in .env'); + } + + const deployerPrivKey = PrivateKey.fromStringECDSA(DEPLOYER_PRIVATE_KEY); + const dfnsPublicKey = PublicKey.fromString(DFNS_WALLET_PUBLIC_KEY); + + // 1-of-2 KeyList: DFNS key + deployer key (threshold can be adjusted above) + const keyList = new KeyList([dfnsPublicKey, deployerPrivKey.publicKey], THRESHOLD); + + const client = Client.forNetwork( + Object.fromEntries([[CONSENSUS_NODE_URL, CONSENSUS_NODE_ID]]), + ).setOperator(AccountId.fromString(DEPLOYER_ACCOUNT_ID), deployerPrivKey); + + console.log(`Creating ${THRESHOLD}-of-2 multisig account...`); + console.log(` Key 1 (DFNS): ${dfnsPublicKey.toString()}`); + console.log(` Key 2 (deployer): ${deployerPrivKey.publicKey.toString()}`); + + const tx = await new AccountCreateTransaction() + .setKeyWithoutAlias(keyList) + .setInitialBalance(new Hbar(0)) + .execute(client); + + const receipt = await tx.getReceipt(client); + const newAccountId = receipt.accountId; + + if (!newAccountId) { + throw new Error('Account creation failed — no accountId in receipt'); + } + + console.log(`\nMultisig account created: ${newAccountId.toString()}`); + console.log(`\nAdd this to your .env:`); + console.log(` DFNS_MULTISIG_ACCOUNT_ID=${newAccountId.toString()}`); + console.log(`\nThen update multisigFreezeDFNS.ts to use DFNS_MULTISIG_ACCOUNT_ID`); + console.log(`as the targetId/accountId for association, role grant, and freeze.`); +} + +main() + .then(() => process.exit(0)) + .catch((err) => { + console.error(err); + process.exit(1); + }); diff --git a/packages/sdk/example/ts/creation.ts b/packages/sdk/example/ts/creation.ts new file mode 100644 index 000000000..2a7950f4d --- /dev/null +++ b/packages/sdk/example/ts/creation.ts @@ -0,0 +1,121 @@ +import { + Network, + InitializationRequest, + CreateRequest, + ConnectRequest, + SupportedWallets, + TokenSupplyType, + StableCoin, +} from '@hashgraph/stablecoin-npm-sdk'; + +// Load environment variables from .env file +console.log('__dirname:', __dirname); +require('dotenv').config({ path: __dirname + '/../../.env' }); + +const mirrorNodeConfig = { + name: 'Testnet Mirror Node', + network: 'testnet', + baseUrl: 'https://testnet.mirrornode.hedera.com/api/v1/', + apiKey: '', + headerName: '', + selected: true, +}; +const RPCNodeConfig = { + name: 'HashIO', + network: 'testnet', + baseUrl: 'https://testnet.hashio.io/api', + apiKey: '', + headerName: '', + selected: true, +}; + +const main = async () => { + // Initialize the network and connect to the Hedera testnet + await Network.init( + new InitializationRequest({ + network: 'testnet', + mirrorNode: mirrorNodeConfig, + rpcNode: RPCNodeConfig, + configuration: { + factoryAddress: process.env.FACTORY_ADDRESS!, + resolverAddress: process.env.RESOLVER_ADDRESS!, + }, + }), + ); + + const account = { + accountId: process.env.MY_ACCOUNT_ID!, + privateKey: { + key: process.env.MY_PRIVATE_KEY!, + type: 'ED25519', + }, + }; + + // Connect to the network using the provided account + await Network.connect( + new ConnectRequest({ + account: account, + network: 'testnet', + mirrorNode: mirrorNodeConfig, + rpcNode: RPCNodeConfig, + wallet: SupportedWallets.CLIENT, + }), + ); + + // Create a new stablecoin with the specified parameters + const request = new CreateRequest({ + name: 'test', + symbol: 'test', + decimals: 6, + initialSupply: '1000', + freezeKey: { + key: 'null', + type: 'null', + }, + kycKey: { + key: 'null', + type: 'null', + }, + wipeKey: { + key: 'null', + type: 'null', + }, + pauseKey: { + key: 'null', + type: 'null', + }, + feeScheduleKey: { + key: 'null', + type: 'null', + }, + supplyType: TokenSupplyType.INFINITE, + createReserve: false, + updatedAtThreshold: '0', + grantKYCToOriginalSender: true, + burnRoleAccount: account.accountId.toString(), + wipeRoleAccount: account.accountId.toString(), + rescueRoleAccount: account.accountId.toString(), + pauseRoleAccount: account.accountId.toString(), + freezeRoleAccount: account.accountId.toString(), + deleteRoleAccount: account.accountId.toString(), + kycRoleAccount: account.accountId.toString(), + cashInRoleAccount: account.accountId.toString(), + feeRoleAccount: account.accountId.toString(), + cashInRoleAllowance: '0', + proxyOwnerAccount: account.accountId.toString(), + configId: + '0x0000000000000000000000000000000000000000000000000000000000000002', + configVersion: 1, + }); + + // Create the stablecoin and log the result + const stableCoin = await StableCoin.create(request); + console.log('StableCoin created:', stableCoin); + process.exit(0); +}; + +try { + main(); +} catch (error) { + console.error(error); +} diff --git a/packages/sdk/example/ts/creationAssigningKeys.ts b/packages/sdk/example/ts/creationAssigningKeys.ts new file mode 100644 index 000000000..229f01acf --- /dev/null +++ b/packages/sdk/example/ts/creationAssigningKeys.ts @@ -0,0 +1,117 @@ +import { + Network, + InitializationRequest, + CreateRequest, + ConnectRequest, + SupportedWallets, + TokenSupplyType, + StableCoin, +} from '@hashgraph/stablecoin-npm-sdk'; + +// Load environment variables from .env file +console.log('__dirname:', __dirname); +require('dotenv').config({ path: __dirname + '/../../.env' }); + +const mirrorNodeConfig = { + name: 'Testnet Mirror Node', + network: 'testnet', + baseUrl: 'https://testnet.mirrornode.hedera.com/api/v1/', + apiKey: '', + headerName: '', + selected: true, +}; +const RPCNodeConfig = { + name: 'HashIO', + network: 'testnet', + baseUrl: 'https://testnet.hashio.io/api', + apiKey: '', + headerName: '', + selected: true, +}; + +const main = async () => { + // Initialize the network and connect to the Hedera testnet + await Network.init( + new InitializationRequest({ + network: 'testnet', + mirrorNode: mirrorNodeConfig, + rpcNode: RPCNodeConfig, + configuration: { + factoryAddress: process.env.FACTORY_ADDRESS!, + resolverAddress: process.env.RESOLVER_ADDRESS!, + }, + }), + ); + + // Define the account details for connecting to the network + const account = { + accountId: process.env.MY_ACCOUNT_ID!, + privateKey: { + key: process.env.MY_PRIVATE_KEY!, + type: 'ED25519', + }, + }; + + // Connect to the network using the provided account + const connection = await Network.connect( + new ConnectRequest({ + account: account, + network: 'testnet', + mirrorNode: mirrorNodeConfig, + rpcNode: RPCNodeConfig, + wallet: SupportedWallets.CLIENT, + }), + ); + + // Create a new stablecoin with the specified parameters + const request = new CreateRequest({ + name: 'test', + symbol: 'test', + decimals: 6, + initialSupply: '1000', + freezeKey: { + key: connection.account!.publicKey!.key, + type: 'ED25519', + }, + kycKey: { + key: connection.account!.publicKey!.key, + type: 'ED25519', + }, + wipeKey: { + key: connection.account!.publicKey!.key, + type: 'ED25519', + }, + pauseKey: { + key: connection.account!.publicKey!.key, + type: 'ED25519', + }, + supplyType: TokenSupplyType.INFINITE, + createReserve: false, + grantKYCToOriginalSender: true, + burnRoleAccount: account.accountId.toString(), + wipeRoleAccount: '0.0.0', + rescueRoleAccount: account.accountId.toString(), + pauseRoleAccount: '0.0.0', + freezeRoleAccount: '0.0.0', + deleteRoleAccount: account.accountId.toString(), + kycRoleAccount: account.accountId.toString(), + cashInRoleAccount: account.accountId.toString(), + feeRoleAccount: '0.0.0', + cashInRoleAllowance: '1', + proxyOwnerAccount: account.accountId.toString(), + configId: + '0x0000000000000000000000000000000000000000000000000000000000000002', + configVersion: 1, + }); + + // Create the stablecoin and log the result + const stableCoin = await StableCoin.create(request); + console.log('StableCoin created:', stableCoin); + process.exit(0); +}; + +try { + main(); +} catch (error) { + console.error(error); +} diff --git a/packages/sdk/example/ts/creationWithReserve.ts b/packages/sdk/example/ts/creationWithReserve.ts new file mode 100644 index 000000000..8c0dca479 --- /dev/null +++ b/packages/sdk/example/ts/creationWithReserve.ts @@ -0,0 +1,124 @@ +import { + Network, + InitializationRequest, + CreateRequest, + ConnectRequest, + SupportedWallets, + TokenSupplyType, + StableCoin, +} from '@hashgraph/stablecoin-npm-sdk'; + +// Load environment variables from .env file +require('dotenv').config({ path: __dirname + '/../../.env' }); + +const mirrorNodeConfig = { + name: 'Testnet Mirror Node', + network: 'testnet', + baseUrl: 'https://testnet.mirrornode.hedera.com/api/v1/', + apiKey: '', + headerName: '', + selected: true, +}; +const RPCNodeConfig = { + name: 'HashIO', + network: 'testnet', + baseUrl: 'https://testnet.hashio.io/api', + apiKey: '', + headerName: '', + selected: true, +}; + +const main = async () => { + // Initialize the network and connect to the Hedera testnet + await Network.init( + new InitializationRequest({ + network: 'testnet', + mirrorNode: mirrorNodeConfig, + rpcNode: RPCNodeConfig, + configuration: { + factoryAddress: process.env.FACTORY_ADDRESS!, + resolverAddress: process.env.RESOLVER_ADDRESS!, + }, + }), + ); + + // Define the account details for connecting to the network + const account = { + accountId: process.env.MY_ACCOUNT_ID!, + privateKey: { + key: process.env.MY_PRIVATE_KEY!, + type: 'ED25519', + }, + }; + + // Connect to the network using the provided account + await Network.connect( + new ConnectRequest({ + account: account, + network: 'testnet', + mirrorNode: mirrorNodeConfig, + rpcNode: RPCNodeConfig, + wallet: SupportedWallets.CLIENT, + }), + ); + + // Create a new stablecoin with the specified parameters with reserve + const request = new CreateRequest({ + name: 'test', + symbol: 'test', + decimals: 6, + initialSupply: '1000', + freezeKey: { + key: 'null', + type: 'null', + }, + kycKey: { + key: 'null', + type: 'null', + }, + wipeKey: { + key: 'null', + type: 'null', + }, + pauseKey: { + key: 'null', + type: 'null', + }, + feeScheduleKey: { + key: 'null', + type: 'null', + }, + supplyType: TokenSupplyType.INFINITE, + reserveInitialAmount: '10000', + reserveConfigId: + '0x0000000000000000000000000000000000000000000000000000000000000003', + reserveConfigVersion: 1, + createReserve: true, + grantKYCToOriginalSender: true, + burnRoleAccount: account.accountId.toString(), + wipeRoleAccount: account.accountId.toString(), + rescueRoleAccount: account.accountId.toString(), + pauseRoleAccount: account.accountId.toString(), + freezeRoleAccount: account.accountId.toString(), + deleteRoleAccount: account.accountId.toString(), + kycRoleAccount: account.accountId.toString(), + cashInRoleAccount: account.accountId.toString(), + feeRoleAccount: account.accountId.toString(), + cashInRoleAllowance: '1', + proxyOwnerAccount: account.accountId.toString(), + configId: + '0x0000000000000000000000000000000000000000000000000000000000000002', + configVersion: 1, + }); + + // Create the stablecoin and log the result + const stableCoin = await StableCoin.create(request); + console.log('StableCoin with reserve created:', stableCoin); + process.exit(0); +}; + +try { + main(); +} catch (error) { + console.error(error); +} diff --git a/packages/sdk/example/ts/creationWithReserveAddress.ts b/packages/sdk/example/ts/creationWithReserveAddress.ts new file mode 100644 index 000000000..e91064d23 --- /dev/null +++ b/packages/sdk/example/ts/creationWithReserveAddress.ts @@ -0,0 +1,121 @@ +import { + Network, + InitializationRequest, + CreateRequest, + ConnectRequest, + SupportedWallets, + TokenSupplyType, + StableCoin, +} from '@hashgraph/stablecoin-npm-sdk'; + +// Load environment variables from .env file +require('dotenv').config({ path: __dirname + '/../../.env' }); + +const mirrorNodeConfig = { + name: 'Testnet Mirror Node', + network: 'testnet', + baseUrl: 'https://testnet.mirrornode.hedera.com/api/v1/', + apiKey: '', + headerName: '', + selected: true, +}; +const RPCNodeConfig = { + name: 'HashIO', + network: 'testnet', + baseUrl: 'https://testnet.hashio.io/api', + apiKey: '', + headerName: '', + selected: true, +}; + +const main = async () => { + // Initialize the network and connect to the Hedera testnet + await Network.init( + new InitializationRequest({ + network: 'testnet', + mirrorNode: mirrorNodeConfig, + rpcNode: RPCNodeConfig, + configuration: { + factoryAddress: process.env.FACTORY_ADDRESS!, + resolverAddress: process.env.RESOLVER_ADDRESS!, + }, + }), + ); + + // Define the account details for connecting to the network + const account = { + accountId: process.env.MY_ACCOUNT_ID!, + privateKey: { + key: process.env.MY_PRIVATE_KEY!, + type: 'ED25519', + }, + }; + + // Connect to the network using the provided account + await Network.connect( + new ConnectRequest({ + account: account, + network: 'testnet', + mirrorNode: mirrorNodeConfig, + rpcNode: RPCNodeConfig, + wallet: SupportedWallets.CLIENT, + }), + ); + + // Create a new stablecoin with the specified parameters with reserve + const request = new CreateRequest({ + name: 'test', + symbol: 'test', + decimals: 0, + initialSupply: '1', + freezeKey: { + key: 'null', + type: 'null', + }, + kycKey: { + key: 'null', + type: 'null', + }, + wipeKey: { + key: 'null', + type: 'null', + }, + pauseKey: { + key: 'null', + type: 'null', + }, + feeScheduleKey: { + key: 'null', + type: 'null', + }, + supplyType: TokenSupplyType.INFINITE, + reserveAddress: '0x765fe75dbb4afcbf7eb4c67fa9e7bdcc5c6bca64', + createReserve: false, + grantKYCToOriginalSender: true, + burnRoleAccount: account.accountId.toString(), + wipeRoleAccount: account.accountId.toString(), + rescueRoleAccount: account.accountId.toString(), + pauseRoleAccount: account.accountId.toString(), + freezeRoleAccount: account.accountId.toString(), + deleteRoleAccount: account.accountId.toString(), + kycRoleAccount: account.accountId.toString(), + cashInRoleAccount: account.accountId.toString(), + feeRoleAccount: account.accountId.toString(), + cashInRoleAllowance: '1', + proxyOwnerAccount: account.accountId.toString(), + configId: + '0x0000000000000000000000000000000000000000000000000000000000000002', + configVersion: 1, + }); + + // Create the stablecoin and log the result + const stableCoin = await StableCoin.create(request); + console.log('StableCoin with reserve created:', stableCoin); + process.exit(0); +}; + +try { + main(); +} catch (error) { + console.error(error); +} diff --git a/packages/sdk/example/ts/mint.ts b/packages/sdk/example/ts/mint.ts new file mode 100644 index 000000000..13155b170 --- /dev/null +++ b/packages/sdk/example/ts/mint.ts @@ -0,0 +1,181 @@ +import { + Network, + InitializationRequest, + CreateRequest, + ConnectRequest, + SupportedWallets, + TokenSupplyType, + StableCoin, + AssociateTokenRequest, + BigDecimal, + GetAccountBalanceRequest, + CashInRequest, + KYCRequest, +} from '@hashgraph/stablecoin-npm-sdk'; +import assert from 'node:assert'; + +// Load environment variables from .env file +console.log('__dirname:', __dirname); +require('dotenv').config({ path: __dirname + '/../../.env' }); + +const mirrorNodeConfig = { + name: 'Testnet Mirror Node', + network: 'testnet', + baseUrl: 'https://testnet.mirrornode.hedera.com/api/v1/', + apiKey: '', + headerName: '', + selected: true, +}; +const RPCNodeConfig = { + name: 'HashIO', + network: 'testnet', + baseUrl: 'https://testnet.hashio.io/api', + apiKey: '', + headerName: '', + selected: true, +}; + +const main = async () => { + // Initialize the network and connect to the Hedera testnet + await Network.init( + new InitializationRequest({ + network: 'testnet', + mirrorNode: mirrorNodeConfig, + rpcNode: RPCNodeConfig, + configuration: { + factoryAddress: process.env.FACTORY_ADDRESS!, + resolverAddress: process.env.RESOLVER_ADDRESS!, + }, + }), + ); + + // Define the account details for connecting to the network + const account = { + accountId: process.env.MY_ACCOUNT_ID!, + privateKey: { + key: process.env.MY_PRIVATE_KEY!, + type: 'ED25519', + }, + }; + + // Connect to the network using the provided account + await Network.connect( + new ConnectRequest({ + account: account, + network: 'testnet', + mirrorNode: mirrorNodeConfig, + rpcNode: RPCNodeConfig, + wallet: SupportedWallets.CLIENT, + }), + ); + + // Create a new stablecoin with the specified parameters + const request = new CreateRequest({ + name: 'test', + symbol: 'test', + decimals: 6, + initialSupply: '1000', + freezeKey: { + key: 'null', + type: 'null', + }, + kycKey: { + key: 'null', + type: 'null', + }, + wipeKey: { + key: 'null', + type: 'null', + }, + pauseKey: { + key: 'null', + type: 'null', + }, + feeScheduleKey: { + key: 'null', + type: 'null', + }, + supplyType: TokenSupplyType.INFINITE, + createReserve: false, + grantKYCToOriginalSender: true, + burnRoleAccount: account.accountId.toString(), + wipeRoleAccount: account.accountId.toString(), + rescueRoleAccount: account.accountId.toString(), + pauseRoleAccount: account.accountId.toString(), + freezeRoleAccount: account.accountId.toString(), + deleteRoleAccount: account.accountId.toString(), + kycRoleAccount: account.accountId.toString(), + cashInRoleAccount: account.accountId.toString(), + feeRoleAccount: account.accountId.toString(), + cashInRoleAllowance: '10', + proxyOwnerAccount: account.accountId.toString(), + configId: + '0x0000000000000000000000000000000000000000000000000000000000000002', + configVersion: 1, + }); + + // Create the stablecoin and log the result + const stableCoin = (await StableCoin.create(request)) as { coin: any; reserve: any }; + console.log('StableCoin created:', stableCoin); + + // Associate the stablecoin with the account + await StableCoin.associate( + new AssociateTokenRequest({ + targetId: account.accountId.toString(), + tokenId: stableCoin?.coin?.tokenId?.toString()!, + }), + ); + + //Grant KYC to the original sender + await StableCoin.grantKyc( + new KYCRequest({ + targetId: account.accountId.toString(), + tokenId: stableCoin?.coin?.tokenId?.toString()!, + }), + ); + + // Check balance before cash-in + const initialAmount = await StableCoin.getBalanceOf( + new GetAccountBalanceRequest({ + tokenId: stableCoin?.coin?.tokenId?.toString()!, + targetId: account.accountId.toString(), + }), + ); + + await new Promise((resolve) => setTimeout(resolve, 5000)); + + // Perform cash-in operation + await StableCoin.cashIn( + new CashInRequest({ + amount: '10', + tokenId: stableCoin?.coin?.tokenId?.toString()!, + targetId: account.accountId.toString(), + }), + ); + + await new Promise((resolve) => setTimeout(resolve, 5000)); + + // Check balance after cash-in + const finalAmount = await StableCoin.getBalanceOf( + new GetAccountBalanceRequest({ + tokenId: stableCoin?.coin?.tokenId?.toString()!, + targetId: account.accountId.toString(), + }), + ); + + // Assert that the final amount is as expected + const final = + initialAmount.value.toBigInt() + new BigDecimal('10', 6).toBigInt(); + + assert( + finalAmount.value.toBigInt().toString() === final.toString(), + 'Cash-in operation failed: balance mismatch', + ); + process.exit(0); +}; + +try { + main(); +} catch (error) { + console.error(error); +} diff --git a/packages/sdk/example/ts/multisigFreeze.ts b/packages/sdk/example/ts/multisigFreeze.ts new file mode 100644 index 000000000..0652e3dbb --- /dev/null +++ b/packages/sdk/example/ts/multisigFreeze.ts @@ -0,0 +1,212 @@ +import { + Network, + InitializationRequest, + ConnectRequest, + SupportedWallets, + StableCoin, + Role, + StableCoinRole, + CreateRequest, + TokenSupplyType, + GrantRoleRequest, + FreezeAccountRequest, + SignTransactionRequest, +} from '@hashgraph/stablecoin-npm-sdk'; +import { + AccountId, + Client, + PrivateKey, + Status, + TokenAssociateTransaction, + TokenId, +} from '@hiero-ledger/sdk'; + +require('dotenv').config({ path: __dirname + '/../../.env' }); + +// === Deployer & signing account (CLIENT wallet) === +const DEPLOYER_ACCOUNT_ID = process.env.MY_ACCOUNT_ID!; +const DEPLOYER_PRIVATE_KEY = process.env.MY_PRIVATE_KEY_ECDSA!; + +// === Multisig account (MULTISIG wallet — only for the freeze) === +const MULTISIG_ACCOUNT_ID = process.env.MULTISIG_ACCOUNT_ID!; + +// === Infrastructure === +const FACTORY_ADDRESS = process.env.FACTORY_ADDRESS!; +const RESOLVER_ADDRESS = process.env.RESOLVER_ADDRESS!; +const BACKEND_URL = process.env.BACKEND_URL ?? 'http://127.0.0.1:3001/api/transactions/'; +const CONSENSUS_NODE_URL = process.env.CONSENSUS_NODE_URL ?? '34.94.106.61:50211'; +const CONSENSUS_NODE_ID = process.env.CONSENSUS_NODE_ID ?? '0.0.3'; + +const consensusNodes = [{ url: CONSENSUS_NODE_URL, nodeId: CONSENSUS_NODE_ID }]; + +const mirrorNodeConfig = { + name: 'Testnet Mirror Node', + network: 'testnet', + baseUrl: 'https://testnet.mirrornode.hedera.com/api/v1/', + apiKey: '', + headerName: '', + selected: true, +}; + +const RPCNodeConfig = { + name: 'HashIO', + network: 'testnet', + baseUrl: 'https://testnet.hashio.io/api', + apiKey: '', + headerName: '', + selected: true, +}; + +const wait = (ms: number) => new Promise((r) => setTimeout(r, ms)); + +const retry = async ( + fn: () => Promise, + label: string, + intervalMs = 5000, + maxAttempts = 12, +): Promise => { + for (let attempt = 1; attempt <= maxAttempts; attempt++) { + try { + return await fn(); + } catch (err: any) { + if (attempt === maxAttempts) throw err; + console.log(` [${label}] attempt ${attempt} failed, retrying in ${intervalMs / 1000}s...`); + await wait(intervalMs); + } + } + throw new Error(`${label} exhausted all retries`); +}; + +const connectDeployer = () => + Network.connect( + new ConnectRequest({ + account: { + accountId: DEPLOYER_ACCOUNT_ID, + privateKey: { key: DEPLOYER_PRIVATE_KEY, type: 'ECDSA' }, + }, + network: 'custom', + mirrorNode: mirrorNodeConfig, + rpcNode: RPCNodeConfig, + wallet: SupportedWallets.CLIENT, + consensusNodes, + }), + ); + +const connectMultisig = () => + Network.connect( + new ConnectRequest({ + account: { accountId: MULTISIG_ACCOUNT_ID }, + network: 'custom', + mirrorNode: mirrorNodeConfig, + rpcNode: RPCNodeConfig, + wallet: SupportedWallets.MULTISIG, + consensusNodes, + }), + ); + +const main = async () => { + // ── Init ──────────────────────────────────────────────────────────────── + await Network.init( + new InitializationRequest({ + network: 'custom', + mirrorNode: mirrorNodeConfig, + rpcNode: RPCNodeConfig, + configuration: { factoryAddress: FACTORY_ADDRESS, resolverAddress: RESOLVER_ADDRESS }, + consensusNodes, + backend: { url: BACKEND_URL }, + }), + ); + + // ── Phase 1: Deploy stablecoin ────────────────────────────────────────── + console.log('\n[1/4] Deploying stablecoin...'); + await connectDeployer(); + + const stableCoin = (await StableCoin.create( + new CreateRequest({ + name: 'MultisigFreezeTest', + symbol: 'MFT', + decimals: 6, + initialSupply: '1000', + freezeKey: { key: 'null', type: 'null' }, + kycKey: { key: 'null', type: 'null' }, + wipeKey: { key: 'null', type: 'null' }, + pauseKey: { key: 'null', type: 'null' }, + feeScheduleKey: { key: 'null', type: 'null' }, + supplyType: TokenSupplyType.INFINITE, + createReserve: false, + grantKYCToOriginalSender: true, + burnRoleAccount: DEPLOYER_ACCOUNT_ID, + wipeRoleAccount: DEPLOYER_ACCOUNT_ID, + rescueRoleAccount: DEPLOYER_ACCOUNT_ID, + pauseRoleAccount: DEPLOYER_ACCOUNT_ID, + freezeRoleAccount: DEPLOYER_ACCOUNT_ID, + deleteRoleAccount: DEPLOYER_ACCOUNT_ID, + kycRoleAccount: DEPLOYER_ACCOUNT_ID, + cashInRoleAccount: DEPLOYER_ACCOUNT_ID, + feeRoleAccount: DEPLOYER_ACCOUNT_ID, + cashInRoleAllowance: '0', + proxyOwnerAccount: DEPLOYER_ACCOUNT_ID, + configId: '0x0000000000000000000000000000000000000000000000000000000000000002', + configVersion: 1, + }), + )) as { coin: any; reserve: any }; + + const tokenId: string = stableCoin.coin.tokenId.toString(); + console.log(` Stablecoin deployed: ${tokenId}`); + await wait(5000); + + // ── Phase 2: Associate multisig account to token (direct HTS tx) ──────── + console.log('\n[2/4] Associating multisig account to token...'); + const hederaClient = Client.forNetwork( + Object.fromEntries(consensusNodes.map((n) => [n.url, n.nodeId])), + ).setOperator(DEPLOYER_ACCOUNT_ID, PrivateKey.fromStringECDSA(DEPLOYER_PRIVATE_KEY)); + + const associateTx = await new TokenAssociateTransaction() + .setAccountId(AccountId.fromString(MULTISIG_ACCOUNT_ID)) + .setTokenIds([TokenId.fromString(tokenId)]) + .freezeWith(hederaClient) + .sign(PrivateKey.fromStringECDSA(DEPLOYER_PRIVATE_KEY)); + + const associateResponse = await associateTx.execute(hederaClient); + const receipt = await associateResponse.getReceipt(hederaClient); + if (receipt.status !== Status.Success) { + throw new Error(`Association failed: ${receipt.status.toString()}`); + } + console.log(' Association done.'); + await wait(5000); + + // ── Phase 3: Grant freeze role to multisig account (CLIENT/deployer) ──── + console.log('\n[3/4] Granting freeze role to multisig account...'); + await Role.grantRole( + new GrantRoleRequest({ + targetId: MULTISIG_ACCOUNT_ID, + tokenId, + role: StableCoinRole.FREEZE_ROLE, + }), + ); + console.log(' Freeze role granted.'); + await wait(5000); + + // ── Phase 4: Freeze via multisig (MULTISIG → sign → autoSubmit) ───────── + console.log('\n[4/4] Submitting freeze via multisig...'); + await connectMultisig(); + + const startDate = new Date(Date.now() + 1 * 60 * 1000).toISOString(); + const freezeResult = await retry( + () => StableCoin.freeze(new FreezeAccountRequest({ tokenId, targetId: MULTISIG_ACCOUNT_ID, startDate })), + 'freeze', + ); + const freezeTxId = freezeResult.transactionId!; + console.log(` Backend tx: ${freezeTxId}`); + + await connectDeployer(); + await StableCoin.signTransaction(new SignTransactionRequest({ transactionId: freezeTxId })); + console.log(' Signature stored. Waiting for autoSubmit...'); + + process.exit(0); +}; + +main().catch((error) => { + console.error(error); + process.exit(1); +}); diff --git a/packages/sdk/example/ts/multisigFreezeDFNS.ts b/packages/sdk/example/ts/multisigFreezeDFNS.ts new file mode 100644 index 000000000..9b3c687ee --- /dev/null +++ b/packages/sdk/example/ts/multisigFreezeDFNS.ts @@ -0,0 +1,293 @@ +import { + Network, + InitializationRequest, + ConnectRequest, + SupportedWallets, + StableCoin, + Role, + StableCoinRole, + CreateRequest, + TokenSupplyType, + GrantRoleRequest, + FreezeAccountRequest, + SignTransactionRequest, +} from '@hashgraph/stablecoin-npm-sdk'; +import type { DFNSConfigRequest } from '@hashgraph/stablecoin-npm-sdk'; +import { + AccountId, + Client, + PrivateKey, + Status, + TokenAssociateTransaction, + TokenId, +} from '@hiero-ledger/sdk'; +import * as fs from 'fs'; + +require('dotenv').config({ path: __dirname + '/../../.env' }); + +// Resolves a value that can be either a raw key string or a path to a key file. +const resolveKeyOrPath = (value: string | undefined): string | undefined => { + if (!value) return undefined; + if (fs.existsSync(value)) return fs.readFileSync(value, 'utf8').trim(); + return value; +}; + +// === Deployer & signing account (CLIENT wallet) === +const DEPLOYER_ACCOUNT_ID = process.env.MY_ACCOUNT_ID!; +const DEPLOYER_PRIVATE_KEY = process.env.MY_PRIVATE_KEY_ECDSA!; + +// === Infrastructure === +const FACTORY_ADDRESS = process.env.FACTORY_ADDRESS!; +const RESOLVER_ADDRESS = process.env.RESOLVER_ADDRESS!; +const BACKEND_URL = process.env.BACKEND_URL ?? 'http://127.0.0.1:3001/api/transactions/'; +const CONSENSUS_NODE_URL = process.env.CONSENSUS_NODE_URL ?? '34.94.106.61:50211'; +const CONSENSUS_NODE_ID = process.env.CONSENSUS_NODE_ID ?? '0.0.3'; + +// === DFNS custodial wallet settings === +const DFNS_AUTH_TOKEN = process.env.DFNS_SERVICE_ACCOUNT_AUTHORIZATION_TOKEN!; +const DFNS_CREDENTIAL_ID = process.env.DFNS_SERVICE_ACCOUNT_CREDENTIAL_ID!; +const DFNS_PRIVATE_KEY = resolveKeyOrPath( + process.env.DFNS_SERVICE_ACCOUNT_PRIVATE_KEY_OR_PATH ?? + process.env.DFNS_SERVICE_ACCOUNT_PRIVATE_KEY_PATH ?? + process.env.DFNS_SERVICE_ACCOUNT_PRIVATE_KEY, +)!; +const DFNS_APP_ORIGIN = process.env.DFNS_APP_ORIGIN!; +const DFNS_APP_ID = process.env.DFNS_APP_ID!; +const DFNS_BASE_URL = process.env.DFNS_BASE_URL ?? process.env.DFNS_TEST_URL!; +const DFNS_WALLET_ID = process.env.DFNS_WALLET_ID!; +const DFNS_WALLET_PUBLIC_KEY = process.env.DFNS_WALLET_PUBLIC_KEY!; +// The Hedera account ID that DFNS controls (used as the connected wallet identity) +const DFNS_HEDERA_ACCOUNT_ID = process.env.DFNS_HEDERA_ACCOUNT_ID!; +// The multisig account created by createDFNSMultisigAccount.ts (KeyList includes DFNS key) +const DFNS_MULTISIG_ACCOUNT_ID = process.env.DFNS_MULTISIG_ACCOUNT_ID!; + +// === Validate required env vars before doing anything === +const REQUIRED_ENV: Record = { + MY_ACCOUNT_ID: DEPLOYER_ACCOUNT_ID, + MY_PRIVATE_KEY_ECDSA: DEPLOYER_PRIVATE_KEY, + FACTORY_ADDRESS, + RESOLVER_ADDRESS, + DFNS_SERVICE_ACCOUNT_AUTHORIZATION_TOKEN: DFNS_AUTH_TOKEN, + DFNS_SERVICE_ACCOUNT_CREDENTIAL_ID: DFNS_CREDENTIAL_ID, + 'DFNS_SERVICE_ACCOUNT_PRIVATE_KEY(_OR_PATH)': DFNS_PRIVATE_KEY, + DFNS_APP_ORIGIN, + DFNS_APP_ID, + 'DFNS_BASE_URL or DFNS_TEST_URL': DFNS_BASE_URL, + DFNS_WALLET_ID, + DFNS_WALLET_PUBLIC_KEY, + DFNS_HEDERA_ACCOUNT_ID, + DFNS_MULTISIG_ACCOUNT_ID, +}; +const missing = Object.entries(REQUIRED_ENV) + .filter(([, v]) => !v) + .map(([k]) => k); +if (missing.length) { + console.error(`Missing required env vars:\n ${missing.join('\n ')}`); + process.exit(1); +} + +const consensusNodes = [{ url: CONSENSUS_NODE_URL, nodeId: CONSENSUS_NODE_ID }]; + +const mirrorNodeConfig = { + name: 'Testnet Mirror Node', + network: 'testnet', + baseUrl: 'https://testnet.mirrornode.hedera.com/api/v1/', + apiKey: '', + headerName: '', + selected: true, +}; + +const RPCNodeConfig = { + name: 'HashIO', + network: 'testnet', + baseUrl: 'https://testnet.hashio.io/api', + apiKey: '', + headerName: '', + selected: true, +}; + +const dfnsCustodialSettings: DFNSConfigRequest = { + authorizationToken: DFNS_AUTH_TOKEN, + credentialId: DFNS_CREDENTIAL_ID, + serviceAccountPrivateKey: DFNS_PRIVATE_KEY, + urlApplicationOrigin: DFNS_APP_ORIGIN, + applicationId: DFNS_APP_ID, + baseUrl: DFNS_BASE_URL, + walletId: DFNS_WALLET_ID, + hederaAccountId: DFNS_HEDERA_ACCOUNT_ID, + publicKey: DFNS_WALLET_PUBLIC_KEY, +}; + +const wait = (ms: number) => new Promise((r) => setTimeout(r, ms)); + +const retry = async ( + fn: () => Promise, + label: string, + intervalMs = 5000, + maxAttempts = 12, +): Promise => { + for (let attempt = 1; attempt <= maxAttempts; attempt++) { + try { + return await fn(); + } catch (err: any) { + if (attempt === maxAttempts) throw err; + console.log(` [${label}] attempt ${attempt} failed, retrying in ${intervalMs / 1000}s...`); + await wait(intervalMs); + } + } + throw new Error(`${label} exhausted all retries`); +}; + +const connectDeployer = () => + Network.connect( + new ConnectRequest({ + account: { + accountId: DEPLOYER_ACCOUNT_ID, + privateKey: { key: DEPLOYER_PRIVATE_KEY, type: 'ECDSA' }, + }, + network: 'custom', + mirrorNode: mirrorNodeConfig, + rpcNode: RPCNodeConfig, + wallet: SupportedWallets.CLIENT, + consensusNodes, + }), + ); + +// Connect as the DFNS-controlled multisig account to initiate the freeze (creates backend tx) +const connectDfnsMultisig = () => + Network.connect( + new ConnectRequest({ + account: { accountId: DFNS_MULTISIG_ACCOUNT_ID }, + network: 'custom', + mirrorNode: mirrorNodeConfig, + rpcNode: RPCNodeConfig, + wallet: SupportedWallets.MULTISIG, + consensusNodes, + }), + ); + +// Connect as DFNS custodial wallet to sign the pending backend tx +const connectDfns = () => + Network.connect( + new ConnectRequest({ + network: 'custom', + mirrorNode: mirrorNodeConfig, + rpcNode: RPCNodeConfig, + wallet: SupportedWallets.DFNS, + custodialWalletSettings: dfnsCustodialSettings, + consensusNodes, + }), + ); + +const main = async () => { + // ── Init ──────────────────────────────────────────────────────────────── + await Network.init( + new InitializationRequest({ + network: 'custom', + mirrorNode: mirrorNodeConfig, + rpcNode: RPCNodeConfig, + configuration: { factoryAddress: FACTORY_ADDRESS, resolverAddress: RESOLVER_ADDRESS }, + consensusNodes, + backend: { url: BACKEND_URL }, + }), + ); + + // ── Phase 1: Deploy stablecoin ────────────────────────────────────────── + console.log('\n[1/4] Deploying stablecoin...'); + await connectDeployer(); + + const stableCoin = (await StableCoin.create( + new CreateRequest({ + name: 'DFNSMultisigFreezeTest', + symbol: 'DMFT', + decimals: 6, + initialSupply: '1000', + freezeKey: { key: 'null', type: 'null' }, + kycKey: { key: 'null', type: 'null' }, + wipeKey: { key: 'null', type: 'null' }, + pauseKey: { key: 'null', type: 'null' }, + feeScheduleKey: { key: 'null', type: 'null' }, + supplyType: TokenSupplyType.INFINITE, + createReserve: false, + grantKYCToOriginalSender: true, + burnRoleAccount: DEPLOYER_ACCOUNT_ID, + wipeRoleAccount: DEPLOYER_ACCOUNT_ID, + rescueRoleAccount: DEPLOYER_ACCOUNT_ID, + pauseRoleAccount: DEPLOYER_ACCOUNT_ID, + freezeRoleAccount: DEPLOYER_ACCOUNT_ID, + deleteRoleAccount: DEPLOYER_ACCOUNT_ID, + kycRoleAccount: DEPLOYER_ACCOUNT_ID, + cashInRoleAccount: DEPLOYER_ACCOUNT_ID, + feeRoleAccount: DEPLOYER_ACCOUNT_ID, + cashInRoleAllowance: '0', + proxyOwnerAccount: DEPLOYER_ACCOUNT_ID, + configId: '0x0000000000000000000000000000000000000000000000000000000000000002', + configVersion: 1, + }), + )) as { coin: any; reserve: any }; + + const tokenId: string = stableCoin.coin.tokenId.toString(); + console.log(` Stablecoin deployed: ${tokenId}`); + await wait(5000); + + // ── Phase 2: Associate multisig account to token (deployer pays, deployer signs) ── + // The multisig account is 1-of-2, so the deployer key alone satisfies the threshold. + console.log('\n[2/4] Associating DFNS multisig account to token...'); + const hederaClient = Client.forNetwork( + Object.fromEntries(consensusNodes.map((n) => [n.url, n.nodeId])), + ).setOperator(DEPLOYER_ACCOUNT_ID, PrivateKey.fromStringECDSA(DEPLOYER_PRIVATE_KEY)); + + const associateTx = await new TokenAssociateTransaction() + .setAccountId(AccountId.fromString(DFNS_MULTISIG_ACCOUNT_ID)) + .setTokenIds([TokenId.fromString(tokenId)]) + .freezeWith(hederaClient) + .sign(PrivateKey.fromStringECDSA(DEPLOYER_PRIVATE_KEY)); + + const associateResponse = await associateTx.execute(hederaClient); + const receipt = await associateResponse.getReceipt(hederaClient); + if (receipt.status !== Status.Success) { + throw new Error(`Association failed: ${receipt.status.toString()}`); + } + console.log(' Association done.'); + await wait(5000); + + // ── Phase 3: Grant freeze role to the DFNS multisig account (the freeze caller) ── + console.log('\n[3/4] Granting freeze role to DFNS multisig account...'); + await Role.grantRole( + new GrantRoleRequest({ + targetId: DFNS_MULTISIG_ACCOUNT_ID, + tokenId, + role: StableCoinRole.FREEZE_ROLE, + }), + ); + console.log(' Freeze role granted.'); + await wait(5000); + + // ── Phase 4: Freeze via multisig → DFNS signs ──────────────────────────── + // Step 4a: connect as the DFNS multisig account to create the pending backend tx + console.log('\n[4/4] Submitting freeze via DFNS multisig...'); + await connectDfnsMultisig(); + + const startDate = new Date(Date.now() + 1 * 60 * 1000).toISOString(); + const freezeResult = await retry<{ transactionId?: string }>( + () => + StableCoin.freeze( + new FreezeAccountRequest({ tokenId, targetId: DFNS_MULTISIG_ACCOUNT_ID, startDate }), + ) as Promise<{ transactionId?: string }>, + 'freeze', + ); + const freezeTxId = freezeResult.transactionId!; + console.log(` Backend tx: ${freezeTxId}`); + + // Step 4b: connect as DFNS custodial wallet to sign the pending tx + await connectDfns(); + await StableCoin.signTransaction(new SignTransactionRequest({ transactionId: freezeTxId })); + console.log(' Signature stored by DFNS. Waiting for autoSubmit...'); + + process.exit(0); +}; + +main().catch((error) => { + console.error(error); + process.exit(1); +}); diff --git a/packages/sdk/example/ts/role.ts b/packages/sdk/example/ts/role.ts new file mode 100644 index 000000000..b199cdf5b --- /dev/null +++ b/packages/sdk/example/ts/role.ts @@ -0,0 +1,171 @@ +import { + Network, + InitializationRequest, + CreateRequest, + ConnectRequest, + SupportedWallets, + TokenSupplyType, + StableCoin, + RevokeRoleRequest, + StableCoinRole, + Role, + HasRoleRequest, + GrantRoleRequest, +} from '@hashgraph/stablecoin-npm-sdk'; +import assert from 'node:assert'; + +// Load environment variables from .env file +console.log('__dirname:', __dirname); +require('dotenv').config({ path: __dirname + '/../../.env' }); + +const mirrorNodeConfig = { + name: 'Testnet Mirror Node', + network: 'testnet', + baseUrl: 'https://testnet.mirrornode.hedera.com/api/v1/', + apiKey: '', + headerName: '', + selected: true, +}; +const RPCNodeConfig = { + name: 'HashIO', + network: 'testnet', + baseUrl: 'https://testnet.hashio.io/api', + apiKey: '', + headerName: '', + selected: true, +}; + +const main = async () => { + // Initialize the network and connect to the Hedera testnet + await Network.init( + new InitializationRequest({ + network: 'testnet', + mirrorNode: mirrorNodeConfig, + rpcNode: RPCNodeConfig, + configuration: { + factoryAddress: process.env.FACTORY_ADDRESS!, + resolverAddress: process.env.RESOLVER_ADDRESS!, + }, + }), + ); + + // Define the account details for connecting to the network + const account = { + accountId: process.env.MY_ACCOUNT_ID!, + privateKey: { + key: process.env.MY_PRIVATE_KEY!, + type: 'ED25519', + }, + }; + + // Connect to the network using the provided account + await Network.connect( + new ConnectRequest({ + account: account, + network: 'testnet', + mirrorNode: mirrorNodeConfig, + rpcNode: RPCNodeConfig, + wallet: SupportedWallets.CLIENT, + }), + ); + + // Create a new stablecoin with the specified parameters + const request = new CreateRequest({ + name: 'test', + symbol: 'test', + decimals: 6, + initialSupply: '1000', + freezeKey: { + key: 'null', + type: 'null', + }, + kycKey: { + key: 'null', + type: 'null', + }, + wipeKey: { + key: 'null', + type: 'null', + }, + pauseKey: { + key: 'null', + type: 'null', + }, + feeScheduleKey: { + key: 'null', + type: 'null', + }, + supplyType: TokenSupplyType.INFINITE, + createReserve: false, + grantKYCToOriginalSender: true, + burnRoleAccount: account.accountId.toString(), + wipeRoleAccount: account.accountId.toString(), + rescueRoleAccount: account.accountId.toString(), + pauseRoleAccount: account.accountId.toString(), + freezeRoleAccount: account.accountId.toString(), + deleteRoleAccount: account.accountId.toString(), + kycRoleAccount: account.accountId.toString(), + cashInRoleAccount: account.accountId.toString(), + feeRoleAccount: account.accountId.toString(), + cashInRoleAllowance: '1', + proxyOwnerAccount: account.accountId.toString(), + configId: + '0x0000000000000000000000000000000000000000000000000000000000000002', + configVersion: 1, + }); + + // Create the stablecoin and log the result + const stableCoin = (await StableCoin.create(request)) as { coin: any; reserve: any }; + console.log('StableCoin created:', stableCoin); + + // Revoke the Wipe role from the account + await Role.revokeRole( + new RevokeRoleRequest({ + targetId: account.accountId.toString(), + tokenId: stableCoin?.coin?.tokenId?.toString()!, + role: StableCoinRole.WIPE_ROLE, + }), + ); + + await new Promise((resolve) => setTimeout(resolve, 5000)); + + // Check if the account has the Wipe role + const noRole = await Role.hasRole( + new HasRoleRequest({ + targetId: account.accountId.toString(), + tokenId: stableCoin?.coin?.tokenId?.toString()!, + role: StableCoinRole.WIPE_ROLE, + }), + ); + + // Assert that the account does not have the Wipe role + assert(noRole === false, 'Expected no role for the account, but found one'); + + // Grant the Wipe role to the account + await Role.grantRole( + new GrantRoleRequest({ + targetId: account.accountId.toString(), + tokenId: stableCoin?.coin?.tokenId?.toString()!, + role: StableCoinRole.WIPE_ROLE, + }), + ); + + await new Promise((resolve) => setTimeout(resolve, 5000)); + + // Assert that the role was granted successfully + const hasRole = await Role.hasRole( + new HasRoleRequest({ + targetId: account.accountId.toString(), + tokenId: stableCoin?.coin?.tokenId?.toString()!, + role: StableCoinRole.WIPE_ROLE, + }), + ); + assert(hasRole === true, 'Expected role to be granted, but it was not'); + process.exit(0); +}; + +try { + main(); +} catch (error) { + console.error(error); +} diff --git a/packages/sdk/example/ts/wipe.ts b/packages/sdk/example/ts/wipe.ts new file mode 100644 index 000000000..9ce08c258 --- /dev/null +++ b/packages/sdk/example/ts/wipe.ts @@ -0,0 +1,212 @@ +import { + Network, + InitializationRequest, + CreateRequest, + ConnectRequest, + SupportedWallets, + TokenSupplyType, + StableCoin, + AssociateTokenRequest, + BigDecimal, + GetAccountBalanceRequest, + CashInRequest, + KYCRequest, + WipeRequest, +} from '@hashgraph/stablecoin-npm-sdk'; +import assert from 'node:assert'; + +// Load environment variables from .env file +console.log('__dirname:', __dirname); +require('dotenv').config({ path: __dirname + '/../../.env' }); + +const mirrorNodeConfig = { + name: 'Testnet Mirror Node', + network: 'testnet', + baseUrl: 'https://testnet.mirrornode.hedera.com/api/v1/', + apiKey: '', + headerName: '', + selected: true, +}; +const RPCNodeConfig = { + name: 'HashIO', + network: 'testnet', + baseUrl: 'https://testnet.hashio.io/api', + apiKey: '', + headerName: '', + selected: true, +}; + +const main = async () => { + // Initialize the network and connect to the Hedera testnet + await Network.init( + new InitializationRequest({ + network: 'testnet', + mirrorNode: mirrorNodeConfig, + rpcNode: RPCNodeConfig, + configuration: { + factoryAddress: process.env.FACTORY_ADDRESS!, + resolverAddress: process.env.RESOLVER_ADDRESS!, + }, + }), + ); + + // Define the account details for connecting to the network + const account = { + accountId: process.env.MY_ACCOUNT_ID!, + privateKey: { + key: process.env.MY_PRIVATE_KEY!, + type: 'ED25519', + }, + }; + + // Connect to the network using the provided account + await Network.connect( + new ConnectRequest({ + account: account, + network: 'testnet', + mirrorNode: mirrorNodeConfig, + rpcNode: RPCNodeConfig, + wallet: SupportedWallets.CLIENT, + }), + ); + + // Create a new stablecoin with the specified parameters + const request = new CreateRequest({ + name: 'test', + symbol: 'test', + decimals: 6, + initialSupply: '1000', + freezeKey: { + key: 'null', + type: 'null', + }, + kycKey: { + key: 'null', + type: 'null', + }, + wipeKey: { + key: 'null', + type: 'null', + }, + pauseKey: { + key: 'null', + type: 'null', + }, + feeScheduleKey: { + key: 'null', + type: 'null', + }, + supplyType: TokenSupplyType.INFINITE, + createReserve: false, + grantKYCToOriginalSender: true, + burnRoleAccount: account.accountId.toString(), + wipeRoleAccount: account.accountId.toString(), + rescueRoleAccount: account.accountId.toString(), + pauseRoleAccount: account.accountId.toString(), + freezeRoleAccount: account.accountId.toString(), + deleteRoleAccount: account.accountId.toString(), + kycRoleAccount: account.accountId.toString(), + cashInRoleAccount: account.accountId.toString(), + feeRoleAccount: account.accountId.toString(), + cashInRoleAllowance: '10', + proxyOwnerAccount: account.accountId.toString(), + configId: + '0x0000000000000000000000000000000000000000000000000000000000000002', + configVersion: 1, + }); + + // Create the stablecoin and log the result + const stableCoin = (await StableCoin.create(request)) as { coin: any; reserve: any }; + console.log('StableCoin created:', stableCoin); + + // Associate the stablecoin with the account + await StableCoin.associate( + new AssociateTokenRequest({ + targetId: account.accountId.toString(), + tokenId: stableCoin?.coin?.tokenId?.toString()!, + }), + ); + + //Grant KYC to the original sender + await StableCoin.grantKyc( + new KYCRequest({ + targetId: account.accountId.toString(), + tokenId: stableCoin?.coin?.tokenId?.toString()!, + }), + ); + + // Check balance before cash-in + const initialAmount = await StableCoin.getBalanceOf( + new GetAccountBalanceRequest({ + tokenId: stableCoin?.coin?.tokenId?.toString()!, + targetId: account.accountId.toString(), + }), + ); + + await new Promise((resolve) => setTimeout(resolve, 5000)); + + // Perform cash-in operation + await StableCoin.cashIn( + new CashInRequest({ + amount: '10', + tokenId: stableCoin?.coin?.tokenId?.toString()!, + targetId: account.accountId.toString(), + }), + ); + + await new Promise((resolve) => setTimeout(resolve, 5000)); + + // Check balance after cash-in + const finalAmount = await StableCoin.getBalanceOf( + new GetAccountBalanceRequest({ + tokenId: stableCoin?.coin?.tokenId?.toString()!, + targetId: account.accountId.toString(), + }), + ); + + // Assert that the final amount is as expected + const final = + initialAmount.value.toBigInt() + new BigDecimal('10', 6).toBigInt(); + + assert( + finalAmount.value.toBigInt().toString() === final.toString(), + 'Cash-in operation failed: balance mismatch', + ); + + // Perform wipe operation + await StableCoin.wipe( + new WipeRequest({ + amount: '1', + tokenId: stableCoin?.coin?.tokenId?.toString()!, + targetId: account.accountId.toString(), + }), + ); + + await new Promise((resolve) => setTimeout(resolve, 5000)); + + // Check balance after wipe + const finalAmountAfterWipe = await StableCoin.getBalanceOf( + new GetAccountBalanceRequest({ + tokenId: stableCoin?.coin?.tokenId?.toString()!, + targetId: account.accountId.toString(), + }), + ); + + // Assert that the final amount after wipe is as expected + const finalAfterWipe = + finalAmount.value.toBigInt() - new BigDecimal('1', 6).toBigInt(); + + assert( + finalAmountAfterWipe.value.toBigInt().toString() === + finalAfterWipe.toString(), + 'Wipe operation failed: balance mismatch', + ); + + process.exit(0); +}; + +try { + main(); +} catch (error) { + console.error(error); +} diff --git a/packages/sdk/jest.config.js b/packages/sdk/jest.config.js index 2cdc6cf59..0bc40d82d 100644 --- a/packages/sdk/jest.config.js +++ b/packages/sdk/jest.config.js @@ -10,6 +10,7 @@ module.exports = { '^(\\.{1,2}/.*)\\.(m)?js$': '$1', '@hashgraph/hedera-wallet-connect': '/__mocks__/hedera-wallet-connect.js', + 'fireblocks-sdk': '/__mocks__/fireblocks-sdk.js', '^uuid$': 'uuid', }, testMatch: ['**/__tests__/**/*.(test|spec).[jt]s?(x)'], diff --git a/packages/sdk/package.json b/packages/sdk/package.json index 126c957a2..1a92d2c0a 100644 --- a/packages/sdk/package.json +++ b/packages/sdk/package.json @@ -1,6 +1,6 @@ { "name": "@hashgraph/stablecoin-npm-sdk", - "version": "4.2.0", + "version": "4.3.0", "description": "stablecoin studio SDK", "main": "./build/cjs/src/index.js", "module": "./build/esm/src/index.js", diff --git a/packages/sdk/scripts/AssociateToken.ts b/packages/sdk/scripts/AssociateToken.ts new file mode 100644 index 000000000..06098a977 --- /dev/null +++ b/packages/sdk/scripts/AssociateToken.ts @@ -0,0 +1,87 @@ +/* + * + * Hedera Stablecoin SDK + * + * Copyright (C) 2023 Hedera Hashgraph, LLC + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + * + */ + +/** + * DESCRIPTION + * Associates token 0.0.7981724 with the multisig account 0.0.8201011. + * The multisig account has a 2-of-2 KeyList (ED25519 + ECDSA), so both + * keys must sign the TokenAssociateTransaction. + * The fee payer is the ECDSA account 0.0.1653 (which has funds). + * + * HOW TO RUN IT + * 1- npm run build (inside packages/sdk) + * 2- node build/cjs/src/scripts/AssociateToken.js + * or: npx ts-node scripts/AssociateToken.ts + */ + +import { + TokenAssociateTransaction, + TokenId, + AccountId, + Client, + PrivateKey, +} from '@hiero-ledger/sdk'; + +// Multisig account keys +const ECDSA_1_PRIVATE_KEY = ''; +const ECDSA_2_PRIVATE_KEY = ''; + +// The multisig account to associate the token to +const MULTISIG_ACCOUNT_ID = ''; + +// Token to associate +const TOKEN_ID = ''; + +// Fee payer — single ECDSA account with funds +const FEE_PAYER = { + id: '', + privateKey: '', +}; + +async function associateToken(): Promise { + const ecdsaKey1 = PrivateKey.fromStringECDSA(ECDSA_1_PRIVATE_KEY); + const ecdsaKey2 = PrivateKey.fromStringECDSA(ECDSA_2_PRIVATE_KEY); + + const client = Client.forTestnet().setOperator( + AccountId.fromString(FEE_PAYER.id), + PrivateKey.fromStringECDSA(FEE_PAYER.privateKey), + ); + + const tx = await new TokenAssociateTransaction() + .setAccountId(AccountId.fromString(MULTISIG_ACCOUNT_ID)) + .setTokenIds([TokenId.fromString(TOKEN_ID)]) + .freezeWith(client); + + // Both keys of the KeyList must sign + const signedTx = await (await tx.sign(ecdsaKey1)).sign(ecdsaKey2); + + const response = await signedTx.execute(client); + const receipt = await response.getReceipt(client); + + console.log(`Token ${TOKEN_ID} associated with ${MULTISIG_ACCOUNT_ID}`); + console.log(`Status: ${receipt.status.toString()}`); +} + +associateToken() + .then(() => process.exit(0)) + .catch((err) => { + console.error(err); + process.exit(1); + }); diff --git a/packages/sdk/scripts/CreateMultisigAccount.ts b/packages/sdk/scripts/CreateMultisigAccount.ts new file mode 100644 index 000000000..839e177b9 --- /dev/null +++ b/packages/sdk/scripts/CreateMultisigAccount.ts @@ -0,0 +1,86 @@ +/* + * + * Hedera Stablecoin SDK + * + * Copyright (C) 2023 Hedera Hashgraph, LLC + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + * + */ + +/** + * DESCRIPTION + * Creates a multisig account with a 2-of-2 KeyList (ED25519 + ECDSA). + * The resulting account requires both keys to sign every transaction. + * The fee payer is the ECDSA account 0.0.1653 (which has funds). + * + * HOW TO RUN IT + * 1- npm run build (inside packages/sdk) + * 2- npm run execute:createMultisig + */ + +import { + AccountCreateTransaction, + AccountId, + Client, + Hbar, + KeyList, + PrivateKey, +} from '@hiero-ledger/sdk'; + +// ECDSA private key of account 1 +const Multisig_ECDSA_1_privateKey = ''; + +// ECDSA private key of account 2 +const Multisig_ECDSA_2_privateKey = ''; + +// Payer account (fee payer): It must have funds in the testnet +const deployingAccount = { + id: '', + ECDSA_privateKey: '', +}; + +async function createMultisigAccount(): Promise { + const ecdsaKey1 = PrivateKey.fromStringECDSA(Multisig_ECDSA_1_privateKey); + const ecdsaKey2 = PrivateKey.fromStringECDSA(Multisig_ECDSA_2_privateKey); + const feePayerKey = PrivateKey.fromStringECDSA(deployingAccount.ECDSA_privateKey); + + // 2-of-2 KeyList: both ECDSA keys must sign + const keyList = new KeyList([ecdsaKey1.publicKey, ecdsaKey2.publicKey], 2); + + const client = Client.forTestnet().setOperator( + AccountId.fromString(deployingAccount.id), + feePayerKey, + ); + + const tx = await new AccountCreateTransaction() + .setKeyWithoutAlias(keyList) + .setInitialBalance(new Hbar(0)) + .execute(client); + + const receipt = await tx.getReceipt(client); + const newAccountId = receipt.accountId; + + if (!newAccountId) { + throw new Error('Error creating multisig account'); + } + + console.log(`Multisig account created: ${newAccountId.toString()}`); +} + +createMultisigAccount() + .then(() => process.exit(0)) + .catch((err) => { + console.error(err); + process.exit(1); + }); diff --git a/packages/sdk/src/app/usecase/command/stablecoin/backend/sign/SignCommandHandler.ts b/packages/sdk/src/app/usecase/command/stablecoin/backend/sign/SignCommandHandler.ts index 5087bddca..b181a4d1b 100644 --- a/packages/sdk/src/app/usecase/command/stablecoin/backend/sign/SignCommandHandler.ts +++ b/packages/sdk/src/app/usecase/command/stablecoin/backend/sign/SignCommandHandler.ts @@ -63,9 +63,23 @@ export class SignCommandHandler implements ICommandHandler { ); // extracts bytes to sign + const signClient = + transaction.network === 'custom' && + transaction.consensus_nodes?.length + ? Client.forNetwork( + Object.fromEntries( + transaction.consensus_nodes.map( + (n: { url: string; nodeId: string }) => [ + n.url, + n.nodeId, + ], + ), + ), + ) + : Client.forName(transaction.network); const deserializedTransaction = Transaction.fromBytes( Hex.toUint8Array(transaction.transaction_message), - ).freezeWith(Client.forName(transaction.network)); + ).freezeWith(signClient); if ( !deserializedTransaction || !deserializedTransaction._signedTransactions diff --git a/packages/sdk/src/domain/context/network/Environment.ts b/packages/sdk/src/domain/context/network/Environment.ts index 035b0a335..4d31688ee 100644 --- a/packages/sdk/src/domain/context/network/Environment.ts +++ b/packages/sdk/src/domain/context/network/Environment.ts @@ -22,6 +22,7 @@ export const testnet = 'testnet'; export const previewnet = 'previewnet'; export const mainnet = 'mainnet'; export const local = 'local'; +export const custom = 'custom'; export const unrecognized = 'unrecognized'; export type Environment = @@ -29,6 +30,7 @@ export type Environment = | 'previewnet' | 'mainnet' | 'local' + | 'custom' | 'unrecognized' | string; diff --git a/packages/sdk/src/domain/context/transaction/MultiSigTransaction.ts b/packages/sdk/src/domain/context/transaction/MultiSigTransaction.ts index 42cffa365..108a490a4 100644 --- a/packages/sdk/src/domain/context/transaction/MultiSigTransaction.ts +++ b/packages/sdk/src/domain/context/transaction/MultiSigTransaction.ts @@ -40,6 +40,7 @@ export class MultiSigTransaction { network: string; hedera_account_id: string; start_date: string; + consensus_nodes?: { url: string; nodeId: string }[]; constructor( id: string, @@ -53,6 +54,7 @@ export class MultiSigTransaction { network: string, hedera_account_id: string, start_date: string, + consensus_nodes?: { url: string; nodeId: string }[], ) { this.id = id; this.transaction_message = transaction_message; @@ -65,6 +67,7 @@ export class MultiSigTransaction { this.network = network; this.hedera_account_id = hedera_account_id; this.start_date = start_date; + this.consensus_nodes = consensus_nodes; } } diff --git a/packages/sdk/src/port/out/backend/BackendAdapter.ts b/packages/sdk/src/port/out/backend/BackendAdapter.ts index 4de1a6bbf..e2caabde8 100644 --- a/packages/sdk/src/port/out/backend/BackendAdapter.ts +++ b/packages/sdk/src/port/out/backend/BackendAdapter.ts @@ -53,6 +53,7 @@ export class BackendAdapter { threshold: number, network: Environment, startDate: Date, + consensusNodes?: { url: string; nodeId: string }[], ): Promise { try { const body = { @@ -63,6 +64,7 @@ export class BackendAdapter { threshold: threshold, network: network, start_date: startDate, + consensus_nodes: consensusNodes ?? null, }; //TODO: error because url is not defined diff --git a/packages/sdk/src/port/out/hs/client/ClientTransactionAdapter.ts b/packages/sdk/src/port/out/hs/client/ClientTransactionAdapter.ts index a718c2b93..0a6539c28 100644 --- a/packages/sdk/src/port/out/hs/client/ClientTransactionAdapter.ts +++ b/packages/sdk/src/port/out/hs/client/ClientTransactionAdapter.ts @@ -84,7 +84,21 @@ export class ClientTransactionAdapter extends BaseHederaTransactionAdapter { this.account = account; this.account.publicKey = accountMirror.publicKey; this.network = this.networkService.environment; - this._client = Client.forName(this.networkService.environment); + if ( + this.networkService.environment === 'custom' && + this.networkService.consensusNodes?.length + ) { + this._client = Client.forNetwork( + Object.fromEntries( + this.networkService.consensusNodes.map((n) => [ + n.url, + n.nodeId, + ]), + ), + ); + } else { + this._client = Client.forName(this.networkService.environment); + } const id = this.account.id?.value ?? ''; if (!account.privateKey) throw new WalletConnectError( @@ -170,10 +184,16 @@ export class ClientTransactionAdapter extends BaseHederaTransactionAdapter { try { const privateKey = this.account.privateKey.toHashgraphKey(); - const signedTx = await message.sign(privateKey); // firma y retorna Transaction - - const bytes = signedTx.toBytes(); // Uint8Array - return Hex.fromUint8Array(bytes); + // Sign only the body bytes and return the raw signature — matching HWC behavior. + // SubmitCommandHandler uses addSignature(publicKey, rawSig) so it expects raw bytes. + const bodyBytes = + message._signedTransactions.get(0)?.bodyBytes; + if (!bodyBytes) + throw new SigningError( + 'No body bytes found in frozen transaction', + ); + const rawSignature = privateKey.sign(bodyBytes); + return Hex.fromUint8Array(rawSignature); } catch (error) { LogService.logError(error); throw new SigningError(error); diff --git a/packages/sdk/src/port/out/hs/custodial/CustodialTransactionAdapter.ts b/packages/sdk/src/port/out/hs/custodial/CustodialTransactionAdapter.ts index e7f2d83d1..872bf7568 100644 --- a/packages/sdk/src/port/out/hs/custodial/CustodialTransactionAdapter.ts +++ b/packages/sdk/src/port/out/hs/custodial/CustodialTransactionAdapter.ts @@ -79,6 +79,21 @@ export abstract class CustodialTransactionAdapter extends BaseHederaTransactionA case 'previewnet': this.client = Client.forPreviewnet(); break; + case 'custom': + if (!this.networkService.consensusNodes?.length) { + throw new Error( + 'Custom network requires at least one consensus node', + ); + } + this.client = Client.forNetwork( + Object.fromEntries( + this.networkService.consensusNodes.map((n) => [ + n.url, + n.nodeId, + ]), + ), + ); + break; default: throw new Error('Network not supported'); } diff --git a/packages/sdk/src/port/out/hs/multiSig/MultiSigTransactionAdapter.ts b/packages/sdk/src/port/out/hs/multiSig/MultiSigTransactionAdapter.ts index 09392609b..d5b5dd33e 100644 --- a/packages/sdk/src/port/out/hs/multiSig/MultiSigTransactionAdapter.ts +++ b/packages/sdk/src/port/out/hs/multiSig/MultiSigTransactionAdapter.ts @@ -34,11 +34,7 @@ import NetworkService from '../../../../app/service/NetworkService.js'; import { MirrorNodeAdapter } from '../../mirror/MirrorNodeAdapter.js'; import { BackendAdapter } from '../../backend/BackendAdapter.js'; import { SupportedWallets } from '../../../../domain/context/network/Wallet.js'; -import { - Environment, - previewnet, - mainnet, -} from '../../../../domain/context/network/Environment.js'; +import { Environment } from '../../../../domain/context/network/Environment.js'; import Injectable from '../../../../core/Injectable.js'; import { InitializationData } from '../../TransactionAdapter.js'; import LogService from '../../../../app/service/LogService.js'; @@ -93,14 +89,6 @@ export class MultiSigTransactionAdapter extends BaseHederaTransactionAdapter { t.setTransactionValidDuration(180); t._freezeWithAccountId(accountId); - let client: Client = Client.forTestnet(); - - if (this.networkService.environment == previewnet) { - client = Client.forPreviewnet(); - } else if (this.networkService.environment == mainnet) { - client = Client.forMainnet(); - } - if ( !this.networkService.consensusNodes || this.networkService.consensusNodes.length == 0 @@ -110,10 +98,11 @@ export class MultiSigTransactionAdapter extends BaseHederaTransactionAdapter { ); } - client.setNetwork({ - [this.networkService.consensusNodes[0].url]: - this.networkService.consensusNodes[0].nodeId, - }); + const client = Client.forNetwork( + Object.fromEntries( + this.networkService.consensusNodes.map((n) => [n.url, n.nodeId]), + ), + ); if (!this.account.multiKey) { throw new Error('MultiKey not found in the account'); @@ -134,6 +123,7 @@ export class MultiSigTransactionAdapter extends BaseHederaTransactionAdapter { this.account.multiKey.threshold, this.networkService.environment, new Date(dateStr), + this.networkService.consensusNodes, ); return new TransactionResponse(transactionId);