Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
Show all changes
23 commits
Select commit Hold shift + click to select a range
7cc712e
feat(1670): added metadata key to create-ft and also other keys as we…
mmyslblocky Mar 20, 2026
ffac0af
feat(1670): set freeze default
mmyslblocky Mar 23, 2026
f3f40a2
feat(1670): set freeze default to undefined
mmyslblocky Mar 23, 2026
16e54ae
feat(1670): add auto-renew to the accounts
mmyslblocky Mar 23, 2026
c0de320
feat(1670): updated scripts and readme
mmyslblocky Mar 23, 2026
45f0f02
feat(1670): changes after code review
mmyslblocky Mar 25, 2026
9ec6632
feat(1670): added new fields to the output in a view command
mmyslblocky Mar 25, 2026
a9494d1
feat(1670): added new fields to the output in a view command
mmyslblocky Mar 25, 2026
0b6bbf2
feat(1670): fix tests
mmyslblocky Mar 25, 2026
e6cdd8a
feat(1670): fix tests
mmyslblocky Mar 25, 2026
a65519d
feat(1670): added validation for expirationTime
mmyslblocky Mar 25, 2026
8a0114c
feat(1670): introduced const in fixtures
mmyslblocky Mar 26, 2026
cd2550b
feat(1670): move function to util
mmyslblocky Mar 26, 2026
334805c
feat(1670): review chagnes
mmyslblocky Mar 26, 2026
9c7cb52
feat(1670): resolved merge conflicts
mmyslblocky Mar 26, 2026
6462614
feat(1670): resolved schema
mmyslblocky Mar 30, 2026
51515aa
feat(1670): resolved merge conflicts
mmyslblocky Mar 30, 2026
66bdd90
feat(1670): removed console logs
mmyslblocky Mar 30, 2026
b6b2959
feat(1670): fix integration tests
mmyslblocky Mar 30, 2026
c146ff4
feat(1670): resolved merge conflicts
mmyslblocky Mar 30, 2026
6535656
feat(1670): resolved PR remarks
mmyslblocky Mar 30, 2026
d9e94ec
feat(1670): resolved PR remarks
mmyslblocky Mar 30, 2026
95d2911
feat(1670): resolved PR remarks
mmyslblocky Mar 30, 2026
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
15 changes: 13 additions & 2 deletions docs/output-schemas-guide.md
Original file line number Diff line number Diff line change
Expand Up @@ -318,10 +318,16 @@ interface CommandOutputSpec {
"initialSupply": "1000000",
"supplyType": "INFINITE",
"transactionId": "0.0.123@1700000000.123456789",
"alias": "my-token"
"alias": "my-token",
"network": "testnet",
"autoRenewPeriodSeconds": 2592000,
"autoRenewAccountId": "0.0.11111",
"expirationTime": "2030-01-01T00:00:00.000Z"
}
```

`autoRenewPeriodSeconds`, `autoRenewAccountId`, and `expirationTime` are **optional**. They are present when auto-renew or fixed expiration was configured; `expirationTime` is an ISO 8601 string when a fixed expiration was used (omitted when auto-renew period + account take precedence).

#### `token transfer-ft`

**Output**:
Expand Down Expand Up @@ -417,10 +423,15 @@ interface CommandOutputSpec {
"success": true,
"transactionId": "0.0.123@1700000000.123456789"
}
]
],
"autoRenewPeriodSeconds": 2592000,
"autoRenewAccountId": "0.0.11111",
"expirationTime": "2030-01-01T00:00:00.000Z"
}
```

Same optional lifecycle fields as `token create-ft`: `autoRenewPeriodSeconds`, `autoRenewAccountId`, `expirationTime` (ISO string when fixed expiration was applied).

#### `token create-nft-from-file`

**Output**:
Expand Down
2 changes: 2 additions & 0 deletions examples/token/token-fixed-fee-hbar.json
Original file line number Diff line number Diff line change
Expand Up @@ -8,6 +8,8 @@
"treasuryKey": "<alias or accountId:privateKey>",
"adminKey": "<alias or accountId:privateKey>",
"memo": "Token with fixed fee paid in HBAR (tinybars)",
"autoRenewPeriod": "30d",
"autoRenewAccount": "<alias or accountId:privateKey>",
"customFees": [
{
"type": "fixed",
Expand Down
2 changes: 2 additions & 0 deletions examples/token/token-fixed-fee-token.json
Original file line number Diff line number Diff line change
Expand Up @@ -8,6 +8,8 @@
"treasuryKey": "<alias or accountId:privateKey>",
"adminKey": "<alias or accountId:privateKey>",
"memo": "Token with fixed fee paid in same token units",
"autoRenewPeriod": "30d",
"autoRenewAccount": "<alias or accountId:privateKey>",
"customFees": [
{
"type": "fixed",
Expand Down
2 changes: 2 additions & 0 deletions examples/token/token-fractional-fee-receiver-pays.json
Original file line number Diff line number Diff line change
Expand Up @@ -8,6 +8,8 @@
"treasuryKey": "<alias or accountId:privateKey>",
"adminKey": "<alias or accountId:privateKey>",
"memo": "Token with fractional fee (receiver pays, netOfTransfers=false)",
"autoRenewPeriod": "30d",
"autoRenewAccount": "<alias or accountId:privateKey>",
"customFees": [
{
"type": "fractional",
Expand Down
2 changes: 2 additions & 0 deletions examples/token/token-fractional-fee-sender-pays.json
Original file line number Diff line number Diff line change
Expand Up @@ -8,6 +8,8 @@
"treasuryKey": "<alias or accountId:privateKey>",
"adminKey": "<alias or accountId:privateKey>",
"memo": "Token with fractional fee (sender pays, netOfTransfers=true)",
"autoRenewPeriod": "30d",
"autoRenewAccount": "<alias or accountId:privateKey>",
"customFees": [
{
"type": "fractional",
Expand Down
2 changes: 2 additions & 0 deletions examples/token/token-full-example.json
Original file line number Diff line number Diff line change
Expand Up @@ -14,6 +14,8 @@
"pauseKey": "<alias or accountId:privateKey>",
"feeScheduleKey": "<alias or accountId:privateKey>",
"memo": "Full example token with all fields",
"autoRenewPeriod": "90d",
"autoRenewAccount": "<alias or accountId:privateKey>",
"associations": ["<alias or accountId:privateKey>", "..."],
"customFees": [
{
Expand Down
39 changes: 23 additions & 16 deletions skills/hiero-cli/references/token.md
Original file line number Diff line number Diff line change
Expand Up @@ -12,21 +12,24 @@ Commands marked **[batchify]** support the `--batch <name>` flag to queue into a

Create a new fungible token with specified properties.

| Option | Short | Type | Required | Default | Description |
| ------------------ | ----- | ------ | -------- | -------------- | ------------------------------------------------------------------------------------------------------------------------------------------ |
| `--token-name` | `-T` | string | **yes** | — | Token name |
| `--symbol` | `-Y` | string | **yes** | — | Token symbol |
| `--treasury` | `-t` | string | no | operator | Treasury account: `accountId:privateKey`, key reference, or alias |
| `--decimals` | `-d` | number | no | `0` | Number of decimal places |
| `--initial-supply` | `-i` | string | no | `1000000` | Initial supply. Default: display units. Append `"t"` for raw units |
| `--supply-type` | `-S` | string | no | `INFINITE` | Supply type: `INFINITE` or `FINITE` |
| `--max-supply` | `-m` | string | no | — | Max supply (required when `supply-type=FINITE`). Append `"t"` for raw units |
| `--admin-key` | `-a` | string | no | operator key | Admin key: `accountId:privateKey`, `{ed25519\|ecdsa}:private:{hex}`, key reference, or alias |
| `--supply-key` | `-s` | string | no | — | Supply key: `accountId:privateKey`, account ID, `{ed25519\|ecdsa}:public:{hex}`, `{ed25519\|ecdsa}:private:{hex}`, key reference, or alias |
| `--name` | `-n` | string | no | — | Local alias to register for this token |
| `--key-manager` | `-k` | string | no | config default | Key manager: `local` or `local_encrypted` |
| `--memo` | `-M` | string | no | — | Token memo (max 100 chars) |
| `--batch` | `-B` | string | no | — | Queue into a named batch instead of executing immediately |
| Option | Short | Type | Required | Default | Description |
| ---------------------- | ----- | ------ | -------- | -------------- | ------------------------------------------------------------------------------------------------------------------------------------------ |
| `--token-name` | `-T` | string | **yes** | — | Token name |
| `--symbol` | `-Y` | string | **yes** | — | Token symbol |
| `--treasury` | `-t` | string | no | operator | Treasury account: `accountId:privateKey`, key reference, or alias |
| `--decimals` | `-d` | number | no | `0` | Number of decimal places |
| `--initial-supply` | `-i` | string | no | `1000000` | Initial supply. Default: display units. Append `"t"` for raw units |
| `--supply-type` | `-S` | string | no | `INFINITE` | Supply type: `INFINITE` or `FINITE` |
| `--max-supply` | `-m` | string | no | — | Max supply (required when `supply-type=FINITE`). Append `"t"` for raw units |
| `--admin-key` | `-a` | string | no | operator key | Admin key: `accountId:privateKey`, `{ed25519\|ecdsa}:private:{hex}`, key reference, or alias |
| `--supply-key` | `-s` | string | no | — | Supply key: `accountId:privateKey`, account ID, `{ed25519\|ecdsa}:public:{hex}`, `{ed25519\|ecdsa}:private:{hex}`, key reference, or alias |
| `--name` | `-n` | string | no | — | Local alias to register for this token |
| `--key-manager` | `-k` | string | no | config default | Key manager: `local` or `local_encrypted` |
| `--memo` | `-M` | string | no | — | Token memo (max 100 chars) |
| `--auto-renew-period` | `-R` | string | no | — | Auto-renew interval: integer = seconds, or suffix `s` / `m` / `h` / `d`. Requires `--auto-renew-account` |
| `--auto-renew-account` | `-A` | string | no | — | Account that pays auto-renewal (alias, `accountId:key`, key reference, etc.) |
| `--expiration-time` | `-x` | string | no | — | Fixed expiration (ISO 8601). Ignored (with warning) if auto-renew period + account are set |
| `--batch` | `-B` | string | no | — | Queue into a named batch instead of executing immediately |

**Example:**

Expand All @@ -35,7 +38,7 @@ hcli token create-ft --token-name "MyToken" --symbol MTK --decimals 2 --initial-
hcli token create-ft --token-name "MyToken" --symbol MTK --batch myBatch
```

**Output:** `{ tokenId, name, symbol, decimals, initialSupply, transactionId }`
**Output:** `{ tokenId, name, symbol, treasuryId, decimals, initialSupply, supplyType, transactionId, alias?, network, autoRenewPeriodSeconds?, autoRenewAccountId?, expirationTime? }` — `expirationTime` is an ISO string when fixed expiration was used; lifecycle fields are omitted when not set.

---

Expand Down Expand Up @@ -85,6 +88,10 @@ hcli token create-ft-from-file --file ./my-token.json
hcli token create-ft-from-file --file ./my-token.json --batch myBatch
```

Optional JSON fields in the definition file: `autoRenewPeriod` (seconds or suffixed duration), `autoRenewAccount` (same formats as treasury/keys), `expirationTime` (ISO 8601). If `autoRenewPeriod` is set, `autoRenewAccount` is required; if both auto-renew fields are set, `expirationTime` is ignored (warning logged).

**Output:** Same shape as CLI `create-ft`, plus `associations[]`, including optional `autoRenewPeriodSeconds`, `autoRenewAccountId`, `expirationTime`.

---

### `hcli token create-nft-from-file` [batchify]
Expand Down
25 changes: 24 additions & 1 deletion src/core/schemas/__tests__/unit/common-schemas.test.ts
Original file line number Diff line number Diff line change
Expand Up @@ -9,8 +9,12 @@ import {
SHORT_KEY,
TEST_ACCOUNT_ID,
} from '@/core/schemas/__tests__/helpers/fixtures';
import { AccountIdWithPrivateKeySchema } from '@/core/schemas/common-schemas';
import {
AccountIdWithPrivateKeySchema,
ExpirationTimeSchema,
} from '@/core/schemas/common-schemas';
import { INVALID_KEY } from '@/core/services/topic/__tests__/unit/mocks';
import { DAY_IN_SECONDS } from '@/core/shared/constants';

describe('AccountIdWithPrivateKeySchema', () => {
const accountId = TEST_ACCOUNT_ID;
Expand Down Expand Up @@ -132,3 +136,22 @@ describe('AccountIdWithPrivateKeySchema', () => {
});
});
});

describe('ExpirationTimeSchema', () => {
test('accepts undefined and omits expiration', () => {
expect(ExpirationTimeSchema.parse(undefined)).toBeUndefined();
});

test('rejects expiration in the past', () => {
expect(() =>
ExpirationTimeSchema.parse('2000-01-01T00:00:00.000Z'),
).toThrow();
});

test('accepts expiration strictly in the future', () => {
const future = new Date(Date.now() + 7 * DAY_IN_SECONDS).toISOString();
const d = ExpirationTimeSchema.parse(future);
expect(d).toBeInstanceOf(Date);
expect(d!.getTime()).toBeGreaterThan(Date.now());
});
});
63 changes: 62 additions & 1 deletion src/core/schemas/common-schemas.ts
Original file line number Diff line number Diff line change
Expand Up @@ -15,12 +15,19 @@ import {
CredentialType,
KeyManager,
} from '@/core/services/kms/kms-types.interface';
import { HederaTokenType, KeyAlgorithm } from '@/core/shared/constants';
import {
HEDERA_AUTO_RENEW_PERIOD_MAX,
HEDERA_AUTO_RENEW_PERIOD_MIN,
HEDERA_EXPIRATION_TIME_MAX,
HederaTokenType,
KeyAlgorithm,
} from '@/core/shared/constants';
import {
EntityReferenceType,
SupplyType,
SupportedNetwork,
} from '@/core/types/shared.types';
import { parseAutoRenewPeriodToSeconds } from '@/core/utils/parse-auto-renew-period';

// Raw key patterns (without prefix) for validation
const PUBLIC_KEY_PATTERN =
Expand Down Expand Up @@ -474,6 +481,8 @@ export const TokenAliasNameSchema = AliasNameSchema.describe(
'Token alias name (local identifier, not on-chain name)',
);

export const TokenFreezeDefaultSchema = z.boolean().default(false);

/**
* Memo Input
* Optional memo field for transactions
Expand Down Expand Up @@ -968,3 +977,55 @@ export const AutoRenewPeriodSchema = z
.min(1, 'Auto renew period must be at least 1 second')
.optional()
.describe('Auto renew period in seconds');

/** Output / mirror fields: optional seconds, validated when present. */
export const HederaAutoRenewPeriodSecondsOptionalSchema = z
.number()
.int()
.min(HEDERA_AUTO_RENEW_PERIOD_MIN)
.max(HEDERA_AUTO_RENEW_PERIOD_MAX)
.optional();

/**
* Optional field from CLI (string/number) or JSON → seconds, or `undefined`.
*/
export const AutoRenewPeriodSecondsSchema: z.ZodType<number | undefined> = z
.union([z.string(), z.number()])
.optional()
.transform((val): number | undefined => {
if (!val || val === '') {
return undefined;
}
return typeof val === 'number' ? val : parseAutoRenewPeriodToSeconds(val);
})
.refine(
(sec) =>
!sec ||
(sec >= HEDERA_AUTO_RENEW_PERIOD_MIN &&
sec <= HEDERA_AUTO_RENEW_PERIOD_MAX),
{
message: `Auto-renew period must be between ${HEDERA_AUTO_RENEW_PERIOD_MIN} and ${HEDERA_AUTO_RENEW_PERIOD_MAX} seconds (30–92 days inclusive).`,
},
);

/**
* Optional ISO 8601 datetime string → `Date`.
* When set, the instant must be strictly after the current time.
*/
export const ExpirationTimeSchema: z.ZodType<Date | undefined> = z.coerce
.date()
.optional()
.refine((s) => !s || !Number.isNaN(new Date(s).getTime()), {
message:
'Invalid expiration time. Use an ISO 8601 datetime (e.g. 2026-12-31T23:59:59.000Z).',
})
.refine(
(d) =>
!d ||
(d.getTime() > Date.now() &&
d.getTime() <=
new Date(Date.now() + HEDERA_EXPIRATION_TIME_MAX).getTime()),
{
message: 'Expiration time must be set in 92 days period.',
},
);
6 changes: 6 additions & 0 deletions src/core/services/mirrornode/__tests__/unit/mocks.ts
Original file line number Diff line number Diff line change
Expand Up @@ -117,6 +117,9 @@ export const createMockTokenInfo = (
created_timestamp: '2024-01-01T12:00:00.000Z',
deleted: false,
freeze_default: false,
auto_renew_period: 7776000,
auto_renew_account: '0.0.1234',
expiry_timestamp: 1893456000000000000,
pause_status: 'UNPAUSED',
memo: '',
...overrides,
Expand All @@ -139,6 +142,9 @@ export const createMockMirrorNodeTokenByIdJson = (
created_timestamp: '2024-01-01T12:00:00.000Z',
deleted: false,
freeze_default: false,
auto_renew_period: 7776000,
auto_renew_account: '0.0.1234',
expiry_timestamp: 1893456000000000000,
pause_status: 'UNPAUSED',
memo: '',
...overrides,
Expand Down
4 changes: 4 additions & 0 deletions src/core/services/mirrornode/schemas.ts
Original file line number Diff line number Diff line change
Expand Up @@ -33,10 +33,14 @@ export const TokenInfoSchema: z.ZodType<TokenInfo> = z.object({
wipe_key: optionalKeyRef,
supply_key: optionalKeyRef,
fee_schedule_key: optionalKeyRef,
metadata_key: optionalKeyRef,
pause_key: optionalKeyRef,
created_timestamp: z.string(),
deleted: z.boolean().nullable().optional(),
freeze_default: z.boolean().optional(),
auto_renew_account: z.string(),
auto_renew_period: z.number(),
expiry_timestamp: z.number(),
pause_status: z.string(),
memo: z.string(),
});
Expand Down
4 changes: 4 additions & 0 deletions src/core/services/mirrornode/types.ts
Original file line number Diff line number Diff line change
Expand Up @@ -83,10 +83,14 @@ export interface TokenInfo {
wipe_key?: MirrorNodeTokenKey | null;
supply_key?: MirrorNodeTokenKey | null;
fee_schedule_key?: MirrorNodeTokenKey | null;
metadata_key?: MirrorNodeTokenKey | null;
pause_key?: MirrorNodeTokenKey | null;
created_timestamp: string;
deleted?: boolean | null;
freeze_default?: boolean;
auto_renew_account?: string;
auto_renew_period?: number;
expiry_timestamp?: number;
pause_status: string;
memo: string;
}
Expand Down
5 changes: 5 additions & 0 deletions src/core/services/token/__tests__/unit/mocks.ts
Original file line number Diff line number Diff line change
Expand Up @@ -23,8 +23,13 @@ export const createMockTokenCreateTransaction = () => ({
setWipeKey: jest.fn().mockReturnThis(),
setKycKey: jest.fn().mockReturnThis(),
setFreezeKey: jest.fn().mockReturnThis(),
setFreezeDefault: jest.fn().mockReturnThis(),
setPauseKey: jest.fn().mockReturnThis(),
setFeeScheduleKey: jest.fn().mockReturnThis(),
setMetadataKey: jest.fn().mockReturnThis(),
setAutoRenewAccountId: jest.fn().mockReturnThis(),
setAutoRenewPeriod: jest.fn().mockReturnThis(),
setExpirationTime: jest.fn().mockReturnThis(),
});

export const createMockTokenAssociateTransaction = () => ({
Expand Down
Loading
Loading