Skip to content

Commit 15e149e

Browse files
committed
fix: harden transaction signing with string-based USDC conversion and sell confirmation
- Replace float-based USDC atomic conversion with string-based usdcToAtomic() - Add confirmTransaction after sendRawTransaction in sell flow - Retain lastValidBlockHeight for proper transaction confirmation - Cache devnet Connection as module-level singleton
1 parent 8d8bd4e commit 15e149e

File tree

2 files changed

+29
-9
lines changed

2 files changed

+29
-9
lines changed

frontend/src/components/Widget.tsx

Lines changed: 6 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -350,14 +350,18 @@ function DepositView({
350350
if (!signTransaction) {
351351
throw new Error('Wallet does not support transaction signing. Use manual deposit.');
352352
}
353-
const { transaction, devnetConnection } = await buildSellTransaction(
353+
const { transaction, devnetConnection, lastValidBlockHeight } = await buildSellTransaction(
354354
publicKey,
355355
order.deposit_address,
356356
(order as SellResponse).amount_sol,
357357
order.memo,
358358
);
359359
const signed = await signTransaction(transaction);
360-
await devnetConnection.sendRawTransaction(signed.serialize());
360+
const sig = await devnetConnection.sendRawTransaction(signed.serialize());
361+
await devnetConnection.confirmTransaction(
362+
{ signature: sig, blockhash: transaction.recentBlockhash!, lastValidBlockHeight },
363+
'confirmed',
364+
);
361365
}
362366
setSent(true);
363367
} catch (err) {

frontend/src/lib/transactions.ts

Lines changed: 23 additions & 7 deletions
Original file line numberDiff line numberDiff line change
@@ -15,6 +15,13 @@ const USDC_MINT = new PublicKey('EPjFWdd5AufqSSqeM2qN1xzybapC8G4wEGGkZwyTDt1v');
1515
const USDC_DECIMALS = 6;
1616
const MEMO_PROGRAM_ID = new PublicKey('MemoSq4gqABAXKb96qnH8TysNcWxMyWCqXgDLGmfcHr');
1717

18+
function usdcToAtomic(amount: number): bigint {
19+
const str = amount.toFixed(USDC_DECIMALS);
20+
const [whole, frac = ''] = str.split('.');
21+
const padded = frac.padEnd(USDC_DECIMALS, '0').slice(0, USDC_DECIMALS);
22+
return BigInt(whole + padded);
23+
}
24+
1825
function memoInstruction(memo: string, signer: PublicKey): TransactionInstruction {
1926
return new TransactionInstruction({
2027
keys: [{ pubkey: signer, isSigner: true, isWritable: false }],
@@ -37,7 +44,7 @@ export async function buildBuyTransaction(
3744
const recipientPubkey = new PublicKey(recipient);
3845
const senderAta = getAssociatedTokenAddressSync(USDC_MINT, sender);
3946
const recipientAta = getAssociatedTokenAddressSync(USDC_MINT, recipientPubkey);
40-
const atomicAmount = BigInt(Math.round(usdcAmount * 10 ** USDC_DECIMALS));
47+
const atomicAmount = usdcToAtomic(usdcAmount);
4148

4249
const tx = new Transaction();
4350
tx.add(
@@ -59,19 +66,27 @@ export async function buildBuyTransaction(
5966

6067
const DEVNET_RPC = import.meta.env.VITE_DEVNET_RPC || 'https://api.devnet.solana.com';
6168

69+
let devnetConnection: Connection | null = null;
70+
function getDevnetConnection(): Connection {
71+
if (!devnetConnection) {
72+
devnetConnection = new Connection(DEVNET_RPC, 'confirmed');
73+
}
74+
return devnetConnection;
75+
}
76+
6277
/**
6378
* Build a devnet SOL transfer transaction for the sell flow.
6479
* User sends devnet SOL to the treasury with a memo for order matching.
65-
* Returns both the transaction and devnet connection since the wallet adapter
66-
* is mainnet-only — we sign with the wallet then submit to devnet ourselves.
80+
* Returns the transaction, devnet connection, and lastValidBlockHeight for confirmation.
81+
* The wallet adapter is mainnet-only — we sign with the wallet then submit to devnet ourselves.
6782
*/
6883
export async function buildSellTransaction(
6984
sender: PublicKey,
7085
recipient: string,
7186
solAmount: number,
7287
memo: string,
73-
): Promise<{ transaction: Transaction; devnetConnection: Connection }> {
74-
const devnetConnection = new Connection(DEVNET_RPC, 'confirmed');
88+
): Promise<{ transaction: Transaction; devnetConnection: Connection; lastValidBlockHeight: number }> {
89+
const conn = getDevnetConnection();
7590
const recipientPubkey = new PublicKey(recipient);
7691
const lamports = Math.round(solAmount * 1e9);
7792

@@ -80,6 +95,7 @@ export async function buildSellTransaction(
8095
tx.add(memoInstruction(memo, sender));
8196

8297
tx.feePayer = sender;
83-
tx.recentBlockhash = (await devnetConnection.getLatestBlockhash()).blockhash;
84-
return { transaction: tx, devnetConnection };
98+
const { blockhash, lastValidBlockHeight } = await conn.getLatestBlockhash();
99+
tx.recentBlockhash = blockhash;
100+
return { transaction: tx, devnetConnection: conn, lastValidBlockHeight };
85101
}

0 commit comments

Comments
 (0)