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
3 changes: 3 additions & 0 deletions .github/workflows/api-dev.yaml
Original file line number Diff line number Diff line change
Expand Up @@ -51,6 +51,9 @@ jobs:
run: npm audit --audit-level=high
continue-on-error: true

- name: Write version file
run: echo "${{ github.sha }}" > dist/version.txt

- name: Deploy to Azure App Service (DEV)
uses: azure/webapps-deploy@v3
with:
Expand Down
31 changes: 31 additions & 0 deletions .github/workflows/api-pr.yaml
Original file line number Diff line number Diff line change
Expand Up @@ -12,8 +12,39 @@ permissions:

env:
NODE_VERSION: '20.x'
DEV_API_URL: https://dev.api.dfx.swiss

jobs:
verify-dev-deployment:
name: Verify DEV Deployment
if: github.base_ref == 'main' && github.head_ref == 'develop'
runs-on: ubuntu-latest
steps:
- name: Wait for DEV deployment
timeout-minutes: 15
run: |
EXPECTED=$(curl -s https://api.github.com/repos/${{ github.repository }}/commits/develop | jq -r '.sha')
echo "Expected commit: $EXPECTED"

for i in {1..30}; do
if RESPONSE=$(curl -sf ${{ env.DEV_API_URL }}/version 2>&1); then
ACTUAL=$(echo "$RESPONSE" | jq -r '.commit')
echo "Attempt $i: DEV running $ACTUAL"

if [ "$EXPECTED" == "$ACTUAL" ]; then
echo "DEV is running the latest develop commit"
exit 0
fi
else
echo "Attempt $i: DEV API not reachable"
fi

sleep 30
done

echo "::error::DEV is not running the latest develop commit after 15 minutes"
exit 1

build:
name: Build and test
if: github.head_ref != 'develop'
Expand Down
3 changes: 3 additions & 0 deletions .github/workflows/api-prd.yaml
Original file line number Diff line number Diff line change
Expand Up @@ -51,6 +51,9 @@ jobs:
run: npm audit --audit-level=high
continue-on-error: true

- name: Write version file
run: echo "${{ github.sha }}" > dist/version.txt

- name: Deploy to Azure App Service (PRD)
uses: azure/webapps-deploy@v3
with:
Expand Down
17 changes: 17 additions & 0 deletions src/app.controller.ts
Original file line number Diff line number Diff line change
Expand Up @@ -2,6 +2,7 @@ import { Controller, Get, Param, Query, Redirect, Req, Res, VERSION_NEUTRAL, Ver
import { ApiExcludeEndpoint } from '@nestjs/swagger';
import { Request, Response } from 'express';
import { UserAgent } from 'express-useragent';
import { readFileSync } from 'fs';
import { RealIP } from 'nestjs-real-ip';
import { Config } from './config/config';
import { AdDto, AdSettings, AdvertisementDto } from './shared/dto/advertisement.dto';
Expand All @@ -27,6 +28,7 @@ enum Manufacturer {

@Controller('')
export class AppController {
private readonly startedAt = new Date();
private readonly homepageUrl = 'https://dfx.swiss/';
private readonly appleStoreUrl = 'https://apps.apple.com/app';
private readonly googleStoreUrl = 'https://play.app.goo.gl/?link=https://play.google.com/store/apps/details';
Expand Down Expand Up @@ -60,6 +62,21 @@ export class AppController {
// nothing to do (redirect to Swagger UI)
}

@Get('version')
@ApiExcludeEndpoint()
@Version(VERSION_NEUTRAL)
getVersion(): { commit: string; startedAt: Date } {
return { commit: this.getCommit(), startedAt: this.startedAt };
}

private getCommit(): string {
try {
return readFileSync('dist/version.txt', 'utf8').trim();
} catch {
return 'unknown';
}
}

@Get('app/announcements')
@ApiExcludeEndpoint()
async getAnnouncements(): Promise<AnnouncementDto[]> {
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -35,7 +35,7 @@ export class BitcoinTestnet4FeeService {
return 1;
}

return feeRate;
return Math.max(feeRate, 1);
},
undefined,
true, // fallbackToCache on error
Expand Down
6 changes: 5 additions & 1 deletion src/integration/exchange/services/exchange.service.ts
Original file line number Diff line number Diff line change
Expand Up @@ -165,7 +165,7 @@ export abstract class ExchangeService extends PricingProvider implements OnModul
this.logger.verbose(`Could not update order ${order.id} price: ${JSON.stringify(updatedOrder)}`);

if (updatedOrder.status === OrderStatus.OPEN)
await this.callApi((e) => e.cancelOrder(order.id, order.symbol)).catch((e) =>
await this.cancelOrder(order.id, order.symbol).catch((e) =>
this.logger.error(`Error while cancelling order ${order.id}:`, e),
);
}
Expand Down Expand Up @@ -376,6 +376,10 @@ export abstract class ExchangeService extends PricingProvider implements OnModul
);
}

protected async cancelOrder(orderId: string, symbol: string): Promise<void> {
await this.callApi((e) => e.cancelOrder(orderId, symbol));
}

// other
protected async callApi<T>(action: (exchange: Exchange) => Promise<T>): Promise<T> {
return this.queue.handle(() =>
Expand Down
9 changes: 6 additions & 3 deletions src/integration/exchange/services/mexc.service.ts
Original file line number Diff line number Diff line change
Expand Up @@ -355,14 +355,17 @@ export class MexcService extends ExchangeService {

protected async updateOrderPrice(order: Order, amount: number, price: number): Promise<string> {
// MEXC doesn't support editOrder, so cancel and create new
await this.cancelOrderById(order.id, order.symbol);
await this.cancelOrder(order.id, order.symbol);

const newOrder = await this.createOrder(order.symbol, order.side as OrderSide, amount, price);
return newOrder.id;
}

private async cancelOrderById(orderId: string, pair: string): Promise<void> {
protected async cancelOrder(orderId: string, pair: string): Promise<void> {
const symbol = pair.replace('/', '');
await this.request<unknown>('DELETE', 'order', { symbol, orderId });
await this.request<unknown>('DELETE', 'order', { symbol, orderId }).catch((e) => {
// -2011 means order is already cancelled
if (!e.message?.includes('-2011')) throw e;
});
}
}
Original file line number Diff line number Diff line change
Expand Up @@ -213,14 +213,14 @@ export class ClementineBridgeAdapter extends LiquidityActionAdapter {
private async withdraw(order: LiquidityManagementOrder): Promise<CorrelationId> {
const {
pipeline: {
rule: { targetAsset: bitcoinAsset },
rule: { targetAsset: citreaAsset },
},
} = order;

// Validate asset is BTC on Bitcoin
if (bitcoinAsset.type !== AssetType.COIN || bitcoinAsset.blockchain !== this.btcBlockchain) {
// Validate asset is cBTC on Citrea (we withdraw FROM cBTC TO BTC)
if (citreaAsset.type !== AssetType.COIN || citreaAsset.blockchain !== this.citreaBlockchain) {
throw new OrderNotProcessableException(
`Clementine withdraw only supports BTC (native coin) on ${this.btcBlockchain}`,
`Clementine withdraw only supports cBTC (native coin) on ${this.citreaBlockchain}`,
);
}

Expand All @@ -230,8 +230,8 @@ export class ClementineBridgeAdapter extends LiquidityActionAdapter {
// Validate network consistency on first use
await this.validateNetworkConsistency();

// Get the corresponding Citrea cBTC asset
const citreaAsset = await this.getCitreaAsset();
// Get the corresponding Bitcoin BTC asset
const bitcoinAsset = await this.getBtcAsset();

// Check cBTC balance on Citrea - must have at least 10 cBTC (fixed bridge amount)
const cbtcBalance = await this.citreaClient.getNativeCoinBalance();
Expand Down Expand Up @@ -739,10 +739,6 @@ export class ClementineBridgeAdapter extends LiquidityActionAdapter {
return this.network === ClementineNetwork.TESTNET4;
}

private get btcBlockchain(): Blockchain {
return this.isTestnet ? Blockchain.BITCOIN_TESTNET4 : Blockchain.BITCOIN;
}

private get citreaBlockchain(): Blockchain {
return this.isTestnet ? Blockchain.CITREA_TESTNET : Blockchain.CITREA;
}
Expand All @@ -751,10 +747,6 @@ export class ClementineBridgeAdapter extends LiquidityActionAdapter {
return this.isTestnet ? this.assetService.getBitcoinTestnet4Coin() : this.assetService.getBtcCoin();
}

private getCitreaAsset(): Promise<Asset> {
return this.isTestnet ? this.assetService.getCitreaTestnetCoin() : this.assetService.getCitreaCoin();
}

private getFeeRate(): Promise<number> {
return this.isTestnet
? this.bitcoinTestnet4FeeService.getRecommendedFeeRate()
Expand Down
Loading