Skip to content

Commit

Permalink
IDL Fixes (#384)
Browse files Browse the repository at this point in the history
* fix long byte array display for anchor ix arguments
* add option to download idl
* add badge to show IDL version
* add toggle for viewing expanded IDL (no more clicking manually to
expand)
  • Loading branch information
ngundotra authored Oct 9, 2024
1 parent 253496f commit 7a5b9c8
Show file tree
Hide file tree
Showing 7 changed files with 156 additions and 29 deletions.
61 changes: 50 additions & 11 deletions app/components/account/AnchorProgramCard.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -2,31 +2,70 @@

import { useAnchorProgram } from '@providers/anchor';
import { useCluster } from '@providers/cluster';
import { useState } from 'react';
import ReactJson from 'react-json-view';

import { getIdlSpecType } from '@/app/utils/convertLegacyIdl';

import { DownloadableButton } from '../common/Downloadable';
import { IDLBadge } from '../common/IDLBadge';

export function AnchorProgramCard({ programId }: { programId: string }) {
const { url } = useCluster();
const { idl } = useAnchorProgram(programId, url);
const [collapsedValue, setCollapsedValue] = useState<boolean | number>(1);

if (!idl) {
return null;
}
const spec = getIdlSpecType(idl);

return (
<>
<div className="card">
<div className="card-header">
<div className="row align-items-center">
<div className="col">
<h3 className="card-header-title">Anchor IDL</h3>
</div>
<div className="card">
<div className="card-header">
<div className="row align-items-center">
<div className="col">
<h3 className="card-header-title">Anchor IDL</h3>
</div>
<div className="col-auto btn btn-sm btn-primary d-flex align-items-center">
<DownloadableButton
data={Buffer.from(JSON.stringify(idl, null, 2)).toString('base64')}
filename={`${programId}-idl.json`}
type="application/json"
>
Download IDL
</DownloadableButton>
</div>
</div>

<div className="card metadata-json-viewer m-4">
<ReactJson src={idl} theme={'solarized'} style={{ padding: 25 }} collapsed={1} name={null} />
</div>
<div className="card-body d-flex justify-content-between align-items-center">
<IDLBadge spec={spec} />
<div className="form-check form-switch">
<input
className="form-check-input"
type="checkbox"
id="expandToggle"
onChange={e => setCollapsedValue(e.target.checked ? false : 1)}
/>
<label className="form-check-label" htmlFor="expandToggle">
Expand All
</label>
</div>
</div>
</>

<div className="card metadata-json-viewer m-4 mt-2">
<ReactJson
src={idl}
theme={'solarized'}
style={{ padding: 25 }}
name={null}
enableClipboard={true}
collapsed={collapsedValue}
displayObjectSize={false}
displayDataTypes={false}
displayArrayKey={false}
/>
</div>
</div>
);
}
6 changes: 3 additions & 3 deletions app/components/account/UpgradeableLoaderAccountSection.tsx
Original file line number Diff line number Diff line change
@@ -1,6 +1,6 @@
import { UnknownAccountCard } from '@components/account/UnknownAccountCard';
import { Address } from '@components/common/Address';
import { Downloadable } from '@components/common/Downloadable';
import { DownloadableIcon } from '@components/common/Downloadable';
import { InfoTooltip } from '@components/common/InfoTooltip';
import { SecurityTXTBadge } from '@components/common/SecurityTXTBadge';
import { Slot } from '@components/common/Slot';
Expand Down Expand Up @@ -233,9 +233,9 @@ export function UpgradeableProgramDataSection({
<tr>
<td>Data Size (Bytes)</td>
<td className="text-lg-end">
<Downloadable data={programData.data[0]} filename={`${account.pubkey.toString()}.bin`}>
<DownloadableIcon data={programData.data[0]} filename={`${account.pubkey.toString()}.bin`}>
<span className="me-2">{account.space}</span>
</Downloadable>
</DownloadableIcon>
</td>
</tr>
)}
Expand Down
44 changes: 41 additions & 3 deletions app/components/common/Downloadable.tsx
Original file line number Diff line number Diff line change
@@ -1,7 +1,15 @@
import { ReactNode } from 'react';
import { Download } from 'react-feather';
import { ComponentType, ReactNode } from 'react';
import { Download, IconProps } from 'react-feather';

export function Downloadable({ data, filename, children }: { data: string; filename: string; children: ReactNode }) {
export function DownloadableIcon({
data,
filename,
children,
}: {
data: string;
filename: string;
children: ReactNode;
}) {
const handleClick = async () => {
const blob = new Blob([Buffer.from(data, 'base64')]);
const fileDownloadUrl = URL.createObjectURL(blob);
Expand All @@ -18,3 +26,33 @@ export function Downloadable({ data, filename, children }: { data: string; filen
</>
);
}

export function DownloadableButton({
data,
filename,
children,
type,
icon: Icon = Download as ComponentType<IconProps>,
}: {
data: string;
filename: string;
children?: ReactNode;
type?: string;
icon?: ComponentType<IconProps>;
}) {
const handleDownload = async () => {
const blob = new Blob([Buffer.from(data, 'base64')], type ? { type } : {});
const fileDownloadUrl = URL.createObjectURL(blob);
const tempLink = document.createElement('a');
tempLink.href = fileDownloadUrl;
tempLink.setAttribute('download', filename);
tempLink.click();
};

return (
<div onClick={handleDownload} style={{ alignItems: 'center', cursor: 'pointer', display: 'inline-flex' }}>
<Icon className="me-2" size={15} />
{children}
</div>
);
}
14 changes: 14 additions & 0 deletions app/components/common/IDLBadge.tsx
Original file line number Diff line number Diff line change
@@ -0,0 +1,14 @@
import React from 'react';

import { IdlSpec } from '@/app/utils/convertLegacyIdl';

interface IDLBadgeProps {
spec: IdlSpec;
}

export function IDLBadge({ spec }: IDLBadgeProps) {
const badgeClass = spec === 'legacy' ? 'bg-warning' : 'bg-success';
const badgeText = spec === 'legacy' ? 'Legacy' : '0.30.1';

return <span className={`badge ${badgeClass}`}>{badgeText} Anchor IDL</span>;
}
7 changes: 7 additions & 0 deletions app/types/react-json-view.d.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,7 @@
import 'react-json-view';

declare module 'react-json-view' {
interface ReactJsonViewProps {
displayArrayKey?: boolean;
}
}
27 changes: 26 additions & 1 deletion app/utils/anchor.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -200,7 +200,7 @@ function mapField(key: string, value: any, type: IdlType, idl: Idl, keySuffix?:
<div>{numberWithSeparator(value.toString())}</div>
</SimpleRow>
);
} else if (type === 'bool' || type === 'bytes' || type === 'string') {
} else if (type === 'bool' || type === 'string') {
return (
<SimpleRow
key={keySuffix ? `${key}-${keySuffix}` : key}
Expand All @@ -212,6 +212,30 @@ function mapField(key: string, value: any, type: IdlType, idl: Idl, keySuffix?:
<div>{value.toString()}</div>
</SimpleRow>
);
} else if (type === 'bytes') {
return (
<SimpleRow
key={keySuffix ? `${key}-${keySuffix}` : key}
rawKey={key}
type={type}
keySuffix={keySuffix}
nestingLevel={nestingLevel}
>
<div
className="text-lg-start"
style={{
fontSize: '0.85rem',
lineHeight: '1.2',
maxWidth: '100%',
overflowWrap: 'break-word',
whiteSpace: 'normal',
wordBreak: 'break-all',
}}
>
{(value as Buffer).toString('base64')}
</div>
</SimpleRow>
);
} else if (type === 'pubkey') {
return (
<SimpleRow
Expand Down Expand Up @@ -465,6 +489,7 @@ function typeDisplayName(
case 'i256':
case 'u256':
case 'bytes':
return 'bytes (Base64)';
case 'string':
return type.toString();
case 'pubkey':
Expand Down
26 changes: 15 additions & 11 deletions app/utils/convertLegacyIdl.ts
Original file line number Diff line number Diff line change
Expand Up @@ -430,17 +430,21 @@ function convertDefinedTypeArg(arg: LegacyIdlDefinedTypeArg): any {
throw new Error(`Unsupported defined type arg: ${JSON.stringify(arg)}`);
}

export function getIdlSpecType(idl: any): IdlSpec {
return idl.metadata?.spec ?? 'legacy';
}

export type IdlSpec = '0.1.0' | 'legacy';

export function formatIdl(idl: any, programAddress?: string): Idl {
const spec = idl.metadata?.spec;

if (spec) {
switch (spec) {
case '0.1.0':
return idl as Idl;
default:
throw new Error(`IDL spec not supported: ${spec}`);
}
} else {
return removeUnusedTypes(convertLegacyIdl(idl as LegacyIdl, programAddress));
const spec = getIdlSpecType(idl);

switch (spec) {
case '0.1.0':
return idl as Idl;
case 'legacy':
return removeUnusedTypes(convertLegacyIdl(idl as LegacyIdl, programAddress));
default:
throw new Error(`IDL spec not supported: ${spec}`);
}
}

0 comments on commit 7a5b9c8

Please sign in to comment.