diff --git a/lamassu-next/.claude/skills/docs.md b/lamassu-next/.claude/skills/docs.md new file mode 100644 index 0000000..b0d4eb8 --- /dev/null +++ b/lamassu-next/.claude/skills/docs.md @@ -0,0 +1,218 @@ +# /docs - Documentation Agent + +## Purpose +Keep documentation synchronized with code, generate API docs, and maintain architecture diagrams. + +## Invocation +``` +/docs [command] [target] +``` + +Commands: +- `sync` - Sync docs with code changes +- `api` - Generate API documentation +- `diagram` - Update architecture diagrams +- `readme` - Update package README +- `changelog` - Generate changelog entry + +## Documentation Structure + +``` +docs/ +├── architecture/ +│ ├── overview.md # High-level architecture +│ ├── nostr-protocol.md # Nostr integration details +│ ├── lightning-flow.md # Payment flows +│ └── hardware-hal.md # HAL documentation +├── api/ +│ ├── nostr-client.md # @lamassu/nostr-client API +│ ├── clink.md # @lamassu/clink API +│ ├── state-machine.md # @lamassu/state-machine API +│ └── hal.md # @lamassu/hal API +├── guides/ +│ ├── development.md # Dev setup guide +│ ├── hardware-testing.md # Testing with real hardware +│ └── deployment.md # NixOS deployment guide +└── adrs/ # Architecture Decision Records + ├── 001-nostr-backbone.md + ├── 002-clink-over-bolt12.md + └── ... +``` + +## Sync Tasks + +### Code → Docs Sync +When code changes: +1. Detect modified files +2. Check if related docs exist +3. Flag outdated documentation +4. Suggest updates + +### Doc → Code Validation +Ensure docs reference actual code: +- [ ] Function names match +- [ ] Parameter types correct +- [ ] Return types accurate +- [ ] Examples compile/run + +## API Documentation + +### TypeScript Packages +Generate from TSDoc comments: + +```typescript +/** + * Create a CLINK offer for receiving payments + * + * @param identity - Machine identity (nsec/npub) + * @param relays - Relay URLs to include in offer + * @param priceType - 'fixed' | 'variable' | 'spontaneous' + * @param amount - Amount in sats (required for fixed) + * @returns Encoded noffer string + * + * @example + * ```typescript + * const offer = createOffer(identity, ['wss://relay.example'], 'fixed', 10000) + * // Returns: noffer1... + * ``` + */ +export function createOffer(...): string +``` + +### Rust Crate (HAL) +Generate from rustdoc comments: + +```rust +/// Connect to a bill validator +/// +/// # Arguments +/// * `port` - Serial port path (e.g., "/dev/ttyUSB0") +/// +/// # Errors +/// Returns `ValidatorError::ConnectionFailed` if port unavailable +/// +/// # Example +/// ``` +/// let mut validator = Id003Validator::new("/dev/ttyUSB0", "USD"); +/// validator.connect().await?; +/// ``` +pub async fn connect(&mut self) -> Result<(), ValidatorError> +``` + +## Diagram Updates + +### Mermaid Diagrams +Keep architecture diagrams current: + +```mermaid +sequenceDiagram + participant U as User + participant A as ATM + participant R as Relay + participant L as Lightning.Pub + + U->>A: Insert $20 bill + A->>A: Validate bill + A->>R: Publish CLINK offer + U->>R: Send payment request + R->>A: Forward request + A->>L: Generate invoice + L->>A: Return invoice + A->>R: Send invoice to user + U->>L: Pay invoice + L->>A: Payment confirmed + A->>U: Display success +``` + +### Auto-update triggers +- New event kinds added → Update protocol diagram +- State machine changes → Update flow diagram +- New hardware driver → Update HAL diagram + +## Changelog Generation + +### Format (Keep a Changelog) +```markdown +## [Unreleased] + +### Added +- CLINK offer support for cash-in flow +- Puloon dispenser driver + +### Changed +- Upgraded to XState v5 actor model + +### Fixed +- Race condition in bill acceptance + +### Security +- Added NIP-44 encryption for receipts +``` + +### Auto-detection +From git commits since last release: +- `feat:` → Added +- `fix:` → Fixed +- `refactor:` → Changed +- `security:` → Security +- `BREAKING:` → Breaking Changes + +## Output Format + +### Sync Report +```markdown +## Documentation Sync: [target] + +### Outdated Docs +| Doc | Code Change | Status | +|-----|-------------|--------| +| api/clink.md | createOffer params | ⚠️ Outdated | +| guides/development.md | New env var | ⚠️ Missing | + +### Suggested Updates +1. `api/clink.md:45` - Add `timeout` parameter to createOffer +2. `guides/development.md` - Add LIGHTNING_PUB_URL env var + +### Missing Documentation +- `packages/cashu/src/wallet.ts` - No API docs +``` + +### Generated Docs +```markdown +## Generated: [file] + +### API Reference + +#### `createOffer(identity, relays, priceType, amount?)` + +Creates a CLINK offer for receiving Lightning payments. + +**Parameters:** +| Name | Type | Description | +|------|------|-------------| +| identity | MachineIdentity | Machine nsec/npub | +| relays | string[] | Relay URLs | +| priceType | 'fixed' \| 'variable' | Pricing model | +| amount | number? | Amount in sats | + +**Returns:** `string` - Encoded noffer + +**Example:** +```typescript +const offer = createOffer(identity, ['wss://relay'], 'fixed', 10000) +``` +``` + +## Example Usage + +``` +/docs sync packages/clink/ +``` + +``` +/docs api packages/nostr-client/src/ +``` + +``` +/docs changelog --since v0.1.0 +``` diff --git a/lamassu-next/.claude/skills/hal-check.md b/lamassu-next/.claude/skills/hal-check.md new file mode 100644 index 0000000..208a105 --- /dev/null +++ b/lamassu-next/.claude/skills/hal-check.md @@ -0,0 +1,200 @@ +# /hal-check - Hardware Abstraction Layer Agent + +## Purpose +Validate HAL driver implementations against existing lamassu-machine drivers and hardware specifications. + +## Invocation +``` +/hal-check [command] [driver] +``` + +Commands: +- `port` - Validate porting from lamassu-machine +- `protocol` - Check protocol implementation +- `safety` - Rust safety review +- `mock` - Validate mock implementation + +Drivers: +- `id003`, `ccnet`, `cashflow`, `bnr`, `genmega`, `hcm2`, `gsr50` (validators) +- `puloon`, `f56`, `genmega`, `hcm2`, `gsr50` (dispensers) +- `nippon`, `zebra`, `genmega` (printers) + +## Porting Validation + +### Source Reference +Each Rust driver should map 1:1 with lamassu-machine JavaScript: + +| Rust File | JavaScript Source | +|-----------|-------------------| +| `validators/id003.rs` | `lib/id003/*.js` | +| `validators/ccnet.rs` | `lib/ccnet/*.js` | +| `dispensers/puloon.rs` | `lib/puloon/*.js` | +| `dispensers/f56.rs` | `lib/f56/*.js` | + +### Porting Checklist +``` +/hal-check port id003 +``` + +Validates: +- [ ] All protocol commands implemented +- [ ] State machine matches JS FSM +- [ ] CRC/checksum calculation identical +- [ ] Timeout values match +- [ ] Error codes mapped correctly +- [ ] Denomination tables match + +### Protocol Commands + +#### ID003 (JCM) +| Command | Code | JS Reference | +|---------|------|--------------| +| RESET | 0x40 | id003.js:reset() | +| ENABLE | 0x13 | id003fsm.js:enable | +| DISABLE | 0x14 | id003fsm.js:disable | +| STACK | 0x15 | id003fsm.js:stack | +| RETURN | 0x16 | id003fsm.js:return | +| STATUS | 0x10 | id003fsm.js:poll | + +#### Puloon +| Command | Code | JS Reference | +|---------|------|--------------| +| RESET | 0x44 | puloonrs232.js | +| DISPENSE | 0x45 | puloonrs232.js | +| STATUS | 0x46 | puloonrs232.js | + +## Safety Review (Rust-specific) + +### Memory Safety +- [ ] No `unsafe` without justification comment +- [ ] Buffer sizes validated before read/write +- [ ] No panic paths in production code +- [ ] Proper error propagation with Result + +### Concurrency Safety +- [ ] Serial port access properly synchronized +- [ ] Event channels bounded +- [ ] No deadlock potential +- [ ] Timeout on all blocking operations + +### Hardware Safety +- [ ] Dispenser amounts validated (prevent over-dispense) +- [ ] Bill count cross-checked +- [ ] Error states trigger hardware reset +- [ ] Graceful degradation on hardware failure + +## Mock Validation + +### Mock Requirements +Mocks must simulate: +1. Normal operation flow +2. Error conditions +3. Timing (realistic delays) +4. State persistence + +### Mock Test Coverage +``` +/hal-check mock puloon +``` + +Validates mock implements: +- [ ] `connect()` - Success and failure paths +- [ ] `dispense()` - Full and partial dispense +- [ ] `reset()` - Error recovery +- [ ] Event emission timing +- [ ] Cassette state tracking + +## Protocol Analysis + +### Packet Structure Validation +``` +/hal-check protocol id003 +``` + +Compares Rust packet building with JS: +```rust +// Rust implementation +fn build_packet(&self, data: &[u8]) -> Vec { + let mut packet = vec![0x02]; // SYNC + packet.push(data.len() as u8 + 4); + packet.extend_from_slice(data); + let crc = self.calculate_crc(&packet); + packet.push((crc & 0xFF) as u8); + packet.push((crc >> 8) as u8); + packet +} +``` + +Against JavaScript: +```javascript +// lamassu-machine/lib/id003/id003rs232.js +function buildPacket(data) { + const buf = Buffer.alloc(data.length + 4) + buf[0] = 0x02 // SYNC + buf[1] = data.length + 4 + data.copy(buf, 2) + const crc = calculateCrc(buf.slice(0, -2)) + buf.writeUInt16LE(crc, buf.length - 2) + return buf +} +``` + +## Output Format + +### Port Validation +```markdown +## HAL Port Validation: id003 + +### Command Coverage +| Command | JS | Rust | Match | +|---------|-------|------|-------| +| RESET | ✅ | ✅ | ✅ | +| ENABLE | ✅ | ✅ | ✅ | +| STATUS | ✅ | ⚠️ | Partial | + +### Protocol Differences +- [ ] `id003.rs:78` - CRC uses different polynomial than JS + +### Missing Implementations +- [ ] `HOLD` command not implemented (used in id003fsm.js:holdBill) + +### Recommendations +1. Verify CRC calculation against test vectors from JS +2. Add HOLD command for escrow mode +``` + +### Safety Review +```markdown +## HAL Safety Review: puloon + +### Memory Safety +- [x] No unsafe blocks +- [x] Buffer bounds checked +- [ ] `dispense()` at line 145 - unwrap() could panic + +### Concurrency Safety +- [x] Serial port mutex protected +- [x] Event channel bounded (16) + +### Hardware Safety +- [x] Amount validation in dispense() +- [ ] No cassette empty check before dispense + +### Critical Issues +1. `puloon.rs:145` - Replace unwrap() with proper error handling +2. `puloon.rs:178` - Add cassette level check before dispensing +``` + +## Example Usage + +``` +/hal-check port id003 +``` + +``` +/hal-check safety packages/hal/src/dispensers/ +``` + +``` +/hal-check mock --all +``` diff --git a/lamassu-next/.claude/skills/lightning-check.md b/lamassu-next/.claude/skills/lightning-check.md new file mode 100644 index 0000000..e78cce5 --- /dev/null +++ b/lamassu-next/.claude/skills/lightning-check.md @@ -0,0 +1,168 @@ +# /lightning-check - Lightning.Pub Conformity Agent + +## Purpose +Validate Lightning.Pub integration and CLINK protocol conformity. + +## Invocation +``` +/lightning-check [target] [--clink] [--wallet] +``` + +Where: +- `target` - File or directory to check +- `--clink` - Focus on CLINK offer/debit flow +- `--wallet` - Focus on wallet operations + +## Lightning.Pub Integration + +### Connection +Lightning.Pub uses Nostr for all communication: + +```typescript +// Connection via nprofile +const nprofile = 'nprofile1...' // Contains pubkey + relay hints + +// All operations are Nostr events to Lightning.Pub's pubkey +await relay.publish({ + kind: 21002, // CLINK debit + content: encrypted_request, + tags: [['p', lightningPubPubkey]], +}) +``` + +### Required Checks +- [ ] Using correct event kinds (21001, 21002, 21003) +- [ ] Proper NIP-44 encryption for requests +- [ ] Handling async responses via subscription +- [ ] Proper error handling for payment failures + +## CLINK Protocol + +### Offer Flow (Kind 21001) +``` +User scans noffer → Wallet sends 21002 → ATM responds with invoice → User pays +``` + +Validation: +- [ ] noffer encoding is valid +- [ ] Relays included in offer +- [ ] Price type correctly specified (fixed/variable/spontaneous) +- [ ] Amount bounds validated + +### Debit Flow (Kind 21002) +Request structure: +```json +{ + "method": "pay_invoice", + "params": { + "invoice": "lnbc...", + "amount_msat": 100000 + } +} +``` + +Response structure: +```json +{ + "result": { + "preimage": "...", + "fee_msat": 1000 + } +} +``` + +Validation: +- [ ] Invoice format valid (BOLT11) +- [ ] Amount matches invoice +- [ ] Preimage verified against payment hash +- [ ] Fee within acceptable bounds +- [ ] Timeout handling + +### Manage Flow (Kind 21003) +For operator commands: +```json +{ + "method": "get_balance", + "params": {} +} +``` + +Validation: +- [ ] Only authorized operators can send +- [ ] Commands are properly authenticated +- [ ] Responses handled securely + +## Invoice Validation + +### BOLT11 Checks +- [ ] Valid bech32 encoding +- [ ] Expiry not passed +- [ ] Amount matches expected +- [ ] Description hash valid (if used) +- [ ] Payment hash extractable + +### Security Checks +- [ ] Never pay same invoice twice +- [ ] Amount limits enforced +- [ ] Rate limiting on payments +- [ ] Proper logging (no sensitive data) + +## Wallet Operations + +### Balance Queries +- [ ] Cached appropriately (not every render) +- [ ] Error handling for offline +- [ ] Display in correct units (sats, not msat) + +### Invoice Generation +- [ ] Unique payment hashes +- [ ] Reasonable expiry times +- [ ] Description for record-keeping +- [ ] Amount in correct units + +## Output Format + +```markdown +## Lightning.Pub Conformity: [target] + +### CLINK Protocol +| Flow | Status | Notes | +|------|--------|-------| +| Offer (21001) | ✅ | | +| Debit (21002) | ⚠️ | Missing timeout | +| Manage (21003) | ✅ | | + +### Invoice Handling +- [ ] `file:line` - Issue description + +### Integration Issues +- [ ] Description with fix suggestion + +### Recommendations +- [ ] Performance/UX improvements +``` + +## Example Usage + +``` +/lightning-check packages/lightning/src/ --clink +``` + +Output: +```markdown +## Lightning.Pub Conformity: packages/lightning/src/ + +### CLINK Protocol +| Flow | Status | Notes | +|------|--------|-------| +| Offer (21001) | ✅ | Properly encoded | +| Debit (21002) | ⚠️ | No timeout handling | + +### Issues Found +- [ ] `client.ts:89` - Debit request has no timeout, could hang indefinitely +- [ ] `client.ts:112` - Payment hash not verified against preimage + +### Recommendations +- Add 30-second timeout for CLINK debit requests +- Implement invoice deduplication to prevent double-pay +``` diff --git a/lamassu-next/.claude/skills/nostr-check.md b/lamassu-next/.claude/skills/nostr-check.md new file mode 100644 index 0000000..c3d1209 --- /dev/null +++ b/lamassu-next/.claude/skills/nostr-check.md @@ -0,0 +1,125 @@ +# /nostr-check - Nostr Conformity Agent + +## Purpose +Validate Nostr Implementation Proposals (NIP) conformity and optimize Nostr-related code. + +## Invocation +``` +/nostr-check [target] [--nips NIP1,NIP2,...] +``` + +Where `target` can be: +- A file path +- A directory +- `--events` to validate event structures +- `--relay` to check relay configuration + +## Relevant NIPs for Lamassu ATM + +### Core NIPs (Must Implement) +| NIP | Description | Usage in Lamassu | +|-----|-------------|------------------| +| NIP-01 | Basic protocol | Event structure, relay communication | +| NIP-19 | bech32 entities | npub, nsec, nprofile encoding | +| NIP-42 | Auth | Private relay authentication | +| NIP-44 | Encrypted payloads | Secure DMs, receipts | +| NIP-59 | Gift wrapping | Anonymous message delivery | + +### Application NIPs +| NIP | Description | Usage in Lamassu | +|-----|-------------|------------------| +| NIP-17 | Private DMs | Receipt delivery | +| NIP-47 | Nostr Wallet Connect | Potential wallet integration | +| NIP-57 | Lightning Zaps | Optional tipping | + +### Custom Event Kinds +| Kind | Description | Structure | +|------|-------------|-----------| +| 21001 | CLINK Offer | `{ pubkey, relays, priceType, amount? }` | +| 21002 | CLINK Debit | Payment request/response | +| 21003 | CLINK Manage | Operator commands | +| 30078 | Machine Status | Replaceable, `d` tag = "status" | +| 30079 | Transaction Record | Replaceable, `d` tag = "tx:{txid}" | + +## Validation Checks + +### Event Structure (NIP-01) +```typescript +interface Event { + id: string // 32-byte hex + pubkey: string // 32-byte hex + created_at: number // Unix timestamp + kind: number // Event kind + tags: string[][] // Array of tag arrays + content: string // Arbitrary string + sig: string // 64-byte hex signature +} +``` + +- [ ] `id` is valid SHA256 of serialized event +- [ ] `pubkey` is valid 32-byte hex +- [ ] `created_at` is reasonable (not far future/past) +- [ ] `kind` is valid for application +- [ ] `tags` are properly formatted +- [ ] `sig` is valid Schnorr signature + +### bech32 Encoding (NIP-19) +- [ ] npub/nsec properly encoded +- [ ] nprofile includes relay hints +- [ ] nevent includes author pubkey +- [ ] No confusion between hex and bech32 + +### Encryption (NIP-44) +- [ ] Using NIP-44 (not deprecated NIP-04) +- [ ] Proper key derivation +- [ ] Random nonce for each message +- [ ] MAC verification before decryption + +### Auth (NIP-42) +- [ ] Challenge-response implemented +- [ ] Auth events have proper `relay` tag +- [ ] Auth events signed correctly +- [ ] Timeout handling for auth flow + +## Optimization Suggestions + +### Relay Communication +- Use connection pooling +- Implement proper reconnection with backoff +- Batch event publishing when possible +- Use REQ filters efficiently + +### Event Handling +- Verify signatures before processing +- Cache verified events +- Use proper indexing for event lookups +- Implement proper subscription management + +## Output Format + +```markdown +## Nostr Conformity: [target] + +### NIP Compliance +| NIP | Status | Notes | +|-----|--------|-------| +| NIP-01 | ✅ Pass | | +| NIP-19 | ⚠️ Issue | See below | + +### Issues Found +- [ ] `file:line` - Description + +### Optimization Opportunities +- [ ] Description with rationale + +### Custom Event Validation +| Kind | Valid | Notes | +|------|-------|-------| +| 30078 | ✅ | Machine status properly formatted | +``` + +## Example Usage + +``` +/nostr-check packages/nostr-client/src/events.ts --nips NIP-01,NIP-19 +``` diff --git a/lamassu-next/.claude/skills/security.md b/lamassu-next/.claude/skills/security.md new file mode 100644 index 0000000..d5501b2 --- /dev/null +++ b/lamassu-next/.claude/skills/security.md @@ -0,0 +1,105 @@ +# /security - Security Review Agent + +## Purpose +Perform security audits on code changes, focusing on Bitcoin/Lightning ATM-specific vulnerabilities. + +## Invocation +``` +/security [target] +``` + +Where `target` can be: +- A file path (e.g., `packages/lightning/src/client.ts`) +- A directory (e.g., `packages/hal/`) +- `--staged` for staged git changes +- `--all` for full codebase scan + +## Review Checklist + +### 1. Bitcoin/Lightning Security +- [ ] Private keys never logged or exposed +- [ ] nsec (Nostr secret keys) protected with proper permissions (0600) +- [ ] Invoice amounts validated before payment +- [ ] Payment preimages handled securely +- [ ] No hardcoded mnemonics, seeds, or keys +- [ ] Proper BOLT11/BOLT12 invoice validation + +### 2. Hardware Security (ATM-specific) +- [ ] Bill validator amounts cross-checked +- [ ] Dispenser commands validated (prevent over-dispensing) +- [ ] Hardware error states handled gracefully +- [ ] No race conditions in cash handling +- [ ] Timeout handling for hardware operations + +### 3. Nostr Security +- [ ] NIP-44 encryption used for sensitive messages +- [ ] Event signatures verified before processing +- [ ] Relay URLs validated (no injection) +- [ ] NIP-42 auth implemented for private relay +- [ ] No pubkey/npub confusion (proper type safety) + +### 4. General Security +- [ ] Input validation on all external data +- [ ] SQL injection prevention (parameterized queries) +- [ ] No command injection in Bash/shell calls +- [ ] Proper error handling (no sensitive data in errors) +- [ ] Rate limiting on API endpoints +- [ ] HTTPS/WSS enforced in production + +### 5. Rust-specific (HAL) +- [ ] No `unsafe` blocks without justification +- [ ] Error handling with Result, no unwrap() in production +- [ ] Buffer bounds checking for serial communication +- [ ] Proper lifetime management + +### 6. TypeScript-specific +- [ ] Strict null checks honored +- [ ] No `any` types +- [ ] Zod validation on external data +- [ ] No eval() or dynamic code execution + +## Output Format + +```markdown +## Security Review: [target] + +### Critical Issues +- [ ] Issue description with file:line reference + +### High Priority +- [ ] Issue description with file:line reference + +### Medium Priority +- [ ] Issue description with file:line reference + +### Low Priority / Suggestions +- [ ] Suggestion with rationale + +### Passed Checks +- [x] Check that passed +``` + +## Example Usage + +``` +/security packages/lightning/src/client.ts +``` + +Output: +```markdown +## Security Review: packages/lightning/src/client.ts + +### Critical Issues +None found. + +### High Priority +- [ ] `client.ts:45` - Invoice amount not validated before `payInvoice()` call + +### Medium Priority +- [ ] `client.ts:78` - Error message includes full stack trace, may leak internal paths + +### Passed Checks +- [x] Private keys not exposed in logs +- [x] NIP-44 encryption used for DMs +- [x] Proper TypeScript strict mode +``` diff --git a/lamassu-next/.claude/skills/test.md b/lamassu-next/.claude/skills/test.md new file mode 100644 index 0000000..43ecc54 --- /dev/null +++ b/lamassu-next/.claude/skills/test.md @@ -0,0 +1,201 @@ +# /test - Testing Agent + +## Purpose +Run tests, analyze coverage, generate test cases, and validate ATM transaction flows. + +## Invocation +``` +/test [command] [target] +``` + +Commands: +- `run` - Run tests (default) +- `coverage` - Run with coverage report +- `generate` - Generate test cases for a file +- `flow` - Validate transaction flow +- `hardware` - Run hardware mock tests + +## Test Categories + +### 1. Unit Tests +Location: `*.test.ts` or `*.spec.ts` alongside source files + +```bash +pnpm test # All tests +pnpm test --filter @lamassu/nostr-client # Specific package +``` + +### 2. Integration Tests +Location: `tests/integration/` + +Tests cross-package interactions: +- Nostr client + CLINK +- State machine + Hardware mocks +- Lightning client + Invoice handling + +### 3. E2E Tests (Playwright) +Location: `tests/e2e/` + +Full transaction flows: +- Cash-in flow (bill insert → Lightning payment) +- Cash-out flow (Lightning receive → dispense) +- Error recovery flows + +### 4. Hardware Mock Tests +Test against mock hardware: + +```bash +pnpm test:hardware # With mock validator/dispenser +REAL_HARDWARE=true pnpm test:hardware # With real hardware (CI skip) +``` + +## Test Generation + +### For a new file: +``` +/test generate packages/lightning/src/client.ts +``` + +Generates test skeleton: +```typescript +import { describe, it, expect, vi } from 'vitest' +import { LightningPubClient } from './client' + +describe('LightningPubClient', () => { + describe('createInvoice', () => { + it('should create valid BOLT11 invoice', async () => { + // TODO: Implement + }) + + it('should handle network errors', async () => { + // TODO: Implement + }) + + it('should validate amount bounds', async () => { + // TODO: Implement + }) + }) +}) +``` + +### Test Case Suggestions +Based on code analysis, suggest test cases for: +- Happy path +- Edge cases (null, empty, max values) +- Error conditions +- Timeout scenarios +- Concurrent operations + +## Transaction Flow Validation + +### Cash-In Flow +``` +/test flow cash-in +``` + +Validates: +1. Idle → Select Cash-In +2. Bill insertion events received +3. Amount calculation correct +4. CLINK offer generated +5. Payment received event +6. Receipt sent (optional) +7. Return to Idle + +### Cash-Out Flow +``` +/test flow cash-out +``` + +Validates: +1. Idle → Select Cash-Out +2. Amount selection +3. Invoice generated +4. Payment received +5. Cash dispensed +6. Bills removed detection (F56) +7. Return to Idle + +### Error Flows +``` +/test flow errors +``` + +Validates: +- Hardware timeout recovery +- Payment failure handling +- Network disconnection +- Partial dispense handling + +## Coverage Requirements + +### Minimum Coverage Targets +| Package | Statements | Branches | Functions | +|---------|------------|----------|-----------| +| nostr-client | 80% | 75% | 80% | +| clink | 80% | 75% | 80% | +| state-machine | 90% | 85% | 90% | +| lightning | 80% | 75% | 80% | +| hal (mocks) | 70% | 65% | 70% | + +### Critical Paths (100% coverage required) +- Payment processing +- Cash dispensing logic +- Key handling +- Amount calculations + +## Output Format + +### Test Run +```markdown +## Test Results: [target] + +### Summary +- Total: 42 +- Passed: 40 +- Failed: 2 +- Skipped: 0 + +### Failed Tests +1. `client.test.ts` > createInvoice > should validate amount + - Expected: Error thrown + - Received: Invoice created with negative amount + +### Coverage +| File | Statements | Branches | Functions | +|------|------------|----------|-----------| +| client.ts | 85% | 78% | 90% | +``` + +### Test Generation +```markdown +## Generated Tests: [file] + +### Test File +`packages/lightning/src/client.test.ts` + +### Suggested Test Cases +1. **createInvoice** + - ✅ Valid amount creates invoice + - ✅ Zero amount rejected + - ✅ Negative amount rejected + - ✅ Network error handled + +### Missing Coverage +- `payInvoice` has no tests +- Error handling branch at line 45 untested +``` + +## Example Usage + +``` +/test coverage packages/state-machine/ +``` + +``` +/test generate packages/clink/src/offer.ts +``` + +``` +/test flow cash-out +``` diff --git a/lamassu-next/.gitignore b/lamassu-next/.gitignore new file mode 100644 index 0000000..258400b --- /dev/null +++ b/lamassu-next/.gitignore @@ -0,0 +1,71 @@ +# Dependencies +node_modules/ +.pnpm-store/ + +# Build outputs +dist/ +.next/ +.nuxt/ +.output/ +target/ +*.node + +# IDE +.idea/ +.vscode/ +*.swp +*.swo +*~ + +# Environment +.env +.env.* +!.env.example + +# Secrets +*.nsec +*.pem +*.key +.secrets.baseline + +# Logs +*.log +npm-debug.log* +pnpm-debug.log* + +# Testing +coverage/ +.nyc_output/ + +# Caches +.turbo/ +.cache/ +.parcel-cache/ +.eslintcache +*.tsbuildinfo + +# OS +.DS_Store +Thumbs.db + +# Tauri +apps/machine/src-tauri/target/ +apps/machine/src-tauri/WixTools/ +apps/machine/src-tauri/icons/ + +# Rust +Cargo.lock +**/*.rs.bk + +# devenv +.devenv/ +.direnv/ +.pre-commit-config.yaml + +# Docker +docker/**/data/ + +# Temporary +tmp/ +temp/ +*.tmp diff --git a/lamassu-next/.prettierrc b/lamassu-next/.prettierrc new file mode 100644 index 0000000..989e171 --- /dev/null +++ b/lamassu-next/.prettierrc @@ -0,0 +1,7 @@ +{ + "semi": false, + "singleQuote": true, + "trailingComma": "es5", + "tabWidth": 2, + "printWidth": 100 +} diff --git a/lamassu-next/CLAUDE.md b/lamassu-next/CLAUDE.md new file mode 100644 index 0000000..a320da1 --- /dev/null +++ b/lamassu-next/CLAUDE.md @@ -0,0 +1,177 @@ +# CLAUDE.md + +This file provides guidance to Claude Code when working with the Lamassu Next codebase. + +## Project Overview + +**Lamassu Next** is a Nostr-native Lightning ATM system. Key principles: + +- **KYC-Free**: No identity collection, no compliance theater +- **Lightning-Native**: Security encapsulated in Lightning protocol +- **Nostr as Infrastructure**: Relay for communication, keypairs for identity +- **Open Source First**: Every component auditable and forkable + +## Architecture + +``` +lamassu-next/ +├── apps/ +│ ├── machine/ # Tauri + Vue 3 ATM kiosk application +│ ├── dashboard/ # Vue 3 operator dashboard +│ └── relay/ # strfry relay configuration +├── packages/ +│ ├── hal/ # Rust Hardware Abstraction Layer (napi-rs) +│ ├── nostr-client/ # Nostr client library +│ ├── clink/ # CLINK protocol implementation +│ ├── state-machine/ # XState v5 ATM state machine +│ ├── lightning/ # Lightning.Pub client +│ ├── cashu/ # Cashu ecash (offline mode) +│ └── ui-shared/ # Shared Vue components +└── docker/ # Development infrastructure +``` + +## Commands + +```bash +# Enter development environment +devenv shell + +# Start development +pnpm dev + +# Build all packages +pnpm build + +# Run tests +pnpm test + +# Start infrastructure (Nostr relay, Lightning.Pub) +infra-up + +# Stop infrastructure +infra-down +``` + +## Key Technologies + +| Component | Technology | Notes | +|-----------|------------|-------| +| Runtime | Node.js 22 LTS | Strict TypeScript | +| ATM Shell | Tauri 2.x | Rust core, Vue 3 UI | +| State Machine | XState v5 | Actor model | +| Hardware | Rust + napi-rs | Ported from lamassu-machine | +| Messaging | Nostr | NIP-01, NIP-17, NIP-42, NIP-44 | +| Payments | CLINK | Kind 21001/21002/21003 | +| Backend | Lightning.Pub | Nostr-native account system | + +## Custom Skills + +The following skills are available for development assistance: + +### `/security` - Security Review +Audit code for Bitcoin/Lightning/ATM-specific vulnerabilities. +``` +/security packages/lightning/src/ +/security --staged +``` + +### `/nostr-check` - Nostr Conformity +Validate NIP compliance and Nostr protocol implementation. +``` +/nostr-check packages/nostr-client/src/events.ts --nips NIP-01,NIP-44 +``` + +### `/lightning-check` - Lightning.Pub Conformity +Validate CLINK protocol and Lightning.Pub integration. +``` +/lightning-check packages/clink/src/ --clink +``` + +### `/test` - Testing Agent +Run tests, generate test cases, validate transaction flows. +``` +/test coverage packages/state-machine/ +/test flow cash-out +/test generate packages/lightning/src/client.ts +``` + +### `/docs` - Documentation Agent +Keep documentation synchronized with code. +``` +/docs sync packages/clink/ +/docs api packages/nostr-client/src/ +``` + +### `/hal-check` - HAL Validation +Validate Rust HAL drivers against lamassu-machine implementations. +``` +/hal-check port id003 +/hal-check safety packages/hal/src/dispensers/ +``` + +## Code Style + +### TypeScript +- ESM only (`import`/`export`) +- Strict mode with `strictNullChecks` and `noUncheckedIndexedAccess` +- Zod for runtime validation +- No `any` types + +### Rust (HAL) +- Stable toolchain +- `#![deny(unsafe_code)]` unless justified +- Error handling with `thiserror` +- Async with `tokio` + +### Formatting +- Prettier for TypeScript (2 spaces, no semicolons, single quotes) +- rustfmt for Rust +- Pre-commit hooks enforce formatting + +## Hardware Drivers + +Drivers are ported from `lamassu-machine/lib/`: + +| Category | Drivers | +|----------|---------| +| Validators | id003, ccnet, cashflow_sc, bnr_advance, genmega, hcm2, gsr50 | +| Dispensers | puloon, f56, genmega, hcm2, gsr50 | +| Printers | nippon, zebra, genmega | + +When porting: +1. Read JS driver thoroughly +2. Document protocol from JS code +3. Implement Rust version +4. Test against same hardware +5. Use `/hal-check port ` to validate + +## Nostr Event Kinds + +| Kind | Description | +|------|-------------| +| 21001 | CLINK Offer | +| 21002 | CLINK Debit | +| 21003 | CLINK Manage | +| 30078 | Machine Status (replaceable) | +| 30079 | Transaction Record (replaceable) | + +## Security Priorities + +1. **Private keys** - Never log nsec, protect with 0600 permissions +2. **Payments** - Validate invoices, verify preimages, prevent double-pay +3. **Hardware** - Validate dispense amounts, handle errors gracefully +4. **Encryption** - Use NIP-44 for all sensitive data + +## Testing Requirements + +- Unit tests for all packages +- Integration tests for cross-package interactions +- E2E tests for full transaction flows +- **Cash-out flow is critical path** (95%+ of activity) + +## Related Documentation + +- `../docs/implementation-plan.md` - Full implementation roadmap +- `../docs/nostr-native-architecture.md` - Architecture details +- `../docs/architecture-review.md` - KYC-free vision +- `.claude/skills/*.md` - Skill documentation diff --git a/lamassu-next/devenv.nix b/lamassu-next/devenv.nix new file mode 100644 index 0000000..a77412b --- /dev/null +++ b/lamassu-next/devenv.nix @@ -0,0 +1,242 @@ +{ pkgs, lib, ... }: + +{ + # Project metadata + name = "lamassu-next"; + + # ============================================ + # Languages + # ============================================ + + languages.javascript = { + enable = true; + package = pkgs.nodejs_22; + pnpm = { + enable = true; + install.enable = true; + }; + }; + + languages.typescript.enable = true; + + languages.rust = { + enable = true; + channel = "stable"; + components = [ "rustc" "cargo" "clippy" "rustfmt" "rust-analyzer" ]; + }; + + # ============================================ + # Services + # ============================================ + + services.postgres = { + enable = true; + initialDatabases = [{ name = "lamassu_dev"; }]; + listen_addresses = "127.0.0.1"; + }; + + # ============================================ + # Packages + # ============================================ + + packages = with pkgs; [ + # Build tools + pkg-config + openssl + openssl.dev + + # Tauri dependencies (Linux) + gtk3 + webkitgtk + libappindicator-gtk3 + librsvg + + # Hardware access + libusb1 + libnfc + udev + + # Serial port access + picocom + + # Nostr tools + nak # Nostr army knife CLI + + # Development utilities + just # Task runner + jq # JSON processing + httpie # HTTP client + websocat # WebSocket client + + # Database tools + pgcli + + # Container tools (for Lightning.Pub, strfry) + docker-compose + ]; + + # ============================================ + # Pre-commit Hooks + # ============================================ + + pre-commit.hooks = { + # TypeScript/JavaScript + prettier = { + enable = true; + excludes = [ "pnpm-lock.yaml" ]; + }; + + eslint = { + enable = true; + entry = "pnpm eslint --fix"; + types = [ "javascript" "typescript" ]; + }; + + # Rust + rustfmt.enable = true; + + clippy = { + enable = true; + entry = "cargo clippy --all-targets --all-features -- -D warnings"; + pass_filenames = false; + }; + + # TypeScript type checking + typecheck = { + enable = true; + entry = "pnpm typecheck"; + pass_filenames = false; + stages = [ "pre-push" ]; # Only on push, not every commit + }; + + # Security: detect secrets + detect-secrets = { + enable = true; + entry = "${pkgs.detect-secrets}/bin/detect-secrets-hook --baseline .secrets.baseline"; + }; + + # Nix formatting + nixpkgs-fmt.enable = true; + + # TOML/YAML validation + check-toml.enable = true; + check-yaml.enable = true; + + # Prevent large files + check-added-large-files = { + enable = true; + entry = "check-added-large-files --maxkb=500"; + }; + + # Custom: Nostr event schema validation + nostr-schema = { + enable = true; + entry = "pnpm --filter @lamassu/nostr-client run validate-schemas"; + files = "packages/nostr-client/.*\\.(ts|json)$"; + pass_filenames = false; + }; + + # Custom: No console.log in production code + no-console-log = { + enable = true; + entry = '' + ${pkgs.gnugrep}/bin/grep -rn "console\\.log" --include="*.ts" --include="*.tsx" \ + apps/ packages/ \ + --exclude-dir=node_modules \ + --exclude-dir=dist \ + --exclude="*.test.ts" \ + --exclude="*.spec.ts" \ + && echo "ERROR: console.log found in production code" && exit 1 || exit 0 + ''; + pass_filenames = false; + }; + }; + + # ============================================ + # Environment Variables + # ============================================ + + env = { + RUST_BACKTRACE = "1"; + DATABASE_URL = "postgresql://localhost/lamassu_dev"; + + # Nostr development relay + NOSTR_RELAY_URL = "ws://localhost:7777"; + + # Lightning.Pub development + LIGHTNING_PUB_URL = "http://localhost:3000"; + }; + + # ============================================ + # Scripts (available as commands in shell) + # ============================================ + + scripts = { + dev.exec = "pnpm turbo dev"; + build.exec = "pnpm turbo build"; + test.exec = "pnpm turbo test"; + lint.exec = "pnpm turbo lint"; + + # Start infrastructure + infra-up.exec = '' + echo "Starting development infrastructure..." + docker-compose -f docker/docker-compose.dev.yml up -d + echo "Waiting for services..." + sleep 5 + echo "Infrastructure ready!" + echo " - Nostr relay: ws://localhost:7777" + echo " - Lightning.Pub: http://localhost:3000" + ''; + + infra-down.exec = "docker-compose -f docker/docker-compose.dev.yml down"; + + # Hardware testing + mock-bill.exec = '' + # Simulate bill insertion for testing + echo "Simulating $1 bill insertion..." + # TODO: Implement mock event publisher + ''; + + # Nostr tools + nostr-publish.exec = '' + nak event --content "$1" ws://localhost:7777 + ''; + + nostr-subscribe.exec = '' + nak req --kinds "$1" ws://localhost:7777 + ''; + }; + + # ============================================ + # Shell Hook + # ============================================ + + enterShell = '' + echo "" + echo " ⚡ Lamassu Next - Nostr-Native Lightning ATM" + echo " ─────────────────────────────────────────────" + echo " Node.js: $(node --version)" + echo " Rust: $(rustc --version | cut -d' ' -f2)" + echo " pnpm: $(pnpm --version)" + echo "" + echo " Commands:" + echo " dev Start development servers" + echo " build Build all packages" + echo " test Run tests" + echo " infra-up Start Docker infrastructure" + echo " infra-down Stop Docker infrastructure" + echo "" + echo " Pre-commit hooks are enabled. Run 'pre-commit run --all-files' to check." + echo "" + ''; + + # ============================================ + # Process Compose (optional background services) + # ============================================ + + # Uncomment to auto-start services in devenv up + # processes = { + # relay.exec = "docker-compose -f docker/docker-compose.dev.yml up strfry"; + # lightning-pub.exec = "docker-compose -f docker/docker-compose.dev.yml up lightning-pub"; + # }; +} diff --git a/lamassu-next/devenv.yaml b/lamassu-next/devenv.yaml new file mode 100644 index 0000000..56984d9 --- /dev/null +++ b/lamassu-next/devenv.yaml @@ -0,0 +1,5 @@ +inputs: + nixpkgs: + url: github:NixOS/nixpkgs/nixpkgs-unstable + pre-commit-hooks: + url: github:cachix/pre-commit-hooks.nix diff --git a/lamassu-next/docker/docker-compose.dev.yml b/lamassu-next/docker/docker-compose.dev.yml new file mode 100644 index 0000000..212595d --- /dev/null +++ b/lamassu-next/docker/docker-compose.dev.yml @@ -0,0 +1,63 @@ +version: '3.8' + +services: + # Private Nostr relay for ATM communication + strfry: + image: ghcr.io/hoytech/strfry:latest + container_name: lamassu-relay + ports: + - "7777:7777" + volumes: + - ./strfry.conf:/etc/strfry.conf:ro + - strfry-data:/app/strfry-db + restart: unless-stopped + + # Lightning.Pub - Nostr-native Lightning account system + lightning-pub: + image: ghcr.io/shocknet/lightning-pub:latest + container_name: lamassu-lightning-pub + ports: + - "3000:3000" + - "9735:9735" + volumes: + - lightning-pub-data:/root/lightning_pub + environment: + - NETWORK=regtest # Use regtest for development + restart: unless-stopped + + # Polar LND node for regtest (optional - for testing without Lightning.Pub) + # Uncomment if you want a separate LND instance + # lnd: + # image: lightninglabs/lnd:v0.18.0-beta + # container_name: lamassu-lnd + # ports: + # - "10009:10009" + # - "9736:9735" + # volumes: + # - lnd-data:/root/.lnd + # command: > + # --bitcoin.active + # --bitcoin.regtest + # --bitcoin.node=neutrino + # --neutrino.connect=faucet.lightning.community + # restart: unless-stopped + + # PostgreSQL for any server-side state (optional) + postgres: + image: postgres:16-alpine + container_name: lamassu-postgres + ports: + - "5432:5432" + environment: + POSTGRES_DB: lamassu_dev + POSTGRES_USER: lamassu + POSTGRES_PASSWORD: lamassu_dev_password + volumes: + - postgres-data:/var/lib/postgresql/data + restart: unless-stopped + +volumes: + strfry-data: + lightning-pub-data: + postgres-data: + # lnd-data: diff --git a/lamassu-next/docker/strfry.conf b/lamassu-next/docker/strfry.conf new file mode 100644 index 0000000..6527b9e --- /dev/null +++ b/lamassu-next/docker/strfry.conf @@ -0,0 +1,52 @@ +## +## strfry configuration for Lamassu ATM development +## + +relay { + bind = "0.0.0.0" + port = 7777 + + info { + name = "Lamassu Dev Relay" + description = "Private Nostr relay for ATM development and testing" + pubkey = "" + contact = "" + } + + # Maximum message size in bytes + maxWebsocketPayloadSize = 131072 + + # Connection limits + maxWebsockets = 100 + maxConnections = 1000 +} + +# Event policies +events { + # Maximum event size + maxEventSize = 65536 + + # Rate limiting + rejectEventsNewerThanSeconds = 900 + rejectEventsOlderThanSeconds = 94608000 + + # Event kinds we care about + # 14: Private DMs (NIP-17) + # 21001-21003: CLINK events + # 22242: NIP-42 auth + # 30078: Machine status (replaceable) + # 30079: Transaction records (replaceable) +} + +# Negentropy sync (for relay federation) +negentropy { + enabled = true + syncOnConnect = false +} + +# Plugins (for NIP-42 auth in production) +# plugins { +# authRequired = true +# writePolicy = "accept" +# readPolicy = "accept" +# } diff --git a/lamassu-next/flake.nix b/lamassu-next/flake.nix new file mode 100644 index 0000000..678ed9a --- /dev/null +++ b/lamassu-next/flake.nix @@ -0,0 +1,86 @@ +{ + description = "Lamassu Next - Nostr-Native Lightning ATM"; + + inputs = { + nixpkgs.url = "github:NixOS/nixpkgs/nixpkgs-unstable"; + flake-utils.url = "github:numtide/flake-utils"; + rust-overlay = { + url = "github:oxalica/rust-overlay"; + inputs.nixpkgs.follows = "nixpkgs"; + }; + devenv = { + url = "github:cachix/devenv"; + inputs.nixpkgs.follows = "nixpkgs"; + }; + }; + + outputs = { self, nixpkgs, flake-utils, rust-overlay, devenv }: + flake-utils.lib.eachDefaultSystem (system: + let + overlays = [ (import rust-overlay) ]; + pkgs = import nixpkgs { inherit system overlays; }; + + rustToolchain = pkgs.rust-bin.stable.latest.default.override { + extensions = [ "rust-src" "rust-analyzer" ]; + targets = [ "wasm32-unknown-unknown" ]; + }; + in + { + # Development shell via devenv + devShells.default = devenv.lib.mkShell { + inherit pkgs; + modules = [ ./devenv.nix ]; + }; + + # Packages + packages = { + # HAL Rust crate + hal = pkgs.rustPlatform.buildRustPackage { + pname = "lamassu-hal"; + version = "0.1.0"; + src = ./packages/hal; + cargoLock.lockFile = ./packages/hal/Cargo.lock; + + nativeBuildInputs = with pkgs; [ + pkg-config + ]; + + buildInputs = with pkgs; [ + openssl + libudev-zero + ]; + }; + + # Full application (TODO) + default = self.packages.${system}.hal; + }; + + # NixOS module for deployment + nixosModules.default = { config, lib, pkgs, ... }: { + options.services.lamassu-atm = { + enable = lib.mkEnableOption "Lamassu ATM service"; + + relayUrl = lib.mkOption { + type = lib.types.str; + description = "Nostr relay URL"; + }; + + lightningPubUrl = lib.mkOption { + type = lib.types.str; + description = "Lightning.Pub connection URL"; + }; + + identityPath = lib.mkOption { + type = lib.types.path; + default = "/etc/lamassu/machine.nsec"; + description = "Path to machine identity (nsec)"; + }; + }; + + config = lib.mkIf config.services.lamassu-atm.enable { + # TODO: systemd service, firewall rules, etc. + }; + }; + } + ); +} diff --git a/lamassu-next/package.json b/lamassu-next/package.json new file mode 100644 index 0000000..f90bb9e --- /dev/null +++ b/lamassu-next/package.json @@ -0,0 +1,26 @@ +{ + "name": "lamassu-next", + "version": "0.1.0", + "private": true, + "description": "Nostr-native Lightning ATM - KYC-free, open-source", + "type": "module", + "scripts": { + "dev": "turbo dev", + "build": "turbo build", + "test": "turbo test", + "lint": "turbo lint", + "format": "prettier --write .", + "format:check": "prettier --check .", + "typecheck": "turbo typecheck" + }, + "devDependencies": { + "@types/node": "^22.0.0", + "prettier": "^3.4.0", + "turbo": "^2.3.0", + "typescript": "^5.7.0" + }, + "packageManager": "pnpm@9.15.0", + "engines": { + "node": ">=22.0.0" + } +} diff --git a/lamassu-next/packages/cashu/package.json b/lamassu-next/packages/cashu/package.json new file mode 100644 index 0000000..f6289bc --- /dev/null +++ b/lamassu-next/packages/cashu/package.json @@ -0,0 +1,30 @@ +{ + "name": "@lamassu/cashu", + "version": "0.1.0", + "description": "Cashu ecash integration for offline ATM operation", + "type": "module", + "main": "./dist/index.js", + "types": "./dist/index.d.ts", + "exports": { + ".": { + "types": "./dist/index.d.ts", + "import": "./dist/index.js" + } + }, + "scripts": { + "build": "tsc", + "dev": "tsc --watch", + "test": "vitest run", + "test:watch": "vitest", + "typecheck": "tsc --noEmit", + "lint": "eslint src/" + }, + "dependencies": { + "@cashu/cashu-ts": "^2.0.0" + }, + "devDependencies": { + "@types/node": "^22.0.0", + "typescript": "^5.7.0", + "vitest": "^2.1.0" + } +} diff --git a/lamassu-next/packages/clink/package.json b/lamassu-next/packages/clink/package.json new file mode 100644 index 0000000..ae70e8e --- /dev/null +++ b/lamassu-next/packages/clink/package.json @@ -0,0 +1,31 @@ +{ + "name": "@lamassu/clink", + "version": "0.1.0", + "description": "CLINK protocol implementation for Nostr-native Lightning payments", + "type": "module", + "main": "./dist/index.js", + "types": "./dist/index.d.ts", + "exports": { + ".": { + "types": "./dist/index.d.ts", + "import": "./dist/index.js" + } + }, + "scripts": { + "build": "tsc", + "dev": "tsc --watch", + "test": "vitest run", + "test:watch": "vitest", + "typecheck": "tsc --noEmit", + "lint": "eslint src/" + }, + "dependencies": { + "@lamassu/nostr-client": "workspace:*", + "nostr-tools": "^2.10.0" + }, + "devDependencies": { + "@types/node": "^22.0.0", + "typescript": "^5.7.0", + "vitest": "^2.1.0" + } +} diff --git a/lamassu-next/packages/hal/Cargo.toml b/lamassu-next/packages/hal/Cargo.toml new file mode 100644 index 0000000..4314786 --- /dev/null +++ b/lamassu-next/packages/hal/Cargo.toml @@ -0,0 +1,50 @@ +[package] +name = "lamassu-hal" +version = "0.1.0" +edition = "2021" +description = "Hardware Abstraction Layer for Lamassu ATM" +license = "MIT" +repository = "https://github.com/lamassu/lamassu-next" + +[lib] +crate-type = ["cdylib"] + +[dependencies] +# napi-rs for Node.js bindings +napi = { version = "2", features = ["async", "tokio_rt"] } +napi-derive = "2" + +# Async runtime +tokio = { version = "1", features = ["full"] } + +# Serial port communication +tokio-serial = "5" + +# Error handling +thiserror = "1" + +# Async traits +async-trait = "0.1" + +# Logging +tracing = "0.1" +tracing-subscriber = { version = "0.3", features = ["env-filter"] } + +# Serialization +serde = { version = "1", features = ["derive"] } +serde_json = "1" + +[build-dependencies] +napi-build = "2" + +[profile.release] +lto = true +strip = true +codegen-units = 1 + +[features] +default = [] +# Enable hardware drivers (for production builds) +hardware = [] +# Mock-only build (for testing/development) +mock-only = [] diff --git a/lamassu-next/packages/hal/build.rs b/lamassu-next/packages/hal/build.rs new file mode 100644 index 0000000..9fc2367 --- /dev/null +++ b/lamassu-next/packages/hal/build.rs @@ -0,0 +1,5 @@ +extern crate napi_build; + +fn main() { + napi_build::setup(); +} diff --git a/lamassu-next/packages/hal/src/dispensers/mock.rs b/lamassu-next/packages/hal/src/dispensers/mock.rs new file mode 100644 index 0000000..e335fb3 --- /dev/null +++ b/lamassu-next/packages/hal/src/dispensers/mock.rs @@ -0,0 +1,134 @@ +//! Mock bill dispenser for testing + +use async_trait::async_trait; + +use crate::error::DispenserError; +use super::traits::{BillDispenser, CassetteStatus}; + +/// Mock bill dispenser for development and testing +pub struct MockDispenser { + connected: bool, + cassettes: Vec, + bills_at_exit: bool, +} + +impl MockDispenser { + /// Create a new mock dispenser with default cassettes + pub fn new() -> Self { + Self { + connected: false, + cassettes: vec![ + CassetteStatus { + denomination: 20, + count: 500, + capacity: 500, + }, + CassetteStatus { + denomination: 50, + count: 200, + capacity: 200, + }, + ], + bills_at_exit: false, + } + } + + /// Create with custom cassette configuration + pub fn with_cassettes(cassettes: Vec) -> Self { + Self { + connected: false, + cassettes, + bills_at_exit: false, + } + } +} + +impl Default for MockDispenser { + fn default() -> Self { + Self::new() + } +} + +#[async_trait] +impl BillDispenser for MockDispenser { + fn driver_name(&self) -> &'static str { + "mock" + } + + async fn connect(&mut self) -> Result<(), DispenserError> { + tokio::time::sleep(tokio::time::Duration::from_millis(100)).await; + self.connected = true; + tracing::info!("MockDispenser connected"); + Ok(()) + } + + async fn disconnect(&mut self) -> Result<(), DispenserError> { + self.connected = false; + tracing::info!("MockDispenser disconnected"); + Ok(()) + } + + async fn get_cassette_status(&self) -> Result, DispenserError> { + Ok(self.cassettes.clone()) + } + + async fn dispense(&mut self, denomination: u32, count: u32) -> Result { + if !self.connected { + return Err(DispenserError::ConnectionFailed("Not connected".into())); + } + + // Find cassette with this denomination + let cassette = self + .cassettes + .iter_mut() + .find(|c| c.denomination == denomination) + .ok_or_else(|| { + DispenserError::HardwareError(format!( + "No cassette for denomination {}", + denomination + )) + })?; + + // Check if we have enough bills + if cassette.count < count { + return Err(DispenserError::InsufficientBills(count, cassette.count)); + } + + // Simulate dispense time + tokio::time::sleep(tokio::time::Duration::from_millis(count as u64 * 200)).await; + + // Update count + cassette.count -= count; + self.bills_at_exit = true; + + tracing::info!( + "MockDispenser dispensed {} x ${} bills ({} remaining)", + count, + denomination, + cassette.count + ); + + Ok(count) + } + + async fn reset(&mut self) -> Result<(), DispenserError> { + tracing::info!("MockDispenser reset"); + self.bills_at_exit = false; + Ok(()) + } + + async fn is_ready(&self) -> Result { + Ok(self.connected && !self.bills_at_exit) + } + + async fn bills_present(&self) -> Result { + Ok(self.bills_at_exit) + } + + async fn wait_for_bills_removed(&self) -> Result<(), DispenserError> { + // Simulate customer taking bills + tokio::time::sleep(tokio::time::Duration::from_millis(500)).await; + tracing::info!("MockDispenser: bills removed"); + Ok(()) + } +} diff --git a/lamassu-next/packages/hal/src/dispensers/mod.rs b/lamassu-next/packages/hal/src/dispensers/mod.rs new file mode 100644 index 0000000..4bd2900 --- /dev/null +++ b/lamassu-next/packages/hal/src/dispensers/mod.rs @@ -0,0 +1,125 @@ +//! Bill dispenser drivers +//! +//! This module contains implementations for various bill dispenser protocols: +//! - Puloon LCDM series +//! - Fujitsu F53/F56 +//! - Genmega +//! - HCM2 (Hitachi recycler) +//! - GSR50 (recycler) + +pub mod traits; +pub mod mock; +// pub mod puloon; +// pub mod f56; +// pub mod genmega; +// pub mod hcm2; +// pub mod gsr50; + +pub use traits::*; +pub use mock::MockDispenser; + +use napi::bindgen_prelude::*; +use napi_derive::napi; + +use crate::{DispenserDriver, error::DispenserError}; + +/// Wrapper for bill dispenser that exposes napi-rs bindings +#[napi] +pub struct BillDispenserWrapper { + inner: Box, +} + +#[napi] +impl BillDispenserWrapper { + /// Create a new bill dispenser instance + #[napi(constructor)] + pub fn new( + driver: DispenserDriver, + port: Option, + fiat_code: Option, + ) -> Result { + let _fiat = fiat_code.unwrap_or_else(|| "USD".to_string()); + + let dispenser: Box = match driver { + DispenserDriver::Mock => Box::new(MockDispenser::new()), + // TODO: Implement other drivers + _ => { + return Err(Error::from_reason(format!( + "Driver {:?} not yet implemented. Use Mock for development.", + driver + ))) + } + }; + + Ok(Self { inner: dispenser }) + } + + /// Get the driver name + #[napi(getter)] + pub fn driver_name(&self) -> String { + self.inner.driver_name().to_string() + } + + /// Connect to the dispenser + #[napi] + pub async fn connect(&mut self) -> Result<()> { + self.inner + .connect() + .await + .map_err(|e| Error::from_reason(e.to_string())) + } + + /// Disconnect from the dispenser + #[napi] + pub async fn disconnect(&mut self) -> Result<()> { + self.inner + .disconnect() + .await + .map_err(|e| Error::from_reason(e.to_string())) + } + + /// Dispense bills + #[napi] + pub async fn dispense(&mut self, denomination: u32, count: u32) -> Result { + self.inner + .dispense(denomination, count) + .await + .map_err(|e| Error::from_reason(e.to_string())) + } + + /// Reset the dispenser + #[napi] + pub async fn reset(&mut self) -> Result<()> { + self.inner + .reset() + .await + .map_err(|e| Error::from_reason(e.to_string())) + } + + /// Check if dispenser is ready + #[napi] + pub async fn is_ready(&self) -> Result { + self.inner + .is_ready() + .await + .map_err(|e| Error::from_reason(e.to_string())) + } + + /// Check if bills are present at exit + #[napi] + pub async fn bills_present(&self) -> Result { + self.inner + .bills_present() + .await + .map_err(|e| Error::from_reason(e.to_string())) + } + + /// Wait for bills to be removed + #[napi] + pub async fn wait_for_bills_removed(&self) -> Result<()> { + self.inner + .wait_for_bills_removed() + .await + .map_err(|e| Error::from_reason(e.to_string())) + } +} diff --git a/lamassu-next/packages/hal/src/dispensers/traits.rs b/lamassu-next/packages/hal/src/dispensers/traits.rs new file mode 100644 index 0000000..1f25e4c --- /dev/null +++ b/lamassu-next/packages/hal/src/dispensers/traits.rs @@ -0,0 +1,55 @@ +//! Bill dispenser trait definitions + +use async_trait::async_trait; + +use crate::error::DispenserError; + +/// Status of a cassette in the dispenser +#[derive(Debug, Clone)] +pub struct CassetteStatus { + /// Bill denomination in this cassette + pub denomination: u32, + /// Current bill count + pub count: u32, + /// Maximum capacity + pub capacity: u32, +} + +/// Unified interface for all bill dispensers +/// +/// Implementations: Puloon, F56, Genmega, HCM2, GSR50, Mock +#[async_trait] +pub trait BillDispenser: Send + Sync { + /// Get dispenser driver name (for logging/debugging) + fn driver_name(&self) -> &'static str; + + /// Connect to the dispenser + async fn connect(&mut self) -> Result<(), DispenserError>; + + /// Disconnect from the dispenser + async fn disconnect(&mut self) -> Result<(), DispenserError>; + + /// Get status of all cassettes + async fn get_cassette_status(&self) -> Result, DispenserError>; + + /// Dispense bills from a specific cassette + /// + /// Returns the number of bills actually dispensed. + async fn dispense(&mut self, denomination: u32, count: u32) -> Result; + + /// Reset the dispenser after a jam or error + async fn reset(&mut self) -> Result<(), DispenserError>; + + /// Check if dispenser is ready to dispense + async fn is_ready(&self) -> Result; + + /// Check if bills are present at the exit (for customer to take) + /// + /// Not all dispensers support this - some will always return true. + async fn bills_present(&self) -> Result; + + /// Wait for bills to be removed by customer + /// + /// Returns immediately for dispensers that don't support detection. + async fn wait_for_bills_removed(&self) -> Result<(), DispenserError>; +} diff --git a/lamassu-next/packages/hal/src/error.rs b/lamassu-next/packages/hal/src/error.rs new file mode 100644 index 0000000..8c81ff4 --- /dev/null +++ b/lamassu-next/packages/hal/src/error.rs @@ -0,0 +1,91 @@ +//! Error types for the HAL + +use thiserror::Error; + +/// Errors that can occur during bill validator operations +#[derive(Debug, Error)] +pub enum ValidatorError { + /// Failed to connect to the validator + #[error("Connection failed: {0}")] + ConnectionFailed(String), + + /// Communication error during operation + #[error("Communication error: {0}")] + CommunicationError(String), + + /// Bill was rejected by the validator + #[error("Bill rejected: {0}")] + BillRejected(String), + + /// Stacker is full + #[error("Stacker full")] + StackerFull, + + /// General hardware error + #[error("Hardware error: {0}")] + HardwareError(String), + + /// Invalid state for requested operation + #[error("Invalid state: {0}")] + InvalidState(String), + + /// Operation timed out + #[error("Operation timed out")] + Timeout, +} + +/// Errors that can occur during bill dispenser operations +#[derive(Debug, Error)] +pub enum DispenserError { + /// Failed to connect to the dispenser + #[error("Connection failed: {0}")] + ConnectionFailed(String), + + /// Communication error during operation + #[error("Communication error: {0}")] + CommunicationError(String), + + /// Not enough bills to fulfill request + #[error("Insufficient bills: need {0}, have {1}")] + InsufficientBills(u32, u32), + + /// Cassette is empty + #[error("Cassette empty: denomination {0}")] + CassetteEmpty(u32), + + /// Bill jam detected + #[error("Bill jam")] + BillJam, + + /// General hardware error + #[error("Hardware error: {0}")] + HardwareError(String), + + /// Operation timed out + #[error("Operation timed out")] + Timeout, +} + +/// Errors that can occur during printer operations +#[derive(Debug, Error)] +pub enum PrinterError { + /// Failed to connect to the printer + #[error("Connection failed: {0}")] + ConnectionFailed(String), + + /// Communication error during operation + #[error("Communication error: {0}")] + CommunicationError(String), + + /// Printer is out of paper + #[error("Out of paper")] + OutOfPaper, + + /// Paper jam detected + #[error("Paper jam")] + PaperJam, + + /// General hardware error + #[error("Hardware error: {0}")] + HardwareError(String), +} diff --git a/lamassu-next/packages/hal/src/lib.rs b/lamassu-next/packages/hal/src/lib.rs new file mode 100644 index 0000000..6fff68b --- /dev/null +++ b/lamassu-next/packages/hal/src/lib.rs @@ -0,0 +1,94 @@ +//! Lamassu Hardware Abstraction Layer +//! +//! This crate provides Rust implementations of hardware drivers for bill validators, +//! dispensers, printers, and other ATM peripherals. These are exposed to Node.js +//! via napi-rs bindings. +//! +//! # Supported Hardware +//! +//! ## Bill Validators +//! - ID003 (JCM) - Default validator protocol +//! - CCNET (CashCode) +//! - MEI CashFlow SC +//! - MEI BNR Advance +//! - Genmega +//! - HCM2 (Hitachi recycler) +//! - GSR50 (recycler) +//! +//! ## Bill Dispensers +//! - Puloon LCDM series +//! - Fujitsu F53/F56 +//! - Genmega +//! - HCM2 (Hitachi recycler) +//! - GSR50 (recycler) +//! +//! ## Printers +//! - Nippon (ESC/POS) +//! - Zebra (ZPL) +//! - Genmega +//! +//! # Usage +//! +//! ```typescript +//! import { BillValidatorWrapper, ValidatorDriver } from '@lamassu/hal' +//! +//! const validator = new BillValidatorWrapper(ValidatorDriver.Id003, '/dev/ttyUSB0', 'USD') +//! await validator.connect() +//! await validator.enable() +//! ``` + +#![deny(unsafe_code)] +#![warn(missing_docs)] +#![warn(clippy::all)] + +use napi_derive::napi; + +pub mod error; +pub mod validators; +pub mod dispensers; +// pub mod printer; +// pub mod scanner; +// pub mod leds; +// pub mod nfc; + +/// Supported bill validator drivers +#[napi] +pub enum ValidatorDriver { + /// JCM ID-003 protocol (default) + Id003, + /// CashCode CCNET protocol + Ccnet, + /// MEI CashFlow SC + CashflowSc, + /// MEI BNR Advance + BnrAdvance, + /// Genmega validator + Genmega, + /// Hitachi HCM2 recycler + Hcm2, + /// GSR50 recycler + Gsr50, + /// Mock validator for testing + Mock, +} + +/// Supported bill dispenser drivers +#[napi] +pub enum DispenserDriver { + /// Puloon LCDM series + Puloon, + /// Fujitsu F53/F56 + F56, + /// Genmega dispenser + Genmega, + /// Hitachi HCM2 recycler + Hcm2, + /// GSR50 recycler + Gsr50, + /// Mock dispenser for testing + Mock, +} + +// Re-exports for convenience +pub use validators::BillValidatorWrapper; +pub use dispensers::BillDispenserWrapper; diff --git a/lamassu-next/packages/hal/src/validators/mock.rs b/lamassu-next/packages/hal/src/validators/mock.rs new file mode 100644 index 0000000..dbdbe33 --- /dev/null +++ b/lamassu-next/packages/hal/src/validators/mock.rs @@ -0,0 +1,147 @@ +//! Mock bill validator for testing + +use async_trait::async_trait; +use tokio::sync::broadcast; +use std::time::{SystemTime, UNIX_EPOCH}; + +use crate::error::ValidatorError; +use super::traits::{BillValidator, BillEvent, BillEventType}; + +/// Mock bill validator for development and testing +pub struct MockValidator { + connected: bool, + enabled: bool, + escrowed_bill: Option, + bill_count: u32, + event_tx: broadcast::Sender, + fiat_code: String, +} + +impl MockValidator { + /// Create a new mock validator + pub fn new() -> Self { + let (event_tx, _) = broadcast::channel(16); + Self { + connected: false, + enabled: false, + escrowed_bill: None, + bill_count: 0, + event_tx, + fiat_code: "USD".to_string(), + } + } + + /// Simulate a bill being inserted (for testing) + pub fn simulate_bill(&mut self, denomination: u32) { + if self.enabled { + self.escrowed_bill = Some(denomination); + let _ = self.event_tx.send(BillEvent { + denomination, + currency: self.fiat_code.clone(), + timestamp: SystemTime::now() + .duration_since(UNIX_EPOCH) + .unwrap() + .as_secs(), + event_type: BillEventType::Inserted, + }); + } + } + + fn current_timestamp(&self) -> u64 { + SystemTime::now() + .duration_since(UNIX_EPOCH) + .unwrap() + .as_secs() + } +} + +impl Default for MockValidator { + fn default() -> Self { + Self::new() + } +} + +#[async_trait] +impl BillValidator for MockValidator { + fn driver_name(&self) -> &'static str { + "mock" + } + + async fn connect(&mut self) -> Result<(), ValidatorError> { + tokio::time::sleep(tokio::time::Duration::from_millis(100)).await; + self.connected = true; + tracing::info!("MockValidator connected"); + Ok(()) + } + + async fn disconnect(&mut self) -> Result<(), ValidatorError> { + self.connected = false; + self.enabled = false; + tracing::info!("MockValidator disconnected"); + Ok(()) + } + + async fn enable(&mut self) -> Result<(), ValidatorError> { + if !self.connected { + return Err(ValidatorError::ConnectionFailed("Not connected".into())); + } + self.enabled = true; + tracing::info!("MockValidator enabled"); + Ok(()) + } + + async fn disable(&mut self) -> Result<(), ValidatorError> { + self.enabled = false; + tracing::info!("MockValidator disabled"); + Ok(()) + } + + async fn accept(&mut self) -> Result<(), ValidatorError> { + match self.escrowed_bill.take() { + Some(denomination) => { + self.bill_count += 1; + let _ = self.event_tx.send(BillEvent { + denomination, + currency: self.fiat_code.clone(), + timestamp: self.current_timestamp(), + event_type: BillEventType::Stacked, + }); + tracing::info!("MockValidator accepted ${}", denomination); + Ok(()) + } + None => Err(ValidatorError::InvalidState("No bill escrowed".into())), + } + } + + async fn reject(&mut self) -> Result<(), ValidatorError> { + match self.escrowed_bill.take() { + Some(denomination) => { + let _ = self.event_tx.send(BillEvent { + denomination, + currency: self.fiat_code.clone(), + timestamp: self.current_timestamp(), + event_type: BillEventType::Rejected, + }); + tracing::info!("MockValidator rejected ${}", denomination); + Ok(()) + } + None => Err(ValidatorError::InvalidState("No bill escrowed".into())), + } + } + + async fn get_bill_count(&self) -> Result { + Ok(self.bill_count) + } + + fn subscribe(&self) -> broadcast::Receiver { + self.event_tx.subscribe() + } + + async fn run(&mut self) -> Result<(), ValidatorError> { + // Mock validator doesn't need continuous polling + // In real implementation, this would poll the hardware + loop { + tokio::time::sleep(tokio::time::Duration::from_secs(1)).await; + } + } +} diff --git a/lamassu-next/packages/hal/src/validators/mod.rs b/lamassu-next/packages/hal/src/validators/mod.rs new file mode 100644 index 0000000..65e9e4c --- /dev/null +++ b/lamassu-next/packages/hal/src/validators/mod.rs @@ -0,0 +1,119 @@ +//! Bill validator drivers +//! +//! This module contains implementations for various bill validator protocols: +//! - ID003 (JCM) - Default protocol +//! - CCNET (CashCode) +//! - MEI CashFlow SC +//! - MEI BNR Advance +//! - Genmega +//! - HCM2 (Hitachi recycler) +//! - GSR50 (recycler) + +pub mod traits; +pub mod mock; +// pub mod id003; +// pub mod ccnet; +// pub mod mei_cashflow; +// pub mod genmega; +// pub mod hcm2; +// pub mod gsr50; + +pub use traits::*; +pub use mock::MockValidator; + +use napi::bindgen_prelude::*; +use napi_derive::napi; + +use crate::{ValidatorDriver, error::ValidatorError}; + +/// Wrapper for bill validator that exposes napi-rs bindings +#[napi] +pub struct BillValidatorWrapper { + inner: Box, +} + +#[napi] +impl BillValidatorWrapper { + /// Create a new bill validator instance + #[napi(constructor)] + pub fn new( + driver: ValidatorDriver, + port: Option, + fiat_code: Option, + ) -> Result { + let _fiat = fiat_code.unwrap_or_else(|| "USD".to_string()); + + let validator: Box = match driver { + ValidatorDriver::Mock => Box::new(MockValidator::new()), + // TODO: Implement other drivers + _ => { + return Err(Error::from_reason(format!( + "Driver {:?} not yet implemented. Use Mock for development.", + driver + ))) + } + }; + + Ok(Self { inner: validator }) + } + + /// Get the driver name + #[napi(getter)] + pub fn driver_name(&self) -> String { + self.inner.driver_name().to_string() + } + + /// Connect to the validator + #[napi] + pub async fn connect(&mut self) -> Result<()> { + self.inner + .connect() + .await + .map_err(|e| Error::from_reason(e.to_string())) + } + + /// Disconnect from the validator + #[napi] + pub async fn disconnect(&mut self) -> Result<()> { + self.inner + .disconnect() + .await + .map_err(|e| Error::from_reason(e.to_string())) + } + + /// Enable bill acceptance + #[napi] + pub async fn enable(&mut self) -> Result<()> { + self.inner + .enable() + .await + .map_err(|e| Error::from_reason(e.to_string())) + } + + /// Disable bill acceptance + #[napi] + pub async fn disable(&mut self) -> Result<()> { + self.inner + .disable() + .await + .map_err(|e| Error::from_reason(e.to_string())) + } + + /// Accept the currently escrowed bill + #[napi] + pub async fn accept(&mut self) -> Result<()> { + self.inner + .accept() + .await + .map_err(|e| Error::from_reason(e.to_string())) + } + + /// Reject the currently escrowed bill + #[napi] + pub async fn reject(&mut self) -> Result<()> { + self.inner + .reject() + .await + .map_err(|e| Error::from_reason(e.to_string())) + } +} diff --git a/lamassu-next/packages/hal/src/validators/traits.rs b/lamassu-next/packages/hal/src/validators/traits.rs new file mode 100644 index 0000000..aa735cb --- /dev/null +++ b/lamassu-next/packages/hal/src/validators/traits.rs @@ -0,0 +1,73 @@ +//! Bill validator trait definitions + +use async_trait::async_trait; +use tokio::sync::broadcast; + +use crate::error::ValidatorError; + +/// Event types for bill validator operations +#[derive(Debug, Clone)] +pub enum BillEventType { + /// Bill detected and validated + Inserted, + /// Bill accepted into stacker + Accepted, + /// Bill rejected (returned to customer) + Rejected, + /// Bill successfully stacked + Stacked, + /// Bill jammed + Jammed, +} + +/// Event emitted by bill validators +#[derive(Debug, Clone)] +pub struct BillEvent { + /// Denomination of the bill + pub denomination: u32, + /// Currency code (e.g., "USD") + pub currency: String, + /// Unix timestamp + pub timestamp: u64, + /// Type of event + pub event_type: BillEventType, +} + +/// Unified interface for all bill validators +/// +/// Implementations: ID003, CCNET, MEI CashFlow, MEI BNR, Genmega, HCM2, GSR50, Mock +#[async_trait] +pub trait BillValidator: Send + Sync { + /// Get validator driver name (for logging/debugging) + fn driver_name(&self) -> &'static str; + + /// Connect to the validator + async fn connect(&mut self) -> Result<(), ValidatorError>; + + /// Disconnect from the validator + async fn disconnect(&mut self) -> Result<(), ValidatorError>; + + /// Enable bill acceptance + async fn enable(&mut self) -> Result<(), ValidatorError>; + + /// Disable bill acceptance + async fn disable(&mut self) -> Result<(), ValidatorError>; + + /// Accept the currently held bill into stacker + async fn accept(&mut self) -> Result<(), ValidatorError>; + + /// Reject the currently held bill + async fn reject(&mut self) -> Result<(), ValidatorError>; + + /// Get current bill count in stacker (if supported) + async fn get_bill_count(&self) -> Result; + + /// Subscribe to bill events + fn subscribe(&self) -> broadcast::Receiver; + + /// Run the validator state machine (poll for events) + /// + /// Most validators need continuous polling. This method should be + /// called in a separate task and will run indefinitely. + async fn run(&mut self) -> Result<(), ValidatorError>; +} diff --git a/lamassu-next/packages/lightning/package.json b/lamassu-next/packages/lightning/package.json new file mode 100644 index 0000000..7f22f37 --- /dev/null +++ b/lamassu-next/packages/lightning/package.json @@ -0,0 +1,32 @@ +{ + "name": "@lamassu/lightning", + "version": "0.1.0", + "description": "Lightning.Pub client for Nostr-native Lightning operations", + "type": "module", + "main": "./dist/index.js", + "types": "./dist/index.d.ts", + "exports": { + ".": { + "types": "./dist/index.d.ts", + "import": "./dist/index.js" + } + }, + "scripts": { + "build": "tsc", + "dev": "tsc --watch", + "test": "vitest run", + "test:watch": "vitest", + "typecheck": "tsc --noEmit", + "lint": "eslint src/" + }, + "dependencies": { + "@lamassu/nostr-client": "workspace:*", + "@lamassu/clink": "workspace:*", + "nostr-tools": "^2.10.0" + }, + "devDependencies": { + "@types/node": "^22.0.0", + "typescript": "^5.7.0", + "vitest": "^2.1.0" + } +} diff --git a/lamassu-next/packages/nostr-client/package.json b/lamassu-next/packages/nostr-client/package.json new file mode 100644 index 0000000..ea59877 --- /dev/null +++ b/lamassu-next/packages/nostr-client/package.json @@ -0,0 +1,35 @@ +{ + "name": "@lamassu/nostr-client", + "version": "0.1.0", + "description": "Nostr client library for Lamassu ATM", + "type": "module", + "main": "./dist/index.js", + "types": "./dist/index.d.ts", + "exports": { + ".": { + "types": "./dist/index.d.ts", + "import": "./dist/index.js" + } + }, + "scripts": { + "build": "tsc", + "dev": "tsc --watch", + "test": "vitest run", + "test:watch": "vitest", + "typecheck": "tsc --noEmit", + "lint": "eslint src/", + "validate-schemas": "tsx scripts/validate-schemas.ts" + }, + "dependencies": { + "nostr-tools": "^2.10.0" + }, + "devDependencies": { + "@types/node": "^22.0.0", + "typescript": "^5.7.0", + "vitest": "^2.1.0", + "tsx": "^4.19.0" + }, + "peerDependencies": { + "typescript": "^5.0.0" + } +} diff --git a/lamassu-next/packages/state-machine/package.json b/lamassu-next/packages/state-machine/package.json new file mode 100644 index 0000000..6a8e483 --- /dev/null +++ b/lamassu-next/packages/state-machine/package.json @@ -0,0 +1,30 @@ +{ + "name": "@lamassu/state-machine", + "version": "0.1.0", + "description": "XState v5 state machine for ATM transaction flows", + "type": "module", + "main": "./dist/index.js", + "types": "./dist/index.d.ts", + "exports": { + ".": { + "types": "./dist/index.d.ts", + "import": "./dist/index.js" + } + }, + "scripts": { + "build": "tsc", + "dev": "tsc --watch", + "test": "vitest run", + "test:watch": "vitest", + "typecheck": "tsc --noEmit", + "lint": "eslint src/" + }, + "dependencies": { + "xstate": "^5.18.0" + }, + "devDependencies": { + "@types/node": "^22.0.0", + "typescript": "^5.7.0", + "vitest": "^2.1.0" + } +} diff --git a/lamassu-next/packages/ui-shared/package.json b/lamassu-next/packages/ui-shared/package.json new file mode 100644 index 0000000..7122e98 --- /dev/null +++ b/lamassu-next/packages/ui-shared/package.json @@ -0,0 +1,40 @@ +{ + "name": "@lamassu/ui-shared", + "version": "0.1.0", + "description": "Shared Vue 3 components for Lamassu ATM and dashboard", + "type": "module", + "main": "./dist/index.js", + "types": "./dist/index.d.ts", + "exports": { + ".": { + "types": "./dist/index.d.ts", + "import": "./dist/index.js" + }, + "./components/*": { + "types": "./dist/components/*.d.ts", + "import": "./dist/components/*.js" + } + }, + "scripts": { + "build": "vite build", + "dev": "vite build --watch", + "test": "vitest run", + "test:watch": "vitest", + "typecheck": "vue-tsc --noEmit", + "lint": "eslint src/" + }, + "dependencies": { + "vue": "^3.5.0" + }, + "devDependencies": { + "@types/node": "^22.0.0", + "@vitejs/plugin-vue": "^5.2.0", + "typescript": "^5.7.0", + "vite": "^6.0.0", + "vitest": "^2.1.0", + "vue-tsc": "^2.1.0" + }, + "peerDependencies": { + "vue": "^3.5.0" + } +} diff --git a/lamassu-next/pnpm-workspace.yaml b/lamassu-next/pnpm-workspace.yaml new file mode 100644 index 0000000..e9b0dad --- /dev/null +++ b/lamassu-next/pnpm-workspace.yaml @@ -0,0 +1,3 @@ +packages: + - 'apps/*' + - 'packages/*' diff --git a/lamassu-next/tsconfig.json b/lamassu-next/tsconfig.json new file mode 100644 index 0000000..f0298f0 --- /dev/null +++ b/lamassu-next/tsconfig.json @@ -0,0 +1,35 @@ +{ + "compilerOptions": { + "target": "ES2022", + "module": "ESNext", + "moduleResolution": "bundler", + "lib": ["ES2022", "DOM", "DOM.Iterable"], + "strict": true, + "strictNullChecks": true, + "noUncheckedIndexedAccess": true, + "noImplicitOverride": true, + "noImplicitReturns": true, + "noFallthroughCasesInSwitch": true, + "noPropertyAccessFromIndexSignature": true, + "exactOptionalPropertyTypes": true, + "forceConsistentCasingInFileNames": true, + "verbatimModuleSyntax": true, + "skipLibCheck": true, + "declaration": true, + "declarationMap": true, + "sourceMap": true, + "esModuleInterop": true, + "resolveJsonModule": true, + "isolatedModules": true, + "paths": { + "@lamassu/nostr-client": ["./packages/nostr-client/src"], + "@lamassu/clink": ["./packages/clink/src"], + "@lamassu/state-machine": ["./packages/state-machine/src"], + "@lamassu/lightning": ["./packages/lightning/src"], + "@lamassu/cashu": ["./packages/cashu/src"], + "@lamassu/ui-shared": ["./packages/ui-shared/src"], + "@lamassu/hal": ["./packages/hal"] + } + }, + "exclude": ["node_modules", "dist", "**/dist", "**/*.test.ts", "**/*.spec.ts"] +} diff --git a/lamassu-next/turbo.json b/lamassu-next/turbo.json new file mode 100644 index 0000000..d6d2fb5 --- /dev/null +++ b/lamassu-next/turbo.json @@ -0,0 +1,23 @@ +{ + "$schema": "https://turbo.build/schema.json", + "tasks": { + "build": { + "dependsOn": ["^build"], + "outputs": ["dist/**", ".next/**", "!.next/cache/**"] + }, + "dev": { + "cache": false, + "persistent": true + }, + "test": { + "dependsOn": ["build"], + "outputs": ["coverage/**"] + }, + "typecheck": { + "dependsOn": ["^build"] + }, + "lint": { + "dependsOn": ["^build"] + } + } +}