Skip to content
Merged
Show file tree
Hide file tree
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
1 change: 1 addition & 0 deletions prisma/schema.prisma
Original file line number Diff line number Diff line change
Expand Up @@ -31,6 +31,7 @@ model Wallet {
type String
isArchived Boolean @default(false)
clarityApiKey String?
rawImportBodies Json?
migrationTargetWalletId String?
}

Expand Down
82 changes: 63 additions & 19 deletions src/pages/api/v1/README.md
Original file line number Diff line number Diff line change
Expand Up @@ -5,13 +5,15 @@ A comprehensive REST API implementation for the multisig wallet application, pro
## Authentication & Security

### JWT-Based Authentication

- **Bearer Token Authentication**: All endpoints require valid JWT tokens
- **Address-Based Authorization**: Token payload contains user address for authorization
- **Session Management**: 1-hour token expiration with automatic renewal
- **CORS Support**: Cross-origin resource sharing enabled for web clients
- **Address Validation**: Strict address matching between token and request parameters

### Security Features

- **Input Validation**: Comprehensive parameter validation and sanitization
- **Error Handling**: Detailed error responses without sensitive information exposure
- **Rate Limiting**: Built-in protection against abuse (via CORS and validation)
Expand All @@ -22,6 +24,7 @@ A comprehensive REST API implementation for the multisig wallet application, pro
### Transaction Management

#### `addTransaction.ts` - POST `/api/v1/addTransaction`

- **Purpose**: Submit external transactions for multisig wallet processing
- **Authentication**: Required (JWT Bearer token)
- **Features**:
Expand All @@ -40,7 +43,24 @@ A comprehensive REST API implementation for the multisig wallet application, pro
- **Response**: Transaction object with ID, state, and metadata
- **Error Handling**: 400 (validation), 401 (auth), 403 (authorization), 500 (server)

#### `signTransaction.ts` - POST `/api/v1/signTransaction`

- **Purpose**: Record a signature for a pending multisig transaction
- **Authentication**: Required (JWT Bearer token)
- **Features**:
- Signature tracking with duplicate and rejection safeguards
- Wallet membership validation and JWT address enforcement
- Threshold detection with automatic submission when the final signature is collected
- **Request Body**:
- `walletId`: Wallet identifier
- `transactionId`: Pending transaction identifier
- `address`: Signer address
- `signedTx`: CBOR transaction payload after applying the signature
- **Response**: Updated transaction record with threshold status metadata; includes `txHash` when submission succeeds
- **Error Handling**: 400 (validation), 401 (auth), 403 (authorization), 404 (not found), 409 (duplicate/rejected), 502 (submission failure), 500 (server)

#### `submitDatum.ts` - POST `/api/v1/submitDatum`

- **Purpose**: Submit signable payloads for multisig signature collection
- **Authentication**: Required (JWT Bearer token)
- **Features**:
Expand All @@ -63,6 +83,7 @@ A comprehensive REST API implementation for the multisig wallet application, pro
### Wallet Management

#### `walletIds.ts` - GET `/api/v1/walletIds`

- **Purpose**: Retrieve all wallet IDs and names for a user address
- **Authentication**: Required (JWT Bearer token)
- **Features**:
Expand All @@ -76,6 +97,7 @@ A comprehensive REST API implementation for the multisig wallet application, pro
- **Error Handling**: 400 (validation), 401 (auth), 403 (authorization), 404 (not found), 500 (server)

#### `nativeScript.ts` - GET `/api/v1/nativeScript`

- **Purpose**: Generate native scripts for multisig wallet operations
- **Authentication**: Required (JWT Bearer token)
- **Features**:
Expand All @@ -90,6 +112,7 @@ A comprehensive REST API implementation for the multisig wallet application, pro
- **Error Handling**: 400 (validation), 401 (auth), 403 (authorization), 404 (not found), 500 (server)

#### `lookupMultisigWallet.ts` - GET `/api/v1/lookupMultisigWallet`

- **Purpose**: Lookup multisig wallet metadata using public key hashes
- **Authentication**: Not required (public endpoint)
- **Features**:
Expand All @@ -106,6 +129,7 @@ A comprehensive REST API implementation for the multisig wallet application, pro
### UTxO Management

#### `freeUtxos.ts` - GET `/api/v1/freeUtxos`

- **Purpose**: Retrieve unblocked UTxOs for a multisig wallet
- **Authentication**: Required (JWT Bearer token)
- **Features**:
Expand All @@ -123,6 +147,7 @@ A comprehensive REST API implementation for the multisig wallet application, pro
### Authentication Endpoints

#### `getNonce.ts` - GET `/api/v1/getNonce`

- **Purpose**: Request authentication nonce for address-based signing
- **Authentication**: Not required (public endpoint)
- **Features**:
Expand All @@ -136,6 +161,7 @@ A comprehensive REST API implementation for the multisig wallet application, pro
- **Error Handling**: 400 (validation), 404 (user not found), 500 (server)

#### `authSigner.ts` - POST `/api/v1/authSigner`

- **Purpose**: Verify signed nonce and return JWT bearer token
- **Authentication**: Not required (public endpoint)
- **Features**:
Expand All @@ -153,6 +179,7 @@ A comprehensive REST API implementation for the multisig wallet application, pro
### Utility Endpoints

#### `og.ts` - GET `/api/v1/og`

- **Purpose**: Extract Open Graph metadata from URLs
- **Authentication**: Not required (public endpoint)
- **Features**:
Expand All @@ -168,25 +195,29 @@ A comprehensive REST API implementation for the multisig wallet application, pro
## API Architecture

### Request/Response Patterns

- **RESTful Design**: Standard HTTP methods and status codes
- **JSON Format**: All requests and responses use JSON
- **Error Consistency**: Standardized error response format
- **CORS Support**: Cross-origin requests enabled

### Authentication Flow

1. **Nonce Request**: Client requests nonce for address
2. **Signature Generation**: Client signs nonce with private key
3. **Token Exchange**: Client exchanges signature for JWT token
4. **API Access**: Client uses JWT token for authenticated requests

### Error Handling

- **HTTP Status Codes**: Proper status code usage
- **Error Messages**: Descriptive error messages
- **Validation Errors**: Detailed parameter validation
- **Security Errors**: Authentication and authorization failures
- **Server Errors**: Internal server error handling

### Database Integration

- **Prisma ORM**: Type-safe database operations
- **Transaction Management**: Database transaction handling
- **Data Validation**: Input validation and sanitization
Expand All @@ -195,18 +226,21 @@ A comprehensive REST API implementation for the multisig wallet application, pro
## Security Considerations

### Input Validation

- **Parameter Validation**: All input parameters validated
- **Type Checking**: Strict type validation for all inputs
- **Address Validation**: Cardano address format validation
- **Signature Verification**: Cryptographic signature validation

### Authorization

- **Address Matching**: Token address must match request address
- **Wallet Access**: Users can only access their own wallets
- **Resource Protection**: Sensitive operations require authentication
- **Session Management**: Token expiration and renewal

### Data Protection

- **Sensitive Data**: No sensitive data in error messages
- **Logging**: Comprehensive logging without sensitive information
- **CORS**: Proper cross-origin resource sharing configuration
Expand All @@ -215,71 +249,81 @@ A comprehensive REST API implementation for the multisig wallet application, pro
## Dependencies

### Core Dependencies

- **Next.js API Routes**: Server-side API implementation
- **Prisma**: Database ORM and query builder
- **jsonwebtoken**: JWT token generation and verification
- **@meshsdk/core**: Cardano blockchain interactions
- **@meshsdk/core-cst**: Cryptographic signature verification

### Utility Dependencies

- **crypto**: Node.js cryptographic functions
- **cors**: Cross-origin resource sharing
- **fetch**: HTTP client for external requests

## Environment Variables

### Required Variables

- `JWT_SECRET`: Secret key for JWT token generation
- `NEXT_PUBLIC_BLOCKFROST_API_KEY_PREPROD`: Preprod network API key
- `NEXT_PUBLIC_BLOCKFROST_API_KEY_MAINNET`: Mainnet network API key

### Database Configuration

- Database connection via Prisma configuration
- Environment-specific database URLs
- Connection pooling and optimization

## Usage Examples

### Authentication Flow

```typescript
// 1. Request nonce
const nonceResponse = await fetch('/api/v1/getNonce?address=addr1...');
const nonceResponse = await fetch("/api/v1/getNonce?address=addr1...");
const { nonce } = await nonceResponse.json();

// 2. Sign nonce and get token
const signature = await wallet.signData(nonce, address);
const tokenResponse = await fetch('/api/v1/authSigner', {
method: 'POST',
body: JSON.stringify({ address, signature, key: publicKey })
const tokenResponse = await fetch("/api/v1/authSigner", {
method: "POST",
body: JSON.stringify({ address, signature, key: publicKey }),
});
const { token } = await tokenResponse.json();
```

### Transaction Submission

```typescript
const response = await fetch('/api/v1/addTransaction', {
method: 'POST',
const response = await fetch("/api/v1/addTransaction", {
method: "POST",
headers: {
'Authorization': `Bearer ${token}`,
'Content-Type': 'application/json'
Authorization: `Bearer ${token}`,
"Content-Type": "application/json",
},
body: JSON.stringify({
walletId: 'wallet-id',
address: 'addr1...',
txCbor: 'tx-cbor-data',
txJson: 'tx-json-data',
description: 'Transaction description'
})
walletId: "wallet-id",
address: "addr1...",
txCbor: "tx-cbor-data",
txJson: "tx-json-data",
description: "Transaction description",
}),
});
```

### UTxO Retrieval

```typescript
const response = await fetch('/api/v1/freeUtxos?walletId=wallet-id&address=addr1...', {
headers: {
'Authorization': `Bearer ${token}`
}
});
const response = await fetch(
"/api/v1/freeUtxos?walletId=wallet-id&address=addr1...",
{
headers: {
Authorization: `Bearer ${token}`,
},
},
);
const freeUtxos = await response.json();
```

Expand Down
72 changes: 72 additions & 0 deletions src/pages/api/v1/pendingTransactions.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,72 @@
import { cors, addCorsCacheBustingHeaders } from "@/lib/cors";
import { verifyJwt } from "@/lib/verifyJwt";
import { createCaller } from "@/server/api/root";
import { db } from "@/server/db";
import type { NextApiRequest, NextApiResponse } from "next";

export default async function handler(
req: NextApiRequest,
res: NextApiResponse,
) {
// Add cache-busting headers for CORS
addCorsCacheBustingHeaders(res);

await cors(req, res);
if (req.method === "OPTIONS") {
return res.status(200).end();
}

if (req.method !== "GET") {
return res.status(405).json({ error: "Method Not Allowed" });
}

const authHeader = req.headers.authorization;
const token = authHeader?.startsWith("Bearer ") ? authHeader.slice(7) : null;

if (!token) {
return res.status(401).json({ error: "Unauthorized - Missing token" });
}

const payload = verifyJwt(token);
if (!payload) {
return res.status(401).json({ error: "Invalid or expired token" });
}

const session = {
user: { id: payload.address },
expires: new Date(Date.now() + 60 * 60 * 1000).toISOString(),
} as const;

const { walletId, address } = req.query;

if (typeof address !== "string") {
return res.status(400).json({ error: "Invalid address parameter" });
}
if (payload.address !== address) {
return res.status(403).json({ error: "Address mismatch" });
}
if (typeof walletId !== "string") {
return res.status(400).json({ error: "Invalid walletId parameter" });
}

try {
const caller = createCaller({ db, session });

const wallet = await caller.wallet.getWallet({ walletId, address });
if (!wallet) {
return res.status(404).json({ error: "Wallet not found" });
}

const pendingTransactions =
await caller.transaction.getPendingTransactions({ walletId });

return res.status(200).json(pendingTransactions);
} catch (error) {
console.error("Error in pendingTransactions handler", {
message: (error as Error)?.message,
stack: (error as Error)?.stack,
});
return res.status(500).json({ error: "Internal Server Error" });
}
}

Loading