From f49a9c2acd41bac198d0876dddbca96ee49de0cb Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Andr=C3=A9=20Abadesso?= Date: Wed, 28 Jan 2026 12:35:51 -0300 Subject: [PATCH 1/2] feat(web-wallet): restored fee-based-tokens with unleash flag --- .../src/components/CreateTokenDialog.tsx | 18 +++++++++--------- packages/web-wallet/src/constants.ts | 2 ++ .../src/contexts/FeatureToggleContext.tsx | 5 ++++- 3 files changed, 15 insertions(+), 10 deletions(-) diff --git a/packages/web-wallet/src/components/CreateTokenDialog.tsx b/packages/web-wallet/src/components/CreateTokenDialog.tsx index 6f428819..8b075d17 100644 --- a/packages/web-wallet/src/components/CreateTokenDialog.tsx +++ b/packages/web-wallet/src/components/CreateTokenDialog.tsx @@ -4,14 +4,15 @@ import { useForm } from 'react-hook-form'; import { zodResolver } from '@hookform/resolvers/zod'; import { z } from 'zod'; import { useWallet } from '../contexts/WalletContext'; +import { useFeatureToggle } from '../contexts/FeatureToggleContext'; import { useInvokeSnap } from '@hathor/snap-utils'; import { helpersUtils, tokensUtils, constants } from '@hathor/wallet-lib'; import { formatAmount, amountToCents } from '../utils/hathor'; import { readOnlyWalletWrapper } from '../services/ReadOnlyWalletWrapper'; import { getAddressForMode } from '../utils/addressMode'; -// TODO: Re-enable when fee token feature is ready -// import { Select, SelectContent, SelectItem, SelectTrigger, SelectValue } from './ui/select'; +import { Select, SelectContent, SelectItem, SelectTrigger, SelectValue } from './ui/select'; import { useToast } from '@/hooks/use-toast'; +import { Info } from 'lucide-react'; interface CreateTokenDialogProps { isOpen: boolean; @@ -103,6 +104,7 @@ const CreateTokenDialog: React.FC = ({ isOpen, onClose } } | null>(null); const { registerToken, balances, addressMode } = useWallet(); + const { isFeeTokensEnabled } = useFeatureToggle(); const invokeSnap = useInvokeSnap(); const { toast } = useToast(); @@ -114,8 +116,7 @@ const CreateTokenDialog: React.FC = ({ isOpen, onClose } handleSubmit, formState: { errors }, watch, - // TODO: Re-enable when fee token feature is ready - // setValue, + setValue, reset, } = useForm({ resolver: zodResolver(createTokenSchema), @@ -134,8 +135,7 @@ const CreateTokenDialog: React.FC = ({ isOpen, onClose } const isNFT = watch('isNFT'); const amount = watch('amount'); - // TODO: Re-enable when fee token feature is ready - // const tokenType = watch('tokenType'); + const tokenType = watch('tokenType'); // Calculate 1% HTR deposit (1 HTR per 100 tokens) // For every 100 tokens, 1 HTR (100 cents) deposit is required @@ -533,8 +533,8 @@ const CreateTokenDialog: React.FC = ({ isOpen, onClose } - {/* TODO: Re-enable Token Type dropdown when fee token feature is ready */} - {/* {!isNFT && ( + {/* Token Type dropdown - only shown when fee tokens feature is enabled */} + {isFeeTokensEnabled && !isNFT && (
@@ -575,7 +575,7 @@ const CreateTokenDialog: React.FC = ({ isOpen, onClose }
- )} */} + )} {/* Deposit Display */} {amount && depositInCents > 0n && ( diff --git a/packages/web-wallet/src/constants.ts b/packages/web-wallet/src/constants.ts index 4cc5b7fc..8a6d9224 100644 --- a/packages/web-wallet/src/constants.ts +++ b/packages/web-wallet/src/constants.ts @@ -80,10 +80,12 @@ export const UNLEASH_POLLING_INTERVAL = 15 * 1000; // 15s // Feature toggle names export const WEB_WALLET_MAINTENANCE_TOGGLE = 'web-wallet.maintenance'; +export const FEE_TOKENS_RELEASE_TOGGLE = 'fee-tokens.release'; // Feature toggle defaults // Note: Unleash Proxy only returns enabled toggles. If a toggle is disabled or // doesn't exist, it won't be in the response. export const FEATURE_TOGGLE_DEFAULTS: Record = { [WEB_WALLET_MAINTENANCE_TOGGLE]: false, // Default: not under maintenance + [FEE_TOKENS_RELEASE_TOGGLE]: false, // Default: fee tokens feature disabled }; diff --git a/packages/web-wallet/src/contexts/FeatureToggleContext.tsx b/packages/web-wallet/src/contexts/FeatureToggleContext.tsx index 96d40f4a..da8a50b9 100644 --- a/packages/web-wallet/src/contexts/FeatureToggleContext.tsx +++ b/packages/web-wallet/src/contexts/FeatureToggleContext.tsx @@ -6,6 +6,7 @@ import { UNLEASH_CLIENT_KEY, UNLEASH_POLLING_INTERVAL, WEB_WALLET_MAINTENANCE_TOGGLE, + FEE_TOKENS_RELEASE_TOGGLE, FEATURE_TOGGLE_DEFAULTS, STAGE, SKIP_FEATURE_TOGGLE, @@ -32,6 +33,7 @@ function getBrowserId(): string { interface FeatureToggleContextType { isUnderMaintenance: boolean; + isFeeTokensEnabled: boolean; isLoading: boolean; featureToggles: Record; /** Unique browser identifier used for feature toggle targeting */ @@ -148,9 +150,10 @@ export function FeatureToggleProvider({ children }: FeatureToggleProviderProps) }, []); const isUnderMaintenance = featureToggles[WEB_WALLET_MAINTENANCE_TOGGLE] ?? FEATURE_TOGGLE_DEFAULTS[WEB_WALLET_MAINTENANCE_TOGGLE]; + const isFeeTokensEnabled = featureToggles[FEE_TOKENS_RELEASE_TOGGLE] ?? FEATURE_TOGGLE_DEFAULTS[FEE_TOKENS_RELEASE_TOGGLE]; return ( - + {children} ); From 421cc9a92e09cf18be6bdcfd2cc663b129ff3a5b Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Andr=C3=A9=20Abadesso?= Date: Wed, 28 Jan 2026 12:51:30 -0300 Subject: [PATCH 2/2] feat(web-wallet): implemented fee based token --- .../src/components/CreateTokenDialog.tsx | 60 ++++++++++++------- 1 file changed, 40 insertions(+), 20 deletions(-) diff --git a/packages/web-wallet/src/components/CreateTokenDialog.tsx b/packages/web-wallet/src/components/CreateTokenDialog.tsx index 8b075d17..01d21533 100644 --- a/packages/web-wallet/src/components/CreateTokenDialog.tsx +++ b/packages/web-wallet/src/components/CreateTokenDialog.tsx @@ -6,7 +6,7 @@ import { z } from 'zod'; import { useWallet } from '../contexts/WalletContext'; import { useFeatureToggle } from '../contexts/FeatureToggleContext'; import { useInvokeSnap } from '@hathor/snap-utils'; -import { helpersUtils, tokensUtils, constants } from '@hathor/wallet-lib'; +import { helpersUtils, tokensUtils, constants, TokenVersion } from '@hathor/wallet-lib'; import { formatAmount, amountToCents } from '../utils/hathor'; import { readOnlyWalletWrapper } from '../services/ReadOnlyWalletWrapper'; import { getAddressForMode } from '../utils/addressMode'; @@ -137,13 +137,20 @@ const CreateTokenDialog: React.FC = ({ isOpen, onClose } const amount = watch('amount'); const tokenType = watch('tokenType'); + // Determine if this is a fee-based token (no deposit required) + const isFeeBasedToken = isFeeTokensEnabled && tokenType === 'fee' && !isNFT; + // Calculate 1% HTR deposit (1 HTR per 100 tokens) // For every 100 tokens, 1 HTR (100 cents) deposit is required // Formula: (tokens / 100) * 100 cents = tokens * 1 cent + // Fee-based tokens don't require a deposit const depositInCents = useMemo(() => { try { if (!amount || amount === '0') return 0n; + // Fee-based tokens don't require a deposit + if (isFeeBasedToken) return 0n; + let amountInBaseUnits: bigint; if (isNFT) { // NFTs: whole numbers only, 1 NFT = 0.01 HTR (1 base unit) @@ -160,7 +167,7 @@ const CreateTokenDialog: React.FC = ({ isOpen, onClose } } catch { return 0n; } - }, [amount, isNFT]); + }, [amount, isNFT, isFeeBasedToken]); // Check if user has insufficient balance const hasInsufficientBalance = useMemo(() => { @@ -172,15 +179,14 @@ const CreateTokenDialog: React.FC = ({ isOpen, onClose } setError(null); try { + // Determine if this is a fee-based token creation + const isCreatingFeeToken = isFeeTokensEnabled && data.tokenType === 'fee' && !data.isNFT; + // Derive mint/melt settings // For NFTs: use the checkbox values - // For regular tokens: based on token type (deposit = true, fee = false) - const createMint = data.isNFT - ? (data.createMintAuthority || false) - : data.tokenType === 'deposit'; - const createMelt = data.isNFT - ? (data.createMeltAuthority || false) - : data.tokenType === 'deposit'; + // For regular tokens: use the checkbox values + const createMint = data.createMintAuthority || false; + const createMelt = data.createMeltAuthority || false; // Get addresses based on address mode const changeAddress = await getAddressForMode(addressMode, readOnlyWalletWrapper); @@ -193,7 +199,7 @@ const CreateTokenDialog: React.FC = ({ isOpen, onClose } ? BigInt(data.amount) : amountToCents(data.amount); - // Prepare RPC params matching createTokenRpcSchema + // Prepare RPC params const params = { name: data.name, symbol: data.symbol, @@ -207,6 +213,7 @@ const CreateTokenDialog: React.FC = ({ isOpen, onClose } allow_external_melt_authority_address: false, data: data.isNFT && data.nftData ? [data.nftData] : null, address: mintAddress, // Mint address where tokens are sent + ...(isCreatingFeeToken && { token_version: TokenVersion.FEE }), }; // Call RPC @@ -578,19 +585,32 @@ const CreateTokenDialog: React.FC = ({ isOpen, onClose } )} {/* Deposit Display */} - {amount && depositInCents > 0n && ( + {amount && parseFloat(amount) > 0 && (
-

- DEPOSIT: {formatAmount(depositInCents, false)} HTR -

-

- TOTAL: {formatAmount(depositInCents, false)} HTR ({formatAmount(htrBalance, false)} HTR AVAILABLE) -

+ {isFeeBasedToken ? ( + <> +

+ DEPOSIT: No deposit required +

+

+ Fee-based tokens charge 1 HTR per transaction output instead of requiring an upfront deposit. +

+ + ) : depositInCents > 0n && ( + <> +

+ DEPOSIT: {formatAmount(depositInCents, false)} HTR +

+

+ TOTAL: {formatAmount(depositInCents, false)} HTR ({formatAmount(htrBalance, false)} HTR AVAILABLE) +

+ + )}
)} - {/* Insufficient Balance Warning */} - {hasInsufficientBalance && amount && parseFloat(amount) > 0 && ( + {/* Insufficient Balance Warning - only for deposit-based tokens */} + {hasInsufficientBalance && !isFeeBasedToken && amount && parseFloat(amount) > 0 && (
@@ -610,7 +630,7 @@ const CreateTokenDialog: React.FC = ({ isOpen, onClose } {/* Create Button */}