🗂

[Astar]コントラクト備忘録48(Polkadot.jsを使って、接続したウォレットからトランザクションを起こしてみよう!)

2023/03/17に公開

今回は、「Polkadot.js」を用いて、接続されたウォレットからトランザクションを行ってみましょう。

ポイントはsignerに「injector.signer」を指定しているところです。


https://polkadot.js.org/docs/extension/cookbook

簡単に見てみましょう。

下のように、shibuyaのノードから「provider」を作り、そこから「api」を作成しています。

そして、そのapiを使い、「api.tx.balances.transfer」を作成しています。
ここではまだ署名されていません。

それを元にして、signAndSendを行っています。

ここでポイントなのは「account」から取得した「injector」を用いて、「signer」に「injector.signer」を指定していることです。

では、実際にやってみましょう。

ウォレットを接続した後に、「transfer」を押します。

すると、「talisman」のウォレットを接続していた場合には、このようにポップアップが起動し、送付することができます。

今回は以上です。

import Head from 'next/head'
import Image from 'next/image'
import { Inter } from 'next/font/google'
import styles from '@/styles/Home.module.css'
import React, { useState } from 'react';
import { stringToHex } from "@polkadot/util";
import { InjectedAccountWithMeta } from "@polkadot/extension-inject/types";
import { ApiPromise, WsProvider } from '@polkadot/api';

const inter = Inter({ subsets: ['latin'] })

export default function Home() {
  const [address, setAddress] = useState('');
  const [source, setSource] = useState('');
  const [account, setAccount] = useState<InjectedAccountWithMeta | null>(null);;
  const [signature, setSignature] = useState('');
  
  async function connectWallet () {

    const { web3Accounts, web3Enable} = await import(
      "@polkadot/extension-dapp"
    );

    const allInjected = await web3Enable('my dapp');
  
    if (allInjected.length === 0) {
      return;
    }
    const accounts = await web3Accounts();
    console.log("accounts",accounts)

    const account = accounts[0];
    setAccount(account);
    console.log("account",account)

    const address = account?.address
    const source = account?.meta?.source

    console.log("address",address)
    console.log("source",source)

    setAddress(address);
    setSource(source);
  }

  async function signMessage () {
    const { web3FromSource} = await import(
      "@polkadot/extension-dapp"
    );
    if (account) {
      const injector = await web3FromSource(account.meta.source);
      console.log("injector",injector)

      const signRaw = injector?.signer?.signRaw;
      console.log("signRaw",signRaw)

      if (!!signRaw) {
        // after making sure that signRaw is defined
        // we can use it to sign our message
        const { signature } = await signRaw({
            address: account.address,
            data: stringToHex('message to sign'),
            type: 'bytes'
        });
        console.log("signature",signature)
        setSignature(signature);
      }
    }
  }
  async function transfer () {
    const { web3FromSource} = await import(
      "@polkadot/extension-dapp"
    );
    const provider = new WsProvider('wss://rpc.shibuya.astar.network');

    // Create the API and wait until ready
    const api = await ApiPromise.create({ provider });

    const transferExtrinsic = api.tx.balances.transfer('5CfmGa2TsXxP7vuyTYzp22XvtfcaS2MeydXmP4B9yMzUyxkk', 1)
    if (account) {
      const injector = await web3FromSource(account.meta.source);
      transferExtrinsic.signAndSend(account.address, { signer: injector.signer }, ({ status }) => {

        if (status.isInBlock) {
            console.log(`Completed at block hash #${status.asInBlock.toString()}`);
        } else {
            console.log(`Current status: ${status.type}`);
            console.log(`Current status: ${status.hash.toString()}`);
        }
      }).catch((error: any) => {
          console.log(':( transaction failed', error);
      });
    }
  }
<div>
            <button onClick={connectWallet}>Connect Wallet</button>
            {address && <p>Address: {address}</p>}
            {source && <p>Source: {source}</p>}

            <button onClick={signMessage} style={{marginTop: "20px"}}>sign a message</button>
            {signature && <p>Signature: {signature}</p>}

            <button onClick={transfer} style={{marginTop: "20px"}}>transfer</button>
            
          </div>

↓色々と機能を加えたものです。

port Head from 'next/head'
import Image from 'next/image'
import { Inter } from 'next/font/google'
import styles from '@/styles/Home.module.css'
import React, { useState } from 'react';
import { stringToHex } from "@polkadot/util";
import { InjectedAccountWithMeta } from "@polkadot/extension-inject/types";
import { ApiPromise, WsProvider } from '@polkadot/api';


const inter = Inter({ subsets: ['latin'] })

export default function Home() {
  const [address, setAddress] = useState('');
  const [source, setSource] = useState('');
  const [account, setAccount] = useState<InjectedAccountWithMeta | null>(null);;
  const [signature, setSignature] = useState('');
  const [amount, setAmount] = useState(1);
  const [error, setError] = useState('');
  const [toError, setToError] = useState('');
  const [destination, setDestination] = useState('');

  const handleAmountChange = (event:any) => {
    const value = Number(event.target.value);
    if (value <= 0) {
      setError('Amount must be greater than 0');
    } else {
      setError('');
      setAmount(value);
    }
  }

  const handleDestinationChange = (event:any) => {
    setDestination(event.target.value);
  };


  async function connectWallet () {

    const { web3Accounts, web3Enable} = await import(
      "@polkadot/extension-dapp"
    );

    const allInjected = await web3Enable('my dapp');
  
    if (allInjected.length === 0) {
      return;
    }
    const accounts = await web3Accounts();
    console.log("accounts",accounts)

    const account = accounts[0];
    setAccount(account);
    console.log("account",account)

    const address = account?.address
    const source = account?.meta?.source

    console.log("address",address)
    console.log("source",source)

    setAddress(address);
    setSource(source);
  }

  async function signMessage () {
    const { web3FromSource} = await import(
      "@polkadot/extension-dapp"
    );
    if (account) {
      const injector = await web3FromSource(account.meta.source);
      console.log("injector",injector)

      const signRaw = injector?.signer?.signRaw;
      console.log("signRaw",signRaw)

      if (!!signRaw) {
        // after making sure that signRaw is defined
        // we can use it to sign our message
        const { signature } = await signRaw({
            address: account.address,
            data: stringToHex('message to sign'),
            type: 'bytes'
        });
        console.log("signature",signature)
        setSignature(signature);
      }
    }
  }
  async function transfer () {
    if (destination.trim() === '') {
    setToError('to is required');
    return;
    }
    const { web3FromSource} = await import(
      "@polkadot/extension-dapp"
    );
    const provider = new WsProvider('wss://rpc.shibuya.astar.network');

    // Create the API and wait until ready
    const api = await ApiPromise.create({ provider });

    const transferExtrinsic = api.tx.balances.transfer(destination, amount)
    if (account) {
      const injector = await web3FromSource(account.meta.source);
      transferExtrinsic.signAndSend(account.address, { signer: injector.signer }, ({ status }) => {

        if (status.isInBlock) {
            console.log(`Completed at block hash #${status.asInBlock.toString()}`);
        } else {
            console.log(`Current status: ${status.type}`);
            console.log(`Current status: ${status.hash.toString()}`);
        }
      }).catch((error: any) => {
          console.log(':( transaction failed', error);
      });
    }
  }
<div>
            <button className={styles.rotatebutton} onClick={connectWallet}>Connect Wallet</button>
            {address && <p>Address: {address}</p>}
            {source && <p>Source: {source}</p>}

            <button className={styles.rotatebutton} onClick={signMessage} style={{marginTop: "20px"}}>sign a message</button>
            {signature && <p>Signature: {signature}</p>}

            <button className={styles.rotatebutton} onClick={transfer} style={{marginTop: "20px"}}>transfer</button>
            <div>
              <p>amount:<input type="number" value={amount} onChange={handleAmountChange}  style={{ width: '60px' }}/>
              {error && <p style={{ color: 'red' }}>{error}</p>}</p>
              <p>to:<input type="text" value={destination} onChange={handleDestinationChange} style={{ width: '400px' }}/>
              {toError && <p style={{ color: 'red' }}>{toError}</p>}</p>
            </div>
          </div>

Discussion