diff --git a/src/common/entropy-base.ts b/src/common/entropy-base.ts index 1e14bf3..f981f97 100644 --- a/src/common/entropy-base.ts +++ b/src/common/entropy-base.ts @@ -4,9 +4,11 @@ import { EntropyLogger } from "./logger"; export abstract class EntropyBase { protected logger: EntropyLogger protected entropy: Entropy + protected endpoint: string constructor (entropy: Entropy, endpoint: string, flowContext: string) { this.logger = new EntropyLogger(flowContext, endpoint) this.entropy = entropy + this.endpoint = endpoint } } \ No newline at end of file diff --git a/src/faucet/command.ts b/src/faucet/command.ts new file mode 100644 index 0000000..e69de29 diff --git a/src/flows/entropyFaucet/signer.ts b/src/faucet/helpers/signer.ts similarity index 100% rename from src/flows/entropyFaucet/signer.ts rename to src/faucet/helpers/signer.ts index bb49229..d3a9140 100644 --- a/src/flows/entropyFaucet/signer.ts +++ b/src/faucet/helpers/signer.ts @@ -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 { diff --git a/src/flows/entropyFaucet/index.ts b/src/faucet/interaction.ts similarity index 82% rename from src/flows/entropyFaucet/index.ts rename to src/faucet/interaction.ts index e76b7f1..ed16e32 100644 --- a/src/flows/entropyFaucet/index.ts +++ b/src/faucet/interaction.ts @@ -1,9 +1,9 @@ import Entropy from "@entropyxyz/sdk" -import { getSelectedAccount, print } from "../../common/utils" -import { initializeEntropy } from "../../common/initializeEntropy" -import { EntropyLogger } from '../../common/logger' -import { getRandomFaucet, sendMoney } from "./faucet" -import { TESTNET_PROGRAM_HASH } from "./constants" +import { getSelectedAccount, print } from "../common/utils" +import { initializeEntropy } from "../common/initializeEntropy" +import { EntropyLogger } from '../common/logger' +import { TESTNET_PROGRAM_HASH } from "./utils" +import { EntropyFaucet } from "./main" let chosenVerifyingKeys = [] export async function entropyFaucet ({ accounts, selectedAccount: selectedAccountAddress }, options, logger: EntropyLogger) { @@ -20,12 +20,12 @@ export async function entropyFaucet ({ accounts, selectedAccount: selectedAccoun try { // @ts-ignore (see TODO on aliceAccount) entropy = await initializeEntropy({ keyMaterial: selectedAccount.data, endpoint }) - + const FaucetService = new EntropyFaucet(entropy, options.endpoint) if (!entropy.registrationManager.signer.pair) { throw new Error("Keys are undefined") } - ({ chosenVerifyingKey, faucetAddress, verifyingKeys } = await getRandomFaucet(entropy, chosenVerifyingKeys)) + ({ chosenVerifyingKey, faucetAddress, verifyingKeys } = await FaucetService.getRandomFaucet(chosenVerifyingKeys)) await sendMoney(entropy, options.endpoint, { amount, addressToSendTo: selectedAccountAddress, faucetAddress, chosenVerifyingKey, faucetProgramPointer: TESTNET_PROGRAM_HASH }) // reset chosen keys after successful transfer diff --git a/src/faucet/main.ts b/src/faucet/main.ts new file mode 100644 index 0000000..386b848 --- /dev/null +++ b/src/faucet/main.ts @@ -0,0 +1,100 @@ +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 { viewPrograms } from "src/flows/programs/view"; +import FaucetSigner from "./helpers/signer"; + +const FLOW_CONTEXT = 'ENTROPY-FAUCET' + +export class EntropyFaucet extends EntropyBase { + constructor(entropy: Entropy, endpoint: string) { + super(entropy, endpoint, FLOW_CONTEXT) + } + + async faucetSignAndSend (call: any, amount: number, senderAddress: string, chosenVerifyingKey: any): Promise { + 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 getRandomFaucet (previousVerifyingKeys: string[] = [], programModKey = FAUCET_PROGRAM_MOD_KEY) { + const modifiableKeys = await this.entropy.substrate.query.registry.modifiableKeys(programModKey) + const verifyingKeys = JSON.parse(JSON.stringify(modifiableKeys.toJSON())) + + // Choosing one of the 5 verifiying keys at random to be used as the faucet sender + if (verifyingKeys.length === previousVerifyingKeys.length) { + throw new Error('FaucetError: There are no more faucets to choose from') + } + let chosenVerifyingKey = verifyingKeys[Math.floor(Math.random() * verifyingKeys.length)] + if (previousVerifyingKeys.length && previousVerifyingKeys.includes(chosenVerifyingKey)) { + const filteredVerifyingKeys = verifyingKeys.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, verifyingKeys } + } + + async sendMoney ( + { + amount, + addressToSendTo, + faucetAddress, + chosenVerifyingKey, + faucetProgramPointer = TESTNET_PROGRAM_HASH + }: { + amount: string, + addressToSendTo: string, + faucetAddress: string, + chosenVerifyingKey: string, + faucetProgramPointer: string + } + ): Promise { + const BalanceService = new EntropyBalance(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 viewPrograms(this.entropy, { 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 + } +} \ No newline at end of file diff --git a/src/faucet/types.ts b/src/faucet/types.ts new file mode 100644 index 0000000..e69de29 diff --git a/src/flows/entropyFaucet/constants.ts b/src/faucet/utils.ts similarity index 100% rename from src/flows/entropyFaucet/constants.ts rename to src/faucet/utils.ts diff --git a/src/flows/entropyFaucet/faucet.ts b/src/flows/entropyFaucet/faucet.ts deleted file mode 100644 index 0a9c304..0000000 --- a/src/flows/entropyFaucet/faucet.ts +++ /dev/null @@ -1,96 +0,0 @@ -// check verifying key has the balance and proper program hash - -import Entropy from "@entropyxyz/sdk"; -import { blake2AsHex, encodeAddress } from "@polkadot/util-crypto"; -import { viewPrograms } from "../programs/view"; -import FaucetSigner from "./signer"; -import { FAUCET_PROGRAM_MOD_KEY, TESTNET_PROGRAM_HASH } from "./constants"; -import { EntropyBalance } from "src/balance/main"; - -// only the faucet program should be on the key -async function faucetSignAndSend (call: any, entropy: Entropy, amount: number, senderAddress: string, chosenVerifyingKey: any): Promise { - const api = entropy.substrate - const faucetSigner = new FaucetSigner(api.registry, 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) - }) - }) -} - -export async function getRandomFaucet (entropy: Entropy, previousVerifyingKeys: string[] = [], programModKey = FAUCET_PROGRAM_MOD_KEY) { - const modifiableKeys = await entropy.substrate.query.registry.modifiableKeys(programModKey) - const verifyingKeys = JSON.parse(JSON.stringify(modifiableKeys.toJSON())) - - // Choosing one of the 5 verifiying keys at random to be used as the faucet sender - if (verifyingKeys.length === previousVerifyingKeys.length) { - throw new Error('FaucetError: There are no more faucets to choose from') - } - let chosenVerifyingKey = verifyingKeys[Math.floor(Math.random() * verifyingKeys.length)] - if (previousVerifyingKeys.length && previousVerifyingKeys.includes(chosenVerifyingKey)) { - const filteredVerifyingKeys = verifyingKeys.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, verifyingKeys } -} - -export async function sendMoney ( - entropy: Entropy, - endpoint: string, - { - amount, - addressToSendTo, - faucetAddress, - chosenVerifyingKey, - faucetProgramPointer = TESTNET_PROGRAM_HASH - }: { - amount: string, - addressToSendTo: string, - faucetAddress: string, - chosenVerifyingKey: string, - faucetProgramPointer: string - } -): Promise { - const BalanceService = new EntropyBalance(entropy, 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 viewPrograms(entropy, { 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 = entropy.substrate.tx.balances.transferAllowDeath(addressToSendTo, BigInt(amount)); - const transferStatus = await faucetSignAndSend(transfer, entropy, parseInt(amount), faucetAddress, chosenVerifyingKey ) - if (transferStatus.isFinalized) return transferStatus -} \ No newline at end of file diff --git a/tests/faucet.test.ts b/tests/faucet.test.ts index 517d0b7..33a35ca 100644 --- a/tests/faucet.test.ts +++ b/tests/faucet.test.ts @@ -55,7 +55,7 @@ test('Faucet Tests', async t => { const { chosenVerifyingKey, faucetAddress } = await getRandomFaucet(entropy, [], entropy.keyring.accounts.registration.address) // adding funds to faucet address - await run('Transfer funds to faucet address', TransferService.transfer(faucetAddress, "100000000000000")) + await run('Transfer funds to faucet address', TransferService.transfer(faucetAddress, "1000")) const transferStatus = await sendMoney( naynayEntropy,