Skip to content

Commit efa0ddd

Browse files
authored
DCA dapp using userOpbuilder Service (#686)
* handle userOp building using UserOpBuilderService * refactor dashboard * refactored wc cosigner service * updated USEROP_BUILDER_SERVICE_BASE_URL * chores: text change * chores: run prettier, move const to constantUtils * update CoSignResponse type * remove NEXT_PUBLIC_APPLICATION_PRIVATE_KEY * add .env.example
1 parent 4fec2f0 commit efa0ddd

18 files changed

+446
-567
lines changed
Lines changed: 4 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,4 @@
1+
NEXT_PUBLIC_PROJECT_ID=
2+
NEXT_PUBLIC_RELAY_URL=wss://relay.walletconnect.org
3+
NEXT_PUBLIC_SECURE_SITE_SDK_URL=
4+
APPLICATION_PRIVATE_KEY=

advanced/dapps/dca-dapp-demo/.gitignore

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -27,6 +27,7 @@ yarn-error.log*
2727

2828
# local env files
2929
.env*.local
30+
.env
3031

3132
# vercel
3233
.vercel

advanced/dapps/dca-dapp-demo/package.json

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -17,6 +17,7 @@
1717
"@radix-ui/react-slot": "^1.1.0",
1818
"@radix-ui/react-tabs": "^1.1.0",
1919
"@radix-ui/react-toast": "^1.2.1",
20+
"@radix-ui/react-tooltip": "^1.1.2",
2021
"@shadcn/ui": "^0.0.4",
2122
"@tanstack/react-query": "5.24.8",
2223
"@wagmi/connectors": "5.1.2",
@@ -33,7 +34,6 @@
3334
"lucide-react": "^0.427.0",
3435
"next": "14.2.5",
3536
"next-themes": "^0.3.0",
36-
"permissionless": "0.1.31",
3737
"pino-pretty": "^11.2.2",
3838
"react": "^18",
3939
"react-dom": "^18",

advanced/dapps/dca-dapp-demo/src/app/api/dca/execute/route.ts

Lines changed: 8 additions & 9 deletions
Original file line numberDiff line numberDiff line change
@@ -3,7 +3,7 @@ import {
33
address as donutContractAddress,
44
} from "@/utils/DonutContract";
55
import { executeActionsWithECDSAAndCosignerPermissions } from "@/utils/ERC7715PermissionsAsyncUtils";
6-
import { CoSignerApiError } from "@/utils/WalletConnectCosigner";
6+
import { CoSignerApiError } from "@/utils/WalletConnectCosignerUtils";
77
import { NextResponse } from "next/server";
88
import { encodeFunctionData, parseEther } from "viem";
99
import { GrantPermissionsReturnType } from "viem/experimental";
@@ -20,8 +20,7 @@ export async function POST(request: Request) {
2020
permissions: GrantPermissionsReturnType;
2121
pci: string;
2222
} = await request.json();
23-
const APPLICATION_PRIVATE_KEY = process.env
24-
.NEXT_PUBLIC_APPLICATION_PRIVATE_KEY as `0x${string}`;
23+
const APPLICATION_PRIVATE_KEY = process.env.APPLICATION_PRIVATE_KEY as `0x${string}`;
2524

2625
try {
2726
if (!APPLICATION_PRIVATE_KEY) {
@@ -32,18 +31,18 @@ export async function POST(request: Request) {
3231
}
3332
if (!strategy) {
3433
return NextResponse.json(
35-
{ message: "No strategy provider" },
34+
{ message: "No strategy provided" },
3635
{ status: 400 },
3736
);
3837
}
3938
if (!permissions) {
4039
return NextResponse.json(
41-
{ message: "No permissions provider" },
40+
{ message: "No permissions provided" },
4241
{ status: 400 },
4342
);
4443
}
4544
if (!pci) {
46-
return NextResponse.json({ message: "No pci provider" }, { status: 400 });
45+
return NextResponse.json({ message: "No pci provided" }, { status: 400 });
4746
}
4847

4948
const purchaseDonutCallData = encodeFunctionData({
@@ -53,12 +52,12 @@ export async function POST(request: Request) {
5352
});
5453
const purchaseDonutCallDataExecution = [
5554
{
56-
target: donutContractAddress as `0x${string}`,
55+
to: donutContractAddress as `0x${string}`,
5756
value: parseEther("0.0001"),
58-
callData: purchaseDonutCallData,
57+
data: purchaseDonutCallData,
5958
},
6059
];
61-
executeActionsWithECDSAAndCosignerPermissions({
60+
await executeActionsWithECDSAAndCosignerPermissions({
6261
ecdsaPrivateKey: APPLICATION_PRIVATE_KEY,
6362
pci,
6463
permissions,

advanced/dapps/dca-dapp-demo/src/app/api/signer/route.ts

Lines changed: 2 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -3,16 +3,15 @@ import { privateKeyToAccount } from "viem/accounts";
33

44
export function GET() {
55
try {
6-
const APPLICATION_PRIVATE_KEY = process.env
7-
.NEXT_PUBLIC_APPLICATION_PRIVATE_KEY as `0x${string}`;
6+
const APPLICATION_PRIVATE_KEY = process.env.APPLICATION_PRIVATE_KEY as `0x${string}`;
87
const account = privateKeyToAccount(APPLICATION_PRIVATE_KEY);
98

109
return NextResponse.json({ key: account.publicKey });
1110
} catch (e) {
1211
console.warn("Error getting signer:", e);
1312

1413
return NextResponse.json(
15-
{ message: "Error getting signer", error: (e as Error).message },
14+
{ message: "Error getting application signer" },
1615
{ status: 500 },
1716
);
1817
}

advanced/dapps/dca-dapp-demo/src/app/page.tsx

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -21,7 +21,7 @@ export default function Home() {
2121
</div>
2222
) : (
2323
<p className="text-lg text-gray-600 dark:text-gray-400 font-bold">
24-
Connect Wallet to get started.
24+
Connect wallet to create startegy.
2525
</p>
2626
)}
2727

Lines changed: 53 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,53 @@
1+
import React, { useState } from "react";
2+
import { Button } from "@/components/ui/button";
3+
import { Copy, Check } from "lucide-react";
4+
import {
5+
Tooltip,
6+
TooltipContent,
7+
TooltipProvider,
8+
TooltipTrigger,
9+
} from "@/components/ui/tooltip";
10+
11+
interface AddressDisplayProps {
12+
address: string;
13+
}
14+
15+
export default function AddressDisplay({ address }: AddressDisplayProps) {
16+
const [copied, setCopied] = useState(false);
17+
18+
function shortenAddress(address: string) {
19+
return `${address.slice(0, 6)}...${address.slice(-4)}`;
20+
}
21+
22+
function copyToClipboard() {
23+
navigator.clipboard.writeText(address).then(() => {
24+
setCopied(true);
25+
setTimeout(() => setCopied(false), 2000); // Reset after 2 seconds
26+
});
27+
}
28+
29+
return (
30+
<div className="flex justify-between items-center border-b pb-2">
31+
<p className="font-semibold">Address</p>
32+
<div className="flex items-center space-x-2">
33+
<p className="text-sm">{shortenAddress(address)}</p>
34+
<TooltipProvider>
35+
<Tooltip>
36+
<TooltipTrigger asChild>
37+
<Button variant="outline" size="icon" onClick={copyToClipboard}>
38+
{copied ? (
39+
<Check className="h-4 w-4" />
40+
) : (
41+
<Copy className="h-4 w-4" />
42+
)}
43+
</Button>
44+
</TooltipTrigger>
45+
<TooltipContent>
46+
<p>{copied ? "Copied!" : "Copy"}</p>
47+
</TooltipContent>
48+
</Tooltip>
49+
</TooltipProvider>
50+
</div>
51+
</div>
52+
);
53+
}
Lines changed: 26 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,26 @@
1+
import React from "react";
2+
3+
interface AssetBalanceProps {
4+
assetName: string;
5+
balance: string | undefined;
6+
isLoading: boolean;
7+
}
8+
9+
export default function AssetBalance({
10+
assetName,
11+
balance,
12+
isLoading,
13+
}: AssetBalanceProps) {
14+
return (
15+
<>
16+
<div className="flex justify-between border-b pb-2">
17+
<p className="font-semibold">Asset</p>
18+
<p className="font-semibold">Balance</p>
19+
</div>
20+
<div className="flex justify-between items-center">
21+
<p>{assetName}</p>
22+
{isLoading ? <p>...</p> : <p>{balance}</p>}
23+
</div>
24+
</>
25+
);
26+
}

advanced/dapps/dca-dapp-demo/src/components/Dashboard.tsx

Lines changed: 38 additions & 21 deletions
Original file line numberDiff line numberDiff line change
@@ -6,9 +6,33 @@ import {
66
} from "@/utils/DonutContract";
77
import { useReadContract } from "wagmi";
88
import { Card, CardContent, CardHeader, CardTitle } from "@/components/ui/card";
9+
import AddressDisplay from "./AddressDisplay";
10+
import AssetBalance from "./AssetBalance";
911

1012
export default function Dashboard() {
11-
const { address: accountAddress } = useDcaApplicationContext();
13+
const { address: connectedAddress, grantedPermissions } =
14+
useDcaApplicationContext();
15+
const lastAddress = grantedPermissions
16+
? grantedPermissions.signerData?.submitToAddress
17+
: connectedAddress;
18+
19+
return (
20+
<Card className="max-w-md mx-auto mt-8">
21+
<CardHeader>
22+
<CardTitle className="text-center">Dashboard</CardTitle>
23+
</CardHeader>
24+
<CardContent>
25+
{lastAddress ? (
26+
<DashboardContent address={lastAddress} />
27+
) : (
28+
<EmptyDashboardContent />
29+
)}
30+
</CardContent>
31+
</Card>
32+
);
33+
}
34+
35+
function DashboardContent({ address }: { address: string }) {
1236
const {
1337
data: donutsOwned,
1438
isLoading: donutsQueryLoading,
@@ -17,31 +41,24 @@ export default function Dashboard() {
1741
abi: donutAbi,
1842
address: donutAddress,
1943
functionName: "getBalance",
20-
args: [accountAddress],
44+
args: [address],
2145
query: {
2246
refetchOnWindowFocus: false,
2347
},
2448
});
2549

2650
return (
27-
<Card className="max-w-md mx-auto mt-8">
28-
<CardHeader>
29-
<CardTitle className="text-center">Dashboard</CardTitle>
30-
</CardHeader>
31-
<CardContent>
32-
<div className="flex justify-between border-b pb-2 mb-2">
33-
<p className="font-semibold">Asset</p>
34-
<p className="font-semibold">Balance</p>
35-
</div>
36-
<div className="flex justify-between items-center">
37-
<p>Donut</p>
38-
{donutsQueryLoading || donutsQueryRefetching ? (
39-
<p>...</p>
40-
) : (
41-
<p>{donutsOwned?.toString()}</p>
42-
)}
43-
</div>
44-
</CardContent>
45-
</Card>
51+
<div className="flex flex-col space-y-4">
52+
<AddressDisplay address={address} />
53+
<AssetBalance
54+
assetName="Donut"
55+
balance={donutsOwned?.toString()}
56+
isLoading={donutsQueryLoading || donutsQueryRefetching}
57+
/>
58+
</div>
4659
);
4760
}
61+
62+
function EmptyDashboardContent() {
63+
return <p className="text-center">No address found</p>;
64+
}
Lines changed: 30 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,30 @@
1+
"use client";
2+
3+
import * as React from "react";
4+
import * as TooltipPrimitive from "@radix-ui/react-tooltip";
5+
6+
import { cn } from "@/lib/utils";
7+
8+
const TooltipProvider = TooltipPrimitive.Provider;
9+
10+
const Tooltip = TooltipPrimitive.Root;
11+
12+
const TooltipTrigger = TooltipPrimitive.Trigger;
13+
14+
const TooltipContent = React.forwardRef<
15+
React.ElementRef<typeof TooltipPrimitive.Content>,
16+
React.ComponentPropsWithoutRef<typeof TooltipPrimitive.Content>
17+
>(({ className, sideOffset = 4, ...props }, ref) => (
18+
<TooltipPrimitive.Content
19+
ref={ref}
20+
sideOffset={sideOffset}
21+
className={cn(
22+
"z-50 overflow-hidden rounded-md border bg-popover px-3 py-1.5 text-sm text-popover-foreground shadow-md animate-in fade-in-0 zoom-in-95 data-[state=closed]:animate-out data-[state=closed]:fade-out-0 data-[state=closed]:zoom-out-95 data-[side=bottom]:slide-in-from-top-2 data-[side=left]:slide-in-from-right-2 data-[side=right]:slide-in-from-left-2 data-[side=top]:slide-in-from-bottom-2",
23+
className,
24+
)}
25+
{...props}
26+
/>
27+
));
28+
TooltipContent.displayName = TooltipPrimitive.Content.displayName;
29+
30+
export { Tooltip, TooltipTrigger, TooltipContent, TooltipProvider };

0 commit comments

Comments
 (0)