near-safe 0.0.0
Sign up to get free protection for your applications and to get access to all the features.
- 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
|
+
}
|