From e31b81ad3d117800d5460e8ed783cb330c76c773 Mon Sep 17 00:00:00 2001 From: qingfeng312 <86755530+qingfeng312@users.noreply.github.com> Date: Tue, 23 Jun 2026 02:37:40 +0800 Subject: [PATCH] Add local Stellar demo seed command --- veilend-backend/README.md | 30 ++++ veilend-backend/package.json | 1 + .../scripts/seed-local-stellar-data.mjs | 160 ++++++++++++++++++ 3 files changed, 191 insertions(+) create mode 100644 veilend-backend/scripts/seed-local-stellar-data.mjs diff --git a/veilend-backend/README.md b/veilend-backend/README.md index 9c40e8b..ca91d9b 100644 --- a/veilend-backend/README.md +++ b/veilend-backend/README.md @@ -37,19 +37,25 @@ The application architecture is organized into distinct domain modules to clearl To maintain a consistent API structure, we enforce strict Data Transfer Object (DTO) validation and response formatting. ### Directory Structure + Shared contracts and common code reside in `src/common`. ### DTO Validation + - All controllers use NestJS `ValidationPipe`. - DTOs strictly define boundaries using `class-validator` decorators (e.g., `@IsString()`, `@IsNumber()`). - Data transformation uses `class-transformer` decorators (e.g., `@Type()`). ### Standardized Responses + We utilize standard API wrapper formats to ensure predictable frontend consumption. + - **Success/Error Wrapper**: `ApiResponseDto` (e.g., `{ success: true, data: { ... } }`) ### Pagination + For list-based endpoints, the following conventions apply: + - **Request**: `PageOptionsDto` defines query options (`page`, `take`, `order`). - **Response**: `PageDto` wraps an array of data alongside pagination metadata (`PageMetaDto`). @@ -72,6 +78,30 @@ $ npm run start:dev $ npm run start:prod ``` +## Seed local Stellar demo data + +The local indexer read models are stored in `veilend-db.json`. To create a +deterministic dataset for dashboard and history endpoint development, run: + +```bash +$ npm run seed:local +``` + +The command writes representative Stellar lending activity to `veilend-db.json`: +supported assets, user positions, and deposit/borrow/repay/withdraw history. +After starting the API, the seeded records are available through: + +```bash +$ curl http://localhost:3000/indexer/positions/GDEMOLOCALSTELLARLENDER00000000000000000000000000000001 +$ curl http://localhost:3000/indexer/transactions/GDEMOLOCALSTELLARBORROWER000000000000000000000000000001 +``` + +To write the seed file somewhere else, pass an output path: + +```bash +$ npm run seed:local -- --output ./tmp/veilend-db.json +``` + ## Run tests ```bash diff --git a/veilend-backend/package.json b/veilend-backend/package.json index d4111a1..f2689ee 100644 --- a/veilend-backend/package.json +++ b/veilend-backend/package.json @@ -12,6 +12,7 @@ "start:dev": "nest start --watch", "start:debug": "nest start --debug --watch", "start:prod": "node dist/main", + "seed:local": "node scripts/seed-local-stellar-data.mjs", "lint": "eslint \"{src,apps,libs,test}/**/*.ts\" --fix", "test": "jest", "test:watch": "jest --watch", diff --git a/veilend-backend/scripts/seed-local-stellar-data.mjs b/veilend-backend/scripts/seed-local-stellar-data.mjs new file mode 100644 index 0000000..8c27ed6 --- /dev/null +++ b/veilend-backend/scripts/seed-local-stellar-data.mjs @@ -0,0 +1,160 @@ +import { mkdir, writeFile } from 'node:fs/promises'; +import path from 'node:path'; + +const DEFAULT_OUTPUT = path.join(process.cwd(), 'veilend-db.json'); +const FIXED_TIMESTAMP = '2026-01-15T12:00:00.000Z'; + +const args = process.argv.slice(2); + +function readOption(name) { + const prefix = `${name}=`; + const inline = args.find((arg) => arg.startsWith(prefix)); + if (inline) return inline.slice(prefix.length); + + const index = args.indexOf(name); + if (index !== -1) return args[index + 1]; + + return undefined; +} + +const outputPath = path.resolve( + readOption('--output') || process.env.VEILEND_SEED_OUTPUT || DEFAULT_OUTPUT, +); + +const users = { + lender: 'GDEMOLOCALSTELLARLENDER00000000000000000000000000000001', + borrower: 'GDEMOLOCALSTELLARBORROWER000000000000000000000000000001', +}; + +const assets = { + usdc: 'CCW57ZST4NV43YS7JZKMGLG62624NV43YS7JZKMGLG62624USDC', + xlm: 'CCW57ZST4NV43YS7JZKMGLG62624NV43YS7JZKMGLG62624XLM1', +}; + +function transaction( + id, + userAddress, + type, + assetAddress, + amount, + ledger, + txHash, +) { + return { + id, + userAddress, + type, + assetAddress, + amount, + ledger, + txHash, + timestamp: new Date(Date.parse(FIXED_TIMESTAMP) + ledger).toISOString(), + }; +} + +const seed = { + checkpoint: { lastIndexedLedger: 424242 }, + transactions: [ + transaction( + 'demo-424201-0001', + users.lender, + 'deposit', + assets.usdc, + '2500000000', + 424201, + 'demo_tx_424201_deposit_usdc', + ), + transaction( + 'demo-424202-0001', + users.borrower, + 'deposit', + assets.xlm, + '5000000000', + 424202, + 'demo_tx_424202_deposit_xlm', + ), + transaction( + 'demo-424203-0001', + users.borrower, + 'borrow', + assets.usdc, + '750000000', + 424203, + 'demo_tx_424203_borrow_usdc', + ), + transaction( + 'demo-424204-0001', + users.borrower, + 'repay', + assets.usdc, + '125000000', + 424204, + 'demo_tx_424204_repay_usdc', + ), + transaction( + 'demo-424205-0001', + users.lender, + 'withdraw', + assets.usdc, + '250000000', + 424205, + 'demo_tx_424205_withdraw_usdc', + ), + ], + positions: [ + { + userAddress: users.lender, + assetAddress: assets.usdc, + deposited: '2250000000', + borrowed: '0', + updatedAt: FIXED_TIMESTAMP, + }, + { + userAddress: users.borrower, + assetAddress: assets.xlm, + deposited: '5000000000', + borrowed: '0', + updatedAt: FIXED_TIMESTAMP, + }, + { + userAddress: users.borrower, + assetAddress: assets.usdc, + deposited: '0', + borrowed: '625000000', + updatedAt: FIXED_TIMESTAMP, + }, + ], + assets: [ + { + assetAddress: assets.usdc, + supported: true, + updatedAt: FIXED_TIMESTAMP, + }, + { + assetAddress: assets.xlm, + supported: true, + updatedAt: FIXED_TIMESTAMP, + }, + { + assetAddress: 'CCW57ZST4NV43YS7JZKMGLG62624NV43YS7JZKMGLG62624DISA', + supported: false, + updatedAt: FIXED_TIMESTAMP, + }, + ], +}; + +const seenTransactionIds = new Set(); +for (const tx of seed.transactions) { + if (seenTransactionIds.has(tx.id)) { + throw new Error(`Duplicate seed transaction id: ${tx.id}`); + } + seenTransactionIds.add(tx.id); +} + +await mkdir(path.dirname(outputPath), { recursive: true }); +await writeFile(outputPath, `${JSON.stringify(seed, null, 2)}\n`, 'utf8'); + +console.log(`Seeded VeilLend local indexer data at ${outputPath}`); +console.log( + `Created ${seed.assets.length} assets, ${seed.positions.length} positions, and ${seed.transactions.length} transactions.`, +);