Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

[NayNay] File Restructure: Faucet #225

Merged
merged 9 commits into from
Sep 24, 2024
3 changes: 2 additions & 1 deletion src/balance/command.ts
Original file line number Diff line number Diff line change
@@ -1,4 +1,5 @@
import { Command } from "commander";
import Entropy from "@entropyxyz/sdk";
import { cliWrite, endpointOption, loadEntropy, passwordOption } from "src/common/utils-cli";
import { EntropyBalance } from "./main";

Expand All @@ -10,7 +11,7 @@ export function entropyBalanceCommand () {
.addOption(passwordOption())
.addOption(endpointOption())
.action(async (address, opts) => {
const entropy = await loadEntropy(address, opts.endpoint)
const entropy: Entropy = await loadEntropy(address, opts.endpoint)
const BalanceService = new EntropyBalance(entropy, opts.endpoint)
const balance = await BalanceService.getBalance(address)
cliWrite(`${balance.toLocaleString('en-US')} BITS`)
Expand Down
2 changes: 2 additions & 0 deletions src/common/entropy-base.ts
Original file line number Diff line number Diff line change
Expand Up @@ -4,9 +4,11 @@ import { EntropyLogger } from "./logger";
export abstract class EntropyBase {
protected logger: EntropyLogger
protected entropy: Entropy
protected endpoint: string

constructor ({ entropy, endpoint, flowContext }: { entropy: Entropy, endpoint: string, flowContext: string }) {
this.logger = new EntropyLogger(flowContext, endpoint)
this.entropy = entropy
this.endpoint = endpoint
}
}
Original file line number Diff line number Diff line change
Expand Up @@ -2,8 +2,8 @@ import Entropy from "@entropyxyz/sdk";
import type { Signer, SignerResult } from "@polkadot/api/types";
import { Registry, SignerPayloadJSON } from "@polkadot/types/types";
import { u8aToHex } from "@polkadot/util";
import { stripHexPrefix } from "../../common/utils";
import { blake2AsHex, decodeAddress, encodeAddress, signatureVerify } from "@polkadot/util-crypto";
import { stripHexPrefix } from "../../common/utils";

let id = 0
export default class FaucetSigner implements Signer {
Expand Down
44 changes: 44 additions & 0 deletions src/faucet/interaction.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,44 @@
import Entropy from "@entropyxyz/sdk"
import { EntropyLogger } from '../common/logger'
import { TESTNET_PROGRAM_HASH } from "./utils"
import { EntropyFaucet } from "./main"
import { print } from "src/common/utils"

let chosenVerifyingKeys = []
const amount = "10000000000"
// context for logging file
const FLOW_CONTEXT = 'ENTROPY_FAUCET_INTERACTION'
export async function entropyFaucet (entropy: Entropy, options, logger: EntropyLogger) {
const { endpoint } = options
if (!entropy.registrationManager.signer.pair) {
throw new Error("Keys are undefined")
}
const faucetService = new EntropyFaucet(entropy, endpoint)
const verifyingKeys = await faucetService.getAllFaucetVerifyingKeys()
// @ts-expect-error
return sendMoneyFromRandomFaucet(entropy, options.endpoint, verifyingKeys, logger)
}

// Method that takes in the initial list of verifying keys (to avoid multiple calls to the rpc) and recursively retries each faucet until
// a successful transfer is made
async function sendMoneyFromRandomFaucet (entropy: Entropy, endpoint: string, verifyingKeys: string[], logger: EntropyLogger) {
const faucetService = new EntropyFaucet(entropy, endpoint)
const selectedAccountAddress = entropy.keyring.accounts.registration.address
const { chosenVerifyingKey, faucetAddress } = faucetService.getRandomFaucet(chosenVerifyingKeys, verifyingKeys)
try {
await faucetService.sendMoney({ amount, addressToSendTo: selectedAccountAddress, faucetAddress, chosenVerifyingKey, faucetProgramPointer: TESTNET_PROGRAM_HASH })
// reset chosen keys after successful transfer
chosenVerifyingKeys = []
print(`Account: ${selectedAccountAddress} has been successfully funded with ${parseInt(amount).toLocaleString('en-US')} BITS`)
} catch (error) {
logger.error('Error issuing funds through faucet', error, FLOW_CONTEXT)
chosenVerifyingKeys.push(chosenVerifyingKey)
if (error.message.includes('FaucetError') || chosenVerifyingKeys.length === verifyingKeys.length) {
console.error('ERR::', error.message)
return
} else {
// Check for non faucet errors (FaucetError) and retry faucet
await sendMoneyFromRandomFaucet(entropy, endpoint, verifyingKeys, logger)
}
}
}
101 changes: 101 additions & 0 deletions src/faucet/main.ts
frankiebee marked this conversation as resolved.
Show resolved Hide resolved
Original file line number Diff line number Diff line change
@@ -0,0 +1,101 @@
import Entropy from "@entropyxyz/sdk";
import { EntropyBase } from "../common/entropy-base";
import { blake2AsHex, encodeAddress } from "@polkadot/util-crypto";
import { FAUCET_PROGRAM_MOD_KEY, TESTNET_PROGRAM_HASH } from "./utils";
import { EntropyBalance } from "src/balance/main";
import { EntropyProgram } from "src/program/main";
import FaucetSigner from "./helpers/signer";
import { SendMoneyParams } from "./types";

const FLOW_CONTEXT = 'ENTROPY-FAUCET'

export class EntropyFaucet extends EntropyBase {
constructor (entropy: Entropy, endpoint: string) {
super({ entropy, endpoint, flowContext: FLOW_CONTEXT })
}

// Method used to sign and send the transfer request (transfer request = call argument) using the custom signer
// created to overwrite how we sign the payload that is sent up chain
async faucetSignAndSend (call: any, amount: number, senderAddress: string, chosenVerifyingKey: any): Promise<any> {
const api = this.entropy.substrate
const faucetSigner = new FaucetSigner(api.registry, this.entropy, amount, chosenVerifyingKey)

const sig = await call.signAsync(senderAddress, {
signer: faucetSigner,
});
return new Promise((resolve, reject) => {
sig.send(({ status, dispatchError }: any) => {
// status would still be set, but in the case of error we can shortcut
// to just check it (so an error would indicate InBlock or Finalized)
if (dispatchError) {
let msg: string
if (dispatchError.isModule) {
// for module errors, we have the section indexed, lookup
const decoded = api.registry.findMetaError(dispatchError.asModule);
// @ts-ignore
const { documentation, method, section } = decoded;

msg = `${section}.${method}: ${documentation.join(' ')}`
} else {
// Other, CannotLookup, BadOrigin, no extra info
msg = dispatchError.toString()
}
return reject(Error(msg))
}
if (status.isFinalized) resolve(status)
})
})
}

async getAllFaucetVerifyingKeys (programModKey = FAUCET_PROGRAM_MOD_KEY) {
const modifiableKeys = await this.entropy.substrate.query.registry.modifiableKeys(programModKey)
return modifiableKeys.toJSON()
}

// To handle overloading the individual faucet, multiple faucet accounts have been generated, and here is
// where we choose one of those faucet's at random
getRandomFaucet (previousVerifyingKeys: string[] = [], allVerifyingKeys: string[] = []) {
if (allVerifyingKeys.length === previousVerifyingKeys.length) {
throw new Error('FaucetError: There are no more faucets to choose from')
}
let chosenVerifyingKey = allVerifyingKeys[Math.floor(Math.random() * allVerifyingKeys.length)]
if (previousVerifyingKeys.length && previousVerifyingKeys.includes(chosenVerifyingKey)) {
const filteredVerifyingKeys = allVerifyingKeys.filter((key: string) => !previousVerifyingKeys.includes(key))
chosenVerifyingKey = filteredVerifyingKeys[Math.floor(Math.random() * filteredVerifyingKeys.length)]
}
const hashedKey = blake2AsHex(chosenVerifyingKey)
const faucetAddress = encodeAddress(hashedKey, 42).toString()

return { chosenVerifyingKey, faucetAddress }
}

async sendMoney (
{
amount,
addressToSendTo,
faucetAddress,
chosenVerifyingKey,
faucetProgramPointer = TESTNET_PROGRAM_HASH
}: SendMoneyParams
): Promise<any> {
const balanceService = new EntropyBalance(this.entropy, this.endpoint)
const programService = new EntropyProgram(this.entropy, this.endpoint)
// check balance of faucet address
const balance = await balanceService.getBalance(faucetAddress)
if (balance <= 0) throw new Error('FundsError: Faucet Account does not have funds')
// check verifying key for only one program matching the program hash
const programs = await programService.list({ verifyingKey: chosenVerifyingKey })
if (programs.length) {
if (programs.length > 1) throw new Error('ProgramsError: Faucet Account has too many programs attached, expected less')
if (programs.length === 1 && programs[0].program_pointer !== faucetProgramPointer) {
throw new Error('ProgramsError: Faucet Account does not possess Faucet program')
}
} else {
throw new Error('ProgramsError: Faucet Account has no programs attached')
}

const transfer = this.entropy.substrate.tx.balances.transferAllowDeath(addressToSendTo, BigInt(amount));
const transferStatus = await this.faucetSignAndSend(transfer, parseInt(amount), faucetAddress, chosenVerifyingKey)
if (transferStatus.isFinalized) return transferStatus
}
}
7 changes: 7 additions & 0 deletions src/faucet/types.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,7 @@
export interface SendMoneyParams {
amount: string
addressToSendTo: string
faucetAddress: string
chosenVerifyingKey: string
faucetProgramPointer: string
}
File renamed without changes.
98 changes: 0 additions & 98 deletions src/flows/entropyFaucet/faucet.ts

This file was deleted.

45 changes: 0 additions & 45 deletions src/flows/entropyFaucet/index.ts

This file was deleted.

1 change: 0 additions & 1 deletion src/flows/index.ts

This file was deleted.

19 changes: 13 additions & 6 deletions src/tui.ts
Original file line number Diff line number Diff line change
@@ -1,7 +1,6 @@
import inquirer from 'inquirer'
import Entropy from '@entropyxyz/sdk'
import * as config from './config'
import * as flows from './flows'
import { EntropyTuiOptions } from './types'
import { logo } from './common/ascii'
import { print } from './common/utils'
Expand All @@ -12,6 +11,7 @@ import { entropyAccount, entropyRegister } from './account/interaction'
import { entropySign } from './sign/interaction'
import { entropyBalance } from './balance/interaction'
import { entropyTransfer } from './transfer/interaction'
import { entropyFaucet } from './faucet/interaction'
import { entropyProgram, entropyProgramDev } from './program/interaction'

async function setupConfig () {
Expand Down Expand Up @@ -46,14 +46,13 @@ export default function tui (entropy: Entropy, options: EntropyTuiOptions) {
// TODO: design programs in TUI (merge deploy+user programs)
'Deploy Program': () => {},
'User Programs': () => {},
'Entropy Faucet': flows.entropyFaucet,
}

// const devChoices = {
// // 'Entropy Faucet': flows.entropyFaucet,
// }
const devChoices = {
'Entropy Faucet': () => {},
}

// if (options.dev) Object.assign(choices, devChoices)
if (options.dev) Object.assign(choices, devChoices)

// assign exit so its last
Object.assign(choices, { 'Exit': async () => {} })
Expand Down Expand Up @@ -116,6 +115,14 @@ async function main (entropy: Entropy, choices, options, logger: EntropyLogger)
.catch(err => console.error('There was an issue with signing', err))
break
}
case 'Entropy Faucet': {
try {
await entropyFaucet(entropy, options, logger)
} catch (error) {
console.error('There was an issue with running the faucet', error);
}
break
}
case 'User Programs': {
await entropyProgram(entropy, options.endpoint)
.catch(err => console.error('There was an error with programs', err))
Expand Down
Loading
Loading