mirror of
https://github.com/cosmos/cosmjs.git
synced 2025-03-11 14:09:15 +00:00
Move credit function infot Faucet
This commit is contained in:
parent
bf023748f1
commit
6075d2900c
@ -5,21 +5,13 @@ import bodyParser from "koa-bodyparser";
|
|||||||
|
|
||||||
import { isValidAddress } from "../../addresses";
|
import { isValidAddress } from "../../addresses";
|
||||||
import * as constants from "../../constants";
|
import * as constants from "../../constants";
|
||||||
import { logAccountsState, logSendJob } from "../../debugging";
|
import { logAccountsState } from "../../debugging";
|
||||||
import { Faucet } from "../../faucet";
|
import { Faucet } from "../../faucet";
|
||||||
import { availableTokensFromHolder } from "../../multichainhelpers";
|
import { availableTokensFromHolder } from "../../multichainhelpers";
|
||||||
import { setSecretAndCreateIdentities } from "../../profile";
|
import { setSecretAndCreateIdentities } from "../../profile";
|
||||||
import { SendJob } from "../../types";
|
|
||||||
import { HttpError } from "./httperror";
|
import { HttpError } from "./httperror";
|
||||||
import { RequestParser } from "./requestparser";
|
import { RequestParser } from "./requestparser";
|
||||||
|
|
||||||
let count = 0;
|
|
||||||
|
|
||||||
/** returns an integer >= 0 that increments and is unique in module scope */
|
|
||||||
function getCount(): number {
|
|
||||||
return count++;
|
|
||||||
}
|
|
||||||
|
|
||||||
export async function start(args: ReadonlyArray<string>): Promise<void> {
|
export async function start(args: ReadonlyArray<string>): Promise<void> {
|
||||||
if (args.length < 1) {
|
if (args.length < 1) {
|
||||||
throw Error(
|
throw Error(
|
||||||
@ -43,7 +35,7 @@ export async function start(args: ReadonlyArray<string>): Promise<void> {
|
|||||||
const profile = await setSecretAndCreateIdentities(constants.mnemonic, connection.chainId());
|
const profile = await setSecretAndCreateIdentities(constants.mnemonic, connection.chainId());
|
||||||
|
|
||||||
// Faucet
|
// Faucet
|
||||||
const faucet = new Faucet(constants.tokenConfig, connection, connector.codec, profile);
|
const faucet = new Faucet(constants.tokenConfig, connection, connector.codec, profile, true);
|
||||||
const chainTokens = await faucet.loadTokenTickers();
|
const chainTokens = await faucet.loadTokenTickers();
|
||||||
console.info("Chain tokens:", chainTokens);
|
console.info("Chain tokens:", chainTokens);
|
||||||
const accounts = await faucet.loadAccounts();
|
const accounts = await faucet.loadAccounts();
|
||||||
@ -110,16 +102,8 @@ export async function start(args: ReadonlyArray<string>): Promise<void> {
|
|||||||
throw new HttpError(422, `Token is not available. Available tokens are: ${tokens}`);
|
throw new HttpError(422, `Token is not available. Available tokens are: ${tokens}`);
|
||||||
}
|
}
|
||||||
|
|
||||||
const sender = faucet.distributors[getCount() % faucet.distributors.length];
|
|
||||||
|
|
||||||
try {
|
try {
|
||||||
const job: SendJob = {
|
await faucet.credit(address, ticker);
|
||||||
sender: sender,
|
|
||||||
recipient: address,
|
|
||||||
amount: faucet.tokenManager.creditAmount(ticker),
|
|
||||||
};
|
|
||||||
logSendJob(job);
|
|
||||||
await faucet.send(job);
|
|
||||||
} catch (e) {
|
} catch (e) {
|
||||||
console.error(e);
|
console.error(e);
|
||||||
throw new HttpError(500, "Sending tokens failed");
|
throw new HttpError(500, "Sending tokens failed");
|
||||||
|
@ -3,10 +3,11 @@ import { CosmosAddressBech32Prefix } from "@cosmwasm/sdk";
|
|||||||
import { Address, ChainId, Identity, TokenTicker } from "@iov/bcp";
|
import { Address, ChainId, Identity, TokenTicker } from "@iov/bcp";
|
||||||
import { Random } from "@iov/crypto";
|
import { Random } from "@iov/crypto";
|
||||||
import { Bech32 } from "@iov/encoding";
|
import { Bech32 } from "@iov/encoding";
|
||||||
import { HdPaths, Secp256k1HdWallet, UserProfile } from "@iov/keycontrol";
|
import { UserProfile } from "@iov/keycontrol";
|
||||||
import { assert } from "@iov/utils";
|
import { assert } from "@iov/utils";
|
||||||
|
|
||||||
import { Faucet } from "./faucet";
|
import { Faucet } from "./faucet";
|
||||||
|
import { createUserProfile } from "./profile";
|
||||||
|
|
||||||
function pendingWithoutCosmos(): void {
|
function pendingWithoutCosmos(): void {
|
||||||
if (!process.env.COSMOS_ENABLED) {
|
if (!process.env.COSMOS_ENABLED) {
|
||||||
@ -55,15 +56,15 @@ function makeRandomAddress(): Address {
|
|||||||
|
|
||||||
const faucetMnemonic =
|
const faucetMnemonic =
|
||||||
"economy stock theory fatal elder harbor betray wasp final emotion task crumble siren bottom lizard educate guess current outdoor pair theory focus wife stone";
|
"economy stock theory fatal elder harbor betray wasp final emotion task crumble siren bottom lizard educate guess current outdoor pair theory focus wife stone";
|
||||||
const faucetPath = HdPaths.cosmos(0);
|
|
||||||
|
|
||||||
async function makeProfile(): Promise<{ readonly profile: UserProfile; readonly holder: Identity }> {
|
async function makeProfile(
|
||||||
const profile = new UserProfile();
|
distributors = 0,
|
||||||
const wallet = profile.addWallet(Secp256k1HdWallet.fromMnemonic(faucetMnemonic));
|
): Promise<{ readonly profile: UserProfile; readonly holder: Identity; readonly distributors: Identity[] }> {
|
||||||
const holder = await profile.createIdentity(wallet.id, defaultChainId, faucetPath);
|
const [profile, identities] = await createUserProfile(faucetMnemonic, defaultChainId, distributors);
|
||||||
return {
|
return {
|
||||||
profile: profile,
|
profile: profile,
|
||||||
holder: holder,
|
holder: identities[0],
|
||||||
|
distributors: identities.slice(1),
|
||||||
};
|
};
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -108,6 +109,71 @@ describe("Faucet", () => {
|
|||||||
});
|
});
|
||||||
});
|
});
|
||||||
|
|
||||||
|
describe("refill", () => {
|
||||||
|
it("works", async () => {
|
||||||
|
pendingWithoutCosmos();
|
||||||
|
const connection = await CosmWasmConnection.establish(httpUrl, defaultPrefix, defaultConfig);
|
||||||
|
const { profile, distributors } = await makeProfile(1);
|
||||||
|
const faucet = new Faucet(defaultConfig, connection, codec, profile);
|
||||||
|
await faucet.refill();
|
||||||
|
const distributorBalance = (await connection.getAccount({ pubkey: distributors[0].pubkey }))?.balance;
|
||||||
|
assert(distributorBalance);
|
||||||
|
expect(distributorBalance).toEqual([
|
||||||
|
jasmine.objectContaining({
|
||||||
|
tokenTicker: "COSM",
|
||||||
|
fractionalDigits: 6,
|
||||||
|
}),
|
||||||
|
jasmine.objectContaining({
|
||||||
|
tokenTicker: "STAKE",
|
||||||
|
fractionalDigits: 6,
|
||||||
|
}),
|
||||||
|
]);
|
||||||
|
expect(Number.parseInt(distributorBalance[0].quantity, 10)).toBeGreaterThanOrEqual(80_000000);
|
||||||
|
expect(Number.parseInt(distributorBalance[1].quantity, 10)).toBeGreaterThanOrEqual(80_000000);
|
||||||
|
connection.disconnect();
|
||||||
|
});
|
||||||
|
});
|
||||||
|
|
||||||
|
describe("credit", () => {
|
||||||
|
it("works for fee token", async () => {
|
||||||
|
pendingWithoutCosmos();
|
||||||
|
const connection = await CosmWasmConnection.establish(httpUrl, defaultPrefix, defaultConfig);
|
||||||
|
const { profile } = await makeProfile(1);
|
||||||
|
const faucet = new Faucet(defaultConfig, connection, codec, profile);
|
||||||
|
const recipient = makeRandomAddress();
|
||||||
|
await faucet.credit(recipient, "COSM" as TokenTicker);
|
||||||
|
const account = await connection.getAccount({ address: recipient });
|
||||||
|
assert(account);
|
||||||
|
expect(account.balance).toEqual([
|
||||||
|
{
|
||||||
|
quantity: "10000000",
|
||||||
|
fractionalDigits: 6,
|
||||||
|
tokenTicker: "COSM" as TokenTicker,
|
||||||
|
},
|
||||||
|
]);
|
||||||
|
connection.disconnect();
|
||||||
|
});
|
||||||
|
|
||||||
|
it("works for stake token", async () => {
|
||||||
|
pendingWithoutCosmos();
|
||||||
|
const connection = await CosmWasmConnection.establish(httpUrl, defaultPrefix, defaultConfig);
|
||||||
|
const { profile } = await makeProfile(1);
|
||||||
|
const faucet = new Faucet(defaultConfig, connection, codec, profile);
|
||||||
|
const recipient = makeRandomAddress();
|
||||||
|
await faucet.credit(recipient, "STAKE" as TokenTicker);
|
||||||
|
const account = await connection.getAccount({ address: recipient });
|
||||||
|
assert(account);
|
||||||
|
expect(account.balance).toEqual([
|
||||||
|
{
|
||||||
|
quantity: "10000000",
|
||||||
|
fractionalDigits: 6,
|
||||||
|
tokenTicker: "STAKE" as TokenTicker,
|
||||||
|
},
|
||||||
|
]);
|
||||||
|
connection.disconnect();
|
||||||
|
});
|
||||||
|
});
|
||||||
|
|
||||||
describe("loadTokenTickers", () => {
|
describe("loadTokenTickers", () => {
|
||||||
it("works", async () => {
|
it("works", async () => {
|
||||||
pendingWithoutCosmos();
|
pendingWithoutCosmos();
|
||||||
|
@ -1,6 +1,7 @@
|
|||||||
import { TokenConfiguration } from "@cosmwasm/bcp";
|
import { TokenConfiguration } from "@cosmwasm/bcp";
|
||||||
import {
|
import {
|
||||||
Account,
|
Account,
|
||||||
|
Address,
|
||||||
BlockchainConnection,
|
BlockchainConnection,
|
||||||
Identity,
|
Identity,
|
||||||
isBlockInfoFailed,
|
isBlockInfoFailed,
|
||||||
@ -19,8 +20,6 @@ import { TokenManager } from "./tokenmanager";
|
|||||||
import { SendJob } from "./types";
|
import { SendJob } from "./types";
|
||||||
|
|
||||||
export class Faucet {
|
export class Faucet {
|
||||||
/** will be private soon */
|
|
||||||
public readonly tokenManager: TokenManager;
|
|
||||||
public get holder(): Identity {
|
public get holder(): Identity {
|
||||||
return identitiesOfFirstWallet(this.profile)[0];
|
return identitiesOfFirstWallet(this.profile)[0];
|
||||||
}
|
}
|
||||||
@ -28,20 +27,25 @@ export class Faucet {
|
|||||||
return identitiesOfFirstWallet(this.profile).slice(1);
|
return identitiesOfFirstWallet(this.profile).slice(1);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
private readonly tokenManager: TokenManager;
|
||||||
private readonly connection: BlockchainConnection;
|
private readonly connection: BlockchainConnection;
|
||||||
private readonly codec: TxCodec;
|
private readonly codec: TxCodec;
|
||||||
private readonly profile: UserProfile;
|
private readonly profile: UserProfile;
|
||||||
|
private readonly logging: boolean;
|
||||||
|
private creditCount = 0;
|
||||||
|
|
||||||
public constructor(
|
public constructor(
|
||||||
config: TokenConfiguration,
|
config: TokenConfiguration,
|
||||||
connection: BlockchainConnection,
|
connection: BlockchainConnection,
|
||||||
codec: TxCodec,
|
codec: TxCodec,
|
||||||
profile: UserProfile,
|
profile: UserProfile,
|
||||||
|
logging = false,
|
||||||
) {
|
) {
|
||||||
this.tokenManager = new TokenManager(config);
|
this.tokenManager = new TokenManager(config);
|
||||||
this.connection = connection;
|
this.connection = connection;
|
||||||
this.codec = codec;
|
this.codec = codec;
|
||||||
this.profile = profile;
|
this.profile = profile;
|
||||||
|
this.logging = logging;
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
@ -68,6 +72,19 @@ export class Faucet {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/** Use one of the distributor accounts to send tokend to user */
|
||||||
|
public async credit(recipient: Address, ticker: TokenTicker): Promise<void> {
|
||||||
|
if (this.distributors.length === 0) throw new Error("No distributor account available");
|
||||||
|
const sender = this.distributors[this.getCreditCount() % this.distributors.length];
|
||||||
|
const job: SendJob = {
|
||||||
|
sender: sender,
|
||||||
|
recipient: recipient,
|
||||||
|
amount: this.tokenManager.creditAmount(ticker),
|
||||||
|
};
|
||||||
|
if (this.logging) logSendJob(job);
|
||||||
|
await this.send(job);
|
||||||
|
}
|
||||||
|
|
||||||
public async loadTokenTickers(): Promise<ReadonlyArray<TokenTicker>> {
|
public async loadTokenTickers(): Promise<ReadonlyArray<TokenTicker>> {
|
||||||
return (await this.connection.getAllTokens()).map(token => token.tokenTicker);
|
return (await this.connection.getAllTokens()).map(token => token.tokenTicker);
|
||||||
}
|
}
|
||||||
@ -95,27 +112,30 @@ export class Faucet {
|
|||||||
}
|
}
|
||||||
|
|
||||||
public async refill(): Promise<void> {
|
public async refill(): Promise<void> {
|
||||||
console.info(`Connected to network: ${this.connection.chainId()}`);
|
if (this.logging) {
|
||||||
console.info(`Tokens on network: ${(await this.loadTokenTickers()).join(", ")}`);
|
console.info(`Connected to network: ${this.connection.chainId()}`);
|
||||||
|
console.info(`Tokens on network: ${(await this.loadTokenTickers()).join(", ")}`);
|
||||||
|
}
|
||||||
|
|
||||||
const accounts = await this.loadAccounts();
|
const accounts = await this.loadAccounts();
|
||||||
logAccountsState(accounts);
|
if (this.logging) logAccountsState(accounts);
|
||||||
const holderAccount = accounts[0];
|
const [holderAccount, ...distributorAccounts] = accounts;
|
||||||
const distributorAccounts = accounts.slice(1);
|
|
||||||
|
|
||||||
const availableTokens = availableTokensFromHolder(holderAccount);
|
const availableTokens = availableTokensFromHolder(holderAccount);
|
||||||
console.info("Available tokens:", availableTokens);
|
if (this.logging) console.info("Available tokens:", availableTokens);
|
||||||
|
|
||||||
const jobs: SendJob[] = [];
|
const jobs: SendJob[] = [];
|
||||||
|
|
||||||
for (const token of availableTokens) {
|
for (const token of availableTokens) {
|
||||||
const refillDistibutors = distributorAccounts.filter(account =>
|
const refillDistibutors = distributorAccounts.filter(account =>
|
||||||
this.tokenManager.needsRefill(account, token),
|
this.tokenManager.needsRefill(account, token),
|
||||||
);
|
);
|
||||||
console.info(`Refilling ${token} of:`);
|
|
||||||
console.info(
|
if (this.logging) {
|
||||||
refillDistibutors.length ? refillDistibutors.map(r => ` ${debugAccount(r)}`).join("\n") : " none",
|
console.info(`Refilling ${token} of:`);
|
||||||
);
|
console.info(
|
||||||
|
refillDistibutors.length ? refillDistibutors.map(r => ` ${debugAccount(r)}`).join("\n") : " none",
|
||||||
|
);
|
||||||
|
}
|
||||||
for (const refillDistibutor of refillDistibutors) {
|
for (const refillDistibutor of refillDistibutors) {
|
||||||
jobs.push({
|
jobs.push({
|
||||||
sender: this.holder,
|
sender: this.holder,
|
||||||
@ -131,10 +151,19 @@ export class Faucet {
|
|||||||
await sleep(50);
|
await sleep(50);
|
||||||
}
|
}
|
||||||
|
|
||||||
console.info("Done refilling accounts.");
|
if (this.logging) {
|
||||||
logAccountsState(await this.loadAccounts());
|
console.info("Done refilling accounts.");
|
||||||
|
logAccountsState(await this.loadAccounts());
|
||||||
|
}
|
||||||
} else {
|
} else {
|
||||||
console.info("Nothing to be done. Anyways, thanks for checking.");
|
if (this.logging) {
|
||||||
|
console.info("Nothing to be done. Anyways, thanks for checking.");
|
||||||
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/** returns an integer >= 0 that increments and is unique for this instance */
|
||||||
|
private getCreditCount(): number {
|
||||||
|
return this.creditCount++;
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
@ -1,28 +1,35 @@
|
|||||||
import { ChainId } from "@iov/bcp";
|
import { ChainId, Identity } from "@iov/bcp";
|
||||||
import { HdPaths, Secp256k1HdWallet, UserProfile } from "@iov/keycontrol";
|
import { HdPaths, Secp256k1HdWallet, UserProfile } from "@iov/keycontrol";
|
||||||
|
|
||||||
import { identityToAddress } from "./addresses";
|
import { identityToAddress } from "./addresses";
|
||||||
import * as constants from "./constants";
|
import * as constants from "./constants";
|
||||||
import { debugPath } from "./hdpaths";
|
import { debugPath } from "./hdpaths";
|
||||||
|
|
||||||
export async function setSecretAndCreateIdentities(mnemonic: string, chainId: ChainId): Promise<UserProfile> {
|
export async function createUserProfile(
|
||||||
|
mnemonic: string,
|
||||||
|
chainId: ChainId,
|
||||||
|
numberOfDistributors: number,
|
||||||
|
logging = false,
|
||||||
|
): Promise<[UserProfile, readonly Identity[]]> {
|
||||||
const profile = new UserProfile();
|
const profile = new UserProfile();
|
||||||
if (profile.wallets.value.length !== 0) {
|
|
||||||
throw new Error("Profile already contains wallets");
|
|
||||||
}
|
|
||||||
const wallet = profile.addWallet(Secp256k1HdWallet.fromMnemonic(mnemonic));
|
const wallet = profile.addWallet(Secp256k1HdWallet.fromMnemonic(mnemonic));
|
||||||
|
const identities = new Array<Identity>();
|
||||||
|
|
||||||
// first account is the token holder
|
// first account is the token holder
|
||||||
const numberOfIdentities = 1 + constants.concurrency;
|
const numberOfIdentities = 1 + numberOfDistributors;
|
||||||
for (let i = 0; i < numberOfIdentities; i++) {
|
for (let i = 0; i < numberOfIdentities; i++) {
|
||||||
// create
|
|
||||||
const path = HdPaths.cosmos(i);
|
const path = HdPaths.cosmos(i);
|
||||||
const identity = await profile.createIdentity(wallet.id, chainId, path);
|
const identity = await profile.createIdentity(wallet.id, chainId, path);
|
||||||
|
if (logging) {
|
||||||
// log
|
const role = i === 0 ? "token holder " : `distributor ${i}`;
|
||||||
const role = i === 0 ? "token holder " : `distributor ${i}`;
|
const address = identityToAddress(identity);
|
||||||
const address = identityToAddress(identity);
|
console.info(`Created ${role} (${debugPath(path)}): ${address}`);
|
||||||
console.info(`Created ${role} (${debugPath(path)}): ${address}`);
|
}
|
||||||
|
identities.push(identity);
|
||||||
}
|
}
|
||||||
return profile;
|
return [profile, identities];
|
||||||
|
}
|
||||||
|
|
||||||
|
export async function setSecretAndCreateIdentities(mnemonic: string, chainId: ChainId): Promise<UserProfile> {
|
||||||
|
return (await createUserProfile(mnemonic, chainId, constants.concurrency, true))[0];
|
||||||
}
|
}
|
||||||
|
Loading…
x
Reference in New Issue
Block a user