Skip to content

Commit

Permalink
Add init/upgrade Anchor IDL from the UI
Browse files Browse the repository at this point in the history
Requested in #111, related with #93
  • Loading branch information
acheroncrypto committed Jan 30, 2023
1 parent 9f83741 commit b428065
Show file tree
Hide file tree
Showing 6 changed files with 170 additions and 10 deletions.
2 changes: 2 additions & 0 deletions client/package.json
Original file line number Diff line number Diff line change
Expand Up @@ -34,13 +34,15 @@
"@solana/wallet-adapter-phantom": "^0.9.3",
"@solana/wallet-adapter-react": "^0.15.4",
"@solana/web3.js": "^1.63.1",
"@types/pako": "^2.0.0",
"assert": "^2.0.0",
"crypto-browserify": "^3.12.0",
"file-saver": "^2.0.5",
"jotai": "^1.6.1",
"jszip": "^3.10.1",
"mocha": "^10.0.0",
"monaco-editor": "^0.34.0",
"pako": "^2.1.0",
"prettier": "^2.7.1",
"process": "^0.11.10",
"re-resizable": "^6.9.6",
Expand Down
Original file line number Diff line number Diff line change
@@ -1,20 +1,25 @@
import { ChangeEvent } from "react";
import { ChangeEvent, useCallback, useEffect, useMemo, useState } from "react";
import { useAtom } from "jotai";
import styled from "styled-components";

import Button from "../../../../../Button";
import DownloadButton from "../../../../../DownloadButton";
import UploadButton from "../../../../../UploadButton";
import { buildCountAtom } from "../../../../../../state";
import {
PgCommand,
PgCommon,
PgExplorer,
PgProgramInfo,
PgTerminal,
PgWallet,
} from "../../../../../../utils/pg";

const IDL = () => (
<Wrapper>
<Import />
<Export />
<InitOrUpgrade />
</Wrapper>
);

Expand All @@ -31,7 +36,7 @@ const Import = () => {
PgProgramInfo.update({
idl: JSON.parse(decodedString),
});
PgExplorer.run({ saveProgramInfo: [] });
await PgExplorer.run({ saveProgramInfo: [] });
} catch (e: any) {
console.log(e.message);
}
Expand Down Expand Up @@ -62,12 +67,114 @@ const Export = () => {
);
};

enum InitOrUpgradeState {
HAS_ERROR,
INCORRECT_AUTHORITY,
IS_FETCHING,
IS_INITIALIZING,
IS_UPGRADING,
CAN_INIT,
CAN_UPGRADE,
}

const InitOrUpgrade = () => {
const [state, setState] = useState<InitOrUpgradeState>(
InitOrUpgradeState.IS_FETCHING
);

const buttonText = useMemo(() => {
switch (state) {
case InitOrUpgradeState.HAS_ERROR:
return "Retry";
case InitOrUpgradeState.IS_FETCHING:
return "Fetching";
case InitOrUpgradeState.IS_INITIALIZING:
return "Initializing";
case InitOrUpgradeState.IS_UPGRADING:
return "Upgrading";
case InitOrUpgradeState.CAN_INIT:
return "Initialize";
case InitOrUpgradeState.CAN_UPGRADE:
case InitOrUpgradeState.INCORRECT_AUTHORITY:
return "Upgrade";
}
}, [state]);

const [loading, disabled] = useMemo(() => {
switch (state) {
case InitOrUpgradeState.IS_FETCHING:
case InitOrUpgradeState.IS_INITIALIZING:
case InitOrUpgradeState.IS_UPGRADING:
return [true, true];
case InitOrUpgradeState.INCORRECT_AUTHORITY:
return [false, true];
default:
return [false, false];
}
}, [state]);

const getIdl = useCallback(async () => {
try {
setState(InitOrUpgradeState.IS_FETCHING);
const idlResult = await PgCommon.transition(
PgProgramInfo.getIdlFromChain()
);
if (!idlResult) {
setState(InitOrUpgradeState.CAN_INIT);
return;
}

const wallet = await PgWallet.get();
if (idlResult.authority.equals(wallet.publicKey)) {
setState(InitOrUpgradeState.CAN_UPGRADE);
return;
}

setState(InitOrUpgradeState.INCORRECT_AUTHORITY);
} catch (e: any) {
console.log("Couldn't get IDL:", e.message);
setState(InitOrUpgradeState.HAS_ERROR);
}
}, []);

// Initial run
useEffect(() => {
getIdl();
}, [getIdl]);

const handleInitOrUpgrade = async () => {
switch (state) {
case InitOrUpgradeState.CAN_INIT: {
setState(InitOrUpgradeState.IS_INITIALIZING);
PgTerminal.runCmdFromStr(`${PgCommand.ANCHOR} idl init`);
await PgCommon.sleep(4000);
break;
}
case InitOrUpgradeState.CAN_UPGRADE: {
setState(InitOrUpgradeState.IS_UPGRADING);
PgTerminal.runCmdFromStr(`${PgCommand.ANCHOR} idl upgrade`);
await PgCommon.sleep(2000);
break;
}
}

await getIdl();
};

return (
<Button
disabled={disabled}
btnLoading={loading}
onClick={handleInitOrUpgrade}
>
{buttonText}
</Button>
);
};

const Wrapper = styled.div`
display: flex;
& > a {
margin-left: 1rem;
}
gap: 1rem;
`;

export default IDL;
Original file line number Diff line number Diff line change
Expand Up @@ -20,7 +20,7 @@ const ProgramSettings = () => (
/>
<ProgramSetting
title="IDL"
text="Import/export the program IDL."
text="Anchor IDL interactions."
InsideEl={<IDL />}
/>
</Wrapper>
Expand Down
3 changes: 1 addition & 2 deletions client/src/utils/pg/connection.ts
Original file line number Diff line number Diff line change
Expand Up @@ -2,7 +2,6 @@ import { Commitment, Connection, ConnectionConfig } from "@solana/web3.js";

import { Endpoint, EventName } from "../../constants";
import { PgCommon } from "./common";
import { PgPlaynet } from "./playnet";
import { PgSet } from "./types";

export interface PgConnectionConfig {
Expand Down Expand Up @@ -109,7 +108,7 @@ export class PgConnection {
* @returns whether the connection is ready to be used
*/
static isReady(conn: Connection & { overridden?: boolean }) {
if (PgPlaynet.isUrlPlaynet(conn.rpcEndpoint)) {
if (conn.rpcEndpoint === Endpoint.PLAYNET) {
return conn.overridden;
}

Expand Down
44 changes: 43 additions & 1 deletion client/src/utils/pg/program-info.ts
Original file line number Diff line number Diff line change
@@ -1,5 +1,12 @@
import { Idl } from "@project-serum/anchor";
import * as pako from "pako";
import { Idl, utils } from "@project-serum/anchor";
import { Keypair, PublicKey } from "@solana/web3.js";
import {
decodeIdlAccount,
idlAddress,
} from "@project-serum/anchor/dist/cjs/idl";

import { PgConnection } from "./connection";

export interface ProgramInfo {
uuid?: string;
Expand Down Expand Up @@ -111,5 +118,40 @@ export class PgProgramInfo {
return { programPk };
}

/**
* Fetch the Anchor IDL from chain.
*
* NOTE: This is a reimplementation of `anchor.Program.fetchIdl` because that
* function only returns the IDL and not the IDL authority.
*
* @param programId optional program id
* @returns the IDL and the authority of the IDL or `null` if IDL doesn't exist
*/
static async getIdlFromChain(programId?: PublicKey) {
if (!programId) {
const programPkResult = PgProgramInfo.getPk();
if (programPkResult.err) {
throw new Error(programPkResult.err);
}
programId = programPkResult.programPk!;
}

const idlPk = await idlAddress(programId);

const conn = await PgConnection.get();
const accountInfo = await conn.getAccountInfo(idlPk);
if (!accountInfo) {
return null;
}

// Chop off account discriminator
const idlAccount = decodeIdlAccount(accountInfo.data.slice(8));
const inflatedIdl = pako.inflate(idlAccount.data);
const idl: Idl = JSON.parse(utils.bytes.utf8.decode(inflatedIdl));

return { idl, authority: idlAccount.authority };
}

/** localStorage key */
private static readonly _PROGRAM_INFO_KEY = "programInfo";
}
10 changes: 10 additions & 0 deletions client/yarn.lock
Original file line number Diff line number Diff line change
Expand Up @@ -3043,6 +3043,11 @@
resolved "https://registry.yarnpkg.com/@types/node/-/node-17.0.23.tgz#3b41a6e643589ac6442bdbd7a4a3ded62f33f7da"
integrity sha512-UxDxWn7dl97rKVeVS61vErvw086aCYhDLyvRQZ5Rk65rZKepaFdm53GeqXaKBuOhED4e9uWq34IC3TdSdJJ2Gw==

"@types/pako@^2.0.0":
version "2.0.0"
resolved "https://registry.yarnpkg.com/@types/pako/-/pako-2.0.0.tgz#12ab4c19107528452e73ac99132c875ccd43bdfb"
integrity sha512-10+iaz93qR5WYxTo+PMifD5TSxiOtdRaxBf7INGGXMQgTCu8Z/7GYWYFUOS3q/G0nE5boj1r4FEB+WSy7s5gbA==

"@types/parse-json@^4.0.0":
version "4.0.0"
resolved "https://registry.yarnpkg.com/@types/parse-json/-/parse-json-4.0.0.tgz#2f8bb441434d163b35fb8ffdccd7138927ffb8c0"
Expand Down Expand Up @@ -9344,6 +9349,11 @@ pako@^2.0.3:
resolved "https://registry.yarnpkg.com/pako/-/pako-2.0.4.tgz#6cebc4bbb0b6c73b0d5b8d7e8476e2b2fbea576d"
integrity sha512-v8tweI900AUkZN6heMU/4Uy4cXRc2AYNRggVmTR+dEncawDJgCdLMximOVA2p4qO57WMynangsfGRb5WD6L1Bg==

pako@^2.1.0:
version "2.1.0"
resolved "https://registry.yarnpkg.com/pako/-/pako-2.1.0.tgz#266cc37f98c7d883545d11335c00fbd4062c9a86"
integrity sha512-w+eufiZ1WuJYgPXbV/PO3NCMEc3xqylkKHzp8bxp1uW4qaSNQUkwmLLEc3kKsfz8lpV1F8Ht3U1Cm+9Srog2ug==

pako@~1.0.2:
version "1.0.11"
resolved "https://registry.yarnpkg.com/pako/-/pako-1.0.11.tgz#6c9599d340d54dfd3946380252a35705a6b992bf"
Expand Down

0 comments on commit b428065

Please sign in to comment.