本地运行节点
本地已经安装,目录:
/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等方法