aifinpay-agent 0.1.0a1__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.
- aifinpay_agent-0.1.0a1/PKG-INFO +99 -0
- aifinpay_agent-0.1.0a1/README.md +73 -0
- aifinpay_agent-0.1.0a1/aifinpay/__init__.py +23 -0
- aifinpay_agent-0.1.0a1/aifinpay/client.py +232 -0
- aifinpay_agent-0.1.0a1/aifinpay/errors.py +14 -0
- aifinpay_agent-0.1.0a1/aifinpay_agent.egg-info/PKG-INFO +99 -0
- aifinpay_agent-0.1.0a1/aifinpay_agent.egg-info/SOURCES.txt +10 -0
- aifinpay_agent-0.1.0a1/aifinpay_agent.egg-info/dependency_links.txt +1 -0
- aifinpay_agent-0.1.0a1/aifinpay_agent.egg-info/requires.txt +3 -0
- aifinpay_agent-0.1.0a1/aifinpay_agent.egg-info/top_level.txt +1 -0
- aifinpay_agent-0.1.0a1/pyproject.toml +41 -0
- aifinpay_agent-0.1.0a1/setup.cfg +4 -0
|
@@ -0,0 +1,99 @@
|
|
|
1
|
+
Metadata-Version: 2.4
|
|
2
|
+
Name: aifinpay-agent
|
|
3
|
+
Version: 0.1.0a1
|
|
4
|
+
Summary: Non-custodial x402 payment client for autonomous AI agents on AiFinPay (Solana + Polygon)
|
|
5
|
+
Author-email: "CoinSecurities (SECCO)" <contact@aifinpay.company>
|
|
6
|
+
License: MIT
|
|
7
|
+
Project-URL: Homepage, https://aifinpay.company
|
|
8
|
+
Project-URL: Documentation, https://aifinpay.company/docs
|
|
9
|
+
Project-URL: Repository, https://github.com/enot3615/oracle-financial-hub-59
|
|
10
|
+
Project-URL: Issues, https://github.com/enot3615/oracle-financial-hub-59/issues
|
|
11
|
+
Keywords: x402,ai-agent,solana,polygon,payments,non-custodial,ed25519
|
|
12
|
+
Classifier: Development Status :: 3 - Alpha
|
|
13
|
+
Classifier: Intended Audience :: Developers
|
|
14
|
+
Classifier: License :: OSI Approved :: MIT License
|
|
15
|
+
Classifier: Programming Language :: Python :: 3
|
|
16
|
+
Classifier: Programming Language :: Python :: 3.9
|
|
17
|
+
Classifier: Programming Language :: Python :: 3.10
|
|
18
|
+
Classifier: Programming Language :: Python :: 3.11
|
|
19
|
+
Classifier: Programming Language :: Python :: 3.12
|
|
20
|
+
Classifier: Topic :: Software Development :: Libraries :: Python Modules
|
|
21
|
+
Requires-Python: >=3.9
|
|
22
|
+
Description-Content-Type: text/markdown
|
|
23
|
+
Requires-Dist: PyNaCl>=1.5
|
|
24
|
+
Requires-Dist: base58>=2.1
|
|
25
|
+
Requires-Dist: requests>=2.28
|
|
26
|
+
|
|
27
|
+
# aifinpay-agent (Python)
|
|
28
|
+
|
|
29
|
+
Non-custodial x402 payment client for autonomous AI agents on
|
|
30
|
+
[AiFinPay](https://aifinpay.company).
|
|
31
|
+
|
|
32
|
+
The Ed25519 keypair is generated locally and never leaves your process. The
|
|
33
|
+
SDK only sends a one-time SHA-256 + Ed25519 signature in the `x-signature`
|
|
34
|
+
header to authenticate against AiFinPay-protected endpoints.
|
|
35
|
+
|
|
36
|
+
## Install
|
|
37
|
+
|
|
38
|
+
```bash
|
|
39
|
+
pip install aifinpay-agent
|
|
40
|
+
```
|
|
41
|
+
|
|
42
|
+
## Quick start
|
|
43
|
+
|
|
44
|
+
```python
|
|
45
|
+
from aifinpay import Agent
|
|
46
|
+
|
|
47
|
+
# Generate a fresh keypair locally — never transmitted
|
|
48
|
+
agent = Agent.new()
|
|
49
|
+
print("Fund this address:", agent.address)
|
|
50
|
+
print("Save this secret:", agent.secret_b58) # store securely!
|
|
51
|
+
|
|
52
|
+
# Wait until the wallet has at least $0.01 worth on-chain
|
|
53
|
+
agent.wait_for_funding(min_usd_cents=1)
|
|
54
|
+
|
|
55
|
+
# Request an invoice for a Seat (USDC on Solana)
|
|
56
|
+
invoice = agent.reserve_seat_invoice(amount_usd=1.00, asset="USDC")
|
|
57
|
+
print("Invoice:", invoice.raw)
|
|
58
|
+
# Build + sign + submit the Solana transaction with @solana/web3.js, anchorpy,
|
|
59
|
+
# or solana-py — the invoice contains program_id, treasury_vault, mints, etc.
|
|
60
|
+
|
|
61
|
+
# Once the Seat is on-chain, any AiFinPay-gated endpoint just works:
|
|
62
|
+
resp = agent.get("https://aifinpay.company/api/stats")
|
|
63
|
+
print(resp.json())
|
|
64
|
+
```
|
|
65
|
+
|
|
66
|
+
## Loading an existing keypair
|
|
67
|
+
|
|
68
|
+
```python
|
|
69
|
+
# from solana-keygen JSON file
|
|
70
|
+
agent = Agent.from_keypair_file("~/agent-wallet.json")
|
|
71
|
+
|
|
72
|
+
# from base58 secret string
|
|
73
|
+
agent = Agent.from_secret_b58("3RvZm7Gw...")
|
|
74
|
+
```
|
|
75
|
+
|
|
76
|
+
## How x402 auth works under the hood
|
|
77
|
+
|
|
78
|
+
For every gated request the SDK:
|
|
79
|
+
|
|
80
|
+
1. `GET /nonce` → receives a one-time UUID with 60s TTL
|
|
81
|
+
2. computes `SHA-256("AiFinPay-x402:{nonce}:{pubkey}")`
|
|
82
|
+
3. signs with Ed25519, base58-encodes the signature
|
|
83
|
+
4. retries the original request with headers:
|
|
84
|
+
- `x-agent-pubkey: <base58 pubkey>`
|
|
85
|
+
- `x-nonce: <uuid>`
|
|
86
|
+
- `x-signature: <base58 sig>`
|
|
87
|
+
|
|
88
|
+
The server verifies the signature, checks the agent has a live Seat PDA
|
|
89
|
+
on-chain, and serves the resource.
|
|
90
|
+
|
|
91
|
+
## Privacy
|
|
92
|
+
|
|
93
|
+
- **The server never sees your private key.** Period.
|
|
94
|
+
- Nonces are consumed on use; replay-resistant.
|
|
95
|
+
- All transactions are public and on-chain — Solana + Polygon mainnet.
|
|
96
|
+
|
|
97
|
+
## License
|
|
98
|
+
|
|
99
|
+
MIT.
|
|
@@ -0,0 +1,73 @@
|
|
|
1
|
+
# aifinpay-agent (Python)
|
|
2
|
+
|
|
3
|
+
Non-custodial x402 payment client for autonomous AI agents on
|
|
4
|
+
[AiFinPay](https://aifinpay.company).
|
|
5
|
+
|
|
6
|
+
The Ed25519 keypair is generated locally and never leaves your process. The
|
|
7
|
+
SDK only sends a one-time SHA-256 + Ed25519 signature in the `x-signature`
|
|
8
|
+
header to authenticate against AiFinPay-protected endpoints.
|
|
9
|
+
|
|
10
|
+
## Install
|
|
11
|
+
|
|
12
|
+
```bash
|
|
13
|
+
pip install aifinpay-agent
|
|
14
|
+
```
|
|
15
|
+
|
|
16
|
+
## Quick start
|
|
17
|
+
|
|
18
|
+
```python
|
|
19
|
+
from aifinpay import Agent
|
|
20
|
+
|
|
21
|
+
# Generate a fresh keypair locally — never transmitted
|
|
22
|
+
agent = Agent.new()
|
|
23
|
+
print("Fund this address:", agent.address)
|
|
24
|
+
print("Save this secret:", agent.secret_b58) # store securely!
|
|
25
|
+
|
|
26
|
+
# Wait until the wallet has at least $0.01 worth on-chain
|
|
27
|
+
agent.wait_for_funding(min_usd_cents=1)
|
|
28
|
+
|
|
29
|
+
# Request an invoice for a Seat (USDC on Solana)
|
|
30
|
+
invoice = agent.reserve_seat_invoice(amount_usd=1.00, asset="USDC")
|
|
31
|
+
print("Invoice:", invoice.raw)
|
|
32
|
+
# Build + sign + submit the Solana transaction with @solana/web3.js, anchorpy,
|
|
33
|
+
# or solana-py — the invoice contains program_id, treasury_vault, mints, etc.
|
|
34
|
+
|
|
35
|
+
# Once the Seat is on-chain, any AiFinPay-gated endpoint just works:
|
|
36
|
+
resp = agent.get("https://aifinpay.company/api/stats")
|
|
37
|
+
print(resp.json())
|
|
38
|
+
```
|
|
39
|
+
|
|
40
|
+
## Loading an existing keypair
|
|
41
|
+
|
|
42
|
+
```python
|
|
43
|
+
# from solana-keygen JSON file
|
|
44
|
+
agent = Agent.from_keypair_file("~/agent-wallet.json")
|
|
45
|
+
|
|
46
|
+
# from base58 secret string
|
|
47
|
+
agent = Agent.from_secret_b58("3RvZm7Gw...")
|
|
48
|
+
```
|
|
49
|
+
|
|
50
|
+
## How x402 auth works under the hood
|
|
51
|
+
|
|
52
|
+
For every gated request the SDK:
|
|
53
|
+
|
|
54
|
+
1. `GET /nonce` → receives a one-time UUID with 60s TTL
|
|
55
|
+
2. computes `SHA-256("AiFinPay-x402:{nonce}:{pubkey}")`
|
|
56
|
+
3. signs with Ed25519, base58-encodes the signature
|
|
57
|
+
4. retries the original request with headers:
|
|
58
|
+
- `x-agent-pubkey: <base58 pubkey>`
|
|
59
|
+
- `x-nonce: <uuid>`
|
|
60
|
+
- `x-signature: <base58 sig>`
|
|
61
|
+
|
|
62
|
+
The server verifies the signature, checks the agent has a live Seat PDA
|
|
63
|
+
on-chain, and serves the resource.
|
|
64
|
+
|
|
65
|
+
## Privacy
|
|
66
|
+
|
|
67
|
+
- **The server never sees your private key.** Period.
|
|
68
|
+
- Nonces are consumed on use; replay-resistant.
|
|
69
|
+
- All transactions are public and on-chain — Solana + Polygon mainnet.
|
|
70
|
+
|
|
71
|
+
## License
|
|
72
|
+
|
|
73
|
+
MIT.
|
|
@@ -0,0 +1,23 @@
|
|
|
1
|
+
"""
|
|
2
|
+
AiFinPay agent SDK — non-custodial x402 payment client.
|
|
3
|
+
|
|
4
|
+
Quick start:
|
|
5
|
+
|
|
6
|
+
from aifinpay import Agent
|
|
7
|
+
|
|
8
|
+
agent = Agent.new() # generate fresh Ed25519 keypair locally
|
|
9
|
+
print("Fund:", agent.address)
|
|
10
|
+
|
|
11
|
+
agent.wait_for_funding(min_usd_cents=100)
|
|
12
|
+
invoice = agent.reserve_seat(amount_usd=1.00, asset="USDC")
|
|
13
|
+
receipt = agent.send_signed(invoice)
|
|
14
|
+
|
|
15
|
+
# any HTTP call to AiFinPay-protected endpoints auto-handles x402:
|
|
16
|
+
resp = agent.request("GET", "https://aifinpay.company/api/stats")
|
|
17
|
+
"""
|
|
18
|
+
|
|
19
|
+
from .client import Agent
|
|
20
|
+
from .errors import AiFinPayError, FundingTimeoutError, X402Error
|
|
21
|
+
|
|
22
|
+
__version__ = "0.1.0a1"
|
|
23
|
+
__all__ = ["Agent", "AiFinPayError", "FundingTimeoutError", "X402Error"]
|
|
@@ -0,0 +1,232 @@
|
|
|
1
|
+
"""Low-level Agent client. Non-custodial: keypair never leaves this process."""
|
|
2
|
+
from __future__ import annotations
|
|
3
|
+
|
|
4
|
+
import hashlib
|
|
5
|
+
import json
|
|
6
|
+
import os
|
|
7
|
+
import time
|
|
8
|
+
from dataclasses import dataclass
|
|
9
|
+
from typing import Any, Dict, Optional
|
|
10
|
+
|
|
11
|
+
import nacl.signing # PyNaCl
|
|
12
|
+
import base58
|
|
13
|
+
import requests
|
|
14
|
+
|
|
15
|
+
from .errors import (
|
|
16
|
+
AiFinPayError,
|
|
17
|
+
FundingTimeoutError,
|
|
18
|
+
SeatNotFoundError,
|
|
19
|
+
X402Error,
|
|
20
|
+
)
|
|
21
|
+
|
|
22
|
+
DEFAULT_BASE_URL = "https://aifinpay.company"
|
|
23
|
+
DEFAULT_TIMEOUT = 30 # seconds
|
|
24
|
+
|
|
25
|
+
|
|
26
|
+
@dataclass
|
|
27
|
+
class Invoice:
|
|
28
|
+
"""An on-chain payment invoice returned by the AiFinPay backend."""
|
|
29
|
+
|
|
30
|
+
amount_usd: float
|
|
31
|
+
treasury_vault: str
|
|
32
|
+
program_id: str
|
|
33
|
+
nonce: str
|
|
34
|
+
raw: Dict[str, Any]
|
|
35
|
+
|
|
36
|
+
|
|
37
|
+
class Agent:
|
|
38
|
+
"""A non-custodial AiFinPay agent.
|
|
39
|
+
|
|
40
|
+
The Ed25519 keypair lives only in this Python process. The SDK never
|
|
41
|
+
transmits the secret key; only ``signature(SHA256("AiFinPay-x402:{nonce}:{pubkey}"))``
|
|
42
|
+
is sent in the ``x-signature`` header.
|
|
43
|
+
"""
|
|
44
|
+
|
|
45
|
+
def __init__(
|
|
46
|
+
self,
|
|
47
|
+
signing_key: nacl.signing.SigningKey,
|
|
48
|
+
base_url: str = DEFAULT_BASE_URL,
|
|
49
|
+
timeout: int = DEFAULT_TIMEOUT,
|
|
50
|
+
):
|
|
51
|
+
self._sk = signing_key
|
|
52
|
+
self._vk = signing_key.verify_key
|
|
53
|
+
self.base_url = base_url.rstrip("/")
|
|
54
|
+
self.timeout = timeout
|
|
55
|
+
self._session = requests.Session()
|
|
56
|
+
self._session.headers["User-Agent"] = "aifinpay-agent-py/0.1.0"
|
|
57
|
+
|
|
58
|
+
# ── Constructors ────────────────────────────────────────────────────────
|
|
59
|
+
|
|
60
|
+
@classmethod
|
|
61
|
+
def new(cls, **kwargs) -> "Agent":
|
|
62
|
+
"""Generate a fresh Ed25519 keypair locally."""
|
|
63
|
+
return cls(nacl.signing.SigningKey.generate(), **kwargs)
|
|
64
|
+
|
|
65
|
+
@classmethod
|
|
66
|
+
def from_secret_b58(cls, secret_b58: str, **kwargs) -> "Agent":
|
|
67
|
+
"""Load from a 64-byte base58 secret (Solana style: secret + pub)."""
|
|
68
|
+
raw = base58.b58decode(secret_b58)
|
|
69
|
+
if len(raw) == 64:
|
|
70
|
+
raw = raw[:32]
|
|
71
|
+
if len(raw) != 32:
|
|
72
|
+
raise AiFinPayError(
|
|
73
|
+
f"secret must decode to 32 or 64 bytes, got {len(raw)}"
|
|
74
|
+
)
|
|
75
|
+
return cls(nacl.signing.SigningKey(raw), **kwargs)
|
|
76
|
+
|
|
77
|
+
@classmethod
|
|
78
|
+
def from_keypair_file(cls, path: str, **kwargs) -> "Agent":
|
|
79
|
+
"""Load a Solana CLI ``solana-keygen`` JSON file (array of 64 ints)."""
|
|
80
|
+
with open(path) as f:
|
|
81
|
+
arr = json.load(f)
|
|
82
|
+
if not isinstance(arr, list) or len(arr) != 64:
|
|
83
|
+
raise AiFinPayError(f"{path}: expected 64-byte array")
|
|
84
|
+
return cls(nacl.signing.SigningKey(bytes(arr[:32])), **kwargs)
|
|
85
|
+
|
|
86
|
+
# ── Public properties ──────────────────────────────────────────────────
|
|
87
|
+
|
|
88
|
+
@property
|
|
89
|
+
def address(self) -> str:
|
|
90
|
+
"""Solana base58 public key."""
|
|
91
|
+
return base58.b58encode(bytes(self._vk)).decode()
|
|
92
|
+
|
|
93
|
+
@property
|
|
94
|
+
def secret_b58(self) -> str:
|
|
95
|
+
"""Base58-encoded secret. Save this somewhere safe."""
|
|
96
|
+
return base58.b58encode(bytes(self._sk) + bytes(self._vk)).decode()
|
|
97
|
+
|
|
98
|
+
# ── Manifesto / discovery ──────────────────────────────────────────────
|
|
99
|
+
|
|
100
|
+
def manifesto(self) -> Dict[str, Any]:
|
|
101
|
+
r = self._session.get(
|
|
102
|
+
f"{self.base_url}/manifesto.json", timeout=self.timeout
|
|
103
|
+
)
|
|
104
|
+
r.raise_for_status()
|
|
105
|
+
return r.json()
|
|
106
|
+
|
|
107
|
+
def well_known(self) -> Dict[str, Any]:
|
|
108
|
+
r = self._session.get(
|
|
109
|
+
f"{self.base_url}/.well-known/x402.json", timeout=self.timeout
|
|
110
|
+
)
|
|
111
|
+
r.raise_for_status()
|
|
112
|
+
return r.json()
|
|
113
|
+
|
|
114
|
+
# ── x402 auth ──────────────────────────────────────────────────────────
|
|
115
|
+
|
|
116
|
+
def _fetch_nonce(self) -> Dict[str, Any]:
|
|
117
|
+
r = self._session.get(f"{self.base_url}/nonce", timeout=self.timeout)
|
|
118
|
+
r.raise_for_status()
|
|
119
|
+
return r.json()
|
|
120
|
+
|
|
121
|
+
def _sign_nonce(self, nonce: str) -> str:
|
|
122
|
+
msg = f"AiFinPay-x402:{nonce}:{self.address}".encode()
|
|
123
|
+
digest = hashlib.sha256(msg).digest()
|
|
124
|
+
sig = self._sk.sign(digest).signature
|
|
125
|
+
return base58.b58encode(sig).decode()
|
|
126
|
+
|
|
127
|
+
def auth_headers(self) -> Dict[str, str]:
|
|
128
|
+
"""Build a fresh x402 header set (nonces are one-time, 60s TTL)."""
|
|
129
|
+
nonce_info = self._fetch_nonce()
|
|
130
|
+
nonce = nonce_info["nonce"]
|
|
131
|
+
return {
|
|
132
|
+
"x-agent-pubkey": self.address,
|
|
133
|
+
"x-nonce": nonce,
|
|
134
|
+
"x-signature": self._sign_nonce(nonce),
|
|
135
|
+
}
|
|
136
|
+
|
|
137
|
+
# ── Funding / Seat ────────────────────────────────────────────────────
|
|
138
|
+
|
|
139
|
+
def has_seat(self) -> bool:
|
|
140
|
+
r = self._session.get(
|
|
141
|
+
f"{self.base_url}/api/seat/{self.address}", timeout=self.timeout
|
|
142
|
+
)
|
|
143
|
+
r.raise_for_status()
|
|
144
|
+
return bool(r.json().get("has_seat"))
|
|
145
|
+
|
|
146
|
+
def wait_for_funding(
|
|
147
|
+
self, min_usd_cents: int = 100, poll_seconds: int = 5, timeout: int = 600
|
|
148
|
+
) -> None:
|
|
149
|
+
"""Poll the leaderboard until this address shows up with at least
|
|
150
|
+
``min_usd_cents`` reserved on its Seat. Raises FundingTimeoutError."""
|
|
151
|
+
deadline = time.time() + timeout
|
|
152
|
+
while time.time() < deadline:
|
|
153
|
+
r = self._session.get(
|
|
154
|
+
f"{self.base_url}/api/leaderboard?merge=true",
|
|
155
|
+
timeout=self.timeout,
|
|
156
|
+
)
|
|
157
|
+
r.raise_for_status()
|
|
158
|
+
for entry in r.json().get("leaderboard", []):
|
|
159
|
+
if entry.get("pubkey") == self.address:
|
|
160
|
+
cents = int(float(entry.get("usd", 0)) * 100)
|
|
161
|
+
if cents >= min_usd_cents:
|
|
162
|
+
return
|
|
163
|
+
time.sleep(poll_seconds)
|
|
164
|
+
raise FundingTimeoutError(
|
|
165
|
+
f"address {self.address} never reached {min_usd_cents} cents on-chain"
|
|
166
|
+
)
|
|
167
|
+
|
|
168
|
+
# ── Invoices (server returns instructions, agent executes on-chain) ────
|
|
169
|
+
|
|
170
|
+
def reserve_seat_invoice(
|
|
171
|
+
self, amount_usd: float, asset: str = "USDC"
|
|
172
|
+
) -> Invoice:
|
|
173
|
+
"""Request an invoice for reserving a Seat. The returned ``raw`` dict
|
|
174
|
+
contains everything you need to build and submit the on-chain
|
|
175
|
+
transaction with the Solana / Polygon SDK of your choice."""
|
|
176
|
+
endpoint = "/api/invoice" if asset.upper() == "SOL" else "/api/invoice-spl"
|
|
177
|
+
payload: Dict[str, Any] = {
|
|
178
|
+
"amount_usd": amount_usd,
|
|
179
|
+
"agent_pubkey": self.address,
|
|
180
|
+
}
|
|
181
|
+
if asset.upper() != "SOL":
|
|
182
|
+
payload["asset"] = asset.upper()
|
|
183
|
+
r = self._session.post(
|
|
184
|
+
f"{self.base_url}{endpoint}", json=payload, timeout=self.timeout
|
|
185
|
+
)
|
|
186
|
+
r.raise_for_status()
|
|
187
|
+
data = r.json()
|
|
188
|
+
return Invoice(
|
|
189
|
+
amount_usd=amount_usd,
|
|
190
|
+
treasury_vault=data.get("treasury_vault", ""),
|
|
191
|
+
program_id=data.get("program_id", ""),
|
|
192
|
+
nonce=data.get("nonce", ""),
|
|
193
|
+
raw=data,
|
|
194
|
+
)
|
|
195
|
+
|
|
196
|
+
# ── Convenience: x402-aware HTTP request ──────────────────────────────
|
|
197
|
+
|
|
198
|
+
def request(
|
|
199
|
+
self,
|
|
200
|
+
method: str,
|
|
201
|
+
url: str,
|
|
202
|
+
*,
|
|
203
|
+
max_retries: int = 1,
|
|
204
|
+
**kwargs,
|
|
205
|
+
) -> requests.Response:
|
|
206
|
+
"""HTTP request that auto-injects x-agent-pubkey/x-nonce/x-signature
|
|
207
|
+
headers on first try, and retries with fresh auth on 402."""
|
|
208
|
+
headers = kwargs.pop("headers", {}) or {}
|
|
209
|
+
attempt = 0
|
|
210
|
+
while True:
|
|
211
|
+
attempt += 1
|
|
212
|
+
merged = {**self.auth_headers(), **headers}
|
|
213
|
+
resp = self._session.request(
|
|
214
|
+
method, url, headers=merged, timeout=self.timeout, **kwargs
|
|
215
|
+
)
|
|
216
|
+
if resp.status_code != 402 or attempt > max_retries:
|
|
217
|
+
return resp
|
|
218
|
+
# 402 → caller hasn't reserved a Seat yet. Surface the challenge.
|
|
219
|
+
try:
|
|
220
|
+
challenge = resp.json()
|
|
221
|
+
except Exception:
|
|
222
|
+
challenge = {"raw": resp.text}
|
|
223
|
+
raise X402Error(
|
|
224
|
+
f"402 Payment Required after {attempt} attempt(s). "
|
|
225
|
+
f"Reserve a Seat first via reserve_seat_invoice(). "
|
|
226
|
+
f"Challenge: {challenge}"
|
|
227
|
+
)
|
|
228
|
+
|
|
229
|
+
# ── Aliases for ergonomics ────────────────────────────────────────────
|
|
230
|
+
|
|
231
|
+
get = lambda self, url, **kw: self.request("GET", url, **kw)
|
|
232
|
+
post = lambda self, url, **kw: self.request("POST", url, **kw)
|
|
@@ -0,0 +1,14 @@
|
|
|
1
|
+
class AiFinPayError(Exception):
|
|
2
|
+
"""Base class for all SDK errors."""
|
|
3
|
+
|
|
4
|
+
|
|
5
|
+
class X402Error(AiFinPayError):
|
|
6
|
+
"""Raised when the x402 challenge/response flow fails."""
|
|
7
|
+
|
|
8
|
+
|
|
9
|
+
class FundingTimeoutError(AiFinPayError):
|
|
10
|
+
"""Raised when polling for funding exceeds the timeout."""
|
|
11
|
+
|
|
12
|
+
|
|
13
|
+
class SeatNotFoundError(AiFinPayError):
|
|
14
|
+
"""Raised when the agent has no Seat PDA on-chain."""
|
|
@@ -0,0 +1,99 @@
|
|
|
1
|
+
Metadata-Version: 2.4
|
|
2
|
+
Name: aifinpay-agent
|
|
3
|
+
Version: 0.1.0a1
|
|
4
|
+
Summary: Non-custodial x402 payment client for autonomous AI agents on AiFinPay (Solana + Polygon)
|
|
5
|
+
Author-email: "CoinSecurities (SECCO)" <contact@aifinpay.company>
|
|
6
|
+
License: MIT
|
|
7
|
+
Project-URL: Homepage, https://aifinpay.company
|
|
8
|
+
Project-URL: Documentation, https://aifinpay.company/docs
|
|
9
|
+
Project-URL: Repository, https://github.com/enot3615/oracle-financial-hub-59
|
|
10
|
+
Project-URL: Issues, https://github.com/enot3615/oracle-financial-hub-59/issues
|
|
11
|
+
Keywords: x402,ai-agent,solana,polygon,payments,non-custodial,ed25519
|
|
12
|
+
Classifier: Development Status :: 3 - Alpha
|
|
13
|
+
Classifier: Intended Audience :: Developers
|
|
14
|
+
Classifier: License :: OSI Approved :: MIT License
|
|
15
|
+
Classifier: Programming Language :: Python :: 3
|
|
16
|
+
Classifier: Programming Language :: Python :: 3.9
|
|
17
|
+
Classifier: Programming Language :: Python :: 3.10
|
|
18
|
+
Classifier: Programming Language :: Python :: 3.11
|
|
19
|
+
Classifier: Programming Language :: Python :: 3.12
|
|
20
|
+
Classifier: Topic :: Software Development :: Libraries :: Python Modules
|
|
21
|
+
Requires-Python: >=3.9
|
|
22
|
+
Description-Content-Type: text/markdown
|
|
23
|
+
Requires-Dist: PyNaCl>=1.5
|
|
24
|
+
Requires-Dist: base58>=2.1
|
|
25
|
+
Requires-Dist: requests>=2.28
|
|
26
|
+
|
|
27
|
+
# aifinpay-agent (Python)
|
|
28
|
+
|
|
29
|
+
Non-custodial x402 payment client for autonomous AI agents on
|
|
30
|
+
[AiFinPay](https://aifinpay.company).
|
|
31
|
+
|
|
32
|
+
The Ed25519 keypair is generated locally and never leaves your process. The
|
|
33
|
+
SDK only sends a one-time SHA-256 + Ed25519 signature in the `x-signature`
|
|
34
|
+
header to authenticate against AiFinPay-protected endpoints.
|
|
35
|
+
|
|
36
|
+
## Install
|
|
37
|
+
|
|
38
|
+
```bash
|
|
39
|
+
pip install aifinpay-agent
|
|
40
|
+
```
|
|
41
|
+
|
|
42
|
+
## Quick start
|
|
43
|
+
|
|
44
|
+
```python
|
|
45
|
+
from aifinpay import Agent
|
|
46
|
+
|
|
47
|
+
# Generate a fresh keypair locally — never transmitted
|
|
48
|
+
agent = Agent.new()
|
|
49
|
+
print("Fund this address:", agent.address)
|
|
50
|
+
print("Save this secret:", agent.secret_b58) # store securely!
|
|
51
|
+
|
|
52
|
+
# Wait until the wallet has at least $0.01 worth on-chain
|
|
53
|
+
agent.wait_for_funding(min_usd_cents=1)
|
|
54
|
+
|
|
55
|
+
# Request an invoice for a Seat (USDC on Solana)
|
|
56
|
+
invoice = agent.reserve_seat_invoice(amount_usd=1.00, asset="USDC")
|
|
57
|
+
print("Invoice:", invoice.raw)
|
|
58
|
+
# Build + sign + submit the Solana transaction with @solana/web3.js, anchorpy,
|
|
59
|
+
# or solana-py — the invoice contains program_id, treasury_vault, mints, etc.
|
|
60
|
+
|
|
61
|
+
# Once the Seat is on-chain, any AiFinPay-gated endpoint just works:
|
|
62
|
+
resp = agent.get("https://aifinpay.company/api/stats")
|
|
63
|
+
print(resp.json())
|
|
64
|
+
```
|
|
65
|
+
|
|
66
|
+
## Loading an existing keypair
|
|
67
|
+
|
|
68
|
+
```python
|
|
69
|
+
# from solana-keygen JSON file
|
|
70
|
+
agent = Agent.from_keypair_file("~/agent-wallet.json")
|
|
71
|
+
|
|
72
|
+
# from base58 secret string
|
|
73
|
+
agent = Agent.from_secret_b58("3RvZm7Gw...")
|
|
74
|
+
```
|
|
75
|
+
|
|
76
|
+
## How x402 auth works under the hood
|
|
77
|
+
|
|
78
|
+
For every gated request the SDK:
|
|
79
|
+
|
|
80
|
+
1. `GET /nonce` → receives a one-time UUID with 60s TTL
|
|
81
|
+
2. computes `SHA-256("AiFinPay-x402:{nonce}:{pubkey}")`
|
|
82
|
+
3. signs with Ed25519, base58-encodes the signature
|
|
83
|
+
4. retries the original request with headers:
|
|
84
|
+
- `x-agent-pubkey: <base58 pubkey>`
|
|
85
|
+
- `x-nonce: <uuid>`
|
|
86
|
+
- `x-signature: <base58 sig>`
|
|
87
|
+
|
|
88
|
+
The server verifies the signature, checks the agent has a live Seat PDA
|
|
89
|
+
on-chain, and serves the resource.
|
|
90
|
+
|
|
91
|
+
## Privacy
|
|
92
|
+
|
|
93
|
+
- **The server never sees your private key.** Period.
|
|
94
|
+
- Nonces are consumed on use; replay-resistant.
|
|
95
|
+
- All transactions are public and on-chain — Solana + Polygon mainnet.
|
|
96
|
+
|
|
97
|
+
## License
|
|
98
|
+
|
|
99
|
+
MIT.
|
|
@@ -0,0 +1,10 @@
|
|
|
1
|
+
README.md
|
|
2
|
+
pyproject.toml
|
|
3
|
+
aifinpay/__init__.py
|
|
4
|
+
aifinpay/client.py
|
|
5
|
+
aifinpay/errors.py
|
|
6
|
+
aifinpay_agent.egg-info/PKG-INFO
|
|
7
|
+
aifinpay_agent.egg-info/SOURCES.txt
|
|
8
|
+
aifinpay_agent.egg-info/dependency_links.txt
|
|
9
|
+
aifinpay_agent.egg-info/requires.txt
|
|
10
|
+
aifinpay_agent.egg-info/top_level.txt
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
aifinpay
|
|
@@ -0,0 +1,41 @@
|
|
|
1
|
+
[build-system]
|
|
2
|
+
requires = ["setuptools>=61.0", "wheel"]
|
|
3
|
+
build-backend = "setuptools.build_meta"
|
|
4
|
+
|
|
5
|
+
[project]
|
|
6
|
+
name = "aifinpay-agent"
|
|
7
|
+
version = "0.1.0a1"
|
|
8
|
+
description = "Non-custodial x402 payment client for autonomous AI agents on AiFinPay (Solana + Polygon)"
|
|
9
|
+
readme = "README.md"
|
|
10
|
+
requires-python = ">=3.9"
|
|
11
|
+
license = { text = "MIT" }
|
|
12
|
+
authors = [
|
|
13
|
+
{ name = "CoinSecurities (SECCO)", email = "contact@aifinpay.company" },
|
|
14
|
+
]
|
|
15
|
+
keywords = ["x402", "ai-agent", "solana", "polygon", "payments", "non-custodial", "ed25519"]
|
|
16
|
+
classifiers = [
|
|
17
|
+
"Development Status :: 3 - Alpha",
|
|
18
|
+
"Intended Audience :: Developers",
|
|
19
|
+
"License :: OSI Approved :: MIT License",
|
|
20
|
+
"Programming Language :: Python :: 3",
|
|
21
|
+
"Programming Language :: Python :: 3.9",
|
|
22
|
+
"Programming Language :: Python :: 3.10",
|
|
23
|
+
"Programming Language :: Python :: 3.11",
|
|
24
|
+
"Programming Language :: Python :: 3.12",
|
|
25
|
+
"Topic :: Software Development :: Libraries :: Python Modules",
|
|
26
|
+
]
|
|
27
|
+
dependencies = [
|
|
28
|
+
"PyNaCl>=1.5",
|
|
29
|
+
"base58>=2.1",
|
|
30
|
+
"requests>=2.28",
|
|
31
|
+
]
|
|
32
|
+
|
|
33
|
+
[project.urls]
|
|
34
|
+
Homepage = "https://aifinpay.company"
|
|
35
|
+
Documentation = "https://aifinpay.company/docs"
|
|
36
|
+
Repository = "https://github.com/enot3615/oracle-financial-hub-59"
|
|
37
|
+
Issues = "https://github.com/enot3615/oracle-financial-hub-59/issues"
|
|
38
|
+
|
|
39
|
+
[tool.setuptools.packages.find]
|
|
40
|
+
where = ["."]
|
|
41
|
+
include = ["aifinpay*"]
|