Skip to content
Merged
4 changes: 2 additions & 2 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -88,10 +88,10 @@ hub = og.ModelHub(email="you@example.com", password="...")

### OPG Token Approval

Before making LLM requests, your wallet must approve OPG token spending via the [Permit2](https://github.com/Uniswap/permit2) protocol. Call this once (it's idempotent — no transaction is sent if the allowance already covers the requested amount):
Before making LLM requests, your wallet must approve OPG token spending via the [Permit2](https://github.com/Uniswap/permit2) protocol. This only sends an on-chain transaction when the current allowance drops below the threshold:

```python
llm.ensure_opg_approval(opg_amount=5)
llm.ensure_opg_approval(min_allowance=5)
```

See [Payment Settlement](#payment-settlement) for details on settlement modes.
Expand Down
4 changes: 2 additions & 2 deletions docs/CLAUDE_SDK_USERS.md
Original file line number Diff line number Diff line change
Expand Up @@ -21,8 +21,8 @@ import os
# Create an LLM client
llm = og.LLM(private_key=os.environ["OG_PRIVATE_KEY"])

# One-time OPG token approval (idempotent — skips if allowance already sufficient)
llm.ensure_opg_approval(opg_amount=0.1)
# Ensure sufficient OPG allowance (only sends tx when below threshold)
llm.ensure_opg_approval(min_allowance=0.1)

# LLM Chat (TEE-verified with x402 payments, async)
result = await llm.chat(
Expand Down
2 changes: 1 addition & 1 deletion docs/opengradient/client/index.md
Original file line number Diff line number Diff line change
Expand Up @@ -24,7 +24,7 @@ import opengradient as og

# LLM inference (Base Sepolia OPG tokens)
llm = og.LLM(private_key="0x...")
llm.ensure_opg_approval(opg_amount=5)
llm.ensure_opg_approval(min_allowance=5)
result = await llm.chat(model=og.TEE_LLM.CLAUDE_HAIKU_4_5, messages=[...])

# On-chain model inference (OpenGradient testnet gas tokens)
Expand Down
31 changes: 21 additions & 10 deletions docs/opengradient/client/llm.md
Original file line number Diff line number Diff line change
Expand Up @@ -22,8 +22,6 @@ All request methods (``chat``, ``completion``) are async.

Before making LLM requests, ensure your wallet has approved sufficient
OPG tokens for Permit2 spending by calling ``ensure_opg_approval``.
This only sends an on-chain transaction when the current allowance is
below the requested amount.

#### Constructor

Expand Down Expand Up @@ -193,18 +191,30 @@ TextGenerationOutput: Generated text results including:
#### `ensure_opg_approval()`

```python
def ensure_opg_approval(self, opg_amount: float) ‑> [Permit2ApprovalResult](./opg_token)
def ensure_opg_approval(
self,
min_allowance: float,
approve_amount: Optional[float] = None
) ‑> [Permit2ApprovalResult](./opg_token)
```
Ensure the Permit2 allowance for OPG is at least ``opg_amount``.
Ensure the Permit2 allowance stays above a minimum threshold.

Only sends a transaction when the current allowance drops below
``min_allowance``. When approval is needed, approves ``approve_amount``
(defaults to ``2 * min_allowance``) to create a buffer that survives
multiple service restarts without re-approving.

Best for backend servers that call this on startup::

Checks the current Permit2 allowance for the wallet. If the allowance
is already >= the requested amount, returns immediately without sending
a transaction. Otherwise, sends an ERC-20 approve transaction.
llm.ensure_opg_approval(min_allowance=5.0, approve_amount=100.0)

**Arguments**

* **`opg_amount`**: Minimum number of OPG tokens required (e.g. ``0.1``
for 0.1 OPG). Must be at least 0.1 OPG.
* **`min_allowance`**: The minimum acceptable allowance in OPG. Must be
at least 0.1 OPG.
* **`approve_amount`**: The amount of OPG to approve when a transaction
is needed. Defaults to ``2 * min_allowance``. Must be
>= ``min_allowance``.

**Returns**

Expand All @@ -214,5 +224,6 @@ Permit2ApprovalResult: Contains ``allowance_before``,

**Raises**

* **`ValueError`**: If the OPG amount is less than 0.1.
* **`ValueError`**: If ``min_allowance`` is less than 0.1 or
``approve_amount`` is less than ``min_allowance``.
* **`RuntimeError`**: If the approval transaction fails.
9 changes: 5 additions & 4 deletions docs/opengradient/client/model_hub.md
Original file line number Diff line number Diff line change
Expand Up @@ -43,15 +43,16 @@ Create a new model with the given model_name and model_desc, and a specified ver

* **`model_name (str)`**: The name of the model.
* **`model_desc (str)`**: The description of the model.
* **`version (str)`**: The version identifier (default is "1.00").
* **`version (str)`**: A label used in the initial version notes (default is "1.00").
* **`Note`**: the actual version string is assigned by the server.

**Returns**

dict: The server response containing model details.
ModelRepository: Object containing the model name and server-assigned version string.

**Raises**

* **`CreateModelError`**: If the model creation fails.
* **`RuntimeError`**: If the model creation fails.

---

Expand Down Expand Up @@ -125,7 +126,7 @@ Upload a model file to the server.

**Returns**

dict: The processed result.
FileUploadResult: The processed result.

**Raises**

Expand Down
73 changes: 0 additions & 73 deletions docs/opengradient/client/opg_token.md

This file was deleted.

2 changes: 1 addition & 1 deletion docs/opengradient/index.md
Original file line number Diff line number Diff line change
Expand Up @@ -35,7 +35,7 @@ import opengradient as og
llm = og.LLM(private_key="0x...")

# One-time OPG token approval (idempotent -- skips if allowance is sufficient)
llm.ensure_opg_approval(opg_amount=5)
llm.ensure_opg_approval(min_allowance=5)

# Chat with an LLM (TEE-verified)
response = asyncio.run(llm.chat(
Expand Down
4 changes: 2 additions & 2 deletions examples/langchain_react_agent.py
Original file line number Diff line number Diff line change
Expand Up @@ -17,9 +17,9 @@

private_key = os.environ["OG_PRIVATE_KEY"]

# One-time Permit2 approval for OPG spending (idempotent)
# Ensure sufficient OPG allowance for Permit2 spending
llm_client = og.LLM(private_key=private_key)
llm_client.ensure_opg_approval(opg_amount=5)
llm_client.ensure_opg_approval(min_allowance=5)

# Create the OpenGradient LangChain adapter
llm = og.agents.langchain_adapter(
Expand Down
2 changes: 1 addition & 1 deletion examples/llm_chat.py
Original file line number Diff line number Diff line change
Expand Up @@ -10,7 +10,7 @@

async def main():
llm = og.LLM(private_key=os.environ.get("OG_PRIVATE_KEY"))
llm.ensure_opg_approval(opg_amount=0.1)
llm.ensure_opg_approval(min_allowance=0.1)

messages = [
{"role": "user", "content": "What is the capital of France?"},
Expand Down
2 changes: 1 addition & 1 deletion examples/llm_chat_streaming.py
Original file line number Diff line number Diff line change
Expand Up @@ -6,7 +6,7 @@

async def main():
llm = og.LLM(private_key=os.environ.get("OG_PRIVATE_KEY"))
llm.ensure_opg_approval(opg_amount=0.1)
llm.ensure_opg_approval(min_allowance=0.1)

messages = [
{"role": "user", "content": "What is Python?"},
Expand Down
4 changes: 2 additions & 2 deletions examples/llm_tool_calling.py
Original file line number Diff line number Diff line change
Expand Up @@ -50,8 +50,8 @@ async def main():
{"role": "user", "content": "What's the weather like in Dallas, Texas? Give me the temperature in fahrenheit."},
]

# One-time Permit2 approval for OPG spending (idempotent)
llm.ensure_opg_approval(opg_amount=0.1)
# Ensure sufficient OPG allowance for Permit2 spending
llm.ensure_opg_approval(min_allowance=0.1)

print("Testing Gemini tool calls...")
print(f"Model: {og.TEE_LLM.GEMINI_2_5_FLASH_LITE}")
Expand Down
18 changes: 16 additions & 2 deletions integrationtest/llm/test_llm_chat.py
Original file line number Diff line number Diff line change
Expand Up @@ -20,6 +20,13 @@
"stateMutability": "nonpayable",
"type": "function",
},
{
"inputs": [{"name": "account", "type": "address"}],
"name": "balanceOf",
"outputs": [{"name": "", "type": "uint256"}],
"stateMutability": "view",
"type": "function",
},
]

# Amount of OPG tokens to fund the test account with
Expand Down Expand Up @@ -76,14 +83,21 @@ def _fund_account(funder_key: str, recipient_address: str):
if opg_receipt.status != 1:
raise RuntimeError(f"OPG transfer failed: {opg_hash.hex()}")

# Wait for the recipient balance to be visible on the RPC node
# Wait for the recipient balances to be visible on the RPC node
for _ in range(5):
if w3.eth.get_balance(recipient) > 0:
break
time.sleep(1)
else:
raise RuntimeError("Recipient ETH balance is still 0 after funding")

for _ in range(10):
if token.functions.balanceOf(recipient).call() > 0:
break
time.sleep(1)
else:
raise RuntimeError("Recipient OPG token balance is still 0 after funding")


@pytest.fixture(scope="module")
def llm_client():
Expand All @@ -99,7 +113,7 @@ def llm_client():
print("Account funded with ETH and OPG")

llm = og.LLM(private_key=account.key.hex())
llm.ensure_opg_approval(opg_amount=OPG_FUND_AMOUNT)
llm.ensure_opg_approval(min_allowance=OPG_FUND_AMOUNT, approve_amount=OPG_FUND_AMOUNT)
print("Permit2 approval complete")

# Wait for the approval to propagate on-chain
Expand Down
2 changes: 1 addition & 1 deletion src/opengradient/__init__.py
Original file line number Diff line number Diff line change
Expand Up @@ -26,7 +26,7 @@
llm = og.LLM(private_key="0x...")

# One-time OPG token approval (idempotent -- skips if allowance is sufficient)
llm.ensure_opg_approval(opg_amount=5)
llm.ensure_opg_approval(min_allowance=5)

# Chat with an LLM (TEE-verified)
response = asyncio.run(llm.chat(
Expand Down
2 changes: 1 addition & 1 deletion src/opengradient/client/__init__.py
Original file line number Diff line number Diff line change
Expand Up @@ -17,7 +17,7 @@

# LLM inference (Base Sepolia OPG tokens)
llm = og.LLM(private_key="0x...")
llm.ensure_opg_approval(opg_amount=5)
llm.ensure_opg_approval(min_allowance=5)
result = await llm.chat(model=og.TEE_LLM.CLAUDE_HAIKU_4_5, messages=[...])

# On-chain model inference (OpenGradient testnet gas tokens)
Expand Down
41 changes: 26 additions & 15 deletions src/opengradient/client/llm.py
Original file line number Diff line number Diff line change
Expand Up @@ -59,8 +59,6 @@ class LLM:

Before making LLM requests, ensure your wallet has approved sufficient
OPG tokens for Permit2 spending by calling ``ensure_opg_approval``.
This only sends an on-chain transaction when the current allowance is
below the requested amount.

Usage:
# Via on-chain registry (default)
Expand All @@ -69,8 +67,8 @@ class LLM:
# Via hardcoded URL (development / self-hosted)
llm = og.LLM.from_url(private_key="0x...", llm_server_url="https://1.2.3.4")

# One-time approval (idempotent — skips if allowance is already sufficient)
llm.ensure_opg_approval(opg_amount=5)
# Ensure sufficient OPG allowance (only sends tx when below threshold)
llm.ensure_opg_approval(min_allowance=5)

result = await llm.chat(model=TEE_LLM.CLAUDE_HAIKU_4_5, messages=[...])
result = await llm.completion(model=TEE_LLM.CLAUDE_HAIKU_4_5, prompt="Hello")
Expand Down Expand Up @@ -184,29 +182,42 @@ async def _call_with_tee_retry(

# ── Public API ──────────────────────────────────────────────────────

def ensure_opg_approval(self, opg_amount: float) -> Permit2ApprovalResult:
"""Ensure the Permit2 allowance for OPG is at least ``opg_amount``.
def ensure_opg_approval(
self,
min_allowance: float,
approve_amount: Optional[float] = None,
) -> Permit2ApprovalResult:
"""Ensure the Permit2 allowance stays above a minimum threshold.

Only sends a transaction when the current allowance drops below
``min_allowance``. When approval is needed, approves ``approve_amount``
(defaults to ``2 * min_allowance``) to create a buffer that survives
multiple service restarts without re-approving.

Best for backend servers that call this on startup::

Checks the current Permit2 allowance for the wallet. If the allowance
is already >= the requested amount, returns immediately without sending
a transaction. Otherwise, sends an ERC-20 approve transaction.
llm.ensure_opg_approval(min_allowance=5.0, approve_amount=100.0)

Args:
opg_amount: Minimum number of OPG tokens required (e.g. ``0.1``
for 0.1 OPG). Must be at least 0.1 OPG.
min_allowance: The minimum acceptable allowance in OPG. Must be
at least 0.1 OPG.
approve_amount: The amount of OPG to approve when a transaction
is needed. Defaults to ``2 * min_allowance``. Must be
>= ``min_allowance``.

Returns:
Permit2ApprovalResult: Contains ``allowance_before``,
``allowance_after``, and ``tx_hash`` (None when no approval
was needed).

Raises:
ValueError: If the OPG amount is less than 0.1.
ValueError: If ``min_allowance`` is less than 0.1 or
``approve_amount`` is less than ``min_allowance``.
RuntimeError: If the approval transaction fails.
"""
if opg_amount < 0.1:
raise ValueError("OPG amount must be at least 0.1.")
return ensure_opg_approval(self._wallet_account, opg_amount)
if min_allowance < 0.1:
raise ValueError("min_allowance must be at least 0.1.")
return ensure_opg_approval(self._wallet_account, min_allowance, approve_amount)

async def completion(
self,
Expand Down
Loading
Loading