Skip to content

Commit

Permalink
feat: migrate to coinbase smart wallet contract
Browse files Browse the repository at this point in the history
  • Loading branch information
stephancill committed Jul 24, 2024
1 parent 544181a commit ac26023
Show file tree
Hide file tree
Showing 30 changed files with 849 additions and 221 deletions.
2 changes: 1 addition & 1 deletion front/.env.local.example
Original file line number Diff line number Diff line change
@@ -1,5 +1,5 @@
NEXT_PUBLIC_STACKUP_BUNDLER_API_KEY=""
NEXT_PUBLIC_RPC_ENDPOINT=""
ETHERSCAN_API_KEY=""
NEXT_PUBLIC_FACTORY_CONTRACT_ADDRESS="0xE4e6BF50E1fb02b323ca855dF0d8D76a75290FfC"
NEXT_PUBLIC_FACTORY_CONTRACT_ADDRESS="0x0BA5ED0c6AA8c49038F819E587E2633c4A9F428a"
RELAYER_PRIVATE_KEY=""
4 changes: 3 additions & 1 deletion front/package.json
Original file line number Diff line number Diff line change
Expand Up @@ -11,6 +11,7 @@
"dependencies": {
"@emotion/react": "^11.11.4",
"@emotion/styled": "^11.11.5",
"@noble/curves": "^1.4.2",
"@peculiar/asn1-ecc": "2.3.6",
"@peculiar/asn1-schema": "2.3.6",
"@radix-ui/react-form": "^0.0.3",
Expand All @@ -33,7 +34,8 @@
"react-dom": "^18.3.1",
"react-qr-reader-es6": "2.2.1-2",
"viem": "^1.21.4",
"wagmi": "^1.4.13"
"wagmi": "^1.4.13",
"webauthn-p256": "^0.0.5"
},
"devDependencies": {
"@types/node": "^17.0.45",
Expand Down
22 changes: 18 additions & 4 deletions front/pnpm-lock.yaml

Some generated files are not rendered by default. Learn more about how customized files appear on GitHub.

8 changes: 2 additions & 6 deletions front/src/app/api/balance/[address]/route.ts
Original file line number Diff line number Diff line change
@@ -1,16 +1,12 @@
import { Hex, stringify } from "viem";
import { PUBLIC_CLIENT } from "@/constants";

export async function GET(_req: Request, { params }: { params: { address: Hex } }) {
const { address } = params;
if (!address) {
return Response.json(JSON.parse(stringify({ error: "address is required" })));
}
const result = await fetch(
`https://api-sepolia.etherscan.io/api?module=account&action=balance&address=${address}&tag=latest&apikey=${process.env.ETHERSCAN_API_KEY}`,
{ cache: "no-store" },
);
const resultJSON = await result.json();
const balance = BigInt(resultJSON?.result || 0);
const balance = await PUBLIC_CLIENT.getBalance({ address });

return Response.json(JSON.parse(stringify({ balance })));
}
32 changes: 0 additions & 32 deletions front/src/app/api/users/[id]/route.ts

This file was deleted.

21 changes: 21 additions & 0 deletions front/src/app/api/users/[pubKey]/route.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,21 @@
import { PUBLIC_CLIENT } from "@/constants";
import { getSmartWalletAddress } from "@/utils/smartWalletUtils";
import { Hex, stringify } from "viem";

export async function GET(_req: Request, { params }: { params: { pubKey: Hex } }) {
const { pubKey } = params;
if (!pubKey) {
return Response.json(JSON.parse(stringify({ error: "pubkey is required" })));
}

const smartWalletAddress = await getSmartWalletAddress({ pubKey });

const balance = await PUBLIC_CLIENT.getBalance({ address: smartWalletAddress });

const createdUser = {
account: smartWalletAddress,
pubKey,
};

return Response.json(JSON.parse(stringify({ ...createdUser, balance })));
}
51 changes: 9 additions & 42 deletions front/src/app/api/users/save/route.ts
Original file line number Diff line number Diff line change
@@ -1,50 +1,17 @@
import { CHAIN, PUBLIC_CLIENT, transport } from "@/constants";
import { FACTORY_ABI } from "@/constants/factory";
import { Hex, createWalletClient, toHex, zeroAddress } from "viem";
import { privateKeyToAccount } from "viem/accounts";
import { Hex } from "viem";
import { getSmartWalletAddress } from "@/utils/smartWalletUtils";

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

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

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

if (user.account !== zeroAddress) {
return Response.json(undefined);
}

await walletClient.writeContract({
address: process.env.NEXT_PUBLIC_FACTORY_CONTRACT_ADDRESS as Hex,
abi: FACTORY_ABI,
functionName: "saveUser",
args: [BigInt(id), pubKey],
});

const smartWalletAddress = await PUBLIC_CLIENT.readContract({
address: process.env.NEXT_PUBLIC_FACTORY_CONTRACT_ADDRESS as Hex,
abi: FACTORY_ABI,
functionName: "getAddress",
args: [pubKey],
});

await walletClient.sendTransaction({
to: smartWalletAddress,
value: BigInt(1),
});
/**
* Limitations: passkey owner needs to be the only initial owner in order for lookups to be accurate
* This is because the smart wallet address depends on the owner's public key
* Could implement a record which maps public keys to smart wallet addresses in the future
*/
const smartWalletAddress = await getSmartWalletAddress({ pubKey });

const createdUser = {
id,
account: smartWalletAddress,
pubKey,
};
Expand Down
10 changes: 5 additions & 5 deletions front/src/app/connect/page.tsx
Original file line number Diff line number Diff line change
@@ -1,18 +1,18 @@
"use client";

import { useEffect, useState } from "react";
import { walletConnect } from "../../libs/wallet-connect/service/wallet-connect";
import { useMe } from "../../providers/MeProvider";
import { SCWKeyManager } from "../../utils/SCWKeyManager";
import { walletConnect } from "@/libs/wallet-connect/service/wallet-connect";
import { useMe } from "@/providers/MeProvider";
import { SCWKeyManager } from "@/utils/SCWKeyManager";
import {
decryptContent,
encryptContent,
exportKeyToHexString,
importKeyFromHexString,
RPCRequest,
RPCResponse,
} from "../../utils/cipher";
import { smartWallet } from "../../libs/smart-wallet";
} from "@/utils/cipher";
import { smartWallet } from "@/libs/smart-wallet";

const keyManager = new SCWKeyManager();

Expand Down
3 changes: 2 additions & 1 deletion front/src/components/History/index.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -4,6 +4,7 @@ import { Button, Flex, Callout } from "@radix-ui/themes";
import { useMe } from "@/providers/MeProvider";
import { ArrowRightIcon } from "@radix-ui/react-icons";
import LogoAnimatedLight from "../LogoAnimatedLight";
import { CHAIN } from "@/constants";

export default function History() {
const { me } = useMe();
Expand All @@ -21,7 +22,7 @@ export default function History() {
variant="outline"
style={{ marginTop: ".3rem" }}
onClick={() => {
window.open(`https://sepolia.etherscan.io/address/${me?.account}`, "_blank");
window.open(`${CHAIN.blockExplorers.default.url}/address/${me?.account}`, "_blank");
}}
>
Browse history on etherscan
Expand Down
4 changes: 2 additions & 2 deletions front/src/components/HomePage/index.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -7,7 +7,7 @@ import Balance from "../Balance";
import NavBar from "../NavBar";
import History from "../History";
import TopBar from "../TopBar";
import LogoAnimated from "../LogoAnimated";
import { smartWallet } from "@/libs/smart-wallet";

export default function Home() {
const { me, isMounted } = useMe();
Expand All @@ -18,7 +18,7 @@ export default function Home() {
return (
<Flex direction="column" width="100%" justify={"between"}>
<Flex direction="column" width="100%">
<TopBar />
<TopBar chainId={smartWallet.client.chain?.id} />
<Balance />
<NavBar />
</Flex>
Expand Down
36 changes: 23 additions & 13 deletions front/src/components/SendTxModal/index.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -14,7 +14,7 @@ import {
} from "@radix-ui/react-icons";
import { useMe } from "@/providers/MeProvider";
import Spinner from "../Spinner";
import { MAINNET_PUBLIC_CLIENT } from "@/constants";
import { CHAIN, MAINNET_PUBLIC_CLIENT } from "@/constants";
import { normalize } from "viem/ens";

smartWallet.init();
Expand All @@ -29,7 +29,7 @@ export default function SendTxModal() {
const [isBelowBalance, setIsBelowBalance] = useState(false);
const [ensIsLoading, setEnsIsLoading] = useState(false);
const [destination, setDestination] = useState("");
const { me } = useMe();
const { me, get } = useMe();
const { balance, refreshBalance } = useBalance();

const addressInputRef = useRef<HTMLInputElement>(null);
Expand All @@ -53,13 +53,13 @@ export default function SendTxModal() {

function handleUserInputAmount(e: any) {
const value = e.target.value;
const amount = Number(value);
if ((amount > Number(balance) && value !== "") || value === "") {
setIsBelowBalance(false);
}
if (amount <= Number(balance) && value !== "") {
setIsBelowBalance(true);
}
// const amount = Number(value);
// if ((amount > Number(balance) && value !== "") || value === "") {
// setIsBelowBalance(false);
// }
// if (amount <= Number(balance) && value !== "") {
// setIsBelowBalance(true);
// }
setUserInputAmount(value);
}

Expand Down Expand Up @@ -106,6 +106,15 @@ export default function SendTxModal() {
e.preventDefault();

try {
let user = me;
if (!user?.pubKey) {
user = await get();
}

if (!user?.pubKey) {
throw new Error("User not found");
}

const price: { ethereum: { usd: number } } = await (
await fetch("/api/price?ids=ethereum&currencies=usd")
).json();
Expand All @@ -124,7 +133,8 @@ export default function SendTxModal() {
],
maxFeePerGas: maxFeePerGas as bigint,
maxPriorityFeePerGas: maxPriorityFeePerGas as bigint,
keyId: me?.keyId as Hex,
keyId: user.keyId as Hex,
pubKey: user.pubKey as Hex,
});
const hash = await smartWallet.sendUserOperation({ userOp });
const receipt = await smartWallet.waitForUserOperationReceipt({ hash });
Expand Down Expand Up @@ -154,7 +164,7 @@ export default function SendTxModal() {
<>
<CheckCircledIcon height="32" width="100%" color="var(--teal-11)" />
<Link
href={`https://sepolia.etherscan.io/tx/${txReceipt?.receipt?.transactionHash}`}
href={`${CHAIN.blockExplorers.default.url}/tx/${txReceipt?.receipt?.transactionHash}`}
target="_blank"
style={{ textDecoration: "none" }}
>
Expand All @@ -168,7 +178,7 @@ export default function SendTxModal() {
<>
<CrossCircledIcon height="32" width="100%" />
<Link
href={`https://sepolia.etherscan.io/tx/${txReceipt?.receipt?.transactionHash}`}
href={`${CHAIN.blockExplorers.default.url}/tx/${txReceipt?.receipt?.transactionHash}`}
target="_blank"
style={{ textDecoration: "none" }}
>
Expand Down Expand Up @@ -257,7 +267,7 @@ export default function SendTxModal() {
type="number"
inputMode="decimal"
min={0}
max={balance?.toString() || 0}
// max={balance?.toString() || 0}
size={"3"}
step={0.01}
value={userInputAmount}
Expand Down
1 change: 0 additions & 1 deletion front/src/components/SettingsPage/index.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -36,7 +36,6 @@ export default function SettingsPage() {
Settings
</Heading>
<Flex direction="column" width={"100%"} align={"start"} gap={"2"}>
<Text>On Sepolia testnet only:</Text>
<Flex style={{ width: "100%" }} align={"center"} gap="2" justify={"between"}>
<Tooltip content="Copied!" open={isCopied}>
<Button
Expand Down
Loading

0 comments on commit ac26023

Please sign in to comment.