博客
关于我
强烈建议你试试无所不能的chatGPT,快点击我
mascara-2(MetaMask/mascara本地实现)-连接线上钱包
阅读量:6312 次
发布时间:2019-06-22

本文共 14143 字,大约阅读时间需要 47 分钟。

 https://github.com/MetaMask/mascara

 (beta) Add MetaMask to your dapp even if the user doesn't have the extension installed

 可以开始分析一下这里的代码,从package.json中我们可以看到start中的内容:

"scripts": {    "test": "echo \"Error: no test specified\" && exit 1",    "start": "node example/server/"  },

 

那么就从example/server/开始,这里有两个文件index.js和util.js:

index.js

const express = require('express')//const createMetamascaraServer = require('../server/'),这个是自己设置服务器,而不是使用wallet.metamask.io的时候使用的,之后再讲const createBundle = require('./util').createBundle //这两个的作用其实就是实时监督app.js的变化并将其使用browserify转成浏览器使用的模式app-bundle.jsconst serveBundle = require('./util').serveBundle//// Dapp Server//const dappServer = express()// serve dapp bundleserveBundle(dappServer, '/app-bundle.js', createBundle(require.resolve('../app.js')))dappServer.use(express.static(__dirname + '/../app/')) //这样使用http://localhost:9010访问时就会去(__dirname + '/../app/')的位置调用index.html// start the serverconst dappPort = '9010' //网页监听端口dappServer.listen(dappPort)console.log(`Dapp listening on port ${dappPort}`)

 

util.js

const browserify = require('browserify')const watchify = require('watchify') module.exports = { serveBundle, createBundle, } function serveBundle(server, path, bundle){//就是当浏览器中调用了path时,上面可知为'/app-bundle.js' server.get(path, function(req, res){ res.setHeader('Content-Type', 'application/javascript; charset=UTF-8') //设置header res.send(bundle.latest) //4 并且返回打包后的文件,即可以用于浏览器的app-bundle.js }) } function createBundle(entryPoint){//entryPoint是'../app.js'的完整绝对路径 var bundleContainer = {} var bundler = browserify({//这一部分的内容与browserify的插件watchify有关 entries: [entryPoint], cache: {}, packageCache: {}, plugin: [watchify],//watchify让文件每次变动都编译 }) bundler.on('update', bundle)//2 当文件有变化,就会重新再打包一次,调用bundle() bundle()//1 先执行一次完整的打包 return bundleContainer function bundle() { bundler.bundle(function(err, result){//3 即将browserify后的文件打包成一个 if (err) { console.log(`Bundle failed! (${entryPoint})`) console.error(err) return } console.log(`Bundle updated! (${entryPoint})`) bundleContainer.latest = result.toString()// }) } }

 

⚠️下面的http://localhost:9001是设置的本地的server port(就是连接的区块链的端口),但是从上面的index.js文件可以看出它这里只设置了dapp server,端口为9010,所以这里我们不设置host,使用其默认的https://wallet.metamask.io,去调用页面版

 //app/index.html

  
MetaMask ZeroClient Example

 

再来就是

//app.js

const metamask = require('../mascara')const EthQuery = require('ethjs-query')window.addEventListener('load', loadProvider)window.addEventListener('message', console.warn)// metamask.setupWidget({host: 'http://localhost:9001'}),改了,看下面的/setup-widget.jsmetamask.setupWidget()async function loadProvider() {  // const ethereumProvider = metamask.createDefaultProvider({host: 'http://localhost:9001'}),改了  const ethereumProvider = metamask.createDefaultProvider()  global.ethQuery = new EthQuery(ethereumProvider)  const accounts = await ethQuery.accounts()   window.METAMASK_ACCOUNT = accounts[0] || 'locked'  logToDom(accounts.length ? accounts[0] : 'LOCKED or undefined', 'account') //在
处显示账户信息或者'LOCKED or undefined',一开始不点击get account也会显示 setupButtons(ethQuery)}function logToDom(message, context){ document.getElementById(context).innerText = message console.log(message)}function setupButtons (ethQuery) { const accountButton = document.getElementById('action-button-1') accountButton.addEventListener('click', async () => {//当点击了get account按钮就会显示你在wallet.metamask.io钱包上的账户的信息(当有账户且账户解锁)或者'LOCKED or undefined' const accounts = await ethQuery.accounts() window.METAMASK_ACCOUNT = accounts[0] || 'locked' logToDom(accounts.length ? accounts[0] : 'LOCKED or undefined', 'account') }) const txButton = document.getElementById('action-button-2') txButton.addEventListener('click', async () => {//当点击send Transaction按钮时,将会弹出一个窗口确认交易 if (!window.METAMASK_ACCOUNT || window.METAMASK_ACCOUNT === 'locked') return const txHash = await ethQuery.sendTransaction({//产生一个自己到自己的交易,钱数为0,但会花费gas from: window.METAMASK_ACCOUNT, to: window.METAMASK_ACCOUNT, data: '', }) logToDom(txHash, 'cb-value')//然后在
处得到交易hash })}

 

 接下来就是const metamask = require('../mascara')中调用的

/mascara.js

const setupProvider = require('./lib/setup-provider.js')const setupDappAutoReload = require('./lib/auto-reload.js')const setupWidget = require('./lib/setup-widget.js')const config = require('./config.json')//设置了调用后会导致弹出窗口的方法module.exports = {  createDefaultProvider,  // disabled for now  setupWidget,}function createDefaultProvider (opts = {}) {
//1使用这个来设置你连接的本地区块链等,如果没有设置则默认为连接一个在线版的metamask钱包 const host = opts.host || 'https://wallet.metamask.io' //2 这里host假设设置index.js处写的http://localhost:9001,那么就会调用本地,而不会去调用线上钱包了https://wallet.metamask.io // // setup provider // const provider = setupProvider({
//3这个就会去调用setup-provider.js中的getProvider(opts)函数,opts为{mascaraUrl: 'http://localhost:9001/proxy/'},或'http://wallet.metamask.io/proxy/' mascaraUrl: host + '/proxy/', })//14 然后这里就能够得到inpagePrivider instrumentForUserInteractionTriggers(provider)//15 就是如果用户通过provider.sendAsync异步调用的是config.json中指明的几个运行要弹出页面的方法的话 // // ui stuff // let shouldPop = false//17如果用户调用的不是需要弹窗的方法,则设置为false window.addEventListener('click', maybeTriggerPopup)//18 当页面有点击的操作时,调用函数maybeTriggerPopup return !window.web3 ? setupDappAutoReload(provider, provider.publicConfigStore) : provider // // util // function maybeTriggerPopup(event){
//19 查看是否需要弹出窗口 if (!shouldPop) return//20 不需要则返回 shouldPop = false//21需要则先设为false window.open(host, '', 'width=360 height=500')//22 然后打开一个窗口,host为你设置的区块链http://localhost:9001,或者在线钱包'https://wallet.metamask.io'设置的弹出页面 } function instrumentForUserInteractionTriggers(provider){//用来查看调用的方法是否需要弹出窗口,如果需要就将shouldPop设为true if (window.web3) return provider const _super = provider.sendAsync.bind(provider)//16 将_super上下文环境设置为传入的provider环境 provider.sendAsync = function (payload, cb) {
//16 重新定义provider.sendAsync要先设置shouldPop = true if (config.ethereum['should-show-ui'].includes(payload.method)) { shouldPop = true } _super(payload, cb)//16 然后再次调用该_super方法,即在传入的provider环境运行provider.sendAsync函数,就是使用的还是之前的provider.sendAsync方法,而不是上面新定义的方法 } }}// function setupWidget (opts = {}) {// }

 

接下来就是对lib文档的讲解了

//setup-provider.js

const setupIframe = require('./setup-iframe.js')const MetamaskInpageProvider = require('./inpage-provider.js') module.exports = getProvider function getProvider(opts){ //4 opts为{mascaraUrl: 'http://localhost:9001/proxy/'}或'http://wallet.metamask.io/proxy/' if (global.web3) { //5 如果测试到全局有一个web3接口,就说明连接的是在线钱包,那么就返回在线钱包的provider console.log('MetaMask ZeroClient - using environmental web3 provider') return global.web3.currentProvider } console.log('MetaMask ZeroClient - injecting zero-client iframe!') let iframeStream = setupIframe({ //6 否则就说明我们使用的是自己的区块链,那么就要插入mascara iframe了,调用setup-iframe.js的setupIframe(opts) zeroClientProvider: opts.mascaraUrl,//7 opts = {zeroClientProvider: 'http://localhost:9001/proxy/'}或'http://wallet.metamask.io/proxy/' })//返回Iframe{src:'http://localhost:9001/proxy/',container:document.head,sandboxAttributes:['allow-scripts', 'allow-popups', 'allow-same-origin']} return new MetamaskInpageProvider(iframeStream)//11 13 MetamaskInpageProvider与页面连接,返回其self作为provider }

 

//setup-iframe.js

const Iframe = require('iframe')//看本博客的学习使用const createIframeStream = require('iframe-stream').IframeStreamfunction setupIframe(opts) {
//8 opts = {zeroClientProvider: 'http://localhost:9001/proxy/'}或'http://wallet.metamask.io/proxy/' opts = opts || {} let frame = Iframe({
//9 设置

sandbox是安全级别,加上sandbox表示该iframe框架的限制:

描述
"" 应用以下所有的限制。
allow-same-origin 允许 iframe 内容与包含文档是有相同的来源的
allow-top-navigation 允许 iframe 内容是从包含文档导航(加载)内容。
allow-forms 允许表单提交。
allow-scripts 允许脚本执行。

 

//inpage-provider.js 详细学习看本博客

const pump = require('pump')const RpcEngine = require('json-rpc-engine')const createIdRemapMiddleware = require('json-rpc-engine/src/idRemapMiddleware')const createStreamMiddleware = require('json-rpc-middleware-stream')const LocalStorageStore = require('obs-store')const ObjectMultiplex = require('obj-multiplex')const config = require('../config.json')module.exports = MetamaskInpageProviderfunction MetamaskInpageProvider (connectionStream) {
//12 connectionStream为生成的IframeStream const self = this // setup connectionStream multiplexing const mux = self.mux = new ObjectMultiplex() pump( connectionStream, mux, connectionStream, (err) => logStreamDisconnectWarning('MetaMask', err) ) // subscribe to metamask public config (one-way) self.publicConfigStore = new LocalStorageStore({ storageKey: 'MetaMask-Config' }) pump( mux.createStream('publicConfig'), self.publicConfigStore, (err) => logStreamDisconnectWarning('MetaMask PublicConfigStore', err) ) // ignore phishing warning message (handled elsewhere) mux.ignoreStream('phishing') // connect to async provider const streamMiddleware = createStreamMiddleware() pump( streamMiddleware.stream, mux.createStream('provider'), streamMiddleware.stream, (err) => logStreamDisconnectWarning('MetaMask RpcProvider', err) ) // handle sendAsync requests via dapp-side rpc engine const rpcEngine = new RpcEngine() rpcEngine.push(createIdRemapMiddleware()) // deprecations rpcEngine.push((req, res, next, end) =>{ const deprecationMessage = config['ethereum']['deprecated-methods'][req.method]//看你是不是用了eth_sign这个将要被弃用的方法 if (!deprecationMessage) return next()//如果不是的话,就继续往下执行 end(new Error(`MetaMask - ${deprecationMessage}`))//如果是的话,就返回弃用的消息,并推荐使用新方法eth_signTypedData }) rpcEngine.push(streamMiddleware) self.rpcEngine = rpcEngine}// handle sendAsync requests via asyncProvider// also remap ids inbound and outboundMetamaskInpageProvider.prototype.sendAsync = function (payload, cb) { const self = this self.rpcEngine.handle(payload, cb)}MetamaskInpageProvider.prototype.send = function (payload) { const self = this let selectedAddress let result = null switch (payload.method) { case 'eth_accounts': // read from localStorage selectedAddress = self.publicConfigStore.getState().selectedAddress result = selectedAddress ? [selectedAddress] : [] break case 'eth_coinbase': // read from localStorage selectedAddress = self.publicConfigStore.getState().selectedAddress result = selectedAddress || null break case 'eth_uninstallFilter': self.sendAsync(payload, noop) result = true break case 'net_version': const networkVersion = self.publicConfigStore.getState().networkVersion result = networkVersion || null break // throw not-supported Error default: let link = 'https://github.com/MetaMask/faq/blob/master/DEVELOPERS.md#dizzy-all-async---think-of-metamask-as-a-light-client' let message = `The MetaMask Web3 object does not support synchronous methods like ${payload.method} without a callback parameter. See ${link} for details.` throw new Error(message) } // return the result return { id: payload.id, jsonrpc: payload.jsonrpc, result: result, }}MetamaskInpageProvider.prototype.isConnected = function () { return true}MetamaskInpageProvider.prototype.isMetaMask = true// utilfunction logStreamDisconnectWarning (remoteLabel, err) { let warningMsg = `MetamaskInpageProvider - lost connection to ${remoteLabel}` if (err) warningMsg += '\n' + err.stack console.warn(warningMsg)}function noop () {}

 

 //setup-widget.js

const Iframe = require('iframe')module.exports = function setupWidget (opts = {}) {  let iframe  let style = `    border: 0px;    position: absolute;    right: 0;    top: 0;    height: 7rem;`  let resizeTimeout  const changeStyle = () => {    iframe.style = style + (window.outerWidth > 575 ? 'width: 19rem;' : 'width: 7rem;')  }  const resizeThrottler = () => {    if ( !resizeTimeout ) {      resizeTimeout = setTimeout(() => {        resizeTimeout = null;        changeStyle();        // 15fps      }, 66);    }  }  window.addEventListener('load', () => {    if (window.web3) return    const frame = Iframe({      src: `${opts.host}/proxy/widget.html` || 'https://wallet.metamask.io/proxy/widget.html',//下面被改掉了      container: opts.container || document.body,      sandboxAttributes: opts.sandboxAttributes ||        ['allow-scripts', 'allow-popups', 'allow-same-origin', 'allow-top-navigation'],      scrollingDisabled: true,    })    iframe = frame.iframe    changeStyle()  })  window.addEventListener('resize', resizeThrottler, false);}

 

 

/config.json

说明哪些方法是要弹出窗口来让用户confirm的

{  "ethereum": {    "deprecated-methods": { "eth_sign": "eth_sign has been deprecated in metamascara due to security concerns please use eth_signTypedData" }, "should-show-ui": [//会导致窗口弹出的method "eth_personalSign", "eth_signTypedData", "eth_sendTransaction" ] } }

 

 然后我们在终端运行node example/server/来打开dapp server,然后在浏览器中运行http://localhost:9010来访问:

因为我之前有在Chrome浏览器中访问过线上钱包,所以这个时候它能够get account 得到我在线上钱包的账户

点击send Transaction后,就能够得到弹窗信息了:

从上面我们可以看见有出现很对的错误信息,那个主要是因为想要在<iframe></iframe>中显示线上钱包的内容导致的,但是我们可以看见,线上钱包拒绝了这样的访问

 在上面我们可以看见有一个错误信息cannot get /undefined/proxy/index.html,解决方法是将/setup-widget.js中下面的代码改了:

// src: `${opts.host}/proxy/index.html` || 'https://wallet.metamask.io/proxy/index.html',改成:      src: 'https://wallet.metamask.io/proxy/index.html',

改后:

 

 改成:

src: 'https://wallet.metamask.io/proxy/widget.html',

 发现widget.html 这个file好像是不存在的,算是这个的bug吧

 

 

点击comfirm后,就会得到交易hash值:

0x4d1ff956c4fdaafc7cb0a2ca3e144a0bf7534e6db70d3caade2b2ebdfd4f6c20

 

然后我们可以去etherscan中查看这笔交易是否成功,发现是成功了的:

 

转载于:https://www.cnblogs.com/wanghui-garcia/p/9878654.html

你可能感兴趣的文章
母版页 MasterPage
查看>>
[转] ReactNative Animated动画详解
查看>>
DNS原理及其解析过程
查看>>
记录自写AFNetWorking封装类
查看>>
没想到cnblog也有月经贴,其实C#值不值钱不重要。
查看>>
【转】LUA内存分析
查看>>
springboot使用schedule定时任务
查看>>
[转] Entity Framework Query Samples for PostgreSQL
查看>>
XDUOJ 1115
查看>>
PHP学习(四)---PHP与数据库MySql
查看>>
模版方法模式--实现的capp流程创建与管理
查看>>
软件需求分析的重要性
查看>>
eclipse的scala环境搭建
查看>>
UVA465:Overflow
查看>>
HTML5-placeholder属性
查看>>
Android选择本地图片过大程序停止的经历
查看>>
poj 2187:Beauty Contest(旋转卡壳)
查看>>
《Flask Web开发》里的坑
查看>>
Python-库安装
查看>>
Git笔记
查看>>