diff --git a/src/components/settings/ConfigurationTab.tsx b/src/components/settings/ConfigurationTab.tsx index 8db1d8d..ee6cc12 100644 --- a/src/components/settings/ConfigurationTab.tsx +++ b/src/components/settings/ConfigurationTab.tsx @@ -233,15 +233,70 @@ export function ConfigurationTab() { )} - {/* Username */} - {(config.translator?.user_identity || config.jdc?.user_identity) && ( -
-

{isSoloMode ? 'Bitcoin Address' : 'Pool Username'}

-

- {config.translator?.user_identity || config.jdc?.user_identity} -

-
- )} + {/* Username / Identity */} + {(config.translator?.user_identity || config.jdc?.user_identity) && (() => { + const identity = config.translator?.user_identity || config.jdc?.user_identity || ''; + + if (isSoloMode && (identity.startsWith('sri/solo/') || identity.startsWith('sri/donate'))) { + let addr = ''; + let worker = ''; + let donation = ''; + + if (identity.startsWith('sri/solo/')) { + const rest = identity.slice('sri/solo/'.length); + const idx = rest.indexOf('/'); + addr = idx === -1 ? rest : rest.slice(0, idx); + worker = idx === -1 ? '' : rest.slice(idx + 1); + donation = '0%'; + } else if (identity === 'sri/donate') { + donation = '100%'; + } else if (identity.startsWith('sri/donate/')) { + const rest = identity.slice('sri/donate/'.length); + const parts = rest.split('/'); + const pct = parseInt(parts[0], 10); + if (!isNaN(pct) && String(pct) === parts[0] && parts.length >= 2) { + donation = `${pct}%`; + addr = parts[1]; + worker = parts.slice(2).join('/'); + } else { + donation = '100%'; + worker = rest; + } + } + + return ( +
+ {addr && ( +
+

Payout Address

+

{addr}

+
+ )} + {worker && ( +
+

Worker Name

+

{worker}

+
+ )} +
+

Donation

+

{donation}

+
+
+

User Identity

+

{identity}

+
+
+ ); + } + + return ( +
+

{isSoloMode ? 'Bitcoin Address' : 'Pool Username'}

+

{identity}

+
+ ); + })()} {/* Bitcoin Core (JD mode) */} {isJdMode && config.bitcoin && ( diff --git a/src/components/setup/steps/MiningIdentityStep.tsx b/src/components/setup/steps/MiningIdentityStep.tsx index 620fc79..5f27d43 100644 --- a/src/components/setup/steps/MiningIdentityStep.tsx +++ b/src/components/setup/steps/MiningIdentityStep.tsx @@ -3,66 +3,223 @@ import { StepProps } from '../types'; import { Info } from 'lucide-react'; import { isValidBitcoinAddress, getBitcoinAddressError } from '@/lib/utils'; +const SRI_POOL_AUTHORITY_KEY = '9auqWEzQDVyd2oe1JVGFLMLHZtCo2FFqZwtKA5gd9xbuEu7PH72'; + +interface SriIdentityParts { + address: string; + workerName: string; + donationPercent: number; +} + +function parseSriIdentity(identity: string): SriIdentityParts { + if (identity.startsWith('sri/solo/')) { + const rest = identity.slice('sri/solo/'.length); + const idx = rest.indexOf('/'); + if (idx === -1) return { address: rest, workerName: '', donationPercent: 0 }; + return { address: rest.slice(0, idx), workerName: rest.slice(idx + 1), donationPercent: 0 }; + } + + if (identity === 'sri/donate') { + return { address: '', workerName: '', donationPercent: 100 }; + } + + if (identity.startsWith('sri/donate/')) { + const rest = identity.slice('sri/donate/'.length); + const parts = rest.split('/'); + const pct = parseInt(parts[0], 10); + if (!isNaN(pct) && String(pct) === parts[0] && parts.length >= 2) { + return { address: parts[1], workerName: parts.slice(2).join('/'), donationPercent: pct }; + } + return { address: '', workerName: rest, donationPercent: 100 }; + } + + return { address: identity, workerName: '', donationPercent: 0 }; +} + +function buildSriIdentity(address: string, workerName: string, donationPercent: number): string { + const addr = address.trim(); + const worker = workerName.trim(); + + if (donationPercent >= 100) { + return worker ? `sri/donate/${worker}` : 'sri/donate'; + } + + if (donationPercent > 0 && donationPercent < 100) { + if (!addr) return ''; + return worker + ? `sri/donate/${donationPercent}/${addr}/${worker}` + : `sri/donate/${donationPercent}/${addr}`; + } + + if (!addr) return ''; + return worker ? `sri/solo/${addr}/${worker}` : `sri/solo/${addr}`; +} + export function MiningIdentityStep({ data, updateData, onNext }: StepProps) { const isSoloMode = data.miningMode === 'solo'; const isJdMode = data.mode === 'jd'; + const isSriPool = data.pool?.authority_public_key === SRI_POOL_AUTHORITY_KEY; + const useSriConventions = isSoloMode && isSriPool; - const [userIdentity, setUserIdentity] = useState( - data.translator?.user_identity || data.jdc?.user_identity || '' - ); + const existingIdentity = data.translator?.user_identity || data.jdc?.user_identity || ''; + const parsed = useSriConventions ? parseSriIdentity(existingIdentity) : { address: '', workerName: '', donationPercent: 0 }; + + const [payoutAddress, setPayoutAddress] = useState(useSriConventions ? parsed.address : ''); + const [workerName, setWorkerName] = useState(useSriConventions ? parsed.workerName : ''); + const [donationPercent, setDonationPercent] = useState(useSriConventions ? parsed.donationPercent : 0); + + const [userIdentity, setUserIdentity] = useState(!useSriConventions ? existingIdentity : ''); const [coinbaseAddress, setCoinbaseAddress] = useState(data.jdc?.coinbase_reward_address || ''); + const finalIdentity = useSriConventions + ? buildSriIdentity(payoutAddress, workerName, donationPercent) + : userIdentity; + useEffect(() => { updateData({ jdc: isJdMode - ? { user_identity: userIdentity, coinbase_reward_address: coinbaseAddress, jdc_signature: data.jdc?.jdc_signature || '' } + ? { user_identity: finalIdentity, coinbase_reward_address: coinbaseAddress, jdc_signature: data.jdc?.jdc_signature || '' } : null, translator: data.translator - ? { ...data.translator, user_identity: userIdentity, enable_vardiff: true } - : { user_identity: userIdentity, enable_vardiff: true, aggregate_channels: false, min_hashrate: 0 }, + ? { ...data.translator, user_identity: finalIdentity, enable_vardiff: true } + : { user_identity: finalIdentity, enable_vardiff: true, aggregate_channels: false, min_hashrate: 0 }, }); - }, [userIdentity, coinbaseAddress, isJdMode, data.jdc?.jdc_signature, data.translator, updateData]); + }, [finalIdentity, coinbaseAddress, isJdMode, data.jdc?.jdc_signature, data.translator, updateData]); const network = data.bitcoin?.network ?? 'mainnet'; + const needsAddress = donationPercent < 100; - const isValid = - userIdentity.length > 0 && - (!isSoloMode || isValidBitcoinAddress(userIdentity, network)) && - (!isJdMode || isValidBitcoinAddress(coinbaseAddress, network)); + const isValid = useSriConventions + ? (!needsAddress || (payoutAddress.trim().length > 0 && isValidBitcoinAddress(payoutAddress.trim(), network))) + : (userIdentity.length > 0 && + (!isSoloMode || isValidBitcoinAddress(userIdentity, network)) && + (!isJdMode || isValidBitcoinAddress(coinbaseAddress, network))); return (

Mining Identity

- {isSoloMode ? 'Configure your payout address' : 'Configure your pool credentials'} + {useSriConventions + ? 'Configure your solo mining payout' + : isSoloMode + ? 'Configure your mining identity' + : 'Configure your pool credentials'}

-
- - setUserIdentity(e.target.value)} - placeholder={isSoloMode ? 'bc1q...' : 'username.worker1'} - aria-required="true" - autoComplete="off" - className="w-full h-10 px-3 rounded-lg border border-input bg-background focus-visible:border-primary focus-visible:ring-2 focus-visible:ring-primary/15 outline-none transition-all font-mono text-sm" - /> - {isSoloMode && getBitcoinAddressError(userIdentity, network) && ( -

{getBitcoinAddressError(userIdentity, network)}

- )} -

- {isSoloMode - ? 'Your Bitcoin address where you want to receive mining rewards' - : 'Your pool account username (e.g., username.workername)'} -

-
+ {useSriConventions ? ( + <> + {needsAddress && ( +
+ + setPayoutAddress(e.target.value)} + placeholder="bc1q..." + aria-required="true" + autoComplete="off" + className="w-full h-10 px-3 rounded-lg border border-input bg-background focus-visible:border-primary focus-visible:ring-2 focus-visible:ring-primary/15 outline-none transition-all font-mono text-sm" + /> + {getBitcoinAddressError(payoutAddress, network) && ( +

{getBitcoinAddressError(payoutAddress, network)}

+ )} +

+ Your Bitcoin address where you want to receive mining rewards +

+
+ )} + +
+ + setWorkerName(e.target.value)} + placeholder="worker1" + autoComplete="off" + className="w-full h-10 px-3 rounded-lg border border-input bg-background focus-visible:border-primary focus-visible:ring-2 focus-visible:ring-primary/15 outline-none transition-all font-mono text-sm" + /> +

+ A name to identify this mining device +

+
+ +
+ +
+ setDonationPercent(Number(e.target.value))} + aria-label={`Donation: ${donationPercent}%`} + aria-valuemin={0} + aria-valuemax={100} + aria-valuenow={donationPercent} + className="w-full accent-primary" + /> +
+ 0%25%50%75%100% +
+
+

+ {donationPercent === 0 + ? 'Full block reward goes to your payout address' + : donationPercent >= 100 + ? 'Full block reward is donated to SRI development' + : `${donationPercent}% of the block reward goes to SRI development, ${100 - donationPercent}% to your address`} +

+
+ + {finalIdentity && ( +
+
+ )} + + ) : ( +
+ + setUserIdentity(e.target.value)} + placeholder={isSoloMode ? 'bc1q...' : 'username.worker1'} + aria-required="true" + autoComplete="off" + className="w-full h-10 px-3 rounded-lg border border-input bg-background focus-visible:border-primary focus-visible:ring-2 focus-visible:ring-primary/15 outline-none transition-all font-mono text-sm" + /> + {isSoloMode && getBitcoinAddressError(userIdentity, network) && ( +

{getBitcoinAddressError(userIdentity, network)}

+ )} +

+ {isSoloMode + ? 'Your Bitcoin address where you want to receive mining rewards' + : 'Your pool account username (e.g., username.workername)'} +

+
+ )} {isJdMode && (
diff --git a/src/components/setup/steps/ReviewStart.tsx b/src/components/setup/steps/ReviewStart.tsx index 75e097d..836ffcf 100644 --- a/src/components/setup/steps/ReviewStart.tsx +++ b/src/components/setup/steps/ReviewStart.tsx @@ -149,7 +149,49 @@ export function ReviewStart({ data, onComplete }: ReviewStartProps) {
-
{data.translator?.user_identity ?? data.jdc?.user_identity ?? '—'}
+ {(() => { + const identity = data.translator?.user_identity ?? data.jdc?.user_identity ?? ''; + if (!identity) return
; + + if (isSoloMode && (identity.startsWith('sri/solo/') || identity.startsWith('sri/donate'))) { + let addr = ''; + let worker = ''; + let donation = ''; + + if (identity.startsWith('sri/solo/')) { + const rest = identity.slice('sri/solo/'.length); + const idx = rest.indexOf('/'); + addr = idx === -1 ? rest : rest.slice(0, idx); + worker = idx === -1 ? '' : rest.slice(idx + 1); + donation = '0%'; + } else if (identity === 'sri/donate') { + donation = '100%'; + } else if (identity.startsWith('sri/donate/')) { + const rest = identity.slice('sri/donate/'.length); + const parts = rest.split('/'); + const pct = parseInt(parts[0], 10); + if (!isNaN(pct) && String(pct) === parts[0] && parts.length >= 2) { + donation = `${pct}%`; + addr = parts[1]; + worker = parts.slice(2).join('/'); + } else { + donation = '100%'; + worker = rest; + } + } + + return ( + <> + {addr &&
Payout Address: {addr}
} + {worker &&
Worker: {worker}
} +
Donation: {donation}
+
{identity}
+ + ); + } + + return
{identity}
; + })()} {isJdMode && data.jdc?.coinbase_reward_address && (
{data.jdc.coinbase_reward_address}
)}