sincor-x402 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.
- sincor_x402-0.1.0/PKG-INFO +251 -0
- sincor_x402-0.1.0/README.md +225 -0
- sincor_x402-0.1.0/pyproject.toml +32 -0
- sincor_x402-0.1.0/setup.cfg +4 -0
- sincor_x402-0.1.0/src/sincor_x402/__init__.py +42 -0
- sincor_x402-0.1.0/src/sincor_x402/buyer.py +257 -0
- sincor_x402-0.1.0/src/sincor_x402/errors.py +65 -0
- sincor_x402-0.1.0/src/sincor_x402/facilitator.py +75 -0
- sincor_x402-0.1.0/src/sincor_x402/fastapi_adapter.py +62 -0
- sincor_x402-0.1.0/src/sincor_x402/hub_engine.py +623 -0
- sincor_x402-0.1.0/src/sincor_x402/mcp_adapter.py +91 -0
- sincor_x402-0.1.0/src/sincor_x402/platform.py +102 -0
- sincor_x402-0.1.0/src/sincor_x402/server.py +204 -0
- sincor_x402-0.1.0/src/sincor_x402/types.py +142 -0
- sincor_x402-0.1.0/src/sincor_x402/verifier.py +89 -0
- sincor_x402-0.1.0/src/sincor_x402.egg-info/PKG-INFO +251 -0
- sincor_x402-0.1.0/src/sincor_x402.egg-info/SOURCES.txt +24 -0
- sincor_x402-0.1.0/src/sincor_x402.egg-info/dependency_links.txt +1 -0
- sincor_x402-0.1.0/src/sincor_x402.egg-info/requires.txt +16 -0
- sincor_x402-0.1.0/src/sincor_x402.egg-info/top_level.txt +1 -0
- sincor_x402-0.1.0/tests/test_accepts_hedge.py +115 -0
- sincor_x402-0.1.0/tests/test_buyer_idempotency.py +149 -0
- sincor_x402-0.1.0/tests/test_full_loop.py +106 -0
- sincor_x402-0.1.0/tests/test_hub_engine.py +300 -0
- sincor_x402-0.1.0/tests/test_mcp_adapter.py +79 -0
- sincor_x402-0.1.0/tests/test_verifier.py +57 -0
|
@@ -0,0 +1,251 @@
|
|
|
1
|
+
Metadata-Version: 2.4
|
|
2
|
+
Name: sincor-x402
|
|
3
|
+
Version: 0.1.0
|
|
4
|
+
Summary: Verified x402 + MCP wrapper: escrowed, SIGIL-committed settlement for x402 endpoints and MCP tools (reference implementation — see README honesty notes)
|
|
5
|
+
Author: SINCOR Business Solutions
|
|
6
|
+
License: Apache-2.0
|
|
7
|
+
Project-URL: Homepage, https://getsincor.com
|
|
8
|
+
Project-URL: Repository, https://github.com/OrderofChaos33/SINCOR2
|
|
9
|
+
Project-URL: AgentCard, https://getsincor.com/.well-known/agent-card.json
|
|
10
|
+
Keywords: x402,mcp,escrow,agent-payments,base
|
|
11
|
+
Requires-Python: >=3.10
|
|
12
|
+
Description-Content-Type: text/markdown
|
|
13
|
+
Provides-Extra: fastapi
|
|
14
|
+
Requires-Dist: fastapi>=0.110; extra == "fastapi"
|
|
15
|
+
Provides-Extra: hub
|
|
16
|
+
Requires-Dist: web3>=6.15; extra == "hub"
|
|
17
|
+
Requires-Dist: eth-account>=0.11; extra == "hub"
|
|
18
|
+
Requires-Dist: eth-utils>=4; extra == "hub"
|
|
19
|
+
Provides-Extra: dev
|
|
20
|
+
Requires-Dist: pytest>=8; extra == "dev"
|
|
21
|
+
Requires-Dist: web3>=6.15; extra == "dev"
|
|
22
|
+
Requires-Dist: eth-account>=0.11; extra == "dev"
|
|
23
|
+
Requires-Dist: eth-utils>=4; extra == "dev"
|
|
24
|
+
Requires-Dist: eth-tester>=0.13; extra == "dev"
|
|
25
|
+
Requires-Dist: py-solc-x>=1.1; extra == "dev"
|
|
26
|
+
|
|
27
|
+
# sincor-x402 — Verified x402 + MCP wrapper
|
|
28
|
+
|
|
29
|
+
**"Pay-and-pray" is the gap.** Coinbase's x402 lets an agent pay USDC to an
|
|
30
|
+
endpoint up front — with no escrow, no committed spec, and no guarantee the
|
|
31
|
+
seller won't return garbage or time out after taking the funds. `sincor-x402`
|
|
32
|
+
wraps any x402 endpoint or MCP tool in the missing piece:
|
|
33
|
+
|
|
34
|
+
```
|
|
35
|
+
request → HTTP 402 (escrow terms + committed spec hash)
|
|
36
|
+
→ payment verified → funds held in escrow
|
|
37
|
+
→ work executes (timeout-bounded)
|
|
38
|
+
→ result verified against the committed spec (SIGIL-style commitment)
|
|
39
|
+
→ SEAL release on pass · refund on fail or timeout
|
|
40
|
+
```
|
|
41
|
+
|
|
42
|
+
## Honesty first — what is real and what is not
|
|
43
|
+
|
|
44
|
+
* **Live on getsincor.com today:** CNP task bidding/auctions, pool
|
|
45
|
+
settlement, and KYA, plus the platform's own x402 seller/buyer code this
|
|
46
|
+
package's wire shapes are modelled on.
|
|
47
|
+
* **Integration-pilot framing:** SEAL (EIP-712 settlement) and SIGIL
|
|
48
|
+
(verification) are the platform concepts this wrapper's interfaces are
|
|
49
|
+
shaped around. This package implements them as **pluggable interfaces
|
|
50
|
+
with labelled local reference implementations** (`LocalReferencePlatform`,
|
|
51
|
+
`LocalFacilitator`, `ReferenceVerifier`). They do NOT talk to a live
|
|
52
|
+
SIGIL service or a production escrow endpoint — none is wired yet.
|
|
53
|
+
* **No SINCOR escrow contract is deployed on Base mainnet.** Nothing in
|
|
54
|
+
this package moves real funds. Do not represent it otherwise.
|
|
55
|
+
|
|
56
|
+
## Networks & constants
|
|
57
|
+
|
|
58
|
+
* **Base mainnet:** chain ID **8453**
|
|
59
|
+
* **Base Sepolia:** chain ID **84532** — testnet rehearsal only
|
|
60
|
+
* **USDC on Base:** `0x833589fCD6eDb6E08f4c7C32D4f71b54bdA02913`
|
|
61
|
+
* **Live agent card:** https://getsincor.com/.well-known/agent-card.json
|
|
62
|
+
|
|
63
|
+
Nothing in this package is deployed on either network — these are the
|
|
64
|
+
chain IDs and contract constants the wire shapes and the Hub engine are
|
|
65
|
+
written against, not live deployments.
|
|
66
|
+
|
|
67
|
+
## Install
|
|
68
|
+
|
|
69
|
+
```bash
|
|
70
|
+
pip install sincor-x402 # core: zero dependencies, stdlib only
|
|
71
|
+
pip install "sincor-x402[fastapi]" # + the FastAPI adapter
|
|
72
|
+
```
|
|
73
|
+
|
|
74
|
+
## Server: wrap an endpoint (2 lines)
|
|
75
|
+
|
|
76
|
+
```python
|
|
77
|
+
from sincor_x402 import VerifiedEndpoint, PaymentRequirement
|
|
78
|
+
endpoint = VerifiedEndpoint(summarize, requirement=REQ, spec={"required_keys": ["summary"]})
|
|
79
|
+
```
|
|
80
|
+
|
|
81
|
+
`REQ` is your price/terms (`PaymentRequirement(scheme="exact", network="base",
|
|
82
|
+
max_amount_required="10000", asset=<USDC on Base>, pay_to=<your address>,
|
|
83
|
+
max_timeout_seconds=60)`). Feed requests to `endpoint.handle(headers=..., kwargs=...)`:
|
|
84
|
+
no payment header → a 402 challenge carrying your `accepts[]` + escrow terms;
|
|
85
|
+
a paid request runs the full escrow → execute → verify → release/refund loop.
|
|
86
|
+
|
|
87
|
+
With FastAPI (`sincor-x402[fastapi]`):
|
|
88
|
+
|
|
89
|
+
```python
|
|
90
|
+
from sincor_x402.fastapi_adapter import sincor_verified
|
|
91
|
+
@app.post("/summarize")
|
|
92
|
+
@sincor_verified(requirement=REQ, spec={"required_keys": ["summary"]})
|
|
93
|
+
def summarize(payload: dict): ...
|
|
94
|
+
```
|
|
95
|
+
|
|
96
|
+
## MCP: wrap a tool (2 lines)
|
|
97
|
+
|
|
98
|
+
```python
|
|
99
|
+
from sincor_x402 import verified_tool
|
|
100
|
+
@verified_tool(requirement=REQ, spec={"required_keys": ["answer"]})
|
|
101
|
+
def lookup(symbol: str): ...
|
|
102
|
+
```
|
|
103
|
+
|
|
104
|
+
No MCP SDK is imported or required: the wrapped tool exposes
|
|
105
|
+
`tool_definition()` (an MCP `tools/list`-shaped descriptor) and
|
|
106
|
+
`call_paid(payment_header=..., **kwargs)` for your host's `tools/call`
|
|
107
|
+
handler, plus a best-effort `register(server)` for FastMCP-style hosts.
|
|
108
|
+
|
|
109
|
+
## Buyer: pay a wrapped endpoint — exactly once per instruction
|
|
110
|
+
|
|
111
|
+
```python
|
|
112
|
+
from sincor_x402 import VerifiedBuyer
|
|
113
|
+
buyer = VerifiedBuyer(wallet_address, signer_fn) # signer is injected; keys never enter this package
|
|
114
|
+
out = buyer.fetch(url, instruction_id="job-123", body={"q": "..."})
|
|
115
|
+
```
|
|
116
|
+
|
|
117
|
+
* **Instruction-scoped idempotency:** `instruction_id` is your unit of work.
|
|
118
|
+
Re-calling `fetch` with the same id and the same request returns the
|
|
119
|
+
recorded outcome and **never signs a second payment** — a historical x402
|
|
120
|
+
retry bug produced 40 payments for one request; the journal + same-payload
|
|
121
|
+
resend design here makes that impossible (covered by a 40× retry-storm test).
|
|
122
|
+
Reusing an id with a *different* request raises `IdempotencyConflict`.
|
|
123
|
+
* **Per-payee velocity cap:** distinct instructions to one `payTo` are
|
|
124
|
+
capped by count and total amount per sliding window (`VelocityCap`),
|
|
125
|
+
checked **before** signing, so a capped attempt creates no payment.
|
|
126
|
+
* **No blind retries:** an ambiguous paid response raises `AmbiguousPayment`
|
|
127
|
+
and locks the instruction rather than risking a double-pay.
|
|
128
|
+
* **Per-call cap:** challenges above `max_payment_usd` are refused unsigned.
|
|
129
|
+
|
|
130
|
+
## Server safety: the accepts[] hedge
|
|
131
|
+
|
|
132
|
+
A client echoes the terms it "accepted" inside its payment payload — and a
|
|
133
|
+
buggy or malicious client can rewrite them (lower price, swapped `payTo`).
|
|
134
|
+
This server **never trusts the client's copy**: it matches the claim against
|
|
135
|
+
its own retained `accepts[]` entry on scheme, network, amount, asset,
|
|
136
|
+
payTo, and timeout, rejects mismatches before the facilitator is touched,
|
|
137
|
+
and passes **its own** `PaymentRequirement` object to the facilitator.
|
|
138
|
+
Price-rewrite and payTo-swap attacks have dedicated negative tests.
|
|
139
|
+
|
|
140
|
+
## Hub settlement engine (`sincor_x402.hub_engine`)
|
|
141
|
+
|
|
142
|
+
A client for the **SincorSettlementHub** contract interface from the
|
|
143
|
+
2026-11-11 spec drop — the on-chain counterpart to the wrapper above.
|
|
144
|
+
Requires the `hub` extra: `pip install "sincor-x402[hub]"`.
|
|
145
|
+
|
|
146
|
+
* **Buyer side:** `create_buyer_seal_header(...)` signs an EIP-712 SEAL
|
|
147
|
+
envelope (`SealAuthorization` under domain `SINCOR_SEAL` / `"1"`, nonce
|
|
148
|
+
read fresh from the Hub's `nonces(buyer)`) and returns it as a base64
|
|
149
|
+
X-PAYMENT header, scheme `sincor-seal-v1`. The buyer's key stays with
|
|
150
|
+
the injected signer callable; the package never holds keys.
|
|
151
|
+
* **Settler side:** `verify_and_settle(...)` runs the package's
|
|
152
|
+
`Verifier` seam against the committed spec first, then submits
|
|
153
|
+
`verifyAndSettleSeal` through an injected relayer signer and parses the
|
|
154
|
+
`SealSettled` event (gross / net payout / protocol fee).
|
|
155
|
+
* **SIGIL canon on this path:** `keccak256(taskId ‖ canonical-JSON
|
|
156
|
+
output)` — the EVM-native hash, matching the Hub's own typehashes.
|
|
157
|
+
|
|
158
|
+
### Status: the Hub is NOT deployed
|
|
159
|
+
|
|
160
|
+
The Hub contract exists today only as an unverified spec-drop draft;
|
|
161
|
+
**deploying it is a founder gate** and nothing here deploys anything. The
|
|
162
|
+
engine is proven end-to-end against a *local* test chain
|
|
163
|
+
(`tests/test_hub_engine.py`): a vendored test copy of the Hub + mock USDC
|
|
164
|
+
on eth-tester, Python-signed SEALs settling through the real Solidity
|
|
165
|
+
`ecrecover` path.
|
|
166
|
+
|
|
167
|
+
### Setup step buyers must not skip: the USDC allowance
|
|
168
|
+
|
|
169
|
+
The SEAL signature alone moves **nothing** — the Hub pulls funds with
|
|
170
|
+
ERC-20 `transferFrom` at settlement time, and nothing is escrowed at
|
|
171
|
+
authorization time. Before a buyer's first settlement, the buyer must
|
|
172
|
+
`approve` the Hub on the USDC contract for at least the seal amount. The
|
|
173
|
+
engine preflights this and raises `AllowanceInsufficient` naming the exact
|
|
174
|
+
approval step instead of letting the settlement revert on-chain.
|
|
175
|
+
|
|
176
|
+
### Trust model — stated plainly
|
|
177
|
+
|
|
178
|
+
The Hub **does not verify work output**: it records whatever
|
|
179
|
+
`sigilCommitment` the submitter hands it, and never checks the delivered
|
|
180
|
+
output against the seal's `expectedSchemaHash` on-chain. On-chain
|
|
181
|
+
`isTaskVerified` therefore proves only that a settlement was recorded
|
|
182
|
+
with that commitment. The enforcement layer is this engine, off-chain:
|
|
183
|
+
it recomputes the SIGIL commitment from the delivered result, requires
|
|
184
|
+
the seal's `expectedSchemaHash` to equal the committed spec hash, runs
|
|
185
|
+
the `Verifier`, and **refuses to submit when verification fails — the Hub
|
|
186
|
+
is never called**. Anyone submitting directly to the Hub bypasses that
|
|
187
|
+
enforcement; the Hub alone is not a verification system.
|
|
188
|
+
|
|
189
|
+
### Fee canon
|
|
190
|
+
|
|
191
|
+
The locked SINCOR platform fee is **5% (500 bps)**. The spec-drop draft
|
|
192
|
+
hardcodes 100 bps — a known defect. The vendored test copy under
|
|
193
|
+
`tests/fixtures/` sets `protocolFeeBps = 500` (change recorded in its
|
|
194
|
+
header), and the chain tests assert the canon split on-chain: provider
|
|
195
|
+
95% / treasury 5%.
|
|
196
|
+
|
|
197
|
+
Lower effective rates exist only as negotiated enterprise/volume
|
|
198
|
+
arrangements applied at the platform layer — never as a different code
|
|
199
|
+
or docs default. The default in this package's code, tests, and docs is
|
|
200
|
+
500 bps.
|
|
201
|
+
|
|
202
|
+
## Plugging in real SIGIL / SEAL / facilitator services
|
|
203
|
+
|
|
204
|
+
Three protocols, three seams — implement one method each and pass the
|
|
205
|
+
object in (`verifier=`, `platform=`, `facilitator=`):
|
|
206
|
+
|
|
207
|
+
* `Verifier.verify(*, spec, spec_hash, result)` — judge a delivered result
|
|
208
|
+
against the spec committed before payment; fail closed if
|
|
209
|
+
`sha256(spec) != spec_hash`. A SIGIL service client drops in here.
|
|
210
|
+
* `PlatformClient.open_escrow / release / refund` — the SEAL settlement
|
|
211
|
+
surface. `LocalReferencePlatform` is an in-memory ledger with
|
|
212
|
+
settle-exactly-once semantics; a production client implements the same
|
|
213
|
+
protocol against the real platform when its endpoint ships.
|
|
214
|
+
* `FacilitatorClient.verify_and_hold(*, payment_payload, requirement)` —
|
|
215
|
+
x402 facilitator verification (e.g. Coinbase CDP). `LocalFacilitator`
|
|
216
|
+
checks structure only; it does not verify EIP-712 signatures or touch a
|
|
217
|
+
chain, and is labelled accordingly.
|
|
218
|
+
|
|
219
|
+
## Tests
|
|
220
|
+
|
|
221
|
+
```bash
|
|
222
|
+
pip install pytest && python -m pytest # 46 tests, no network
|
|
223
|
+
```
|
|
224
|
+
|
|
225
|
+
Full loop with mocked platform + mocked facilitator: good result →
|
|
226
|
+
release; garbage → refund; timeout → refund; exception → refund; 40× retry
|
|
227
|
+
storms → exactly one payment; accepts[] rewrite attacks → rejected. Plus
|
|
228
|
+
the Hub engine's cross-implementation suite (needs the `dev` extra:
|
|
229
|
+
web3 + eth-tester + py-solc-x): Python-signed SEALs settling through the
|
|
230
|
+
compiled Solidity Hub on a local chain — canon 95/5 fee split,
|
|
231
|
+
`isTaskVerified`, and the Hub's own reverts (wrong signer →
|
|
232
|
+
`InvalidSealSignature`, expired → `SealExpired`, replay → `InvalidNonce`),
|
|
233
|
+
with the engine-refusal and allowance-preflight paths proven to never
|
|
234
|
+
reach the chain.
|
|
235
|
+
|
|
236
|
+
## PyPI publish checklist (founder gate — do NOT publish without sign-off)
|
|
237
|
+
|
|
238
|
+
- [ ] Founder approves publishing under the `sincor-x402` name
|
|
239
|
+
- [ ] Confirm PyPI name availability / claim the name (and `sincor-mcp` if used)
|
|
240
|
+
- [ ] Bump `version` in `pyproject.toml`; tag the release commit
|
|
241
|
+
- [ ] `python -m build` → inspect sdist + wheel contents (no stray files)
|
|
242
|
+
- [ ] Upload to TestPyPI first; install from TestPyPI in a clean venv; run tests
|
|
243
|
+
- [ ] Verify README renders on the PyPI project page (honesty section intact)
|
|
244
|
+
- [ ] Publish to PyPI via a project-scoped API token stored in the vault —
|
|
245
|
+
never a password, never a token in the repo
|
|
246
|
+
- [ ] Post-publish: `pip install sincor-x402` in a clean venv and smoke-test
|
|
247
|
+
the 2-line server + MCP examples from this README
|
|
248
|
+
|
|
249
|
+
## License
|
|
250
|
+
|
|
251
|
+
Apache-2.0 (declared in `pyproject.toml`).
|
|
@@ -0,0 +1,225 @@
|
|
|
1
|
+
# sincor-x402 — Verified x402 + MCP wrapper
|
|
2
|
+
|
|
3
|
+
**"Pay-and-pray" is the gap.** Coinbase's x402 lets an agent pay USDC to an
|
|
4
|
+
endpoint up front — with no escrow, no committed spec, and no guarantee the
|
|
5
|
+
seller won't return garbage or time out after taking the funds. `sincor-x402`
|
|
6
|
+
wraps any x402 endpoint or MCP tool in the missing piece:
|
|
7
|
+
|
|
8
|
+
```
|
|
9
|
+
request → HTTP 402 (escrow terms + committed spec hash)
|
|
10
|
+
→ payment verified → funds held in escrow
|
|
11
|
+
→ work executes (timeout-bounded)
|
|
12
|
+
→ result verified against the committed spec (SIGIL-style commitment)
|
|
13
|
+
→ SEAL release on pass · refund on fail or timeout
|
|
14
|
+
```
|
|
15
|
+
|
|
16
|
+
## Honesty first — what is real and what is not
|
|
17
|
+
|
|
18
|
+
* **Live on getsincor.com today:** CNP task bidding/auctions, pool
|
|
19
|
+
settlement, and KYA, plus the platform's own x402 seller/buyer code this
|
|
20
|
+
package's wire shapes are modelled on.
|
|
21
|
+
* **Integration-pilot framing:** SEAL (EIP-712 settlement) and SIGIL
|
|
22
|
+
(verification) are the platform concepts this wrapper's interfaces are
|
|
23
|
+
shaped around. This package implements them as **pluggable interfaces
|
|
24
|
+
with labelled local reference implementations** (`LocalReferencePlatform`,
|
|
25
|
+
`LocalFacilitator`, `ReferenceVerifier`). They do NOT talk to a live
|
|
26
|
+
SIGIL service or a production escrow endpoint — none is wired yet.
|
|
27
|
+
* **No SINCOR escrow contract is deployed on Base mainnet.** Nothing in
|
|
28
|
+
this package moves real funds. Do not represent it otherwise.
|
|
29
|
+
|
|
30
|
+
## Networks & constants
|
|
31
|
+
|
|
32
|
+
* **Base mainnet:** chain ID **8453**
|
|
33
|
+
* **Base Sepolia:** chain ID **84532** — testnet rehearsal only
|
|
34
|
+
* **USDC on Base:** `0x833589fCD6eDb6E08f4c7C32D4f71b54bdA02913`
|
|
35
|
+
* **Live agent card:** https://getsincor.com/.well-known/agent-card.json
|
|
36
|
+
|
|
37
|
+
Nothing in this package is deployed on either network — these are the
|
|
38
|
+
chain IDs and contract constants the wire shapes and the Hub engine are
|
|
39
|
+
written against, not live deployments.
|
|
40
|
+
|
|
41
|
+
## Install
|
|
42
|
+
|
|
43
|
+
```bash
|
|
44
|
+
pip install sincor-x402 # core: zero dependencies, stdlib only
|
|
45
|
+
pip install "sincor-x402[fastapi]" # + the FastAPI adapter
|
|
46
|
+
```
|
|
47
|
+
|
|
48
|
+
## Server: wrap an endpoint (2 lines)
|
|
49
|
+
|
|
50
|
+
```python
|
|
51
|
+
from sincor_x402 import VerifiedEndpoint, PaymentRequirement
|
|
52
|
+
endpoint = VerifiedEndpoint(summarize, requirement=REQ, spec={"required_keys": ["summary"]})
|
|
53
|
+
```
|
|
54
|
+
|
|
55
|
+
`REQ` is your price/terms (`PaymentRequirement(scheme="exact", network="base",
|
|
56
|
+
max_amount_required="10000", asset=<USDC on Base>, pay_to=<your address>,
|
|
57
|
+
max_timeout_seconds=60)`). Feed requests to `endpoint.handle(headers=..., kwargs=...)`:
|
|
58
|
+
no payment header → a 402 challenge carrying your `accepts[]` + escrow terms;
|
|
59
|
+
a paid request runs the full escrow → execute → verify → release/refund loop.
|
|
60
|
+
|
|
61
|
+
With FastAPI (`sincor-x402[fastapi]`):
|
|
62
|
+
|
|
63
|
+
```python
|
|
64
|
+
from sincor_x402.fastapi_adapter import sincor_verified
|
|
65
|
+
@app.post("/summarize")
|
|
66
|
+
@sincor_verified(requirement=REQ, spec={"required_keys": ["summary"]})
|
|
67
|
+
def summarize(payload: dict): ...
|
|
68
|
+
```
|
|
69
|
+
|
|
70
|
+
## MCP: wrap a tool (2 lines)
|
|
71
|
+
|
|
72
|
+
```python
|
|
73
|
+
from sincor_x402 import verified_tool
|
|
74
|
+
@verified_tool(requirement=REQ, spec={"required_keys": ["answer"]})
|
|
75
|
+
def lookup(symbol: str): ...
|
|
76
|
+
```
|
|
77
|
+
|
|
78
|
+
No MCP SDK is imported or required: the wrapped tool exposes
|
|
79
|
+
`tool_definition()` (an MCP `tools/list`-shaped descriptor) and
|
|
80
|
+
`call_paid(payment_header=..., **kwargs)` for your host's `tools/call`
|
|
81
|
+
handler, plus a best-effort `register(server)` for FastMCP-style hosts.
|
|
82
|
+
|
|
83
|
+
## Buyer: pay a wrapped endpoint — exactly once per instruction
|
|
84
|
+
|
|
85
|
+
```python
|
|
86
|
+
from sincor_x402 import VerifiedBuyer
|
|
87
|
+
buyer = VerifiedBuyer(wallet_address, signer_fn) # signer is injected; keys never enter this package
|
|
88
|
+
out = buyer.fetch(url, instruction_id="job-123", body={"q": "..."})
|
|
89
|
+
```
|
|
90
|
+
|
|
91
|
+
* **Instruction-scoped idempotency:** `instruction_id` is your unit of work.
|
|
92
|
+
Re-calling `fetch` with the same id and the same request returns the
|
|
93
|
+
recorded outcome and **never signs a second payment** — a historical x402
|
|
94
|
+
retry bug produced 40 payments for one request; the journal + same-payload
|
|
95
|
+
resend design here makes that impossible (covered by a 40× retry-storm test).
|
|
96
|
+
Reusing an id with a *different* request raises `IdempotencyConflict`.
|
|
97
|
+
* **Per-payee velocity cap:** distinct instructions to one `payTo` are
|
|
98
|
+
capped by count and total amount per sliding window (`VelocityCap`),
|
|
99
|
+
checked **before** signing, so a capped attempt creates no payment.
|
|
100
|
+
* **No blind retries:** an ambiguous paid response raises `AmbiguousPayment`
|
|
101
|
+
and locks the instruction rather than risking a double-pay.
|
|
102
|
+
* **Per-call cap:** challenges above `max_payment_usd` are refused unsigned.
|
|
103
|
+
|
|
104
|
+
## Server safety: the accepts[] hedge
|
|
105
|
+
|
|
106
|
+
A client echoes the terms it "accepted" inside its payment payload — and a
|
|
107
|
+
buggy or malicious client can rewrite them (lower price, swapped `payTo`).
|
|
108
|
+
This server **never trusts the client's copy**: it matches the claim against
|
|
109
|
+
its own retained `accepts[]` entry on scheme, network, amount, asset,
|
|
110
|
+
payTo, and timeout, rejects mismatches before the facilitator is touched,
|
|
111
|
+
and passes **its own** `PaymentRequirement` object to the facilitator.
|
|
112
|
+
Price-rewrite and payTo-swap attacks have dedicated negative tests.
|
|
113
|
+
|
|
114
|
+
## Hub settlement engine (`sincor_x402.hub_engine`)
|
|
115
|
+
|
|
116
|
+
A client for the **SincorSettlementHub** contract interface from the
|
|
117
|
+
2026-11-11 spec drop — the on-chain counterpart to the wrapper above.
|
|
118
|
+
Requires the `hub` extra: `pip install "sincor-x402[hub]"`.
|
|
119
|
+
|
|
120
|
+
* **Buyer side:** `create_buyer_seal_header(...)` signs an EIP-712 SEAL
|
|
121
|
+
envelope (`SealAuthorization` under domain `SINCOR_SEAL` / `"1"`, nonce
|
|
122
|
+
read fresh from the Hub's `nonces(buyer)`) and returns it as a base64
|
|
123
|
+
X-PAYMENT header, scheme `sincor-seal-v1`. The buyer's key stays with
|
|
124
|
+
the injected signer callable; the package never holds keys.
|
|
125
|
+
* **Settler side:** `verify_and_settle(...)` runs the package's
|
|
126
|
+
`Verifier` seam against the committed spec first, then submits
|
|
127
|
+
`verifyAndSettleSeal` through an injected relayer signer and parses the
|
|
128
|
+
`SealSettled` event (gross / net payout / protocol fee).
|
|
129
|
+
* **SIGIL canon on this path:** `keccak256(taskId ‖ canonical-JSON
|
|
130
|
+
output)` — the EVM-native hash, matching the Hub's own typehashes.
|
|
131
|
+
|
|
132
|
+
### Status: the Hub is NOT deployed
|
|
133
|
+
|
|
134
|
+
The Hub contract exists today only as an unverified spec-drop draft;
|
|
135
|
+
**deploying it is a founder gate** and nothing here deploys anything. The
|
|
136
|
+
engine is proven end-to-end against a *local* test chain
|
|
137
|
+
(`tests/test_hub_engine.py`): a vendored test copy of the Hub + mock USDC
|
|
138
|
+
on eth-tester, Python-signed SEALs settling through the real Solidity
|
|
139
|
+
`ecrecover` path.
|
|
140
|
+
|
|
141
|
+
### Setup step buyers must not skip: the USDC allowance
|
|
142
|
+
|
|
143
|
+
The SEAL signature alone moves **nothing** — the Hub pulls funds with
|
|
144
|
+
ERC-20 `transferFrom` at settlement time, and nothing is escrowed at
|
|
145
|
+
authorization time. Before a buyer's first settlement, the buyer must
|
|
146
|
+
`approve` the Hub on the USDC contract for at least the seal amount. The
|
|
147
|
+
engine preflights this and raises `AllowanceInsufficient` naming the exact
|
|
148
|
+
approval step instead of letting the settlement revert on-chain.
|
|
149
|
+
|
|
150
|
+
### Trust model — stated plainly
|
|
151
|
+
|
|
152
|
+
The Hub **does not verify work output**: it records whatever
|
|
153
|
+
`sigilCommitment` the submitter hands it, and never checks the delivered
|
|
154
|
+
output against the seal's `expectedSchemaHash` on-chain. On-chain
|
|
155
|
+
`isTaskVerified` therefore proves only that a settlement was recorded
|
|
156
|
+
with that commitment. The enforcement layer is this engine, off-chain:
|
|
157
|
+
it recomputes the SIGIL commitment from the delivered result, requires
|
|
158
|
+
the seal's `expectedSchemaHash` to equal the committed spec hash, runs
|
|
159
|
+
the `Verifier`, and **refuses to submit when verification fails — the Hub
|
|
160
|
+
is never called**. Anyone submitting directly to the Hub bypasses that
|
|
161
|
+
enforcement; the Hub alone is not a verification system.
|
|
162
|
+
|
|
163
|
+
### Fee canon
|
|
164
|
+
|
|
165
|
+
The locked SINCOR platform fee is **5% (500 bps)**. The spec-drop draft
|
|
166
|
+
hardcodes 100 bps — a known defect. The vendored test copy under
|
|
167
|
+
`tests/fixtures/` sets `protocolFeeBps = 500` (change recorded in its
|
|
168
|
+
header), and the chain tests assert the canon split on-chain: provider
|
|
169
|
+
95% / treasury 5%.
|
|
170
|
+
|
|
171
|
+
Lower effective rates exist only as negotiated enterprise/volume
|
|
172
|
+
arrangements applied at the platform layer — never as a different code
|
|
173
|
+
or docs default. The default in this package's code, tests, and docs is
|
|
174
|
+
500 bps.
|
|
175
|
+
|
|
176
|
+
## Plugging in real SIGIL / SEAL / facilitator services
|
|
177
|
+
|
|
178
|
+
Three protocols, three seams — implement one method each and pass the
|
|
179
|
+
object in (`verifier=`, `platform=`, `facilitator=`):
|
|
180
|
+
|
|
181
|
+
* `Verifier.verify(*, spec, spec_hash, result)` — judge a delivered result
|
|
182
|
+
against the spec committed before payment; fail closed if
|
|
183
|
+
`sha256(spec) != spec_hash`. A SIGIL service client drops in here.
|
|
184
|
+
* `PlatformClient.open_escrow / release / refund` — the SEAL settlement
|
|
185
|
+
surface. `LocalReferencePlatform` is an in-memory ledger with
|
|
186
|
+
settle-exactly-once semantics; a production client implements the same
|
|
187
|
+
protocol against the real platform when its endpoint ships.
|
|
188
|
+
* `FacilitatorClient.verify_and_hold(*, payment_payload, requirement)` —
|
|
189
|
+
x402 facilitator verification (e.g. Coinbase CDP). `LocalFacilitator`
|
|
190
|
+
checks structure only; it does not verify EIP-712 signatures or touch a
|
|
191
|
+
chain, and is labelled accordingly.
|
|
192
|
+
|
|
193
|
+
## Tests
|
|
194
|
+
|
|
195
|
+
```bash
|
|
196
|
+
pip install pytest && python -m pytest # 46 tests, no network
|
|
197
|
+
```
|
|
198
|
+
|
|
199
|
+
Full loop with mocked platform + mocked facilitator: good result →
|
|
200
|
+
release; garbage → refund; timeout → refund; exception → refund; 40× retry
|
|
201
|
+
storms → exactly one payment; accepts[] rewrite attacks → rejected. Plus
|
|
202
|
+
the Hub engine's cross-implementation suite (needs the `dev` extra:
|
|
203
|
+
web3 + eth-tester + py-solc-x): Python-signed SEALs settling through the
|
|
204
|
+
compiled Solidity Hub on a local chain — canon 95/5 fee split,
|
|
205
|
+
`isTaskVerified`, and the Hub's own reverts (wrong signer →
|
|
206
|
+
`InvalidSealSignature`, expired → `SealExpired`, replay → `InvalidNonce`),
|
|
207
|
+
with the engine-refusal and allowance-preflight paths proven to never
|
|
208
|
+
reach the chain.
|
|
209
|
+
|
|
210
|
+
## PyPI publish checklist (founder gate — do NOT publish without sign-off)
|
|
211
|
+
|
|
212
|
+
- [ ] Founder approves publishing under the `sincor-x402` name
|
|
213
|
+
- [ ] Confirm PyPI name availability / claim the name (and `sincor-mcp` if used)
|
|
214
|
+
- [ ] Bump `version` in `pyproject.toml`; tag the release commit
|
|
215
|
+
- [ ] `python -m build` → inspect sdist + wheel contents (no stray files)
|
|
216
|
+
- [ ] Upload to TestPyPI first; install from TestPyPI in a clean venv; run tests
|
|
217
|
+
- [ ] Verify README renders on the PyPI project page (honesty section intact)
|
|
218
|
+
- [ ] Publish to PyPI via a project-scoped API token stored in the vault —
|
|
219
|
+
never a password, never a token in the repo
|
|
220
|
+
- [ ] Post-publish: `pip install sincor-x402` in a clean venv and smoke-test
|
|
221
|
+
the 2-line server + MCP examples from this README
|
|
222
|
+
|
|
223
|
+
## License
|
|
224
|
+
|
|
225
|
+
Apache-2.0 (declared in `pyproject.toml`).
|
|
@@ -0,0 +1,32 @@
|
|
|
1
|
+
[build-system]
|
|
2
|
+
requires = ["setuptools>=68"]
|
|
3
|
+
build-backend = "setuptools.build_meta"
|
|
4
|
+
|
|
5
|
+
[project]
|
|
6
|
+
name = "sincor-x402"
|
|
7
|
+
version = "0.1.0"
|
|
8
|
+
description = "Verified x402 + MCP wrapper: escrowed, SIGIL-committed settlement for x402 endpoints and MCP tools (reference implementation — see README honesty notes)"
|
|
9
|
+
readme = "README.md"
|
|
10
|
+
requires-python = ">=3.10"
|
|
11
|
+
license = { text = "Apache-2.0" }
|
|
12
|
+
authors = [{ name = "SINCOR Business Solutions" }]
|
|
13
|
+
keywords = ["x402", "mcp", "escrow", "agent-payments", "base"]
|
|
14
|
+
dependencies = []
|
|
15
|
+
|
|
16
|
+
[project.urls]
|
|
17
|
+
Homepage = "https://getsincor.com"
|
|
18
|
+
Repository = "https://github.com/OrderofChaos33/SINCOR2"
|
|
19
|
+
AgentCard = "https://getsincor.com/.well-known/agent-card.json"
|
|
20
|
+
|
|
21
|
+
[project.optional-dependencies]
|
|
22
|
+
fastapi = ["fastapi>=0.110"]
|
|
23
|
+
hub = ["web3>=6.15", "eth-account>=0.11", "eth-utils>=4"]
|
|
24
|
+
dev = ["pytest>=8", "web3>=6.15", "eth-account>=0.11", "eth-utils>=4",
|
|
25
|
+
"eth-tester>=0.13", "py-solc-x>=1.1"]
|
|
26
|
+
|
|
27
|
+
[tool.setuptools.packages.find]
|
|
28
|
+
where = ["src"]
|
|
29
|
+
|
|
30
|
+
[tool.pytest.ini_options]
|
|
31
|
+
testpaths = ["tests"]
|
|
32
|
+
pythonpath = ["src"]
|
|
@@ -0,0 +1,42 @@
|
|
|
1
|
+
"""sincor-x402 — Verified x402 + MCP wrapper (reference implementation).
|
|
2
|
+
|
|
3
|
+
Turns a plain function, FastAPI endpoint, or MCP tool into an escrowed,
|
|
4
|
+
SIGIL-committed, SEAL-settled paid call: 402 → escrow → verify → release,
|
|
5
|
+
refund on fail/timeout. See README.md for the honesty notes: escrow here
|
|
6
|
+
runs through a pluggable PlatformClient whose only implementation in this
|
|
7
|
+
package is a labelled local reference; no Base-mainnet contract is
|
|
8
|
+
deployed by this package.
|
|
9
|
+
"""
|
|
10
|
+
from .buyer import VerifiedBuyer, VelocityCap
|
|
11
|
+
from .errors import (AcceptsMismatch, AllowanceInsufficient, AmbiguousPayment,
|
|
12
|
+
EscrowTimeout, IdempotencyConflict, MalformedChallenge,
|
|
13
|
+
PaymentCapExceeded, SettlementReverted, SincorX402Error,
|
|
14
|
+
UnsupportedPayment, VelocityCapExceeded, VerificationFailed)
|
|
15
|
+
from .facilitator import FacilitatorClient, FacilitatorResult, LocalFacilitator
|
|
16
|
+
from .hub_engine import (SEAL_SCHEME, HubSettlementEngine,
|
|
17
|
+
HubSettlementReceipt, SealEnvelope,
|
|
18
|
+
compute_sigil_commitment, to_bytes32)
|
|
19
|
+
from .mcp_adapter import VerifiedTool, verified_tool
|
|
20
|
+
from .platform import EscrowRecord, LocalReferencePlatform, PlatformClient
|
|
21
|
+
from .server import HandlerResponse, VerifiedEndpoint, verified_function
|
|
22
|
+
from .types import (USDC_BASE_ADDRESS, EscrowTerms, PaymentRequirement,
|
|
23
|
+
sha256_hex)
|
|
24
|
+
from .verifier import ReferenceVerifier, VerificationResult, Verifier
|
|
25
|
+
|
|
26
|
+
__version__ = "0.1.0"
|
|
27
|
+
|
|
28
|
+
__all__ = [
|
|
29
|
+
"VerifiedEndpoint", "verified_function", "HandlerResponse",
|
|
30
|
+
"VerifiedTool", "verified_tool",
|
|
31
|
+
"VerifiedBuyer", "VelocityCap",
|
|
32
|
+
"PaymentRequirement", "EscrowTerms", "USDC_BASE_ADDRESS", "sha256_hex",
|
|
33
|
+
"PlatformClient", "LocalReferencePlatform", "EscrowRecord",
|
|
34
|
+
"FacilitatorClient", "LocalFacilitator", "FacilitatorResult",
|
|
35
|
+
"Verifier", "ReferenceVerifier", "VerificationResult",
|
|
36
|
+
"HubSettlementEngine", "HubSettlementReceipt", "SealEnvelope",
|
|
37
|
+
"SEAL_SCHEME", "compute_sigil_commitment", "to_bytes32",
|
|
38
|
+
"SincorX402Error", "PaymentCapExceeded", "UnsupportedPayment",
|
|
39
|
+
"MalformedChallenge", "AmbiguousPayment", "IdempotencyConflict",
|
|
40
|
+
"VelocityCapExceeded", "AcceptsMismatch", "VerificationFailed",
|
|
41
|
+
"EscrowTimeout", "AllowanceInsufficient", "SettlementReverted",
|
|
42
|
+
]
|