Skip to content

Commit cc27aa5

Browse files
committed
KEEP-1150 Move Wallet to integration rather than automatic creation
1 parent ebd79eb commit cc27aa5

File tree

11 files changed

+1749
-230
lines changed

11 files changed

+1749
-230
lines changed

PARA_INTEGRATION.md

Lines changed: 0 additions & 90 deletions
This file was deleted.

PARA_WORKFLOW_STEP.md

Lines changed: 0 additions & 44 deletions
This file was deleted.

app/api/user/wallet/route.ts

Lines changed: 229 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1,6 +1,15 @@
11
import { NextResponse } from "next/server";
2+
import { Environment, Para as ParaServer } from "@getpara/server-sdk";
3+
import { eq, and } from "drizzle-orm";
24
import { auth } from "@/lib/auth";
5+
import { db } from "@/lib/db";
6+
import { paraWallets, integrations } from "@/lib/db/schema";
7+
import { encryptUserShare } from "@/lib/encryption";
38
import { getUserWallet, userHasWallet } from "@/lib/para/wallet-helpers";
9+
import { createIntegration } from "@/lib/db/integrations";
10+
11+
const PARA_API_KEY = process.env.PARA_API_KEY!;
12+
const PARA_ENV = process.env.PARA_ENVIRONMENT || "beta";
413

514
export async function GET(request: Request) {
615
try {
@@ -40,3 +49,223 @@ export async function GET(request: Request) {
4049
);
4150
}
4251
}
52+
53+
export async function POST(request: Request) {
54+
try {
55+
// 1. Authenticate user
56+
const session = await auth.api.getSession({
57+
headers: request.headers,
58+
});
59+
60+
if (!session?.user) {
61+
return NextResponse.json({ error: "Unauthorized" }, { status: 401 });
62+
}
63+
64+
const user = session.user;
65+
66+
// 2. Check user has valid email (not anonymous)
67+
if (!user.email) {
68+
return NextResponse.json(
69+
{ error: "Email required to create wallet" },
70+
{ status: 400 }
71+
);
72+
}
73+
74+
// Check if user is anonymous (has email like temp-xxx@http://localhost:3000)
75+
if (user.email.includes("@http://") || user.email.includes("@https://") || user.email.startsWith("temp-")) {
76+
return NextResponse.json(
77+
{ error: "Anonymous users cannot create wallets. Please sign in with a real account." },
78+
{ status: 400 }
79+
);
80+
}
81+
82+
// 3. Check if wallet already exists
83+
const hasWallet = await userHasWallet(user.id);
84+
if (hasWallet) {
85+
return NextResponse.json(
86+
{ error: "Wallet already exists for this user" },
87+
{ status: 400 }
88+
);
89+
}
90+
91+
// 4. Check if Web3 integration already exists (additional safety check)
92+
const existingIntegration = await db
93+
.select()
94+
.from(integrations)
95+
.where(
96+
and(
97+
eq(integrations.userId, user.id),
98+
eq(integrations.type, "web3")
99+
)
100+
)
101+
.limit(1);
102+
103+
if (existingIntegration.length > 0) {
104+
return NextResponse.json(
105+
{ error: "Web3 integration already exists for this user" },
106+
{ status: 400 }
107+
);
108+
}
109+
110+
// 5. Validate Para API key
111+
if (!PARA_API_KEY) {
112+
console.error("[Para] PARA_API_KEY not configured");
113+
return NextResponse.json(
114+
{ error: "Para API key not configured" },
115+
{ status: 500 }
116+
);
117+
}
118+
119+
// 6. Initialize Para SDK
120+
const environment = PARA_ENV === "prod" ? Environment.PROD : Environment.BETA;
121+
console.log(`[Para] Initializing SDK with environment: ${PARA_ENV} (${environment})`);
122+
console.log(`[Para] API key: ${PARA_API_KEY.slice(0, 8)}...`);
123+
124+
const paraClient = new ParaServer(environment, PARA_API_KEY);
125+
126+
// 7. Skip wallet existence check - might be causing 403
127+
// Note: createPregenWallet should be idempotent anyway
128+
129+
// 8. Create wallet via Para SDK
130+
console.log(`[Para] Creating wallet for user ${user.id} (${user.email})`);
131+
132+
const wallet = await paraClient.createPregenWallet({
133+
type: "EVM",
134+
pregenId: { email: user.email },
135+
});
136+
137+
// 9. Get user share (cryptographic key for signing)
138+
const userShare = await paraClient.getUserShare();
139+
140+
if (!userShare) {
141+
throw new Error("Failed to get user share from Para");
142+
}
143+
144+
if (!(wallet.id && wallet.address)) {
145+
throw new Error("Invalid wallet data from Para");
146+
}
147+
148+
// 10. Store wallet in para_wallets table
149+
await db.insert(paraWallets).values({
150+
userId: user.id,
151+
email: user.email,
152+
walletId: wallet.id,
153+
walletAddress: wallet.address,
154+
userShare: encryptUserShare(userShare), // Encrypted!
155+
});
156+
157+
console.log(`[Para] ✓ Wallet created: ${wallet.address}`);
158+
159+
// 11. Create Web3 integration record with truncated address as name
160+
const truncatedAddress = `${wallet.address.slice(0, 6)}...${wallet.address.slice(-4)}`;
161+
162+
await createIntegration(
163+
user.id,
164+
truncatedAddress,
165+
"web3",
166+
{} // Empty config for web3
167+
);
168+
169+
console.log(`[Para] ✓ Web3 integration created: ${truncatedAddress}`);
170+
171+
// 12. Return success
172+
return NextResponse.json({
173+
success: true,
174+
wallet: {
175+
address: wallet.address,
176+
walletId: wallet.id,
177+
email: user.email,
178+
},
179+
});
180+
} catch (error) {
181+
console.error("[Para] Wallet creation failed:", error);
182+
183+
// Extract user-friendly error message
184+
let errorMessage = "Failed to create wallet";
185+
let statusCode = 500;
186+
187+
if (error instanceof Error) {
188+
const message = error.message.toLowerCase();
189+
190+
// Check for specific Para API errors
191+
if (message.includes("already exists")) {
192+
errorMessage = "A wallet already exists for this email address";
193+
statusCode = 409;
194+
} else if (message.includes("invalid email")) {
195+
errorMessage = "Invalid email format";
196+
statusCode = 400;
197+
} else if (message.includes("forbidden") || message.includes("403")) {
198+
errorMessage = "API key authentication failed. Please contact support.";
199+
statusCode = 403;
200+
} else {
201+
// Include the actual error message for other errors
202+
errorMessage = error.message;
203+
}
204+
}
205+
206+
return NextResponse.json(
207+
{
208+
error: errorMessage,
209+
},
210+
{ status: statusCode }
211+
);
212+
}
213+
}
214+
215+
export async function DELETE(request: Request) {
216+
try {
217+
// 1. Authenticate user
218+
const session = await auth.api.getSession({
219+
headers: request.headers,
220+
});
221+
222+
if (!session?.user) {
223+
return NextResponse.json({ error: "Unauthorized" }, { status: 401 });
224+
}
225+
226+
const user = session.user;
227+
228+
// 2. Delete wallet data
229+
const deletedWallet = await db
230+
.delete(paraWallets)
231+
.where(eq(paraWallets.userId, user.id))
232+
.returning();
233+
234+
if (deletedWallet.length === 0) {
235+
return NextResponse.json(
236+
{ error: "No wallet found to delete" },
237+
{ status: 404 }
238+
);
239+
}
240+
241+
console.log(
242+
`[Para] Wallet deleted for user ${user.id}: ${deletedWallet[0].walletAddress}`
243+
);
244+
245+
// 3. Delete associated Web3 integration record
246+
await db
247+
.delete(integrations)
248+
.where(
249+
and(
250+
eq(integrations.userId, user.id),
251+
eq(integrations.type, "web3")
252+
)
253+
);
254+
255+
console.log(`[Para] Web3 integration deleted for user ${user.id}`);
256+
257+
return NextResponse.json({
258+
success: true,
259+
message: "Wallet deleted successfully",
260+
});
261+
} catch (error) {
262+
console.error("[Para] Wallet deletion failed:", error);
263+
return NextResponse.json(
264+
{
265+
error: "Failed to delete wallet",
266+
details: error instanceof Error ? error.message : "Unknown error",
267+
},
268+
{ status: 500 }
269+
);
270+
}
271+
}

0 commit comments

Comments
 (0)