golem-vm-provider 0.1.51__tar.gz → 0.1.53__tar.gz

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.
Files changed (44) hide show
  1. {golem_vm_provider-0.1.51 → golem_vm_provider-0.1.53}/PKG-INFO +3 -1
  2. {golem_vm_provider-0.1.51 → golem_vm_provider-0.1.53}/README.md +2 -0
  3. {golem_vm_provider-0.1.51 → golem_vm_provider-0.1.53}/provider/main.py +166 -39
  4. golem_vm_provider-0.1.53/provider/security/l2_faucet.py +63 -0
  5. {golem_vm_provider-0.1.51 → golem_vm_provider-0.1.53}/provider/utils/pricing.py +8 -0
  6. {golem_vm_provider-0.1.51 → golem_vm_provider-0.1.53}/pyproject.toml +1 -1
  7. {golem_vm_provider-0.1.51 → golem_vm_provider-0.1.53}/provider/__init__.py +0 -0
  8. {golem_vm_provider-0.1.51 → golem_vm_provider-0.1.53}/provider/api/__init__.py +0 -0
  9. {golem_vm_provider-0.1.51 → golem_vm_provider-0.1.53}/provider/api/models.py +0 -0
  10. {golem_vm_provider-0.1.51 → golem_vm_provider-0.1.53}/provider/api/routes.py +0 -0
  11. {golem_vm_provider-0.1.51 → golem_vm_provider-0.1.53}/provider/config.py +0 -0
  12. {golem_vm_provider-0.1.51 → golem_vm_provider-0.1.53}/provider/container.py +0 -0
  13. {golem_vm_provider-0.1.51 → golem_vm_provider-0.1.53}/provider/data/deployments/l2.json +0 -0
  14. {golem_vm_provider-0.1.51 → golem_vm_provider-0.1.53}/provider/discovery/__init__.py +0 -0
  15. {golem_vm_provider-0.1.51 → golem_vm_provider-0.1.53}/provider/discovery/advertiser.py +0 -0
  16. {golem_vm_provider-0.1.51 → golem_vm_provider-0.1.53}/provider/discovery/golem_base_advertiser.py +0 -0
  17. {golem_vm_provider-0.1.51 → golem_vm_provider-0.1.53}/provider/discovery/golem_base_utils.py +0 -0
  18. {golem_vm_provider-0.1.51 → golem_vm_provider-0.1.53}/provider/discovery/multi_advertiser.py +0 -0
  19. {golem_vm_provider-0.1.51 → golem_vm_provider-0.1.53}/provider/discovery/resource_monitor.py +0 -0
  20. {golem_vm_provider-0.1.51 → golem_vm_provider-0.1.53}/provider/discovery/resource_tracker.py +0 -0
  21. {golem_vm_provider-0.1.51 → golem_vm_provider-0.1.53}/provider/discovery/service.py +0 -0
  22. {golem_vm_provider-0.1.51 → golem_vm_provider-0.1.53}/provider/network/port_verifier.py +0 -0
  23. {golem_vm_provider-0.1.51 → golem_vm_provider-0.1.53}/provider/payments/blockchain_service.py +0 -0
  24. {golem_vm_provider-0.1.51 → golem_vm_provider-0.1.53}/provider/payments/monitor.py +0 -0
  25. {golem_vm_provider-0.1.51 → golem_vm_provider-0.1.53}/provider/payments/stream_map.py +0 -0
  26. {golem_vm_provider-0.1.51 → golem_vm_provider-0.1.53}/provider/security/ethereum.py +0 -0
  27. {golem_vm_provider-0.1.51 → golem_vm_provider-0.1.53}/provider/security/faucet.py +0 -0
  28. {golem_vm_provider-0.1.51 → golem_vm_provider-0.1.53}/provider/service.py +0 -0
  29. {golem_vm_provider-0.1.51 → golem_vm_provider-0.1.53}/provider/utils/__init__.py +0 -0
  30. {golem_vm_provider-0.1.51 → golem_vm_provider-0.1.53}/provider/utils/ascii_art.py +0 -0
  31. {golem_vm_provider-0.1.51 → golem_vm_provider-0.1.53}/provider/utils/logging.py +0 -0
  32. {golem_vm_provider-0.1.51 → golem_vm_provider-0.1.53}/provider/utils/port_display.py +0 -0
  33. {golem_vm_provider-0.1.51 → golem_vm_provider-0.1.53}/provider/utils/retry.py +0 -0
  34. {golem_vm_provider-0.1.51 → golem_vm_provider-0.1.53}/provider/utils/setup.py +0 -0
  35. {golem_vm_provider-0.1.51 → golem_vm_provider-0.1.53}/provider/vm/__init__.py +0 -0
  36. {golem_vm_provider-0.1.51 → golem_vm_provider-0.1.53}/provider/vm/cloud_init.py +0 -0
  37. {golem_vm_provider-0.1.51 → golem_vm_provider-0.1.53}/provider/vm/models.py +0 -0
  38. {golem_vm_provider-0.1.51 → golem_vm_provider-0.1.53}/provider/vm/multipass.py +0 -0
  39. {golem_vm_provider-0.1.51 → golem_vm_provider-0.1.53}/provider/vm/multipass_adapter.py +0 -0
  40. {golem_vm_provider-0.1.51 → golem_vm_provider-0.1.53}/provider/vm/name_mapper.py +0 -0
  41. {golem_vm_provider-0.1.51 → golem_vm_provider-0.1.53}/provider/vm/port_manager.py +0 -0
  42. {golem_vm_provider-0.1.51 → golem_vm_provider-0.1.53}/provider/vm/provider.py +0 -0
  43. {golem_vm_provider-0.1.51 → golem_vm_provider-0.1.53}/provider/vm/proxy_manager.py +0 -0
  44. {golem_vm_provider-0.1.51 → golem_vm_provider-0.1.53}/provider/vm/service.py +0 -0
@@ -1,6 +1,6 @@
1
1
  Metadata-Version: 2.3
2
2
  Name: golem-vm-provider
3
- Version: 0.1.51
3
+ Version: 0.1.53
4
4
  Summary: VM on Golem Provider Node - Run your own provider node to offer VMs on the Golem Network
5
5
  Keywords: golem,vm,provider,cloud,decentralized
6
6
  Author: Phillip Jensen
@@ -454,6 +454,8 @@ poetry run golem-provider streams withdraw --vm-id <vm_id>
454
454
  poetry run golem-provider streams withdraw --all
455
455
  ```
456
456
 
457
+ Note: On testnets, the withdraw command auto-attempts to fund the provider's L2 address via the configured faucet if native gas balance is low.
458
+
457
459
  Configure monitor and withdraw via CLI:
458
460
 
459
461
  ```bash
@@ -409,6 +409,8 @@ poetry run golem-provider streams withdraw --vm-id <vm_id>
409
409
  poetry run golem-provider streams withdraw --all
410
410
  ```
411
411
 
412
+ Note: On testnets, the withdraw command auto-attempts to fund the provider's L2 address via the configured faucet if native gas balance is low.
413
+
412
414
  Configure monitor and withdraw via CLI:
413
415
 
414
416
  ```bash
@@ -147,38 +147,17 @@ def main():
147
147
  def wallet_faucet_l2():
148
148
  """Request L2 faucet funds for the provider's payment address (native ETH)."""
149
149
  from .config import settings
150
- from golem_faucet import PowFaucetClient
151
- from web3 import Web3
150
+ from .security.l2_faucet import L2FaucetService
152
151
  try:
153
152
  addr = settings.PROVIDER_ID
154
- faucet = PowFaucetClient(settings.L2_FAUCET_URL, settings.L2_CAPTCHA_URL, settings.L2_CAPTCHA_API_KEY)
155
- # Check current L2 balance
156
- w3 = Web3(Web3.HTTPProvider(settings.POLYGON_RPC_URL))
157
- bal = 0.0
158
- try:
159
- bal = float(w3.from_wei(w3.eth.get_balance(Web3.to_checksum_address(addr)), 'ether'))
160
- except Exception:
161
- pass
162
- if bal > 0.01:
163
- print(f"Sufficient L2 funds ({bal} ETH); skipping faucet.")
164
- return
165
153
  async def _run():
166
- chall = await faucet.get_challenge()
167
- if not chall:
168
- print("Failed to get challenge")
169
- raise typer.Exit(code=1)
170
- sols = []
171
- for salt, target in chall.get('challenge') or []:
172
- sols.append((salt, target, PowFaucetClient.solve_challenge(salt, target)))
173
- redeemed = await faucet.redeem(chall.get('token'), sols)
174
- if not redeemed:
175
- print("Failed to redeem solutions")
176
- raise typer.Exit(code=1)
177
- tx = await faucet.request_funds(addr, redeemed)
154
+ svc = L2FaucetService(settings)
155
+ tx = await svc.request_funds(addr)
178
156
  if tx:
179
157
  print(f"Faucet tx: {tx}")
180
158
  else:
181
- print("Faucet request failed")
159
+ # Either skipped due to sufficient balance or failed
160
+ pass
182
161
  asyncio.run(_run())
183
162
  except Exception as e:
184
163
  print(f"Error: {e}")
@@ -191,6 +170,9 @@ def streams_list(json_out: bool = typer.Option(False, "--json", help="Output in
191
170
  from .container import Container
192
171
  from .config import settings
193
172
  from .payments.blockchain_service import StreamPaymentReader
173
+ from .utils.pricing import fetch_glm_usd_price, fetch_eth_usd_price
174
+ from decimal import Decimal
175
+ from web3 import Web3
194
176
  import json as _json
195
177
  try:
196
178
  if not settings.STREAM_PAYMENT_ADDRESS or settings.STREAM_PAYMENT_ADDRESS == "0x0000000000000000000000000000000000000000":
@@ -213,6 +195,7 @@ def streams_list(json_out: bool = typer.Option(False, "--json", help="Output in
213
195
  rows.append({
214
196
  "vm_id": vm_id,
215
197
  "stream_id": int(stream_id),
198
+ "token": str(s.get("token")),
216
199
  "recipient": s["recipient"],
217
200
  "start": int(s["startTime"]),
218
201
  "stop": int(s["stopTime"]),
@@ -232,12 +215,66 @@ def streams_list(json_out: bool = typer.Option(False, "--json", help="Output in
232
215
  if not rows:
233
216
  print("No streams mapped.")
234
217
  return
235
- print("\nStreams (VM stream_id, remaining s, verified):")
218
+ # Prepare human-friendly display (ETH/GLM + USD)
219
+ ZERO = "0x0000000000000000000000000000000000000000"
220
+ # Cache prices so we don't query per-row
221
+ price_cache: dict[str, Optional[Decimal]] = {"ETH": None, "GLM": None}
222
+ # Determine which symbols are present
223
+ symbols_present = set()
236
224
  for r in rows:
237
225
  if "error" in r:
238
- print(f"- {r['vm_id']}: {r['stream_id']} ERROR: {r['error']}")
239
- else:
240
- print(f"- {r['vm_id']}: {r['stream_id']} remaining={r['remaining']}s verified={r['verified']} reason={r['reason']} withdrawable={r['withdrawable']}")
226
+ continue
227
+ token_addr = (r.get("token") or "").lower()
228
+ sym = "ETH" if token_addr == ZERO.lower() else "GLM"
229
+ symbols_present.add(sym)
230
+ if "ETH" in symbols_present:
231
+ price_cache["ETH"] = fetch_eth_usd_price()
232
+ if "GLM" in symbols_present:
233
+ price_cache["GLM"] = fetch_glm_usd_price()
234
+
235
+ # Build table rows
236
+ table_rows = []
237
+ for r in rows:
238
+ if "error" in r:
239
+ table_rows.append([r["vm_id"], str(r["stream_id"]), "—", "ERROR", r.get("error", ""), "—"])
240
+ continue
241
+ token_addr = (r.get("token") or "").lower()
242
+ sym = "ETH" if token_addr == ZERO.lower() else "GLM"
243
+ withdrawable_eth = Decimal(str(Web3.from_wei(int(r["withdrawable"]), "ether")))
244
+ withdrawable_str = f"{withdrawable_eth:.6f} {sym}"
245
+ price = price_cache.get(sym)
246
+ usd_str = "—"
247
+ if price is not None:
248
+ try:
249
+ usd_val = (withdrawable_eth * price).quantize(Decimal("0.01"))
250
+ usd_str = f"${usd_val}"
251
+ except Exception:
252
+ usd_str = "—"
253
+ table_rows.append([
254
+ r["vm_id"],
255
+ str(r["stream_id"]),
256
+ f"{int(r['remaining'])}s",
257
+ "yes" if r["verified"] else "no",
258
+ withdrawable_str,
259
+ usd_str,
260
+ ])
261
+
262
+ headers = ["VM", "Stream", "Remaining", "Verified", "Withdrawable", "USD"]
263
+ # Compute column widths
264
+ cols = len(headers)
265
+ col_widths = [len(h) for h in headers]
266
+ for row in table_rows:
267
+ for i in range(cols):
268
+ col_widths[i] = max(col_widths[i], len(str(row[i])))
269
+
270
+ def fmt_row(values: list[str]) -> str:
271
+ return " ".join(str(values[i]).ljust(col_widths[i]) for i in range(cols))
272
+
273
+ print("\nStreams")
274
+ print(fmt_row(headers))
275
+ print(" ".join("-" * w for w in col_widths))
276
+ for row in table_rows:
277
+ print(fmt_row(row))
241
278
  except Exception as e:
242
279
  print(f"Error: {e}")
243
280
  raise typer.Exit(code=1)
@@ -249,6 +286,9 @@ def streams_show(vm_id: str = typer.Argument(..., help="VM id (requestor_name)")
249
286
  from .container import Container
250
287
  from .config import settings
251
288
  from .payments.blockchain_service import StreamPaymentReader
289
+ from .utils.pricing import fetch_glm_usd_price, fetch_eth_usd_price
290
+ from decimal import Decimal
291
+ from web3 import Web3
252
292
  import json as _json
253
293
  try:
254
294
  c = Container()
@@ -281,7 +321,43 @@ def streams_show(vm_id: str = typer.Argument(..., help="VM id (requestor_name)")
281
321
  if json_out:
282
322
  print(_json.dumps(out, indent=2))
283
323
  else:
284
- print(f"VM {vm_id}: stream {sid} remaining={remaining}s verified={ok} withdrawable={withdrawable}")
324
+ ZERO = "0x0000000000000000000000000000000000000000"
325
+ token_addr = (s.get("token") or "").lower()
326
+ sym = "ETH" if token_addr == ZERO.lower() else "GLM"
327
+ nat = Decimal(str(Web3.from_wei(int(withdrawable), "ether")))
328
+ price = fetch_eth_usd_price() if sym == "ETH" else fetch_glm_usd_price()
329
+ usd_str = "—"
330
+ if price is not None:
331
+ try:
332
+ usd_val = (nat * price).quantize(Decimal("0.01"))
333
+ usd_str = f"${usd_val}"
334
+ except Exception:
335
+ usd_str = "—"
336
+ def _fmt_seconds(sec: int) -> str:
337
+ m, s2 = divmod(int(sec), 60)
338
+ h, m = divmod(m, 60)
339
+ d, h = divmod(h, 24)
340
+ parts = []
341
+ if d: parts.append(f"{d}d")
342
+ if h: parts.append(f"{h}h")
343
+ if m and not d: parts.append(f"{m}m")
344
+ if s2 and not d and not h and not m: parts.append(f"{s2}s")
345
+ return " ".join(parts) or "0s"
346
+ # Pretty single-record display
347
+ print("\nStream Details")
348
+ headers = ["VM", "Stream", "Remaining", "Verified", "Withdrawable", "USD"]
349
+ cols = [
350
+ vm_id,
351
+ str(sid),
352
+ _fmt_seconds(remaining),
353
+ "yes" if ok else "no",
354
+ f"{nat:.6f} {sym}",
355
+ usd_str,
356
+ ]
357
+ w = [max(len(headers[i]), len(str(cols[i]))) for i in range(len(headers))]
358
+ print(" ".join(headers[i].ljust(w[i]) for i in range(len(w))))
359
+ print(" ".join("-" * wi for wi in w))
360
+ print(" ".join(str(cols[i]).ljust(w[i]) for i in range(len(w))))
285
361
  except Exception as e:
286
362
  print(f"Error: {e}")
287
363
  raise typer.Exit(code=1)
@@ -292,6 +368,9 @@ def streams_earnings(json_out: bool = typer.Option(False, "--json", help="Output
292
368
  from .container import Container
293
369
  from .config import settings
294
370
  from .payments.blockchain_service import StreamPaymentReader
371
+ from .utils.pricing import fetch_glm_usd_price, fetch_eth_usd_price
372
+ from decimal import Decimal
373
+ from web3 import Web3
295
374
  import json as _json
296
375
  try:
297
376
  c = Container()
@@ -304,6 +383,8 @@ def streams_earnings(json_out: bool = typer.Option(False, "--json", help="Output
304
383
  total_vested = 0
305
384
  total_withdrawn = 0
306
385
  total_withdrawable = 0
386
+ ZERO = "0x0000000000000000000000000000000000000000"
387
+ sums_native: dict[str, Decimal] = {"ETH": Decimal("0"), "GLM": Decimal("0")}
307
388
  for vm_id, stream_id in items.items():
308
389
  try:
309
390
  s = reader.get_stream(int(stream_id))
@@ -312,9 +393,12 @@ def streams_earnings(json_out: bool = typer.Option(False, "--json", help="Output
312
393
  total_vested += int(vested)
313
394
  total_withdrawn += int(s["withdrawn"]) # type: ignore
314
395
  total_withdrawable += int(withdrawable)
396
+ sym = "ETH" if (s.get("token") or "").lower() == ZERO.lower() else "GLM"
397
+ sums_native[sym] += Decimal(str(Web3.from_wei(int(withdrawable), "ether")))
315
398
  rows.append({
316
399
  "vm_id": vm_id,
317
400
  "stream_id": int(stream_id),
401
+ "token": str(s.get("token")),
318
402
  "vested": int(vested),
319
403
  "withdrawn": int(s["withdrawn"]),
320
404
  "withdrawable": int(withdrawable),
@@ -332,17 +416,53 @@ def streams_earnings(json_out: bool = typer.Option(False, "--json", help="Output
332
416
  if json_out:
333
417
  print(_json.dumps(out, indent=2))
334
418
  return
335
- print("\nEarnings summary (wei):")
336
- print(f" Vested total : {total_vested}")
337
- print(f" Withdrawn total : {total_withdrawn}")
338
- print(f" Withdrawable total: {total_withdrawable}")
419
+ # Human summary by token with USD
420
+ price_eth = fetch_eth_usd_price()
421
+ price_glm = fetch_glm_usd_price()
422
+ def _fmt_usd(amount_native: Decimal, price: Optional[Decimal]) -> str:
423
+ if price is None:
424
+ return "—"
425
+ try:
426
+ return f"${(amount_native * price).quantize(Decimal('0.01'))}"
427
+ except Exception:
428
+ return "—"
429
+ print("\nEarnings Summary")
430
+ headers = ["Token", "Withdrawable", "USD"]
431
+ data_rows = [
432
+ ["ETH", f"{sums_native['ETH']:.6f} ETH", _fmt_usd(sums_native["ETH"], price_eth)],
433
+ ["GLM", f"{sums_native['GLM']:.6f} GLM", _fmt_usd(sums_native["GLM"], price_glm)],
434
+ ]
435
+ # Table widths
436
+ w = [len(h) for h in headers]
437
+ for r in data_rows:
438
+ for i in range(3):
439
+ w[i] = max(w[i], len(str(r[i])))
440
+ print(" ".join(headers[i].ljust(w[i]) for i in range(3)))
441
+ print(" ".join("-" * wi for wi in w))
442
+ for r in data_rows:
443
+ print(" ".join(str(r[i]).ljust(w[i]) for i in range(3)))
444
+ # Per stream table
339
445
  if rows:
340
- print("\nPer-stream:")
446
+ table = []
341
447
  for r in rows:
342
448
  if "error" in r:
343
- print(f"- {r['vm_id']} [{r['stream_id']}] ERROR: {r['error']}")
344
- else:
345
- print(f"- {r['vm_id']} [{r['stream_id']}]: vested={r['vested']} withdrawn={r['withdrawn']} withdrawable={r['withdrawable']}")
449
+ table.append([r["vm_id"], str(r["stream_id"]), "ERROR", r.get("error", "")])
450
+ continue
451
+ sym = "ETH" if (r.get("token") or "").lower() == ZERO.lower() else "GLM"
452
+ nat = Decimal(str(Web3.from_wei(int(r["withdrawable"]), "ether")))
453
+ price = price_eth if sym == "ETH" else price_glm
454
+ usd = _fmt_usd(nat, price)
455
+ table.append([r["vm_id"], str(r["stream_id"]), f"{nat:.6f} {sym}", usd])
456
+ h2 = ["VM", "Stream", "Withdrawable", "USD"]
457
+ w2 = [len(x) for x in h2]
458
+ for row in table:
459
+ for i in range(4):
460
+ w2[i] = max(w2[i], len(str(row[i])))
461
+ print("\nPer Stream")
462
+ print(" ".join(h2[i].ljust(w2[i]) for i in range(4)))
463
+ print(" ".join("-" * wi for wi in w2))
464
+ for row in table:
465
+ print(" ".join(str(row[i]).ljust(w2[i]) for i in range(4)))
346
466
  except Exception as e:
347
467
  print(f"Error: {e}")
348
468
  raise typer.Exit(code=1)
@@ -356,6 +476,7 @@ def streams_withdraw(
356
476
  """Withdraw vested funds for one or all streams."""
357
477
  from .container import Container
358
478
  from .config import settings
479
+ from .security.l2_faucet import L2FaucetService
359
480
  try:
360
481
  if not vm_id and not all_streams:
361
482
  print("Specify --vm-id or --all")
@@ -364,6 +485,12 @@ def streams_withdraw(
364
485
  c.config.from_pydantic(settings)
365
486
  stream_map = c.stream_map()
366
487
  client = c.stream_client()
488
+ # Ensure we have L2 gas for withdrawals (testnets)
489
+ try:
490
+ asyncio.run(L2FaucetService(settings).request_funds(settings.PROVIDER_ID))
491
+ except Exception:
492
+ # Non-fatal; proceed with withdraw attempt
493
+ pass
367
494
  targets = []
368
495
  if all_streams:
369
496
  items = asyncio.run(stream_map.all_items())
@@ -0,0 +1,63 @@
1
+ from __future__ import annotations
2
+
3
+ from typing import Optional, List, Tuple
4
+
5
+ from web3 import Web3
6
+ from golem_faucet import PowFaucetClient
7
+ from ..utils.logging import setup_logger
8
+
9
+ logger = setup_logger(__name__)
10
+
11
+
12
+ class L2FaucetService:
13
+ """Request native ETH on the L2 payments chain via PoW faucet.
14
+
15
+ Uses provider settings for RPC and faucet endpoints.
16
+ """
17
+
18
+ def __init__(self, config):
19
+ # Config is expected to expose POLYGON_RPC_URL, L2_* faucet fields
20
+ self.cfg = config
21
+ self.web3 = Web3(Web3.HTTPProvider(config.POLYGON_RPC_URL))
22
+ self.client = PowFaucetClient(
23
+ faucet_url=getattr(config, "L2_FAUCET_URL", "https://l2.holesky.golemdb.io/faucet"),
24
+ captcha_base_url=getattr(config, "L2_CAPTCHA_URL", "https://cap.gobas.me"),
25
+ captcha_api_key=getattr(config, "L2_CAPTCHA_API_KEY", "05381a2cef5e"),
26
+ )
27
+
28
+ def _balance_eth(self, address: str) -> float:
29
+ try:
30
+ wei = self.web3.eth.get_balance(Web3.to_checksum_address(address))
31
+ return float(self.web3.from_wei(wei, "ether"))
32
+ except Exception as e:
33
+ logger.warning(f"L2 balance check failed: {e}")
34
+ return 0.0
35
+
36
+ async def request_funds(self, address: str) -> Optional[str]:
37
+ """Ensure some native ETH on L2; if low, solve PoW and request faucet payout.
38
+
39
+ Returns tx hash string on payout, or None if skipped/failed.
40
+ """
41
+ bal = self._balance_eth(address)
42
+ if bal > 0.01:
43
+ logger.info(f"Sufficient L2 funds ({bal} ETH), skipping faucet.")
44
+ return None
45
+ chall = await self.client.get_challenge()
46
+ if not chall:
47
+ logger.error("could not fetch faucet challenge")
48
+ return None
49
+ token = chall.get("token")
50
+ challenge_list = chall.get("challenge") or []
51
+ solutions: List[Tuple[str, str, int]] = []
52
+ for salt, target in challenge_list:
53
+ nonce = PowFaucetClient.solve_challenge(salt, target)
54
+ solutions.append((salt, target, nonce))
55
+ redeemed = await self.client.redeem(token, solutions)
56
+ if not redeemed:
57
+ logger.error("failed to redeem challenge")
58
+ return None
59
+ tx = await self.client.request_funds(address, redeemed)
60
+ if tx:
61
+ logger.success(f"L2 faucet sent tx: {tx}")
62
+ return tx
63
+
@@ -47,6 +47,14 @@ def fetch_glm_usd_price() -> Optional[Decimal]:
47
47
  return _coingecko_simple_price(settings.COINGECKO_IDS)
48
48
 
49
49
 
50
+ def fetch_eth_usd_price() -> Optional[Decimal]:
51
+ """Fetch the current ETH price in USD from CoinGecko.
52
+
53
+ Uses the canonical "ethereum" id.
54
+ """
55
+ return _coingecko_simple_price("ethereum")
56
+
57
+
50
58
  def usd_to_glm(usd_amount: Decimal, glm_usd: Decimal) -> Decimal:
51
59
  if glm_usd <= 0:
52
60
  raise ValueError("Invalid GLM/USD price")
@@ -1,6 +1,6 @@
1
1
  [tool.poetry]
2
2
  name = "golem-vm-provider"
3
- version = "0.1.51"
3
+ version = "0.1.53"
4
4
  description = "VM on Golem Provider Node - Run your own provider node to offer VMs on the Golem Network"
5
5
  authors = ["Phillip Jensen <phillip+vm-on-golem@golemgrid.com>"]
6
6
  readme = "README.md"