-
Notifications
You must be signed in to change notification settings - Fork 0
Open
Labels
enhancementNew feature or requestNew feature or request
Description
Is your feature request related to a problem?
No response
Describe the solution to the problem
User Story: as a user, I want to search for someone by their web3 name and add them to a group chat.
Technical details: I need to give the backend an arbitrary query string and have it return in search results the metadata for that identity.
There can be various states here as seen below. If they aren't clear, please feel free to ping me.
mirror the code we previously had on the frontend:
const searchForValue = async () => {
setStatus(({ loading }) => ({
loading,
error: "",
inviteToConverse: "",
profileSearchResults: {},
}));
if (isSupportedPeer(value)) {
setStatus(({ error }) => ({
loading: true,
error,
inviteToConverse: "",
profileSearchResults: {},
}));
searchingForValue.current = value;
const resolvedAddress = await getAddressForPeer(value);
if (searchingForValue.current === value) {
// If we're still searching for this one
if (!resolvedAddress) {
const isLens = value.endsWith(config.lensSuffix);
const isFarcaster = value.endsWith(".fc");
setStatus({
loading: false,
profileSearchResults: {},
inviteToConverse: "",
error:
isLens || isFarcaster
? "This handle does not exist. Please try again."
: "No address has been set for this domain.",
});
return;
}
const address = getCleanAddress(resolvedAddress);
const addressIsOnXmtp = await accountCanMessagePeer({
account: getSafeCurrentSender().ethereumAddress,
peer: address,
});
if (searchingForValue.current === value) {
if (addressIsOnXmtp) {
// Let's search with the exact address!
const profiles = await searchProfiles({
searchQuery: address,
});
if (!isEmptyObject(profiles)) {
// Let's save the profiles for future use
setStatus({
loading: false,
error: "",
inviteToConverse: "",
profileSearchResults: profiles.reduce((acc, profile) => {
acc[profile.xmtpId] = profile;
return acc;
}, {} as { [address: string]: ISearchProfilesResult }),
});
} else {
setStatus({
loading: false,
error: "",
inviteToConverse: "",
profileSearchResults: {},
});
}
} else {
setStatus({
loading: false,
error: `${value} does not use Converse or XMTP yet`,
inviteToConverse: value,
profileSearchResults: {},
});
}
}
}
} else {
setStatus({
loading: true,
error: "",
inviteToConverse: "",
profileSearchResults: {},
});
const profiles = await searchProfiles({
searchQuery: value,
});
if (!isEmptyObject(profiles)) {
// Let's save the profiles for future use
setStatus({
loading: false,
error: "",
inviteToConverse: "",
profileSearchResults: profiles.reduce((acc, profile) => {
acc[profile.xmtpId] = profile;
return acc;
}, {} as { [address: string]: ISearchProfilesResult }),
});
} else {
setStatus({
loading: false,
error: "",
inviteToConverse: "",
profileSearchResults: {},
});
}
}
};import { captureError } from "@/utils/capture-error";
import { isUNSAddress } from "@utils/uns";
import axios from "axios";
import { isAddress } from "ethers/lib/utils";
import { config } from "../../config";
import { ethers } from "ethers";
import logger from "@/utils/logger";
export const isSupportedPeer = (peer: string) => {
// new backend is going to do all of this for us
return false;
const is0x = isAddress(peer.toLowerCase());
const isUserName = peer.endsWith(config.usernameSuffix);
const isENS = peer.endsWith(".eth");
const isLens = peer.endsWith(config.lensSuffix);
const isFarcaster = peer.endsWith(".fc");
const isCbId = peer.endsWith(".cb.id");
const isUNS = isUNSAddress(peer);
return (
isUserName ||
is0x ||
isLens ||
isENS ||
isENS ||
isFarcaster ||
isCbId ||
isUNS
);
};
export const getAddressForPeer = async (peer: string) => {
// new backend is going to do all of this for us
return undefined;
if (!isSupportedPeer(peer)) {
throw new Error(`Peer ${peer} is invalid`);
}
const isLens = peer.endsWith(config.lensSuffix);
const isUserName = peer.endsWith(config.usernameSuffix);
const isENS = peer.endsWith(".eth");
const isFarcaster = peer.endsWith(".fc");
const isCbId = peer.endsWith(".cb.id");
const isUNS = isUNSAddress(peer);
const isENSCompatible = isUserName || isCbId || isENS;
const resolvedAddress = isENSCompatible
? await resolveEnsName(peer)
: isUNS
? await resolveUnsDomain(peer)
: isFarcaster
? await resolveFarcasterUsername(peer.slice(0, peer.length - 3))
: isLens
? await getLensOwner(peer)
: peer;
return resolvedAddress || undefined;
};
export function getCleanEthAddress(address: string): string {
return address.toLowerCase();
}
async function getLensOwner(handle: string) {
try {
const { data } = await axios.post(`https://${config.lensApiDomain}/`, {
operationName: "Profile",
query:
"query Profile($handle: Handle) {\n profile(request: {handle: $handle}) {\n ownedBy\n }\n}\n",
variables: {
handle,
},
});
return data.data?.profile?.ownedBy || null;
} catch (e) {
captureError(e);
}
return null;
}
/**
* Resolves a Coinbase ID to an Ethereum address using ENS resolution
* @param cbId The Coinbase ID to resolve (e.g. "username.cb.id")
* @returns The resolved Ethereum address or null if resolution fails
*/
export async function resolveCoinbaseId(cbId: string): Promise<string | null> {
try {
if (!cbId.endsWith(".cb.id")) {
throw new Error("Invalid Coinbase ID format. Must end with .cb.id");
}
const provider = new ethers.providers.StaticJsonRpcProvider({
url: config.evm.rpcEndpoint,
skipFetchSetup: true,
});
const address = await provider.resolveName(cbId);
return address;
} catch (error) {
logger.error(`Failed to resolve Coinbase ID ${cbId}:`, error);
return null;
}
}Describe the uses cases for the feature
No response
Additional details
No response
Metadata
Metadata
Assignees
Labels
enhancementNew feature or requestNew feature or request