0

We are trying to modify the Polkadot-JS/apps UI to add an app of our own. We have cloned the Contracts page to make our own page so that we can easily call the contract message functions. Contract, like accounts, must be loaded into the browser of the user. We can do this with the Add component. But we want to automate it so that the user will not have to do it themselves. We have narrowed it down to this one file: Add.tsx (code below). Is it possible to alter this file to add just one particular contract whose public key and JSON file are known? We are OK with having a button that says "load the app", but would like to auto populate all the elements in that form. Something even more automated would be even better but we will take what we can get!

// Copyright 2017-2023 @polkadot/app-contracts authors & contributors
// SPDX-License-Identifier: Apache-2.0

//import { Abi } from '@polkadot/api-contract';

import type { ActionStatus } from '@polkadot/react-components/Status/types';
import JSONlifeAndWork from './claim_registration_v20230314.json';
import React, { useCallback, useState } from 'react';

import { AddressRow, Button, Input, Modal } from '@polkadot/react-components';
import { useApi, useNonEmptyString } from '@polkadot/react-hooks';
import { keyring } from '@polkadot/ui-keyring';

import { ABI, InputName } from '../shared';
import { useTranslation } from '../translate';
import useAbi from '../useAbi';
import ValidateAddr from './ValidateAddr';

interface Props {
  defaultAddress: string;
  onClose: () => void;
}

function Add ({ defaultAddress, onClose }: Props): React.ReactElement {
  const { t } = useTranslation();
  //const JSONlifeAndWork: string | null ='./claim_registration_v20230314.json';
  //const _JSONParse = JSON.parse(JSONlifeAndWork.toString());
  
  const JSONabi: string | null = JSON.stringify(JSONlifeAndWork);
  const _contractName: string = 'Life & Work';
  const { api } = useApi();
  const [address, setAddress] = useState<string | null>(defaultAddress);
  const [isAddressValid, setIsAddressValid] = useState(false);
  const [name, isNameValid, setName] = useNonEmptyString(_contractName);
  const { abi, contractAbi, errorText, isAbiError, isAbiSupplied, isAbiValid, onChangeAbi, onRemoveAbi } = useAbi([null, null], null, true);

  const isTestInfo: boolean = false;

  const _onAdd = useCallback(
    (): void => {
      const status: Partial<ActionStatus> = { action: 'create' };
      
      if (!address || !abi || !name) {
        return;
      }
      //const contract = new ContractPromise(api, JSONlifeAndWork, address);
      try {       
        let json = {
          contract: {
            abi,
            genesisHash: api.genesisHash.toHex()
          },
          name,
          tags: []
        };
      
        keyring.saveContract(address, json);

        status.account = address;
        status.status = address ? 'success' : 'error';
        status.message = 'contract added';

        onClose();
      } catch (error) {
        console.error(error);

        status.status = 'error';
        status.message = (error as Error).message;
      }
    },
    [abi, address, api, name, onClose]
  );

  const isValid = isAddressValid && isNameValid && isAbiValid;
  
  return (
    <Modal
      header={t('Load the Life & Work Contract to your local browser: (Click Save)')}
      onClose={onClose}
    >
      <Modal.Content>
      {isTestInfo && (
      <div>
      <strong>{'STEPS: '}</strong>  <br />
      {'(1) Is Address Correct?: '}{(address && address.length===48)? 'YES' : 'NO'}<br />
      {'(2) Is the Contract Name Correct: '}{(name && name==='Life & Work')? 'YES' : 'NO'}<br />
      {'(3) Is JSON Loaded?: '}{(JSONabi.length>0)? 'YES' : 'NO'}<br />
      {'(4) Is ABI Loaded?: '}{(abi)? 'YES! - Click Save' : 'NO'}<br /><br />
      {'abi Length: '}{abi?.length}<br />
      {'json Length: '}{JSONabi.length}<br />
      </div>
      )}            
        <AddressRow
          defaultName={name}
          isValid
          value={address || null}
        >
          <Input
            autoFocus
            isError={!isAddressValid}
            label={t<string>('contract address')}
            onChange={setAddress}
            value={address || ''}
          />
          <ValidateAddr
            address={address}
            onChange={setIsAddressValid}
          />
          <InputName
            isContract
            isError={!isNameValid}
            onChange={setName}
            value={name || undefined}
          />
          <ABI
            contractAbi={contractAbi}
            errorText={errorText}
            isError={isAbiError || !isAbiValid}
            isSupplied={isAbiSupplied}
            isValid={isAbiValid}
            onChange={onChangeAbi}
            onRemove={onRemoveAbi}
          />
        </AddressRow>
      </Modal.Content>
      <Modal.Actions>
        <Button
          icon='save'
          isDisabled={!isValid}
          label={t<string>('Save')}
          onClick={_onAdd}
        />
      </Modal.Actions>
    </Modal>
  );
}

export default React.memo(Add);

2 Answers 2

1

Once you load the metadata.json file from your "known source" you just instantiate a contract promise. I am not completely sure what exactly you are trying to do, but this is how I would set up a UI for a contract where we have the metadata that was generated from compiling an ink! contract. Here is an example. You can then use the contract promise instance to call each message / transaction using something like this, or like this:

        const params = ['John', 'some other message argument'];
        const message = contract.abi.findMessage("myMessage");
        const inputData = message.toU8a(params);

        const { storageDeposit, gasRequired, result } =
          await api.call.contractsApi.call<ContractExecResult>(
            account?.address,
            contract.address,
            0,
            null,
            null,
            inputData
          );

There will be examples coming out for useink, a React Hooks library to make all of this easier.

3
  • Loading the metadata.json is exactly where we run into trouble. Because our UI is a clone of the Polkadot-JS/apps, we need to get the contract into the list of contracts on the UI > Developer > Contracts page BUT we need to do it in a way that makes it easy for the user. For example, is there a way to have one button that says "Load this app" on our app page in the UI, and when the user clicks that button, the contract is loaded into the UI > Developer > Contracts list? Commented Mar 26, 2023 at 13:52
  • We have been trying to automate the actions that the Add.tsx file does, but we cannot figure out how to automate the uploading of the metadata.json file without making the user drag and drop or some other action. Is there a way to load the contract (which already exists on chain) into the Developer > Contracts page list of contracts with just one button? Commented Mar 26, 2023 at 13:52
  • I tried asking my question in a better way (I hope)... see this one: substrate.stackexchange.com/questions/7786/… Commented Mar 26, 2023 at 23:53
1

No you cannot just use polkadot.js Add.tsx it depends upon internal @polkadot/react-hooks package which requires complete setup with all the networks. Why not use simple @polkadot/api-contract to setup your dapp.

Sample code is given here: https://github.com/swanky-dapps/wasm-lottery/blob/master/src/Home.tsx

const abi = new Abi(ABI, api.registry.getChainProperties())

const contract = new ContractPromise(api, abi, address)

await contract.tx
  .method({
    gasLimit: gasRequired,
    storageDepositLimit: null,
    value
  })
  .signAndSend(account.address, (res) => {
    if (res.status.isInBlock) {
      console.log('in a block')
    }
    if (res.status.isFinalized) {
      console.log('finalized')
    }
  })

Not the answer you're looking for? Browse other questions tagged or ask your own question.