Skip to content

Commit fd96442

Browse files
authored
feat(1670): token create ft missing options (#1681)
Signed-off-by: mmyslblocky <michal.myslinski@blockydevs.com>
1 parent 8ddb952 commit fd96442

Some content is hidden

Large Commits have some content hidden by default. Use the searchbox below for content that may be hidden.

46 files changed

+1145
-138
lines changed

docs/output-schemas-guide.md

Lines changed: 13 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -318,10 +318,16 @@ interface CommandOutputSpec {
318318
"initialSupply": "1000000",
319319
"supplyType": "INFINITE",
320320
"transactionId": "0.0.123@1700000000.123456789",
321-
"alias": "my-token"
321+
"alias": "my-token",
322+
"network": "testnet",
323+
"autoRenewPeriodSeconds": 2592000,
324+
"autoRenewAccountId": "0.0.11111",
325+
"expirationTime": "2030-01-01T00:00:00.000Z"
322326
}
323327
```
324328

329+
`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).
330+
325331
#### `token transfer-ft`
326332

327333
**Output**:
@@ -417,10 +423,15 @@ interface CommandOutputSpec {
417423
"success": true,
418424
"transactionId": "0.0.123@1700000000.123456789"
419425
}
420-
]
426+
],
427+
"autoRenewPeriodSeconds": 2592000,
428+
"autoRenewAccountId": "0.0.11111",
429+
"expirationTime": "2030-01-01T00:00:00.000Z"
421430
}
422431
```
423432

433+
Same optional lifecycle fields as `token create-ft`: `autoRenewPeriodSeconds`, `autoRenewAccountId`, `expirationTime` (ISO string when fixed expiration was applied).
434+
424435
#### `token create-nft-from-file`
425436

426437
**Output**:

examples/token/token-fixed-fee-hbar.json

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -8,6 +8,8 @@
88
"treasuryKey": "<alias or accountId:privateKey>",
99
"adminKey": "<alias or accountId:privateKey>",
1010
"memo": "Token with fixed fee paid in HBAR (tinybars)",
11+
"autoRenewPeriod": "30d",
12+
"autoRenewAccount": "<alias or accountId:privateKey>",
1113
"customFees": [
1214
{
1315
"type": "fixed",

examples/token/token-fixed-fee-token.json

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -8,6 +8,8 @@
88
"treasuryKey": "<alias or accountId:privateKey>",
99
"adminKey": "<alias or accountId:privateKey>",
1010
"memo": "Token with fixed fee paid in same token units",
11+
"autoRenewPeriod": "30d",
12+
"autoRenewAccount": "<alias or accountId:privateKey>",
1113
"customFees": [
1214
{
1315
"type": "fixed",

examples/token/token-fractional-fee-receiver-pays.json

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -8,6 +8,8 @@
88
"treasuryKey": "<alias or accountId:privateKey>",
99
"adminKey": "<alias or accountId:privateKey>",
1010
"memo": "Token with fractional fee (receiver pays, netOfTransfers=false)",
11+
"autoRenewPeriod": "30d",
12+
"autoRenewAccount": "<alias or accountId:privateKey>",
1113
"customFees": [
1214
{
1315
"type": "fractional",

examples/token/token-fractional-fee-sender-pays.json

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -8,6 +8,8 @@
88
"treasuryKey": "<alias or accountId:privateKey>",
99
"adminKey": "<alias or accountId:privateKey>",
1010
"memo": "Token with fractional fee (sender pays, netOfTransfers=true)",
11+
"autoRenewPeriod": "30d",
12+
"autoRenewAccount": "<alias or accountId:privateKey>",
1113
"customFees": [
1214
{
1315
"type": "fractional",

examples/token/token-full-example.json

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -14,6 +14,8 @@
1414
"pauseKey": "<alias or accountId:privateKey>",
1515
"feeScheduleKey": "<alias or accountId:privateKey>",
1616
"memo": "Full example token with all fields",
17+
"autoRenewPeriod": "90d",
18+
"autoRenewAccount": "<alias or accountId:privateKey>",
1719
"associations": ["<alias or accountId:privateKey>", "..."],
1820
"customFees": [
1921
{

skills/hiero-cli/references/token.md

Lines changed: 23 additions & 16 deletions
Original file line numberDiff line numberDiff line change
@@ -12,21 +12,24 @@ Commands marked **[batchify]** support the `--batch <name>` flag to queue into a
1212

1313
Create a new fungible token with specified properties.
1414

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

3134
**Example:**
3235

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

38-
**Output:** `{ tokenId, name, symbol, decimals, initialSupply, transactionId }`
41+
**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.
3942

4043
---
4144

@@ -85,6 +88,10 @@ hcli token create-ft-from-file --file ./my-token.json
8588
hcli token create-ft-from-file --file ./my-token.json --batch myBatch
8689
```
8790

91+
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).
92+
93+
**Output:** Same shape as CLI `create-ft`, plus `associations[]`, including optional `autoRenewPeriodSeconds`, `autoRenewAccountId`, `expirationTime`.
94+
8895
---
8996

9097
### `hcli token create-nft-from-file` [batchify]

src/core/schemas/__tests__/unit/common-schemas.test.ts

Lines changed: 24 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -9,8 +9,12 @@ import {
99
SHORT_KEY,
1010
TEST_ACCOUNT_ID,
1111
} from '@/core/schemas/__tests__/helpers/fixtures';
12-
import { AccountIdWithPrivateKeySchema } from '@/core/schemas/common-schemas';
12+
import {
13+
AccountIdWithPrivateKeySchema,
14+
ExpirationTimeSchema,
15+
} from '@/core/schemas/common-schemas';
1316
import { INVALID_KEY } from '@/core/services/topic/__tests__/unit/mocks';
17+
import { DAY_IN_SECONDS } from '@/core/shared/constants';
1418

1519
describe('AccountIdWithPrivateKeySchema', () => {
1620
const accountId = TEST_ACCOUNT_ID;
@@ -132,3 +136,22 @@ describe('AccountIdWithPrivateKeySchema', () => {
132136
});
133137
});
134138
});
139+
140+
describe('ExpirationTimeSchema', () => {
141+
test('accepts undefined and omits expiration', () => {
142+
expect(ExpirationTimeSchema.parse(undefined)).toBeUndefined();
143+
});
144+
145+
test('rejects expiration in the past', () => {
146+
expect(() =>
147+
ExpirationTimeSchema.parse('2000-01-01T00:00:00.000Z'),
148+
).toThrow();
149+
});
150+
151+
test('accepts expiration strictly in the future', () => {
152+
const future = new Date(Date.now() + 7 * DAY_IN_SECONDS).toISOString();
153+
const d = ExpirationTimeSchema.parse(future);
154+
expect(d).toBeInstanceOf(Date);
155+
expect(d!.getTime()).toBeGreaterThan(Date.now());
156+
});
157+
});

src/core/schemas/common-schemas.ts

Lines changed: 62 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -15,12 +15,19 @@ import {
1515
CredentialType,
1616
KeyManager,
1717
} from '@/core/services/kms/kms-types.interface';
18-
import { HederaTokenType, KeyAlgorithm } from '@/core/shared/constants';
18+
import {
19+
HEDERA_AUTO_RENEW_PERIOD_MAX,
20+
HEDERA_AUTO_RENEW_PERIOD_MIN,
21+
HEDERA_EXPIRATION_TIME_MAX,
22+
HederaTokenType,
23+
KeyAlgorithm,
24+
} from '@/core/shared/constants';
1925
import {
2026
EntityReferenceType,
2127
SupplyType,
2228
SupportedNetwork,
2329
} from '@/core/types/shared.types';
30+
import { parseAutoRenewPeriodToSeconds } from '@/core/utils/parse-auto-renew-period';
2431

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

484+
export const TokenFreezeDefaultSchema = z.boolean().default(false);
485+
477486
/**
478487
* Memo Input
479488
* Optional memo field for transactions
@@ -968,3 +977,55 @@ export const AutoRenewPeriodSchema = z
968977
.min(1, 'Auto renew period must be at least 1 second')
969978
.optional()
970979
.describe('Auto renew period in seconds');
980+
981+
/** Output / mirror fields: optional seconds, validated when present. */
982+
export const HederaAutoRenewPeriodSecondsOptionalSchema = z
983+
.number()
984+
.int()
985+
.min(HEDERA_AUTO_RENEW_PERIOD_MIN)
986+
.max(HEDERA_AUTO_RENEW_PERIOD_MAX)
987+
.optional();
988+
989+
/**
990+
* Optional field from CLI (string/number) or JSON → seconds, or `undefined`.
991+
*/
992+
export const AutoRenewPeriodSecondsSchema: z.ZodType<number | undefined> = z
993+
.union([z.string(), z.number()])
994+
.optional()
995+
.transform((val): number | undefined => {
996+
if (!val || val === '') {
997+
return undefined;
998+
}
999+
return typeof val === 'number' ? val : parseAutoRenewPeriodToSeconds(val);
1000+
})
1001+
.refine(
1002+
(sec) =>
1003+
!sec ||
1004+
(sec >= HEDERA_AUTO_RENEW_PERIOD_MIN &&
1005+
sec <= HEDERA_AUTO_RENEW_PERIOD_MAX),
1006+
{
1007+
message: `Auto-renew period must be between ${HEDERA_AUTO_RENEW_PERIOD_MIN} and ${HEDERA_AUTO_RENEW_PERIOD_MAX} seconds (30–92 days inclusive).`,
1008+
},
1009+
);
1010+
1011+
/**
1012+
* Optional ISO 8601 datetime string → `Date`.
1013+
* When set, the instant must be strictly after the current time.
1014+
*/
1015+
export const ExpirationTimeSchema: z.ZodType<Date | undefined> = z.coerce
1016+
.date()
1017+
.optional()
1018+
.refine((s) => !s || !Number.isNaN(new Date(s).getTime()), {
1019+
message:
1020+
'Invalid expiration time. Use an ISO 8601 datetime (e.g. 2026-12-31T23:59:59.000Z).',
1021+
})
1022+
.refine(
1023+
(d) =>
1024+
!d ||
1025+
(d.getTime() > Date.now() &&
1026+
d.getTime() <=
1027+
new Date(Date.now() + HEDERA_EXPIRATION_TIME_MAX).getTime()),
1028+
{
1029+
message: 'Expiration time must be set in 92 days period.',
1030+
},
1031+
);

src/core/services/mirrornode/__tests__/unit/mocks.ts

Lines changed: 6 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -115,6 +115,9 @@ export const createMockTokenInfo = (
115115
created_timestamp: '2024-01-01T12:00:00.000Z',
116116
deleted: false,
117117
freeze_default: false,
118+
auto_renew_period: 7776000,
119+
auto_renew_account: '0.0.1234',
120+
expiry_timestamp: 1893456000000000000,
118121
pause_status: 'UNPAUSED',
119122
memo: '',
120123
...overrides,
@@ -137,6 +140,9 @@ export const createMockMirrorNodeTokenByIdJson = (
137140
created_timestamp: '2024-01-01T12:00:00.000Z',
138141
deleted: false,
139142
freeze_default: false,
143+
auto_renew_period: 7776000,
144+
auto_renew_account: '0.0.1234',
145+
expiry_timestamp: 1893456000000000000,
140146
pause_status: 'UNPAUSED',
141147
memo: '',
142148
...overrides,

0 commit comments

Comments
 (0)