Skip to content
Closed
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
30 changes: 30 additions & 0 deletions veilend-backend/README.md
Original file line number Diff line number Diff line change
Expand Up @@ -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<T>` (e.g., `{ success: true, data: { ... } }`)

### Pagination

For list-based endpoints, the following conventions apply:

- **Request**: `PageOptionsDto` defines query options (`page`, `take`, `order`).
- **Response**: `PageDto<T>` wraps an array of data alongside pagination metadata (`PageMetaDto`).

Expand All @@ -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
Expand Down
1 change: 1 addition & 0 deletions veilend-backend/package.json
Original file line number Diff line number Diff line change
Expand Up @@ -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",
Expand Down
160 changes: 160 additions & 0 deletions veilend-backend/scripts/seed-local-stellar-data.mjs
Original file line number Diff line number Diff line change
@@ -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.`,
);