diff --git a/site/src/components/Icon/identity-special.svg b/site/src/components/Icon/identity-special.svg new file mode 100644 index 000000000..2de50c042 --- /dev/null +++ b/site/src/components/Icon/identity-special.svg @@ -0,0 +1,11 @@ + + + + + + + + + + + diff --git a/site/src/components/User/Username.jsx b/site/src/components/User/Username.jsx index a5f1b904f..5db4e50d3 100644 --- a/site/src/components/User/Username.jsx +++ b/site/src/components/User/Username.jsx @@ -5,6 +5,10 @@ import { Popup } from "semantic-ui-react"; import TextMinor from "../TextMinor"; import { useDisablePopup } from "../../utils/hooks"; import { truncate } from "../../styles/tailwindcss"; +import { KNOWN_ADDR_MATCHERS } from "../../utils/knownAddr"; +import IdentitySpecial from "../Icon/identity-special.svg"; +import Tooltip from "../Tooltip"; +import { p_12_medium } from "../../styles/text"; const TextUsername = styled(TextMinor)` white-space: nowrap; @@ -27,8 +31,20 @@ const TextUsername = styled(TextMinor)` `} `; +const SpecialAccountWrapper = styled.div` + display: flex; + align-items: center; + gap: 4px; +`; + +const Text = styled.span` + ${p_12_medium} + color: var(--textPrimaryContrast); +`; + const Username = ({ address, name, ellipsis, popup, popupContent, noLink }) => { const disabledPopup = useDisablePopup(); + let displayAddress; if (typeof address === "string") { if (ellipsis) { @@ -50,16 +66,27 @@ const Username = ({ address, name, ellipsis, popup, popupContent, noLink }) => { } } - const displayName = name ? name : displayAddress; + const knownAddr = KNOWN_ADDR_MATCHERS.map((matcher) => matcher(address)).find( + Boolean, + ); + + const displayName = name ? name : knownAddr ? knownAddr : displayAddress; return ( - {displayName}} - /> + + {knownAddr && ( + Special account}> + special + + )} + {displayName}} + /> + ); }; diff --git a/site/src/utils/knownAddr.js b/site/src/utils/knownAddr.js new file mode 100644 index 000000000..61ed6bf51 --- /dev/null +++ b/site/src/utils/knownAddr.js @@ -0,0 +1,60 @@ +import { TypeRegistry } from "@polkadot/types/create"; +import { + formatNumber, + isCodec, + stringToU8a, + u8aEmpty, + u8aEq, + u8aToBn, +} from "@polkadot/util"; + +const registry = new TypeRegistry(); + +function createAllMatcher(prefix, name) { + const test = registry.createType( + "AccountId", + stringToU8a(prefix.padEnd(32, "\0")), + ); + + return (addr) => (test.eq(addr) ? name : null); +} + +function createNumMatcher(prefix, name, add) { + const test = stringToU8a(prefix); + + // 4 bytes for u32 (more should not hurt, LE) + const minLength = test.length + 4; + + return (addr) => { + try { + const u8a = isCodec(addr) + ? addr.toU8a() + : registry.createType("AccountId", addr).toU8a(); + + return u8a.length >= minLength && + u8aEq(test, u8a.subarray(0, test.length)) && + u8aEmpty(u8a.subarray(minLength)) + ? `${name} ${formatNumber( + u8aToBn(u8a.subarray(test.length, minLength)), + )}${add ? ` (${add})` : ""}` + : null; + } catch (e) { + return null; + } + }; +} + +export const KNOWN_ADDR_MATCHERS = [ + createAllMatcher("modlpy/socie", "Society"), + createAllMatcher("modlpy/trsry", "Treasury"), + createAllMatcher("modlpy/xcmch", "XCM"), + createNumMatcher("modlpy/cfund", "Crowdloan"), + // Substrate master + createNumMatcher("modlpy/npols\x00", "Pool", "Stash"), + createNumMatcher("modlpy/npols\x01", "Pool", "Reward"), + // Westend + createNumMatcher("modlpy/nopls\x00", "Pool", "Stash"), + createNumMatcher("modlpy/nopls\x01", "Pool", "Reward"), + createNumMatcher("para", "Parachain"), + createNumMatcher("sibl", "Sibling"), +];