Skip to content

Commit

Permalink
feat: Add faucet for Swanky node (#60)
Browse files Browse the repository at this point in the history
Co-authored-by: Igor Papandinas <igor.papandinas@posteo.net>
  • Loading branch information
prxgr4mm3r and ipapandinas authored Feb 12, 2024
1 parent cd93607 commit 0914aed
Show file tree
Hide file tree
Showing 11 changed files with 199 additions and 29 deletions.
63 changes: 63 additions & 0 deletions src/commands/account/balance.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,63 @@
import { Args } from "@oclif/core";
import { ApiPromise } from "@polkadot/api";
import type { AccountInfo, Balance as BalanceType } from "@polkadot/types/interfaces";
import { ChainApi, resolveNetworkUrl } from "../../lib/index.js";
import { SwankyCommand } from "../../lib/swankyCommand.js";
import { InputError } from "../../lib/errors.js";
import { formatBalance } from "@polkadot/util";

export class Balance extends SwankyCommand<typeof Balance> {
static description = "Balance of an account";

static args = {
alias: Args.string({
name: "alias",
description: "Alias of account to be used",
}),
};
async run(): Promise<void> {
const { args } = await this.parse(Balance);

if (!args.alias) {
throw new InputError(
"Missing argument! Please provide an alias account to get the balance from. Example usage: `swanky account balance <YourAliasAccount>`"
);
}

const accountData = this.findAccountByAlias(args.alias);
const networkUrl = resolveNetworkUrl(this.swankyConfig, "");

const api = (await this.spinner.runCommand(async () => {
const api = await ChainApi.create(networkUrl);
await api.start();
return api.apiInst;
}, "Connecting to node")) as ApiPromise;

const decimals = api.registry.chainDecimals[0];
formatBalance.setDefaults({ unit: "UNIT", decimals });

const { nonce, data: balance } = await api.query.system.account<AccountInfo>(
accountData.address
);
const { free, reserved, miscFrozen, feeFrozen } = balance;

let frozen: BalanceType;
if (feeFrozen.gt(miscFrozen)) {
frozen = feeFrozen;
} else {
frozen = miscFrozen;
}

const transferrableBalance = free.sub(frozen);
const totalBalance = free.add(reserved);

console.log("Transferrable Balance:", formatBalance(transferrableBalance));
if (!transferrableBalance.eq(totalBalance)) {
console.log("Total Balance:", formatBalance(totalBalance));
console.log("Raw Balances:", balance.toHuman());
}
console.log("Account Nonce:", nonce.toHuman());

await api.disconnect();
}
}
9 changes: 6 additions & 3 deletions src/commands/account/create.ts
Original file line number Diff line number Diff line change
Expand Up @@ -3,8 +3,9 @@ import chalk from "chalk";
import { ChainAccount, encrypt } from "../../lib/index.js";
import { AccountData } from "../../types/index.js";
import inquirer from "inquirer";
import { SwankyCommand } from "../../lib/swankyCommand.js";
export class CreateAccount extends SwankyCommand<typeof CreateAccount> {
import { SwankyAccountCommand } from "./swankyAccountCommands.js";

export class CreateAccount extends SwankyAccountCommand<typeof CreateAccount> {
static description = "Create a new dev account in config";

static flags = {
Expand Down Expand Up @@ -35,7 +36,7 @@ export class CreateAccount extends SwankyCommand<typeof CreateAccount> {
);
}

let tmpMnemonic = "";
let tmpMnemonic: string;
if (flags.generate) {
tmpMnemonic = ChainAccount.generate();
console.log(
Expand Down Expand Up @@ -84,5 +85,7 @@ export class CreateAccount extends SwankyCommand<typeof CreateAccount> {
accountData.alias
)} stored to config`
);

await this.performFaucetTransfer(accountData, true);
}
}
23 changes: 23 additions & 0 deletions src/commands/account/faucet.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,23 @@
import { Args } from "@oclif/core";
import { SwankyAccountCommand } from "./swankyAccountCommands.js";

export class Faucet extends SwankyAccountCommand<typeof Faucet> {
static description = "Transfer some tokens from faucet to an account";

static aliases = [`account:faucet`];

static args = {
alias: Args.string({
name: "alias",
required: true,
description: "Alias of account to be used",
}),
};

async run(): Promise<void> {
const { args } = await this.parse(Faucet);

const accountData = this.findAccountByAlias(args.alias);
await this.performFaucetTransfer(accountData);
}
}
45 changes: 45 additions & 0 deletions src/commands/account/swankyAccountCommands.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,45 @@
import { Command } from "@oclif/core";
import chalk from "chalk";
import { AccountData, ChainApi, resolveNetworkUrl } from "../../index.js";
import { LOCAL_FAUCET_AMOUNT } from "../../lib/consts.js";
import { SwankyCommand } from "../../lib/swankyCommand.js";
import { ApiError } from "../../lib/errors.js";

export abstract class SwankyAccountCommand<T extends typeof Command> extends SwankyCommand<T> {
async performFaucetTransfer(accountData: AccountData, canBeSkipped = false) {
let api: ChainApi | null = null;
try {
api = (await this.spinner.runCommand(async () => {
const networkUrl = resolveNetworkUrl(this.swankyConfig, "");
const api = await ChainApi.create(networkUrl);
await api.start();
return api;
}, "Connecting to node")) as ChainApi;

if (api)
await this.spinner.runCommand(
async () => {
if (api) await api.faucet(accountData);
},
`Transferring ${LOCAL_FAUCET_AMOUNT} units from faucet account to ${accountData.alias}`,
`Transferred ${LOCAL_FAUCET_AMOUNT} units from faucet account to ${accountData.alias}`,
`Failed to transfer ${LOCAL_FAUCET_AMOUNT} units from faucet account to ${accountData.alias}`,
true
);
} catch (cause) {
if (cause instanceof Error) {
if (cause.message.includes('ECONNREFUSED') && canBeSkipped) {
this.warn(`Unable to connect to the node. Skipping faucet transfer for ${chalk.yellowBright(accountData.alias)}.`);
} else {
throw new ApiError("Error transferring tokens from faucet account", { cause });
}
} else {
throw new ApiError("An unknown error occurred during faucet transfer", { cause: new Error(String(cause)) });
}
} finally {
if (api) {
await api.disconnect();
}
}
}
}
10 changes: 2 additions & 8 deletions src/commands/contract/deploy.ts
Original file line number Diff line number Diff line change
Expand Up @@ -3,7 +3,7 @@ import path from "node:path";
import { writeJSON } from "fs-extra/esm";
import { cryptoWaitReady } from "@polkadot/util-crypto/crypto";
import { resolveNetworkUrl, ChainApi, ChainAccount, decrypt, AbiType } from "../../lib/index.js";
import { AccountData, Encrypted } from "../../types/index.js";
import { Encrypted } from "../../types/index.js";
import inquirer from "inquirer";
import chalk from "chalk";
import { Contract } from "../../lib/contract.js";
Expand Down Expand Up @@ -70,13 +70,7 @@ export class DeployContract extends SwankyCommand<typeof DeployContract> {
);
}

const accountData = this.swankyConfig.accounts.find(
(account: AccountData) => account.alias === flags.account
);
if (!accountData) {
throw new ConfigError("Provided account alias not found in swanky.config.json");
}

const accountData = this.findAccountByAlias(flags.account);
const mnemonic = accountData.isDev
? (accountData.mnemonic as string)
: decrypt(
Expand Down
9 changes: 5 additions & 4 deletions src/commands/init/index.ts
Original file line number Diff line number Diff line change
Expand Up @@ -18,6 +18,7 @@ import {
getTemplates, swankyNodeVersions,
} from "../../lib/index.js";
import {
ALICE_URI, BOB_URI,
DEFAULT_ASTAR_NETWORK_URL,
DEFAULT_NETWORK_URL, DEFAULT_NODE_INFO,
DEFAULT_SHIBUYA_NETWORK_URL,
Expand Down Expand Up @@ -191,15 +192,15 @@ export class Init extends SwankyCommand<typeof Init> {
this.configBuilder.accounts = [
{
alias: "alice",
mnemonic: "//Alice",
mnemonic: ALICE_URI,
isDev: true,
address: new ChainAccount("//Alice").pair.address,
address: new ChainAccount(ALICE_URI).pair.address,
},
{
alias: "bob",
mnemonic: "//Bob",
mnemonic: BOB_URI,
isDev: true,
address: new ChainAccount("//Bob").pair.address,
address: new ChainAccount(BOB_URI).pair.address,
},
];

Expand Down
3 changes: 2 additions & 1 deletion src/lib/account.ts
Original file line number Diff line number Diff line change
Expand Up @@ -2,6 +2,7 @@ import { mnemonicGenerate } from "@polkadot/util-crypto";
import { Keyring } from "@polkadot/keyring";
import { KeyringPair } from "@polkadot/keyring/types";
import { ChainProperty, KeypairType } from "../types/index.js";
import { KEYPAIR_TYPE } from "./consts.js";

interface IChainAccount {
pair: KeyringPair;
Expand All @@ -17,7 +18,7 @@ export class ChainAccount implements IChainAccount {
return mnemonicGenerate();
}

constructor(mnemonic: string, type: KeypairType = "sr25519") {
constructor(mnemonic: string, type: KeypairType = KEYPAIR_TYPE) {
this._keyringType = type;
this._keyring = new Keyring({ type: type });
this._mnemonic = mnemonic;
Expand Down
7 changes: 6 additions & 1 deletion src/lib/consts.ts
Original file line number Diff line number Diff line change
@@ -1,5 +1,7 @@
import { swankyNodeVersions } from "./nodeInfo.js";

export const DEFAULT_NODE_INFO = swankyNodeVersions.get("1.6.0")!;

export const DEFAULT_NETWORK_URL = "ws://127.0.0.1:9944";
export const DEFAULT_ASTAR_NETWORK_URL = "wss://rpc.astar.network";
export const DEFAULT_SHIDEN_NETWORK_URL = "wss://rpc.shiden.astar.network";
Expand All @@ -8,4 +10,7 @@ export const DEFAULT_SHIBUYA_NETWORK_URL = "wss://shibuya.public.blastapi.io";
export const ARTIFACTS_PATH = "artifacts";
export const TYPED_CONTRACTS_PATH = "typedContracts";

export const DEFAULT_NODE_INFO = swankyNodeVersions.get("1.6.0")!;
export const LOCAL_FAUCET_AMOUNT = 100;
export const KEYPAIR_TYPE = "sr25519";
export const ALICE_URI = "//Alice";
export const BOB_URI = "//Bob";
10 changes: 2 additions & 8 deletions src/lib/contractCall.ts
Original file line number Diff line number Diff line change
@@ -1,5 +1,5 @@
import { AbiType, ChainAccount, ChainApi, decrypt, resolveNetworkUrl } from "./index.js";
import { AccountData, ContractData, DeploymentData, Encrypted } from "../types/index.js";
import { ContractData, DeploymentData, Encrypted } from "../types/index.js";
import { Args, Command, Flags, Interfaces } from "@oclif/core";
import inquirer from "inquirer";
import chalk from "chalk";
Expand Down Expand Up @@ -77,13 +77,7 @@ export abstract class ContractCall<T extends typeof Command> extends SwankyComma

this.deploymentInfo = deploymentData;

const accountData = this.swankyConfig.accounts.find(
(account: AccountData) => account.alias === flags.account || "alice"
);
if (!accountData) {
throw new ConfigError("Provided account alias not found in swanky.config.json");
}

const accountData = this.findAccountByAlias(flags.account || "alice");
const networkUrl = resolveNetworkUrl(this.swankyConfig, flags.network ?? "");
const api = await ChainApi.create(networkUrl);
this.api = api;
Expand Down
34 changes: 31 additions & 3 deletions src/lib/substrate-api.ts
Original file line number Diff line number Diff line change
@@ -1,17 +1,19 @@
import { ApiPromise } from "@polkadot/api/promise";
import { WsProvider } from "@polkadot/api";
import { Keyring, WsProvider } from "@polkadot/api";
import { SignerOptions } from "@polkadot/api/types";
import { Codec, ITuple } from "@polkadot/types-codec/types";
import { ISubmittableResult } from "@polkadot/types/types";
import { TypeRegistry } from "@polkadot/types";
import { DispatchError, BlockHash } from "@polkadot/types/interfaces";
import { ChainAccount } from "./account.js";
import BN from "bn.js";
import { ChainProperty, ExtrinsicPayload } from "../types/index.js";
import { ChainProperty, ExtrinsicPayload, AccountData } from "../types/index.js";

import { KeyringPair } from "@polkadot/keyring/types";
import { Abi, CodePromise } from "@polkadot/api-contract";
import { ApiError, UnknownError } from "./errors.js";
import { ALICE_URI, KEYPAIR_TYPE, LOCAL_FAUCET_AMOUNT } from "./consts.js";
import { BN_TEN } from "@polkadot/util";

export type AbiType = Abi;
// const AUTO_CONNECT_MS = 10_000; // [ms]
Expand Down Expand Up @@ -101,6 +103,10 @@ export class ChainApi {
return this._registry;
}

public async disconnect(): Promise<void> {
await this._provider.disconnect();
}

public async start(): Promise<void> {
const chainProperties = await this._api.rpc.system.properties();

Expand Down Expand Up @@ -210,7 +216,6 @@ export class ChainApi {
if (handler) handler(result);
});
}

public async deploy(
abi: Abi,
wasm: Buffer,
Expand Down Expand Up @@ -247,4 +252,27 @@ export class ChainApi {
});
});
}

public async faucet(accountData: AccountData): Promise<void> {
const keyring = new Keyring({ type: KEYPAIR_TYPE });
const alicePair = keyring.addFromUri(ALICE_URI);

const chainDecimals = this._api.registry.chainDecimals[0];
const amount = new BN(LOCAL_FAUCET_AMOUNT).mul(BN_TEN.pow(new BN(chainDecimals)));

const tx = this._api.tx.balances.transfer(accountData.address, amount);

return new Promise((resolve, reject) => {
this.signAndSend(alicePair, tx, {}, ({ status, events }) => {
if (status.isInBlock || status.isFinalized) {
const transferEvent = events.find(({ event }) => event?.method === "Transfer");
if (!transferEvent) {
reject();
return;
}
resolve();
}
}).catch((error) => reject(error));
});
}
}
15 changes: 14 additions & 1 deletion src/lib/swankyCommand.ts
Original file line number Diff line number Diff line change
@@ -1,6 +1,7 @@
import { Command, Flags, Interfaces } from "@oclif/core";
import chalk from "chalk";
import { getSwankyConfig, Spinner } from "./index.js";
import { SwankyConfig } from "../types/index.js";
import { AccountData, SwankyConfig } from "../types/index.js";
import { writeJSON } from "fs-extra/esm";
import { BaseError, ConfigError, UnknownError } from "./errors.js";
import { swankyLogger } from "./logger.js";
Expand Down Expand Up @@ -51,6 +52,18 @@ export abstract class SwankyCommand<T extends typeof Command> extends Command {
Full command: ${JSON.stringify(process.argv)}`);
}

protected findAccountByAlias(alias: string): AccountData {
const accountData = this.swankyConfig.accounts.find(
(account: AccountData) => account.alias === alias
);

if (!accountData) {
throw new ConfigError(`Provided account alias ${chalk.yellowBright(alias)} not found in swanky.config.json`);
}

return accountData;
}

protected async storeConfig() {
await writeJSON("swanky.config.json", this.swankyConfig, { spaces: 2 });
}
Expand Down

0 comments on commit 0914aed

Please sign in to comment.