From f113154261e6fc67323aa97cb01a783bdabcb6a6 Mon Sep 17 00:00:00 2001 From: pandablue0809 Date: Fri, 15 Aug 2025 16:17:37 +0900 Subject: [PATCH 1/8] init issue currency page --- components/Layout/Header/MobileMenu.js | 3 + components/Layout/Header/index.js | 1 + pages/services/issue-currency.js | 193 +++++++++++++++++++++++++ 3 files changed, 197 insertions(+) create mode 100644 pages/services/issue-currency.js diff --git a/components/Layout/Header/MobileMenu.js b/components/Layout/Header/MobileMenu.js index 035c07c05..913dcaf4d 100644 --- a/components/Layout/Header/MobileMenu.js +++ b/components/Layout/Header/MobileMenu.js @@ -186,6 +186,9 @@ export default function MobileMenu({ {t('menu.services.tax-reports')} + + Issue Currency + {t('menu.project-registration')} diff --git a/components/Layout/Header/index.js b/components/Layout/Header/index.js index 16d3c7f11..92d2b631f 100644 --- a/components/Layout/Header/index.js +++ b/components/Layout/Header/index.js @@ -202,6 +202,7 @@ export default function Header({ {t('menu.services.nft-mint')} {t('menu.usernames')} {t('menu.services.tax-reports')} + Issue Currency {t('menu.project-registration')} {!devNet && {t('menu.price-alerts', { nativeCurrency })}} {t('menu.submit-offline-tx')} diff --git a/pages/services/issue-currency.js b/pages/services/issue-currency.js new file mode 100644 index 000000000..acc8c841d --- /dev/null +++ b/pages/services/issue-currency.js @@ -0,0 +1,193 @@ + +import { useState } from 'react' + +import SEO from '../../components/SEO' +import FormInput from '../../components/UI/FormInput' +import NetworkTabs from '../../components/Tabs/NetworkTabs' +import { ledgerName } from '../../utils' + + +export default function IssueCurrency({ subscriptionExpired, openEmailLogin, sessionToken }) { + const [currencyCode, setCurrencyCode] = useState('') + const [totalSupply, setTotalSupply] = useState('') + const [transferRate, setTransferRate] = useState('0') + const [issuerAddress, setIssuerAddress] = useState('') + const [isSubmitting, setIsSubmitting] = useState(false) + + // Check if user has Pro subscription + const hasProAccess = sessionToken && !subscriptionExpired + + const handleSubmit = async (e) => { + e.preventDefault() + if (!hasProAccess) { + openEmailLogin() + return + } + + setIsSubmitting(true) + // TODO: Implement currency issuance logic + console.log('Issuing currency:', { currencyCode, totalSupply, transferRate, issuerAddress }) + setIsSubmitting(false) + } + + if (!hasProAccess) { + return ( + <> + +
+

Issue Your Own Currency

+
+

Bithomp Pro Required

+

This feature is available exclusively to Bithomp Pro subscribers.

+

Upgrade to Pro to unlock the ability to create and issue your own custom currencies on the XRPL.

+ +
+
+ + ) + } + + return ( + <> + + +
+

Issue Your Own Currency

+

Create and manage your own custom tokens on the {ledgerName}

+ +
+ +
+

How to Issue Your Own Currency

+ +
+

1. Understanding {ledgerName} Tokens

+

+ The {ledgerName} allows anyone to create custom tokens (also called "issued currencies" or "IOUs") + that can represent anything from stablecoins to loyalty points, gaming assets, or real-world + commodities. These tokens are backed by the issuer's account and can be traded on decentralized + exchanges. +

+
+ +
+

2. Prerequisites

+
    +
  • Funded Account: Your account must hold at least 20 XRP (base reserve) plus additional XRP for transaction fees
  • +
  • Trust Lines: Other accounts need to establish trust lines to hold your tokens
  • +
  • Regulatory Compliance: Ensure your token complies with local laws and regulations
  • +
+
+ +
+

3. Token Properties

+
    +
  • Currency Code: 3-letter ISO code (e.g., USD, EUR) or 40-character hex string for custom codes
  • +
  • Total Supply: Maximum amount of tokens that can exist
  • +
  • Transfer Rate: Fee percentage charged on transfers (0-100%)
  • +
  • Issuer Address: Your account address that will issue and control the tokens
  • +
+
+ +
+

4. Best Practices

+
    +
  • Choose a meaningful and unique currency code
  • +
  • Set reasonable transfer rates to encourage adoption
  • +
  • Maintain transparency about your token's purpose and backing
  • +
  • Consider implementing a freeze feature for compliance
  • +
  • Provide clear documentation for users
  • +
+
+ +
+

5. Security Considerations

+
    +
  • Never share your private keys
  • +
  • Use hardware wallets for large amounts
  • +
  • Regularly monitor your account for suspicious activity
  • +
  • Consider multi-signing for additional security
  • +
+
+ +
+

Issue New Currency

+ { + if (e.key === 'Enter') { + e.preventDefault() + handleSubmit(e) + } + }} + /> + 3-letter ISO code or up to 40-character hex string + + { + if (e.key === 'Enter') { + e.preventDefault() + handleSubmit(e) + } + }} + /> + Maximum number of tokens that can exist + + { + if (e.key === 'Enter') { + e.preventDefault() + handleSubmit(e) + } + }} + /> + Fee percentage charged on transfers (0-100%) + + { + if (e.key === 'Enter') { + e.preventDefault() + handleSubmit(e) + } + }} + /> + Your {ledgerName} account address + + +
+
+ + ) +} From 50454d499a1fef470db7b3d435b4565f685ddb27 Mon Sep 17 00:00:00 2001 From: pandablue0809 Date: Sat, 16 Aug 2025 15:58:07 +0900 Subject: [PATCH 2/8] fix stylke --- pages/services/issue-currency.js | 208 ++++++++++++++++++------------- 1 file changed, 124 insertions(+), 84 deletions(-) diff --git a/pages/services/issue-currency.js b/pages/services/issue-currency.js index acc8c841d..b751a319a 100644 --- a/pages/services/issue-currency.js +++ b/pages/services/issue-currency.js @@ -4,16 +4,18 @@ import { useState } from 'react' import SEO from '../../components/SEO' import FormInput from '../../components/UI/FormInput' import NetworkTabs from '../../components/Tabs/NetworkTabs' -import { ledgerName } from '../../utils' - +import { ledgerName, nativeCurrency } from '../../utils' +import { useRouter } from 'next/router' export default function IssueCurrency({ subscriptionExpired, openEmailLogin, sessionToken }) { + const router = useRouter() const [currencyCode, setCurrencyCode] = useState('') const [totalSupply, setTotalSupply] = useState('') const [transferRate, setTransferRate] = useState('0') const [issuerAddress, setIssuerAddress] = useState('') const [isSubmitting, setIsSubmitting] = useState(false) + // Check if user has Pro subscription const hasProAccess = sessionToken && !subscriptionExpired @@ -69,124 +71,162 @@ export default function IssueCurrency({ subscriptionExpired, openEmailLogin, ses
-

How to Issue Your Own Currency

- -
-

1. Understanding {ledgerName} Tokens

-

+

How to Issue Your Own Currency

+ +
+

1. Understanding {ledgerName} Tokens

+

The {ledgerName} allows anyone to create custom tokens (also called "issued currencies" or "IOUs") that can represent anything from stablecoins to loyalty points, gaming assets, or real-world commodities. These tokens are backed by the issuer's account and can be traded on decentralized exchanges. -

-
+

+
-
-

2. Prerequisites

-
    -
  • Funded Account: Your account must hold at least 20 XRP (base reserve) plus additional XRP for transaction fees
  • +
    +

    2. Prerequisites

    +
      +
    • Funded Account: Your account must hold at least 20 {nativeCurrency} (base reserve) plus additional {nativeCurrency} for transaction fees
    • Trust Lines: Other accounts need to establish trust lines to hold your tokens
    • Regulatory Compliance: Ensure your token complies with local laws and regulations
    • -
    -
    +
+
-
-

3. Token Properties

-
    -
  • Currency Code: 3-letter ISO code (e.g., USD, EUR) or 40-character hex string for custom codes
  • -
  • Total Supply: Maximum amount of tokens that can exist
  • -
  • Transfer Rate: Fee percentage charged on transfers (0-100%)
  • -
  • Issuer Address: Your account address that will issue and control the tokens
  • -
-
+
+

3. Token Properties

+
    +
  • Currency Code: 3-letter ISO code (e.g., USD, EUR) or 40-character hex string for custom codes
  • +
  • Total Supply: Maximum amount of tokens that can exist
  • +
  • Transfer Rate: Fee percentage charged on transfers (0-100%)
  • +
  • Issuer Address: Your account address that will issue and control the tokens
  • +
+
-
-

4. Best Practices

-
    -
  • Choose a meaningful and unique currency code
  • -
  • Set reasonable transfer rates to encourage adoption
  • -
  • Maintain transparency about your token's purpose and backing
  • -
  • Consider implementing a freeze feature for compliance
  • -
  • Provide clear documentation for users
  • -
-
+
+

4. Best Practices

+
    +
  • Choose a meaningful and unique currency code
  • +
  • Set reasonable transfer rates to encourage adoption
  • +
  • Maintain transparency about your token's purpose and backing
  • +
  • Consider implementing a freeze feature for compliance
  • +
  • Provide clear documentation for users
  • +
+
-
-

5. Security Considerations

-
    -
  • Never share your private keys
  • -
  • Use hardware wallets for large amounts
  • -
  • Regularly monitor your account for suspicious activity
  • -
  • Consider multi-signing for additional security
  • -
-
+
+

5. Security Considerations

+
    +
  • Never share your private keys
  • +
  • Use hardware wallets for large amounts
  • +
  • Regularly monitor your account for suspicious activity
  • +
  • Consider multi-signing for additional security
  • +
+
+
+

Step-by-Step Token Issuance

+
-

Issue New Currency

+

1. Set Currency Properties

{ - if (e.key === 'Enter') { - e.preventDefault() - handleSubmit(e) - } - }} + hideButton={true} /> - 3-letter ISO code or up to 40-character hex string - +
{ - if (e.key === 'Enter') { - e.preventDefault() - handleSubmit(e) - } - }} + hideButton={true} /> - Maximum number of tokens that can exist - +
{ - if (e.key === 'Enter') { - e.preventDefault() - handleSubmit(e) - } - }} + hideButton={true} /> - Fee percentage charged on transfers (0-100%) - +
{ - if (e.key === 'Enter') { - e.preventDefault() - handleSubmit(e) - } - }} + hideButton={true} /> - Your {ledgerName} account address +
+ +
- + Open TrustLine Service + + +
+ +
+

TrustLine Setup Details:

+
    +
  • Currency: {currencyCode || 'Enter currency code above'}
  • +
  • Issuer: Your own address ({issuerAddress || 'Enter address above'})
  • +
  • Limit: {totalSupply || 'Enter total supply above'} (or higher)
  • +
+
+
+ +
+

3. Issue the Tokens

+

Now you can issue the tokens to your account using a Payment transaction with the specified currency and amount.

+ +
+ + +
+ +
+

Payment Transaction Details:

+
    +
  • Destination: Your own address ({issuerAddress || 'Enter address above'})
  • +
  • Currency: {currencyCode || 'Enter currency code above'}
  • +
  • Amount: {totalSupply || 'Enter total supply above'}
  • +
  • Issuer: Your own address (same as destination)
  • +
+
+
) From e7c52af6b3635ee53cbb9f93bc84891b4d228596 Mon Sep 17 00:00:00 2001 From: pandablue0809 Date: Mon, 25 Aug 2025 23:52:04 +0900 Subject: [PATCH 3/8] move order in menu --- components/Layout/Header/MobileMenu.js | 6 +++--- components/Layout/Header/index.js | 2 +- 2 files changed, 4 insertions(+), 4 deletions(-) diff --git a/components/Layout/Header/MobileMenu.js b/components/Layout/Header/MobileMenu.js index 6b1135344..044ca1ce2 100644 --- a/components/Layout/Header/MobileMenu.js +++ b/components/Layout/Header/MobileMenu.js @@ -164,6 +164,9 @@ export default function MobileMenu({ Issue Check + + Issue Currency + Create Escrow @@ -186,9 +189,6 @@ export default function MobileMenu({ {t('menu.services.tax-reports')} - - Issue Currency - {t('menu.project-registration')} diff --git a/components/Layout/Header/index.js b/components/Layout/Header/index.js index 1f6b365f4..4a96e781e 100644 --- a/components/Layout/Header/index.js +++ b/components/Layout/Header/index.js @@ -196,13 +196,13 @@ export default function Header({ Send Payment Set Trust (Trustline) Issue Check + Issue Currency Create Escrow {!xahauNetwork && !devNet && AMM Services} Account Settings {t('menu.services.nft-mint')} {t('menu.usernames')} {t('menu.services.tax-reports')} - Issue Currency {t('menu.services.tax-reports')} {t('menu.project-registration')} {!devNet && {t('menu.price-alerts', { nativeCurrency })}} From 695578fa67a5552daa3505de2eaffde1b7770fe5 Mon Sep 17 00:00:00 2001 From: pandablue0809 Date: Sun, 7 Sep 2025 09:48:43 +0900 Subject: [PATCH 4/8] create a new page to issue your own currency --- pages/_app.js | 1 + pages/services/issue-currency.js | 886 +++++++++++++++++++++++++------ styles/pages/issue-currency.scss | 637 ++++++++++++++++++++++ 3 files changed, 1372 insertions(+), 152 deletions(-) create mode 100644 styles/pages/issue-currency.scss diff --git a/pages/_app.js b/pages/_app.js index 45438d61d..0da573206 100644 --- a/pages/_app.js +++ b/pages/_app.js @@ -25,6 +25,7 @@ const WalletConnectModalSign = dynamic( import '../styles/globals.css' import '../styles/ui.scss' import '../styles/components/nprogress.css' +import '../styles/pages/issue-currency.scss' import { ThemeProvider } from '../components/Layout/ThemeContext' import { fetchCurrentFiatRate } from '../utils/common' diff --git a/pages/services/issue-currency.js b/pages/services/issue-currency.js index b751a319a..785bc3275 100644 --- a/pages/services/issue-currency.js +++ b/pages/services/issue-currency.js @@ -1,35 +1,83 @@ - -import { useState } from 'react' - +import { useState, useEffect } from 'react' import SEO from '../../components/SEO' import FormInput from '../../components/UI/FormInput' +import AddressInput from '../../components/UI/AddressInput' import NetworkTabs from '../../components/Tabs/NetworkTabs' -import { ledgerName, nativeCurrency } from '../../utils' -import { useRouter } from 'next/router' +import { ledgerName } from '../../utils' -export default function IssueCurrency({ subscriptionExpired, openEmailLogin, sessionToken }) { - const router = useRouter() +export default function IssueCurrency({ subscriptionExpired, openEmailLogin, sessionToken, account, setSignRequest }) { + const [currentStep, setCurrentStep] = useState(1) + const [supplyType, setSupplyType] = useState('') // 'closed' or 'open' + const [coldWalletAddress, setColdWalletAddress] = useState('') + const [hotWalletAddress, setHotWalletAddress] = useState('') const [currencyCode, setCurrencyCode] = useState('') const [totalSupply, setTotalSupply] = useState('') const [transferRate, setTransferRate] = useState('0') - const [issuerAddress, setIssuerAddress] = useState('') - const [isSubmitting, setIsSubmitting] = useState(false) + const [isSettingColdAddress, setIsSettingColdAddress] = useState(false) + const [coldAddressError, setColdAddressError] = useState('') + const [coldAddressSuccess, setColdAddressSuccess] = useState('') + + // AccountSet transaction fields for cold wallet + const [coldTransferRate, setColdTransferRate] = useState('0') + const [tickSize, setTickSize] = useState('5') + const [domain, setDomain] = useState('') + const [disallowXRP, setDisallowXRP] = useState(false) + const [requireDestTag, setRequireDestTag] = useState(false) + + // Hot wallet AccountSet fields + const [hotDomain, setHotDomain] = useState('') + const [hotRequireAuth, setHotRequireAuth] = useState(false) + const [hotDisallowXRP, setHotDisallowXRP] = useState(false) + const [hotRequireDestTag, setHotRequireDestTag] = useState(false) + const [isSettingHotWallet, setIsSettingHotWallet] = useState(false) + const [hotWalletError, setHotWalletError] = useState('') + const [hotWalletSuccess, setHotWalletSuccess] = useState('') + const [canProceedFromStep1, setCanProceedFromStep1] = useState(false) + const [canProceedFromStep2, setCanProceedFromStep2] = useState(false) + + // Token issuance and sending fields + const [issueQuantity, setIssueQuantity] = useState('') + const [destinationTag, setDestinationTag] = useState('1') + const [isIssuingTokens, setIsIssuingTokens] = useState(false) + const [tokenError, setTokenError] = useState('') + const [tokenSuccess, setTokenSuccess] = useState('') + + // Flag constants + const ASF_FLAGS = { + asfDefaultRipple: 8, + asfRequireAuth: 2 + } + + const TF_FLAGS = { + requireDestTag: { set: 0x00010000, clear: 0x00020000 }, + requireAuth: { set: 0x00040000, clear: 0x00080000 }, + disallowXRP: { set: 0x00100000, clear: 0x00200000 } + } // Check if user has Pro subscription const hasProAccess = sessionToken && !subscriptionExpired - const handleSubmit = async (e) => { - e.preventDefault() - if (!hasProAccess) { - openEmailLogin() - return + // Update canProceedFromStep1 when dependent values change + useEffect(() => { + setCanProceedFromStep1(supplyType && coldWalletAddress) + }, [supplyType, coldWalletAddress]) + + // Update canProceedFromStep2 when dependent values change + useEffect(() => { + setCanProceedFromStep2(hotWalletAddress && currencyCode && totalSupply) + }, [hotWalletAddress, currencyCode, totalSupply]) + + const handleNextStep = () => { + if (currentStep < 3) { + setCurrentStep(currentStep + 1) + } + } + + const handlePrevStep = () => { + if (currentStep > 1) { + setCurrentStep(currentStep - 1) } - - setIsSubmitting(true) - // TODO: Implement currency issuance logic - console.log('Issuing currency:', { currencyCode, totalSupply, transferRate, issuerAddress }) - setIsSubmitting(false) } if (!hasProAccess) { @@ -41,7 +89,7 @@ export default function IssueCurrency({ subscriptionExpired, openEmailLogin, ses />

Issue Your Own Currency

-
+

Bithomp Pro Required

This feature is available exclusively to Bithomp Pro subscribers.

Upgrade to Pro to unlock the ability to create and issue your own custom currencies on the XRPL.

@@ -57,6 +105,201 @@ export default function IssueCurrency({ subscriptionExpired, openEmailLogin, ses ) } + + // Generate WalletLocator from cold wallet address + const generateWalletLocator = (address) => { + if (!address) return '' + // Convert address to hex and pad to 64 characters + const addressHex = Array.from(address) + .map(char => char.charCodeAt(0).toString(16).padStart(2, '0')) + .join('') + return addressHex.padEnd(64, '0').substring(0, 64).toUpperCase() + } + + // Convert domain to hex + const domainToHex = (domain) => { + if (!domain) return '' + return Array.from(domain) + .map(char => char.charCodeAt(0).toString(16).padStart(2, '0')) + .join('') + .toUpperCase() + } + + // Handle cold address setting with comprehensive AccountSet + const handleSetColdAddress = () => { + if (!coldWalletAddress) { + setColdAddressError('Please enter a cold wallet address') + return + } + + if (!account?.address) { + setColdAddressError('Please connect your wallet first') + return + } + + const generatedWalletLocator = generateWalletLocator(coldWalletAddress) + setColdAddressError('') + setColdAddressSuccess('') + + // Build TF flags + let tfFlags = 0 + if (requireDestTag) { + tfFlags |= TF_FLAGS.requireDestTag.set + } + if (disallowXRP) { + tfFlags |= TF_FLAGS.disallowXRP.set + } + + // Create comprehensive AccountSet transaction + const tx = { + TransactionType: 'AccountSet', + Account: account.address, + TransferRate: parseInt(coldTransferRate) * 1000000000, // Convert to drops + TickSize: parseInt(tickSize), + WalletLocator: generatedWalletLocator, + setFlag: ASF_FLAGS.asfDefaultRipple + } + + // Add domain if provided + if (domain) { + tx.Domain = domainToHex(domain) + } + + // Add TF flags if any are set + if (tfFlags > 0) { + tx.Flags = tfFlags + } + + setIsSettingColdAddress(true) + setSignRequest({ + request: tx, + callback: () => { + setColdAddressSuccess('Cold address AccountSet transaction submitted successfully!') + setColdAddressError('') + setIsSettingColdAddress(false) + }, + errorCallback: (error) => { + setColdAddressError(`Transaction failed: ${error.message || 'Unknown error'}`) + setColdAddressSuccess('') + setIsSettingColdAddress(false) + } + }) + } + + // Handle hot wallet AccountSet configuration + const handleSetHotWallet = () => { + + if (!hotWalletAddress) { + setHotWalletError('Please enter a hot wallet address') + return + } + + if (!account?.address) { + setHotWalletError('Please connect your wallet first') + return + } + + setHotWalletError('') + setHotWalletSuccess('') + + // Build TF flags for hot wallet + let hotTfFlags = 0 + if (hotRequireDestTag) { + hotTfFlags |= TF_FLAGS.requireDestTag.set + } + if (hotDisallowXRP) { + hotTfFlags |= TF_FLAGS.disallowXRP.set + } + + // Create hot wallet AccountSet transaction + const tx = { + TransactionType: 'AccountSet', + Account: account.address + } + + // Add domain if provided + if (hotDomain) { + tx.Domain = domainToHex(hotDomain) + } + + // Add SetFlag for RequireAuth if enabled + if (hotRequireAuth) { + tx.SetFlag = ASF_FLAGS.asfRequireAuth + } + + // Add TF flags if any are set + if (hotTfFlags > 0) { + tx.Flags = hotTfFlags + } + + setIsSettingHotWallet(true) + setSignRequest({ + request: tx, + callback: () => { + setHotWalletSuccess('Hot wallet AccountSet transaction submitted successfully!') + setHotWalletError('') + setIsSettingHotWallet(false) + }, + errorCallback: (error) => { + setHotWalletError(`Transaction failed: ${error.message || 'Unknown error'}`) + setHotWalletSuccess('') + setIsSettingHotWallet(false) + } + }) + } + + // Handle token issuance from cold to hot wallet + const handleIssueTokens = () => { + + if (!issueQuantity || !currencyCode || !coldWalletAddress || !hotWalletAddress) { + setTokenError('Please fill in all required fields for token issuance') + return + } + + if (!account?.address) { + setTokenError('Please connect your wallet first') + return + } + + setTokenError('') + setTokenSuccess('') + + // Create Payment transaction for token issuance + const tx = { + TransactionType: 'Payment', + Account: account.address, + Amount: { + currency: currencyCode, + value: issueQuantity, + issuer: coldWalletAddress + }, + Destination: hotWalletAddress + } + + // Add destination tag if required + if (hotRequireDestTag && destinationTag) { + tx.DestinationTag = parseInt(destinationTag) + } + + setIsIssuingTokens(true) + try { + setSignRequest({ + request: tx, + callback: () => { + setTokenSuccess(`Successfully issued ${issueQuantity} ${currencyCode} to hot wallet!`) + setTokenError('') + setIsIssuingTokens(false) + } + }) + } catch (error) { + setTokenError(`Token issuance failed: ${error.message || 'Unknown error'}`) + setTokenSuccess('') + setIsIssuingTokens(false) + } finally { + setIsIssuingTokens(false) + } + } + return ( <>
-

How to Issue Your Own Currency

- -
-

1. Understanding {ledgerName} Tokens

-

- The {ledgerName} allows anyone to create custom tokens (also called "issued currencies" or "IOUs") - that can represent anything from stablecoins to loyalty points, gaming assets, or real-world - commodities. These tokens are backed by the issuer's account and can be traded on decentralized - exchanges. -

+ {/* Progress Indicator */} +
+
+
= 1 ? 'active' : ''}`}>1
+
= 2 ? 'active' : ''}`}>2
+
= 3 ? 'active' : ''}`}>3
+
-
-

2. Prerequisites

-
    -
  • Funded Account: Your account must hold at least 20 {nativeCurrency} (base reserve) plus additional {nativeCurrency} for transaction fees
  • -
  • Trust Lines: Other accounts need to establish trust lines to hold your tokens
  • -
  • Regulatory Compliance: Ensure your token complies with local laws and regulations
  • -
-
+ {/* Step 1: Choose Supply Type and Create Issuer Account */} + {currentStep === 1 && ( +
+

Step 1: Choose Supply Type and Create Issuer Account

+

First, decide whether you want a Closed Supply or Open Supply currency, and create your Issuer Account (cold wallet).

+ +
+

Choose Your Supply Type:

+
+ + + +
+
-
-

3. Token Properties

-
    -
  • Currency Code: 3-letter ISO code (e.g., USD, EUR) or 40-character hex string for custom codes
  • -
  • Total Supply: Maximum amount of tokens that can exist
  • -
  • Transfer Rate: Fee percentage charged on transfers (0-100%)
  • -
  • Issuer Address: Your account address that will issue and control the tokens
  • -
-
+
+

Create and Configure Your Issuer Account (Cold Wallet):

+

This will be your cold wallet that issues the currency. {supplyType === 'closed' ? 'It will be blackholed after issuance for maximum security.' : 'You will maintain control over this account.'} Configure comprehensive AccountSet settings including transfer rates, tick size, domain, and security flags.

+ + + + {supplyType === 'closed' && ( +
+

⚠️ Blackhole Warning

+

For Closed Supply, the issuer account will be blackholed (keys destroyed) after issuance. This means:

+
    +
  • No one can ever mint new tokens
  • +
  • No one can freeze or modify the currency
  • +
  • The total supply becomes permanently fixed
  • +
  • This action is irreversible
  • +
+
+ )} + +
+
+

Basic Settings

+ + + + + + +
-
-

4. Best Practices

-
    -
  • Choose a meaningful and unique currency code
  • -
  • Set reasonable transfer rates to encourage adoption
  • -
  • Maintain transparency about your token's purpose and backing
  • -
  • Consider implementing a freeze feature for compliance
  • -
  • Provide clear documentation for users
  • -
-
+
+

Security Flags

+
+
+
+ + + ✓ Enabled + +
+ Allow rippling on trust lines by default +
+
{ + setDisallowXRP(!disallowXRP) + setIsSettingColdAddress(false) + }} + > +
+ + + {disallowXRP ? '✓ Enabled' : '✗ Disabled'} + +
+ Prevent incoming XRP payments +
+
{ + setRequireDestTag(!requireDestTag) + setIsSettingColdAddress(false) + }} + > +
+ + + {requireDestTag ? '✓ Enabled' : '✗ Disabled'} + +
+ Require destination tag for incoming payments +
+
+
-
-

5. Security Considerations

-
    -
  • Never share your private keys
  • -
  • Use hardware wallets for large amounts
  • -
  • Regularly monitor your account for suspicious activity
  • -
  • Consider multi-signing for additional security
  • -
-
+ {coldAddressError && ( +
+ {coldAddressError} +
+ )} -
-

Step-by-Step Token Issuance

- -
-

1. Set Currency Properties

- -
- -
- -
- -
- -
+ {coldAddressSuccess && ( +
+ {coldAddressSuccess} +
+ )} -
-

2. Create TrustLine

-

Before you can issue tokens, you need to create a trustline to your own account. This allows your account to hold the tokens you're about to create.

- -
+
+ +
+
+
+ +
- -
- -
-

TrustLine Setup Details:

-
    -
  • Currency: {currencyCode || 'Enter currency code above'}
  • -
  • Issuer: Your own address ({issuerAddress || 'Enter address above'})
  • -
  • Limit: {totalSupply || 'Enter total supply above'} (or higher)
  • -
+ )} -
-

3. Issue the Tokens

-

Now you can issue the tokens to your account using a Payment transaction with the specified currency and amount.

+ {/* Step 2: Configure Hot Wallet Settings */} + {currentStep === 2 && ( +
+

Step 2: Configure Hot Wallet Settings

+

Set up your hot wallet that will hold and manage the issued currency for daily operations.

-
+
+

Hot Wallet Configuration:

+

The hot wallet is where you'll store the issued currency for trading, transfers, and other operations. This should be a separate account from your issuer account.

+ + +
+

Currency Properties:

+ +
+ +
+ +
+
+ +
+

Configure Hot Wallet AccountSet Settings:

+

Set up security and operational settings for your hot wallet to prevent accidental trust line usage and enhance security.

+ +
+

Basic Settings

+ +
+ +
+

Security Flags

+
+
{ + setHotRequireAuth(!hotRequireAuth) + setIsSettingHotWallet(false) + }} + > +
+ + + {hotRequireAuth ? '✓ Enabled' : '✗ Disabled'} + +
+ Prevents accidental trust line usage by requiring explicit authorization +
+ +
{ + setHotDisallowXRP(!hotDisallowXRP) + setIsSettingHotWallet(false) + }} + > +
+ + + {hotDisallowXRP ? '✓ Enabled' : '✗ Disabled'} + +
+ Prevent incoming XRP payments to hot wallet +
+ +
{ + setHotRequireDestTag(!hotRequireDestTag) + setIsSettingHotWallet(false) + }} + > +
+ + + {hotRequireDestTag ? '✓ Enabled' : '✗ Disabled'} + +
+ Require destination tag for incoming payments +
+
+
+ + {hotWalletError && ( +
+ {hotWalletError} +
+ )} + + {hotWalletSuccess && ( +
+ {hotWalletSuccess} +
+ )} + +
+ + {(hotWalletAddress && account?.address) && ( +
+ ✓ Ready to configure hot wallet +
+ )} +
+
+ +
+
+ )} + + {/* Step 3: Issue Currency and Send to Hot Wallet */} + {currentStep === 3 && ( +
+

Step 3: Issue Currency and Send to Customers

+

Now you'll issue the currency and send it to your hot wallet, then distribute it to customers.

+ + {/* Step 1: Create TrustLine */} +
+

1. Create TrustLine

+

First, set up a trustline from your hot wallet to the issuer account to allow holding the tokens.

+
+

TrustLine Setup Details:

+
    +
  • Currency: {currencyCode}
  • +
  • Issuer: {coldWalletAddress}
  • +
  • Limit: {totalSupply} (or higher)
  • +
+
+
+ + {/* Step 2: Issue Tokens from Cold to Hot */} +
+

2. Issue Tokens (Cold to Hot Wallet)

+

Issue tokens from your cold wallet to the hot wallet for operational use.

+ +
+

Token Issuance Settings

+ + + {hotRequireDestTag && ( + + )} +
+ +
+ + {(issueQuantity && account?.address) && ( +
+ ✓ Ready to issue tokens +
+ )} +
+
-
-

Payment Transaction Details:

-
    -
  • Destination: Your own address ({issuerAddress || 'Enter address above'})
  • -
  • Currency: {currencyCode || 'Enter currency code above'}
  • -
  • Amount: {totalSupply || 'Enter total supply above'}
  • -
  • Issuer: Your own address (same as destination)
  • -
+ {/* Error and Success Messages */} + {tokenError && ( +
+ {tokenError} +
+ )} + + {tokenSuccess && ( +
+ {tokenSuccess} +
+ )} + +
+
-
+ )}
) diff --git a/styles/pages/issue-currency.scss b/styles/pages/issue-currency.scss new file mode 100644 index 000000000..c7e0816b2 --- /dev/null +++ b/styles/pages/issue-currency.scss @@ -0,0 +1,637 @@ +// Issue Currency Page Styles + +.ic-step-progress { + display: flex; + justify-content: center; + margin-bottom: 30px; +} + +.ic-step-indicator { + display: flex; + gap: 20px; + align-items: center; +} + +.ic-step { + width: 40px; + height: 40px; + border-radius: 50%; + background-color: #e9ecef; + color: #6c757d; + display: flex; + align-items: center; + justify-content: center; + font-weight: bold; + font-size: 16px; + transition: all 0.3s ease; +} + +.ic-step.active { + background-color: var(--accent-icon); + color: white; +} + +.ic-step-container { + max-width: 800px; + margin: 0 auto; + padding: 20px; +} + +.ic-supply-type-selection { + margin: 20px 0; +} + +.ic-radio-options { + display: flex; + flex-direction: row; + flex-wrap: wrap; + gap: 15px; + margin-top: 15px; +} + +.ic-radio-option { + display: flex; + align-items: flex-start; + padding: 20px; + border: 2px solid #e0e0e0; + border-radius: 8px; + cursor: pointer; + transition: all 0.3s ease; + flex: 1; + height: 280px; + overflow: hidden; +} + +.ic-radio-option:hover { + border-color: var(--accent-icon); +} + +.ic-radio-option.selected { + border-color: var(--accent-icon); + background-color: #f8f9ff; +} + +.ic-radio-option input[type="radio"] { + margin-right: 15px; + margin-top: 5px; +} + +.ic-option-content { + flex: 1; + display: flex; + flex-direction: column; + justify-content: flex-start; + height: 100%; + overflow: hidden; +} + +.ic-option-content h4 { + margin: 0 0 10px 0; + color: var(--accent-icon); +} + +.ic-option-content p { + margin: 0 0 12px 0; + color: #666; + line-height: 1.4; + flex-shrink: 0; +} + +.ic-option-content ul { + margin: 0; + padding-left: 20px; + flex: 1; + overflow: hidden; +} + +.ic-option-content li { + margin: 4px 0; + color: #666; + line-height: 1.3; + font-size: 14px; +} + +.ic-blackhole-warning { + background-color: #fff3cd; + border: 1px solid #ffeaa7; + border-radius: 8px; + padding: 15px; + margin: 15px 0; +} + +.ic-blackhole-warning h4 { + color: #856404; + margin: 0 0 10px 0; +} + +.ic-blackhole-warning p { + color: #856404; + margin: 0 0 10px 0; +} + +.ic-blackhole-warning ul { + margin: 0; + padding-left: 20px; +} + +.ic-blackhole-warning li { + color: #856404; + margin: 5px 0; +} + +.ic-cold-wallet-setup { + margin: 20px 0; + padding: 20px; + background-color: #f8f9fa; + border: 1px solid #e9ecef; + border-radius: 8px; +} + +.ic-cold-wallet-setup h3 { + color: var(--accent-icon); + margin: 0 0 15px 0; + padding-bottom: 10px; + border-bottom: 2px solid var(--accent-icon); + font-size: 1.5em; +} + +.ic-cold-address-form { + margin-top: 15px; +} + +.ic-form-section { + margin: 20px 0; + padding: 15px; + background-color: #fff; + border: 1px solid #e9ecef; + border-radius: 6px; +} + +.ic-form-section h4 { + margin: 0 0 15px 0; + color: var(--accent-icon); + font-size: 1.2em; +} + +.ic-form-spacing { + height: 15px; +} + +.ic-flag-options { + display: flex; + flex-direction: column; + gap: 12px; +} + +.ic-flag-option { + display: flex; + flex-direction: column; + padding: 12px; + background-color: #f8f9fa; + border: 1px solid #e9ecef; + border-radius: 6px; + transition: all 0.2s ease; + cursor: pointer; +} + +.ic-flag-option.enabled { + border-color: #28a745; + background-color: #f8fff9; +} + +.ic-flag-option.disabled { + border-color: #dc3545; + background-color: #fff8f8; +} + +.ic-flag-option:hover { + background-color: #e9ecef; + border-color: var(--accent-icon); +} + +.ic-flag-option.enabled:hover { + background-color: #e8f5e8; +} + +.ic-flag-option.disabled:hover { + background-color: #ffeaea; +} + +.ic-flag-header { + display: flex; + align-items: center; + justify-content: space-between; + margin-bottom: 5px; +} + +.ic-flag-option label { + display: flex; + align-items: center; + gap: 10px; +} + +.ic-flag-option input[type="checkbox"] { + margin-right: 10px; + margin-top: 0; +} + +.ic-flag-option span { + font-weight: 500; + color: #333; +} + +.ic-flag-status-indicator { + font-size: 12px; + font-weight: 600; + padding: 2px 8px; + border-radius: 12px; + transition: all 0.3s ease; +} + +.ic-flag-option.enabled .ic-flag-status-indicator { + background-color: #d4edda; + color: #155724; +} + +.ic-flag-option.disabled .ic-flag-status-indicator { + background-color: #f8d7da; + color: #721c24; +} + +.ic-flag-option small { + color: #666; + font-size: 12px; + margin-left: 0; +} + +.ic-wallet-locator-preview { + margin: 15px 0; + padding: 15px; + background-color: #e9ecef; + border-radius: 6px; +} + +.ic-wallet-locator-preview h4 { + margin: 0 0 10px 0; + color: var(--accent-icon); +} + +.ic-locator-display { + background-color: #fff; + padding: 10px; + border-radius: 4px; + border: 1px solid #ddd; + font-family: 'Courier New', monospace; + font-size: 12px; + word-break: break-all; +} + +.ic-hot-wallet-setup { + margin: 20px 0; + padding: 20px; + background-color: #f8f9fa; + border: 1px solid #e9ecef; + border-radius: 8px; +} + +.ic-currency-properties { + margin: 20px 0; +} + +.ic-currency-properties h3 { + color: var(--accent-icon); + margin: 0 0 15px 0; +} + +.ic-hot-wallet-accountset { + margin: 20px 0; + padding: 20px; + background-color: #f0f8ff; + border: 1px solid #b3d9ff; + border-radius: 8px; +} + +.ic-hot-wallet-accountset h3 { + color: var(--accent-icon); + margin: 0 0 15px 0; +} + +.ic-hot-wallet-actions { + margin-top: 15px; +} + +.ic-trustline-step, .ic-issue-tokens-step, .ic-send-to-customer-step, .ic-blackhole-step { + margin: 20px 0; + padding: 20px; + background-color: #f8f9fa; + border: 1px solid #e9ecef; + border-radius: 8px; +} + +.ic-trustline-info, .ic-transaction-preview { + margin: 15px 0; + padding: 15px; + background-color: #e9ecef; + border-radius: 6px; +} + +.ic-trustline-info h4, .ic-transaction-preview h4 { + margin: 0 0 10px 0; + color: var(--accent-icon); +} + +.ic-trustline-info ul, .ic-transaction-preview ul { + margin: 10px 0 0 0; + padding-left: 20px; +} + +.ic-trustline-info li, .ic-transaction-preview li { + margin: 5px 0; +} + +.ic-preview-box { + background-color: #fff; + padding: 15px; + border-radius: 4px; + border: 1px solid #ddd; + font-family: 'Courier New', monospace; + font-size: 13px; +} + +.ic-preview-box p { + margin: 5px 0; + color: #333; +} + +.ic-issue-actions, .ic-customer-actions { + margin-top: 15px; +} + +.ic-blackhole-warning { + background-color: #fff3cd; + border: 1px solid #ffeaa7; + border-radius: 6px; + padding: 15px; + margin: 15px 0; +} + +.ic-blackhole-warning h4 { + color: #856404; + margin: 0 0 10px 0; +} + +.ic-blackhole-warning p { + color: #856404; + margin: 0; +} + +.ic-step-actions { + display: flex; + gap: 15px; + margin-top: 30px; + justify-content: center; +} + +.ic-button-action { + text-decoration: none; + display: inline-block; + box-sizing: content-box; + line-height: 38px; + background-color: var(--accent-icon); + border: 1px solid var(--accent-icon); + color: var(--text-contrast); + height: 40px; + min-width: 100px; + cursor: pointer; + padding-left: 10px; + padding-right: 10px; + padding-top: 0; + padding-bottom: 0; + font-size: 16px; + + .xaman-logo { + width: 24px; + height: 24px; + border-radius: 5px; + margin-right: 5px; + margin-bottom: 5px; + vertical-align: middle; + &.disabled { + filter: grayscale(100%); + } + } + + &:hover { + background-color: var(--background-menu); + color: var(--text-contrast) !important; + border-color: var(--accent-link); + } + &.thin { + height: 29px; + line-height: 28px; + .xaman-logo { + width: 18px; + height: 18px; + margin-bottom: 2px; + } + } + &.narrow { + min-width: unset; + } + &.wide { + width: calc(100% - 22px); + } + &.disabled, + &:disabled, + &[disabled] { + border: 1px solid #999999; + background-color: #cccccc; + color: #666666; + cursor: default; + pointer-events: none; + } + &.danger { + background-color: #dc3545; + border-color: #dc3545; + &:hover:not(:disabled) { + background-color: #c82333; + border-color: #c82333; + } + } +} + +.ic-error-message { + background-color: #f8d7da; + border: 1px solid #f5c6cb; + color: #721c24; + padding: 12px; + border-radius: 6px; + margin: 15px 0; +} + +.ic-success-message { + background-color: #d4edda; + border: 1px solid #c3e6cb; + color: #155724; + padding: 12px; + border-radius: 6px; + margin: 15px 0; +} + +.ic-issuance-summary { + margin: 20px 0; + padding: 20px; + background-color: #f8f9fa; + border: 1px solid #e9ecef; + border-radius: 8px; +} + +.ic-summary-box { + background-color: #fff; + padding: 20px; + border-radius: 6px; + border: 1px solid #e9ecef; +} + +.ic-summary-item { + margin: 10px 0; + padding: 8px 0; + border-bottom: 1px solid #f0f0f0; +} + +.ic-summary-item:last-child { + border-bottom: none; +} + +// Responsive Design +@media (min-width: 1400px) { + .ic-radio-options { + flex-direction: row; + flex-wrap: nowrap; + max-width: 1200px; + margin: 0 auto; + } + + .ic-radio-option { + flex: 1; + min-width: 0; + max-width: 50%; + height: 320px; + width: 45%; + } +} + +@media (min-width: 1200px) and (max-width: 1399px) { + .ic-radio-options { + flex-direction: row; + flex-wrap: nowrap; + max-width: 1000px; + margin: 0 auto; + } + + .ic-radio-option { + flex: 1; + min-width: 0; + max-width: 50%; + height: 300px; + width: 48%; + } +} + +@media (min-width: 1024px) and (max-width: 1199px) { + .ic-radio-options { + flex-direction: row; + flex-wrap: nowrap; + max-width: 900px; + margin: 0 auto; + } + + .ic-radio-option { + flex: 1; + min-width: 0; + max-width: 50%; + height: 280px; + width: 48%; + } +} + +@media (min-width: 768px) and (max-width: 1023px) { + .ic-radio-options { + flex-direction: row; + flex-wrap: nowrap; + max-width: 700px; + margin: 0 auto; + } + + .ic-radio-option { + flex: 1; + min-width: 0; + max-width: 50%; + height: 260px; + width: 48%; + } +} + +@media (max-width: 767px) { + .ic-radio-options { + flex-direction: column; + max-width: 100%; + margin: 0; + } + + .ic-radio-option { + height: 240px; + width: 90%; + max-width: 100%; + } + + .ic-option-content { + overflow: visible; + } + + .ic-option-content ul { + overflow: visible; + } + + .ic-step-actions { + flex-direction: column; + } + + .ic-step-indicator { + gap: 10px; + } + + .ic-step { + width: 30px; + height: 30px; + font-size: 14px; + } + + .ic-cold-wallet-setup { + padding: 15px; + } + + .ic-form-section { + padding: 12px; + } + + .ic-flag-options { + gap: 8px; + } + + .ic-flag-option { + padding: 10px; + } + + .ic-flag-option label { + margin-bottom: 3px; + } + + .ic-flag-option input[type="checkbox"] { + margin-right: 8px; + } + + .ic-locator-display code { + font-size: 10px; + } +} From eb9881c8951cf5a9ac137ff282d1a92143c15cc7 Mon Sep 17 00:00:00 2001 From: pandablue0809 Date: Sun, 7 Sep 2025 19:56:45 +0900 Subject: [PATCH 5/8] add getServerSideProps --- pages/services/issue-currency.js | 12 ++++++++++++ 1 file changed, 12 insertions(+) diff --git a/pages/services/issue-currency.js b/pages/services/issue-currency.js index 785bc3275..495a3c273 100644 --- a/pages/services/issue-currency.js +++ b/pages/services/issue-currency.js @@ -1,9 +1,21 @@ import { useState, useEffect } from 'react' +import { serverSideTranslations } from 'next-i18next/serverSideTranslations' import SEO from '../../components/SEO' import FormInput from '../../components/UI/FormInput' import AddressInput from '../../components/UI/AddressInput' import NetworkTabs from '../../components/Tabs/NetworkTabs' import { ledgerName } from '../../utils' +import { getIsSsrMobile } from '../../utils/mobile' + +export const getServerSideProps = async (context) => { + const { locale } = context + return { + props: { + ...(await serverSideTranslations(locale, ['common'])), + isSsrMobile: getIsSsrMobile(context) + } + } +} export default function IssueCurrency({ subscriptionExpired, openEmailLogin, sessionToken, account, setSignRequest }) { const [currentStep, setCurrentStep] = useState(1) From 97cf87bc2d7e7b8fcf82b35294f95b09adc1beb2 Mon Sep 17 00:00:00 2001 From: pandablue0809 Date: Mon, 15 Sep 2025 23:50:46 +0900 Subject: [PATCH 6/8] fix in dark theme --- components/Layout/Header/index.js | 1 - hooks/useEmailLogin.js | 3 --- pages/services/issue-currency.js | 44 +++++++++++++++++++++++++------ styles/pages/issue-currency.scss | 18 ++++++------- 4 files changed, 45 insertions(+), 21 deletions(-) diff --git a/components/Layout/Header/index.js b/components/Layout/Header/index.js index 4a96e781e..7fd2c6fed 100644 --- a/components/Layout/Header/index.js +++ b/components/Layout/Header/index.js @@ -202,7 +202,6 @@ export default function Header({ Account Settings {t('menu.services.nft-mint')} {t('menu.usernames')} - {t('menu.services.tax-reports')} {t('menu.services.tax-reports')} {t('menu.project-registration')} {!devNet && {t('menu.price-alerts', { nativeCurrency })}} diff --git a/hooks/useEmailLogin.js b/hooks/useEmailLogin.js index f71cf02da..eeecfd5e2 100644 --- a/hooks/useEmailLogin.js +++ b/hooks/useEmailLogin.js @@ -15,9 +15,6 @@ export const useEmailLogin = () => { }, []) const handleLoginSuccess = useCallback(() => { - if (onLoginSuccess) { - onLoginSuccess() - } closeEmailLogin() }, [onLoginSuccess, closeEmailLogin]) diff --git a/pages/services/issue-currency.js b/pages/services/issue-currency.js index 495a3c273..de8000b4e 100644 --- a/pages/services/issue-currency.js +++ b/pages/services/issue-currency.js @@ -73,11 +73,13 @@ export default function IssueCurrency({ subscriptionExpired, openEmailLogin, ses // Update canProceedFromStep1 when dependent values change useEffect(() => { setCanProceedFromStep1(supplyType && coldWalletAddress) + setIsSettingColdAddress(false) }, [supplyType, coldWalletAddress]) // Update canProceedFromStep2 when dependent values change useEffect(() => { setCanProceedFromStep2(hotWalletAddress && currencyCode && totalSupply) + setIsSettingHotWallet(false) }, [hotWalletAddress, currencyCode, totalSupply]) const handleNextStep = () => { @@ -105,12 +107,12 @@ export default function IssueCurrency({ subscriptionExpired, openEmailLogin, ses

Bithomp Pro Required

This feature is available exclusively to Bithomp Pro subscribers.

Upgrade to Pro to unlock the ability to create and issue your own custom currencies on the XRPL.

- +
+
+ +
@@ -150,6 +152,13 @@ export default function IssueCurrency({ subscriptionExpired, openEmailLogin, ses } const generatedWalletLocator = generateWalletLocator(coldWalletAddress) + + // Validate WalletLocator format + if (!/^[0-9A-F]{64}$/.test(generatedWalletLocator)) { + setColdAddressError('Invalid cold wallet address format') + return + } + setColdAddressError('') setColdAddressSuccess('') @@ -162,18 +171,37 @@ export default function IssueCurrency({ subscriptionExpired, openEmailLogin, ses tfFlags |= TF_FLAGS.disallowXRP.set } + // Validate TransferRate and TickSize + const transferRateValue = parseInt(coldTransferRate) + const tickSizeValue = parseInt(tickSize) + + if (transferRateValue < 0 || transferRateValue > 100) { + setColdAddressError('Transfer rate must be between 0 and 100 percent') + return + } + + if (tickSizeValue < 0 || tickSizeValue > 15) { + setColdAddressError('Tick size must be between 0 and 15') + return + } + // Create comprehensive AccountSet transaction const tx = { TransactionType: 'AccountSet', Account: account.address, - TransferRate: parseInt(coldTransferRate) * 1000000000, // Convert to drops - TickSize: parseInt(tickSize), + TransferRate: transferRateValue === 0 ? 0 : transferRateValue * 1000000000, // Convert to drops, 0 means no fee + TickSize: tickSizeValue, WalletLocator: generatedWalletLocator, setFlag: ASF_FLAGS.asfDefaultRipple } // Add domain if provided if (domain) { + // Validate domain format (basic validation) + if (domain.length > 256) { + setColdAddressError('Domain name too long (maximum 256 characters)') + return + } tx.Domain = domainToHex(domain) } diff --git a/styles/pages/issue-currency.scss b/styles/pages/issue-currency.scss index c7e0816b2..0a0f9c4e9 100644 --- a/styles/pages/issue-currency.scss +++ b/styles/pages/issue-currency.scss @@ -142,7 +142,7 @@ .ic-cold-wallet-setup { margin: 20px 0; padding: 20px; - background-color: #f8f9fa; + background-color: var(--background-secondary); border: 1px solid #e9ecef; border-radius: 8px; } @@ -162,7 +162,7 @@ .ic-form-section { margin: 20px 0; padding: 15px; - background-color: #fff; + background-color: var(--background-secondary); border: 1px solid #e9ecef; border-radius: 6px; } @@ -289,7 +289,7 @@ .ic-hot-wallet-setup { margin: 20px 0; padding: 20px; - background-color: #f8f9fa; + background-color: var(--background-secondary); border: 1px solid #e9ecef; border-radius: 8px; } @@ -306,7 +306,7 @@ .ic-hot-wallet-accountset { margin: 20px 0; padding: 20px; - background-color: #f0f8ff; + background-color: var(--background-secondary); border: 1px solid #b3d9ff; border-radius: 8px; } @@ -323,7 +323,7 @@ .ic-trustline-step, .ic-issue-tokens-step, .ic-send-to-customer-step, .ic-blackhole-step { margin: 20px 0; padding: 20px; - background-color: #f8f9fa; + background-color: var(--background-secondary); border: 1px solid #e9ecef; border-radius: 8px; } @@ -331,7 +331,7 @@ .ic-trustline-info, .ic-transaction-preview { margin: 15px 0; padding: 15px; - background-color: #e9ecef; + background-color: var(--background-secondary); border-radius: 6px; } @@ -350,7 +350,7 @@ } .ic-preview-box { - background-color: #fff; + background-color: var(--background-secondary); padding: 15px; border-radius: 4px; border: 1px solid #ddd; @@ -481,13 +481,13 @@ .ic-issuance-summary { margin: 20px 0; padding: 20px; - background-color: #f8f9fa; + background-color: var(--background-secondary); border: 1px solid #e9ecef; border-radius: 8px; } .ic-summary-box { - background-color: #fff; + background-color: var(--background-secondary); padding: 20px; border-radius: 6px; border: 1px solid #e9ecef; From 226704660386cf233323811f30c456c8b2d431ef Mon Sep 17 00:00:00 2001 From: pandablue0809 Date: Tue, 16 Sep 2025 01:30:45 +0900 Subject: [PATCH 7/8] fix for Xahau Network --- pages/services/issue-currency.js | 16 ++++++++-------- 1 file changed, 8 insertions(+), 8 deletions(-) diff --git a/pages/services/issue-currency.js b/pages/services/issue-currency.js index de8000b4e..562aaf0e2 100644 --- a/pages/services/issue-currency.js +++ b/pages/services/issue-currency.js @@ -4,7 +4,7 @@ import SEO from '../../components/SEO' import FormInput from '../../components/UI/FormInput' import AddressInput from '../../components/UI/AddressInput' import NetworkTabs from '../../components/Tabs/NetworkTabs' -import { ledgerName } from '../../utils' +import { ledgerName, nativeCurrency } from '../../utils' import { getIsSsrMobile } from '../../utils/mobile' export const getServerSideProps = async (context) => { @@ -99,14 +99,14 @@ export default function IssueCurrency({ subscriptionExpired, openEmailLogin, ses <>

Issue Your Own Currency

Bithomp Pro Required

This feature is available exclusively to Bithomp Pro subscribers.

-

Upgrade to Pro to unlock the ability to create and issue your own custom currencies on the XRPL.

+

Upgrade to Pro to unlock the ability to create and issue your own custom currencies on the {ledgerName}.


- Prevent incoming XRP payments + Prevent incoming {nativeCurrency} payments
- Disallow XRP + Disallow {nativeCurrency} {hotDisallowXRP ? '✓ Enabled' : '✗ Disabled'}
- Prevent incoming XRP payments to hot wallet + Prevent incoming {nativeCurrency} payments to hot wallet
Date: Wed, 8 Oct 2025 10:26:03 +0900 Subject: [PATCH 8/8] add descriptions and fix step3 --- components/UI/AddressInput.js | 2 +- pages/services/account-settings.js | 109 +++++++++++++ pages/services/issue-currency.js | 241 +++++++++++++++++++---------- styles/pages/issue-currency.scss | 26 ++-- 4 files changed, 286 insertions(+), 92 deletions(-) diff --git a/components/UI/AddressInput.js b/components/UI/AddressInput.js index 5c96428ae..6f4536cdb 100644 --- a/components/UI/AddressInput.js +++ b/components/UI/AddressInput.js @@ -27,7 +27,7 @@ export default function AddressInput({ const hasRun = useRef(false) const initialRawData = useRef(rawData) - const [inputValue, setInputValue] = useState('') + const [inputValue, setInputValue] = useState(rawData || '') const [errorMessage, setErrorMessage] = useState('') const [isMounted, setIsMounted] = useState(false) const [searchSuggestions, setSearchSuggestions] = useState([]) diff --git a/pages/services/account-settings.js b/pages/services/account-settings.js index c1809a37e..9a2a70cde 100644 --- a/pages/services/account-settings.js +++ b/pages/services/account-settings.js @@ -67,6 +67,8 @@ export default function AccountSettings({ const [currentEmailHash, setCurrentEmailHash] = useState('') const [messageKeyInput, setMessageKeyInput] = useState('') const [currentMessageKey, setCurrentMessageKey] = useState('') + const [regularKeyInput, setRegularKeyInput] = useState('') + const [currentRegularKey, setCurrentRegularKey] = useState('') const [transferRateInput, setTransferRateInput] = useState('') const [currentTransferRate, setCurrentTransferRate] = useState(null) const [tickSizeInput, setTickSizeInput] = useState('') @@ -282,6 +284,8 @@ export default function AccountSettings({ setEmailHashInput(response.data?.ledgerInfo?.emailHash || '') setCurrentMessageKey(response.data?.ledgerInfo?.messageKey || '') setMessageKeyInput(response.data?.ledgerInfo?.messageKey || '') + setCurrentRegularKey(response.data?.ledgerInfo?.regularKey || '') + setRegularKeyInput(response.data?.ledgerInfo?.regularKey || '') setCurrentTransferRate( typeof response.data?.ledgerInfo?.transferRate === 'number' ? multiply(response.data.ledgerInfo.transferRate, 1000000000) @@ -607,6 +611,68 @@ export default function AccountSettings({ }) } + const handleSetRegularKey = () => { + const value = regularKeyInput.trim() + if (!isAddressValid(value)) { + setErrorMessage('Please enter a valid RegularKey address.') + return + } + if (account?.address && value === account.address) { + setErrorMessage('RegularKey must not be the same as your account address (master key).') + return + } + + const tx = { + TransactionType: 'SetRegularKey', + Account: account.address, + RegularKey: value + } + + setSignRequest({ + request: tx, + callback: () => { + setSuccessMessage('RegularKey set successfully.') + setErrorMessage('') + setCurrentRegularKey(value) + setRegularKeyInput('') + setAccountData((prev) => { + if (prev && prev.ledgerInfo) { + return { + ...prev, + ledgerInfo: { ...prev.ledgerInfo, regularKey: value } + } + } + return prev + }) + } + }) + } + + const handleClearRegularKey = () => { + const tx = { + TransactionType: 'SetRegularKey', + Account: account.address + } + + setSignRequest({ + request: tx, + callback: () => { + setSuccessMessage('RegularKey cleared successfully.') + setErrorMessage('') + setCurrentRegularKey('') + setRegularKeyInput('') + setAccountData((prev) => { + if (prev && prev.ledgerInfo) { + const updatedLedgerInfo = { ...prev.ledgerInfo } + delete updatedLedgerInfo.regularKey + return { ...prev, ledgerInfo: updatedLedgerInfo } + } + return prev + }) + } + }) + } + const handleSetTransferRate = () => { const percent = Number(transferRateInput) if (isNaN(percent) || percent < 0 || percent > 100) { @@ -1127,6 +1193,49 @@ export default function AccountSettings({
+
+
+
+ RegularKey + {account?.address && ( + {currentRegularKey ? currentRegularKey : 'Not Set'} + )} +
+
+ {currentRegularKey && ( + + )} + +
+
+
+ + + Assign a regular key pair for signing. Must not equal your account address. + +
+ {errorMessage && {errorMessage}} +
+
diff --git a/pages/services/issue-currency.js b/pages/services/issue-currency.js index 562aaf0e2..ea0f18b02 100644 --- a/pages/services/issue-currency.js +++ b/pages/services/issue-currency.js @@ -6,6 +6,7 @@ import AddressInput from '../../components/UI/AddressInput' import NetworkTabs from '../../components/Tabs/NetworkTabs' import { ledgerName, nativeCurrency } from '../../utils' import { getIsSsrMobile } from '../../utils/mobile' +import { shortAddress } from '../../utils/format' export const getServerSideProps = async (context) => { const { locale } = context @@ -24,14 +25,13 @@ export default function IssueCurrency({ subscriptionExpired, openEmailLogin, ses const [hotWalletAddress, setHotWalletAddress] = useState('') const [currencyCode, setCurrencyCode] = useState('') const [totalSupply, setTotalSupply] = useState('') - const [transferRate, setTransferRate] = useState('0') const [isSettingColdAddress, setIsSettingColdAddress] = useState(false) const [coldAddressError, setColdAddressError] = useState('') const [coldAddressSuccess, setColdAddressSuccess] = useState('') // AccountSet transaction fields for cold wallet - const [coldTransferRate, setColdTransferRate] = useState('0') - const [tickSize, setTickSize] = useState('5') + const [coldTransferRate, setColdTransferRate] = useState(0) + const [tickSize, setTickSize] = useState(5) const [domain, setDomain] = useState('') const [disallowXRP, setDisallowXRP] = useState(false) const [requireDestTag, setRequireDestTag] = useState(false) @@ -53,6 +53,11 @@ export default function IssueCurrency({ subscriptionExpired, openEmailLogin, ses const [isIssuingTokens, setIsIssuingTokens] = useState(false) const [tokenError, setTokenError] = useState('') const [tokenSuccess, setTokenSuccess] = useState('') + + // TrustLine creation fields + const [isCreatingTrustLine, setIsCreatingTrustLine] = useState(false) + const [trustLineError, setTrustLineError] = useState('') + const [trustLineSuccess, setTrustLineSuccess] = useState('') // Flag constants const ASF_FLAGS = { @@ -82,6 +87,22 @@ export default function IssueCurrency({ subscriptionExpired, openEmailLogin, ses setIsSettingHotWallet(false) }, [hotWalletAddress, currencyCode, totalSupply]) + useEffect(() => { + if (!(coldTransferRate >= 0 && coldTransferRate <= 1)) { + setTimeout(() => { + setColdTransferRate(0) + }, 1000) + } + }, [coldTransferRate]) + + useEffect(() => { + if (!(tickSize >= 0 && tickSize <= 15)) { + setTimeout(() => { + setTickSize(5) + }, 1000) + } + }, [tickSize]) + const handleNextStep = () => { if (currentStep < 3) { setCurrentStep(currentStep + 1) @@ -216,14 +237,13 @@ export default function IssueCurrency({ subscriptionExpired, openEmailLogin, ses callback: () => { setColdAddressSuccess('Cold address AccountSet transaction submitted successfully!') setColdAddressError('') - setIsSettingColdAddress(false) }, errorCallback: (error) => { setColdAddressError(`Transaction failed: ${error.message || 'Unknown error'}`) setColdAddressSuccess('') - setIsSettingColdAddress(false) } }) + setIsSettingColdAddress(false) } // Handle hot wallet AccountSet configuration @@ -278,14 +298,13 @@ export default function IssueCurrency({ subscriptionExpired, openEmailLogin, ses callback: () => { setHotWalletSuccess('Hot wallet AccountSet transaction submitted successfully!') setHotWalletError('') - setIsSettingHotWallet(false) }, errorCallback: (error) => { setHotWalletError(`Transaction failed: ${error.message || 'Unknown error'}`) setHotWalletSuccess('') - setIsSettingHotWallet(false) } }) + setIsSettingHotWallet(false) } // Handle token issuance from cold to hot wallet @@ -340,6 +359,36 @@ export default function IssueCurrency({ subscriptionExpired, openEmailLogin, ses } } + const handleCreateTrustLine = () => { + setTrustLineError('') + setTrustLineSuccess('') + + const tx = { + TransactionType: 'TrustSet', + Account: hotWalletAddress, + LimitAmount: { + currency: currencyCode, + issuer: coldWalletAddress, + value: totalSupply + } + } + + setIsCreatingTrustLine(true) + setSignRequest({ + request: tx, + callback: () => { + setTrustLineSuccess('TrustLine created successfully!') + setTrustLineError('') + setIsCreatingTrustLine(false) + }, + errorCallback: (error) => { + setTrustLineError(`TrustLine creation failed: ${error.message || 'Unknown error'}`) + setTrustLineSuccess('') + setIsCreatingTrustLine(false) + } + }) + } + return ( <> -
+
{/* Progress Indicator */} -
+
= 1 ? 'active' : ''}`}>1
= 2 ? 'active' : ''}`}>2
@@ -416,15 +465,22 @@ export default function IssueCurrency({ subscriptionExpired, openEmailLogin, ses

Create and Configure Your Issuer Account (Cold Wallet):

-

This will be your cold wallet that issues the currency. {supplyType === 'closed' ? 'It will be blackholed after issuance for maximum security.' : 'You will maintain control over this account.'} Configure comprehensive AccountSet settings including transfer rates, tick size, domain, and security flags.

+

+ This will be your cold wallet that issues the currency. + {supplyType === 'closed' ? + 'It will be blackholed after issuance for maximum security.' : + 'You will maintain control over this account.' + } + Configure comprehensive AccountSet settings including transfer rates, tick size, domain, and security flags. +

- + Cold Wallet Address (Issuer Account)} + placeholder="r..." + setInnerValue={setColdWalletAddress} + rawData={coldWalletAddress} + hideButton={true} + /> {supplyType === 'closed' && (
@@ -442,30 +498,43 @@ export default function IssueCurrency({ subscriptionExpired, openEmailLogin, ses

Basic Settings

- + Transfer Rate (%)} placeholder="0" setInnerValue={setColdTransferRate} defaultValue={coldTransferRate} hideButton={true} - /> + /> + Transfer rate is the percentage of the total supply that will be transferred to the hot wallet. +
+ {!(coldTransferRate >= 0 && coldTransferRate <= 1) && window.innerWidth > 800 && + Transfer rate must be between 0 and 1 + } +
Tick Size} placeholder="5" setInnerValue={setTickSize} defaultValue={tickSize} hideButton={true} /> + Tick size is the number of significant digits for the currency. +
+ {!(tickSize >= 0 && tickSize <= 15) && window.innerWidth > 800 && + Tick size must be between 0 and 15 + } +
Domain (optional)} placeholder="example.com" setInnerValue={setDomain} defaultValue={domain} hideButton={true} /> + Domain is the domain name for the currency.
@@ -474,17 +543,16 @@ export default function IssueCurrency({ subscriptionExpired, openEmailLogin, ses
- +
+
+ Allow rippling on trust lines by default + ✓ Enabled
- Allow rippling on trust lines by default
Disallow {nativeCurrency} - +
+
+ Prevent incoming {nativeCurrency} payments + {disallowXRP ? '✓ Enabled' : '✗ Disabled'}
- Prevent incoming {nativeCurrency} payments
Require Destination Tag - +
+
+ Require destination tag for incoming payments + {requireDestTag ? '✓ Enabled' : '✗ Disabled'}
- Require destination tag for incoming payments
@@ -585,7 +657,7 @@ export default function IssueCurrency({ subscriptionExpired, openEmailLogin, ses

Hot Wallet Configuration:

The hot wallet is where you'll store the issued currency for trading, transfers, and other operations. This should be a separate account from your issuer account.

Hot Wallet Address} placeholder="r..." setInnerValue={setHotWalletAddress} rawData={hotWalletAddress} @@ -595,7 +667,7 @@ export default function IssueCurrency({ subscriptionExpired, openEmailLogin, ses

Currency Properties:

Currency Code} placeholder="e.g., USD, EUR, or custom hex" setInnerValue={setCurrencyCode} defaultValue={currencyCode} @@ -603,20 +675,12 @@ export default function IssueCurrency({ subscriptionExpired, openEmailLogin, ses />
Total Supply} placeholder="e.g., 1000000" setInnerValue={setTotalSupply} defaultValue={totalSupply} hideButton={true} /> -
-
@@ -627,7 +691,7 @@ export default function IssueCurrency({ subscriptionExpired, openEmailLogin, ses

Basic Settings

Domain (optional)} placeholder="example.com" setInnerValue={setHotDomain} defaultValue={hotDomain} @@ -657,11 +721,14 @@ export default function IssueCurrency({ subscriptionExpired, openEmailLogin, ses /> Require Authorization - + +
+ Prevents accidental trust line usage by requiring explicit authorization + {hotRequireAuth ? '✓ Enabled' : '✗ Disabled'} - + +
- Prevents accidental trust line usage by requiring explicit authorization
Disallow {nativeCurrency} - +
+ Prevent incoming {nativeCurrency} payments to hot wallet + {hotDisallowXRP ? '✓ Enabled' : '✗ Disabled'} - + +
- Prevent incoming {nativeCurrency} payments to hot wallet
Require Destination Tag - - {hotRequireDestTag ? '✓ Enabled' : '✗ Disabled'} - +
+ Require destination tag for incoming payments + + {hotRequireDestTag ? '✓ Enabled' : '✗ Disabled'} + +
- Require destination tag for incoming payments
@@ -738,11 +809,6 @@ export default function IssueCurrency({ subscriptionExpired, openEmailLogin, ses > {isSettingHotWallet ? 'Configuring Hot Wallet...' : 'Configure Hot Wallet'} - {(hotWalletAddress && account?.address) && ( -
- ✓ Ready to configure hot wallet -
- )}
@@ -773,15 +839,32 @@ export default function IssueCurrency({ subscriptionExpired, openEmailLogin, ses {/* Step 1: Create TrustLine */}

1. Create TrustLine

-

First, set up a trustline from your hot wallet to the issuer account to allow holding the tokens.

+

HotWallet must have Coldwallet address as RegularKey.

+

(Services > Account Settings > RegularKey)

TrustLine Setup Details:

  • Currency: {currencyCode}
  • -
  • Issuer: {coldWalletAddress}
  • -
  • Limit: {totalSupply} (or higher)
  • +
  • Issuer: {window.innerWidth > 800 ? coldWalletAddress : shortAddress(coldWalletAddress)}
  • +
  • Limit: {totalSupply}
+ + {trustLineError && ( +
+ {trustLineError} +
+ )} + {trustLineSuccess && ( +
+ {trustLineSuccess} +
+ )}
{/* Step 2: Issue Tokens from Cold to Hot */} @@ -792,7 +875,7 @@ export default function IssueCurrency({ subscriptionExpired, openEmailLogin, ses

Token Issuance Settings

Issue Quantity} placeholder="e.g., 1000" setInnerValue={setIssueQuantity} defaultValue={issueQuantity} @@ -801,7 +884,7 @@ export default function IssueCurrency({ subscriptionExpired, openEmailLogin, ses {hotRequireDestTag && ( Destination Tag} placeholder="1" setInnerValue={setDestinationTag} defaultValue={destinationTag} @@ -818,26 +901,20 @@ export default function IssueCurrency({ subscriptionExpired, openEmailLogin, ses > {isIssuingTokens ? 'Issuing Tokens...' : 'Issue Tokens to Hot Wallet'} - {(issueQuantity && account?.address) && ( -
- ✓ Ready to issue tokens -
- )}
-
- - {/* Error and Success Messages */} - {tokenError && ( -
- {tokenError} -
- )} + {/* Error and Success Messages */} + {tokenError && ( +
+ {tokenError} +
+ )} - {tokenSuccess && ( -
- {tokenSuccess} -
- )} + {tokenSuccess && ( +
+ {tokenSuccess} +
+ )} +