Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
Show all changes
22 commits
Select commit Hold shift + click to select a range
614d8ea
Update registration flow for SCWs for ensip 19
arjun-dureja Aug 15, 2025
ee8e736
first stab at EOA updates for registration flow
amiecorso Aug 22, 2025
bf7ae22
Add contract addresses
arjun-dureja Sep 9, 2025
3e8c3a6
works with EOA for regular registration and set-primary
amiecorso Sep 9, 2025
7d3ebd7
some fixes after rebase
amiecorso Sep 10, 2025
9b1d482
review comments
amiecorso Sep 10, 2025
9ddefb3
Merge branch 'master' into amiecorso/ba-2133-registration-flow-for-eo…
amiecorso Sep 10, 2025
a1ad726
second round review comments
amiecorso Sep 10, 2025
44138ef
initial cursor stab at including setReverseRecord stuff
amiecorso Sep 10, 2025
9824676
remove unused import
amiecorso Sep 10, 2025
3a4f68e
Merge branch 'amiecorso/ba-2133-registration-flow-for-eoas-in-web' in…
amiecorso Sep 10, 2025
b9af725
use utility for constructing signature digest
amiecorso Sep 10, 2025
28099c0
clean up
amiecorso Sep 10, 2025
629be42
Merge branch 'master' into amiecorso/ba-2555-set-primary-flow-for-eoa…
amiecorso Sep 10, 2025
369ba5e
fix rendering on success
amiecorso Sep 11, 2025
843af38
set error for potential downstream use if errors during setPrimary flow
amiecorso Sep 11, 2025
0c63511
remove unused refetchPrimaryUsername
amiecorso Sep 11, 2025
cebb1c7
fix build
amiecorso Sep 11, 2025
771e96d
add spinner to primary badge
amiecorso Sep 11, 2025
b49447a
simplify spinner
amiecorso Sep 12, 2025
5acb83e
remove context and use state for primary spinner
amiecorso Sep 12, 2025
d6b0b0c
simplify spinner logic
amiecorso Sep 12, 2025
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
11 changes: 8 additions & 3 deletions apps/web/src/components/Basenames/ManageNames/NameDisplay.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -59,13 +59,13 @@ export default function NameDisplay({
const expirationText = formatDistanceToNow(parseISO(expiresAt), { addSuffix: true });
const name = domain.split('.')[0];

const { setPrimaryUsername } = useUpdatePrimaryName(domain as Basename);
const { removeNameFromUI } = useRemoveNameFromUI();
const { setPrimaryUsername, isPending } = useUpdatePrimaryName(domain as Basename);

// Transfer state and callbacks
const [isTransferModalOpen, setIsTransferModalOpen] = useState<boolean>(false);
const openTransferModal = useCallback(() => setIsTransferModalOpen(true), []);
const closeTransferModal = useCallback(() => setIsTransferModalOpen(false), []);
const { removeNameFromUI } = useRemoveNameFromUI(domain as Basename);

// Renewal state and callbacks
const [isRenewalModalOpen, setIsRenewalModalOpen] = useState<boolean>(false);
Expand Down Expand Up @@ -102,7 +102,12 @@ export default function NameDisplay({
</Link>
<div className="flex items-center gap-2">
{isPrimary && (
<span className="rounded-full bg-white px-2 py-1 text-sm text-black">Primary</span>
<span className="flex items-center gap-2 rounded-full bg-white px-2 py-1 text-sm text-black">
{isPending ? (
<Icon name="spinner" color="currentColor" width="12px" height="12px" />
) : null}
<span>Primary</span>
</span>
)}
<Dropdown>
<DropdownToggle>
Expand Down
37 changes: 10 additions & 27 deletions apps/web/src/components/Basenames/ManageNames/hooks.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -43,6 +43,7 @@ export function useNameList() {
}
},
enabled: !!address,
placeholderData: (prev) => prev,
});

// Navigation functions
Expand Down Expand Up @@ -102,21 +103,16 @@ export function useNameList() {
};
}

export function useRemoveNameFromUI(domain: Basename) {
export function useRemoveNameFromUI() {
const { address } = useAccount();
const chainId = useChainId();

const network = chainId === 8453 ? 'base-mainnet' : 'base-sepolia';
const queryClient = useQueryClient();

const removeNameFromUI = useCallback(() => {
queryClient.setQueryData(
['usernames', address, network],
(prevData: ManagedAddressesResponse) => {
return { ...prevData, data: prevData.data.filter((name) => name.domain !== domain) };
},
);
}, [address, domain, network, queryClient]);
void queryClient.invalidateQueries({ queryKey: ['usernames', address, network] });
}, [address, network, queryClient]);

return { removeNameFromUI };
}
Expand All @@ -131,38 +127,25 @@ export function useUpdatePrimaryName(domain: Basename) {
const network = chainId === 8453 ? 'base-mainnet' : 'base-sepolia';

// Hook to update primary name
const { setPrimaryName, transactionIsSuccess } = useSetPrimaryBasename({
const { setPrimaryName, transactionIsSuccess, transactionPending } = useSetPrimaryBasename({
secondaryUsername: domain,
});

const setPrimaryUsername = useCallback(async () => {
try {
await setPrimaryName();
void queryClient.invalidateQueries({ queryKey: ['usernames', address, network] });
} catch (error) {
logError(error, 'Failed to update primary name');
throw error;
}
}, [logError, setPrimaryName]);
}, [address, network, logError, queryClient, setPrimaryName]);

useEffect(() => {
if (transactionIsSuccess) {
queryClient.setQueryData(
['usernames', address, network],
(prevData: ManagedAddressesResponse) => {
return {
...prevData,
data: prevData.data.map((name) =>
name.domain === domain
? { ...name, is_primary: true }
: name.is_primary
? { ...name, is_primary: false }
: name,
),
};
},
);
void queryClient.invalidateQueries({ queryKey: ['usernames', address, network] });
}
}, [transactionIsSuccess, address, domain, network, queryClient]);
}, [transactionIsSuccess, address, network, queryClient]);

return { setPrimaryUsername };
return { setPrimaryUsername, isPending: transactionPending, transactionIsSuccess };
}
67 changes: 56 additions & 11 deletions apps/web/src/hooks/useSetPrimaryBasename.ts
Original file line number Diff line number Diff line change
Expand Up @@ -5,16 +5,20 @@ import {
USERNAME_REVERSE_REGISTRAR_ADDRESSES,
} from 'apps/web/src/addresses/usernames';
import useBasenameChain from 'apps/web/src/hooks/useBasenameChain';
import { useCallback, useEffect } from 'react';
import { useCallback, useEffect, useState } from 'react';
import { Basename } from '@coinbase/onchainkit/identity';
import { useAccount } from 'wagmi';
import { useAccount, useSignMessage } from 'wagmi';
import useBaseEnsName from 'apps/web/src/hooks/useBaseEnsName';
import { useErrors } from 'apps/web/contexts/Errors';
import useWriteContractWithReceipt from 'apps/web/src/hooks/useWriteContractWithReceipt';
import { useUsernameProfile } from 'apps/web/src/components/Basenames/UsernameProfileContext';
import useWriteContractsWithLogs from 'apps/web/src/hooks/useWriteContractsWithLogs';
import useCapabilitiesSafe from 'apps/web/src/hooks/useCapabilitiesSafe';
import L2ReverseRegistrarAbi from 'apps/web/src/abis/L2ReverseRegistrarAbi';
import UpgradeableRegistrarControllerAbi from 'apps/web/src/abis/UpgradeableRegistrarControllerAbi';
import { UPGRADEABLE_REGISTRAR_CONTROLLER_ADDRESSES } from 'apps/web/src/addresses/usernames';
import { type AbiFunction } from 'viem';
import { buildReverseRegistrarSignatureDigest } from 'apps/web/src/utils/usernames';

/*
A hook to set a name as primary for resolution.
Expand All @@ -38,6 +42,9 @@ export default function useSetPrimaryBasename({ secondaryUsername }: UseSetPrima
const { paymasterService: paymasterServiceEnabled } = useCapabilitiesSafe({
chainId: secondaryUsernameChain.id,
});
const { signMessageAsync } = useSignMessage();

const [signatureError, setSignatureError] = useState<Error | null>(null);

// Get current primary username
// Note: This is sometimes undefined
Expand Down Expand Up @@ -65,6 +72,28 @@ export default function useSetPrimaryBasename({ secondaryUsername }: UseSetPrima
eventName: 'update_primary_name',
});

const signMessageForReverseRecord = useCallback(async () => {
if (!address) throw new Error('No address');

const reverseRegistrar = USERNAME_L2_REVERSE_REGISTRAR_ADDRESSES[secondaryUsernameChain.id];
const functionAbi = L2ReverseRegistrarAbi.find(
(f) => f.type === 'function' && f.name === 'setNameForAddrWithSignature',
) as unknown as AbiFunction;

const signatureExpiry = BigInt(Math.floor(Date.now() / 1000) + 5 * 60);
const nameLabel = (secondaryUsername as string).split('.')[0];
const { digest, coinTypes } = buildReverseRegistrarSignatureDigest({
reverseRegistrar,
functionAbi,
address,
chainId: secondaryUsernameChain.id,
name: nameLabel,
signatureExpiry,
});
const signature = await signMessageAsync({ message: { raw: digest } });
return { coinTypes, signatureExpiry, signature } as const;
}, [address, secondaryUsername, secondaryUsernameChain.id, signMessageAsync]);

useEffect(() => {
if (transactionIsSuccess) {
refetchPrimaryUsername()
Expand All @@ -82,16 +111,28 @@ export default function useSetPrimaryBasename({ secondaryUsername }: UseSetPrima

try {
if (!paymasterServiceEnabled) {
let payload: {
coinTypes: readonly bigint[];
signatureExpiry: bigint;
signature: `0x${string}`;
};
try {
setSignatureError(null);
payload = await signMessageForReverseRecord();
} catch (e) {
logError(e, 'Reverse record signature step failed');
const msg = e instanceof Error && e.message ? e.message : 'Unknown error';
setSignatureError(new Error(`Could not prepare reverse record signature: ${msg}`));
return undefined;
}

const nameLabel = (secondaryUsername as string).split('.')[0];

await initiateTransaction({
abi: ReverseRegistrarAbi,
address: USERNAME_REVERSE_REGISTRAR_ADDRESSES[secondaryUsernameChain.id],
args: [
address,
address,
USERNAME_L2_RESOLVER_ADDRESSES[secondaryUsernameChain.id],
secondaryUsername,
],
functionName: 'setNameForAddr',
abi: UpgradeableRegistrarControllerAbi,
address: UPGRADEABLE_REGISTRAR_CONTROLLER_ADDRESSES[secondaryUsernameChain.id],
args: [nameLabel, payload.signatureExpiry, payload.coinTypes, payload.signature],
functionName: 'setReverseRecord',
});
} else {
await initiateBatchCalls({
Expand Down Expand Up @@ -130,7 +171,9 @@ export default function useSetPrimaryBasename({ secondaryUsername }: UseSetPrima
address,
paymasterServiceEnabled,
initiateTransaction,
secondaryUsernameChain,
secondaryUsernameChain.id,
signMessageForReverseRecord,
initiateBatchCalls,
logError,
]);
Expand All @@ -146,5 +189,7 @@ export default function useSetPrimaryBasename({ secondaryUsername }: UseSetPrima
canSetUsernameAsPrimary,
isLoading,
transactionIsSuccess: transactionIsSuccess || batchCallsIsSuccess,
transactionPending: transactionIsLoading || batchCallsIsLoading,
Copy link
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Is this used anywhere?

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

yeah, it's used to set isPending which is returned from useUpdatePrimaryName and then used in the name display to manage the badge

error: signatureError,
};
}