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 }