Skip to content

Commit

Permalink
Preparing Project as NPM Package (#23)
Browse files Browse the repository at this point in the history
- add tsconfig with a bunch of standard stuff.
- make index an export * file
- move the former main script to examples and
- update the package json accordingly.

Issues Encountered:

Many Issues were encountered. Mostly involving CommonJS, ESModules and our dependency near-ca (which has since been updated to support both). We also had painful problems with ts-node and now use tsx instead (it is wonderful 🥇 )
  • Loading branch information
bh2smith authored Jul 1, 2024
1 parent d47431e commit 4e68981
Show file tree
Hide file tree
Showing 13 changed files with 715 additions and 659 deletions.
7 changes: 5 additions & 2 deletions .github/workflows/pull-request.yaml
Original file line number Diff line number Diff line change
Expand Up @@ -17,5 +17,8 @@ jobs:
with:
node-version: "20"

- name: Install & Lint
run: yarn && yarn lint
- name: Install, Lint & Build
run: |
yarn
yarn lint
yarn build
1 change: 1 addition & 0 deletions .gitignore
Original file line number Diff line number Diff line change
@@ -1,2 +1,3 @@
node_modules/
.env
dist/
File renamed without changes.
2 changes: 1 addition & 1 deletion src/cli.ts → examples/cli.ts
Original file line number Diff line number Diff line change
@@ -1,6 +1,6 @@
import yargs from "yargs";
import { hideBin } from "yargs/helpers";
import { UserOptions } from "./types";
import { UserOptions } from "../src";

export async function loadArgs(): Promise<UserOptions> {
return yargs(hideBin(process.argv))
Expand Down
65 changes: 65 additions & 0 deletions examples/send-tx.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,65 @@
import dotenv from "dotenv";
import { ethers } from "ethers";
import { loadArgs } from "./cli";
import { TransactionManager } from "../src";

dotenv.config();

async function main(): Promise<void> {
const options = await loadArgs();
const txManager = await TransactionManager.create({
ethRpc: process.env.ETH_RPC!,
erc4337BundlerUrl: process.env.ERC4337_BUNDLER_URL!,
safeSaltNonce: options.safeSaltNonce,
});
const transactions = [
// TODO: Replace dummy transaction with real user transaction.
{
to: "0xbeef4dad00000000000000000000000000000000",
value: "0", // 0 value transfer with non-trivial data.
data: "0xbeef",
},
];
// Add Recovery if safe not deployed & recoveryAddress was provided.
if (txManager.safeNotDeployed && options.recoveryAddress) {
const recoveryTx = txManager.addOwnerTx(options.recoveryAddress);
// This would happen (sequentially) after the userTx, but all executed in a single
transactions.push(recoveryTx);
}

const { unsignedUserOp, safeOpHash } = await txManager.buildTransaction({
transactions,
options,
});
console.log("Unsigned UserOp", unsignedUserOp);
console.log("Safe Op Hash", safeOpHash);

// TODO: Evaluate gas cost (in ETH)
const gasCost = ethers.parseEther("0.01");
// Whenever not using paymaster, or on value transfer, the Safe must be funded.
const sufficientFunded = await txManager.safeSufficientlyFunded(
transactions,
options.usePaymaster ? 0n : gasCost
);
if (!sufficientFunded) {
console.warn(
`Safe ${txManager.safeAddress} insufficiently funded to perform this transaction. Exiting...`
);
process.exit(0); // soft exit with warning!
}

console.log("Signing with Near...");
const signature = await txManager.signTransaction(safeOpHash);

console.log("Executing UserOp...");
const userOpReceipt = await txManager.executeTransaction({
...unsignedUserOp,
signature,
});
console.log("userOp Receipt", userOpReceipt);
}

main().catch((err) => {
console.error(err);
process.exitCode = 1;
});
18 changes: 13 additions & 5 deletions package.json
Original file line number Diff line number Diff line change
@@ -1,18 +1,26 @@
{
"name": "near-safe",
"version": "0.0.0",
"module": "index.ts",
"main": "dist/index.js",
"types": "dist/index.d.ts",
"files": [
"dist/**/*"
],
"private": true,
"scripts": {
"start": "ts-node src/index.ts",
"lint": "eslint .",
"fmt": "prettier -w ."
"build": "rm -rf ./dist && tsc",
"example": "tsx examples/send-tx.ts",
"lint": "eslint . --ignore-pattern dist/",
"fmt": "prettier --write '{src,examples,tests}/**/*.{js,jsx,ts,tsx}'",
"all": "yarn fmt && yarn lint && yarn build && yarn ex"
},
"dependencies": {
"@safe-global/safe-deployments": "^1.37.0",
"@safe-global/safe-modules-deployments": "^2.2.0",
"ethers": "^6.13.1",
"ethers-multisend": "^3.1.0",
"near-ca": "^0.0.10-alpha.1",
"near-ca": "^0.1.0",
"yargs": "^17.7.2"
},
"devDependencies": {
Expand All @@ -23,7 +31,7 @@
"dotenv": "^16.4.5",
"eslint": "^9.6.0",
"prettier": "^3.3.2",
"ts-node": "^10.9.2",
"tsx": "^4.16.0",
"typescript": "^5.5.2"
}
}
68 changes: 3 additions & 65 deletions src/index.ts
Original file line number Diff line number Diff line change
@@ -1,65 +1,3 @@
import dotenv from "dotenv";
import { TransactionManager } from "./tx-manager";
import { loadArgs } from "./cli";
import { ethers } from "ethers";

dotenv.config();

async function main(): Promise<void> {
const options = await loadArgs();
const txManager = await TransactionManager.create({
ethRpc: process.env.ETH_RPC!,
erc4337BundlerUrl: process.env.ERC4337_BUNDLER_URL!,
safeSaltNonce: options.safeSaltNonce,
});
const transactions = [
// TODO: Replace dummy transaction with real user transaction.
{
to: "0xbeef4dad00000000000000000000000000000000",
value: "0", // 0 value transfer with non-trivial data.
data: "0xbeef",
},
];
// Add Recovery if safe not deployed & recoveryAddress was provided.
if (txManager.safeNotDeployed && options.recoveryAddress) {
const recoveryTx = txManager.addOwnerTx(options.recoveryAddress);
// This would happen (sequentially) after the userTx, but all executed in a single
transactions.push(recoveryTx);
}

const { unsignedUserOp, safeOpHash } = await txManager.buildTransaction({
transactions,
options,
});
console.log("Unsigned UserOp", unsignedUserOp);
console.log("Safe Op Hash", safeOpHash);

// TODO: Evaluate gas cost (in ETH)
const gasCost = ethers.parseEther("0.01");
// Whenever not using paymaster, or on value transfer, the Safe must be funded.
const sufficientFunded = await txManager.safeSufficientlyFunded(
transactions,
options.usePaymaster ? 0n : gasCost
);
if (!sufficientFunded) {
console.warn(
`Safe ${txManager.safeAddress} insufficiently funded to perform this transaction. Exiting...`
);
process.exit(0); // soft exit with warning!
}

console.log("Signing with Near...");
const signature = await txManager.signTransaction(safeOpHash);

console.log("Executing UserOp...");
const userOpReceipt = await txManager.executeTransaction({
...unsignedUserOp,
signature,
});
console.log("userOp Receipt", userOpReceipt);
}

main().catch((err) => {
console.error(err);
process.exitCode = 1;
});
export * from "./tx-manager.js";
export * from "./types.js";
export * from "./util.js";
4 changes: 2 additions & 2 deletions src/lib/bundler.ts
Original file line number Diff line number Diff line change
Expand Up @@ -4,8 +4,8 @@ import {
UnsignedUserOperation,
UserOperation,
UserOperationReceipt,
} from "../types";
import { PLACEHOLDER_SIG } from "../util";
} from "../types.js";
import { PLACEHOLDER_SIG } from "../util.js";

export class Erc4337Bundler {
provider: ethers.JsonRpcProvider;
Expand Down
8 changes: 6 additions & 2 deletions src/lib/safe.ts
Original file line number Diff line number Diff line change
Expand Up @@ -7,8 +7,12 @@ import {
getSafe4337ModuleDeployment,
getSafeModuleSetupDeployment,
} from "@safe-global/safe-modules-deployments";
import { PLACEHOLDER_SIG, packGas, packPaymasterData } from "../util";
import { PaymasterData, UnsignedUserOperation, UserOperation } from "../types";
import { PLACEHOLDER_SIG, packGas, packPaymasterData } from "../util.js";
import {
PaymasterData,
UnsignedUserOperation,
UserOperation,
} from "../types.js";
import { MetaTransaction } from "ethers-multisend";

/**
Expand Down
10 changes: 5 additions & 5 deletions src/tx-manager.ts
Original file line number Diff line number Diff line change
@@ -1,11 +1,11 @@
import { ethers } from "ethers";
import { NearEthAdapter, MultichainContract } from "near-ca";
import { ContractSuite } from "./lib/safe";
import { Erc4337Bundler } from "./lib/bundler";
import { packSignature } from "./util";
import { getNearSignature } from "./lib/near";
import { UserOperation, UserOperationReceipt, UserOptions } from "./types";
import { Erc4337Bundler } from "./lib/bundler.js";
import { packSignature } from "./util.js";
import { getNearSignature } from "./lib/near.js";
import { UserOperation, UserOperationReceipt, UserOptions } from "./types.js";
import { MetaTransaction, encodeMulti } from "ethers-multisend";
import { ContractSuite } from "./lib/safe.js";

export class TransactionManager {
readonly provider: ethers.JsonRpcProvider;
Expand Down
2 changes: 1 addition & 1 deletion src/util.ts
Original file line number Diff line number Diff line change
@@ -1,5 +1,5 @@
import { ethers } from "ethers";
import { PaymasterData } from "./types";
import { PaymasterData } from "./types.js";
import { MetaTransaction } from "ethers-multisend";

export const PLACEHOLDER_SIG = ethers.solidityPacked(
Expand Down
27 changes: 27 additions & 0 deletions tsconfig.json
Original file line number Diff line number Diff line change
@@ -0,0 +1,27 @@
{
"compilerOptions": {
"target": "ES2020",
"module": "ES6",
"moduleResolution": "Node",
"esModuleInterop": true,

/* Emit */
"declaration": true,
"outDir": "./dist",
"allowSyntheticDefaultImports": true,
"forceConsistentCasingInFileNames": true,

/* Type Checking */
"strict": true,
"noImplicitAny": true,
"strictNullChecks": true,
"strictFunctionTypes": true,
"alwaysStrict": true,
"noUnusedLocals": true,
"noUnusedParameters": true,

/* Completeness */
"skipLibCheck": true /* Skip type checking all .d.ts files. */
},
"exclude": ["node_modules", "dist", "examples", "tests", "eslint.config.cjs"]
}
Loading

0 comments on commit 4e68981

Please sign in to comment.