UNPKG

@ar.io/sdk

Version:

[![codecov](https://codecov.io/gh/ar-io/ar-io-sdk/graph/badge.svg?token=7dXKcT7dJy)](https://codecov.io/gh/ar-io/ar-io-sdk)

527 lines (526 loc) 19.2 kB
"use strict"; var __importDefault = (this && this.__importDefault) || function (mod) { return (mod && mod.__esModule) ? mod : { "default": mod }; }; Object.defineProperty(exports, "__esModule", { value: true }); exports.defaultTtlSecondsCLI = void 0; exports.stringifyJsonForCLIDisplay = stringifyJsonForCLIDisplay; exports.runCommand = runCommand; exports.applyOptions = applyOptions; exports.makeCommand = makeCommand; exports.arioProcessIdFromOptions = arioProcessIdFromOptions; exports.requiredJwkFromOptions = requiredJwkFromOptions; exports.jwkToAddress = jwkToAddress; exports.getLoggerFromOptions = getLoggerFromOptions; exports.readARIOFromOptions = readARIOFromOptions; exports.contractSignerFromOptions = contractSignerFromOptions; exports.requiredContractSignerFromOptions = requiredContractSignerFromOptions; exports.requiredAoSignerFromOptions = requiredAoSignerFromOptions; exports.writeARIOFromOptions = writeARIOFromOptions; exports.formatARIOWithCommas = formatARIOWithCommas; exports.formatMARIOToARIOWithCommas = formatMARIOToARIOWithCommas; exports.addressFromOptions = addressFromOptions; exports.requiredAddressFromOptions = requiredAddressFromOptions; exports.paginationParamsFromOptions = paginationParamsFromOptions; exports.epochInputFromOptions = epochInputFromOptions; exports.requiredInitiatorFromOptions = requiredInitiatorFromOptions; exports.customTagsFromOptions = customTagsFromOptions; exports.gatewaySettingsFromOptions = gatewaySettingsFromOptions; exports.requiredTargetAndQuantityFromOptions = requiredTargetAndQuantityFromOptions; exports.redelegateParamsFromOptions = redelegateParamsFromOptions; exports.recordTypeFromOptions = recordTypeFromOptions; exports.requiredMARIOFromOptions = requiredMARIOFromOptions; exports.assertEnoughBalanceForArNSPurchase = assertEnoughBalanceForArNSPurchase; exports.assertEnoughMARIOBalance = assertEnoughMARIOBalance; exports.confirmationPrompt = confirmationPrompt; exports.assertConfirmationPrompt = assertConfirmationPrompt; exports.requiredProcessIdFromOptions = requiredProcessIdFromOptions; exports.readANTFromOptions = readANTFromOptions; exports.writeANTFromOptions = writeANTFromOptions; exports.booleanFromOptions = booleanFromOptions; exports.requiredStringFromOptions = requiredStringFromOptions; exports.stringArrayFromOptions = stringArrayFromOptions; exports.requiredStringArrayFromOptions = requiredStringArrayFromOptions; exports.positiveIntegerFromOptions = positiveIntegerFromOptions; exports.requiredPositiveIntegerFromOptions = requiredPositiveIntegerFromOptions; exports.getANTStateFromOptions = getANTStateFromOptions; exports.getTokenCostParamsFromOptions = getTokenCostParamsFromOptions; exports.fundFromFromOptions = fundFromFromOptions; exports.referrerFromOptions = referrerFromOptions; exports.assertLockLengthInRange = assertLockLengthInRange; /** * Copyright (C) 2022-2024 Permanent Data Solutions, Inc. * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. * You may obtain a copy of the License at * * http://www.apache.org/licenses/LICENSE-2.0 * * Unless required by applicable law or agreed to in writing, software * distributed under the License is distributed on an "AS IS" BASIS, * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. * See the License for the specific language governing permissions and * limitations under the License. */ const arbundles_1 = require("@dha-team/arbundles"); const aoconnect_1 = require("@permaweb/aoconnect"); const commander_1 = require("commander"); const fs_1 = require("fs"); const prompts_1 = __importDefault(require("prompts")); const index_js_1 = require("../node/index.js"); const options_js_1 = require("./options.js"); exports.defaultTtlSecondsCLI = 3600; function stringifyJsonForCLIDisplay(json) { return JSON.stringify(json, null, 2); } function logCommandOutput(output) { console.log(stringifyJsonForCLIDisplay(output)); } function exitWithErrorLog(error, debug = false) { let errorLog; if (error instanceof Error) { errorLog = error.message; if (debug && error.stack !== undefined) { errorLog = error.stack; } } else { errorLog = stringifyJsonForCLIDisplay(error); } console.error(errorLog); process.exit(1); } async function runCommand(command, action) { const options = command.optsWithGlobals(); try { const output = await action(options); logCommandOutput(output); process.exit(0); } catch (error) { exitWithErrorLog(error, options.debug); } } function applyOptions(command, options) { [...options].forEach((option) => { command.option(option.alias, option.description, option.default); }); return command; } function makeCommand({ description, name, options = [], action, }) { const command = commander_1.program.command(name).description(description); const appliedCommand = applyOptions(command, [...options, ...options_js_1.globalOptions]); if (action !== undefined) { appliedCommand.action(() => runCommand(appliedCommand, action)); } return appliedCommand; } function arioProcessIdFromOptions({ arioProcessId, devnet, testnet, }) { if (arioProcessId !== undefined) { return arioProcessId; } if (devnet) { return index_js_1.ARIO_DEVNET_PROCESS_ID; } if (testnet) { return index_js_1.ARIO_TESTNET_PROCESS_ID; } return index_js_1.ARIO_MAINNET_PROCESS_ID; } function walletFromOptions({ privateKey, walletFile, }) { if (privateKey !== undefined) { return JSON.parse(privateKey); } if (walletFile !== undefined) { return JSON.parse((0, fs_1.readFileSync)(walletFile, 'utf-8')); } return undefined; } function requiredJwkFromOptions(options) { const jwk = walletFromOptions(options); if (jwk === undefined) { throw new Error('No JWK provided for signing!\nPlease provide a stringified JWK with `--private-key` or the file path of a jwk.json file with `--wallet-file`'); } return jwk; } function jwkToAddress(jwk) { return (0, index_js_1.sha256B64Url)((0, index_js_1.fromB64Url)(jwk.n)); } function setLoggerIfDebug(options) { if (options.debug) { index_js_1.Logger.default.setLogLevel('debug'); } } function getLoggerFromOptions(options) { setLoggerIfDebug(options); return index_js_1.Logger.default; } function aoProcessFromOptions(options) { return new index_js_1.AOProcess({ processId: arioProcessIdFromOptions(options), ao: (0, aoconnect_1.connect)({ MODE: 'legacy', CU_URL: options.cuUrl, }), }); } function readARIOFromOptions(options) { setLoggerIfDebug(options); return index_js_1.ARIO.init({ process: aoProcessFromOptions({ cuUrl: 'https://cu.ardrive.io', // default to ardrive cu for ARIO process ...options, }), paymentUrl: options.paymentUrl, }); } function contractSignerFromOptions(options) { const wallet = walletFromOptions(options); if (wallet === undefined) { return undefined; } const token = options.token ?? 'arweave'; if (token === 'ethereum') { const signer = new arbundles_1.EthereumSigner(wallet); // For EthereumSigner, we need to convert the JWK to a string return { signer, signerAddress: signer.publicKey.toString('hex') }; } // TODO: Support other wallet types const signer = new index_js_1.ArweaveSigner(wallet); return { signer, signerAddress: jwkToAddress(wallet) }; } function requiredContractSignerFromOptions(options) { const contractSigner = contractSignerFromOptions(options); if (contractSigner === undefined) { throw new Error('No signer provided for signing!\nPlease provide a stringified JWK or Ethereum private key with `--private-key` or the file path of an arweave.jwk.json or eth.private.key.txt file with `--wallet-file`'); } return contractSigner; } function requiredAoSignerFromOptions(options) { return (0, index_js_1.createAoSigner)(requiredContractSignerFromOptions(options).signer); } function writeARIOFromOptions(options) { const { signer, signerAddress } = requiredContractSignerFromOptions(options); setLoggerIfDebug(options); return { ario: index_js_1.ARIO.init({ process: aoProcessFromOptions(options), signer, paymentUrl: options.paymentUrl, }), signerAddress, }; } function formatARIOWithCommas(value) { const [integerPart, decimalPart] = value.toString().split('.'); const integerWithCommas = integerPart.replace(/\B(?=(\d{3})+(?!\d))/g, ','); if (decimalPart === undefined) { return integerWithCommas; } return integerWithCommas + '.' + decimalPart; } function formatMARIOToARIOWithCommas(value) { return formatARIOWithCommas(value.toARIO()); } /** helper to get address from --address option first, then check wallet options */ function addressFromOptions(options) { if (options.address !== undefined) { return options.address; } const signer = contractSignerFromOptions(options); if (signer !== undefined) { return signer.signerAddress; } return undefined; } function requiredAddressFromOptions(options) { const address = addressFromOptions(options); if (address !== undefined) { return address; } throw new Error('No address provided. Use --address or --wallet-file'); } const defaultCliPaginationLimit = 10; // more friendly UX than 100 function paginationParamsFromOptions(options) { const { cursor, limit, sortBy, sortOrder } = options; if (sortOrder !== undefined && !['asc', 'desc'].includes(sortOrder)) { throw new Error(`Invalid sort order: ${sortOrder}, must be "asc" or "desc"`); } const numberLimit = limit !== undefined ? +limit : defaultCliPaginationLimit; if (isNaN(numberLimit) || numberLimit <= 0) { throw new Error(`Invalid limit: ${numberLimit}, must be a positive number`); } return { cursor, limit: numberLimit, sortBy: sortBy, sortOrder, }; } function epochInputFromOptions(options) { if (options.epochIndex !== undefined) { return { epochIndex: +options.epochIndex }; } if (options.timestamp !== undefined) { return { timestamp: +options.timestamp }; } return undefined; } function requiredInitiatorFromOptions(options) { if (options.initiator !== undefined) { return options.initiator; } return requiredAddressFromOptions(options); } function customTagsFromOptions(options) { if (options.tags === undefined) { return {}; } if (!Array.isArray(options.tags)) { throw new Error('Tags must be an array'); } if (options.tags.length === 0) { return {}; } if (options.tags.length % 2 !== 0) { throw new Error('Tags must be an array of key-value pairs'); } const tags = []; for (let i = 0; i < options.tags.length; i += 2) { tags.push({ name: options.tags[i], value: options.tags[i + 1], }); } return { tags, }; } function gatewaySettingsFromOptions({ allowDelegatedStaking, autoStake, delegateRewardShareRatio, fqdn, label, minDelegatedStake, note, observerAddress, port, properties, allowedDelegates, }) { return { observerAddress, allowDelegatedStaking, autoStake, delegateRewardShareRatio: delegateRewardShareRatio !== undefined ? +delegateRewardShareRatio : undefined, allowedDelegates, fqdn, label, minDelegatedStake: minDelegatedStake !== undefined ? +minDelegatedStake : undefined, note, port: port !== undefined ? +port : undefined, properties, }; } function requiredTargetAndQuantityFromOptions(options) { if (options.target === undefined) { throw new Error('No target provided. Use --target'); } if (options.quantity === undefined) { throw new Error('No quantity provided. Use --quantity'); } return { target: options.target, arioQuantity: new index_js_1.ARIOToken(+options.quantity), }; } function redelegateParamsFromOptions(options) { const { target, arioQuantity: aRIOQuantity } = requiredTargetAndQuantityFromOptions(options); const source = options.source; if (source === undefined) { throw new Error('No source provided. Use --source'); } return { target, source, vaultId: options.vaultId, stakeQty: aRIOQuantity.toMARIO(), }; } function recordTypeFromOptions(options) { options.type ??= 'lease'; if (options.type !== 'lease' && options.type !== 'permabuy') { throw new Error(`Invalid type. Valid types are: lease, permabuy`); } return options.type; } function requiredMARIOFromOptions(options, key) { if (options[key] === undefined) { throw new Error(`No ${key} provided. Use --${key} denominated in ARIO`); } return new index_js_1.ARIOToken(+options[key]).toMARIO(); } async function assertEnoughBalanceForArNSPurchase({ ario, address, costDetailsParams, }) { if (costDetailsParams.fundFrom === 'turbo') { // TODO: Get turbo balance and assert it is enough -- retain paid-by from balance result and pass to CLI logic return; } const costDetails = await ario.getCostDetails(costDetailsParams); if (costDetails.fundingPlan) { if (costDetails.fundingPlan.shortfall > 0) { throw new Error(`Insufficient balance for action. Shortfall: ${formatMARIOToARIOWithCommas(new index_js_1.mARIOToken(costDetails.fundingPlan.shortfall))}\n${JSON.stringify(costDetails, null, 2)}`); } } else { await assertEnoughMARIOBalance({ ario, address, mARIOQuantity: costDetails.tokenCost, }); } } async function assertEnoughMARIOBalance({ address, ario, mARIOQuantity, }) { if (typeof mARIOQuantity === 'number') { mARIOQuantity = new index_js_1.mARIOToken(mARIOQuantity); } const balance = await ario.getBalance({ address }); if (balance < mARIOQuantity.valueOf()) { throw new Error(`Insufficient ARIO balance for action. Balance available: ${formatMARIOToARIOWithCommas(new index_js_1.mARIOToken(balance))} ARIO`); } } async function confirmationPrompt(message) { const { confirm } = await (0, prompts_1.default)({ type: 'confirm', name: 'confirm', message, }); return confirm; } async function assertConfirmationPrompt(message, options) { if (options.skipConfirmation) { return true; } return confirmationPrompt(message); } function requiredProcessIdFromOptions(o) { if (o.processId === undefined) { throw new Error('--process-id is required'); } return o.processId; } function ANTProcessFromOptions(options) { return new index_js_1.AOProcess({ processId: requiredProcessIdFromOptions(options), ao: (0, aoconnect_1.connect)({ MODE: 'legacy', CU_URL: options.cuUrl, }), }); } function readANTFromOptions(options) { return index_js_1.ANT.init({ process: ANTProcessFromOptions(options), }); } function writeANTFromOptions(options, signer) { signer ??= requiredContractSignerFromOptions(options).signer; return index_js_1.ANT.init({ process: ANTProcessFromOptions(options), signer, }); } function booleanFromOptions(options, key) { return !!options[key]; } function requiredStringFromOptions(options, key) { const value = options[key]; if (value === undefined) { throw new Error(`--${key} is required`); } return value; } function stringArrayFromOptions(options, key) { const value = options[key]; if (value === undefined) { return undefined; } if (!Array.isArray(value)) { throw new Error(`--${key} must be an array`); } return value; } function requiredStringArrayFromOptions(options, key) { const value = stringArrayFromOptions(options, key); if (value === undefined) { throw new Error(`--${key} is required`); } return value; } function positiveIntegerFromOptions(options, key) { const value = options[key]; if (value === undefined) { return undefined; } const numberValue = +value; if (isNaN(numberValue) || numberValue <= 0) { throw new Error(`Invalid ${key}: ${value}, must be a positive number`); } return numberValue; } function requiredPositiveIntegerFromOptions(options, key) { const value = positiveIntegerFromOptions(options, key); if (value === undefined) { throw new Error(`--${key} is required`); } return value; } function getANTStateFromOptions(options) { return (0, index_js_1.initANTStateForAddress)({ owner: requiredAddressFromOptions(options), targetId: options.target, controllers: options.controllers, description: options.description, ticker: options.ticker, name: options.name, keywords: options.keywords, logo: options.logo, ttlSeconds: options.ttlSeconds !== undefined ? +options.ttlSeconds : exports.defaultTtlSecondsCLI, }); } function getTokenCostParamsFromOptions(o) { o.intent ??= 'Buy-Name'; o.type ??= 'lease'; o.years ??= '1'; if (!(0, index_js_1.isValidIntent)(o.intent)) { throw new Error(`Invalid intent. Valid intents are: ${index_js_1.validIntents.join(', ')}`); } if (o.type !== 'lease' && o.type !== 'permabuy') { throw new Error(`Invalid type. Valid types are: lease, permabuy`); } return { type: o.type, quantity: o.quantity !== undefined ? +o.quantity : undefined, years: +o.years, intent: o.intent, name: requiredStringFromOptions(o, 'name'), fromAddress: addressFromOptions(o), }; } function fundFromFromOptions(o) { if (o.fundFrom !== undefined) { if (!(0, index_js_1.isValidFundFrom)(o.fundFrom)) { throw new Error(`Invalid fund from: ${o.fundFrom}. Please use one of ${index_js_1.fundFromOptions.join(', ')}`); } } return o.fundFrom ?? 'balance'; } function referrerFromOptions(o) { return o.referrer; } function assertLockLengthInRange(lockLengthMs, assertMin = true) { const minLockLengthMs = 1209600000; // 14 days const maxLockLengthMs = 378432000000; // ~12 years if (lockLengthMs > maxLockLengthMs) { throw new Error(`Lock length must be at most 12 years (378432000000 ms). Provided lock length: ${lockLengthMs} ms`); } if (!assertMin) { return; } if (lockLengthMs < minLockLengthMs) { throw new Error(`Lock length must be at least 14 days (1209600000 ms). Provided lock length: ${lockLengthMs} ms`); } }