Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
13 changes: 0 additions & 13 deletions foundry.toml
Original file line number Diff line number Diff line change
Expand Up @@ -6,17 +6,4 @@ out = "out"
libs = ["lib"]
via_ir = true

#currently, forge does not allow remapping of individual files
# (see here: https://github.com/foundry-rs/foundry/issues/7527#issuecomment-2269444829)
#so for now we are using the IERC20 prefix as a workaround that allows users
# to override the IERC20 interface with whatever they use in their project
# (well, as long as their file is also called IERC20.sol and is interface compatible)
remappings = [
"ds-test/=lib/forge-std/lib/ds-test/src/",
"forge-std/=lib/forge-std/src/",
"wormhole-sdk/=src/",
"IERC20/=src/interfaces/token/",
"SafeERC20/=src/libraries/",
]

verbosity = 3
58 changes: 58 additions & 0 deletions npm_publish/clean_remappings.js
Original file line number Diff line number Diff line change
@@ -0,0 +1,58 @@
if (process.argv.length !== 4) {
console.error("Usage: node clean_remappings.js <root dir (containing remappings.txt)> <contracts dir>");
process.exit(1);
}
const remappingsFilePath = process.argv[2] + "/remappings.txt";
const contractsDir = process.argv[3];

const fs = require("fs");
const path = require("path");

const remappings = fs.readFileSync(remappingsFilePath, "utf8")
.split("\n")
.reduce(
(acc, line) => {
if (line && !line.startsWith("#")) {
const [label, target] = line.split("/=");
if (target.startsWith("src/"))
acc = { ...acc, [label]: target.slice(4) };
}
return acc;
},
{}
);

const findSolFiles = (dir) => fs.readdirSync(dir)
.reduce(
(acc, item) => {
const fullPath = path.join(dir, item);
const stat = fs.statSync(fullPath);
if (stat.isDirectory())
acc.push(...findSolFiles(fullPath));
else if (stat.isFile() && path.extname(item) === ".sol")
acc.push(fullPath);
return acc;
},
[]
);

//match plain import statements or starting at from to avoid hassle of parsing multi-line imports
const importRe = /((?:import|from)\s+")([^/]+)\/((?:[^/"]+)\/)*([^.]+\.sol";)/g;

for (const filePath of findSolFiles(contractsDir)) {
let modified = false;
const fileContent = fs.readFileSync(filePath, "utf8").replace(importRe, (match, head, label, subdir, tail) => {
const target = remappings[label];
if (target !== undefined) {
modified = true;
const longPath = path.join(contractsDir, target, subdir ?? "");
const shortPath = path.relative(path.dirname(filePath), longPath) || ".";
const finalPath = (shortPath.startsWith(".") ? "" : "./") + shortPath + "/";
return `${head}${finalPath}${tail}`;
}
return match;
});

if (modified)
fs.writeFileSync(filePath, fileContent, "utf8");
}
22 changes: 22 additions & 0 deletions npm_publish/package.json
Original file line number Diff line number Diff line change
@@ -0,0 +1,22 @@
{
"name": "@wormhole-foundation/solidity-sdk",
"version": "1.0.0",
"description": "Wormhole Solidity SDK",
"license": "Apache-2.0",
"repository": {
"type": "git",
"url": "https://github.com/wormhole-foundation/wormhole-solidity-sdk.git"
},
"files": [
"contracts/**/*.sol"
],
"keywords": [
"solidity",
"wormhole",
"cross-chain",
"evm",
"foundry",
"hardhat",
"sdk"
]
}
124 changes: 124 additions & 0 deletions npm_publish/publish.sh
Original file line number Diff line number Diff line change
@@ -0,0 +1,124 @@
#!/usr/bin/env bash

# NPM Publishing Script for Wormhole Solidity SDK
#
# Usage:
# ./publish.sh [tag] [dry-run]
#
# Examples:
# ./publish.sh # Publish with 'latest' tag
# ./publish.sh beta # Publish with 'beta' tag
# ./publish.sh latest true # Dry run with 'latest' tag
# ./publish.sh beta true # Dry run with 'beta' tag

set -euo pipefail

SCRIPT_DIR="$(cd "$(dirname "${BASH_SOURCE[0]}")" && pwd)"
PROJECT_ROOT_DIR="$(cd "${SCRIPT_DIR}/.." && pwd)"
TEMP_PUBLISH_DIR="${PROJECT_ROOT_DIR}/dist_npm"

NPM_PACKAGE_NAME="$(node -p "require('${SCRIPT_DIR}/package.json').name")"
NPM_PACKAGE_VERSION="$(node -p "require('${SCRIPT_DIR}/package.json').version")"

NPM_TAG="${1:-latest}"
DRY_RUN="${2:-false}"

# read Solidity version from foundry.toml for Hardhat compilation check
SOLC_VERSION=$(grep -oP '(?<=solc_version = ")[^"]*' "${PROJECT_ROOT_DIR}/foundry.toml")

fail() {
echo "ERROR: $1" >&2
exit 1
}

get_npm_version() {
local result=$(npm view "$1" "$2" --json --silent 2>/dev/null || echo "null")
if echo "${result}" | grep -q '"error":'; then
echo "${3:-}"
elif echo "${result}" | grep -q '"'; then
echo "${result}" | tr -d '"'
else
fail "Failed to get version for package '${1}' with tag '${2}' - error: ${result}"
fi
}

check_version_increment() {
LATEST_VERSION=$(get_npm_version "${NPM_PACKAGE_NAME}" "version" "0.0.0")
if [ "${LATEST_VERSION}" = "0.0.0" ]; then
echo "Package not found on npm registry. Assuming first publish."
else
echo "Latest version on npm: ${LATEST_VERSION}"
fi

TAG_VERSION=$(get_npm_version "${NPM_PACKAGE_NAME}" "dist-tags.${NPM_TAG}" "")
echo "Current '${NPM_TAG}' tag points to: ${TAG_VERSION:-none}"

if [ "${TAG_VERSION}" = "${NPM_PACKAGE_VERSION}" ]; then
fail "Version '${NPM_PACKAGE_VERSION}' is already published to tag '${NPM_TAG}'."
fi

if [ "${LATEST_VERSION}" != "0.0.0" ] &&
[ "$(printf '%s\n' "${NPM_PACKAGE_VERSION}" "${LATEST_VERSION}" | sort -V | head -n1)" = "${NPM_PACKAGE_VERSION}" ] &&
[ "${NPM_PACKAGE_VERSION}" != "${LATEST_VERSION}" ]; then
fail "Version '${NPM_PACKAGE_VERSION}' is older than latest version '${LATEST_VERSION}'."
fi
}

# --- Main Script ---

echo "Starting NPM publish process..."
echo "Version: ${NPM_PACKAGE_VERSION}"
echo "Tag: ${NPM_TAG}"
echo "Dry Run: ${DRY_RUN}"

echo "1. Check Version Against NPM Registry"
check_version_increment

echo "2. Git Status Checks"
if [ "${NPM_TAG}" = "latest" ] && [ "$(git rev-parse --abbrev-ref HEAD)" != "main" ]; then
fail "Publishing to 'latest' tag but not on the 'main' branch."
fi
if ! git diff-index --quiet HEAD --; then
fail "There are uncommitted changes. Please commit or stash them before publishing."
fi

echo "3. Cleaning up and setting up temporary publish directory"
rm -rf "${TEMP_PUBLISH_DIR}"
mkdir -p "${TEMP_PUBLISH_DIR}"
(
cd "${TEMP_PUBLISH_DIR}"
cp -a "${PROJECT_ROOT_DIR}/src" .
mv "src" "contracts"
rm -rf "contracts/testing" "contracts/legacy" # remove stuff that depends on forge-std
cp "${SCRIPT_DIR}/package.json" "${PROJECT_ROOT_DIR}/README.md" "${PROJECT_ROOT_DIR}/LICENSE" .
)

echo "4. Transforming Foundry remappings to relative paths for Hardhat..."
node "${SCRIPT_DIR}/clean_remappings.js" "${PROJECT_ROOT_DIR}" "${TEMP_PUBLISH_DIR}/contracts"

echo "5. Hardhat compilation check"
(
cd "${TEMP_PUBLISH_DIR}"
echo "module.exports = { solidity: { version: '${SOLC_VERSION}', settings: { viaIR: true, optimizer: { enabled: true } } } };" > hardhat.config.js
npm install --no-save --silent hardhat
if ! npx hardhat compile > /dev/null 2>&1; then
fail "Hardhat compilation failed. Aborting."
fi
)

echo "6. Cleaning up temporary files before publishing"
(
cd "${TEMP_PUBLISH_DIR}"
rm -rf hardhat.config.js node_modules cache artifacts
)

echo "7. Publishing version ${NPM_PACKAGE_VERSION} with tag '${NPM_TAG}'"
(
cd "${TEMP_PUBLISH_DIR}"
npm publish --silent --tag "${NPM_TAG}" $([ "${DRY_RUN}" = "true" ] && echo "--dry-run")
)

echo "8. Cleaning up temporary publish directory: ${TEMP_PUBLISH_DIR}"
rm -rf "${TEMP_PUBLISH_DIR}"

echo "Successfully $([ "${DRY_RUN}" = "true" ] && echo "completed dry run for" || echo "published") version ${NPM_PACKAGE_VERSION} with tag '${NPM_TAG}'"
4 changes: 4 additions & 0 deletions remappings.txt
Original file line number Diff line number Diff line change
@@ -0,0 +1,4 @@
forge-std/=lib/forge-std/src/
wormhole-sdk/=src/
IERC20/=src/interfaces/token/
SafeERC20/=src/libraries/