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
6 changes: 6 additions & 0 deletions .env.example
Original file line number Diff line number Diff line change
Expand Up @@ -8,6 +8,12 @@ DATABASE_URL=postgresql://user:password@localhost:5432/stellarsettle
# Stellar
STELLAR_NETWORK=testnet
STELLAR_HORIZON_URL=https://horizon-testnet.stellar.org
STELLAR_USDC_ASSET_CODE=USDC
STELLAR_USDC_ASSET_ISSUER=GXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXX
STELLAR_ESCROW_PUBLIC_KEY=GXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXX
STELLAR_VERIFY_ALLOWED_AMOUNT_DELTA=0.0001
STELLAR_VERIFY_RETRY_ATTEMPTS=3
STELLAR_VERIFY_RETRY_BASE_DELAY_MS=250
PLATFORM_SECRET_KEY=SXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXX

# Smart Contracts
Expand Down
8 changes: 7 additions & 1 deletion Readme.md
Original file line number Diff line number Diff line change
Expand Up @@ -90,6 +90,12 @@ DATABASE_URL=postgresql://user:password@localhost:5432/stellarsettle
# Stellar
STELLAR_NETWORK=testnet
STELLAR_HORIZON_URL=https://horizon-testnet.stellar.org
STELLAR_USDC_ASSET_CODE=USDC
STELLAR_USDC_ASSET_ISSUER=GXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXX
STELLAR_ESCROW_PUBLIC_KEY=GXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXX
STELLAR_VERIFY_ALLOWED_AMOUNT_DELTA=0.0001
STELLAR_VERIFY_RETRY_ATTEMPTS=3
STELLAR_VERIFY_RETRY_BASE_DELAY_MS=250
PLATFORM_SECRET_KEY=SXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXX

# Smart Contracts
Expand Down Expand Up @@ -216,4 +222,4 @@ MIT License - see [LICENSE](LICENSE) file for details

---

Built with ❤️ on Stellar
Built with ❤️ on Stellar
65 changes: 65 additions & 0 deletions src/config/stellar.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,65 @@
export interface PaymentVerificationConfig {
horizonUrl: string;
usdcAssetCode: string;
usdcAssetIssuer: string;
escrowPublicKey: string;
allowedAmountDelta: string;
retryAttempts: number;
retryBaseDelayMs: number;
}

const DEFAULT_ALLOWED_AMOUNT_DELTA = "0.0001";
const DEFAULT_RETRY_ATTEMPTS = 3;
const DEFAULT_RETRY_BASE_DELAY_MS = 250;

function requireEnv(value: string | undefined, name: string): string {
if (!value) {
throw new Error(`${name} is required.`);
}

return value;
}

function parsePositiveInteger(value: string | undefined, fallback: number, name: string): number {
if (!value) {
return fallback;
}

const parsed = Number(value);

if (!Number.isInteger(parsed) || parsed <= 0) {
throw new Error(`${name} must be a positive integer.`);
}

return parsed;
}

export function getPaymentVerificationConfig(): PaymentVerificationConfig {
return {
horizonUrl: requireEnv(process.env.STELLAR_HORIZON_URL, "STELLAR_HORIZON_URL"),
usdcAssetCode: requireEnv(
process.env.STELLAR_USDC_ASSET_CODE,
"STELLAR_USDC_ASSET_CODE",
),
usdcAssetIssuer: requireEnv(
process.env.STELLAR_USDC_ASSET_ISSUER,
"STELLAR_USDC_ASSET_ISSUER",
),
escrowPublicKey: requireEnv(
process.env.STELLAR_ESCROW_PUBLIC_KEY,
"STELLAR_ESCROW_PUBLIC_KEY",
),
allowedAmountDelta:
process.env.STELLAR_VERIFY_ALLOWED_AMOUNT_DELTA ?? DEFAULT_ALLOWED_AMOUNT_DELTA,
retryAttempts: parsePositiveInteger(
process.env.STELLAR_VERIFY_RETRY_ATTEMPTS,
DEFAULT_RETRY_ATTEMPTS,
"STELLAR_VERIFY_RETRY_ATTEMPTS",
),
retryBaseDelayMs: parsePositiveInteger(
process.env.STELLAR_VERIFY_RETRY_BASE_DELAY_MS,
DEFAULT_RETRY_BASE_DELAY_MS,
"STELLAR_VERIFY_RETRY_BASE_DELAY_MS",
),
};
}
54 changes: 54 additions & 0 deletions src/migrations/1731700000000-AddInvestmentPaymentVerification.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,54 @@
import { MigrationInterface, QueryRunner } from "typeorm";

export class AddInvestmentPaymentVerification1731700000000
implements MigrationInterface
{
name = "AddInvestmentPaymentVerification1731700000000";

public async up(queryRunner: QueryRunner): Promise<void> {
await queryRunner.query(`
ALTER TABLE "investments"
ADD COLUMN "stellar_operation_index" integer;
`);

await queryRunner.query(`
ALTER TABLE "transactions"
ADD COLUMN "investment_id" uuid,
ADD COLUMN "stellar_operation_index" integer;
`);

await queryRunner.query(`
CREATE INDEX "idx_transactions_investment_id"
ON "transactions" ("investment_id");
`);

await queryRunner.query(`
ALTER TABLE "transactions"
ADD CONSTRAINT "FK_transactions_investment"
FOREIGN KEY ("investment_id") REFERENCES "investments"("id")
ON DELETE SET NULL;
`);
}

public async down(queryRunner: QueryRunner): Promise<void> {
await queryRunner.query(`
ALTER TABLE "transactions"
DROP CONSTRAINT "FK_transactions_investment";
`);

await queryRunner.query(`
DROP INDEX "public"."idx_transactions_investment_id";
`);

await queryRunner.query(`
ALTER TABLE "transactions"
DROP COLUMN "stellar_operation_index",
DROP COLUMN "investment_id";
`);

await queryRunner.query(`
ALTER TABLE "investments"
DROP COLUMN "stellar_operation_index";
`);
}
}
7 changes: 7 additions & 0 deletions src/models/Investment.model.ts
Original file line number Diff line number Diff line change
Expand Up @@ -6,6 +6,7 @@ import {
UpdateDateColumn,
DeleteDateColumn,
ManyToOne,
OneToMany,
JoinColumn,
Index,
} from "typeorm";
Expand Down Expand Up @@ -46,6 +47,9 @@ export class Investment {
@Column({ name: "transaction_hash", type: "varchar", length: 64, nullable: true })
transactionHash!: string | null;

@Column({ name: "stellar_operation_index", type: "integer", nullable: true })
stellarOperationIndex!: number | null;

@CreateDateColumn({ name: "created_at" })
createdAt!: Date;

Expand All @@ -62,4 +66,7 @@ export class Investment {
@ManyToOne("User", "investments", { onDelete: "CASCADE" })
@JoinColumn({ name: "investor_id" })
investor!: User;

@OneToMany("Transaction", "investment")
transactions!: import("./Transaction.model").Transaction[];
}
12 changes: 12 additions & 0 deletions src/models/Transaction.model.ts
Original file line number Diff line number Diff line change
Expand Up @@ -7,6 +7,7 @@ import {
Index,
} from "typeorm";
import { TransactionType, TransactionStatus } from "../types/enums";
import type { Investment } from "./Investment.model";

@Entity("transactions")
export class Transaction {
Expand All @@ -17,6 +18,10 @@ export class Transaction {
@Index("idx_transactions_user_id")
userId!: string;

@Column({ name: "investment_id", type: "uuid", nullable: true })
@Index("idx_transactions_investment_id")
investmentId!: string | null;

@Column({
type: "enum",
enum: TransactionType,
Expand All @@ -30,6 +35,9 @@ export class Transaction {
@Column({ name: "stellar_tx_hash", type: "varchar", length: 64, nullable: true })
stellarTxHash!: string | null;

@Column({ name: "stellar_operation_index", type: "integer", nullable: true })
stellarOperationIndex!: number | null;

@Column({
type: "enum",
enum: TransactionStatus,
Expand All @@ -44,4 +52,8 @@ export class Transaction {
@ManyToOne("User", "transactions", { onDelete: "CASCADE" })
@JoinColumn({ name: "user_id" })
user!: import("./User.model").User;

@ManyToOne("Investment", "transactions", { onDelete: "SET NULL", nullable: true })
@JoinColumn({ name: "investment_id" })
investment!: Investment | null;
}
Loading
Loading