Skip to content

Commit 0dfb385

Browse files
authored
Merge pull request #50 from pandarudra/preview
Add phantom public key to env and update transaction verification
2 parents 0893ce9 + 79a0c18 commit 0dfb385

3 files changed

Lines changed: 117 additions & 72 deletions

File tree

be/src/routes/payment.routes.ts

Lines changed: 101 additions & 65 deletions
Original file line numberDiff line numberDiff line change
@@ -1,5 +1,10 @@
11
import { Router, Response } from "express";
2-
import { Connection, PublicKey } from "@solana/web3.js";
2+
import {
3+
Connection,
4+
PublicKey,
5+
ParsedInstruction,
6+
PartiallyDecodedInstruction,
7+
} from "@solana/web3.js";
38
import { AuthenticatedRequest } from "../middleware";
49
import { treasury_wallet } from "../constants";
510
import { TierService } from "../services/tier.service";
@@ -9,7 +14,7 @@ export const paymentRouter = Router();
914
const connection = new Connection("https://api.devnet.solana.com", "confirmed");
1015

1116
// Expected SOL amount for premium upgrade (e.g. 0.1 SOL)
12-
const PREMIUM_UPGRADE_LAMPORTS = 0.1 * 1_000_000_000;
17+
const PREMIUM_UPGRADE_LAMPORTS = 100_000_000;
1318

1419
/**
1520
* @swagger
@@ -40,69 +45,100 @@ const PREMIUM_UPGRADE_LAMPORTS = 0.1 * 1_000_000_000;
4045
* 500:
4146
* description: Internal verification error
4247
*/
43-
paymentRouter.post("/verify-upgrade", async (req: AuthenticatedRequest, res: Response): Promise<any> => {
44-
try {
45-
const walletAddress = req.walletAddress;
46-
const { signature } = req.body;
47-
48-
if (!walletAddress) {
49-
return res.status(401).json({ error: "Unauthorized" });
50-
}
51-
52-
if (!signature) {
53-
return res.status(400).json({ error: "Transaction signature is required" });
54-
}
55-
56-
Logger.info(`Verifying transaction ${signature} for wallet ${walletAddress}`);
57-
58-
// Wait slightly to ensure RPC node has the transaction
59-
await new Promise(resolve => setTimeout(resolve, 2000));
60-
61-
// Fetch transaction details
62-
const tx = await connection.getTransaction(signature, {
63-
maxSupportedTransactionVersion: 0,
64-
commitment: "confirmed",
65-
});
66-
67-
if (!tx || !tx.meta) {
68-
return res.status(400).json({ error: "Transaction not found or not confirmed" });
69-
}
70-
71-
if (tx.meta.err) {
72-
return res.status(400).json({ error: "Transaction failed on-chain" });
73-
}
74-
75-
// Verify it's a transfer to our treasury wallet
76-
// Look at postBalances - preBalances to see net change for treasury
77-
const accountKeys = tx.transaction.message.getAccountKeys();
78-
79-
// Find index of treasury wallet
80-
const treasuryIndex = accountKeys.staticAccountKeys.findIndex(
81-
(key) => key.toBase58() === treasury_wallet
82-
);
83-
84-
if (treasuryIndex === -1) {
85-
return res.status(400).json({ error: "Treasury wallet not involved in transaction" });
86-
}
87-
88-
const preBalance = tx.meta.preBalances[treasuryIndex];
89-
const postBalance = tx.meta.postBalances[treasuryIndex];
90-
const amountReceived = postBalance - preBalance;
48+
paymentRouter.post(
49+
"/verify-upgrade",
50+
async (req: AuthenticatedRequest, res: Response): Promise<any> => {
51+
try {
52+
const walletAddress = req.walletAddress;
53+
const { signature } = req.body;
54+
55+
if (!walletAddress) {
56+
return res.status(401).json({ error: "Unauthorized" });
57+
}
58+
59+
Logger.info("Wallet address:", walletAddress);
60+
61+
if (!signature) {
62+
return res
63+
.status(400)
64+
.json({ error: "Transaction signature is required" });
65+
}
66+
67+
Logger.info(
68+
`Verifying transaction ${signature} for wallet ${walletAddress}`,
69+
);
70+
71+
// Wait slightly to ensure RPC node has the transaction
72+
await new Promise((resolve) => setTimeout(resolve, 2000));
73+
74+
// Verify it's a transfer to our treasury wallet
75+
// Look at postBalances - preBalances to see net change for treasury
76+
const parsedTx = await connection.getParsedTransaction(signature, {
77+
maxSupportedTransactionVersion: 0,
78+
commitment: "confirmed",
79+
});
9180

92-
if (amountReceived < PREMIUM_UPGRADE_LAMPORTS) {
93-
return res.status(400).json({
94-
error: "Insufficient payment",
95-
expected: PREMIUM_UPGRADE_LAMPORTS,
96-
received: amountReceived
81+
if (!parsedTx || !parsedTx.meta) {
82+
return res.status(400).json({
83+
error: "Transaction not found or not confirmed",
84+
});
85+
}
86+
87+
if (parsedTx.meta.err) {
88+
return res.status(400).json({
89+
error: "Transaction failed on-chain",
90+
});
91+
}
92+
93+
const instructions = parsedTx.transaction.message.instructions;
94+
95+
Logger.info("Instructions:", JSON.stringify(instructions, null, 2));
96+
97+
const transferInstruction = instructions.find(
98+
(ix): ix is ParsedInstruction =>
99+
"parsed" in ix &&
100+
ix.program === "system" &&
101+
ix.parsed?.type === "transfer" &&
102+
ix.parsed?.info?.destination === treasury_wallet,
103+
);
104+
105+
if (!transferInstruction) {
106+
return res.status(400).json({
107+
error: "No valid transfer to treasury wallet found",
108+
});
109+
}
110+
111+
const sender = transferInstruction.parsed.info.source;
112+
113+
if (sender !== walletAddress) {
114+
return res.status(400).json({
115+
error: "Transaction sender mismatch",
116+
});
117+
}
118+
119+
const lamports = Number(transferInstruction.parsed.info.lamports);
120+
121+
Logger.info("Transfer sender:", sender);
122+
Logger.info("Transfer amount:", lamports);
123+
124+
if (lamports < PREMIUM_UPGRADE_LAMPORTS) {
125+
return res.status(400).json({
126+
error: "Insufficient payment",
127+
expected: PREMIUM_UPGRADE_LAMPORTS,
128+
received: lamports,
129+
});
130+
}
131+
132+
// Upgrade the user
133+
await TierService.upgradeUserToPremium(walletAddress);
134+
135+
return res.json({
136+
success: true,
137+
message: "Upgraded to Premium successfully",
97138
});
139+
} catch (error) {
140+
Logger.error("Failed to verify transaction:", error);
141+
return res.status(500).json({ error: "Internal verification error" });
98142
}
99-
100-
// Upgrade the user
101-
await TierService.upgradeUserToPremium(walletAddress);
102-
103-
return res.json({ success: true, message: "Upgraded to Premium successfully" });
104-
} catch (error) {
105-
Logger.error("Failed to verify transaction:", error);
106-
return res.status(500).json({ error: "Internal verification error" });
107-
}
108-
});
143+
},
144+
);

fe/src/components/custom/modals/PremiumUpgradeModal.tsx

Lines changed: 15 additions & 7 deletions
Original file line numberDiff line numberDiff line change
@@ -21,7 +21,7 @@ import {
2121
} from "@solana/web3.js";
2222
import { useConnection } from "@solana/wallet-adapter-react";
2323
import { getToken } from "@/services/auth.service";
24-
import { be_url } from "@/env/e";
24+
import { be_url, phantom_pub_key } from "@/env/e";
2525

2626
interface PremiumUpgradeModalProps {
2727
isOpen: boolean;
@@ -121,9 +121,7 @@ export const PremiumUpgradeModal = ({
121121
toast.loading("Initiating transaction...", { id: "upgrade" });
122122

123123
// Devnet treasury address
124-
const treasuryPubkey = new PublicKey(
125-
"6U2V5fGjJ2XJz5V4hVwGqU5nF5V5x5Y2tG6P1f6N4t9k",
126-
);
124+
const treasuryPubkey = new PublicKey(phantom_pub_key);
127125
const transaction = new Transaction().add(
128126
SystemProgram.transfer({
129127
fromPubkey: wallet.publicKey,
@@ -135,17 +133,24 @@ export const PremiumUpgradeModal = ({
135133
toast.loading("Please approve the transaction in your wallet...", {
136134
id: "upgrade",
137135
});
136+
137+
const { blockhash, lastValidBlockHeight } =
138+
await connection.getLatestBlockhash();
139+
140+
transaction.recentBlockhash = blockhash;
141+
transaction.feePayer = wallet.publicKey;
142+
138143
const signature = await wallet.sendTransaction(transaction, connection);
139144

140145
toast.loading(
141146
`Transaction sent! Waiting for confirmation... (${signature.slice(0, 8)}...)`,
142147
{ id: "upgrade" },
143148
);
144149

145-
const latestBlockhash = await connection.getLatestBlockhash();
146150
await connection.confirmTransaction({
147151
signature,
148-
...latestBlockhash,
152+
blockhash,
153+
lastValidBlockHeight,
149154
});
150155

151156
toast.loading("Transaction confirmed! Upgrading account...", {
@@ -164,7 +169,10 @@ export const PremiumUpgradeModal = ({
164169
});
165170

166171
if (!verifyRes.ok) {
167-
throw new Error("Backend verification failed");
172+
const errData = await verifyRes.json();
173+
console.error("Backend verify error:", errData);
174+
175+
throw new Error(errData.error || JSON.stringify(errData));
168176
}
169177

170178
toast.success("Successfully upgraded to Premium! Enjoy!", {

fe/src/env/e.ts

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -2,3 +2,4 @@ export const be_url =
22
import.meta.env.VITE_ENV === "prod"
33
? (import.meta.env.VITE_BE_URL as string)
44
: "http://localhost:3000";
5+
export const phantom_pub_key = import.meta.env.VITE_PHANTOM_PUB_KEY as string;

0 commit comments

Comments
 (0)