near-safe 0.0.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/README.md +57 -0
- package/dist/cjs/index.js +19 -0
- package/dist/cjs/lib/bundler.js +60 -0
- package/dist/cjs/lib/near.js +14 -0
- package/dist/cjs/lib/safe.js +106 -0
- package/dist/cjs/tx-manager.js +95 -0
- package/dist/cjs/types.js +2 -0
- package/dist/cjs/util.js +26 -0
- package/dist/esm/index.d.ts +3 -0
- package/dist/esm/index.js +3 -0
- package/dist/esm/lib/bundler.d.ts +12 -0
- package/dist/esm/lib/bundler.js +58 -0
- package/dist/esm/lib/near.d.ts +3 -0
- package/dist/esm/lib/near.js +11 -0
- package/dist/esm/lib/safe.d.ts +24 -0
- package/dist/esm/lib/safe.js +108 -0
- package/dist/esm/tx-manager.d.ts +39 -0
- package/dist/esm/tx-manager.js +99 -0
- package/dist/esm/types.d.ts +85 -0
- package/dist/esm/types.js +1 -0
- package/dist/esm/util.d.ts +8 -0
- package/dist/esm/util.js +19 -0
- package/package.json +41 -0
package/README.md
ADDED
@@ -0,0 +1,57 @@
|
|
1
|
+
# Near-Safe
|
2
|
+
|
3
|
+
**TLDR;** This project provides a TypeScript implementation for controling Ethereum Smart Accounts via ERC4337 standard from a Near Account.
|
4
|
+
It includes utilities for packing data, managing transactions, and interacting with a bundler.
|
5
|
+
|
6
|
+
The account structure is defined as follows:
|
7
|
+
|
8
|
+
1. **Near Account** produces signatures for a deterministic EOA via Near's MPC Contract for [Chain Signatures](https://docs.near.org/concepts/abstraction/chain-signatures)
|
9
|
+
2. This EOA (controled by the Near Account) is the owner of a deterministic [Safe](https://safe.global/) with configured support for [ERC4337](https://www.erc4337.io/) standard.
|
10
|
+
|
11
|
+
## Features
|
12
|
+
|
13
|
+
1. Users first transaction is bundled together with the Safe's deployement (i.e. Safe does not need to be created before it is used). This is achived as multisend transaction.
|
14
|
+
2. No need to fund the EOA Account (it is only used for signatures).
|
15
|
+
3. Account Recovery: Near's MPC service provides signatures for accounts that users control, but do not hold the private key for. Provide a "recoveryAddress" that will be added as an additional owner of the Safe.
|
16
|
+
4. Paymaster Support for an entirely gasless experience!
|
17
|
+
5. Same address on all chains!
|
18
|
+
|
19
|
+
## Installation & Configuration
|
20
|
+
|
21
|
+
To get started, clone the repository and install the dependencies:
|
22
|
+
|
23
|
+
```sh
|
24
|
+
yarn install
|
25
|
+
```
|
26
|
+
|
27
|
+
Create a `.env` (or use our `.env.sample`) file in the root of the project and add the following environment variables:
|
28
|
+
|
29
|
+
```sh
|
30
|
+
ETH_RPC=https://rpc2.sepolia.org
|
31
|
+
|
32
|
+
NEAR_ACCOUNT_ID=
|
33
|
+
NEAR_ACCOUNT_PRIVATE_KEY=
|
34
|
+
|
35
|
+
# Head to https://www.pimlico.io/ for an API key
|
36
|
+
ERC4337_BUNDLER_URL=
|
37
|
+
```
|
38
|
+
|
39
|
+
|
40
|
+
## Usage
|
41
|
+
|
42
|
+
### Running the Example
|
43
|
+
|
44
|
+
The example script `examples/send-tx.ts` demonstrates how to use the transaction manager. You can run it with the following command:
|
45
|
+
|
46
|
+
```sh
|
47
|
+
yarn start --usePaymaster --recoveryAddress <recovery_address> --safeSaltNonce <safe_salt_nonce>
|
48
|
+
```
|
49
|
+
|
50
|
+
### Example Arguments
|
51
|
+
|
52
|
+
The example script accepts the following arguments:
|
53
|
+
|
54
|
+
- `--usePaymaster`: Boolean flag to indicate if the transaction should be sponsored by a paymaster service.
|
55
|
+
- `--recoveryAddress`: The recovery address to be attached as the owner of the Safe (immediately after deployment).
|
56
|
+
- `--safeSaltNonce`: The salt nonce used for the Safe deployment.
|
57
|
+
|
@@ -0,0 +1,19 @@
|
|
1
|
+
"use strict";
|
2
|
+
var __createBinding = (this && this.__createBinding) || (Object.create ? (function(o, m, k, k2) {
|
3
|
+
if (k2 === undefined) k2 = k;
|
4
|
+
var desc = Object.getOwnPropertyDescriptor(m, k);
|
5
|
+
if (!desc || ("get" in desc ? !m.__esModule : desc.writable || desc.configurable)) {
|
6
|
+
desc = { enumerable: true, get: function() { return m[k]; } };
|
7
|
+
}
|
8
|
+
Object.defineProperty(o, k2, desc);
|
9
|
+
}) : (function(o, m, k, k2) {
|
10
|
+
if (k2 === undefined) k2 = k;
|
11
|
+
o[k2] = m[k];
|
12
|
+
}));
|
13
|
+
var __exportStar = (this && this.__exportStar) || function(m, exports) {
|
14
|
+
for (var p in m) if (p !== "default" && !Object.prototype.hasOwnProperty.call(exports, p)) __createBinding(exports, m, p);
|
15
|
+
};
|
16
|
+
Object.defineProperty(exports, "__esModule", { value: true });
|
17
|
+
__exportStar(require("./tx-manager"), exports);
|
18
|
+
__exportStar(require("./types"), exports);
|
19
|
+
__exportStar(require("./util"), exports);
|
@@ -0,0 +1,60 @@
|
|
1
|
+
"use strict";
|
2
|
+
Object.defineProperty(exports, "__esModule", { value: true });
|
3
|
+
exports.Erc4337Bundler = void 0;
|
4
|
+
const ethers_1 = require("ethers");
|
5
|
+
const util_1 = require("../util");
|
6
|
+
class Erc4337Bundler {
|
7
|
+
constructor(bundlerUrl, entryPointAddress) {
|
8
|
+
this.entryPointAddress = entryPointAddress;
|
9
|
+
this.provider = new ethers_1.ethers.JsonRpcProvider(bundlerUrl);
|
10
|
+
}
|
11
|
+
async getPaymasterData(rawUserOp, usePaymaster, safeNotDeployed) {
|
12
|
+
// TODO: Keep this option out of the bundler
|
13
|
+
if (usePaymaster) {
|
14
|
+
console.log("Requesting paymaster data...");
|
15
|
+
const data = this.provider.send("pm_sponsorUserOperation", [
|
16
|
+
{ ...rawUserOp, signature: util_1.PLACEHOLDER_SIG },
|
17
|
+
this.entryPointAddress,
|
18
|
+
]);
|
19
|
+
return data;
|
20
|
+
}
|
21
|
+
return defaultPaymasterData(safeNotDeployed);
|
22
|
+
}
|
23
|
+
async sendUserOperation(userOp) {
|
24
|
+
try {
|
25
|
+
const userOpHash = await this.provider.send("eth_sendUserOperation", [
|
26
|
+
userOp,
|
27
|
+
this.entryPointAddress,
|
28
|
+
]);
|
29
|
+
return userOpHash;
|
30
|
+
}
|
31
|
+
catch (err) {
|
32
|
+
const error = err.error;
|
33
|
+
throw new Error(`Failed to send user op with: ${error.message}`);
|
34
|
+
}
|
35
|
+
}
|
36
|
+
async getGasPrice() {
|
37
|
+
return this.provider.send("pimlico_getUserOperationGasPrice", []);
|
38
|
+
}
|
39
|
+
async _getUserOpReceiptInner(userOpHash) {
|
40
|
+
return this.provider.send("eth_getUserOperationReceipt", [userOpHash]);
|
41
|
+
}
|
42
|
+
async getUserOpReceipt(userOpHash) {
|
43
|
+
let userOpReceipt = null;
|
44
|
+
while (!userOpReceipt) {
|
45
|
+
// Wait 2 seconds before checking the status again
|
46
|
+
await new Promise((resolve) => setTimeout(resolve, 2000));
|
47
|
+
userOpReceipt = await this._getUserOpReceiptInner(userOpHash);
|
48
|
+
}
|
49
|
+
return userOpReceipt;
|
50
|
+
}
|
51
|
+
}
|
52
|
+
exports.Erc4337Bundler = Erc4337Bundler;
|
53
|
+
// TODO(bh2smith) Should probably get reasonable estimates here:
|
54
|
+
const defaultPaymasterData = (safeNotDeployed) => {
|
55
|
+
return {
|
56
|
+
verificationGasLimit: ethers_1.ethers.toBeHex(safeNotDeployed ? 500000 : 100000),
|
57
|
+
callGasLimit: ethers_1.ethers.toBeHex(100000),
|
58
|
+
preVerificationGas: ethers_1.ethers.toBeHex(100000),
|
59
|
+
};
|
60
|
+
};
|
@@ -0,0 +1,14 @@
|
|
1
|
+
"use strict";
|
2
|
+
Object.defineProperty(exports, "__esModule", { value: true });
|
3
|
+
exports.getNearSignature = getNearSignature;
|
4
|
+
const ethers_1 = require("ethers");
|
5
|
+
async function getNearSignature(adapter, hash) {
|
6
|
+
const viemHash = typeof hash === "string" ? hash : hash;
|
7
|
+
// MPC Contract produces two possible signatures.
|
8
|
+
const signature = await adapter.sign(viemHash);
|
9
|
+
if (ethers_1.ethers.recoverAddress(hash, signature).toLocaleLowerCase() ===
|
10
|
+
adapter.address.toLocaleLowerCase()) {
|
11
|
+
return signature;
|
12
|
+
}
|
13
|
+
throw new Error("Invalid signature!");
|
14
|
+
}
|
@@ -0,0 +1,106 @@
|
|
1
|
+
"use strict";
|
2
|
+
Object.defineProperty(exports, "__esModule", { value: true });
|
3
|
+
exports.ContractSuite = void 0;
|
4
|
+
const ethers_1 = require("ethers");
|
5
|
+
const safe_deployments_1 = require("@safe-global/safe-deployments");
|
6
|
+
const safe_modules_deployments_1 = require("@safe-global/safe-modules-deployments");
|
7
|
+
const util_1 = require("../util");
|
8
|
+
/**
|
9
|
+
* All contracts used in account creation & execution
|
10
|
+
*/
|
11
|
+
class ContractSuite {
|
12
|
+
constructor(provider, singleton, proxyFactory, m4337, moduleSetup, entryPoint) {
|
13
|
+
this.provider = provider;
|
14
|
+
this.singleton = singleton;
|
15
|
+
this.proxyFactory = proxyFactory;
|
16
|
+
this.m4337 = m4337;
|
17
|
+
this.moduleSetup = moduleSetup;
|
18
|
+
this.entryPoint = entryPoint;
|
19
|
+
}
|
20
|
+
static async init(provider) {
|
21
|
+
const safeDeployment = (fn) => getDeployment(fn, { provider, version: "1.4.1" });
|
22
|
+
const m4337Deployment = (fn) => getDeployment(fn, { provider, version: "0.3.0" });
|
23
|
+
// Need this first to get entryPoint address
|
24
|
+
const m4337 = await m4337Deployment(safe_modules_deployments_1.getSafe4337ModuleDeployment);
|
25
|
+
const [singleton, proxyFactory, moduleSetup, supportedEntryPoint] = await Promise.all([
|
26
|
+
safeDeployment(safe_deployments_1.getSafeL2SingletonDeployment),
|
27
|
+
safeDeployment(safe_deployments_1.getProxyFactoryDeployment),
|
28
|
+
m4337Deployment(safe_modules_deployments_1.getSafeModuleSetupDeployment),
|
29
|
+
m4337.SUPPORTED_ENTRYPOINT(),
|
30
|
+
]);
|
31
|
+
const entryPoint = new ethers_1.ethers.Contract(supportedEntryPoint, ["function getNonce(address, uint192 key) view returns (uint256 nonce)"], provider);
|
32
|
+
return new ContractSuite(provider, singleton, proxyFactory, m4337, moduleSetup, entryPoint);
|
33
|
+
}
|
34
|
+
async addressForSetup(setup, saltNonce) {
|
35
|
+
// bytes32 salt = keccak256(abi.encodePacked(keccak256(initializer), saltNonce));
|
36
|
+
// cf: https://github.com/safe-global/safe-smart-account/blob/499b17ad0191b575fcadc5cb5b8e3faeae5391ae/contracts/proxies/SafeProxyFactory.sol#L58
|
37
|
+
const salt = ethers_1.ethers.keccak256(ethers_1.ethers.solidityPacked(["bytes32", "uint256"], [ethers_1.ethers.keccak256(setup), saltNonce || 0]));
|
38
|
+
// abi.encodePacked(type(SafeProxy).creationCode, uint256(uint160(_singleton)));
|
39
|
+
// cf: https://github.com/safe-global/safe-smart-account/blob/499b17ad0191b575fcadc5cb5b8e3faeae5391ae/contracts/proxies/SafeProxyFactory.sol#L29
|
40
|
+
const initCode = ethers_1.ethers.solidityPacked(["bytes", "uint256"], [
|
41
|
+
await this.proxyFactory.proxyCreationCode(),
|
42
|
+
await this.singleton.getAddress(),
|
43
|
+
]);
|
44
|
+
return ethers_1.ethers.getCreate2Address(await this.proxyFactory.getAddress(), salt, ethers_1.ethers.keccak256(initCode));
|
45
|
+
}
|
46
|
+
async getSetup(owners) {
|
47
|
+
const setup = await this.singleton.interface.encodeFunctionData("setup", [
|
48
|
+
owners,
|
49
|
+
1, // We use sign threshold of 1.
|
50
|
+
this.moduleSetup.target,
|
51
|
+
this.moduleSetup.interface.encodeFunctionData("enableModules", [
|
52
|
+
[this.m4337.target],
|
53
|
+
]),
|
54
|
+
this.m4337.target,
|
55
|
+
ethers_1.ethers.ZeroAddress,
|
56
|
+
0,
|
57
|
+
ethers_1.ethers.ZeroAddress,
|
58
|
+
]);
|
59
|
+
return setup;
|
60
|
+
}
|
61
|
+
async getOpHash(unsignedUserOp, paymasterData) {
|
62
|
+
return this.m4337.getOperationHash({
|
63
|
+
...unsignedUserOp,
|
64
|
+
initCode: unsignedUserOp.factory
|
65
|
+
? ethers_1.ethers.solidityPacked(["address", "bytes"], [unsignedUserOp.factory, unsignedUserOp.factoryData])
|
66
|
+
: "0x",
|
67
|
+
accountGasLimits: (0, util_1.packGas)(unsignedUserOp.verificationGasLimit, unsignedUserOp.callGasLimit),
|
68
|
+
gasFees: (0, util_1.packGas)(unsignedUserOp.maxPriorityFeePerGas, unsignedUserOp.maxFeePerGas),
|
69
|
+
paymasterAndData: (0, util_1.packPaymasterData)(paymasterData),
|
70
|
+
signature: util_1.PLACEHOLDER_SIG,
|
71
|
+
});
|
72
|
+
}
|
73
|
+
factoryDataForSetup(safeNotDeployed, setup, safeSaltNonce) {
|
74
|
+
return safeNotDeployed
|
75
|
+
? {
|
76
|
+
factory: this.proxyFactory.target,
|
77
|
+
factoryData: this.proxyFactory.interface.encodeFunctionData("createProxyWithNonce", [this.singleton.target, setup, safeSaltNonce]),
|
78
|
+
}
|
79
|
+
: {};
|
80
|
+
}
|
81
|
+
async buildUserOp(txData, safeAddress, feeData, setup, safeNotDeployed, safeSaltNonce) {
|
82
|
+
const rawUserOp = {
|
83
|
+
sender: safeAddress,
|
84
|
+
nonce: ethers_1.ethers.toBeHex(await this.entryPoint.getNonce(safeAddress, 0)),
|
85
|
+
...this.factoryDataForSetup(safeNotDeployed, setup, safeSaltNonce),
|
86
|
+
// <https://github.com/safe-global/safe-modules/blob/9a18245f546bf2a8ed9bdc2b04aae44f949ec7a0/modules/4337/contracts/Safe4337Module.sol#L172>
|
87
|
+
callData: this.m4337.interface.encodeFunctionData("executeUserOp", [
|
88
|
+
txData.to,
|
89
|
+
BigInt(txData.value),
|
90
|
+
txData.data,
|
91
|
+
txData.operation || 0,
|
92
|
+
]),
|
93
|
+
...feeData,
|
94
|
+
};
|
95
|
+
return rawUserOp;
|
96
|
+
}
|
97
|
+
}
|
98
|
+
exports.ContractSuite = ContractSuite;
|
99
|
+
async function getDeployment(fn, { provider, version }) {
|
100
|
+
const { chainId } = await provider.getNetwork();
|
101
|
+
const deployment = fn({ version });
|
102
|
+
if (!deployment || !deployment.networkAddresses[`${chainId}`]) {
|
103
|
+
throw new Error(`Deployment not found for version ${version} and chainId ${chainId}`);
|
104
|
+
}
|
105
|
+
return new ethers_1.ethers.Contract(deployment.networkAddresses[`${chainId}`], deployment.abi, provider);
|
106
|
+
}
|
@@ -0,0 +1,95 @@
|
|
1
|
+
"use strict";
|
2
|
+
Object.defineProperty(exports, "__esModule", { value: true });
|
3
|
+
exports.TransactionManager = void 0;
|
4
|
+
const ethers_1 = require("ethers");
|
5
|
+
const near_ca_1 = require("near-ca");
|
6
|
+
const bundler_1 = require("./lib/bundler");
|
7
|
+
const util_1 = require("./util");
|
8
|
+
const near_1 = require("./lib/near");
|
9
|
+
const ethers_multisend_1 = require("ethers-multisend");
|
10
|
+
const safe_1 = require("./lib/safe");
|
11
|
+
class TransactionManager {
|
12
|
+
constructor(provider, nearAdapter, safePack, bundler, setup, safeAddress, safeSaltNonce, safeNotDeployed) {
|
13
|
+
this.provider = provider;
|
14
|
+
this.nearAdapter = nearAdapter;
|
15
|
+
this.safePack = safePack;
|
16
|
+
this.bundler = bundler;
|
17
|
+
this.setup = setup;
|
18
|
+
this.safeAddress = safeAddress;
|
19
|
+
this.safeSaltNonce = safeSaltNonce;
|
20
|
+
this._safeNotDeployed = safeNotDeployed;
|
21
|
+
}
|
22
|
+
static async create(config) {
|
23
|
+
const provider = new ethers_1.ethers.JsonRpcProvider(config.ethRpc);
|
24
|
+
const [nearAdapter, safePack] = await Promise.all([
|
25
|
+
near_ca_1.NearEthAdapter.fromConfig({
|
26
|
+
mpcContract: new near_ca_1.MpcContract(config.nearAccount, config.mpcContractId),
|
27
|
+
}),
|
28
|
+
safe_1.ContractSuite.init(provider),
|
29
|
+
]);
|
30
|
+
console.log(`Near Adapter: ${nearAdapter.nearAccountId()} <> ${nearAdapter.address}`);
|
31
|
+
const bundler = new bundler_1.Erc4337Bundler(config.erc4337BundlerUrl, await safePack.entryPoint.getAddress());
|
32
|
+
const setup = await safePack.getSetup([nearAdapter.address]);
|
33
|
+
const safeAddress = await safePack.addressForSetup(setup, config.safeSaltNonce);
|
34
|
+
const safeNotDeployed = (await provider.getCode(safeAddress)) === "0x";
|
35
|
+
console.log(`Safe Address: ${safeAddress} - deployed? ${!safeNotDeployed}`);
|
36
|
+
return new TransactionManager(provider, nearAdapter, safePack, bundler, setup, safeAddress, config.safeSaltNonce || "0", safeNotDeployed);
|
37
|
+
}
|
38
|
+
get safeNotDeployed() {
|
39
|
+
return this._safeNotDeployed;
|
40
|
+
}
|
41
|
+
get nearEOA() {
|
42
|
+
return this.nearAdapter.address;
|
43
|
+
}
|
44
|
+
async getSafeBalance() {
|
45
|
+
return await this.provider.getBalance(this.safeAddress);
|
46
|
+
}
|
47
|
+
async buildTransaction(args) {
|
48
|
+
const { transactions, options } = args;
|
49
|
+
const gasFees = (await this.bundler.getGasPrice()).fast;
|
50
|
+
// const gasFees = await this.provider.getFeeData();
|
51
|
+
// Build Singular MetaTransaction for Multisend from transaction list.
|
52
|
+
if (transactions.length === 0) {
|
53
|
+
throw new Error("Empty transaction set!");
|
54
|
+
}
|
55
|
+
const tx = transactions.length > 1 ? (0, ethers_multisend_1.encodeMulti)(transactions) : transactions[0];
|
56
|
+
const rawUserOp = await this.safePack.buildUserOp(tx, this.safeAddress, gasFees, this.setup, this.safeNotDeployed, this.safeSaltNonce);
|
57
|
+
const paymasterData = await this.bundler.getPaymasterData(rawUserOp, options.usePaymaster, this.safeNotDeployed);
|
58
|
+
const unsignedUserOp = { ...rawUserOp, ...paymasterData };
|
59
|
+
const safeOpHash = await this.safePack.getOpHash(unsignedUserOp, paymasterData);
|
60
|
+
return {
|
61
|
+
safeOpHash,
|
62
|
+
unsignedUserOp,
|
63
|
+
};
|
64
|
+
}
|
65
|
+
async signTransaction(safeOpHash) {
|
66
|
+
const signature = await (0, near_1.getNearSignature)(this.nearAdapter, safeOpHash);
|
67
|
+
return (0, util_1.packSignature)(signature);
|
68
|
+
}
|
69
|
+
async executeTransaction(userOp) {
|
70
|
+
const userOpHash = await this.bundler.sendUserOperation(userOp);
|
71
|
+
console.log("UserOp Hash", userOpHash);
|
72
|
+
const userOpReceipt = await this.bundler.getUserOpReceipt(userOpHash);
|
73
|
+
console.log("userOp Receipt", userOpReceipt);
|
74
|
+
// Update safeNotDeployed after the first transaction
|
75
|
+
this._safeNotDeployed =
|
76
|
+
(await this.provider.getCode(this.safeAddress)) === "0x";
|
77
|
+
return userOpReceipt;
|
78
|
+
}
|
79
|
+
addOwnerTx(address) {
|
80
|
+
return {
|
81
|
+
to: this.safeAddress,
|
82
|
+
value: "0",
|
83
|
+
data: this.safePack.singleton.interface.encodeFunctionData("addOwnerWithThreshold", [address, 1]),
|
84
|
+
};
|
85
|
+
}
|
86
|
+
async safeSufficientlyFunded(transactions, gasCost) {
|
87
|
+
const txValue = transactions.reduce((acc, tx) => acc + BigInt(tx.value), 0n);
|
88
|
+
if (txValue + gasCost === 0n) {
|
89
|
+
return true;
|
90
|
+
}
|
91
|
+
const safeBalance = await this.getSafeBalance();
|
92
|
+
return txValue + gasCost < safeBalance;
|
93
|
+
}
|
94
|
+
}
|
95
|
+
exports.TransactionManager = TransactionManager;
|
package/dist/cjs/util.js
ADDED
@@ -0,0 +1,26 @@
|
|
1
|
+
"use strict";
|
2
|
+
Object.defineProperty(exports, "__esModule", { value: true });
|
3
|
+
exports.packGas = exports.PLACEHOLDER_SIG = void 0;
|
4
|
+
exports.packSignature = packSignature;
|
5
|
+
exports.packPaymasterData = packPaymasterData;
|
6
|
+
exports.containsValue = containsValue;
|
7
|
+
const ethers_1 = require("ethers");
|
8
|
+
exports.PLACEHOLDER_SIG = ethers_1.ethers.solidityPacked(["uint48", "uint48"], [0, 0]);
|
9
|
+
const packGas = (hi, lo) => ethers_1.ethers.solidityPacked(["uint128", "uint128"], [hi, lo]);
|
10
|
+
exports.packGas = packGas;
|
11
|
+
function packSignature(signature, validFrom = 0, validTo = 0) {
|
12
|
+
return ethers_1.ethers.solidityPacked(["uint48", "uint48", "bytes"], [validFrom, validTo, signature]);
|
13
|
+
}
|
14
|
+
function packPaymasterData(data) {
|
15
|
+
return data.paymaster
|
16
|
+
? ethers_1.ethers.hexlify(ethers_1.ethers.concat([
|
17
|
+
data.paymaster,
|
18
|
+
ethers_1.ethers.toBeHex(data.paymasterVerificationGasLimit || "0x", 16),
|
19
|
+
ethers_1.ethers.toBeHex(data.paymasterPostOpGasLimit || "0x", 16),
|
20
|
+
data.paymasterData || "0x",
|
21
|
+
]))
|
22
|
+
: "0x";
|
23
|
+
}
|
24
|
+
function containsValue(transactions) {
|
25
|
+
return transactions.some((tx) => tx.value !== "0");
|
26
|
+
}
|
@@ -0,0 +1,12 @@
|
|
1
|
+
import { ethers } from "ethers";
|
2
|
+
import { GasPrices, PaymasterData, UnsignedUserOperation, UserOperation, UserOperationReceipt } from "../types";
|
3
|
+
export declare class Erc4337Bundler {
|
4
|
+
provider: ethers.JsonRpcProvider;
|
5
|
+
entryPointAddress: string;
|
6
|
+
constructor(bundlerUrl: string, entryPointAddress: string);
|
7
|
+
getPaymasterData(rawUserOp: UnsignedUserOperation, usePaymaster: boolean, safeNotDeployed: boolean): Promise<PaymasterData>;
|
8
|
+
sendUserOperation(userOp: UserOperation): Promise<string>;
|
9
|
+
getGasPrice(): Promise<GasPrices>;
|
10
|
+
_getUserOpReceiptInner(userOpHash: string): Promise<UserOperationReceipt | null>;
|
11
|
+
getUserOpReceipt(userOpHash: string): Promise<UserOperationReceipt>;
|
12
|
+
}
|
@@ -0,0 +1,58 @@
|
|
1
|
+
import { ethers } from "ethers";
|
2
|
+
import { PLACEHOLDER_SIG } from "../util";
|
3
|
+
export class Erc4337Bundler {
|
4
|
+
provider;
|
5
|
+
entryPointAddress;
|
6
|
+
constructor(bundlerUrl, entryPointAddress) {
|
7
|
+
this.entryPointAddress = entryPointAddress;
|
8
|
+
this.provider = new ethers.JsonRpcProvider(bundlerUrl);
|
9
|
+
}
|
10
|
+
async getPaymasterData(rawUserOp, usePaymaster, safeNotDeployed) {
|
11
|
+
// TODO: Keep this option out of the bundler
|
12
|
+
if (usePaymaster) {
|
13
|
+
console.log("Requesting paymaster data...");
|
14
|
+
const data = this.provider.send("pm_sponsorUserOperation", [
|
15
|
+
{ ...rawUserOp, signature: PLACEHOLDER_SIG },
|
16
|
+
this.entryPointAddress,
|
17
|
+
]);
|
18
|
+
return data;
|
19
|
+
}
|
20
|
+
return defaultPaymasterData(safeNotDeployed);
|
21
|
+
}
|
22
|
+
async sendUserOperation(userOp) {
|
23
|
+
try {
|
24
|
+
const userOpHash = await this.provider.send("eth_sendUserOperation", [
|
25
|
+
userOp,
|
26
|
+
this.entryPointAddress,
|
27
|
+
]);
|
28
|
+
return userOpHash;
|
29
|
+
}
|
30
|
+
catch (err) {
|
31
|
+
const error = err.error;
|
32
|
+
throw new Error(`Failed to send user op with: ${error.message}`);
|
33
|
+
}
|
34
|
+
}
|
35
|
+
async getGasPrice() {
|
36
|
+
return this.provider.send("pimlico_getUserOperationGasPrice", []);
|
37
|
+
}
|
38
|
+
async _getUserOpReceiptInner(userOpHash) {
|
39
|
+
return this.provider.send("eth_getUserOperationReceipt", [userOpHash]);
|
40
|
+
}
|
41
|
+
async getUserOpReceipt(userOpHash) {
|
42
|
+
let userOpReceipt = null;
|
43
|
+
while (!userOpReceipt) {
|
44
|
+
// Wait 2 seconds before checking the status again
|
45
|
+
await new Promise((resolve) => setTimeout(resolve, 2000));
|
46
|
+
userOpReceipt = await this._getUserOpReceiptInner(userOpHash);
|
47
|
+
}
|
48
|
+
return userOpReceipt;
|
49
|
+
}
|
50
|
+
}
|
51
|
+
// TODO(bh2smith) Should probably get reasonable estimates here:
|
52
|
+
const defaultPaymasterData = (safeNotDeployed) => {
|
53
|
+
return {
|
54
|
+
verificationGasLimit: ethers.toBeHex(safeNotDeployed ? 500000 : 100000),
|
55
|
+
callGasLimit: ethers.toBeHex(100000),
|
56
|
+
preVerificationGas: ethers.toBeHex(100000),
|
57
|
+
};
|
58
|
+
};
|
@@ -0,0 +1,11 @@
|
|
1
|
+
import { ethers } from "ethers";
|
2
|
+
export async function getNearSignature(adapter, hash) {
|
3
|
+
const viemHash = typeof hash === "string" ? hash : hash;
|
4
|
+
// MPC Contract produces two possible signatures.
|
5
|
+
const signature = await adapter.sign(viemHash);
|
6
|
+
if (ethers.recoverAddress(hash, signature).toLocaleLowerCase() ===
|
7
|
+
adapter.address.toLocaleLowerCase()) {
|
8
|
+
return signature;
|
9
|
+
}
|
10
|
+
throw new Error("Invalid signature!");
|
11
|
+
}
|
@@ -0,0 +1,24 @@
|
|
1
|
+
import { ethers } from "ethers";
|
2
|
+
import { GasPrice, PaymasterData, UnsignedUserOperation, UserOperation } from "../types";
|
3
|
+
import { MetaTransaction } from "ethers-multisend";
|
4
|
+
/**
|
5
|
+
* All contracts used in account creation & execution
|
6
|
+
*/
|
7
|
+
export declare class ContractSuite {
|
8
|
+
provider: ethers.JsonRpcProvider;
|
9
|
+
singleton: ethers.Contract;
|
10
|
+
proxyFactory: ethers.Contract;
|
11
|
+
m4337: ethers.Contract;
|
12
|
+
moduleSetup: ethers.Contract;
|
13
|
+
entryPoint: ethers.Contract;
|
14
|
+
constructor(provider: ethers.JsonRpcProvider, singleton: ethers.Contract, proxyFactory: ethers.Contract, m4337: ethers.Contract, moduleSetup: ethers.Contract, entryPoint: ethers.Contract);
|
15
|
+
static init(provider: ethers.JsonRpcProvider): Promise<ContractSuite>;
|
16
|
+
addressForSetup(setup: ethers.BytesLike, saltNonce?: string): Promise<string>;
|
17
|
+
getSetup(owners: string[]): Promise<string>;
|
18
|
+
getOpHash(unsignedUserOp: UserOperation, paymasterData: PaymasterData): Promise<string>;
|
19
|
+
factoryDataForSetup(safeNotDeployed: boolean, setup: string, safeSaltNonce: string): {
|
20
|
+
factory?: ethers.AddressLike;
|
21
|
+
factoryData?: string;
|
22
|
+
};
|
23
|
+
buildUserOp(txData: MetaTransaction, safeAddress: ethers.AddressLike, feeData: GasPrice, setup: string, safeNotDeployed: boolean, safeSaltNonce: string): Promise<UnsignedUserOperation>;
|
24
|
+
}
|
@@ -0,0 +1,108 @@
|
|
1
|
+
import { ethers } from "ethers";
|
2
|
+
import { getProxyFactoryDeployment, getSafeL2SingletonDeployment, } from "@safe-global/safe-deployments";
|
3
|
+
import { getSafe4337ModuleDeployment, getSafeModuleSetupDeployment, } from "@safe-global/safe-modules-deployments";
|
4
|
+
import { PLACEHOLDER_SIG, packGas, packPaymasterData } from "../util";
|
5
|
+
/**
|
6
|
+
* All contracts used in account creation & execution
|
7
|
+
*/
|
8
|
+
export class ContractSuite {
|
9
|
+
provider;
|
10
|
+
singleton;
|
11
|
+
proxyFactory;
|
12
|
+
m4337;
|
13
|
+
moduleSetup;
|
14
|
+
entryPoint;
|
15
|
+
constructor(provider, singleton, proxyFactory, m4337, moduleSetup, entryPoint) {
|
16
|
+
this.provider = provider;
|
17
|
+
this.singleton = singleton;
|
18
|
+
this.proxyFactory = proxyFactory;
|
19
|
+
this.m4337 = m4337;
|
20
|
+
this.moduleSetup = moduleSetup;
|
21
|
+
this.entryPoint = entryPoint;
|
22
|
+
}
|
23
|
+
static async init(provider) {
|
24
|
+
const safeDeployment = (fn) => getDeployment(fn, { provider, version: "1.4.1" });
|
25
|
+
const m4337Deployment = (fn) => getDeployment(fn, { provider, version: "0.3.0" });
|
26
|
+
// Need this first to get entryPoint address
|
27
|
+
const m4337 = await m4337Deployment(getSafe4337ModuleDeployment);
|
28
|
+
const [singleton, proxyFactory, moduleSetup, supportedEntryPoint] = await Promise.all([
|
29
|
+
safeDeployment(getSafeL2SingletonDeployment),
|
30
|
+
safeDeployment(getProxyFactoryDeployment),
|
31
|
+
m4337Deployment(getSafeModuleSetupDeployment),
|
32
|
+
m4337.SUPPORTED_ENTRYPOINT(),
|
33
|
+
]);
|
34
|
+
const entryPoint = new ethers.Contract(supportedEntryPoint, ["function getNonce(address, uint192 key) view returns (uint256 nonce)"], provider);
|
35
|
+
return new ContractSuite(provider, singleton, proxyFactory, m4337, moduleSetup, entryPoint);
|
36
|
+
}
|
37
|
+
async addressForSetup(setup, saltNonce) {
|
38
|
+
// bytes32 salt = keccak256(abi.encodePacked(keccak256(initializer), saltNonce));
|
39
|
+
// cf: https://github.com/safe-global/safe-smart-account/blob/499b17ad0191b575fcadc5cb5b8e3faeae5391ae/contracts/proxies/SafeProxyFactory.sol#L58
|
40
|
+
const salt = ethers.keccak256(ethers.solidityPacked(["bytes32", "uint256"], [ethers.keccak256(setup), saltNonce || 0]));
|
41
|
+
// abi.encodePacked(type(SafeProxy).creationCode, uint256(uint160(_singleton)));
|
42
|
+
// cf: https://github.com/safe-global/safe-smart-account/blob/499b17ad0191b575fcadc5cb5b8e3faeae5391ae/contracts/proxies/SafeProxyFactory.sol#L29
|
43
|
+
const initCode = ethers.solidityPacked(["bytes", "uint256"], [
|
44
|
+
await this.proxyFactory.proxyCreationCode(),
|
45
|
+
await this.singleton.getAddress(),
|
46
|
+
]);
|
47
|
+
return ethers.getCreate2Address(await this.proxyFactory.getAddress(), salt, ethers.keccak256(initCode));
|
48
|
+
}
|
49
|
+
async getSetup(owners) {
|
50
|
+
const setup = await this.singleton.interface.encodeFunctionData("setup", [
|
51
|
+
owners,
|
52
|
+
1, // We use sign threshold of 1.
|
53
|
+
this.moduleSetup.target,
|
54
|
+
this.moduleSetup.interface.encodeFunctionData("enableModules", [
|
55
|
+
[this.m4337.target],
|
56
|
+
]),
|
57
|
+
this.m4337.target,
|
58
|
+
ethers.ZeroAddress,
|
59
|
+
0,
|
60
|
+
ethers.ZeroAddress,
|
61
|
+
]);
|
62
|
+
return setup;
|
63
|
+
}
|
64
|
+
async getOpHash(unsignedUserOp, paymasterData) {
|
65
|
+
return this.m4337.getOperationHash({
|
66
|
+
...unsignedUserOp,
|
67
|
+
initCode: unsignedUserOp.factory
|
68
|
+
? ethers.solidityPacked(["address", "bytes"], [unsignedUserOp.factory, unsignedUserOp.factoryData])
|
69
|
+
: "0x",
|
70
|
+
accountGasLimits: packGas(unsignedUserOp.verificationGasLimit, unsignedUserOp.callGasLimit),
|
71
|
+
gasFees: packGas(unsignedUserOp.maxPriorityFeePerGas, unsignedUserOp.maxFeePerGas),
|
72
|
+
paymasterAndData: packPaymasterData(paymasterData),
|
73
|
+
signature: PLACEHOLDER_SIG,
|
74
|
+
});
|
75
|
+
}
|
76
|
+
factoryDataForSetup(safeNotDeployed, setup, safeSaltNonce) {
|
77
|
+
return safeNotDeployed
|
78
|
+
? {
|
79
|
+
factory: this.proxyFactory.target,
|
80
|
+
factoryData: this.proxyFactory.interface.encodeFunctionData("createProxyWithNonce", [this.singleton.target, setup, safeSaltNonce]),
|
81
|
+
}
|
82
|
+
: {};
|
83
|
+
}
|
84
|
+
async buildUserOp(txData, safeAddress, feeData, setup, safeNotDeployed, safeSaltNonce) {
|
85
|
+
const rawUserOp = {
|
86
|
+
sender: safeAddress,
|
87
|
+
nonce: ethers.toBeHex(await this.entryPoint.getNonce(safeAddress, 0)),
|
88
|
+
...this.factoryDataForSetup(safeNotDeployed, setup, safeSaltNonce),
|
89
|
+
// <https://github.com/safe-global/safe-modules/blob/9a18245f546bf2a8ed9bdc2b04aae44f949ec7a0/modules/4337/contracts/Safe4337Module.sol#L172>
|
90
|
+
callData: this.m4337.interface.encodeFunctionData("executeUserOp", [
|
91
|
+
txData.to,
|
92
|
+
BigInt(txData.value),
|
93
|
+
txData.data,
|
94
|
+
txData.operation || 0,
|
95
|
+
]),
|
96
|
+
...feeData,
|
97
|
+
};
|
98
|
+
return rawUserOp;
|
99
|
+
}
|
100
|
+
}
|
101
|
+
async function getDeployment(fn, { provider, version }) {
|
102
|
+
const { chainId } = await provider.getNetwork();
|
103
|
+
const deployment = fn({ version });
|
104
|
+
if (!deployment || !deployment.networkAddresses[`${chainId}`]) {
|
105
|
+
throw new Error(`Deployment not found for version ${version} and chainId ${chainId}`);
|
106
|
+
}
|
107
|
+
return new ethers.Contract(deployment.networkAddresses[`${chainId}`], deployment.abi, provider);
|
108
|
+
}
|
@@ -0,0 +1,39 @@
|
|
1
|
+
import { ethers } from "ethers";
|
2
|
+
import { NearEthAdapter } from "near-ca";
|
3
|
+
import { Erc4337Bundler } from "./lib/bundler";
|
4
|
+
import { UserOperation, UserOperationReceipt, UserOptions } from "./types";
|
5
|
+
import { MetaTransaction } from "ethers-multisend";
|
6
|
+
import { ContractSuite } from "./lib/safe";
|
7
|
+
import { Account } from "near-api-js";
|
8
|
+
export declare class TransactionManager {
|
9
|
+
readonly provider: ethers.JsonRpcProvider;
|
10
|
+
readonly nearAdapter: NearEthAdapter;
|
11
|
+
private safePack;
|
12
|
+
private bundler;
|
13
|
+
private setup;
|
14
|
+
readonly safeAddress: string;
|
15
|
+
private safeSaltNonce;
|
16
|
+
private _safeNotDeployed;
|
17
|
+
constructor(provider: ethers.JsonRpcProvider, nearAdapter: NearEthAdapter, safePack: ContractSuite, bundler: Erc4337Bundler, setup: string, safeAddress: string, safeSaltNonce: string, safeNotDeployed: boolean);
|
18
|
+
static create(config: {
|
19
|
+
ethRpc: string;
|
20
|
+
erc4337BundlerUrl: string;
|
21
|
+
nearAccount: Account;
|
22
|
+
mpcContractId: string;
|
23
|
+
safeSaltNonce?: string;
|
24
|
+
}): Promise<TransactionManager>;
|
25
|
+
get safeNotDeployed(): boolean;
|
26
|
+
get nearEOA(): `0x${string}`;
|
27
|
+
getSafeBalance(): Promise<bigint>;
|
28
|
+
buildTransaction(args: {
|
29
|
+
transactions: MetaTransaction[];
|
30
|
+
options: UserOptions;
|
31
|
+
}): Promise<{
|
32
|
+
safeOpHash: string;
|
33
|
+
unsignedUserOp: UserOperation;
|
34
|
+
}>;
|
35
|
+
signTransaction(safeOpHash: string): Promise<string>;
|
36
|
+
executeTransaction(userOp: UserOperation): Promise<UserOperationReceipt>;
|
37
|
+
addOwnerTx(address: string): MetaTransaction;
|
38
|
+
safeSufficientlyFunded(transactions: MetaTransaction[], gasCost: bigint): Promise<boolean>;
|
39
|
+
}
|
@@ -0,0 +1,99 @@
|
|
1
|
+
import { ethers } from "ethers";
|
2
|
+
import { NearEthAdapter, MpcContract } from "near-ca";
|
3
|
+
import { Erc4337Bundler } from "./lib/bundler";
|
4
|
+
import { packSignature } from "./util";
|
5
|
+
import { getNearSignature } from "./lib/near";
|
6
|
+
import { encodeMulti } from "ethers-multisend";
|
7
|
+
import { ContractSuite } from "./lib/safe";
|
8
|
+
export class TransactionManager {
|
9
|
+
provider;
|
10
|
+
nearAdapter;
|
11
|
+
safePack;
|
12
|
+
bundler;
|
13
|
+
setup;
|
14
|
+
safeAddress;
|
15
|
+
safeSaltNonce;
|
16
|
+
_safeNotDeployed;
|
17
|
+
constructor(provider, nearAdapter, safePack, bundler, setup, safeAddress, safeSaltNonce, safeNotDeployed) {
|
18
|
+
this.provider = provider;
|
19
|
+
this.nearAdapter = nearAdapter;
|
20
|
+
this.safePack = safePack;
|
21
|
+
this.bundler = bundler;
|
22
|
+
this.setup = setup;
|
23
|
+
this.safeAddress = safeAddress;
|
24
|
+
this.safeSaltNonce = safeSaltNonce;
|
25
|
+
this._safeNotDeployed = safeNotDeployed;
|
26
|
+
}
|
27
|
+
static async create(config) {
|
28
|
+
const provider = new ethers.JsonRpcProvider(config.ethRpc);
|
29
|
+
const [nearAdapter, safePack] = await Promise.all([
|
30
|
+
NearEthAdapter.fromConfig({
|
31
|
+
mpcContract: new MpcContract(config.nearAccount, config.mpcContractId),
|
32
|
+
}),
|
33
|
+
ContractSuite.init(provider),
|
34
|
+
]);
|
35
|
+
console.log(`Near Adapter: ${nearAdapter.nearAccountId()} <> ${nearAdapter.address}`);
|
36
|
+
const bundler = new Erc4337Bundler(config.erc4337BundlerUrl, await safePack.entryPoint.getAddress());
|
37
|
+
const setup = await safePack.getSetup([nearAdapter.address]);
|
38
|
+
const safeAddress = await safePack.addressForSetup(setup, config.safeSaltNonce);
|
39
|
+
const safeNotDeployed = (await provider.getCode(safeAddress)) === "0x";
|
40
|
+
console.log(`Safe Address: ${safeAddress} - deployed? ${!safeNotDeployed}`);
|
41
|
+
return new TransactionManager(provider, nearAdapter, safePack, bundler, setup, safeAddress, config.safeSaltNonce || "0", safeNotDeployed);
|
42
|
+
}
|
43
|
+
get safeNotDeployed() {
|
44
|
+
return this._safeNotDeployed;
|
45
|
+
}
|
46
|
+
get nearEOA() {
|
47
|
+
return this.nearAdapter.address;
|
48
|
+
}
|
49
|
+
async getSafeBalance() {
|
50
|
+
return await this.provider.getBalance(this.safeAddress);
|
51
|
+
}
|
52
|
+
async buildTransaction(args) {
|
53
|
+
const { transactions, options } = args;
|
54
|
+
const gasFees = (await this.bundler.getGasPrice()).fast;
|
55
|
+
// const gasFees = await this.provider.getFeeData();
|
56
|
+
// Build Singular MetaTransaction for Multisend from transaction list.
|
57
|
+
if (transactions.length === 0) {
|
58
|
+
throw new Error("Empty transaction set!");
|
59
|
+
}
|
60
|
+
const tx = transactions.length > 1 ? encodeMulti(transactions) : transactions[0];
|
61
|
+
const rawUserOp = await this.safePack.buildUserOp(tx, this.safeAddress, gasFees, this.setup, this.safeNotDeployed, this.safeSaltNonce);
|
62
|
+
const paymasterData = await this.bundler.getPaymasterData(rawUserOp, options.usePaymaster, this.safeNotDeployed);
|
63
|
+
const unsignedUserOp = { ...rawUserOp, ...paymasterData };
|
64
|
+
const safeOpHash = await this.safePack.getOpHash(unsignedUserOp, paymasterData);
|
65
|
+
return {
|
66
|
+
safeOpHash,
|
67
|
+
unsignedUserOp,
|
68
|
+
};
|
69
|
+
}
|
70
|
+
async signTransaction(safeOpHash) {
|
71
|
+
const signature = await getNearSignature(this.nearAdapter, safeOpHash);
|
72
|
+
return packSignature(signature);
|
73
|
+
}
|
74
|
+
async executeTransaction(userOp) {
|
75
|
+
const userOpHash = await this.bundler.sendUserOperation(userOp);
|
76
|
+
console.log("UserOp Hash", userOpHash);
|
77
|
+
const userOpReceipt = await this.bundler.getUserOpReceipt(userOpHash);
|
78
|
+
console.log("userOp Receipt", userOpReceipt);
|
79
|
+
// Update safeNotDeployed after the first transaction
|
80
|
+
this._safeNotDeployed =
|
81
|
+
(await this.provider.getCode(this.safeAddress)) === "0x";
|
82
|
+
return userOpReceipt;
|
83
|
+
}
|
84
|
+
addOwnerTx(address) {
|
85
|
+
return {
|
86
|
+
to: this.safeAddress,
|
87
|
+
value: "0",
|
88
|
+
data: this.safePack.singleton.interface.encodeFunctionData("addOwnerWithThreshold", [address, 1]),
|
89
|
+
};
|
90
|
+
}
|
91
|
+
async safeSufficientlyFunded(transactions, gasCost) {
|
92
|
+
const txValue = transactions.reduce((acc, tx) => acc + BigInt(tx.value), 0n);
|
93
|
+
if (txValue + gasCost === 0n) {
|
94
|
+
return true;
|
95
|
+
}
|
96
|
+
const safeBalance = await this.getSafeBalance();
|
97
|
+
return txValue + gasCost < safeBalance;
|
98
|
+
}
|
99
|
+
}
|
@@ -0,0 +1,85 @@
|
|
1
|
+
import { ethers } from "ethers";
|
2
|
+
export interface UnsignedUserOperation {
|
3
|
+
sender: ethers.AddressLike;
|
4
|
+
nonce: string;
|
5
|
+
factory?: ethers.AddressLike;
|
6
|
+
factoryData?: ethers.BytesLike;
|
7
|
+
callData: string;
|
8
|
+
maxPriorityFeePerGas: string;
|
9
|
+
maxFeePerGas: string;
|
10
|
+
}
|
11
|
+
/**
|
12
|
+
* Supported Representation of UserOperation for EntryPoint v0.7
|
13
|
+
*/
|
14
|
+
export interface UserOperation extends UnsignedUserOperation {
|
15
|
+
verificationGasLimit: string;
|
16
|
+
callGasLimit: string;
|
17
|
+
preVerificationGas: string;
|
18
|
+
signature?: string;
|
19
|
+
}
|
20
|
+
export interface PaymasterData {
|
21
|
+
paymaster?: string;
|
22
|
+
paymasterData?: string;
|
23
|
+
paymasterVerificationGasLimit?: string;
|
24
|
+
paymasterPostOpGasLimit?: string;
|
25
|
+
verificationGasLimit: string;
|
26
|
+
callGasLimit: string;
|
27
|
+
preVerificationGas: string;
|
28
|
+
}
|
29
|
+
export interface UserOptions {
|
30
|
+
usePaymaster: boolean;
|
31
|
+
safeSaltNonce: string;
|
32
|
+
mpcContractId: string;
|
33
|
+
recoveryAddress?: string;
|
34
|
+
}
|
35
|
+
export type TStatus = "success" | "reverted";
|
36
|
+
export type Address = ethers.AddressLike;
|
37
|
+
export type Hex = `0x${string}`;
|
38
|
+
export type Hash = `0x${string}`;
|
39
|
+
interface Log {
|
40
|
+
logIndex: string;
|
41
|
+
transactionIndex: string;
|
42
|
+
transactionHash: string;
|
43
|
+
blockHash: string;
|
44
|
+
blockNumber: string;
|
45
|
+
address: string;
|
46
|
+
data: string;
|
47
|
+
topics: string[];
|
48
|
+
}
|
49
|
+
interface Receipt {
|
50
|
+
transactionHash: Hex;
|
51
|
+
transactionIndex: bigint;
|
52
|
+
blockHash: Hash;
|
53
|
+
blockNumber: bigint;
|
54
|
+
from: Address;
|
55
|
+
to: Address | null;
|
56
|
+
cumulativeGasUsed: bigint;
|
57
|
+
status: TStatus;
|
58
|
+
gasUsed: bigint;
|
59
|
+
contractAddress: Address | null;
|
60
|
+
logsBloom: Hex;
|
61
|
+
effectiveGasPrice: bigint;
|
62
|
+
}
|
63
|
+
export type UserOperationReceipt = {
|
64
|
+
userOpHash: Hash;
|
65
|
+
entryPoint: Address;
|
66
|
+
sender: Address;
|
67
|
+
nonce: bigint;
|
68
|
+
paymaster?: Address;
|
69
|
+
actualGasUsed: bigint;
|
70
|
+
actualGasCost: bigint;
|
71
|
+
success: boolean;
|
72
|
+
reason?: string;
|
73
|
+
receipt: Receipt;
|
74
|
+
logs: Log[];
|
75
|
+
};
|
76
|
+
export interface GasPrices {
|
77
|
+
slow: GasPrice;
|
78
|
+
standard: GasPrice;
|
79
|
+
fast: GasPrice;
|
80
|
+
}
|
81
|
+
export interface GasPrice {
|
82
|
+
maxFeePerGas: Hex;
|
83
|
+
maxPriorityFeePerGas: Hex;
|
84
|
+
}
|
85
|
+
export {};
|
@@ -0,0 +1 @@
|
|
1
|
+
export {};
|
@@ -0,0 +1,8 @@
|
|
1
|
+
import { ethers } from "ethers";
|
2
|
+
import { PaymasterData } from "./types";
|
3
|
+
import { MetaTransaction } from "ethers-multisend";
|
4
|
+
export declare const PLACEHOLDER_SIG: string;
|
5
|
+
export declare const packGas: (hi: ethers.BigNumberish, lo: ethers.BigNumberish) => string;
|
6
|
+
export declare function packSignature(signature: string, validFrom?: number, validTo?: number): string;
|
7
|
+
export declare function packPaymasterData(data: PaymasterData): string;
|
8
|
+
export declare function containsValue(transactions: MetaTransaction[]): boolean;
|
package/dist/esm/util.js
ADDED
@@ -0,0 +1,19 @@
|
|
1
|
+
import { ethers } from "ethers";
|
2
|
+
export const PLACEHOLDER_SIG = ethers.solidityPacked(["uint48", "uint48"], [0, 0]);
|
3
|
+
export const packGas = (hi, lo) => ethers.solidityPacked(["uint128", "uint128"], [hi, lo]);
|
4
|
+
export function packSignature(signature, validFrom = 0, validTo = 0) {
|
5
|
+
return ethers.solidityPacked(["uint48", "uint48", "bytes"], [validFrom, validTo, signature]);
|
6
|
+
}
|
7
|
+
export function packPaymasterData(data) {
|
8
|
+
return data.paymaster
|
9
|
+
? ethers.hexlify(ethers.concat([
|
10
|
+
data.paymaster,
|
11
|
+
ethers.toBeHex(data.paymasterVerificationGasLimit || "0x", 16),
|
12
|
+
ethers.toBeHex(data.paymasterPostOpGasLimit || "0x", 16),
|
13
|
+
data.paymasterData || "0x",
|
14
|
+
]))
|
15
|
+
: "0x";
|
16
|
+
}
|
17
|
+
export function containsValue(transactions) {
|
18
|
+
return transactions.some((tx) => tx.value !== "0");
|
19
|
+
}
|
package/package.json
ADDED
@@ -0,0 +1,41 @@
|
|
1
|
+
{
|
2
|
+
"name": "near-safe",
|
3
|
+
"version": "0.0.0",
|
4
|
+
"license": "MIT",
|
5
|
+
"main": "dist/cjs/index.js",
|
6
|
+
"module": "dist/cjs/index.js",
|
7
|
+
"types": "dist/esm/index.d.ts",
|
8
|
+
"files": [
|
9
|
+
"dist/**/*"
|
10
|
+
],
|
11
|
+
"scripts": {
|
12
|
+
"build": "rm -fr dist/* && yarn build:esm && yarn build:cjs",
|
13
|
+
"build:esm": "tsc -p tsconfig.esm.json",
|
14
|
+
"build:cjs": "tsc -p tsconfig.cjs.json",
|
15
|
+
"start": "yarn example",
|
16
|
+
"example": "tsx examples/send-tx.ts",
|
17
|
+
"lint": "eslint . --ignore-pattern dist/",
|
18
|
+
"fmt": "prettier --write '{src,examples,tests}/**/*.{js,jsx,ts,tsx}'",
|
19
|
+
"all": "yarn fmt && yarn lint && yarn build"
|
20
|
+
},
|
21
|
+
"dependencies": {
|
22
|
+
"@safe-global/safe-deployments": "^1.37.0",
|
23
|
+
"@safe-global/safe-modules-deployments": "^2.2.0",
|
24
|
+
"ethers": "^6.13.1",
|
25
|
+
"ethers-multisend": "^3.1.0",
|
26
|
+
"near-api-js": "^4.0.3",
|
27
|
+
"near-ca": "^0.3.1"
|
28
|
+
},
|
29
|
+
"devDependencies": {
|
30
|
+
"@types/node": "^22.3.0",
|
31
|
+
"@types/yargs": "^17.0.32",
|
32
|
+
"@typescript-eslint/eslint-plugin": "^8.1.0",
|
33
|
+
"@typescript-eslint/parser": "^8.1.0",
|
34
|
+
"dotenv": "^16.4.5",
|
35
|
+
"eslint": "^9.6.0",
|
36
|
+
"prettier": "^3.3.2",
|
37
|
+
"tsx": "^4.16.0",
|
38
|
+
"typescript": "^5.5.2",
|
39
|
+
"yargs": "^17.7.2"
|
40
|
+
}
|
41
|
+
}
|