Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

feat: Support restricted address type #1363

Merged
merged 9 commits into from
Mar 29, 2024
7 changes: 7 additions & 0 deletions api/src/models/api/nova/IAddressDetails.ts
Original file line number Diff line number Diff line change
Expand Up @@ -7,4 +7,11 @@ export interface IAddressDetails {
hex?: string;
type?: AddressType;
label?: string;
restricted?: IRestrictedAddressDetails;
}

export interface IRestrictedAddressDetails {
bech32: string;
innerAddressType?: AddressType;
capabilities?: number[];
}
24 changes: 22 additions & 2 deletions api/src/utils/nova/addressHelper.ts
Original file line number Diff line number Diff line change
Expand Up @@ -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 {
Expand All @@ -32,6 +33,7 @@ export class AddressHelper {
let bech32: string;
let hex: string;
let type: AddressType;
let restricted: IRestrictedAddressDetails;

if (Utils.isAddressValid(addressString)) {
try {
Expand All @@ -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}`);
Expand All @@ -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;
Expand All @@ -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,
};
}

Expand Down
24 changes: 4 additions & 20 deletions client/src/app/components/nova/address/AddressView.tsx
Original file line number Diff line number Diff line change
@@ -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;
Expand All @@ -15,29 +16,12 @@ const AddressView: React.FC<AddressViewProps> = ({ address }) => {

return (
<div className="address-type">
<div className="card--label">{getAddressTypeName(address.type)}</div>
<div className="card--label">{NameHelper.getAddressTypeName(address.type)}</div>
<div className="card--value">
<TruncatedId id={addressDetails.bech32} link={link} showCopyButton />
<TruncatedId id={addressDetails.bech32} link={link} linkState={{ addressDetails }} showCopyButton />
</div>
</div>
);
};

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;
16 changes: 16 additions & 0 deletions client/src/app/components/nova/address/Bech32Address.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -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;
Expand All @@ -12,6 +13,9 @@ interface Bech32AddressProps {
}

const Bech32Address: React.FC<Bech32AddressProps> = ({ network, history, addressDetails, advancedMode, hideLabel }) => {
const isRestricted = !!addressDetails?.restricted;
const capabilites = addressDetails?.restricted?.capabilities ?? [];

return (
<div className="bech32-address">
{addressDetails?.bech32 && (
Expand Down Expand Up @@ -48,6 +52,18 @@ const Bech32Address: React.FC<Bech32AddressProps> = ({ network, history, address
</div>
</div>
)}
{isRestricted && capabilites.length > 0 && (
<div className="section--data">
<div className="label">Allowed capabilities</div>
<ul style={{ marginLeft: "24px" }}>
{capabilites.map((capability, index) => (
<li key={index} className="value">
{CAPABILITY_FLAG_TO_DESCRIPTION[capability] ?? "unknown"}
</li>
))}
</ul>
</div>
)}
</div>
);
};
Expand Down
8 changes: 6 additions & 2 deletions client/src/app/components/stardust/TruncatedId.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -6,13 +6,17 @@ import CopyButton from "../CopyButton";
interface TruncatedIdProps {
readonly id: string;
readonly link?: string;
readonly linkState?: Record<string, unknown>;
readonly showCopyButton?: boolean;
}

const TruncatedId: React.FC<TruncatedIdProps> = ({ id, link, showCopyButton }) => {
const TruncatedId: React.FC<TruncatedIdProps> = ({ id, link, linkState, showCopyButton }) => {
const content =
link && link.length > 0 ? (
<Link to={link} className={classNames("truncate", "highlight", { "margin-r-t": showCopyButton })}>
<Link
to={{ pathname: link, state: linkState }}
className={classNames("truncate", "highlight", { "margin-r-t": showCopyButton })}
>
{id}
</Link>
) : (
Expand Down
11 changes: 11 additions & 0 deletions client/src/app/lib/constants/allowed.capabilities.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,11 @@
export const CAPABILITY_FLAG_TO_DESCRIPTION: Record<number, string> = {
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.",
};
15 changes: 14 additions & 1 deletion client/src/app/routes/nova/AddressPage.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -17,20 +17,33 @@ 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<RouteComponentProps<AddressRouteProps>> = ({
match: {
params: { address: addressString },
},
}) => {
const { bech32Hrp } = useNetworkInfoNova((s) => s.networkInfo);
const isValidBech32Address = Utils.isAddressValid(addressString);

if (!isValidBech32Address) {
return <AddressNotFoundPage address={addressString} />;
}

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) {
Expand Down
24 changes: 22 additions & 2 deletions client/src/helpers/nova/addressHelper.ts
Original file line number Diff line number Diff line change
Expand Up @@ -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 {
Expand All @@ -28,6 +29,7 @@ export class AddressHelper {
let bech32;
let hex;
let type;
let restricted;

if (Utils.isAddressValid(addressString)) {
try {
Expand All @@ -39,6 +41,7 @@ export class AddressHelper {
bech32 = addressDetails.bech32;
type = addressDetails.type;
hex = addressDetails.hex;
restricted = addressDetails.restricted;
}
} catch {}
}
Expand All @@ -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;
Expand All @@ -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,
};
}

Expand Down
26 changes: 25 additions & 1 deletion client/src/helpers/nova/nameHelper.ts
Original file line number Diff line number Diff line change
@@ -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.
Expand Down
7 changes: 7 additions & 0 deletions client/src/models/api/nova/IAddressDetails.ts
Original file line number Diff line number Diff line change
Expand Up @@ -5,4 +5,11 @@ export interface IAddressDetails {
hex?: string;
type?: AddressType;
label?: string;
restricted?: IRestrictedAddressDetails;
}

export interface IRestrictedAddressDetails {
bech32: string;
innerAddressType?: AddressType;
capabilities?: number[];
}
Loading