tensorfeed 1.28.0__tar.gz → 2.0.0__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.
@@ -1,6 +1,6 @@
1
1
  Metadata-Version: 2.4
2
2
  Name: tensorfeed
3
- Version: 1.28.0
3
+ Version: 2.0.0
4
4
  Summary: Python SDK for the TensorFeed.ai API: AI news, status, model pricing, premium routing + history series + webhook watches, agent payments via USDC on Base (optional web3 auto-send)
5
5
  Author-email: "TensorFeed.ai" <feedback@tensorfeed.ai>
6
6
  License-Expression: MIT
@@ -4,7 +4,7 @@ build-backend = "setuptools.build_meta"
4
4
 
5
5
  [project]
6
6
  name = "tensorfeed"
7
- version = "1.28.0"
7
+ version = "2.0.0"
8
8
  description = "Python SDK for the TensorFeed.ai API: AI news, status, model pricing, premium routing + history series + webhook watches, agent payments via USDC on Base (optional web3 auto-send)"
9
9
  readme = "README.md"
10
10
  license = "MIT"
@@ -21,4 +21,4 @@ __all__ = [
21
21
  "PaymentRequired",
22
22
  "RateLimited",
23
23
  ]
24
- __version__ = "1.28.0"
24
+ __version__ = "1.29.0"
@@ -565,26 +565,35 @@ class TensorFeed:
565
565
  """
566
566
  return self._get("/payment/info")
567
567
 
568
- def buy_credits(self, *, amount_usd: float) -> dict[str, Any]:
569
- """Generate a 30-min payment quote.
568
+ def buy_credits(self, *, amount_usd: float, sender_wallet: str) -> dict[str, Any]:
569
+ """Generate a 30-min payment quote bound to a sender wallet.
570
570
 
571
571
  Returns a dict with ``wallet``, ``memo``, ``amount_usd``, ``credits``,
572
- ``expires_at``, ``ttl_seconds``, ``next_step``.
572
+ ``sender_wallet``, ``expires_at``, ``ttl_seconds``, ``next_step``.
573
573
 
574
- Send the USDC on Base to ``wallet`` (memo is optional but recommended),
575
- then call ``confirm()`` with the tx hash and the ``memo`` as nonce.
574
+ Send the USDC on Base FROM ``sender_wallet`` TO ``wallet``, then
575
+ call ``confirm()`` with the tx hash and the returned ``memo`` as
576
+ nonce. The on-chain sender must match ``sender_wallet`` exactly
577
+ or ``confirm()`` will reject with ``sender_mismatch``. This is
578
+ the post-2026-05-05 binding that closes the public-mempool tx
579
+ sniper attack.
576
580
 
577
581
  Args:
578
582
  amount_usd: USD value of credits to buy. Must be 0.5 to 10000.
579
583
  Volume discounts apply at $5 (10%), $30 (25%), $200 (40%).
584
+ sender_wallet: 0x-prefixed 40-char EVM address that will sign
585
+ the on-chain USDC transfer. Required.
580
586
  """
581
- return self._post("/payment/buy-credits", {"amount_usd": amount_usd})
587
+ return self._post(
588
+ "/payment/buy-credits",
589
+ {"amount_usd": amount_usd, "sender_wallet": sender_wallet},
590
+ )
582
591
 
583
592
  def confirm(
584
593
  self,
585
594
  *,
586
595
  tx_hash: str,
587
- nonce: str | None = None,
596
+ nonce: str,
588
597
  ) -> dict[str, Any]:
589
598
  """Verify a USDC tx on-chain and mint a credit token.
590
599
 
@@ -597,14 +606,12 @@ class TensorFeed:
597
606
 
598
607
  Args:
599
608
  tx_hash: 0x-prefixed Base mainnet transaction hash
600
- nonce: optional memo from ``buy_credits()``. If provided and
601
- the tx amount matches the quote within $0.01, the quoted
602
- credits (with volume discount) are applied. Otherwise
603
- credits are calculated from the actual tx amount.
609
+ nonce: memo from ``buy_credits()``. Required as of 2026-05-05;
610
+ the legacy no-nonce path is closed because it allowed
611
+ public-mempool observers to steal credits by racing the
612
+ rightful payer.
604
613
  """
605
- body: dict[str, Any] = {"tx_hash": tx_hash}
606
- if nonce is not None:
607
- body["nonce"] = nonce
614
+ body: dict[str, Any] = {"tx_hash": tx_hash, "nonce": nonce}
608
615
  result = self._post("/payment/confirm", body)
609
616
  if isinstance(result, dict) and result.get("ok") and result.get("token"):
610
617
  self.token = str(result["token"])
@@ -1026,6 +1033,54 @@ class TensorFeed:
1026
1033
  fire_cap=fire_cap,
1027
1034
  )
1028
1035
 
1036
+ def create_leaderboard_rank_watch(
1037
+ self,
1038
+ *,
1039
+ provider: str,
1040
+ op: str,
1041
+ threshold: int | None = None,
1042
+ callback_url: str,
1043
+ secret: str | None = None,
1044
+ fire_cap: int | None = None,
1045
+ ) -> dict[str, Any]:
1046
+ """Convenience helper for leaderboard rank-change webhook watches.
1047
+
1048
+ Costs 1 credit at registration. Fires when the provider's rank in
1049
+ the cross-provider 7-day uptime leaderboard crosses a threshold or
1050
+ changes. Useful for SRE / vendor-management workflows tracking
1051
+ which AI provider is currently most reliable relative to the field.
1052
+
1053
+ Rank semantics: rank 1 = best (highest uptime).
1054
+
1055
+ Args:
1056
+ provider: Provider name or slug (case-insensitive). e.g.
1057
+ 'claude', 'OpenAI API', 'AWS Bedrock'.
1058
+ op: 'drops_below', 'rises_above', or 'changes'.
1059
+ threshold: Required for drops_below / rises_above; integer
1060
+ rank position (e.g. 5 means "below #5"). Ignored for
1061
+ 'changes'.
1062
+ callback_url: HTTPS URL to POST to on each fire.
1063
+ secret: Optional HMAC shared secret.
1064
+ fire_cap: Max fires before auto-disable (default 100).
1065
+
1066
+ Returns:
1067
+ Same shape as ``create_watch``.
1068
+
1069
+ Raises:
1070
+ ValueError: if no token is set on the client
1071
+ PaymentRequired: if the token has insufficient credits
1072
+ TensorFeedError: 400 on invalid spec or callback URL
1073
+ """
1074
+ spec: dict[str, Any] = {"type": "leaderboard_rank", "provider": provider, "op": op}
1075
+ if threshold is not None:
1076
+ spec["threshold"] = threshold
1077
+ return self.create_watch(
1078
+ spec=spec,
1079
+ callback_url=callback_url,
1080
+ secret=secret,
1081
+ fire_cap=fire_cap,
1082
+ )
1083
+
1029
1084
  # ── Paid: provider deep-dive (Tier 1, 1 credit) ────────────────
1030
1085
 
1031
1086
  def provider_deepdive(self, provider: str) -> dict[str, Any]:
@@ -106,8 +106,10 @@ def auto_purchase_credits(
106
106
  account = Account.from_key(private_key)
107
107
  sender = account.address
108
108
 
109
- # Step 1: TensorFeed-side quote
110
- quote = tf.buy_credits(amount_usd=amount_usd)
109
+ # Step 1: TensorFeed-side quote, bound to this sender wallet so the
110
+ # post-2026-05-05 Tx-Sniper guard accepts the confirm. Must pass the
111
+ # exact lowercased address that will sign the on-chain transfer.
112
+ quote = tf.buy_credits(amount_usd=amount_usd, sender_wallet=sender.lower())
111
113
  receiving_wallet = quote["wallet"]
112
114
  memo = quote["memo"]
113
115
 
@@ -135,7 +137,12 @@ def auto_purchase_credits(
135
137
  signed = Account.sign_transaction(tx, private_key=private_key)
136
138
  raw_tx = getattr(signed, "raw_transaction", None) or getattr(signed, "rawTransaction")
137
139
  tx_hash_bytes = w3.eth.send_raw_transaction(raw_tx)
138
- tx_hash = "0x" + tx_hash_bytes.hex().lstrip("0x")
140
+ # Older fix used `.lstrip("0x")` which silently corrupts hashes that
141
+ # contain leading zero bytes (rare but real, ~0.4% of txs). Modern
142
+ # web3.py returns HexBytes whose .hex() may or may not include the
143
+ # 0x prefix depending on version, so check and prepend explicitly.
144
+ raw_hex = tx_hash_bytes.hex()
145
+ tx_hash = raw_hex if raw_hex.startswith("0x") else "0x" + raw_hex
139
146
 
140
147
  # Step 4: wait for confirmation
141
148
  deadline = time.time() + wait_seconds
@@ -1,6 +1,6 @@
1
1
  Metadata-Version: 2.4
2
2
  Name: tensorfeed
3
- Version: 1.28.0
3
+ Version: 2.0.0
4
4
  Summary: Python SDK for the TensorFeed.ai API: AI news, status, model pricing, premium routing + history series + webhook watches, agent payments via USDC on Base (optional web3 auto-send)
5
5
  Author-email: "TensorFeed.ai" <feedback@tensorfeed.ai>
6
6
  License-Expression: MIT
File without changes
File without changes