Skip to content

Commit 74ac032

Browse files
authored
Merge pull request #105 from MeshJS/API
add submitDatum endpoint
2 parents 6330855 + 9e8e8ab commit 74ac032

File tree

6 files changed

+357
-162
lines changed

6 files changed

+357
-162
lines changed
Lines changed: 3 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,3 @@
1+
-- AlterTable
2+
ALTER TABLE "Signable" ADD COLUMN "callbackUrl" TEXT,
3+
ADD COLUMN "remoteOrigin" TEXT;

prisma/schema.prisma

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -60,6 +60,8 @@ model Signable {
6060
state Int
6161
createdAt DateTime @default(now())
6262
updatedAt DateTime @updatedAt
63+
callbackUrl String?
64+
remoteOrigin String?
6365
}
6466

6567
model NewWallet {

src/components/pages/wallet/signing/signable-card.tsx

Lines changed: 5 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -2,15 +2,13 @@ import React, { useMemo, useState, useCallback } from "react";
22
import { Signable } from "@prisma/client";
33

44
import { useWallet } from "@meshsdk/react";
5-
import { csl } from "@meshsdk/core-csl";
6-
import { pubKeyAddress, serializeAddressObj } from "@meshsdk/core";
75
import { sign } from "@/utils/signing";
86

97
import { useToast } from "@/hooks/use-toast";
108
import useAppWallet from "@/hooks/useAppWallet";
119
import { api } from "@/utils/api";
1210
import { useUserStore } from "@/lib/zustand/user";
13-
import { dateToFormatted, getFirstAndLast, lovelaceToAda } from "@/utils/strings";
11+
import { dateToFormatted, getFirstAndLast } from "@/utils/strings";
1412
import sendDiscordMessage from "@/lib/discord/sendDiscordMessage";
1513
import { TooltipProvider, TooltipTrigger } from "@radix-ui/react-tooltip";
1614
import { QuestionMarkIcon } from "@radix-ui/react-icons";
@@ -224,7 +222,7 @@ function SignableCard({
224222
try {
225223
setLoading(true);
226224
const userRewardAddress = (await wallet.getRewardAddresses())[0];
227-
const nonce = generateNonce("Reject this transaction: ");
225+
const nonce = generateNonce("Reject this Datum: ");
228226
const signature = await wallet.signData(nonce, userRewardAddress);
229227
const result = await checkSignature(nonce, signature);
230228

@@ -314,7 +312,7 @@ function SignableCard({
314312
</DropdownMenuItem>
315313
<DropdownMenuSeparator />
316314
<DropdownMenuItem onClick={deletePayload}>
317-
Delete Transaction
315+
Delete Datum
318316
</DropdownMenuItem>
319317
</DropdownMenuContent>
320318
</DropdownMenu>
@@ -344,7 +342,8 @@ function SignableCard({
344342
</TableHeader>
345343
<TableBody>
346344
{signable.signatures.map((sigStr, idx) => {
347-
const [sigPart = "", keyPart = ""] = sigStr.split(", key: ");
345+
const [sigPart = "", keyPart = ""] =
346+
sigStr.split(", key: ");
348347
const signature = sigPart.replace("signature: ", "");
349348
return (
350349
<TableRow key={idx}>

src/pages/api/v1/addTransaction.ts

Lines changed: 41 additions & 27 deletions
Original file line numberDiff line numberDiff line change
@@ -2,10 +2,13 @@ import type { NextApiRequest, NextApiResponse } from "next";
22
import { db } from "@/server/db";
33
import { verifyJwt } from "@/lib/verifyJwt";
44
import { cors } from "@/lib/cors";
5+
import { getProvider } from "@/utils/get-provider";
56

6-
export default async function handler(req: NextApiRequest, res: NextApiResponse) {
7-
8-
await cors(req, res);
7+
export default async function handler(
8+
req: NextApiRequest,
9+
res: NextApiResponse,
10+
) {
11+
await cors(req, res);
912
if (req.method === "OPTIONS") {
1013
return res.status(200).end();
1114
}
@@ -31,48 +34,59 @@ export default async function handler(req: NextApiRequest, res: NextApiResponse)
3134
expires: new Date(Date.now() + 60 * 60 * 1000).toISOString(),
3235
};
3336

37+
const {
38+
walletId,
39+
address,
40+
txCbor,
41+
txJson,
42+
description = "External Tx",
43+
callbackUrl,
44+
} = req.body;
3445

35-
//JSON.stringify(txBuilder.meshTxBuilderBody),
36-
37-
38-
const { walletId, address, txCbor, txJson, description="External Tx" } = req.body;
39-
40-
console.log( walletId)
41-
42-
if (!walletId ) {
46+
if (!walletId) {
4347
return res.status(400).json({ error: "Missing required field walletId!" });
4448
}
45-
if (!address ) {
49+
if (!address) {
4650
return res.status(400).json({ error: "Missing required field address!" });
4751
}
4852
// Optionally check that the address matches the session user.id for security
4953
if (session.user.id !== address) {
5054
return res.status(403).json({ error: "Address mismatch" });
5155
}
52-
if (!txCbor ) {
56+
if (!txCbor) {
5357
return res.status(400).json({ error: "Missing required field txCbor!" });
5458
}
55-
if (!txJson ) {
59+
if (!txJson) {
5660
return res.status(400).json({ error: "Missing required field txJson!" });
5761
}
58-
62+
const wallet = await db.wallet.findUnique({ where: { id: walletId } });
63+
const reqSigners = wallet?.numRequiredSigners;
64+
const type = wallet?.type;
65+
const network = address.includes("test") ? 0 : 1;
5966

6067
try {
61-
const newTx = await db.transaction.create({
62-
data: {
63-
walletId,
64-
txJson: typeof txJson === "object" ? JSON.stringify(txJson) : txJson,
65-
txCbor,
66-
signedAddresses: [address],
67-
rejectedAddresses: [],
68-
description,
69-
state: 0,
70-
},
71-
});
68+
let newTx;
69+
//ToDo refactor to more cases.
70+
if (reqSigners === 1 || type === "any") {
71+
const blockchainProvider = getProvider(network);
72+
newTx = blockchainProvider.submitTx(txCbor);
73+
} else {
74+
newTx = await db.transaction.create({
75+
data: {
76+
walletId,
77+
txJson: typeof txJson === "object" ? JSON.stringify(txJson) : txJson,
78+
txCbor,
79+
signedAddresses: [address],
80+
rejectedAddresses: [],
81+
description,
82+
state: 0,
83+
},
84+
});
85+
}
7286

7387
res.status(201).json(newTx);
7488
} catch (error) {
7589
console.error("Error creating transaction:", error);
7690
res.status(500).json({ error: "Internal Server Error" });
7791
}
78-
}
92+
}

src/pages/api/v1/submitDatum.ts

Lines changed: 99 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,99 @@
1+
import type { NextApiRequest, NextApiResponse } from "next";
2+
import { db } from "@/server/db";
3+
import { verifyJwt } from "@/lib/verifyJwt";
4+
import { cors } from "@/lib/cors";
5+
import { DataSignature } from "@meshsdk/core";
6+
import { checkSignature } from "@meshsdk/core-cst";
7+
8+
export default async function handler(
9+
req: NextApiRequest,
10+
res: NextApiResponse,
11+
) {
12+
await cors(req, res);
13+
if (req.method === "OPTIONS") {
14+
return res.status(200).end();
15+
}
16+
17+
if (req.method !== "POST") {
18+
return res.status(405).json({ error: "Method Not Allowed" });
19+
}
20+
21+
const authHeader = req.headers.authorization;
22+
const token = authHeader?.startsWith("Bearer ") ? authHeader.slice(7) : null;
23+
24+
if (!token) {
25+
return res.status(401).json({ error: "Unauthorized - Missing token" });
26+
}
27+
28+
const payload = verifyJwt(token);
29+
if (!payload) {
30+
return res.status(401).json({ error: "Invalid or expired token" });
31+
}
32+
33+
const session = {
34+
user: { id: payload.address },
35+
expires: new Date(Date.now() + 60 * 60 * 1000).toISOString(),
36+
};
37+
38+
const {
39+
walletId,
40+
signature,
41+
key,
42+
address,
43+
datum,
44+
callbackUrl,
45+
description = "External Tx",
46+
} = req.body;
47+
48+
if (!walletId) {
49+
return res.status(400).json({ error: "Missing required field walletId!" });
50+
}
51+
if (!signature) {
52+
return res.status(400).json({ error: "Missing required field signature!" });
53+
}
54+
if (!signature) {
55+
return res.status(400).json({ error: "Missing required field key!" });
56+
}
57+
if (!address) {
58+
return res.status(400).json({ error: "Missing required field address!" });
59+
}
60+
// Optionally check that the address matches the session user.id for security
61+
if (session.user.id !== address) {
62+
return res.status(403).json({ error: "Address mismatch" });
63+
}
64+
if (!datum) {
65+
return res.status(400).json({ error: "Missing required field Datum!" });
66+
}
67+
if (!callbackUrl) {
68+
return res.status(400).json({ error: "Missing required field Datum!" });
69+
}
70+
71+
const sig: DataSignature = { signature: signature, key: key };
72+
73+
const isValid = await checkSignature(datum, sig, address);
74+
75+
if (!isValid) {
76+
return res.status(401).json({ error: "Invalid signature" });
77+
}
78+
79+
try {
80+
const newSignable = await db.signable.create({
81+
data: {
82+
walletId,
83+
payload: datum,
84+
signatures: [`signature: ${sig.signature}, key: ${sig.key}`],
85+
signedAddresses: [address],
86+
rejectedAddresses: [],
87+
description,
88+
callbackUrl: callbackUrl || null,
89+
remoteOrigin: req.headers.origin || null,
90+
state: 0,
91+
},
92+
});
93+
94+
res.status(201).json(newSignable);
95+
} catch (error) {
96+
console.error("Error creating signable:", error);
97+
res.status(500).json({ error: "Internal Server Error" });
98+
}
99+
}

0 commit comments

Comments
 (0)