arc-devkit 0.2.0__py3-none-any.whl

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
@@ -0,0 +1,176 @@
1
+ """Comandos CLI para gerenciamento de carteiras e agentes Arc."""
2
+
3
+ import typer
4
+ from rich.console import Console
5
+ from rich.panel import Panel
6
+ from rich.table import Table
7
+
8
+ app = typer.Typer(help="Gerenciamento de carteiras e agentes econômicos Arc.")
9
+ console = Console()
10
+
11
+
12
+ @app.command(name="create-wallet")
13
+ def create_wallet() -> None:
14
+ """
15
+ Cria uma nova carteira EVM e exibe endereço e chave privada.
16
+
17
+ A chave privada gerada é exibida UMA ÚNICA VEZ. Guarde em local seguro.
18
+ """
19
+ from arc_devkit.core.wallet import create_wallet as _criar
20
+
21
+ carteira = _criar()
22
+
23
+ console.print(
24
+ Panel(
25
+ f"[bold green]✓ Nova carteira criada![/bold green]\n\n"
26
+ f" [bold]Endereço:[/bold]\n"
27
+ f" [cyan]{carteira['address']}[/cyan]\n\n"
28
+ f" [bold]Chave Privada:[/bold]\n"
29
+ f" [dim]{carteira['private_key']}[/dim]\n\n"
30
+ "[bold red]⚠ ATENÇÃO:[/bold red] A chave privada é exibida apenas agora.\n"
31
+ "Guarde-a em local seguro. Nunca compartilhe ou commite no git.",
32
+ title="[bold green]Nova Carteira Arc[/bold green]",
33
+ border_style="green",
34
+ padding=(1, 2),
35
+ )
36
+ )
37
+
38
+
39
+ @app.command()
40
+ def balance(
41
+ address: str = typer.Argument(..., help="Endereço EVM a consultar."),
42
+ ) -> None:
43
+ """Exibe o saldo de uma carteira na Arc testnet."""
44
+ from arc_devkit.core.wallet import get_balance
45
+
46
+ with console.status("[bold]Consultando saldo...[/bold]", spinner="dots"):
47
+ resultado = get_balance(address)
48
+
49
+ console.print(f"\n [bold]Carteira:[/bold] [cyan]{resultado['address']}[/cyan]")
50
+ console.print(f" [bold]Saldo: [/bold] [green]{resultado['balance_usdc']}[/green] USDC\n")
51
+
52
+
53
+ @app.command()
54
+ def status() -> None:
55
+ """Exibe informações da rede Arc (bloco atual, chain ID, gas price)."""
56
+ from arc_devkit.core.connection import get_web3
57
+
58
+ with console.status("[bold]Consultando a rede Arc...[/bold]", spinner="dots"):
59
+ w3 = get_web3()
60
+ bloco = w3.eth.block_number
61
+ chain_id = w3.eth.chain_id
62
+ gas_price_gwei = w3.from_wei(w3.eth.gas_price, "gwei")
63
+ conectado = w3.is_connected()
64
+
65
+ tabela = Table(
66
+ title="Status da Rede Arc",
67
+ show_header=True,
68
+ header_style="bold magenta",
69
+ border_style="magenta",
70
+ )
71
+ tabela.add_column("Propriedade", style="bold", min_width=14)
72
+ tabela.add_column("Valor")
73
+
74
+ tabela.add_row("Conectado", "[green]✓ Sim[/green]" if conectado else "[red]✗ Não[/red]")
75
+ tabela.add_row("Bloco Atual", f"[bold]#{bloco}[/bold]")
76
+ tabela.add_row("Chain ID", str(chain_id))
77
+ tabela.add_row("Gas Price", f"{gas_price_gwei} gwei")
78
+
79
+ console.print(tabela)
80
+
81
+
82
+ @app.command()
83
+ def pay(
84
+ to: str = typer.Argument(..., help="Endereço EVM de destino."),
85
+ amount: float = typer.Argument(..., help="Valor a transferir (em USDC)."),
86
+ send: bool = typer.Option(False, "--send", help="Envia a transação à rede (requer ARC_PRIVATE_KEY)."),
87
+ private_key: str = typer.Option("", "--key", help="Chave privada (sobrescreve ARC_PRIVATE_KEY)."),
88
+ ) -> None:
89
+ """
90
+ Prepara (e opcionalmente envia) um pagamento na Arc.
91
+
92
+ Sem --send, exibe a transação assinada sem enviá-la (modo seguro padrão).
93
+
94
+ Exemplos:
95
+ arcdevkit agent pay 0xDest... 5.0
96
+ arcdevkit agent pay 0xDest... 5.0 --send
97
+ arcdevkit agent pay 0xDest... 5.0 --send --key 0xSUAKEY...
98
+ """
99
+ from arc_devkit.agents.payment_agent import PaymentAgent
100
+
101
+ agente = PaymentAgent(private_key=private_key or None)
102
+
103
+ with console.status(
104
+ f"[bold green]{'Enviando' if send else 'Preparando'} pagamento de {amount} USDC → {to[:10]}...[/bold green]",
105
+ spinner="dots",
106
+ ):
107
+ resultado = agente.execute(to=to, amount_usdc=amount, enviar=send)
108
+
109
+ if resultado.get("status") == "erro":
110
+ console.print(f"\n[red]✗ Erro:[/red] {resultado.get('error')}\n")
111
+ raise typer.Exit(1)
112
+
113
+ tabela = Table(
114
+ title="Pagamento Arc",
115
+ show_header=True,
116
+ header_style="bold green",
117
+ border_style="green",
118
+ )
119
+ tabela.add_column("Campo", style="bold", min_width=14)
120
+ tabela.add_column("Valor")
121
+
122
+ tabela.add_row("Status", f"[green]{resultado['status']}[/green]")
123
+ tabela.add_row("De", resultado.get("from", "N/A"))
124
+ tabela.add_row("Para", resultado.get("to", "N/A"))
125
+ tabela.add_row("Valor", f"{resultado.get('amount_usdc', amount)} USDC")
126
+
127
+ if resultado.get("tx_hash"):
128
+ tabela.add_row("TX Hash", f"[cyan]{resultado['tx_hash']}[/cyan]")
129
+ if resultado.get("nota"):
130
+ tabela.add_row("Nota", f"[dim]{resultado['nota']}[/dim]")
131
+
132
+ console.print(tabela)
133
+
134
+
135
+ @app.command()
136
+ def monitor(
137
+ address: str = typer.Argument(..., help="Endereço EVM a monitorar."),
138
+ interval: int = typer.Option(15, "--interval", "-i", help="Intervalo de polling em segundos."),
139
+ max_iter: int = typer.Option(0, "--max", help="Número máximo de iterações (0 = infinito)."),
140
+ ) -> None:
141
+ """
142
+ Monitora uma carteira Arc e exibe alertas ao detectar mudanças de saldo.
143
+
144
+ Pressione Ctrl+C para encerrar o monitoramento.
145
+
146
+ Exemplos:
147
+ arcdevkit agent monitor 0xCarteira...
148
+ arcdevkit agent monitor 0xCarteira... --interval 5 --max 20
149
+ """
150
+ from arc_devkit.agents.monitor_agent import MonitorAgent
151
+
152
+ def _callback(evento: dict) -> None:
153
+ tipo = evento["tipo"]
154
+ diferenca_wei = int(evento["diferenca_wei"])
155
+ cor = "green" if tipo == "credito" else "red"
156
+ sinal = "+" if tipo == "credito" else "-"
157
+ console.print(
158
+ f" [{cor}]{sinal}{abs(diferenca_wei)} wei ({tipo})[/{cor}]"
159
+ f" → saldo: {evento['saldo_atual_wei']} wei"
160
+ )
161
+
162
+ agente = MonitorAgent(watched_address=address, interval_seconds=interval)
163
+
164
+ console.print(
165
+ Panel.fit(
166
+ f"[bold]Monitorando:[/bold] [cyan]{address}[/cyan]\n"
167
+ f"[dim]Intervalo: {interval}s | Ctrl+C para parar[/dim]",
168
+ border_style="magenta",
169
+ )
170
+ )
171
+
172
+ try:
173
+ agente.execute(callback=_callback, max_iterations=max_iter)
174
+ except KeyboardInterrupt:
175
+ agente.stop()
176
+ console.print("\n[dim]Monitoramento encerrado.[/dim]\n")
@@ -0,0 +1,40 @@
1
+ """Comandos CLI para o Dev Copilot."""
2
+
3
+ import typer
4
+ from rich.console import Console
5
+ from rich.markdown import Markdown
6
+ from rich.panel import Panel
7
+
8
+ app = typer.Typer(help="Assistente de IA para desenvolvimento na Arc blockchain.")
9
+ console = Console()
10
+
11
+
12
+ @app.command()
13
+ def ask(
14
+ prompt: str = typer.Argument(..., help="Pergunta ou instrução para o Dev Copilot."),
15
+ ) -> None:
16
+ """
17
+ Envia uma pergunta ao Dev Copilot e exibe a resposta formatada.
18
+
19
+ Exemplos:
20
+ arcdevkit copilot ask "Como criar uma carteira na Arc?"
21
+ arcdevkit copilot ask "Gere um contrato ERC-20 para Arc testnet"
22
+ """
23
+ from arc_devkit.copilot.agent import DevCopilot
24
+
25
+ copilot = DevCopilot()
26
+
27
+ with console.status(
28
+ "[bold cyan]Consultando Dev Copilot...[/bold cyan]",
29
+ spinner="dots",
30
+ ):
31
+ resposta = copilot.ask(prompt)
32
+
33
+ console.print(
34
+ Panel(
35
+ Markdown(resposta),
36
+ title="[bold cyan]Dev Copilot[/bold cyan]",
37
+ border_style="cyan",
38
+ padding=(1, 2),
39
+ )
40
+ )
@@ -0,0 +1,110 @@
1
+ """Comandos CLI para o Tx Debugger."""
2
+
3
+ import typer
4
+ from rich.console import Console
5
+ from rich.markdown import Markdown
6
+ from rich.panel import Panel
7
+ from rich.table import Table
8
+
9
+ app = typer.Typer(help="Análise e debug de transações Arc.")
10
+ console = Console()
11
+
12
+
13
+ @app.command()
14
+ def tx(
15
+ tx_hash: str = typer.Argument(..., help="Hash da transação a analisar (0x...)."),
16
+ json_output: bool = typer.Option(False, "--json", help="Exibir resultado em JSON bruto."),
17
+ ) -> None:
18
+ """
19
+ Analisa uma transação Arc e exibe diagnóstico completo.
20
+
21
+ Exemplos:
22
+ arcdevkit debug tx 0xabc123...
23
+ arcdevkit debug tx 0xabc123... --json
24
+ """
25
+ import json
26
+
27
+ from arc_devkit.debugger.tx_analyzer import TxAnalyzer
28
+
29
+ analyzer = TxAnalyzer()
30
+
31
+ with console.status(
32
+ f"[bold yellow]Analisando transação {tx_hash[:16]}...[/bold yellow]",
33
+ spinner="dots",
34
+ ):
35
+ resultado = analyzer.analyze(tx_hash)
36
+
37
+ # Saída JSON bruto
38
+ if json_output:
39
+ console.print_json(json.dumps(resultado, default=str))
40
+ return
41
+
42
+ # Tabela resumo
43
+ status = resultado.get("status", "desconhecido")
44
+ cor_status = "green" if status == "sucesso" else "red"
45
+ icone = "✓" if status == "sucesso" else "✗"
46
+
47
+ tabela = Table(
48
+ title=f"Transação [dim]{tx_hash[:20]}...[/dim]",
49
+ show_header=True,
50
+ header_style="bold yellow",
51
+ border_style="yellow",
52
+ )
53
+ tabela.add_column("Campo", style="bold", min_width=14)
54
+ tabela.add_column("Valor")
55
+
56
+ tabela.add_row("Hash", f"{tx_hash[:20]}...")
57
+ tabela.add_row("Status", f"[{cor_status}]{icone} {status}[/{cor_status}]")
58
+ tabela.add_row("Custo Gás", f"{resultado.get('custo_usdc', 'N/A')} USDC")
59
+
60
+ if resultado.get("erro"):
61
+ tabela.add_row("Erro", f"[red]{resultado['erro']}[/red]")
62
+
63
+ console.print(tabela)
64
+
65
+ # Análise em linguagem natural (se disponível)
66
+ if resultado.get("resumo"):
67
+ console.print(
68
+ Panel(
69
+ Markdown(resultado["resumo"]),
70
+ title="[bold yellow]Análise[/bold yellow]",
71
+ border_style="yellow",
72
+ padding=(1, 2),
73
+ )
74
+ )
75
+
76
+
77
+ @app.command()
78
+ def estimate(
79
+ to: str = typer.Argument(..., help="Endereço EVM de destino."),
80
+ amount: float = typer.Argument(..., help="Valor a transferir (em USDC)."),
81
+ from_address: str = typer.Option("", "--from", help="Endereço remetente (opcional)."),
82
+ ) -> None:
83
+ """
84
+ Estima o custo de gás para uma transferência na Arc.
85
+
86
+ Exemplos:
87
+ arcdevkit debug estimate 0xDest... 10.5
88
+ arcdevkit debug estimate 0xDest... 10.5 --from 0xRemetente...
89
+ """
90
+ from arc_devkit.core.gas import estimate_transfer
91
+
92
+ with console.status("[bold]Estimando custo de gás...[/bold]", spinner="dots"):
93
+ est = estimate_transfer(to, amount, from_address or None)
94
+
95
+ tabela = Table(
96
+ title="Estimativa de Gás",
97
+ show_header=True,
98
+ header_style="bold blue",
99
+ border_style="blue",
100
+ )
101
+ tabela.add_column("Campo", style="bold", min_width=16)
102
+ tabela.add_column("Valor")
103
+
104
+ tabela.add_row("Destino", est["to"])
105
+ tabela.add_row("Transferência", f"{amount} USDC")
106
+ tabela.add_row("Gas Limit", str(est["gas_limit"]))
107
+ tabela.add_row("Gas Price", f"{est['gas_price_gwei']} gwei")
108
+ tabela.add_row("Custo de Gás", f"[bold green]{est['custo_usdc']}[/bold green] USDC")
109
+
110
+ console.print(tabela)
arc_devkit/cli/main.py ADDED
@@ -0,0 +1,69 @@
1
+ """Entry point da CLI Arc DevKit — construída com Typer."""
2
+
3
+ import typer
4
+ from rich.console import Console
5
+ from rich.panel import Panel
6
+
7
+ from arc_devkit import __version__
8
+ from arc_devkit.cli.commands import agent, copilot, debug
9
+
10
+ # Aplicação Typer principal
11
+ app = typer.Typer(
12
+ name="arcdevkit",
13
+ help="[bold cyan]Arc DevKit[/bold cyan] — Ferramentas para desenvolvedores da Arc blockchain.",
14
+ add_completion=True,
15
+ rich_markup_mode="rich",
16
+ no_args_is_help=False,
17
+ )
18
+
19
+ # Registrar grupos de subcomandos
20
+ app.add_typer(copilot.app, name="copilot", help="Assistente de IA para desenvolvimento Arc.")
21
+ app.add_typer(agent.app, name="agent", help="Gerenciamento de carteiras e agentes.")
22
+ app.add_typer(debug.app, name="debug", help="Análise e debug de transações.")
23
+
24
+ console = Console()
25
+
26
+
27
+ @app.callback(invoke_without_command=True)
28
+ def main(
29
+ ctx: typer.Context,
30
+ version: bool = typer.Option(False, "--version", "-v", help="Exibe a versão instalada."),
31
+ ) -> None:
32
+ """Arc DevKit — Ferramentas para desenvolvedores da Arc blockchain."""
33
+ if version:
34
+ console.print(f"[bold]Arc DevKit[/bold] v{__version__}")
35
+ raise typer.Exit()
36
+
37
+ # Exibir banner se nenhum subcomando foi invocado
38
+ if ctx.invoked_subcommand is None:
39
+ console.print(
40
+ Panel.fit(
41
+ f"[bold cyan]Arc DevKit[/bold cyan] [dim]v{__version__}[/dim]\n\n"
42
+ " [white]Ferramentas para desenvolvedores da Arc blockchain[/white]\n"
43
+ " [dim]EVM · USDC como gás · Malachite (<1s)[/dim]\n\n"
44
+ " Use [bold]arcdevkit --help[/bold] para ver os comandos.",
45
+ border_style="cyan",
46
+ padding=(1, 3),
47
+ )
48
+ )
49
+
50
+
51
+ @app.command()
52
+ def status() -> None:
53
+ """Verifica a conexão com a Arc testnet e exibe informações da rede."""
54
+ from arc_devkit.core.connection import check_connection, get_web3
55
+
56
+ console.print("\n[bold]Verificando conexão com a Arc...[/bold]\n")
57
+
58
+ if not check_connection():
59
+ console.print("[red]✗[/red] Não foi possível conectar à Arc.")
60
+ console.print(" Verifique se [bold]ARC_RPC_URL[/bold] está configurado corretamente.")
61
+ raise typer.Exit(1)
62
+
63
+ w3 = get_web3()
64
+ console.print("[green]✓[/green] Conectado à Arc testnet!\n")
65
+ console.print(f" Bloco atual : [bold]#{w3.eth.block_number}[/bold]")
66
+ console.print(f" Chain ID : [bold]{w3.eth.chain_id}[/bold]")
67
+ console.print(
68
+ f" Gas Price : [bold]{w3.from_wei(w3.eth.gas_price, 'gwei')} gwei[/bold]\n"
69
+ )
arc_devkit/config.py ADDED
@@ -0,0 +1,73 @@
1
+ """Carregamento e validação de configuração via variáveis de ambiente."""
2
+
3
+ import logging
4
+ import os
5
+ from dataclasses import dataclass
6
+
7
+ from dotenv import load_dotenv
8
+
9
+ # Carregar .env antes de qualquer leitura de os.getenv
10
+ load_dotenv()
11
+
12
+
13
+ @dataclass(frozen=True)
14
+ class Settings:
15
+ """Configurações globais do Arc DevKit, carregadas do ambiente."""
16
+
17
+ anthropic_api_key: str
18
+ arc_rpc_url: str
19
+ arc_chain_id: int
20
+ arc_private_key: str | None
21
+ log_level: str
22
+
23
+
24
+ def _require(name: str) -> str:
25
+ """Retorna o valor da variável ou levanta erro descritivo."""
26
+ value = os.getenv(name, "").strip()
27
+ if not value:
28
+ raise EnvironmentError(
29
+ f"\n\n Variável obrigatória '{name}' não está configurada.\n"
30
+ f" Crie um arquivo .env baseado no .env.example e defina {name}.\n"
31
+ f" Exemplo: cp .env.example .env\n"
32
+ )
33
+ return value
34
+
35
+
36
+ def _load_settings() -> Settings:
37
+ """Lê, valida e retorna todas as configurações do ambiente."""
38
+ # Coletar erros de uma só vez para exibição agrupada
39
+ erros: list[str] = []
40
+
41
+ api_key = os.getenv("ANTHROPIC_API_KEY", "").strip()
42
+ rpc_url = os.getenv("ARC_RPC_URL", "").strip()
43
+
44
+ if not api_key:
45
+ erros.append("ANTHROPIC_API_KEY")
46
+ if not rpc_url:
47
+ erros.append("ARC_RPC_URL")
48
+
49
+ if erros:
50
+ lista = ", ".join(erros)
51
+ raise EnvironmentError(
52
+ f"\n\n Variáveis obrigatórias não configuradas: {lista}\n"
53
+ f" Execute: cp .env.example .env e preencha os valores.\n"
54
+ )
55
+
56
+ return Settings(
57
+ anthropic_api_key=api_key,
58
+ arc_rpc_url=rpc_url,
59
+ arc_chain_id=int(os.getenv("ARC_CHAIN_ID", "7777777")),
60
+ arc_private_key=os.getenv("ARC_PRIVATE_KEY", "").strip() or None,
61
+ log_level=os.getenv("LOG_LEVEL", "INFO").upper(),
62
+ )
63
+
64
+
65
+ # Objeto global — importado por todos os módulos
66
+ settings = _load_settings()
67
+
68
+ # Configurar logging global com o nível definido no .env
69
+ logging.basicConfig(
70
+ level=getattr(logging, settings.log_level, logging.INFO),
71
+ format="%(asctime)s [%(levelname)s] %(name)s: %(message)s",
72
+ datefmt="%Y-%m-%d %H:%M:%S",
73
+ )
@@ -0,0 +1,5 @@
1
+ """Módulo Dev Copilot — assistente de IA para desenvolvimento na Arc."""
2
+
3
+ from arc_devkit.copilot.agent import DevCopilot
4
+
5
+ __all__ = ["DevCopilot"]
@@ -0,0 +1,74 @@
1
+ """Dev Copilot — assistente de IA especializado na Arc blockchain."""
2
+
3
+ import logging
4
+
5
+ import anthropic
6
+
7
+ from arc_devkit.config import settings
8
+
9
+ logger = logging.getLogger(__name__)
10
+
11
+ # System prompt com contexto completo da Arc e boas práticas de resposta
12
+ _SYSTEM_PROMPT = """\
13
+ Você é um assistente especializado em desenvolvimento na Arc blockchain.
14
+
15
+ ## Sobre a Arc
16
+ - Layer 1 EVM-compatível desenvolvida pela Circle (criadores do USDC)
17
+ - USDC é o token de gás (não ETH) — custo sempre expresso em USDC
18
+ - Consenso Malachite: finalidade em menos de 1 segundo por bloco
19
+ - Circle Agent Stack: infraestrutura nativa para agentes econômicos autônomos
20
+ - Testnet ativa desde outubro de 2025; mainnet prevista para verão de 2026
21
+ - RPC EVM padrão: compatível com web3.py, ethers.js, Hardhat, Foundry
22
+
23
+ ## Diretrizes de resposta
24
+ 1. Gere sempre código Python funcional com comentários em português brasileiro
25
+ 2. Use web3.py para toda interação com a blockchain Arc
26
+ 3. Use Decimal (nunca float) para todos os valores monetários em USDC
27
+ 4. Informe o custo estimado em USDC quando relevante para a operação
28
+ 5. Separe claramente a explicação do bloco de código
29
+ 6. Se houver risco de segurança (chaves privadas, valores altos), alerte o usuário
30
+ """
31
+
32
+
33
+ class DevCopilot:
34
+ """
35
+ Assistente de IA para desenvolvimento na Arc blockchain.
36
+
37
+ Usa o modelo claude-sonnet-4-6 da Anthropic com contexto especializado
38
+ em Arc, EVM, USDC e agentes econômicos.
39
+ """
40
+
41
+ # Modelo usado — definido em nível de classe para facilitar override em testes
42
+ MODEL = "claude-sonnet-4-6"
43
+ MAX_TOKENS = 1500
44
+
45
+ def __init__(self) -> None:
46
+ # Cliente Anthropic instanciado uma única vez e reutilizado
47
+ self._client = anthropic.Anthropic(api_key=settings.anthropic_api_key)
48
+ logger.debug("DevCopilot inicializado com modelo %s", self.MODEL)
49
+
50
+ def ask(self, prompt: str) -> str:
51
+ """
52
+ Envia uma pergunta ao Dev Copilot e retorna a resposta completa.
53
+
54
+ Args:
55
+ prompt: Pergunta ou instrução do desenvolvedor.
56
+
57
+ Returns:
58
+ Resposta formatada com explicação e código (Markdown).
59
+
60
+ Raises:
61
+ anthropic.APIError: Em caso de erro na API Anthropic.
62
+ """
63
+ logger.info("Dev Copilot consultado — prompt: %.80s...", prompt)
64
+
65
+ message = self._client.messages.create(
66
+ model=self.MODEL,
67
+ max_tokens=self.MAX_TOKENS,
68
+ system=_SYSTEM_PROMPT,
69
+ messages=[{"role": "user", "content": prompt}],
70
+ )
71
+
72
+ resposta = message.content[0].text
73
+ logger.info("Resposta recebida: %d caracteres", len(resposta))
74
+ return resposta
@@ -0,0 +1 @@
1
+ """Módulo core — conexão RPC e operações de carteira Arc."""
@@ -0,0 +1,50 @@
1
+ """Conexão com a Arc blockchain via web3.py."""
2
+
3
+ import logging
4
+
5
+ from web3 import Web3
6
+ from web3.middleware import ExtraDataToPOAMiddleware
7
+
8
+ logger = logging.getLogger(__name__)
9
+
10
+
11
+ def get_web3() -> Web3:
12
+ """
13
+ Retorna instância Web3 conectada ao nó RPC configurado em ARC_RPC_URL.
14
+
15
+ Aplica o middleware PoA necessário para redes EVM que usam blocos
16
+ com extraData maior que 32 bytes (comum em testnets).
17
+ """
18
+ from arc_devkit.config import settings
19
+
20
+ w3 = Web3(Web3.HTTPProvider(settings.arc_rpc_url))
21
+
22
+ # Middleware necessário para compatibilidade com redes PoA/testnets
23
+ w3.middleware_onion.inject(ExtraDataToPOAMiddleware, layer=0)
24
+
25
+ return w3
26
+
27
+
28
+ def check_connection() -> bool:
29
+ """
30
+ Testa a conexão com o nó RPC da Arc.
31
+
32
+ Returns:
33
+ True se conectado com sucesso, False caso contrário.
34
+ """
35
+ try:
36
+ w3 = get_web3()
37
+ if w3.is_connected():
38
+ bloco = w3.eth.block_number
39
+ chain_id = w3.eth.chain_id
40
+ logger.info(
41
+ "Conectado à Arc! Bloco: #%d | Chain ID: %d", bloco, chain_id
42
+ )
43
+ return True
44
+
45
+ logger.warning("Web3 instanciado mas is_connected() retornou False.")
46
+ return False
47
+
48
+ except Exception as exc:
49
+ logger.error("Falha ao conectar ao Arc RPC: %s", exc)
50
+ return False
arc_devkit/core/gas.py ADDED
@@ -0,0 +1,62 @@
1
+ """Estimativa de custo de gás para transações Arc."""
2
+
3
+ import logging
4
+ from decimal import Decimal
5
+
6
+ from web3 import Web3
7
+
8
+ from arc_devkit.core.connection import get_web3
9
+
10
+ logger = logging.getLogger(__name__)
11
+
12
+ # Custo fixo de transferência nativa (ETH/USDC) em unidades de gás
13
+ GAS_TRANSFERENCIA = 21_000
14
+
15
+
16
+ def estimate_transfer(to: str, amount_usdc: float, from_address: str | None = None) -> dict:
17
+ """
18
+ Estima o custo de gás para uma transferência nativa na Arc.
19
+
20
+ Args:
21
+ to: Endereço EVM de destino.
22
+ amount_usdc: Valor a transferir (em USDC).
23
+ from_address: Endereço remetente (opcional — usado para estimativa mais precisa).
24
+
25
+ Returns:
26
+ Dict com gas_limit, gas_price_gwei, custo_usdc e custo_wei.
27
+ """
28
+ w3 = get_web3()
29
+
30
+ destino = Web3.to_checksum_address(to)
31
+ gas_price_wei = w3.eth.gas_price
32
+ gas_price_gwei = Decimal(str(w3.from_wei(gas_price_wei, "gwei")))
33
+
34
+ # Para transferências nativas o gás é fixo em 21.000
35
+ # Para contratos, usa eth_estimateGas (mais preciso mas requer from_address)
36
+ if from_address:
37
+ try:
38
+ remetente = Web3.to_checksum_address(from_address)
39
+ gas_limit = w3.eth.estimate_gas({
40
+ "from": remetente,
41
+ "to": destino,
42
+ "value": w3.to_wei(amount_usdc, "ether"),
43
+ })
44
+ except Exception:
45
+ gas_limit = GAS_TRANSFERENCIA
46
+ else:
47
+ gas_limit = GAS_TRANSFERENCIA
48
+
49
+ custo_wei = gas_limit * gas_price_wei
50
+ custo_usdc = Decimal(str(w3.from_wei(custo_wei, "ether")))
51
+
52
+ logger.debug("Estimativa: %d gas × %s gwei = %s USDC", gas_limit, gas_price_gwei, custo_usdc)
53
+
54
+ return {
55
+ "gas_limit": gas_limit,
56
+ "gas_price_gwei": str(gas_price_gwei),
57
+ "gas_price_wei": str(gas_price_wei),
58
+ "custo_usdc": str(custo_usdc),
59
+ "custo_wei": str(custo_wei),
60
+ "amount_usdc": amount_usdc,
61
+ "to": str(destino),
62
+ }