import React from 'react' import type { Address } from 'viem' import { Box, Text } from 'ink' import { Surface } from '../../../ui/Surface.js' import { Select } from '../../../ui/Select.js' import { theme } from '../../../ui/theme.js' import type { ProfileUpdates, Step } from '../reducer.js' import { displayCustodyMode, identityOwnerAddress, readCustodyMode, readIdentityStateString, } from './state.js' import { ensValidationReasonText, selectEnsStatus } from '../ens/state.js' import { shortAddress } from '../shared/model/format.js' import { FieldRow } from '../shared/components/FieldRow.js' import { lastBackupLabel } from '../profile/identity.js' import { type AgentReconciliation, } from '../shared/reconciliation/index.js' const footerHint = (hint: string) => {hint} type CustodyStep = Extract interface CustodyEditFlowProps { step: CustodyStep reconciliation?: AgentReconciliation vaultAddress?: Address onSetStep: (step: Step) => void onSwitchToAdvanced: (returnTo: Step, profileUpdates: ProfileUpdates) => void onSwitchToSimple: (returnTo: Step, profileUpdates: ProfileUpdates) => void onResumeAdvanced: (returnTo: Step) => void onManageOperatorWallets: () => void onPrepareTransfer: () => void onBack: () => void } export function isCustodyEditStep(step: Step): step is CustodyStep { return step.kind === 'custody-model' || step.kind === 'custody-advanced-confirm' || step.kind === 'custody-simple-confirm' } export const CustodyEditFlow: React.FC = ({ step, reconciliation, vaultAddress, onSetStep, onSwitchToAdvanced, onSwitchToSimple, onResumeAdvanced, onManageOperatorWallets, onPrepareTransfer, onBack, }) => { const identity = step.identity const registry = step.registry const returnTo = step.returnTo const state = (identity.state ?? {}) as Record const custodyMode = readCustodyMode(state) const ownerAddress = identityOwnerAddress(identity, reconciliation?.onChainOwner) const activeOperator = readIdentityStateString(state, 'activeOperatorAddress') const approvedOperatorCount = Array.isArray(state.approvedOperatorWallets) ? (state.approvedOperatorWallets as unknown[]).length : 0 const agentName = readIdentityStateString(state, 'name') const tokenLabel = identity.agentId ? `Token #${identity.agentId}` : 'Token #unknown' const tokenOwner = identity.ownerAddress ?? identity.address if (step.kind === 'custody-model') { type Action = 'switch-advanced' | 'switch-simple' | 'resume-advanced' | 'cancel-advanced' | 'manage-operator-wallets' | 'back' const onChainCustody = reconciliation?.custody const midFlow = onChainCustody === 'mid-flow-uri-pending' const isAdvanced = onChainCustody === 'advanced' || midFlow || custodyMode === 'advanced' const vaultHolds = onChainCustody === 'advanced' || midFlow const subtitle = midFlow ? 'Advanced setup pending.' : isAdvanced ? 'Advanced custody active.' : 'Simple custody active.' const modeLabel = midFlow ? 'Advanced (setup pending)' : displayCustodyMode(isAdvanced ? 'advanced' : 'simple') const options: Array<{ value: Action; role?: 'section' | 'utility'; label: string; hint?: string }> = [] if (midFlow) { options.push({ value: 'resume-advanced', label: 'Resume Advanced Setup', }) options.push({ value: 'cancel-advanced', label: 'Cancel Advanced Setup', }) } if (!isAdvanced) { options.push({ value: 'switch-advanced', label: 'Switch to Advanced', hint: 'Operators publish', }) } else { if (!midFlow) { options.push({ value: 'switch-simple', label: 'Switch to Simple', hint: 'Remove Vault', }) } options.push({ value: 'manage-operator-wallets', label: 'Manage Operators', hint: 'Add or revoke', }) } options.push({ value: 'back', label: 'Back', role: 'utility' }) const notice = step.kind === 'custody-model' ? step.notice : undefined return ( {notice ? ( {notice} ) : null} {(() => { const ensStatus = selectEnsStatus(identity) return ( {ensStatus.name} : ensStatus.kind === 'issue' ? {ensStatus.name} ({ensValidationReasonText(ensStatus.reason)}) : Not Linked} /> ) })()} {isAdvanced && vaultAddress ? : null} {isAdvanced ? ( 1 ? `${approvedOperatorCount} authorized` : activeOperator ? shortAddress(activeOperator) : 'None Authorized'} muted={!activeOperator && approvedOperatorCount === 0} /> ) : null} {(() => { const lastBackup = lastBackupLabel(identity) return })()} options={options} hintLayout="inline" onSubmit={choice => { if (choice === 'back') return onBack() if (choice === 'manage-operator-wallets') return onManageOperatorWallets() if (choice === 'resume-advanced') return onResumeAdvanced(returnTo ?? { kind: 'menu' }) if (choice === 'cancel-advanced') { onSetStep({ kind: 'custody-simple-confirm', identity, registry, returnTo }) return } if (choice === 'switch-advanced') { onSetStep({ kind: 'custody-advanced-confirm', identity, registry, returnTo }) return } if (choice === 'switch-simple') { onSetStep({ kind: 'custody-simple-confirm', identity, registry, returnTo }) return } }} onCancel={onBack} /> ) } if (step.kind === 'custody-advanced-confirm') { type Action = 'confirm' | 'transfer' | 'back' return ( {agentName ? : null} options={[ { value: 'confirm', label: 'Yes, Switch to Advanced' }, { value: 'transfer', label: 'Prepare Token Transfer' }, { value: 'back', label: 'Back', role: 'utility' }, ]} hintLayout="inline" onSubmit={choice => { if (choice === 'back') return onBack() if (choice === 'transfer') return onPrepareTransfer() const updates: ProfileUpdates = { custodyMode: 'advanced', ownerAddress: ownerAddress || tokenOwner, bumpRestoreAccessEpoch: true, custodyPhase: 'switch-advanced', } onSwitchToAdvanced(returnTo ?? { kind: 'menu' }, updates) }} onCancel={onBack} /> ) } type Action = 'confirm' | 'back' return ( {agentName ? : null} Operators lose access immediately. options={[ { value: 'confirm', label: 'Yes, Switch to Simple' }, { value: 'back', label: 'Back', role: 'utility' }, ]} hintLayout="inline" onSubmit={choice => { if (choice === 'back') return onBack() const updates: ProfileUpdates = { custodyMode: 'simple', bumpRestoreAccessEpoch: true, custodyPhase: 'switch-simple', approvedOperatorWallets: [], activeOperatorAddress: '', operatorVaultAddress: '', } onSwitchToSimple(returnTo ?? { kind: 'menu' }, updates) }} onCancel={onBack} /> ) } const Row: React.FC<{ label: string; value: string; muted?: boolean }> = ({ label, value, muted }) => ( )