import express from 'express';
import { ServerSDK, NetworkId } from '@phantom/server-sdk';
import { PrismaClient } from '@prisma/client';
import { Connection, Transaction, SystemProgram, PublicKey, LAMPORTS_PER_SOL } from '@solana/web3.js';
import bs58 from 'bs58';
import dotenv from 'dotenv';
dotenv.config();
const app = express();
app.use(express.json());
const prisma = new PrismaClient();
const sdk = new ServerSDK({
apiPrivateKey: process.env.PRIVATE_KEY!,
organizationId: process.env.ORGANIZATION_ID!,
apiBaseUrl: process.env.PHANTOM_API_URL!
});
const connection = new Connection(process.env.SOLANA_RPC_URL!);
// List all wallets for the organization
app.get('/api/wallets', async (req, res) => {
try {
const limit = parseInt(req.query.limit as string) || 20;
const offset = parseInt(req.query.offset as string) || 0;
const result = await sdk.getWallets(limit, offset);
res.json({
wallets: result.wallets,
pagination: {
total: result.totalCount,
limit: result.limit,
offset: result.offset,
hasMore: result.offset + result.wallets.length < result.totalCount
}
});
} catch (error) {
console.error('Failed to list wallets:', error);
res.status(500).json({ error: 'Failed to list wallets' });
}
});
// Create or get wallet for user
app.post('/api/users/:userId/wallet', async (req, res) => {
try {
const { userId } = req.params;
// Check if user already has a wallet
let wallet = await prisma.wallet.findUnique({
where: { userId }
});
if (wallet) {
// Return existing wallet
return res.json({
walletId: wallet.walletId,
address: wallet.address,
addressType: wallet.addressType
});
}
// Create new wallet with meaningful name
const walletName = `user_${userId}`;
const result = await sdk.createWallet(walletName);
// Find Solana address from the addresses array
const solanaAddress = result.addresses.find(
addr => addr.addressType === 'Solana'
);
if (!solanaAddress) {
throw new Error('No Solana address found in wallet');
}
// Immediately persist wallet information
wallet = await prisma.wallet.create({
data: {
userId,
walletId: result.walletId,
walletName,
address: solanaAddress.address,
addressType: solanaAddress.addressType
}
});
res.json({
walletId: wallet.walletId,
address: wallet.address,
addressType: wallet.addressType
});
} catch (error) {
console.error('Wallet operation failed:', error);
res.status(500).json({ error: 'Failed to process wallet request' });
}
});
// Sign a message
app.post('/api/users/:userId/sign-message', async (req, res) => {
try {
const { userId } = req.params;
const { message } = req.body;
// Get user's wallet from database
const wallet = await prisma.wallet.findUnique({
where: { userId }
});
if (!wallet) {
return res.status(404).json({ error: 'Wallet not found' });
}
// Sign message using stored wallet ID
const signature = await sdk.signMessage({
walletId: wallet.walletId,
message,
networkId: NetworkId.SOLANA_MAINNET
});
res.json({
message,
signature,
publicKey: wallet.address
});
} catch (error) {
console.error('Message signing failed:', error);
res.status(500).json({ error: 'Failed to sign message' });
}
});
// Send SOL transaction
app.post('/api/users/:userId/send-sol', async (req, res) => {
try {
const { userId } = req.params;
const { recipientAddress, amount } = req.body;
// Validate inputs
if (!recipientAddress || !amount || amount <= 0) {
return res.status(400).json({ error: 'Invalid request parameters' });
}
// Get user's wallet
const wallet = await prisma.wallet.findUnique({
where: { userId }
});
if (!wallet) {
return res.status(404).json({ error: 'Wallet not found' });
}
// Check balance (optional but recommended)
const balance = await connection.getBalance(new PublicKey(wallet.address));
if (balance < amount * LAMPORTS_PER_SOL) {
return res.status(400).json({ error: 'Insufficient balance' });
}
// Create transaction
const transaction = new Transaction().add(
SystemProgram.transfer({
fromPubkey: new PublicKey(wallet.address),
toPubkey: new PublicKey(recipientAddress),
lamports: amount * LAMPORTS_PER_SOL
})
);
// Prepare transaction
const { blockhash } = await connection.getLatestBlockhash();
transaction.recentBlockhash = blockhash;
transaction.feePayer = new PublicKey(wallet.address);
// Sign and send using stored wallet ID
const signed = await sdk.signAndSendTransaction({
walletId: wallet.walletId,
transaction,
networkId: NetworkId.SOLANA_MAINNET
});
// Extract the transaction signature from the signed transaction
const signedTransaction = Transaction.from(
Buffer.from(signed.rawTransaction, 'base64url')
);
// Get the signature (transaction hash)
const signature = signedTransaction.signature
? bs58.encode(signedTransaction.signature)
: signedTransaction.signatures[0].signature
? bs58.encode(signedTransaction.signatures[0].signature)
: null;
if (!signature) {
return res.status(500).json({ error: 'Failed to extract transaction signature' });
}
// Wait for confirmation
const confirmation = await connection.confirmTransaction(signature);
res.json({
signature: signature,
txHash: signature,
amount,
recipient: recipientAddress,
confirmed: !confirmation.value.err
});
} catch (error) {
console.error('Transaction failed:', error);
res.status(500).json({ error: 'Failed to send transaction' });
}
});
const PORT = process.env.PORT || 3000;
app.listen(PORT, () => {
console.log(`Server running on port ${PORT}`);
});