Skip to content
Merged
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: 14 additions & 16 deletions ARCHITECTURE.md
Original file line number Diff line number Diff line change
Expand Up @@ -21,7 +21,8 @@ graph TD
end

subgraph "Integration Layer"
SorobanUtils[Soroban Utils (src/utils/soroban.ts)]
ContractsService[Contracts Service (src/lib/backend/services/contracts.ts)]
SorobanUtils[Soroban Utils (src/utils/soroban.ts) - Addresses Only]
Wallet[Wallet Adapter (Freighter)]
end

Expand All @@ -33,9 +34,9 @@ graph TD
User -->|Interacts| Page
Page -->|Renders| Comp
Comp -->|Uses| Hooks
Hooks -->|Calls| SorobanUtils
SorobanUtils -->|Connects| Wallet
SorobanUtils -->|RPC Calls| Stellar
Hooks -->|Calls| ContractsService
ContractsService -->|RPC Calls| Stellar
ContractsService -->|Gets Addresses| SorobanUtils
Wallet -->|Signs Tx| Stellar
Stellar -->|Executes| Contracts
```
Expand Down Expand Up @@ -84,20 +85,16 @@ A secondary market for trading active commitments.
sequenceDiagram
participant User
participant UI as Create Page
participant Utils as Soroban Utils
participant Wallet as Freighter Wallet
participant Service as Contracts Service
participant Chain as Stellar Network

User->>UI: Select Commitment Type
User->>UI: Configure Amount & Duration
User->>UI: Click "Create Commitment"
UI->>Utils: Prepare Transaction (create_commitment)
Utils->>Wallet: Request Signature
Wallet->>User: Prompt Approval
User->>Wallet: Approve Transaction
Wallet->>Chain: Submit Signed Transaction
Chain-->>Utils: Transaction Hash
Utils-->>UI: Success / Failure
UI->>Service: createCommitmentOnChain()
Service->>Chain: Invoke Contract (create_commitment)
Chain-->>Service: Transaction Hash & Result
Service-->>UI: Success / Failure
UI->>User: Show Success Modal
```

Expand All @@ -112,9 +109,10 @@ The application interacts with three primary contracts:

### Wallet Integration

- **Provider**: `@stellar/freighter-api`
- **Usage**: Used to sign transactions for creating commitments and listing items on the marketplace.
- **Status**: Currently in development (see `src/utils/soroban.ts`).
- **Provider**: Server-side signing using configured Soroban keys
- **Usage**: The contracts service handles transaction signing via configured server keys
- **Implementation**: See `src/lib/backend/services/contracts.ts` for chain interaction logic
- **Configuration**: Contract addresses are managed in `src/utils/soroban.ts`

## 🛡 Security & Performance

Expand Down
37 changes: 37 additions & 0 deletions docs/backend-changelog.md
Original file line number Diff line number Diff line change
Expand Up @@ -112,3 +112,40 @@ Copy this block for each new change:
### Migration Notes

- First true backend contract break after this date must be added as a new dated entry.

## 2026-05-28 — Compliance score scaling consistency fix

- **Status:** Released
- **Effective Date:** 2026-05-28
- **API Surface:** Contracts service (src/lib/backend/services/contracts.ts)
- **Change Type:** Bug fix (data consistency)
- **Owner:** Frontend team
- **Tracking:** Internal issue

### What Changed

- Fixed compliance score scaling asymmetry in the contracts service
- Previously: `recordAttestationOnChain` divided scores by 100 before sending on-chain, but `parseChainCommitment` and `parseAttestationResult` did not re-scale when reading back
- Now: Both write and read paths consistently use ANALYTICS_SCALE (100) for scaling
- Added comprehensive documentation for the ANALYTICS_SCALE constant
- Added round-trip scaling tests covering boundary values (0, 50, 100)

### Frontend Impact

- Compliance scores displayed to users will now show correct values (e.g., 85 instead of 0.85)
- Previously corrupted scores from blockchain reads will now display correctly
- No API contract changes - this is an internal implementation fix

### Required Frontend Action

- [x] Fix scaling in parseChainCommitment (multiply by ANALYTICS_SCALE)
- [x] Fix scaling in parseAttestationResult (multiply by ANALYTICS_SCALE)
- [x] Add documentation for ANALYTICS_SCALE constant
- [x] Add round-trip scaling tests with boundary values

### Migration Notes

- This fix corrects a data consistency bug where compliance scores were incorrectly displayed
- The scaling convention is now: divide by 100 on write, multiply by 100 on read
- Example: Score 85 → 0.85 on-chain → 85 in application (correct round-trip)
- Tests verify no float precision loss for typical scores (0, 25, 50, 75, 85, 92, 100)
14 changes: 12 additions & 2 deletions src/lib/backend/services/contracts.ts
Original file line number Diff line number Diff line change
Expand Up @@ -121,6 +121,16 @@ interface ContractInvocationResult {
txHash?: string;
}

/**
* Scaling factor for compliance scores sent to/from the blockchain.
* Compliance scores are stored on-chain as integers in the range [0, 100]
* to avoid floating-point precision issues. When writing to the chain,
* scores are divided by this scale; when reading from the chain, they
* are multiplied by this scale to restore the original value.
*
* Example: A compliance score of 85 is stored as 0.85 on-chain,
* and read back as 85 in the application.
*/
const ANALYTICS_SCALE = 100;

function getRpcUrl(): string {
Expand Down Expand Up @@ -470,7 +480,7 @@ function parseChainCommitment(value: unknown): ChainCommitment {
asset: asString(raw.asset),
amount: asString(raw.amount, "0"),
status: normalizeStatus(raw.status),
complianceScore: asNumber(raw.complianceScore ?? raw.compliance_score),
complianceScore: asNumber(raw.complianceScore ?? raw.compliance_score) * ANALYTICS_SCALE,
currentValue: asString(
raw.currentValue ?? raw.current_value ?? raw.amount,
"0",
Expand Down Expand Up @@ -534,7 +544,7 @@ function parseAttestationResult(
return {
attestationId,
commitmentId,
complianceScore: asNumber(raw.complianceScore ?? raw.compliance_score),
complianceScore: asNumber(raw.complianceScore ?? raw.compliance_score) * ANALYTICS_SCALE,
violation: Boolean(raw.violation),
feeEarned: asString(raw.feeEarned ?? raw.fees_earned, "0"),
recordedAt:
Expand Down
38 changes: 38 additions & 0 deletions src/utils/soroban.test.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,38 @@
import { describe, it, expect } from 'vitest';
import * as soroban from './soroban';

describe('soroban.ts - No stub functions', () => {
it('should not export connectWallet stub function', () => {
expect(soroban).not.toHaveProperty('connectWallet');
});

it('should not export callContract stub function', () => {
expect(soroban).not.toHaveProperty('callContract');
});

it('should not export readContract stub function', () => {
expect(soroban).not.toHaveProperty('readContract');
});

it('should export contractAddresses getters', () => {
expect(soroban).toHaveProperty('contractAddresses');
expect(typeof soroban.contractAddresses).toBe('object');
expect(soroban.contractAddresses).toHaveProperty('commitmentNFT');
expect(soroban.contractAddresses).toHaveProperty('commitmentCore');
expect(soroban.contractAddresses).toHaveProperty('attestationEngine');
});

it('should export network configuration constants', () => {
expect(soroban).toHaveProperty('rpcUrl');
expect(typeof soroban.rpcUrl).toBe('string');
expect(soroban).toHaveProperty('networkPassphrase');
expect(typeof soroban.networkPassphrase).toBe('string');
});

it('should ensure contractAddresses getters return strings', () => {
const addresses = soroban.contractAddresses;
expect(typeof addresses.commitmentNFT).toBe('string');
expect(typeof addresses.commitmentCore).toBe('string');
expect(typeof addresses.attestationEngine).toBe('string');
});
});
43 changes: 8 additions & 35 deletions src/utils/soroban.ts
Original file line number Diff line number Diff line change
@@ -1,18 +1,17 @@
/**
* Soroban Utility Functions
*
* This module handles all interactions with the Stellar network and Soroban smart contracts.
* It uses the @stellar/stellar-sdk and @stellar/freighter-api libraries.
* This module provides contract address configuration and network constants.
* All actual blockchain interactions are handled by the contracts service.
*
* CURRENT STATUS:
* - Contract addresses are loaded from environment variables.
* - Wallet connection and contract interaction functions are placeholders.
* - TODO: Implement `connectWallet`, `callContract`, and `readContract` using the SDK.
* SINGLE SOURCE OF TRUTH:
* - Contract addresses: This module (via contractAddresses getters)
* - Chain interactions: src/lib/backend/services/contracts.ts
*
* For wallet connection, contract calls, and contract reads, use:
* @see src/lib/backend/services/contracts.ts
*/

// Soroban utility functions and configuration
// TODO: Implement actual Soroban contract interactions

export const rpcUrl =
process.env.NEXT_PUBLIC_SOROBAN_RPC_URL ||
"https://soroban-testnet.stellar.org:443";
Expand Down Expand Up @@ -49,29 +48,3 @@ export const contractAddresses = {
}
},
};

// TODO: Implement wallet connection
export async function connectWallet() {
// Placeholder for wallet connection logic
throw new Error("Wallet connection not implemented");
}

// TODO: Implement contract calls
export async function callContract(
contractAddress: string, // eslint-disable-line @typescript-eslint/no-unused-vars
functionName: string, // eslint-disable-line @typescript-eslint/no-unused-vars
args: any[], // eslint-disable-line @typescript-eslint/no-unused-vars, @typescript-eslint/no-explicit-any
) {
// Placeholder for contract call logic
throw new Error("Contract calls not implemented");
}

// TODO: Implement contract reads
export async function readContract(
contractAddress: string, // eslint-disable-line @typescript-eslint/no-unused-vars
functionName: string, // eslint-disable-line @typescript-eslint/no-unused-vars
args: any[], // eslint-disable-line @typescript-eslint/no-unused-vars, @typescript-eslint/no-explicit-any
) {
// Placeholder for contract read logic
throw new Error("Contract reads not implemented");
}
Loading
Loading