github.com/hyperledger/burrow@v0.34.5-0.20220512172541-77f09336001d/js/src/client.ts (about)

     1  import { Interface } from '@ethersproject/abi';
     2  import * as grpc from '@grpc/grpc-js';
     3  import { TxExecution } from '../proto/exec_pb';
     4  import { CallTx } from '../proto/payload_pb';
     5  import { ExecutionEventsClient, IExecutionEventsClient } from '../proto/rpcevents_grpc_pb';
     6  import { IQueryClient, QueryClient } from '../proto/rpcquery_grpc_pb';
     7  import { GetMetadataParam, StatusParam } from '../proto/rpcquery_pb';
     8  import { ITransactClient, TransactClient } from '../proto/rpctransact_grpc_pb';
     9  import { ResultStatus } from '../proto/rpc_pb';
    10  import { ContractCodec, getContractCodec } from './codec';
    11  import { Address } from './contracts/abi';
    12  import { getContractMeta, makeCallTx } from './contracts/call';
    13  import { CallOptions, Contract, ContractInstance } from './contracts/contract';
    14  import { toBuffer } from './convert';
    15  import { getException } from './error';
    16  import { Bounds, Event, EventCallback, EventStream, getBlockRange, queryFor, stream } from './events';
    17  import { Namereg } from './namereg';
    18  import { Provider } from './solts/interface.gd';
    19  
    20  type TxCallback = (error: grpc.ServiceError | null, txe: TxExecution) => void;
    21  
    22  export type Pipe = (payload: CallTx, callback: TxCallback) => void;
    23  
    24  export type Interceptor = (result: TxExecution) => Promise<TxExecution>;
    25  
    26  export class Client implements Provider {
    27    interceptor: Interceptor;
    28    readonly namereg: Namereg;
    29  
    30    readonly executionEvents: IExecutionEventsClient;
    31    readonly transact: ITransactClient;
    32    readonly query: IQueryClient;
    33  
    34    readonly callPipe: Pipe;
    35    readonly simPipe: Pipe;
    36  
    37    constructor(public readonly url: string, public readonly account: string) {
    38      const credentials = grpc.credentials.createInsecure();
    39      this.executionEvents = new ExecutionEventsClient(url, credentials);
    40      this.transact = new TransactClient(url, credentials);
    41      this.query = new QueryClient(url, credentials);
    42      // Contracts stuff running on top of grpc
    43      this.namereg = new Namereg(this.transact, this.query, this.account);
    44      // NOTE: in general interceptor may be async
    45      this.interceptor = async (data) => data;
    46  
    47      this.callPipe = this.transact.callTxSync.bind(this.transact);
    48      this.simPipe = this.transact.callTxSim.bind(this.transact);
    49    }
    50  
    51    /**
    52     * Looks up the ABI for a deployed contract from Burrow's contract metadata store.
    53     * Contract metadata is only stored when provided by the contract deployer so is not guaranteed to exist.
    54     *
    55     * @method address
    56     * @param {string} address
    57     * @param handler
    58     * @throws an error if no metadata found and contract could not be instantiated
    59     * @returns {Contract} interface object
    60     */
    61    contractAt(address: string, handler?: CallOptions): Promise<ContractInstance> {
    62      const msg = new GetMetadataParam();
    63      msg.setAddress(Buffer.from(address, 'hex'));
    64  
    65      return new Promise((resolve, reject) =>
    66        this.query.getMetadata(msg, (err, res) => {
    67          if (err) {
    68            reject(err);
    69          }
    70          const metadata = res.getMetadata();
    71          if (!metadata) {
    72            throw new Error(`could not find any metadata for account ${address}`);
    73          }
    74  
    75          // TODO: parse with io-ts
    76          const abi = JSON.parse(metadata).Abi;
    77  
    78          const contract = new Contract(abi);
    79          resolve(contract.at(address, this, handler));
    80        }),
    81      );
    82    }
    83  
    84    callTxSync(callTx: CallTx): Promise<TxExecution> {
    85      return new Promise((resolve, reject) =>
    86        this.transact.callTxSync(callTx, (error, txe) => {
    87          if (error) {
    88            return reject(error);
    89          }
    90          const err = getException(txe);
    91          if (err) {
    92            return reject(err);
    93          }
    94          return resolve(this.interceptor(txe));
    95        }),
    96      );
    97    }
    98  
    99    callTxSim(callTx: CallTx): Promise<TxExecution> {
   100      return new Promise((resolve, reject) =>
   101        this.transact.callTxSim(callTx, (error, txe) => {
   102          if (error) {
   103            return reject(error);
   104          }
   105          const err = getException(txe);
   106          if (err) {
   107            return reject(err);
   108          }
   109          return resolve(txe);
   110        }),
   111      );
   112    }
   113  
   114    status(): Promise<ResultStatus> {
   115      return new Promise((resolve, reject) =>
   116        this.query.status(new StatusParam(), (err, resp) => (err ? reject(err) : resolve(resp))),
   117      );
   118    }
   119  
   120    async latestHeight(): Promise<number> {
   121      const status = await this.status();
   122      return status.getSyncinfo()?.getLatestblockheight() ?? 0;
   123    }
   124  
   125    // Methods below implement the generated codegen provider
   126    // TODO: should probably generate canonical version of Provider interface somewhere outside of files
   127  
   128    async deploy(
   129      data: string | Uint8Array,
   130      contractMeta: { abi: string; codeHash: Uint8Array }[] = [],
   131    ): Promise<Address> {
   132      const tx = makeCallTx(
   133        toBuffer(data),
   134        this.account,
   135        undefined,
   136        contractMeta.map(({ abi, codeHash }) => getContractMeta(abi, codeHash)),
   137      );
   138      const txe = await this.callTxSync(tx);
   139      const contractAddress = txe.getReceipt()?.getContractaddress_asU8();
   140      if (!contractAddress) {
   141        throw new Error(`deploy appears to have succeeded but contract address is missing from result: ${txe}`);
   142      }
   143      return Buffer.from(contractAddress).toString('hex').toUpperCase();
   144    }
   145  
   146    async call(data: string | Uint8Array, address: string): Promise<Uint8Array | undefined> {
   147      const tx = makeCallTx(toBuffer(data), this.account, address);
   148      const txe = await this.callTxSync(tx);
   149      return txe.getResult()?.getReturn_asU8();
   150    }
   151  
   152    async callSim(data: string | Uint8Array, address: string): Promise<Uint8Array | undefined> {
   153      const tx = makeCallTx(toBuffer(data), this.account, address);
   154      const txe = await this.callTxSim(tx);
   155      return txe.getResult()?.getReturn_asU8();
   156    }
   157  
   158    listen(
   159      signatures: string[],
   160      address: string,
   161      callback: EventCallback<Event>,
   162      start: Bounds = 'latest',
   163      end: Bounds = 'stream',
   164    ): EventStream {
   165      return stream(this.executionEvents, getBlockRange(start, end), queryFor({ address, signatures }), callback);
   166    }
   167  
   168    contractCodec(contractABI: string): ContractCodec {
   169      const iface = new Interface(contractABI);
   170      return getContractCodec(iface);
   171    }
   172  }