diff --git a/.changeset/sixty-readers-talk.md b/.changeset/sixty-readers-talk.md new file mode 100644 index 0000000000..bb722576ce --- /dev/null +++ b/.changeset/sixty-readers-talk.md @@ -0,0 +1,6 @@ +--- +"@layerzerolabs/hyperliquid-composer": patch +"@layerzerolabs/oft-hyperliquid-example": patch +--- + +improved cli and docs on expansion spot market pairs diff --git a/examples/oft-hyperliquid/HYPERLIQUID.README.md b/examples/oft-hyperliquid/HYPERLIQUID.README.md index 3073569bcb..5ded2c9fef 100644 --- a/examples/oft-hyperliquid/HYPERLIQUID.README.md +++ b/examples/oft-hyperliquid/HYPERLIQUID.README.md @@ -90,6 +90,8 @@ npx @layerzerolabs/hyperliquid-composer set-genesis \ ### 4. Register Trading Spot +Registers a trading pair between your token and a quote asset (USDC, HYPE, or custom quote token). + ```bash npx @layerzerolabs/hyperliquid-composer register-spot \ --token-index \ @@ -98,16 +100,54 @@ npx @layerzerolabs/hyperliquid-composer register-spot \ [--log-level {info | verbose}] ``` +On success, this command outputs the allocated **spot index** and the exact command to run for finalization: + +``` +============================================================ +SPOT REGISTRATION SUCCESSFUL +============================================================ +Allocated Spot Index: 1421 +Base Token: 1502 +Quote Token: USDC (0) + +NEXT STEP: Finalize the spot pair with: + npx @layerzerolabs/hyperliquid-composer create-spot-deployment \ + --token-index 1502 \ + --network testnet \ + --spot-index 1421 \ + --private-key $PRIVATE_KEY +============================================================ +``` + +**Note:** For additional spot pairs (beyond the first), this command participates in the spot pair deployment Dutch auction. + ### 5. Create Spot Deployment +Finalizes a spot pair by setting hyperliquidity parameters. Required after `register-spot` to make the trading pair live. + ```bash npx @layerzerolabs/hyperliquid-composer create-spot-deployment \ --token-index \ --network {testnet | mainnet} \ --private-key $PRIVATE_KEY_HYPERLIQUID \ + [--spot-index ] \ [--log-level {info | verbose}] ``` +**Options:** + +- `--spot-index `: Directly specify the spot index to finalize (recommended). Use the spot index from the `register-spot` output. + +**Example:** + +```bash +npx @layerzerolabs/hyperliquid-composer create-spot-deployment \ + --token-index 1502 \ + --network testnet \ + --spot-index 1421 \ + --private-key $PRIVATE_KEY +``` + ### 6. Set Trading Fee Share (Optional) Can be done at any time after deployment. **Note:** If you plan to enable quote token capability, read the [Permissionless Spot Quote Assets](https://hyperliquid.gitbook.io/hyperliquid-docs/hypercore/permissionless-spot-quote-assets) documentation before setting this value. @@ -469,7 +509,7 @@ npx @layerzerolabs/hyperliquid-composer set-genesis \ ### Step 4/7 `registerSpot` -This is the step that registers the Core Spot on `HyperCore` and creates a base-quote pair. You can now choose between USDC, USDT0, or custom quote tokens. +This is the step that registers the Core Spot on `HyperCore` and creates a base-quote pair. You can choose between USDC, USDT0, HYPE, or custom quote tokens. ```bash npx @layerzerolabs/hyperliquid-composer register-spot \ @@ -479,9 +519,32 @@ npx @layerzerolabs/hyperliquid-composer register-spot \ [--log-level {info | verbose}] ``` +On success, this command outputs the allocated **spot index** and the exact command for finalization: + +``` +============================================================ +SPOT REGISTRATION SUCCESSFUL +============================================================ +Allocated Spot Index: 1421 +Base Token: 1502 +Quote Token: USDC (0) + +NEXT STEP: Finalize the spot pair with: + npx @layerzerolabs/hyperliquid-composer create-spot-deployment \ + --token-index 1502 \ + --network testnet \ + --spot-index 1421 \ + --private-key $PRIVATE_KEY +============================================================ +``` + +**Note:** For additional spot pairs (beyond the first), this command participates in the spot pair deployment Dutch auction. + ### Step 5/7 `createSpotDeployment` -This is the step that creates a spot deployment without hyperliquidity. This step is meant for tokens deployed with Hyperliquidity but is also required for tokens deployed without Hyperliquidity to be listed on Spot trading, as such the values for `startPx` and `orderSz` are not required as they are set by the market and the value set does not matter. The value for `nOrders` however MUST be 0 as we do not support Hyperliquidity - +This step finalizes a spot deployment by setting hyperliquidity parameters. Required after `register-spot` to make the trading pair live on HyperCore. + +For tokens deployed without Hyperliquidity, the values for `startPx` and `orderSz` are not significant as they are set by the market. The value for `nOrders` MUST be 0 as we do not support Hyperliquidity - You will NOT be prompted for the following and instead the values will be set to 0: @@ -497,9 +560,24 @@ npx @layerzerolabs/hyperliquid-composer create-spot-deployment \ --token-index \ --network {testnet | mainnet} \ --private-key $PRIVATE_KEY_HYPERLIQUID \ + [--spot-index ] \ [--log-level {info | verbose}] ``` +**Options:** + +- `--spot-index `: Directly specify the spot index to finalize. Use the spot index from `register-spot` output. This is the recommended approach. + +**Example:** + +```bash +npx @layerzerolabs/hyperliquid-composer create-spot-deployment \ + --token-index 1502 \ + --network testnet \ + --spot-index 1421 \ + --private-key $PRIVATE_KEY +``` + > ⚠️ Note: `spot-deploy-state` should fail after completing this step. Your Core Spot (that does not use Hyperliquidity) has now been deployed and registered on `HyperCore`. diff --git a/packages/hyperliquid-composer/HYPERLIQUID.README.md b/packages/hyperliquid-composer/HYPERLIQUID.README.md index 72512dceae..f10b1b3358 100644 --- a/packages/hyperliquid-composer/HYPERLIQUID.README.md +++ b/packages/hyperliquid-composer/HYPERLIQUID.README.md @@ -490,6 +490,8 @@ npx @layerzerolabs/hyperliquid-composer set-genesis \ ### 4. Register Trading Spot +Registers a trading pair between your token and a quote asset (USDC, HYPE, or custom quote token). + ```bash npx @layerzerolabs/hyperliquid-composer register-spot \ --token-index \ @@ -498,16 +500,53 @@ npx @layerzerolabs/hyperliquid-composer register-spot \ [--log-level {info | verbose}] ``` +On success, this command outputs the allocated **spot index** and the exact command to run for finalization: + +``` +============================================================ +SPOT REGISTRATION SUCCESSFUL +============================================================ +Allocated Spot Index: 1421 +Base Token: 1502 +Quote Token: USDC (0) + +NEXT STEP: Finalize the spot pair with: + npx @layerzerolabs/hyperliquid-composer create-spot-deployment \ + --token-index 1502 \ + --network testnet \ + --spot-index 1421 \ + --private-key $PRIVATE_KEY +============================================================ +``` + +**Note:** For additional spot pairs (beyond the first), this command participates in the spot pair deployment Dutch auction. Check the current auction status with `spot-auction-status`. + ### 5. Create Spot Deployment +Finalizes a spot pair by setting hyperliquidity parameters. This step is required after `register-spot` to make the trading pair live. + ```bash npx @layerzerolabs/hyperliquid-composer create-spot-deployment \ --token-index \ --network {testnet | mainnet} \ --private-key $PRIVATE_KEY_HYPERLIQUID \ + [--spot-index ] \ [--log-level {info | verbose}] ``` +**Options:** +- `--spot-index `: Directly specify the spot index to finalize (recommended). This skips discovery and uses the spot index provided by `register-spot`. + +**Example (using spot index from register-spot output):** + +```bash +npx @layerzerolabs/hyperliquid-composer create-spot-deployment \ + --token-index 1502 \ + --network testnet \ + --spot-index 1421 \ + --private-key $PRIVATE_KEY +``` + ### 6. Set Trading Fee Share (Optional) Can be done at any time after deployment. **Note:** If you plan to enable quote token capability, read the [Permissionless Spot Quote Assets](https://hyperliquid.gitbook.io/hyperliquid-docs/hypercore/permissionless-spot-quote-assets) documentation before setting this value. @@ -917,7 +956,9 @@ npx @layerzerolabs/hyperliquid-composer register-spot \ ### Step 5/7 `createSpotDeployment` -This is the step that creates a spot deployment without hyperliquidity. This step is meant for tokens deployed with Hyperliquidity but is also required for tokens deployed without Hyperliquidity to be listed on Spot trading, as such the values for `startPx` and `orderSz` are not required as they are set by the market and the value set does not matter. The value for `nOrders` however MUST be 0 as we do not support Hyperliquidity - +This step finalizes a spot deployment by setting hyperliquidity parameters. It is required after `register-spot` to make the trading pair live on HyperCore. + +For tokens deployed without Hyperliquidity, the values for `startPx` and `orderSz` are not significant as they are set by the market. The value for `nOrders` MUST be 0 as we do not support Hyperliquidity - You will NOT be prompted for the following and instead the values will be set to 0: @@ -933,9 +974,24 @@ npx @layerzerolabs/hyperliquid-composer create-spot-deployment \ --token-index \ --network {testnet | mainnet} \ --private-key $PRIVATE_KEY_HYPERLIQUID \ + [--spot-index ] \ [--log-level {info | verbose}] ``` +**Options:** +- `--spot-index `: Directly specify the spot index to finalize. Use the spot index output from the `register-spot` command. This is the recommended approach as it skips network-wide discovery. + +**Example:** + +```bash +# Using the spot index from register-spot output +npx @layerzerolabs/hyperliquid-composer create-spot-deployment \ + --token-index 1502 \ + --network testnet \ + --spot-index 1421 \ + --private-key $PRIVATE_KEY +``` + > ⚠️ Note: `spot-deploy-state` should fail after completing this step. Your Core Spot (that does not use Hyperliquidity) has now been deployed and registered on `HyperCore`. diff --git a/packages/hyperliquid-composer/src/cli.ts b/packages/hyperliquid-composer/src/cli.ts index 3397b28e54..87daca1916 100644 --- a/packages/hyperliquid-composer/src/cli.ts +++ b/packages/hyperliquid-composer/src/cli.ts @@ -170,6 +170,7 @@ optionGroups program .command(CLI_COMMANDS.CREATE_SPOT_DEPLOYMENT) .description('HIP-1 Deployment 4. Create spot deployment without hyperliquidity') + .option('-s, --spot-index ', 'Directly specify spot index to finalize (skips discovery)') ) .action(withNormalizedNetwork(createSpotDeployment)) diff --git a/packages/hyperliquid-composer/src/commands/spot-deploy.ts b/packages/hyperliquid-composer/src/commands/spot-deploy.ts index 2fc209bd4a..417a3956b2 100644 --- a/packages/hyperliquid-composer/src/commands/spot-deploy.ts +++ b/packages/hyperliquid-composer/src/commands/spot-deploy.ts @@ -95,10 +95,15 @@ export async function createSpotDeployment(args: CreateSpotDeploymentArgs): Prom const signer = await getHyperliquidSigner(args.privateKey) const isTestnet = args.network === 'testnet' const tokenIndex: number = parseInt(args.tokenIndex) + const spotIndex: number | undefined = args.spotIndex ? parseInt(args.spotIndex) : undefined - logger.info(`Setting no hyperliquidity for token ${tokenIndex}`) + if (spotIndex !== undefined) { + logger.info(`Finalizing spot ${spotIndex} for token ${tokenIndex}`) + } else { + logger.info(`Setting no hyperliquidity for token ${tokenIndex}`) + } - await setNoHyperliquidity(signer, isTestnet, tokenIndex, args.logLevel) + await setNoHyperliquidity(signer, isTestnet, tokenIndex, args.logLevel, spotIndex) } export async function registerTradingSpot(args: RegisterTradingSpotArgs): Promise { diff --git a/packages/hyperliquid-composer/src/operations/spotDeploy.ts b/packages/hyperliquid-composer/src/operations/spotDeploy.ts index 7f18ba162e..f6c666d074 100644 --- a/packages/hyperliquid-composer/src/operations/spotDeploy.ts +++ b/packages/hyperliquid-composer/src/operations/spotDeploy.ts @@ -9,7 +9,13 @@ import { } from '../io' import { HyperliquidClient, IHyperliquidSigner } from '../signer' import { MAX_HYPERCORE_SUPPLY, QUOTE_TOKENS } from '../types' -import { getSpotDeployState, getExistingQuoteTokens, getSpotPairDeployAuctionStatus, isQuoteAsset } from './spotMeta' +import { + getSpotDeployState, + getExistingQuoteTokens, + getSpotPairDeployAuctionStatus, + isQuoteAsset, + getPendingSpotPairs, +} from './spotMeta' import type { SpotDeployAction, SpotDeployStates } from '../types' import { RegisterHyperliquidity } from '@/types/spotDeploy' import { LOGGER_MODULES } from '@/types/cli-constants' @@ -173,54 +179,95 @@ export async function setNoHyperliquidity( signer: IHyperliquidSigner, isTestnet: boolean, tokenIndex: number, - logLevel: string + logLevel: string, + directSpotIndex?: number ) { const logger = createModuleLogger(LOGGER_MODULES.SET_NO_HYPERLIQUIDITY, logLevel) - const signerAddress = await signer.getAddress() - const deployStates = (await getSpotDeployState(signerAddress, isTestnet, logLevel)) as SpotDeployStates + let finalSpotId: number - const state = deployStates.states.find((state) => state.token === tokenIndex) - if (!state) { - logger.error( - `No in progress deployment state found for token ${tokenIndex}. This means your token is deployed.` + // If spot index is directly provided, skip all discovery + if (directSpotIndex !== undefined) { + logger.info(`Using directly provided spot index: ${directSpotIndex}`) + finalSpotId = directSpotIndex + } else { + // Discovery mode - find available spot ids + const signerAddress = await signer.getAddress() + const deployStates = (await getSpotDeployState(signerAddress, isTestnet, logLevel)) as SpotDeployStates + + const state = deployStates.states.find((state) => state.token === tokenIndex) + + let spotIds: number[] = [] + let isPendingSpotMode = false + + if (!state) { + // Token is already deployed - check for pending spot pairs in spotMetaAndAssetCtxs + logger.info( + `No in progress deployment state found for token ${tokenIndex}. Checking for pending spot pairs network-wide...` + ) + + const pendingSpots = await getPendingSpotPairs(isTestnet, logLevel) + + if (pendingSpots.length === 0) { + logger.error(`No pending spot pairs found network-wide.`) + logger.info(`Tip: If you know the spot index, use --spot-index to directly specify it.`) + process.exit(1) + } + + logger.warn( + `WARNING: The API does not provide token composition for pending spots. ` + + `The following are ALL pending spots network-wide, not filtered by token ${tokenIndex}.` + ) + logger.warn(`Use --spot-index to directly specify your spot if you know it from register-spot output.`) + logger.info(`Found ${pendingSpots.length} pending spot pair(s) network-wide:`) + pendingSpots.forEach((spot) => { + logger.info(` - ${spot.coin} (spot index: ${spot.spotIndex}, markPx: ${spot.markPx})`) + }) + + spotIds = pendingSpots.map((spot) => spot.spotIndex) + isPendingSpotMode = true + } else { + spotIds = state.spots + } + + logger.info( + `For information on valid input values, refer to: https://hyperliquid.gitbook.io/hyperliquid-docs/hyperliquid-improvement-proposals-hips/frontend-checks#hyperliquidity` ) - process.exit(1) - } + logger.info(`Available spot ids: ${spotIds.join(', ')}`) - const spotIds = state.spots + const { spotId } = await inquirer.prompt([ + { + type: 'input', + name: 'spotId', + message: isPendingSpotMode + ? `Enter the pending spot id to finalize:` + : `Enter the spot id that you would like to create a spot deployment for.`, + }, + ]) - logger.info( - `For information on valid input values, refer to: https://hyperliquid.gitbook.io/hyperliquid-docs/hyperliquid-improvement-proposals-hips/frontend-checks#hyperliquidity` - ) - logger.info(`Available spot ids: ${spotIds}`) - const { spotId } = await inquirer.prompt([ - { - type: 'input', - name: 'spotId', - message: `Enter the spot id that you would like to create a spot deployment for.`, - }, - ]) + if (!spotIds.includes(parseInt(spotId))) { + logger.error(`Invalid spot id: ${spotId}. Available: ${spotIds.join(', ')}`) + process.exit(1) + } - if (!spotIds.includes(parseInt(spotId))) { - logger.error(`Invalid spot id: ${spotId}`) - process.exit(1) + finalSpotId = parseInt(spotId) } logger.info( - 'The following values will be set: startPx as 1, orderSz as 0, and nOrders as 0. This is because the pricing is determined by the market as we do not support hyperliquidity, which is what these values are used for.' + 'The following values will be set: startPx, orderSz as 0, and nOrders as 0. This is because the pricing is determined by the market as we do not support hyperliquidity.' ) const { startPxApprox } = await inquirer.prompt([ { type: 'input', name: 'startPxApprox', - message: `Enter the start price of the token in the same order as what you expect it to be in. This is because market makers can't change the price to outside of 95% the current.`, + message: `Enter the start price of the token (approximate order of magnitude). Market makers can't change price outside of 95% of this.`, + default: '1.0', }, ]) const registerHyperliquidity: RegisterHyperliquidity = { - spot: parseInt(spotId), + spot: finalSpotId, startPx: startPxApprox.toString(), orderSz: '0', nOrders: 0, @@ -231,7 +278,7 @@ export async function setNoHyperliquidity( registerHyperliquidity: registerHyperliquidity, } - logger.info('Registering hyperliquidity') + logger.info(`Finalizing spot pair ${finalSpotId} with registerHyperliquidity (no hyperliquidity)`) const hyperliquidClient = new HyperliquidClient(isTestnet, logLevel) const response = await hyperliquidClient.submitHyperliquidAction('/exchange', signer, actionForNoHyperliquidity) return response @@ -417,6 +464,70 @@ export async function registerSpot( logger.info(`Register trading spot against ${quoteTokenName}`) const hyperliquidClient = new HyperliquidClient(isTestnet, logLevel) const response = await hyperliquidClient.submitHyperliquidAction('/exchange', signer, actionForRegisterSpot) + + // Parse and display the allocated spot index + if (response.status === 'ok' && response.response?.data?.spot !== undefined) { + const spotIndex = response.response.data.spot + logger.info('') + logger.info('='.repeat(60)) + logger.info('SPOT REGISTRATION SUCCESSFUL') + logger.info('='.repeat(60)) + logger.info(`Allocated Spot Index: ${spotIndex}`) + logger.info(`Base Token: ${coreSpotTokenId}`) + logger.info(`Quote Token: ${quoteTokenName} (${quoteTokenId})`) + logger.info('') + logger.info('NEXT STEP: Finalize the spot pair with:') + logger.info(` npx @layerzerolabs/hyperliquid-composer create-spot-deployment \\`) + logger.info(` --token-index ${coreSpotTokenId} \\`) + logger.info(` --network ${isTestnet ? 'testnet' : 'mainnet'} \\`) + logger.info(` --spot-index ${spotIndex} \\`) + logger.info(` --private-key $PRIVATE_KEY`) + logger.info('='.repeat(60)) + } else if (response.status === 'ok') { + // Response ok but no spot data - try to extract from statuses + const statuses = response.response?.statuses + let foundSpot = false + if (statuses && Array.isArray(statuses)) { + for (const status of statuses) { + if (status.spot !== undefined) { + const spotIndex = status.spot + logger.info('') + logger.info('='.repeat(60)) + logger.info('SPOT REGISTRATION SUCCESSFUL') + logger.info('='.repeat(60)) + logger.info(`Allocated Spot Index: ${spotIndex}`) + logger.info('') + logger.info('NEXT STEP: Finalize the spot pair with:') + logger.info(` npx @layerzerolabs/hyperliquid-composer create-spot-deployment \\`) + logger.info(` --token-index ${coreSpotTokenId} \\`) + logger.info(` --network ${isTestnet ? 'testnet' : 'mainnet'} \\`) + logger.info(` --spot-index ${spotIndex} \\`) + logger.info(` --private-key $PRIVATE_KEY`) + logger.info('='.repeat(60)) + foundSpot = true + break + } + } + } + if (!foundSpot) { + // Fallback: transaction succeeded but spot index not found in response + logger.info('') + logger.info('='.repeat(60)) + logger.info('SPOT REGISTRATION SUCCESSFUL') + logger.info('='.repeat(60)) + logger.info('Note: Could not extract spot index from response.') + logger.info('Check the transaction on the explorer to find the allocated spot index.') + logger.info('') + logger.info('Once you have the spot index, finalize with:') + logger.info(` npx @layerzerolabs/hyperliquid-composer create-spot-deployment \\`) + logger.info(` --token-index ${coreSpotTokenId} \\`) + logger.info(` --network ${isTestnet ? 'testnet' : 'mainnet'} \\`) + logger.info(` --spot-index \\`) + logger.info(` --private-key $PRIVATE_KEY`) + logger.info('='.repeat(60)) + } + } + return response } diff --git a/packages/hyperliquid-composer/src/operations/spotMeta.ts b/packages/hyperliquid-composer/src/operations/spotMeta.ts index 158f815e1a..53a683ebce 100644 --- a/packages/hyperliquid-composer/src/operations/spotMeta.ts +++ b/packages/hyperliquid-composer/src/operations/spotMeta.ts @@ -175,6 +175,60 @@ export async function getExistingQuoteTokens( * @param logLevel Logging level for the client * @returns Object containing isQuoteAsset boolean and tokenName string */ +/** + * Gets pending spot pairs that exist in spotMetaAndAssetCtxs but NOT in the universe. + * These are spots that have been registered via registerSpot but not yet finalized. + * + * IMPORTANT: The Hyperliquid API does not provide token composition info for pending spots, + * so this function returns ALL pending spots network-wide, not filtered by token. + * Users should use the --spot-index flag to directly specify which spot to finalize. + * + * @param isTestnet Whether to query testnet or mainnet + * @param logLevel Logging level for the client + * @returns Array of pending spot indices and their details (network-wide, not token-specific) + */ +export async function getPendingSpotPairs( + isTestnet: boolean, + logLevel: string +): Promise> { + const hyperliquidClient = new HyperliquidClient(isTestnet, logLevel) + + // Get spotMetaAndAssetCtxs which includes pending spots + const assetCtxsAction: BaseInfoRequest = { + type: 'spotMetaAndAssetCtxs', + } + const assetCtxsResponse = (await hyperliquidClient.submitHyperliquidAction('/info', null, assetCtxsAction)) as [ + SpotMetaUniverse, + Array<{ coin: string; markPx: string; circulatingSupply: string; totalSupply: string }>, + ] + + const universe = assetCtxsResponse[0].universe + const assetCtxs = assetCtxsResponse[1] + + // Get spot indices that are in the universe (live) + const liveSpotIndices = new Set(universe.map((pair) => pair.index)) + + // Find spots in assetCtxs that are NOT in the universe (pending) + const pendingSpots: Array<{ spotIndex: number; coin: string; markPx: string }> = [] + + for (const ctx of assetCtxs) { + // Extract spot index from coin name (e.g., "@1421" -> 1421) + const match = ctx.coin.match(/^@(\d+)$/) + if (match && match[1]) { + const spotIndex = parseInt(match[1]) + if (!liveSpotIndices.has(spotIndex)) { + pendingSpots.push({ + spotIndex, + coin: ctx.coin, + markPx: ctx.markPx, + }) + } + } + } + + return pendingSpots +} + export async function isQuoteAsset( isTestnet: boolean, tokenIndex: number | null, diff --git a/packages/hyperliquid-composer/src/types/cli-args.ts b/packages/hyperliquid-composer/src/types/cli-args.ts index c39465aa82..50ac2a7c92 100644 --- a/packages/hyperliquid-composer/src/types/cli-args.ts +++ b/packages/hyperliquid-composer/src/types/cli-args.ts @@ -67,7 +67,9 @@ export interface ListQuoteAssetArgs extends BaseArgs { // Simple command args - using concrete interfaces instead of empty extends export interface GenesisArgs extends TokenIndexArgs, PrivateKeyArgs {} -export interface CreateSpotDeploymentArgs extends TokenIndexArgs, PrivateKeyArgs {} +export interface CreateSpotDeploymentArgs extends TokenIndexArgs, PrivateKeyArgs { + spotIndex?: string // Optional: directly specify spot index to finalize (skips discovery) +} export interface RegisterTradingSpotArgs extends TokenIndexArgs, PrivateKeyArgs {} export interface EnableTokenFreezePrivilegeArgs extends TokenIndexArgs, PrivateKeyArgs {} export interface RevokeTokenFreezePrivilegeArgs extends TokenIndexArgs, PrivateKeyArgs {}