parent
144a051e39
commit
59d336e54a
@ -40,6 +40,7 @@
|
||||
"@iov/crypto": "^2.0.0-alpha.7",
|
||||
"@iov/encoding": "^2.0.0-alpha.7",
|
||||
"@iov/keycontrol": "^2.0.0-alpha.7",
|
||||
"@iov/utils": "^2.0.0-alpha.7",
|
||||
"@koa/cors": "^3.0.0",
|
||||
"axios": "^0.19.0",
|
||||
"fast-deep-equal": "^3.1.1",
|
||||
|
||||
@ -3,16 +3,15 @@ import cors = require("@koa/cors");
|
||||
import Koa from "koa";
|
||||
import bodyParser from "koa-bodyparser";
|
||||
|
||||
import { creditAmount, setFractionalDigits } from "../../cashflow";
|
||||
import { codecDefaultFractionalDigits, codecImplementation, establishConnection } from "../../codec";
|
||||
import { codecImplementation, establishConnection } from "../../codec";
|
||||
import * as constants from "../../constants";
|
||||
import { logAccountsState, logSendJob } from "../../debugging";
|
||||
import { Faucet } from "../../faucet";
|
||||
import {
|
||||
availableTokensFromHolder,
|
||||
identitiesOfFirstWallet,
|
||||
loadAccounts,
|
||||
loadTokenTickers,
|
||||
refill,
|
||||
send,
|
||||
} from "../../multichainhelpers";
|
||||
import { setSecretAndCreateIdentities } from "../../profile";
|
||||
@ -48,7 +47,6 @@ export async function start(args: ReadonlyArray<string>): Promise<void> {
|
||||
const connectedChainId = connection.chainId();
|
||||
console.info(`Connected to network: ${connectedChainId}`);
|
||||
|
||||
setFractionalDigits(codecDefaultFractionalDigits());
|
||||
await setSecretAndCreateIdentities(profile, constants.mnemonic, connectedChainId);
|
||||
|
||||
const chainTokens = await loadTokenTickers(connection);
|
||||
@ -67,8 +65,10 @@ export async function start(args: ReadonlyArray<string>): Promise<void> {
|
||||
|
||||
const distibutorIdentities = identitiesOfFirstWallet(profile).slice(1);
|
||||
|
||||
await refill(profile, connection);
|
||||
setInterval(async () => refill(profile, connection), 60_000); // ever 60 seconds
|
||||
const faucet = new Faucet(constants.tokenConfig);
|
||||
|
||||
await faucet.refill(profile, connection);
|
||||
setInterval(async () => faucet.refill(profile, connection), 60_000); // ever 60 seconds
|
||||
|
||||
console.info("Creating webserver ...");
|
||||
const api = new Koa();
|
||||
@ -127,7 +127,7 @@ export async function start(args: ReadonlyArray<string>): Promise<void> {
|
||||
const job: SendJob = {
|
||||
sender: sender,
|
||||
recipient: address,
|
||||
amount: creditAmount(ticker),
|
||||
amount: faucet.creditAmount(ticker),
|
||||
tokenTicker: ticker,
|
||||
};
|
||||
logSendJob(job);
|
||||
|
||||
@ -1,124 +0,0 @@
|
||||
import { TokenTicker } from "@iov/bcp";
|
||||
|
||||
import { creditAmount, refillAmount, refillThreshold, setFractionalDigits } from "./cashflow";
|
||||
|
||||
describe("Cashflow", () => {
|
||||
beforeAll(() => {
|
||||
setFractionalDigits(3);
|
||||
});
|
||||
|
||||
describe("creditAmount", () => {
|
||||
it("returns '10' + '000' by default", () => {
|
||||
expect(creditAmount("TOKENZ" as TokenTicker)).toEqual({
|
||||
quantity: "10000",
|
||||
fractionalDigits: 3,
|
||||
tokenTicker: "TOKENZ",
|
||||
});
|
||||
expect(creditAmount("TRASH" as TokenTicker)).toEqual({
|
||||
quantity: "10000",
|
||||
fractionalDigits: 3,
|
||||
tokenTicker: "TRASH",
|
||||
});
|
||||
});
|
||||
|
||||
it("returns value from env variable + '000' when set", () => {
|
||||
process.env.FAUCET_CREDIT_AMOUNT_WTF = "22";
|
||||
expect(creditAmount("WTF" as TokenTicker)).toEqual({
|
||||
quantity: "22000",
|
||||
fractionalDigits: 3,
|
||||
tokenTicker: "WTF",
|
||||
});
|
||||
});
|
||||
|
||||
it("returns default from env variable + '000' when set to empty", () => {
|
||||
process.env.FAUCET_CREDIT_AMOUNT_WTF = "";
|
||||
expect(creditAmount("WTF" as TokenTicker)).toEqual({
|
||||
quantity: "10000",
|
||||
fractionalDigits: 3,
|
||||
tokenTicker: "WTF",
|
||||
});
|
||||
});
|
||||
});
|
||||
|
||||
describe("refillAmount", () => {
|
||||
beforeEach(() => {
|
||||
process.env.FAUCET_REFILL_FACTOR = "";
|
||||
});
|
||||
it("returns 20*10 + '000' by default", () => {
|
||||
expect(refillAmount("TOKENZ" as TokenTicker)).toEqual({
|
||||
quantity: "200000",
|
||||
fractionalDigits: 3,
|
||||
tokenTicker: "TOKENZ",
|
||||
});
|
||||
});
|
||||
|
||||
it("returns 20*22 + '000' when credit amount is 22", () => {
|
||||
process.env.FAUCET_CREDIT_AMOUNT_WTF = "22";
|
||||
expect(refillAmount("WTF" as TokenTicker)).toEqual({
|
||||
quantity: "440000",
|
||||
fractionalDigits: 3,
|
||||
tokenTicker: "WTF",
|
||||
});
|
||||
});
|
||||
|
||||
it("returns 30*10 + '000' when refill factor is 30", () => {
|
||||
process.env.FAUCET_REFILL_FACTOR = "30";
|
||||
expect(refillAmount("TOKENZ" as TokenTicker)).toEqual({
|
||||
quantity: "300000",
|
||||
fractionalDigits: 3,
|
||||
tokenTicker: "TOKENZ",
|
||||
});
|
||||
});
|
||||
|
||||
it("returns 30*22 + '000' when refill factor is 30 and credit amount is 22", () => {
|
||||
process.env.FAUCET_REFILL_FACTOR = "30";
|
||||
process.env.FAUCET_CREDIT_AMOUNT_WTF = "22";
|
||||
expect(refillAmount("WTF" as TokenTicker)).toEqual({
|
||||
quantity: "660000",
|
||||
fractionalDigits: 3,
|
||||
tokenTicker: "WTF",
|
||||
});
|
||||
});
|
||||
});
|
||||
|
||||
describe("refillThreshold", () => {
|
||||
beforeEach(() => {
|
||||
process.env.FAUCET_REFILL_THRESHOLD = "";
|
||||
});
|
||||
it("returns 8*10 + '000' by default", () => {
|
||||
expect(refillThreshold("TOKENZ" as TokenTicker)).toEqual({
|
||||
quantity: "80000",
|
||||
fractionalDigits: 3,
|
||||
tokenTicker: "TOKENZ",
|
||||
});
|
||||
});
|
||||
|
||||
it("returns 8*22 + '000' when credit amount is 22", () => {
|
||||
process.env.FAUCET_CREDIT_AMOUNT_WTF = "22";
|
||||
expect(refillThreshold("WTF" as TokenTicker)).toEqual({
|
||||
quantity: "176000",
|
||||
fractionalDigits: 3,
|
||||
tokenTicker: "WTF",
|
||||
});
|
||||
});
|
||||
|
||||
it("returns 5*10 + '000' when refill threshold is 5", () => {
|
||||
process.env.FAUCET_REFILL_THRESHOLD = "5";
|
||||
expect(refillThreshold("TOKENZ" as TokenTicker)).toEqual({
|
||||
quantity: "50000",
|
||||
fractionalDigits: 3,
|
||||
tokenTicker: "TOKENZ",
|
||||
});
|
||||
});
|
||||
|
||||
it("returns 5*22 + '000' when refill threshold is 5 and credit amount is 22", () => {
|
||||
process.env.FAUCET_REFILL_THRESHOLD = "5";
|
||||
process.env.FAUCET_CREDIT_AMOUNT_WTF = "22";
|
||||
expect(refillThreshold("WTF" as TokenTicker)).toEqual({
|
||||
quantity: "110000",
|
||||
fractionalDigits: 3,
|
||||
tokenTicker: "WTF",
|
||||
});
|
||||
});
|
||||
});
|
||||
});
|
||||
@ -1,64 +0,0 @@
|
||||
import { Account, Amount, TokenTicker } from "@iov/bcp";
|
||||
import { Decimal, Uint53 } from "@iov/encoding";
|
||||
|
||||
/** Send `factor` times credit amount on refilling */
|
||||
const defaultRefillFactor = 20;
|
||||
|
||||
/** refill when balance gets below `factor` times credit amount */
|
||||
const defaultRefillThresholdFactor = 8;
|
||||
|
||||
// Load this from connection?
|
||||
let globalFractionalDigits: number | undefined;
|
||||
|
||||
export function setFractionalDigits(input: number): void {
|
||||
globalFractionalDigits = input;
|
||||
}
|
||||
|
||||
export function getFractionalDigits(): number {
|
||||
if (globalFractionalDigits === undefined) {
|
||||
throw new Error("Fractional digits not set");
|
||||
}
|
||||
return globalFractionalDigits;
|
||||
}
|
||||
|
||||
/** The amount of tokens that will be sent to the user */
|
||||
export function creditAmount(token: TokenTicker, factor: Uint53 = new Uint53(1)): Amount {
|
||||
const amountFromEnv = process.env[`FAUCET_CREDIT_AMOUNT_${token}`];
|
||||
const amount = amountFromEnv ? Uint53.fromString(amountFromEnv).toNumber() : 10;
|
||||
const value = new Uint53(amount * factor.toNumber());
|
||||
|
||||
const fractionalDigits = getFractionalDigits();
|
||||
return {
|
||||
quantity: value.toString() + "0".repeat(fractionalDigits),
|
||||
fractionalDigits: fractionalDigits,
|
||||
tokenTicker: token,
|
||||
};
|
||||
}
|
||||
|
||||
export function refillAmount(token: TokenTicker): Amount {
|
||||
const factorFromEnv = Number.parseInt(process.env.FAUCET_REFILL_FACTOR || "0", 10) || undefined;
|
||||
const factor = new Uint53(factorFromEnv || defaultRefillFactor);
|
||||
return creditAmount(token, factor);
|
||||
}
|
||||
|
||||
export function refillThreshold(token: TokenTicker): Amount {
|
||||
const factorFromEnv = Number.parseInt(process.env.FAUCET_REFILL_THRESHOLD || "0", 10) || undefined;
|
||||
const factor = new Uint53(factorFromEnv || defaultRefillThresholdFactor);
|
||||
return creditAmount(token, factor);
|
||||
}
|
||||
|
||||
/** true iff the distributor account needs a refill */
|
||||
export function needsRefill(account: Account, token: TokenTicker): boolean {
|
||||
const balanceAmount = account.balance.find(b => b.tokenTicker === token);
|
||||
|
||||
const balance = balanceAmount
|
||||
? Decimal.fromAtomics(balanceAmount.quantity, balanceAmount.fractionalDigits)
|
||||
: Decimal.fromAtomics("0", 0);
|
||||
|
||||
const thresholdAmount = refillThreshold(token);
|
||||
const threshold = Decimal.fromAtomics(thresholdAmount.quantity, thresholdAmount.fractionalDigits);
|
||||
|
||||
// TODO: perform < operation on Decimal type directly
|
||||
// https://github.com/iov-one/iov-core/issues/1375
|
||||
return balance.toFloatApproximation() < threshold.toFloatApproximation();
|
||||
}
|
||||
@ -1,32 +1,14 @@
|
||||
import { CosmWasmCodec, CosmWasmConnection, TokenConfiguration } from "@cosmwasm/bcp";
|
||||
import { CosmWasmCodec, CosmWasmConnection } from "@cosmwasm/bcp";
|
||||
import { TxCodec } from "@iov/bcp";
|
||||
|
||||
import { tokenConfig } from "./constants";
|
||||
|
||||
const prefix = "cosmos";
|
||||
const config: TokenConfiguration = {
|
||||
bankTokens: [
|
||||
{
|
||||
fractionalDigits: 6,
|
||||
name: "Fee Token",
|
||||
ticker: "COSM",
|
||||
denom: "cosm",
|
||||
},
|
||||
{
|
||||
fractionalDigits: 6,
|
||||
name: "Staking Token",
|
||||
ticker: "STAKE",
|
||||
denom: "stake",
|
||||
},
|
||||
],
|
||||
};
|
||||
|
||||
export async function establishConnection(url: string): Promise<CosmWasmConnection> {
|
||||
return CosmWasmConnection.establish(url, prefix, config);
|
||||
return CosmWasmConnection.establish(url, prefix, tokenConfig);
|
||||
}
|
||||
|
||||
export function codecImplementation(): TxCodec {
|
||||
return new CosmWasmCodec(prefix, config.bankTokens);
|
||||
}
|
||||
|
||||
export function codecDefaultFractionalDigits(): number {
|
||||
return 6;
|
||||
return new CosmWasmCodec(prefix, tokenConfig.bankTokens);
|
||||
}
|
||||
|
||||
@ -1,4 +1,23 @@
|
||||
import { TokenConfiguration } from "@cosmwasm/bcp";
|
||||
|
||||
export const binaryName = "cosmwasm-faucet";
|
||||
export const concurrency: number = Number.parseInt(process.env.FAUCET_CONCURRENCY || "", 10) || 5;
|
||||
export const port: number = Number.parseInt(process.env.FAUCET_PORT || "", 10) || 8000;
|
||||
export const mnemonic: string | undefined = process.env.FAUCET_MNEMONIC;
|
||||
|
||||
export const tokenConfig: TokenConfiguration = {
|
||||
bankTokens: [
|
||||
{
|
||||
fractionalDigits: 6,
|
||||
name: "Fee Token",
|
||||
ticker: "COSM",
|
||||
denom: "cosm",
|
||||
},
|
||||
{
|
||||
fractionalDigits: 6,
|
||||
name: "Staking Token",
|
||||
ticker: "STAKE",
|
||||
denom: "stake",
|
||||
},
|
||||
],
|
||||
};
|
||||
|
||||
157
packages/faucet/src/faucet.spec.ts
Normal file
157
packages/faucet/src/faucet.spec.ts
Normal file
@ -0,0 +1,157 @@
|
||||
import { TokenConfiguration } from "@cosmwasm/bcp";
|
||||
import { TokenTicker } from "@iov/bcp";
|
||||
|
||||
import { Faucet } from "./faucet";
|
||||
|
||||
const dummyConfig: TokenConfiguration = {
|
||||
bankTokens: [
|
||||
{
|
||||
ticker: "TOKENZ",
|
||||
name: "The tokenz",
|
||||
fractionalDigits: 6,
|
||||
denom: "utokenz",
|
||||
},
|
||||
{
|
||||
ticker: "TRASH",
|
||||
name: "Trash token",
|
||||
fractionalDigits: 3,
|
||||
denom: "mtrash",
|
||||
},
|
||||
],
|
||||
};
|
||||
|
||||
describe("Faucet", () => {
|
||||
describe("constructor", () => {
|
||||
it("can be constructed", () => {
|
||||
const faucet = new Faucet(dummyConfig);
|
||||
expect(faucet).toBeTruthy();
|
||||
});
|
||||
});
|
||||
|
||||
describe("creditAmount", () => {
|
||||
const faucet = new Faucet(dummyConfig);
|
||||
|
||||
it("returns 10 tokens by default", () => {
|
||||
expect(faucet.creditAmount("TOKENZ" as TokenTicker)).toEqual({
|
||||
quantity: "10000000",
|
||||
fractionalDigits: 6,
|
||||
tokenTicker: "TOKENZ",
|
||||
});
|
||||
expect(faucet.creditAmount("TRASH" as TokenTicker)).toEqual({
|
||||
quantity: "10000",
|
||||
fractionalDigits: 3,
|
||||
tokenTicker: "TRASH",
|
||||
});
|
||||
});
|
||||
|
||||
it("returns value from env variable when set", () => {
|
||||
process.env.FAUCET_CREDIT_AMOUNT_TRASH = "22";
|
||||
expect(faucet.creditAmount("TRASH" as TokenTicker)).toEqual({
|
||||
quantity: "22000",
|
||||
fractionalDigits: 3,
|
||||
tokenTicker: "TRASH",
|
||||
});
|
||||
process.env.FAUCET_CREDIT_AMOUNT_TRASH = "";
|
||||
});
|
||||
|
||||
it("returns default when env variable is set to empty", () => {
|
||||
process.env.FAUCET_CREDIT_AMOUNT_TRASH = "";
|
||||
expect(faucet.creditAmount("TRASH" as TokenTicker)).toEqual({
|
||||
quantity: "10000",
|
||||
fractionalDigits: 3,
|
||||
tokenTicker: "TRASH",
|
||||
});
|
||||
process.env.FAUCET_CREDIT_AMOUNT_TRASH = "";
|
||||
});
|
||||
});
|
||||
|
||||
describe("refillAmount", () => {
|
||||
const faucet = new Faucet(dummyConfig);
|
||||
|
||||
beforeEach(() => {
|
||||
process.env.FAUCET_REFILL_FACTOR = "";
|
||||
process.env.FAUCET_CREDIT_AMOUNT_TRASH = "";
|
||||
});
|
||||
|
||||
it("returns 20*10 + '000' by default", () => {
|
||||
expect(faucet.refillAmount("TRASH" as TokenTicker)).toEqual({
|
||||
quantity: "200000",
|
||||
fractionalDigits: 3,
|
||||
tokenTicker: "TRASH",
|
||||
});
|
||||
});
|
||||
|
||||
it("returns 20*22 + '000' when credit amount is 22", () => {
|
||||
process.env.FAUCET_CREDIT_AMOUNT_TRASH = "22";
|
||||
expect(faucet.refillAmount("TRASH" as TokenTicker)).toEqual({
|
||||
quantity: "440000",
|
||||
fractionalDigits: 3,
|
||||
tokenTicker: "TRASH",
|
||||
});
|
||||
});
|
||||
|
||||
it("returns 30*10 + '000' when refill factor is 30", () => {
|
||||
process.env.FAUCET_REFILL_FACTOR = "30";
|
||||
expect(faucet.refillAmount("TRASH" as TokenTicker)).toEqual({
|
||||
quantity: "300000",
|
||||
fractionalDigits: 3,
|
||||
tokenTicker: "TRASH",
|
||||
});
|
||||
});
|
||||
|
||||
it("returns 30*22 + '000' when refill factor is 30 and credit amount is 22", () => {
|
||||
process.env.FAUCET_REFILL_FACTOR = "30";
|
||||
process.env.FAUCET_CREDIT_AMOUNT_TRASH = "22";
|
||||
expect(faucet.refillAmount("TRASH" as TokenTicker)).toEqual({
|
||||
quantity: "660000",
|
||||
fractionalDigits: 3,
|
||||
tokenTicker: "TRASH",
|
||||
});
|
||||
});
|
||||
});
|
||||
|
||||
describe("refillThreshold", () => {
|
||||
const faucet = new Faucet(dummyConfig);
|
||||
|
||||
beforeEach(() => {
|
||||
process.env.FAUCET_REFILL_THRESHOLD = "";
|
||||
process.env.FAUCET_CREDIT_AMOUNT_TRASH = "";
|
||||
});
|
||||
|
||||
it("returns 8*10 + '000' by default", () => {
|
||||
expect(faucet.refillThreshold("TRASH" as TokenTicker)).toEqual({
|
||||
quantity: "80000",
|
||||
fractionalDigits: 3,
|
||||
tokenTicker: "TRASH",
|
||||
});
|
||||
});
|
||||
|
||||
it("returns 8*22 + '000' when credit amount is 22", () => {
|
||||
process.env.FAUCET_CREDIT_AMOUNT_TRASH = "22";
|
||||
expect(faucet.refillThreshold("TRASH" as TokenTicker)).toEqual({
|
||||
quantity: "176000",
|
||||
fractionalDigits: 3,
|
||||
tokenTicker: "TRASH",
|
||||
});
|
||||
});
|
||||
|
||||
it("returns 5*10 + '000' when refill threshold is 5", () => {
|
||||
process.env.FAUCET_REFILL_THRESHOLD = "5";
|
||||
expect(faucet.refillThreshold("TRASH" as TokenTicker)).toEqual({
|
||||
quantity: "50000",
|
||||
fractionalDigits: 3,
|
||||
tokenTicker: "TRASH",
|
||||
});
|
||||
});
|
||||
|
||||
it("returns 5*22 + '000' when refill threshold is 5 and credit amount is 22", () => {
|
||||
process.env.FAUCET_REFILL_THRESHOLD = "5";
|
||||
process.env.FAUCET_CREDIT_AMOUNT_TRASH = "22";
|
||||
expect(faucet.refillThreshold("TRASH" as TokenTicker)).toEqual({
|
||||
quantity: "110000",
|
||||
fractionalDigits: 3,
|
||||
tokenTicker: "TRASH",
|
||||
});
|
||||
});
|
||||
});
|
||||
});
|
||||
124
packages/faucet/src/faucet.ts
Normal file
124
packages/faucet/src/faucet.ts
Normal file
@ -0,0 +1,124 @@
|
||||
import { TokenConfiguration } from "@cosmwasm/bcp";
|
||||
import { Account, Amount, BlockchainConnection, TokenTicker } from "@iov/bcp";
|
||||
import { Decimal, Uint53 } from "@iov/encoding";
|
||||
import { UserProfile } from "@iov/keycontrol";
|
||||
import { sleep } from "@iov/utils";
|
||||
|
||||
import { debugAccount, logAccountsState, logSendJob } from "./debugging";
|
||||
import {
|
||||
availableTokensFromHolder,
|
||||
identitiesOfFirstWallet,
|
||||
loadAccounts,
|
||||
loadTokenTickers,
|
||||
send,
|
||||
} from "./multichainhelpers";
|
||||
import { SendJob } from "./types";
|
||||
|
||||
/** Send `factor` times credit amount on refilling */
|
||||
const defaultRefillFactor = 20;
|
||||
|
||||
/** refill when balance gets below `factor` times credit amount */
|
||||
const defaultRefillThresholdFactor = 8;
|
||||
|
||||
export class Faucet {
|
||||
private readonly config: TokenConfiguration;
|
||||
|
||||
public constructor(config: TokenConfiguration) {
|
||||
this.config = config;
|
||||
}
|
||||
|
||||
/** The amount of tokens that will be sent to the user */
|
||||
public creditAmount(token: TokenTicker, factor: Uint53 = new Uint53(1)): Amount {
|
||||
const amountFromEnv = process.env[`FAUCET_CREDIT_AMOUNT_${token}`];
|
||||
const amount = amountFromEnv ? Uint53.fromString(amountFromEnv).toNumber() : 10;
|
||||
const value = new Uint53(amount * factor.toNumber());
|
||||
|
||||
const fractionalDigits = this.getFractionalDigits(token);
|
||||
return {
|
||||
quantity: value.toString() + "0".repeat(fractionalDigits),
|
||||
fractionalDigits: fractionalDigits,
|
||||
tokenTicker: token,
|
||||
};
|
||||
}
|
||||
|
||||
public refillAmount(token: TokenTicker): Amount {
|
||||
const factorFromEnv = Number.parseInt(process.env.FAUCET_REFILL_FACTOR || "0", 10) || undefined;
|
||||
const factor = new Uint53(factorFromEnv || defaultRefillFactor);
|
||||
return this.creditAmount(token, factor);
|
||||
}
|
||||
|
||||
public refillThreshold(token: TokenTicker): Amount {
|
||||
const factorFromEnv = Number.parseInt(process.env.FAUCET_REFILL_THRESHOLD || "0", 10) || undefined;
|
||||
const factor = new Uint53(factorFromEnv || defaultRefillThresholdFactor);
|
||||
return this.creditAmount(token, factor);
|
||||
}
|
||||
|
||||
public async refill(profile: UserProfile, connection: BlockchainConnection): Promise<void> {
|
||||
console.info(`Connected to network: ${connection.chainId()}`);
|
||||
console.info(`Tokens on network: ${(await loadTokenTickers(connection)).join(", ")}`);
|
||||
|
||||
const holderIdentity = identitiesOfFirstWallet(profile)[0];
|
||||
|
||||
const accounts = await loadAccounts(profile, connection);
|
||||
logAccountsState(accounts);
|
||||
const holderAccount = accounts[0];
|
||||
const distributorAccounts = accounts.slice(1);
|
||||
|
||||
const availableTokens = availableTokensFromHolder(holderAccount);
|
||||
console.info("Available tokens:", availableTokens);
|
||||
|
||||
const jobs: SendJob[] = [];
|
||||
|
||||
for (const token of availableTokens) {
|
||||
const refillDistibutors = distributorAccounts.filter(account => this.needsRefill(account, token));
|
||||
console.info(`Refilling ${token} of:`);
|
||||
console.info(
|
||||
refillDistibutors.length ? refillDistibutors.map(r => ` ${debugAccount(r)}`).join("\n") : " none",
|
||||
);
|
||||
for (const refillDistibutor of refillDistibutors) {
|
||||
jobs.push({
|
||||
sender: holderIdentity,
|
||||
recipient: refillDistibutor.address,
|
||||
tokenTicker: token,
|
||||
amount: this.refillAmount(token),
|
||||
});
|
||||
}
|
||||
}
|
||||
if (jobs.length > 0) {
|
||||
for (const job of jobs) {
|
||||
logSendJob(job);
|
||||
await send(profile, connection, job);
|
||||
await sleep(50);
|
||||
}
|
||||
|
||||
console.info("Done refilling accounts.");
|
||||
logAccountsState(await loadAccounts(profile, connection));
|
||||
} else {
|
||||
console.info("Nothing to be done. Anyways, thanks for checking.");
|
||||
}
|
||||
}
|
||||
|
||||
/** true iff the distributor account needs a refill */
|
||||
public needsRefill(account: Account, token: TokenTicker): boolean {
|
||||
const balanceAmount = account.balance.find(b => b.tokenTicker === token);
|
||||
|
||||
const balance = balanceAmount
|
||||
? Decimal.fromAtomics(balanceAmount.quantity, balanceAmount.fractionalDigits)
|
||||
: Decimal.fromAtomics("0", 0);
|
||||
|
||||
const thresholdAmount = this.refillThreshold(token);
|
||||
const threshold = Decimal.fromAtomics(thresholdAmount.quantity, thresholdAmount.fractionalDigits);
|
||||
|
||||
// TODO: perform < operation on Decimal type directly
|
||||
// https://github.com/iov-one/iov-core/issues/1375
|
||||
return balance.toFloatApproximation() < threshold.toFloatApproximation();
|
||||
}
|
||||
|
||||
private getFractionalDigits(ticker: TokenTicker): number {
|
||||
const match = [...this.config.bankTokens, ...(this.config.erc20Tokens || [])].find(
|
||||
token => token.ticker === ticker,
|
||||
);
|
||||
if (!match) throw new Error(`No token found for ticker symbol: ${ticker}`);
|
||||
return match.fractionalDigits;
|
||||
}
|
||||
}
|
||||
@ -9,15 +9,9 @@ import {
|
||||
} from "@iov/bcp";
|
||||
import { UserProfile } from "@iov/keycontrol";
|
||||
|
||||
import { needsRefill, refillAmount } from "./cashflow";
|
||||
import { codecImplementation } from "./codec";
|
||||
import { debugAccount, logAccountsState, logSendJob } from "./debugging";
|
||||
import { SendJob } from "./types";
|
||||
|
||||
async function sleep(ms: number): Promise<void> {
|
||||
return new Promise(resolve => setTimeout(resolve, ms));
|
||||
}
|
||||
|
||||
export function identitiesOfFirstWallet(profile: UserProfile): ReadonlyArray<Identity> {
|
||||
const wallet = profile.wallets.value[0];
|
||||
return profile.getIdentities(wallet.id);
|
||||
@ -88,48 +82,3 @@ export async function send(
|
||||
export function availableTokensFromHolder(holderAccount: Account): ReadonlyArray<TokenTicker> {
|
||||
return holderAccount.balance.map(coin => coin.tokenTicker);
|
||||
}
|
||||
|
||||
export async function refill(profile: UserProfile, connection: BlockchainConnection): Promise<void> {
|
||||
console.info(`Connected to network: ${connection.chainId()}`);
|
||||
console.info(`Tokens on network: ${(await loadTokenTickers(connection)).join(", ")}`);
|
||||
|
||||
const holderIdentity = identitiesOfFirstWallet(profile)[0];
|
||||
|
||||
const accounts = await loadAccounts(profile, connection);
|
||||
logAccountsState(accounts);
|
||||
const holderAccount = accounts[0];
|
||||
const distributorAccounts = accounts.slice(1);
|
||||
|
||||
const availableTokens = availableTokensFromHolder(holderAccount);
|
||||
console.info("Available tokens:", availableTokens);
|
||||
|
||||
const jobs: SendJob[] = [];
|
||||
|
||||
for (const token of availableTokens) {
|
||||
const refillDistibutors = distributorAccounts.filter(account => needsRefill(account, token));
|
||||
console.info(`Refilling ${token} of:`);
|
||||
console.info(
|
||||
refillDistibutors.length ? refillDistibutors.map(r => ` ${debugAccount(r)}`).join("\n") : " none",
|
||||
);
|
||||
for (const refillDistibutor of refillDistibutors) {
|
||||
jobs.push({
|
||||
sender: holderIdentity,
|
||||
recipient: refillDistibutor.address,
|
||||
tokenTicker: token,
|
||||
amount: refillAmount(token),
|
||||
});
|
||||
}
|
||||
}
|
||||
if (jobs.length > 0) {
|
||||
for (const job of jobs) {
|
||||
logSendJob(job);
|
||||
await send(profile, connection, job);
|
||||
await sleep(50);
|
||||
}
|
||||
|
||||
console.info("Done refilling accounts.");
|
||||
logAccountsState(await loadAccounts(profile, connection));
|
||||
} else {
|
||||
console.info("Nothing to be done. Anyways, thanks for checking.");
|
||||
}
|
||||
}
|
||||
|
||||
Loading…
Reference in New Issue
Block a user