SigningRequest.tsx 4.27 KiB
Newer Older
Andrei Eres's avatar
Andrei Eres committed
import {
Andrei Eres's avatar
Andrei Eres committed
  AccountJson,
  RequestSign,
} from '@polkadot/extension-base/background/types'
import { wrapBytes } from '@polkadot/extension-dapp/wrapBytes'
Andrei Eres's avatar
Andrei Eres committed
import { QrDisplayPayload, QrScanSignature } from '@polkadot/react-qr'
Andrei Eres's avatar
Andrei Eres committed
import { useStore } from '@nanostores/react'
import React, { useEffect, useRef, useState } from 'react'
Andrei Eres's avatar
Andrei Eres committed
import styled from 'styled-components'
Andrei Eres's avatar
Andrei Eres committed
import Address from '../components/Address'
Andrei Eres's avatar
Andrei Eres committed
import { Button } from '../components/Button'
import {
Andrei Eres's avatar
Andrei Eres committed
  accountNamesByAddressStore,
  accountsStore,
} from '../../stores/accounts'
Andrei Eres's avatar
Andrei Eres committed
import { addHeaderAction, resetHeaderActions } from '../../stores/headerActions'
Andrei Eres's avatar
Andrei Eres committed
import { BaseProps } from '../types'
Andrei Eres's avatar
Andrei Eres committed
import { isRawPayload } from '../../utils/guards'
import {
  approveSignSignature,
  cancelSignRequest,
} from '../../messaging/uiActions'
Andrei Eres's avatar
Andrei Eres committed
import { getGenesisHashByAddress } from '../../utils/getGenesisHashByAddress'
import { getExtrinsicPayload } from '../../utils/getExtrinsicPayload'
Andrei Eres's avatar
Andrei Eres committed

const CMD_MORTAL = 2
const CMD_SIGN_MESSAGE = 3
Andrei Eres's avatar
Andrei Eres committed
type Props = BaseProps & {
Andrei Eres's avatar
Andrei Eres committed
  account: AccountJson
  buttonText: string
  isFirst: boolean
  request: RequestSign
  signId: string
  url: string
}

Andrei Eres's avatar
Andrei Eres committed
const Request: React.FC<Props> = ({ request, signId, className }) => {
  const accounts = useStore(accountsStore)
  const accountNamesByAddress = useStore(accountNamesByAddressStore)
  const [beginning, setBeginning] = useState(true)
  const payloadRef = useRef(getExtrinsicPayload(request.payload))
  const isRaw = isRawPayload(request.payload)
  const cmd = isRaw ? CMD_SIGN_MESSAGE : CMD_MORTAL
  const address = request.payload.address
  const name = accountNamesByAddress[address]
  const genesisHash = isRaw
    ? getGenesisHashByAddress(accounts, address)
    : request.payload.genesisHash
  const payloadU8a = isRaw
    ? wrapBytes(request.payload.data)
    : payloadRef.current?.toU8a()

  const toggleOnQr = () => setBeginning((v) => !v)
Andrei Eres's avatar
Andrei Eres committed
  const onSignature = ({ signature }: { signature: string }) =>
    approveSignSignature(signId, signature).catch(console.error)

Andrei Eres's avatar
Andrei Eres committed
  useEffect(() => {
    addHeaderAction({
      label: 'Cancel',
      onAction: () => cancelSignRequest(signId).catch(console.error),
    })
    return () => resetHeaderActions()
  }, [signId])

  return (
    <div className={className}>
      <div className='row'>
        <div className='guide'>
          <div>
            <h1>
              Signing
              <br />
              request
            </h1>
            <div className='steps'>
              <div className={beginning ? 'current' : ''}>
Andrei Eres's avatar
Andrei Eres committed
                1. Scan signature via Signer
              </div>
              <div className={!beginning ? 'current' : ''}>
Andrei Eres's avatar
Andrei Eres committed
                2. Show signed transaction
              </div>
            </div>
          </div>
          <div>
            {beginning && <Button onClick={toggleOnQr}>Next to signing</Button>}
            {!beginning && (
              <Button onClick={toggleOnQr}>Back to signature</Button>
            )}
Andrei Eres's avatar
Andrei Eres committed
          </div>
        </div>
        <div className='scanner'>
          <div className='spacer' />
          <div className='qr'>
            {beginning && payloadU8a && genesisHash && (
Andrei Eres's avatar
Andrei Eres committed
              <QrDisplayPayload
                address={address}
                cmd={cmd}
                genesisHash={genesisHash}
Andrei Eres's avatar
Andrei Eres committed
                payload={payloadU8a}
              />
            )}
            {!beginning && <QrScanSignature onScan={onSignature} />}
Andrei Eres's avatar
Andrei Eres committed
          </div>
        </div>
      </div>
      <div className='using-key'>
        <div className='using-key-heading'>Using key</div>
        <Address address={address} name={name} genesisHash={genesisHash} />
Andrei Eres's avatar
Andrei Eres committed
      </div>
Andrei Eres's avatar
Andrei Eres committed
    </div>
  )
}

export default styled(Request)`
  .row {
    display: flex;
    flex-direction: row;
    width: 100%;
    margin-bottom: 1rem;
Andrei Eres's avatar
Andrei Eres committed
  .row > div {
    display: flex;
    flex-direction: column;
    flex-basis: 50%;
Andrei Eres's avatar
Andrei Eres committed
  .guide {
    justify-content: space-between;
  }
Andrei Eres's avatar
Andrei Eres committed
  .steps > div {
    margin-bottom: 0.25rem;
    color: var(--color-faded-text);
Andrei Eres's avatar
Andrei Eres committed
  }

  .steps > .current {
    color: var(--color-main-text);
Andrei Eres's avatar
Andrei Eres committed
  }

  .using-key {
    margin-top: 2rem;
  }

  .using-key-heading {
    margin-bottom: 0.5rem;
    font-weight: bold;
  }

  .scanner {
    position: relative;
  }

  .spacer {
    width: 100%;
    padding-bottom: 100%;
  }

  .qr {
    position: absolute;
    width: 100%;
  }
Andrei Eres's avatar
Andrei Eres committed
`