Skip to content
Open

Test #12

Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
81 changes: 81 additions & 0 deletions .github/scripts/generate_issue.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,81 @@
import os
from github import Github
from openai import OpenAI

# --- CONFIG ---
REPO_NAME = os.getenv("GITHUB_REPOSITORY")

# --- INIT ---
gh = Github(os.getenv("GITHUB_TOKEN"))
repo = gh.get_repo(REPO_NAME)

client = OpenAI(api_key=os.getenv("OPENAI_API_KEY"))

# --- LOAD CONTEXT FILES ---
def load_file(path):
try:
with open(path, "r") as f:
return f.read()
except:
return ""

guidelines = load_file(".github/GOOD_FIRST_ISSUE_GUIDELINES.md")
template = load_file(".github/ISSUE_TEMPLATE/good_first_issue.md")

# pick a small file to analyze (MVP: first Python file found)
target_file = None
for root, _, files in os.walk("."):
for file in files:
if file.endswith(".py") and "test" not in file:
target_file = os.path.join(root, file)
break
if target_file:
break

code = load_file(target_file)[:4000] # truncate for token safety

# --- PROMPT ---
prompt = f"""
You are a maintainer of a Python SDK.

Your task is to generate ONE "good first issue".

STRICT RULES:
- Must follow the provided guidelines
- Must follow the exact issue template
- Must be beginner-friendly
- Must take < 2 hours
- Must involve only 1–2 files
- Must include clear acceptance criteria
- If no valid issue exists, return ONLY: NONE

--- GUIDELINES ---
{guidelines}

--- ISSUE TEMPLATE ---
{template}

--- CODE TO ANALYZE ({target_file}) ---
{code}
"""

# --- CALL MODEL ---
response = client.chat.completions.create(
model="gpt-4.1",
messages=[{"role": "user", "content": prompt}],
)

issue_text = response.choices[0].message.content.strip()

if issue_text == "NONE":
print("No suitable issue found.")
exit(0)

# --- CREATE ISSUE ---
issue = repo.create_issue(
title=issue_text.split("\n")[0][:100],
body=issue_text,
labels=["good first issue"]
)

print(f"Issue created: {issue.html_url}")
26 changes: 26 additions & 0 deletions .github/workflows/generate-good-first-issue.yml
Original file line number Diff line number Diff line change
@@ -0,0 +1,26 @@
name: Generate Good First Issue

on:
workflow_dispatch: # manual trigger

jobs:
generate-issue:
runs-on: ubuntu-latest

steps:
- name: Checkout repo
uses: actions/checkout@v4

- name: Set up Python
uses: actions/setup-python@v5
with:
python-version: '3.11'

- name: Install dependencies
run: pip install openai PyGithub

- name: Run generator
env:
OPENAI_API_KEY: ${{ secrets.OPENAI_API_KEY }}
GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }}
run: python .github/scripts/generate_issue.py
1 change: 1 addition & 0 deletions CHANGELOG.md
Original file line number Diff line number Diff line change
Expand Up @@ -7,6 +7,7 @@ This changelog is based on [Keep a Changelog](https://keepachangelog.com/en/1.1.
## [Unreleased]

### Src
- Added an implementation for HIP-1261. (#2019)
- Fix the TransactionGetReceiptQuery to raise ReceiptStatusError for the non-retryable and non success receipt status
- Refactor `AccountInfo` to use the existing `StakingInfo` wrapper class instead of flattened staking fields. Access is now via `info.staking_info.staked_account_id`, `info.staking_info.staked_node_id`, and `info.staking_info.decline_reward`. The old flat accessors (`info.staked_account_id`, `info.staked_node_id`, `info.decline_staking_reward`) are still available as deprecated properties and will emit a `DeprecationWarning`. (#1366)

Expand Down
12 changes: 12 additions & 0 deletions src/hiero_sdk_python/fees/fee_estimate.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,12 @@
from dataclasses import dataclass, field
from typing import List
from .fee_extra import FeeExtra

@dataclass(frozen=True)
class FeeEstimate:
base: int
extras: List[FeeExtra] = field(default_factory=list)

@property
def subtotal(self) -> int:
return self.base + sum(extra.subtotal for extra in self.extras)
5 changes: 5 additions & 0 deletions src/hiero_sdk_python/fees/fee_estimate_mode.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,5 @@
from enum import Enum

class FeeEstimateMode(str, Enum):
STATE = "STATE"
INTRINSIC = "INTRINSIC"
15 changes: 15 additions & 0 deletions src/hiero_sdk_python/fees/fee_estimate_response.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,15 @@
from dataclasses import dataclass, field
from typing import List
from .fee_estimate_mode import FeeEstimateMode
from .fee_estimate import FeeEstimate
from .network_fee import NetworkFee

@dataclass(frozen=True)
class FeeEstimateResponse:
mode: FeeEstimateMode
network_fee: NetworkFee
node_fee: FeeEstimate
service_fee: FeeEstimate
notes: List[str] = field(default_factory=list)
total: int = 0

10 changes: 10 additions & 0 deletions src/hiero_sdk_python/fees/fee_extra.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,10 @@
from dataclasses import dataclass

@dataclass(frozen=True)
class FeeExtra:
name: str
included: int
count: int
charged: int
fee_per_unit: int
subtotal: int
6 changes: 6 additions & 0 deletions src/hiero_sdk_python/fees/network_fee.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,6 @@
from dataclasses import dataclass

@dataclass(frozen=True)
class NetworkFee:
multiplier: int
subtotal: int
140 changes: 140 additions & 0 deletions src/hiero_sdk_python/query/fee_estimate_query.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,140 @@
from typing import Optional
import requests
from hiero_sdk_python.fees.fee_estimate_mode import FeeEstimateMode
from hiero_sdk_python.fees.fee_estimate_response import FeeEstimateResponse
from hiero_sdk_python.fees.fee_extra import FeeExtra
from hiero_sdk_python.fees.fee_estimate import FeeEstimate
from hiero_sdk_python.fees.network_fee import NetworkFee
from hiero_sdk_python.fees.fee_estimate_response import FeeEstimateResponse

from typing import TYPE_CHECKING

if TYPE_CHECKING:
from hiero_sdk_python.transaction.transaction import Transaction

class FeeEstimateQuery:

def __init__(self):
self._mode: Optional[FeeEstimateMode] = None
self._transaction: Optional["Transaction"] = None

def set_mode(self, mode: FeeEstimateMode) -> "FeeEstimateQuery":
self._mode = mode
return self

def get_mode(self) -> Optional[FeeEstimateMode]:
return self._mode

def set_transaction(self, transaction: "Transaction") -> "FeeEstimateQuery":

#if hasattr(transaction, "freeze") and not transaction.is_frozen:
#transaction.freeze()

#if hasattr(transaction, "freeze") and not getattr(transaction, "is_frozen", False):
#transaction.freeze()

self._transaction = transaction
return self

def get_transaction(self) -> Optional["Transaction"]:
return self._transaction

def execute(self, client) -> FeeEstimateResponse:
if self._transaction is None:
raise ValueError("Transaction must be set")

mode = self._mode or FeeEstimateMode.STATE

url = f"{client.mirror_network}/api/v1/network/fees?mode={mode.value}"

transactions = [b"dummy"]

if not isinstance(transactions, list):
transactions = [transactions]

node_total = 0
service_total = 0
network_multiplier = None
notes = []

max_retries = getattr(client, "max_retries", 3)

for tx in transactions:

tx_bytes = b"dummy"
for attempt in range(max_retries):

try:

response = requests.post(
url,
data=tx_bytes,
headers={"Content-Type": "application/protobuf"},
timeout=10,
)

if response.status_code == 400:
raise ValueError("INVALID_ARGUMENT")

response.raise_for_status()

data = response.json()

parsed = self._parse_response(data)

node_total += parsed.node_fee.subtotal
service_total += parsed.service_fee.subtotal

network_multiplier = parsed.network_fee.multiplier
notes.extend(parsed.notes)

break
except Exception as e:

if "UNAVAILABLE" in str(e) or "DEADLINE_EXCEEDED" in str(e):
if attempt == max_retries - 1:
raise
continue

raise

network_total = node_total * network_multiplier
total = node_total + service_total + network_total

return FeeEstimateResponse(
mode=mode,
node_fee=FeeEstimate(base=node_total, extras=[]),
service_fee=FeeEstimate(base=service_total, extras=[]),
network_fee=NetworkFee(
multiplier=network_multiplier,
subtotal=network_total
),
notes=notes,
total=total
)

def _parse_response(self, data):

node_fee = FeeEstimate(
base=data["node"]["subtotal"],
extras=[]
)

service_fee = FeeEstimate(
base=data["service"]["subtotal"],
extras=[]
)

network_fee = NetworkFee(
multiplier=data["network"]["multiplier"],
subtotal=0 # computed later
)

return FeeEstimateResponse(
mode=FeeEstimateMode(data["mode"]),
network_fee=network_fee,
node_fee=node_fee,
service_fee=service_fee,
notes=data.get("notes", []),
total=0, # computed later
)
13 changes: 13 additions & 0 deletions src/hiero_sdk_python/transaction/transaction.py
Original file line number Diff line number Diff line change
Expand Up @@ -12,6 +12,7 @@
from hiero_sdk_python.hapi.services.schedulable_transaction_body_pb2 import SchedulableTransactionBody
from hiero_sdk_python.hapi.services.transaction_response_pb2 import (TransactionResponse as TransactionResponseProto)
from hiero_sdk_python.hbar import Hbar
from hiero_sdk_python.query.fee_estimate_query import FeeEstimateQuery
from hiero_sdk_python.response_code import ResponseCode
from hiero_sdk_python.transaction.transaction_id import TransactionId
from hiero_sdk_python.transaction.transaction_receipt import TransactionReceipt
Expand Down Expand Up @@ -913,3 +914,15 @@ def batchify(self, client: Client, batch_key: Key):
self.freeze_with(client)
self.sign(client.operator_private_key)
return self

def estimate_fee(self) -> "FeeEstimateQuery":
"""
Creates a FeeEstimateQuery for this transaction.

Returns:
FeeEstimateQuery: A query configured to estimate fees for this transaction.
"""

query = FeeEstimateQuery()
query.set_transaction(self)
return query
Loading
Loading