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.
- arc_devkit/__init__.py +3 -0
- arc_devkit/agents/__init__.py +7 -0
- arc_devkit/agents/base_agent.py +87 -0
- arc_devkit/agents/monitor_agent.py +104 -0
- arc_devkit/agents/payment_agent.py +97 -0
- arc_devkit/api/__init__.py +1 -0
- arc_devkit/api/main.py +42 -0
- arc_devkit/api/routes/__init__.py +1 -0
- arc_devkit/api/routes/agents.py +126 -0
- arc_devkit/api/routes/copilot.py +37 -0
- arc_devkit/api/routes/debugger.py +55 -0
- arc_devkit/cli/__init__.py +1 -0
- arc_devkit/cli/commands/__init__.py +1 -0
- arc_devkit/cli/commands/agent.py +176 -0
- arc_devkit/cli/commands/copilot.py +40 -0
- arc_devkit/cli/commands/debug.py +110 -0
- arc_devkit/cli/main.py +69 -0
- arc_devkit/config.py +73 -0
- arc_devkit/copilot/__init__.py +5 -0
- arc_devkit/copilot/agent.py +74 -0
- arc_devkit/core/__init__.py +1 -0
- arc_devkit/core/connection.py +50 -0
- arc_devkit/core/gas.py +62 -0
- arc_devkit/core/wallet.py +72 -0
- arc_devkit/debugger/__init__.py +5 -0
- arc_devkit/debugger/tx_analyzer.py +107 -0
- arc_devkit-0.2.0.dist-info/METADATA +238 -0
- arc_devkit-0.2.0.dist-info/RECORD +31 -0
- arc_devkit-0.2.0.dist-info/WHEEL +5 -0
- arc_devkit-0.2.0.dist-info/entry_points.txt +2 -0
- arc_devkit-0.2.0.dist-info/top_level.txt +1 -0
|
@@ -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,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
|
+
}
|