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

Allow users to specify an address alias + misc updates #23

Merged
merged 4 commits into from
Oct 19, 2023
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
14 changes: 14 additions & 0 deletions getTez/README.md
Original file line number Diff line number Diff line change
Expand Up @@ -13,6 +13,7 @@ npm install @oxheadalpha/get-tez
## Usage

### JS / TS

After installing the package, you can import it in your Node.js JavaScript or TypeScript project:

```javascript
Expand All @@ -29,6 +30,7 @@ You can then use the `getTez` function to interact with the Tezos faucet. The fu
- `amount`: The amount of Tez to request.
- `network`: The faucet's network name. Must match a network name with a faucet listed at https://teztnets.xyz. Ignored if `faucetUrl` is set.
- `faucetUrl`: The custom faucet URL. Ignores `network`.
- `clientDir`: (Optional) Specifies a custom client directory path to look up address aliases. If not set, it will default to `$HOME/.tezos-client/` or `$TEZOS_CLIENT_DIR/public_key_hashs` if the `TEZOS_CLIENT_DIR` environment variable is specified.

Here is an example of how to use the `getTez` function:

Expand All @@ -41,7 +43,19 @@ const txHash = await getTez({
// txHash: ooaEskbj...
```

Using an address alias:

```javascript
const txHash = await getTez({
address: "alice",
amount: 10,
network: "ghostnet",
})
// txHash: ooaEskbj...
```

Example using the `faucetUrl` parameter:

```js
const txHash = await getTez({
address: "tz1...",
Expand Down
156 changes: 111 additions & 45 deletions getTez/getTez.ts
Original file line number Diff line number Diff line change
@@ -1,6 +1,8 @@
#!/usr/bin/env node

import * as crypto from "crypto"
import * as fs from "fs"
import * as path from "path"
import * as pkgJson from "./package.json"

const isMainModule = require.main === module

Expand Down Expand Up @@ -29,22 +31,31 @@ const [time, timeLog, timeEnd] = [

const displayHelp = () => {
log(`CLI Usage: npx @oxheadalpha/get-tez [options] <address>

<address>:
The address where Tez should be sent. This can be either a standard Tezos public key hash (e.g. tz1234abc...)
or a local alias. If an alias is provided (e.g., 'alice'), the program will attempt to resolve it to a public
key hash by looking it up in the specified client directory (set by --client-dir or by the TEZOS_CLIENT_DIR
environment variable). If neither is set, the default lookup location is $HOME/.tezos-client/public_key_hashes.

Options:
-h, --help Display help information.
-a, --amount <value> The amount of Tez to request.
-n, --network <value> Set the faucet's network name. Must match a
network name with a faucet listed at https://teztnets.xyz.
Ignored if --faucet-url is set.
-f, --faucet-url <value> Set the custom faucet URL. Ignores --network.
-d, --client-dir <value> Custom client directory path to look up an address alias.
-t, --time Enable PoW challenges timer.
-v, --verbose Enable verbose logging.`)
-v, --verbose Enable verbose logging.
--version Log the package version.`)
}

const DISPLAY_HELP = isMainModule && true

const handleError = (message: string, help?: boolean) => {
if (isMainModule) {
log(`ERROR: ${message}`, "\n")
log(`ERROR: ${message}\n`)
help && displayHelp()
process.exit(1)
} else {
Expand All @@ -53,11 +64,30 @@ const handleError = (message: string, help?: boolean) => {
}
}

const DEFAULT_CLIENT_DIR =
process.env.TEZOS_CLIENT_DIR || path.join(process.env.HOME!, ".tezos-client")

const resolveAliasToPkh = (
alias: string,
clientDir: string = DEFAULT_CLIENT_DIR
): string | null => {
const pkhsFilePath = path.join(clientDir, "public_key_hashs")
if (fs.existsSync(pkhsFilePath)) {
const pkhsData: Array<{ name: string; value: string }> = JSON.parse(
fs.readFileSync(pkhsFilePath, "utf8")
)
return pkhsData.find(({ name }) => name === alias)?.value || null
}
return null
}

type GetTezArgs = {
/** The address to send Tez to. */
address: string
/** The amount of Tez to request. */
amount: number
/** Custom client directory path to look up address alias. */
clientDir?: string
/** Set the faucet's network name. Must match a network name with a faucet
* listed at https://teztnets.xyz. Ignored if `faucetUrl` is set. */
network?: string
Expand All @@ -69,7 +99,7 @@ type GetTezArgs = {
time?: boolean
}

const parseCliArgs = (args: string | string[]) => {
const parseCliArgs = (args: string | string[]): GetTezArgs => {
if (typeof args === "string") args = args.split(" ")

const parsedArgs: GetTezArgs = {
Expand Down Expand Up @@ -102,6 +132,14 @@ const parseCliArgs = (args: string | string[]) => {
case "--faucet-url":
parsedArgs.faucetUrl = args.shift() || ""
break
case "-d":
case "--client-dir":
const clientDir = args.shift()
if (!clientDir) {
handleError(`The ${arg} flag expects an argument.`, DISPLAY_HELP)
}
parsedArgs.clientDir = clientDir
break
case "-v":
case "--verbose":
VERBOSE = true
Expand All @@ -110,8 +148,15 @@ const parseCliArgs = (args: string | string[]) => {
case "--time":
TIME = true
break
case "--version":
log(pkgJson.version)
process.exit(0)
default:
parsedArgs.address = arg || ""
if (!parsedArgs.address) {
parsedArgs.address = arg || ""
} else {
handleError(`Unexpected argument provided: '${arg}'`, DISPLAY_HELP)
}
break
}
}
Expand All @@ -122,8 +167,19 @@ const parseCliArgs = (args: string | string[]) => {
type ValidatedArgs = Required<Omit<GetTezArgs, "verbose" | "time" | "network">>

const validateArgs = async (args: GetTezArgs): Promise<ValidatedArgs> => {
if (args.clientDir && !fs.existsSync(args.clientDir)) {
handleError(`Client dir '${args.clientDir}' doesn't exist.`)
}

if (!args.address) {
handleError("Tezos address is required.", DISPLAY_HELP)
} else if (!args.address.startsWith("tz")) {
const resolvedAddress = resolveAliasToPkh(args.address, args.clientDir)
if (!resolvedAddress) {
handleError(`Alias '${args.address}' not found.`)
} else {
args.address = resolvedAddress
}
}

if (!args.amount || args.amount <= 0) {
Expand All @@ -140,7 +196,7 @@ const validateArgs = async (args: GetTezArgs): Promise<ValidatedArgs> => {
if (!args.faucetUrl) {
const teztnetsUrl = "https://teztnets.xyz/teztnets.json"
const response = await fetch(teztnetsUrl, {
signal: AbortSignal.timeout(5000),
signal: AbortSignal.timeout(10_000),
})

if (!response.ok) {
Expand Down Expand Up @@ -180,7 +236,7 @@ const getInfo = async (faucetUrl: string) => {

const response = await fetch(`${faucetUrl}/info`, {
headers: requestHeaders,
signal: AbortSignal.timeout(5000),
signal: AbortSignal.timeout(10_000),
})

const body = await response.json()
Expand All @@ -200,7 +256,7 @@ const getChallenge = async ({ address, amount, faucetUrl }: ValidatedArgs) => {
const response = await fetch(`${faucetUrl}/challenge`, {
method: "POST",
headers: requestHeaders,
signal: AbortSignal.timeout(5000),
signal: AbortSignal.timeout(10_000),
body: JSON.stringify({ address, amount }),
})

Expand Down Expand Up @@ -285,7 +341,7 @@ const verifySolution = async ({
const response = await fetch(`${faucetUrl}/verify`, {
method: "POST",
headers: requestHeaders,
signal: AbortSignal.timeout(5000),
signal: AbortSignal.timeout(10_000),
body: JSON.stringify({ address, amount, nonce, solution }),
})

Expand Down Expand Up @@ -316,61 +372,71 @@ const formatAmount = (amount: number) =>
})

const getTez = async (args: GetTezArgs) => {
const validatedArgs = await validateArgs(args)
try {
const validatedArgs = await validateArgs(args)

const { challengesEnabled, minTez, maxTez } = await getInfo(
validatedArgs.faucetUrl
)

if (!(args.amount >= minTez && args.amount <= maxTez)) {
handleError(
`Amount must be between ${formatAmount(minTez)} and ${formatAmount(
maxTez
)} tez.`
const { challengesEnabled, minTez, maxTez } = await getInfo(
validatedArgs.faucetUrl
)
}

if (!challengesEnabled) {
const txHash = (
await verifySolution({ solution: "", nonce: 0, ...validatedArgs })
)?.txHash
return txHash
}
if (!(args.amount >= minTez && args.amount <= maxTez)) {
handleError(
`Amount must be between ${formatAmount(minTez)} and ${formatAmount(
maxTez
)} tez.`
)
}

if (!challengesEnabled) {
const txHash = (
await verifySolution({ solution: "", nonce: 0, ...validatedArgs })
)?.txHash
return txHash
}

let { challenge, difficulty, challengeCounter, challengesNeeded } =
await getChallenge(validatedArgs)
let { challenge, difficulty, challengeCounter, challengesNeeded } =
await getChallenge(validatedArgs)

time("getTez time")
time("getTez time")

while (challenge && difficulty && challengeCounter && challengesNeeded) {
verboseLog({ challenge, difficulty, challengeCounter })
while (challenge && difficulty && challengeCounter && challengesNeeded) {
verboseLog({ challenge, difficulty, challengeCounter })

const { solution, nonce } = solveChallenge({
challenge,
difficulty,
challengeCounter,
challengesNeeded,
})
const { solution, nonce } = solveChallenge({
challenge,
difficulty,
challengeCounter,
challengesNeeded,
})

verboseLog({ nonce, solution })
verboseLog({ nonce, solution })

let txHash
;({ challenge, difficulty, challengeCounter, txHash } =
await verifySolution({ solution, nonce, ...validatedArgs }))
let txHash
;({ challenge, difficulty, challengeCounter, txHash } =
await verifySolution({ solution, nonce, ...validatedArgs }))

if (txHash) {
timeEnd("getTez time")
return txHash
if (txHash) {
timeEnd("getTez time")
return txHash
}
}
} catch (err) {
if (err instanceof DOMException && err.name === "TimeoutError") {
handleError("Connection timeout. Please try again.")
} else {
throw err
}
}
}

if (isMainModule) {
log("getTez.js by Oxhead Alpha - Get Free Tez\n")
// If the file is executed directly by node and not via import then argv will
// include the file name.
const args = process.argv.slice(isMainModule ? 2 : 1)
const parsedArgs = parseCliArgs(args)

log(`get-tez v${pkgJson.version} by Oxhead Alpha - Get Free Tez\n`)

getTez(parsedArgs).then(
(txHash) => txHash && process.stdout.write("\n" + txHash)
)
Expand Down
2 changes: 1 addition & 1 deletion getTez/tsconfig.json
Original file line number Diff line number Diff line change
Expand Up @@ -39,7 +39,7 @@
// "resolvePackageJsonExports": true, /* Use the package.json 'exports' field when resolving package imports. */
// "resolvePackageJsonImports": true, /* Use the package.json 'imports' field when resolving imports. */
// "customConditions": [], /* Conditions to set in addition to the resolver-specific defaults when resolving imports. */
// "resolveJsonModule": true, /* Enable importing .json files. */
"resolveJsonModule": true, /* Enable importing .json files. */
// "allowArbitraryExtensions": true, /* Enable importing files with any extension, provided a declaration file is present. */
// "noResolve": true, /* Disallow 'import's, 'require's or '<reference>'s from expanding the number of files TypeScript should add to a project. */

Expand Down
Loading