• ethereum/EIPs-1193 Ethereum Provider JavaScript API 如metamask更新后的接口


    eiptitleauthordiscussions-tostatustypecategorycreatedrequires
    1193
    Ethereum Provider JavaScript API
    Ryan Ghods (@ryanio), Marc Garreau (@marcgarreau)
    Draft
    Standards Track
    Interface
    2018-06-30
    1102

     

    Summary

    This EIP formalizes an Ethereum Provider JavaScript API for consistency across clients and applications.

    The provider is designed to be minimal, containing 4 methods: enable, send, subscribe, and unsubscribe. It emits 4 types of events: connect, close, networkChanged, and accountsChanged.

    It is intended to be available on window.ethereum.

    这个EIP就是之前ethereum/EIPs-1102 Opt-in provider access metamask不再默认直接连入网页的详细接口的实现的解释

    API

    Enable

    By default a "read-only" provider is supplied to allow access to the blockchain while preserving user privacy.

    就是为了保证用户的隐私,一个Dapp一开始连接上的provider是只读的状态

    A full provider can be requested to allow account-level methods:

    如果想要获取full provider的使用权限,得到钱包中账户的信息,并能够进行相应的调用,就要使用ethereum.enable()去请求用户同意连接

    ethereum.enable(): Promise<[String]>;
    window.addEventListener('load', async () => {
        // Read-only provider is exposed by default
        console.log(await ethereum.send('net_version'));//能够读取信息
        try {
            // Request full provider if needed
            await ethereum.enable();
            // Full provider exposed
            await ethereum.send('eth_sendTransaction', [/* ... */]);
        } catch (error) {
            // User denied full provider access
        }
    });

    Promise resolves with an array of the accounts' public keys, or rejects with Error.

    If the dapp has been previously authenticated and remembered by the user, then the provider supplied on load MAYautomatically be enabled with the previously authenticated accounts.就是如果之前有通过身份验证并且被用户所记得,那么provider可能会自动连接上

    Send

    Ethereum API methods can be sent and received:

    成功连接后就能够进行方法的调用了

    ethereum.send(method: String, params?: Array<any>): Promise<any>;

    Promise resolves with result or rejects with Error.

    See the available methods.相关的方法如下所示,详细使用信息看

    https://github.com/ethereum/wiki/wiki/JSON-RPC#json-rpc-methods

    JSON-RPC methods
    
    web3_clientVersion
    web3_sha3
    net_version
    net_peerCount
    net_listening
    eth_protocolVersion
    eth_syncing
    eth_coinbase
    eth_mining
    eth_hashrate
    eth_gasPrice
    eth_accounts
    eth_blockNumber
    eth_getBalance
    eth_getStorageAt
    eth_getTransactionCount
    eth_getBlockTransactionCountByHash
    eth_getBlockTransactionCountByNumber
    eth_getUncleCountByBlockHash
    eth_getUncleCountByBlockNumber
    eth_getCode
    eth_sign
    eth_sendTransaction
    eth_sendRawTransaction
    eth_call
    eth_estimateGas
    eth_getBlockByHash
    eth_getBlockByNumber
    eth_getTransactionByHash
    eth_getTransactionByBlockHashAndIndex
    eth_getTransactionByBlockNumberAndIndex
    eth_getTransactionReceipt
    eth_getUncleByBlockHashAndIndex
    eth_getUncleByBlockNumberAndIndex
    eth_getCompilers
    eth_compileLLL
    eth_compileSolidity
    eth_compileSerpent
    eth_newFilter
    eth_newBlockFilter
    eth_newPendingTransactionFilter
    eth_uninstallFilter
    eth_getFilterChanges
    eth_getFilterLogs
    eth_getLogs
    eth_getWork
    eth_submitWork
    eth_submitHashrate
    db_putString
    db_getString
    db_putHex
    db_getHex
    shh_post
    shh_version
    shh_newIdentity
    shh_hasIdentity
    shh_newGroup
    shh_addToGroup
    shh_newFilter
    shh_uninstallFilter
    shh_getFilterChanges
    shh_getMessages

    但是你也可以使用如下的方法web3来调用方法:

    window.addEventListener('load', async () => {
        // Modern dapp browsers...现在的连接方式
        if (window.ethereum) {//如果安装了metamask,且登录了,暴露个目前只读的provider;如果没有安装metamask或没有登录,那么window.ethereum将为undefined
            window.web3 = new Web3(ethereum); //provider通过ethereum暴露,相当于以前的currentProvider
            try {
                // Request account access if needed
                await ethereum.enable();
                // Acccounts now exposed
                web3.eth.sendTransaction({/* ... */});//举个调用的例子
            } catch (error) {//用户拒绝
                // User denied account access...
            }
        }
        // Non-dapp browsers...
        else {
            console.log('Non-Ethereum browser detected. You should consider trying MetaMask!');
        }
    });

    Subscriptions

    Subscribe

    ethereum.subscribe(subscriptionType: String, params?: Array<any>): Promise<String>;

    Promise resolves with subscriptionId: String (是一个类似于"0x9cef478923ff08bf67fde6c64013158d"的字符串)or rejects with Error.

    See the types of subscriptions.

    详细信息看:https://github.com/ethereum/go-ethereum/wiki/RPC-PUB-SUB

    比如:

    Create subscription

    Subscriptions are creates with a regular RPC call with eth_subscribe as method and the subscription name as first parameter. If successful it returns the subscription id.

    就是使用方法eth_subscribe用于订阅,第一个参数是订阅名,比如"newHeads"即你想知道最新产生的区块头的信息、"logs"即相应的日志信息、"newPendingTransactions"即新生成的正在等待验证的交易信息;第二个参数即一些过滤信息,比如你只想知道与某个账户address相关的logs信息等。如果成功,将会返回订阅id(即满足你的要求的订阅),然后能够通过这个id去查看到详细的返回信息。

    Parameters

    1. subscription name
    2. optional arguments

    Example

    >> {"id": 1, "method": "eth_subscribe", "params": ["newHeads", {"includeTransactions": true}]}
    << {"id": 1, "jsonrpc": "2.0", "result": "0x9cef478923ff08bf67fde6c64013158d"}

    Results emit on subscriptionId using EventEmitter. Attach listeners with:

    ethereum.on(subscriptionId, listener: (result: any) => void): this;

    The event emits with result, the subscription result or an Error object.

    这个的意思就是:比如上面的订阅是想要找到新的区块头并包含Transactions,订阅ID是代表这种要求的订阅,在区块生成的过程中,满足这种订阅条件的区块头是很多的,所以是需要EventEmitter来进行获取的,只要不取消订阅,那么EventEmitter将一直等待满足订阅ID的订阅并触发

    比较详细的信息我们就可以通过EventEmitter获得,结果如下:

        << {
      "jsonrpc": "2.0",
      "method": "eth_subscription",
      "params": {
        "result": {
          "difficulty": "0x15d9223a23aa",
          "extraData": "0xd983010305844765746887676f312e342e328777696e646f7773",
          "gasLimit": "0x47e7c4",
          "gasUsed": "0x38658",
          "logsBloom": "0x00000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000",
          "miner": "0xf8b483dba2c3b7176a3da549ad41a48bb3121069",
          "nonce": "0x084149998194cc5f",
          "number": "0x1348c9",
          "parentHash": "0x7736fab79e05dc611604d22470dadad26f56fe494421b5b333de816ce1f25701",
          "receiptRoot": "0x2fab35823ad00c7bb388595cb46652fe7886e00660a01e867824d3dceb1c8d36",
          "sha3Uncles": "0x1dcc4de8dec75d7aab85b567b6ccd41ad312451b948a7413f0a142fd40d49347",
          "stateRoot": "0xb3346685172db67de536d8765c43c31009d0eb3bd9c501c9be3229203f15f378",
          "timestamp": "0x56ffeff8",
          "transactionsRoot": "0x0167ffa60e3ebc0b080cdb95f7c0087dd6c0e61413140e39d94d3468d7c9689f"
        },
        "subscription": "0x9ce59a13059e417087c02d3236a0b1cc"
      }
    }
     

    Unsubscribe取消订阅

    ethereum.unsubscribe(subscriptionId: String): Promise<Boolean>;

    Promise resolves with success: Boolean or rejects with Error.

    All EventEmitter listeners on subscriptionId will also be removed.

    当用户不想要再得到满足订阅ID的某种订阅后,就可以取消订阅,相应的EventEmitter也会停下来

     

    Events

    Events are emitted using EventEmitter.

    connect

    The provider emits connect on connect to a network.就是当你钱包连接上了网络就会触发这个connect事件

    ethereum.on('connect', listener: () => void): this;

    You can detect which network by sending net_version:

    const network = await ethereum.send('net_version');
    > '1'

    close

    The provider emits close on disconnect from a network.当你钱包断开网络连接

    ethereum.on('close', listener: (code: Number, reason: String) => void): this;

    The event emits with code and reason. The code follows the table of CloseEvent status codes.

    networkChanged

    The provider emits networkChanged on connect to a new network.当你钱包中换了一个新网络进行连接

    ethereum.on('networkChanged', listener: (networkId: String) => void): this;

    The event emits with networkId, the new network returned from net_version.

    accountsChanged

    The provider emits accountsChanged if the accounts returned from the provider (eth_accounts) changes.当你钱包更换了账户

    ethereum.on('accountsChanged', listener: (accounts: Array<String>) => void): this;

    The event emits with accounts, an array of the accounts' public keys.

    Constructor

    ethereum.constructor.name;
    > 'EthereumProvider'

    Examples

    const ethereum = window.ethereum;
    
    //这是两种使用方法,一种即将EthereumProvider设置到web3上,然后之后就能够使用web3的接口来调用方法,如web3.eth.sendTransaction({/* ... */});
    // A) Primary use case - set provider in web3.js web3.setProvider(ethereum);
    //不然另一种方法就是直接使用ethereum,然后用本文上面的方式来进行方法的调用,即sendsubscribe等
    // B) Secondary use case - use provider object directly // Example 1: Log last block ethereum .send('eth_getBlockByNumber', ['latest', 'true'])//得到最新的区块账户信息 .then(block => { console.log(`Block ${block.number}: ${block}`); }) .catch(error => { console.error( `Error fetching last block: ${error.message}. Code: ${error.code}. Data: ${error.data}` ); }); // Example 2: Enable full provider ethereum .enable() .then(accounts => { console.log(`Enabled accounts: ${accounts.join(' ')}`);//得到钱包中所有的账户信息 }) .catch(error => { console.error( `Error enabling provider: ${error.message}. Code: ${error.code}. Data: ${error.data}` ); }); // Example 3: Log available accounts ethereum .send('eth_accounts') .then(accounts => { console.log(`Accounts: ${accounts.join(' ')}`);//得到钱包中所有的账户信息 }) .catch(error => { console.error( `Error fetching accounts: ${error.message}. Code: ${error.code}. Data: ${error.data}` ); }); } // Example 4: Log new blocks let subId;//全局变量,用于之后取消订阅 ethereum .subscribe('newHeads')//订阅得到新区块头的信息 .then(subscriptionId => { subId = subscriptionId; ethereum.on(subscriptionId, block => { if (result instanceOf Error) { const error = result; console.error( `Error from newHeads subscription: ${error.message}. Code: ${error.code}. Data: ${error.data}` ); } else { console.log(`New block ${block.number}: ${block}`); } }); }) .catch(error => { console.error( `Error making newHeads subscription: ${error.message}. Code: ${error.code}. Data: ${error.data}` ); }); // to unsubscribe ethereum .unsubscribe(subId)//取消订阅 .then(result => { console.log(`Unsubscribed newHeads subscription ${subscriptionId}`); }) .catch(error => { console.error( `Error unsubscribing newHeads subscription: ${error.message}. Code: ${error.code}. Data: ${error.data}` ); }); // Example 5: Log when accounts change const logAccounts = accounts => { console.log(`Accounts: ${accounts.join(' ')}`); }; ethereum.on('accountsChanged', logAccounts);//账户更改的话则调用回调logAccounts // to unsubscribe ethereum.removeListener('accountsChanged', logAccounts); // Example 6: Log if connection ends ethereum.on('close', (code, reason) => { console.log( `Ethereum provider connection closed: ${reason}. Code: ${code}` ); });

    Class

    The name of the constructor of the Ethereum Provider MUST be EthereumProvider.

    web3.js Provider

    The implementing Ethereum Provider MUST be compatible as a web3.js provider. This is accomplished by providing two methods in the EthereumProvider:

     sendAsync(payload: Object, callback: (error: any, result: any) => void): void
    
    isConnected(): Boolean.

    Ethereum Provider与web3.js provider兼容是通过两个函数实现的,详情如下代码所示

    Error object and codes

    If an Error object is returned, it MUST contain a human readable string message describing the error and SHOULDpopulate the code and data properties on the error object with additional error details.

    Appropriate error codes SHOULD follow the table of CloseEvent status codes, along with the following table:

    Status codeNameDescription
    4001 User Denied Full Provider User denied the enabling of the full Ethereum Provider by choosing not to authorize any accounts for the dapp.
         
         

     Sample Class Implementation

    class EthereumProvider extends EventEmitter {
      constructor() {
        // Call super for `this` to be defined
        super();
    
        // Init storage
        this._isConnected = false;
        this._nextJsonrpcId = 0;
        this._promises = {};
        this._activeSubscriptions = [];
    
        // Fire the connect
        this._connect();
    
        // Listen for jsonrpc responses
        window.addEventListener('message', this._handleJsonrpcMessage.bind(this));
      }
    
      /* Methods */
    
      enable() {
        return new Promise((resolve, reject) => {
          window.mist
            .requestAccounts()
            .then(resolve)
            .catch(reject);
        });
      }
    
      send(method, params = []) {
        if (!method || typeof method !== 'string') {
          return new Error('Method is not a valid string.');
        }
    
        if (!(params instanceof Array)) {
          return new Error('Params is not a valid array.');
        }
    
        const id = this._nextJsonrpcId++;
        const jsonrpc = '2.0';
        const payload = { jsonrpc, id, method, params };
    
        const promise = new Promise((resolve, reject) => {
          this._promises[payload.id] = { resolve, reject };
        });
    
        // Send jsonrpc request to Mist
        window.postMessage(
          { type: 'mistAPI_ethereum_provider_write', message: payload },
          origin
        );
    
        return promise;
      }
    
      subscribe(subscriptionType, params) {
        return this.send('eth_subscribe', [subscriptionType, ...params]).then(
          subscriptionId => {
            this._activeSubscriptions.push(subscriptionId);
          }
        );
      }
    
      unsubscribe(subscriptionId) {
        return this.send('eth_unsubscribe', [subscriptionId]).then(success => {
          if (success) {
            // Remove subscription
            this._activeSubscription = this._activeSubscription.filter(
              id => id !== subscriptionId
            );
            // Remove listeners on subscriptionId
            this.removeAllListeners(subscriptionId);
          }
        });
      }
    
      /* Internal methods */
    
      _handleJsonrpcMessage(event) {
        // Return if no data to parse
        if (!event || !event.data) {
          return;
        }
    
        let data;
        try {
          data = JSON.parse(event.data);
        } catch (error) {
          // Return if we can't parse a valid object
          return;
        }
    
        // Return if not a jsonrpc response
        if (!data || !data.message || !data.message.jsonrpc) {
          return;
        }
    
        const message = data.message;
        const { id, method, error, result } = message;
    
        if (typeof id !== 'undefined') {
          const promise = this._promises[id];
          if (promise) {
            // Handle pending promise
            if (data.type === 'error') {
              promise.reject(message);
            } else if (message.error) {
              promise.reject(error);
            } else {
              promise.resolve(result);
            }
            delete this._promises[id];
          }
        } else {
          if (method && method.indexOf('_subscription') > -1) {
            // Emit subscription result
            const { subscription, result } = message.params;
            this.emit(subscription, result);
          }
        }
      }
    
      /* Connection handling */
    
      _connect() {
        // Send to Mist
        window.postMessage({ type: 'mistAPI_ethereum_provider_connect' }, origin);
    
        // Reconnect on close
        this.once('close', this._connect.bind(this));
      }
    
      /* Events */
    
      _emitConnect() {
        this._isConnected = true;
        this.emit('connect');
      }
    
      _emitClose(code, reason) {
        this._isConnected = false;
        this.emit('close', code, reason);
    
        // Send Error objects to any open subscriptions
        this._activeSubscriptions.forEach(id => {
          const error = new Error(
            `Provider connection to network closed.
             Subscription lost, please subscribe again.`
          );
          this.emit(id, error);
        });
        // Clear subscriptions
        this._activeSubscriptions = [];
      }
    
      _emitNetworkChanged(networkId) {
        this.emit('networkChanged', networkId);
      }
    
      _emitAccountsChanged(accounts) {
        this.emit('accountsChanged', accounts);
      }
    
      /* web3.js provider compatibility */
    
      sendAsync(payload, callback) {
        return this.send(payload.method, payload.params)
          .then(result => {
            const response = payload;
            response.result = result;
            callback(null, response);
          })
          .catch(error => {
            callback(error, null);
            // eslint-disable-next-line no-console
            console.error(
              `Error from EthereumProvider sendAsync ${payload}: ${error}`
            );
          });
      }
    
      isConnected() {
        return this._isConnected;
      }
    }
     

     

  • 相关阅读:
    XCode快捷键 转
    [iOS] UIView的clipsTobounds属性
    ios 重用UI部分代码的好方法(再也不用为局部变量的命名而烦恼啦!)
    symbol(s) not found for architecture armv7
    duplicate symbol _main in: / linker command failed with exit code 1
    xcode4.3.2 arc模式下导入非arc的文件 转
    objective-c block 详解 转
    将asi-http-request引入到ARC工程需要做的 转
    浅用block 转
    在Xcode4.5中禁用ARC(Automatic Referencing Counting) 转
  • 原文地址:https://www.cnblogs.com/wanghui-garcia/p/9709453.html
Copyright © 2020-2023  润新知