near-safe 0.2.1 → 0.3.1
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/dist/cjs/lib/bundler.d.ts +30 -8
- package/dist/cjs/lib/bundler.js +45 -26
- package/dist/cjs/lib/safe-message.d.ts +27 -0
- package/dist/cjs/lib/safe-message.js +112 -0
- package/dist/cjs/lib/safe.d.ts +19 -16
- package/dist/cjs/lib/safe.js +117 -71
- package/dist/cjs/tx-manager.d.ts +20 -5
- package/dist/cjs/tx-manager.js +80 -37
- package/dist/cjs/util.d.ts +5 -1
- package/dist/cjs/util.js +34 -0
- package/dist/esm/lib/bundler.d.ts +30 -8
- package/dist/esm/lib/bundler.js +47 -28
- package/dist/esm/lib/safe-message.d.ts +27 -0
- package/dist/esm/lib/safe-message.js +108 -0
- package/dist/esm/lib/safe.d.ts +19 -16
- package/dist/esm/lib/safe.js +120 -73
- package/dist/esm/tx-manager.d.ts +20 -5
- package/dist/esm/tx-manager.js +82 -40
- package/dist/esm/util.d.ts +5 -1
- package/dist/esm/util.js +32 -1
- package/package.json +11 -5
package/dist/esm/lib/safe.js
CHANGED
@@ -1,19 +1,20 @@
|
|
1
1
|
import { getProxyFactoryDeployment, getSafeL2SingletonDeployment, } from "@safe-global/safe-deployments";
|
2
2
|
import { getSafe4337ModuleDeployment, getSafeModuleSetupDeployment, } from "@safe-global/safe-modules-deployments";
|
3
|
-
import {
|
4
|
-
import { PLACEHOLDER_SIG, packGas, packPaymasterData } from "../util";
|
3
|
+
import { encodeFunctionData, encodePacked, getCreate2Address, keccak256, parseAbi, toHex, zeroAddress, } from "viem";
|
4
|
+
import { PLACEHOLDER_SIG, getClient, packGas, packPaymasterData, } from "../util";
|
5
5
|
/**
|
6
6
|
* All contracts used in account creation & execution
|
7
7
|
*/
|
8
8
|
export class ContractSuite {
|
9
|
-
|
9
|
+
// Used only for stateless contract reads.
|
10
|
+
dummyClient;
|
10
11
|
singleton;
|
11
12
|
proxyFactory;
|
12
13
|
m4337;
|
13
14
|
moduleSetup;
|
14
15
|
entryPoint;
|
15
|
-
constructor(
|
16
|
-
this.
|
16
|
+
constructor(client, singleton, proxyFactory, m4337, moduleSetup, entryPoint) {
|
17
|
+
this.dummyClient = client;
|
17
18
|
this.singleton = singleton;
|
18
19
|
this.proxyFactory = proxyFactory;
|
19
20
|
this.m4337 = m4337;
|
@@ -22,108 +23,154 @@ export class ContractSuite {
|
|
22
23
|
}
|
23
24
|
static async init() {
|
24
25
|
// TODO - this is a cheeky hack.
|
25
|
-
const
|
26
|
-
const safeDeployment = (fn) => getDeployment(fn, {
|
26
|
+
const client = getClient(11155111);
|
27
|
+
const safeDeployment = (fn) => getDeployment(fn, { version: "1.4.1" });
|
27
28
|
const m4337Deployment = async (fn) => {
|
28
|
-
return getDeployment(fn, {
|
29
|
+
return getDeployment(fn, { version: "0.3.0" });
|
29
30
|
};
|
30
|
-
|
31
|
-
const m4337 = await m4337Deployment(getSafe4337ModuleDeployment);
|
32
|
-
const [singleton, proxyFactory, moduleSetup, supportedEntryPoint] = await Promise.all([
|
31
|
+
const [singleton, proxyFactory, moduleSetup, m4337] = await Promise.all([
|
33
32
|
safeDeployment(getSafeL2SingletonDeployment),
|
34
33
|
safeDeployment(getProxyFactoryDeployment),
|
35
34
|
m4337Deployment(getSafeModuleSetupDeployment),
|
36
|
-
|
35
|
+
m4337Deployment(getSafe4337ModuleDeployment),
|
37
36
|
]);
|
38
|
-
|
39
|
-
|
40
|
-
|
41
|
-
|
42
|
-
|
43
|
-
|
44
|
-
|
37
|
+
// console.log("Initialized ERC4337 & Safe Module Contracts:", {
|
38
|
+
// singleton: await singleton.getAddress(),
|
39
|
+
// proxyFactory: await proxyFactory.getAddress(),
|
40
|
+
// m4337: await m4337.getAddress(),
|
41
|
+
// moduleSetup: await moduleSetup.getAddress(),
|
42
|
+
// entryPoint: await entryPoint.getAddress(),
|
43
|
+
// });
|
44
|
+
return new ContractSuite(client, singleton, proxyFactory, m4337, moduleSetup,
|
45
|
+
// EntryPoint:
|
46
|
+
{
|
47
|
+
address: (await client.readContract({
|
48
|
+
address: m4337.address,
|
49
|
+
abi: m4337.abi,
|
50
|
+
functionName: "SUPPORTED_ENTRYPOINT",
|
51
|
+
})),
|
52
|
+
abi: parseAbi([
|
53
|
+
"function getNonce(address, uint192 key) view returns (uint256 nonce)",
|
54
|
+
]),
|
45
55
|
});
|
46
|
-
return new ContractSuite(provider, singleton, proxyFactory, m4337, moduleSetup, entryPoint);
|
47
56
|
}
|
48
57
|
async addressForSetup(setup, saltNonce) {
|
49
58
|
// bytes32 salt = keccak256(abi.encodePacked(keccak256(initializer), saltNonce));
|
50
59
|
// cf: https://github.com/safe-global/safe-smart-account/blob/499b17ad0191b575fcadc5cb5b8e3faeae5391ae/contracts/proxies/SafeProxyFactory.sol#L58
|
51
|
-
const salt =
|
60
|
+
const salt = keccak256(encodePacked(["bytes32", "uint256"], [keccak256(setup), BigInt(saltNonce || "0")]));
|
52
61
|
// abi.encodePacked(type(SafeProxy).creationCode, uint256(uint160(_singleton)));
|
53
62
|
// cf: https://github.com/safe-global/safe-smart-account/blob/499b17ad0191b575fcadc5cb5b8e3faeae5391ae/contracts/proxies/SafeProxyFactory.sol#L29
|
54
|
-
const initCode =
|
55
|
-
await this.
|
56
|
-
|
63
|
+
const initCode = encodePacked(["bytes", "uint256"], [
|
64
|
+
(await this.dummyClient.readContract({
|
65
|
+
address: this.proxyFactory.address,
|
66
|
+
abi: this.proxyFactory.abi,
|
67
|
+
functionName: "proxyCreationCode",
|
68
|
+
})),
|
69
|
+
BigInt(this.singleton.address),
|
57
70
|
]);
|
58
|
-
return
|
71
|
+
return getCreate2Address({
|
72
|
+
from: this.proxyFactory.address,
|
73
|
+
salt,
|
74
|
+
bytecodeHash: keccak256(initCode),
|
75
|
+
});
|
59
76
|
}
|
60
|
-
|
61
|
-
|
62
|
-
|
63
|
-
|
64
|
-
|
65
|
-
|
66
|
-
|
67
|
-
|
68
|
-
|
69
|
-
|
70
|
-
|
71
|
-
|
72
|
-
|
73
|
-
|
77
|
+
getSetup(owners) {
|
78
|
+
return encodeFunctionData({
|
79
|
+
abi: this.singleton.abi,
|
80
|
+
functionName: "setup",
|
81
|
+
args: [
|
82
|
+
owners,
|
83
|
+
1, // We use sign threshold of 1.
|
84
|
+
this.moduleSetup.address,
|
85
|
+
encodeFunctionData({
|
86
|
+
abi: this.moduleSetup.abi,
|
87
|
+
functionName: "enableModules",
|
88
|
+
args: [[this.m4337.address]],
|
89
|
+
}),
|
90
|
+
this.m4337.address,
|
91
|
+
zeroAddress,
|
92
|
+
0,
|
93
|
+
zeroAddress,
|
94
|
+
],
|
95
|
+
});
|
96
|
+
}
|
97
|
+
addOwnerData(newOwner) {
|
98
|
+
return encodeFunctionData({
|
99
|
+
abi: this.singleton.abi,
|
100
|
+
functionName: "addOwnerWithThreshold",
|
101
|
+
args: [newOwner, 1],
|
102
|
+
});
|
74
103
|
}
|
75
104
|
async getOpHash(unsignedUserOp) {
|
76
105
|
const { factory, factoryData, verificationGasLimit, callGasLimit, maxPriorityFeePerGas, maxFeePerGas, } = unsignedUserOp;
|
77
|
-
|
78
|
-
|
79
|
-
|
80
|
-
|
81
|
-
|
82
|
-
|
83
|
-
|
84
|
-
|
85
|
-
|
106
|
+
const opHash = await this.dummyClient.readContract({
|
107
|
+
address: this.m4337.address,
|
108
|
+
abi: this.m4337.abi,
|
109
|
+
functionName: "getOperationHash",
|
110
|
+
args: [
|
111
|
+
{
|
112
|
+
...unsignedUserOp,
|
113
|
+
initCode: factory
|
114
|
+
? encodePacked(["address", "bytes"], [factory, factoryData])
|
115
|
+
: "0x",
|
116
|
+
accountGasLimits: packGas(verificationGasLimit, callGasLimit),
|
117
|
+
gasFees: packGas(maxPriorityFeePerGas, maxFeePerGas),
|
118
|
+
paymasterAndData: packPaymasterData(unsignedUserOp),
|
119
|
+
signature: PLACEHOLDER_SIG,
|
120
|
+
},
|
121
|
+
],
|
86
122
|
});
|
123
|
+
return opHash;
|
87
124
|
}
|
88
125
|
factoryDataForSetup(safeNotDeployed, setup, safeSaltNonce) {
|
89
126
|
return safeNotDeployed
|
90
127
|
? {
|
91
|
-
factory: this.proxyFactory.
|
92
|
-
factoryData:
|
128
|
+
factory: this.proxyFactory.address,
|
129
|
+
factoryData: encodeFunctionData({
|
130
|
+
abi: this.proxyFactory.abi,
|
131
|
+
functionName: "createProxyWithNonce",
|
132
|
+
args: [this.singleton.address, setup, safeSaltNonce],
|
133
|
+
}),
|
93
134
|
}
|
94
135
|
: {};
|
95
136
|
}
|
96
|
-
async buildUserOp(txData, safeAddress, feeData, setup, safeNotDeployed, safeSaltNonce) {
|
97
|
-
|
137
|
+
async buildUserOp(nonce, txData, safeAddress, feeData, setup, safeNotDeployed, safeSaltNonce) {
|
138
|
+
return {
|
98
139
|
sender: safeAddress,
|
99
|
-
nonce:
|
140
|
+
nonce: toHex(nonce),
|
100
141
|
...this.factoryDataForSetup(safeNotDeployed, setup, safeSaltNonce),
|
101
142
|
// <https://github.com/safe-global/safe-modules/blob/9a18245f546bf2a8ed9bdc2b04aae44f949ec7a0/modules/4337/contracts/Safe4337Module.sol#L172>
|
102
|
-
callData:
|
103
|
-
|
104
|
-
|
105
|
-
|
106
|
-
|
107
|
-
|
143
|
+
callData: encodeFunctionData({
|
144
|
+
abi: this.m4337.abi,
|
145
|
+
functionName: "executeUserOp",
|
146
|
+
args: [
|
147
|
+
txData.to,
|
148
|
+
BigInt(txData.value),
|
149
|
+
txData.data,
|
150
|
+
txData.operation || 0,
|
151
|
+
],
|
152
|
+
}),
|
108
153
|
...feeData,
|
109
154
|
};
|
110
|
-
|
155
|
+
}
|
156
|
+
async getNonce(address, chainId) {
|
157
|
+
const nonce = (await getClient(chainId).readContract({
|
158
|
+
abi: this.entryPoint.abi,
|
159
|
+
address: this.entryPoint.address,
|
160
|
+
functionName: "getNonce",
|
161
|
+
args: [address, 0],
|
162
|
+
}));
|
163
|
+
return nonce;
|
111
164
|
}
|
112
165
|
}
|
113
|
-
async function getDeployment(fn, {
|
114
|
-
const { chainId } = await provider.getNetwork();
|
166
|
+
async function getDeployment(fn, { version }) {
|
115
167
|
const deployment = fn({ version });
|
116
168
|
if (!deployment) {
|
117
|
-
throw new Error(`Deployment not found for ${fn.name} version ${version}
|
118
|
-
}
|
119
|
-
let address = deployment.networkAddresses[`${chainId}`];
|
120
|
-
if (!address) {
|
121
|
-
// console.warn(
|
122
|
-
// `Deployment asset ${fn.name} not listed on chainId ${chainId}, using likely fallback. For more info visit https://github.com/safe-global/safe-modules-deployments`
|
123
|
-
// );
|
124
|
-
// TODO: This is a cheeky hack. Real solution proposed in
|
125
|
-
// https://github.com/Mintbase/near-safe/issues/42
|
126
|
-
address = deployment.networkAddresses["11155111"];
|
169
|
+
throw new Error(`Deployment not found for ${fn.name} version ${version}`);
|
127
170
|
}
|
128
|
-
|
171
|
+
// TODO: maybe call parseAbi on deployment.abi here.
|
172
|
+
return {
|
173
|
+
address: deployment.networkAddresses["11155111"],
|
174
|
+
abi: deployment.abi,
|
175
|
+
};
|
129
176
|
}
|
package/dist/esm/tx-manager.d.ts
CHANGED
@@ -1,5 +1,5 @@
|
|
1
1
|
import { FinalExecutionOutcome } from "near-api-js/lib/providers";
|
2
|
-
import { NearEthAdapter, NearEthTxData,
|
2
|
+
import { NearEthAdapter, SignRequestData, NearEthTxData, RecoveryData } from "near-ca";
|
3
3
|
import { Address, Hash, Hex } from "viem";
|
4
4
|
import { Erc4337Bundler } from "./lib/bundler";
|
5
5
|
import { ContractSuite } from "./lib/safe";
|
@@ -7,13 +7,12 @@ import { MetaTransaction, UserOperation, UserOperationReceipt } from "./types";
|
|
7
7
|
export declare class TransactionManager {
|
8
8
|
readonly nearAdapter: NearEthAdapter;
|
9
9
|
readonly address: Address;
|
10
|
-
readonly entryPointAddress: Address;
|
11
10
|
private safePack;
|
12
11
|
private setup;
|
13
12
|
private pimlicoKey;
|
14
13
|
private safeSaltNonce;
|
15
14
|
private deployedChains;
|
16
|
-
constructor(nearAdapter: NearEthAdapter, safePack: ContractSuite, pimlicoKey: string, setup: string, safeAddress: Address,
|
15
|
+
constructor(nearAdapter: NearEthAdapter, safePack: ContractSuite, pimlicoKey: string, setup: string, safeAddress: Address, safeSaltNonce: string);
|
17
16
|
static create(config: {
|
18
17
|
accountId: string;
|
19
18
|
mpcContractId: string;
|
@@ -22,6 +21,7 @@ export declare class TransactionManager {
|
|
22
21
|
safeSaltNonce?: string;
|
23
22
|
}): Promise<TransactionManager>;
|
24
23
|
get mpcAddress(): Address;
|
24
|
+
get mpcContractId(): string;
|
25
25
|
getBalance(chainId: number): Promise<bigint>;
|
26
26
|
bundlerForChainId(chainId: number): Erc4337Bundler;
|
27
27
|
buildTransaction(args: {
|
@@ -31,13 +31,28 @@ export declare class TransactionManager {
|
|
31
31
|
}): Promise<UserOperation>;
|
32
32
|
signTransaction(safeOpHash: Hex): Promise<Hex>;
|
33
33
|
opHash(userOp: UserOperation): Promise<Hash>;
|
34
|
-
encodeSignRequest(
|
34
|
+
encodeSignRequest(signRequest: SignRequestData, usePaymaster: boolean): Promise<NearEthTxData>;
|
35
35
|
executeTransaction(chainId: number, userOp: UserOperation): Promise<UserOperationReceipt>;
|
36
36
|
safeDeployed(chainId: number): Promise<boolean>;
|
37
|
-
addOwnerTx(address:
|
37
|
+
addOwnerTx(address: Address): MetaTransaction;
|
38
38
|
safeSufficientlyFunded(chainId: number, transactions: MetaTransaction[], gasCost: bigint): Promise<boolean>;
|
39
39
|
broadcastEvm(chainId: number, outcome: FinalExecutionOutcome, unsignedUserOp: UserOperation): Promise<{
|
40
40
|
signature: Hex;
|
41
41
|
receipt: UserOperationReceipt;
|
42
42
|
}>;
|
43
|
+
/**
|
44
|
+
* Handles routing of signature requests based on the provided method, chain ID, and parameters.
|
45
|
+
*
|
46
|
+
* @async
|
47
|
+
* @function requestRouter
|
48
|
+
* @param {SignRequestData} params - An object containing the method, chain ID, and request parameters.
|
49
|
+
* @returns {Promise<{ evmMessage: string; payload: number[]; recoveryData: RecoveryData }>}
|
50
|
+
* - Returns a promise that resolves to an object containing the Ethereum Virtual Machine (EVM) message,
|
51
|
+
* the payload (hashed data), and recovery data needed for reconstructing the signature request.
|
52
|
+
*/
|
53
|
+
requestRouter({ method, chainId, params }: SignRequestData, usePaymaster: boolean): Promise<{
|
54
|
+
evmMessage: string;
|
55
|
+
payload: number[];
|
56
|
+
recoveryData: RecoveryData;
|
57
|
+
}>;
|
43
58
|
}
|
package/dist/esm/tx-manager.js
CHANGED
@@ -1,23 +1,22 @@
|
|
1
|
-
import {
|
1
|
+
import { setupAdapter, signatureFromOutcome, toPayload, } from "near-ca";
|
2
2
|
import { serializeSignature } from "viem";
|
3
3
|
import { Erc4337Bundler } from "./lib/bundler";
|
4
4
|
import { encodeMulti } from "./lib/multisend";
|
5
5
|
import { ContractSuite } from "./lib/safe";
|
6
|
-
import {
|
6
|
+
import { decodeSafeMessage, safeMessageTxData } from "./lib/safe-message";
|
7
|
+
import { getClient, isContract, metaTransactionsFromRequest, packSignature, } from "./util";
|
7
8
|
export class TransactionManager {
|
8
9
|
nearAdapter;
|
9
10
|
address;
|
10
|
-
entryPointAddress;
|
11
11
|
safePack;
|
12
12
|
setup;
|
13
13
|
pimlicoKey;
|
14
14
|
safeSaltNonce;
|
15
15
|
deployedChains;
|
16
|
-
constructor(nearAdapter, safePack, pimlicoKey, setup, safeAddress,
|
16
|
+
constructor(nearAdapter, safePack, pimlicoKey, setup, safeAddress, safeSaltNonce) {
|
17
17
|
this.nearAdapter = nearAdapter;
|
18
18
|
this.safePack = safePack;
|
19
19
|
this.pimlicoKey = pimlicoKey;
|
20
|
-
this.entryPointAddress = entryPointAddress;
|
21
20
|
this.setup = setup;
|
22
21
|
this.address = safeAddress;
|
23
22
|
this.safeSaltNonce = safeSaltNonce;
|
@@ -30,34 +29,38 @@ export class TransactionManager {
|
|
30
29
|
ContractSuite.init(),
|
31
30
|
]);
|
32
31
|
console.log(`Near Adapter: ${nearAdapter.nearAccountId()} <> ${nearAdapter.address}`);
|
33
|
-
const setup =
|
32
|
+
const setup = safePack.getSetup([nearAdapter.address]);
|
34
33
|
const safeAddress = await safePack.addressForSetup(setup, config.safeSaltNonce);
|
35
|
-
const entryPointAddress = (await safePack.entryPoint.getAddress());
|
36
34
|
console.log(`Safe Address: ${safeAddress}`);
|
37
|
-
return new TransactionManager(nearAdapter, safePack, pimlicoKey, setup, safeAddress,
|
35
|
+
return new TransactionManager(nearAdapter, safePack, pimlicoKey, setup, safeAddress, config.safeSaltNonce || "0");
|
38
36
|
}
|
39
37
|
get mpcAddress() {
|
40
38
|
return this.nearAdapter.address;
|
41
39
|
}
|
40
|
+
get mpcContractId() {
|
41
|
+
return this.nearAdapter.mpcContract.contract.contractId;
|
42
|
+
}
|
42
43
|
async getBalance(chainId) {
|
43
|
-
|
44
|
-
return await provider.getBalance({ address: this.address });
|
44
|
+
return await getClient(chainId).getBalance({ address: this.address });
|
45
45
|
}
|
46
46
|
bundlerForChainId(chainId) {
|
47
|
-
return new Erc4337Bundler(this.
|
47
|
+
return new Erc4337Bundler(this.safePack.entryPoint.address, this.pimlicoKey, chainId);
|
48
48
|
}
|
49
49
|
async buildTransaction(args) {
|
50
50
|
const { transactions, usePaymaster, chainId } = args;
|
51
|
-
const bundler = this.bundlerForChainId(chainId);
|
52
|
-
const gasFees = (await bundler.getGasPrice()).fast;
|
53
|
-
// Build Singular MetaTransaction for Multisend from transaction list.
|
54
51
|
if (transactions.length === 0) {
|
55
52
|
throw new Error("Empty transaction set!");
|
56
53
|
}
|
54
|
+
const bundler = this.bundlerForChainId(chainId);
|
55
|
+
const [gasFees, nonce, safeDeployed] = await Promise.all([
|
56
|
+
bundler.getGasPrice(),
|
57
|
+
this.safePack.getNonce(this.address, chainId),
|
58
|
+
this.safeDeployed(chainId),
|
59
|
+
]);
|
60
|
+
// Build Singular MetaTransaction for Multisend from transaction list.
|
57
61
|
const tx = transactions.length > 1 ? encodeMulti(transactions) : transactions[0];
|
58
|
-
const
|
59
|
-
const
|
60
|
-
const paymasterData = await bundler.getPaymasterData(rawUserOp, usePaymaster, safeNotDeployed);
|
62
|
+
const rawUserOp = await this.safePack.buildUserOp(nonce, tx, this.address, gasFees.fast, this.setup, !safeDeployed, this.safeSaltNonce);
|
63
|
+
const paymasterData = await bundler.getPaymasterData(rawUserOp, usePaymaster, !safeDeployed);
|
61
64
|
const unsignedUserOp = { ...rawUserOp, ...paymasterData };
|
62
65
|
return unsignedUserOp;
|
63
66
|
}
|
@@ -68,27 +71,15 @@ export class TransactionManager {
|
|
68
71
|
async opHash(userOp) {
|
69
72
|
return this.safePack.getOpHash(userOp);
|
70
73
|
}
|
71
|
-
async encodeSignRequest(
|
72
|
-
const
|
73
|
-
chainId: tx.chainId,
|
74
|
-
transactions: [
|
75
|
-
{
|
76
|
-
to: tx.to,
|
77
|
-
value: (tx.value || 0n).toString(),
|
78
|
-
data: tx.data || "0x",
|
79
|
-
},
|
80
|
-
],
|
81
|
-
usePaymaster: true,
|
82
|
-
});
|
83
|
-
const safeOpHash = (await this.opHash(unsignedUserOp));
|
84
|
-
const signRequest = await this.nearAdapter.encodeSignRequest({
|
85
|
-
method: "hash",
|
86
|
-
chainId: 0,
|
87
|
-
params: safeOpHash,
|
88
|
-
});
|
74
|
+
async encodeSignRequest(signRequest, usePaymaster) {
|
75
|
+
const data = await this.requestRouter(signRequest, usePaymaster);
|
89
76
|
return {
|
90
|
-
|
91
|
-
|
77
|
+
nearPayload: await this.nearAdapter.mpcContract.encodeSignatureRequestTx({
|
78
|
+
path: this.nearAdapter.derivationPath,
|
79
|
+
payload: data.payload,
|
80
|
+
key_version: 0,
|
81
|
+
}),
|
82
|
+
...data,
|
92
83
|
};
|
93
84
|
}
|
94
85
|
async executeTransaction(chainId, userOp) {
|
@@ -102,11 +93,11 @@ export class TransactionManager {
|
|
102
93
|
return userOpReceipt;
|
103
94
|
}
|
104
95
|
async safeDeployed(chainId) {
|
96
|
+
// Early exit if already known.
|
105
97
|
if (chainId in this.deployedChains) {
|
106
98
|
return true;
|
107
99
|
}
|
108
|
-
const
|
109
|
-
const deployed = (await provider.getCode({ address: this.address })) !== "0x";
|
100
|
+
const deployed = await isContract(this.address, chainId);
|
110
101
|
if (deployed) {
|
111
102
|
this.deployedChains.add(chainId);
|
112
103
|
}
|
@@ -116,7 +107,7 @@ export class TransactionManager {
|
|
116
107
|
return {
|
117
108
|
to: this.address,
|
118
109
|
value: "0",
|
119
|
-
data: this.safePack.
|
110
|
+
data: this.safePack.addOwnerData(address),
|
120
111
|
};
|
121
112
|
}
|
122
113
|
async safeSufficientlyFunded(chainId, transactions, gasCost) {
|
@@ -142,4 +133,55 @@ export class TransactionManager {
|
|
142
133
|
throw new Error(`Failed EVM broadcast: ${error instanceof Error ? error.message : String(error)}`);
|
143
134
|
}
|
144
135
|
}
|
136
|
+
/**
|
137
|
+
* Handles routing of signature requests based on the provided method, chain ID, and parameters.
|
138
|
+
*
|
139
|
+
* @async
|
140
|
+
* @function requestRouter
|
141
|
+
* @param {SignRequestData} params - An object containing the method, chain ID, and request parameters.
|
142
|
+
* @returns {Promise<{ evmMessage: string; payload: number[]; recoveryData: RecoveryData }>}
|
143
|
+
* - Returns a promise that resolves to an object containing the Ethereum Virtual Machine (EVM) message,
|
144
|
+
* the payload (hashed data), and recovery data needed for reconstructing the signature request.
|
145
|
+
*/
|
146
|
+
async requestRouter({ method, chainId, params }, usePaymaster) {
|
147
|
+
const safeInfo = {
|
148
|
+
address: { value: this.address },
|
149
|
+
chainId: chainId.toString(),
|
150
|
+
// TODO: Should be able to read this from on chain.
|
151
|
+
version: "1.4.1+L2",
|
152
|
+
};
|
153
|
+
// TODO: We are provided with sender in the input, but also expect safeInfo.
|
154
|
+
// We should either confirm they agree or ignore one of the two.
|
155
|
+
switch (method) {
|
156
|
+
case "eth_signTypedData":
|
157
|
+
case "eth_signTypedData_v4":
|
158
|
+
case "eth_sign": {
|
159
|
+
const [sender, messageOrData] = params;
|
160
|
+
return safeMessageTxData(method, decodeSafeMessage(messageOrData, safeInfo), sender);
|
161
|
+
}
|
162
|
+
case "personal_sign": {
|
163
|
+
const [messageHash, sender] = params;
|
164
|
+
return safeMessageTxData(method, decodeSafeMessage(messageHash, safeInfo), sender);
|
165
|
+
}
|
166
|
+
case "eth_sendTransaction": {
|
167
|
+
const transactions = metaTransactionsFromRequest(params);
|
168
|
+
const userOp = await this.buildTransaction({
|
169
|
+
chainId,
|
170
|
+
transactions,
|
171
|
+
usePaymaster,
|
172
|
+
});
|
173
|
+
const opHash = await this.opHash(userOp);
|
174
|
+
return {
|
175
|
+
payload: toPayload(opHash),
|
176
|
+
evmMessage: JSON.stringify(userOp),
|
177
|
+
recoveryData: {
|
178
|
+
type: method,
|
179
|
+
// TODO: Double check that this is sufficient for UI.
|
180
|
+
// We may want to adapt and return the `MetaTransactions` instead.
|
181
|
+
data: opHash,
|
182
|
+
},
|
183
|
+
};
|
184
|
+
}
|
185
|
+
}
|
186
|
+
}
|
145
187
|
}
|
package/dist/esm/util.d.ts
CHANGED
@@ -1,4 +1,5 @@
|
|
1
|
-
import {
|
1
|
+
import { SessionRequestParams } from "near-ca";
|
2
|
+
import { Address, Hex, PublicClient } from "viem";
|
2
3
|
import { PaymasterData, MetaTransaction } from "./types";
|
3
4
|
export declare const PLACEHOLDER_SIG: `0x${string}`;
|
4
5
|
type IntLike = Hex | bigint | string | number;
|
@@ -6,4 +7,7 @@ export declare const packGas: (hi: IntLike, lo: IntLike) => string;
|
|
6
7
|
export declare function packSignature(signature: `0x${string}`, validFrom?: number, validTo?: number): Hex;
|
7
8
|
export declare function packPaymasterData(data: PaymasterData): Hex;
|
8
9
|
export declare function containsValue(transactions: MetaTransaction[]): boolean;
|
10
|
+
export declare function isContract(address: Address, chainId: number): Promise<boolean>;
|
11
|
+
export declare function getClient(chainId: number): PublicClient;
|
12
|
+
export declare function metaTransactionsFromRequest(params: SessionRequestParams): MetaTransaction[];
|
9
13
|
export {};
|
package/dist/esm/util.js
CHANGED
@@ -1,4 +1,6 @@
|
|
1
|
-
import {
|
1
|
+
import { Network } from "near-ca";
|
2
|
+
import { concatHex, encodePacked, toHex, isHex, parseTransaction, zeroAddress, } from "viem";
|
3
|
+
//
|
2
4
|
export const PLACEHOLDER_SIG = encodePacked(["uint48", "uint48"], [0, 0]);
|
3
5
|
export const packGas = (hi, lo) => encodePacked(["uint128", "uint128"], [BigInt(hi), BigInt(lo)]);
|
4
6
|
export function packSignature(signature, validFrom = 0, validTo = 0) {
|
@@ -17,3 +19,32 @@ export function packPaymasterData(data) {
|
|
17
19
|
export function containsValue(transactions) {
|
18
20
|
return transactions.some((tx) => tx.value !== "0");
|
19
21
|
}
|
22
|
+
export async function isContract(address, chainId) {
|
23
|
+
return (await getClient(chainId).getCode({ address })) !== undefined;
|
24
|
+
}
|
25
|
+
export function getClient(chainId) {
|
26
|
+
return Network.fromChainId(chainId).client;
|
27
|
+
}
|
28
|
+
export function metaTransactionsFromRequest(params) {
|
29
|
+
let transactions;
|
30
|
+
if (isHex(params)) {
|
31
|
+
// If RLP hex is given, decode the transaction and build EthTransactionParams
|
32
|
+
const tx = parseTransaction(params);
|
33
|
+
transactions = [
|
34
|
+
{
|
35
|
+
from: zeroAddress, // TODO: This is a hack - but its unused.
|
36
|
+
to: tx.to,
|
37
|
+
value: tx.value ? toHex(tx.value) : "0x00",
|
38
|
+
data: tx.data || "0x",
|
39
|
+
},
|
40
|
+
];
|
41
|
+
}
|
42
|
+
else {
|
43
|
+
transactions = params;
|
44
|
+
}
|
45
|
+
return transactions.map((tx) => ({
|
46
|
+
to: tx.to,
|
47
|
+
value: tx.value || "0x00",
|
48
|
+
data: tx.data || "0x",
|
49
|
+
}));
|
50
|
+
}
|
package/package.json
CHANGED
@@ -1,6 +1,6 @@
|
|
1
1
|
{
|
2
2
|
"name": "near-safe",
|
3
|
-
"version": "0.
|
3
|
+
"version": "0.3.1",
|
4
4
|
"license": "MIT",
|
5
5
|
"description": "An SDK for controlling Ethereum Smart Accounts via ERC4337 from a Near Account.",
|
6
6
|
"author": "bh2smith",
|
@@ -41,27 +41,33 @@
|
|
41
41
|
},
|
42
42
|
"dependencies": {
|
43
43
|
"@safe-global/safe-deployments": "^1.37.0",
|
44
|
+
"@safe-global/safe-gateway-typescript-sdk": "^3.22.2",
|
44
45
|
"@safe-global/safe-modules-deployments": "^2.2.0",
|
45
|
-
"ethers": "^6.13.1",
|
46
46
|
"near-api-js": "^5.0.0",
|
47
|
-
"near-ca": "^0.5.
|
48
|
-
"
|
49
|
-
"
|
47
|
+
"near-ca": "^0.5.6",
|
48
|
+
"semver": "^7.6.3",
|
49
|
+
"viem": "^2.16.5"
|
50
50
|
},
|
51
51
|
"devDependencies": {
|
52
52
|
"@types/jest": "^29.5.12",
|
53
53
|
"@types/node": "^22.3.0",
|
54
|
+
"@types/semver": "^7.5.8",
|
54
55
|
"@types/yargs": "^17.0.32",
|
55
56
|
"@typescript-eslint/eslint-plugin": "^8.1.0",
|
56
57
|
"@typescript-eslint/parser": "^8.1.0",
|
57
58
|
"dotenv": "^16.4.5",
|
58
59
|
"eslint": "^9.6.0",
|
59
60
|
"eslint-plugin-import": "^2.30.0",
|
61
|
+
"ethers": "^6.13.1",
|
60
62
|
"jest": "^29.7.0",
|
61
63
|
"prettier": "^3.3.2",
|
62
64
|
"ts-jest": "^29.1.5",
|
63
65
|
"tsx": "^4.16.0",
|
64
66
|
"typescript": "^5.5.2",
|
65
67
|
"yargs": "^17.7.2"
|
68
|
+
},
|
69
|
+
"resolutions": {
|
70
|
+
"glob": "^9.0.0",
|
71
|
+
"base-x": "^3.0.0"
|
66
72
|
}
|
67
73
|
}
|