diff --git a/api/README.md b/api/README.md index 3a39d970..e3f5f7ed 100644 --- a/api/README.md +++ b/api/README.md @@ -4,7 +4,8 @@ REST API for StellarLend core lending operations (deposit, borrow, repay, withdr ## Features -- REST endpoints for deposit, borrow, repay, withdraw operations +- Unsigned transaction preparation for deposit, borrow, repay, withdraw operations +- Signed transaction submission endpoint - Request validation and error handling - Transaction submission and monitoring - Rate limiting and security middleware @@ -38,48 +39,46 @@ JWT_SECRET= ### Health Check `GET /api/health` - Check service status -### Deposit Collateral -`POST /api/lending/deposit` +### Prepare Transaction +`GET /api/lending/prepare/:operation` ```json { "userAddress": "G...", - "amount": "10000000", - "userSecret": "S..." + "amount": "10000000" } ``` -### Borrow Assets -`POST /api/lending/borrow` +Response: ```json { - "userAddress": "G...", - "amount": "5000000", - "userSecret": "S..." + "unsignedXdr": "AAAA...", + "operation": "deposit", + "expiresAt": "2026-03-28T12:34:56.000Z" } ``` -### Repay Debt -`POST /api/lending/repay` +### Submit Signed Transaction +`POST /api/lending/submit` ```json { - "userAddress": "G...", - "amount": "5500000", - "userSecret": "S..." + "signedXdr": "AAAA..." } ``` -### Withdraw Collateral -`POST /api/lending/withdraw` +Response: ```json { - "userAddress": "G...", - "amount": "2000000", - "userSecret": "S..." + "success": true, + "transactionHash": "abc123...", + "status": "success", + "ledger": 12345 } ``` All amounts in stroops (1 XLM = 10,000,000 stroops) +Clients must sign the returned XDR locally. The API does not accept Stellar secret keys. + ## Testing ```bash diff --git a/api/examples/usage.ts b/api/examples/usage.ts index 4436896b..36d3b16e 100644 --- a/api/examples/usage.ts +++ b/api/examples/usage.ts @@ -1,6 +1,6 @@ /** * StellarLend API Usage Examples - * + * * This file demonstrates how to interact with the StellarLend API * for common lending operations. */ @@ -9,6 +9,14 @@ import axios, { AxiosError } from 'axios'; const API_BASE_URL = process.env.API_BASE_URL || 'http://localhost:3000/api'; +type LendingOperation = 'deposit' | 'borrow' | 'repay' | 'withdraw'; + +interface PrepareResponse { + unsignedXdr: string; + operation: LendingOperation; + expiresAt: string; +} + interface TransactionResponse { success: boolean; transactionHash?: string; @@ -37,149 +45,67 @@ async function checkHealth(): Promise { } /** - * Deposit collateral into the lending protocol + * Request an unsigned XDR from the API */ -async function depositCollateral( +async function prepareTransaction( + operation: LendingOperation, userAddress: string, amount: string, - userSecret: string, assetAddress?: string -): Promise { +): Promise { try { - console.log(`\nšŸ“„ Depositing ${amount} stroops...`); - - const response = await axios.post( - `${API_BASE_URL}/lending/deposit`, - { - userAddress, - assetAddress, - amount, - userSecret, - } - ); - - if (response.data.success) { - console.log('āœ… Deposit successful!'); - console.log(` Transaction Hash: ${response.data.transactionHash}`); - console.log(` Ledger: ${response.data.ledger}`); - } else { - console.log('āŒ Deposit failed:', response.data.error); - } - - return response.data; - } catch (error) { - handleError('Deposit', error); - throw error; - } -} + console.log(`\nšŸ“ Preparing ${operation} transaction for ${amount} stroops...`); -/** - * Borrow assets against deposited collateral - */ -async function borrowAssets( - userAddress: string, - amount: string, - userSecret: string, - assetAddress?: string -): Promise { - try { - console.log(`\nšŸ’° Borrowing ${amount} stroops...`); - - const response = await axios.post( - `${API_BASE_URL}/lending/borrow`, + const response = await axios.get( + `${API_BASE_URL}/lending/prepare/${operation}`, { - userAddress, - assetAddress, - amount, - userSecret, + params: { + userAddress, + assetAddress, + amount, + }, } ); - if (response.data.success) { - console.log('āœ… Borrow successful!'); - console.log(` Transaction Hash: ${response.data.transactionHash}`); - console.log(` Ledger: ${response.data.ledger}`); - } else { - console.log('āŒ Borrow failed:', response.data.error); - } + console.log('āœ… Unsigned transaction prepared'); + console.log(` Operation: ${response.data.operation}`); + console.log(` Expires At: ${response.data.expiresAt}`); + console.log(` XDR Preview: ${response.data.unsignedXdr.slice(0, 20)}...`); return response.data; } catch (error) { - handleError('Borrow', error); + handleError(`Prepare ${operation}`, error); throw error; } } /** - * Repay borrowed assets with interest + * Submit a client-signed XDR back to the API */ -async function repayDebt( - userAddress: string, - amount: string, - userSecret: string, - assetAddress?: string +async function submitSignedTransaction( + signedXdr: string ): Promise { try { - console.log(`\nšŸ’³ Repaying ${amount} stroops...`); - - const response = await axios.post( - `${API_BASE_URL}/lending/repay`, - { - userAddress, - assetAddress, - amount, - userSecret, - } - ); + console.log('\nšŸš€ Submitting signed transaction...'); - if (response.data.success) { - console.log('āœ… Repayment successful!'); - console.log(` Transaction Hash: ${response.data.transactionHash}`); - console.log(` Ledger: ${response.data.ledger}`); - } else { - console.log('āŒ Repayment failed:', response.data.error); - } - - return response.data; - } catch (error) { - handleError('Repay', error); - throw error; - } -} - -/** - * Withdraw collateral from the protocol - */ -async function withdrawCollateral( - userAddress: string, - amount: string, - userSecret: string, - assetAddress?: string -): Promise { - try { - console.log(`\nšŸ“¤ Withdrawing ${amount} stroops...`); - const response = await axios.post( - `${API_BASE_URL}/lending/withdraw`, + `${API_BASE_URL}/lending/submit`, { - userAddress, - assetAddress, - amount, - userSecret, + signedXdr, } ); if (response.data.success) { - console.log('āœ… Withdrawal successful!'); + console.log('āœ… Transaction successful!'); console.log(` Transaction Hash: ${response.data.transactionHash}`); console.log(` Ledger: ${response.data.ledger}`); } else { - console.log('āŒ Withdrawal failed:', response.data.error); + console.log('āŒ Transaction failed:', response.data.error); } return response.data; } catch (error) { - handleError('Withdraw', error); + handleError('Submit', error); throw error; } } @@ -203,6 +129,16 @@ function handleError(operation: string, error: unknown): void { } } +/** + * Placeholder signing hook for local wallet integration. + * Replace this with Freighter, WalletKit, or another signer in real usage. + */ +async function signTransactionLocally(unsignedXdr: string): Promise { + console.log('\nšŸ” Sign the XDR locally in your wallet before submitting it'); + console.log(` Unsigned XDR: ${unsignedXdr}`); + throw new Error('Implement local signing before using this example'); +} + /** * Complete lending lifecycle example */ @@ -211,34 +147,27 @@ async function completeLendingCycle(): Promise { console.log('StellarLend API - Complete Lending Cycle Example'); console.log('='.repeat(60)); - // Replace with your actual testnet credentials + // Replace with your actual testnet public address const USER_ADDRESS = 'GXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXX'; - const USER_SECRET = 'SXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXX'; - + const OPERATIONS: Array<{ operation: LendingOperation; amount: string }> = [ + { operation: 'deposit', amount: '100000000' }, + { operation: 'borrow', amount: '50000000' }, + { operation: 'repay', amount: '55000000' }, + { operation: 'withdraw', amount: '50000000' }, + ]; + try { // 1. Check health await checkHealth(); - // 2. Deposit collateral (10 XLM) - await depositCollateral(USER_ADDRESS, '100000000', USER_SECRET); - - // Wait a bit for transaction to settle - await new Promise(resolve => setTimeout(resolve, 5000)); - - // 3. Borrow assets (5 XLM) - await borrowAssets(USER_ADDRESS, '50000000', USER_SECRET); + for (const { operation, amount } of OPERATIONS) { + const prepared = await prepareTransaction(operation, USER_ADDRESS, amount); + const signedXdr = await signTransactionLocally(prepared.unsignedXdr); + await submitSignedTransaction(signedXdr); - // Wait a bit for transaction to settle - await new Promise(resolve => setTimeout(resolve, 5000)); - - // 4. Repay debt (5.5 XLM with interest) - await repayDebt(USER_ADDRESS, '55000000', USER_SECRET); - - // Wait a bit for transaction to settle - await new Promise(resolve => setTimeout(resolve, 5000)); - - // 5. Withdraw collateral (5 XLM) - await withdrawCollateral(USER_ADDRESS, '50000000', USER_SECRET); + // Wait a bit for transaction to settle before the next step + await new Promise((resolve) => setTimeout(resolve, 5000)); + } console.log('\n' + '='.repeat(60)); console.log('āœ… Complete lending cycle finished successfully!'); @@ -259,12 +188,11 @@ async function errorHandlingExamples(): Promise { console.log('='.repeat(60)); const USER_ADDRESS = 'GXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXX'; - const USER_SECRET = 'SXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXX'; // Example 1: Invalid amount (zero) try { console.log('\n1. Testing zero amount (should fail)...'); - await depositCollateral(USER_ADDRESS, '0', USER_SECRET); + await prepareTransaction('deposit', USER_ADDRESS, '0'); } catch (error) { console.log(' Expected error caught āœ“'); } @@ -272,18 +200,16 @@ async function errorHandlingExamples(): Promise { // Example 2: Invalid address try { console.log('\n2. Testing invalid address (should fail)...'); - await depositCollateral('invalid_address', '1000000', USER_SECRET); + await prepareTransaction('deposit', 'invalid_address', '1000000'); } catch (error) { console.log(' Expected error caught āœ“'); } - // Example 3: Missing required field + // Example 3: Missing signed XDR try { - console.log('\n3. Testing missing secret (should fail)...'); - await axios.post(`${API_BASE_URL}/lending/deposit`, { - userAddress: USER_ADDRESS, - amount: '1000000', - // userSecret missing + console.log('\n3. Testing missing signed XDR (should fail)...'); + await axios.post(`${API_BASE_URL}/lending/submit`, { + // signedXdr missing }); } catch (error) { console.log(' Expected error caught āœ“'); @@ -312,10 +238,9 @@ if (require.main === module) { export { checkHealth, - depositCollateral, - borrowAssets, - repayDebt, - withdrawCollateral, + prepareTransaction, + submitSignedTransaction, + signTransactionLocally, completeLendingCycle, errorHandlingExamples, };