Skip to content
Open
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
29 changes: 29 additions & 0 deletions .github/workflows/ci.yml
Original file line number Diff line number Diff line change
Expand Up @@ -59,3 +59,32 @@ jobs:
- name: test
run: cargo test --all
working-directory: apps/contracts

audit:
name: Security Audit (pnpm + cargo)
runs-on: ubuntu-latest
steps:
- uses: actions/checkout@v4
- uses: pnpm/action-setup@v4
with:
version: 10
- uses: actions/setup-node@v4
with:
node-version: 22
cache: pnpm
- run: pnpm install --frozen-lockfile
- run: pnpm audit --audit-level high
- uses: actions-rs/audit-check@v1
with:
token: ${{ secrets.GITHUB_TOKEN }}

secrets:
name: Secret Scanning
runs-on: ubuntu-latest
steps:
- uses: actions/checkout@v4
with:
fetch-depth: 0
- uses: gitleaks/gitleaks-action@v2
env:
GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }}
4 changes: 4 additions & 0 deletions apps/web/next-env.d.ts
Original file line number Diff line number Diff line change
@@ -1,2 +1,6 @@
/// <reference types="next" />
/// <reference types="next/image-types/global" />
/// <reference path="./.next/types/routes.d.ts" />

// NOTE: This file should not be edited
// see https://nextjs.org/docs/app/api-reference/config/typescript for more information.
3 changes: 2 additions & 1 deletion apps/web/package.json
Original file line number Diff line number Diff line change
Expand Up @@ -11,12 +11,13 @@
"clean": "rm -rf .next"
},
"dependencies": {
"@noble/ed25519": "^3.1.0",
"@solarproof/stellar": "workspace:*",
"@stellar/stellar-sdk": "^13.1.0",
"@supabase/supabase-js": "^2.46.2",
"@tanstack/react-query": "^5.62.7",
"lucide-react": "^0.468.0",
"next": "15.1.3",
"next": "15.5.18",
"react": "^19.0.0",
"react-dom": "^19.0.0",
"recharts": "^2.14.1",
Expand Down
25 changes: 15 additions & 10 deletions apps/web/src/app/api/readings/route.ts
Original file line number Diff line number Diff line change
Expand Up @@ -32,7 +32,7 @@ export async function POST(req: NextRequest) {
const db = createServiceClient()

// Fetch meter + cooperative
const { data: meter } = await db
const { data: meter } = await (db as any)
.from('meters')
.select('id, pubkey_hex, cooperative_id, cooperatives(admin_address)')
.eq('id', meter_id)
Expand All @@ -48,18 +48,23 @@ export async function POST(req: NextRequest) {
const readingHash = computeReadingHash(meter_id, kwhStroops, BigInt(timestamp))

// Verify Ed25519 signature
const sigValid = await verify(
Buffer.from(signature_hex, 'hex'),
readingHash,
Buffer.from(meter.pubkey_hex, 'hex')
).catch(() => false)
let sigValid = false
try {
sigValid = verify(
Buffer.from(signature_hex, 'hex'),
readingHash,
Buffer.from((meter as any).pubkey_hex, 'hex')
)
} catch (err) {
sigValid = false
}

if (!sigValid) {
return NextResponse.json({ error: 'Invalid meter signature' }, { status: 401 })
}

// Persist reading
const { data: reading, error: readingErr } = await db
const { data: reading, error: readingErr } = await (db as any)
.from('readings')
.insert({
meter_id,
Expand Down Expand Up @@ -88,7 +93,7 @@ export async function POST(req: NextRequest) {
meterId: meter_id,
timestampUnix: BigInt(timestamp),
})
await db.from('readings').update({ anchored: true, anchor_tx_hash: anchorTxHash }).eq('id', reading.id)
await (db as any).from('readings').update({ anchored: true, anchor_tx_hash: anchorTxHash }).eq('id', reading.id)
} catch (err) {
const message = err instanceof Error ? err.message : 'Anchor failed'
return NextResponse.json({ error: message, reading_id: reading.id }, { status: 500 })
Expand All @@ -101,8 +106,8 @@ export async function POST(req: NextRequest) {
if (!recipient) throw new Error('No cooperative admin address')

const mintTxHash = await mintCertificates(recipient, kwh)
await db.from('readings').update({ minted: true, mint_tx_hash: mintTxHash }).eq('id', reading.id)
await db.from('certificates').insert({
await (db as any).from('readings').update({ minted: true, mint_tx_hash: mintTxHash }).eq('id', reading.id)
await (db as any).from('certificates').insert({
cooperative_id: meter.cooperative_id,
reading_id: reading.id,
reading_hash: readingHash.toString('hex'),
Expand Down
36 changes: 18 additions & 18 deletions apps/web/src/app/api/verify/route.ts
Original file line number Diff line number Diff line change
Expand Up @@ -16,7 +16,7 @@ export async function GET(req: NextRequest) {
const db = createServiceClient()

// Try certificate ID first, then reading_hash, then mint_tx_hash
const { data: cert } = await db
const { data: cert } = await (db as any)
.from('certificates')
.select('*')
.or(`id.eq.${id},reading_hash.eq.${id},mint_tx_hash.eq.${id}`)
Expand All @@ -27,34 +27,34 @@ export async function GET(req: NextRequest) {
}

// Fetch the associated reading for full audit trail
const { data: reading } = await db
const { data: reading } = await (db as any)
.from('readings')
.select('*')
.eq('id', cert.reading_id)
.eq('id', (cert as any).reading_id)
.single()

const chain = {
certificate: {
id: cert.id,
kwh: cert.kwh,
issued_at: cert.issued_at,
retired: cert.retired,
retired_at: cert.retired_at,
retired_by: cert.retired_by,
id: (cert as any).id,
kwh: (cert as any).kwh,
issued_at: (cert as any).issued_at,
retired: (cert as any).retired,
retired_at: (cert as any).retired_at,
retired_by: (cert as any).retired_by,
},
on_chain: {
anchor_tx: cert.anchor_tx_hash,
anchor_explorer: `https://stellar.expert/explorer/testnet/tx/${cert.anchor_tx_hash}`,
mint_tx: cert.mint_tx_hash,
mint_explorer: `https://stellar.expert/explorer/testnet/tx/${cert.mint_tx_hash}`,
anchor_tx: (cert as any).anchor_tx_hash,
anchor_explorer: `https://stellar.expert/explorer/testnet/tx/${(cert as any).anchor_tx_hash}`,
mint_tx: (cert as any).mint_tx_hash,
mint_explorer: `https://stellar.expert/explorer/testnet/tx/${(cert as any).mint_tx_hash}`,
},
meter_proof: reading
? {
meter_id: reading.meter_id,
reading_hash: reading.reading_hash,
signature_hex: reading.signature_hex,
kwh: reading.kwh,
timestamp: reading.timestamp,
meter_id: (reading as any).meter_id,
reading_hash: (reading as any).reading_hash,
signature_hex: (reading as any).signature_hex,
kwh: (reading as any).kwh,
timestamp: (reading as any).timestamp,
verified: true,
}
: null,
Expand Down
5 changes: 2 additions & 3 deletions apps/web/src/lib/stellar.ts
Original file line number Diff line number Diff line change
@@ -1,13 +1,12 @@
import { Keypair, TransactionBuilder, Networks, BASE_FEE, Contract } from '@stellar/stellar-sdk'
import { SorobanRpc } from '@stellar/stellar-sdk'
import { Keypair, TransactionBuilder, Networks, BASE_FEE, Contract, rpc } from '@stellar/stellar-sdk'
import { kwhToStroops, amountToScVal, addressToScVal, bytesToScVal } from '@solarproof/stellar'
import { nativeToScVal } from '@stellar/stellar-sdk'

const NETWORK_PASSPHRASE = Networks.TESTNET
const RPC_URL = 'https://soroban-testnet.stellar.org'

function getServer() {
return new SorobanRpc.Server(RPC_URL)
return new rpc.Server(RPC_URL)
}

async function submitTx(tx: ReturnType<typeof TransactionBuilder.prototype.build>, signer: Keypair) {
Expand Down
32 changes: 27 additions & 5 deletions apps/web/tsconfig.json
Original file line number Diff line number Diff line change
Expand Up @@ -2,14 +2,36 @@
"extends": "../../tsconfig.base.json",
"compilerOptions": {
"target": "ES2022",
"lib": ["dom", "dom.iterable", "ES2022"],
"lib": [
"dom",
"dom.iterable",
"ES2022"
],
"module": "ESNext",
"moduleResolution": "Bundler",
"jsx": "preserve",
"incremental": true,
"plugins": [{ "name": "next" }],
"paths": { "@/*": ["./src/*"] }
"plugins": [
{
"name": "next"
}
],
"paths": {
"@/*": [
"./src/*"
]
},
"allowJs": true,
"noEmit": true,
"isolatedModules": true
},
"include": ["next-env.d.ts", "**/*.ts", "**/*.tsx", ".next/types/**/*.ts"],
"exclude": ["node_modules"]
"include": [
"next-env.d.ts",
"**/*.ts",
"**/*.tsx",
".next/types/**/*.ts"
],
"exclude": [
"node_modules"
]
}
6 changes: 3 additions & 3 deletions packages/stellar/src/index.ts
Original file line number Diff line number Diff line change
@@ -1,5 +1,5 @@
import {
Contract, Networks, SorobanRpc, TransactionBuilder,
Contract, Networks, rpc, TransactionBuilder,
BASE_FEE, xdr, nativeToScVal, scValToNative, Address,
} from '@stellar/stellar-sdk'

Expand All @@ -26,11 +26,11 @@ export const CONTRACT_IDS: Record<NetworkName, Record<string, string>> = {
}

export function getRpcServer(network: NetworkName = 'testnet') {
return new SorobanRpc.Server(NETWORKS[network].rpcUrl, { allowHttp: false })
return new rpc.Server(NETWORKS[network].rpcUrl, { allowHttp: false })
}

export async function buildTransaction(
server: SorobanRpc.Server,
server: rpc.Server,
sourcePublicKey: string,
contractId: string,
method: string,
Expand Down
Loading