diff --git a/.vscode/settings.json b/.vscode/settings.json
index 26fcc2d0cf..f320a0d975 100644
--- a/.vscode/settings.json
+++ b/.vscode/settings.json
@@ -21,6 +21,7 @@
"dfip",
"dilisense",
"ebics",
+ "firo",
"firstname",
"forex",
"frankencoin",
diff --git a/infrastructure/bicep/container-groups/README.md b/infrastructure/bicep/container-groups/README.md
index 3738f06620..f5657828d7 100644
--- a/infrastructure/bicep/container-groups/README.md
+++ b/infrastructure/bicep/container-groups/README.md
@@ -11,9 +11,7 @@
Container Instances are:
- hb-deuro-usdt: Hummingbot (dEURO/USDT)
-- hb-deuro-btc: Hummingbot (dEURO/BTC)
- hb-deps-usdt: Hummingbot (dEPS/USDT)
-- hb-deps-btc: Hummingbot (dEPS/BTC)
### Fileshare
@@ -28,9 +26,7 @@ There is an entrypoint script in the container to setup the individual environme
Connect to the running container:
- az container exec --resource-group rg-dfx-api-dev --name ci-dfx-hb-deuro-usdt-dev --exec-command /bin/bash
-- az container exec --resource-group rg-dfx-api-dev --name ci-dfx-hb-deuro-btc-dev --exec-command /bin/bash
- az container exec --resource-group rg-dfx-api-dev --name ci-dfx-hb-deps-usdt-dev --exec-command /bin/bash
-- az container exec --resource-group rg-dfx-api-dev --name ci-dfx-hb-deps-btc-dev --exec-command /bin/bash
Start the Hummingbot within the container:
diff --git a/infrastructure/bicep/container-groups/deploy.sh b/infrastructure/bicep/container-groups/deploy.sh
index 4d07cce409..aa7f0b45be 100644
--- a/infrastructure/bicep/container-groups/deploy.sh
+++ b/infrastructure/bicep/container-groups/deploy.sh
@@ -9,10 +9,8 @@ API_NAME="api"
environmentOptions=("loc" "dev" "prd")
# "hb-deuro-usdt": Hummingbot (dEURO/USDT)
-# "hb-deuro-btc": Hummingbot (dEURO/BTC)
# "hb-deps-usdt": Hummingbot (dEPS/USDT)
-# "hb-deps-btc": Hummingbot (dEPS/BTC)
-instanceNameOptions=("hb-deuro-usdt" "hb-deuro-btc" "hb-deps-usdt" "hb-deps-btc")
+instanceNameOptions=("hb-deuro-usdt" "hb-deps-usdt")
# --- ARGUMENTS --- #
DOCKER_USERNAME=
diff --git a/infrastructure/bicep/container-groups/parameters/dev-hb-deps-btc.json b/infrastructure/bicep/container-groups/parameters/dev-hb-deps-btc.json
deleted file mode 100644
index e8a08a09b3..0000000000
--- a/infrastructure/bicep/container-groups/parameters/dev-hb-deps-btc.json
+++ /dev/null
@@ -1,42 +0,0 @@
-{
- "$schema": "https://schema.management.azure.com/schemas/2019-04-01/deploymentParameters.json#",
- "contentVersion": "1.0.0.0",
- "parameters": {
- "fileShareQuota": {
- "value": 100
- },
- "containerImage": {
- "value": "dfxswiss/hummingbot:beta"
- },
- "containerVolumeMounts": {
- "value": [
- {
- "name": "volume",
- "mountPath": "/mnt/hummingbot",
- "readOnly": false
- }
- ]
- },
- "containerCPU": {
- "value": 0.5
- },
- "containerMemory": {
- "value": 1
- },
- "containerEnv": {
- "value": [
- {
- "name": "BOT_DIR",
- "value": "deps-btc-dev"
- },
- {
- "name": "STRATEGY_FILE",
- "value": "deps-btc-conf_pure_mm.yml"
- }
- ]
- },
- "containerCommand": {
- "value": []
- }
- }
-}
\ No newline at end of file
diff --git a/infrastructure/bicep/container-groups/parameters/dev-hb-deuro-btc.json b/infrastructure/bicep/container-groups/parameters/dev-hb-deuro-btc.json
deleted file mode 100644
index 56320d1e1b..0000000000
--- a/infrastructure/bicep/container-groups/parameters/dev-hb-deuro-btc.json
+++ /dev/null
@@ -1,42 +0,0 @@
-{
- "$schema": "https://schema.management.azure.com/schemas/2019-04-01/deploymentParameters.json#",
- "contentVersion": "1.0.0.0",
- "parameters": {
- "fileShareQuota": {
- "value": 100
- },
- "containerImage": {
- "value": "dfxswiss/hummingbot:beta"
- },
- "containerVolumeMounts": {
- "value": [
- {
- "name": "volume",
- "mountPath": "/mnt/hummingbot",
- "readOnly": false
- }
- ]
- },
- "containerCPU": {
- "value": 0.5
- },
- "containerMemory": {
- "value": 1
- },
- "containerEnv": {
- "value": [
- {
- "name": "BOT_DIR",
- "value": "deuro-btc-dev"
- },
- {
- "name": "STRATEGY_FILE",
- "value": "deuro-btc-conf_pure_mm.yml"
- }
- ]
- },
- "containerCommand": {
- "value": []
- }
- }
-}
\ No newline at end of file
diff --git a/infrastructure/bicep/container-groups/parameters/prd-hb-deps-btc.json b/infrastructure/bicep/container-groups/parameters/prd-hb-deps-btc.json
deleted file mode 100644
index c92bfe7838..0000000000
--- a/infrastructure/bicep/container-groups/parameters/prd-hb-deps-btc.json
+++ /dev/null
@@ -1,42 +0,0 @@
-{
- "$schema": "https://schema.management.azure.com/schemas/2019-04-01/deploymentParameters.json#",
- "contentVersion": "1.0.0.0",
- "parameters": {
- "fileShareQuota": {
- "value": 100
- },
- "containerImage": {
- "value": "dfxswiss/hummingbot:latest"
- },
- "containerVolumeMounts": {
- "value": [
- {
- "name": "volume",
- "mountPath": "/mnt/hummingbot",
- "readOnly": false
- }
- ]
- },
- "containerCPU": {
- "value": 0.5
- },
- "containerMemory": {
- "value": 1
- },
- "containerEnv": {
- "value": [
- {
- "name": "BOT_DIR",
- "value": "deps-btc"
- },
- {
- "name": "STRATEGY_FILE",
- "value": "deps-btc-conf_pure_mm.yml"
- }
- ]
- },
- "containerCommand": {
- "value": []
- }
- }
-}
\ No newline at end of file
diff --git a/infrastructure/bicep/container-groups/parameters/prd-hb-deuro-btc.json b/infrastructure/bicep/container-groups/parameters/prd-hb-deuro-btc.json
deleted file mode 100644
index e8e6fd2f45..0000000000
--- a/infrastructure/bicep/container-groups/parameters/prd-hb-deuro-btc.json
+++ /dev/null
@@ -1,42 +0,0 @@
-{
- "$schema": "https://schema.management.azure.com/schemas/2019-04-01/deploymentParameters.json#",
- "contentVersion": "1.0.0.0",
- "parameters": {
- "fileShareQuota": {
- "value": 100
- },
- "containerImage": {
- "value": "dfxswiss/hummingbot:latest"
- },
- "containerVolumeMounts": {
- "value": [
- {
- "name": "volume",
- "mountPath": "/mnt/hummingbot",
- "readOnly": false
- }
- ]
- },
- "containerCPU": {
- "value": 0.5
- },
- "containerMemory": {
- "value": 1
- },
- "containerEnv": {
- "value": [
- {
- "name": "BOT_DIR",
- "value": "deuro-btc"
- },
- {
- "name": "STRATEGY_FILE",
- "value": "deuro-btc-conf_pure_mm.yml"
- }
- ]
- },
- "containerCommand": {
- "value": []
- }
- }
-}
\ No newline at end of file
diff --git a/infrastructure/config/docker/docker-compose-firo.yml b/infrastructure/config/docker/docker-compose-firo.yml
new file mode 100644
index 0000000000..2826fb2259
--- /dev/null
+++ b/infrastructure/config/docker/docker-compose-firo.yml
@@ -0,0 +1,19 @@
+name: 'firo'
+
+services:
+ firod:
+ image: firoorg/firod:0.14.15.2
+ restart: unless-stopped
+ volumes:
+ - ./volumes/firo:/home/firod/.firo
+ ports:
+ - '8168:8168'
+ - '8888:8888'
+ healthcheck:
+ test: firo-cli -conf=/home/firod/.firo/firo.conf getblockchaininfo || exit 1
+ start_period: 120s
+ interval: 30s
+ timeout: 60s
+ retries: 10
+ command: >
+ -conf=/home/firod/.firo/firo.conf
diff --git a/infrastructure/config/firo/firo.conf b/infrastructure/config/firo/firo.conf
new file mode 100644
index 0000000000..5f3e0c79a9
--- /dev/null
+++ b/infrastructure/config/firo/firo.conf
@@ -0,0 +1,26 @@
+# Firo Full Node Configuration (DFX Integration)
+# Based on firoorg/firo v0.14.15.2 source code
+
+# Server
+server=1
+listen=1
+daemon=0
+logtimestamps=1
+
+# RPC
+rpcuser=[RPC_USER]
+rpcpassword=[RPC_PASSWORD]
+rpcport=8888
+rpcbind=0.0.0.0
+rpcallowip=0.0.0.0/0
+
+# Indexes
+txindex=1
+addressindex=0
+timestampindex=0
+spentindex=0
+
+# Performance
+dbcache=1024
+maxconnections=125
+rpcthreads=8
diff --git a/migration/1771337617394-AddChargebackAsset.js b/migration/1771337617394-AddChargebackAsset.js
new file mode 100644
index 0000000000..040fc44697
--- /dev/null
+++ b/migration/1771337617394-AddChargebackAsset.js
@@ -0,0 +1,40 @@
+/**
+ * @typedef {import('typeorm').MigrationInterface} MigrationInterface
+ * @typedef {import('typeorm').QueryRunner} QueryRunner
+ */
+
+/**
+ * @class
+ * @implements {MigrationInterface}
+ */
+module.exports = class AddChargebackAsset1771337617394 {
+ name = 'AddChargebackAsset1771337617394'
+
+ /**
+ * @param {QueryRunner} queryRunner
+ */
+ async up(queryRunner) {
+ await queryRunner.query(`ALTER TABLE "bank_tx_return" ADD "inputAmount" float`);
+ await queryRunner.query(`ALTER TABLE "bank_tx_return" ADD "inputAsset" nvarchar(256)`);
+ await queryRunner.query(`ALTER TABLE "bank_tx_return" ADD "chargebackReferenceAmount" float`);
+ await queryRunner.query(`ALTER TABLE "bank_tx_return" ADD "chargebackAsset" nvarchar(256)`);
+ await queryRunner.query(`ALTER TABLE "buy_fiat" ADD "chargebackReferenceAmount" float`);
+ await queryRunner.query(`ALTER TABLE "buy_fiat" ADD "chargebackAsset" nvarchar(256)`);
+ await queryRunner.query(`ALTER TABLE "buy_crypto" ADD "chargebackReferenceAmount" float`);
+ await queryRunner.query(`ALTER TABLE "buy_crypto" ADD "chargebackAsset" nvarchar(256)`);
+ }
+
+ /**
+ * @param {QueryRunner} queryRunner
+ */
+ async down(queryRunner) {
+ await queryRunner.query(`ALTER TABLE "buy_crypto" DROP COLUMN "chargebackAsset"`);
+ await queryRunner.query(`ALTER TABLE "buy_crypto" DROP COLUMN "chargebackReferenceAmount"`);
+ await queryRunner.query(`ALTER TABLE "buy_fiat" DROP COLUMN "chargebackAsset"`);
+ await queryRunner.query(`ALTER TABLE "buy_fiat" DROP COLUMN "chargebackReferenceAmount"`);
+ await queryRunner.query(`ALTER TABLE "bank_tx_return" DROP COLUMN "chargebackAsset"`);
+ await queryRunner.query(`ALTER TABLE "bank_tx_return" DROP COLUMN "chargebackReferenceAmount"`);
+ await queryRunner.query(`ALTER TABLE "bank_tx_return" DROP COLUMN "inputAsset"`);
+ await queryRunner.query(`ALTER TABLE "bank_tx_return" DROP COLUMN "inputAmount"`);
+ }
+}
diff --git a/migration/seed/asset.csv b/migration/seed/asset.csv
index 65eb046912..f7b2f3168e 100644
--- a/migration/seed/asset.csv
+++ b/migration/seed/asset.csv
@@ -225,3 +225,4 @@ id,name,type,buyable,sellable,chainId,sellCommand,dexName,category,blockchain,un
113,BTC,Coin,TRUE,TRUE,,,BTC,Public,Bitcoin,Bitcoin/BTC,Bitcoin,FALSE,,87278.97353,FALSE,11,68819.65463,FALSE,FALSE,FALSE,FALSE,BTC,,TRUE,0,0,74099.1069,TRUE
112,BNB,Coin,TRUE,TRUE,0xbb4cdb9cbd36b01bd1cbaebf2de08d9173bc095c,,BNB,Public,BinanceSmartChain,BinanceSmartChain/BNB,Binance Coin,FALSE,601,833.1583387,FALSE,36,656.9471065,FALSE,FALSE,FALSE,FALSE,Other,18,FALSE,0,0,707.34435,TRUE
111,ETH,Coin,TRUE,TRUE,0xc02aaa39b223fe8d0a0e5c4f27ead9083c756cc2,,ETH,Public,Ethereum,Ethereum/ETH,Ether,FALSE,1,2922.079246,FALSE,6,2304.065646,FALSE,FALSE,FALSE,FALSE,Other,18,TRUE,0,0,2480.82045,TRUE
+409,FIRO,Coin,TRUE,TRUE,,,FIRO,Public,Firo,Firo/FIRO,,FALSE,,1.5,FALSE,,1.35,FALSE,FALSE,FALSE,FALSE,Other,8,FALSE,0,0,1.4,TRUE
diff --git a/rest-client/dfx-api.http b/rest-client/dfx-api.http
index d05422c0c5..c5fe17694f 100644
--- a/rest-client/dfx-api.http
+++ b/rest-client/dfx-api.http
@@ -192,7 +192,7 @@ Authorization: Bearer {{authToken}}
@authToken = {{login.response.body.accessToken}}
-PUT {{url}}/v1/buyCrypto/refVolumes
+PUT {{url}}/v1/buyCrypto/refVolumes?start=xxx&end=yyy
Authorization: Bearer {{authToken}}
###
@@ -295,9 +295,14 @@ Authorization: Bearer {{authToken}}
@authToken = {{login.response.body.accessToken}}
-PUT {{url}}/v1/kyc/admin/step/xxx/ident
+PUT {{url}}/v1/kyc/admin/step/xxx
+content-type: application/json
Authorization: Bearer {{authToken}}
+{
+ "status": "Completed"
+}
+
###
@authToken = {{login.response.body.accessToken}}
@@ -355,22 +360,9 @@ Authorization: Bearer {{authToken}}
{
"address": "xxx",
- "blockchain": "Cardano"
-}
-
-###
-
-@authToken = {{login.response.body.accessToken}}
-
-POST {{url}}/v1/alchemy/syncTransactions
-content-type: application/json
-Authorization: Bearer {{authToken}}
-
-{
- "blockchain": "Polygon",
- "fromBlock": 63869127,
- "toBlock": 63869127,
- "address": "0x..."
+ "blockchain": "Ethereum",
+ "fromBlock": 24525738,
+ "toBlock": 24525740
}
###
diff --git a/src/config/config.ts b/src/config/config.ts
index dc11a46087..12a19d9fc6 100644
--- a/src/config/config.ts
+++ b/src/config/config.ts
@@ -149,6 +149,7 @@ export class Configuration {
bitcoinAddressFormat = '([13]|bc1)[a-zA-HJ-NP-Z0-9]{25,62}';
lightningAddressFormat = '(LNURL|LNDHUB)[A-Z0-9]{25,250}|LNNID[A-Z0-9]{66}';
sparkAddressFormat = 'sp1[a-z0-9]{6,87}';
+ firoAddressFormat = 'a[a-zA-HJ-NP-Z0-9]{33}';
moneroAddressFormat = '[48][0-9AB][1-9A-HJ-NP-Za-km-z]{93}';
ethereumAddressFormat = '0x\\w{40}';
liquidAddressFormat = '(VTp|VJL)[a-zA-HJ-NP-Z0-9]{77}';
@@ -161,13 +162,14 @@ export class Configuration {
tronAddressFormat = 'T[1-9A-HJ-NP-Za-km-z]{32,34}';
zanoAddressFormat = 'Z[a-zA-Z0-9]{96}|iZ[a-zA-Z0-9]{106}';
- allAddressFormat = `${this.bitcoinAddressFormat}|${this.lightningAddressFormat}|${this.sparkAddressFormat}|${this.moneroAddressFormat}|${this.ethereumAddressFormat}|${this.liquidAddressFormat}|${this.arweaveAddressFormat}|${this.cardanoAddressFormat}|${this.defichainAddressFormat}|${this.railgunAddressFormat}|${this.solanaAddressFormat}|${this.tronAddressFormat}|${this.zanoAddressFormat}`;
+ allAddressFormat = `${this.bitcoinAddressFormat}|${this.lightningAddressFormat}|${this.sparkAddressFormat}|${this.firoAddressFormat}|${this.moneroAddressFormat}|${this.ethereumAddressFormat}|${this.liquidAddressFormat}|${this.arweaveAddressFormat}|${this.cardanoAddressFormat}|${this.defichainAddressFormat}|${this.railgunAddressFormat}|${this.solanaAddressFormat}|${this.tronAddressFormat}|${this.zanoAddressFormat}`;
masterKeySignatureFormat = '[0-9a-fA-F]{8}\\b-[0-9a-fA-F]{4}\\b-[0-9a-fA-F]{4}\\b-[0-9a-fA-F]{4}\\b-[0-9a-fA-F]{12}';
hashSignatureFormat = '[A-Fa-f0-9]{64}';
bitcoinSignatureFormat = '(.{87}=|[A-Za-z0-9+/]+={0,2})';
lightningSignatureFormat = '[a-z0-9]{104}';
lightningCustodialSignatureFormat = '[a-z0-9]{140,146}';
+ firoSignatureFormat = '(.{87}=|[A-Za-z0-9+/]+={0,2})';
moneroSignatureFormat = 'SigV\\d[0-9a-zA-Z]{88}';
ethereumSignatureFormat = '(0x)?[a-f0-9]{130}';
arweaveSignatureFormat = '[\\w\\-]{683}';
@@ -177,7 +179,7 @@ export class Configuration {
tronSignatureFormat = '(0x)?[a-f0-9]{130}';
zanoSignatureFormat = '[a-f0-9]{128}';
- allSignatureFormat = `${this.masterKeySignatureFormat}|${this.hashSignatureFormat}|${this.bitcoinSignatureFormat}|${this.lightningSignatureFormat}|${this.lightningCustodialSignatureFormat}|${this.moneroSignatureFormat}|${this.ethereumSignatureFormat}|${this.arweaveSignatureFormat}|${this.cardanoSignatureFormat}|${this.railgunSignatureFormat}|${this.solanaSignatureFormat}|${this.tronSignatureFormat}|${this.zanoSignatureFormat}`;
+ allSignatureFormat = `${this.masterKeySignatureFormat}|${this.hashSignatureFormat}|${this.bitcoinSignatureFormat}|${this.lightningSignatureFormat}|${this.lightningCustodialSignatureFormat}|${this.firoSignatureFormat}|${this.moneroSignatureFormat}|${this.ethereumSignatureFormat}|${this.arweaveSignatureFormat}|${this.cardanoSignatureFormat}|${this.railgunSignatureFormat}|${this.solanaSignatureFormat}|${this.tronSignatureFormat}|${this.zanoSignatureFormat}`;
arweaveKeyFormat = '[\\w\\-]{683}';
cardanoKeyFormat = '.*';
@@ -628,10 +630,15 @@ export class Configuration {
tronSeed: process.env.PAYMENT_TRON_SEED,
cardanoSeed: process.env.PAYMENT_CARDANO_SEED,
bitcoinAddress: process.env.PAYMENT_BITCOIN_ADDRESS,
+ firoAddress: process.env.PAYMENT_FIRO_ADDRESS,
moneroAddress: process.env.PAYMENT_MONERO_ADDRESS,
zanoAddress: process.env.PAYMENT_ZANO_ADDRESS,
minConfirmations: (blockchain: Blockchain) =>
- [Blockchain.ETHEREUM, Blockchain.BITCOIN, Blockchain.MONERO, Blockchain.ZANO].includes(blockchain) ? 6 : 100,
+ [Blockchain.ETHEREUM, Blockchain.BITCOIN, Blockchain.FIRO, Blockchain.MONERO, Blockchain.ZANO].includes(
+ blockchain,
+ )
+ ? 6
+ : 100,
minVolume: 0.01, // CHF
maxDepositBalance: 10000, // CHF
cryptoPayoutMinAmount: +(process.env.PAYMENT_CRYPTO_PAYOUT_MIN ?? 1000), // CHF
@@ -882,6 +889,21 @@ export class Configuration {
},
certificate: process.env.LIGHTNING_API_CERTIFICATE?.split('
').join('\n'),
},
+ spark: {
+ sparkWalletSeed: process.env.SPARK_WALLET_SEED,
+ },
+ firo: {
+ node: {
+ url: process.env.FIRO_NODE_URL,
+ },
+ user: process.env.FIRO_NODE_USER,
+ password: process.env.FIRO_NODE_PASSWORD,
+ walletPassword: process.env.FIRO_NODE_WALLET_PASSWORD,
+ walletAddress: process.env.FIRO_WALLET_ADDRESS,
+ allowUnconfirmedUtxos: process.env.FIRO_ALLOW_UNCONFIRMED_UTXOS === 'true',
+ cpfpFeeMultiplier: +(process.env.FIRO_CPFP_FEE_MULTIPLIER ?? '2.0'),
+ defaultFeeMultiplier: +(process.env.FIRO_DEFAULT_FEE_MULTIPLIER ?? '1.5'),
+ },
monero: {
node: {
url: process.env.MONERO_NODE_URL,
@@ -892,6 +914,17 @@ export class Configuration {
walletAddress: process.env.MONERO_WALLET_ADDRESS,
certificate: process.env.MONERO_RPC_CERTIFICATE?.split('
').join('\n'),
},
+ zano: {
+ node: {
+ url: process.env.ZANO_NODE_URL,
+ },
+ wallet: {
+ url: process.env.ZANO_WALLET_URL,
+ address: process.env.ZANO_WALLET_ADDRESS,
+ },
+ coinId: 'd6329b5b1f7c0805b5c345f4957554002a2f557845f64d7645dae0e051a6498a',
+ fee: 0.01,
+ },
solana: {
solanaWalletSeed: process.env.SOLANA_WALLET_SEED,
solanaGatewayUrl: process.env.SOLANA_GATEWAY_URL,
@@ -938,20 +971,6 @@ export class Configuration {
index: accountIndex,
}),
},
- zano: {
- node: {
- url: process.env.ZANO_NODE_URL,
- },
- wallet: {
- url: process.env.ZANO_WALLET_URL,
- address: process.env.ZANO_WALLET_ADDRESS,
- },
- coinId: 'd6329b5b1f7c0805b5c345f4957554002a2f557845f64d7645dae0e051a6498a',
- fee: 0.01,
- },
- spark: {
- sparkWalletSeed: process.env.SPARK_WALLET_SEED,
- },
frankencoin: {
zchfGraphUrl: process.env.ZCHF_GRAPH_URL,
contractAddress: {
diff --git a/src/integration/alchemy/controllers/alchemy.controller.ts b/src/integration/alchemy/controllers/alchemy.controller.ts
index f57ebf14dd..b4afee65df 100644
--- a/src/integration/alchemy/controllers/alchemy.controller.ts
+++ b/src/integration/alchemy/controllers/alchemy.controller.ts
@@ -1,6 +1,5 @@
import {
BadRequestException,
- Body,
Controller,
Get,
Headers,
@@ -17,20 +16,15 @@ import { RoleGuard } from 'src/shared/auth/role.guard';
import { UserActiveGuard } from 'src/shared/auth/user-active.guard';
import { UserRole } from 'src/shared/auth/user-role.enum';
import { DfxLogger } from 'src/shared/services/dfx-logger';
-import { AlchemySyncTransactionsDto } from '../dto/alchemy-sync-transactions.dto';
import { AlchemyWebhookDto } from '../dto/alchemy-webhook.dto';
import { AlchemyWebhookService } from '../services/alchemy-webhook.service';
-import { AlchemyService } from '../services/alchemy.service';
@ApiTags('Alchemy')
@Controller('alchemy')
export class AlchemyController {
private readonly logger = new DfxLogger(AlchemyController);
- constructor(
- private readonly alchemyWebhookService: AlchemyWebhookService,
- private readonly alchemyService: AlchemyService,
- ) {}
+ constructor(private readonly alchemyWebhookService: AlchemyWebhookService) {}
@Post('addressWebhook')
@ApiExcludeEndpoint()
@@ -52,14 +46,6 @@ export class AlchemyController {
}
}
- @Post('syncTransactions')
- @ApiBearerAuth()
- @ApiExcludeEndpoint()
- @UseGuards(AuthGuard(), RoleGuard(UserRole.ADMIN), UserActiveGuard())
- async syncTransactions(@Body() dto: AlchemySyncTransactionsDto) {
- return this.alchemyService.syncTransactions(dto);
- }
-
@Get('addresses/:webhookId')
@ApiBearerAuth()
@ApiExcludeEndpoint()
diff --git a/src/integration/alchemy/dto/alchemy-sync-transactions.dto.ts b/src/integration/alchemy/dto/alchemy-sync-transactions.dto.ts
deleted file mode 100644
index 968a441eab..0000000000
--- a/src/integration/alchemy/dto/alchemy-sync-transactions.dto.ts
+++ /dev/null
@@ -1,22 +0,0 @@
-import { ApiProperty } from '@nestjs/swagger';
-import { IsEnum, IsNotEmpty } from 'class-validator';
-import { Blockchain } from 'src/integration/blockchain/shared/enums/blockchain.enum';
-
-export class AlchemySyncTransactionsDto {
- @ApiProperty()
- @IsNotEmpty()
- @IsEnum(Blockchain)
- blockchain: Blockchain;
-
- @ApiProperty()
- @IsNotEmpty()
- fromBlock: number;
-
- @ApiProperty()
- @IsNotEmpty()
- toBlock: number;
-
- @ApiProperty()
- @IsNotEmpty()
- address: string;
-}
diff --git a/src/integration/alchemy/services/alchemy.service.ts b/src/integration/alchemy/services/alchemy.service.ts
index 49ce055a65..130333bcf1 100644
--- a/src/integration/alchemy/services/alchemy.service.ts
+++ b/src/integration/alchemy/services/alchemy.service.ts
@@ -19,9 +19,9 @@ import { Blockchain } from 'src/integration/blockchain/shared/enums/blockchain.e
import { EvmUtil } from 'src/integration/blockchain/shared/evm/evm.util';
import { Asset } from 'src/shared/models/asset/asset.entity';
import { Util } from 'src/shared/utils/util';
+import { PollAddressDto } from 'src/subdomains/supporting/payin/interfaces';
import { AlchemyNetworkMapper } from '../alchemy-network-mapper';
import { AlchemyAssetTransfersDto } from '../dto/alchemy-asset-transfers.dto';
-import { AlchemySyncTransactionsDto } from '../dto/alchemy-sync-transactions.dto';
export interface AssetTransfersParams {
fromAddress?: string;
@@ -184,18 +184,17 @@ export class AlchemyService {
});
}
- async syncTransactions(syncTransactions: AlchemySyncTransactionsDto) {
- const blockchain = syncTransactions.blockchain;
- const chainId = EvmUtil.getChainId(blockchain);
+ async syncTransactions(dto: PollAddressDto) {
+ const chainId = EvmUtil.getChainId(dto.blockchain);
const categories = this.getNativeCoinCategories(chainId);
categories.push(...this.getERC20Categories(chainId));
const params: AssetTransfersParams = {
fromAddress: undefined,
- toAddress: syncTransactions.address,
- fromBlock: syncTransactions.fromBlock,
- toBlock: syncTransactions.toBlock,
+ toAddress: dto.address,
+ fromBlock: dto.fromBlock,
+ toBlock: dto.toBlock,
categories: categories,
};
@@ -204,7 +203,7 @@ export class AlchemyService {
if (assetTransfers.length) {
assetTransfers.sort((atr1, atr2) => Number(atr1.blockNum) - Number(atr2.blockNum));
- this.assetTransfersSubject.next({ blockchain, assetTransfers });
+ this.assetTransfersSubject.next({ blockchain: dto.blockchain, assetTransfers });
}
}
diff --git a/src/integration/blockchain/base/base-client.ts b/src/integration/blockchain/base/base-client.ts
index b7ebc44432..ac3b70bf5b 100644
--- a/src/integration/blockchain/base/base-client.ts
+++ b/src/integration/blockchain/base/base-client.ts
@@ -157,14 +157,19 @@ export class BaseClient extends EvmClient implements L2BridgeEvmClient {
}
async getCurrentGasCostForTokenTransaction(token: Asset): Promise {
- const totalGasCost = await estimateTotalGasCost(this.l2Provider, {
- from: this.walletAddress,
- to: token.chainId,
- data: this.dummyTokenPayload,
- type: 2,
- });
-
- return EvmUtil.fromWeiAmount(totalGasCost);
+ try {
+ const totalGasCost = await estimateTotalGasCost(this.l2Provider, {
+ from: this.walletAddress,
+ to: token.chainId,
+ data: this.dummyTokenPayload,
+ type: 2,
+ });
+
+ return EvmUtil.fromWeiAmount(totalGasCost);
+ } catch (e) {
+ this.logger.verbose(`Gas estimation failed for token ${token.uniqueName}: ${e.message}. Using default.`);
+ return 0.00001;
+ }
}
async getTxActualFee(txHash: string): Promise {
diff --git a/src/integration/blockchain/bitcoin/bitcoin.module.ts b/src/integration/blockchain/bitcoin/bitcoin.module.ts
index 0dcd1d0498..f60bcd4989 100644
--- a/src/integration/blockchain/bitcoin/bitcoin.module.ts
+++ b/src/integration/blockchain/bitcoin/bitcoin.module.ts
@@ -1,8 +1,8 @@
import { Module } from '@nestjs/common';
import { SharedModule } from 'src/shared/shared.module';
-import { BitcoinService } from './node/bitcoin.service';
import { NodeController } from './node/node.controller';
import { BitcoinFeeService } from './services/bitcoin-fee.service';
+import { BitcoinService } from './services/bitcoin.service';
@Module({
imports: [SharedModule],
diff --git a/src/integration/blockchain/bitcoin/node/__tests__/bitcoin-rpc.integration.spec.ts b/src/integration/blockchain/bitcoin/node/__tests__/bitcoin-rpc.integration.spec.ts
index e868c434f6..e932edc2de 100644
--- a/src/integration/blockchain/bitcoin/node/__tests__/bitcoin-rpc.integration.spec.ts
+++ b/src/integration/blockchain/bitcoin/node/__tests__/bitcoin-rpc.integration.spec.ts
@@ -14,12 +14,12 @@
* npm run test -- --testPathPattern=bitcoin-rpc.integration
*/
-import { Test, TestingModule } from '@nestjs/testing';
-import { ConfigModule } from '@nestjs/config';
-import { BitcoinService, BitcoinNodeType } from '../bitcoin.service';
-import { BitcoinClient } from '../bitcoin-client';
import { HttpModule } from '@nestjs/axios';
+import { ConfigModule } from '@nestjs/config';
+import { Test, TestingModule } from '@nestjs/testing';
import { HttpService } from 'src/shared/services/http.service';
+import { BitcoinNodeType, BitcoinService } from '../../services/bitcoin.service';
+import { BitcoinClient } from '../bitcoin-client';
// Skip tests if no Bitcoin node is configured
const SKIP_INTEGRATION_TESTS = !process.env.NODE_BTC_INP_URL_ACTIVE;
diff --git a/src/integration/blockchain/bitcoin/node/bitcoin-based-client.ts b/src/integration/blockchain/bitcoin/node/bitcoin-based-client.ts
index 73750ca0b7..ba3a7bdb28 100644
--- a/src/integration/blockchain/bitcoin/node/bitcoin-based-client.ts
+++ b/src/integration/blockchain/bitcoin/node/bitcoin-based-client.ts
@@ -3,6 +3,7 @@ import { Asset } from 'src/shared/models/asset/asset.entity';
import { HttpService } from 'src/shared/services/http.service';
import { BlockchainTokenBalance } from '../../shared/dto/blockchain-token-balance.dto';
import { BlockchainSignedTransactionResponse } from '../../shared/dto/signed-transaction-reponse.dto';
+import { CoinOnly } from '../../shared/util/blockchain-client';
import { NodeClient, NodeClientConfig } from './node-client';
export interface TransactionHistory {
@@ -24,7 +25,7 @@ export interface TestMempoolResult {
'reject-reason': string;
}
-export abstract class BitcoinBasedClient extends NodeClient {
+export abstract class BitcoinBasedClient extends NodeClient implements CoinOnly {
constructor(http: HttpService, url: string, config: NodeClientConfig) {
super(http, url, config);
}
diff --git a/src/integration/blockchain/bitcoin/node/node.controller.ts b/src/integration/blockchain/bitcoin/node/node.controller.ts
index 188c9e5ce8..a2b29db33d 100644
--- a/src/integration/blockchain/bitcoin/node/node.controller.ts
+++ b/src/integration/blockchain/bitcoin/node/node.controller.ts
@@ -1,13 +1,13 @@
import { BadRequestException, Body, Controller, Get, Param, Post, UseGuards } from '@nestjs/common';
-import { InWalletTransaction } from './node-client';
import { AuthGuard } from '@nestjs/passport';
import { ApiBearerAuth, ApiExcludeEndpoint } from '@nestjs/swagger';
import { RoleGuard } from 'src/shared/auth/role.guard';
import { UserActiveGuard } from 'src/shared/auth/user-active.guard';
import { UserRole } from 'src/shared/auth/user-role.enum';
import { HttpError } from 'src/shared/services/http.service';
-import { BitcoinNodeType, BitcoinService } from './bitcoin.service';
+import { BitcoinNodeType, BitcoinService } from '../services/bitcoin.service';
import { CommandDto } from './dto/command.dto';
+import { InWalletTransaction } from './node-client';
@Controller('node')
export class NodeController {
diff --git a/src/integration/blockchain/bitcoin/services/__tests__/bitcoin-fee.service.spec.ts b/src/integration/blockchain/bitcoin/services/__tests__/bitcoin-fee.service.spec.ts
index 9b6cd70728..8fab3f6904 100644
--- a/src/integration/blockchain/bitcoin/services/__tests__/bitcoin-fee.service.spec.ts
+++ b/src/integration/blockchain/bitcoin/services/__tests__/bitcoin-fee.service.spec.ts
@@ -5,9 +5,9 @@
* including fee rate caching, TX fee rate lookup, and batch operations.
*/
-import { BitcoinFeeService } from '../bitcoin-fee.service';
-import { BitcoinService, BitcoinNodeType } from '../../node/bitcoin.service';
import { BitcoinClient } from '../../node/bitcoin-client';
+import { BitcoinFeeService } from '../bitcoin-fee.service';
+import { BitcoinNodeType, BitcoinService } from '../bitcoin.service';
describe('BitcoinFeeService', () => {
let service: BitcoinFeeService;
diff --git a/src/integration/blockchain/bitcoin/services/__tests__/crypto.service.spec.ts b/src/integration/blockchain/bitcoin/services/__tests__/crypto.service.spec.ts
index 65813a1cde..69cc0f5b9d 100644
--- a/src/integration/blockchain/bitcoin/services/__tests__/crypto.service.spec.ts
+++ b/src/integration/blockchain/bitcoin/services/__tests__/crypto.service.spec.ts
@@ -2,6 +2,7 @@ import { createMock } from '@golevelup/ts-jest';
import { Test } from '@nestjs/testing';
import { ArweaveService } from 'src/integration/blockchain/arweave/services/arweave.service';
import { CardanoService } from 'src/integration/blockchain/cardano/services/cardano.service';
+import { FiroService } from 'src/integration/blockchain/firo/services/firo.service';
import { MoneroService } from 'src/integration/blockchain/monero/services/monero.service';
import { Blockchain } from 'src/integration/blockchain/shared/enums/blockchain.enum';
import { BlockchainRegistryService } from 'src/integration/blockchain/shared/services/blockchain-registry.service';
@@ -14,7 +15,7 @@ import { LightningService } from 'src/integration/lightning/services/lightning.s
import { RailgunService } from 'src/integration/railgun/railgun.service';
import { TestUtil } from 'src/shared/utils/test.util';
import { UserAddressType } from 'src/subdomains/generic/user/models/user/user.enum';
-import { BitcoinService } from '../../node/bitcoin.service';
+import { BitcoinService } from '../bitcoin.service';
describe('CryptoService', () => {
beforeEach(async () => {
@@ -24,6 +25,7 @@ describe('CryptoService', () => {
{ provide: BitcoinService, useValue: createMock() },
{ provide: LightningService, useValue: createMock() },
{ provide: SparkService, useValue: createMock() },
+ { provide: FiroService, useValue: createMock() },
{ provide: MoneroService, useValue: createMock() },
{ provide: ZanoService, useValue: createMock() },
{ provide: SolanaService, useValue: createMock() },
@@ -121,6 +123,10 @@ describe('CryptoService', () => {
).toEqual(UserAddressType.LN_NID);
});
+ it('should return Blockchain.FIRO for address a8MuyHBKL3nYZKAa82x13FxqtExP2sQCqu', () => {
+ expect(CryptoService.getBlockchainsBasedOn('a8MuyHBKL3nYZKAa82x13FxqtExP2sQCqu')).toEqual([Blockchain.FIRO]);
+ });
+
it('should return Blockchain.ETHEREUM and Blockchain.BINANCE_SMART_CHAIN for address 0x2d84553B3A4753009A314106d58F0CC21f441234', () => {
expect(CryptoService.getBlockchainsBasedOn('0x2d84553B3A4753009A314106d58F0CC21f441234')).toEqual([
Blockchain.ETHEREUM,
diff --git a/src/integration/blockchain/bitcoin/services/bitcoin-based-fee.service.ts b/src/integration/blockchain/bitcoin/services/bitcoin-based-fee.service.ts
new file mode 100644
index 0000000000..a4a9775b7d
--- /dev/null
+++ b/src/integration/blockchain/bitcoin/services/bitcoin-based-fee.service.ts
@@ -0,0 +1,77 @@
+import { DfxLogger } from 'src/shared/services/dfx-logger';
+import { AsyncCache, CacheItemResetPeriod } from 'src/shared/utils/async-cache';
+import { NodeClient } from '../node/node-client';
+
+export type TxFeeRateStatus = 'unconfirmed' | 'confirmed' | 'not_found' | 'error';
+
+export interface TxFeeRateResult {
+ status: TxFeeRateStatus;
+ feeRate?: number;
+}
+
+export abstract class BitcoinBasedFeeService {
+ private readonly logger = new DfxLogger(BitcoinBasedFeeService);
+
+ private readonly feeRateCache = new AsyncCache(CacheItemResetPeriod.EVERY_30_SECONDS);
+ private readonly txFeeRateCache = new AsyncCache(CacheItemResetPeriod.EVERY_30_SECONDS);
+
+ constructor(protected readonly client: NodeClient) {}
+
+ async getRecommendedFeeRate(): Promise {
+ return this.feeRateCache.get(
+ 'fastestFee',
+ async () => {
+ const feeRate = await this.client.estimateSmartFee(1);
+ if (feeRate === null) {
+ throw new Error('Failed to estimate fee rate from node');
+ }
+ return feeRate;
+ },
+ undefined,
+ true,
+ );
+ }
+
+ async getTxFeeRate(txid: string): Promise {
+ return this.txFeeRateCache.get(
+ txid,
+ async () => {
+ try {
+ const entry = await this.client.getMempoolEntry(txid);
+
+ if (entry === null) {
+ const tx = await this.client.getTx(txid);
+ if (tx && tx.confirmations > 0) {
+ return { status: 'confirmed' as const };
+ }
+ return { status: 'not_found' as const };
+ }
+
+ return { status: 'unconfirmed' as const, feeRate: entry.feeRate };
+ } catch (e) {
+ this.logger.error(`Failed to get TX fee rate for ${txid}:`, e);
+ return { status: 'error' as const };
+ }
+ },
+ undefined,
+ true,
+ );
+ }
+
+ async getTxFeeRates(txids: string[]): Promise