diff --git a/front-end-tools/CHANGELOG.md b/front-end-tools/CHANGELOG.md
index 0927b048..4a00204e 100644
--- a/front-end-tools/CHANGELOG.md
+++ b/front-end-tools/CHANGELOG.md
@@ -1,5 +1,9 @@
## Unreleased changes
+## 3.0.0
+
+- Add reading from smart contract and writing to smart contract feature
+
## 2.2.0
- Add warning box if module references in step 1 and step 2 are not the same. Add warning box if embedded schema indicates an input parameter but no input parameter is provided.
diff --git a/front-end-tools/package.json b/front-end-tools/package.json
index 84d513fb..4d81c71e 100644
--- a/front-end-tools/package.json
+++ b/front-end-tools/package.json
@@ -1,7 +1,7 @@
{
"name": "front-end-tools",
"packageManager": "yarn@3.2.0",
- "version": "2.2.0",
+ "version": "3.0.0",
"license": "Apache-2.0",
"engines": {
"node": ">=16.x"
@@ -15,6 +15,8 @@
"react": "^18.1.0",
"react-bootstrap": "^2.7.4",
"react-dom": "^18.1.0",
+ "react-hook-form": "^7.47.0",
+ "react-select": "^5.7.7",
"react-switch": "^7.0.0"
},
"devDependencies": {
diff --git a/front-end-tools/src/Main.tsx b/front-end-tools/src/Main.tsx
index 894643fd..3d7f88fa 100644
--- a/front-end-tools/src/Main.tsx
+++ b/front-end-tools/src/Main.tsx
@@ -1,5 +1,5 @@
-/* eslint-disable no-console */
-import React, { useEffect, useState, ChangeEvent, PropsWithChildren, useCallback, useRef } from 'react';
+import React, { useEffect, useState } from 'react';
+
import {
WalletConnectionProps,
useConnection,
@@ -7,436 +7,94 @@ import {
useGrpcClient,
TESTNET,
MAINNET,
+ useWalletConnectorSelector,
} from '@concordium/react-components';
-import { Buffer } from 'buffer';
-import {
- AccountAddress,
- ModuleReference,
- TransactionKindString,
- TransactionSummaryType,
- displayTypeSchemaTemplate,
- sha256,
- toBuffer,
- getInitContractParameterSchema,
-} from '@concordium/web-sdk';
-import { WalletConnectionTypeButton } from './WalletConnectorTypeButton';
-
-import { initialize, deploy } from './writing_to_blockchain';
-
-import { BROWSER_WALLET, REFRESH_INTERVAL, EXAMPLE_ARRAYS, EXAMPLE_JSON_OBJECT } from './constants';
-type TestBoxProps = PropsWithChildren<{
- header: string;
-}>;
+import { Alert } from 'react-bootstrap';
+import DeployComponent from './components/DeployComponent';
+import ReadComponent from './components/ReadComponent';
+import UpdateComponent from './components/UpdateComponent';
+import InitComponent from './components/InitComponent';
+import { AccountLink } from './components/CCDScanLinks';
+import { getAccountInfo } from './reading_from_blockchain';
-function TestBox({ header, children }: TestBoxProps) {
- return (
-
- {header}
- {children}
-
-
- );
-}
+import { BROWSER_WALLET, REFRESH_INTERVAL } from './constants';
interface ConnectionProps {
walletConnectionProps: WalletConnectionProps;
isTestnet: boolean;
}
+/** The main component manages the connection to the browser wallet and
+ * combines the rest of the components (DeployComponent, InitComponent, ReadComponent, and UpdateComponent) to form the page.
+ * The connected account address, and its balance are displayed at the top. Links for further reading are displayed at the bottom.
+ */
export default function Main(props: ConnectionProps) {
+ // Network state
const { walletConnectionProps, isTestnet } = props;
-
const { activeConnectorType, activeConnector, activeConnectorError, connectedAccounts, genesisHashes } =
walletConnectionProps;
-
const { connection, setConnection, account } = useConnection(connectedAccounts, genesisHashes);
- const { connect, isConnecting, connectError } = useConnect(activeConnector, setConnection);
- const client = useGrpcClient(isTestnet ? TESTNET : MAINNET);
+ const { isConnected, select } = useWalletConnectorSelector(BROWSER_WALLET, connection, {
+ ...walletConnectionProps,
+ });
+ const { connect, connectError } = useConnect(activeConnector, setConnection);
- const [viewErrorAccountInfo, setViewErrorAccountInfo] = useState('');
- const [viewErrorModuleReference, setViewErrorModuleReference] = useState('');
- const [transactionErrorDeploy, setTransactionErrorDeploy] = useState('');
- const [transactionErrorInit, setTransactionErrorInit] = useState('');
- const [uploadError, setUploadError] = useState('');
- const [uploadError2, setUploadError2] = useState('');
- const [parsingError, setParsingError] = useState('');
- const [smartContractIndexError, setSmartContractIndexError] = useState('');
- const [moduleReferenceError, setModuleReferenceError] = useState('');
- const [moduleReferenceLengthError, setModuleReferenceLengthError] = useState('');
- const [schemaError, setSchemaError] = useState('');
+ const client = useGrpcClient(isTestnet ? TESTNET : MAINNET);
+ // Account state
+ const [viewErrorAccountInfo, setViewErrorAccountInfo] = useState(undefined);
const [accountExistsOnNetwork, setAccountExistsOnNetwork] = useState(true);
- const [moduleReferenceCalculated, setModuleReferenceCalculated] = useState('');
- const [isModuleReferenceAlreadyDeployedStep1, setIsModuleReferenceAlreadyDeployedStep1] = useState(false);
- const [isModuleReferenceAlreadyDeployedStep2, setIsModuleReferenceAlreadyDeployedStep2] = useState(false);
- const [moduleReferenceDeployed, setModuleReferenceDeployed] = useState('');
-
- const [shouldWarnDifferenceModuleReferences, setShouldWarnDifferenceModuleReferences] = useState(false);
- const [shouldWarnInputParameterInSchemaIgnored, setShouldWarnInputParameterInSchemaIgnored] = useState(false);
+ const [accountBalance, setAccountBalance] = useState(undefined);
- const [txHashDeploy, setTxHashDeploy] = useState('');
- const [txHashInit, setTxHashInit] = useState('');
-
- const [accountBalance, setAccountBalance] = useState('');
- const [inputParameter, setInputParameter] = useState('');
- const [contractName, setContractName] = useState('');
- const [moduleReference, setModuleReference] = useState('');
- const [cCDAmount, setCCDAmount] = useState('');
- const [base64Module, setBase64Module] = useState('');
- const [uploadedModuleSchemaBase64, setUploadedModuleSchemaBase64] = useState('');
- const [dropDown, setDropDown] = useState('number');
- const [smartContractIndex, setSmartContractIndex] = useState('');
- const [maxContractExecutionEnergy, setMaxContractExecutionEnergy] = useState('');
- const [useModuleFromStep1, setUseModuleFromStep1] = useState(false);
+ // Shared state between deploy step and init step
+ const [moduleReferenceCalculated, setModuleReferenceCalculated] = useState(undefined);
+ const [moduleReferenceDeployed, setModuleReferenceDeployed] = useState(undefined);
const [contracts, setContracts] = useState([]);
- const [displayContracts, setDisplayContracts] = useState([]);
- const [inputParameterTemplate, setInputParameterTemplate] = useState('');
- const [embeddedModuleSchemaBase64, setEmbeddedModuleSchemaBase64] = useState('');
-
- const [isWaitingForTransaction, setWaitingForUser] = useState(false);
- const [hasInputParameter, setHasInputParameter] = useState(false);
- const [isPayable, setIsPayable] = useState(false);
-
- const moduleFileRef = useRef(null);
- const inputParameterDropDownRef = useRef(null);
- const contractNameDropDownRef = useRef(null);
- const schemaFileRef = useRef(null);
- const inputParameterTextAreaRef = useRef(null);
- const useModuleReferenceFromStep1Ref = useRef(null);
- const moduleReferenceRef = useRef(null);
- const inputParameterFieldRef = useRef(null);
-
- function arraysEqual(a: Uint8Array, b: Uint8Array) {
- if (a === b) return true;
- if (a == null || b == null) return false;
- if (a.length !== b.length) return false;
-
- for (let i = 0; i < a.length; i += 1) {
- if (a[i] !== b[i]) return false;
- }
- return true;
- }
-
- function getObjectExample(template: string) {
- return template !== ''
- ? JSON.stringify(JSON.parse(template), undefined, 2)
- : JSON.stringify(EXAMPLE_JSON_OBJECT, undefined, 2);
- }
-
- function getArrayExample(template: string) {
- return template !== '' ? JSON.stringify(JSON.parse(template), undefined, 2) : EXAMPLE_ARRAYS;
- }
-
- const changeModuleReferenceHandler = useCallback((event: ChangeEvent) => {
- setTransactionErrorInit('');
- setModuleReferenceLengthError('');
- setModuleReference('');
-
- const target = event.target as HTMLTextAreaElement;
-
- const moduleReferenceInput = target.value;
-
- if (moduleReferenceInput.length !== 64) {
- setModuleReferenceLengthError('Module reference has to be of length 64');
- } else {
- setModuleReference(target.value);
- }
- }, []);
-
- const changeInputParameterDropDownHandler = useCallback(() => {
- setParsingError('');
- setInputParameter('');
- setTransactionErrorInit('');
- const e = inputParameterDropDownRef.current as unknown as HTMLSelectElement;
- const sel = e.selectedIndex;
- const { value } = e.options[sel];
- setDropDown(value);
- }, []);
-
- const changeSmarContractDropDownHandler = useCallback(() => {
- setTransactionErrorInit('');
- const e = contractNameDropDownRef.current as unknown as HTMLSelectElement;
- const sel = e.selectedIndex;
- const { value } = e.options[sel];
- setContractName(value);
- }, []);
-
- const changeCCDAmountHandler = useCallback((event: ChangeEvent) => {
- const target = event.target as HTMLTextAreaElement;
- setCCDAmount(target.value);
- }, []);
-
- const changeMaxExecutionEnergyHandler = useCallback((event: ChangeEvent) => {
- const target = event.target as HTMLTextAreaElement;
- setMaxContractExecutionEnergy(target.value);
- }, []);
-
- const changeContractNameHandler = useCallback((event: ChangeEvent) => {
- setTransactionErrorInit('');
- const target = event.target as HTMLTextAreaElement;
- setContractName(target.value);
- }, []);
-
- const changeInputParameterFieldHandler = useCallback((event: ChangeEvent) => {
- setParsingError('');
- setTransactionErrorInit('');
- const target = event.target as HTMLTextAreaElement;
- setInputParameter(target.value);
- }, []);
-
- const changeInputParameterTextAreaHandler = useCallback((event: ChangeEvent) => {
- setParsingError('');
- setTransactionErrorInit('');
- const inputTextArea = inputParameterTextAreaRef.current as unknown as HTMLTextAreaElement;
- inputTextArea?.setAttribute('style', `height:${inputTextArea.scrollHeight}px;overflow-y:hidden;`);
- const target = event.target as HTMLTextAreaElement;
-
- try {
- JSON.parse(target.value);
- } catch (e) {
- setParsingError((e as Error).message);
- return;
- }
-
- setInputParameter(JSON.stringify(JSON.parse(target.value)));
- }, []);
+ const [embeddedModuleSchemaBase64Init, setEmbeddedModuleSchemaBase64Init] = useState(undefined);
// Refresh accountInfo periodically.
// eslint-disable-next-line consistent-return
useEffect(() => {
if (connection && client && account) {
const interval = setInterval(() => {
- console.log('refreshing_accountInfo');
- client
- .getAccountInfo(new AccountAddress(account))
- .then((value) => {
- if (value !== undefined) {
- setAccountBalance(value.accountAmount.toString());
- setAccountExistsOnNetwork(true);
- } else {
- setAccountExistsOnNetwork(false);
- }
- setViewErrorAccountInfo('');
- })
- .catch((e) => {
- setAccountBalance('');
- setViewErrorAccountInfo((e as Error).message.replaceAll('%20', ' '));
- setAccountExistsOnNetwork(false);
- });
+ getAccountInfo(client, account, setAccountBalance, setAccountExistsOnNetwork, setViewErrorAccountInfo);
}, REFRESH_INTERVAL.asMilliseconds());
return () => clearInterval(interval);
}
}, [connection, account, client]);
- // Refresh moduleReference periodically.
- // eslint-disable-next-line consistent-return
- useEffect(() => {
- if (connection && client && account && txHashDeploy !== '') {
- const interval = setInterval(() => {
- console.log('refreshing_moduleReference');
- client
- .getBlockItemStatus(txHashDeploy)
- .then((report) => {
- if (report !== undefined) {
- setViewErrorModuleReference('');
- if (
- report.status === 'finalized' &&
- report.outcome.summary.type === TransactionSummaryType.AccountTransaction &&
- report.outcome.summary.transactionType === TransactionKindString.DeployModule
- ) {
- setModuleReferenceDeployed(report.outcome.summary.moduleDeployed.contents);
- }
- }
- })
- .catch((e) => {
- setModuleReferenceDeployed('');
- setViewErrorModuleReference((e as Error).message);
- });
- }, REFRESH_INTERVAL.asMilliseconds());
- return () => clearInterval(interval);
- }
- }, [connection, account, client, txHashDeploy]);
-
- // Refresh smartContractIndex periodically.
- // eslint-disable-next-line consistent-return
- useEffect(() => {
- if (connection && client && account && txHashInit !== '') {
- const interval = setInterval(() => {
- console.log('refreshing_smartContractIndex');
- client
- .getBlockItemStatus(txHashInit)
- .then((report) => {
- if (report !== undefined) {
- setViewErrorModuleReference('');
- if (report.status === 'finalized') {
- if (
- report.outcome.summary.type === TransactionSummaryType.AccountTransaction &&
- report.outcome.summary.transactionType === TransactionKindString.InitContract
- ) {
- setSmartContractIndex(
- report.outcome.summary.contractInitialized.address.index.toString()
- );
- } else {
- setSmartContractIndexError('Contract initialization failed');
- }
- }
- }
- })
- .catch((e) => {
- setModuleReferenceDeployed('');
- setViewErrorModuleReference((e as Error).message);
- });
- }, REFRESH_INTERVAL.asMilliseconds());
- return () => clearInterval(interval);
- }
- }, [connection, account, client, txHashInit]);
-
- useEffect(() => {
- if (connection && client && account && moduleReferenceCalculated) {
- client
- .getModuleSource(new ModuleReference(moduleReferenceCalculated))
- .then((value) => {
- if (value === undefined) {
- setIsModuleReferenceAlreadyDeployedStep1(false);
- } else {
- setIsModuleReferenceAlreadyDeployedStep1(true);
- }
- })
- .catch(() => {
- setIsModuleReferenceAlreadyDeployedStep1(false);
- });
- }
- }, [connection, account, client, moduleReferenceCalculated]);
-
- useEffect(() => {
- if (connection && client && account && moduleReference) {
- client
- .getModuleSource(new ModuleReference(moduleReference))
- .then((value) => {
- if (value === undefined) {
- setIsModuleReferenceAlreadyDeployedStep2(false);
- } else {
- setIsModuleReferenceAlreadyDeployedStep2(true);
- }
- })
- .catch(() => {
- setIsModuleReferenceAlreadyDeployedStep2(false);
- });
- }
- }, [connection, account, client, moduleReference]);
-
- useEffect(() => {
- if (
- moduleReference !== '' &&
- moduleReferenceCalculated !== '' &&
- moduleReferenceCalculated !== moduleReference
- ) {
- setShouldWarnDifferenceModuleReferences(true);
- } else {
- setShouldWarnDifferenceModuleReferences(false);
- }
- }, [moduleReference, moduleReferenceCalculated]);
-
- useEffect(() => {
- if (inputParameterTemplate !== '' && hasInputParameter === false) {
- setShouldWarnInputParameterInSchemaIgnored(true);
- } else {
- setShouldWarnInputParameterInSchemaIgnored(false);
- }
- }, [inputParameterTemplate, hasInputParameter]);
-
- useEffect(() => {
- setSchemaError('');
- setInputParameterTemplate('');
-
- let template = '';
-
- if (contractName !== '' && (useModuleFromStep1 || uploadedModuleSchemaBase64 !== '')) {
- try {
- const inputParamterTypeSchemaBuffer = getInitContractParameterSchema(
- toBuffer(useModuleFromStep1 ? embeddedModuleSchemaBase64 : uploadedModuleSchemaBase64, 'base64'),
- contractName,
- 2
- );
-
- template = displayTypeSchemaTemplate(inputParamterTypeSchemaBuffer);
-
- setInputParameterTemplate(template);
- } catch (e) {
- if (useModuleFromStep1) {
- setSchemaError(
- `Could not get embedded input parameter schema from the uploaded module. \nUncheck "Use Module from Step 1" checkbox to manually upload a schema. Original error: ${e}`
- );
- } else {
- setSchemaError(`Could not get input parameter schema from uploaded schema. Original error: ${e}`);
- }
- }
- }
-
- if (dropDown === 'array') {
- const element = inputParameterTextAreaRef.current as unknown as HTMLSelectElement;
- element.value = getArrayExample(template);
- } else if (dropDown === 'object') {
- const element = inputParameterTextAreaRef.current as unknown as HTMLSelectElement;
- element.value = getObjectExample(template);
- }
- }, [hasInputParameter, useModuleFromStep1, contractName, uploadedModuleSchemaBase64, dropDown]);
-
useEffect(() => {
if (connection && client && account) {
- client
- .getAccountInfo(new AccountAddress(account))
- .then((value) => {
- if (value !== undefined) {
- setAccountBalance(value.accountAmount.toString());
- setAccountExistsOnNetwork(true);
- } else {
- setAccountExistsOnNetwork(false);
- }
- setViewErrorAccountInfo('');
- })
- .catch((e) => {
- setViewErrorAccountInfo((e as Error).message.replaceAll('%20', ' '));
- setAccountBalance('');
- setAccountExistsOnNetwork(false);
- });
+ getAccountInfo(client, account, setAccountBalance, setAccountExistsOnNetwork, setViewErrorAccountInfo);
}
}, [connection, account, client]);
+ useEffect(() => {
+ select();
+ }, []);
+
return (
-
- {activeConnectorError && (
-
- Connector Error: {activeConnectorError}.
-
- )}
- {!activeConnectorError && !isWaitingForTransaction && activeConnectorType && !activeConnector && (
+ {activeConnectorError &&
Connector Error: {activeConnectorError}. }
+ {!activeConnectorError && activeConnectorType && !activeConnector && (
Loading connector...
)}
- {connectError && (
-
- Connect Error: {connectError}.
-
- )}
- {!connection && !isWaitingForTransaction && activeConnectorType && activeConnector && (
-
-
- {isConnecting && 'Connecting...'}
- {!isConnecting && activeConnectorType === BROWSER_WALLET && 'Connect Browser Wallet'}
-
-
+ {connectError &&
Connect Error: {connectError}. }
+ {!isConnected && (
+
{
+ connect();
+ }}
+ >
+ Connect To Browser Wallet
+
)}
{connection && !accountExistsOnNetwork && (
<>
@@ -461,639 +119,48 @@ export default function Main(props: ConnectionProps) {
Error: {viewErrorAccountInfo}.
)}
- {viewErrorModuleReference && (
-
- Error: {viewErrorModuleReference}.
-
- )}
Connected account:
-
+
- Your account balance:
- {accountBalance.replace(/(\d)(?=(\d\d\d\d\d\d)+(?!\d))/g, '$1.')} CCD
-
-
- Upload Smart Contract Module File (e.g. myContract.wasm.v1):
-
-
- {
- setUploadError('');
- setModuleReferenceDeployed('');
- setTransactionErrorDeploy('');
- setTxHashDeploy('');
-
- const hTMLInputElement =
- moduleFileRef.current as unknown as HTMLInputElement;
-
- if (
- hTMLInputElement.files !== undefined &&
- hTMLInputElement.files !== null &&
- hTMLInputElement.files.length > 0
- ) {
- const file = hTMLInputElement.files[0];
- const arrayBuffer = await file.arrayBuffer();
-
- const module = btoa(
- new Uint8Array(arrayBuffer).reduce((data, byte) => {
- return data + String.fromCharCode(byte);
- }, '')
- );
-
- setBase64Module(module);
- setModuleReferenceCalculated(
- Buffer.from(sha256([new Uint8Array(arrayBuffer)])).toString('hex')
- );
-
- // Concordium's tooling create versioned modules e.g. `.wasm.v1` now.
- // Unversioned modules `.wasm` cannot be created by Concordium's tooling anymore.
- // If the module is versioned, the first 8 bytes are the version, followed by the `magicValue` below.
- // If the module is an old unversioned one, the module starts with the `magicValue` below.
- const magicValue = new Uint8Array([0x00, 0x61, 0x73, 0x6d]);
- let uploadedModuleFirst4Bytes = new Uint8Array([]);
-
- if (arrayBuffer.byteLength >= 4) {
- uploadedModuleFirst4Bytes = new Uint8Array(arrayBuffer).subarray(
- 0,
- 4
- );
- } else {
- setUploadError(`You might have not uploaded a Concordium module.`);
- }
-
- // If we have an unversioned module, we remove no bytes.
- // If we have a versioned module, we remove 8 bytes (remove the versioned 8 bytes at the beginning)
- const slice = arraysEqual(uploadedModuleFirst4Bytes, magicValue)
- ? 0
- : 8;
-
- let wasmModule;
- try {
- wasmModule = await WebAssembly.compile(arrayBuffer.slice(slice));
- } catch (e) {
- setUploadError(
- `You might have not uploaded a Concordium module. Original error: ${
- (e as Error).message
- }`
- );
- }
-
- if (wasmModule) {
- const moduleFunctions = WebAssembly.Module.exports(wasmModule);
-
- const contractNames = [];
- for (let i = 0; i < moduleFunctions.length; i += 1) {
- if (moduleFunctions[i].name.slice(0, 5) === 'init_') {
- contractNames.push(moduleFunctions[i].name.slice(5));
- }
- }
- setContracts(contractNames);
-
- const customSection = WebAssembly.Module.customSections(
- wasmModule,
- 'concordium-schema'
- );
-
- const schema = new Uint8Array(customSection[0]);
-
- const moduleSchemaBase64Embedded = btoa(
- new Uint8Array(schema).reduce((data, byte) => {
- return data + String.fromCharCode(byte);
- }, '')
- );
-
- setEmbeddedModuleSchemaBase64(moduleSchemaBase64Embedded);
- } else {
- setUploadError('Upload module file is undefined');
- }
- }
- }}
- />
-
-
-
- {uploadError !== '' && (
-
- Error: {uploadError}.
-
- )}
-
- {base64Module && moduleReferenceCalculated && (
- <>
-
- Calculated module reference:
-
{moduleReferenceCalculated}
-
-
- Module in base64:
-
{base64Module.toString().slice(0, 30)} ...
-
- {isModuleReferenceAlreadyDeployedStep1 && (
-
- Module reference already deployed.
-
- )}
-
- {!isModuleReferenceAlreadyDeployedStep1 && (
- {
- setTxHashDeploy('');
- setTransactionErrorDeploy('');
- const tx = deploy(connection, account, base64Module);
- tx.then((txHash) => {
- setModuleReferenceDeployed('');
- setTxHashDeploy(txHash);
- }).catch((err: Error) =>
- setTransactionErrorDeploy((err as Error).message)
- );
- }}
- >
- Deploy smart contract module
-
- )}
-
-
- >
- )}
- {!txHashDeploy && transactionErrorDeploy && (
-
- Error: {transactionErrorDeploy}.
-
- )}
- {txHashDeploy && (
- <>
-
-
-
- CCDScan will take a moment to pick up the above transaction, hence the above
- link will work in a bit.
-
-
- Deployed module reference will appear below once the transaction is
- finalized.
-
- >
- )}
- {moduleReferenceDeployed && (
- <>
-
-
-
- Module Reference deployed:
-
{moduleReferenceDeployed}
-
- >
- )}
-
-
-
-
-
-
- {
- setModuleReferenceError('');
- setModuleReference('');
- setContractName('');
- setUploadedModuleSchemaBase64('');
-
- const checkboxElement =
- useModuleReferenceFromStep1Ref.current as unknown as HTMLInputElement;
-
- setUseModuleFromStep1(checkboxElement.checked);
-
- const element =
- moduleReferenceRef.current as unknown as HTMLTextAreaElement;
-
- element.value = '';
-
- if (
- checkboxElement.checked &&
- moduleReferenceDeployed === '' &&
- moduleReferenceCalculated === ''
- ) {
- setModuleReferenceError('Module reference is not set in step 1');
- }
-
- if (
- checkboxElement.checked &&
- (moduleReferenceDeployed !== '' || moduleReferenceCalculated !== '')
- ) {
- element.value =
- moduleReferenceDeployed !== ''
- ? moduleReferenceDeployed
- : moduleReferenceCalculated;
-
- setModuleReference(
- moduleReferenceDeployed !== ''
- ? moduleReferenceDeployed
- : moduleReferenceCalculated
- );
-
- setDisplayContracts(contracts);
- setContractName(contracts[0]);
- }
- }}
- />
- {' Use Module from Step 1'}
-
-
- {useModuleFromStep1 && (
- <>
-
-
-
- This checkbox autofilled the module reference
, the{' '}
- smart contract name
, and the{' '}
- input parameter schema
from the module in step1.
-
-
-
- Uncheck this box, if you want to manually fill in a{' '}
- module reference
, the smart contract name
, or
- an input parameter schema
.
-
-
-
- Uncheck and check this box again, if you want to load a
- new module from step 1.
-
-
-
- >
- )}
- {moduleReferenceError && (
-
- Error: {moduleReferenceError}.
-
- )}
-
- Module Reference:
-
-
-
-
- Max Execution Energy:
-
-
-
- {useModuleFromStep1 &&
- displayContracts.length > 0 &&
- (moduleReferenceDeployed !== '' || moduleReferenceCalculated !== '') ? (
-
- Smart Contract Name:
-
-
- {displayContracts?.map((contract) => (
- {contract}
- ))}
-
-
- ) : (
-
- Smart Contract Name:
-
-
-
- )}
- {moduleReferenceLengthError && (
-
- Error: {moduleReferenceLengthError}.
-
- )}
-
-
-
-
- {
- setIsPayable(!isPayable);
- }}
- />
- {' Is Payable'}
-
-
- {isPayable && (
-
-
- CCD amount (micro):
-
-
-
-
- )}
-
-
-
- {
- setParsingError('');
- setInputParameter('');
- setUploadedModuleSchemaBase64('');
- setTransactionErrorInit('');
- setDropDown('number');
- setHasInputParameter(!hasInputParameter);
- setInputParameterTemplate('');
- setSchemaError('');
- }}
- />
- {' Has Input Parameter'}
-
-
- {hasInputParameter && (
-
- {!useModuleFromStep1 && (
- <>
-
- Upload Smart Contract Module Schema File (e.g. schema.bin):
-
-
- {
- setUploadError('');
-
- const hTMLInputElement =
- schemaFileRef.current as unknown as HTMLInputElement;
-
- if (
- hTMLInputElement.files !== undefined &&
- hTMLInputElement.files !== null &&
- hTMLInputElement.files.length > 0
- ) {
- const file = hTMLInputElement.files[0];
- const arrayBuffer = await file.arrayBuffer();
-
- const schema = btoa(
- new Uint8Array(arrayBuffer).reduce((data, byte) => {
- return data + String.fromCharCode(byte);
- }, '')
- );
+ {accountBalance && (
+ <>
+ Your account balance:
+ {accountBalance.replace(/(\d)(?=(\d\d\d\d\d\d)+(?!\d))/g, '$1.')} CCD
+ >
+ )}
- setUploadedModuleSchemaBase64(schema);
- } else {
- setUploadError2('Upload schema file is undefined');
- }
- }}
- />
-
-
-
-
- {uploadedModuleSchemaBase64 && (
-
- Schema in base64:
-
- {uploadedModuleSchemaBase64.toString().slice(0, 30)} ...
-
-
- )}
- >
- )}
- {uploadError2 !== '' && (
-
- Error: {uploadError2}.
-
- )}
- {schemaError !== '' && (
-
- Error: {schemaError}.
-
- )}
-
- {inputParameterTemplate && (
-
- Input Parameter Template:
-
- {JSON.stringify(JSON.parse(inputParameterTemplate), undefined, 2)}
-
-
- )}
-
- Select input parameter type:
-
-
- number
- string
- JSON object
- array
-
-
-
- {(dropDown === 'object' || dropDown === 'array') && (
-
- Add your input parameter ({dropDown}):
-
- {dropDown === 'array' && (
-
- )}
- {dropDown === 'object' && (
-
- )}
-
- )}
- {(dropDown === 'string' || dropDown === 'number') && (
-
- Add your input parameter ({dropDown}):
-
-
-
- )}
- {parsingError && (
-
- Error: {parsingError}.
-
- )}
-
- )}
-
-
- {
- setTxHashInit('');
- setSmartContractIndexError('');
- setSmartContractIndex('');
- setTransactionErrorInit('');
- const tx = initialize(
- connection,
- account,
- isModuleReferenceAlreadyDeployedStep2,
- moduleReference,
- inputParameter,
- contractName,
- hasInputParameter,
- useModuleFromStep1,
- useModuleFromStep1
- ? embeddedModuleSchemaBase64
- : uploadedModuleSchemaBase64,
- dropDown,
- maxContractExecutionEnergy,
- cCDAmount
- );
- tx.then(setTxHashInit).catch((err: Error) =>
- setTransactionErrorInit((err as Error).message)
- );
- }}
- >
- Initialize Smart Contract
-
-
-
- {shouldWarnDifferenceModuleReferences && (
-
- Warning: Module references in step 1 and step 2 are different.
-
- )}
- {shouldWarnInputParameterInSchemaIgnored && (
-
- Warning: Input parameter schema found but "Has Input Parameter"
- checkbox is unchecked.
-
- )}
- {!txHashInit && transactionErrorInit && (
-
- Error: {transactionErrorInit}.
-
- )}
- {txHashInit && (
- <>
-
-
-
- CCDScan will take a moment to pick up the above transaction, hence the above
- link will work in a bit.
-
-
- The smart contract index will appear below once the transaction is
- finalized.
-
- >
- )}
-
-
- {smartContractIndexError !== '' && (
-
- Error: {smartContractIndexError}.
-
- )}
- {smartContractIndex !== '' && (
-
- Smart Contract Inedex:
-
{smartContractIndex}
-
- )}
-
+
+
+
+
+
+
+
+
)}
diff --git a/front-end-tools/src/Root.tsx b/front-end-tools/src/Root.tsx
index 272b6d59..ef88b0f4 100644
--- a/front-end-tools/src/Root.tsx
+++ b/front-end-tools/src/Root.tsx
@@ -6,7 +6,7 @@ import Main from './Main';
import { version } from '../package.json';
/**
- * Connect to wallet, setup application state context, and render children when the wallet API is ready for use.
+ * Select mainnet/testnet and display WithWalletConnector component for respective network.
*/
export default function Root() {
const [isTestnet, setIsTestnet] = useState(true);
@@ -34,9 +34,17 @@ export default function Root() {
Mainnet
-
- {(props) => }
-
+ {/* Changes to the network value will remove the activeConnector. We switch between components here without changing the network value. */}
+ {isTestnet && (
+
+ {(props) => }
+
+ )}
+ {!isTestnet && (
+
+ {(props) => }
+
+ )}
);
diff --git a/front-end-tools/src/WalletConnectorTypeButton.tsx b/front-end-tools/src/WalletConnectorTypeButton.tsx
deleted file mode 100644
index b02cbeba..00000000
--- a/front-end-tools/src/WalletConnectorTypeButton.tsx
+++ /dev/null
@@ -1,48 +0,0 @@
-import React, { useCallback } from 'react';
-import {
- ConnectorType,
- useWalletConnectorSelector,
- WalletConnection,
- WalletConnectionProps,
-} from '@concordium/react-components';
-
-function connectorTypeStyle(isSelected: boolean, isConnected: boolean) {
- if (isConnected) {
- return { backgroundColor: '#823030', border: '1px solid #520C0C' };
- }
- if (isSelected) {
- return { backgroundColor: '#174039', border: '1px solid #0c221f' };
- }
- return {};
-}
-
-interface Props extends WalletConnectionProps {
- connectorType: ConnectorType;
- connectorName: string;
- setWaitingForUser: (v: boolean) => void;
- connection: WalletConnection | undefined;
-}
-
-export function WalletConnectionTypeButton(props: Props) {
- const { connectorType, connectorName, setWaitingForUser, connection } = props;
- const { isSelected, isConnected, isDisabled, select } = useWalletConnectorSelector(
- connectorType,
- connection,
- props
- );
- const onClick = useCallback(() => {
- setWaitingForUser(false);
- select();
- }, [select]);
- return (
-
- {isConnected ? `Disconnect ${connectorName}` : `Use ${connectorName}`}
-
- );
-}
diff --git a/front-end-tools/src/components/Box.tsx b/front-end-tools/src/components/Box.tsx
new file mode 100644
index 00000000..9845134c
--- /dev/null
+++ b/front-end-tools/src/components/Box.tsx
@@ -0,0 +1,20 @@
+import React, { PropsWithChildren } from 'react';
+
+type BoxProps = PropsWithChildren<{
+ header: string;
+}>;
+
+/**
+ * A component that creates a box with a header and background/borderLine styling.
+ */
+export default function Box(props: BoxProps) {
+ const { children, header } = props;
+
+ return (
+
+ {header}
+ {children}
+
+
+ );
+}
diff --git a/front-end-tools/src/components/CCDScanLinks.tsx b/front-end-tools/src/components/CCDScanLinks.tsx
new file mode 100644
index 00000000..6aff5585
--- /dev/null
+++ b/front-end-tools/src/components/CCDScanLinks.tsx
@@ -0,0 +1,69 @@
+import React from 'react';
+
+function ccdScanUrl(isTestnet: boolean): string {
+ return `https://${isTestnet ? `testnet.` : ``}ccdscan.io`;
+}
+
+interface TxHashLinkProps {
+ isTestnet: boolean;
+ txHash: string;
+ message: string;
+}
+
+/**
+ * A component that displays the CCDScan link of a transaction hash.
+ * A message at the bottom can be used to add some custom description to the link.
+ * If `isTestnet` is true, the testnet CCDScan link is displayed.
+ * If `isTestnet` is false, the mainnet CCDScan link is displayed.
+ */
+export const TxHashLink = function TxHashLink(props: TxHashLinkProps) {
+ const { isTestnet, txHash, message } = props;
+
+ return (
+ <>
+
+
+
+ CCDScan will take a moment to pick up the above transaction, hence the above link will work in a bit.
+
+ {message}
+ >
+ );
+};
+
+interface AccountLinkProps {
+ isTestnet: boolean;
+ account: string;
+}
+
+/**
+ * A component that displays the CCDScan link to an account address.
+ * If `isTestnet` is true, the testnet CCDScan link is displayed.
+ * If `isTestnet` is false, the mainnet CCDScan link is displayed.
+ */
+export const AccountLink = function AccountLink(props: AccountLinkProps) {
+ const { isTestnet, account } = props;
+
+ return (
+
+ );
+};
diff --git a/front-end-tools/src/components/DeployComponent.tsx b/front-end-tools/src/components/DeployComponent.tsx
new file mode 100644
index 00000000..78b1bcf7
--- /dev/null
+++ b/front-end-tools/src/components/DeployComponent.tsx
@@ -0,0 +1,283 @@
+import React, { useEffect, useState } from 'react';
+import { useForm } from 'react-hook-form';
+import { Alert, Button, Form } from 'react-bootstrap';
+import { Buffer } from 'buffer';
+
+import { WalletConnection } from '@concordium/react-components';
+import {
+ ModuleReference,
+ ConcordiumGRPCClient,
+ sha256,
+ TransactionSummaryType,
+ TransactionKindString,
+} from '@concordium/web-sdk';
+
+import { TxHashLink } from './CCDScanLinks';
+import Box from './Box';
+import { deploy } from '../writing_to_blockchain';
+import { arraysEqual } from '../utils';
+import { REFRESH_INTERVAL } from '../constants';
+
+interface ConnectionProps {
+ account: string;
+ connection: WalletConnection;
+ client: ConcordiumGRPCClient | undefined;
+ isTestnet: boolean;
+ setContracts: (contracts: string[]) => void;
+ setEmbeddedModuleSchemaBase64Init: (embeddedModuleSchemaBase64Init: string) => void;
+ setModuleReferenceDeployed: (moduleReferenceDeployed: string | undefined) => void;
+ setModuleReferenceCalculated: (moduleReferenceCalculated: string) => void;
+ moduleReferenceCalculated: string | undefined;
+}
+
+/**
+ * A component that manages the input fields and corresponding state to deploy a new smart contract wasm module on chain.
+ * This components creates a `DeployModule` transaction.
+ */
+export default function DeployComponenet(props: ConnectionProps) {
+ const {
+ isTestnet,
+ client,
+ connection,
+ account,
+ setContracts,
+ setModuleReferenceDeployed,
+ setModuleReferenceCalculated,
+ moduleReferenceCalculated,
+ setEmbeddedModuleSchemaBase64Init,
+ } = props;
+
+ type FormType = {
+ file: FileList | undefined;
+ };
+ const form = useForm({ mode: 'all' });
+
+ const [transactionErrorDeploy, setTransactionErrorDeploy] = useState(undefined);
+ const [uploadError, setUploadError] = useState(undefined);
+ const [isModuleReferenceAlreadyDeployedStep1, setIsModuleReferenceAlreadyDeployedStep1] = useState(false);
+ const [txHashDeploy, setTxHashDeploy] = useState(undefined);
+ const [base64Module, setBase64Module] = useState(undefined);
+ const [transactionOutcome, setTransactionOutcome] = useState(undefined);
+
+ // Refresh moduleReference periodically.
+ // eslint-disable-next-line consistent-return
+ useEffect(() => {
+ if (connection && client && txHashDeploy !== undefined) {
+ const interval = setInterval(() => {
+ client
+ .getBlockItemStatus(txHashDeploy)
+ .then((report) => {
+ if (report !== undefined && report.status === 'finalized') {
+ if (
+ report.outcome.summary.type === TransactionSummaryType.AccountTransaction &&
+ report.outcome.summary.transactionType === TransactionKindString.DeployModule
+ ) {
+ setTransactionOutcome('Success');
+ setModuleReferenceDeployed(report.outcome.summary.moduleDeployed.contents);
+ clearInterval(interval);
+ } else {
+ setTransactionOutcome('Fail');
+ clearInterval(interval);
+ }
+ }
+ })
+ .catch((e) => {
+ setModuleReferenceDeployed(undefined);
+ setTransactionOutcome(`Fail; Error: ${(e as Error).message}`);
+ clearInterval(interval);
+ });
+ }, REFRESH_INTERVAL.asMilliseconds());
+ return () => clearInterval(interval);
+ }
+ }, [connection, client, txHashDeploy]);
+
+ useEffect(() => {
+ if (connection && client && moduleReferenceCalculated) {
+ client
+ .getModuleSource(new ModuleReference(moduleReferenceCalculated))
+ .then((value) => {
+ if (value === undefined) {
+ setIsModuleReferenceAlreadyDeployedStep1(false);
+ } else {
+ setIsModuleReferenceAlreadyDeployedStep1(true);
+ }
+ })
+ .catch(() => {
+ setIsModuleReferenceAlreadyDeployedStep1(false);
+ });
+ }
+ }, [connection, client, moduleReferenceCalculated]);
+
+ function onSubmit() {
+ setTxHashDeploy(undefined);
+ setTransactionErrorDeploy(undefined);
+ setTransactionOutcome(undefined);
+
+ // Send deploy transaction
+
+ const tx = deploy(connection, account, base64Module);
+ tx.then((txHash) => {
+ setModuleReferenceDeployed(undefined);
+ setTxHashDeploy(txHash);
+ }).catch((err: Error) => setTransactionErrorDeploy((err as Error).message));
+ }
+
+ return (
+
+
+ Upload Smart Contract Module File (e.g. myContract.wasm.v1)
+ {
+ const register = form.register('file');
+
+ register.onChange(e);
+
+ setUploadError(undefined);
+ setModuleReferenceDeployed(undefined);
+ setTransactionErrorDeploy(undefined);
+ setTxHashDeploy(undefined);
+
+ const files = form.getValues('file');
+
+ if (files !== undefined && files !== null && files.length > 0) {
+ const file = files[0];
+ const arrayBuffer = await file.arrayBuffer();
+
+ // Use `reduce` to be able to convert large modules.
+ const module = btoa(
+ new Uint8Array(arrayBuffer).reduce((data, byte) => {
+ return data + String.fromCharCode(byte);
+ }, '')
+ );
+
+ setBase64Module(module);
+ setModuleReferenceCalculated(
+ Buffer.from(sha256([new Uint8Array(arrayBuffer)])).toString('hex')
+ );
+
+ // Concordium's tooling create versioned modules e.g. `.wasm.v1` now.
+ // Unversioned modules `.wasm` cannot be created by Concordium's tooling anymore.
+ // If the module is versioned, the first 4 bytes are the version, the next 4 bytes are the length, followed by the `magicValue` below.
+ // If the module is an old unversioned one, the module starts with the `magicValue` below.
+ // The `magicValue` is the magic value for Wasm modules as specified by the Wasm spec.
+ const magicValue = new Uint8Array([0x00, 0x61, 0x73, 0x6d]);
+ let uploadedModuleFirst4Bytes = new Uint8Array([]);
+
+ if (arrayBuffer.byteLength >= 4) {
+ uploadedModuleFirst4Bytes = new Uint8Array(arrayBuffer).subarray(0, 4);
+ } else {
+ setUploadError(
+ `You might have not uploaded a valid Wasm module. Byte length of a Wasm module needs to be at least 4.`
+ );
+ }
+
+ // If we have an unversioned module, we remove no bytes.
+ // If we have a versioned module, we remove 8 bytes at the beginning (version and length information).
+ const slice = arraysEqual(uploadedModuleFirst4Bytes, magicValue) ? 0 : 8;
+
+ let wasmModule;
+ try {
+ wasmModule = await WebAssembly.compile(arrayBuffer.slice(slice));
+ } catch (err) {
+ setUploadError(
+ `You might have not uploaded a Concordium module. Original error: ${
+ (err as Error).message
+ }`
+ );
+ }
+
+ if (wasmModule) {
+ const moduleFunctions = WebAssembly.Module.exports(wasmModule);
+
+ const contractNames = [];
+ for (let i = 0; i < moduleFunctions.length; i += 1) {
+ if (moduleFunctions[i].name.startsWith('init_')) {
+ contractNames.push(moduleFunctions[i].name.slice(5));
+ }
+ }
+ setContracts(contractNames);
+
+ const customSection = WebAssembly.Module.customSections(
+ wasmModule,
+ 'concordium-schema'
+ );
+
+ const schema = new Uint8Array(customSection[0]);
+
+ // Use `reduce` to be able to convert large schema.
+ const moduleSchemaBase64Embedded = btoa(
+ new Uint8Array(schema).reduce((data, byte) => {
+ return data + String.fromCharCode(byte);
+ }, '')
+ );
+
+ setEmbeddedModuleSchemaBase64Init(moduleSchemaBase64Embedded);
+ } else {
+ setUploadError('Upload module file is undefined');
+ }
+ }
+ }}
+ />
+
+
+ {uploadError !== undefined && Error: {uploadError}. }
+
+ {base64Module && moduleReferenceCalculated && (
+ <>
+
+ Calculated module reference:
+
{moduleReferenceCalculated}
+
+
+ Module in base64:
+
{base64Module.toString().slice(0, 30)} ...
+
+ {isModuleReferenceAlreadyDeployedStep1 && (
+ Module is already deployed.
+ )}
+
+ {!isModuleReferenceAlreadyDeployedStep1 && (
+
+ Deploy smart contract module
+
+ )}
+
+
+ >
+ )}
+
+
+ {!txHashDeploy && transactionErrorDeploy && (
+ Error: {transactionErrorDeploy}.
+ )}
+ {txHashDeploy && (
+
+ )}
+ {transactionOutcome === 'Success' && (
+ <>
+
+
+ Outcome of transaction:
+
{transactionOutcome}
+
+ >
+ )}
+ {transactionOutcome !== undefined && transactionOutcome !== 'Success' && (
+ <>
+
+ Outcome of transaction:
+
+ Error: {transactionOutcome}.
+ >
+ )}
+
+ );
+}
diff --git a/front-end-tools/src/components/InitComponent.tsx b/front-end-tools/src/components/InitComponent.tsx
new file mode 100644
index 00000000..da0c7a44
--- /dev/null
+++ b/front-end-tools/src/components/InitComponent.tsx
@@ -0,0 +1,655 @@
+import React, { useEffect, useMemo, useState } from 'react';
+import { useForm, useWatch } from 'react-hook-form';
+import Select from 'react-select';
+import { Alert, Button, Form, Row } from 'react-bootstrap';
+
+import { WalletConnection } from '@concordium/react-components';
+import {
+ ModuleReference,
+ TransactionKindString,
+ TransactionSummaryType,
+ displayTypeSchemaTemplate,
+ toBuffer,
+ ConcordiumGRPCClient,
+ getInitContractParameterSchema,
+} from '@concordium/web-sdk';
+
+import { TxHashLink } from './CCDScanLinks';
+import Box from './Box';
+import { initialize } from '../writing_to_blockchain';
+import { getObjectExample, getArrayExample } from '../utils';
+import { REFRESH_INTERVAL, INPUT_PARAMETER_TYPES_OPTIONS, REG_MODULE_REF } from '../constants';
+
+interface ConnectionProps {
+ isTestnet: boolean;
+ account: string;
+ connection: WalletConnection;
+ client: ConcordiumGRPCClient | undefined;
+ contracts: string[];
+ embeddedModuleSchemaBase64: undefined | string;
+ moduleReferenceCalculated: undefined | string;
+ moduleReferenceDeployed: undefined | string;
+}
+
+/**
+ * A component that manages the input fields and corresponding state to initialize a new smart contract instance on chain.
+ * This components creates an `InitContract` transaction.
+ */
+export default function InitComponent(props: ConnectionProps) {
+ const {
+ isTestnet,
+ account,
+ connection,
+ client,
+ moduleReferenceCalculated,
+ moduleReferenceDeployed,
+ embeddedModuleSchemaBase64,
+ contracts,
+ } = props;
+
+ type FormType = {
+ moduleReference: string | undefined;
+ smartContractName: string | undefined;
+ file: FileList | undefined;
+ useModuleReferenceFromStep1: boolean;
+ inputParameterType: string | undefined;
+ inputParameter: string | undefined;
+ hasInputParameter: boolean;
+ maxExecutionEnergy: number;
+ isPayable: boolean;
+ cCDAmount: number;
+ };
+
+ const form = useForm({ mode: 'all' });
+
+ const [
+ useModuleReferenceFromStep1,
+ smartContractName,
+ inputParameterType,
+ isPayable,
+ hasInputParameter,
+ moduleReference,
+ ] = useWatch({
+ control: form.control,
+ name: [
+ 'useModuleReferenceFromStep1',
+ 'smartContractName',
+ 'inputParameterType',
+ 'isPayable',
+ 'hasInputParameter',
+ 'moduleReference',
+ ],
+ });
+
+ const [transactionError, setTransactionError] = useState(undefined);
+
+ const [uploadError2, setUploadError2] = useState(undefined);
+ const [parsingError, setParsingError] = useState(undefined);
+ const [smartContractIndexError, setSmartContractIndexError] = useState(undefined);
+ const [moduleReferenceError, setModuleReferenceError] = useState(undefined);
+ const [moduleReferenceLengthError, setModuleReferenceLengthError] = useState(undefined);
+ const [schemaError, setSchemaError] = useState(undefined);
+
+ const [isModuleReferenceAlreadyDeployedStep2, setIsModuleReferenceAlreadyDeployedStep2] = useState(false);
+
+ const [txHash, setTxHash] = useState(undefined);
+
+ const [uploadedModuleSchemaBase64, setUploadedModuleSchemaBase64] = useState(undefined);
+
+ const [smartContractIndex, setSmartContractIndex] = useState(undefined);
+ const [inputParameterTemplate, setInputParameterTemplate] = useState(undefined);
+ const [displayContracts, setDisplayContracts] = useState([]);
+
+ // Refresh smartContractIndex periodically.
+ // eslint-disable-next-line consistent-return
+ useEffect(() => {
+ if (connection && client && txHash !== undefined) {
+ const interval = setInterval(() => {
+ client
+ .getBlockItemStatus(txHash)
+ .then((report) => {
+ if (report !== undefined) {
+ setSmartContractIndex(undefined);
+ if (report.status === 'finalized') {
+ if (
+ report.outcome.summary.type === TransactionSummaryType.AccountTransaction &&
+ report.outcome.summary.transactionType === TransactionKindString.InitContract
+ ) {
+ setSmartContractIndex(
+ report.outcome.summary.contractInitialized.address.index.toString()
+ );
+ clearInterval(interval);
+ } else {
+ setSmartContractIndexError('Contract initialization failed');
+ clearInterval(interval);
+ }
+ }
+ }
+ })
+ .catch((e) => {
+ setSmartContractIndex(undefined);
+ setSmartContractIndexError((e as Error).message);
+ clearInterval(interval);
+ });
+ }, REFRESH_INTERVAL.asMilliseconds());
+ return () => clearInterval(interval);
+ }
+ }, [connection, client, txHash]);
+
+ useEffect(() => {
+ if (connection && client && moduleReference) {
+ if (REG_MODULE_REF.test(moduleReference)) {
+ client
+ .getModuleSource(new ModuleReference(moduleReference))
+ .then((value) => {
+ if (value === undefined) {
+ setIsModuleReferenceAlreadyDeployedStep2(false);
+ } else {
+ setIsModuleReferenceAlreadyDeployedStep2(true);
+ }
+ })
+ .catch(() => {
+ setIsModuleReferenceAlreadyDeployedStep2(false);
+ });
+ }
+ }
+ }, [connection, client, moduleReference]);
+
+ const shouldWarnDifferenceModuleReferences = useMemo(() => {
+ if (
+ moduleReference !== undefined &&
+ moduleReferenceCalculated !== undefined &&
+ moduleReferenceCalculated !== moduleReference
+ ) {
+ return true;
+ }
+ return false;
+ }, [moduleReference, moduleReferenceCalculated]);
+
+ const shouldWarnInputParameterInSchemaIgnored = useMemo(() => {
+ if (inputParameterTemplate !== undefined && hasInputParameter === false) {
+ return true;
+ }
+ return false;
+ }, [inputParameterTemplate, hasInputParameter]);
+
+ useEffect(() => {
+ setSchemaError(undefined);
+ setInputParameterTemplate(undefined);
+
+ let initTemplate;
+
+ try {
+ if (smartContractName === undefined) {
+ throw new Error('Set smart contract name');
+ }
+
+ let schema = '';
+
+ const schemaFromModule = useModuleReferenceFromStep1
+ ? embeddedModuleSchemaBase64
+ : uploadedModuleSchemaBase64;
+
+ if (schemaFromModule !== undefined) {
+ schema = schemaFromModule;
+ }
+
+ const inputParamterTypeSchemaBuffer = getInitContractParameterSchema(
+ toBuffer(schema, 'base64'),
+ smartContractName,
+ 2
+ );
+
+ initTemplate = displayTypeSchemaTemplate(inputParamterTypeSchemaBuffer);
+
+ setInputParameterTemplate(initTemplate);
+ } catch (e) {
+ if (useModuleReferenceFromStep1) {
+ setSchemaError(
+ `Could not get embedded schema from the uploaded module. Uncheck "Use Module from Step 1" checkbox to manually upload a schema or uncheck "Has Input Paramter" checkbox if this entrypoint has no input parameter. Original error: ${e}`
+ );
+ } else {
+ setSchemaError(
+ `Could not get schema from uploaded schema. Uncheck "Has Input Paramter" checkbox if this entrypoint has no input parameter. Original error: ${e}`
+ );
+ }
+ }
+
+ if (initTemplate) {
+ if (inputParameterType === 'array') {
+ form.setValue('inputParameter', JSON.stringify(JSON.parse(initTemplate), undefined, 2));
+ } else if (inputParameterType === 'object') {
+ form.setValue('inputParameter', JSON.stringify(JSON.parse(initTemplate), undefined, 2));
+ }
+ }
+ }, [
+ hasInputParameter,
+ useModuleReferenceFromStep1,
+ smartContractName,
+ uploadedModuleSchemaBase64,
+ inputParameterType,
+ ]);
+
+ function onSubmit(data: FormType) {
+ setTxHash(undefined);
+ setSmartContractIndexError(undefined);
+ setSmartContractIndex(undefined);
+ setTransactionError(undefined);
+
+ const schema = data.useModuleReferenceFromStep1 ? embeddedModuleSchemaBase64 : uploadedModuleSchemaBase64;
+
+ // Send init transaction
+
+ const tx = initialize(
+ connection,
+ account,
+ isModuleReferenceAlreadyDeployedStep2,
+ data.moduleReference,
+ data.inputParameter,
+ data.smartContractName,
+ data.hasInputParameter,
+ data.useModuleReferenceFromStep1,
+ schema,
+ data.inputParameterType,
+ BigInt(data.maxExecutionEnergy),
+ data.cCDAmount ? BigInt(data.cCDAmount) : BigInt(0)
+ );
+ tx.then(setTxHash).catch((err: Error) => setTransactionError((err as Error).message));
+ }
+
+ return (
+
+
+ {
+ const register = form.register('useModuleReferenceFromStep1');
+
+ register.onChange(e);
+
+ setModuleReferenceError(undefined);
+ form.setValue('moduleReference', undefined);
+
+ setUploadedModuleSchemaBase64(undefined);
+
+ const checkboxElement = form.getValues('useModuleReferenceFromStep1');
+
+ form.setValue('moduleReference', undefined);
+ setModuleReferenceLengthError(undefined);
+
+ if (
+ checkboxElement &&
+ moduleReferenceDeployed === undefined &&
+ moduleReferenceCalculated === undefined
+ ) {
+ setModuleReferenceError('No module is uploaded in step 1');
+ }
+
+ const newModuleReference =
+ moduleReferenceDeployed !== undefined
+ ? moduleReferenceDeployed
+ : moduleReferenceCalculated;
+
+ if (checkboxElement && newModuleReference !== undefined) {
+ form.setValue('moduleReference', newModuleReference);
+
+ setDisplayContracts(contracts);
+ form.setValue('smartContractName', contracts[0]);
+ }
+ }}
+ />
+
+
+ {useModuleReferenceFromStep1 && (
+ <>
+
+
+
+ This checkbox autofilled the module reference
, the{' '}
+ smart contract name
, and the input parameter schema
from the
+ module in step1.
+
+
+
+ Uncheck this box, if you want to manually fill in a module reference
+ , the smart contract name
, or an input parameter schema
.
+
+
+
+ Uncheck and check this box again, if you want to load a new module from
+ step 1.
+
+
+
+ >
+ )}
+
+ {moduleReferenceError && Error: {moduleReferenceError}. }
+
+
+
+ Module Reference
+ {
+ const register = form.register('moduleReference', {
+ required: true,
+ });
+
+ register.onChange(e);
+
+ setModuleReferenceLengthError(undefined);
+
+ const moduleRef = form.getValues('moduleReference');
+
+ if (moduleRef !== undefined && !REG_MODULE_REF.test(moduleRef)) {
+ setModuleReferenceLengthError(
+ 'Module reference has to be a valid hex string `[0-9A-Fa-f]` of length 64'
+ );
+ }
+ }}
+ />
+
+ {form.formState.errors.moduleReference && (
+ Module reference is required
+ )}
+
+
+ {useModuleReferenceFromStep1 && displayContracts.length > 0 ? (
+
+ Smart Contract Name
+
+ ({
+ value: contract,
+ label: contract,
+ }))}
+ placeholder={displayContracts[0]}
+ onChange={(e) => {
+ form.setValue('smartContractName', e?.value);
+ }}
+ />
+ {form.formState.errors.smartContractName && (
+ Smart contract name is required
+ )}
+
+
+ ) : (
+
+ Smart Contract Name
+
+ {form.formState.errors.smartContractName && (
+ Smart contract name is required
+ )}
+
+
+ )}
+
+
+ Max Execution Energy
+
+
+ {form.formState.errors.maxExecutionEnergy && (
+ Max executiong energy is required
+ )}
+
+
+
+ {moduleReferenceLengthError && Error: {moduleReferenceLengthError}. }
+
+
+ {
+ const isPayableRegister = form.register('isPayable');
+
+ isPayableRegister.onChange(e);
+
+ form.setValue('cCDAmount', 0);
+ }}
+ />
+
+
+ {isPayable && (
+ <>
+
+
+ CCD amount (micro):
+
+
+ {form.formState.errors.cCDAmount && (
+ cCDAmount is required
+ )}
+
+
+
+ >
+ )}
+
+
+ {
+ const hasInputParameterRegister = form.register('hasInputParameter');
+
+ hasInputParameterRegister.onChange(e);
+
+ setParsingError(undefined);
+ form.setValue('inputParameterType', undefined);
+ form.setValue('inputParameter', undefined);
+ setInputParameterTemplate(undefined);
+ setUploadedModuleSchemaBase64(undefined);
+ setSchemaError(undefined);
+ }}
+ />
+
+
+ {hasInputParameter && (
+
+ {!useModuleReferenceFromStep1 && (
+
+ Upload Smart Contract Module Schema File (e.g. schema.bin)
+ {
+ const fileRegister = form.register('file');
+
+ fileRegister.onChange(e);
+
+ setUploadError2(undefined);
+ setUploadedModuleSchemaBase64(undefined);
+
+ const files = form.getValues('file');
+
+ if (files !== undefined && files !== null && files.length > 0) {
+ const file = files[0];
+ const arrayBuffer = await file.arrayBuffer();
+
+ // Use `reduce` to be able to convert large schemas.
+ const schema = btoa(
+ new Uint8Array(arrayBuffer).reduce((data, byte) => {
+ return data + String.fromCharCode(byte);
+ }, '')
+ );
+ setUploadedModuleSchemaBase64(schema);
+ } else {
+ setUploadError2('Upload schema file is undefined');
+ }
+ }}
+ />
+
+
+ )}
+ {!useModuleReferenceFromStep1 && uploadedModuleSchemaBase64 && (
+
+ Schema in base64:
+
{uploadedModuleSchemaBase64.toString().slice(0, 30)} ...
+
+ )}
+ {uploadError2 !== undefined &&
Error: {uploadError2}. }
+ {schemaError !== undefined &&
Error: {schemaError}. }
+ {inputParameterTemplate && (
+ <>
+
+
+
+ Parameter Template:
+
{JSON.stringify(JSON.parse(inputParameterTemplate), undefined, 2)}
+
+ >
+ )}
+
+
+ Select input parameter type:
+ {
+ form.setValue('inputParameterType', e?.value);
+ form.setValue('inputParameter', undefined);
+
+ setParsingError(undefined);
+ }}
+ />
+
+
+
+ {(inputParameterType === 'number' || inputParameterType === 'string') && (
+
+ Add your input parameter ({inputParameterType}):
+ {
+ const register = form.register('inputParameter', {
+ required: true,
+ });
+
+ register.onChange(e);
+
+ setParsingError(undefined);
+ }}
+ />
+ {form.formState.errors.inputParameter && (
+ Input parameter is required
+ )}
+
+
+ )}
+
+ {(inputParameterType === 'object' || inputParameterType === 'array') && (
+
+ Add your input parameter ({inputParameterType}):
+
+ {inputParameterType === 'array' && (
+
+ )}
+ {inputParameterType === 'object' && (
+
+ )}
+
+ {form.formState.errors.inputParameter && (
+ Input parameter is required
+ )}
+
+
+ )}
+
+ {parsingError !== undefined &&
Error: {parsingError}. }
+
+ )}
+
+
+
+ Initialize Smart Contract
+
+
+
+ {shouldWarnDifferenceModuleReferences && (
+ Warning: Module references in step 1 and step 2 are different.
+ )}
+ {shouldWarnInputParameterInSchemaIgnored && (
+
+ {' '}
+ Warning: Input parameter schema found but "Has Input Parameter" checkbox is unchecked.
+
+ )}
+ {!txHash && transactionError && Error: {transactionError}. }
+ {txHash && (
+
+ )}
+
+ {smartContractIndexError !== undefined && (
+ Error: {smartContractIndexError}.
+ )}
+ {smartContractIndex !== undefined && (
+
+ Smart Contract Inedex:
+
{smartContractIndex}
+
+ )}
+
+
+ );
+}
diff --git a/front-end-tools/src/components/ReadComponent.tsx b/front-end-tools/src/components/ReadComponent.tsx
new file mode 100644
index 00000000..d43fb2fe
--- /dev/null
+++ b/front-end-tools/src/components/ReadComponent.tsx
@@ -0,0 +1,534 @@
+import React, { useEffect, useMemo, useState } from 'react';
+import { useForm, useWatch } from 'react-hook-form';
+import Select from 'react-select';
+import { Alert, Button, Form, Row } from 'react-bootstrap';
+
+import { WalletConnection } from '@concordium/react-components';
+import {
+ ModuleReference,
+ displayTypeSchemaTemplate,
+ toBuffer,
+ getUpdateContractParameterSchema,
+ ConcordiumGRPCClient,
+} from '@concordium/web-sdk';
+
+import Box from './Box';
+import { read, getEmbeddedSchema, getContractInfo } from '../reading_from_blockchain';
+import { getObjectExample, getArrayExample } from '../utils';
+import { INPUT_PARAMETER_TYPES_OPTIONS } from '../constants';
+
+interface ConnectionProps {
+ account: string;
+ connection: WalletConnection;
+ client: ConcordiumGRPCClient | undefined;
+}
+
+/**
+ * A component that manages the input fields and corresponding state to read from a smart contract instance on the chain.
+ * The `invoke` action is used in this component which does not create a transaction.
+ */
+export default function ReadComponenet(props: ConnectionProps) {
+ const { client } = props;
+
+ type FormType = {
+ smartContractIndex: number;
+ smartContractName: string;
+ entryPointName: string | undefined;
+ file: FileList | undefined;
+ hasInputParameter: boolean;
+ deriveFromSmartContractIndex: boolean;
+ inputParameterType: string | undefined;
+ inputParameter: string | undefined;
+ };
+
+ const form = useForm({ mode: 'all' });
+
+ const [deriveContractInfo, smartContractName, inputParameterType, entryPointName, hasInputParameter] = useWatch({
+ control: form.control,
+ name: [
+ 'deriveFromSmartContractIndex',
+ 'smartContractName',
+ 'inputParameterType',
+ 'entryPointName',
+ 'hasInputParameter',
+ ],
+ });
+
+ const [schemaError, setSchemaError] = useState(undefined);
+
+ const [uploadError, setUploadError] = useState(undefined);
+ const [parsingError, setParsingError] = useState(undefined);
+
+ const [uploadedModuleSchemaBase64, setUploadedModuleSchemaBase64] = useState(undefined);
+
+ const [contractInstanceInfo, setContractInstanceInfo] = useState<
+ { contractName: string; methods: string[]; sourceModule: ModuleReference } | undefined
+ >(undefined);
+ const [returnValue, setReturnValue] = useState(undefined);
+ const [error, setError] = useState(undefined);
+
+ const [entryPointTemplate, setEntryPointTemplate] = useState(undefined);
+
+ const [embeddedModuleSchemaBase64, setEmbeddedModuleSchemaBase64] = useState(undefined);
+
+ const shouldWarnInputParameterInSchemaIgnored = useMemo(() => {
+ if (entryPointTemplate !== undefined && form.getValues('hasInputParameter') === false) {
+ return true;
+ }
+ return false;
+ }, [entryPointTemplate, hasInputParameter]);
+
+ useEffect(() => {
+ setSchemaError(undefined);
+ setEntryPointTemplate(undefined);
+
+ let receiveTemplate;
+
+ try {
+ if (entryPointName === undefined) {
+ throw new Error('Set entry point name');
+ }
+
+ if (smartContractName === undefined) {
+ throw new Error('Set smart contract name');
+ }
+
+ let schema = '';
+
+ const schemaFromModule = deriveContractInfo ? embeddedModuleSchemaBase64 : uploadedModuleSchemaBase64;
+
+ if (schemaFromModule !== undefined) {
+ schema = schemaFromModule;
+ }
+
+ const readFunctionTemplate = getUpdateContractParameterSchema(
+ toBuffer(schema, 'base64'),
+ smartContractName,
+ entryPointName
+ );
+
+ receiveTemplate = displayTypeSchemaTemplate(readFunctionTemplate);
+
+ setEntryPointTemplate(receiveTemplate);
+ } catch (e) {
+ if (deriveContractInfo) {
+ setSchemaError(
+ `Could not derive the embedded schema from the smart contract index. Uncheck "Derive From Smart Contract Index" checkbox to manually upload a schema or uncheck "Has Input Paramter" checkbox if this entrypoint has no input parameter. Original error: ${e}`
+ );
+ } else {
+ setSchemaError(
+ `Could not get schema from uploaded schema. Uncheck "Has Input Paramter" checkbox if this entrypoint has no input parameter. Original error: ${e}`
+ );
+ }
+ }
+
+ if (receiveTemplate) {
+ if (inputParameterType === 'array') {
+ form.setValue('inputParameter', JSON.stringify(JSON.parse(receiveTemplate), undefined, 2));
+ } else if (inputParameterType === 'object') {
+ form.setValue('inputParameter', JSON.stringify(JSON.parse(receiveTemplate), undefined, 2));
+ }
+ }
+ }, [entryPointName, hasInputParameter, smartContractName, uploadedModuleSchemaBase64, inputParameterType]);
+
+ function onSubmit(data: FormType) {
+ setError(undefined);
+ setReturnValue(undefined);
+
+ const schema = data.deriveFromSmartContractIndex ? embeddedModuleSchemaBase64 : uploadedModuleSchemaBase64;
+
+ // Invoke smart contract (read)
+
+ const promise = read(
+ client,
+ data.smartContractName,
+ BigInt(data.smartContractIndex),
+ data.entryPointName,
+ schema,
+ data.inputParameter,
+ data.inputParameterType,
+ data.hasInputParameter,
+ data.deriveFromSmartContractIndex
+ );
+
+ promise
+ .then((value) => {
+ setReturnValue(value);
+ })
+ .catch((err: Error) => setError((err as Error).message));
+ }
+
+ return (
+
+
+ Upload Smart Contract Module Schema File (e.g. schema.bin)
+ {
+ const fileRegister = form.register('file');
+
+ fileRegister.onChange(e);
+
+ setUploadError(undefined);
+ setUploadedModuleSchemaBase64(undefined);
+
+ const files = form.getValues('file');
+
+ if (files !== undefined && files !== null && files.length > 0) {
+ const file = files[0];
+ const arrayBuffer = await file.arrayBuffer();
+
+ // Use `reduce` to be able to convert large schemas.
+ const schema = btoa(
+ new Uint8Array(arrayBuffer).reduce((data, byte) => {
+ return data + String.fromCharCode(byte);
+ }, '')
+ );
+ setUploadedModuleSchemaBase64(schema);
+ } else {
+ setUploadError('Upload schema file is undefined');
+ }
+ }}
+ />
+
+
+ )}
+ {deriveContractInfo && (
+ <>
+
+
+
+ This checkbox autofilled the smart contract name
, the{' '}
+ entry point name
, and the{' '}
+ receive return_value/parameter schema
from the smart contract index.
+
+
+
+ Uncheck this box, if you want to manually fill in a{' '}
+ smart contract name
, an entry point name
, or a{' '}
+ receive return_value/parameter schema
.
+
+
+
+ Uncheck and check this box again, if you want to load a new smart contract
+ index.
+
+
+ >
+ )}
+
+
+ {
+ const hasInputParameterRegister = form.register('hasInputParameter');
+
+ hasInputParameterRegister.onChange(e);
+
+ setParsingError(undefined);
+ form.setValue('inputParameterType', undefined);
+ form.setValue('inputParameter', undefined);
+ setEntryPointTemplate(undefined);
+ setSchemaError(undefined);
+ }}
+ />
+
+
+ {hasInputParameter && (
+
+ {!deriveContractInfo && uploadedModuleSchemaBase64 && (
+
+ Schema in base64:
+
{uploadedModuleSchemaBase64.toString().slice(0, 30)} ...
+
+ )}
+ {uploadError !== undefined &&
Error: {uploadError}. }
+ {schemaError !== undefined &&
Error: {schemaError}. }
+ {entryPointTemplate && (
+ <>
+
+
+
+ Parameter Template:
+
{JSON.stringify(JSON.parse(entryPointTemplate), undefined, 2)}
+
+ >
+ )}
+
+
+ Select input parameter type:
+ {
+ form.setValue('inputParameterType', e?.value);
+ form.setValue('inputParameter', undefined);
+
+ setParsingError(undefined);
+ }}
+ />
+
+
+
+ {(inputParameterType === 'number' || inputParameterType === 'string') && (
+
+ Add your input parameter ({inputParameterType}):
+ {
+ const inputParameterRegister = form.register('inputParameter', {
+ required: true,
+ });
+
+ inputParameterRegister.onChange(e);
+
+ setParsingError(undefined);
+ }}
+ />
+ {form.formState.errors.inputParameter && (
+
+ {' '}
+ Input parameter is required{' '}
+
+ )}
+
+
+ )}
+
+ {(inputParameterType === 'object' || inputParameterType === 'array') && (
+
+ Add your input parameter ({inputParameterType}):
+
+ {inputParameterType === 'array' && (
+
+ )}
+ {inputParameterType === 'object' && (
+
+ )}
+
+ {form.formState.errors.inputParameter && (
+
+ {' '}
+ Input parameter is required{' '}
+
+ )}
+
+
+ )}
+
+ {parsingError !== undefined &&
Error: {parsingError}. }
+
+ )}
+
+
+
+
+ Read Smart Contract
+
+
+
+ {(deriveContractInfo ? embeddedModuleSchemaBase64 : uploadedModuleSchemaBase64) === undefined && (
+
+ {' '}
+ Warning: There is no module schema, so the return value cannot be decoded.{' '}
+
+ )}
+ {shouldWarnInputParameterInSchemaIgnored && (
+
+ {' '}
+ Warning: Input parameter schema found but "Has Input Parameter" checkbox is unchecked.{' '}
+
+ )}
+ {error && Error: {error}. }
+ {returnValue && (
+
+ Read value:
+
{JSON.stringify(JSON.parse(returnValue), undefined, 2)}
+
+ )}
+
+
+ );
+}
diff --git a/front-end-tools/src/components/UpdateComponent.tsx b/front-end-tools/src/components/UpdateComponent.tsx
new file mode 100644
index 00000000..15ddee32
--- /dev/null
+++ b/front-end-tools/src/components/UpdateComponent.tsx
@@ -0,0 +1,623 @@
+import React, { useEffect, useMemo, useState } from 'react';
+import { useForm, useWatch } from 'react-hook-form';
+import Select from 'react-select';
+import { Alert, Button, Form, Row } from 'react-bootstrap';
+
+import { WalletConnection } from '@concordium/react-components';
+import {
+ ModuleReference,
+ TransactionKindString,
+ TransactionSummaryType,
+ displayTypeSchemaTemplate,
+ toBuffer,
+ getUpdateContractParameterSchema,
+ ConcordiumGRPCClient,
+} from '@concordium/web-sdk';
+
+import Box from './Box';
+import { TxHashLink } from './CCDScanLinks';
+import { update } from '../writing_to_blockchain';
+import { getEmbeddedSchema, getContractInfo } from '../reading_from_blockchain';
+import { getObjectExample, getArrayExample } from '../utils';
+import { REFRESH_INTERVAL, INPUT_PARAMETER_TYPES_OPTIONS } from '../constants';
+
+interface ConnectionProps {
+ isTestnet: boolean;
+ account: string;
+ connection: WalletConnection;
+ client: ConcordiumGRPCClient | undefined;
+}
+
+/* A component that manages the input fields and corresponding state to update a smart contract instance on the chain.
+ * This components creates an `Update` transaction.
+ */
+export default function UpdateComponenet(props: ConnectionProps) {
+ const { isTestnet, account, connection, client } = props;
+
+ type FormType = {
+ smartContractIndex: number;
+ smartContractName: string;
+ entryPointName: string | undefined;
+ file: FileList | undefined;
+ hasInputParameter: boolean;
+ deriveFromSmartContractIndex: boolean;
+ inputParameterType: string | undefined;
+ inputParameter: string | undefined;
+ maxExecutionEnergy: number;
+ isPayable: boolean;
+ cCDAmount: number;
+ };
+
+ const form = useForm({ mode: 'all' });
+
+ const [deriveContractInfo, hasInputParameter, inputParameterType, isPayable, smartContractName, entryPointName] =
+ useWatch({
+ control: form.control,
+ name: [
+ 'deriveFromSmartContractIndex',
+ 'hasInputParameter',
+ 'inputParameterType',
+ 'isPayable',
+ 'smartContractName',
+ 'entryPointName',
+ ],
+ });
+
+ const [uploadError, setUploadError] = useState(undefined);
+ const [parsingError, setParsingError] = useState(undefined);
+ const [schemaError, setSchemaError] = useState(undefined);
+
+ const [transactionErrorUpdate, setTransactionErrorUpdate] = useState(undefined);
+ const [txHashUpdate, setTxHashUpdate] = useState(undefined);
+ const [uploadedModuleSchemaBase64, setUploadedModuleSchemaBase64] = useState(undefined);
+
+ const [contractInstanceInfo, setContractInstanceInfo] = useState<
+ { contractName: string; methods: string[]; sourceModule: ModuleReference } | undefined
+ >(undefined);
+ const [error, setError] = useState(undefined);
+
+ const [entryPointTemplate, setEntryPointTemplate] = useState(undefined);
+
+ const [transactionOutcome, setTransactionOutcome] = useState(undefined);
+
+ const [embeddedModuleSchemaBase64, setEmbeddedModuleSchemaBase64] = useState(undefined);
+
+ // Refresh transactionOutcome periodically.
+ // eslint-disable-next-line consistent-return
+ useEffect(() => {
+ if (connection && client && txHashUpdate !== undefined) {
+ const interval = setInterval(() => {
+ client
+ .getBlockItemStatus(txHashUpdate)
+ .then((report) => {
+ if (report !== undefined && report.status === 'finalized') {
+ if (
+ report.outcome.summary.type === TransactionSummaryType.AccountTransaction &&
+ report.outcome.summary.transactionType === TransactionKindString.Update
+ ) {
+ setTransactionOutcome('Success');
+ clearInterval(interval);
+ } else {
+ setTransactionOutcome('Fail');
+ clearInterval(interval);
+ }
+ }
+ })
+ .catch((e) => {
+ setTransactionOutcome(`Fail; Error: ${(e as Error).message}`);
+ clearInterval(interval);
+ });
+ }, REFRESH_INTERVAL.asMilliseconds());
+ return () => clearInterval(interval);
+ }
+ }, [connection, client, txHashUpdate]);
+
+ const shouldWarnInputParameterInSchemaIgnored = useMemo(() => {
+ if (entryPointTemplate !== undefined && hasInputParameter === false) {
+ return true;
+ }
+ return false;
+ }, [entryPointTemplate, hasInputParameter]);
+
+ useEffect(() => {
+ setSchemaError(undefined);
+ setEntryPointTemplate(undefined);
+
+ let receiveTemplate;
+
+ try {
+ if (entryPointName === undefined) {
+ throw new Error('Set entry point name');
+ }
+
+ if (smartContractName === undefined) {
+ throw new Error('Set smart contract name');
+ }
+
+ let schema = '';
+
+ const schemaFromModule = deriveContractInfo ? embeddedModuleSchemaBase64 : uploadedModuleSchemaBase64;
+
+ if (schemaFromModule !== undefined) {
+ schema = schemaFromModule;
+ }
+
+ const functionTemplate = getUpdateContractParameterSchema(
+ toBuffer(schema, 'base64'),
+ smartContractName,
+ entryPointName
+ );
+
+ receiveTemplate = displayTypeSchemaTemplate(functionTemplate);
+
+ setEntryPointTemplate(receiveTemplate);
+ } catch (e) {
+ if (deriveContractInfo) {
+ setSchemaError(
+ `Could not derive the embedded schema from the smart contract index. Uncheck "Derive From Smart Contract Index" checkbox to manually upload a schema or uncheck "Has Input Paramter" checkbox if this entrypoint has no input parameter. Original error: ${e}`
+ );
+ } else {
+ setSchemaError(
+ `Could not get schema from uploaded schema. Uncheck "Has Input Paramter" checkbox if this entrypoint has no input parameter. Original error: ${e}`
+ );
+ }
+ }
+
+ if (receiveTemplate) {
+ if (inputParameterType === 'array') {
+ form.setValue('inputParameter', JSON.stringify(JSON.parse(receiveTemplate), undefined, 2));
+ } else if (inputParameterType === 'object') {
+ form.setValue('inputParameter', JSON.stringify(JSON.parse(receiveTemplate), undefined, 2));
+ }
+ }
+ }, [entryPointName, hasInputParameter, smartContractName, uploadedModuleSchemaBase64, inputParameterType]);
+
+ function onSubmit(data: FormType) {
+ setTxHashUpdate(undefined);
+ setTransactionErrorUpdate(undefined);
+ setTransactionOutcome(undefined);
+
+ const schema = deriveContractInfo ? embeddedModuleSchemaBase64 : uploadedModuleSchemaBase64;
+
+ // Send update transaction
+
+ const tx = update(
+ connection,
+ account,
+ data.inputParameter,
+ data.smartContractName,
+ data.entryPointName,
+ data.hasInputParameter,
+ data.deriveFromSmartContractIndex,
+ schema,
+ data.inputParameterType,
+ BigInt(data.maxExecutionEnergy),
+ BigInt(data.smartContractIndex),
+ data.cCDAmount ? BigInt(data.cCDAmount) : BigInt(0)
+ );
+
+ tx.then(setTxHashUpdate).catch((err: Error) => setTransactionErrorUpdate((err as Error).message));
+ }
+
+ return (
+
+
+ {
+ const isPayableRegister = form.register('isPayable');
+
+ isPayableRegister.onChange(e);
+
+ form.setValue('cCDAmount', 0);
+ }}
+ />
+
+
+ {isPayable && (
+ <>
+
+
+ CCD amount (micro):
+
+
+ {form.formState.errors.cCDAmount && (
+ cCDAmount is required
+ )}
+
+
+
+ >
+ )}
+
+
+ {
+ const hasInputParameterRegister = form.register('hasInputParameter');
+
+ hasInputParameterRegister.onChange(e);
+
+ setParsingError(undefined);
+ form.setValue('inputParameterType', undefined);
+ form.setValue('inputParameter', undefined);
+ setUploadedModuleSchemaBase64(undefined);
+ setEntryPointTemplate(undefined);
+ setSchemaError(undefined);
+ }}
+ />
+
+
+ {hasInputParameter && (
+
+ {!deriveContractInfo && (
+
+ Upload Smart Contract Module Schema File (e.g. schema.bin)
+ {
+ const fileRegister = form.register('file');
+
+ fileRegister.onChange(e);
+
+ setUploadError(undefined);
+ setUploadedModuleSchemaBase64(undefined);
+
+ const files = form.getValues('file');
+
+ if (files !== undefined && files !== null && files.length > 0) {
+ const file = files[0];
+ const arrayBuffer = await file.arrayBuffer();
+
+ const schema = btoa(
+ new Uint8Array(arrayBuffer).reduce((data, byte) => {
+ return data + String.fromCharCode(byte);
+ }, '')
+ );
+ setUploadedModuleSchemaBase64(schema);
+ } else {
+ setUploadError('Upload schema file is undefined');
+ }
+ }}
+ />
+
+
+ )}
+
+ {!deriveContractInfo && uploadedModuleSchemaBase64 && (
+
+ Schema in base64:
+
{uploadedModuleSchemaBase64.toString().slice(0, 30)} ...
+
+ )}
+ {uploadError !== undefined &&
Error: {uploadError}. }
+ {error &&
Error: {error}. }
+ {schemaError !== undefined &&
Error: {schemaError}. }
+ {entryPointTemplate && (
+ <>
+
+
+
+ Parameter Template:
+
{JSON.stringify(JSON.parse(entryPointTemplate), undefined, 2)}
+
+ >
+ )}
+
+
+ Select input parameter type:
+ {
+ form.setValue('inputParameterType', e?.value);
+ form.setValue('inputParameter', undefined);
+
+ setParsingError(undefined);
+ }}
+ />
+
+
+
+ {(inputParameterType === 'number' || inputParameterType === 'string') && (
+
+ Add your input parameter ({inputParameterType}):
+ {
+ const inputParameterRegister = form.register('inputParameter', {
+ required: true,
+ });
+
+ inputParameterRegister.onChange(e);
+
+ setParsingError(undefined);
+ }}
+ />
+ {form.formState.errors.inputParameter && (
+ Input parameter is required
+ )}
+
+
+ )}
+
+ {(inputParameterType === 'object' || inputParameterType === 'array') && (
+
+ Add your input parameter ({inputParameterType}):
+
+ {inputParameterType === 'array' && (
+
+ )}
+ {inputParameterType === 'object' && (
+
+ )}
+
+ {form.formState.errors.inputParameter && (
+ Input parameter is required
+ )}
+
+
+ )}
+
+ {parsingError !== undefined &&
Error: {parsingError}. }
+
+ )}
+
+
+
+
+ Update Smart Contract
+
+
+
+ {shouldWarnInputParameterInSchemaIgnored && (
+
+ {' '}
+ Warning: Input parameter schema found but "Has Input Parameter" checkbox is unchecked.
+
+ )}
+ {!txHashUpdate && transactionErrorUpdate && (
+ Error: {transactionErrorUpdate}.
+ )}
+ {txHashUpdate && (
+
+ )}
+ {transactionOutcome === 'Success' && (
+ <>
+
+
+ Outcome of transaction:
+
{transactionOutcome}
+
+ >
+ )}
+ {transactionOutcome !== undefined && transactionOutcome !== 'Success' && (
+ <>
+
+ Outcome of transaction:
+
+ Error: {transactionOutcome}.
+ >
+ )}
+
+
+ );
+}
diff --git a/front-end-tools/src/constants.ts b/front-end-tools/src/constants.ts
index b39c9181..7504616a 100644
--- a/front-end-tools/src/constants.ts
+++ b/front-end-tools/src/constants.ts
@@ -1,6 +1,7 @@
import { BrowserWalletConnector, ephemeralConnectorType } from '@concordium/react-components';
import moment from 'moment';
+// The refresh interval is used by polling at the front end.
export const REFRESH_INTERVAL = moment.duration(5, 'seconds');
export const BROWSER_WALLET = ephemeralConnectorType(BrowserWalletConnector.create);
@@ -19,3 +20,17 @@ export const EXAMPLE_JSON_OBJECT = {
// These are the example arrays that are shown in the input parameter textarea as a placeholder when the user has no embedded schema in the module
// or does not want to use the embedded schema (meaning if the checkbox "Use module from step 1" is unchecked).
export const EXAMPLE_ARRAYS = 'Examples: \n\n[1,2,3] or \n\n["abc","def"] or \n\n[{"myFieldKey":"myFieldValue"}]';
+
+// The input parameter can have one of these type options.
+export const INPUT_PARAMETER_TYPES_OPTIONS = [
+ { label: 'number', value: 'number' },
+ { label: 'string', value: 'string' },
+ { label: 'object', value: 'object' },
+ { label: 'array', value: 'array' },
+];
+
+// The subindex of all smart contracts.
+export const CONTRACT_SUB_INDEX = 0n;
+
+// Regular expression of a valid module reference which has to be a hex string `[0-9A-Fa-f]` of length 64.
+export const REG_MODULE_REF = /^[0-9A-Fa-f]{64}$/;
diff --git a/front-end-tools/src/index.css b/front-end-tools/src/index.css
index 51b1f761..e6dc0f98 100644
--- a/front-end-tools/src/index.css
+++ b/front-end-tools/src/index.css
@@ -12,10 +12,11 @@ h3 {
}
textarea {
- left: 10px;
- top: 10px;
width: 100%;
- height: 300px;
+ height: 200px;
+ box-sizing: border-box;
+ border-radius: 4px;
+ font-size: 16px;
}
pre {
@@ -46,10 +47,6 @@ label,
margin-top: 20px;
}
-.field p {
- margin: 0;
-}
-
.link {
color: #308274;
}
@@ -64,12 +61,12 @@ label,
justify-content: center;
}
-.testBoxFields {
+.boxFields {
padding: 10px 10px 10px 10px;
text-align: center;
}
-.testBox {
+.box {
border: 1px solid grey;
background-color: #f2f2f2;
border-radius: 10px;
@@ -78,12 +75,6 @@ label,
padding: 30px 20px 10px;
}
-.note {
- margin: 0;
- color: #666666;
-
-}
-
.actionResultBox {
padding: 10px 10px 10px 10px;
background-color: greenyellow;
@@ -115,4 +106,4 @@ label,
border: 1px solid #308274;
margin: 7px 0px 7px 0px;
padding: 9px 184px 9px 20px;
-}
+}
\ No newline at end of file
diff --git a/front-end-tools/src/reading_from_blockchain.ts b/front-end-tools/src/reading_from_blockchain.ts
new file mode 100644
index 00000000..bde89fa8
--- /dev/null
+++ b/front-end-tools/src/reading_from_blockchain.ts
@@ -0,0 +1,198 @@
+import {
+ toBuffer,
+ ConcordiumGRPCClient,
+ deserializeReceiveReturnValue,
+ serializeUpdateContractParameters,
+ ModuleReference,
+ InvokeContractFailedResult,
+ RejectedReceive,
+ AccountAddress,
+ AccountInfo,
+} from '@concordium/web-sdk';
+
+import { CONTRACT_SUB_INDEX } from './constants';
+
+/** This function gets the contract info of a smart contract index. */
+export async function getContractInfo(rpcClient: ConcordiumGRPCClient | undefined, contractIndex: bigint) {
+ if (rpcClient === undefined) {
+ throw new Error(`rpcClient undefined`);
+ }
+ if (contractIndex === undefined) {
+ throw new Error(`Set smart contract index`);
+ }
+
+ const info = await rpcClient.getInstanceInfo({ index: contractIndex, subindex: CONTRACT_SUB_INDEX });
+
+ // Removing the `init_` prefix.
+ const contractName = info.name.substring(5);
+
+ // Removing the `contractName.` prefix.
+ const methods = info.methods.map((element) => element.substring(contractName.length + 1));
+
+ const returnValue = { contractName, methods, sourceModule: info.sourceModule };
+ return returnValue;
+}
+
+/** This function gets the embedded schema of a module reference. */
+export async function getEmbeddedSchema(
+ rpcClient: ConcordiumGRPCClient | undefined,
+ moduleRef: ModuleReference | undefined
+) {
+ if (rpcClient === undefined) {
+ throw new Error(`rpcClient undefined`);
+ }
+ if (moduleRef === undefined) {
+ throw new Error(`Set module ref`);
+ }
+
+ return rpcClient.getEmbeddedSchema(moduleRef);
+}
+
+/** This function gets the account info and its balance. */
+export function getAccountInfo(
+ client: ConcordiumGRPCClient,
+ account: string,
+ setAccountBalance: (arg0: undefined | string) => void,
+ setAccountExistsOnNetwork: (arg0: boolean) => void,
+ setViewErrorAccountInfo: (arg0: undefined | string) => void
+) {
+ client
+ .getAccountInfo(new AccountAddress(account))
+ .then((value: AccountInfo) => {
+ if (value !== undefined) {
+ setAccountBalance(value.accountAmount.toString());
+ setAccountExistsOnNetwork(true);
+ } else {
+ setAccountExistsOnNetwork(false);
+ }
+ setViewErrorAccountInfo(undefined);
+ })
+ .catch((e) => {
+ setAccountBalance(undefined);
+ setViewErrorAccountInfo((e as Error).message.replaceAll('%20', ' '));
+ setAccountExistsOnNetwork(false);
+ });
+}
+
+/** This function invokes a smart contract entry point and returns its return_value.
+ * This function expects that the entry point is a `typical` smart contract view/read/getter function that returns a return_value.
+ * This function throws an error if the entry point does not return a return_value.
+ * If the moduleSchema parameter is undefined, the return_value is in raw bytes.
+ * If a valid moduleSchema is provided, the return_value is deserialized.
+ */
+export async function read(
+ rpcClient: ConcordiumGRPCClient | undefined,
+ contractName: string,
+ contractIndex: bigint,
+ entryPoint: string | undefined,
+ moduleSchema: string | undefined,
+ inputParameter: string | undefined,
+ inputParameterType: string | undefined,
+ hasInputParameter: boolean,
+ deriveContractInfo: boolean
+) {
+ if (rpcClient === undefined) {
+ throw new Error(`rpcClient undefined`);
+ }
+
+ if (entryPoint === undefined) {
+ throw new Error(`Set entry point name`);
+ }
+
+ let param = toBuffer('', 'hex');
+
+ if (hasInputParameter) {
+ if (!deriveContractInfo && moduleSchema === undefined) {
+ throw new Error(`Set schema`);
+ } else if (deriveContractInfo && moduleSchema === undefined) {
+ throw new Error(`No embedded module schema found in module`);
+ }
+
+ if (inputParameterType === undefined) {
+ throw new Error(`Select input parameter type`);
+ }
+
+ let inputParameterFormated;
+
+ if (inputParameter === undefined) {
+ throw new Error(`Set input parameter`);
+ }
+
+ switch (inputParameterType) {
+ case 'number':
+ inputParameterFormated = Number(inputParameter);
+ break;
+ case 'string':
+ inputParameterFormated = inputParameter;
+ break;
+ case 'object':
+ inputParameterFormated = JSON.parse(inputParameter);
+ break;
+ case 'array':
+ inputParameterFormated = JSON.parse(inputParameter);
+ break;
+ default:
+ throw new Error(`InputParameterType does not exist`);
+ }
+
+ if (moduleSchema !== undefined) {
+ param = serializeUpdateContractParameters(
+ contractName,
+ entryPoint,
+ inputParameterFormated,
+ toBuffer(moduleSchema, 'base64')
+ );
+ }
+ }
+
+ const res = await rpcClient.invokeContract({
+ method: `${contractName}.${entryPoint}`,
+ contract: { index: contractIndex, subindex: CONTRACT_SUB_INDEX },
+ parameter: param,
+ });
+
+ if (!res || res.tag === 'failure') {
+ const rejectReason = JSON.stringify(
+ ((res as InvokeContractFailedResult)?.reason as RejectedReceive)?.rejectReason
+ );
+
+ throw new Error(
+ `RPC call 'invokeContract' on method '${contractName}.${entryPoint}' of contract '${contractIndex}' failed.
+ ${rejectReason !== undefined ? `Reject reason: ${rejectReason}` : ''}`
+ );
+ }
+ if (!res.returnValue) {
+ throw new Error(
+ `RPC call 'invokeContract' on method '${contractName}.${entryPoint}' of contract '${contractIndex}' returned no return_value`
+ );
+ }
+
+ if (moduleSchema === undefined) {
+ // If no schema is provided return the raw bytes
+ return JSON.stringify(res.returnValue);
+ }
+
+ let returnValue;
+
+ try {
+ // If schema is provided deserialize return value
+ returnValue = deserializeReceiveReturnValue(
+ toBuffer(res.returnValue, 'hex'),
+ toBuffer(moduleSchema, 'base64'),
+ contractName,
+ entryPoint
+ );
+ } catch (e) {
+ throw new Error(
+ `Deserializing the returnValue from the '${contractName}.${entryPoint}' method of contract '${contractIndex}' failed. Original error: ${e}`
+ );
+ }
+
+ if (returnValue === undefined) {
+ throw new Error(
+ `Deserializing the returnValue from the '${contractName}.${entryPoint}' method of contract '${contractIndex}' failed.`
+ );
+ } else {
+ return JSON.stringify(returnValue);
+ }
+}
diff --git a/front-end-tools/src/utils.ts b/front-end-tools/src/utils.ts
new file mode 100644
index 00000000..5faf2e4c
--- /dev/null
+++ b/front-end-tools/src/utils.ts
@@ -0,0 +1,22 @@
+import { EXAMPLE_ARRAYS, EXAMPLE_JSON_OBJECT } from './constants';
+
+export function arraysEqual(a: Uint8Array, b: Uint8Array) {
+ if (a === b) return true;
+ if (a == null || b == null) return false;
+ if (a.length !== b.length) return false;
+
+ for (let i = 0; i < a.length; i += 1) {
+ if (a[i] !== b[i]) return false;
+ }
+ return true;
+}
+
+export function getObjectExample(template: string | undefined) {
+ return template !== undefined
+ ? JSON.stringify(JSON.parse(template), undefined, 2)
+ : JSON.stringify(EXAMPLE_JSON_OBJECT, undefined, 2);
+}
+
+export function getArrayExample(template: string | undefined) {
+ return template !== undefined ? JSON.stringify(JSON.parse(template), undefined, 2) : EXAMPLE_ARRAYS;
+}
diff --git a/front-end-tools/src/writing_to_blockchain.ts b/front-end-tools/src/writing_to_blockchain.ts
index 86e59ecf..7af43493 100644
--- a/front-end-tools/src/writing_to_blockchain.ts
+++ b/front-end-tools/src/writing_to_blockchain.ts
@@ -1,17 +1,20 @@
-import { createContext } from 'react';
import {
AccountTransactionType,
CcdAmount,
DeployModulePayload,
InitContractPayload,
ModuleReference,
+ UpdateContractPayload,
toBuffer,
} from '@concordium/web-sdk';
import { WalletConnection } from '@concordium/react-components';
import { moduleSchemaFromBase64 } from '@concordium/wallet-connectors';
+import { CONTRACT_SUB_INDEX } from './constants';
-export async function deploy(connection: WalletConnection, account: string, base64Module: string) {
- if (base64Module === '') {
+/** This function signs and sends a `DeployModule` transaction.
+ */
+export async function deploy(connection: WalletConnection, account: string, base64Module: string | undefined) {
+ if (base64Module === undefined) {
throw new Error(`Upload a smart contract module first`);
}
@@ -20,74 +23,167 @@ export async function deploy(connection: WalletConnection, account: string, base
} as DeployModulePayload);
}
+/** This function signs and sends an `Update` transaction.
+ * If the transaction should include an input parameter, `hasInputParameter` needs to be true
+ * and the `inputParameter`, its `inputParameterType`, and the contract `moduleSchema` have to be provided.
+ */
+export async function update(
+ connection: WalletConnection,
+ account: string,
+ inputParameter: string | undefined,
+ contractName: string,
+ entryPoint: string | undefined,
+ hasInputParameter: boolean,
+ deriveContractInfo: boolean,
+ moduleSchema: string | undefined,
+ inputParameterType: string | undefined,
+ maxContractExecutionEnergy: bigint,
+ contractIndex: bigint,
+ amount: bigint
+) {
+ if (entryPoint === undefined) {
+ throw new Error(`Set entry point name`);
+ }
+
+ let schema;
+
+ if (hasInputParameter) {
+ if (!deriveContractInfo && moduleSchema === undefined) {
+ throw new Error(`Set schema`);
+ } else if (deriveContractInfo && moduleSchema === undefined) {
+ throw new Error(`No embedded module schema found in module`);
+ }
+
+ if (moduleSchema !== undefined) {
+ if (inputParameterType === undefined) {
+ throw new Error(`InputParameterType is undefined`);
+ }
+
+ if (inputParameter === undefined) {
+ throw new Error(`Set input parameter`);
+ }
+
+ switch (inputParameterType) {
+ case 'number':
+ schema = {
+ parameters: Number(inputParameter),
+ schema: moduleSchemaFromBase64(moduleSchema),
+ };
+ break;
+ case 'string':
+ schema = {
+ parameters: inputParameter,
+ schema: moduleSchemaFromBase64(moduleSchema),
+ };
+ break;
+ case 'object':
+ schema = {
+ parameters: JSON.parse(inputParameter),
+ schema: moduleSchemaFromBase64(moduleSchema),
+ };
+ break;
+ case 'array':
+ schema = {
+ parameters: JSON.parse(inputParameter),
+ schema: moduleSchemaFromBase64(moduleSchema),
+ };
+ break;
+ default:
+ throw new Error(`Input paramter type option does not exist`);
+ }
+ }
+ }
+
+ return connection.signAndSendTransaction(
+ account,
+ AccountTransactionType.Update,
+ {
+ amount: new CcdAmount(amount),
+ address: { index: contractIndex, subindex: CONTRACT_SUB_INDEX },
+ receiveName: `${contractName}.${entryPoint}`,
+ maxContractExecutionEnergy,
+ } as UpdateContractPayload,
+ schema
+ );
+}
+
+/** This function signs and sends an `InitContract` transaction.
+ * If the transaction should include an input parameter,
+ * `hasInputParameter` needs to be true and the `inputParameter`,
+ * its `inputParameterType`, and the contract `moduleSchema` have to be provided.
+ */
export async function initialize(
connection: WalletConnection,
account: string,
moduleReferenceAlreadyDeployed: boolean,
- moduleReference: string,
- inputParameter: string,
- contractName: string,
+ moduleReference: string | undefined,
+ inputParameter: string | undefined,
+ contractName: string | undefined,
hasInputParameter: boolean,
useModuleFromStep1: boolean,
- moduleSchema: string,
- dropDown: string,
- maxContractExecutionEnergy: string,
- amount?: string
+ moduleSchema: string | undefined,
+ inputParamterType: string | undefined,
+ maxContractExecutionEnergy: bigint,
+ amount: bigint
) {
if (moduleReferenceAlreadyDeployed === false) {
- throw new Error(`Module reference does not exist on chain. First, deploy your module in step 1.`);
+ throw new Error(`Module reference does not exist on chain. First, deploy your module in step 1 and change/refresh the module reference field in step 2 to remove this error.`);
}
- if (moduleReference === '') {
+ if (moduleReference === undefined) {
throw new Error(`Set module reference`);
}
- if (contractName === '') {
+ if (contractName === undefined) {
throw new Error(`Set smart contract name`);
}
- if (maxContractExecutionEnergy === '') {
- throw new Error(`Set max contract execution energy`);
- }
+ let schema;
if (hasInputParameter) {
- if (!useModuleFromStep1 && moduleSchema === '') {
+ if (!useModuleFromStep1 && moduleSchema === undefined) {
throw new Error(`Set schema`);
- } else if (useModuleFromStep1 && moduleSchema === '') {
+ } else if (useModuleFromStep1 && moduleSchema === undefined) {
throw new Error(`No embedded module schema found in module`);
}
- }
- let schema;
+ if (moduleSchema !== undefined) {
+ if (inputParamterType === undefined) {
+ throw new Error(`Set input paramter type`);
+ }
- if (hasInputParameter) {
- switch (dropDown) {
- case 'number':
- schema = {
- parameters: Number(inputParameter),
- schema: moduleSchemaFromBase64(moduleSchema),
- };
- break;
- case 'string':
- schema = {
- parameters: inputParameter,
- schema: moduleSchemaFromBase64(moduleSchema),
- };
- break;
- case 'object':
- schema = {
- parameters: JSON.parse(inputParameter),
- schema: moduleSchemaFromBase64(moduleSchema),
- };
- break;
- case 'array':
- schema = {
- parameters: JSON.parse(inputParameter),
- schema: moduleSchemaFromBase64(moduleSchema),
- };
- break;
- default:
- throw new Error(`Dropdown option does not exist`);
+ if (inputParameter === undefined) {
+ throw new Error(`Set input parameter`);
+ }
+
+ switch (inputParamterType) {
+ case 'number':
+ schema = {
+ parameters: Number(inputParameter),
+ schema: moduleSchemaFromBase64(moduleSchema),
+ };
+ break;
+ case 'string':
+ schema = {
+ parameters: inputParameter,
+ schema: moduleSchemaFromBase64(moduleSchema),
+ };
+ break;
+ case 'object':
+ schema = {
+ parameters: JSON.parse(inputParameter),
+ schema: moduleSchemaFromBase64(moduleSchema),
+ };
+ break;
+ case 'array':
+ schema = {
+ parameters: JSON.parse(inputParameter),
+ schema: moduleSchemaFromBase64(moduleSchema),
+ };
+ break;
+ default:
+ throw new Error(`Input paramter type does not exist`);
+ }
}
}
@@ -95,22 +191,12 @@ export async function initialize(
account,
AccountTransactionType.InitContract,
{
- amount: new CcdAmount(BigInt(amount ? Number(amount) : 0)),
+ amount: new CcdAmount(amount),
moduleRef: new ModuleReference(moduleReference),
initName: contractName,
param: toBuffer(''),
- maxContractExecutionEnergy: BigInt(maxContractExecutionEnergy),
+ maxContractExecutionEnergy,
} as InitContractPayload,
schema
);
}
-
-/**
- * Global application state.
- */
-export type State = {
- isConnected: boolean;
- account: string | undefined;
-};
-
-export const state = createContext({ isConnected: false, account: undefined });
diff --git a/front-end-tools/yarn.lock b/front-end-tools/yarn.lock
index 9cb7dac2..cfcdc1b0 100644
--- a/front-end-tools/yarn.lock
+++ b/front-end-tools/yarn.lock
@@ -107,6 +107,15 @@ __metadata:
languageName: node
linkType: hard
+"@babel/helper-module-imports@npm:^7.16.7":
+ version: 7.22.15
+ resolution: "@babel/helper-module-imports@npm:7.22.15"
+ dependencies:
+ "@babel/types": ^7.22.15
+ checksum: ecd7e457df0a46f889228f943ef9b4a47d485d82e030676767e6a2fdcbdaa63594d8124d4b55fd160b41c201025aec01fc27580352b1c87a37c9c6f33d116702
+ languageName: node
+ linkType: hard
+
"@babel/helper-module-imports@npm:^7.21.4":
version: 7.21.4
resolution: "@babel/helper-module-imports@npm:7.21.4"
@@ -157,6 +166,13 @@ __metadata:
languageName: node
linkType: hard
+"@babel/helper-string-parser@npm:^7.22.5":
+ version: 7.22.5
+ resolution: "@babel/helper-string-parser@npm:7.22.5"
+ checksum: 836851ca5ec813077bbb303acc992d75a360267aa3b5de7134d220411c852a6f17de7c0d0b8c8dcc0f567f67874c00f4528672b2a4f1bc978a3ada64c8c78467
+ languageName: node
+ linkType: hard
+
"@babel/helper-validator-identifier@npm:^7.18.6, @babel/helper-validator-identifier@npm:^7.19.1":
version: 7.19.1
resolution: "@babel/helper-validator-identifier@npm:7.19.1"
@@ -164,6 +180,13 @@ __metadata:
languageName: node
linkType: hard
+"@babel/helper-validator-identifier@npm:^7.22.20":
+ version: 7.22.20
+ resolution: "@babel/helper-validator-identifier@npm:7.22.20"
+ checksum: 136412784d9428266bcdd4d91c32bcf9ff0e8d25534a9d94b044f77fe76bc50f941a90319b05aafd1ec04f7d127cd57a179a3716009ff7f3412ef835ada95bdc
+ languageName: node
+ linkType: hard
+
"@babel/helper-validator-option@npm:^7.21.0":
version: 7.21.0
resolution: "@babel/helper-validator-option@npm:7.21.0"
@@ -202,6 +225,15 @@ __metadata:
languageName: node
linkType: hard
+"@babel/runtime@npm:^7.12.0, @babel/runtime@npm:^7.12.5, @babel/runtime@npm:^7.18.3":
+ version: 7.23.2
+ resolution: "@babel/runtime@npm:7.23.2"
+ dependencies:
+ regenerator-runtime: ^0.14.0
+ checksum: 6c4df4839ec75ca10175f636d6362f91df8a3137f86b38f6cd3a4c90668a0fe8e9281d320958f4fbd43b394988958585a17c3aab2a4ea6bf7316b22916a371fb
+ languageName: node
+ linkType: hard
+
"@babel/runtime@npm:^7.20.7":
version: 7.22.3
resolution: "@babel/runtime@npm:7.22.3"
@@ -260,6 +292,17 @@ __metadata:
languageName: node
linkType: hard
+"@babel/types@npm:^7.22.15":
+ version: 7.23.0
+ resolution: "@babel/types@npm:7.23.0"
+ dependencies:
+ "@babel/helper-string-parser": ^7.22.5
+ "@babel/helper-validator-identifier": ^7.22.20
+ to-fast-properties: ^2.0.0
+ checksum: 215fe04bd7feef79eeb4d33374b39909ce9cad1611c4135a4f7fdf41fe3280594105af6d7094354751514625ea92d0875aba355f53e86a92600f290e77b0e604
+ languageName: node
+ linkType: hard
+
"@concordium/browser-wallet-api-helpers@npm:^2.5.0":
version: 2.5.0
resolution: "@concordium/browser-wallet-api-helpers@npm:2.5.0"
@@ -403,6 +446,123 @@ __metadata:
languageName: node
linkType: hard
+"@emotion/babel-plugin@npm:^11.11.0":
+ version: 11.11.0
+ resolution: "@emotion/babel-plugin@npm:11.11.0"
+ dependencies:
+ "@babel/helper-module-imports": ^7.16.7
+ "@babel/runtime": ^7.18.3
+ "@emotion/hash": ^0.9.1
+ "@emotion/memoize": ^0.8.1
+ "@emotion/serialize": ^1.1.2
+ babel-plugin-macros: ^3.1.0
+ convert-source-map: ^1.5.0
+ escape-string-regexp: ^4.0.0
+ find-root: ^1.1.0
+ source-map: ^0.5.7
+ stylis: 4.2.0
+ checksum: 6b363edccc10290f7a23242c06f88e451b5feb2ab94152b18bb8883033db5934fb0e421e2d67d09907c13837c21218a3ac28c51707778a54d6cd3706c0c2f3f9
+ languageName: node
+ linkType: hard
+
+"@emotion/cache@npm:^11.11.0, @emotion/cache@npm:^11.4.0":
+ version: 11.11.0
+ resolution: "@emotion/cache@npm:11.11.0"
+ dependencies:
+ "@emotion/memoize": ^0.8.1
+ "@emotion/sheet": ^1.2.2
+ "@emotion/utils": ^1.2.1
+ "@emotion/weak-memoize": ^0.3.1
+ stylis: 4.2.0
+ checksum: 8eb1dc22beaa20c21a2e04c284d5a2630a018a9d51fb190e52de348c8d27f4e8ca4bbab003d68b4f6cd9cc1c569ca747a997797e0f76d6c734a660dc29decf08
+ languageName: node
+ linkType: hard
+
+"@emotion/hash@npm:^0.9.1":
+ version: 0.9.1
+ resolution: "@emotion/hash@npm:0.9.1"
+ checksum: 716e17e48bf9047bf9383982c071de49f2615310fb4e986738931776f5a823bc1f29c84501abe0d3df91a3803c80122d24e28b57351bca9e01356ebb33d89876
+ languageName: node
+ linkType: hard
+
+"@emotion/memoize@npm:^0.8.1":
+ version: 0.8.1
+ resolution: "@emotion/memoize@npm:0.8.1"
+ checksum: a19cc01a29fcc97514948eaab4dc34d8272e934466ed87c07f157887406bc318000c69ae6f813a9001c6a225364df04249842a50e692ef7a9873335fbcc141b0
+ languageName: node
+ linkType: hard
+
+"@emotion/react@npm:^11.8.1":
+ version: 11.11.1
+ resolution: "@emotion/react@npm:11.11.1"
+ dependencies:
+ "@babel/runtime": ^7.18.3
+ "@emotion/babel-plugin": ^11.11.0
+ "@emotion/cache": ^11.11.0
+ "@emotion/serialize": ^1.1.2
+ "@emotion/use-insertion-effect-with-fallbacks": ^1.0.1
+ "@emotion/utils": ^1.2.1
+ "@emotion/weak-memoize": ^0.3.1
+ hoist-non-react-statics: ^3.3.1
+ peerDependencies:
+ react: ">=16.8.0"
+ peerDependenciesMeta:
+ "@types/react":
+ optional: true
+ checksum: aec3c36650f5f0d3d4445ff44d73dd88712b1609645b6af3e6d08049cfbc51f1785fe13dea1a1d4ab1b0800d68f2339ab11e459687180362b1ef98863155aae5
+ languageName: node
+ linkType: hard
+
+"@emotion/serialize@npm:^1.1.2":
+ version: 1.1.2
+ resolution: "@emotion/serialize@npm:1.1.2"
+ dependencies:
+ "@emotion/hash": ^0.9.1
+ "@emotion/memoize": ^0.8.1
+ "@emotion/unitless": ^0.8.1
+ "@emotion/utils": ^1.2.1
+ csstype: ^3.0.2
+ checksum: 413c352e657f1b5e27ea6437b3ef7dcc3860669b7ae17fd5c18bfbd44e033af1acc56b64d252284a813ca4f3b3e1b0841c42d3fb08e02d2df56fd3cd63d72986
+ languageName: node
+ linkType: hard
+
+"@emotion/sheet@npm:^1.2.2":
+ version: 1.2.2
+ resolution: "@emotion/sheet@npm:1.2.2"
+ checksum: d973273c9c15f1c291ca2269728bf044bd3e92a67bca87943fa9ec6c3cd2b034f9a6bfe95ef1b5d983351d128c75b547b43ff196a00a3875f7e1d269793cecfe
+ languageName: node
+ linkType: hard
+
+"@emotion/unitless@npm:^0.8.1":
+ version: 0.8.1
+ resolution: "@emotion/unitless@npm:0.8.1"
+ checksum: 385e21d184d27853bb350999471f00e1429fa4e83182f46cd2c164985999d9b46d558dc8b9cc89975cb337831ce50c31ac2f33b15502e85c299892e67e7b4a88
+ languageName: node
+ linkType: hard
+
+"@emotion/use-insertion-effect-with-fallbacks@npm:^1.0.1":
+ version: 1.0.1
+ resolution: "@emotion/use-insertion-effect-with-fallbacks@npm:1.0.1"
+ peerDependencies:
+ react: ">=16.8.0"
+ checksum: 700b6e5bbb37a9231f203bb3af11295eed01d73b2293abece0bc2a2237015e944d7b5114d4887ad9a79776504aa51ed2a8b0ddbc117c54495dd01a6b22f93786
+ languageName: node
+ linkType: hard
+
+"@emotion/utils@npm:^1.2.1":
+ version: 1.2.1
+ resolution: "@emotion/utils@npm:1.2.1"
+ checksum: e0b44be0705b56b079c55faff93952150be69e79b660ae70ddd5b6e09fc40eb1319654315a9f34bb479d7f4ec94be6068c061abbb9e18b9778ae180ad5d97c73
+ languageName: node
+ linkType: hard
+
+"@emotion/weak-memoize@npm:^0.3.1":
+ version: 0.3.1
+ resolution: "@emotion/weak-memoize@npm:0.3.1"
+ checksum: b2be47caa24a8122622ea18cd2d650dbb4f8ad37b636dc41ed420c2e082f7f1e564ecdea68122b546df7f305b159bf5ab9ffee872abd0f052e687428459af594
+ languageName: node
+ linkType: hard
+
"@esbuild/linux-loong64@npm:0.14.54":
version: 0.14.54
resolution: "@esbuild/linux-loong64@npm:0.14.54"
@@ -452,6 +612,32 @@ __metadata:
languageName: node
linkType: hard
+"@floating-ui/core@npm:^1.4.2":
+ version: 1.5.0
+ resolution: "@floating-ui/core@npm:1.5.0"
+ dependencies:
+ "@floating-ui/utils": ^0.1.3
+ checksum: 54b4fe26b3c228746ac5589f97303abf158b80aa5f8b99027259decd68d1c2030c4c637648ebd33dfe78a4212699453bc2bd7537fd5a594d3bd3e63d362f666f
+ languageName: node
+ linkType: hard
+
+"@floating-ui/dom@npm:^1.0.1":
+ version: 1.5.3
+ resolution: "@floating-ui/dom@npm:1.5.3"
+ dependencies:
+ "@floating-ui/core": ^1.4.2
+ "@floating-ui/utils": ^0.1.3
+ checksum: 00053742064aac70957f0bd5c1542caafb3bfe9716588bfe1d409fef72a67ed5e60450d08eb492a77f78c22ed1ce4f7955873cc72bf9f9caf2b0f43ae3561c21
+ languageName: node
+ linkType: hard
+
+"@floating-ui/utils@npm:^0.1.3":
+ version: 0.1.6
+ resolution: "@floating-ui/utils@npm:0.1.6"
+ checksum: b34d4b5470869727f52e312e08272edef985ba5a450a76de0917ba0a9c6f5df2bdbeb99448e2c60f39b177fb8981c772ff1831424e75123471a27ebd5b52c1eb
+ languageName: node
+ linkType: hard
+
"@gar/promisify@npm:^1.1.3":
version: 1.1.3
resolution: "@gar/promisify@npm:1.1.3"
@@ -1221,6 +1407,15 @@ __metadata:
languageName: node
linkType: hard
+"@types/react-transition-group@npm:^4.4.0":
+ version: 4.4.8
+ resolution: "@types/react-transition-group@npm:4.4.8"
+ dependencies:
+ "@types/react": "*"
+ checksum: ad7ba2bce97631fda9d89b4ed9772489bd050fec3ccd7563041b206dbe219d37d22e0d7731b1f90f56e89daf40e69ba16beba8066c42165bf8a584533feb6a2c
+ languageName: node
+ linkType: hard
+
"@types/react-transition-group@npm:^4.4.5":
version: 4.4.6
resolution: "@types/react-transition-group@npm:4.4.6"
@@ -2116,6 +2311,17 @@ __metadata:
languageName: node
linkType: hard
+"babel-plugin-macros@npm:^3.1.0":
+ version: 3.1.0
+ resolution: "babel-plugin-macros@npm:3.1.0"
+ dependencies:
+ "@babel/runtime": ^7.12.5
+ cosmiconfig: ^7.0.0
+ resolve: ^1.19.0
+ checksum: 765de4abebd3e4688ebdfbff8571ddc8cd8061f839bb6c3e550b0344a4027b04c60491f843296ce3f3379fb356cc873d57a9ee6694262547eb822c14a25be9a6
+ languageName: node
+ linkType: hard
+
"balanced-match@npm:^1.0.0":
version: 1.0.2
resolution: "balanced-match@npm:1.0.2"
@@ -2660,7 +2866,7 @@ colors@latest:
languageName: node
linkType: hard
-"convert-source-map@npm:^1.7.0":
+"convert-source-map@npm:^1.5.0, convert-source-map@npm:^1.7.0":
version: 1.9.0
resolution: "convert-source-map@npm:1.9.0"
checksum: dc55a1f28ddd0e9485ef13565f8f756b342f9a46c4ae18b843fe3c30c675d058d6a4823eff86d472f187b176f0adf51ea7b69ea38be34be4a63cbbf91b0593c8
@@ -2700,7 +2906,7 @@ cors@latest:
languageName: node
linkType: hard
-"cosmiconfig@npm:^7.0.1, cosmiconfig@npm:^7.1.0":
+"cosmiconfig@npm:^7.0.0, cosmiconfig@npm:^7.0.1, cosmiconfig@npm:^7.1.0":
version: 7.1.0
resolution: "cosmiconfig@npm:7.1.0"
dependencies:
@@ -4073,6 +4279,13 @@ cors@latest:
languageName: node
linkType: hard
+"find-root@npm:^1.1.0":
+ version: 1.1.0
+ resolution: "find-root@npm:1.1.0"
+ checksum: b2a59fe4b6c932eef36c45a048ae8f93c85640212ebe8363164814990ee20f154197505965f3f4f102efc33bfb1cbc26fd17c4a2fc739ebc51b886b137cbefaf
+ languageName: node
+ linkType: hard
+
"find-up@npm:^3.0.0":
version: 3.0.0
resolution: "find-up@npm:3.0.0"
@@ -4202,6 +4415,8 @@ cors@latest:
react: ^18.1.0
react-bootstrap: ^2.7.4
react-dom: ^18.1.0
+ react-hook-form: ^7.47.0
+ react-select: ^5.7.7
react-switch: ^7.0.0
stylelint: ^14.7.1
stylelint-config-prettier: ^9.0.3
@@ -4264,6 +4479,13 @@ cors@latest:
languageName: node
linkType: hard
+"function-bind@npm:^1.1.2":
+ version: 1.1.2
+ resolution: "function-bind@npm:1.1.2"
+ checksum: 2b0ff4ce708d99715ad14a6d1f894e2a83242e4a52ccfcefaee5e40050562e5f6dafc1adbb4ce2d4ab47279a45dc736ab91ea5042d843c3c092820dfe032efb1
+ languageName: node
+ linkType: hard
+
"function.prototype.name@npm:^1.1.5":
version: 1.1.5
resolution: "function.prototype.name@npm:1.1.5"
@@ -4629,6 +4851,24 @@ cors@latest:
languageName: node
linkType: hard
+"hasown@npm:^2.0.0":
+ version: 2.0.0
+ resolution: "hasown@npm:2.0.0"
+ dependencies:
+ function-bind: ^1.1.2
+ checksum: 6151c75ca12554565098641c98a40f4cc86b85b0fd5b6fe92360967e4605a4f9610f7757260b4e8098dd1c2ce7f4b095f2006fe72a570e3b6d2d28de0298c176
+ languageName: node
+ linkType: hard
+
+"hoist-non-react-statics@npm:^3.3.1":
+ version: 3.3.2
+ resolution: "hoist-non-react-statics@npm:3.3.2"
+ dependencies:
+ react-is: ^16.7.0
+ checksum: b1538270429b13901ee586aa44f4cc3ecd8831c061d06cb8322e50ea17b3f5ce4d0e2e66394761e6c8e152cd8c34fb3b4b690116c6ce2bd45b18c746516cb9e8
+ languageName: node
+ linkType: hard
+
"hosted-git-info@npm:^2.1.4":
version: 2.8.9
resolution: "hosted-git-info@npm:2.8.9"
@@ -4978,6 +5218,15 @@ cors@latest:
languageName: node
linkType: hard
+"is-core-module@npm:^2.13.0":
+ version: 2.13.1
+ resolution: "is-core-module@npm:2.13.1"
+ dependencies:
+ hasown: ^2.0.0
+ checksum: 256559ee8a9488af90e4bad16f5583c6d59e92f0742e9e8bb4331e758521ee86b810b93bae44f390766ffbc518a0488b18d9dab7da9a5ff997d499efc9403f7c
+ languageName: node
+ linkType: hard
+
"is-data-descriptor@npm:^0.1.4":
version: 0.1.4
resolution: "is-data-descriptor@npm:0.1.4"
@@ -5770,6 +6019,13 @@ cors@latest:
languageName: node
linkType: hard
+"memoize-one@npm:^6.0.0":
+ version: 6.0.0
+ resolution: "memoize-one@npm:6.0.0"
+ checksum: f185ea69f7cceae5d1cb596266dcffccf545e8e7b4106ec6aa93b71ab9d16460dd118ac8b12982c55f6d6322fcc1485de139df07eacffaae94888b9b3ad7675f
+ languageName: node
+ linkType: hard
+
"meow@npm:^9.0.0":
version: 9.0.0
resolution: "meow@npm:9.0.0"
@@ -6785,7 +7041,7 @@ opn@latest:
languageName: node
linkType: hard
-"prop-types@npm:^15.6.2, prop-types@npm:^15.7.2, prop-types@npm:^15.8.1":
+"prop-types@npm:^15.6.0, prop-types@npm:^15.6.2, prop-types@npm:^15.7.2, prop-types@npm:^15.8.1":
version: 15.8.1
resolution: "prop-types@npm:15.8.1"
dependencies:
@@ -6940,7 +7196,16 @@ proxy-middleware@latest:
languageName: node
linkType: hard
-"react-is@npm:^16.13.1, react-is@npm:^16.3.2":
+"react-hook-form@npm:^7.47.0":
+ version: 7.47.0
+ resolution: "react-hook-form@npm:7.47.0"
+ peerDependencies:
+ react: ^16.8.0 || ^17 || ^18
+ checksum: dec192fec9c54e436f9e47008635dd7849b6b119ed477a9b0cd491367a0b2ced3427cd937febfb245e1cb7578c863917181d903eff4519c2787bf713ec7d3426
+ languageName: node
+ linkType: hard
+
+"react-is@npm:^16.13.1, react-is@npm:^16.3.2, react-is@npm:^16.7.0":
version: 16.13.1
resolution: "react-is@npm:16.13.1"
checksum: f7a19ac3496de32ca9ae12aa030f00f14a3d45374f1ceca0af707c831b2a6098ef0d6bdae51bd437b0a306d7f01d4677fcc8de7c0d331eb47ad0f46130e53c5f
@@ -6954,6 +7219,26 @@ proxy-middleware@latest:
languageName: node
linkType: hard
+"react-select@npm:^5.7.7":
+ version: 5.7.7
+ resolution: "react-select@npm:5.7.7"
+ dependencies:
+ "@babel/runtime": ^7.12.0
+ "@emotion/cache": ^11.4.0
+ "@emotion/react": ^11.8.1
+ "@floating-ui/dom": ^1.0.1
+ "@types/react-transition-group": ^4.4.0
+ memoize-one: ^6.0.0
+ prop-types: ^15.6.0
+ react-transition-group: ^4.3.0
+ use-isomorphic-layout-effect: ^1.1.2
+ peerDependencies:
+ react: ^16.8.0 || ^17.0.0 || ^18.0.0
+ react-dom: ^16.8.0 || ^17.0.0 || ^18.0.0
+ checksum: 6fd0c211d377addba6e6762a614ae674936df39a3f46ec19fd06e7acae8d6cadeb93d4723b10e25eff1ff8235077bae9459f293936334d82b28fe5071081c057
+ languageName: node
+ linkType: hard
+
"react-switch@npm:^7.0.0":
version: 7.0.0
resolution: "react-switch@npm:7.0.0"
@@ -6966,7 +7251,7 @@ proxy-middleware@latest:
languageName: node
linkType: hard
-"react-transition-group@npm:^4.4.5":
+"react-transition-group@npm:^4.3.0, react-transition-group@npm:^4.4.5":
version: 4.4.5
resolution: "react-transition-group@npm:4.4.5"
dependencies:
@@ -7074,6 +7359,13 @@ proxy-middleware@latest:
languageName: node
linkType: hard
+"regenerator-runtime@npm:^0.14.0":
+ version: 0.14.0
+ resolution: "regenerator-runtime@npm:0.14.0"
+ checksum: 1c977ad82a82a4412e4f639d65d22be376d3ebdd30da2c003eeafdaaacd03fc00c2320f18120007ee700900979284fc78a9f00da7fb593f6e6eeebc673fba9a3
+ languageName: node
+ linkType: hard
+
"regex-not@npm:^1.0.0, regex-not@npm:^1.0.2":
version: 1.0.2
resolution: "regex-not@npm:1.0.2"
@@ -7178,6 +7470,19 @@ proxy-middleware@latest:
languageName: node
linkType: hard
+"resolve@npm:^1.19.0":
+ version: 1.22.8
+ resolution: "resolve@npm:1.22.8"
+ dependencies:
+ is-core-module: ^2.13.0
+ path-parse: ^1.0.7
+ supports-preserve-symlinks-flag: ^1.0.0
+ bin:
+ resolve: bin/resolve
+ checksum: f8a26958aa572c9b064562750b52131a37c29d072478ea32e129063e2da7f83e31f7f11e7087a18225a8561cfe8d2f0df9dbea7c9d331a897571c0a2527dbb4c
+ languageName: node
+ linkType: hard
+
"resolve@npm:^2.0.0-next.4":
version: 2.0.0-next.4
resolution: "resolve@npm:2.0.0-next.4"
@@ -7204,6 +7509,19 @@ proxy-middleware@latest:
languageName: node
linkType: hard
+"resolve@patch:resolve@^1.19.0#~builtin":
+ version: 1.22.8
+ resolution: "resolve@patch:resolve@npm%3A1.22.8#~builtin::version=1.22.8&hash=07638b"
+ dependencies:
+ is-core-module: ^2.13.0
+ path-parse: ^1.0.7
+ supports-preserve-symlinks-flag: ^1.0.0
+ bin:
+ resolve: bin/resolve
+ checksum: 5479b7d431cacd5185f8db64bfcb7286ae5e31eb299f4c4f404ad8aa6098b77599563ac4257cb2c37a42f59dfc06a1bec2bcf283bb448f319e37f0feb9a09847
+ languageName: node
+ linkType: hard
+
"resolve@patch:resolve@^2.0.0-next.4#~builtin":
version: 2.0.0-next.4
resolution: "resolve@patch:resolve@npm%3A2.0.0-next.4#~builtin::version=2.0.0-next.4&hash=07638b"
@@ -7603,7 +7921,7 @@ send@latest:
languageName: node
linkType: hard
-"source-map@npm:^0.5.6":
+"source-map@npm:^0.5.6, source-map@npm:^0.5.7":
version: 0.5.7
resolution: "source-map@npm:0.5.7"
checksum: 5dc2043b93d2f194142c7f38f74a24670cd7a0063acdaf4bf01d2964b402257ae843c2a8fa822ad5b71013b5fcafa55af7421383da919752f22ff488bc553f4d
@@ -8016,6 +8334,13 @@ send@latest:
languageName: node
linkType: hard
+"stylis@npm:4.2.0":
+ version: 4.2.0
+ resolution: "stylis@npm:4.2.0"
+ checksum: 0eb6cc1b866dc17a6037d0a82ac7fa877eba6a757443e79e7c4f35bacedbf6421fadcab4363b39667b43355cbaaa570a3cde850f776498e5450f32ed2f9b7584
+ languageName: node
+ linkType: hard
+
"supports-color@npm:^5.3.0":
version: 5.5.0
resolution: "supports-color@npm:5.5.0"
@@ -8531,6 +8856,18 @@ send@latest:
languageName: node
linkType: hard
+"use-isomorphic-layout-effect@npm:^1.1.2":
+ version: 1.1.2
+ resolution: "use-isomorphic-layout-effect@npm:1.1.2"
+ peerDependencies:
+ react: ^16.8.0 || ^17.0.0 || ^18.0.0
+ peerDependenciesMeta:
+ "@types/react":
+ optional: true
+ checksum: a6532f7fc9ae222c3725ff0308aaf1f1ddbd3c00d685ef9eee6714fd0684de5cb9741b432fbf51e61a784e2955424864f7ea9f99734a02f237b17ad3e18ea5cb
+ languageName: node
+ linkType: hard
+
"use@npm:^3.1.0":
version: 3.1.1
resolution: "use@npm:3.1.1"