Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Add Near Adapter for Signature #1

Merged
merged 12 commits into from
Jun 14, 2024
7 changes: 7 additions & 0 deletions .env.sample
Original file line number Diff line number Diff line change
@@ -0,0 +1,7 @@
NEAR_MULTICHAIN_CONTRACT=v2.multichain-mpc.testnet

NEAR_ACCOUNT_ID=
NEAR_ACCOUNT_PRIVATE_KEY=

SAFE_SALT_NONCE=
ERC4337_BUNDLER_URL=
107 changes: 92 additions & 15 deletions index.ts
Original file line number Diff line number Diff line change
Expand Up @@ -11,9 +11,10 @@ import {
} from "@safe-global/safe-modules-deployments";
import dotenv from "dotenv";
import { ethers } from "ethers";
import { NearEthAdapter, MultichainContract } from "near-ca";

dotenv.config();
const { NEAR_EVM_ADDRESS, SAFE_SALT_NONCE, ERC4337_BUNDLER_URL } = process.env;
const { SAFE_SALT_NONCE, ERC4337_BUNDLER_URL } = process.env;

type DeploymentFunction = (filter?: {
version: string;
Expand All @@ -40,14 +41,26 @@ async function getDeployment(
);
}

async function getNearSignature(hash: ethers.BytesLike) {
// TODO(bh2smith): do your thing
//assert(ethers.recoverAddress(hash, "0x...") === NEAR_EVM_ADDRESS);
return ethers.solidityPacked(["uint256", "uint256", "uint8"], [1, 2, 27]);
async function getNearSignature(
adapter: NearEthAdapter,
hash: ethers.BytesLike,
): Promise<`0x${string}`> {
const viemHash = typeof hash === "string" ? (hash as `0x${string}`) : hash;
// MPC Contract produces two possible signatures.
const signatures = await adapter.sign(viemHash);
for (const sig of signatures) {
if (
ethers.recoverAddress(hash, sig).toLocaleLowerCase() ===
adapter.address.toLocaleLowerCase()
) {
return sig;
}
}
throw new Error("Invalid signature!");
}

async function sendUserOperation(userOp: unknown, entryPoint: string) {
const response = await fetch(ERC4337_BUNDLER_URL!, {
const requestPayload = {
method: "POST",
headers: {
"Content-Type": "application/json",
Expand All @@ -58,14 +71,16 @@ async function sendUserOperation(userOp: unknown, entryPoint: string) {
id: 4337,
params: [userOp, entryPoint],
}),
});
};
console.log("Request Body", requestPayload.body);
const response = await fetch(ERC4337_BUNDLER_URL!, requestPayload);
const body = await response.text();
if (!response.ok) {
throw new Error(`Failed to send user op ${body}`);
}
const json = JSON.parse(body);
if (json.error) {
throw new Error(json.error.message ?? JSON.stringify(json.error));
throw new Error(JSON.stringify(json.error));
}
return json.result;
}
Expand All @@ -83,16 +98,21 @@ async function main() {
m4337: await m4337Deployment(getSafe4337ModuleDeployment),
moduleSetup: await m4337Deployment(getSafeModuleSetupDeployment),
entryPoint: new ethers.Contract(
"0x0000000071727De22E5E9d8BAf0edAc6f37da032",
// "0x0000000071727De22E5E9d8BAf0edAc6f37da032",
"0x5ff137d4b0fdcd49dca30c7cf57e578a026d2789",
bh2smith marked this conversation as resolved.
Show resolved Hide resolved
[`function getNonce(address, uint192 key) view returns (uint256 nonce)`],
provider,
),
};

const nearAdapter = await NearEthAdapter.fromConfig({
mpcContract: await MultichainContract.fromEnv(),
});

const setup = await contracts.singleton.interface.encodeFunctionData(
"setup",
[
[NEAR_EVM_ADDRESS],
[nearAdapter.address],
1,
contracts.moduleSetup.target,
contracts.moduleSetup.interface.encodeFunctionData("enableModules", [
Expand All @@ -110,6 +130,7 @@ async function main() {
setup,
SAFE_SALT_NONCE,
);
console.log("Safe Address:", safeAddress);

const safeNotDeployed = (await provider.getCode(safeAddress)) === "0x";
const { maxPriorityFeePerGas, maxFeePerGas } = await provider.getFeeData();
Expand All @@ -135,9 +156,9 @@ async function main() {
"0x",
0,
]),
verificationGasLimit: ethers.toBeHex(safeNotDeployed ? 500000 : 100000),
callGasLimit: ethers.toBeHex(100000),
preVerificationGas: ethers.toBeHex(100000),
verificationGasLimit: ethers.toBeHex(safeNotDeployed ? 1_000_000 : 200_000),
callGasLimit: ethers.toBeHex(200_000),
preVerificationGas: ethers.toBeHex(200_000),
maxPriorityFeePerGas: ethers.toBeHex((maxPriorityFeePerGas * 13n) / 10n),
maxFeePerGas: ethers.toBeHex(maxFeePerGas),
// TODO(bh2smith): Use paymaster at some point
Expand Down Expand Up @@ -170,15 +191,71 @@ async function main() {
});
console.log(safeOpHash);

const signature = await getNearSignature(safeOpHash);
// const signature =
// "0xef028109f94aabaf9cd4fb7f2cce311527567341d3439a40fe15e6e01c45249b790e5e5c1f0803f668219f97a95947a982e4f88d8f9abc675adcdb926ed7504d1c";
const signature = await getNearSignature(nearAdapter, safeOpHash);
bh2smith marked this conversation as resolved.
Show resolved Hide resolved
console.log(
await sendUserOperation(
{ ...unsignedUserOp, signature },
asERC4337Payload({ ...unsignedUserOp, signature }),
await contracts.entryPoint.getAddress(),
),
);
}

interface UserOpData {
bh2smith marked this conversation as resolved.
Show resolved Hide resolved
sender: string;
nonce: string;
factory?: string | ethers.Addressable;
factoryData?: string | ethers.Addressable;
bh2smith marked this conversation as resolved.
Show resolved Hide resolved
callData: string;
verificationGasLimit: string;
callGasLimit: string;
preVerificationGas: string;
maxPriorityFeePerGas: string;
maxFeePerGas: string;
signature: string;
}

interface SendUserOpPayload {
bh2smith marked this conversation as resolved.
Show resolved Hide resolved
sender: string;
nonce: string;
initCode: string;
callData: string;
maxFeePerGas: string;
maxPriorityFeePerGas: string;
verificationGasLimit: string;
callGasLimit: string;
preVerificationGas: string;
paymasterAndData: string;
signature: string;
nlordell marked this conversation as resolved.
Show resolved Hide resolved
}

function asERC4337Payload(input: UserOpData): SendUserOpPayload {
let initCode: string;
if (input.factory && input.factoryData) {
initCode = input.factory
.toString()
.concat(input.factoryData.toString().slice(2));
} else if (input.factory || input.factoryData) {
throw new Error("Must have both or neither (factory, factoryData)");
} else {
initCode = "0x";
}
return {
sender: input.sender,
nonce: input.nonce,
initCode,
callData: input.callData,
maxFeePerGas: input.maxFeePerGas,
maxPriorityFeePerGas: input.maxPriorityFeePerGas,
verificationGasLimit: input.verificationGasLimit,
callGasLimit: input.callGasLimit,
preVerificationGas: input.preVerificationGas,
paymasterAndData: "0x", // Setting default value for paymasterAndData
signature: input.signature,
};
}

main().catch((err) => {
console.error(err);
process.exitCode = 1;
Expand Down
3 changes: 2 additions & 1 deletion package.json
Original file line number Diff line number Diff line change
Expand Up @@ -11,9 +11,10 @@
"@safe-global/safe-modules-deployments": "^2.1.1",
"dotenv": "^16.4.5",
"ethers": "^6.13.0",
"near-ca": "^0.0.9"
"near-ca": "^0.0.10-alpha.1"
},
"devDependencies": {
"@types/node": "^20.14.2",
bh2smith marked this conversation as resolved.
Show resolved Hide resolved
"prettier": "^3.3.2",
"ts-node": "^10.9.2",
"typescript": "^5.4.5"
Expand Down
24 changes: 18 additions & 6 deletions yarn.lock
Original file line number Diff line number Diff line change
Expand Up @@ -640,6 +640,13 @@
resolved "https://registry.yarnpkg.com/@types/node/-/node-18.15.13.tgz#f64277c341150c979e42b00e4ac289290c9df469"
integrity sha512-N+0kuo9KgrUQ1Sn/ifDXsvg0TTleP7rIy4zOBGECxAljqvqfqpTfzx0Q1NUedOixRMBfe2Whhb056a42cWs26Q==

"@types/node@^20.14.2":
version "20.14.2"
resolved "https://registry.yarnpkg.com/@types/node/-/node-20.14.2.tgz#a5f4d2bcb4b6a87bffcaa717718c5a0f208f4a18"
integrity sha512-xyu6WAMVwv6AKFLB+e/7ySZVr/0zLCzOa7rSpq6jNwpqOrUbcACDWC+53d4n2QHOnDou0fbIsg8wZu/sxrnI4Q==
dependencies:
undici-types "~5.26.4"

"@walletconnect/[email protected]":
version "2.1.2"
resolved "https://registry.yarnpkg.com/@walletconnect/auth-client/-/auth-client-2.1.2.tgz#cee304fb0cdca76f6bf4aafac96ef9301862a7e8"
Expand Down Expand Up @@ -1544,16 +1551,16 @@ near-api-js@^3.0.3:
near-abi "0.1.1"
node-fetch "2.6.7"

near-ca@^0.0.9:
version "0.0.9"
resolved "https://registry.yarnpkg.com/near-ca/-/near-ca-0.0.9.tgz#1bb6e36203af1957b1dfd646cccb6bcd2349edfe"
integrity sha512-7gGxWAUI/DfzbYzBtTW+e5V6gtyomarDBK+XL6aGiQ7/EGwt0Kpqx5/0uGqbNS20GS7tvRH2UrZm5dLzz0SPfw==
near-ca@^0.0.10-alpha.1:
version "0.0.10-alpha.1"
resolved "https://registry.yarnpkg.com/near-ca/-/near-ca-0.0.10-alpha.1.tgz#d5a06fa1e5208484798bbaf2fdaa218c1b9d4dc4"
integrity sha512-M2mA3pPBshJJfWh1icFssNiUwao+hZk0ePXoAjFbXtxBZ60mP+QqGw5DtoVNWCj1w0GhzNNnA2KooJZOI0At3Q==
dependencies:
"@near-wallet-selector/core" "^8.9.5"
"@walletconnect/web3wallet" "^1.12.0"
elliptic "^6.5.5"
near-api-js "^3.0.3"
viem "^2.10.8"
viem "^2.12.5"

node-addon-api@^7.0.0:
version "7.1.0"
Expand Down Expand Up @@ -1946,6 +1953,11 @@ uncrypto@^0.1.3:
resolved "https://registry.yarnpkg.com/uncrypto/-/uncrypto-0.1.3.tgz#e1288d609226f2d02d8d69ee861fa20d8348ef2b"
integrity sha512-Ql87qFHB3s/De2ClA9e0gsnS6zXG27SkTiSJwjCc9MebbfapQfuPzumMIUMi38ezPZVNFcHI9sUIepeQfw8J8Q==

undici-types@~5.26.4:
version "5.26.5"
resolved "https://registry.yarnpkg.com/undici-types/-/undici-types-5.26.5.tgz#bcd539893d00b56e964fd2657a4866b221a65617"
integrity sha512-JlCMO+ehdEIKqlFxk6IfVoAUVmgz7cU7zD/h9XZ0qzeosSHmUJVOzSQvvYSYWXkFXC+IfLKSIffhv0sVZup6pA==

unenv@^1.9.0:
version "1.9.0"
resolved "https://registry.yarnpkg.com/unenv/-/unenv-1.9.0.tgz#469502ae85be1bd3a6aa60f810972b1a904ca312"
Expand Down Expand Up @@ -2009,7 +2021,7 @@ v8-compile-cache-lib@^3.0.1:
resolved "https://registry.yarnpkg.com/v8-compile-cache-lib/-/v8-compile-cache-lib-3.0.1.tgz#6336e8d71965cb3d35a1bbb7868445a7c05264bf"
integrity sha512-wa7YjyUGfNZngI/vtK0UHAN+lgDCxBPCylVXGp0zu59Fz5aiGtNXaq3DhIov063MorB+VfufLh3JlF2KdTK3xg==

viem@^2.10.8:
viem@^2.12.5:
version "2.13.8"
resolved "https://registry.yarnpkg.com/viem/-/viem-2.13.8.tgz#d6aaeecc84e5ee5cac1566f103ed4cf0335ef811"
integrity sha512-JX8dOrCJKazNVs7YAahXnX+NANp0nlK16GyYjtQXILnar1daCPsLy4uzKgZDBVBD6DdRP2lsbPfo4X7QX3q5EQ==
Expand Down