oryne 0.1.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.
oryne-0.1.0/.gitignore ADDED
@@ -0,0 +1,26 @@
1
+ node_modules
2
+ dist
3
+ *.local
4
+ .DS_Store
5
+ .env
6
+ # Local API database and downloaded contract builds
7
+ services/api/data/
8
+ .oryne/
9
+
10
+ # The admin site lives in its own repository (git init inside apps/admin).
11
+ apps/admin/
12
+
13
+ # SDK builds for npm (npm run packages)
14
+ packages/*/npm/
15
+
16
+ # TypeScript incremental build caches
17
+ *.tsbuildinfo
18
+
19
+ # Soroban test snapshots, written by every cargo test run
20
+ test_snapshots/
21
+
22
+ # Python SDK
23
+ .venv/
24
+ __pycache__/
25
+ .pytest_cache/
26
+ *.egg-info/
oryne-0.1.0/LICENSE ADDED
@@ -0,0 +1,21 @@
1
+ MIT License
2
+
3
+ Copyright (c) 2026 Oryne
4
+
5
+ Permission is hereby granted, free of charge, to any person obtaining a copy
6
+ of this software and associated documentation files (the "Software"), to deal
7
+ in the Software without restriction, including without limitation the rights
8
+ to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
9
+ copies of the Software, and to permit persons to whom the Software is
10
+ furnished to do so, subject to the following conditions:
11
+
12
+ The above copyright notice and this permission notice shall be included in all
13
+ copies or substantial portions of the Software.
14
+
15
+ THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
16
+ IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
17
+ FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
18
+ AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
19
+ LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
20
+ OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
21
+ SOFTWARE.
oryne-0.1.0/PKG-INFO ADDED
@@ -0,0 +1,146 @@
1
+ Metadata-Version: 2.5
2
+ Name: oryne
3
+ Version: 0.1.0
4
+ Summary: Give an AI agent a wallet it can spend from, with limits it cannot break: x402 payments in USDC on Stellar.
5
+ Project-URL: Homepage, https://oryne.org
6
+ Project-URL: Source, https://github.com/m2moryne/Oryne/tree/master/packages/python
7
+ Project-URL: Issues, https://github.com/m2moryne/Oryne/issues
8
+ Author: Oryne
9
+ License-Expression: MIT
10
+ License-File: LICENSE
11
+ Keywords: agent-payments,ai-agents,soroban,stellar,usdc,x402
12
+ Classifier: Development Status :: 4 - Beta
13
+ Classifier: Intended Audience :: Developers
14
+ Classifier: Programming Language :: Python :: 3
15
+ Classifier: Topic :: Office/Business :: Financial
16
+ Classifier: Topic :: Scientific/Engineering :: Artificial Intelligence
17
+ Requires-Python: >=3.10
18
+ Requires-Dist: httpx<1,>=0.27
19
+ Requires-Dist: stellar-sdk<18,>=17.0.1
20
+ Provides-Extra: dev
21
+ Requires-Dist: pytest>=8; extra == 'dev'
22
+ Description-Content-Type: text/markdown
23
+
24
+ # oryne (Python)
25
+
26
+ Give an AI agent a wallet it can spend from, with limits it cannot break.
27
+
28
+ `agent.fetch()` makes an HTTP request. When a service answers `402 Payment Required` ([x402](https://x402.org)), it pays in USDC from the agent's Oryne wallet on Stellar and retries. The wallet's smart contract checks every payment against the mandate the owner set: per-payment limit, budget, allowed payees, expiry. Nothing the agent does can get past it.
29
+
30
+ The same SDK as [`@oryne/agent`](https://www.npmjs.com/package/@oryne/agent) for TypeScript, for Python agents: LangChain, CrewAI, LlamaIndex, or a plain script.
31
+
32
+ ```sh
33
+ pip install oryne
34
+ ```
35
+
36
+ Needs Python 3.10 or later.
37
+
38
+ ## Use
39
+
40
+ Connect an agent in the [Oryne dashboard](https://oryne.org/app). You get its secret key once.
41
+
42
+ ```python
43
+ import os
44
+ from oryne import OryneAgent
45
+
46
+ agent = OryneAgent(os.environ["ORYNE_AGENT_SECRET"])
47
+
48
+ response = agent.fetch("https://api.example.com/search", params={"q": "stellar"})
49
+ print(response.json())
50
+ ```
51
+
52
+ `fetch` takes the same keyword arguments as `httpx.Client.request` (`params`, `json`, `headers`, `content`, …) and returns an `httpx.Response`.
53
+
54
+ When the wallet refuses a payment, `fetch` raises `PaymentRefusedError` with the reason (`OverPerTxLimit`, `OverBudget`, `PayeeNotAllowed`, …). The agent can then ask the owner:
55
+
56
+ ```python
57
+ from oryne import PaymentRefusedError
58
+
59
+ try:
60
+ agent.fetch(url)
61
+ except PaymentRefusedError as refused:
62
+ if refused.source != "wallet":
63
+ raise # refused before signing, see below
64
+ approval = agent.request_approval(
65
+ pay_to=refused.requirements["payTo"],
66
+ amount="30",
67
+ description="Research report for the weekly summary",
68
+ resource=refused.requirements["resource"],
69
+ reason=refused.reason,
70
+ )
71
+ agent.wait_for_approval(approval["id"]) # the owner approves in the dashboard
72
+ agent.fetch(url) # now within what was approved
73
+ ```
74
+
75
+ ## Check a payment before signing
76
+
77
+ The wallet contract is the hard limit. Before signing, the SDK can also check whether a payment request makes sense at all. That catches a request that is inside the mandate but still wrong: a service that suddenly asks for more, or a hijacked endpoint that asks to be paid at someone else's address.
78
+
79
+ ```python
80
+ agent = OryneAgent(
81
+ os.environ["ORYNE_AGENT_SECRET"],
82
+ payees="directory", # only services in the Oryne directory, at the address they're listed with
83
+ # Your own rule, last before signing: True to pay, or a reason to refuse.
84
+ before_pay=lambda payment: payment.url.startswith("https://api.example.com/") or "only example.com",
85
+ )
86
+
87
+ # This search should cost at most 0.10; a 402 asking more is refused unpaid.
88
+ agent.fetch("https://api.example.com/search?q=stellar", max_price="0.10")
89
+ ```
90
+
91
+ A refusal here raises `PaymentRefusedError` with `source == "client"`. Nothing was signed and no money moved. Refusals from the wallet contract have `source == "wallet"`.
92
+
93
+ The directory doesn't verify who owns a listed URL yet. `payees="directory"` stops payments to unlisted hosts and to addresses a host isn't listed with, but for a guarantee, set the allowed payees in the agent's on-chain mandate in the dashboard.
94
+
95
+ ## Options
96
+
97
+ | Option | |
98
+ |---|---|
99
+ | `secret` | The agent's secret key (`S…`). |
100
+ | `api_url` | Oryne API. Defaults to `ORYNE_API_URL`, then `https://oryne.onrender.com` (Stellar testnet). |
101
+ | `max_per_request` | Most this client pays for one request, whatever a service asks, e.g. `"1"`. |
102
+ | `payees` | Who it may pay: `"any"` (default), `"directory"`, or a list of Stellar addresses. |
103
+ | `before_pay` | `(PaymentCheck) -> True \| False \| reason`. Last say before signing. |
104
+ | `on_payment` | Called with a `PaymentEvent`: `paying`, then `paid` or `refused`. |
105
+ | `http` | Your own `httpx.Client`, e.g. with other timeouts or a proxy. |
106
+
107
+ Per request: `agent.fetch(url, max_price="0.10")`.
108
+
109
+ Amounts are strings (`"0.05"`), never floats, so money doesn't depend on float rounding.
110
+
111
+ Other methods: `agent.me()` (the agent, its mandate and what's left of its budget), `agent.approval(id)`. `OryneAgent` is a context manager that closes its HTTP client.
112
+
113
+ ## As a tool for your agent framework
114
+
115
+ `fetch` is a plain function, so wrapping it as a tool is a few lines. With LangChain:
116
+
117
+ ```python
118
+ from langchain_core.tools import tool
119
+
120
+ @tool
121
+ def fetch_paid(url: str, max_price: str = "1") -> str:
122
+ """Fetch a URL. If it costs money, pay up to max_price USDC from the agent's wallet."""
123
+ try:
124
+ return agent.fetch(url, max_price=max_price).text[:20_000]
125
+ except PaymentRefusedError as refused:
126
+ return f"Not paid: {refused.reason}"
127
+ ```
128
+
129
+ ## Develop
130
+
131
+ ```sh
132
+ python -m venv .venv && .venv/bin/pip install -e ".[dev]"
133
+ .venv/bin/pytest # unit tests, no network
134
+ ORYNE_TESTNET=1 .venv/bin/pytest -m testnet # end to end on Stellar testnet
135
+ ```
136
+
137
+ The unit tests compare signatures and encodings with fixtures produced by the TypeScript SDK, so the two can't drift apart.
138
+
139
+ ## Links
140
+
141
+ - [Oryne](https://oryne.org)
142
+ - [@oryne/agent](https://www.npmjs.com/package/@oryne/agent): the TypeScript SDK
143
+ - [@oryne/mcp](https://www.npmjs.com/package/@oryne/mcp): the same as tools for MCP clients like Claude
144
+ - [Source](https://github.com/m2moryne/Oryne/tree/master/packages/python)
145
+
146
+ MIT licensed.
oryne-0.1.0/README.md ADDED
@@ -0,0 +1,123 @@
1
+ # oryne (Python)
2
+
3
+ Give an AI agent a wallet it can spend from, with limits it cannot break.
4
+
5
+ `agent.fetch()` makes an HTTP request. When a service answers `402 Payment Required` ([x402](https://x402.org)), it pays in USDC from the agent's Oryne wallet on Stellar and retries. The wallet's smart contract checks every payment against the mandate the owner set: per-payment limit, budget, allowed payees, expiry. Nothing the agent does can get past it.
6
+
7
+ The same SDK as [`@oryne/agent`](https://www.npmjs.com/package/@oryne/agent) for TypeScript, for Python agents: LangChain, CrewAI, LlamaIndex, or a plain script.
8
+
9
+ ```sh
10
+ pip install oryne
11
+ ```
12
+
13
+ Needs Python 3.10 or later.
14
+
15
+ ## Use
16
+
17
+ Connect an agent in the [Oryne dashboard](https://oryne.org/app). You get its secret key once.
18
+
19
+ ```python
20
+ import os
21
+ from oryne import OryneAgent
22
+
23
+ agent = OryneAgent(os.environ["ORYNE_AGENT_SECRET"])
24
+
25
+ response = agent.fetch("https://api.example.com/search", params={"q": "stellar"})
26
+ print(response.json())
27
+ ```
28
+
29
+ `fetch` takes the same keyword arguments as `httpx.Client.request` (`params`, `json`, `headers`, `content`, …) and returns an `httpx.Response`.
30
+
31
+ When the wallet refuses a payment, `fetch` raises `PaymentRefusedError` with the reason (`OverPerTxLimit`, `OverBudget`, `PayeeNotAllowed`, …). The agent can then ask the owner:
32
+
33
+ ```python
34
+ from oryne import PaymentRefusedError
35
+
36
+ try:
37
+ agent.fetch(url)
38
+ except PaymentRefusedError as refused:
39
+ if refused.source != "wallet":
40
+ raise # refused before signing, see below
41
+ approval = agent.request_approval(
42
+ pay_to=refused.requirements["payTo"],
43
+ amount="30",
44
+ description="Research report for the weekly summary",
45
+ resource=refused.requirements["resource"],
46
+ reason=refused.reason,
47
+ )
48
+ agent.wait_for_approval(approval["id"]) # the owner approves in the dashboard
49
+ agent.fetch(url) # now within what was approved
50
+ ```
51
+
52
+ ## Check a payment before signing
53
+
54
+ The wallet contract is the hard limit. Before signing, the SDK can also check whether a payment request makes sense at all. That catches a request that is inside the mandate but still wrong: a service that suddenly asks for more, or a hijacked endpoint that asks to be paid at someone else's address.
55
+
56
+ ```python
57
+ agent = OryneAgent(
58
+ os.environ["ORYNE_AGENT_SECRET"],
59
+ payees="directory", # only services in the Oryne directory, at the address they're listed with
60
+ # Your own rule, last before signing: True to pay, or a reason to refuse.
61
+ before_pay=lambda payment: payment.url.startswith("https://api.example.com/") or "only example.com",
62
+ )
63
+
64
+ # This search should cost at most 0.10; a 402 asking more is refused unpaid.
65
+ agent.fetch("https://api.example.com/search?q=stellar", max_price="0.10")
66
+ ```
67
+
68
+ A refusal here raises `PaymentRefusedError` with `source == "client"`. Nothing was signed and no money moved. Refusals from the wallet contract have `source == "wallet"`.
69
+
70
+ The directory doesn't verify who owns a listed URL yet. `payees="directory"` stops payments to unlisted hosts and to addresses a host isn't listed with, but for a guarantee, set the allowed payees in the agent's on-chain mandate in the dashboard.
71
+
72
+ ## Options
73
+
74
+ | Option | |
75
+ |---|---|
76
+ | `secret` | The agent's secret key (`S…`). |
77
+ | `api_url` | Oryne API. Defaults to `ORYNE_API_URL`, then `https://oryne.onrender.com` (Stellar testnet). |
78
+ | `max_per_request` | Most this client pays for one request, whatever a service asks, e.g. `"1"`. |
79
+ | `payees` | Who it may pay: `"any"` (default), `"directory"`, or a list of Stellar addresses. |
80
+ | `before_pay` | `(PaymentCheck) -> True \| False \| reason`. Last say before signing. |
81
+ | `on_payment` | Called with a `PaymentEvent`: `paying`, then `paid` or `refused`. |
82
+ | `http` | Your own `httpx.Client`, e.g. with other timeouts or a proxy. |
83
+
84
+ Per request: `agent.fetch(url, max_price="0.10")`.
85
+
86
+ Amounts are strings (`"0.05"`), never floats, so money doesn't depend on float rounding.
87
+
88
+ Other methods: `agent.me()` (the agent, its mandate and what's left of its budget), `agent.approval(id)`. `OryneAgent` is a context manager that closes its HTTP client.
89
+
90
+ ## As a tool for your agent framework
91
+
92
+ `fetch` is a plain function, so wrapping it as a tool is a few lines. With LangChain:
93
+
94
+ ```python
95
+ from langchain_core.tools import tool
96
+
97
+ @tool
98
+ def fetch_paid(url: str, max_price: str = "1") -> str:
99
+ """Fetch a URL. If it costs money, pay up to max_price USDC from the agent's wallet."""
100
+ try:
101
+ return agent.fetch(url, max_price=max_price).text[:20_000]
102
+ except PaymentRefusedError as refused:
103
+ return f"Not paid: {refused.reason}"
104
+ ```
105
+
106
+ ## Develop
107
+
108
+ ```sh
109
+ python -m venv .venv && .venv/bin/pip install -e ".[dev]"
110
+ .venv/bin/pytest # unit tests, no network
111
+ ORYNE_TESTNET=1 .venv/bin/pytest -m testnet # end to end on Stellar testnet
112
+ ```
113
+
114
+ The unit tests compare signatures and encodings with fixtures produced by the TypeScript SDK, so the two can't drift apart.
115
+
116
+ ## Links
117
+
118
+ - [Oryne](https://oryne.org)
119
+ - [@oryne/agent](https://www.npmjs.com/package/@oryne/agent): the TypeScript SDK
120
+ - [@oryne/mcp](https://www.npmjs.com/package/@oryne/mcp): the same as tools for MCP clients like Claude
121
+ - [Source](https://github.com/m2moryne/Oryne/tree/master/packages/python)
122
+
123
+ MIT licensed.
@@ -0,0 +1,40 @@
1
+ [build-system]
2
+ requires = ["hatchling>=1.25"]
3
+ build-backend = "hatchling.build"
4
+
5
+ [project]
6
+ name = "oryne"
7
+ version = "0.1.0"
8
+ description = "Give an AI agent a wallet it can spend from, with limits it cannot break: x402 payments in USDC on Stellar."
9
+ readme = "README.md"
10
+ license = "MIT"
11
+ license-files = ["LICENSE"]
12
+ requires-python = ">=3.10"
13
+ authors = [{ name = "Oryne" }]
14
+ keywords = ["stellar", "x402", "ai-agents", "agent-payments", "usdc", "soroban"]
15
+ classifiers = [
16
+ "Development Status :: 4 - Beta",
17
+ "Intended Audience :: Developers",
18
+ "Programming Language :: Python :: 3",
19
+ "Topic :: Office/Business :: Financial",
20
+ "Topic :: Scientific/Engineering :: Artificial Intelligence",
21
+ ]
22
+ dependencies = [
23
+ "stellar-sdk>=17.0.1,<18",
24
+ "httpx>=0.27,<1",
25
+ ]
26
+
27
+ [project.optional-dependencies]
28
+ dev = ["pytest>=8"]
29
+
30
+ [project.urls]
31
+ Homepage = "https://oryne.org"
32
+ Source = "https://github.com/m2moryne/Oryne/tree/master/packages/python"
33
+ Issues = "https://github.com/m2moryne/Oryne/issues"
34
+
35
+ [tool.hatch.build.targets.wheel]
36
+ packages = ["src/oryne"]
37
+
38
+ [tool.pytest.ini_options]
39
+ testpaths = ["tests"]
40
+ markers = ["testnet: runs against Stellar testnet and the hosted Oryne API (set ORYNE_TESTNET=1)"]
@@ -0,0 +1,55 @@
1
+ """Oryne: give an AI agent a wallet it can spend from, with limits it cannot break.
2
+
3
+ >>> from oryne import OryneAgent, PaymentRefusedError
4
+ >>> agent = OryneAgent(secret=os.environ["ORYNE_AGENT_SECRET"])
5
+ >>> response = agent.fetch("https://api.example.com/search?q=stellar")
6
+ """
7
+
8
+ from .agent import (
9
+ DEFAULT_API_URL,
10
+ Approval,
11
+ OryneAgent,
12
+ OryneAPIError,
13
+ PaymentEvent,
14
+ PaymentRefusedError,
15
+ RefusalSource,
16
+ )
17
+ from .guard import BeforePay, PayeeRule, PaymentCheck, parse_payee_rule, payee_refusal
18
+ from .payment import (
19
+ PAYMENT_HEADER,
20
+ PAYMENT_RESPONSE_HEADER,
21
+ PaymentRequirements,
22
+ SettleResponse,
23
+ StellarError,
24
+ agent_signature_scval,
25
+ contract_error_name,
26
+ sign_payment,
27
+ )
28
+ from .units import from_base_units, to_base_units
29
+
30
+ __version__ = "0.1.0"
31
+
32
+ __all__ = [
33
+ "DEFAULT_API_URL",
34
+ "PAYMENT_HEADER",
35
+ "PAYMENT_RESPONSE_HEADER",
36
+ "Approval",
37
+ "BeforePay",
38
+ "OryneAPIError",
39
+ "OryneAgent",
40
+ "PayeeRule",
41
+ "PaymentCheck",
42
+ "PaymentEvent",
43
+ "PaymentRefusedError",
44
+ "PaymentRequirements",
45
+ "RefusalSource",
46
+ "SettleResponse",
47
+ "StellarError",
48
+ "agent_signature_scval",
49
+ "contract_error_name",
50
+ "from_base_units",
51
+ "parse_payee_rule",
52
+ "payee_refusal",
53
+ "sign_payment",
54
+ "to_base_units",
55
+ ]