UNPKG

@andrekorol/kollateral

Version:

Typescript library for Kollateral, the flash loan building block

234 lines (191 loc) 8.65 kB
import {BigNumber} from "bignumber.js"; import {Utils} from "./util/utils"; import {KollateralConfig} from "./config/kollateral-config"; import {Invoker} from "./generated/Invoker"; import {KErc20} from "./generated/KErc20"; import {KEther} from "./generated/KEther"; import Web3 from "web3"; import {TestToken} from "./generated/TestToken"; import {AbiItem} from "web3-utils"; import {KToken} from "./generated/KToken"; import {Network, NetworkUtils} from "./static/network"; import {InvokerUtils} from "./static/invoker"; import {Token, TokenUtils} from "./static/tokens"; import {KTokenUtils} from "./static/ktokens"; import {Execution} from "./models/Invocation"; import {TokenAmount} from "./models/token-amount"; import {TransactionConfig} from "web3-core"; import {AnyNumber} from "./models/const"; export class Kollateral { public static MAX_UINT256: BigNumber = new BigNumber('FFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFF', 16); private _provider: any; private _config: KollateralConfig; private _web3: Web3; private _invoker: Invoker; private _kEther: KEther; private _kErc20s: Map<string, KErc20>; private _kTokens: Map<string, KToken>; private _erc20Abi: AbiItem; constructor(provider: any, config: KollateralConfig) { this._provider = provider; this._config = config; this._web3 = new Web3(provider); const invokerAbi = require('./abi/Invoker.json').abi; this._invoker = new this._web3.eth.Contract(invokerAbi, config.invokerAddress) as Invoker; const kEtherAbi = require('./abi/KEther.json').abi; this._kEther = new this._web3.eth.Contract(kEtherAbi, config.network.tokens.get(Token.ETH)!.kTokenAddress) as KEther; const kErc20Abi = require('./abi/KErc20.json').abi; this._kErc20s = new Map<string, KErc20>(); const kTokenAbi = require('./abi/KToken.json').abi; this._kTokens = new Map<string, KToken>(); this._erc20Abi = require('./abi/TestToken.json').abi; config.network.tokens.forEach((config, token) => { if (token != Token.ETH) { this._kErc20s.set(config.kTokenAddress, new this._web3.eth.Contract(kErc20Abi, config.kTokenAddress) as KErc20); } this._kTokens.set(config.kTokenAddress, new this._web3.eth.Contract(kTokenAbi, config.kTokenAddress) as KToken); }); } static async init(provider: any): Promise<Kollateral> { const networkId = await new Web3(provider).eth.net.getId(); const network = NetworkUtils.fromId(networkId); /* Fail if not a complete static config available for network */ if (!Kollateral.isSupportedNetwork(network)) { throw("Unsupported Network"); } const config: KollateralConfig = { invokerAddress: InvokerUtils.getAddress(network)!, network: { network: network, tokens: new Map(TokenUtils.getSupportedTokens(network) .map(token => [token, { tokenAddress: TokenUtils.getAddress(network, token)!, kTokenAddress: KTokenUtils.getAddress(network, token)! }])) } }; return new Kollateral(provider, config); } static isSupportedNetwork(network: Network): boolean { return ([Network.Ropsten, Network.Rinkeby, Network.Mainnet].includes(network)); } /* KToken Supplying */ public unlock(sender: string, kTokenAddress: string): Promise<boolean> { return this.unlockAmount(sender, kTokenAddress, Kollateral.MAX_UINT256); } public async unlockAmount(sender: string, kTokenAddress: string, amount: BigNumber): Promise<boolean> { const tokenAddress = await this._kTokens.get(kTokenAddress)!.methods.underlying().call(); return this.tokenOf(tokenAddress).methods.approve(kTokenAddress, amount.toFixed()).send({ from: sender }); } public async allowance(owner: string, kTokenAddress: string): Promise<BigNumber> { const tokenAddress = await this._kTokens.get(kTokenAddress)!.methods.underlying().call(); return this.tokenOf(tokenAddress).methods.allowance(owner, kTokenAddress).call().then(bn => Utils.bnToBigNumber(bn)); } public async isUnlocked(owner: string, kTokenAddress: string): Promise<boolean> { const allowance = await this.allowance(owner, kTokenAddress); return allowance.eq(Kollateral.MAX_UINT256); } public mint(sender: string, kTokenAddress: string, amount: BigNumber): Promise<boolean> { if (this.isKEtherAddress(kTokenAddress)) { return this._kEther.methods.mint().send({ from: sender, value: amount.toFixed() }); } else { return this._kErc20s.get(kTokenAddress)!.methods.mint(amount.toFixed()).send({ from: sender }); } } public redeem(sender: string, kTokenAddress: string, amount: BigNumber): Promise<boolean> { return this._kTokens.get(kTokenAddress)!.methods.redeem(amount.toFixed()).send({ from: sender }); } public redeemUnderlying(sender: string, kTokenAddress: string, amount: BigNumber): Promise<boolean> { return this._kTokens.get(kTokenAddress)!.methods.redeemUnderlying(amount.toFixed()).send({ from: sender }); } private isKEtherAddress(kTokenAddress: string): boolean { return kTokenAddress == this._config.network.tokens.get(Token.ETH)!.kTokenAddress; } public balanceOf(owner: string, tokenAddress: string): Promise<BigNumber> { return this.tokenOf(tokenAddress).methods.balanceOf(owner).call() .then(bn => Utils.bnToBigNumber(bn)); } public balanceOfUnderlying(owner: string, kTokenAddress: string): Promise<BigNumber> { return this._kTokens.get(kTokenAddress)!.methods.balanceOfUnderlying(owner).call() .then(bn => Utils.bnToBigNumber(bn)); } public underlyingAmountToNativeAmount(kTokenAddress: string, tokenAmount: BigNumber, ceiling = false): Promise<BigNumber> { return this._kTokens.get(kTokenAddress)!.methods.underlyingAmountToNativeAmount(tokenAmount.toFixed(), ceiling).call() .then(bn => Utils.bnToBigNumber(bn)); } public nativeAmountToUnderlyingAmount(kTokenAddress: string, kTokenAmount: BigNumber): Promise<BigNumber> { return this._kTokens.get(kTokenAddress)!.methods.nativeAmountToUnderlyingAmount(kTokenAmount.toFixed()).call() .then(bn => Utils.bnToBigNumber(bn)); } public totalSupply(tokenAddress: string): Promise<BigNumber> { return this.tokenOf(tokenAddress).methods.totalSupply().call() .then(bn => Utils.bnToBigNumber(bn)); } public totalReserve(kTokenAddress: string): Promise<BigNumber> { return this._kTokens.get(kTokenAddress)!.methods.totalReserve().call() .then(bn => Utils.bnToBigNumber(bn)); } private tokenOf(tokenAddress: string): TestToken { return new this._web3.eth.Contract(this._erc20Abi, tokenAddress) as TestToken; } /* Invocation */ public async invoke( execution: Execution, tokenAmount: TokenAmount, txOpt: TransactionConfig = {} ): Promise<void> { if (txOpt.from == undefined) { txOpt.from = (await this._web3.eth.getAccounts())[0]; } txOpt.value = this.valueOrDefault(execution.value).toFixed(); const tokenAddress = this.getTokenAddressOrThrow(tokenAmount.token); return this._invoker.methods.invoke( execution.contract, this.dataOrDefault(execution.data), tokenAddress, Utils.normalizeNumber(tokenAmount.amount).toFixed() ).send(txOpt); } public totalLiquidity(token: Token): Promise<BigNumber> { const tokenAddress = this.getTokenAddressOrThrow(token); return this._invoker.methods.totalLiquidity(tokenAddress).call() .then(bn => Utils.bnToBigNumber(bn)); } /* Testnet */ public faucet(sender: string, tokenAmount: TokenAmount): Promise<boolean> { const tokenAddress = this.getTokenAddressOrThrow(tokenAmount.token); const token = new this._web3.eth.Contract(this._erc20Abi, tokenAddress) as TestToken; return token.methods.mint(Utils.normalizeNumber(tokenAmount.amount).toFixed()).send({ from: sender }); } /* Private */ private getTokenAddressOrThrow(token: Token): string { if (!TokenUtils.isSupportedToken(this._config.network.network, token)) { throw("Unsupported token"); } return TokenUtils.getAddress(this._config.network.network, token)!; } private valueOrDefault(value: AnyNumber | undefined): BigNumber { return Utils.normalizeNumber(value == undefined ? 0 : value); } private dataOrDefault(data: string | undefined): string | number[] { return data == undefined ? [] : data; } } export * from './static/tokens'; export * from './static/ktokens'; export * from './static/network'; export * from './static/invoker';