@polkadot/api#SubmittableResult TypeScript Examples

The following examples show how to use @polkadot/api#SubmittableResult. You can vote up the ones you like or vote down the ones you don't like, and go to the original project or source file by following the links above each example. You may check out the related API usage on the sidebar.
Example #1
Source File: util.ts    From subscan-multisig-react with Apache License 2.0 7 votes vote down vote up
export function handleTxResults(
  handler: 'send' | 'signAndSend',
  queueSetTxStatus: QueueTxMessageSetStatus,
  { id, txFailedCb = NOOP, txSuccessCb = NOOP, txUpdateCb = NOOP }: QueueTx,
  unsubscribe: () => void
): (result: SubmittableResult) => void {
  return (result: SubmittableResult): void => {
    if (!result || !result.status) {
      return;
    }

    const status = result.status.type.toLowerCase() as QueueTxStatus;

    queueSetTxStatus(id, status, result);
    txUpdateCb(result);

    if (result.status.isFinalized || result.status.isInBlock) {
      result.events
        .filter(({ event: { section } }) => section === 'system')
        .forEach(({ event: { method } }): void => {
          if (method === 'ExtrinsicFailed') {
            txFailedCb(result);
          } else if (method === 'ExtrinsicSuccess') {
            txSuccessCb(result);
          }
        });
    } else if (result.isError) {
      txFailedCb(result);
    }

    if (result.isCompleted) {
      unsubscribe();
    }
  };
}
Example #2
Source File: util.ts    From crust-apps with Apache License 2.0 7 votes vote down vote up
export function handleTxResults (handler: 'send' | 'signAndSend', queueSetTxStatus: QueueTxMessageSetStatus, { id, txFailedCb = NOOP, txSuccessCb = NOOP, txUpdateCb = NOOP }: QueueTx, unsubscribe: () => void): (result: SubmittableResult) => void {
  return (result: SubmittableResult): void => {
    if (!result || !result.status) {
      return;
    }

    const status = result.status.type.toLowerCase() as QueueTxStatus;

    console.log(`${handler}: status :: ${JSON.stringify(result)}`);

    queueSetTxStatus(id, status, result);
    txUpdateCb(result);

    if (result.status.isFinalized || result.status.isInBlock) {
      result.events
        .filter(({ event: { section } }) => section === 'system')
        .forEach(({ event: { method } }): void => {
          if (method === 'ExtrinsicFailed') {
            txFailedCb(result);
          } else if (method === 'ExtrinsicSuccess') {
            txSuccessCb(result);
          }
        });
    } else if (result.isError) {
      txFailedCb(result);
    }

    if (result.isCompleted) {
      unsubscribe();
    }
  };
}
Example #3
Source File: useQueueTx.ts    From contracts-ui with GNU General Public License v3.0 6 votes vote down vote up
export function useQueueTx(
  extrinsic: SubmittableExtrinsic<'promise'> | null,
  accountId: string | null | undefined,
  onSuccess: (_: SubmittableResult) => Promise<void>,
  onError: VoidFn,
  isValid: (_: SubmittableResult) => boolean
): [VoidFn, VoidFn, boolean, boolean] {
  const { queue, dismiss, process, txs } = useTransactions();
  const [txId, setTxId] = useState<number>(0);

  const txIdRef = useRef(txId);

  const isProcessing = useMemo(
    (): boolean => !!(txs[txId] && txs[txId]?.status === 'processing'),
    [txs, txId]
  );

  const onSubmit = useCallback((): void => {
    txId && process(txId);
  }, [process, txId]);

  const onCancel = useCallback((): void => {
    txId && dismiss(txId);
    setTxId(0);
  }, [dismiss, txId]);

  useEffect((): void => {
    if (extrinsic && accountId && txId === 0) {
      const newId = queue({ extrinsic, accountId, onSuccess, onError, isValid });

      setTxId(newId);
      txIdRef.current = newId;
    }
  }, [accountId, extrinsic, isValid, onError, onSuccess, queue, txId]);

  return [onSubmit, onCancel, !isNull(txId), isProcessing];
}
Example #4
Source File: ApiSigner.ts    From subscan-multisig-react with Apache License 2.0 6 votes vote down vote up
public update(id: number, result: Hash | SubmittableResult): void {
    // eslint-disable-next-line @typescript-eslint/ban-ts-comment
    // @ts-ignore
    if (result instanceof this.#registry.createClass('Hash')) {
      // if (result instanceof ClassOf(this.#registry, 'Hash')) {
      this.#queueSetTxStatus(id, 'sent', result.toHex());
    } else {
      this.#queueSetTxStatus(id, (result as SubmittableResult).status.type.toLowerCase() as QueueTxStatus, status);
    }
  }
Example #5
Source File: utils.ts    From evm-provider.js with Apache License 2.0 6 votes vote down vote up
export function handleTxResponse(
  result: SubmittableResult,
  api: ApiPromise
): Promise<{
  result: SubmittableResult;
  message?: string;
}> {
  return new Promise((resolve, reject) => {
    if (result.status.isFinalized || result.status.isInBlock) {
      const createdFailed = result.findRecord('evm', 'CreatedFailed');
      const executedFailed = result.findRecord('evm', 'ExecutedFailed');

      result.events
        .filter(({ event: { section } }): boolean => section === 'system')
        .forEach((event): void => {
          const {
            event: { data, method }
          } = event;

          if (method === 'ExtrinsicFailed') {
            // eslint-disable-next-line @typescript-eslint/no-explicit-any
            const [dispatchError] = data as any[];

            let message = dispatchError.type;

            if (dispatchError.isModule) {
              try {
                const mod = dispatchError.asModule;
                const error = api.registry.findMetaError(
                  new Uint8Array([mod.index.toNumber(), mod.error.toNumber()])
                );
                message = `${error.section}.${error.name}`;
              } catch (error) {
                // swallow
              }
            }

            reject({ message, result });
          } else if (method === 'ExtrinsicSuccess') {
            const failed = createdFailed || executedFailed;
            if (failed) {
              reject({
                message: decodeMessage(
                  failed.event.data[1].toJSON(),
                  failed.event.data[2].toJSON() as string
                ),
                result
              });
            }
            resolve({ result });
          }
        });
    } else if (result.isError) {
      reject({ result });
    }
  });
}
Example #6
Source File: Signer.ts    From evm-provider.js with Apache License 2.0 6 votes vote down vote up
/**
   * Claims a default EVM address for this signer's substrate address
   */
  async claimDefaultAccount(): Promise<void> {
    const extrinsic = this.provider.api.tx.evmAccounts.claimDefaultAccount();

    await extrinsic.signAsync(this._substrateAddress);

    await new Promise<void>((resolve, reject) => {
      extrinsic
        .send((result: SubmittableResult) => {
          handleTxResponse(result, this.provider.api)
            .then(() => {
              resolve();
            })
            .catch(({ message, result }) => {
              if (message === 'evmAccounts.AccountIdHasMapped') {
                resolve();
              }
              reject(message);
            });
        })
        .catch((error) => {
          reject(error && error.message);
        });
    });
  }
Example #7
Source File: Signer.ts    From evm-provider.js with Apache License 2.0 6 votes vote down vote up
async claimEvmAccount(evmAddress: string): Promise<void> {
    const isConnented = await this.isClaimed(evmAddress);

    if (isConnented) return;

    const publicKey = decodeAddress(this._substrateAddress);
    const data = 'Reef evm:' + Buffer.from(publicKey).toString('hex');
    const signature = await this._signMessage(evmAddress, data);
    const extrinsic = this.provider.api.tx.evmAccounts.claimAccount(
      evmAddress,
      signature
    );

    await extrinsic.signAsync(this._substrateAddress);

    await new Promise<void>((resolve, reject) => {
      extrinsic
        .send((result: SubmittableResult) => {
          handleTxResponse(result, this.provider.api)
            .then(() => {
              resolve();
            })
            .catch(({ message, result }) => {
              if (message === 'evmAccounts.AccountIdHasMapped') {
                resolve();
              }
              reject(message);
            });
        })
        .catch((error) => {
          reject(error && error.message);
        });
    });
  }
Example #8
Source File: keyring.ts    From sdk with Apache License 2.0 6 votes vote down vote up
/**
 * send tx with signed data from QR
 */
function addSignatureAndSend(api: ApiPromise, address: string, signed: string) {
  return new Promise((resolve) => {
    const { tx, payload } = getSubmittable();
    if (!!tx.addSignature) {
      tx.addSignature(address, `0x${signed}`, payload);

      let unsub = () => {};
      const onStatusChange = (result: SubmittableResult) => {
        if (result.status.isInBlock || result.status.isFinalized) {
          const { success, error } = _extractEvents(api, result);
          if (success) {
            resolve({ hash: tx.hash.toString(), blockHash: (result.status.asInBlock || result.status.asFinalized).toHex() });
          }
          if (error) {
            resolve({ error });
          }
          unsub();
        } else {
          (<any>window).send("txStatusChange", result.status.type);
        }
      };

      tx.send(onStatusChange)
        .then((res) => {
          unsub = res;
        })
        .catch((err) => {
          resolve({ error: err.message });
        });
    } else {
      resolve({ error: "invalid tx" });
    }
  });
}
Example #9
Source File: Signer.ts    From bodhi.js with Apache License 2.0 6 votes vote down vote up
async claimEvmAccount(evmAddress: string): Promise<void> {
    const isConnented = await this.isClaimed(evmAddress);

    if (isConnented) return;

    const publicKey = decodeAddress(this._substrateAddress);
    const data = 'acala evm:' + Buffer.from(publicKey).toString('hex');
    const signature = await this._signMessage(evmAddress, data);
    const extrinsic = this.provider.api.tx.evmAccounts.claimAccount(evmAddress, signature);

    await extrinsic.signAsync(this._substrateAddress);

    await new Promise<void>((resolve, reject) => {
      extrinsic
        .send((result: SubmittableResult) => {
          handleTxResponse(result, this.provider.api)
            .then(() => {
              resolve();
            })
            .catch((err) => {
              if (err.message === 'evmAccounts.AccountIdHasMapped') {
                resolve();
              }
              reject(err);
            });
        })
        .catch(reject);
    });
  }
Example #10
Source File: common.ts    From subsocial-js with GNU General Public License v3.0 6 votes vote down vote up
export function getNewIdsFromEvent (txResult: SubmittableResult, eventType: ResultEventType = 'Created'): BN[] {
  const newIds: BN[] = []

  txResult.events.find(event => {
    const { event: { data, method } } = event
    if (method.indexOf(eventType) >= 0) {
      const [ /* owner */, ...ids ] = data.toArray()
      newIds.push(...ids as unknown as BN[])
      return true
    }
    return false
  })

  return newIds
}
Example #11
Source File: sendTx.ts    From bodhi.js with Apache License 2.0 6 votes vote down vote up
sendTx = (api: ApiPromise, extrinsic: SubmittableExtrinsic<'promise'>): Promise<SubmittableResult> => {
  return new Promise((resolve, reject) => {
    extrinsic
      .send((result) => {
        handleTxResponse(result, api)
          .then(({ result }) => {
            resolve(result);
          })
          .catch((err) => {
            reject(err);
          });
      })
      .catch((error) => {
        reject(error);
      });
  });
}
Example #12
Source File: Signer.ts    From bodhi.js with Apache License 2.0 6 votes vote down vote up
/**
   * Claims a default EVM address for this signer's substrate address
   */
  async claimDefaultAccount(): Promise<void> {
    const extrinsic = this.provider.api.tx.evmAccounts.claimDefaultAccount();

    await extrinsic.signAsync(this._substrateAddress);

    await new Promise<void>((resolve, reject) => {
      extrinsic
        .send((result: SubmittableResult) => {
          handleTxResponse(result, this.provider.api)
            .then(() => {
              resolve();
            })
            .catch((err) => {
              if (err.message === 'evmAccounts.AccountIdHasMapped') {
                resolve();
              }
              reject(err);
            });
        })
        .catch((error) => {
          reject(error);
        });
    });
  }
Example #13
Source File: ApiSigner.ts    From crust-apps with Apache License 2.0 5 votes vote down vote up
public update (id: number, result: Hash | SubmittableResult): void {
    if (result instanceof ClassOf(this.#registry, 'Hash')) {
      this.#queueSetTxStatus(id, 'sent', result.toHex());
    } else {
      this.#queueSetTxStatus(id, result.status.type.toLowerCase() as QueueTxStatus, status);
    }
  }
Example #14
Source File: keyring.ts    From sdk with Apache License 2.0 5 votes vote down vote up
function _extractEvents(api: ApiPromise, result: SubmittableResult) {
  if (!result || !result.events) {
    return {};
  }

  let success = false;
  let error: DispatchError["type"] = "";
  result.events
    .filter((event) => !!event.event)
    .map(({ event: { data, method, section } }) => {
      if (section === "system" && method === "ExtrinsicFailed") {
        const [dispatchError] = (data as unknown) as ITuple<[DispatchError]>;
        let message = dispatchError.type;

        if (dispatchError.isModule) {
          try {
            const mod = dispatchError.asModule;
            const err = api.registry.findMetaError(new Uint8Array([mod.index.toNumber(), mod.error.toNumber()]));

            message = `${err.section}.${err.name}`;
          } catch (error) {
            // swallow error
          }
        }
        (<any>window).send("txUpdateEvent", {
          title: `${section}.${method}`,
          message,
        });
        error = message;
      } else {
        (<any>window).send("txUpdateEvent", {
          title: `${section}.${method}`,
          message: "ok",
        });
        if (section == "system" && method == "ExtrinsicSuccess") {
          success = true;
        }
      }
    });
  return { success, error };
}
Example #15
Source File: keyring.ts    From sdk with Apache License 2.0 5 votes vote down vote up
/**
 * sign and send extrinsic to network and wait for result.
 */
function sendTx(api: ApiPromise, txInfo: any, paramList: any[], password: string, msgId: string) {
  return new Promise(async (resolve) => {
    let tx: SubmittableExtrinsic<"promise">;
    // wrap tx with council.propose for treasury propose
    if (txInfo.txName == "treasury.approveProposal") {
      tx = await gov.makeTreasuryProposalSubmission(api, paramList[0], false);
    } else if (txInfo.txName == "treasury.rejectProposal") {
      tx = await gov.makeTreasuryProposalSubmission(api, paramList[0], true);
    } else {
      tx = api.tx[txInfo.module][txInfo.call](...paramList);
    }
    let unsub = () => {};
    const onStatusChange = (result: SubmittableResult) => {
      if (result.status.isInBlock || result.status.isFinalized) {
        const { success, error } = _extractEvents(api, result);
        if (success) {
          resolve({ hash: tx.hash.toString(), blockHash: (result.status.asInBlock || result.status.asFinalized).toHex() });
        }
        if (error) {
          resolve({ error });
        }
        unsub();
      } else {
        (<any>window).send(msgId, result.status.type);
      }
    };
    if (txInfo.isUnsigned) {
      tx.send(onStatusChange)
        .then((res) => {
          unsub = res;
        })
        .catch((err) => {
          resolve({ error: err.message });
        });
      return;
    }

    let keyPair: KeyringPair;
    if (!txInfo.proxy) {
      keyPair = keyring.getPair(hexToU8a(txInfo.sender.pubKey));
    } else {
      // wrap tx with recovery.asRecovered for proxy tx
      tx = api.tx.recovery.asRecovered(txInfo.sender.address, tx);
      keyPair = keyring.getPair(hexToU8a(txInfo.proxy.pubKey));
    }

    try {
      keyPair.decodePkcs8(password);
    } catch (err) {
      resolve({ error: "password check failed" });
    }
    tx.signAndSend(keyPair, { tip: new BN(txInfo.tip, 10) }, onStatusChange)
      .then((res) => {
        unsub = res;
      })
      .catch((err) => {
        resolve({ error: err.message });
      });
  });
}
Example #16
Source File: Queue.tsx    From crust-apps with Apache License 2.0 5 votes vote down vote up
function extractEvents (result?: SubmittableResult): ActionStatus[] {
  return mergeStatus(
    ((result && result.events) || [])
      // filter events handled globally, or those we are not interested in, these are
      // handled by the global overview, so don't add them here
      .filter((record): boolean => !!record.event && record.event.section !== 'democracy')
      .map(({ event: { data, method, section } }): ActionStatusPartial => {
        if (section === 'system' && method === 'ExtrinsicFailed') {
          const [dispatchError] = data as unknown as ITuple<[DispatchError]>;
          let message = dispatchError.type;

          if (dispatchError.isModule) {
            try {
              const mod = dispatchError.asModule;
              const error = dispatchError.registry.findMetaError(mod);

              message = `${error.section}.${error.name}`;
            } catch (error) {
              // swallow
            }
          }

          return {
            action: `${section}.${method}`,
            message,
            status: 'error'
          };
        } else if (section === 'contracts') {
          if (method === 'ContractExecution' && data.length === 2) {
            // see if we have info for this contract
            const [accountId, encoded] = data;

            try {
              const abi = getContractAbi(accountId.toString());

              if (abi) {
                const decoded = abi.decodeEvent(encoded as Bytes);

                return {
                  action: decoded.event.identifier,
                  message: 'contract event',
                  status: 'event'
                };
              }
            } catch (error) {
              // ABI mismatch?
              console.error(error);
            }
          } else if (method === 'Evicted') {
            return {
              action: `${section}.${method}`,
              message: 'contract evicted',
              status: 'error'
            };
          }
        }

        return {
          action: `${section}.${method}`,
          message: EVENT_MESSAGE,
          status: 'event'
        };
      })
  );
}
Example #17
Source File: handleTxResponse.ts    From bodhi.js with Apache License 2.0 5 votes vote down vote up
export function handleTxResponse(
  result: SubmittableResult,
  api: ApiPromise
): Promise<{
  result: SubmittableResult;
  message?: string;
}> {
  return new Promise((resolve, reject) => {
    if (result.status.isFinalized || result.status.isInBlock) {
      const createdFailed = result.findRecord('evm', 'CreatedFailed');
      const executedFailed = result.findRecord('evm', 'ExecutedFailed');

      result.events
        .filter(({ event: { section } }): boolean => section === 'system')
        .forEach((event): void => {
          const {
            event: { data, method }
          } = event;

          if (method === 'ExtrinsicFailed') {
            // eslint-disable-next-line @typescript-eslint/no-explicit-any
            const [dispatchError] = data as any[];

            let message = dispatchError.type;

            if (dispatchError.isModule) {
              try {
                const mod = dispatchError.asModule;
                const error = api.registry.findMetaError(new Uint8Array([mod.index.toNumber(), mod.error.toNumber()]));
                message = `${error.section}.${error.name}: ${error.docs}`;
              } catch (error) {
                // swallow
              }
            }

            reject(makeError(message, { result }));
          } else if (method === 'ExtrinsicSuccess') {
            const failed = createdFailed || executedFailed;
            if (failed) {
              reject(
                makeError(decodeMessage(failed.event.data[2].toJSON(), failed.event.data[3].toJSON() as string), {
                  result
                })
              );
            }
            resolve({ result });
          }
        });
    } else if (result.isError) {
      reject({ result });
    }
  });
}
Example #18
Source File: Queue.tsx    From subscan-multisig-react with Apache License 2.0 5 votes vote down vote up
function extractEvents(result?: SubmittableResult): ActionStatus[] {
  return mergeStatus(
    ((result && result.events) || [])
      // filter events handled globally, or those we are not interested in, these are
      // handled by the global overview, so don't add them here
      .filter((record): boolean => !!record.event && record.event.section !== 'democracy')
      // eslint-disable-next-line complexity
      .map(({ event: { data, method, section } }): ActionStatusPartial => {
        if (section === 'system' && method === 'ExtrinsicFailed') {
          const [dispatchError] = data as unknown as ITuple<[DispatchError]>;
          let message: string = dispatchError.type;

          if (dispatchError.isModule) {
            try {
              const mod = dispatchError.asModule;
              const error = dispatchError.registry.findMetaError(mod);

              message = `${error.section}.${error.name}`;
            } catch (error) {
              // swallow
            }
          } else if (dispatchError.isToken) {
            message = `${dispatchError.type}.${dispatchError.asToken.type}`;
          }

          return {
            action: `${section}.${method}`,
            message,
            status: 'error',
          };
        } else if (section === 'contracts') {
          // eslint-disable-next-line no-magic-numbers
          if (method === 'ContractExecution' && data.length === 2) {
            // see if we have info for this contract
            const [accountId, encoded] = data;

            try {
              const abi = getContractAbi(accountId.toString());

              if (abi) {
                const decoded = abi.decodeEvent(encoded as Bytes);

                return {
                  action: decoded.event.identifier,
                  message: 'contract event',
                  status: 'event',
                };
              }
            } catch (error) {
              // ABI mismatch?
              console.error(error);
            }
          } else if (method === 'Evicted') {
            return {
              action: `${section}.${method}`,
              message: 'contract evicted',
              status: 'error',
            };
          }
        }

        return {
          action: `${section}.${method}`,
          message: EVENT_MESSAGE,
          status: 'event',
        };
      })
  );
}
Example #19
Source File: shared.ts    From commonwealth with GNU General Public License v3.0 4 votes vote down vote up
public createTXModalData(
    author: SubstrateAccount,
    txFunc: (api: ApiPromise) => SubmittableExtrinsic<'promise'>,
    txName: string,
    objName: string,
    cb?: (success: boolean) => void, // TODO: remove this argument
  ): ITXModalData {
    // TODO: check if author has funds for tx fee
    const events = new EventEmitter();
    return {
      author,
      txType: txName,
      cb,
      txData: {
        events,
        unsignedData: async (): Promise<ISubstrateTXData> => {
          const txHex = txFunc(this.api).method.toHex();
          const nonce = this.api.query.system.accountNonce
            ? await this.api.query.system.accountNonce(author.address)
            : (await this.api.query.system.account(author.address)).nonce;
          const genesisHash = this.api.genesisHash.toHex();
          return {
            call: txHex,
            nonce: (+nonce).toString(),
            blockHash: genesisHash,
            isEd25519: author.isEd25519,
          };
        },
        transact: (hexTxOrAddress?: string, signer?: Signer): void => {
          let unsubscribe: Promise<VoidFn>;
          const txResultHandler = (result: SubmittableResult) => {
            const status = result.status;
            if (status.isReady) {
              console.log(`Pending ${txName}: "${objName}"`);
              events.emit(TransactionStatus.Ready.toString(), {});
            } else if (status.isFinalized || status.isInBlock) {
              for (const e of result.events) {
                if (this.api.events.system.ExtrinsicSuccess.is(e.event)) {
                  notifySuccess(`Confirmed ${txName}`);
                  events.emit(TransactionStatus.Success.toString(), {
                    hash: status.isFinalized ? status.asFinalized.toHex() : status.asInBlock.toHex(),
                    blocknum: this.app.chain.block.height,
                    timestamp: this.app.chain.block.lastTime,
                  });
                  if (unsubscribe) unsubscribe.then((u) => u());
                } else if (this.api.events.system.ExtrinsicFailed.is(e.event)) {
                  const errorData = e.event.data[0] as unknown as DispatchError;
                  let errorInfo;
                  if (errorData.isModule) {
                    const decoded = this.registry.findMetaError(errorData.asModule);
                    const { docs, method, section } = decoded;
                    errorInfo = `${section}.${method}: ${docs.join(' ')}`;
                  } else if (errorData.isBadOrigin) {
                    errorInfo = 'TX Error: invalid sender origin';
                  } else if (errorData.isCannotLookup) {
                    errorInfo = 'TX Error: cannot lookup call';
                  } else {
                    errorInfo = 'TX Error: unknown';
                  }
                  console.error(errorInfo);
                  notifyError(`Failed ${txName}: "${objName}"`);
                  events.emit(TransactionStatus.Failed.toString(), {
                    hash: status.isFinalized ? status.asFinalized.toHex() : status.asInBlock.toHex(),
                    blocknum: this.app.chain.block.height,
                    timestamp: this.app.chain.block.lastTime,
                    err: errorInfo,
                  });
                  if (unsubscribe) unsubscribe.then((u) => u());
                }
              }
            }
          };
          try {
            if (signer) {
              this.api.setSigner(signer);
              unsubscribe = txFunc(this.api).signAndSend(hexTxOrAddress, txResultHandler);
            } else if (hexTxOrAddress) {
              unsubscribe = this.api.tx(hexTxOrAddress).send(txResultHandler);
            } else {
              throw new Error('no signer found');
            }
          } catch (err) {
            if (err.message.indexOf('1014: Priority is too low') !== -1) {
              notifyError('Another transaction is already queued for processing');
            } else {
              notifyError(err.toString());
            }
            m.redraw();
            events.emit(TransactionStatus.Error.toString(), { err: err.toString() });
          }
        },
      }
    };
  }
Example #20
Source File: TxButton.tsx    From crust-apps with Apache License 2.0 4 votes vote down vote up
function TxButton ({ accountId, className = '', extrinsic: propsExtrinsic, icon, isBasic, isBusy, isDisabled, isIcon, isToplevel, isUnsigned, label, onClick, onFailed, onSendRef, onStart, onSuccess, onUpdate, params, tooltip, tx, withSpinner, withoutLink }: Props): React.ReactElement<Props> {
  const { t } = useTranslation();
  const mountedRef = useIsMountedRef();
  const { queueExtrinsic } = useContext(StatusContext);
  const [isSending, setIsSending] = useState(false);
  const [isStarted, setIsStarted] = useState(false);

  useEffect((): void => {
    (isStarted && onStart) && onStart();
  }, [isStarted, onStart]);

  const _onFailed = useCallback(
    (result: SubmittableResult | null): void => {
      mountedRef.current && setIsSending(false);

      onFailed && onFailed(result);
    },
    [onFailed, setIsSending, mountedRef]
  );

  const _onSuccess = useCallback(
    (result: SubmittableResult): void => {
      mountedRef.current && setIsSending(false);

      onSuccess && onSuccess(result);
    },
    [onSuccess, setIsSending, mountedRef]
  );

  const _onStart = useCallback(
    (): void => {
      mountedRef.current && setIsStarted(true);
    },
    [setIsStarted, mountedRef]
  );

  const _onSend = useCallback(
    (): void => {
      let extrinsics: SubmittableExtrinsic<'promise'>[] | undefined;

      if (propsExtrinsic) {
        extrinsics = Array.isArray(propsExtrinsic)
          ? propsExtrinsic
          : [propsExtrinsic];
      } else if (tx) {
        extrinsics = [
          tx(...(
            isFunction(params)
              ? params()
              : (params || [])
          ))
        ];
      }

      assert(extrinsics?.length, 'Expected generated extrinsic passed to TxButton');

      mountedRef.current && withSpinner && setIsSending(true);

      extrinsics.forEach((extrinsic): void => {
        queueExtrinsic({
          accountId: accountId && accountId.toString(),
          extrinsic,
          isUnsigned,
          txFailedCb: withSpinner ? _onFailed : onFailed,
          txStartCb: _onStart,
          txSuccessCb: withSpinner ? _onSuccess : onSuccess,
          txUpdateCb: onUpdate
        });
      });

      onClick && onClick();
    },
    [_onFailed, _onStart, _onSuccess, accountId, isUnsigned, onClick, onFailed, onSuccess, onUpdate, params, propsExtrinsic, queueExtrinsic, setIsSending, tx, withSpinner, mountedRef]
  );

  if (onSendRef) {
    onSendRef.current = _onSend;
  }

  return (
    <Button
      className={className}
      icon={icon || 'check'}
      isBasic={isBasic}
      isBusy={isBusy}
      isDisabled={isSending || isDisabled || (!isUnsigned && !accountId) || (
        tx
          ? false
          : Array.isArray(propsExtrinsic)
            ? propsExtrinsic.length === 0
            : !propsExtrinsic
      )}
      isIcon={isIcon}
      isToplevel={isToplevel}
      label={label || (isIcon ? '' : t<string>('Submit'))}
      onClick={_onSend}
      tooltip={tooltip}
      withoutLink={withoutLink}
    />
  );
}
Example #21
Source File: Queue.tsx    From crust-apps with Apache License 2.0 4 votes vote down vote up
function Queue ({ children }: Props): React.ReactElement<Props> {
  const [stqueue, _setStQueue] = useState<QueueStatus[]>([]);
  const [txqueue, _setTxQueue] = useState<QueueTx[]>([]);
  const stRef = useRef(stqueue);
  const txRef = useRef(txqueue);

  const setStQueue = useCallback(
    (st: QueueStatus[]): void => {
      stRef.current = st;
      _setStQueue(st);
    },
    []
  );

  const setTxQueue = useCallback(
    (tx: QueueTx[]): void => {
      txRef.current = tx;
      _setTxQueue(tx);
    },
    []
  );

  const addToTxQueue = useCallback(
    (value: QueueTxExtrinsic | QueueTxRpc | QueueTx): void => {
      const id = ++nextId;
      const removeItem = () => setTxQueue([
        ...txRef.current.map((item): QueueTx =>
          item.id === id
            ? { ...item, status: 'completed' }
            : item
        )
      ]);

      setTxQueue([...txRef.current, {
        ...value,
        id,
        removeItem,
        rpc: (value as QueueTxRpc).rpc || SUBMIT_RPC,
        status: 'queued'
      }]);
    },
    [setTxQueue]
  );

  const queueAction = useCallback(
    (_status: ActionStatus | ActionStatus[]): void => {
      const status = Array.isArray(_status) ? _status : [_status];

      status.length && setStQueue([
        ...stRef.current,
        ...(status.map((item): QueueStatus => {
          const id = ++nextId;
          const removeItem = (): void =>
            setStQueue([...stRef.current.filter((item): boolean => item.id !== id)]);

          setTimeout(removeItem, REMOVE_TIMEOUT);

          return {
            ...item,
            id,
            isCompleted: false,
            removeItem
          };
        }))
      ]);
    },
    [setStQueue]
  );

  const queueExtrinsic = useCallback(
    (value: PartialQueueTxExtrinsic) => addToTxQueue({ ...value }),
    [addToTxQueue]
  );

  const queuePayload = useCallback(
    (registry: Registry, payload: SignerPayloadJSON, signerCb: SignerCallback): void => {
      addToTxQueue({
        accountId: payload.address,
        // this is not great, but the Extrinsic doesn't need a submittable
        extrinsic: registry.createType('Extrinsic',
          { method: registry.createType('Call', payload.method) },
          { version: payload.version }
        ) as unknown as SubmittableExtrinsic,
        payload,
        signerCb
      });
    },
    [addToTxQueue]
  );

  const queueRpc = useCallback(
    (value: PartialQueueTxRpc) => addToTxQueue({ ...value }),
    [addToTxQueue]
  );

  const queueSetTxStatus = useCallback(
    (id: number, status: QueueTxStatus, result?: SubmittableResult, error?: Error): void => {
      setTxQueue([
        ...txRef.current.map((item): QueueTx =>
          item.id === id
            ? {
              ...item,
              error: error === undefined
                ? item.error
                : error,
              result: result === undefined
                ? item.result as SubmittableResult
                : result,
              status: item.status === 'completed'
                ? item.status
                : status
            }
            : item
        )
      ]);

      queueAction(extractEvents(result));

      if (STATUS_COMPLETE.includes(status)) {
        setTimeout((): void => {
          const item = txRef.current.find((item): boolean => item.id === id);

          item && item.removeItem();
        }, REMOVE_TIMEOUT);
      }
    },
    [queueAction, setTxQueue]
  );

  return (
    <QueueProvider value={{
      queueAction,
      queueExtrinsic,
      queuePayload,
      queueRpc,
      queueSetTxStatus,
      stqueue,
      txqueue
    }}>
      {children}
    </QueueProvider>
  );
}
Example #22
Source File: Signer.ts    From evm-provider.js with Apache License 2.0 4 votes vote down vote up
/**
   *
   * @param transaction
   * @returns A promise that resolves to the transaction's response
   */
  async sendTransaction(
    _transaction: Deferrable<TransactionRequest>
  ): Promise<TransactionResponse> {
    this._checkProvider('sendTransaction');

    const signerAddress = await this.getSubstrateAddress();
    const evmAddress = await this.getAddress();

    // estimateResources requires the from parameter.
    // However, when creating the contract, there is no from parameter in the tx
    const transaction = {
      from: evmAddress,
      ..._transaction
    };

    const resources = await this.provider.estimateResources(transaction);

    // Multiply by 3.1
    const gasLimit: BigNumber = resources.gas.mul(31).div(10);
    let storageLimit: BigNumber;

    // If the storage limit is supplied, override it from the estimateResources
    if (transaction.customData) {
      if ('storageLimit' in transaction.customData) {
        storageLimit = transaction.customData.storageLimit;
        if (isNumber(storageLimit)) {
          storageLimit = BigNumber.from(storageLimit);
        }
      }
    } else {
      storageLimit = resources.storage.mul(31).div(10);
    }

    let totalLimit = await transaction.gasLimit;

    if (totalLimit === null || totalLimit === undefined) {
      totalLimit = gasLimit.add(storageLimit);
    }

    transaction.gasLimit = totalLimit;

    const tx = await this.populateTransaction(transaction);

    const data = tx.data;
    const from = tx.from;

    if (!data) {
      return logger.throwError('Request data not found');
    }

    if (!from) {
      return logger.throwError('Request from not found');
    }

    let extrinsic: SubmittableExtrinsic<'promise'>;

    // @TODO create contract
    if (!tx.to) {
      extrinsic = this.provider.api.tx.evm.create(
        tx.data,
        toBN(tx.value),
        toBN(gasLimit),
        toBN(storageLimit.isNegative() ? 0 : storageLimit)
      );
    } else {
      extrinsic = this.provider.api.tx.evm.call(
        tx.to,
        tx.data,
        toBN(tx.value),
        toBN(gasLimit),
        toBN(storageLimit.isNegative() ? 0 : storageLimit)
      );
    }

    await extrinsic.signAsync(signerAddress);

    return new Promise((resolve, reject) => {
      extrinsic
        .send((result: SubmittableResult) => {
          handleTxResponse(result, this.provider.api)
            .then(() => {
              resolve({
                hash: extrinsic.hash.toHex(),
                from: from || '',
                confirmations: 0,
                nonce: toBN(tx.nonce).toNumber(),
                gasLimit: BigNumber.from(tx.gasLimit || '0'),
                gasPrice: BigNumber.from(0),
                data: dataToString(data),
                value: BigNumber.from(tx.value || '0'),
                chainId: 13939,
                wait: (confirmations?: number): Promise<TransactionReceipt> => {
                  return this.provider._resolveTransactionReceipt(
                    extrinsic.hash.toHex(),
                    result.status.asInBlock.toHex(),
                    from
                  );
                }
              });
            })
            .catch(({ message, result }) => {
              reject(message);
            });
        })
        .catch((error) => {
          reject(error && error.message);
        });
    });
  }
Example #23
Source File: Queue.tsx    From subscan-multisig-react with Apache License 2.0 4 votes vote down vote up
function Queue({ children }: Props): React.ReactElement<Props> {
  const [stqueue, _setStQueue] = useState<QueueStatus[]>([]);
  const [txqueue, _setTxQueue] = useState<QueueTx[]>([]);
  const stRef = useRef(stqueue);
  const txRef = useRef(txqueue);

  const setStQueue = useCallback((st: QueueStatus[]): void => {
    stRef.current = st;
    _setStQueue(st);
  }, []);

  const setTxQueue = useCallback((tx: QueueTx[]): void => {
    txRef.current = tx;
    _setTxQueue(tx);
  }, []);

  const addToTxQueue = useCallback(
    (value: QueueTxExtrinsic | QueueTxRpc | QueueTx): void => {
      const id = ++nextId;
      const removeItem = () =>
        setTxQueue([
          ...txRef.current.map((item): QueueTx => (item.id === id ? { ...item, status: 'completed' } : item)),
        ]);

      setTxQueue([
        ...txRef.current,
        {
          ...value,
          id,
          removeItem,
          rpc: (value as QueueTxRpc).rpc || SUBMIT_RPC,
          status: 'queued',
        },
      ]);
    },
    [setTxQueue]
  );

  const queueAction = useCallback(
    (_status: ActionStatus | ActionStatus[]): void => {
      const status = Array.isArray(_status) ? _status : [_status];

      // eslint-disable-next-line
      status.length &&
        setStQueue([
          ...stRef.current,
          ...status.map((item): QueueStatus => {
            const id = ++nextId;
            const removeItem = (): void => setStQueue([...stRef.current.filter((item): boolean => item.id !== id)]);

            setTimeout(removeItem, REMOVE_TIMEOUT);

            return {
              ...item,
              id,
              isCompleted: false,
              removeItem,
            };
          }),
        ]);
    },
    [setStQueue]
  );

  const queueExtrinsic = useCallback((value: PartialQueueTxExtrinsic) => addToTxQueue({ ...value }), [addToTxQueue]);

  const queuePayload = useCallback(
    (registry: Registry, payload: SignerPayloadJSON, signerCb: SignerCallback): void => {
      addToTxQueue({
        accountId: payload.address,
        // this is not great, but the Extrinsic doesn't need a submittable
        extrinsic: registry.createType(
          'Extrinsic',
          { method: registry.createType('Call', payload.method) },
          { version: payload.version }
        ) as unknown as SubmittableExtrinsic,
        payload,
        signerCb,
      });
    },
    [addToTxQueue]
  );

  const queueRpc = useCallback((value: PartialQueueTxRpc) => addToTxQueue({ ...value }), [addToTxQueue]);

  const queueSetTxStatus = useCallback(
    (id: number, status: QueueTxStatus, result?: SubmittableResult, error?: Error): void => {
      setTxQueue([
        ...txRef.current.map(
          (item): QueueTx =>
            item.id === id
              ? {
                  ...item,
                  error: error === undefined ? item.error : error,
                  result: result === undefined ? (item.result as SubmittableResult) : result,
                  status: item.status === 'completed' ? item.status : status,
                }
              : item
        ),
      ]);

      queueAction(extractEvents(result));

      if (STATUS_COMPLETE.includes(status)) {
        setTimeout((): void => {
          const item = txRef.current.find((item): boolean => item.id === id);

          // eslint-disable-next-line
          item && item.removeItem();
        }, REMOVE_TIMEOUT);
      }
    },
    [queueAction, setTxQueue]
  );

  return (
    <QueueProvider
      value={{
        queueAction,
        queueExtrinsic,
        queuePayload,
        queueRpc,
        queueSetTxStatus,
        stqueue,
        txqueue,
      }}
    >
      {children}
    </QueueProvider>
  );
}
Example #24
Source File: TxButton.tsx    From subscan-multisig-react with Apache License 2.0 4 votes vote down vote up
function TxButton({
  accountId,
  className = '',
  extrinsic: propsExtrinsic,
  icon,
  isBasic,
  isBusy,
  isDisabled,
  isIcon,
  isToplevel,
  isUnsigned,
  label,
  onClick,
  onFailed,
  onSendRef,
  onStart,
  onSuccess,
  onUpdate,
  params,
  tooltip,
  tx,
  withSpinner,
  withoutLink,
}: Props): React.ReactElement<Props> {
  const { t } = useTranslation();
  const mountedRef = useIsMountedRef();
  const { queueExtrinsic } = useContext(StatusContext);
  const [isSending, setIsSending] = useState(false);
  const [isStarted, setIsStarted] = useState(false);

  useEffect((): void => {
    // eslint-disable-next-line
    isStarted && onStart && onStart();
  }, [isStarted, onStart]);

  const _onFailed = useCallback(
    (result: SubmittableResult | null): void => {
      // eslint-disable-next-line
      mountedRef.current && setIsSending(false);

      // eslint-disable-next-line
      onFailed && onFailed(result);
    },
    [onFailed, setIsSending, mountedRef]
  );

  const _onSuccess = useCallback(
    (result: SubmittableResult): void => {
      // eslint-disable-next-line
      mountedRef.current && setIsSending(false);

      // eslint-disable-next-line
      onSuccess && onSuccess(result);
    },
    [onSuccess, setIsSending, mountedRef]
  );

  const _onStart = useCallback((): void => {
    // eslint-disable-next-line
    mountedRef.current && setIsStarted(true);
  }, [setIsStarted, mountedRef]);

  const _onSend = useCallback((): void => {
    let extrinsics: SubmittableExtrinsic<'promise'>[] | undefined;

    if (propsExtrinsic) {
      extrinsics = Array.isArray(propsExtrinsic) ? propsExtrinsic : [propsExtrinsic];
    } else if (tx) {
      extrinsics = [tx(...(isFunction(params) ? params() : params || []))];
    }

    assert(extrinsics?.length, 'Expected generated extrinsic passed to TxButton');

    // eslint-disable-next-line
    mountedRef.current && withSpinner && setIsSending(true);

    extrinsics.forEach((extrinsic): void => {
      queueExtrinsic({
        accountId: accountId && accountId.toString(),
        extrinsic,
        isUnsigned,
        txFailedCb: withSpinner ? _onFailed : onFailed,
        txStartCb: _onStart,
        txSuccessCb: withSpinner ? _onSuccess : onSuccess,
        txUpdateCb: onUpdate,
      });
    });

    // eslint-disable-next-line
    onClick && onClick();
  }, [
    _onFailed,
    _onStart,
    _onSuccess,
    accountId,
    isUnsigned,
    onClick,
    onFailed,
    onSuccess,
    onUpdate,
    params,
    propsExtrinsic,
    queueExtrinsic,
    setIsSending,
    tx,
    withSpinner,
    mountedRef,
  ]);

  if (onSendRef) {
    onSendRef.current = _onSend;
  }

  return (
    <Button
      className={className}
      icon={icon || 'check'}
      isBasic={isBasic}
      isBusy={isBusy}
      isDisabled={
        isSending ||
        isDisabled ||
        (!isUnsigned && !accountId) ||
        (tx ? false : Array.isArray(propsExtrinsic) ? propsExtrinsic.length === 0 : !propsExtrinsic)
      }
      isIcon={isIcon}
      isToplevel={isToplevel}
      label={label || (isIcon ? '' : t<string>('Submit'))}
      onClick={_onSend}
      tooltip={tooltip}
      withoutLink={withoutLink}
    />
  );
}
Example #25
Source File: Signer.ts    From bodhi.js with Apache License 2.0 4 votes vote down vote up
/**
   *
   * @param transaction
   * @returns A promise that resolves to the transaction's response
   */
  async sendTransaction(_transaction: Deferrable<TransactionRequest>): Promise<TransactionResponse> {
    this._checkProvider('sendTransaction');

    const signerAddress = await this.getSubstrateAddress();
    const evmAddress = await this.getAddress();

    // estimateResources requires the from parameter.
    // However, when creating the contract, there is no from parameter in the tx
    const transaction = {
      from: evmAddress,
      ..._transaction
    };

    const resources = await this.provider.estimateResources(transaction);

    let gasLimit: BigNumber;
    let storageLimit: BigNumber;

    let totalLimit = await transaction.gasLimit;

    if (totalLimit === null || totalLimit === undefined) {
      gasLimit = resources.gas;
      storageLimit = resources.storage;
      totalLimit = resources.gas.add(resources.storage);
    } else {
      const estimateTotalLimit = resources.gas.add(resources.storage);
      gasLimit = BigNumber.from(totalLimit).mul(resources.gas).div(estimateTotalLimit).add(1);
      storageLimit = BigNumber.from(totalLimit).mul(resources.storage).div(estimateTotalLimit).add(1);
    }

    transaction.gasLimit = totalLimit;

    const tx = await this.populateTransaction(transaction);

    const data = tx.data;
    const from = tx.from;

    if (!data) {
      return logger.throwError('Request data not found');
    }

    if (!from) {
      return logger.throwError('Request from not found');
    }

    let extrinsic: SubmittableExtrinsic<'promise'>;

    // @TODO create contract
    if (!tx.to) {
      extrinsic = this.provider.api.tx.evm.create(
        tx.data,
        toBN(tx.value),
        toBN(gasLimit),
        toBN(storageLimit.isNegative() ? 0 : storageLimit),
        tx.accessList || []
      );
    } else {
      extrinsic = this.provider.api.tx.evm.call(
        tx.to,
        tx.data,
        toBN(tx.value),
        toBN(gasLimit),
        toBN(storageLimit.isNegative() ? 0 : storageLimit),
        tx.accessList || []
      );
    }

    await extrinsic.signAsync(signerAddress);

    return new Promise((resolve, reject) => {
      extrinsic
        .send((result: SubmittableResult) => {
          handleTxResponse(result, this.provider.api)
            .then(() => {
              resolve({
                hash: extrinsic.hash.toHex(),
                from: from || '',
                confirmations: 0,
                nonce: toBN(tx.nonce).toNumber(),
                gasLimit: BigNumber.from(tx.gasLimit || '0'),
                gasPrice: BigNumber.from(1),
                data: dataToString(data),
                value: BigNumber.from(tx.value || '0'),
                chainId: +this.provider.api.consts.evmAccounts.chainId.toString(),
                wait: (confirmations?: number): Promise<TransactionReceipt> => {
                  const hex = result.status.isInBlock
                    ? result.status.asInBlock.toHex()
                    : result.status.asFinalized.toHex();
                  return this.provider.getTransactionReceiptAtBlock(extrinsic.hash.toHex(), hex);
                }
              });
            })
            .catch(reject);
        })
        .catch(reject);
    });
  }