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: 5 additions & 1 deletion src/config/chains.config.ts
Original file line number Diff line number Diff line change
Expand Up @@ -56,7 +56,11 @@ export const EVM_CHAINS = {
},
citreaTestnet: {
chainId: 5115,
gatewayUrl: 'https://rpc.testnet.citrea.xyz',
gatewayUrl: 'http://10.0.1.6:8085',
},
citrea: {
chainId: 4114,
gatewayUrl: 'http://10.0.1.6:8085',
},
} satisfies Record<string, EvmChainStaticConfig>;

Expand Down
8 changes: 8 additions & 0 deletions src/config/config.ts
Original file line number Diff line number Diff line change
Expand Up @@ -797,6 +797,14 @@ export class Configuration {
bscApiKey: process.env.ALCHEMY_API_KEY,
gasPrice: process.env.BSC_GAS_PRICE,
},
citrea: {
...EVM_CHAINS.citrea,
citreaGatewayUrl: EVM_CHAINS.citrea.gatewayUrl,
citreaChainId: EVM_CHAINS.citrea.chainId,
citreaWalletAddress: process.env.CITREA_WALLET_ADDRESS,
citreaWalletPrivateKey: process.env.CITREA_WALLET_PRIVATE_KEY,
citreaApiKey: process.env.CITREA_API_KEY,
},
citreaTestnet: {
...EVM_CHAINS.citreaTestnet,
citreaTestnetGatewayUrl: EVM_CHAINS.citreaTestnet.gatewayUrl,
Expand Down
11 changes: 8 additions & 3 deletions src/integration/bank/services/iso20022.service.ts
Original file line number Diff line number Diff line change
Expand Up @@ -253,9 +253,6 @@ export class Iso20022Service {
const bookingDate = bookingDateStr ? this.parseDate(bookingDateStr) : new Date();
const valueDate = valueDateStr ? this.parseDate(valueDateStr) : bookingDate;

// reference
const accountServiceRef = entry.NtryRef || entry.AcctSvcrRef || Util.createUniqueId(accountIban);

// transaction details
const entryDtls = entry.NtryDtls;
const txDtlsArray = Array.isArray(entryDtls) ? entryDtls : entryDtls ? [entryDtls] : [];
Expand Down Expand Up @@ -298,6 +295,14 @@ export class Iso20022Service {
remittanceInfo = entry.AddtlNtryInf;
}

// reference - check transaction-level refs first (matches camt.054), then entry-level AcctSvcrRef
const accountServiceRef =
txDtls.Refs?.AcctSvcrRef ||
txDtls.Refs?.TxId ||
entry.AcctSvcrRef ||
entry.NtryRef ||
Util.createUniqueId(accountIban);

// end-to-end ID
const endToEndId = txDtls.Refs?.EndToEndId || '';

Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -132,6 +132,7 @@ describe('CryptoService', () => {
Blockchain.BASE,
Blockchain.GNOSIS,
Blockchain.HAQQ,
Blockchain.CITREA,
Blockchain.CITREA_TESTNET,
]);
});
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -20,6 +20,7 @@ export enum Blockchain {
SOLANA = 'Solana',
GNOSIS = 'Gnosis',
TRON = 'Tron',
CITREA = 'Citrea',
CITREA_TESTNET = 'CitreaTestnet',

// Payment Provider
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -15,6 +15,7 @@ describe('EvmUtil', () => {
base: { baseChainId: 8453 },
gnosis: { gnosisChainId: 100 },
bsc: { bscChainId: 56 },
citrea: { citreaChainId: 4114 },
citreaTestnet: { citreaTestnetChainId: 5115 },
},
};
Expand Down
1 change: 1 addition & 0 deletions src/integration/blockchain/shared/evm/evm.util.ts
Original file line number Diff line number Diff line change
Expand Up @@ -35,6 +35,7 @@ export class EvmUtil {
[Blockchain.BASE, this.blockchainConfig.base.baseChainId],
[Blockchain.GNOSIS, this.blockchainConfig.gnosis.gnosisChainId],
[Blockchain.BINANCE_SMART_CHAIN, this.blockchainConfig.bsc.bscChainId],
[Blockchain.CITREA, this.blockchainConfig.citrea.citreaChainId],
[Blockchain.CITREA_TESTNET, this.blockchainConfig.citreaTestnet.citreaTestnetChainId],
]);

Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -77,6 +77,7 @@ export class CryptoService {
case Blockchain.GNOSIS:
case Blockchain.HAQQ:
case Blockchain.BINANCE_SMART_CHAIN:
case Blockchain.CITREA:
case Blockchain.CITREA_TESTNET:
return EvmUtil.getPaymentRequest(address, asset, amount);

Expand Down Expand Up @@ -125,6 +126,7 @@ export class CryptoService {
case Blockchain.BASE:
case Blockchain.GNOSIS:
case Blockchain.HAQQ:
case Blockchain.CITREA:
case Blockchain.CITREA_TESTNET:
return UserAddressType.EVM;

Expand Down
5 changes: 5 additions & 0 deletions src/integration/blockchain/shared/util/blockchain.util.ts
Original file line number Diff line number Diff line change
Expand Up @@ -14,6 +14,7 @@ export const EvmBlockchains = [
Blockchain.BASE,
Blockchain.GNOSIS,
Blockchain.HAQQ,
Blockchain.CITREA,
Blockchain.CITREA_TESTNET,
];

Expand Down Expand Up @@ -79,6 +80,7 @@ const BlockchainExplorerUrls: { [b in Blockchain]: string } = {
[Blockchain.GNOSIS]: 'https://gnosisscan.io',
[Blockchain.SOLANA]: 'https://solscan.io',
[Blockchain.TRON]: 'https://tronscan.org/#',
[Blockchain.CITREA]: 'https://citreascan.com',
[Blockchain.CITREA_TESTNET]: 'https://testnet.citreascan.com',
[Blockchain.HAQQ]: 'https://explorer.haqq.network',
[Blockchain.LIQUID]: 'https://blockstream.info/liquid',
Expand Down Expand Up @@ -115,6 +117,7 @@ const TxPaths: { [b in Blockchain]: string } = {
[Blockchain.GNOSIS]: 'tx',
[Blockchain.SOLANA]: 'tx',
[Blockchain.TRON]: 'transaction',
[Blockchain.CITREA]: 'tx',
[Blockchain.CITREA_TESTNET]: 'tx',
[Blockchain.HAQQ]: 'tx',
[Blockchain.LIQUID]: 'tx',
Expand Down Expand Up @@ -154,6 +157,7 @@ function assetPaths(asset: Asset): string | undefined {
case Blockchain.POLYGON:
case Blockchain.BASE:
case Blockchain.GNOSIS:
case Blockchain.CITREA:
case Blockchain.CITREA_TESTNET:
case Blockchain.SOLANA:
case Blockchain.HAQQ:
Expand Down Expand Up @@ -181,6 +185,7 @@ function addressPaths(blockchain: Blockchain): string | undefined {
case Blockchain.POLYGON:
case Blockchain.BASE:
case Blockchain.GNOSIS:
case Blockchain.CITREA:
case Blockchain.CITREA_TESTNET:
case Blockchain.TRON:
case Blockchain.HAQQ:
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -36,6 +36,7 @@ export class TestExchangeService extends ExchangeService {
KucoinPay: undefined,
Solana: undefined,
Tron: undefined,
Citrea: undefined,
CitreaTestnet: undefined,
Kraken: undefined,
Binance: undefined,
Expand Down
1 change: 1 addition & 0 deletions src/integration/exchange/services/binance.service.ts
Original file line number Diff line number Diff line change
Expand Up @@ -33,6 +33,7 @@ export class BinanceService extends ExchangeService {
KucoinPay: undefined,
Solana: 'SOL',
Tron: 'TRX',
Citrea: undefined,
CitreaTestnet: undefined,
Kraken: undefined,
Binance: undefined,
Expand Down
1 change: 1 addition & 0 deletions src/integration/exchange/services/bitstamp.service.ts
Original file line number Diff line number Diff line change
Expand Up @@ -33,6 +33,7 @@ export class BitstampService extends ExchangeService {
KucoinPay: undefined,
Solana: undefined,
Tron: undefined,
Citrea: undefined,
CitreaTestnet: undefined,
Kraken: undefined,
Binance: undefined,
Expand Down
1 change: 1 addition & 0 deletions src/integration/exchange/services/kraken.service.ts
Original file line number Diff line number Diff line change
Expand Up @@ -40,6 +40,7 @@ export class KrakenService extends ExchangeService {
KucoinPay: undefined,
Solana: false,
Tron: undefined,
Citrea: undefined,
CitreaTestnet: undefined,
Kraken: undefined,
Binance: undefined,
Expand Down
1 change: 1 addition & 0 deletions src/integration/exchange/services/kucoin.service.ts
Original file line number Diff line number Diff line change
Expand Up @@ -33,6 +33,7 @@ export class KucoinService extends ExchangeService {
KucoinPay: undefined,
Solana: undefined,
Tron: undefined,
Citrea: undefined,
CitreaTestnet: undefined,
Kraken: undefined,
Binance: undefined,
Expand Down
1 change: 1 addition & 0 deletions src/integration/exchange/services/mexc.service.ts
Original file line number Diff line number Diff line change
Expand Up @@ -37,6 +37,7 @@ export class MexcService extends ExchangeService {
KucoinPay: undefined,
Solana: 'SOL',
Tron: 'TRX',
Citrea: undefined,
CitreaTestnet: undefined,
Kraken: undefined,
Binance: undefined,
Expand Down
1 change: 1 addition & 0 deletions src/integration/exchange/services/xt.service.ts
Original file line number Diff line number Diff line change
Expand Up @@ -33,6 +33,7 @@ export class XtService extends ExchangeService {
KucoinPay: undefined,
Solana: undefined,
Tron: undefined,
Citrea: undefined,
CitreaTestnet: undefined,
Kraken: undefined,
Binance: undefined,
Expand Down
8 changes: 8 additions & 0 deletions src/shared/models/asset/asset.service.ts
Original file line number Diff line number Diff line change
Expand Up @@ -229,6 +229,14 @@ export class AssetService {
});
}

async getCitreaCoin(): Promise<Asset> {
return this.getAssetByQuery({
name: 'cBTC',
blockchain: Blockchain.CITREA,
type: AssetType.COIN,
});
}

async getCitreaTestnetCoin(): Promise<Asset> {
return this.getAssetByQuery({
name: 'cBTC',
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -37,7 +37,7 @@ import { CustodyService } from './custody.service';

@Injectable()
export class CustodyOrderService {
private readonly CustodyChains = [Blockchain.ETHEREUM];
private readonly CustodyChains = [Blockchain.ETHEREUM, Blockchain.CITREA];

constructor(
private readonly userService: UserService,
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -46,6 +46,7 @@ import { BankService } from 'src/subdomains/supporting/bank/bank/bank.service';
import { CardBankName } from 'src/subdomains/supporting/bank/bank/dto/bank.dto';
import { VirtualIbanService } from 'src/subdomains/supporting/bank/virtual-iban/virtual-iban.service';
import { PayInType } from 'src/subdomains/supporting/payin/entities/crypto-input.entity';
import { FiatPaymentMethod } from 'src/subdomains/supporting/payment/dto/payment-method.enum';
import { TxStatementType } from 'src/subdomains/supporting/payment/dto/transaction-helper/tx-statement-details.dto';
import { TransactionRequest } from 'src/subdomains/supporting/payment/entities/transaction-request.entity';
import { Transaction, TransactionTypeInternal } from 'src/subdomains/supporting/payment/entities/transaction.entity';
Expand Down Expand Up @@ -436,6 +437,51 @@ export class TransactionController {
@ApiOkResponse({ type: PdfDto })
async generateInvoiceFromTransaction(@GetJwt() jwt: JwtPayload, @Param('id') id: string): Promise<PdfDto> {
const txIdOrUid = isNaN(+id) ? id : +id;

// For string UIDs, first try to find a TransactionRequest (for pending transactions)
if (typeof txIdOrUid === 'string') {
const request = await this.transactionRequestService.getTransactionRequestByUid(txIdOrUid, {
user: { userData: { organization: true }, wallet: true },
});

if (request) {
// Validate ownership and state
if (request.user.userData.id !== jwt.account) throw new ForbiddenException('Not your transaction request');
if (!request.userData.isDataComplete) throw new BadRequestException('User data is not complete');
if (!request.isValid) throw new BadRequestException('Transaction request is not valid');

// Generate invoice from request (pending transaction)
const currency = await this.fiatService.getFiat(request.sourceId);
if (!Config.invoice.currencies.includes(currency.name)) {
throw new Error('PDF invoice is only available for CHF and EUR transactions');
}

const buy = await this.buyService.get(jwt.account, request.routeId);
const bankInfo = await this.buyService.getBankInfo(
{
amount: request.amount,
currency: currency.name,
paymentMethod: request.sourcePaymentMethod as FiatPaymentMethod,
userData: request.userData,
},
buy,
buy?.asset,
buy?.user?.wallet,
);

return {
pdfData: await this.swissQrService.createInvoiceFromRequest(
request.amount,
currency.name,
bankInfo.reference,
bankInfo,
request,
),
};
}
}

// Try to find a completed Transaction
const txStatementDetails = await this.transactionHelper.getTxStatementDetails(
jwt.account,
txIdOrUid,
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -113,6 +113,7 @@ export class BlockchainAdapter implements LiquidityBalanceIntegration {
case Blockchain.BASE:
case Blockchain.GNOSIS:
case Blockchain.BINANCE_SMART_CHAIN:
case Blockchain.CITREA:
case Blockchain.CITREA_TESTNET:
await this.updateEvmBalance(assets);
break;
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -45,6 +45,7 @@ const PayoutLimits: { [k in Blockchain]: number } = {
[Blockchain.KUCOIN_PAY]: undefined,
[Blockchain.GNOSIS]: undefined,
[Blockchain.TRON]: undefined,
[Blockchain.CITREA]: undefined,
[Blockchain.CITREA_TESTNET]: undefined,
[Blockchain.KRAKEN]: undefined,
[Blockchain.BINANCE]: undefined,
Expand Down
Original file line number Diff line number Diff line change
@@ -1,4 +1,5 @@
import { BankInfoDto } from 'src/subdomains/core/buy-crypto/routes/buy/dto/buy-payment-info.dto';
import { TransactionRequest } from '../../entities/transaction-request.entity';
import { Transaction } from '../../entities/transaction.entity';
import { TransactionType } from '../transaction.dto';

Expand All @@ -14,4 +15,5 @@ export interface TxStatementDetails {
currency: string;
bankInfo?: BankInfoDto;
reference?: string;
request?: TransactionRequest;
}
30 changes: 25 additions & 5 deletions src/subdomains/supporting/payment/services/swiss-qr.service.ts
Original file line number Diff line number Diff line change
Expand Up @@ -81,7 +81,7 @@ export class SwissQRService {
}

async createTxStatement(
{ statementType, transactionType, transaction, currency, bankInfo, reference }: TxStatementDetails,
{ statementType, transactionType, transaction, currency, bankInfo, reference, request }: TxStatementDetails,
brand: PdfBrand = PdfBrand.DFX,
): Promise<string> {
const debtor = this.getDebtor(transaction.userData);
Expand All @@ -94,14 +94,15 @@ export class SwissQRService {

const userLanguage = transaction.userData.language.symbol.toUpperCase();
const language = this.isSupportedInvoiceLanguage(userLanguage) ? userLanguage : 'EN';
const tableData = await this.getTableData(statementType, transactionType, transaction, currency);
const tableData = await this.getTableData(statementType, transactionType, transaction, currency, request);

const defaultCreditor = brand === PdfBrand.REALUNIT ? this.realunitCreditor() : this.dfxCreditor();
const amount = request?.amount ?? transaction.buyCrypto?.inputAmount;
const billData: QrBillData = {
creditor: (bankInfo && this.getCreditor(bankInfo)) || (defaultCreditor as unknown as Creditor),
debtor,
currency,
amount: bankInfo && transaction.buyCrypto?.inputAmount,
amount: bankInfo && amount,
message: reference,
};

Expand Down Expand Up @@ -396,6 +397,7 @@ export class SwissQRService {
statementType: TxStatementType,
transactionType: TransactionType,
transaction: Transaction,
request?: TransactionRequest,
): string {
let titleKey: string;

Expand All @@ -407,8 +409,10 @@ export class SwissQRService {
titleKey = 'invoice.title';
}

const invoiceId = request?.id ?? transaction.id;

return this.translate(titleKey, transaction.userData.language.symbol.toLowerCase(), {
invoiceId: transaction.id,
invoiceId,
});
}

Expand Down Expand Up @@ -453,14 +457,30 @@ export class SwissQRService {
transactionType: TransactionType,
transaction: Transaction,
currency: string,
request?: TransactionRequest,
): Promise<SwissQRBillTableData> {
const titleAndDate = {
title: this.getStatementTitle(statementType, transactionType, transaction),
title: this.getStatementTitle(statementType, transactionType, transaction, request),
date: this.getStatementDate(statementType, transaction),
};

switch (transactionType) {
case TransactionType.BUY: {
// Handle pending transactions with request data
if (request) {
const asset = await this.assetService.getAssetById(request.targetId);
return {
quantity: request.estimatedAmount,
description: {
assetDescription: asset.description ?? asset.name,
assetName: asset.name,
assetBlockchain: asset.blockchain,
},
fiatAmount: request.amount,
...titleAndDate,
};
}

const outputAsset = transaction.buyCrypto?.outputAsset;

return {
Expand Down
Loading
Loading