Big News: Socket raises $60M Series C at a $1B valuation to secure software supply chains for AI-driven development.Announcement
Sign In

@stacks/rendezvous

Package Overview
Dependencies
Maintainers
4
Versions
25
Alerts
File Explorer

Advanced tools

Socket logo

Install Socket

Detect and block malicious and high-risk dependencies

Install

@stacks/rendezvous - npm Package Compare versions

Comparing version
0.12.0
to
0.13.0
+2
-30
dist/app.js

@@ -12,11 +12,7 @@ #!/usr/bin/env node

};
var __importDefault = (this && this.__importDefault) || function (mod) {
return (mod && mod.__esModule) ? mod : { "default": mod };
};
Object.defineProperty(exports, "__esModule", { value: true });
exports.tryParseRemoteDataSettings = exports.getManifestFileName = exports.noRemoteData = void 0;
exports.getManifestFileName = void 0;
exports.main = main;
const path_1 = require("path");
const events_1 = require("events");
const toml_1 = __importDefault(require("toml"));
const property_1 = require("./property");

@@ -35,9 +31,2 @@ const invariant_1 = require("./invariant");

/**
* The object used to initialize an empty simnet session with, when no remote
* data is enabled in the `Clarinet.toml` file.
*/
exports.noRemoteData = {
enabled: false,
};
/**
* Gets the manifest file name for a Clarinet project.

@@ -58,18 +47,2 @@ * If a custom manifest exists (`Clarinet-<contract-name>.toml`), it is used.

exports.getManifestFileName = getManifestFileName;
const tryParseRemoteDataSettings = (manifestPath, radio) => {
var _a, _b;
const clarinetToml = toml_1.default.parse((0, fs_1.readFileSync)((0, path_1.resolve)(manifestPath), "utf-8"));
const remoteDataUserSettings = (_b = (_a = clarinetToml.repl) === null || _a === void 0 ? void 0 : _a.remote_data) !== null && _b !== void 0 ? _b : undefined;
if (remoteDataUserSettings && (remoteDataUserSettings === null || remoteDataUserSettings === void 0 ? void 0 : remoteDataUserSettings.enabled) === true) {
radio.emit("logMessage", (0, ansicolor_1.yellow)("\nUsing remote data. Setting up the environment can take up to a minute..."));
}
// If no remote data settings are provided, we still need to return an object
// with the `enabled` property set to `false`. That is what simnet expects
// at least in order to initialize an empty simnet session.
if (!remoteDataUserSettings) {
return exports.noRemoteData;
}
return remoteDataUserSettings;
};
exports.tryParseRemoteDataSettings = tryParseRemoteDataSettings;
const helpMessage = `

@@ -175,4 +148,3 @@ rv v${package_json_1.version}

}
const remoteDataSettings = (0, exports.tryParseRemoteDataSettings)(manifestPath, radio);
const simnet = yield (0, citizen_1.issueFirstClassCitizenship)(runConfig.manifestDir, manifestPath, remoteDataSettings, runConfig.sutContractName);
const simnet = yield (0, citizen_1.issueFirstClassCitizenship)(runConfig.manifestDir, manifestPath, runConfig.sutContractName, radio);
/**

@@ -179,0 +151,0 @@ * The list of contract IDs for the SUT contract names, as per the simnet.

+1
-1
"use strict";
Object.defineProperty(exports, "__esModule", { value: true });
// This file is a placeholder for the Rendezvous CLI-related types.

@@ -15,167 +15,116 @@ "use strict";

Object.defineProperty(exports, "__esModule", { value: true });
exports.getSbtcBalancesFromSimnet = exports.getTestContractSource = exports.buildRendezvousData = exports.getContractSource = exports.deployContracts = exports.groupContractsByEpochFromDeploymentPlan = exports.issueFirstClassCitizenship = void 0;
exports.getTestContractSource = exports.buildRendezvousData = exports.issueFirstClassCitizenship = void 0;
exports.scheduleRendezvous = scheduleRendezvous;
const fs_1 = require("fs");
const path_1 = require("path");
const toml_1 = __importDefault(require("toml"));
const os_1 = require("os");
const toml_1 = require("@iarna/toml");
const yaml_1 = __importDefault(require("yaml"));
const clarinet_sdk_1 = require("@stacks/clarinet-sdk");
const transactions_1 = require("@stacks/transactions");
const ansicolor_1 = require("ansicolor");
/**
* Prepares the simnet instance and assures the target contract's corresponding
* test contract is treated as a first-class citizen, relying on their
* concatenation. This function handles:
* - Contract sorting by epoch based on the deployment plan.
* - Combining the target contract with its tests and deploying all contracts
* to the simnet.
* Prepares the simnet with the Rendezvous tests as first-class citizens of the
* target contract.
*
* This function works in an isolated temporary copy of the Clarinet project
* in /tmp/ to avoid lingering temporary files in the user's project directory.
* In case of system crashes, power outages, etc., the temp directory is
* automatically cleaned up by the OS on reboot.
*
* @param manifestDir The relative path to the manifest directory.
* @param manifestPath The absolute path to the manifest file.
* @param remoteDataSettings The remote data settings.
* @param manifestPath The path to the manifest file.
* @param sutContractName The target contract name.
* @returns The initialized simnet instance with all contracts deployed, with
* the test contract treated as a first-class citizen of the target contract.
* @param radio The event emitter to send log messages to.
* @returns The initialized simnet.
*/
const issueFirstClassCitizenship = (manifestDir, manifestPath, remoteDataSettings, sutContractName) => __awaiter(void 0, void 0, void 0, function* () {
var _a;
// Initialize the simnet, to generate the deployment plan and instance. The
// empty session will be set up, and contracts will be deployed in the
// correct order based on the deployment plan a few lines below.
const simnet = yield (0, clarinet_sdk_1.initSimnet)(manifestPath);
const issueFirstClassCitizenship = (manifestDir, manifestPath, sutContractName, radio) => __awaiter(void 0, void 0, void 0, function* () {
var _a, _b, _c, _d;
// First simnet initialization: This will generate the deployment plan and
// will type check the project without any Rendezvous tests.
try {
radio.emit("logMessage", `\nType-checking your Clarinet project...`);
yield (0, clarinet_sdk_1.generateDeployement)(manifestPath);
}
catch (error) {
throw new Error(`Error initializing simnet: ${(_a = error.message) !== null && _a !== void 0 ? _a : error}`);
}
const deploymentPlan = yaml_1.default.parse((0, fs_1.readFileSync)((0, path_1.join)(manifestDir, "deployments", "default.simnet-plan.yaml"), {
encoding: "utf-8",
}));
const sortedContractsByEpoch = (0, exports.groupContractsByEpochFromDeploymentPlan)(deploymentPlan);
const simnetAddresses = [...simnet.getAccounts().values()];
const stxBalancesMap = new Map(simnetAddresses.map((address) => {
const balanceHex = simnet.runSnippet(`(stx-get-balance '${address})`);
return [address, (0, transactions_1.cvToValue)((0, transactions_1.hexToCV)(balanceHex))];
}));
// If the sbtc-token contract is included in the deployment plan, we need to
// restore the sBTC balances. Otherwise, use an empty map.
const sbtcBalancesMap = (0, exports.getSbtcBalancesFromSimnet)(simnet, deploymentPlan, remoteDataSettings);
yield simnet.initEmptySession(remoteDataSettings);
simnetAddresses.forEach((address) => {
simnet.mintSTX(address, stxBalancesMap.get(address));
});
// Combine the target contract with its tests into a single contract. The
// resulting contract will replace the target contract in the simnet.
/** The contract names mapped to the concatenated source code. */
const rendezvousSources = new Map([sutContractName]
// For each target contract name, execute the processing steps to get the
// concatenated contract source code and the contract ID.
.map((contractName) => (0, exports.buildRendezvousData)(deploymentPlan, contractName, manifestDir))
// Use the contract ID as a key, mapping to the concatenated contract
// source code.
.map((rendezvousContractData) => [
rendezvousContractData.rendezvousContractId,
rendezvousContractData.rendezvousSourceCode,
]));
const clarinetToml = toml_1.default.parse((0, fs_1.readFileSync)(manifestPath, { encoding: "utf-8" }));
const cacheDir = ((_a = clarinetToml.project) === null || _a === void 0 ? void 0 : _a.cache_dir) || "./.cache";
// Deploy the contracts to the empty simnet session in the correct order.
yield (0, exports.deployContracts)(simnet, sortedContractsByEpoch, manifestDir, cacheDir, (name, sender, props) => (0, exports.getContractSource)([sutContractName], rendezvousSources, name, sender, props, manifestDir));
// Filter out addresses with zero balance. They do not need to be restored.
const sbtcBalancesToRestore = new Map([...sbtcBalancesMap.entries()].filter(([_, balance]) => balance !== 0));
// After all the contracts and requirements are deployed, if the test wallets
// had sBTC balances previously, restore them. If no test wallet previously
// owned sBTC, skip this step.
if ([...sbtcBalancesToRestore.keys()].length > 0) {
restoreSbtcBalances(simnet, sbtcBalancesToRestore);
const parsedManifest = (0, toml_1.parse)((0, fs_1.readFileSync)(manifestPath, { encoding: "utf-8" }));
const cacheDir = (_c = (_b = parsedManifest.project) === null || _b === void 0 ? void 0 : _b.cache_dir) !== null && _c !== void 0 ? _c : "./.cache";
const rendezvousData = (0, exports.buildRendezvousData)(cacheDir, deploymentPlan, sutContractName, manifestDir);
// Create isolated temp directory for the Rendezvous testing run.
const tempProjectDir = (0, fs_1.mkdtempSync)((0, path_1.join)((0, os_1.tmpdir)(), "rendezvous-run-"));
(0, fs_1.cpSync)(manifestDir, tempProjectDir, { recursive: true });
const [, contractName] = rendezvousData.rendezvousContractId.split(".");
const rendezvousContractsDir = (0, path_1.join)(tempProjectDir, "contracts");
const rendezvousPath = (0, path_1.join)(rendezvousContractsDir, `${contractName}-rendezvous.clar`);
(0, fs_1.writeFileSync)(rendezvousPath, rendezvousData.rendezvousSourceCode);
radio.emit("logMessage", `\nType-checking your Rendezvous project...`);
// Update the manifest in the temp directory to point to the Rendezvous
// concatenation.
const manifestFileName = (0, path_1.basename)(manifestPath);
const tempManifestPath = (0, path_1.join)(tempProjectDir, manifestFileName);
const tempParsedManifest = (0, toml_1.parse)((0, fs_1.readFileSync)(tempManifestPath, { encoding: "utf-8" }));
if (!tempParsedManifest.contracts) {
tempParsedManifest.contracts = {};
}
return simnet;
});
exports.issueFirstClassCitizenship = issueFirstClassCitizenship;
/**
* Groups contracts by epoch from the deployment plan.
* @param deploymentPlan The parsed deployment plan.
* @returns A record of contracts grouped by epoch. The record key is the epoch
* string, and the value is an array of contracts. Each contract is represented
* as a record with the contract name as the key and a record containing the
* contract path and clarity version as the value.
*/
const groupContractsByEpochFromDeploymentPlan = (deploymentPlan) => {
return deploymentPlan.plan.batches.reduce((acc, batch) => {
const epoch = batch.epoch;
const contracts = batch.transactions
.filter((tx) => tx["emulated-contract-publish"])
.map((tx) => {
const contract = tx["emulated-contract-publish"];
return {
[contract["contract-name"]]: {
path: contract.path,
clarity_version: contract["clarity-version"],
},
};
});
if (contracts.length > 0) {
acc[epoch] = (acc[epoch] || []).concat(contracts);
if (!tempParsedManifest.contracts[sutContractName]) {
tempParsedManifest.contracts[sutContractName] = {};
}
const relativeRendezvousPath = (0, path_1.relative)(tempProjectDir, rendezvousPath);
tempParsedManifest.contracts[sutContractName] = {
epoch: ((_d = tempParsedManifest.contracts[sutContractName].epoch) !== null && _d !== void 0 ? _d : "latest"),
path: relativeRendezvousPath,
};
// Convert epoch values to strings for TOML compatibility.
for (const contractName in tempParsedManifest.contracts) {
const contract = tempParsedManifest.contracts[contractName];
if ((contract === null || contract === void 0 ? void 0 : contract.epoch) && typeof contract.epoch === "number") {
contract.epoch = String(contract.epoch);
}
return acc;
}, {});
};
exports.groupContractsByEpochFromDeploymentPlan = groupContractsByEpochFromDeploymentPlan;
/**
* Deploys the contracts to the simnet in the correct order.
* @param simnet The simnet instance.
* @param contractsByEpoch The record of contracts by epoch.
* @param getContractSourceFn The function to retrieve the contract source.
*/
const deployContracts = (simnet, contractsByEpoch, manifestDir, cacheDir, getContractSourceFn) => __awaiter(void 0, void 0, void 0, function* () {
for (const [epoch, contracts] of Object.entries(contractsByEpoch)) {
// Move to the next epoch and deploy the contracts in the correct order.
simnet.setEpoch(epoch);
for (const contract of contracts.flatMap(Object.entries)) {
const [name, props] = contract;
// Resolve paths to absolute for proper comparison.
const absoluteContractPath = (0, path_1.resolve)(manifestDir, props.path);
const absoluteRequirementsPath = (0, path_1.resolve)(manifestDir, cacheDir, "requirements");
// Check if contract is in requirements directory.
const isRequirement = absoluteContractPath.startsWith(absoluteRequirementsPath);
const sender = isRequirement
? (0, path_1.basename)(props.path).split(".")[0]
: simnet.deployer;
const source = getContractSourceFn(name, sender, props);
simnet.deployContract(name, source, { clarityVersion: props.clarity_version }, sender);
}
(0, fs_1.writeFileSync)(tempManifestPath, (0, toml_1.stringify)(tempParsedManifest));
// Final simnet initialization: This will initialize the simnet with the
// target contract containing Rendezvous tests as first-class citizens.
//
// Windows cannot initialize simnet with absolute paths. Use relative path
// from the temp project directory.
// See: https://github.com/stx-labs/clarinet/issues/1634
const originalCwd = process.cwd();
try {
// Change the current working directory to the temp project directory.
// This is necessary because the simnet initialization requires the
// manifest file to be in the current working directory.
process.chdir(tempProjectDir);
// Initialize the simnet while suppressing stdout to avoid polluting output.
// Errors are still printed to stderr to help troubleshoot issues.
const originalWrite = process.stdout.write;
process.stdout.write = () => true;
try {
const simnet = yield (0, clarinet_sdk_1.initSimnet)(manifestFileName);
return simnet;
}
finally {
// Restore stdout.
process.stdout.write = originalWrite;
}
}
});
exports.deployContracts = deployContracts;
/**
* Conditionally retrieves the contract source based on whether the contract is
* a SUT contract or not.
* @param targetContractNames The list of target contract names.
* @param rendezvousSourcesMap The target contract IDs mapped to the resulting
* concatenated source code.
* @param contractName The contract name.
* @param contractSender The emulated sender of the contract according to the
* deployment plan.
* @param contractProps The contract deployment properties.
* @param manifestDir The relative path to the manifest directory.
* @returns The contract source code.
*/
const getContractSource = (targetContractNames, rendezvousSourcesMap, contractName, contractSender, contractProps, manifestDir) => {
const contractId = `${contractSender}.${contractName}`;
// Checking if a contract is a SUT one just by using the name is not enough.
// There can be multiple contracts with the same name, but different senders
// in the deployment plan. The contract ID is the unique identifier used to
// store the concatenated Rendezvous source codes in the
// `rendezvousSourcesMap`.
if (targetContractNames.includes(contractName) &&
rendezvousSourcesMap.has(contractId)) {
const contractSource = rendezvousSourcesMap.get(contractId);
if (!contractSource) {
throw new Error(`Contract source not found for ${contractName}`);
finally {
// Restore the original current working directory.
process.chdir(originalCwd);
// Cleanup the temp project directory.
try {
(0, fs_1.rmSync)(tempProjectDir, { recursive: true, force: true });
}
return contractSource;
catch (error) {
radio.emit("logMessage", (0, ansicolor_1.yellow)(`Error cleaning up temporary project directory ${tempProjectDir}: ${error.message}. Remove it manually to avoid unnecessary disk space usage.`));
}
}
else {
return (0, fs_1.readFileSync)((0, path_1.join)(manifestDir, contractProps.path), {
encoding: "utf-8",
});
}
};
exports.getContractSource = getContractSource;
});
exports.issueFirstClassCitizenship = issueFirstClassCitizenship;
/**
* Builds the Rendezvous data.
* @param cacheDir The cache directory path.
* @param deploymentPlan The parsed deployment plan.

@@ -187,6 +136,6 @@ * @param contractName The contract name.

*/
const buildRendezvousData = (deploymentPlan, contractName, manifestDir) => {
const buildRendezvousData = (cacheDir, deploymentPlan, contractName, manifestDir) => {
try {
const sutContractSource = getDeploymentPlanContractSource(deploymentPlan, contractName, manifestDir);
const testContractSource = (0, exports.getTestContractSource)(deploymentPlan, contractName, manifestDir);
const testContractSource = (0, exports.getTestContractSource)(cacheDir, deploymentPlan, contractName, manifestDir);
const rendezvousSource = scheduleRendezvous(sutContractSource, testContractSource);

@@ -221,39 +170,2 @@ const rendezvousContractEmulatedSender = getSutContractDeploymentPlanEmulatedPublish(deploymentPlan, contractName)["emulated-sender"];

/**
* Retrieves the test contract source code.
* @param deploymentPlan The parsed deployment plan.
* @param sutContractName The target contract name.
* @param manifestDir The relative path to the manifest directory.
* @returns The test contract source code.
*/
const getTestContractSource = (deploymentPlan, sutContractName, manifestDir) => {
const sutContractPath = getSutContractDeploymentPlanEmulatedPublish(deploymentPlan, sutContractName).path;
const clarityExtension = ".clar";
if (!sutContractPath.endsWith(clarityExtension)) {
throw new Error(`Invalid contract extension for the "${sutContractName}" contract.`);
}
// If the sutContractPath is located under .cache/requirements/ path, search
// for the test contract in the classic `contracts` directory.
if (sutContractPath.includes(".cache")) {
const relativePath = sutContractPath.split(".cache/requirements/")[1];
const relativePathTestContract = relativePath.replace(clarityExtension, `.tests${clarityExtension}`);
return (0, fs_1.readFileSync)((0, path_1.join)(manifestDir, "contracts", relativePathTestContract), {
encoding: "utf-8",
}).toString();
}
// If the contract is not under the `.cache/requirements/` path, we assume it
// is located in a regular path specified in the manifest file. Just search
// for the test contract near the SUT one, following the naming
// convention: `<contract-name>.tests.clar`.
const testContractPath = sutContractPath.replace(clarityExtension, `.tests${clarityExtension}`);
try {
return (0, fs_1.readFileSync)((0, path_1.join)(manifestDir, testContractPath), {
encoding: "utf-8",
}).toString();
}
catch (error) {
throw new Error(`Error retrieving the corresponding test contract for the "${sutContractName}" contract. ${error.message}`);
}
};
exports.getTestContractSource = getTestContractSource;
/**
* Retrieves the emulated contract publish data of the target contract from the

@@ -294,2 +206,5 @@ * deployment plan. If multiple contracts share the same name in the deployment

deployer)) === null || _b === void 0 ? void 0 : _b["emulated-contract-publish"];
// TODO: Consider handling requirements and project contracts separately.
// Eventually let the user specify if the contract is a requirement or a
// project contract.
// This is an edge case that can happen in practice. If the project has two

@@ -312,2 +227,83 @@ // requirements that share the same contract name, Rendezvous will not be

/**
* Retrieves the test contract source code for a project contract.
* @param contractPath The relative path to the contract.
* @param manifestDir The relative path to the manifest directory.
* @returns The test contract source code or `null` if the test contract is not
* found.
*/
const getProjectContractTestSrc = (contractPath, manifestDir) => {
const clarityExtension = ".clar";
const lastExtensionIndex = contractPath.lastIndexOf(clarityExtension);
const testContractPath = lastExtensionIndex !== -1
? contractPath.slice(0, lastExtensionIndex) +
`.tests${clarityExtension}` +
contractPath.slice(lastExtensionIndex + clarityExtension.length)
: `${contractPath}.tests${clarityExtension}`;
try {
const fullPath = (0, path_1.join)(manifestDir, testContractPath);
const content = (0, fs_1.readFileSync)(fullPath, {
encoding: "utf-8",
}).toString();
return content;
}
catch (error) {
return null;
}
};
/**
* Retrieves the test contract source code for a requirement contract. It
* searches for the test contract in the `contracts` directory of the Clarinet
* project.
* @param cacheDir The cache directory path.
* @param sutContractPath The path to the SUT contract.
* @param manifestDir The relative path to the manifest directory.
* @returns The test contract source code or `null` if the test contract is not
* found.
*/
const getRequirementContractTestSrc = (cacheDir, sutContractPath, manifestDir) => {
const normalizedCacheDir = cacheDir.replace(/[\/\\]$/, "");
const requirementsRelativePath = `${normalizedCacheDir}/requirements/`;
if (!sutContractPath.includes(requirementsRelativePath)) {
return null;
}
const relativePath = sutContractPath.split(requirementsRelativePath)[1];
const clarityExtension = ".clar";
const lastExtensionIndex = relativePath.lastIndexOf(clarityExtension);
const relativePathTestContract = lastExtensionIndex !== -1
? relativePath.slice(0, lastExtensionIndex) +
`.tests${clarityExtension}` +
relativePath.slice(lastExtensionIndex + clarityExtension.length)
: `${relativePath}.tests${clarityExtension}`;
return (0, fs_1.readFileSync)((0, path_1.join)(manifestDir, "contracts", relativePathTestContract), {
encoding: "utf-8",
}).toString();
};
/**
* Retrieves the test contract source code.
* Project contracts have priority. Requirement contracts are only checked
* if project contract test is not found.
* @param cacheDir The cache directory path.
* @param deploymentPlan The parsed deployment plan.
* @param sutContractName The target contract name.
* @param manifestDir The relative path to the manifest directory.
* @returns The test contract source code.
*/
const getTestContractSource = (cacheDir, deploymentPlan, sutContractName, manifestDir) => {
const sutContractPath = getSutContractDeploymentPlanEmulatedPublish(deploymentPlan, sutContractName).path;
// Prioritize project contracts. Try project contract test first.
const projectTestContract = getProjectContractTestSrc(sutContractPath, manifestDir);
if (projectTestContract !== null) {
return projectTestContract;
}
// Fallback to requirement contract test if project contract test not found.
const normalizedCacheDir = cacheDir || "./.cache";
const requirementTestContract = getRequirementContractTestSrc(normalizedCacheDir, sutContractPath, manifestDir);
if (requirementTestContract !== null) {
return requirementTestContract;
}
// No corresponding test contract was found for the SUT contract.
throw new Error(`Error retrieving the corresponding test contract for the "${sutContractName}" contract.`);
};
exports.getTestContractSource = getTestContractSource;
/**
* Schedules a Rendezvous between the System Under Test (`SUT`) and the test

@@ -333,135 +329,1 @@ * contract.

}
/**
* Checks if a contract can be found in the deployment plan.
* @param deploymentPlan The parsed deployment plan.
* @param contractAddress The address of the contract.
* @param contractName The name of the contract.
* @returns True if the contract can be found in the deployment plan, false
* otherwise.
*/
const isContractInDeploymentPlan = (deploymentPlan, contractAddress, contractName) => {
return deploymentPlan.plan.batches.some((batch) => batch.transactions.some((transaction) => {
var _a, _b;
return ((_a = transaction["emulated-contract-publish"]) === null || _a === void 0 ? void 0 : _a["contract-name"]) ===
contractName &&
((_b = transaction["emulated-contract-publish"]) === null || _b === void 0 ? void 0 : _b["emulated-sender"]) ===
contractAddress;
}));
};
/**
* Maps the simnet accounts to their sBTC balances. The function tries to call
* the `get-balance` function of the `sbtc-token` contract for each address. If
* the call fails, it returns a balance of 0 for that address. The call fails
* if the user is not working with sBTC.
* @param simnet The simnet instance.
* @param deploymentPlan The parsed deployment plan.
* @param remoteDataSettings The remote data settings.
* @returns A map of addresses to their sBTC balances.
*/
const getSbtcBalancesFromSimnet = (simnet, deploymentPlan, remoteDataSettings) => {
// If the user is not using remote data and the deployment plan does not
// contain the `sbtc-token` contract, return a map with 0 balances for all
// addresses. When remote data is enabled, the sbtc-token contract will not
// necessarily be present in the deployment plan.
if (!remoteDataSettings.enabled &&
!isContractInDeploymentPlan(deploymentPlan, "SM3VDXK3WZZSA84XXFKAFAF15NNZX32CTSG82JFQ4", "sbtc-token")) {
return new Map([...simnet.getAccounts().values()].map((address) => [address, 0]));
}
return new Map([...simnet.getAccounts().values()].map((address) => {
try {
const { result: getBalanceResult } = simnet.callReadOnlyFn("SM3VDXK3WZZSA84XXFKAFAF15NNZX32CTSG82JFQ4.sbtc-token", "get-balance", [transactions_1.Cl.principal(address)], address);
// If the previous read-only call works, the user is working with
// sBTC. This means we can proceed with restoring sBTC balances.
const sbtcBalanceJSON = (0, transactions_1.cvToJSON)(getBalanceResult);
// The `get-balance` function returns a response containing the uint
// balance of the address. In the JSON representation, the balance is
// represented as a string. We need to parse it to an integer.
const sbtcBalance = parseInt(sbtcBalanceJSON.value.value, 10);
return [address, sbtcBalance];
}
catch (e) {
return [address, 0];
}
}));
};
exports.getSbtcBalancesFromSimnet = getSbtcBalancesFromSimnet;
/**
* Utility function that restores the test wallets' initial sBTC balances in
* the re-initialized first-class citizenship simnet.
*
* @param simnet The simnet instance.
* @param sbtcBalancesMap A map containing the test wallets' balances to be
* restored.
*/
const restoreSbtcBalances = (simnet, sbtcBalancesMap) => {
// For each address present in the balances map, restore the balance.
[...sbtcBalancesMap.entries()]
// Re-assure the map does not contain nil balances.
.filter(([_, balance]) => balance !== 0)
.forEach(([address, balance]) => {
// To deposit sBTC, one needs a txId and a sweep txId. A deposit transaction
// must have a unique txId and sweep txId.
const txId = getUniqueHex();
const sweepTxId = getUniqueHex();
mintSbtc(simnet, balance, address, txId, sweepTxId);
});
};
/**
* Utility function to deposit an amount of sBTC to a Stacks address.
*
* @param simnet The simnet instance.
* @param amountSats The amount to mint in sats.
* @param recipient The Stacks address to mint sBTC to.
* @param txId A unique hex to use for the deposit.
* @param sweepTxId A unique hex to use for the deposit.
*/
const mintSbtc = (simnet, amountSats, recipient, txId, sweepTxId) => {
// Calling `get-burn-block-info?` only works for past burn heights. We mine
// one empty Bitcoin block if the initial height is 0 and use the previous
// burn height to retrieve the burn header hash.
if (simnet.burnBlockHeight === 0) {
simnet.mineEmptyBurnBlock();
}
const previousBurnHeight = simnet.burnBlockHeight - 1;
const burnHash = (0, transactions_1.hexToCV)(simnet.runSnippet(`(get-burn-block-info? header-hash u${previousBurnHeight})`));
if (burnHash === null || burnHash.type === transactions_1.ClarityType.OptionalNone) {
throw new Error("Something went wrong trying to retrieve the burn header.");
}
const completeDepositTx = (0, transactions_1.cvToJSON)(simnet.callPublicFn("SM3VDXK3WZZSA84XXFKAFAF15NNZX32CTSG82JFQ4.sbtc-deposit", "complete-deposit-wrapper", [
// (txid (buff 32))
transactions_1.Cl.bufferFromHex(txId),
// (vout-index uint)
transactions_1.Cl.uint(1),
// (amount uint)
transactions_1.Cl.uint(amountSats),
// (recipient principal)
transactions_1.Cl.principal(recipient),
// (burn-hash (buff 32))
burnHash.value,
// (burn-height uint)
transactions_1.Cl.uint(previousBurnHeight),
// (sweep-txid (buff 32))
transactions_1.Cl.bufferFromHex(sweepTxId),
], "SM3VDXK3WZZSA84XXFKAFAF15NNZX32CTSG82JFQ4").result);
// If the deposit transaction fails, an unexpected outcome can happen. Throw
// an error if the transaction is not successful.
if (!completeDepositTx.success) {
throw new Error("Something went wrong trying to restore sBTC balances.");
}
};
/**
* Utility function that generates a random, unique hex to be used as txId in
* `mintSbtc`.
*
* @returns A random hex string.
*/
const getUniqueHex = () => {
let hex;
// Generate a 32-byte (64 character) random hex string.
const bytes = new Uint8Array(32);
crypto.getRandomValues(bytes);
hex = Array.from(bytes)
.map((byte) => byte.toString(16).padStart(2, "0"))
.join("");
return hex;
};
{
"name": "@stacks/rendezvous",
"version": "0.12.0",
"version": "0.13.0",
"description": "Meet your contract's vulnerabilities head-on.",

@@ -33,11 +33,11 @@ "main": "app.js",

"dependencies": {
"@stacks/clarinet-sdk": "^3.9.1",
"@iarna/toml": "^2.2.5",
"@stacks/clarinet-sdk": "^3.12.0",
"@stacks/transactions": "^7.2.0",
"ansicolor": "^2.0.3",
"fast-check": "^4.3.0",
"toml": "^3.0.0",
"yaml": "^2.8.1"
},
"devDependencies": {
"@stacks/clarinet-sdk-wasm": "^3.9.1",
"@stacks/clarinet-sdk-wasm": "^3.12.0",
"@types/jest": "^30.0.0",

@@ -44,0 +44,0 @@ "jest": "^30.2.0",

{
"name": "@stacks/rendezvous",
"version": "0.12.0",
"version": "0.13.0",
"description": "Meet your contract's vulnerabilities head-on.",

@@ -33,11 +33,11 @@ "main": "app.js",

"dependencies": {
"@stacks/clarinet-sdk": "^3.9.1",
"@iarna/toml": "^2.2.5",
"@stacks/clarinet-sdk": "^3.12.0",
"@stacks/transactions": "^7.2.0",
"ansicolor": "^2.0.3",
"fast-check": "^4.3.0",
"toml": "^3.0.0",
"yaml": "^2.8.1"
},
"devDependencies": {
"@stacks/clarinet-sdk-wasm": "^3.9.1",
"@stacks/clarinet-sdk-wasm": "^3.12.0",
"@types/jest": "^30.0.0",

@@ -44,0 +44,0 @@ "jest": "^30.2.0",

@@ -11,3 +11,3 @@ <div align="center">

- **Node.js**: Supported versions include 20, 22, and 23. Other versions may work, but they are untested.
- **Node.js**: Supported versions include 20, 22, and 24. Other versions may work, but they are untested.

@@ -14,0 +14,0 @@ ### Inspiration