Skip to content

Commit 843c02f

Browse files
adambaloghbalogh.adam@icloud.comCopilot
authored
Make $OPG permits more explicit (#161)
* permit logic * idempotent function * rm excample * readme * Update src/opengradient/client/opg_token.py Co-authored-by: Copilot <175728472+Copilot@users.noreply.github.com> * opg test * docs * tests * example * version bump --------- Co-authored-by: balogh.adam@icloud.com <adambalogh@Adams-MacBook-Pro.local> Co-authored-by: Copilot <175728472+Copilot@users.noreply.github.com>
1 parent c0078fb commit 843c02f

15 files changed

Lines changed: 616 additions & 185 deletions

File tree

Makefile

Lines changed: 5 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -31,7 +31,7 @@ docs:
3131
# Testing
3232
# ============================================================================
3333

34-
test: utils_test client_test langchain_adapter_test
34+
test: utils_test client_test langchain_adapter_test opg_token_test
3535

3636
utils_test:
3737
pytest tests/utils_test.py -v
@@ -42,6 +42,9 @@ client_test:
4242
langchain_adapter_test:
4343
pytest tests/langchain_adapter_test.py -v
4444

45+
opg_token_test:
46+
pytest tests/opg_token_test.py -v
47+
4548
integrationtest:
4649
python integrationtest/agent/test_agent.py
4750
python integrationtest/workflow_models/test_workflow_models.py
@@ -89,5 +92,5 @@ chat-tool:
8992
--max-tokens 100 \
9093
--stream
9194

92-
.PHONY: install build publish check docs test utils_test client_test integrationtest examples \
95+
.PHONY: install build publish check docs test utils_test client_test langchain_adapter_test opg_token_test integrationtest examples \
9396
infer completion chat chat-stream chat-tool

README.md

Lines changed: 12 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -291,6 +291,18 @@ result = client.llm.chat(
291291
)
292292
```
293293

294+
### OPG Token Approval
295+
296+
LLM inference payments use OPG tokens via the [Permit2](https://github.com/Uniswap/permit2) protocol. Before making requests, ensure your wallet has approved sufficient OPG for spending:
297+
298+
```python
299+
# Checks current Permit2 allowance — only sends an on-chain transaction
300+
# if the allowance is below the requested amount.
301+
client.llm.ensure_opg_approval(opg_amount=5)
302+
```
303+
304+
This is idempotent: if your wallet already has an allowance >= the requested amount, no transaction is sent.
305+
294306
## Examples
295307

296308
Additional code examples are available in the [examples](./examples) directory.

docs/opengradient/client/client.md

Lines changed: 16 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -15,22 +15,33 @@ Main Client class that unifies all OpenGradient service namespaces.
1515
Main OpenGradient SDK client.
1616

1717
Provides unified access to all OpenGradient services including LLM inference,
18-
on-chain model inference, and the Model Hub. Handles authentication via
19-
blockchain private key and optional Model Hub credentials.
18+
on-chain model inference, and the Model Hub.
19+
20+
The client operates across two chains:
21+
22+
- **LLM inference** (``client.llm``) settles via x402 on **Base Sepolia**
23+
using OPG tokens (funded by ``private_key``).
24+
- **Alpha Testnet** (``client.alpha``) runs on the **OpenGradient network**
25+
using testnet gas tokens (funded by ``alpha_private_key``, or ``private_key``
26+
when not provided).
2027

2128
#### Constructor
2229

2330
```python
24-
def __init__(private_keystr, email: Optional[str= None, password: Optional[str= None, twins_api_key: Optional[str= None, wallet_addressstr = None, rpc_urlstr = 'https://ogevmdevnet.opengradient.ai', api_urlstr = 'https://sdk-devnet.opengradient.ai', contract_addressstr = '0x8383C9bD7462F12Eb996DD02F78234C0421A6FaE', og_llm_server_url: Optional[str= 'https://llm.opengradient.ai', og_llm_streaming_server_url: Optional[str= 'https://llm.opengradient.ai')
31+
def __init__(private_keystr, alpha_private_key: Optional[str= None, email: Optional[str= None, password: Optional[str= None, twins_api_key: Optional[str= None, wallet_addressstr = None, rpc_urlstr = 'https://ogevmdevnet.opengradient.ai', api_urlstr = 'https://sdk-devnet.opengradient.ai', contract_addressstr = '0x8383C9bD7462F12Eb996DD02F78234C0421A6FaE', og_llm_server_url: Optional[str= 'https://llm.opengradient.ai', og_llm_streaming_server_url: Optional[str= 'https://llm.opengradient.ai')
2532
```
2633

2734
**Arguments**
2835

29-
* **`private_key`**: Private key for OpenGradient transactions.
36+
* **`private_key`**: Private key whose wallet holds **Base Sepolia OPG tokens**
37+
for x402 LLM payments.
38+
* **`alpha_private_key`**: Private key whose wallet holds **OpenGradient testnet
39+
gas tokens** for on-chain inference. Optional -- falls back to
40+
``private_key`` for backward compatibility.
3041
* **`email`**: Email for Model Hub authentication. Optional.
3142
* **`password`**: Password for Model Hub authentication. Optional.
3243
* **`twins_api_key`**: API key for digital twins chat (twin.fun). Optional.
33-
* **`rpc_url`**: RPC URL for the blockchain network.
44+
* **`rpc_url`**: RPC URL for the OpenGradient Alpha Testnet.
3445
* **`api_url`**: API URL for the OpenGradient API.
3546
* **`contract_address`**: Inference contract address.
3647
* **`og_llm_server_url`**: OpenGradient LLM server URL.

docs/opengradient/client/index.md

Lines changed: 30 additions & 7 deletions
Original file line numberDiff line numberDiff line change
@@ -12,18 +12,29 @@ OpenGradient Client -- the central entry point to all SDK services.
1212

1313
The [Client](./client) class provides unified access to four service namespaces:
1414

15-
- **[llm](./llm)** -- LLM chat and text completion with TEE-verified execution and x402 payment settlement
15+
- **[llm](./llm)** -- LLM chat and text completion with TEE-verified execution and x402 payment settlement (Base Sepolia OPG tokens)
1616
- **[model_hub](./model_hub)** -- Model repository management: create, version, and upload ML models
17-
- **[alpha](./alpha)** -- Alpha Testnet features: on-chain ONNX model inference (VANILLA, TEE, ZKML modes), workflow deployment, and scheduled ML model execution
17+
- **[alpha](./alpha)** -- Alpha Testnet features: on-chain ONNX model inference (VANILLA, TEE, ZKML modes), workflow deployment, and scheduled ML model execution (OpenGradient testnet gas tokens)
1818
- **[twins](./twins)** -- Digital twins chat via OpenGradient verifiable inference
1919

20+
## Private Keys
21+
22+
The SDK operates across two chains:
23+
24+
- **`private_key`** -- used for LLM inference (``client.llm``). Pays via x402 on **Base Sepolia** with OPG tokens.
25+
- **`alpha_private_key`** *(optional)* -- used for Alpha Testnet features (``client.alpha``). Pays gas on the **OpenGradient network** with testnet tokens. Falls back to ``private_key`` when omitted.
26+
2027
## Usage
2128

2229
```python
2330
import opengradient as og
2431

32+
# Single key for both chains (backward compatible)
2533
client = og.init(private_key="0x...")
2634

35+
# Separate keys: Base Sepolia OPG for LLM, OpenGradient testnet gas for Alpha
36+
client = og.init(private_key="0xLLM_KEY...", alpha_private_key="0xALPHA_KEY...")
37+
2738
# LLM chat (TEE-verified, streamed)
2839
for chunk in client.llm.chat(
2940
model=og.TEE_LLM.CLAUDE_3_5_HAIKU,
@@ -53,6 +64,7 @@ repo = client.model_hub.create_model("my-model", "A price prediction model")
5364
* [exceptions](./exceptions): Exception types for OpenGradient SDK errors.
5465
* [llm](./llm): LLM chat and completion via TEE-verified execution with x402 payments.
5566
* [model_hub](./model_hub): Model Hub for creating, versioning, and uploading ML models.
67+
* [opg_token](./opg_token): OPG token Permit2 approval utilities for x402 payments.
5668
* [twins](./twins): Digital twins chat via OpenGradient verifiable inference.
5769

5870
## Classes
@@ -62,22 +74,33 @@ repo = client.model_hub.create_model("my-model", "A price prediction model")
6274
Main OpenGradient SDK client.
6375

6476
Provides unified access to all OpenGradient services including LLM inference,
65-
on-chain model inference, and the Model Hub. Handles authentication via
66-
blockchain private key and optional Model Hub credentials.
77+
on-chain model inference, and the Model Hub.
78+
79+
The client operates across two chains:
80+
81+
- **LLM inference** (``client.llm``) settles via x402 on **Base Sepolia**
82+
using OPG tokens (funded by ``private_key``).
83+
- **Alpha Testnet** (``client.alpha``) runs on the **OpenGradient network**
84+
using testnet gas tokens (funded by ``alpha_private_key``, or ``private_key``
85+
when not provided).
6786

6887
#### Constructor
6988

7089
```python
71-
def __init__(private_keystr, email: Optional[str= None, password: Optional[str= None, twins_api_key: Optional[str= None, wallet_addressstr = None, rpc_urlstr = 'https://ogevmdevnet.opengradient.ai', api_urlstr = 'https://sdk-devnet.opengradient.ai', contract_addressstr = '0x8383C9bD7462F12Eb996DD02F78234C0421A6FaE', og_llm_server_url: Optional[str= 'https://llm.opengradient.ai', og_llm_streaming_server_url: Optional[str= 'https://llm.opengradient.ai')
90+
def __init__(private_keystr, alpha_private_key: Optional[str= None, email: Optional[str= None, password: Optional[str= None, twins_api_key: Optional[str= None, wallet_addressstr = None, rpc_urlstr = 'https://ogevmdevnet.opengradient.ai', api_urlstr = 'https://sdk-devnet.opengradient.ai', contract_addressstr = '0x8383C9bD7462F12Eb996DD02F78234C0421A6FaE', og_llm_server_url: Optional[str= 'https://llm.opengradient.ai', og_llm_streaming_server_url: Optional[str= 'https://llm.opengradient.ai')
7291
```
7392

7493
**Arguments**
7594

76-
* **`private_key`**: Private key for OpenGradient transactions.
95+
* **`private_key`**: Private key whose wallet holds **Base Sepolia OPG tokens**
96+
for x402 LLM payments.
97+
* **`alpha_private_key`**: Private key whose wallet holds **OpenGradient testnet
98+
gas tokens** for on-chain inference. Optional -- falls back to
99+
``private_key`` for backward compatibility.
77100
* **`email`**: Email for Model Hub authentication. Optional.
78101
* **`password`**: Password for Model Hub authentication. Optional.
79102
* **`twins_api_key`**: API key for digital twins chat (twin.fun). Optional.
80-
* **`rpc_url`**: RPC URL for the blockchain network.
103+
* **`rpc_url`**: RPC URL for the OpenGradient Alpha Testnet.
81104
* **`api_url`**: API URL for the OpenGradient API.
82105
* **`contract_address`**: Inference contract address.
83106
* **`og_llm_server_url`**: OpenGradient LLM server URL.

docs/opengradient/client/llm.md

Lines changed: 35 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -18,6 +18,11 @@ Provides access to large language model completions and chat via TEE
1818
(Trusted Execution Environment) with x402 payment protocol support.
1919
Supports both streaming and non-streaming responses.
2020

21+
Before making LLM requests, ensure your wallet has approved sufficient
22+
OPG tokens for Permit2 spending by calling ``ensure_opg_approval``.
23+
This only sends an on-chain transaction when the current allowance is
24+
below the requested amount.
25+
2126
#### Constructor
2227

2328
```python
@@ -31,7 +36,7 @@ def __init__(wallet_account: `LocalAccount`, og_llm_server_url: str, og_llm_st
3136
#### `chat()`
3237

3338
```python
34-
def chat(self, model`TEE_LLM`, messages: List[Dict], max_tokensint = 100, stop_sequence: Optional[List[str]] = None, temperaturefloat = 0.0, tools: Optional[List[Dict]] = [], tool_choice: Optional[str= None, x402_settlement_mode: Optional[`x402SettlementMode`= x402SettlementMode.SETTLE_BATCH, streambool = False) ‑> Union[`TextGenerationOutput`, `TextGenerationStream`]
39+
def chat(self, model`TEE_LLM`, messages: List[Dict], max_tokensint = 100, stop_sequence: Optional[List[str]] = None, temperaturefloat = 0.0, tools: Optional[List[Dict]] = None, tool_choice: Optional[str= None, x402_settlement_mode: Optional[`x402SettlementMode`= x402SettlementMode.SETTLE_BATCH, streambool = False) ‑> Union[`TextGenerationOutput`, `TextGenerationStream`]
3540
```
3641
Perform inference on an LLM model using chat via TEE.
3742

@@ -92,4 +97,32 @@ TextGenerationOutput: Generated text results including:
9297

9398
**Raises**
9499

95-
* **`OpenGradientError`**: If the inference fails.
100+
* **`OpenGradientError`**: If the inference fails.
101+
102+
---
103+
104+
#### `ensure_opg_approval()`
105+
106+
```python
107+
def ensure_opg_approval(self, opg_amountfloat) ‑> `Permit2ApprovalResult`
108+
```
109+
Ensure the Permit2 allowance for OPG is at least ``opg_amount``.
110+
111+
Checks the current Permit2 allowance for the wallet. If the allowance
112+
is already >= the requested amount, returns immediately without sending
113+
a transaction. Otherwise, sends an ERC-20 approve transaction.
114+
115+
**Arguments**
116+
117+
* **`opg_amount`**: Minimum number of OPG tokens required (e.g. ``5.0``
118+
for 5 OPG). Converted to base units (18 decimals) internally.
119+
120+
**Returns**
121+
122+
Permit2ApprovalResult: Contains ``allowance_before``,
123+
``allowance_after``, and ``tx_hash`` (None when no approval
124+
was needed).
125+
126+
**Raises**
127+
128+
* **`OpenGradientError`**: If the approval transaction fails.
Lines changed: 64 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,64 @@
1+
---
2+
outline: [2,3]
3+
---
4+
5+
[opengradient](../index) / [client](./index) / opg_token
6+
7+
# Package opengradient.client.opg_token
8+
9+
OPG token Permit2 approval utilities for x402 payments.
10+
11+
## Functions
12+
13+
---
14+
15+
### `ensure_opg_approval()`
16+
17+
```python
18+
def ensure_opg_approval(wallet_account`LocalAccount`, opg_amountfloat) ‑> `Permit2ApprovalResult`
19+
```
20+
Ensure the Permit2 allowance for OPG is at least ``opg_amount``.
21+
22+
Checks the current Permit2 allowance for the wallet. If the allowance
23+
is already >= the requested amount, returns immediately without sending
24+
a transaction. Otherwise, sends an ERC-20 approve transaction.
25+
26+
**Arguments**
27+
28+
* **`wallet_account`**: The wallet account to check and approve from.
29+
* **`opg_amount`**: Minimum number of OPG tokens required (e.g. ``5.0``
30+
for 5 OPG). Converted to base units (18 decimals) internally.
31+
32+
**Returns**
33+
34+
Permit2ApprovalResult: Contains ``allowance_before``,
35+
``allowance_after``, and ``tx_hash`` (None when no approval
36+
was needed).
37+
38+
**Raises**
39+
40+
* **`OpenGradientError`**: If the approval transaction fails.
41+
42+
## Classes
43+
44+
### `Permit2ApprovalResult`
45+
46+
Result of a Permit2 allowance check / approval.
47+
48+
**Attributes**
49+
50+
* **`allowance_before`**: The Permit2 allowance before the method ran.
51+
* **`allowance_after`**: The Permit2 allowance after the method ran.
52+
* **`tx_hash`**: Transaction hash of the approval, or None if no transaction was needed.
53+
54+
#### Constructor
55+
56+
```python
57+
def __init__(allowance_beforeint, allowance_afterint, tx_hash: Optional[str= None)
58+
```
59+
60+
#### Variables
61+
62+
* static `allowance_after` : int
63+
* static `allowance_before` : int
64+
* static `tx_hash` : Optional[str]

docs/opengradient/index.md

Lines changed: 37 additions & 10 deletions
Original file line numberDiff line numberDiff line change
@@ -6,7 +6,7 @@ opengradient
66

77
# Package opengradient
88

9-
**Version: 0.7.0**
9+
**Version: 0.7.1**
1010

1111
OpenGradient Python SDK for decentralized AI inference with end-to-end verification.
1212

@@ -55,12 +55,24 @@ result = client.alpha.infer(
5555
print(result.model_output)
5656
```
5757

58+
## Private Keys
59+
60+
The SDK operates across two chains. You can use a single key for both, or provide separate keys:
61+
62+
- **``private_key``** -- pays for LLM inference via x402 on **Base Sepolia** (requires OPG tokens)
63+
- **``alpha_private_key``** *(optional)* -- pays gas for Alpha Testnet on-chain inference on the **OpenGradient network** (requires testnet gas tokens). Falls back to ``private_key`` when omitted.
64+
65+
```python
66+
# Separate keys for each chain
67+
client = og.init(private_key="0xBASE_KEY...", alpha_private_key="0xALPHA_KEY...")
68+
```
69+
5870
## Client Namespaces
5971

6072
The [Client](./client/index) object exposes four namespaces:
6173

62-
- **[llm](./client/llm)** -- Verifiable LLM chat and completion via TEE-verified execution with x402 payments
63-
- **[alpha](./client/alpha)** -- On-chain ONNX model inference, workflow deployment, and scheduled ML model execution (only available on the Alpha Testnet)
74+
- **[llm](./client/llm)** -- Verifiable LLM chat and completion via TEE-verified execution with x402 payments (Base Sepolia OPG tokens)
75+
- **[alpha](./client/alpha)** -- On-chain ONNX model inference, workflow deployment, and scheduled ML model execution (OpenGradient testnet gas tokens)
6476
- **[model_hub](./client/model_hub)** -- Model repository management
6577
- **[twins](./client/twins)** -- Digital twins chat via OpenGradient verifiable inference (requires twins API key)
6678

@@ -96,7 +108,7 @@ The SDK includes adapters for popular AI frameworks -- see the `agents` submodul
96108
### `init()`
97109

98110
```python
99-
def init(private_keystr, email: Optional[str= None, password: Optional[str= None, **kwargs) ‑> `Client`
111+
def init(private_keystr, alpha_private_key: Optional[str= None, email: Optional[str= None, password: Optional[str= None, **kwargs) ‑> `Client`
100112
```
101113
Initialize the global OpenGradient client.
102114

@@ -105,7 +117,11 @@ and stores it as the global client for convenience.
105117

106118
**Arguments**
107119

108-
* **`private_key`**: Private key for OpenGradient transactions.
120+
* **`private_key`**: Private key whose wallet holds **Base Sepolia OPG tokens**
121+
for x402 LLM payments.
122+
* **`alpha_private_key`**: Private key whose wallet holds **OpenGradient testnet
123+
gas tokens** for on-chain inference. Optional -- falls back to
124+
``private_key`` for backward compatibility.
109125
* **`email`**: Email for Model Hub authentication. Optional.
110126
* **`password`**: Password for Model Hub authentication. Optional.
111127
**kwargs: Additional arguments forwarded to `Client`.
@@ -121,22 +137,33 @@ The newly created `Client` instance.
121137
Main OpenGradient SDK client.
122138

123139
Provides unified access to all OpenGradient services including LLM inference,
124-
on-chain model inference, and the Model Hub. Handles authentication via
125-
blockchain private key and optional Model Hub credentials.
140+
on-chain model inference, and the Model Hub.
141+
142+
The client operates across two chains:
143+
144+
- **LLM inference** (``client.llm``) settles via x402 on **Base Sepolia**
145+
using OPG tokens (funded by ``private_key``).
146+
- **Alpha Testnet** (``client.alpha``) runs on the **OpenGradient network**
147+
using testnet gas tokens (funded by ``alpha_private_key``, or ``private_key``
148+
when not provided).
126149

127150
#### Constructor
128151

129152
```python
130-
def __init__(private_keystr, email: Optional[str= None, password: Optional[str= None, twins_api_key: Optional[str= None, wallet_addressstr = None, rpc_urlstr = 'https://ogevmdevnet.opengradient.ai', api_urlstr = 'https://sdk-devnet.opengradient.ai', contract_addressstr = '0x8383C9bD7462F12Eb996DD02F78234C0421A6FaE', og_llm_server_url: Optional[str= 'https://llm.opengradient.ai', og_llm_streaming_server_url: Optional[str= 'https://llm.opengradient.ai')
153+
def __init__(private_keystr, alpha_private_key: Optional[str= None, email: Optional[str= None, password: Optional[str= None, twins_api_key: Optional[str= None, wallet_addressstr = None, rpc_urlstr = 'https://ogevmdevnet.opengradient.ai', api_urlstr = 'https://sdk-devnet.opengradient.ai', contract_addressstr = '0x8383C9bD7462F12Eb996DD02F78234C0421A6FaE', og_llm_server_url: Optional[str= 'https://llm.opengradient.ai', og_llm_streaming_server_url: Optional[str= 'https://llm.opengradient.ai')
131154
```
132155

133156
**Arguments**
134157

135-
* **`private_key`**: Private key for OpenGradient transactions.
158+
* **`private_key`**: Private key whose wallet holds **Base Sepolia OPG tokens**
159+
for x402 LLM payments.
160+
* **`alpha_private_key`**: Private key whose wallet holds **OpenGradient testnet
161+
gas tokens** for on-chain inference. Optional -- falls back to
162+
``private_key`` for backward compatibility.
136163
* **`email`**: Email for Model Hub authentication. Optional.
137164
* **`password`**: Password for Model Hub authentication. Optional.
138165
* **`twins_api_key`**: API key for digital twins chat (twin.fun). Optional.
139-
* **`rpc_url`**: RPC URL for the blockchain network.
166+
* **`rpc_url`**: RPC URL for the OpenGradient Alpha Testnet.
140167
* **`api_url`**: API URL for the OpenGradient API.
141168
* **`contract_address`**: Inference contract address.
142169
* **`og_llm_server_url`**: OpenGradient LLM server URL.

0 commit comments

Comments
 (0)