Feat: Backend commit

This commit is contained in:
Miranda
2026-02-26 21:35:37 -03:00
committed by LORDBABUINO
parent 7f2bc724a1
commit 67db81448b
9 changed files with 825 additions and 0 deletions
@@ -0,0 +1,231 @@
# Conexão com Bitcoin Testnet4
Este guia mostra como conectar na blockchain Bitcoin testnet4 usando BDK-JVM.
## 🚀 Como Funciona
### 1. Executar o Exemplo de Conexão
#### Opção A: Via Código Java
Execute a classe `BitcoinConnectionExample`:
```bash
cd /home/herbe/src/stealth/backend/src/StealthBackend
mvn compile
mvn exec:java -Dexec.mainClass="org.backend.stealth.service.BitcoinConnectionExample"
```
#### Opção B: Via REST API
Inicie o servidor Quarkus:
```bash
./mvnw quarkus:dev
```
Acesse os endpoints:
**1. Conectar na blockchain testnet4:**
```bash
curl -X POST http://localhost:8080/api/testnet4/connect
```
**2. Obter informações da blockchain:**
```bash
curl http://localhost:8080/api/testnet4/info
```
**3. Gerar novo endereço:**
```bash
curl http://localhost:8080/api/testnet4/address
```
**4. Verificar saldo:**
```bash
curl http://localhost:8080/api/testnet4/balance
```
**5. Sincronizar wallet:**
```bash
curl -X POST http://localhost:8080/api/testnet4/sync
```
## 📊 O Que o Código Faz
### 1. Configuração da Network
```java
Network network = Network.TESTNET;
```
Define que vamos usar a testnet do Bitcoin.
### 2. Configuração do Esplora
```java
String esploraUrl = "https://mempool.space/testnet4/api";
EsploraConfig esploraConfig = new EsploraConfig(
esploraUrl, // URL do servidor Esplora
null, // Proxy (null = sem proxy)
5L, // Timeout em segundos
null, // Stop gap
null // Timeout para requests longos
);
```
Esplora é uma API que permite acessar dados da blockchain sem rodar um nó completo.
### 3. Conexão com Blockchain
```java
BlockchainConfig blockchainConfig = BlockchainConfig.esplora(esploraConfig);
Blockchain blockchain = new Blockchain(blockchainConfig);
```
Cria a conexão com a blockchain testnet4.
### 4. Verificar Conexão
```java
long height = blockchain.getHeight();
String blockHash = blockchain.getBlockHash(height);
```
Obtém a altura atual (número de blocos) e o hash do último bloco.
### 5. Criar Wallet
```java
Mnemonic mnemonic = new Mnemonic(WordCount.WORDS12);
DescriptorSecretKey descriptorSecretKey = new DescriptorSecretKey(network, mnemonic, null);
String descriptor = "wpkh(" + descriptorSecretKey.asString() + "/84'/1'/0'/0/*)";
String changeDescriptor = "wpkh(" + descriptorSecretKey.asString() + "/84'/1'/0'/1/*)";
Wallet wallet = new Wallet(descriptor, changeDescriptor, network, databaseConfig);
```
Cria uma wallet HD (Hierarchical Deterministic) usando BIP84 (native segwit).
### 6. Sincronizar Wallet
```java
wallet.sync(blockchain, null);
```
Sincroniza a wallet com a blockchain para obter transações e saldo.
### 7. Gerar Endereço
```java
AddressInfo addressInfo = wallet.getAddress(AddressIndex.NEW);
```
Gera um novo endereço para receber bitcoins.
## 🔑 Componentes Principais
### Blockchain
- Representa a conexão com a rede Bitcoin
- Permite consultar blocos, altura, e broadcast de transações
### Wallet
- Gerencia chaves privadas e endereços
- Rastreia saldo e transações
- Cria e assina transações
### Mnemonic
- 12 palavras que permitem recuperar a wallet
- **MUITO IMPORTANTE**: Guarde com segurança!
- Qualquer pessoa com essas palavras tem acesso aos fundos
### Descriptor
- Define a estrutura da wallet
- `wpkh` = Witness Public Key Hash (native segwit)
- `/84'/1'/0'/0/*` = Caminho BIP84 para testnet
## 💰 Como Obter Testnet4 Bitcoins
1. Execute o código para gerar um endereço
2. Copie o endereço gerado (começa com `tb1...`)
3. Acesse um faucet de testnet4:
- https://mempool.space/testnet4
- Procure por "faucet" na página
4. Cole seu endereço e solicite bitcoins
5. Aguarde alguns minutos para confirmação
6. Sincronize a wallet e verifique o saldo
## 🔧 Estrutura do Código
```
src/main/java/org/backend/stealth/
├── service/
│ ├── BitcoinController.java # Controller principal com lógica de conexão
│ └── BitcoinConnectionExample.java # Exemplo standalone
├── controller/
│ └── BitcoinTestnet4Resource.java # REST API endpoints
└── service/dto/
├── BlockchainInfoDTO.java # DTO para info da blockchain
├── AddressResponseDTO.java # DTO para endereços
├── BalanceDTO.java # DTO para saldo
├── ErrorDTO.java # DTO para erros
└── MessageDTO.java # DTO para mensagens
```
## 📝 Exemplo de Resposta
### GET /api/testnet4/info
```json
{
"network": "TESTNET4",
"height": 150234,
"latestBlockHash": "00000000000000123abc...",
"esploraUrl": "https://mempool.space/testnet4/api"
}
```
### GET /api/testnet4/address
```json
{
"address": "tb1qxy2kgdygjrsqtzq2n0yrf2493p83kkfjhx0wlh",
"instructions": "Use um faucet para receber testnet4 bitcoins: https://mempool.space/testnet4"
}
```
### GET /api/testnet4/balance
```json
{
"total": 100000,
"confirmed": 100000,
"immature": 0,
"trustedPending": 0,
"untrustedPending": 0
}
```
## ⚠️ Notas Importantes
1. **Testnet4**: Esta é uma rede de testes. Os bitcoins não têm valor real.
2. **Mnemonic**: Sempre guarde as 12 palavras em local seguro.
3. **Esplora**: Dependemos de um servidor externo. Se estiver lento, pode ser problema na API.
4. **Sincronização**: A primeira sincronização pode demorar alguns segundos.
## 🐛 Troubleshooting
### Erro: "Connection timeout"
- Verifique sua conexão com internet
- Tente usar outro servidor Esplora
- Aumente o timeout na configuração
### Erro: "Invalid descriptor"
- Verifique se está usando Network.TESTNET
- Confirme que o descriptor está correto
### Saldo sempre zero
- Aguarde a confirmação da transação (10-60 minutos)
- Sincronize a wallet novamente
- Verifique se usou o endereço correto no faucet
## 📚 Recursos Adicionais
- [BDK Documentation](https://bitcoindevkit.org/)
- [Mempool.space Testnet4](https://mempool.space/testnet4)
- [BIP84 Specification](https://github.com/bitcoin/bips/blob/master/bip-0084.mediawiki)
- [Bitcoin Testnet Guide](https://developer.bitcoin.org/examples/testing.html)
## ✅ Checklist de Teste
- [ ] Executar `BitcoinConnectionExample`
- [ ] Ver log de conexão bem-sucedida
- [ ] Verificar altura da blockchain
- [ ] Salvar as 12 palavras do mnemonic
- [ ] Copiar endereço gerado
- [ ] Solicitar bitcoins no faucet
- [ ] Sincronizar wallet
- [ ] Verificar saldo atualizado
@@ -0,0 +1,127 @@
package org.backend.stealth.controller;
import jakarta.inject.Inject;
import jakarta.ws.rs.*;
import jakarta.ws.rs.core.MediaType;
import jakarta.ws.rs.core.Response;
import org.backend.stealth.domain.repository.BitcoinRepository;
import org.backend.stealth.service.dto.*;
import org.bitcoindevkit.Balance;
import org.eclipse.microprofile.openapi.annotations.Operation;
import org.eclipse.microprofile.openapi.annotations.tags.Tag;
/**
* REST API para conexão com Bitcoin Testnet4
*/
@Path("/api/testnet4")
@Produces(MediaType.APPLICATION_JSON)
@Consumes(MediaType.APPLICATION_JSON)
@Tag(name = "Bitcoin Testnet4", description = "Operações com blockchain Bitcoin testnet4")
public class Testnet4Resource {
@Inject
BitcoinRepository bitcoinRepository;
@GET
@Path("/status")
@Operation(summary = "Verificar status da conexão", description = "Verifica se está conectado na blockchain testnet4")
public Response getStatus() {
try {
boolean connected = bitcoinRepository.isConnected();
long height = connected ? bitcoinRepository.getHeight() : -1;
String status = connected ? "✅ Conectado" : "❌ Desconectado";
return Response.ok(new Testnet4StatusDTO(
connected,
status,
height,
"https://mempool.space/testnet4/api"
)).build();
} catch (Exception e) {
return Response.status(Response.Status.INTERNAL_SERVER_ERROR)
.entity(new ErrorDTO("Erro: " + e.getMessage()))
.build();
}
}
@GET
@Path("/height")
@Operation(summary = "Obter altura da blockchain", description = "Retorna o número atual de blocos")
public Response getHeight() {
try {
long height = bitcoinRepository.getHeight();
return Response.ok(new BlockHeightDTO(height)).build();
} catch (Exception e) {
return Response.status(Response.Status.INTERNAL_SERVER_ERROR)
.entity(new ErrorDTO("Erro: " + e.getMessage()))
.build();
}
}
@GET
@Path("/address")
@Operation(summary = "Gerar novo endereço", description = "Gera um novo endereço para receber testnet4 bitcoins")
public Response getNewAddress() {
try {
String address = bitcoinRepository.getNewAddress();
if (address == null) {
return Response.status(Response.Status.INTERNAL_SERVER_ERROR)
.entity(new ErrorDTO("Erro ao gerar endereço"))
.build();
}
return Response.ok(new AddressResponseDTO(
address,
"Use um faucet para receber testnet4 bitcoins: https://mempool.space/testnet4"
)).build();
} catch (Exception e) {
return Response.status(Response.Status.INTERNAL_SERVER_ERROR)
.entity(new ErrorDTO("Erro: " + e.getMessage()))
.build();
}
}
@GET
@Path("/balance")
@Operation(summary = "Verificar saldo", description = "Retorna o saldo da wallet")
public Response getBalance() {
try {
Balance balance = bitcoinRepository.getBalance();
if (balance == null) {
return Response.status(Response.Status.INTERNAL_SERVER_ERROR)
.entity(new ErrorDTO("Erro ao obter saldo"))
.build();
}
return Response.ok(new BalanceDTO(
balance.total(),
balance.confirmed(),
balance.immature(),
balance.trustedPending(),
balance.untrustedPending()
)).build();
} catch (Exception e) {
return Response.status(Response.Status.INTERNAL_SERVER_ERROR)
.entity(new ErrorDTO("Erro: " + e.getMessage()))
.build();
}
}
@POST
@Path("/sync")
@Operation(summary = "Sincronizar wallet", description = "Sincroniza a wallet com a blockchain")
public Response sync() {
try {
bitcoinRepository.syncWallet();
return Response.ok(new MessageDTO("✅ Wallet sincronizada com sucesso!")).build();
} catch (Exception e) {
return Response.status(Response.Status.INTERNAL_SERVER_ERROR)
.entity(new ErrorDTO("Erro: " + e.getMessage()))
.build();
}
}
}
@@ -0,0 +1,189 @@
package org.backend.stealth.domain.repository;
import jakarta.annotation.PostConstruct;
import jakarta.enterprise.context.ApplicationScoped;
import org.bitcoindevkit.*;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
/**
* Repository for Bitcoin blockchain connection (Testnet4)
*/
@ApplicationScoped
public class BitcoinRepository {
private static final Logger logger = LoggerFactory.getLogger(BitcoinRepository.class);
private Blockchain blockchain;
private Wallet wallet;
private final Network network = Network.TESTNET;
private final String esploraUrl = "https://mempool.space/testnet4/api";
@PostConstruct
public void init() {
logger.info("🚀 Iniciando conexão com Bitcoin Testnet4...");
connectBlockchain();
}
/**
* Connect to Bitcoin blockchain
*/
public void connectBlockchain() {
try {
logger.info("📡 Conectando em: {}", esploraUrl);
// Create Esplora config - using correct Kotlin UByte conversion
EsploraConfig esploraConfig = new EsploraConfig(
esploraUrl,
null,
kotlin.UByte.valueOf((byte) 5), // timeout 5 seconds
(long) 100,
null
);
// Create blockchain config using correct method
BlockchainConfig blockchainConfig = new BlockchainConfig.Esplora(esploraConfig);
// Create blockchain
blockchain = new Blockchain(blockchainConfig);
logger.info("✅ Blockchain conectado com sucesso!");
// Get height
long height = blockchain.height();
logger.info("📊 Altura da blockchain: {} blocos", height);
// Initialize wallet
createWallet();
} catch (Exception e) {
logger.error("❌ Erro ao conectar: {}", e.getMessage(), e);
}
}
/**
* Create a new wallet
*/
private void createWallet() {
try {
logger.info("🔑 Criando wallet...");
// Generate mnemonic
Mnemonic mnemonic = new Mnemonic(WordCount.WORDS12);
logger.warn("⚠️ GUARDE ESTAS PALAVRAS:");
logger.warn("📝 {}", mnemonic.asString());
// Create descriptor secret key
DescriptorSecretKey descriptorSecretKey = new DescriptorSecretKey(
network,
mnemonic,
null
);
// Create descriptors (BIP84)
String descStr = "wpkh(" + descriptorSecretKey.asString() + "/84'/1'/0'/0/*)";
String changeDescStr = "wpkh(" + descriptorSecretKey.asString() + "/84'/1'/0'/1/*)";
Descriptor descriptor = new Descriptor(descStr, network);
Descriptor changeDescriptor = new Descriptor(changeDescStr, network);
// Create database config
DatabaseConfig databaseConfig = DatabaseConfig.memory();
// Create wallet
wallet = new Wallet(descriptor, changeDescriptor, network, databaseConfig);
logger.info("✅ Wallet criada!");
// Sync and show address
syncWallet();
showAddress();
} catch (Exception e) {
logger.error("❌ Erro ao criar wallet: {}", e.getMessage(), e);
}
}
/**
* Sync wallet with blockchain
*/
public void syncWallet() {
try {
if (wallet != null && blockchain != null) {
logger.info("🔄 Sincronizando...");
wallet.sync(blockchain, null);
logger.info("✅ Sincronizado!");
Balance balance = wallet.balance();
logger.info("💵 Saldo: {} satoshis", balance.total());
}
} catch (Exception e) {
logger.error("❌ Erro ao sincronizar: {}", e.getMessage(), e);
}
}
/**
* Show a receiving address
*/
private void showAddress() {
try {
AddressInfo addressInfo = wallet.revealNextAddress(KeychainKind.EXTERNAL);
logger.info("💰 Endereço: {}", addressInfo.address().asString());
logger.info("📍 Para receber testnet coins: https://mempool.space/testnet4");
} catch (Exception e) {
logger.error("Erro ao gerar endereço: {}", e.getMessage(), e);
}
}
/**
* Get blockchain height
*/
public long getHeight() {
return blockchain != null ? blockchain.height() : -1;
}
/**
* Get new address
*/
public String getNewAddress() {
try {
if (wallet == null) return null;
AddressInfo addressInfo = wallet.revealNextAddress(KeychainKind.EXTERNAL);
return addressInfo.address().asString();
} catch (Exception e) {
logger.error("Erro: {}", e.getMessage());
return null;
}
}
/**
* Get balance
*/
public Balance getBalance() {
try {
if (wallet == null) return null;
syncWallet();
return wallet.balance();
} catch (Exception e) {
logger.error("Erro: {}", e.getMessage());
return null;
}
}
public boolean isConnected() {
return blockchain != null;
}
public Blockchain getBlockchain() {
return blockchain;
}
public Wallet getWallet() {
return wallet;
}
public Network getNetwork() {
return network;
}
}
@@ -0,0 +1,231 @@
package org.backend.stealth.service;
import jakarta.annotation.PostConstruct;
import jakarta.enterprise.context.ApplicationScoped;
import org.bitcoindevkit.*;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
/**
* Service for connecting to Bitcoin Testnet4 blockchain
* This is a simplified version that connects to testnet4
*/
@ApplicationScoped
public class Testnet4ConnectionService {
private static final Logger logger = LoggerFactory.getLogger(Testnet4ConnectionService.class);
private Blockchain blockchain;
private Wallet wallet;
private Network network = Network.TESTNET;
private String esploraUrl = "https://mempool.space/testnet4/api";
/**
* Initialize connection on startup
*/
@PostConstruct
public void init() {
logger.info("🚀 Iniciando conexão com Bitcoin Testnet4...");
connectToBlockchain();
}
/**
* Connect to Bitcoin testnet4 blockchain
*/
public boolean connectToBlockchain() {
try {
logger.info("📡 Configurando conexão Esplora: {}", esploraUrl);
// Create Esplora configuration
EsploraConfig esploraConfig = new EsploraConfig(
esploraUrl,
null, // proxy
(byte) 5, // timeout in seconds
(long) 100, // stop_gap
null // timeout for long requests
);
// Create blockchain config
BlockchainConfig blockchainConfig = BlockchainConfig.newEsploraConfig(esploraConfig);
// Initialize blockchain connection
blockchain = new Blockchain(blockchainConfig);
logger.info("✅ Blockchain conectado com sucesso!");
// Get blockchain info
long height = blockchain.getHeight();
logger.info("📊 Altura atual da blockchain: {} blocos", height);
// Initialize a simple wallet
initializeWallet();
return true;
} catch (Exception e) {
logger.error("❌ Erro ao conectar na blockchain: {}", e.getMessage(), e);
return false;
}
}
/**
* Initialize a simple wallet
*/
private void initializeWallet() {
try {
logger.info("🔑 Inicializando wallet...");
// Generate mnemonic
Mnemonic mnemonic = new Mnemonic(WordCount.WORDS12);
String mnemonicWords = mnemonic.asString();
logger.warn("⚠️ IMPORTANTE - Guarde essas palavras com segurança:");
logger.warn("📝 {}", mnemonicWords);
// Create descriptor secret key
DescriptorSecretKey descriptorSecretKey = new DescriptorSecretKey(
network,
mnemonic,
null // no password
);
// Create descriptors (BIP84 - native segwit)
String descriptorStr = "wpkh(" + descriptorSecretKey.asString() + "/84'/1'/0'/0/*)";
String changeDescriptorStr = "wpkh(" + descriptorSecretKey.asString() + "/84'/1'/0'/1/*)";
// Create descriptor objects
Descriptor descriptor = new Descriptor(descriptorStr, network);
Descriptor changeDescriptor = new Descriptor(changeDescriptorStr, network);
// Create database config
DatabaseConfig databaseConfig = new DatabaseConfig.Memory();
// Create wallet
wallet = new Wallet(
descriptor,
changeDescriptor,
network,
databaseConfig
);
logger.info("✅ Wallet criada com sucesso!");
// Sync wallet
syncWallet();
// Generate and show address
AddressInfo addressInfo = wallet.getAddress(new AddressIndex.New());
logger.info("💰 Endereço para receber: {}", addressInfo.getAddress());
logger.info("📍 Índice: {}", addressInfo.getIndex());
} catch (Exception e) {
logger.error("❌ Erro ao criar wallet: {}", e.getMessage(), e);
}
}
/**
* Synchronize wallet with blockchain
*/
public void syncWallet() {
try {
if (wallet == null || blockchain == null) {
logger.error("Wallet ou blockchain não inicializados");
return;
}
logger.info("🔄 Sincronizando wallet...");
wallet.sync(blockchain, null);
logger.info("✅ Sincronização completa!");
// Show balance
Balance balance = wallet.getBalance();
logger.info("💵 Saldo total: {} satoshis", balance.getTotal());
logger.info("💵 Saldo confirmado: {} satoshis", balance.getConfirmed());
} catch (Exception e) {
logger.error("❌ Erro ao sincronizar: {}", e.getMessage(), e);
}
}
/**
* Get blockchain height
*/
public long getBlockchainHeight() {
try {
if (blockchain == null) {
throw new IllegalStateException("Blockchain não conectado");
}
return blockchain.getHeight();
} catch (Exception e) {
logger.error("Erro ao obter altura: {}", e.getMessage());
return -1;
}
}
/**
* Get new receiving address
*/
public String getNewAddress() {
try {
if (wallet == null) {
throw new IllegalStateException("Wallet não inicializada");
}
AddressInfo addressInfo = wallet.getAddress(new AddressIndex.New());
return addressInfo.getAddress();
} catch (Exception e) {
logger.error("Erro ao gerar endereço: {}", e.getMessage());
return null;
}
}
/**
* Get wallet balance
*/
public Balance getBalance() {
try {
if (wallet == null) {
throw new IllegalStateException("Wallet não inicializada");
}
syncWallet();
return wallet.getBalance();
} catch (Exception e) {
logger.error("Erro ao obter saldo: {}", e.getMessage());
return null;
}
}
/**
* Check if connected
*/
public boolean isConnected() {
return blockchain != null;
}
/**
* Get blockchain instance
*/
public Blockchain getBlockchain() {
return blockchain;
}
/**
* Get wallet instance
*/
public Wallet getWallet() {
return wallet;
}
/**
* Get network
*/
public Network getNetwork() {
return network;
}
/**
* Get Esplora URL
*/
public String getEsploraUrl() {
return esploraUrl;
}
}
@@ -0,0 +1,11 @@
package org.backend.stealth.service.dto;
/**
* DTO for address response with instructions
*/
public record AddressResponseDTO(
String address,
String instructions
) {
}
@@ -0,0 +1,10 @@
package org.backend.stealth.service.dto;
/**
* DTO for block height
*/
public record BlockHeightDTO(
long height
) {
}
@@ -0,0 +1,13 @@
package org.backend.stealth.service.dto;
/**
* DTO for blockchain information
*/
public record BlockchainInfoDTO(
String network,
long height,
String latestBlockHash,
String esploraUrl
) {
}
@@ -0,0 +1,13 @@
package org.backend.stealth.service.dto;
/**
* DTO for testnet4 connection status
*/
public record Testnet4StatusDTO(
boolean connected,
String status,
long blockHeight,
String esploraUrl
) {
}