diff --git a/api/src/models/api/nova/IAddressDetails.ts b/api/src/models/api/nova/IAddressDetails.ts index 8aaca791d..8ea1dbd37 100644 --- a/api/src/models/api/nova/IAddressDetails.ts +++ b/api/src/models/api/nova/IAddressDetails.ts @@ -7,4 +7,11 @@ export interface IAddressDetails { hex?: string; type?: AddressType; label?: string; + restricted?: IRestrictedAddressDetails; +} + +export interface IRestrictedAddressDetails { + bech32: string; + innerAddressType?: AddressType; + capabilities?: number[]; } diff --git a/api/src/utils/nova/addressHelper.ts b/api/src/utils/nova/addressHelper.ts index 2290852c2..8566d8720 100644 --- a/api/src/utils/nova/addressHelper.ts +++ b/api/src/utils/nova/addressHelper.ts @@ -10,10 +10,11 @@ import { AnchorAddress, Utils, ImplicitAccountCreationAddress, + RestrictedAddress, } from "@iota/sdk-nova"; import { plainToInstance } from "class-transformer"; import logger from "../../logger"; -import { IAddressDetails } from "../../models/api/nova/IAddressDetails"; +import { IAddressDetails, IRestrictedAddressDetails } from "../../models/api/nova/IAddressDetails"; import { HexHelper } from "../hexHelper"; export class AddressHelper { @@ -32,6 +33,7 @@ export class AddressHelper { let bech32: string; let hex: string; let type: AddressType; + let restricted: IRestrictedAddressDetails; if (Utils.isAddressValid(addressString)) { try { @@ -43,6 +45,7 @@ export class AddressHelper { bech32 = addressDetails.bech32; type = addressDetails.type; hex = addressDetails.hex; + restricted = addressDetails.restricted; } } catch (e) { logger.debug(`Failed parsing Address. Cause: ${e}`); @@ -63,12 +66,14 @@ export class AddressHelper { hex: hex ? HexHelper.addPrefix(hex) : hex, type: type ?? typeHint, label: AddressHelper.typeLabel(type), + restricted, }; } private static buildAddressFromTypes(address: Address, hrp: string): IAddressDetails { let hex: string = ""; let bech32: string = ""; + let restricted: IRestrictedAddressDetails | undefined; if (address.type === AddressType.Ed25519) { hex = (address as Ed25519Address).pubKeyHash; @@ -82,15 +87,30 @@ export class AddressHelper { const implicitAccountCreationAddress = plainToInstance(ImplicitAccountCreationAddress, address); const innerAddress = implicitAccountCreationAddress.address(); hex = innerAddress.pubKeyHash; + } else if (address.type === AddressType.Restricted) { + const restrictedAddress = RestrictedAddress.parse(address) as RestrictedAddress; + const capabilities = Array.from(restrictedAddress.getAllowedCapabilities()); + const innerAddress = Address.parse(restrictedAddress.address); + const innerAddressDetails = this.buildAddressFromTypes(innerAddress, hrp); + + restricted = { + bech32: innerAddressDetails.bech32, + innerAddressType: innerAddress.type, + capabilities, + }; } bech32 = Utils.addressToBech32(address, hrp); + const label = restricted + ? `Restricted ${AddressHelper.typeLabel(restricted.innerAddressType)}` + : AddressHelper.typeLabel(address.type); return { bech32, hex, type: address.type, - label: AddressHelper.typeLabel(address.type), + label, + restricted, }; } diff --git a/client/src/app/components/nova/address/AddressView.tsx b/client/src/app/components/nova/address/AddressView.tsx index 70823f26f..28c237f84 100644 --- a/client/src/app/components/nova/address/AddressView.tsx +++ b/client/src/app/components/nova/address/AddressView.tsx @@ -1,8 +1,9 @@ import React from "react"; -import { Address, AddressType } from "@iota/sdk-wasm-nova/web"; +import { Address } from "@iota/sdk-wasm-nova/web"; import { useNetworkInfoNova } from "~/helpers/nova/networkInfo"; import { AddressHelper } from "~/helpers/nova/addressHelper"; import TruncatedId from "../../stardust/TruncatedId"; +import { NameHelper } from "~/helpers/nova/nameHelper"; interface AddressViewProps { address: Address; @@ -15,29 +16,12 @@ const AddressView: React.FC = ({ address }) => { return (
-
{getAddressTypeName(address.type)}
+
{NameHelper.getAddressTypeName(address.type)}
- +
); }; -function getAddressTypeName(type: AddressType): string { - switch (type) { - case AddressType.Ed25519: - return "Ed25519"; - case AddressType.Account: - return "Account"; - case AddressType.Nft: - return "Nft"; - case AddressType.Anchor: - return "Anchor"; - case AddressType.ImplicitAccountCreation: - return "ImplicitAccountCreation"; - default: - return "Unknown"; - } -} - export default AddressView; diff --git a/client/src/app/components/nova/address/Bech32Address.tsx b/client/src/app/components/nova/address/Bech32Address.tsx index c5a78aca7..58db58a07 100644 --- a/client/src/app/components/nova/address/Bech32Address.tsx +++ b/client/src/app/components/nova/address/Bech32Address.tsx @@ -2,6 +2,7 @@ import React from "react"; import * as H from "history"; import { IAddressDetails } from "~/models/api/nova/IAddressDetails"; import TruncatedId from "../../stardust/TruncatedId"; +import { CAPABILITY_FLAG_TO_DESCRIPTION } from "~/app/lib/constants/allowed.capabilities"; interface Bech32AddressProps { network?: string; @@ -12,6 +13,9 @@ interface Bech32AddressProps { } const Bech32Address: React.FC = ({ network, history, addressDetails, advancedMode, hideLabel }) => { + const isRestricted = !!addressDetails?.restricted; + const capabilites = addressDetails?.restricted?.capabilities ?? []; + return (
{addressDetails?.bech32 && ( @@ -48,6 +52,18 @@ const Bech32Address: React.FC = ({ network, history, address
)} + {isRestricted && capabilites.length > 0 && ( +
+
Allowed capabilities
+
    + {capabilites.map((capability, index) => ( +
  • + {CAPABILITY_FLAG_TO_DESCRIPTION[capability] ?? "unknown"} +
  • + ))} +
+
+ )} ); }; diff --git a/client/src/app/components/stardust/TruncatedId.tsx b/client/src/app/components/stardust/TruncatedId.tsx index ebf5d780d..0f1edde21 100644 --- a/client/src/app/components/stardust/TruncatedId.tsx +++ b/client/src/app/components/stardust/TruncatedId.tsx @@ -6,13 +6,17 @@ import CopyButton from "../CopyButton"; interface TruncatedIdProps { readonly id: string; readonly link?: string; + readonly linkState?: Record; readonly showCopyButton?: boolean; } -const TruncatedId: React.FC = ({ id, link, showCopyButton }) => { +const TruncatedId: React.FC = ({ id, link, linkState, showCopyButton }) => { const content = link && link.length > 0 ? ( - + {id} ) : ( diff --git a/client/src/app/lib/constants/allowed.capabilities.ts b/client/src/app/lib/constants/allowed.capabilities.ts new file mode 100644 index 000000000..c4ebecd92 --- /dev/null +++ b/client/src/app/lib/constants/allowed.capabilities.ts @@ -0,0 +1,11 @@ +export const CAPABILITY_FLAG_TO_DESCRIPTION: Record = { + 0: "Can receive Outputs with Native Tokens.", + 1: "Can receive Outputs with Mana.", + 2: "Can receive Outputs with a Timelock Unlock Condition.", + 3: "Can receive Outputs with an Expiration Unlock Condition.", + 4: "Can receive Outputs with a Storage Deposit Return Unlock Condition.", + 5: "Can receive Account Outputs.", + 6: "Can receive Anchor Outputs.", + 7: "Can receive NFT Outputs.", + 8: "Can receive Delegation Outputs.", +}; diff --git a/client/src/app/routes/nova/AddressPage.tsx b/client/src/app/routes/nova/AddressPage.tsx index ac13d7727..a74cf55bb 100644 --- a/client/src/app/routes/nova/AddressPage.tsx +++ b/client/src/app/routes/nova/AddressPage.tsx @@ -17,6 +17,8 @@ import Ed25519AddressView from "~/app/components/nova/address/Ed25519AddressView import NftAddressView from "~/app/components/nova/address/NftAddressView"; import AnchorAddressView from "~/app/components/nova/address/AnchorAddressView"; import ImplicitAccountCreationAddressView from "~/app/components/nova/address/ImplicitAccountCreationAddressView"; +import { useNetworkInfoNova } from "~/helpers/nova/networkInfo"; +import { AddressHelper } from "~/helpers/nova/addressHelper"; import "./AddressPage.scss"; const AddressPage: React.FC> = ({ @@ -24,13 +26,24 @@ const AddressPage: React.FC> = ({ params: { address: addressString }, }, }) => { + const { bech32Hrp } = useNetworkInfoNova((s) => s.networkInfo); const isValidBech32Address = Utils.isAddressValid(addressString); if (!isValidBech32Address) { return ; } - const parsedAddress = Utils.parseBech32Address(addressString); + let parsedAddress = Utils.parseBech32Address(addressString); + + if (parsedAddress.type === AddressType.Restricted) { + const addressDetails = AddressHelper.buildAddress(bech32Hrp, parsedAddress); + + const innerAddressBech32 = addressDetails.restricted?.bech32; + + if (innerAddressBech32) { + parsedAddress = Utils.parseBech32Address(innerAddressBech32); + } + } const renderAddressView = (parsedAddress: Address) => { switch (parsedAddress.type) { diff --git a/client/src/helpers/nova/addressHelper.ts b/client/src/helpers/nova/addressHelper.ts index 3463a7ba8..ea3f618dc 100644 --- a/client/src/helpers/nova/addressHelper.ts +++ b/client/src/helpers/nova/addressHelper.ts @@ -7,9 +7,10 @@ import { AnchorAddress, Utils, ImplicitAccountCreationAddress, + RestrictedAddress, } from "@iota/sdk-wasm-nova/web"; import { HexHelper } from "../stardust/hexHelper"; -import { IAddressDetails } from "~models/api/nova/IAddressDetails"; +import { IAddressDetails, IRestrictedAddressDetails } from "~models/api/nova/IAddressDetails"; import { plainToInstance } from "class-transformer"; export class AddressHelper { @@ -28,6 +29,7 @@ export class AddressHelper { let bech32; let hex; let type; + let restricted; if (Utils.isAddressValid(addressString)) { try { @@ -39,6 +41,7 @@ export class AddressHelper { bech32 = addressDetails.bech32; type = addressDetails.type; hex = addressDetails.hex; + restricted = addressDetails.restricted; } } catch {} } @@ -55,12 +58,14 @@ export class AddressHelper { hex: hex ? HexHelper.addPrefix(hex) : hex, type: type ?? typeHint, label: AddressHelper.typeLabel(type), + restricted, }; } private static buildAddressFromTypes(address: Address, hrp: string): IAddressDetails { let hex: string = ""; let bech32: string = ""; + let restricted: IRestrictedAddressDetails | undefined; if (address.type === AddressType.Ed25519) { hex = (address as Ed25519Address).pubKeyHash; @@ -74,15 +79,30 @@ export class AddressHelper { const implicitAccountCreationAddress = plainToInstance(ImplicitAccountCreationAddress, address); const innerAddress = implicitAccountCreationAddress.address(); hex = (innerAddress as Ed25519Address).pubKeyHash; + } else if (address.type === AddressType.Restricted) { + const restrictedAddress = RestrictedAddress.parse(address) as RestrictedAddress; + const capabilities = Array.from(restrictedAddress.getAllowedCapabilities()); + const innerAddress = Address.parse(restrictedAddress.address); + const innerAddressDetails = this.buildAddressFromTypes(innerAddress, hrp); + + restricted = { + bech32: innerAddressDetails.bech32, + innerAddressType: innerAddress.type, + capabilities, + }; } bech32 = Utils.addressToBech32(address, hrp); + const label = restricted + ? `Restricted ${AddressHelper.typeLabel(restricted.innerAddressType)}` + : AddressHelper.typeLabel(address.type); return { bech32, hex, type: address.type, - label: AddressHelper.typeLabel(address.type), + label, + restricted, }; } diff --git a/client/src/helpers/nova/nameHelper.ts b/client/src/helpers/nova/nameHelper.ts index 2253b3dde..b3ad67d0d 100644 --- a/client/src/helpers/nova/nameHelper.ts +++ b/client/src/helpers/nova/nameHelper.ts @@ -1,6 +1,30 @@ -import { FeatureType, OutputType, PayloadType, UnlockType } from "@iota/sdk-wasm-nova/web"; +import { AddressType, FeatureType, OutputType, PayloadType, UnlockType } from "@iota/sdk-wasm-nova/web"; export class NameHelper { + /** + * Get the label for an address type. + * @param type The address type to get the name for. + * @returns The address type name. + */ + public static getAddressTypeName(type: AddressType, isRestricted = false): string { + switch (type) { + case AddressType.Ed25519: + return "Ed25519"; + case AddressType.Account: + return "Account"; + case AddressType.Nft: + return "Nft"; + case AddressType.Anchor: + return "Anchor"; + case AddressType.ImplicitAccountCreation: + return "ImplicitAccountCreation"; + case AddressType.Restricted: + return "Restricted"; + default: + return "Unknown"; + } + } + /** * Get the name for the unlock type. * @param type The type to get the name for. diff --git a/client/src/models/api/nova/IAddressDetails.ts b/client/src/models/api/nova/IAddressDetails.ts index 234c08768..fbe5c365c 100644 --- a/client/src/models/api/nova/IAddressDetails.ts +++ b/client/src/models/api/nova/IAddressDetails.ts @@ -5,4 +5,11 @@ export interface IAddressDetails { hex?: string; type?: AddressType; label?: string; + restricted?: IRestrictedAddressDetails; +} + +export interface IRestrictedAddressDetails { + bech32: string; + innerAddressType?: AddressType; + capabilities?: number[]; }