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

feat: deploy P256Verifier on Sepolia + prefund every smart wallet address with 1 wei for future interaction #47

Merged
merged 3 commits into from
Nov 23, 2023
Merged
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view

Large diffs are not rendered by default.

Large diffs are not rendered by default.

Large diffs are not rendered by default.

Large diffs are not rendered by default.

Large diffs are not rendered by default.

Large diffs are not rendered by default.

Large diffs are not rendered by default.

Large diffs are not rendered by default.

This file was deleted.

This file was deleted.

This file was deleted.

This file was deleted.

This file was deleted.

This file was deleted.

This file was deleted.

This file was deleted.

This file was deleted.

This file was deleted.

This file was deleted.

1 change: 1 addition & 0 deletions contracts/foundry.toml
Original file line number Diff line number Diff line change
@@ -8,3 +8,4 @@ solc_version = "0.8.21"

[rpc_endpoints]
base_goerli = "https://goerli.base.org"
sepolia = "https://rpc.ankr.com/eth_sepolia"
45 changes: 45 additions & 0 deletions contracts/src/P256.sol
Original file line number Diff line number Diff line change
@@ -0,0 +1,45 @@
// SPDX-License-Identifier: MIT
pragma solidity 0.8.21;

import "forge-std/console2.sol";

/**
* Helper library for external contracts to verify P256 signatures.
**/
library P256 {
// We use our own copy of the P256Verifier contract, to be able to use it on Sepolia
address constant VERIFIER = 0x7a29Dc72fa3938705d91A9659455BC54731eD70F;

function verifySignatureAllowMalleability(
bytes32 message_hash,
uint256 r,
uint256 s,
uint256 x,
uint256 y
) internal view returns (bool) {
bytes memory args = abi.encode(message_hash, r, s, x, y);
(bool success, bytes memory ret) = VERIFIER.staticcall(args);
assert(success); // never reverts, always returns 0 or 1

return abi.decode(ret, (uint256)) == 1;
}

/// P256 curve order n/2 for malleability check
uint256 constant P256_N_DIV_2 =
57896044605178124381348723474703786764998477612067880171211129530534256022184;

function verifySignature(
bytes32 message_hash,
uint256 r,
uint256 s,
uint256 x,
uint256 y
) internal view returns (bool) {
// check for signature malleability
if (s > P256_N_DIV_2) {
return false;
}

return verifySignatureAllowMalleability(message_hash, r, s, x, y);
}
}
4 changes: 4 additions & 0 deletions contracts/src/SimpleAccount.sol
Original file line number Diff line number Diff line change
@@ -1,4 +1,5 @@
// SPDX-License-Identifier: MIT

pragma solidity ^0.8.13;

import "forge-std/console2.sol";
@@ -67,6 +68,9 @@ contract SimpleAccount is IAccount, UUPSUpgradeable, Initializable, IERC1271 {
// solhint-disable-next-line no-empty-blocks
receive() external payable {}

// solhint-disable-next-line no-empty-blocks
fallback() external payable {}

function _onlyOwner() internal view {
//directly through the account itself (which gets redirected through execute())
require(msg.sender == address(this), "only account itself can call");
3 changes: 2 additions & 1 deletion contracts/src/WebAuthn.sol
Original file line number Diff line number Diff line change
@@ -1,8 +1,9 @@
// SPDX-License-Identifier: MIT
pragma solidity 0.8.21;

import {P256} from "src/P256.sol";
import "p256-verifier/utils/Base64URL.sol";
import "p256-verifier/P256.sol";
import "forge-std/console2.sol";

/**
* Helper library for external contracts to verify WebAuthn signatures.
31 changes: 16 additions & 15 deletions contracts/test/SendUserOp.t.sol
Original file line number Diff line number Diff line change
@@ -18,13 +18,13 @@ contract SendUserOpTest is Test {

function setUp() public {
// setup fork
vm.createSelectFork("base_goerli");
vm.createSelectFork("sepolia");

entryPoint = EntryPoint(
payable(0x5FF137D4b0FDCD49DcA30c7CF57E578a026d2789)
);
factory = SimpleAccountFactory(
0x7236f1BB9BE463437261AA3f74008Bdf76d4ceC1
0xDD0f9cB4Cf53d28b976C13e7ee4a169F841924c0
);
}

@@ -60,7 +60,7 @@ contract SendUserOpTest is Test {

uint8 version = 1;
uint48 validUntil = 0;
bytes32 expectedUserOpHash = hex"72fe91f1b68f75ce391ac973c52d8c525356199dbc5bef6c7bc6f8e2308ead87";
bytes32 expectedUserOpHash = hex"e97f70cada097ce881426a3f199e4f95895765659985161e1930def8e1f7b04f";
bytes memory challengeToSign = abi.encodePacked(
version,
validUntil,
@@ -73,8 +73,8 @@ contract SendUserOpTest is Test {
abi.encode( // signature
Utils.rawSignatureToSignature({
challenge: challengeToSign,
r: 0x813d6d26f828f855a570eff45308c8bde0d5a417d0f3e07484b0d90efef19382,
s: 0x282e1b0004a893bf6d22fec8cf97190f591ffec78a3b0ab3ea45dfe9fc035d29
r: 0xafb3561771f09d5119b12350f9089874e21a193a37b40c3f872ff4a93730f727,
s: 0x9f2756dc68bd36de31ed67b3f775bf604e86547867796e9679d4b4673aef81e9
})
)
);
@@ -110,7 +110,6 @@ contract SendUserOpTest is Test {
// add signature to op after calculating hash
op.signature = ownerSig;

// expect a valid but reverting op
UserOperation[] memory ops = new UserOperation[](1);
ops[0] = op;
vm.expectEmit(true, true, true, false);
@@ -125,7 +124,6 @@ contract SendUserOpTest is Test {
);
entryPoint.handleOps(ops, payable(address(account)));

// code coverage can't handle indirect calls
// call validateUserOp directly
SimpleAccount account2 = new SimpleAccount(account.entryPoint());
vm.store(address(account2), 0, 0); // set _initialized = 0
@@ -147,7 +145,7 @@ contract SendUserOpTest is Test {

uint8 version = 1;
uint48 validUntil = 0;
bytes32 expectedUserOpHash = hex"ed8c67fc3b6e6eb7b867c90201f13fa61a45f8eeaea636057443e64f56013f31";
bytes32 expectedUserOpHash = hex"2215b15dca57f3e9431889b9355a2ad6f0de47ed49e225779f499cd851441528";
bytes memory challengeToSign = abi.encodePacked(
version,
validUntil,
@@ -160,16 +158,16 @@ contract SendUserOpTest is Test {
abi.encode( // signature
Utils.rawSignatureToSignature({
challenge: challengeToSign,
r: 0xbced80a2f0cc4f977e145107744da4475b7f127e9d0ec73c77785279874ca8dc,
s: 0x68597940751e2f8cfd60433a9a22fdb4fe704a3148e47ee69b7a5909ab4b3948
r: 0x5bfe729e37e1849b62d5409c600e39c8394df69e86ba55f91de5728431fad828,
s: 0xb0ecaa260794c4993b46216ca08c4432f5c2fe070ea4e7347612c21663eca932
})
)
);

// account not deployed yet
// we want to test the initCode feature of UserOperation
SimpleAccount account = SimpleAccount(
payable(0x31A5Ae294082B7E0A243d64B98DFc290Ae519EDB)
payable(0xa3ec6EEeDb3bBcAA232e5a8836A5745455098327)
);
vm.deal(address(account), 1 ether);

@@ -179,9 +177,12 @@ contract SendUserOpTest is Test {
abi.encodeCall(factory.createAccount, (publicKey))
);

// send 42 wei to bigq dev
// send 42 wei to another smart wallet
SimpleAccount otherAccount = factory.createAccount(
[bytes32(uint256(0x1)), bytes32(uint256(0x2))]
);
Call[] memory calls = new Call[](1);
calls[0] = Call({dest: bigqDevAddress, value: 42, data: hex""});
calls[0] = Call({dest: address(otherAccount), value: 42, data: hex""});

bytes memory callData = abi.encodeCall(
SimpleAccount.executeBatch,
@@ -215,7 +216,7 @@ contract SendUserOpTest is Test {
op.signature = ownerSig;

// compute balance before userOp validation and execution
uint256 balanceBefore = bigqDevAddress.balance;
uint256 balanceBefore = address(otherAccount).balance;

UserOperation[] memory ops = new UserOperation[](1);
ops[0] = op;
@@ -233,7 +234,7 @@ contract SendUserOpTest is Test {
entryPoint.handleOps(ops, payable(address(account)));

// compute balance after userOp validation and execution
uint256 balanceAfter = bigqDevAddress.balance;
uint256 balanceAfter = address(otherAccount).balance;
assertEq(balanceAfter - balanceBefore, 42);
}
}
4 changes: 2 additions & 2 deletions front/src/app/api/users/[id]/route.ts
Original file line number Diff line number Diff line change
@@ -1,5 +1,5 @@
import { PUBLIC_CLIENT } from "@/constants/client";
import { FACTORY_ABI, FACTORY_ADDRESS } from "@/constants/factory";
import { FACTORY_ABI } from "@/constants/factory";
import { Hex, stringify, toHex } from "viem";

export async function GET(_req: Request, { params }: { params: { id: Hex } }) {
@@ -9,7 +9,7 @@ export async function GET(_req: Request, { params }: { params: { id: Hex } }) {
}

const user = await PUBLIC_CLIENT.readContract({
address: FACTORY_ADDRESS,
address: process.env.NEXT_PUBLIC_FACTORY_CONTRACT_ADDRESS as Hex,
abi: FACTORY_ABI,
functionName: "getUser",
args: [BigInt(id)],
24 changes: 16 additions & 8 deletions front/src/app/api/users/save/route.ts
Original file line number Diff line number Diff line change
@@ -1,25 +1,25 @@
import { FACTORY_ABI, FACTORY_ADDRESS } from "@/constants/factory";
import { CHAIN } from "@/constants";
import { FACTORY_ABI } from "@/constants/factory";
import { Hex, createPublicClient, createWalletClient, http, toHex, zeroAddress } from "viem";
import { privateKeyToAccount } from "viem/accounts";
import { baseGoerli } from "viem/chains";

export async function POST(req: Request) {
const { id, pubKey } = (await req.json()) as { id: Hex; pubKey: [Hex, Hex] };

const account = privateKeyToAccount(process.env.RELAYER_PRIVATE_KEY as Hex);
const walletClient = createWalletClient({
account,
chain: baseGoerli,
chain: CHAIN,
transport: http(),
});

const publicClient = createPublicClient({
chain: baseGoerli,
chain: CHAIN,
transport: http(),
});

const user = await publicClient.readContract({
address: FACTORY_ADDRESS,
address: process.env.NEXT_PUBLIC_FACTORY_CONTRACT_ADDRESS as Hex,
abi: FACTORY_ABI,
functionName: "getUser",
args: [BigInt(id)],
@@ -30,7 +30,7 @@ export async function POST(req: Request) {
}

const hash = await walletClient.writeContract({
address: FACTORY_ADDRESS,
address: process.env.NEXT_PUBLIC_FACTORY_CONTRACT_ADDRESS as Hex,
abi: FACTORY_ABI,
functionName: "saveUser",
args: [BigInt(id), pubKey],
@@ -39,12 +39,20 @@ export async function POST(req: Request) {
await publicClient.waitForTransactionReceipt({ hash });

const createdUser = await publicClient.readContract({
address: FACTORY_ADDRESS,
address: process.env.NEXT_PUBLIC_FACTORY_CONTRACT_ADDRESS as Hex,
abi: FACTORY_ABI,
functionName: "getUser",
args: [BigInt(id)],
});

await publicClient.waitForTransactionReceipt({ hash });
// send 1 wei to the user
// so that anyone can send a transaction to the user's smart wallet
const hash2 = await walletClient.sendTransaction({
to: createdUser.account,
value: BigInt(1),
});

await publicClient.waitForTransactionReceipt({ hash: hash2 });

return Response.json({ ...createdUser, id: toHex(createdUser.id) });
}
8 changes: 4 additions & 4 deletions front/src/components/Balance/index.tsx
Original file line number Diff line number Diff line change
@@ -2,23 +2,23 @@

import { useBalance } from "@/providers/BalanceProvider";
import { Flex, Text } from "@radix-ui/themes";
import { CSSProperties, useEffect } from "react";
import { CSSProperties } from "react";

const css: CSSProperties = {
padding: "4rem 0",
};

export default function Balance() {
const { balance, refreshBalance } = useBalance();
let [intBalance, decimals] = balance.toFixed(2).split(".");
const { balance } = useBalance();
let [intBalance, decimals] = balance.toString().split(".");

return (
<Flex style={css} direction="row" justify="center">
<Text highContrast={true} weight="bold" size="9">
${intBalance}
</Text>
<Text highContrast={true} weight="bold" size="6" style={{ color: "var(--accent-12)" }}>
.{decimals}
.{(decimals || "00").slice(0, 2)}
</Text>
</Flex>
);
7 changes: 4 additions & 3 deletions front/src/components/SendTransaction.tsx
Original file line number Diff line number Diff line change
@@ -58,7 +58,8 @@ export function SendTransaction() {
{
dest: address.toLowerCase() as Hex,
value:
(BigInt(usdAmount) * BigInt(1e18)) / (BigInt(price.ethereum.usd * 100) / BigInt(100)), // 100 is the price precision
(BigInt(usdAmount) * BigInt(1e18)) /
(BigInt(Math.trunc(price.ethereum.usd * 100)) / BigInt(100)), // 100 is the price precision
data: emptyHex,
},
],
@@ -179,7 +180,7 @@ export function SendTransaction() {
<>
<CheckCircledIcon height="50" width="100%" color="var(--teal-11)" />
<Link
href={`https://goerli.basescan.org/tx/${txReceipt.receipt.transactionHash}`}
href={`https://sepolia.etherscan.io/tx/${txReceipt.receipt.transactionHash}`}
target="_blank"
style={{ textDecoration: "none" }}
>
@@ -193,7 +194,7 @@ export function SendTransaction() {
<>
<CrossCircledIcon height="50" width="100%" color="var(--red-9)" />
<Link
href={`https://goerli.basescan.org/tx/${txReceipt.receipt.transactionHash}`}
href={`https://sepolia.etherscan.io/tx/${txReceipt.receipt.transactionHash}`}
target="_blank"
style={{ textDecoration: "none" }}
>
12 changes: 5 additions & 7 deletions front/src/components/SendTxModal/index.tsx
Original file line number Diff line number Diff line change
@@ -107,9 +107,6 @@ export default function SendTxModal() {
e.preventDefault();

try {
const formData = new FormData(e.target as HTMLFormElement);
const address = formData?.get("address") as Hex;
const usdAmount = formData?.get("amount") as `${number}`;
const price: { ethereum: { usd: number } } = await (
await fetch("/api/price?ids=ethereum&currencies=usd")
).json();
@@ -118,9 +115,10 @@ export default function SendTxModal() {
const userOp = await builder.buildUserOp({
calls: [
{
dest: address.toLowerCase() as Hex,
dest: destination.toLowerCase() as Hex,
value:
(BigInt(usdAmount) * BigInt(1e18)) / (BigInt(price.ethereum.usd * 100) / BigInt(100)), // 100 is the price precision
(BigInt(userInputAmount) * BigInt(1e18)) /
(BigInt(price.ethereum.usd * 100) / BigInt(100)), // 100 is the price precision
data: emptyHex,
},
],
@@ -156,7 +154,7 @@ export default function SendTxModal() {
<>
<CheckCircledIcon height="32" width="100%" color="var(--teal-11)" />
<Link
href={`https://sepolia.etherscan.org/tx/${txReceipt?.receipt?.transactionHash}`}
href={`https://sepolia.etherscan.io/tx/${txReceipt?.receipt?.transactionHash}`}
target="_blank"
style={{ textDecoration: "none" }}
>
@@ -170,7 +168,7 @@ export default function SendTxModal() {
<>
<CrossCircledIcon height="32" width="100%" />
<Link
href={`https://sepolia.etherscan.org/tx/${txReceipt?.receipt?.transactionHash}`}
href={`https://sepolia.etherscan.io/tx/${txReceipt?.receipt?.transactionHash}`}
target="_blank"
style={{ textDecoration: "none" }}
>
4 changes: 2 additions & 2 deletions front/src/components/WCSendTransactionModal/index.tsx
Original file line number Diff line number Diff line change
@@ -95,7 +95,7 @@ export default function WCSendTransactionModal({ params }: Props) {
gap={"3"}
>
<Link
href={`https://goerli.basescan.org/tx/${txReceipt?.receipt?.transactionHash}`}
href={`https://sepolia.etherscan.io/tx/${txReceipt?.receipt?.transactionHash}`}
target="_blank"
style={{ textDecoration: "none" }}
>
@@ -120,7 +120,7 @@ export default function WCSendTransactionModal({ params }: Props) {
gap={"3"}
>
<Link
href={`https://goerli.basescan.org/tx/${txReceipt?.receipt?.transactionHash}`}
href={`https://sepolia.etherscan.io/tx/${txReceipt?.receipt?.transactionHash}`}
target="_blank"
style={{ textDecoration: "none" }}
>
14 changes: 12 additions & 2 deletions front/src/constants/client.ts
Original file line number Diff line number Diff line change
@@ -1,8 +1,18 @@
import { createPublicClient, http } from "viem";
import { baseGoerli, sepolia, mainnet } from "viem/chains";
import { sepolia, mainnet } from "viem/chains";


export const CHAIN = {
...sepolia,
rpcUrls: {
...sepolia.rpcUrls,
default: { http: ["https://rpc.ankr.com/eth_sepolia"] },
public: { http: ["https://rpc.ankr.com/eth_sepolia"] },
},
};

export const PUBLIC_CLIENT = createPublicClient({
chain: baseGoerli,
chain: CHAIN,
transport: http(),
});

2 changes: 0 additions & 2 deletions front/src/constants/factory.ts
Original file line number Diff line number Diff line change
@@ -1,5 +1,3 @@
export const FACTORY_ADDRESS = "0x7236f1BB9BE463437261AA3f74008Bdf76d4ceC1";

export const FACTORY_ABI = [
{
inputs: [
4 changes: 2 additions & 2 deletions front/src/libs/smart-wallet/SmartWalletProvider.tsx
Original file line number Diff line number Diff line change
@@ -4,7 +4,7 @@ import React, { useContext } from "react";
import { useSmartWalletHook } from "@/libs/smart-wallet/hook/useSmartWalletHook";
import { WagmiConfig, configureChains, createConfig } from "wagmi";
import { publicProvider } from "wagmi/providers/public";
import { baseGoerli } from "viem/chains";
import { CHAIN } from "@/constants";

type UseSmartWallet = ReturnType<typeof useSmartWalletHook>;

@@ -20,7 +20,7 @@ export const useWalletConnect = (): UseSmartWallet => {
export function SmartWalletProvider({ children }: { children: React.ReactNode }) {
const smartWalletValue = useSmartWalletHook();

const { publicClient } = configureChains([baseGoerli], [publicProvider()]);
const { publicClient } = configureChains([CHAIN], [publicProvider()]);

const wagmiConfig = createConfig({
autoConnect: true,
4 changes: 2 additions & 2 deletions front/src/libs/smart-wallet/service/smart-wallet.ts
Original file line number Diff line number Diff line change
@@ -8,10 +8,10 @@ import {
PublicClient,
createPublicClient,
} from "viem";
import { baseGoerli } from "viem/chains";
import { SmartWalletActions, smartWalletActions } from "./decorators";
import { transport } from "../config";
import { ERC4337RpcSchema, UserOperationAsHex } from "@/libs/smart-wallet/service/userOps";
import { CHAIN } from "@/constants";

export type SmartWalletClient<chain extends Chain | undefined = Chain | undefined> = Client<
Transport,
@@ -38,7 +38,7 @@ class SmartWallet {

constructor() {
this._client = createSmartWalletClient({
chain: baseGoerli,
chain: CHAIN,
transport,
});
}
4 changes: 2 additions & 2 deletions front/src/libs/smart-wallet/service/userOps/builder.ts
Original file line number Diff line number Diff line change
@@ -19,7 +19,7 @@ import {
import { UserOperationAsHex, UserOperation, Call } from "@/libs/smart-wallet/service/userOps/types";
import { DEFAULT_USER_OP } from "@/libs/smart-wallet/service/userOps/constants";
import { P256Credential, WebAuthn } from "@/libs/web-authn";
import { ENTRYPOINT_ABI, ENTRYPOINT_ADDRESS, FACTORY_ABI, FACTORY_ADDRESS } from "@/constants";
import { ENTRYPOINT_ABI, ENTRYPOINT_ADDRESS, FACTORY_ABI } from "@/constants";

export class UserOpBuilder {
public relayer: Hex = "0x061060a65146b3265C62fC8f3AE977c9B27260fF";
@@ -42,7 +42,7 @@ export class UserOpBuilder {
});

this.factoryContract = getContract({
address: FACTORY_ADDRESS, // only on Base Goerli
address: process.env.NEXT_PUBLIC_FACTORY_CONTRACT_ADDRESS as Hex, // only on Sepolia
abi: FACTORY_ABI,
walletClient,
publicClient: this.publicClient,
8 changes: 8 additions & 0 deletions front/src/libs/wallet-connect/config/EIP155.ts
Original file line number Diff line number Diff line change
@@ -66,6 +66,14 @@ export const EIP155_TEST_CHAINS = {
rpc: "https://goerli.infura.io/v3/9aa3d95b3bc440fa88ea12eaa4456161",
namespace: "eip155",
},
"eip155:11155111": {
chainId: 11155111,
name: "Ethereum Sepolia",
logo: "/chain-logos/eip155-11155111.png",
rgb: "99, 125, 234",
rpc: "https://rpc.ankr.com/eth_sepolia",
namespace: "eip155",
},
"eip155:43113": {
chainId: 43113,
name: "Avalanche Fuji",
2 changes: 0 additions & 2 deletions front/src/libs/wallet-connect/service/wallet-connect.ts
Original file line number Diff line number Diff line change
@@ -173,12 +173,10 @@ class WalletConnect extends EventEmitter {
}

private async _onSessionRequest(event: Web3WalletTypes.SessionRequest): Promise<void> {
console.log("event", event);
if (!this._web3wallet) return;
const { topic, params, id } = event;
const { request } = params;

console.log("request", request);
const result = this._jsonRpcEventRouter(request.method, request.params);

// const { request } = params;