import { z } from "zod"; import type { AuthSecp256k1 } from '../auth/auth.ts'; import type { Database } from "../services/storage/database.ts"; import type { RouteDefinition, RouteStream } from './types.ts'; import type { Accounts } from '../auth/accounts.ts'; import { HTTP_STATUS_CODE_NOT_IMPLEMENTED, HTTP_STATUS_CODE_SUCCESS } from "../constants.ts"; import { UnauthorizedError } from '../errors/index.ts'; const accountSchema = z.object({ publicKey: z.string(), signature: z.string(), timestamp: z.coerce.number(), }); const setBalanceSchema = z.object({ amount: z.number(), }); export class AccountRoute { constructor(private readonly database: Database, private readonly auth: AuthSecp256k1, private readonly accounts: Accounts) {} async getRoutes(): Promise> { return [ { url: '/account', handler: this.getAccount.bind(this), }, { url: '/account/balance', handler: this.getAccount.bind(this), }, { url: '/account/setbalance', handler: this.setBalance.bind(this), }, // This one may not make sense. It could be a large overhead for something that most wont use? // Maybe make this an optional endpoint or one that we don't support, but the client can try to hit or something? { url: '/account/ledger', handler: this.getLedger.bind(this), } ]; } async getAccount(stream: RouteStream): Promise { // Get the public key, signature and timestamp from the headers const { publicKey, signature, timestamp } = accountSchema.parse(stream.headers); // Create the canonical payload for the signature verification const payload = `${stream.path}:${timestamp}`; // Verify the signature const verified = await this.auth.verifySignature(publicKey, signature, payload); if (!verified) { throw new UnauthorizedError('Invalid signature'); } // Get the account's balance from the database const balance = await this.accounts.getBalance(publicKey); // Send the balance to the client stream.send({ statusCode: HTTP_STATUS_CODE_SUCCESS, body: balance, }); } async setBalance(stream: RouteStream): Promise { const { publicKey, signature, timestamp } = accountSchema.parse(stream.headers); const { amount } = setBalanceSchema.parse(stream.body); // Create the canonical payload for the signature verification const payload = `${stream.path}:${timestamp}`; // Verify the signature const verified = await this.auth.verifySignature(publicKey, signature, payload); if (!verified) { throw new UnauthorizedError('Invalid signature'); } // Get the account's balance from the database const balance = await this.accounts.getBalance(publicKey); // Set the balance to the database await this.accounts.setBalance(publicKey, amount); // Send the balance to the client stream.send({ statusCode: HTTP_STATUS_CODE_SUCCESS, body: balance, }); } async getLedger(stream: RouteStream): Promise { stream.send({ statusCode: HTTP_STATUS_CODE_NOT_IMPLEMENTED, body: 'Not implemented', }); } }