From aa2bb79234e77f3a5c38bbc0d6ca7414d90b11fd Mon Sep 17 00:00:00 2001 From: "Ikenga Ifeanyi .M." Date: Fri, 25 Sep 2026 14:23:10 +0100 Subject: [PATCH 1/4] feat: migrate database operations to connection pool with read replicas --- stellar-payment-platform/package.json | 1 + stellar-payment-platform/prismaClient.js | 15 +++++++++++++-- stellar-payment-platform/src/db.js | 17 +++++++++++++++-- 3 files changed, 29 insertions(+), 4 deletions(-) diff --git a/stellar-payment-platform/package.json b/stellar-payment-platform/package.json index ff37b1d3..752c3ae4 100644 --- a/stellar-payment-platform/package.json +++ b/stellar-payment-platform/package.json @@ -47,6 +47,7 @@ "@opentelemetry/exporter-zipkin": "^1.25.0", "@opentelemetry/sdk-node": "^0.52.1", "@prisma/client": "^6.19.3", + "@prisma/extension-read-replicas": "^0.4.0", "@prisma/instrumentation": "^6.19.3", "@sentry/node": "^10.68.0", "@stellar/stellar-sdk": "^16.0.1", diff --git a/stellar-payment-platform/prismaClient.js b/stellar-payment-platform/prismaClient.js index 79a9ccdd..cd4de051 100644 --- a/stellar-payment-platform/prismaClient.js +++ b/stellar-payment-platform/prismaClient.js @@ -17,9 +17,20 @@ try { throw new Error('DATABASE_URL is not set'); } const { PrismaClient } = require('@prisma/client'); - prisma = new PrismaClient(); + const basePrisma = new PrismaClient(); + + if (process.env.DATABASE_READ_URL) { + const { readReplicas } = require('@prisma/extension-read-replicas'); + prisma = basePrisma.$extends( + readReplicas({ + url: process.env.DATABASE_READ_URL, + }) + ); + } else { + prisma = basePrisma; + } } catch (err) { - logger.warn('Prisma client not found. Using fallback mock for tests.'); + logger.warn('Prisma client not found or extension missing. Using fallback mock for tests.', err); prisma = { user: { update: async () => { diff --git a/stellar-payment-platform/src/db.js b/stellar-payment-platform/src/db.js index 0d4580e1..c0d7d12d 100644 --- a/stellar-payment-platform/src/db.js +++ b/stellar-payment-platform/src/db.js @@ -12,10 +12,23 @@ const pool = new Pool({ connectionTimeoutMillis: 10000, }); +const readPool = process.env.DATABASE_READ_URL ? new Pool({ + connectionString: process.env.DATABASE_READ_URL, + max: parseInt(process.env.DB_READ_POOL_MAX, 10) || parseInt(process.env.DB_POOL_MAX, 10) || 10, + idleTimeoutMillis: 30000, + connectionTimeoutMillis: 10000, +}) : pool; + pool.on('error', (err) => { logger.error(err, 'Unexpected error on idle PostgreSQL client'); }); +if (readPool !== pool) { + readPool.on('error', (err) => { + logger.error(err, 'Unexpected error on idle PostgreSQL read client'); + }); +} + (async () => { let retries = 5; while (retries > 0) { @@ -60,7 +73,7 @@ pool.on('error', (err) => { })(); const poolGet = async (sql, params = []) => { - const { rows } = await pool.query(sql, params); + const { rows } = await readPool.query(sql, params); return rows[0] || null; }; @@ -70,7 +83,7 @@ const poolRun = async (sql, params = []) => { }; const poolAll = async (sql, params = []) => { - const { rows } = await pool.query(sql, params); + const { rows } = await readPool.query(sql, params); return rows; }; From 3b87111ece69525aad00b274a87d4f10a9ab3f3e Mon Sep 17 00:00:00 2001 From: "Ikenga Ifeanyi .M." Date: Sun, 4 Oct 2026 13:05:19 +0100 Subject: [PATCH 2/4] Empty commit From ad071acd0b789eabf6cc07d54ccb1033f519d4eb Mon Sep 17 00:00:00 2001 From: "Ikenga Ifeanyi .M." Date: Sun, 4 Oct 2026 15:44:37 +0100 Subject: [PATCH 3/4] chore: regenerate soroban bindings --- packages/types/package.json | 1 + packages/types/src/index.ts | 6 +++--- 2 files changed, 4 insertions(+), 3 deletions(-) diff --git a/packages/types/package.json b/packages/types/package.json index 6a02e114..7bf899c7 100644 --- a/packages/types/package.json +++ b/packages/types/package.json @@ -18,3 +18,4 @@ "main": "./src/index.ts", "types": "./src/index.ts" } + diff --git a/packages/types/src/index.ts b/packages/types/src/index.ts index c00bf8c1..c31457dd 100644 --- a/packages/types/src/index.ts +++ b/packages/types/src/index.ts @@ -115,7 +115,7 @@ export interface Client { * Construct and simulate a version transaction. Returns an `AssembledTransaction` object which will have a `result` field containing the result of the simulation. If this transaction changes contract state, you will need to call `signAndSend()` on the returned object. * Returns the contract version. */ - version: (options?: MethodOptions) => Promise> + version: (options?: MethodOptions) => Promise> /** * Construct and simulate a is_paused transaction. Returns an `AssembledTransaction` object which will have a `result` field containing the result of the simulation. If this transaction changes contract state, you will need to call `signAndSend()` on the returned object. @@ -291,7 +291,7 @@ export class Client extends ContractClient { public readonly fromJSON = { get_fee: this.txFromJSON, upgrade: this.txFromJSON>, - version: this.txFromJSON, + version: this.txFromJSON, is_paused: this.txFromJSON, set_admin: this.txFromJSON>, set_pause: this.txFromJSON>, @@ -313,4 +313,4 @@ export class Client extends ContractClient { set_fee_config_legacy: this.txFromJSON>, set_platform_treasury: this.txFromJSON> } -} \ No newline at end of file +} From 94a1dfe9ebe7d57ac2fa8749ef7a79500541c815 Mon Sep 17 00:00:00 2001 From: "Ikenga Ifeanyi .M." Date: Sun, 4 Oct 2026 16:10:00 +0100 Subject: [PATCH 4/4] fix: align generated bindings with contract version ABI --- packages/types/package.json | 2 +- packages/types/src/index.ts | 12 +++++++----- 2 files changed, 8 insertions(+), 6 deletions(-) diff --git a/packages/types/package.json b/packages/types/package.json index 19b6492d..7bf899c7 100644 --- a/packages/types/package.json +++ b/packages/types/package.json @@ -8,7 +8,7 @@ "build": "tsc" }, "dependencies": { - "@stellar/stellar-sdk": "^14.5.0", + "@stellar/stellar-sdk": "^16.0.1", "buffer": "6.0.3" }, "devDependencies": { diff --git a/packages/types/src/index.ts b/packages/types/src/index.ts index 2fbd8c56..9888b6f8 100644 --- a/packages/types/src/index.ts +++ b/packages/types/src/index.ts @@ -570,11 +570,13 @@ export interface Client { * Construct and simulate a version transaction. Returns an `AssembledTransaction` object which will have a `result` field containing the result of the simulation. If this transaction changes contract state, you will need to call `signAndSend()` on the returned object. * Returns the contract version. * - * # Returns - * The contract's version number, currently `1`. + * This is a read-only view function: it does not write to ledger storage + * and costs only the base invocation fee. The UI calls this before + * submitting transactions to confirm it is compatible with the deployed + * contract. * - * # Panics - * Does not panic. + * # Returns + * A [`String`] in the form `"MAJOR.MINOR.PATCH"` (e.g. `"1.0.0"`). */ version: (options?: MethodOptions) => Promise> @@ -1940,7 +1942,7 @@ export class Client extends ContractClient { public readonly fromJSON = { get_fee: this.txFromJSON, upgrade: this.txFromJSON>, - version: this.txFromJSON, + version: this.txFromJSON, has_role: this.txFromJSON, unfreeze: this.txFromJSON>, get_price: this.txFromJSON>,