@andrekorol/kollateral
Version:
Typescript library for Kollateral, the flash loan building block
234 lines (191 loc) • 8.65 kB
text/typescript
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';