UNPKG

cic-client

Version:

Typescript libraries for building CIC client applications

200 lines (173 loc) 6.73 kB
import {DeclaratorHelper} from "./helper"; const LRU = require('lru-cache'); import { abi, interfaceCodes } from './solidity'; import { zeroAddress } from './const'; import { EVMMethodID, EVMAddress, EVMContract, FungibleToken } from './typ'; import { subtle } from './crypto'; import { bytesToHex } from './digest'; import { FileGetter } from './file'; class KV { store = {}; public put(k:string, v:any) { this.store[k] = v; } public get(k:string): any { return this.store[k]; } public peek(k:string): any { return this.store[k]; } } interface Registry { getAbi(abiName:string): Promise<object>; getContract(address:EVMAddress): Promise<EVMContract>; getContractByName(contractName:string, abiName?:string, requireInterfaces?:EVMMethodID[]): Promise<EVMContract>; getContractAddressByName(contractName:string, abiName?:string, requireInterfaces?:EVMMethodID[]): Promise<string>; getToken(address:EVMAddress): Promise<FungibleToken>; getTokenBySymbol(tokenRegistryContractName:string, symbol:string, checkInterface:boolean): Promise<FungibleToken>; addToken(address:EVMAddress): Promise<EVMContract>; declaratorHelper; } class CICRegistry { w3: any fileGetter: FileGetter address: string abiName: string; contract: any // TODO: replace with web3 contract type store: KV paths: string[] onload: (a:string) => void declaratorHelper: DeclaratorHelper constructor(w3:any, address:EVMAddress, abiName: string, fileGetter:FileGetter, paths?:string[]) { this.w3 = w3; this.address = address; this.abiName = abiName; this.store = new KV(); this.paths = paths; this.fileGetter = fileGetter; this.declaratorHelper = new DeclaratorHelper(w3, this); } public async load() { const registryAbi = await abi(this.fileGetter, this.abiName, this.paths); this.contract = new this.w3.eth.Contract(registryAbi, this.address); const contractIdHex = this.w3.utils.toHex('ContractRegistry'); const contractId = this.w3.eth.abi.encodeParameter('bytes32', contractIdHex); const confirmedContractAddress = await this.contract.methods.addressOf(contractId).call(); if (this.address.toLowerCase() != confirmedContractAddress.toLowerCase()) { throw new Error('cic registry contract entry does not match its own address'); } if (this.onload !== undefined) { this.onload(this.address); } } public async getAbi(abiName:string): Promise<object> { let abiObject = this.store.get(toAbiKey(abiName)) if (abiObject === undefined) { abiObject = await abi(this.fileGetter, abiName, this.paths); this.store.put(abiName, abiObject); } return abiObject; } public async addToken(address:EVMAddress, requireTrust:boolean=false): Promise<EVMContract> { if (requireTrust) { throw new Error('trust check not implemented yet, sorry'); } const erc20Abi = await this.getAbi('ERC20'); const tokenContract = new this.w3.eth.Contract(erc20Abi, address); const tokenSymbol = await tokenContract.methods.symbol().call(); if (tokenSymbol === undefined) { throw new Error('attempted to add token contract ' + address + ' which is not an ERC20 token'); } this.store.put(toTokenKey(tokenSymbol), tokenContract); this.store.put(address, tokenContract); return tokenContract; } public async getContract(address:EVMAddress): Promise<EVMContract> { let contract = this.store.get(address); if (contract === undefined) { throw new Error('unknown contract ' + address); } return contract; } public async getToken(address:EVMAddress): Promise<EVMContract> { const tokenContract = await this.getContract(address); const tokenSymbol = await tokenContract.methods.symbol().call(); if (tokenSymbol === undefined) { throw new Error('contract ' + address + ' is not an ERC20 token'); } return tokenContract; } public async getContractByName(contractName:string, abiName?:string, requireInterfaces?:EVMMethodID[]): Promise<EVMContract> { const contractKey = toContractKey(contractName); let contract = this.store.get(contractKey); if (contract === undefined) { const contractAddress = await this.getContractAddressByName(contractName, abiName, requireInterfaces); if (abiName === undefined) { abiName = contractName; } const contractAbi = await this.getAbi(abiName); contract = new this.w3.eth.Contract(contractAbi, contractAddress); this.store.put(contractKey, contract); this.store.put(contractAddress, contract); } return contract; } public async getContractAddressByName(contractName:string, abiName?:string, requireInterfaces?:EVMMethodID[]): Promise<string> { const contract_id_hex = this.w3.utils.toHex(contractName); const contract_id = this.w3.eth.abi.encodeParameter('bytes32', contract_id_hex); const contractAddress = await this.contract.methods.addressOf(contract_id).call(); if (contractAddress == zeroAddress) { throw new Error('unknown contract ' + contractName + ' (' + contract_id_hex + ')'); } return contractAddress; } public async getFungibleToken(tokenAddress:EVMAddress, checkInterface:boolean=false): Promise<FungibleToken> { const tokenAbi = await this.getAbi('ERC20'); const tokenContract = new this.w3.eth.Contract(tokenAbi, tokenAddress); if (checkInterface) { if (!tokenContract.methods.supportsInterface('ERC20')) { throw 'token does not declare ERC20 interface support'; } } const tokenSymbol = await tokenContract.methods.symbol().call(); this.store.put(toTokenKey(tokenSymbol), tokenContract); this.store.put(tokenAddress, tokenContract); return tokenContract; } public async getTokenBySymbol(tokenRegistryContractName:string, symbol:string, checkInterface:boolean=false): Promise<FungibleToken> { let token = this.store.get(toTokenKey(symbol)); if (token !== undefined) { return token; } const tokenRegistryContract = await this.getContractByName( tokenRegistryContractName, 'Registry', [interfaceCodes.Registry], ); const symbolId = await toRegistryKey(symbol); const tokenAddress = await tokenRegistryContract.methods.addressOf(symbolId).call(); if (tokenAddress === zeroAddress) { throw 'unknown token "' + symbol + '" using registry "' + tokenRegistryContractName + '"'; } token = this.getFungibleToken(tokenAddress, checkInterface); return token; } } async function toRegistryKey(s:string): Promise<string> { const sDigest = await subtle.digest('SHA-256', s); const sId = '0x' + bytesToHex(sDigest); return sId; } function toContractKey(s:string): string { return 'contract:' + s; } function toAbiKey(s:string): string { return 'abi:' + s; } function toTokenKey(s:string): string { return 'token:' + s; } export { CICRegistry, Registry, toContractKey, toRegistryKey }