Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
Original file line number Diff line number Diff line change
Expand Up @@ -85,8 +85,6 @@ describe('Create NFT Integration Tests', () => {
expect(createNftOutput.name).toBe('Test NFT');
expect(createNftOutput.alias).toBe('test-nft');
expect(createNftOutput.treasuryId).toBe(viewAccountOutput.accountId);
expect(createNftOutput.adminAccountId).toBe(viewAccountOutput.accountId);
expect(createNftOutput.supplyAccountId).toBe(viewAccountOutput.accountId);
expect(createNftOutput.symbol).toBe('NFT');
expect(createNftOutput.supplyType).toBe(SupplyType.FINITE);
});
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -90,8 +90,6 @@ describe('Mint NFT Integration Tests', () => {
expect(createNftOutput.name).toBe('Test NFT Collection');
expect(createNftOutput.alias).toBe('test-nft-collection');
expect(createNftOutput.treasuryId).toBe(viewAccountOutput.accountId);
expect(createNftOutput.adminAccountId).toBe(viewAccountOutput.accountId);
expect(createNftOutput.supplyAccountId).toBe(viewAccountOutput.accountId);
expect(createNftOutput.symbol).toBe('TNFT');
expect(createNftOutput.supplyType).toBe(SupplyType.FINITE);

Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -106,12 +106,6 @@ describe('Transfer NFT Integration Tests', () => {
expect(createNftOutput.treasuryId).toBe(
createSourceAccountOutput.accountId,
);
expect(createNftOutput.adminAccountId).toBe(
createSourceAccountOutput.accountId,
);
expect(createNftOutput.supplyAccountId).toBe(
createSourceAccountOutput.accountId,
);
expect(createNftOutput.symbol).toBe('TNFTC');
expect(createNftOutput.supplyType).toBe(SupplyType.FINITE);

Expand Down
7 changes: 5 additions & 2 deletions src/core/services/token/token-service.ts
Original file line number Diff line number Diff line change
Expand Up @@ -112,8 +112,11 @@ export class TokenServiceImpl implements TokenService {
.setTokenType(TokenTypeMap[tokenType])
.setInitialSupply(initialSupplyRaw)
.setSupplyType(tokenSupplyType)
.setTreasuryAccountId(AccountId.fromString(treasuryId))
.setAdminKey(adminPublicKey);
.setTreasuryAccountId(AccountId.fromString(treasuryId));

if (adminPublicKey) {
tokenCreateTx.setAdminKey(adminPublicKey);
}

// Set max supply for finite supply tokens
if (supplyType === SupplyType.FINITE && maxSupplyRaw !== undefined) {
Expand Down
2 changes: 1 addition & 1 deletion src/core/types/token.types.ts
Original file line number Diff line number Diff line change
Expand Up @@ -63,7 +63,7 @@ export interface TokenCreateParams {
tokenType: HederaTokenType;
supplyType: SupplyType;
maxSupplyRaw?: bigint; // Required for FINITE supply type
adminPublicKey: PublicKey;
adminPublicKey?: PublicKey;
supplyPublicKey?: PublicKey;
wipePublicKey?: PublicKey;
kycPublicKey?: PublicKey;
Expand Down
4 changes: 2 additions & 2 deletions src/core/utils/resolve-payer.ts
Original file line number Diff line number Diff line change
@@ -1,7 +1,7 @@
import type { CoreApi } from '@/core';
import type { KeyManagerName } from '@/core/services/kms/kms-types.interface';

import { PrivateKeySchema } from '@/core';
import { KeySchema } from '@/core';

/**
* Resolves payer from string (alias or account-id:private-key format)
Expand All @@ -17,7 +17,7 @@ export async function resolvePayer(
): Promise<void> {
const keyManager =
coreApi.config.getOption<KeyManagerName>('default_key_manager') || 'local';
const parsedPayer = PrivateKeySchema.parse(payerString);
const parsedPayer = KeySchema.parse(payerString);
const resolvedPayer = await coreApi.keyResolver.resolveAccountCredentials(
parsedPayer,
keyManager,
Expand Down
8 changes: 1 addition & 7 deletions src/plugins/account/__tests__/unit/import.test.ts
Original file line number Diff line number Diff line change
Expand Up @@ -63,20 +63,14 @@ describe('account plugin - import command (ADR-003)', () => {

const result = await importAccount(args);

expect(kms.importAndValidatePrivateKey).toHaveBeenCalledWith(
KeyAlgorithm.ECDSA,
'abcdef1234567890abcdef1234567890abcdef1234567890abcdef1234567890',
'0230a1f42abc4794541e4a4389ec7e822666b8a7693c4cc3dedd2746b32f9c015b',
'local',
);
expect(mirrorMock.getAccount).toHaveBeenCalledWith('0.0.9999');
expect(alias.register).toHaveBeenCalledWith(
expect.objectContaining({
alias: 'imported',
type: AliasType.Account,
network: 'testnet',
entityId: '0.0.9999',
publicKey: 'pub-key-test',
publicKey: expect.any(String),
keyRefId: 'kr_test123',
}),
);
Expand Down
59 changes: 19 additions & 40 deletions src/plugins/account/commands/import/handler.ts
Original file line number Diff line number Diff line change
@@ -1,14 +1,9 @@
/**
* Account Import Command Handler
* Handles importing existing accounts using the Core API
* Follows ADR-003 contract: returns CommandExecutionResult
*/
import type { CommandHandlerArgs, CommandResult } from '@/core';
import type { KeyManagerName } from '@/core/services/kms/kms-types.interface';
import type { AccountData } from '@/plugins/account/schema';
import type { ImportAccountOutput } from './output';

import { StateError, ValidationError } from '@/core/errors';
import { StateError } from '@/core/errors';
import { AliasType } from '@/core/services/alias/alias-service.interface';
import { composeKey } from '@/core/utils/key-composer';
import { buildAccountEvmAddress } from '@/plugins/account/utils/account-address';
Expand All @@ -21,53 +16,40 @@ export async function importAccount(
): Promise<CommandResult> {
const { api, logger } = args;

// Initialize Zustand state helper
const accountState = new ZustandAccountStateHelper(api.state, logger);

// Parse and validate command arguments
const validArgs = ImportAccountInputSchema.parse(args.args);

const key = validArgs.key;
const alias = validArgs.name;
const keyManagerArg = validArgs.keyManager;
const accountId = key.accountId;
const network = api.network.getCurrentNetwork();
const accountKey = composeKey(network, accountId);

if (accountState.hasAccount(accountKey)) {
throw new StateError('Account with this ID is already saved in state');
}

// Get keyManager from args or fallback to config
const keyManager =
keyManagerArg ||
keyManagerArg ??
api.config.getOption<KeyManagerName>('default_key_manager');

// Check if account name already exists
api.alias.availableOrThrow(alias, network);

// Get account info from mirror node
const accountInfo = await api.mirror.getAccount(key.accountId);

const { keyRefId, publicKey } = api.kms.importAndValidatePrivateKey(
accountInfo.keyAlgorithm,
key.privateKey,
accountInfo.accountPublicKey,
const resolved = await api.keyResolver.resolveAccountCredentials(
validArgs.key,
keyManager,
['account:import'],
);

logger.info(`Importing account: ${accountKey} (${accountId})`);
const accountId = resolved.accountId;
const accountKey = composeKey(network, accountId);

// Check if account name already exists
if (accountState.hasAccount(accountKey)) {
throw new ValidationError(
`Account with identifier '${accountKey}' already exists`,
);
throw new StateError('Account with this ID is already saved in state');
}

api.alias.availableOrThrow(alias, network);

const accountInfo = await api.mirror.getAccount(accountId);

logger.info(`Importing account: ${accountKey} (${accountId})`);

const evmAddress = buildAccountEvmAddress({
accountId,
publicKey,
publicKey: resolved.publicKey,
keyType: accountInfo.keyAlgorithm,
existingEvmAddress: accountInfo.evmAddress,
});
Expand All @@ -79,27 +61,24 @@ export async function importAccount(
network: api.network.getCurrentNetwork(),
entityId: accountId,
evmAddress,
publicKey,
keyRefId,
publicKey: resolved.publicKey,
keyRefId: resolved.keyRefId,
createdAt: new Date().toISOString(),
});
}

// Create account object (no private key in plugin state)
const account: AccountData = {
name: alias,
accountId,
type: accountInfo.keyAlgorithm,
publicKey: publicKey,
publicKey: resolved.publicKey,
evmAddress,
keyRefId,
keyRefId: resolved.keyRefId,
network: api.network.getCurrentNetwork(),
};

// Store account in state using the helper
accountState.saveAccount(accountKey, account);

// Prepare output data
const outputData: ImportAccountOutput = {
accountId,
name: alias,
Expand Down
6 changes: 3 additions & 3 deletions src/plugins/account/commands/import/input.ts
Original file line number Diff line number Diff line change
@@ -1,18 +1,18 @@
import { z } from 'zod';

import {
AccountIdWithPrivateKeySchema,
AccountNameSchema,
KeyManagerTypeSchema,
KeySchema,
} from '@/core/schemas';

/**
* Input schema for account import command
* Validates arguments for importing an existing account
*/
export const ImportAccountInputSchema = z.object({
key: AccountIdWithPrivateKeySchema.describe(
'Account ID with private key in format accountId:privateKey',
key: KeySchema.describe(
'Account credentials. Can be accountId:privateKey pair, key reference or account alias.',
),
name: AccountNameSchema.optional().describe('Optional account name/alias'),
keyManager: KeyManagerTypeSchema.optional().describe(
Expand Down
8 changes: 2 additions & 6 deletions src/plugins/network/commands/set-operator/input.ts
Original file line number Diff line number Diff line change
@@ -1,17 +1,13 @@
import { z } from 'zod';

import {
KeyManagerTypeSchema,
NetworkSchema,
PrivateKeyWithAccountIdSchema,
} from '@/core/schemas';
import { KeyManagerTypeSchema, KeySchema, NetworkSchema } from '@/core/schemas';

/**
* Input schema for network set-operator command
* Validates arguments for setting operator credentials
*/
export const SetOperatorInputSchema = z.object({
operator: PrivateKeyWithAccountIdSchema.describe(
operator: KeySchema.describe(
'Operator credentials. Can be accountId:privateKey pair, key reference or account alias.',
),
network: NetworkSchema.optional().describe(
Expand Down
14 changes: 12 additions & 2 deletions src/plugins/token/__tests__/unit/create-nft.test.ts
Original file line number Diff line number Diff line change
Expand Up @@ -124,7 +124,7 @@ describe('createNftHandler', () => {
saveToken: mockSaveToken,
}));

const { api, tokenTransactions, signing } = makeApiMocks({
const { api, tokenTransactions, signing, keyResolver } = makeApiMocks({
tokenTransactions: {
createTokenTransaction: jest
.fn()
Expand All @@ -135,11 +135,17 @@ describe('createNftHandler', () => {
},
});

keyResolver.resolveSigningKey.mockResolvedValue({
keyRefId: 'supply-key-ref-id',
publicKey: '302a300506032b6570032100' + '0'.repeat(64),
});

const logger = makeLogger();
const args: CommandHandlerArgs = {
args: {
tokenName: 'TestToken',
symbol: 'TEST',
supplyKey: 'test-supply-key',
},
api,
state: api.state,
Expand All @@ -160,7 +166,7 @@ describe('createNftHandler', () => {
maxSupplyRaw: undefined,
treasuryId: '0.0.100000',
tokenType: HederaTokenType.NON_FUNGIBLE_TOKEN,
adminPublicKey: expect.any(Object),
adminPublicKey: undefined,
supplyPublicKey: expect.any(Object),
memo: undefined,
});
Expand All @@ -181,12 +187,16 @@ describe('createNftHandler', () => {
keyResolver.resolveAccountCredentialsWithFallback.mockImplementation(() =>
Promise.reject(new Error('No operator set')),
);
keyResolver.resolveSigningKey.mockImplementation(() =>
Promise.reject(new Error('No operator set')),
);

const logger = makeLogger();
const args: CommandHandlerArgs = {
args: {
tokenName: 'TestToken',
symbol: 'TEST',
supplyKey: 'test-supply-key',
},
api,
state: api.state,
Expand Down
2 changes: 1 addition & 1 deletion src/plugins/token/__tests__/unit/create.test.ts
Original file line number Diff line number Diff line change
Expand Up @@ -158,7 +158,7 @@ describe('createTokenHandler', () => {
supplyType: SupplyType.INFINITE,
maxSupplyRaw: undefined,
treasuryId: '0.0.100000',
adminPublicKey: expect.any(Object),
adminPublicKey: undefined,
tokenType: HederaTokenType.FUNGIBLE_COMMON,
memo: undefined,
});
Expand Down
2 changes: 1 addition & 1 deletion src/plugins/token/__tests__/unit/createFromFile.test.ts
Original file line number Diff line number Diff line change
Expand Up @@ -612,7 +612,7 @@ describe('createTokenFromFileHandler', () => {

// Act & Assert
await expect(createTokenFromFile(args)).rejects.toThrow(
'Private key with account ID must be a valid account ID and private key pair in {account-id:private-key} format, key reference or alias name',
'Key must be a valid account ID and private key pair in format {account-id:private-key}, account ID, private key in format {ed25519|ecdsa}:{private-key}, public key in format {ed25519|ecdsa}:{public-key}, key reference, EVM address (0x...) or alias name',
);
});

Expand Down
4 changes: 2 additions & 2 deletions src/plugins/token/__tests__/unit/createNftFromFile.test.ts
Original file line number Diff line number Diff line change
Expand Up @@ -618,7 +618,7 @@ describe('createNftFromFileHandler', () => {
});

await expect(createNftFromFile(args)).rejects.toThrow(
'Private key with account ID must be a valid account ID and private key pair in {account-id:private-key} format, key reference or alias name',
'Key must be a valid account ID and private key pair in format {account-id:private-key}, account ID, private key in format {ed25519|ecdsa}:{private-key}, public key in format {ed25519|ecdsa}:{public-key}, key reference, EVM address (0x...) or alias name',
);
});

Expand Down Expand Up @@ -662,7 +662,7 @@ describe('createNftFromFileHandler', () => {
});

await expect(createNftFromFile(args)).rejects.toThrow(
'Private key with account ID must be a valid account ID and private key pair in {account-id:private-key} format, key reference or alias name',
'Key must be a valid account ID and private key pair in format {account-id:private-key}, account ID, private key in format {ed25519|ecdsa}:{private-key}, public key in format {ed25519|ecdsa}:{public-key}, key reference, EVM address (0x...) or alias name',
);
});

Expand Down
4 changes: 2 additions & 2 deletions src/plugins/token/commands/associate/input.ts
Original file line number Diff line number Diff line change
Expand Up @@ -3,7 +3,7 @@ import { z } from 'zod';
import {
EntityReferenceSchema,
KeyManagerTypeSchema,
PrivateKeyWithAccountIdSchema,
KeySchema,
} from '@/core/schemas';

/**
Expand All @@ -12,7 +12,7 @@ import {
*/
export const AssociateTokenInputSchema = z.object({
token: EntityReferenceSchema.describe('Token identifier (ID or name)'),
account: PrivateKeyWithAccountIdSchema.describe(
account: KeySchema.describe(
'Account to associate. Can be {accountId}:{privateKey pair}, key reference or account alias.',
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

change description so that we can use any format of the key

),
keyManager: KeyManagerTypeSchema.optional().describe(
Expand Down
11 changes: 6 additions & 5 deletions src/plugins/token/commands/create-ft/handler.ts
Original file line number Diff line number Diff line change
Expand Up @@ -57,10 +57,11 @@ export async function createToken(
['token:treasury'],
);

const admin = await api.keyResolver.resolveAccountCredentialsWithFallback(
const admin = await resolveOptionalKey(
validArgs.adminKey,
keyManager,
['token:admin'],
api.keyResolver,
'token:admin',
);

const supply = await resolveOptionalKey(
Expand Down Expand Up @@ -99,7 +100,7 @@ export async function createToken(
tokenType,
supplyType,
maxSupplyRaw: finalMaxSupply,
adminPublicKey: PublicKey.fromString(admin.publicKey),
adminPublicKey: admin ? PublicKey.fromString(admin.publicKey) : undefined,
supplyPublicKey: supply
? PublicKey.fromString(supply.publicKey)
: undefined,
Expand All @@ -108,7 +109,7 @@ export async function createToken(

const txSigners = [treasury.keyRefId];

if (validArgs.adminKey) {
if (admin) {
txSigners.push(admin.keyRefId);
}

Expand All @@ -131,7 +132,7 @@ export async function createToken(
initialSupply,
tokenType,
supplyType,
adminPublicKey: admin.publicKey,
adminPublicKey: admin?.publicKey,
supplyPublicKey: supply ? supply.publicKey : undefined,
network: api.network.getCurrentNetwork(),
});
Expand Down
Loading