@taqueria/plugin-taquito
Version:
A taqueria plugin for originating smart contracts using Taquito
649 lines (639 loc) • 25.3 kB
JavaScript
// 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,
getFaucetUrl,
getNetworkConfig,
getSandboxConfig,
sendAsyncErr,
TAQ_OPERATOR_ACCOUNT
} from "@taqueria/node-sdk";
import { InMemorySigner } from "@taquito/signer";
import { importKey, 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) {
const faucetUrl = getFaucetUrl(env);
return sendAsyncErr(
`The account ${publicKeyHash} for the target environment, "${env}", may not be funded
To fund this account:
1. Go to ${faucetUrl}
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)
Alternatively, use --sender <accountAlias> if you have an already-funded account.`
);
}
}
}
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