diff --git a/spl-tokens/app/app/package.json b/spl-tokens/app/app/package.json index 1cfaf08a..045d4342 100644 --- a/spl-tokens/app/app/package.json +++ b/spl-tokens/app/app/package.json @@ -6,7 +6,7 @@ "dependencies": { "@coral-xyz/anchor": "^0.32.1", "@craco/craco": "^7.1.0", - "@magicblock-labs/ephemeral-rollups-sdk": "^0.10.2", + "@magicblock-labs/ephemeral-rollups-sdk": "^0.10.3", "@solana/wallet-adapter-base": "^0.9.23", "@solana/wallet-adapter-react": "^0.15.35", "@solana/wallet-adapter-react-ui": "^0.9.35", diff --git a/spl-tokens/app/app/src/App.tsx b/spl-tokens/app/app/src/App.tsx index 75717e8f..ac453166 100644 --- a/spl-tokens/app/app/src/App.tsx +++ b/spl-tokens/app/app/src/App.tsx @@ -14,6 +14,7 @@ import { import { DELEGATION_PROGRAM_ID, deriveEphemeralAta, + deriveLamportsPda, deriveRentPda, deriveShuttleEphemeralAta, deriveShuttleWalletAta, @@ -22,6 +23,7 @@ import { ensureTransferQueueCrankIx, initRentPdaIx, initTransferQueueIx, + lamportsDelegatedTransferIx, magicFeeVaultPdaFromValidator, transferSpl, withdrawSpl, @@ -473,6 +475,8 @@ const App: React.FC = () => { const [privateMinDelayMs, setPrivateMinDelayMs] = useState('0'); const [privateMaxDelayMs, setPrivateMaxDelayMs] = useState('0'); const [privateSplitCount, setPrivateSplitCount] = useState('1'); + const [lamportsTransferDestination, setLamportsTransferDestination] = useState(''); + const [lamportsTransferAmount, setLamportsTransferAmount] = useState('1000000'); // Cached Blockhash const cachedEphemeralBlockhashRef = useRef(null) @@ -1375,6 +1379,83 @@ const App: React.FC = () => { } }, [accounts, loadSetupQueueKeypair, queueMintAddress]); + const handleLamportsTransfer = useCallback(async () => { + setTransactionError(null); + setTransactionSuccess(null); + + const destinationText = lamportsTransferDestination.trim(); + if (!destinationText) { + setTransactionError('Lamports destination public key is required.'); + return; + } + + const amountText = lamportsTransferAmount.trim(); + if (!/^\d+$/.test(amountText)) { + setTransactionError('Lamports amount must be a whole number.'); + return; + } + + try { + setIsSubmitting(true); + + const configuredQueuePayer = await loadSetupQueueKeypair(); + const payer = configuredQueuePayer ?? accounts[0]?.keypair; + if (!payer) { + setTransactionError('Lamports payer not available.'); + return; + } + + const destination = new PublicKey(destinationText); + const amount = BigInt(amountText); + if (amount <= 0n) { + setTransactionError('Lamports amount must be greater than 0.'); + return; + } + + const payerBalance = BigInt(await connection.getBalance(payer.publicKey, 'confirmed')); + if (payerBalance <= amount) { + setTransactionError('Lamports payer balance is too low for this transfer.'); + return; + } + + const salt = crypto.getRandomValues(new Uint8Array(32)); + const [lamportsPda] = deriveLamportsPda(payer.publicKey, destination, salt); + console.log("Lamports PDA:", lamportsPda.toBase58()); + + const tx = new Transaction().add( + lamportsDelegatedTransferIx( + payer.publicKey, + destination, + amount, + salt, + ), + ); + tx.feePayer = payer.publicKey; + + const sig = await sendAndConfirmTransaction( + connection, + tx, + [payer], + { + commitment: 'confirmed', + preflightCommitment: 'confirmed', + skipPreflight: true, + }, + ); + + setTransactionSuccess( + `Lamports transfer submitted for ${destination.toBase58()} via ${lamportsPda.toBase58()}: ${sig.substring(0, 10)}...${sig.substring(sig.length - 10)}`, + ); + await refreshBalances(); + } catch (e: any) { + setTransactionError(await formatTransactionError(e, connection)); + } finally { + setIsSubmitting(false); + } + }, [accounts, connection, lamportsTransferAmount, lamportsTransferDestination, loadSetupQueueKeypair, refreshBalances]); + + const lamportsTransferSender = setupQueueKeypairPublicKey ?? accounts[0]?.keypair.publicKey ?? null; + return ( <>