Jacky Gu

polkadot学习笔记

02 Dec 2021 Share to

本地运行节点

本地已经安装,目录:

/Users/guqianfeng/web/my_dapps/substrate_learning/substrate-node-template

运行:

target/release/node-template --dev

按下 Ctrl+C / Cmd+C 退出

在默认钱包中打开:

https://polkadot.js.org/apps/?rpc=ws%3A%2F%2F127.0.0.1%3A9944#/

重置区块链:

target/release/node-template purge-chain --dev

使用background

进入background.substrate.dev,链接github帐号,即可进入。

启动前端

本地安装目录:

/Users/guqianfeng/web/my_dapps/substrate_learning/substrate-front-end-template

运行:

yarn start

安装polkadot api

yarn add @polkadot/api

创建实例

// Import
import { ApiPromise, WsProvider } from '@polkadot/api';

...
// Construct
const wsProvider = new WsProvider('wss://rpc.polkadot.io');
const api = await ApiPromise.create({ provider: wsProvider });

// Do something
console.log(api.genesisHash.toHex());

或:

// Create the instance
const api = new ApiPromise({ provider: wsProvider });

// Wait until we are ready and connected
await api.isReady;

// Do something
console.log(api.genesisHash.toHex());

获取常量

无需添加await

// The length of an epoch (session) in Babe
console.log(api.consts.babe.epochDuration.toNumber());

// The amount required to create a new account
console.log(api.consts.balances.creationFee.toNumber());

// The amount required per byte on an extrinsic
console.log(api.consts.balances.transactionByteFee.toNumber());

获取链上状态

方式一:

const { nonce, data: balance } = await api.query.system.account(ADDR);

方式二:

const [now, { nonce, data: balances }] = await Promise.all([
  api.query.system.account(ADDR)
]);

RPC方式

// Retrieve the chain name
const chain = await api.rpc.system.chain();

// Retrieve the latest header
const lastHeader = await api.rpc.chain.getHeader();

// 预定新块
await api.rpc.chain.subscribeNewHeads((lastHeader) => {
  console.log(`${chain}: last block #${lastHeader.number} has hash ${lastHeader.hash}`);
});

取消预订:

...
let count = 0;

// Subscribe to the new headers
const unsubHeads = await api.rpc.chain.subscribeNewHeads((lastHeader) => {
  console.log(`${chain}: last block #${lastHeader.number} has hash ${lastHeader.hash}`);

  if (++count === 10) {
    unsubHeads();
    process.exit(0);
  }
});

发送交易

...

// Create alice (carry-over from the keyring section)
const alice = keyring.addFromUri('//Alice');

// Make a transfer from Alice to BOB, waiting for inclusion
const unsub = await api.tx.balances
  .transfer(BOB, 12345)
  .signAndSend(alice, (result) => {
    console.log(`Current status is ${result.status}`);

    if (result.status.isInBlock) {
      console.log(`Transaction included at blockHash ${result.status.asInBlock}`);
    } else if (result.status.isFinalized) {
      console.log(`Transaction finalized at blockHash ${result.status.asFinalized}`);
      unsub();
    }
  });

发送交易,并且侦听事件

const unsub = await api.tx.balances
  .transfer(BOB, 12345)
  .signAndSend(alice, ({ events = [], status }) => {
    console.log(`Current status is ${status.type}`);

    if (status.isFinalized) {
      console.log(`Transaction included at blockHash ${status.asFinalized}`);

      // Loop through Vec<EventRecord> to display all events
      events.forEach(({ phase, event: { data, method, section } }) => {
        console.log(`\t' ${phase}: ${section}.${method}:: ${data}`);
      });

      unsub();
    }
  });

获取区块头信息

// returns Hash
const blockHash = await api.rpc.chain.getBlockHash(blockNumber);
// returns SignedBlock
const signedBlock = await api.rpc.chain.getBlock(blockHash);

// the hash for the block, always via header (Hash -> toHex()) - will be
// the same as blockHash above (also available on any header retrieved,
// subscription or once-off)
console.log(signedBlock.block.header.hash.toHex());

// the hash for each extrinsic in the block
signedBlock.block.extrinsics.forEach((ex, index) => {
  console.log(index, ex.hash.toHex());
});

获取一个块内的交易信息

// no blockHash is specified, so we retrieve the latest
const signedBlock = await api.rpc.chain.getBlock();

// the information for each of the contained extrinsics
signedBlock.block.extrinsics.forEach((ex, index) => {
  // the extrinsics are decoded by the API, human-like view
  console.log(index, ex.toHuman());

  const { isSigned, meta, method: { args, method, section } } = ex;

  // explicit display of name, args & documentation
  console.log(`${section}.${method}(${args.map((a) => a.toString()).join(', ')})`);
  console.log(meta.documentation.map((d) => d.toString()).join('\n'));

  // signer/nonce info
  if (isSigned) {
    console.log(`signer=${ex.signer.toString()}, nonce=${ex.nonce.toString()}`);
  }
});

判断一笔交易是否成功?

// no blockHash is specified, so we retrieve the latest
const signedBlock = await api.rpc.chain.getBlock();
const allRecords = await api.query.system.events.at(signedBlock.block.header.hash);

// map between the extrinsics and events
signedBlock.block.extrinsics.forEach(({ method: { method, section } }, index) => {
  allRecords
    // filter the specific events based on the phase and then the
    // index of our extrinsic in the block
    .filter(({ phase }) =>
      phase.isApplyExtrinsic &&
      phase.asApplyExtrinsic.eq(index)
    )
    // test the events against the specific types we are looking for
    .forEach(({ event }) => {
      if (api.events.system.ExtrinsicSuccess.is(event)) {
        // extract the data for this event
        // (In TS, because of the guard above, these will be typed)
        const [dispatchInfo] = event.data;

        console.log(`${section}.${method}:: ExtrinsicSuccess:: ${dispatchInfo.toHuman()}`);
      } else if (api.events.system.ExtrinsicFailed.is(event)) {
        // extract the data for this event
        const [dispatchError, dispatchInfo] = event.data;
        let errorInfo;

        // decode the error
        if (dispatchError.isModule) {
          // for module errors, we have the section indexed, lookup
          // (For specific known errors, we can also do a check against the
          // api.errors.<module>.<ErrorName>.is(dispatchError.asModule) guard)
          const decoded = api.registry.findMetaError(dispatchError.asModule);

          errorInfo = `${decoded.section}.${decoded.name}`;
        } else {
          // Other, CannotLookup, BadOrigin, no extra info
          errorInfo = dispatchError.toString();
        }

        console.log(`${section}.${method}:: ExtrinsicFailed:: ${errorInfo}`);
      }
    });
});

预估交易手续费

const info = await api.tx.balances
  .transfer(recipient, 123)
  .paymentInfo(sender);

// log relevant info, partialFee is Balance, estimated for current
console.log(`
  class=${info.class.toString()},
  weight=${info.weight.toString()},
  partialFee=${info.partialFee.toHuman()}
`);

发送批量交易

// construct a list of transactions we want to batch
const txs = [
  api.tx.balances.transfer(addrBob, 12345),
  api.tx.balances.transfer(addrEve, 12345),
  api.tx.staking.unbond(12345)
];

// construct the batch and send the transactions
api.tx.utility
  .batch(txs)
  .signAndSend(sender, ({ status }) => {
    if (status.isInBlock) {
      console.log(`included in ${status.asInBlock}`);
    }
  });

获取未确认交易

for (let i = 0; i < 10; i++) {
  // retrieve sender's next index/nonce, taking txs in the pool into account
  const nonce = await api.rpc.system.accountNextIndex(sender);

  // send, just retrieving the hash, not waiting on status
  const txhash = await api.tx.balances
    .transfer(recipient, 123)
    .signAndSend(sender, { nonce });
}

监听余额变化

// Import the API
const { ApiPromise } = require('@polkadot/api');

// Known account we want to use (available on dev chain, with funds)
const Alice = '5GrwvaEF5zXb26Fz9rcQpDWS57CtERHpNehXCPcNoHGKutQY';

async function main () {
  // Create an await for the API
  const api = await ApiPromise.create();

  // Retrieve the initial balance. Since the call has no callback, it is simply a promise
  // that resolves to the current on-chain value
  let { data: { free: previousFree }, nonce: previousNonce } = await api.query.system.account(Alice);

  console.log(`${Alice} has a balance of ${previousFree}, nonce ${previousNonce}`);
  console.log(`You may leave this example running and start example 06 or transfer any value to ${Alice}`);

  // Here we subscribe to any balance changes and update the on-screen value
  api.query.system.account(Alice, ({ data: { free: currentFree }, nonce: currentNonce }) => {
    // Calculate the delta
    const change = currentFree.sub(previousFree);

    // Only display positive value changes (Since we are pulling `previous` above already,
    // the initial balance change will also be zero)
    if (!change.isZero()) {
      console.log(`New balance change of ${change}, nonce ${currentNonce}`);

      previousFree = currentFree;
      previousNonce = currentNonce;
    }
  });
}

main().catch(console.error);

合约

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

// Attach to an existing contract with a known ABI and address. As per the
// code and blueprint examples the abi is an Abi object, an unparsed JSON
// string or the raw JSON data (after doing a JSON.parse). The address is
// the actual on-chain address as ss58 or AccountId object.
const contract = new ContractPromise(api, abi, address);

substract metadata

assets: 资产类,包括 burn/create/destroy等方法