atp-protocol 1.0.0__py3-none-any.whl → 1.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.
atp/token_prices.py DELETED
@@ -1,97 +0,0 @@
1
- from __future__ import annotations
2
-
3
- import asyncio
4
- from typing import Dict
5
-
6
- import httpx
7
- from loguru import logger
8
-
9
-
10
- class TokenPriceFetcher:
11
- """Fetches real-time token prices from CoinGecko API."""
12
-
13
- COINGECKO_URL = "https://api.coingecko.com/api/v3/simple/price"
14
- CACHE_TTL_SECONDS = 60 # Cache price for 60 seconds
15
-
16
- def __init__(self):
17
- self._cached_prices: Dict[str, float] = {}
18
- self._cache_timestamps: Dict[str, float] = {}
19
- self._lock = asyncio.Lock()
20
-
21
- async def get_price_usd(self, token: str = "SOL") -> float:
22
- import time
23
-
24
- # USDC is pegged to USD
25
- if token.upper() == "USDC":
26
- return 1.0
27
-
28
- async with self._lock:
29
- current_time = time.time()
30
- cache_key = token.upper()
31
-
32
- cached_price = self._cached_prices.get(cache_key)
33
- cached_timestamp = self._cache_timestamps.get(
34
- cache_key, 0
35
- )
36
- if (
37
- cached_price
38
- and (current_time - cached_timestamp)
39
- < self.CACHE_TTL_SECONDS
40
- ):
41
- return cached_price
42
-
43
- coingecko_ids = {"SOL": "solana"}
44
- coingecko_id = coingecko_ids.get(token.upper())
45
- if not coingecko_id:
46
- logger.warning(
47
- f"Unknown token: {token}, defaulting to $1.00"
48
- )
49
- return 1.0
50
-
51
- try:
52
- async with httpx.AsyncClient() as client:
53
- response = await client.get(
54
- self.COINGECKO_URL,
55
- params={
56
- "ids": coingecko_id,
57
- "vs_currencies": "usd",
58
- },
59
- timeout=10.0,
60
- )
61
- response.raise_for_status()
62
- data = response.json()
63
-
64
- price = data.get(coingecko_id, {}).get("usd")
65
- if price is None:
66
- raise ValueError(
67
- f"{token} price not found in response"
68
- )
69
-
70
- self._cached_prices[cache_key] = float(price)
71
- self._cache_timestamps[cache_key] = current_time
72
- return float(price)
73
-
74
- except httpx.HTTPError as e:
75
- logger.warning(
76
- f"Failed to fetch {token} price from CoinGecko: {e}"
77
- )
78
- if cached_price:
79
- return cached_price
80
- return 150.0
81
- except Exception as e:
82
- logger.error(
83
- f"Unexpected error fetching {token} price: {e}"
84
- )
85
- if cached_price:
86
- return cached_price
87
- return 150.0
88
-
89
- async def get_sol_price_usd(self) -> float:
90
- return await self.get_price_usd("SOL")
91
-
92
-
93
- token_price_fetcher = TokenPriceFetcher()
94
-
95
-
96
- # if __name__ == "__main__":
97
- # print(asyncio.run(token_price_fetcher.get_price_usd("SOL")))
atp/utils.py DELETED
@@ -1,174 +0,0 @@
1
- from __future__ import annotations
2
-
3
- from typing import Any, Dict, Optional
4
-
5
- from atp import config
6
- from atp.schemas import PaymentToken
7
-
8
-
9
- def calculate_payment_amounts(
10
- usd_cost: float,
11
- token_price_usd: float,
12
- payment_token: PaymentToken,
13
- ) -> Dict[str, Any]:
14
- """
15
- Calculate payment amounts with settlement fee taken from the total.
16
-
17
- Returns amounts in the smallest unit (lamports for SOL, micro-units for USDC).
18
- """
19
- total_amount_token = usd_cost / token_price_usd
20
- fee_amount_token = (
21
- total_amount_token * config.SETTLEMENT_FEE_PERCENT
22
- )
23
- agent_amount_token = total_amount_token - fee_amount_token
24
-
25
- decimals = (
26
- 9
27
- if payment_token == PaymentToken.SOL
28
- else config.USDC_DECIMALS
29
- )
30
-
31
- total_amount_units = int(total_amount_token * 10**decimals)
32
- fee_amount_units = int(fee_amount_token * 10**decimals)
33
- agent_amount_units = total_amount_units - fee_amount_units
34
-
35
- return {
36
- "total_amount_units": total_amount_units,
37
- "agent_amount_units": agent_amount_units,
38
- "fee_amount_units": fee_amount_units,
39
- "total_amount_token": total_amount_token,
40
- "agent_amount_token": agent_amount_token,
41
- "fee_amount_token": fee_amount_token,
42
- "decimals": decimals,
43
- "fee_percent": config.SETTLEMENT_FEE_PERCENT * 100,
44
- }
45
-
46
-
47
- def _safe_int(v: Any) -> Optional[int]:
48
- if v is None:
49
- return None
50
- if isinstance(v, bool):
51
- return None
52
- if isinstance(v, int):
53
- return v
54
- if isinstance(v, float):
55
- return int(v)
56
- if isinstance(v, str):
57
- s = v.strip()
58
- if not s:
59
- return None
60
- try:
61
- return int(float(s))
62
- except Exception:
63
- return None
64
- return None
65
-
66
-
67
- def extract_usage_token_counts(
68
- usage: Any,
69
- ) -> Dict[str, Optional[int]]:
70
- """
71
- Normalize token counts from various possible upstream shapes.
72
- Common keys:
73
- - prompt_tokens / completion_tokens / total_tokens (OpenAI-like)
74
- - input_tokens / output_tokens / total_tokens
75
- """
76
- if not isinstance(usage, dict):
77
- return {
78
- "input_tokens": None,
79
- "output_tokens": None,
80
- "total_tokens": None,
81
- }
82
-
83
- input_tokens = _safe_int(usage.get("input_tokens"))
84
- if input_tokens is None:
85
- input_tokens = _safe_int(usage.get("prompt_tokens"))
86
-
87
- output_tokens = _safe_int(usage.get("output_tokens"))
88
- if output_tokens is None:
89
- output_tokens = _safe_int(usage.get("completion_tokens"))
90
-
91
- total_tokens = _safe_int(usage.get("total_tokens"))
92
- if (
93
- total_tokens is None
94
- and input_tokens is not None
95
- and output_tokens is not None
96
- ):
97
- total_tokens = input_tokens + output_tokens
98
-
99
- return {
100
- "input_tokens": input_tokens,
101
- "output_tokens": output_tokens,
102
- "total_tokens": total_tokens,
103
- }
104
-
105
-
106
- def compute_usd_cost_from_usage(usage: Any) -> Dict[str, Any]:
107
- """
108
- Returns a pricing breakdown dict containing:
109
- - usd_cost (float)
110
- - source: per_million_rates | upstream_total_cost | fallback_default
111
- - token counts and configured rates
112
- """
113
- usage = usage if isinstance(usage, dict) else {}
114
- counts = extract_usage_token_counts(usage)
115
-
116
- can_compute_input = (
117
- config.INPUT_COST_PER_MILLION_USD is not None
118
- and counts["input_tokens"] is not None
119
- )
120
- can_compute_output = (
121
- config.OUTPUT_COST_PER_MILLION_USD is not None
122
- and counts["output_tokens"] is not None
123
- )
124
-
125
- if can_compute_input or can_compute_output:
126
- input_cost = (
127
- (counts["input_tokens"] or 0)
128
- / 1_000_000.0
129
- * (config.INPUT_COST_PER_MILLION_USD or 0.0)
130
- )
131
- output_cost = (
132
- (counts["output_tokens"] or 0)
133
- / 1_000_000.0
134
- * (config.OUTPUT_COST_PER_MILLION_USD or 0.0)
135
- )
136
- usd_cost = float(input_cost + output_cost)
137
- return {
138
- "usd_cost": usd_cost,
139
- "source": "per_million_rates",
140
- "input_tokens": counts["input_tokens"],
141
- "output_tokens": counts["output_tokens"],
142
- "total_tokens": counts["total_tokens"],
143
- "input_cost_per_million_usd": config.INPUT_COST_PER_MILLION_USD,
144
- "output_cost_per_million_usd": config.OUTPUT_COST_PER_MILLION_USD,
145
- "input_cost_usd": input_cost,
146
- "output_cost_usd": output_cost,
147
- }
148
-
149
- upstream_total_cost = usage.get("total_cost")
150
- try:
151
- if upstream_total_cost is not None:
152
- usd_cost = float(upstream_total_cost)
153
- return {
154
- "usd_cost": usd_cost,
155
- "source": "upstream_total_cost",
156
- "input_tokens": counts["input_tokens"],
157
- "output_tokens": counts["output_tokens"],
158
- "total_tokens": counts["total_tokens"],
159
- "input_cost_per_million_usd": config.INPUT_COST_PER_MILLION_USD,
160
- "output_cost_per_million_usd": config.OUTPUT_COST_PER_MILLION_USD,
161
- }
162
- except Exception:
163
- pass
164
-
165
- usd_cost = 0.01
166
- return {
167
- "usd_cost": usd_cost,
168
- "source": "fallback_default",
169
- "input_tokens": counts["input_tokens"],
170
- "output_tokens": counts["output_tokens"],
171
- "total_tokens": counts["total_tokens"],
172
- "input_cost_per_million_usd": config.INPUT_COST_PER_MILLION_USD,
173
- "output_cost_per_million_usd": config.OUTPUT_COST_PER_MILLION_USD,
174
- }
atp/vault.py DELETED
@@ -1,75 +0,0 @@
1
- from __future__ import annotations
2
-
3
- import asyncio
4
- from typing import Any, Dict, Optional
5
-
6
- from loguru import logger
7
-
8
-
9
- class InMemoryVault:
10
- """In-process job vault with TTL expiration (for local dev/testing).
11
-
12
- Note: data is not shared across processes and is lost on restart.
13
- """
14
-
15
- def __init__(self, default_ttl: int = 600):
16
- self.default_ttl = default_ttl
17
- self._lock = asyncio.Lock()
18
- # key: job_id -> {"data": dict, "expires_at": float}
19
- self._store: Dict[str, Dict[str, Any]] = {}
20
-
21
- async def connect(self) -> None:
22
- logger.warning(
23
- "Using in-memory job vault. Jobs will be lost on restart."
24
- )
25
-
26
- async def disconnect(self) -> None:
27
- return None
28
-
29
- def _is_expired(self, expires_at: float) -> bool:
30
- import time
31
-
32
- return time.time() >= expires_at
33
-
34
- async def store(
35
- self,
36
- job_id: str,
37
- data: Dict[str, Any],
38
- ttl: Optional[int] = None,
39
- ) -> None:
40
- import time
41
-
42
- ttl = ttl or self.default_ttl
43
- expires_at = time.time() + ttl
44
- async with self._lock:
45
- self._store[job_id] = {
46
- "data": data,
47
- "expires_at": expires_at,
48
- }
49
-
50
- async def retrieve(self, job_id: str) -> Optional[Dict[str, Any]]:
51
- async with self._lock:
52
- entry = self._store.get(job_id)
53
- if not entry:
54
- return None
55
- if self._is_expired(entry["expires_at"]):
56
- self._store.pop(job_id, None)
57
- return None
58
- return entry["data"]
59
-
60
- async def delete(self, job_id: str) -> bool:
61
- async with self._lock:
62
- existed = job_id in self._store
63
- self._store.pop(job_id, None)
64
- return existed
65
-
66
- async def pop(self, job_id: str) -> Optional[Dict[str, Any]]:
67
- async with self._lock:
68
- entry = self._store.get(job_id)
69
- if not entry:
70
- return None
71
- if self._is_expired(entry["expires_at"]):
72
- self._store.pop(job_id, None)
73
- return None
74
- self._store.pop(job_id, None)
75
- return entry["data"]
@@ -1,13 +0,0 @@
1
- atp/__init__.py,sha256=lv9yAwpBiySYw2ECZagHUmZHtKXrbkCxF1XoF0IuYeI,205
2
- atp/config.py,sha256=m5hsDeMgbtqMUu9uO4G_ht6WBD8T5FNm-ga2tE5oRmU,2030
3
- atp/middleware.py,sha256=lR0PX2O3ka3u_Wj1T8T5dSm56DxEzduXHkNyk-230Nc,13891
4
- atp/schemas.py,sha256=iASVBPpAhrO-LDXs2UC9ABtfV1oDYsu4kZcz3dVeJNA,6220
5
- atp/settlement_client.py,sha256=jfvhxDqp2kDISWG7tjX5s88goAPxp-lMXvuJtpMyOys,6742
6
- atp/solana_utils.py,sha256=T4KtMZkXVmUENsV5u0V3Yxd9Cp11YkVEpYt9lvMfZJI,36767
7
- atp/token_prices.py,sha256=A5EeNXkLfePktzQieGUVP5SGE5_KsClFVuL6B0vTr5k,3077
8
- atp/utils.py,sha256=VnYg6vmggUtrugwFc9Ecr8y45cxrFWv598WPK0w9y-0,5516
9
- atp/vault.py,sha256=ZMcrjCulI9j36nZtBw0kfL4k0Ul7LgUKyEedtN7o2ks,2193
10
- atp_protocol-1.0.0.dist-info/LICENSE,sha256=xx0jnfkXJvxRnG63LTGOxlggYnIysveWIZ6H3PNdCrQ,11357
11
- atp_protocol-1.0.0.dist-info/METADATA,sha256=gIHO-KWejBOhVkvHFstqCBcqgZMZ6El3WNqGA7GLoO8,18442
12
- atp_protocol-1.0.0.dist-info/WHEEL,sha256=b4K_helf-jlQoXBBETfwnf4B04YC67LOev0jo4fX5m8,88
13
- atp_protocol-1.0.0.dist-info/RECORD,,