UNPKG

@taqueria/plugin-taquito

Version:

A taqueria plugin for originating smart contracts using Taquito

645 lines (636 loc) • 25.2 kB
// index.ts import { Option, Plugin, Task } from "@taqueria/node-sdk"; // main.ts import { sendAsyncErr as sendAsyncErr6 } from "@taqueria/node-sdk"; // fund.ts import { getCurrentEnvironment as getCurrentEnvironment2, getCurrentEnvironmentConfig as getCurrentEnvironmentConfig2, sendAsyncErr as sendAsyncErr3, sendJsonRes as sendJsonRes2, sendWarn } from "@taqueria/node-sdk"; // common.ts import { getAccountPrivateKey, getDefaultSandboxAccount, getNetworkConfig, getSandboxConfig, sendAsyncErr, TAQ_OPERATOR_ACCOUNT } from "@taqueria/node-sdk"; import { importKey, InMemorySigner } from "@taquito/signer"; import { TezosToolkit } from "@taquito/taquito"; var getEnvTypeAndNodeConfig = (parsedArgs, env) => { var _a, _b, _c, _d; const targetConstraintErrMsg = "Each environment can only have one target, be it a network or a sandbox"; if (((_a = env.networks) == null ? void 0 : _a.length) === 1 && ((_b = env.sandboxes) == null ? void 0 : _b.length) === 1) return sendAsyncErr(targetConstraintErrMsg); if (((_c = env.networks) == null ? void 0 : _c.length) === 1) { const networkName = env.networks[0]; const network = getNetworkConfig(parsedArgs)(networkName); if (!network) { return sendAsyncErr( `The current environment is configured to use a network called '${networkName}'; however, no network of this name has been configured in .taq/config.json` ); } return Promise.resolve(["Network", network]); } if (((_d = env.sandboxes) == null ? void 0 : _d.length) === 1) { const sandboxName = env.sandboxes[0]; const sandbox = getSandboxConfig(parsedArgs)(sandboxName); if (!sandbox) { return sendAsyncErr( `The current environment is configured to use a sandbox called '${sandboxName}'; however, no sandbox of this name has been configured in .taq/config.json` ); } return Promise.resolve(["Sandbox", sandbox]); } return sendAsyncErr(targetConstraintErrMsg); }; var configureToolKitForSandbox = async (sandbox, sender) => { let accountKey; if (sender && sender !== "default") { const accounts = getSandboxInstantiatedAccounts(sandbox); if (accounts.hasOwnProperty(sender)) { accountKey = accounts[sender].secretKey; } else { return sendAsyncErr( `${sender} is not an account instantiated in the current environment. Check .taq/config.json` ); } } else { const defaultAccount = getDefaultSandboxAccount(sandbox); if (!defaultAccount) { return sendAsyncErr( `No default account is specified in the sandbox to perform the operation. Please use the --sender flag to explicitly specify the account to use as the sender of the operation` ); } accountKey = defaultAccount.secretKey; } const tezos = new TezosToolkit(sandbox.rpcUrl); tezos.setProvider({ signer: new InMemorySigner(accountKey.replace(/^unencrypted:/, "")) }); return tezos; }; var configureToolKitForNetwork = async (parsedArgs, network, sender) => { let account; if (sender && sender !== TAQ_OPERATOR_ACCOUNT) { const accounts = getNetworkInstantiatedAccounts(network); if (accounts.hasOwnProperty(sender)) { account = sender; } else { return sendAsyncErr( `${sender} is not an account instantiated in the current environment. Check .taq/config.json` ); } } else { account = TAQ_OPERATOR_ACCOUNT; } const tezos = new TezosToolkit(network.rpcUrl); const key = await getAccountPrivateKey(parsedArgs, network, account); await importKey(tezos, key); return tezos; }; var getDeclaredAccounts = (parsedArgs) => Object.entries(parsedArgs.config.accounts ?? {}).reduce( (acc, declaredAccount) => { const alias = declaredAccount[0]; const mutez = declaredAccount[1]; return { ...acc, [alias]: typeof mutez === "string" ? parseFloat(mutez.replaceAll("_", "")) : mutez }; }, {} ); var getSandboxInstantiatedAccounts = (sandbox) => (sandbox == null ? void 0 : sandbox.accounts) ? Object.entries(sandbox.accounts).reduce( (acc, instantiatedAccount) => { const alias = instantiatedAccount[0]; const keys = instantiatedAccount[1]; return alias !== "default" ? { ...acc, [alias]: keys } : acc; }, {} ) : {}; var getNetworkInstantiatedAccounts = (network) => network.accounts ? Object.entries(network.accounts).reduce( (acc, instantiatedAccount) => { const alias = instantiatedAccount[0]; const keys = instantiatedAccount[1]; return alias !== TAQ_OPERATOR_ACCOUNT ? { ...acc, [alias]: keys } : acc; }, {} ) : {}; var generateAccountKeys = async (parsedArgs, network, account) => { const tezos = new TezosToolkit(network.rpcUrl); const key = await getAccountPrivateKey(parsedArgs, network, account); await importKey(tezos, key); }; var handleOpsError = (err, env) => { if (err instanceof Error) { const msg = err.message; if (/ENOTFOUND/.test(msg)) return sendAsyncErr("The RPC URL may be invalid. Check ./.taq/config.json"); if (/ECONNREFUSED/.test(msg)) return sendAsyncErr("The RPC URL may be down or the sandbox is not running"); if (/empty_implicit_contract/.test(msg)) { const result = msg.match(/(?<="implicit":")tz[^"]+(?=")/); const publicKeyHash = result ? result[0] : void 0; if (publicKeyHash) { return sendAsyncErr( `The account ${publicKeyHash} for the target environment, "${env}", may not be funded To fund this account: 1. Go to https://teztnets.xyz and click "Faucet" of the target testnet 2. Copy and paste the above key into the wallet address field 3. Request some Tez (Note that you might need to wait for a few seconds for the network to register the funds)` ); } } } return sendAsyncErr(`Error while performing operation: ${err} ${JSON.stringify(err, null, 2)}`); }; var doWithin = async (seconds, fn) => { let captured = new Error( "Operation timed out. Please try again and perhaps increase the timeout using the --timeout option." ); let timeout; const maxTimeout = new Promise((resolve, reject) => { timeout = setTimeout(() => { reject(captured); }, seconds * 1e3); }); const process2 = async () => { while (true) { try { const retval = await fn(); return retval; } catch (err) { captured = err; } } }; return Promise.race([process2(), maxTimeout]).then((retval) => { clearTimeout(timeout); return retval; }); }; // transfer.ts import { getAddressOfAlias, getCurrentEnvironment, getCurrentEnvironmentConfig, getParameter, RequestArgs as RequestArgs2, sendAsyncErr as sendAsyncErr2, sendJsonRes } from "@taqueria/node-sdk"; import { Parser } from "@taquito/michel-codec"; var isContractAddress = (contract) => contract.startsWith("tz1") || contract.startsWith("tz2") || contract.startsWith("tz3") || contract.startsWith("KT1"); var getContractInfo = async (parsedArgs, env) => { const contract = parsedArgs.contract; const protocolArgs = RequestArgs2.create(parsedArgs); return { contractAlias: isContractAddress(contract) ? "N/A" : contract, contractAddress: isContractAddress(contract) ? contract : await getAddressOfAlias(env, contract), parameter: parsedArgs.param ? await getParameter(protocolArgs, parsedArgs.param) : "Unit", entrypoint: parsedArgs.entrypoint ?? "default", mutezTransfer: parseInt(parsedArgs.mutez ?? "0") }; }; var createBatchForTransfer = (tezos, contractsInfo, gasLimit, storageLimit, fee) => contractsInfo.reduce((acc, contractInfo) => acc.withTransfer({ fee, gasLimit, storageLimit, to: contractInfo.contractAddress, amount: contractInfo.mutezTransfer, parameter: { entrypoint: contractInfo.entrypoint, value: new Parser().parseMichelineExpression(contractInfo.parameter) }, mutez: true }), tezos.wallet.batch()); var performTransferOps = async (tezos, env, contractsInfo, maxTimeout, gasLimit, storageLimit, fee) => { const batch = createBatchForTransfer(tezos, contractsInfo, gasLimit, storageLimit, fee); try { return await doWithin(maxTimeout, async () => { const op = await batch.send(); await op.confirmation(); return op; }); } catch (err) { return handleOpsError(err, env); } }; var prepContractInfoForDisplay = (tezos, contractInfo) => { return { contractAlias: contractInfo.contractAlias, contractAddress: contractInfo.contractAddress, parameter: contractInfo.parameter, entrypoint: contractInfo.entrypoint, mutezTransfer: contractInfo.mutezTransfer.toString(), destination: tezos.rpc.getRpcUrl() }; }; var transfer = async (opts) => { const protocolArgs = RequestArgs2.create(opts); const env = getCurrentEnvironmentConfig(protocolArgs); if (!env) return sendAsyncErr2(`There is no environment called ${protocolArgs.env} in your config.json`); try { const [envType, nodeConfig] = await getEnvTypeAndNodeConfig(protocolArgs, env); const tezos = await (envType === "Network" ? configureToolKitForNetwork(protocolArgs, nodeConfig, opts.sender) : configureToolKitForSandbox(nodeConfig, opts.sender)); const contractInfo = await getContractInfo(opts, env); await performTransferOps( tezos, getCurrentEnvironment(protocolArgs), [contractInfo], opts.timeout, opts.gasLimit, opts.storageLimit, opts.fee ); const contractInfoForDisplay = prepContractInfoForDisplay(tezos, contractInfo); return sendJsonRes([contractInfoForDisplay]); } catch { return sendAsyncErr2("No operations performed"); } }; var transfer_default = transfer; // fund.ts var getAccountsInfo = (parsedArgs, tezos, instantiatedAccounts) => Promise.all( Object.entries(instantiatedAccounts).map(async (instantiatedAccount) => { const alias = instantiatedAccount[0]; const aliasInfo = instantiatedAccount[1]; const declaredMutez = getDeclaredAccounts(parsedArgs)[alias]; const currentBalanceInMutez = (await tezos.tz.getBalance(aliasInfo.publicKeyHash)).toNumber(); const amountToFillInMutez = declaredMutez ? Math.max(declaredMutez - currentBalanceInMutez, 0) : 0; if (!declaredMutez) { sendWarn( `Warning: ${alias} is instantiated in the target environment but not declared in the root level "accounts" field of ./.taq/config.json so ${alias} will not be funded as you don't have a declared tez amount set there for ${alias} ` ); } return { contractAlias: alias, contractAddress: aliasInfo.publicKeyHash, parameter: "Unit", entrypoint: "default", mutezTransfer: parseInt(amountToFillInMutez.toString().replaceAll("_", "")) }; }) ).then((accountsInfo) => accountsInfo.filter((accountInfo) => accountInfo.mutezTransfer !== 0)).catch((err) => sendAsyncErr3(`Something went wrong while extracting account information - ${err}`)); var prepAccountsInfoForDisplay = (accountsInfo) => accountsInfo.map((accountInfo) => { return { accountAlias: accountInfo.contractAlias, accountAddress: accountInfo.contractAddress, mutezFunded: accountInfo.mutezTransfer.toString() }; }); var fund = async (parsedArgs) => { const env = getCurrentEnvironmentConfig2(parsedArgs); if (!env) return sendAsyncErr3(`There is no environment called ${parsedArgs.env} in your config.json`); try { const [envType, nodeConfig] = await getEnvTypeAndNodeConfig(parsedArgs, env); if (envType !== "Network") return sendAsyncErr3("taq fund can only be executed in a network environment"); const tezos = await configureToolKitForNetwork(parsedArgs, nodeConfig); const instantiatedAccounts = getNetworkInstantiatedAccounts(nodeConfig); const accountsInfo = await getAccountsInfo(parsedArgs, tezos, instantiatedAccounts); if (accountsInfo.length === 0) { return sendJsonRes2( `All instantiated accounts in the current environment, "${parsedArgs.env}", are funded up to or beyond the declared amount` ); } await performTransferOps(tezos, getCurrentEnvironment2(parsedArgs), accountsInfo, parsedArgs.timeout); const accountsInfoForDisplay = prepAccountsInfoForDisplay(accountsInfo); return sendJsonRes2(accountsInfoForDisplay); } catch { return sendAsyncErr3("No operations performed"); } }; var fund_default = fund; // instantiate_account.ts import { getCurrentEnvironmentConfig as getCurrentEnvironmentConfig3, sendAsyncErr as sendAsyncErr4, sendJsonRes as sendJsonRes3, sendWarn as sendWarn2 } from "@taqueria/node-sdk"; var instantiate_account = async (parsedArgs) => { const env = getCurrentEnvironmentConfig3(parsedArgs); if (!env) return sendAsyncErr4(`There is no environment called ${parsedArgs.env} in your config.json`); try { const [envType, nodeConfig] = await getEnvTypeAndNodeConfig(parsedArgs, env); if (envType !== "Network") { return sendAsyncErr4("taq instantiate-account can only be executed in a network environment"); } const declaredAccountAliases = Object.keys(getDeclaredAccounts(parsedArgs)); const instantiatedAccounts = getNetworkInstantiatedAccounts(nodeConfig); let accountsInstantiated = []; for (const declaredAccountAlias of declaredAccountAliases) { if (!instantiatedAccounts.hasOwnProperty(declaredAccountAlias)) { await generateAccountKeys(parsedArgs, nodeConfig, declaredAccountAlias); accountsInstantiated.push(declaredAccountAlias); } else { sendWarn2( `Note: ${declaredAccountAlias} is already instantiated in the current environment, "${parsedArgs.env}"` ); } } if (accountsInstantiated.length !== 0) { return sendJsonRes3( `Accounts instantiated: ${accountsInstantiated.join(", ")}. Please execute "taq fund" targeting the same environment to fund these accounts` ); } else { return sendJsonRes3( `No accounts were instantiated because they were all instantiated in the target environment already` ); } } catch (err) { await sendAsyncErr4("No operations performed"); if (parsedArgs.debug) await sendAsyncErr4(String(err)); } }; var instantiate_account_default = instantiate_account; // originate.ts import { addTzExtensionIfMissing, getArtifactsDir, getContractContent, getCurrentEnvironment as getCurrentEnvironment3, getCurrentEnvironmentConfig as getCurrentEnvironmentConfig4, NonEmptyString, RequestArgs as RequestArgs5, sendAsyncErr as sendAsyncErr5, sendJsonRes as sendJsonRes4, updateAddressAlias } from "@taqueria/node-sdk"; import { basename, extname, join } from "path"; var getDefaultStorageFilename = (contractName) => { const baseFilename = basename(contractName, extname(contractName)); const extFilename = extname(contractName); const defaultStorage = `${baseFilename}.default_storage${extFilename}`; return defaultStorage; }; var getContractInfo2 = async (parsedArgs) => { const contract = parsedArgs.contract; const protocolArgs = RequestArgs5.create(parsedArgs); const contractWithTzExtension = addTzExtensionIfMissing(contract); const contractCode = await getContractContent(protocolArgs, contractWithTzExtension); if (contractCode === void 0) { return sendAsyncErr5( `Please generate ${contractWithTzExtension} with one of the compilers (LIGO, SmartPy, Archetype) or write it manually and put it under /${parsedArgs.config.artifactsDir} ` ); } const storageFilename = parsedArgs.storage ?? getDefaultStorageFilename(contractWithTzExtension); const contractInitStorage = await getContractContent(protocolArgs, storageFilename); if (contractInitStorage === void 0) { return sendAsyncErr5( `\u274C No initial storage file was found for ${contractWithTzExtension} Storage must be specified in a file as a Michelson expression and will automatically be linked to this contract if specified with the name "${getDefaultStorageFilename(contractWithTzExtension)}" in the artifacts directory You can also manually pass a storage file to the originate task using the --storage STORAGE_FILE_NAME option ` ); } return { contract, code: contractCode, initStorage: contractInitStorage.trim(), mutezTransfer: parseInt(parsedArgs.mutez ?? "0") }; }; var performOriginateOps = async (tezos, env, contractsInfo, maxTimeout, isSandbox = false, gasLimit, storageLimit, fee) => { try { const result = await tezos.wallet.originate({ code: contractsInfo.code, init: contractsInfo.initStorage, balance: contractsInfo.mutezTransfer.toString(), mutez: true, fee, gasLimit, storageLimit }); const op = await result.send(); await op.confirmation(isSandbox ? 1 : 3); return op; } catch (err) { return handleOpsError(err, env); } }; var prepContractInfoForDisplay2 = async (parsedArgs, tezos, contractInfo, op) => { var _a, _b, _c; const protocolArgs = RequestArgs5.create(parsedArgs); const operationResults = await op.operationResults(); const originationResults = (operationResults ?? []).filter((result2) => result2.kind === "origination").map((result2) => result2); const result = originationResults.length === 1 ? originationResults[0] : void 0; const address = (_c = (_b = (_a = result == null ? void 0 : result.metadata) == null ? void 0 : _a.operation_result) == null ? void 0 : _b.originated_contracts) == null ? void 0 : _c.join(","); const alias = parsedArgs.alias ?? basename(contractInfo.contract, extname(contractInfo.contract)); const displayableAddress = address ?? "Something went wrong during origination"; if (address) { const validatedAddress = NonEmptyString.create(address); await updateAddressAlias(protocolArgs, alias, validatedAddress); } return { contract: contractInfo.contract, address: displayableAddress, alias: address ? alias : "N/A" // destination: tezos.rpc.getRpcUrl(), }; }; var originate = async (parsedArgs) => { const protocolArgs = RequestArgs5.create(parsedArgs); const env = getCurrentEnvironmentConfig4(protocolArgs); if (!env) return sendAsyncErr5(`There is no environment called ${parsedArgs.env} in your config.json`); try { const [envType, nodeConfig] = await getEnvTypeAndNodeConfig(protocolArgs, env); const tezos = await (envType === "Network" ? configureToolKitForNetwork(protocolArgs, nodeConfig, parsedArgs.sender) : configureToolKitForSandbox(nodeConfig, parsedArgs.sender)); const contractInfo = await getContractInfo2(parsedArgs); const op = await performOriginateOps( tezos, getCurrentEnvironment3(protocolArgs), contractInfo, parsedArgs.timeout, envType !== "Network", parsedArgs.gasLimit, parsedArgs.storageLimit, parsedArgs.fee ); const contractInfoForDisplay = await prepContractInfoForDisplay2(parsedArgs, tezos, contractInfo, op); return sendJsonRes4([contractInfoForDisplay]); } catch (e) { if (e instanceof Error && e.message.includes("503")) { try { const [envType, nodeConfig] = await getEnvTypeAndNodeConfig(protocolArgs, env); if (envType === "Network" && nodeConfig.rpcUrl.includes("ghostnet")) { return sendAsyncErr5( `\u274C Ghostnet is returning 503 errors, indicating that the server is under heavy load. Please try again later.` ); } return sendAsyncErr5( `\u274C The node you are trying to connect to is not available Please check if the node is running and the URL is correct in your config.json` ); } catch { } } return sendAsyncErr5("No operations performed"); } }; var originate_default = originate; // main.ts var main = (parsedArgs) => { const unsafeArgs = parsedArgs; switch (unsafeArgs.task) { case "deploy": return originate_default(unsafeArgs); case "transfer": return transfer_default(unsafeArgs); case "instantiate-account": return instantiate_account_default(parsedArgs); case "fund": return fund_default(unsafeArgs); default: return sendAsyncErr6(`${unsafeArgs} is not an understood task by the Taquito plugin`); } }; var main_default = main; // index.ts Plugin.create((_i18n) => ({ alias: "taquito", schema: "1.0", version: "0.1", tasks: [ Task.create({ task: "deploy", command: "deploy <contract>", description: "Deploy a smart contract to a particular environment", options: [ Option.create({ flag: "alias", description: "Alias used to refer to the deployed contract's address", required: false }), Option.create({ flag: "storage", description: "Name of the storage file that contains the storage value as a Michelson expression, in the artifacts directory, used for originating a contract", required: false }), Option.create({ flag: "sender", description: "Name of an instantiated account to use as the sender of the originate operation", required: false }), Option.create({ flag: "mutez", description: "Amount of Mutez to transfer", required: false }), Option.create({ flag: "timeout", shortFlag: "t", defaultValue: 40, description: "Number of seconds to elapse before abandoning the operation (to avoid congestion and network failures)", required: false }), Option.create({ flag: "gasLimit", shortFlag: "g", description: "Gas limit per contract/origination specified in mutez", required: false }), Option.create({ flag: "storageLimit", shortFlag: "s", description: "Storage limit per contract/origination specified in mutez", required: false }), Option.create({ flag: "fee", shortFlag: "f", description: "Fee per contract/origination specified in mutez", required: false }) ], aliases: ["originate"], handler: "proxy", encoding: "application/json" }), Task.create({ task: "transfer", command: "transfer <contract>", description: "Transfer/call an implicit account or a smart contract (specified via its alias or address) deployed to a particular environment", options: [ Option.create({ flag: "mutez", description: "Amount of Mutez to transfer", required: false }), Option.create({ flag: "param", description: "Name of the parameter file that contains the parameter value as a Michelson expression, in the artifacts directory, used for invoking a deployed contract", required: false }), Option.create({ flag: "entrypoint", description: "You may explicitly specify an entrypoint to make the parameter value shorter, without having to specify a chain of (Left (Right ... 14 ...))", required: false }), Option.create({ flag: "sender", description: "Name of an instantiated account to use as the sender of the transfer operation", required: false }), Option.create({ flag: "timeout", shortFlag: "t", defaultValue: 40, description: "Number of retry attempts (to avoid congestion and network failures)", required: false }), Option.create({ flag: "gasLimit", shortFlag: "g", description: "Gas limit per contract/origination specified in mutez", required: false }), Option.create({ flag: "storageLimit", shortFlag: "s", description: "Storage limit per contract/origination specified in mutez", required: false }), Option.create({ flag: "fee", shortFlag: "f", description: "Fee per contract/origination specified in mutez", required: false }) ], aliases: ["call"], handler: "proxy", encoding: "application/json" }), Task.create({ task: "fund", command: "fund", description: "Fund all the instantiated accounts up to the desired/declared amount in a target environment", handler: "proxy", encoding: "application/json", options: [ Option.create({ flag: "timeout", shortFlag: "t", defaultValue: 40, description: "Number of retry attempts (to avoid congestion and network failures)", required: false }) ] }), Task.create({ task: "instantiate-account", command: "instantiate-account", description: 'Instantiate all accounts declared in the "accounts" field at the root level of the config file to a target environment', handler: "proxy", encoding: "application/json" }) ], proxy: main_default }), process.argv); //# sourceMappingURL=index.mjs.map