Skip to content
This repository was archived by the owner on Mar 17, 2026. It is now read-only.

Commit 7f18f38

Browse files
feat(BA-2133): ENSIP-19 updates for registration flow for EOAs (#2594)
* Update registration flow for SCWs for ensip 19 * first stab at EOA updates for registration flow * Add contract addresses * works with EOA for regular registration and set-primary * some fixes after rebase * review comments * second round review comments * remove unused import * fix linter error and warning for useRegisterNameCallback --------- Co-authored-by: Arjun Dureja <arjun.dureja@coinbase.com>
1 parent 8a89231 commit 7f18f38

2 files changed

Lines changed: 84 additions & 6 deletions

File tree

‎apps/web/src/hooks/useRegisterNameCallback.ts‎

Lines changed: 54 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -19,12 +19,15 @@ import {
1919
normalizeEnsDomainName,
2020
REGISTER_CONTRACT_ABI,
2121
REGISTER_CONTRACT_ADDRESSES,
22+
buildReverseRegistrarSignatureDigest,
2223
} from 'apps/web/src/utils/usernames';
2324
import { Dispatch, SetStateAction, useCallback, useMemo, useState } from 'react';
2425
import { encodeFunctionData, namehash } from 'viem';
2526
import { useAccount } from 'wagmi';
2627
import { secondsInYears } from 'apps/web/src/utils/secondsInYears';
2728
import L2ReverseRegistrarAbi from 'apps/web/src/abis/L2ReverseRegistrarAbi';
29+
import { type AbiFunction } from 'viem';
30+
import { useSignMessage } from 'wagmi';
2831

2932
type UseRegisterNameCallbackReturnType = {
3033
callback: () => Promise<void>;
@@ -50,6 +53,8 @@ export function useRegisterNameCallback(
5053
const { paymasterService: paymasterServiceEnabled } = useCapabilitiesSafe({
5154
chainId: basenameChain.id,
5255
});
56+
const { signMessageAsync, status: signMessageStatus } = useSignMessage();
57+
const signMessageIsLoading = signMessageStatus === 'pending';
5358

5459
// If user has a basename, reverse record is set to false
5560
const { data: baseEnsName, isLoading: baseEnsNameIsLoading } = useBaseEnsName({
@@ -62,6 +67,7 @@ export function useRegisterNameCallback(
6267
);
6368

6469
const [reverseRecord, setReverseRecord] = useState<boolean>(!hasExistingBasename);
70+
const [signatureError, setSignatureError] = useState<Error | null>(null);
6571

6672
// Transaction with paymaster enabled
6773
const { initiateBatchCalls, batchCallsStatus, batchCallsIsLoading, batchCallsError } =
@@ -85,9 +91,30 @@ export function useRegisterNameCallback(
8591
const normalizedName = normalizeEnsDomainName(name);
8692
const isDiscounted = Boolean(discountKey && validationData);
8793

94+
const signMessageForReverseRecord = useCallback(async () => {
95+
if (!address) throw new Error('No address');
96+
const reverseRegistrar = USERNAME_L2_REVERSE_REGISTRAR_ADDRESSES[basenameChain.id];
97+
98+
const functionAbi = L2ReverseRegistrarAbi.find(
99+
(f) => f.type === 'function' && f.name === 'setNameForAddrWithSignature',
100+
) as unknown as AbiFunction;
101+
const signatureExpiry = BigInt(Math.floor(Date.now() / 1000) + 5 * 60);
102+
const { digest, coinTypes } = buildReverseRegistrarSignatureDigest({
103+
reverseRegistrar,
104+
functionAbi,
105+
address,
106+
chainId: basenameChain.id,
107+
name,
108+
signatureExpiry,
109+
});
110+
const signature = await signMessageAsync({ message: { raw: digest } });
111+
return { coinTypes, signatureExpiry, signature } as const;
112+
}, [address, basenameChain.id, name, signMessageAsync]);
113+
88114
// Callback
89115
const registerName = useCallback(async () => {
90116
if (!address) return;
117+
setSignatureError(null);
91118

92119
const addressData = encodeFunctionData({
93120
abi: L2ResolverAbi,
@@ -114,16 +141,36 @@ export function useRegisterNameCallback(
114141
],
115142
});
116143

144+
let coinTypesForRequest: readonly bigint[] = [];
145+
let signatureExpiryForRequest = 0n;
146+
let signatureForRequest: `0x${string}` = '0x';
147+
148+
if (!paymasterServiceEnabled && reverseRecord) {
149+
try {
150+
const payload = await signMessageForReverseRecord();
151+
coinTypesForRequest = payload.coinTypes;
152+
signatureExpiryForRequest = payload.signatureExpiry;
153+
signatureForRequest = payload.signature;
154+
} catch (e) {
155+
logError(e, 'Reverse record signature step failed');
156+
const msg = e instanceof Error && e.message ? e.message : 'Unknown error';
157+
setSignatureError(new Error(`Could not prepare reverse record signature: ${msg}`));
158+
return;
159+
}
160+
}
161+
162+
const reverseRecordForRequest = paymasterServiceEnabled ? false : reverseRecord;
163+
117164
const registerRequest = {
118165
name: normalizedName, // The name being registered.
119166
owner: address, // The address of the owner for the name.
120167
duration: secondsInYears(years), // The duration of the registration in seconds.
121168
resolver: UPGRADEABLE_L2_RESOLVER_ADDRESSES[basenameChain.id], // The address of the resolver to set for this name.
122169
data: [addressData, baseCointypeData, nameData], // Multicallable data bytes for setting records in the associated resolver upon registration.
123-
reverseRecord, // Bool to decide whether to set this name as the "primary" name for the `owner`.
124-
coinTypes: [],
125-
signatureExpiry: 0,
126-
signature: '0x',
170+
reverseRecord: reverseRecordForRequest, // When using paymaster (atomic batch), set via separate call instead of signature flow.
171+
coinTypes: coinTypesForRequest,
172+
signatureExpiry: signatureExpiryForRequest,
173+
signature: signatureForRequest,
127174
};
128175

129176
try {
@@ -177,15 +224,16 @@ export function useRegisterNameCallback(
177224
normalizedName,
178225
paymasterServiceEnabled,
179226
reverseRecord,
227+
signMessageForReverseRecord,
180228
validationData,
181229
value,
182230
years,
183231
]);
184232

185233
return {
186234
callback: registerName,
187-
isPending: registerNameIsLoading || batchCallsIsLoading,
188-
error: registerNameError ?? batchCallsError,
235+
isPending: registerNameIsLoading || batchCallsIsLoading || signMessageIsLoading,
236+
error: signatureError ?? registerNameError ?? batchCallsError,
189237
reverseRecord,
190238
setReverseRecord,
191239
hasExistingBasename,

‎apps/web/src/utils/usernames.ts‎

Lines changed: 30 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -9,6 +9,8 @@ import {
99
createPublicClient,
1010
http,
1111
sha256,
12+
getFunctionSelector,
13+
type AbiFunction,
1214
} from 'viem';
1315
import { normalize } from 'viem/ens';
1416
import L2ResolverAbi from 'apps/web/src/abis/L2Resolver';
@@ -366,6 +368,34 @@ export const convertChainIdToCoinTypeUint = (chainId: number): number => {
366368
return (0x80000000 | chainId) >>> 0;
367369
};
368370

371+
export function buildReverseRegistrarSignatureDigest({
372+
reverseRegistrar,
373+
functionAbi,
374+
address,
375+
chainId,
376+
name,
377+
signatureExpiry,
378+
}: {
379+
reverseRegistrar: `0x${string}`;
380+
functionAbi: AbiFunction;
381+
address: `0x${string}`;
382+
chainId: number;
383+
name: string;
384+
signatureExpiry: bigint;
385+
}) {
386+
const coinTypes = [BigInt(convertChainIdToCoinTypeUint(chainId))] as const;
387+
const fullName = formatBaseEthDomain(name, chainId);
388+
const selector = getFunctionSelector(functionAbi);
389+
390+
const preimage = encodePacked(
391+
['address', 'bytes4', 'address', 'uint256', 'string', 'uint256[]'],
392+
[reverseRegistrar, selector, address, signatureExpiry, fullName, coinTypes],
393+
);
394+
const digest = keccak256(preimage);
395+
396+
return { digest, coinTypes, fullName } as const;
397+
}
398+
369399
export const convertReverseNodeToBytes = ({
370400
address,
371401
// eslint-disable-next-line @typescript-eslint/no-unused-vars

0 commit comments

Comments
 (0)