Add user identities to components

Signed-off-by: Sebastian Martinez <sebastinez@me.com>
This commit is contained in:
Sebastian Martinez
2021-07-19 08:06:30 -03:00
parent 4b9205faa2
commit 19c4f2625f
18 changed files with 287 additions and 198 deletions
+8 -12
View File
@@ -2,12 +2,11 @@
import { onMount } from 'svelte';
import { link } from 'svelte-routing';
import { ethers } from 'ethers';
import { safeLink, explorerLink, identifyAddress, formatAddress, AddressType } from '@app/utils';
import { safeLink, explorerLink, identifyAddress, formatAddress, AddressType, parseEnsLabel } from '@app/utils';
import { Profile } from '@app/profile';
import Loading from '@app/Loading.svelte';
import Avatar from "@app/Avatar.svelte";
import type { Config } from '@app/config';
import type { Registration } from '@app/base/registrations/registrar';
import { getRegistration } from '@app/base/registrations/registrar';
export let address: string;
export let config: Config;
@@ -15,24 +14,21 @@
export let noBadge = false;
export let noAvatar = false;
export let compact = false;
// This property allows components eg. Header.svelte to pass a resolved profile object.
export let profile: Profile | null = null;
let checksumAddress = compact
? formatAddress(address)
: ethers.utils.getAddress(address);
let addressType: AddressType | null = null;
let addressName: string | null = null;
let info: Registration | null;
onMount(async () => {
identifyAddress(address, config).then((t: AddressType) => addressType = t);
if (resolve) {
addressName = await config.provider.lookupAddress(address);
if (addressName) {
info = await getRegistration(addressName, config);
}
if (resolve && !profile) {
Profile.get(address, config).then(p => profile = p);
}
});
$: addressLabel = addressName ?? checksumAddress;
$: addressLabel = profile?.name ? compact ? parseEnsLabel(profile.name, config) : profile.name : checksumAddress;
</script>
<style>
@@ -59,7 +55,7 @@
<div class="address" title={address} class:no-badge={noBadge}>
{#if !noAvatar}
<Avatar inline source={info?.avatar ?? address} />
<Avatar inline source={profile?.avatar ?? address} />
{/if}
{#if addressType === AddressType.Org}
<a use:link href={`/orgs/${address}`}>{addressLabel}</a>
+3 -3
View File
@@ -1,7 +1,7 @@
<script lang="ts">
import { onMount } from 'svelte';
import { createIcon } from '@app/blockies';
import { isAddress } from '@app/utils';
import { isAddress, isRadicleId } from '@app/utils';
export let source: string;
export let inline = false;
@@ -10,7 +10,7 @@
let container: HTMLElement;
onMount(() => {
if (isAddress(source)) {
if (isAddress(source) || isRadicleId(source)) {
const seed = source.toLowerCase();
const avatar = createIcon({
seed,
@@ -43,7 +43,7 @@
}
</style>
{#if isAddress(source)}
{#if isAddress(source) || isRadicleId(source)}
<div class="avatar" class:inline bind:this={container} class:glowOnHover title={source}/>
{:else}
<img class="avatar" class:inline src={source} class:glowOnHover alt="avatar"/>
+2 -1
View File
@@ -4,6 +4,7 @@
value: string | null;
label?: string;
placeholder?: string;
resolve: boolean;
editable: boolean;
}
</script>
@@ -105,7 +106,7 @@
<a class="link" href="{field.value}" target="_blank">{field.value}</a>
</span>
{:else if isAddress(field.value)}
<Address address={field.value} {config} />
<Address resolve={field.resolve} address={field.value} {config} />
{:else}
{field.value}
{/if}
+20 -10
View File
@@ -1,17 +1,18 @@
<script lang="ts">
// TODO: Shorten tx hash
import { link } from "svelte-routing";
import { formatBalance, formatAddress } from "@app/utils";
import { formatAddress, formatBalance } from "@app/utils";
import { error, Failure } from '@app/error';
import { disconnectWallet } from "@app/session";
import type { Session } from '@app/session';
import Loading from '@app/Loading.svelte';
import Logo from './Logo.svelte';
import Connect from './Connect.svelte';
import Logo from '@app/Logo.svelte';
import Connect from '@app/Connect.svelte';
import type { Config } from '@app/config';
import { Profile } from "@app/profile";
import Avatar from "./Avatar.svelte";
export let session: Session | null;
export let config: Config | null;
export let config: Config;
let sessionButton: HTMLElement | null = null;
let sessionButtonHover = false;
@@ -63,6 +64,11 @@
text-decoration: none;
}
.address {
display: flex;
flex-direction: row;
align-items: center;
justify-content: center;
min-height: 42px;
margin-left: 2rem;
width: 9.75rem;
}
@@ -148,11 +154,15 @@
on:mouseover={() => sessionButtonHover = true}
on:mouseout={() => sessionButtonHover = false}
>
{#if sessionButtonHover}
Disconnect
{:else}
{formatAddress(address)}
{/if}
{#await Profile.get(address, config)}
<Loading small center />
{:then profile}
{#if sessionButtonHover}
Disconnect
{:else}
<Avatar source={profile.avatar ?? address} inline />{formatAddress(address)}
{/if}
{/await}
</button>
{:else if config}
<span class="connect">
+1 -1
View File
@@ -130,7 +130,7 @@
<span><Address address={org.address} {config} /></span>
<span class="label">Owner</span>
<span><Address address={org.owner} {config} /></span>
<span><Address resolve address={org.owner} {config} /></span>
</div>
</span>
+2 -2
View File
@@ -65,7 +65,7 @@
<Loading center />
</div>
{:then orgs}
<List {orgs}>
<List {config} {orgs}>
<div class="orgs-empty">Orgs you are a member of show up here.</div>
</List>
{/await}
@@ -81,7 +81,7 @@
<Loading center />
</div>
{:then orgs}
<List {orgs}>
<List {config} {orgs}>
<div class="orgs-empty">There are no orgs.</div>
</List>
{/await}
+31 -9
View File
@@ -1,9 +1,15 @@
<script lang="ts">
import { Link } from 'svelte-routing';
import type { Org } from '@app/base/orgs/Org';
import Blockies from '@app/Blockies.svelte';
import Avatar from '@app/Avatar.svelte';
import { Profile } from '@app/profile';
import type { Config } from '@app/config';
import Loading from '@app/Loading.svelte';
export let config: Config;
export let orgs: Org[];
const orgsAddresses = orgs.map(org => org.address);
</script>
<style>
@@ -13,14 +19,30 @@
margin: 3rem;
display: inline-block;
}
.list {
display: flex;
flex-direction: row;
flex-wrap: wrap;
}
.loading {
padding: 3rem 0;
}
</style>
{#each orgs as org}
<div class="org">
<Link to={`/orgs/${org.address}`}>
<Blockies glowOnHover address={org.address} />
</Link>
{#await Profile.getMulti(orgsAddresses, config)}
<div class="loading">
<Loading center />
</div>
{:else}
<slot />
{/each}
{:then profiles}
<div class="list">
{#each profiles as profile}
<div class="org">
<Link to={`/orgs/${profile.address}`}>
<Avatar glowOnHover source={profile.avatar ?? profile.address} />
</Link>
</div>
{:else}
<slot />
{/each}
</div>
{/await}
-4
View File
@@ -60,10 +60,6 @@ export class Org {
this.owner = owner;
}
async lookupAddress(config: Config): Promise<string> {
return await config.provider.lookupAddress(this.address);
}
async setName(name: string, config: Config): Promise<TransactionResponse> {
assert(config.signer);
+3 -4
View File
@@ -2,10 +2,9 @@
import { onMount, createEventDispatcher } from 'svelte';
import Modal from '@app/Modal.svelte';
import type { Config } from '@app/config';
import { formatAddress } from '@app/utils';
import { formatAddress, isAddress } from "@app/utils";
import Loading from '@app/Loading.svelte';
import { assert } from '@app/error';
import * as utils from '@app/utils';
import type { Org } from './Org';
@@ -38,7 +37,7 @@
const onSubmit = async () => {
assert(newOwner);
if (! utils.isAddress(newOwner)) {
if (! isAddress(newOwner)) {
state = State.Failed;
error = `"${newOwner}" is not a valid Ethereum address.`;
return;
@@ -58,7 +57,7 @@
};
</script>
{#if state === State.Success}
{#if state === State.Success && newOwner}
<Modal floating small>
<div slot="title">
✅
+63 -83
View File
@@ -1,40 +1,29 @@
<script lang="ts">
import * as ethers from 'ethers';
import { onMount } from 'svelte';
import type { SvelteComponent } from 'svelte';
import Link from '@app/Link.svelte';
import type { Config } from '@app/config';
import type { Registration } from '@app/base/registrations/registrar';
import { getRegistration } from '@app/base/registrations/registrar';
import { parseEnsLabel, explorerLink } from '@app/utils';
import { session } from '@app/session';
import Loading from '@app/Loading.svelte';
import Modal from '@app/Modal.svelte';
import Error from '@app/Error.svelte';
import Icon from '@app/Icon.svelte';
import Blockies from '@app/Blockies.svelte';
import SetName from '@app/ens/SetName.svelte';
import Project from '@app/base/projects/Widget.svelte';
import Address from '@app/Address.svelte';
import Avatar from '@app/Avatar.svelte';
import Message from '@app/Message.svelte';
import * as utils from '@app/utils';
import { Org } from './Org';
import TransferOwnership from './TransferOwnership.svelte';
import { Org } from '@app/base/orgs/Org';
import TransferOwnership from '@app/base/orgs/TransferOwnership.svelte';
import { Profile } from '@app/profile';
export let address: string;
export let config: Config;
let registration: Registration | null = null;
let name: string | null = null;
const back = () => window.history.back();
onMount(async () => {
name = await config.provider.lookupAddress(address);
if (name) registration = await getRegistration(name, config);
});
let setNameForm: typeof SvelteComponent | null = null;
const setName = () => {
setNameForm = SetName;
@@ -44,8 +33,6 @@
const transferOwnership = () => {
transferOwnerForm = TransferOwnership;
};
$: label = name && parseEnsLabel(name, config);
$: isOwner = (org: Org): boolean => $session
? utils.isAddressEqual(org.owner, $session.address)
: false;
@@ -97,11 +84,6 @@
width: 64px;
height: 64px;
}
.avatar img {
width: 100%;
height: 100%;
border-radius: 50%;
}
.links {
display: flex;
align-items: center;
@@ -119,6 +101,7 @@
}
.members {
margin-top: 2rem;
align-items: center;
display: flex;
}
.members .member {
@@ -126,9 +109,6 @@
align-items: center;
margin-right: 1.5rem;
}
.members .member a {
border-bottom: none;
}
.members .member-icon {
width: 2rem;
height: 2rem;
@@ -141,33 +121,32 @@
{:then org}
{#if org}
<main>
{#await Profile.get(address, config)}
<Loading fadeIn />
{:then profile}
<header>
<div class="avatar">
{#if registration && registration.avatar}
<img src={registration.avatar} alt="avatar" />
{:else}
<Blockies address={org.address} />
{/if}
<Avatar source={profile.avatar ?? address} />
</div>
<div class="info">
<span class="title bold">
{registration ? label : ethers.utils.getAddress(address)}
{parseEnsLabel(profile.name, config) ?? address}
</span>
<div class="links">
{#if registration}
{#if registration.url}
<a class="url" href={registration.url}>{registration.url}</a>
{/if}
{#if registration.twitter}
<a class="url" href={registration.twitter}>
<Icon name="twitter" />
</a>
{/if}
{#if registration.github}
<a class="url" href={registration.github}>
<Icon name="github" />
</a>
{/if}
{#if profile.url}
<a class="url" href={profile.url}>
{profile.url}
</a>
{/if}
{#if profile.twitter}
<a class="url" href={profile.twitter}>
<Icon name="twitter" />
</a>
{/if}
{#if profile.github}
<a class="url" href={profile.github}>
<Icon name="github" />
</a>
{/if}
</div>
</div>
@@ -191,15 +170,11 @@
<!-- Name -->
<div class="label">Name</div>
<div>
{#await org.lookupAddress(config)}
<div class="loading"><Loading small /></div>
{:then name}
{#if name}
<Link to={`/registrations/${label}`}>{name}</Link>
{:else}
<span class="subtle">Not set</span>
{/if}
{/await}
{#if profile.name}
<Link to={`/registrations/${parseEnsLabel(profile.name, config)}`}>{profile.name}</Link>
{:else}
<span class="subtle">Not set</span>
{/if}
</div>
<div>
{#await isAuthorized(org)}
@@ -214,38 +189,43 @@
</div>
</div>
<div class="members">
{#await org.getMembers(config)}
<Loading center />
{:then members}
{#each members as address}
<div class="member">
<div class="member-icon">
<Blockies {address} />
</div>
<a href={explorerLink(address, config)} target="_blank" class="member">
{utils.formatAddress(address)}
</a>
{#await org.getMembers(config)}
<Loading center />
{:then members}
{#if members.length > 0}
<div class="members">
{#each members as address}
{#await Profile.get(address, config)}
<Loading small center />
{:then profile}
<div class="member">
<div class="member-icon">
<Avatar source={profile.avatar ?? address} />
</div>
<Address {address} compact resolve noAvatar {config} />
</div>
{/await}
{/each}
</div>
{/each}
{/if}
{/await}
</div>
<div class="projects">
{#await org.getProjects(config)}
<Loading center />
{:then projects}
{#each projects as project}
<div class="project">
<Project {project} org={org.address} {config} />
</div>
{/each}
{:catch err}
<Message error>
<strong>Error: </strong> failed to load projects: {err.message}.
</Message>
{/await}
</div>
<div class="projects">
{#await org.getProjects(config)}
<Loading center />
{:then projects}
{#each projects as project}
<div class="project">
<Project {project} org={org.address} {config} />
</div>
{/each}
{:catch err}
<Message error>
<strong>Error: </strong> failed to load projects: {err.message}.
</Message>
{/await}
</div>
{/await}
</main>
{:else}
<Modal subtle>
+2 -2
View File
@@ -4,7 +4,7 @@
import * as proj from '@app/project';
import Loading from '@app/Loading.svelte';
import Modal from '@app/Modal.svelte';
import Blockies from '@app/Blockies.svelte';
import Avatar from '@app/Avatar.svelte';
import Browser from './Browser.svelte';
@@ -77,7 +77,7 @@
<span class="maintainers">
{#each project.meta.maintainers as user}
<span class="maintainer">
<Blockies address={user} />
<Avatar source={user} />
</span>
{/each}
</span>
+7 -7
View File
@@ -45,19 +45,19 @@
if (r) {
fields = [
{ name: "owner", placeholder: "",
value: r.owner, editable: false },
value: r.owner, resolve: true, editable: false },
{ name: "address", placeholder: "Not set",
value: r.address, editable: true },
value: r.address, resolve: false, editable: true },
{ name: "seed", placeholder: "Not set",
value: r.seed, editable: true },
value: r.seed, resolve: false, editable: true },
{ name: "url", label: "URL", placeholder: "Not set",
value: r.url, editable: true },
value: r.url, resolve: false, editable: true },
{ name: "avatar", placeholder: "Not set",
value: r.avatar, editable: true },
value: r.avatar, resolve: false, editable: true },
{ name: "twitter", placeholder: "Not set",
value: r.twitter, editable: true },
value: r.twitter, resolve: false, editable: true },
{ name: "github", placeholder: "Not set",
value: r.github, editable: true },
value: r.github, resolve: false, editable: true },
];
state = { status: Status.Found, registration: r };
} else {
+13 -6
View File
@@ -15,21 +15,28 @@
onMount(async () => {
if (query) {
if (ethers.utils.isAddress(query)) {
// Go to org.
navigate(`/orgs/${query}`, { replace: true });
const addressType = query && await utils.identifyAddress(query, config);
if (addressType === utils.AddressType.Org) {
navigate(`/orgs/${query}`, { replace: true });
} else if (addressType === utils.AddressType.EOA) {
navigate(`/users/${query}`, { replace: true });
}
} else if (utils.isRadicleId(query)) {
// Go to Radicle entity.
alert("Radicle IDs are not yet supported");
} else {
let label = utils.parseEnsLabel(query, config);
if (label.includes(".")) {
if (label?.includes(".")) {
error = true;
} else {
// Jump straight to org, if the ENS entry points to an org. Otherwise just go to the
// registration.
// Jump straight to org, if the ENS entry points to an org. Otherwise it checks if the
// address type is an EOA and jumps to the user page else it just goes to the registration.
const address = await utils.resolveLabel(label, config);
if (address && await utils.identifyAddress(address, config) === utils.AddressType.Org) {
const addressType = address && await utils.identifyAddress(address, config);
if (addressType === utils.AddressType.Org) {
navigate(`/orgs/${address}`, { replace: true });
} else if (addressType === utils.AddressType.EOA) {
navigate(`/users/${address}`, { replace: true });
} else {
navigate(`/registrations/${label}`, { replace: true });
}
+19 -24
View File
@@ -1,22 +1,13 @@
<script lang="ts">
import { onMount } from 'svelte';
import type { Config } from '@app/config';
import type { Registration } from '@app/base/registrations/registrar';
import { getRegistration } from '@app/base/registrations/registrar';
import Icon from '@app/Icon.svelte';
import Address from '@app/Address.svelte';
import Avatar from '@app/Avatar.svelte';
import { Profile } from '@app/profile';
import Loading from '@app/Loading.svelte';
export let address: string;
export let config: Config;
let addressName: string | null = null;
let info: Registration | null;
onMount(async () => {
addressName = await config.provider.lookupAddress(address);
info = await getRegistration(addressName, config);
});
</script>
<style>
@@ -56,24 +47,27 @@
}
</style>
<main>
<header>
<div class="avatar">
<Avatar source={info?.avatar ?? address} />
</div>
<div class="info">
<span class="title bold"><Address noAvatar {address} {config} resolve/></span>
{#await Profile.get(address, config)}
<Loading fadeIn />
{:then profile}
<main>
<header>
<div class="avatar">
<Avatar source={profile.avatar ?? address} />
</div>
<div class="info">
<span class="title bold"><Address compact noAvatar {address} {config} resolve/></span>
<div class="links">
{#if info?.url}
<a class="url" href={info.url}>{info.url}</a>
{#if profile.url}
<a class="url" href={profile.url}>{profile.url}</a>
{/if}
{#if info?.twitter}
<a class="url" href={info.twitter}>
{#if profile.twitter}
<a class="url" href={profile.twitter}>
<Icon name="twitter" />
</a>
{/if}
{#if info?.github}
<a class="url" href={info.github}>
{#if profile.github}
<a class="url" href={profile.github}>
<Icon name="github" />
</a>
{/if}
@@ -81,3 +75,4 @@
</div>
</header>
</main>
{/await}
+3 -2
View File
@@ -1,12 +1,13 @@
<script lang="ts">
import { onMount } from 'svelte';
import { formatAddress } from '@app/utils';
import { State, state } from './state';
import { getInfo, withdrawVested } from './vesting';
import type { VestingInfo } from './vesting';
import type { Session } from '@app/session';
import type { Config } from '@app/config';
import Modal from '@app/Modal.svelte';
import Address from '@app/Address.svelte';
import { formatAddress } from '@app/utils';
let input: HTMLElement;
@@ -64,7 +65,7 @@
Tokens successfully withdrawn to {formatAddress(info.beneficiary)}.
{:else}
<table>
<tr><td class="label">Beneficiary</td><td>{info.beneficiary}</td></tr>
<tr><td class="label">Beneficiary</td><td><Address {config} address={info.beneficiary} compact resolve /></td></tr>
<tr><td class="label">Allocation</td><td>{info.totalVesting} <strong>{info.symbol}</strong></td></tr>
<tr><td class="label">Withdrawn</td><td>{info.withdrawn} <strong>{info.symbol}</strong></td></tr>
<tr><td class="label">Withdrawable</td><td>{info.withdrawableBalance} <strong>{info.symbol}</strong></td></tr>
+1
View File
@@ -67,6 +67,7 @@
},
"ceramic": { "api": "https://ceramic-clay.3boxlabs.com" },
"radicle": { "api": "" },
"ipfs": { "gateway": "https://ipfs.io/ipfs/" },
"abi": {
"registrar": [
"function rad() view returns (address)",
+87
View File
@@ -0,0 +1,87 @@
import type { Registration } from "@app/base/registrations/registrar";
import type { BasicProfile } from "@ceramicstudio/idx-constants";
import { formatCAIP10Address, formatIpfsFile, resolveEnsProfile, resolveIdxProfile } from "@app/utils";
import type { Config } from "./config";
export interface IProfile {
ens: Registration | null;
idx: BasicProfile | null;
}
export class Profile {
profile: IProfile;
address: string;
constructor(profile: IProfile, address: string) {
this.profile = profile;
this.address = address;
}
// Using undefined as return type if nothing to be returned since it works better with <a href> links
get github(): string | undefined {
if (this.profile?.ens?.github) return this.profile.ens.github;
else if (this.profile?.idx?.affiliations) return this.profile.idx?.affiliations.find(item => item === "github");
else return undefined;
}
// Using undefined as return type if nothing to be returned since it works better with <a href> links
get twitter(): string | undefined {
if (this.profile?.ens?.twitter) return this.profile.ens.twitter;
else if (this.profile?.idx?.affiliations) return this.profile.idx.affiliations.find(item => item === "twitter");
else return undefined;
}
// Using undefined as return type if nothing to be returned since it works better with <a href> links
get url(): string | undefined {
if (this.profile?.ens?.url) return this.profile.ens.url;
else if (this.profile?.idx?.url) return this.profile.idx.url;
else return undefined;
}
// Using undefined as return type if nothing to be returned since it works better with <a href> links
get name(): string | undefined {
if (this.profile?.ens?.name) return this.profile.ens.name;
else if (this.profile?.idx?.name) return this.profile.idx.name;
else return undefined;
}
// Using undefined as return type if nothing to be returned since it works better with <a href> links
get avatar(): string | undefined {
if (this.profile?.ens?.avatar) return this.profile.ens.avatar;
else if (this.profile?.idx?.image?.original?.src) return formatIpfsFile(this.profile.idx.image.original.src);
else return undefined;
}
// Keeping this function private since the desired entrypoint is .get()
private static async lookupAddress(address: string, config: Config): Promise<[IProfile, string]> {
const profile: IProfile = { ens: null, idx: null };
try {
const [ens, idx] = await Promise.allSettled([
resolveEnsProfile(address, config),
resolveIdxProfile(formatCAIP10Address(address, "eip155", config.network.chainId), config)
]);
if (ens.status == "fulfilled") profile.ens = ens.value;
if (idx.status == "fulfilled") profile.idx = idx.value;
} catch (error) {
console.error(error);
}
return [profile, address];
}
static async getMulti(addresses: string[], config: Config): Promise<Profile[]> {
const profilePromises = addresses.map(address => this.lookupAddress(address, config));
const profiles = await Promise.all(profilePromises);
return profiles.map(profile => { return new Profile(...profile); });
}
static async get(
address: string,
config: Config,
): Promise<Profile> {
const profile = await this.lookupAddress(address, config);
return new Profile(...profile);
}
}
+22 -28
View File
@@ -4,15 +4,12 @@ import multibase from 'multibase';
import multihashes from 'multihashes';
import EthersSafe from "@gnosis.pm/safe-core-sdk";
import type { Config } from '@app/config';
import config from "@app/config.json";
import { assert } from '@app/error';
import type { Registration } from "@app/base/registrations/registrar";
import { getRegistration } from '@app/base/registrations/registrar';
import type { BasicProfile } from "@ceramicstudio/idx-constants";
export interface Profile {
ens: Registration | null;
idx: BasicProfile | null;
}
export enum AddressType {
Contract,
@@ -50,6 +47,11 @@ export function formatAddress(addr: string): string {
return formatHash(ethers.utils.getAddress(addr));
}
export function formatIpfsFile(ipfs: string | undefined): string | undefined {
if (ipfs) return `${config.ipfs.gateway}${ipfs.replace("ipfs://", "")}`;
return undefined;
}
export function formatHash(hash: string): string {
return hash.substring(0, 6)
+ '...'
@@ -62,12 +64,14 @@ export function capitalize(s: string): string {
}
// Takes a domain name, eg. 'cloudhead.radicle.eth' and
// returns the label, eg. 'cloudhead', otherwise `null`.
export function parseEnsLabel(name: string, config: Config): string {
const domain = config.registrar.domain.replace(".", "\\.");
const label = name.replace(new RegExp(`\\.${domain}$`), "");
// returns the label, eg. 'cloudhead', otherwise `undefined`.
export function parseEnsLabel(name: string | undefined, config: Config): string | undefined {
if (name) {
const domain = config.registrar.domain.replace(".", "\\.");
const label = name.replace(new RegExp(`\\.${domain}$`), "");
return label;
return label;
}
}
// Return the current unix time.
@@ -90,6 +94,12 @@ export function isDid(input: string): boolean {
return /^did:[a-zA-Z0-9]+:[a-zA-Z0-9]+$/.test(input);
}
export function isENSName(input: string, config: Config): boolean {
const domain = config.registrar.domain.replace(".", "\\.");
const regEx = new RegExp(`^[a-zA-Z0-9]+.${domain}$`);
return regEx.test(input);
}
// Check whether the input is an Ethereum address.
export function isAddress(input: string): boolean {
return ethers.utils.isAddress(input);
@@ -195,27 +205,11 @@ export async function identifyAddress(address: string, config: Config): Promise<
}
// Resolve a label under the radicle domain.
export async function resolveLabel(label: string, config: Config): Promise<string | null> {
return config.provider.resolveName(`${label}.${config.registrar.domain}`);
export async function resolveLabel(label: string | undefined, config: Config): Promise<string | null> {
if (label) return config.provider.resolveName(`${label}.${config.registrar.domain}`);
return null;
}
export async function lookupAddress(address: string, config: Config): Promise<Profile> {
const profile: Profile = { ens: null, idx: null };
try {
const [ens, idx] = await Promise.allSettled([
resolveEnsProfile(address, config),
resolveIdxProfile(formatCAIP10Address(address, "eip155", config.network.chainId), config)
]);
if (ens.status == "fulfilled") profile.ens = ens.value;
if (idx.status == "fulfilled") profile.idx = idx.value;
} catch (error) {
console.error(error);
}
return profile;
}
// Resolves an IDX profile or return null
export async function resolveIdxProfile(caip10: string, config: Config): Promise<BasicProfile | null> {