koreafilings 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.
- koreafilings-0.1.0/.gitignore +7 -0
- koreafilings-0.1.0/LICENSE +21 -0
- koreafilings-0.1.0/PKG-INFO +163 -0
- koreafilings-0.1.0/README.md +132 -0
- koreafilings-0.1.0/pyproject.toml +45 -0
- koreafilings-0.1.0/src/koreafilings/__init__.py +29 -0
- koreafilings-0.1.0/src/koreafilings/_payment.py +141 -0
- koreafilings-0.1.0/src/koreafilings/client.py +183 -0
- koreafilings-0.1.0/src/koreafilings/errors.py +55 -0
- koreafilings-0.1.0/src/koreafilings/models.py +78 -0
|
@@ -0,0 +1,21 @@
|
|
|
1
|
+
MIT License
|
|
2
|
+
|
|
3
|
+
Copyright (c) 2026 korea-filings-api
|
|
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.
|
|
@@ -0,0 +1,163 @@
|
|
|
1
|
+
Metadata-Version: 2.4
|
|
2
|
+
Name: koreafilings
|
|
3
|
+
Version: 0.1.0
|
|
4
|
+
Summary: Python SDK for koreafilings.com — AI-summarized Korean DART disclosures, paid per call in USDC via x402.
|
|
5
|
+
Project-URL: Homepage, https://koreafilings.com
|
|
6
|
+
Project-URL: Documentation, https://koreafilings.com
|
|
7
|
+
Project-URL: Source, https://github.com/OldTemple91/korea-filings-api
|
|
8
|
+
Project-URL: Issues, https://github.com/OldTemple91/korea-filings-api/issues
|
|
9
|
+
Author: korea-filings-api
|
|
10
|
+
License: MIT
|
|
11
|
+
License-File: LICENSE
|
|
12
|
+
Keywords: dart,disclosures,fintech,korea,llm,usdc,x402
|
|
13
|
+
Classifier: Development Status :: 3 - Alpha
|
|
14
|
+
Classifier: Intended Audience :: Developers
|
|
15
|
+
Classifier: Intended Audience :: Financial and Insurance Industry
|
|
16
|
+
Classifier: License :: OSI Approved :: MIT License
|
|
17
|
+
Classifier: Operating System :: OS Independent
|
|
18
|
+
Classifier: Programming Language :: Python :: 3
|
|
19
|
+
Classifier: Programming Language :: Python :: 3.9
|
|
20
|
+
Classifier: Programming Language :: Python :: 3.10
|
|
21
|
+
Classifier: Programming Language :: Python :: 3.11
|
|
22
|
+
Classifier: Programming Language :: Python :: 3.12
|
|
23
|
+
Classifier: Programming Language :: Python :: 3.13
|
|
24
|
+
Classifier: Topic :: Office/Business :: Financial
|
|
25
|
+
Classifier: Typing :: Typed
|
|
26
|
+
Requires-Python: >=3.9
|
|
27
|
+
Requires-Dist: eth-account<0.14,>=0.13
|
|
28
|
+
Requires-Dist: httpx<1.0,>=0.27
|
|
29
|
+
Requires-Dist: pydantic<3.0,>=2.6
|
|
30
|
+
Description-Content-Type: text/markdown
|
|
31
|
+
|
|
32
|
+
# koreafilings
|
|
33
|
+
|
|
34
|
+
Python SDK for [koreafilings.com](https://koreafilings.com) — AI-summarized
|
|
35
|
+
Korean DART (금융감독원 전자공시) corporate disclosures, paid per call in USDC
|
|
36
|
+
on Base via the [x402](https://www.x402.org/) payment protocol.
|
|
37
|
+
|
|
38
|
+
**No API keys. No monthly fee. No signup.** The wallet that signs the
|
|
39
|
+
payment *is* the identity. First call for a disclosure triggers an LLM
|
|
40
|
+
run; every call after costs the same flat fee from the server-side cache.
|
|
41
|
+
|
|
42
|
+
## Install
|
|
43
|
+
|
|
44
|
+
```bash
|
|
45
|
+
pip install koreafilings
|
|
46
|
+
```
|
|
47
|
+
|
|
48
|
+
## Quickstart
|
|
49
|
+
|
|
50
|
+
```python
|
|
51
|
+
from koreafilings import Client
|
|
52
|
+
|
|
53
|
+
with Client(private_key="0x...", network="base-sepolia") as client:
|
|
54
|
+
summary = client.get_summary("20260424900874")
|
|
55
|
+
|
|
56
|
+
print(f"[{summary.importance_score}/10] {summary.event_type}")
|
|
57
|
+
print(summary.summary_en)
|
|
58
|
+
print("tickers:", summary.ticker_tags)
|
|
59
|
+
print("paid:", client.last_settlement.tx_hash)
|
|
60
|
+
```
|
|
61
|
+
|
|
62
|
+
Example output:
|
|
63
|
+
|
|
64
|
+
```
|
|
65
|
+
[7/10] SINGLE_STOCK_TRADING_SUSPENSION
|
|
66
|
+
Global SM's common stock was suspended from trading by the Korea Exchange
|
|
67
|
+
following irregular trading patterns observed on April 23, 2026...
|
|
68
|
+
tickers: ['001680.KS']
|
|
69
|
+
paid: 0x1dbf9885194261e1ad64be8205897ef9c5335ad254921b337c343c23d34b0a72
|
|
70
|
+
```
|
|
71
|
+
|
|
72
|
+
## What you get
|
|
73
|
+
|
|
74
|
+
Every response is a structured `Summary` with:
|
|
75
|
+
|
|
76
|
+
| field | type | description |
|
|
77
|
+
|--------------------|-----------------------|---------------------------------------------------|
|
|
78
|
+
| `rcpt_no` | `str` | DART receipt number |
|
|
79
|
+
| `summary_en` | `str` | Paraphrased English summary (never verbatim) |
|
|
80
|
+
| `importance_score` | `int` (1–10) | 10 = M&A / insolvency, 1 = routine admin |
|
|
81
|
+
| `event_type` | `str` | Canonical event taxonomy |
|
|
82
|
+
| `sector_tags` | `list[str]` | GICS-style sector labels |
|
|
83
|
+
| `ticker_tags` | `list[str]` | Affected KRX tickers (`.KS` / `.KQ`) |
|
|
84
|
+
| `actionable_for` | `list[str]` | Audience hints (`QUANT`, `M&A`, …) |
|
|
85
|
+
| `generated_at` | `datetime` | When the summary was produced |
|
|
86
|
+
|
|
87
|
+
## Pricing
|
|
88
|
+
|
|
89
|
+
`0.005` USDC per summary call, settled on Base. Call `client.get_pricing()`
|
|
90
|
+
for the live machine-readable pricing descriptor including the current
|
|
91
|
+
recipient wallet and USDC contract address.
|
|
92
|
+
|
|
93
|
+
## Getting a wallet and USDC
|
|
94
|
+
|
|
95
|
+
For testing on Base Sepolia (no real money):
|
|
96
|
+
|
|
97
|
+
1. Create a test wallet (MetaMask → new account → export private key).
|
|
98
|
+
2. Get free Base Sepolia ETH from a faucet.
|
|
99
|
+
3. Get free test USDC from the [Circle faucet](https://faucet.circle.com/).
|
|
100
|
+
4. Pass the 0x-prefixed private key to `Client(private_key=...)`.
|
|
101
|
+
|
|
102
|
+
For production use on Base mainnet, fund a wallet with real USDC and
|
|
103
|
+
pass `network="base"` instead.
|
|
104
|
+
|
|
105
|
+
## How payment works
|
|
106
|
+
|
|
107
|
+
Under the hood, each paid call:
|
|
108
|
+
|
|
109
|
+
1. GET the endpoint without payment → server returns **402** with a
|
|
110
|
+
[x402 `accepts`](https://www.x402.org/) block describing exact
|
|
111
|
+
amount, USDC contract, network, recipient, and expiry.
|
|
112
|
+
2. SDK signs an
|
|
113
|
+
[EIP-3009 `TransferWithAuthorization`](https://eips.ethereum.org/EIPS/eip-3009)
|
|
114
|
+
message locally (your private key never leaves the process).
|
|
115
|
+
3. SDK base64-encodes the signed payload into `X-PAYMENT` and retries
|
|
116
|
+
the GET.
|
|
117
|
+
4. Server verifies the signature via the x402 facilitator, submits it
|
|
118
|
+
on-chain, streams the JSON body back to you, and attaches the
|
|
119
|
+
settlement proof as `X-PAYMENT-RESPONSE`.
|
|
120
|
+
|
|
121
|
+
The SDK exposes the proof as `client.last_settlement` so you can log
|
|
122
|
+
transaction hashes for your accounting system.
|
|
123
|
+
|
|
124
|
+
## Error handling
|
|
125
|
+
|
|
126
|
+
```python
|
|
127
|
+
from koreafilings import Client, ApiError, PaymentError, ConfigurationError
|
|
128
|
+
|
|
129
|
+
try:
|
|
130
|
+
summary = client.get_summary("20260424900874")
|
|
131
|
+
except PaymentError as e:
|
|
132
|
+
# Facilitator rejected the signed payment (bad sig, low balance,
|
|
133
|
+
# expired auth, network mismatch).
|
|
134
|
+
print("payment failed:", e.reason, e.detail)
|
|
135
|
+
except ApiError as e:
|
|
136
|
+
# Non-payment HTTP failure: 404 unknown rcpt_no, 429 rate limit,
|
|
137
|
+
# 5xx upstream outage.
|
|
138
|
+
print("api error:", e.status_code, e.body)
|
|
139
|
+
except ConfigurationError as e:
|
|
140
|
+
# Bad private_key format or unknown network alias.
|
|
141
|
+
print("config:", e)
|
|
142
|
+
```
|
|
143
|
+
|
|
144
|
+
## Security notes
|
|
145
|
+
|
|
146
|
+
- **The private key signs real-money authorizations.** Do not ship it
|
|
147
|
+
in client-side code, do not put it in a `.env` checked into git, do
|
|
148
|
+
not paste it in chat. Prefer a dedicated burner wallet funded with
|
|
149
|
+
only the USDC you plan to spend.
|
|
150
|
+
- The SDK signs locally; the key is never transmitted to koreafilings.com
|
|
151
|
+
or to the facilitator.
|
|
152
|
+
- Every payment carries an `EIP-3009` nonce. The facilitator refuses
|
|
153
|
+
replays.
|
|
154
|
+
- For Base Sepolia testing only, a fresh wallet with faucet funds is
|
|
155
|
+
the safest pattern — nothing on that wallet has production value.
|
|
156
|
+
|
|
157
|
+
## Source & feedback
|
|
158
|
+
|
|
159
|
+
- Repo: <https://github.com/OldTemple91/korea-filings-api>
|
|
160
|
+
- Issues: <https://github.com/OldTemple91/korea-filings-api/issues>
|
|
161
|
+
- Landing: <https://koreafilings.com>
|
|
162
|
+
|
|
163
|
+
MIT-licensed.
|
|
@@ -0,0 +1,132 @@
|
|
|
1
|
+
# koreafilings
|
|
2
|
+
|
|
3
|
+
Python SDK for [koreafilings.com](https://koreafilings.com) — AI-summarized
|
|
4
|
+
Korean DART (금융감독원 전자공시) corporate disclosures, paid per call in USDC
|
|
5
|
+
on Base via the [x402](https://www.x402.org/) payment protocol.
|
|
6
|
+
|
|
7
|
+
**No API keys. No monthly fee. No signup.** The wallet that signs the
|
|
8
|
+
payment *is* the identity. First call for a disclosure triggers an LLM
|
|
9
|
+
run; every call after costs the same flat fee from the server-side cache.
|
|
10
|
+
|
|
11
|
+
## Install
|
|
12
|
+
|
|
13
|
+
```bash
|
|
14
|
+
pip install koreafilings
|
|
15
|
+
```
|
|
16
|
+
|
|
17
|
+
## Quickstart
|
|
18
|
+
|
|
19
|
+
```python
|
|
20
|
+
from koreafilings import Client
|
|
21
|
+
|
|
22
|
+
with Client(private_key="0x...", network="base-sepolia") as client:
|
|
23
|
+
summary = client.get_summary("20260424900874")
|
|
24
|
+
|
|
25
|
+
print(f"[{summary.importance_score}/10] {summary.event_type}")
|
|
26
|
+
print(summary.summary_en)
|
|
27
|
+
print("tickers:", summary.ticker_tags)
|
|
28
|
+
print("paid:", client.last_settlement.tx_hash)
|
|
29
|
+
```
|
|
30
|
+
|
|
31
|
+
Example output:
|
|
32
|
+
|
|
33
|
+
```
|
|
34
|
+
[7/10] SINGLE_STOCK_TRADING_SUSPENSION
|
|
35
|
+
Global SM's common stock was suspended from trading by the Korea Exchange
|
|
36
|
+
following irregular trading patterns observed on April 23, 2026...
|
|
37
|
+
tickers: ['001680.KS']
|
|
38
|
+
paid: 0x1dbf9885194261e1ad64be8205897ef9c5335ad254921b337c343c23d34b0a72
|
|
39
|
+
```
|
|
40
|
+
|
|
41
|
+
## What you get
|
|
42
|
+
|
|
43
|
+
Every response is a structured `Summary` with:
|
|
44
|
+
|
|
45
|
+
| field | type | description |
|
|
46
|
+
|--------------------|-----------------------|---------------------------------------------------|
|
|
47
|
+
| `rcpt_no` | `str` | DART receipt number |
|
|
48
|
+
| `summary_en` | `str` | Paraphrased English summary (never verbatim) |
|
|
49
|
+
| `importance_score` | `int` (1–10) | 10 = M&A / insolvency, 1 = routine admin |
|
|
50
|
+
| `event_type` | `str` | Canonical event taxonomy |
|
|
51
|
+
| `sector_tags` | `list[str]` | GICS-style sector labels |
|
|
52
|
+
| `ticker_tags` | `list[str]` | Affected KRX tickers (`.KS` / `.KQ`) |
|
|
53
|
+
| `actionable_for` | `list[str]` | Audience hints (`QUANT`, `M&A`, …) |
|
|
54
|
+
| `generated_at` | `datetime` | When the summary was produced |
|
|
55
|
+
|
|
56
|
+
## Pricing
|
|
57
|
+
|
|
58
|
+
`0.005` USDC per summary call, settled on Base. Call `client.get_pricing()`
|
|
59
|
+
for the live machine-readable pricing descriptor including the current
|
|
60
|
+
recipient wallet and USDC contract address.
|
|
61
|
+
|
|
62
|
+
## Getting a wallet and USDC
|
|
63
|
+
|
|
64
|
+
For testing on Base Sepolia (no real money):
|
|
65
|
+
|
|
66
|
+
1. Create a test wallet (MetaMask → new account → export private key).
|
|
67
|
+
2. Get free Base Sepolia ETH from a faucet.
|
|
68
|
+
3. Get free test USDC from the [Circle faucet](https://faucet.circle.com/).
|
|
69
|
+
4. Pass the 0x-prefixed private key to `Client(private_key=...)`.
|
|
70
|
+
|
|
71
|
+
For production use on Base mainnet, fund a wallet with real USDC and
|
|
72
|
+
pass `network="base"` instead.
|
|
73
|
+
|
|
74
|
+
## How payment works
|
|
75
|
+
|
|
76
|
+
Under the hood, each paid call:
|
|
77
|
+
|
|
78
|
+
1. GET the endpoint without payment → server returns **402** with a
|
|
79
|
+
[x402 `accepts`](https://www.x402.org/) block describing exact
|
|
80
|
+
amount, USDC contract, network, recipient, and expiry.
|
|
81
|
+
2. SDK signs an
|
|
82
|
+
[EIP-3009 `TransferWithAuthorization`](https://eips.ethereum.org/EIPS/eip-3009)
|
|
83
|
+
message locally (your private key never leaves the process).
|
|
84
|
+
3. SDK base64-encodes the signed payload into `X-PAYMENT` and retries
|
|
85
|
+
the GET.
|
|
86
|
+
4. Server verifies the signature via the x402 facilitator, submits it
|
|
87
|
+
on-chain, streams the JSON body back to you, and attaches the
|
|
88
|
+
settlement proof as `X-PAYMENT-RESPONSE`.
|
|
89
|
+
|
|
90
|
+
The SDK exposes the proof as `client.last_settlement` so you can log
|
|
91
|
+
transaction hashes for your accounting system.
|
|
92
|
+
|
|
93
|
+
## Error handling
|
|
94
|
+
|
|
95
|
+
```python
|
|
96
|
+
from koreafilings import Client, ApiError, PaymentError, ConfigurationError
|
|
97
|
+
|
|
98
|
+
try:
|
|
99
|
+
summary = client.get_summary("20260424900874")
|
|
100
|
+
except PaymentError as e:
|
|
101
|
+
# Facilitator rejected the signed payment (bad sig, low balance,
|
|
102
|
+
# expired auth, network mismatch).
|
|
103
|
+
print("payment failed:", e.reason, e.detail)
|
|
104
|
+
except ApiError as e:
|
|
105
|
+
# Non-payment HTTP failure: 404 unknown rcpt_no, 429 rate limit,
|
|
106
|
+
# 5xx upstream outage.
|
|
107
|
+
print("api error:", e.status_code, e.body)
|
|
108
|
+
except ConfigurationError as e:
|
|
109
|
+
# Bad private_key format or unknown network alias.
|
|
110
|
+
print("config:", e)
|
|
111
|
+
```
|
|
112
|
+
|
|
113
|
+
## Security notes
|
|
114
|
+
|
|
115
|
+
- **The private key signs real-money authorizations.** Do not ship it
|
|
116
|
+
in client-side code, do not put it in a `.env` checked into git, do
|
|
117
|
+
not paste it in chat. Prefer a dedicated burner wallet funded with
|
|
118
|
+
only the USDC you plan to spend.
|
|
119
|
+
- The SDK signs locally; the key is never transmitted to koreafilings.com
|
|
120
|
+
or to the facilitator.
|
|
121
|
+
- Every payment carries an `EIP-3009` nonce. The facilitator refuses
|
|
122
|
+
replays.
|
|
123
|
+
- For Base Sepolia testing only, a fresh wallet with faucet funds is
|
|
124
|
+
the safest pattern — nothing on that wallet has production value.
|
|
125
|
+
|
|
126
|
+
## Source & feedback
|
|
127
|
+
|
|
128
|
+
- Repo: <https://github.com/OldTemple91/korea-filings-api>
|
|
129
|
+
- Issues: <https://github.com/OldTemple91/korea-filings-api/issues>
|
|
130
|
+
- Landing: <https://koreafilings.com>
|
|
131
|
+
|
|
132
|
+
MIT-licensed.
|
|
@@ -0,0 +1,45 @@
|
|
|
1
|
+
[build-system]
|
|
2
|
+
requires = ["hatchling>=1.24"]
|
|
3
|
+
build-backend = "hatchling.build"
|
|
4
|
+
|
|
5
|
+
[project]
|
|
6
|
+
name = "koreafilings"
|
|
7
|
+
version = "0.1.0"
|
|
8
|
+
description = "Python SDK for koreafilings.com — AI-summarized Korean DART disclosures, paid per call in USDC via x402."
|
|
9
|
+
readme = "README.md"
|
|
10
|
+
requires-python = ">=3.9"
|
|
11
|
+
license = { text = "MIT" }
|
|
12
|
+
authors = [{ name = "korea-filings-api" }]
|
|
13
|
+
keywords = ["dart", "korea", "disclosures", "x402", "llm", "fintech", "usdc"]
|
|
14
|
+
classifiers = [
|
|
15
|
+
"Development Status :: 3 - Alpha",
|
|
16
|
+
"Intended Audience :: Developers",
|
|
17
|
+
"Intended Audience :: Financial and Insurance Industry",
|
|
18
|
+
"License :: OSI Approved :: MIT License",
|
|
19
|
+
"Operating System :: OS Independent",
|
|
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
|
+
"Programming Language :: Python :: 3.13",
|
|
26
|
+
"Topic :: Office/Business :: Financial",
|
|
27
|
+
"Typing :: Typed",
|
|
28
|
+
]
|
|
29
|
+
dependencies = [
|
|
30
|
+
"httpx>=0.27,<1.0",
|
|
31
|
+
"pydantic>=2.6,<3.0",
|
|
32
|
+
"eth-account>=0.13,<0.14",
|
|
33
|
+
]
|
|
34
|
+
|
|
35
|
+
[project.urls]
|
|
36
|
+
Homepage = "https://koreafilings.com"
|
|
37
|
+
Documentation = "https://koreafilings.com"
|
|
38
|
+
Source = "https://github.com/OldTemple91/korea-filings-api"
|
|
39
|
+
Issues = "https://github.com/OldTemple91/korea-filings-api/issues"
|
|
40
|
+
|
|
41
|
+
[tool.hatch.build.targets.wheel]
|
|
42
|
+
packages = ["src/koreafilings"]
|
|
43
|
+
|
|
44
|
+
[tool.hatch.build.targets.sdist]
|
|
45
|
+
include = ["/src", "/README.md", "/LICENSE", "/pyproject.toml"]
|
|
@@ -0,0 +1,29 @@
|
|
|
1
|
+
"""koreafilings — Python SDK for AI-summarized Korean DART disclosures.
|
|
2
|
+
|
|
3
|
+
Minimal usage:
|
|
4
|
+
|
|
5
|
+
from koreafilings import Client
|
|
6
|
+
|
|
7
|
+
with Client(private_key="0x...", network="base-sepolia") as client:
|
|
8
|
+
summary = client.get_summary("20260424900874")
|
|
9
|
+
print(summary.importance_score, summary.summary_en)
|
|
10
|
+
print("paid tx:", client.last_settlement.tx_hash)
|
|
11
|
+
"""
|
|
12
|
+
|
|
13
|
+
from .client import Client
|
|
14
|
+
from .errors import ApiError, ConfigurationError, KoreaFilingsError, PaymentError
|
|
15
|
+
from .models import Pricing, PricingEndpoint, SettlementProof, Summary
|
|
16
|
+
|
|
17
|
+
__all__ = [
|
|
18
|
+
"Client",
|
|
19
|
+
"Summary",
|
|
20
|
+
"SettlementProof",
|
|
21
|
+
"Pricing",
|
|
22
|
+
"PricingEndpoint",
|
|
23
|
+
"KoreaFilingsError",
|
|
24
|
+
"ApiError",
|
|
25
|
+
"PaymentError",
|
|
26
|
+
"ConfigurationError",
|
|
27
|
+
]
|
|
28
|
+
|
|
29
|
+
__version__ = "0.1.0"
|
|
@@ -0,0 +1,141 @@
|
|
|
1
|
+
"""x402 payment internals.
|
|
2
|
+
|
|
3
|
+
This module is intentionally underscore-prefixed. It is not part of the
|
|
4
|
+
public SDK surface — callers should reach it only via the public
|
|
5
|
+
``Client`` class.
|
|
6
|
+
|
|
7
|
+
What lives here:
|
|
8
|
+
|
|
9
|
+
- parsing the 402 "accepts" block the API returns,
|
|
10
|
+
- building the EIP-3009 TransferWithAuthorization message,
|
|
11
|
+
- signing it with the caller's private key (EIP-712),
|
|
12
|
+
- serialising the signed ``PaymentPayload`` as the base64 ``X-PAYMENT``
|
|
13
|
+
header the x402 protocol expects.
|
|
14
|
+
|
|
15
|
+
We deliberately do not talk to the facilitator from the client. The
|
|
16
|
+
server's interceptor submits the signed payment to the facilitator,
|
|
17
|
+
which keeps the SDK small and avoids leaking facilitator URL choices
|
|
18
|
+
into user code.
|
|
19
|
+
"""
|
|
20
|
+
|
|
21
|
+
from __future__ import annotations
|
|
22
|
+
|
|
23
|
+
import base64
|
|
24
|
+
import json
|
|
25
|
+
import secrets
|
|
26
|
+
import time
|
|
27
|
+
from typing import Any, Mapping
|
|
28
|
+
|
|
29
|
+
from eth_account import Account
|
|
30
|
+
from eth_account.messages import encode_typed_data
|
|
31
|
+
|
|
32
|
+
from .errors import PaymentError
|
|
33
|
+
|
|
34
|
+
|
|
35
|
+
X402_VERSION = 2
|
|
36
|
+
|
|
37
|
+
|
|
38
|
+
def select_requirement(accepts: list[Mapping[str, Any]]) -> Mapping[str, Any]:
|
|
39
|
+
"""Pick the first payment requirement the server offers.
|
|
40
|
+
|
|
41
|
+
The 402 body carries a list of acceptable payment schemes. Today the
|
|
42
|
+
server only ever offers one (USDC on whatever network it's configured
|
|
43
|
+
for), so we take index 0. If the server starts offering multiple
|
|
44
|
+
schemes, this is the place to prefer the cheaper one.
|
|
45
|
+
"""
|
|
46
|
+
if not accepts:
|
|
47
|
+
raise PaymentError(reason="empty_accepts", detail="server returned 402 with no payment options")
|
|
48
|
+
return accepts[0]
|
|
49
|
+
|
|
50
|
+
|
|
51
|
+
def build_authorization(payer_address: str, requirement: Mapping[str, Any]) -> dict:
|
|
52
|
+
"""Assemble the EIP-3009 authorization fields.
|
|
53
|
+
|
|
54
|
+
``validAfter`` gets a 10-second backfill so a payer clock that is
|
|
55
|
+
slightly behind the facilitator's doesn't cause a rejection.
|
|
56
|
+
``validBefore`` uses the ``maxTimeoutSeconds`` the server advertised.
|
|
57
|
+
``nonce`` is random 32 bytes — the facilitator rejects replays.
|
|
58
|
+
"""
|
|
59
|
+
now = int(time.time())
|
|
60
|
+
return {
|
|
61
|
+
"from": payer_address,
|
|
62
|
+
"to": requirement["payTo"],
|
|
63
|
+
"value": requirement["amount"],
|
|
64
|
+
"validAfter": str(max(0, now - 10)),
|
|
65
|
+
"validBefore": str(now + int(requirement["maxTimeoutSeconds"])),
|
|
66
|
+
"nonce": "0x" + secrets.token_hex(32),
|
|
67
|
+
}
|
|
68
|
+
|
|
69
|
+
|
|
70
|
+
def sign_eip3009(account: Account, requirement: Mapping[str, Any], authorization: Mapping[str, Any]) -> str:
|
|
71
|
+
"""Produce the 65-byte EIP-712 signature over the authorization."""
|
|
72
|
+
chain_id = int(requirement["network"].split(":")[1])
|
|
73
|
+
extra = requirement.get("extra") or {}
|
|
74
|
+
typed = {
|
|
75
|
+
"types": {
|
|
76
|
+
"EIP712Domain": [
|
|
77
|
+
{"name": "name", "type": "string"},
|
|
78
|
+
{"name": "version", "type": "string"},
|
|
79
|
+
{"name": "chainId", "type": "uint256"},
|
|
80
|
+
{"name": "verifyingContract", "type": "address"},
|
|
81
|
+
],
|
|
82
|
+
"TransferWithAuthorization": [
|
|
83
|
+
{"name": "from", "type": "address"},
|
|
84
|
+
{"name": "to", "type": "address"},
|
|
85
|
+
{"name": "value", "type": "uint256"},
|
|
86
|
+
{"name": "validAfter", "type": "uint256"},
|
|
87
|
+
{"name": "validBefore", "type": "uint256"},
|
|
88
|
+
{"name": "nonce", "type": "bytes32"},
|
|
89
|
+
],
|
|
90
|
+
},
|
|
91
|
+
"primaryType": "TransferWithAuthorization",
|
|
92
|
+
"domain": {
|
|
93
|
+
"name": extra.get("name", "USDC"),
|
|
94
|
+
"version": extra.get("version", "2"),
|
|
95
|
+
"chainId": chain_id,
|
|
96
|
+
"verifyingContract": requirement["asset"],
|
|
97
|
+
},
|
|
98
|
+
"message": {
|
|
99
|
+
"from": authorization["from"],
|
|
100
|
+
"to": authorization["to"],
|
|
101
|
+
"value": int(authorization["value"]),
|
|
102
|
+
"validAfter": int(authorization["validAfter"]),
|
|
103
|
+
"validBefore": int(authorization["validBefore"]),
|
|
104
|
+
"nonce": authorization["nonce"],
|
|
105
|
+
},
|
|
106
|
+
}
|
|
107
|
+
signable = encode_typed_data(full_message=typed)
|
|
108
|
+
signed = account.sign_message(signable)
|
|
109
|
+
hex_sig = signed.signature.hex()
|
|
110
|
+
return hex_sig if hex_sig.startswith("0x") else f"0x{hex_sig}"
|
|
111
|
+
|
|
112
|
+
|
|
113
|
+
def build_x_payment_header(
|
|
114
|
+
resource_url: str,
|
|
115
|
+
requirement: Mapping[str, Any],
|
|
116
|
+
authorization: Mapping[str, Any],
|
|
117
|
+
signature: str,
|
|
118
|
+
) -> str:
|
|
119
|
+
"""Base64-encode the signed payload into the ``X-PAYMENT`` header value."""
|
|
120
|
+
payload = {
|
|
121
|
+
"x402Version": X402_VERSION,
|
|
122
|
+
"resource": {
|
|
123
|
+
"url": resource_url,
|
|
124
|
+
"description": requirement.get("description", ""),
|
|
125
|
+
"mimeType": "application/json",
|
|
126
|
+
},
|
|
127
|
+
"accepted": requirement,
|
|
128
|
+
"payload": {"signature": signature, "authorization": authorization},
|
|
129
|
+
}
|
|
130
|
+
raw = json.dumps(payload, separators=(",", ":")).encode("utf-8")
|
|
131
|
+
return base64.b64encode(raw).decode("ascii")
|
|
132
|
+
|
|
133
|
+
|
|
134
|
+
def decode_settlement_header(value: str | None) -> dict | None:
|
|
135
|
+
"""Decode the ``X-PAYMENT-RESPONSE`` settlement proof, or return None."""
|
|
136
|
+
if not value:
|
|
137
|
+
return None
|
|
138
|
+
try:
|
|
139
|
+
return json.loads(base64.b64decode(value))
|
|
140
|
+
except Exception: # noqa: BLE001 — any decode failure returns None
|
|
141
|
+
return None
|
|
@@ -0,0 +1,183 @@
|
|
|
1
|
+
"""Public client for koreafilings.com.
|
|
2
|
+
|
|
3
|
+
The only class callers need:
|
|
4
|
+
|
|
5
|
+
from koreafilings import Client
|
|
6
|
+
|
|
7
|
+
client = Client(private_key="0x...", network="base-sepolia")
|
|
8
|
+
summary = client.get_summary("20260424900874")
|
|
9
|
+
|
|
10
|
+
The client hides the full x402 flow: it issues the GET, detects the
|
|
11
|
+
402 payment prompt, signs an EIP-3009 ``TransferWithAuthorization``,
|
|
12
|
+
resubmits the request with the ``X-PAYMENT`` header, and parses the
|
|
13
|
+
200 body. Settlement proofs from ``X-PAYMENT-RESPONSE`` are attached to
|
|
14
|
+
the returned model via the ``last_settlement`` property.
|
|
15
|
+
"""
|
|
16
|
+
|
|
17
|
+
from __future__ import annotations
|
|
18
|
+
|
|
19
|
+
from typing import Optional
|
|
20
|
+
|
|
21
|
+
import httpx
|
|
22
|
+
from eth_account import Account
|
|
23
|
+
|
|
24
|
+
from . import _payment
|
|
25
|
+
from .errors import ApiError, ConfigurationError, PaymentError
|
|
26
|
+
from .models import Pricing, SettlementProof, Summary
|
|
27
|
+
|
|
28
|
+
|
|
29
|
+
DEFAULT_BASE_URL = "https://api.koreafilings.com"
|
|
30
|
+
DEFAULT_TIMEOUT = 60.0
|
|
31
|
+
|
|
32
|
+
# Maps the short network aliases callers are allowed to pass into the
|
|
33
|
+
# constructor to the CAIP-2 identifiers the server uses in its 402
|
|
34
|
+
# responses. We only use this for up-front validation — the actual
|
|
35
|
+
# network is always whatever the server's 402 body says, so that callers
|
|
36
|
+
# never accidentally sign for a different chain than the server expects.
|
|
37
|
+
_NETWORK_ALIASES = {
|
|
38
|
+
"base-sepolia": "eip155:84532",
|
|
39
|
+
"base": "eip155:8453",
|
|
40
|
+
"base-mainnet": "eip155:8453",
|
|
41
|
+
}
|
|
42
|
+
|
|
43
|
+
|
|
44
|
+
class Client:
|
|
45
|
+
"""Blocking HTTP client for the koreafilings paid API.
|
|
46
|
+
|
|
47
|
+
Parameters
|
|
48
|
+
----------
|
|
49
|
+
private_key:
|
|
50
|
+
A 0x-prefixed 32-byte hex string. This key signs the EIP-3009
|
|
51
|
+
authorizations that move USDC from your wallet to the API's
|
|
52
|
+
recipient wallet. **Keep this secret.** The SDK never transmits
|
|
53
|
+
it — signing happens locally, and only the signature goes on the
|
|
54
|
+
wire.
|
|
55
|
+
network:
|
|
56
|
+
Short alias (``"base-sepolia"`` or ``"base"``) used only for
|
|
57
|
+
sanity checks. The authoritative network comes from the 402
|
|
58
|
+
response the server sends; if it disagrees with ``network``,
|
|
59
|
+
``get_summary`` raises :class:`PaymentError` before signing.
|
|
60
|
+
base_url:
|
|
61
|
+
Override for self-hosted deployments or local development.
|
|
62
|
+
Defaults to ``https://api.koreafilings.com``.
|
|
63
|
+
timeout:
|
|
64
|
+
Per-request timeout in seconds. The paid summary call can take
|
|
65
|
+
up to ~10s on a cold disclosure (LLM latency), so the default
|
|
66
|
+
is generous.
|
|
67
|
+
"""
|
|
68
|
+
|
|
69
|
+
def __init__(
|
|
70
|
+
self,
|
|
71
|
+
private_key: str,
|
|
72
|
+
network: str = "base-sepolia",
|
|
73
|
+
base_url: str = DEFAULT_BASE_URL,
|
|
74
|
+
timeout: float = DEFAULT_TIMEOUT,
|
|
75
|
+
):
|
|
76
|
+
if not private_key.startswith("0x") or len(private_key) != 66:
|
|
77
|
+
raise ConfigurationError("private_key must be a 0x-prefixed 32-byte hex string (66 chars)")
|
|
78
|
+
if network not in _NETWORK_ALIASES:
|
|
79
|
+
raise ConfigurationError(
|
|
80
|
+
f"unknown network '{network}'; expected one of {sorted(_NETWORK_ALIASES)}"
|
|
81
|
+
)
|
|
82
|
+
self._account = Account.from_key(private_key)
|
|
83
|
+
self._expected_chain = _NETWORK_ALIASES[network]
|
|
84
|
+
self._base_url = base_url.rstrip("/")
|
|
85
|
+
self._http = httpx.Client(timeout=timeout)
|
|
86
|
+
self._last_settlement: Optional[SettlementProof] = None
|
|
87
|
+
|
|
88
|
+
# ------------------------------------------------------------------
|
|
89
|
+
# Public API
|
|
90
|
+
# ------------------------------------------------------------------
|
|
91
|
+
|
|
92
|
+
@property
|
|
93
|
+
def address(self) -> str:
|
|
94
|
+
"""Payer wallet address derived from the configured private key."""
|
|
95
|
+
return self._account.address
|
|
96
|
+
|
|
97
|
+
@property
|
|
98
|
+
def last_settlement(self) -> Optional[SettlementProof]:
|
|
99
|
+
"""Settlement proof from the most recent successful paid call, if any."""
|
|
100
|
+
return self._last_settlement
|
|
101
|
+
|
|
102
|
+
def get_pricing(self) -> Pricing:
|
|
103
|
+
"""Fetch the public pricing descriptor. No payment required."""
|
|
104
|
+
resp = self._http.get(f"{self._base_url}/v1/pricing")
|
|
105
|
+
if resp.status_code != 200:
|
|
106
|
+
raise ApiError(resp.status_code, _safe_json(resp))
|
|
107
|
+
return Pricing.model_validate(resp.json())
|
|
108
|
+
|
|
109
|
+
def get_summary(self, rcpt_no: str) -> Summary:
|
|
110
|
+
"""Fetch the AI summary for a DART receipt number, paying if required.
|
|
111
|
+
|
|
112
|
+
The first call for a given ``rcpt_no`` triggers an LLM run on
|
|
113
|
+
the server and costs USDC (0.005 as of v0.1). Every subsequent
|
|
114
|
+
call for the same disclosure is served from the server-side
|
|
115
|
+
cache and still costs the same fee — the cache improves server
|
|
116
|
+
margins, not caller price. Errors map to:
|
|
117
|
+
|
|
118
|
+
- :class:`ApiError` for HTTP failures that aren't payment prompts,
|
|
119
|
+
- :class:`PaymentError` if signing/settlement is rejected.
|
|
120
|
+
"""
|
|
121
|
+
url = f"{self._base_url}/v1/disclosures/{rcpt_no}/summary"
|
|
122
|
+
|
|
123
|
+
unpaid = self._http.get(url)
|
|
124
|
+
if unpaid.status_code == 200:
|
|
125
|
+
# Free/cached response path (the server currently always charges,
|
|
126
|
+
# but a future free-tier could land here without breaking callers).
|
|
127
|
+
self._last_settlement = None
|
|
128
|
+
return Summary.model_validate(unpaid.json())
|
|
129
|
+
if unpaid.status_code != 402:
|
|
130
|
+
raise ApiError(unpaid.status_code, _safe_json(unpaid))
|
|
131
|
+
|
|
132
|
+
body = _safe_json(unpaid) or {}
|
|
133
|
+
requirement = _payment.select_requirement(body.get("accepts") or [])
|
|
134
|
+
|
|
135
|
+
advertised = requirement.get("network")
|
|
136
|
+
if advertised != self._expected_chain:
|
|
137
|
+
raise PaymentError(
|
|
138
|
+
reason="network_mismatch",
|
|
139
|
+
detail=f"server advertises {advertised}, client configured for {self._expected_chain}",
|
|
140
|
+
)
|
|
141
|
+
|
|
142
|
+
authorization = _payment.build_authorization(self._account.address, requirement)
|
|
143
|
+
signature = _payment.sign_eip3009(self._account, requirement, authorization)
|
|
144
|
+
header_value = _payment.build_x_payment_header(url, requirement, authorization, signature)
|
|
145
|
+
|
|
146
|
+
paid = self._http.get(url, headers={"X-PAYMENT": header_value})
|
|
147
|
+
if paid.status_code == 402:
|
|
148
|
+
# Facilitator rejected after we signed — bubble up its reason.
|
|
149
|
+
rejection = _safe_json(paid) or {}
|
|
150
|
+
raise PaymentError(
|
|
151
|
+
reason=rejection.get("error") or "payment_rejected",
|
|
152
|
+
detail=rejection,
|
|
153
|
+
)
|
|
154
|
+
if paid.status_code != 200:
|
|
155
|
+
raise ApiError(paid.status_code, _safe_json(paid))
|
|
156
|
+
|
|
157
|
+
settlement_raw = _payment.decode_settlement_header(paid.headers.get("X-PAYMENT-RESPONSE"))
|
|
158
|
+
self._last_settlement = (
|
|
159
|
+
SettlementProof.model_validate(settlement_raw) if settlement_raw else None
|
|
160
|
+
)
|
|
161
|
+
return Summary.model_validate(paid.json())
|
|
162
|
+
|
|
163
|
+
def close(self) -> None:
|
|
164
|
+
"""Close the underlying HTTP connection pool."""
|
|
165
|
+
self._http.close()
|
|
166
|
+
|
|
167
|
+
# ------------------------------------------------------------------
|
|
168
|
+
# Context manager support
|
|
169
|
+
# ------------------------------------------------------------------
|
|
170
|
+
|
|
171
|
+
def __enter__(self) -> "Client":
|
|
172
|
+
return self
|
|
173
|
+
|
|
174
|
+
def __exit__(self, exc_type, exc, tb) -> None:
|
|
175
|
+
self.close()
|
|
176
|
+
|
|
177
|
+
|
|
178
|
+
def _safe_json(resp: httpx.Response) -> Optional[dict]:
|
|
179
|
+
"""Best-effort JSON parse; returns None on any failure."""
|
|
180
|
+
try:
|
|
181
|
+
return resp.json()
|
|
182
|
+
except Exception: # noqa: BLE001
|
|
183
|
+
return None
|
|
@@ -0,0 +1,55 @@
|
|
|
1
|
+
"""Exception hierarchy for the koreafilings SDK.
|
|
2
|
+
|
|
3
|
+
Callers catch ``KoreaFilingsError`` to handle any SDK-level failure, or
|
|
4
|
+
a more specific subclass when they care to distinguish payment issues
|
|
5
|
+
from transport issues.
|
|
6
|
+
"""
|
|
7
|
+
|
|
8
|
+
from __future__ import annotations
|
|
9
|
+
|
|
10
|
+
from typing import Any, Mapping
|
|
11
|
+
|
|
12
|
+
|
|
13
|
+
class KoreaFilingsError(Exception):
|
|
14
|
+
"""Base class for every error this SDK raises."""
|
|
15
|
+
|
|
16
|
+
|
|
17
|
+
class ApiError(KoreaFilingsError):
|
|
18
|
+
"""The API returned a non-2xx response that is not a 402 payment prompt.
|
|
19
|
+
|
|
20
|
+
Typical causes: 404 (unknown rcpt_no), 429 (rate limit), 5xx (upstream
|
|
21
|
+
DART or LLM transient failure). ``status_code`` is the HTTP status;
|
|
22
|
+
``body`` is the raw JSON body if the server supplied one, else ``None``.
|
|
23
|
+
"""
|
|
24
|
+
|
|
25
|
+
def __init__(self, status_code: int, body: Any | None, message: str | None = None):
|
|
26
|
+
self.status_code = status_code
|
|
27
|
+
self.body = body
|
|
28
|
+
suffix = f": {message}" if message else ""
|
|
29
|
+
super().__init__(f"API returned {status_code}{suffix}")
|
|
30
|
+
|
|
31
|
+
|
|
32
|
+
class PaymentError(KoreaFilingsError):
|
|
33
|
+
"""The facilitator or the API rejected the x402 payment attempt.
|
|
34
|
+
|
|
35
|
+
Raised when a signed payment is rejected by the facilitator (bad
|
|
36
|
+
signature, insufficient balance, expired authorization) or when the
|
|
37
|
+
API replies 402 a second time after we submitted a payment header.
|
|
38
|
+
``reason`` is the short code returned by the facilitator when one
|
|
39
|
+
is available.
|
|
40
|
+
"""
|
|
41
|
+
|
|
42
|
+
def __init__(self, reason: str | None, detail: Mapping[str, Any] | str | None = None):
|
|
43
|
+
self.reason = reason
|
|
44
|
+
self.detail = detail
|
|
45
|
+
parts = [p for p in (reason, str(detail) if detail else None) if p]
|
|
46
|
+
super().__init__(" — ".join(parts) if parts else "payment rejected")
|
|
47
|
+
|
|
48
|
+
|
|
49
|
+
class ConfigurationError(KoreaFilingsError):
|
|
50
|
+
"""The SDK was constructed with invalid arguments.
|
|
51
|
+
|
|
52
|
+
Raised eagerly at construction time so bad configs never reach the
|
|
53
|
+
first HTTP call. Examples: malformed private key, unknown network
|
|
54
|
+
alias.
|
|
55
|
+
"""
|
|
@@ -0,0 +1,78 @@
|
|
|
1
|
+
"""Response models.
|
|
2
|
+
|
|
3
|
+
Every model uses Pydantic v2 so callers get runtime validation, static
|
|
4
|
+
type-checker hints, and ``.model_dump_json()`` for free. Field names
|
|
5
|
+
stay in the API's camelCase form because that's what the wire returns —
|
|
6
|
+
we do not re-camelcase on the way in.
|
|
7
|
+
"""
|
|
8
|
+
|
|
9
|
+
from __future__ import annotations
|
|
10
|
+
|
|
11
|
+
from datetime import datetime
|
|
12
|
+
from typing import List, Optional
|
|
13
|
+
|
|
14
|
+
from pydantic import BaseModel, ConfigDict, Field
|
|
15
|
+
|
|
16
|
+
|
|
17
|
+
class Summary(BaseModel):
|
|
18
|
+
"""AI-generated English summary of a single DART disclosure.
|
|
19
|
+
|
|
20
|
+
This is the payload the paid endpoint returns after a successful
|
|
21
|
+
x402 settlement. ``importance_score`` is 1–10, where 10 is an event
|
|
22
|
+
like M&A or insolvency and 1 is a routine administrative filing.
|
|
23
|
+
"""
|
|
24
|
+
|
|
25
|
+
model_config = ConfigDict(populate_by_name=True, frozen=True)
|
|
26
|
+
|
|
27
|
+
rcpt_no: str = Field(alias="rcptNo")
|
|
28
|
+
summary_en: str = Field(alias="summaryEn")
|
|
29
|
+
importance_score: int = Field(alias="importanceScore", ge=1, le=10)
|
|
30
|
+
event_type: str = Field(alias="eventType")
|
|
31
|
+
sector_tags: List[str] = Field(alias="sectorTags", default_factory=list)
|
|
32
|
+
ticker_tags: List[str] = Field(alias="tickerTags", default_factory=list)
|
|
33
|
+
actionable_for: List[str] = Field(alias="actionableFor", default_factory=list)
|
|
34
|
+
generated_at: datetime = Field(alias="generatedAt")
|
|
35
|
+
|
|
36
|
+
|
|
37
|
+
class SettlementProof(BaseModel):
|
|
38
|
+
"""On-chain settlement proof the API returns via ``X-PAYMENT-RESPONSE``.
|
|
39
|
+
|
|
40
|
+
``tx_hash`` is the Base transaction hash you can look up on
|
|
41
|
+
basescan.org. ``network`` is the CAIP-2 network identifier (e.g.
|
|
42
|
+
``eip155:84532`` for Base Sepolia, ``eip155:8453`` for Base mainnet).
|
|
43
|
+
``error_reason`` is populated only when ``success`` is ``False`` —
|
|
44
|
+
the facilitator normally only reaches this path on partial
|
|
45
|
+
settlements, since hard rejects surface as ``PaymentError`` before
|
|
46
|
+
a proof is emitted.
|
|
47
|
+
"""
|
|
48
|
+
|
|
49
|
+
model_config = ConfigDict(populate_by_name=True, frozen=True)
|
|
50
|
+
|
|
51
|
+
success: bool
|
|
52
|
+
tx_hash: Optional[str] = Field(alias="transaction", default=None)
|
|
53
|
+
network: Optional[str] = None
|
|
54
|
+
payer: Optional[str] = None
|
|
55
|
+
error_reason: Optional[str] = Field(alias="errorReason", default=None)
|
|
56
|
+
|
|
57
|
+
|
|
58
|
+
class Pricing(BaseModel):
|
|
59
|
+
"""Machine-readable pricing descriptor from ``GET /v1/pricing``."""
|
|
60
|
+
|
|
61
|
+
model_config = ConfigDict(populate_by_name=True, frozen=True)
|
|
62
|
+
|
|
63
|
+
x402_network: str = Field(alias="x402Network")
|
|
64
|
+
x402_asset: str = Field(alias="x402Asset")
|
|
65
|
+
x402_recipient: str = Field(alias="x402Recipient")
|
|
66
|
+
endpoints: List["PricingEndpoint"]
|
|
67
|
+
|
|
68
|
+
|
|
69
|
+
class PricingEndpoint(BaseModel):
|
|
70
|
+
model_config = ConfigDict(populate_by_name=True, frozen=True)
|
|
71
|
+
|
|
72
|
+
method: str
|
|
73
|
+
path: str
|
|
74
|
+
price_usdc: str = Field(alias="priceUsdc")
|
|
75
|
+
description: str
|
|
76
|
+
|
|
77
|
+
|
|
78
|
+
Pricing.model_rebuild()
|