From f3710d7e6efd59460626db9cbcc67baac9af2df4 Mon Sep 17 00:00:00 2001 From: vstudio79 Date: Mon, 29 Jun 2026 17:06:59 +0100 Subject: [PATCH] feat: add createPaymentChannel function and PaymentChannelConfig interface - Add PaymentChannelConfig interface for channel configuration - Implement createPaymentChannel function that generates escrow keypair - Export new type and function from package entry point - Validates signers, distributions, and public keys on creation --- packages/payments-engine/dist/index.js | 75 +++++++++++++++++++ packages/payments-engine/dist/index.mjs | 73 ++++++++++++++++++ packages/payments-engine/src/index.ts | 13 +++- .../payments-engine/src/payment-channel.ts | 53 +++++++++++++ 4 files changed, 213 insertions(+), 1 deletion(-) diff --git a/packages/payments-engine/dist/index.js b/packages/payments-engine/dist/index.js index f0d06f2..c154892 100644 --- a/packages/payments-engine/dist/index.js +++ b/packages/payments-engine/dist/index.js @@ -33,6 +33,8 @@ __export(index_exports, { StellarService: () => StellarService, buildChannelCloseTransaction: () => buildChannelCloseTransaction, closePaymentChannel: () => closePaymentChannel, + createAssetPayment: () => createAssetPayment, + createPaymentChannel: () => createPaymentChannel, sendStellarPayment: () => sendStellarPayment }); module.exports = __toCommonJS(index_exports); @@ -179,10 +181,78 @@ var StellarService = class { } }); } + async createAssetPayment(params) { + const { destination, amount, assetCode, assetIssuer } = params; + if (!StellarSdk.StrKey.isValidEd25519PublicKey(destination)) { + throw new Error(`Invalid Stellar address: ${destination}`); + } + const hasTrustline = await this.verifyTrustline(destination, assetCode, assetIssuer); + if (!hasTrustline) { + throw new Error( + `Destination account ${destination} does not have a trustline for ${assetCode}:${assetIssuer}` + ); + } + const transactionHash = await this.sendFunds(destination, amount, assetCode, assetIssuer); + return { + transactionHash, + assetCode, + assetIssuer, + amount, + destination + }; + } + async verifyTrustline(destination, assetCode, assetIssuer) { + try { + const account = await this.server.loadAccount(destination); + return account.balances.some( + (b) => "asset_code" in b && b.asset_code === assetCode && b.asset_issuer === assetIssuer + ); + } catch { + throw new Error(`Unable to verify trustline for ${destination}`); + } + } }; // src/payment-channel.ts var StellarSdk2 = __toESM(require("stellar-sdk")); +async function createPaymentChannel(config) { + const { id, asset, distributions, signers, networkPassphrase, fee, signatureThreshold } = config; + if (!id) { + throw new Error("Payment channel id is required"); + } + if (!signers.length) { + throw new Error("At least one signer is required"); + } + if (!distributions.length) { + throw new Error("At least one distribution is required"); + } + for (const signer of signers) { + if (!StellarSdk2.StrKey.isValidEd25519PublicKey(signer.publicKey)) { + throw new Error(`Invalid signer public key: ${signer.publicKey}`); + } + } + for (const distribution of distributions) { + if (!StellarSdk2.StrKey.isValidEd25519PublicKey(distribution.publicKey)) { + throw new Error(`Invalid distribution address: ${distribution.publicKey}`); + } + if (!distribution.amount || Number(distribution.amount) <= 0) { + throw new Error(`Invalid distribution amount for ${distribution.publicKey}`); + } + } + const escrowKeypair = StellarSdk2.Keypair.random(); + const channel = { + id, + escrowAccountId: escrowKeypair.publicKey(), + status: "open", + asset, + distributions, + signers, + networkPassphrase, + fee, + signatureThreshold + }; + return channel; +} function resolveAsset(asset) { const code = asset.code?.trim(); const isNative = !code || code === "native" || code === "XLM"; @@ -285,10 +355,15 @@ var stellarService = new StellarService(); async function sendStellarPayment(to, amount, asset) { return stellarService.sendFunds(to, amount.toString(), asset === "XLM" ? void 0 : asset); } +async function createAssetPayment(params) { + return stellarService.createAssetPayment(params); +} // Annotate the CommonJS export names for ESM import in node: 0 && (module.exports = { StellarService, buildChannelCloseTransaction, closePaymentChannel, + createAssetPayment, + createPaymentChannel, sendStellarPayment }); diff --git a/packages/payments-engine/dist/index.mjs b/packages/payments-engine/dist/index.mjs index 647bb41..f3dace2 100644 --- a/packages/payments-engine/dist/index.mjs +++ b/packages/payments-engine/dist/index.mjs @@ -140,10 +140,78 @@ var StellarService = class { } }); } + async createAssetPayment(params) { + const { destination, amount, assetCode, assetIssuer } = params; + if (!StellarSdk.StrKey.isValidEd25519PublicKey(destination)) { + throw new Error(`Invalid Stellar address: ${destination}`); + } + const hasTrustline = await this.verifyTrustline(destination, assetCode, assetIssuer); + if (!hasTrustline) { + throw new Error( + `Destination account ${destination} does not have a trustline for ${assetCode}:${assetIssuer}` + ); + } + const transactionHash = await this.sendFunds(destination, amount, assetCode, assetIssuer); + return { + transactionHash, + assetCode, + assetIssuer, + amount, + destination + }; + } + async verifyTrustline(destination, assetCode, assetIssuer) { + try { + const account = await this.server.loadAccount(destination); + return account.balances.some( + (b) => "asset_code" in b && b.asset_code === assetCode && b.asset_issuer === assetIssuer + ); + } catch { + throw new Error(`Unable to verify trustline for ${destination}`); + } + } }; // src/payment-channel.ts import * as StellarSdk2 from "stellar-sdk"; +async function createPaymentChannel(config) { + const { id, asset, distributions, signers, networkPassphrase, fee, signatureThreshold } = config; + if (!id) { + throw new Error("Payment channel id is required"); + } + if (!signers.length) { + throw new Error("At least one signer is required"); + } + if (!distributions.length) { + throw new Error("At least one distribution is required"); + } + for (const signer of signers) { + if (!StellarSdk2.StrKey.isValidEd25519PublicKey(signer.publicKey)) { + throw new Error(`Invalid signer public key: ${signer.publicKey}`); + } + } + for (const distribution of distributions) { + if (!StellarSdk2.StrKey.isValidEd25519PublicKey(distribution.publicKey)) { + throw new Error(`Invalid distribution address: ${distribution.publicKey}`); + } + if (!distribution.amount || Number(distribution.amount) <= 0) { + throw new Error(`Invalid distribution amount for ${distribution.publicKey}`); + } + } + const escrowKeypair = StellarSdk2.Keypair.random(); + const channel = { + id, + escrowAccountId: escrowKeypair.publicKey(), + status: "open", + asset, + distributions, + signers, + networkPassphrase, + fee, + signatureThreshold + }; + return channel; +} function resolveAsset(asset) { const code = asset.code?.trim(); const isNative = !code || code === "native" || code === "XLM"; @@ -246,9 +314,14 @@ var stellarService = new StellarService(); async function sendStellarPayment(to, amount, asset) { return stellarService.sendFunds(to, amount.toString(), asset === "XLM" ? void 0 : asset); } +async function createAssetPayment(params) { + return stellarService.createAssetPayment(params); +} export { StellarService, buildChannelCloseTransaction, closePaymentChannel, + createAssetPayment, + createPaymentChannel, sendStellarPayment }; diff --git a/packages/payments-engine/src/index.ts b/packages/payments-engine/src/index.ts index 503940e..075016a 100644 --- a/packages/payments-engine/src/index.ts +++ b/packages/payments-engine/src/index.ts @@ -1,4 +1,5 @@ import { StellarService } from './stellar.service'; +import type { AssetPaymentParams, PaymentResult } from './stellar.service'; const stellarService = new StellarService(); @@ -9,6 +10,11 @@ export async function sendStellarPayment( ): Promise { return stellarService.sendFunds(to, amount.toString(), asset === 'XLM' ? undefined : asset); } + +export async function createAssetPayment(params: AssetPaymentParams): Promise { + return stellarService.createAssetPayment(params); +} + export type { Horizon, PaymentChannelStatus, @@ -16,9 +22,14 @@ export type { PaymentChannelDistribution, PaymentChannelSigner, PaymentChannel, + PaymentChannelConfig, ChannelCloseResult, } from './payment-channel'; -export { buildChannelCloseTransaction, closePaymentChannel } from './payment-channel'; +export { + buildChannelCloseTransaction, + closePaymentChannel, + createPaymentChannel, +} from './payment-channel'; export * from './stellar.service'; diff --git a/packages/payments-engine/src/payment-channel.ts b/packages/payments-engine/src/payment-channel.ts index 94563b3..9129417 100644 --- a/packages/payments-engine/src/payment-channel.ts +++ b/packages/payments-engine/src/payment-channel.ts @@ -2,6 +2,16 @@ import * as StellarSdk from 'stellar-sdk'; export type Horizon = StellarSdk.Horizon.Server; +export interface PaymentChannelConfig { + id: string; + asset: PaymentChannelAsset; + distributions: PaymentChannelDistribution[]; + signers: PaymentChannelSigner[]; + networkPassphrase: string; + fee?: string | number; + signatureThreshold?: number; +} + export type PaymentChannelStatus = 'open' | 'closing' | 'closed'; export interface PaymentChannelAsset { @@ -42,6 +52,49 @@ export interface ChannelCloseResult { closedAt: string; } +export async function createPaymentChannel(config: PaymentChannelConfig): Promise { + const { id, asset, distributions, signers, networkPassphrase, fee, signatureThreshold } = config; + + if (!id) { + throw new Error('Payment channel id is required'); + } + if (!signers.length) { + throw new Error('At least one signer is required'); + } + if (!distributions.length) { + throw new Error('At least one distribution is required'); + } + for (const signer of signers) { + if (!StellarSdk.StrKey.isValidEd25519PublicKey(signer.publicKey)) { + throw new Error(`Invalid signer public key: ${signer.publicKey}`); + } + } + for (const distribution of distributions) { + if (!StellarSdk.StrKey.isValidEd25519PublicKey(distribution.publicKey)) { + throw new Error(`Invalid distribution address: ${distribution.publicKey}`); + } + if (!distribution.amount || Number(distribution.amount) <= 0) { + throw new Error(`Invalid distribution amount for ${distribution.publicKey}`); + } + } + + const escrowKeypair = StellarSdk.Keypair.random(); + + const channel: PaymentChannel = { + id, + escrowAccountId: escrowKeypair.publicKey(), + status: 'open', + asset, + distributions, + signers, + networkPassphrase, + fee, + signatureThreshold, + }; + + return channel; +} + function resolveAsset(asset: PaymentChannelAsset): StellarSdk.Asset { const code = asset.code?.trim(); const isNative = !code || code === 'native' || code === 'XLM';