Skip to content

Commit

Permalink
refactor(idea/frontend): upload program/code signAndSend (#1585)
Browse files Browse the repository at this point in the history
  • Loading branch information
nikitayutanov authored Jul 4, 2024
1 parent 5422e69 commit b9e7de0
Show file tree
Hide file tree
Showing 11 changed files with 165 additions and 378 deletions.
4 changes: 2 additions & 2 deletions idea/frontend/src/api/program/requests.ts
Original file line number Diff line number Diff line change
Expand Up @@ -8,8 +8,8 @@ import { FetchProgramsParams, ProgramPaginationModel } from './types';

const fetchProgram = (id: string) => rpcService.callRPC<IProgram>(RpcMethods.GetProgram, { id });

const addProgramName = (params: { id: HexString; name: string }, isDevChain: boolean | undefined) =>
isDevChain ? Promise.resolve() : rpcService.callRPC(RpcMethods.AddProgramName, params);
const addProgramName = (params: { id: HexString; name: string }) =>
rpcService.callRPC(RpcMethods.AddProgramName, params);

const fetchPrograms = (params: FetchProgramsParams) =>
rpcService.callRPC<ProgramPaginationModel>(RpcMethods.GetAllPrograms, params);
Expand Down
2 changes: 1 addition & 1 deletion idea/frontend/src/hooks/use-contract-api-with-file.ts
Original file line number Diff line number Diff line change
Expand Up @@ -52,7 +52,7 @@ function useContractApiWithFile(codeIdOrBuffer: HexString | Buffer | undefined)

if (extension === FILE_EXTENSION.IDL) {
metadata.reset();
await sails.set(text);
sails.set(text);
}
};

Expand Down
18 changes: 13 additions & 5 deletions idea/frontend/src/hooks/use-sign-and-send.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -3,21 +3,26 @@ import { SubmittableExtrinsic } from '@polkadot/api/types';
import { Event } from '@polkadot/types/interfaces';
import { ISubmittableResult } from '@polkadot/types/types';
import { web3FromSource } from '@polkadot/extension-dapp';
import { ReactNode } from 'react';

import { useExtrinsicFailedMessage } from './use-extrinsic-failed-message';

type Extrinsic = SubmittableExtrinsic<'promise', ISubmittableResult>;

type Options = {
successAlert: ReactNode;
onSuccess: () => void;
onError: () => void;
onFinally: () => void;
onFinalized: (value: ISubmittableResult) => void;
};

const DEFAULT_OPTIONS: Options = {
const DEFAULT_OPTIONS = {
successAlert: 'Success',
onSuccess: () => {},
onError: () => {},
onFinally: () => {},
onFinalized: () => {},
} as const;

function useSignAndSend() {
Expand All @@ -26,7 +31,7 @@ function useSignAndSend() {
const getExtrinsicFailedMessage = useExtrinsicFailedMessage();

const handleEvent = (event: Event, method: string, options: Options) => {
const { onSuccess, onError, onFinally } = options;
const { successAlert, onSuccess, onError, onFinally } = options;
const alertOptions = { title: `${event.section}.${event.method}` };

if (event.method === 'ExtrinsicFailed') {
Expand All @@ -39,16 +44,17 @@ function useSignAndSend() {
}

if (event.method === method) {
alert.success('Success', alertOptions);
alert.success(successAlert, alertOptions);

onSuccess();
onFinally();
}
};

const handleStatus = ({ events, status }: ISubmittableResult, method: string, options: Options, alertId: string) => {
const handleStatus = (result: ISubmittableResult, method: string, options: Options, alertId: string) => {
const { events, status } = result;
const { isInvalid, isReady, isInBlock, isFinalized } = status;
const { onError, onFinally } = options;
const { onError, onFinally, onFinalized } = options;

if (isInvalid) {
alert.update(alertId, 'Transaction error. Status: isInvalid', DEFAULT_ERROR_OPTIONS);
Expand All @@ -64,6 +70,8 @@ function useSignAndSend() {
if (isFinalized) {
alert.update(alertId, 'Finalized', DEFAULT_SUCCESS_OPTIONS);

onFinalized(result);

events.forEach(({ event }) => handleEvent(event, method, options));
}
};
Expand Down
14 changes: 1 addition & 13 deletions idea/frontend/src/hooks/useCodeUpload/types.ts
Original file line number Diff line number Diff line change
@@ -1,8 +1,4 @@
import { HexString } from '@polkadot/util/types';
import { SubmittableExtrinsic } from '@polkadot/api/types';
import { ISubmittableResult } from '@polkadot/types/types';

import { ParamsToSignAndSend as CommonParamsToSignAndSend } from '@/entities/hooks';

type ParamsToUploadCode = {
optBuffer: Buffer;
Expand All @@ -13,12 +9,4 @@ type ParamsToUploadCode = {
resolve: () => void;
};

type ParamsToSignAndSend = Omit<CommonParamsToSignAndSend, 'reject'> & {
extrinsic: SubmittableExtrinsic<'promise', ISubmittableResult>;
name: string;
codeId: HexString;
metaHex: HexString | undefined;
idl: string | undefined;
};

export type { ParamsToUploadCode, ParamsToSignAndSend };
export type { ParamsToUploadCode };
137 changes: 48 additions & 89 deletions idea/frontend/src/hooks/useCodeUpload/useCodeUpload.tsx
Original file line number Diff line number Diff line change
@@ -1,118 +1,77 @@
import { useCallback } from 'react';
import { web3FromSource } from '@polkadot/extension-dapp';
import { EventRecord } from '@polkadot/types/interfaces';
import { HexString } from '@polkadot/util/types';
import { useApi, useAlert, useAccount, DEFAULT_ERROR_OPTIONS, DEFAULT_SUCCESS_OPTIONS } from '@gear-js/react-hooks';
import { useApi, useAccount } from '@gear-js/react-hooks';

import { useChain, useModal } from '@/hooks';
import { Method } from '@/features/explorer';
import { checkWallet, getExtrinsicFailedMessage } from '@/shared/helpers';
import { PROGRAM_ERRORS, TransactionName, TransactionStatus, UPLOAD_METADATA_TIMEOUT } from '@/shared/config';
import { useChain, useModal, useSignAndSend } from '@/hooks';
import { UPLOAD_METADATA_TIMEOUT } from '@/shared/config';
import { CopiedInfo } from '@/shared/ui/copiedInfo';
import { addMetadata, addCodeName } from '@/api';
import { addIdl } from '@/features/sails';

import { ParamsToUploadCode, ParamsToSignAndSend } from './types';
import { ParamsToUploadCode } from './types';

const useCodeUpload = () => {
const { api, isApiReady } = useApi();
const alert = useAlert();
const { account } = useAccount();
const { showModal } = useModal();
const { isDevChain } = useChain();

const handleEventsStatus = (events: EventRecord[], codeHash: HexString, resolve?: () => void) => {
const signAndSend = useSignAndSend();

// will be refactored in the upcoming local indexer refactoring
const handleMetadataUpload = (
codeId: HexString,
codeName: string,
metaHex: HexString | undefined,
idl: string | undefined,
) => {
if (!isApiReady) throw new Error('API is not initialized');
if (isDevChain) return;

events.forEach(({ event }) => {
const { method, section } = event;
const alertOptions = { title: `${section}.${method}` };

if (method === Method.ExtrinsicFailed) {
alert.error(getExtrinsicFailedMessage(api, event), alertOptions);
} else if (method === Method.CodeChanged) {
alert.success(<CopiedInfo title="Code hash" info={codeHash} />, alertOptions);
// timeout cuz wanna be sure that block data is ready
setTimeout(async () => {
const id = codeId;
const name = codeName || id;

if (resolve) resolve();
}
});
};
await addCodeName({ id, name });
if (idl) addIdl(codeId, idl);

const signAndSend = async ({ extrinsic, signer, codeId, metaHex, idl, name, resolve }: ParamsToSignAndSend) => {
const alertId = alert.loading('SignIn', { title: TransactionName.SubmitCode });

try {
if (!isApiReady) throw new Error('API is not initialized');

await extrinsic.signAndSend(account!.address, { signer }, ({ events, status }) => {
if (status.isReady) {
alert.update(alertId, TransactionStatus.Ready);
} else if (status.isInBlock) {
alert.update(alertId, TransactionStatus.InBlock);
handleEventsStatus(events, codeId, resolve);
} else if (status.isFinalized) {
alert.update(alertId, TransactionStatus.Finalized, DEFAULT_SUCCESS_OPTIONS);

if (isDevChain) return;

// timeout cuz wanna be sure that block data is ready
setTimeout(() => {
const id = codeId;

addCodeName({ id, name: name || id })
.then(async () => {
// TODO: no need to upload if meta/idl is from storage
if (metaHex) addMetadata(await api.code.metaHash(id), metaHex);
if (idl) addIdl(id, idl);
})
.catch(({ message }: Error) => alert.error(message));
}, UPLOAD_METADATA_TIMEOUT);
} else if (status.isInvalid) {
alert.update(alertId, PROGRAM_ERRORS.INVALID_TRANSACTION, DEFAULT_ERROR_OPTIONS);
}
});
} catch (error) {
const message = (error as Error).message;
if (!metaHex) return;
const hash = await api.code.metaHash(id);

alert.update(alertId, message, DEFAULT_ERROR_OPTIONS);
}
addMetadata(hash, metaHex);
}, UPLOAD_METADATA_TIMEOUT);
};

const uploadCode = useCallback(
async ({ optBuffer, name, voucherId, metaHex, idl, resolve }: ParamsToUploadCode) => {
try {
if (!isApiReady) throw new Error('API is not initialized');
checkWallet(account);

const { address, meta } = account!;

const [code, { signer }] = await Promise.all([api.code.upload(optBuffer), web3FromSource(meta.source)]);
return async ({ optBuffer, name, voucherId, metaHex, idl, resolve }: ParamsToUploadCode) => {
if (!isApiReady) throw new Error('API is not initialized');
if (!account) throw new Error('Account not found');

const codeExtrinsic = code.extrinsic;
const codeId = code.codeHash;
const extrinsic = voucherId ? api.voucher.call(voucherId, { UploadCode: codeExtrinsic }) : codeExtrinsic;
const { address, meta } = account;

const { partialFee } = await api.code.paymentInfo(address, { signer });
const [{ codeHash, extrinsic: codeExtrinsic }, { signer }] = await Promise.all([
api.code.upload(optBuffer),
web3FromSource(meta.source),
]);

const handleConfirm = () => signAndSend({ extrinsic, signer, name, codeId, metaHex, idl, resolve });
const extrinsic = voucherId ? api.voucher.call(voucherId, { UploadCode: codeExtrinsic }) : codeExtrinsic;
const { partialFee } = await api.code.paymentInfo(address, { signer });

showModal('transaction', {
fee: partialFee.toHuman(),
name: TransactionName.SubmitCode,
addressFrom: address,
onConfirm: handleConfirm,
});
} catch (error) {
const message = (error as Error).message;
const onFinalized = () => handleMetadataUpload(codeHash, name, metaHex, idl);

alert.error(message);
}
},
// eslint-disable-next-line react-hooks/exhaustive-deps
[api, account],
);
const onConfirm = () =>
signAndSend(extrinsic, 'CodeChanged', {
successAlert: <CopiedInfo title="Code hash" info={codeHash} />,
onSuccess: resolve,
onFinalized,
});

return uploadCode;
showModal('transaction', {
fee: partialFee.toHuman(),
name: `${extrinsic.method.section}.${extrinsic.method.method}`,
addressFrom: address,
onConfirm,
});
};
};

export { useCodeUpload };
7 changes: 0 additions & 7 deletions idea/frontend/src/hooks/useProgramActions/consts.ts

This file was deleted.

25 changes: 1 addition & 24 deletions idea/frontend/src/hooks/useProgramActions/types.ts
Original file line number Diff line number Diff line change
@@ -1,8 +1,6 @@
import { ProgramMetadata } from '@gear-js/api';
import { HexString } from '@polkadot/util/types';

import { OperationCallbacks, ParamsToSignAndSend } from '@/entities/hooks';

type Payload = {
value: string;
gasLimit: string;
Expand All @@ -15,25 +13,4 @@ type Payload = {
idl?: string;
};

type DataToUpload = {
optBuffer: Buffer;
payload: Payload;
};

type DataToCreate = {
codeId: HexString;
payload: Payload;
};

type ParamsToUpload = OperationCallbacks & DataToUpload;

type ParamsToCreate = OperationCallbacks & DataToCreate;

type ParamsToSignAndUpload = ParamsToSignAndSend & {
method: string;
payload: Payload;
programId: HexString;
codeId: HexString;
};

export type { Payload, ParamsToUpload, ParamsToCreate, ParamsToSignAndUpload };
export type { Payload };
Loading

0 comments on commit b9e7de0

Please sign in to comment.