walletcap 0.2.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.
Files changed (54) hide show
  1. walletcap-0.2.0/.circleci/config.yml +35 -0
  2. walletcap-0.2.0/.github/workflows/ci.yml +47 -0
  3. walletcap-0.2.0/.github/workflows/publish.yml +35 -0
  4. walletcap-0.2.0/.gitignore +17 -0
  5. walletcap-0.2.0/LICENSE +201 -0
  6. walletcap-0.2.0/NOTICE +11 -0
  7. walletcap-0.2.0/PKG-INFO +312 -0
  8. walletcap-0.2.0/README.md +289 -0
  9. walletcap-0.2.0/js/LICENSE +201 -0
  10. walletcap-0.2.0/js/NOTICE +11 -0
  11. walletcap-0.2.0/js/README.md +36 -0
  12. walletcap-0.2.0/js/agentic.mjs +434 -0
  13. walletcap-0.2.0/js/cdp.mjs +129 -0
  14. walletcap-0.2.0/js/cli.mjs +257 -0
  15. walletcap-0.2.0/js/errors.mjs +40 -0
  16. walletcap-0.2.0/js/index.d.ts +237 -0
  17. walletcap-0.2.0/js/index.mjs +319 -0
  18. walletcap-0.2.0/js/ledger.mjs +513 -0
  19. walletcap-0.2.0/js/money.mjs +195 -0
  20. walletcap-0.2.0/js/package.json +65 -0
  21. walletcap-0.2.0/js/tests/agentic.test.mjs +224 -0
  22. walletcap-0.2.0/js/tests/budget.test.mjs +204 -0
  23. walletcap-0.2.0/js/tests/cli.test.mjs +80 -0
  24. walletcap-0.2.0/js/tests/money.test.mjs +66 -0
  25. walletcap-0.2.0/js/tests/processes.test.mjs +49 -0
  26. walletcap-0.2.0/js/tests/support/fake-awal/dist/index.js +6 -0
  27. walletcap-0.2.0/js/tests/support/fake-awal/dist/ipcClient.js +22 -0
  28. walletcap-0.2.0/js/tests/support/fake-awal/package.json +1 -0
  29. walletcap-0.2.0/js/tests/support/fake-payments-mcp.mjs +24 -0
  30. walletcap-0.2.0/js/tests/support/payer.mjs +29 -0
  31. walletcap-0.2.0/js/tests/support/race.mjs +35 -0
  32. walletcap-0.2.0/js/tests/support/x402-kit.mjs +39 -0
  33. walletcap-0.2.0/js/tests/support/x402-payer.mjs +33 -0
  34. walletcap-0.2.0/js/tests/x402-fetch.test.mjs +59 -0
  35. walletcap-0.2.0/js/tests/x402.test.mjs +124 -0
  36. walletcap-0.2.0/js/x402.mjs +207 -0
  37. walletcap-0.2.0/pyproject.toml +37 -0
  38. walletcap-0.2.0/pytest.ini +2 -0
  39. walletcap-0.2.0/scripts/ci.sh +34 -0
  40. walletcap-0.2.0/solidity/evm/check.mjs +185 -0
  41. walletcap-0.2.0/solidity/evm/package.json +23 -0
  42. walletcap-0.2.0/solidity/foundry.toml +8 -0
  43. walletcap-0.2.0/solidity/src/AccountCapSpender.sol +151 -0
  44. walletcap-0.2.0/solidity/test/AccountCapSpender.t.sol +135 -0
  45. walletcap-0.2.0/solidity/test/Mocks.sol +55 -0
  46. walletcap-0.2.0/src/walletcap/__init__.py +74 -0
  47. walletcap-0.2.0/src/walletcap/__main__.py +284 -0
  48. walletcap-0.2.0/src/walletcap/budget.py +537 -0
  49. walletcap-0.2.0/src/walletcap/errors.py +52 -0
  50. walletcap-0.2.0/src/walletcap/ledger.py +762 -0
  51. walletcap-0.2.0/src/walletcap/money.py +236 -0
  52. walletcap-0.2.0/src/walletcap/py.typed +0 -0
  53. walletcap-0.2.0/tests/test_budget.py +312 -0
  54. walletcap-0.2.0/tests/test_ledger.py +277 -0
@@ -0,0 +1,35 @@
1
+ # SPDX-License-Identifier: Apache-2.0
2
+ # CircleCI runs the same gate as scripts/ci.sh, directly from the GitHub repo.
3
+ version: 2.1
4
+
5
+ jobs:
6
+ test:
7
+ docker:
8
+ - image: cimg/node:22.18
9
+ steps:
10
+ - checkout
11
+ - run:
12
+ name: JavaScript
13
+ command: |
14
+ cd js
15
+ npm install --no-audit --no-fund --no-package-lock
16
+ node --test
17
+ - run:
18
+ name: Python
19
+ command: |
20
+ sudo apt-get update -qq
21
+ sudo apt-get install -y -qq python3 python3-venv
22
+ python3 -m venv /tmp/venv
23
+ /tmp/venv/bin/pip install --quiet pytest
24
+ /tmp/venv/bin/python -m pytest -q
25
+ - run:
26
+ name: Solidity
27
+ command: |
28
+ cd solidity/evm
29
+ npm install --no-audit --no-fund --no-package-lock
30
+ node check.mjs ..
31
+
32
+ workflows:
33
+ ci:
34
+ jobs:
35
+ - test
@@ -0,0 +1,47 @@
1
+ name: CI
2
+
3
+ on:
4
+ pull_request:
5
+ push:
6
+ branches: [main]
7
+
8
+ permissions:
9
+ contents: read
10
+
11
+ jobs:
12
+ javascript:
13
+ name: JavaScript
14
+ runs-on: ubuntu-latest
15
+ container: node:22-bookworm
16
+ steps:
17
+ - uses: actions/checkout@34e114876b0b11c390a56381ad16ebd13914f8d5
18
+ - name: Install the test dependencies
19
+ run: npm install --no-audit --no-fund
20
+ working-directory: js
21
+ - name: Run the suite
22
+ run: node --test
23
+ working-directory: js
24
+
25
+ python:
26
+ name: Python
27
+ runs-on: ubuntu-latest
28
+ container: node:22-bookworm
29
+ steps:
30
+ - uses: actions/checkout@34e114876b0b11c390a56381ad16ebd13914f8d5
31
+ - uses: astral-sh/setup-uv@d0cc045d04ccac9d8b7881df0226f9e82c39688e
32
+ - name: Run the suite
33
+ # Node is on PATH here, so the cross-language tests run rather than skip.
34
+ run: uv run --with pytest --python 3.12 pytest -q
35
+
36
+ solidity:
37
+ name: Solidity
38
+ runs-on: ubuntu-latest
39
+ container: node:22-bookworm
40
+ steps:
41
+ - uses: actions/checkout@34e114876b0b11c390a56381ad16ebd13914f8d5
42
+ - name: Install the check's dependencies
43
+ run: npm install --no-audit --no-fund
44
+ working-directory: solidity/evm
45
+ - name: Drive the contract on an EVM
46
+ run: node check.mjs ..
47
+ working-directory: solidity/evm
@@ -0,0 +1,35 @@
1
+ # SPDX-License-Identifier: Apache-2.0
2
+ # Publish walletcap to PyPI when a GitHub Release is published, or by hand.
3
+ # PyPI trusted publishing matches this repository, the workflow filename
4
+ # (publish.yml) and the "pypi" environment; no API token is stored.
5
+ name: Publish
6
+
7
+ on:
8
+ release:
9
+ types: [published]
10
+ workflow_dispatch:
11
+
12
+ permissions:
13
+ contents: read
14
+ id-token: write
15
+
16
+ jobs:
17
+ pypi:
18
+ name: PyPI
19
+ runs-on: ubuntu-latest
20
+ environment: pypi
21
+ steps:
22
+ - uses: actions/checkout@34e114876b0b11c390a56381ad16ebd13914f8d5
23
+ - uses: actions/setup-python@a26af69be951a213d495a4c3e4e4022e16d87065
24
+ with:
25
+ python-version: "3.12"
26
+ - name: Run the Python suite
27
+ run: |
28
+ python -m pip install --upgrade pytest
29
+ python -m pytest -q
30
+ - name: Build the distributions
31
+ run: |
32
+ python -m pip install --upgrade build
33
+ python -m build
34
+ - name: Publish to PyPI
35
+ uses: pypa/gh-action-pypi-publish@7245906fb61fa33a13554f9d5fb856fe61758086
@@ -0,0 +1,17 @@
1
+ node_modules/
2
+ dist/
3
+ build/
4
+ *.egg-info/
5
+ __pycache__/
6
+ .venv/
7
+ .pytest_cache/
8
+ .mypy_cache/
9
+ .ruff_cache/
10
+ out/
11
+ cache/
12
+ work/
13
+
14
+ # The fake awal stand-in in the tests is committed on purpose: the CLI test
15
+ # needs its dist/ layout, and the dist/ rule above would hide it.
16
+ !js/tests/support/fake-awal/dist/
17
+ !js/tests/support/fake-awal/dist/**
@@ -0,0 +1,201 @@
1
+ Apache License
2
+ Version 2.0, January 2004
3
+ http://www.apache.org/licenses/
4
+
5
+ TERMS AND CONDITIONS FOR USE, REPRODUCTION, AND DISTRIBUTION
6
+
7
+ 1. Definitions.
8
+
9
+ "License" shall mean the terms and conditions for use, reproduction,
10
+ and distribution as defined by Sections 1 through 9 of this document.
11
+
12
+ "Licensor" shall mean the copyright owner or entity authorized by
13
+ the copyright owner that is granting the License.
14
+
15
+ "Legal Entity" shall mean the union of the acting entity and all
16
+ other entities that control, are controlled by, or are under common
17
+ control with that entity. For the purposes of this definition,
18
+ "control" means (i) the power, direct or indirect, to cause the
19
+ direction or management of such entity, whether by contract or
20
+ otherwise, or (ii) ownership of fifty percent (50%) or more of the
21
+ outstanding shares, or (iii) beneficial ownership of such entity.
22
+
23
+ "You" (or "Your") shall mean an individual or Legal Entity
24
+ exercising permissions granted by this License.
25
+
26
+ "Source" form shall mean the preferred form for making modifications,
27
+ including but not limited to software source code, documentation
28
+ source, and configuration files.
29
+
30
+ "Object" form shall mean any form resulting from mechanical
31
+ transformation or translation of a Source form, including but
32
+ not limited to compiled object code, generated documentation,
33
+ and conversions to other media types.
34
+
35
+ "Work" shall mean the work of authorship, whether in Source or
36
+ Object form, made available under the License, as indicated by a
37
+ copyright notice that is included in or attached to the work
38
+ (an example is provided in the Appendix below).
39
+
40
+ "Derivative Works" shall mean any work, whether in Source or Object
41
+ form, that is based on (or derived from) the Work and for which the
42
+ editorial revisions, annotations, elaborations, or other modifications
43
+ represent, as a whole, an original work of authorship. For the purposes
44
+ of this License, Derivative Works shall not include works that remain
45
+ separable from, or merely link (or bind by name) to the interfaces of,
46
+ the Work and Derivative Works thereof.
47
+
48
+ "Contribution" shall mean any work of authorship, including
49
+ the original version of the Work and any modifications or additions
50
+ to that Work or Derivative Works thereof, that is intentionally
51
+ submitted to Licensor for inclusion in the Work by the copyright owner
52
+ or by an individual or Legal Entity authorized to submit on behalf of
53
+ the copyright owner. For the purposes of this definition, "submitted"
54
+ means any form of electronic, verbal, or written communication sent
55
+ to the Licensor or its representatives, including but not limited to
56
+ communication on electronic mailing lists, source code control systems,
57
+ and issue tracking systems that are managed by, or on behalf of, the
58
+ Licensor for the purpose of discussing and improving the Work, but
59
+ excluding communication that is conspicuously marked or otherwise
60
+ designated in writing by the copyright owner as "Not a Contribution."
61
+
62
+ "Contributor" shall mean Licensor and any individual or Legal Entity
63
+ on behalf of whom a Contribution has been received by Licensor and
64
+ subsequently incorporated within the Work.
65
+
66
+ 2. Grant of Copyright License. Subject to the terms and conditions of
67
+ this License, each Contributor hereby grants to You a perpetual,
68
+ worldwide, non-exclusive, no-charge, royalty-free, irrevocable
69
+ copyright license to reproduce, prepare Derivative Works of,
70
+ publicly display, publicly perform, sublicense, and distribute the
71
+ Work and such Derivative Works in Source or Object form.
72
+
73
+ 3. Grant of Patent License. Subject to the terms and conditions of
74
+ this License, each Contributor hereby grants to You a perpetual,
75
+ worldwide, non-exclusive, no-charge, royalty-free, irrevocable
76
+ (except as stated in this section) patent license to make, have made,
77
+ use, offer to sell, sell, import, and otherwise transfer the Work,
78
+ where such license applies only to those patent claims licensable
79
+ by such Contributor that are necessarily infringed by their
80
+ Contribution(s) alone or by combination of their Contribution(s)
81
+ with the Work to which such Contribution(s) was submitted. If You
82
+ institute patent litigation against any entity (including a
83
+ cross-claim or counterclaim in a lawsuit) alleging that the Work
84
+ or a Contribution incorporated within the Work constitutes direct
85
+ or contributory patent infringement, then any patent licenses
86
+ granted to You under this License for that Work shall terminate
87
+ as of the date such litigation is filed.
88
+
89
+ 4. Redistribution. You may reproduce and distribute copies of the
90
+ Work or Derivative Works thereof in any medium, with or without
91
+ modifications, and in Source or Object form, provided that You
92
+ meet the following conditions:
93
+
94
+ (a) You must give any other recipients of the Work or
95
+ Derivative Works a copy of this License; and
96
+
97
+ (b) You must cause any modified files to carry prominent notices
98
+ stating that You changed the files; and
99
+
100
+ (c) You must retain, in the Source form of any Derivative Works
101
+ that You distribute, all copyright, patent, trademark, and
102
+ attribution notices from the Source form of the Work,
103
+ excluding those notices that do not pertain to any part of
104
+ the Derivative Works; and
105
+
106
+ (d) If the Work includes a "NOTICE" text file as part of its
107
+ distribution, then any Derivative Works that You distribute must
108
+ include a readable copy of the attribution notices contained
109
+ within such NOTICE file, excluding those notices that do not
110
+ pertain to any part of the Derivative Works, in at least one
111
+ of the following places: within a NOTICE text file distributed
112
+ as part of the Derivative Works; within the Source form or
113
+ documentation, if provided along with the Derivative Works; or,
114
+ within a display generated by the Derivative Works, if and
115
+ wherever such third-party notices normally appear. The contents
116
+ of the NOTICE file are for informational purposes only and
117
+ do not modify the License. You may add Your own attribution
118
+ notices within Derivative Works that You distribute, alongside
119
+ or as an addendum to the NOTICE text from the Work, provided
120
+ that such additional attribution notices cannot be construed
121
+ as modifying the License.
122
+
123
+ You may add Your own copyright statement to Your modifications and
124
+ may provide additional or different license terms and conditions
125
+ for use, reproduction, or distribution of Your modifications, or
126
+ for any such Derivative Works as a whole, provided Your use,
127
+ reproduction, and distribution of the Work otherwise complies with
128
+ the conditions stated in this License.
129
+
130
+ 5. Submission of Contributions. Unless You explicitly state otherwise,
131
+ any Contribution intentionally submitted for inclusion in the Work
132
+ by You to the Licensor shall be under the terms and conditions of
133
+ this License, without any additional terms or conditions.
134
+ Notwithstanding the above, nothing herein shall supersede or modify
135
+ the terms of any separate license agreement you may have executed
136
+ with Licensor regarding such Contributions.
137
+
138
+ 6. Trademarks. This License does not grant permission to use the trade
139
+ names, trademarks, service marks, or product names of the Licensor,
140
+ except as required for reasonable and customary use in describing the
141
+ origin of the Work and reproducing the content of the NOTICE file.
142
+
143
+ 7. Disclaimer of Warranty. Unless required by applicable law or
144
+ agreed to in writing, Licensor provides the Work (and each
145
+ Contributor provides its Contributions) on an "AS IS" BASIS,
146
+ WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or
147
+ implied, including, without limitation, any warranties or conditions
148
+ of TITLE, NON-INFRINGEMENT, MERCHANTABILITY, or FITNESS FOR A
149
+ PARTICULAR PURPOSE. You are solely responsible for determining the
150
+ appropriateness of using or redistributing the Work and assume any
151
+ risks associated with Your exercise of permissions under this License.
152
+
153
+ 8. Limitation of Liability. In no event and under no legal theory,
154
+ whether in tort (including negligence), contract, or otherwise,
155
+ unless required by applicable law (such as deliberate and grossly
156
+ negligent acts) or agreed to in writing, shall any Contributor be
157
+ liable to You for damages, including any direct, indirect, special,
158
+ incidental, or consequential damages of any character arising as a
159
+ result of this License or out of the use or inability to use the
160
+ Work (including but not limited to damages for loss of goodwill,
161
+ work stoppage, computer failure or malfunction, or any and all
162
+ other commercial damages or losses), even if such Contributor
163
+ has been advised of the possibility of such damages.
164
+
165
+ 9. Accepting Warranty or Additional Liability. While redistributing
166
+ the Work or Derivative Works thereof, You may choose to offer,
167
+ and charge a fee for, acceptance of support, warranty, indemnity,
168
+ or other liability obligations and/or rights consistent with this
169
+ License. However, in accepting such obligations, You may act only
170
+ on Your own behalf and on Your sole responsibility, not on behalf
171
+ of any other Contributor, and only if You agree to indemnify,
172
+ defend, and hold each Contributor harmless for any liability
173
+ incurred by, or claims asserted against, such Contributor by reason
174
+ of your accepting any such warranty or additional liability.
175
+
176
+ END OF TERMS AND CONDITIONS
177
+
178
+ APPENDIX: How to apply the Apache License to your work.
179
+
180
+ To apply the Apache License to your work, attach the following
181
+ boilerplate notice, with the fields enclosed by brackets "[]"
182
+ replaced with your own identifying information. (Don't include
183
+ the brackets!) The text should be enclosed in the appropriate
184
+ comment syntax for the file format. We also recommend that a
185
+ file or class name and description of purpose be included on the
186
+ same "printed page" as the copyright notice for easier
187
+ identification within third-party archives.
188
+
189
+ Copyright [yyyy] [name of copyright owner]
190
+
191
+ Licensed under the Apache License, Version 2.0 (the "License");
192
+ you may not use this file except in compliance with the License.
193
+ You may obtain a copy of the License at
194
+
195
+ http://www.apache.org/licenses/LICENSE-2.0
196
+
197
+ Unless required by applicable law or agreed to in writing, software
198
+ distributed under the License is distributed on an "AS IS" BASIS,
199
+ WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
200
+ See the License for the specific language governing permissions and
201
+ limitations under the License.
walletcap-0.2.0/NOTICE ADDED
@@ -0,0 +1,11 @@
1
+ SPDX-License-Identifier: Apache-2.0
2
+
3
+ walletcap, by ShadowPath
4
+ Copyright 2026 Coriolis Labs Inc.
5
+
6
+ This distribution is licensed under the Apache License 2.0. It is separate from
7
+ the ShadowPath product, which is proprietary and is not included here.
8
+
9
+ AccountCapSpender's ISpendPermissionManager interface restates the struct layout
10
+ of Coinbase's SpendPermissionManager so the contract can call it; it contains no
11
+ Coinbase code.
@@ -0,0 +1,312 @@
1
+ Metadata-Version: 2.5
2
+ Name: walletcap
3
+ Version: 0.2.0
4
+ Summary: One spend budget per wallet for AI agents: counted across every process, replica, key, permission and payment rail.
5
+ Project-URL: Homepage, https://github.com/coriolislabs/WalletCap
6
+ Project-URL: Repository, https://github.com/coriolislabs/WalletCap
7
+ Project-URL: Issues, https://github.com/coriolislabs/WalletCap/issues
8
+ Author: Coriolis Labs Inc.
9
+ License-Expression: Apache-2.0
10
+ License-File: LICENSE
11
+ License-File: NOTICE
12
+ Keywords: agent-payments,ai-agents,budget,guardrails,spend-limit,stablecoin,usdc,x402
13
+ Classifier: Development Status :: 4 - Beta
14
+ Classifier: Intended Audience :: Developers
15
+ Classifier: Programming Language :: Python :: 3
16
+ Classifier: Programming Language :: Python :: 3.11
17
+ Classifier: Programming Language :: Python :: 3.12
18
+ Classifier: Programming Language :: Python :: 3.13
19
+ Classifier: Topic :: Software Development :: Libraries
20
+ Classifier: Typing :: Typed
21
+ Requires-Python: >=3.11
22
+ Description-Content-Type: text/markdown
23
+
24
+ <!-- SPDX-License-Identifier: Apache-2.0 -->
25
+ # walletcap
26
+
27
+ [![CircleCI](https://dl.circleci.com/status-badge/img/circleci/9kf5Wo9wYAuLZHsV9nFTQC/Pcdc3JTt5shsZfYpip6SBB/tree/main.svg?style=svg)](https://app.circleci.com/pipelines/circleci/9kf5Wo9wYAuLZHsV9nFTQC/Pcdc3JTt5shsZfYpip6SBB)
28
+
29
+ **Your agent's spending limit forgets. This one doesn't.** By ShadowPath.
30
+
31
+ Agent payment kits count "spent so far" per process, per key, per permission or per
32
+ period. So a restart, a second replica, one more key, or the next period hands the agent a
33
+ fresh budget. walletcap counts **per wallet**: one budget that every process,
34
+ replica, key, permission and payment rail spends from.
35
+
36
+ ```js
37
+ import { openBudget } from 'walletcap';
38
+
39
+ const budget = openBudget({ wallet: account.address, perPayment: '$50', perDay: '$150' });
40
+
41
+ await budget.pay('$12.50', () => sendUsdc(payee, '12.50')); // refused past $150 in any 24h, restarts included
42
+ ```
43
+
44
+ ```python
45
+ from walletcap import open_budget
46
+
47
+ budget = open_budget(agent_address, per_payment="$50", per_day="$150")
48
+
49
+ with budget.pay("$12.50"):
50
+ send_usdc(payee, "12.50")
51
+ ```
52
+
53
+ `npm install walletcap` (Node 22.13+) · `pip install walletcap` (Python 3.11+) ·
54
+ no dependencies · Apache-2.0
55
+
56
+ ## Why a per-wallet count
57
+
58
+ Most agent spend controls keep "spent so far" next to something temporary or repeatable: a
59
+ process, a browser window, an API key, a delegate, one permission, or a fixed calendar period.
60
+ The money, though, sits in one wallet. When the count and the wallet don't match, ordinary
61
+ events (a restart, a second replica, another key or permission, the next period) can hand the
62
+ agent a fresh budget against the same money.
63
+
64
+ ShadowPath measures spend controls against that pattern, on owned test chains with the vendors'
65
+ own code. Findings go to the vendors first; this package is the fix, not the findings.
66
+
67
+ ## The fix: four rules
68
+
69
+ 1. **Count per wallet.** The budget belongs to the account the money leaves from, not to a
70
+ process, key, permission or delegate. Every payment from that wallet reserves against one
71
+ number. Checksummed, lowercase and CAIP-10 forms of an address are one wallet, so they
72
+ can't split it.
73
+ 2. **Count over a rolling window.** "$150 a day" means any 24 hours: there is no period
74
+ boundary to straddle.
75
+ 3. **Changing a limit never resets the count.** Raising $150 to $200 after spending $150
76
+ leaves $50, not $200. A restart can lower a limit but never raise one: if the code comes
77
+ back asking for looser limits (a kit's defaults, say), the stored limits stay and you get a
78
+ warning with the command that raises them on purpose.
79
+ 4. **Fail closed.** If the count can't be read, nothing pays. A payment whose outcome is
80
+ unknown keeps counting, because a payment that failed halfway may still have moved money.
81
+
82
+ The count lives in a SQLite file that every process on the machine shares, and each payment is
83
+ reserved inside one write transaction, so replicas racing for the last dollar can't both win.
84
+ JavaScript and Python read and write the same file.
85
+
86
+ ## Use it
87
+
88
+ ### Any payment, any rail
89
+
90
+ `pay()` reserves the amount, runs your payment, and records it as settled.
91
+
92
+ ```js
93
+ const tx = await budget.pay('$5', async reservation => {
94
+ return wallet.sendTransaction(request); // reservation.id makes a good idempotency key
95
+ }, { to: payee, note: 'weather API' });
96
+ ```
97
+
98
+ If the payment throws, it still counts, because it may have left the wallet. If you know it
99
+ didn't, say so and the amount comes back:
100
+
101
+ ```js
102
+ import { notSent } from 'walletcap';
103
+
104
+ throw notSent(error); // inside the payment function
105
+ ```
106
+
107
+ In Python, `raise NotSent("card declined")` inside the `with` block. Set `payment.tx` in the
108
+ block to keep the transaction hash in the history, and use `async with` in async code.
109
+
110
+ `budget.check('$5')` answers `{ ok, reason, remaining }` without spending. `budget.reserve()`
111
+ gives you a reservation to `commit()` or `release()` yourself.
112
+
113
+ ### x402
114
+
115
+ `guard()` puts the budget in front of any x402 v2 client from `@x402/core`, so it works under
116
+ `@x402/fetch` and alongside Coinbase's CDP spend controls.
117
+
118
+ ```js
119
+ import { x402Client } from '@x402/core/client';
120
+ import { ExactEvmScheme } from '@x402/evm/exact/client';
121
+ import { wrapFetchWithPayment } from '@x402/fetch';
122
+
123
+ const client = budget.guard(new x402Client().register('eip155:8453', new ExactEvmScheme(account)));
124
+ const fetchPaid = wrapFetchWithPayment(fetch, client);
125
+ ```
126
+
127
+ - Each payment is checked before it is signed, and reserved once its payload exists and every
128
+ other control has passed.
129
+ - It counts as spent when the server confirms settlement. A payee that answers "payment
130
+ failed" still holds your signed authorization and could settle it until it expires, so the
131
+ payment keeps counting until then.
132
+ - USDC on Base, Ethereum, Polygon, Arbitrum, Optimism, Avalanche and Solana is priced out of
133
+ the box, plus Base Sepolia, Arbitrum Sepolia and Solana devnet. Add another USD stablecoin with
134
+ `budget.guard(client, { assets: [{ network, asset, decimals }] })`. Anything else is refused,
135
+ not guessed.
136
+ - `@x402/fetch` rewraps errors from payment creation, so a refusal reaches you as an `Error`
137
+ whose message starts `Failed to create payment payload:` and carries the budget's reason.
138
+
139
+ ### Coinbase CDP spend controls
140
+
141
+ `applySpendControls` builds on an x402 client, so `budget.guard(client)` works alongside it, and
142
+ it's the one to use:
143
+
144
+ - **It counts every token.** `guard()` prices the USD stablecoins it knows and refuses
145
+ anything else, whatever assets the client would otherwise accept.
146
+ - **It reserves in one transaction.** Each payment is checked and reserved inside one write
147
+ transaction on the budget file, so processes and replicas sharing the file can't both take
148
+ the last dollar.
149
+ - **It keeps counting what the payee calls failed** until the authorization expires, then frees
150
+ it on its own.
151
+
152
+ To keep CDP's own count durable and shared as well, swap its in-memory store for walletcap's,
153
+ set `maxCumulativeSpend` (CDP only records through its store when a cumulative cap is set), and
154
+ set `allowedAssets`: CDP never consults its store for a token outside the cap.
155
+
156
+ ```js
157
+ applySpendControls(client, {
158
+ maxAmountPerPayment: { atomic: 50_000_000n, asset: USDC },
159
+ maxCumulativeSpend: { atomic: 150_000_000n, asset: USDC },
160
+ maxCumulativeSpendWindow: '24h',
161
+ allowedAssets: [USDC],
162
+ store: budget.cdpStore({ asset: USDC }),
163
+ });
164
+ budget.guard(client);
165
+ ```
166
+
167
+ walletcap's store re-checks the budget when CDP records a payment, so it holds across replicas
168
+ on its own. When CDP removes a payment from its store, the store can't tell why, so the payment
169
+ stays counted as in-flight until you check the chain and run `npx walletcap release <id>`. Use
170
+ CDP 1.56.0 or later.
171
+
172
+ ### Coinbase Agentic Wallet (awal CLI and MCP server)
173
+
174
+ Run the Agentic Wallet's CLI and MCP server through walletcap, and every payment counts in the
175
+ wallet's budget, whichever process, session or machine makes it:
176
+
177
+ ```console
178
+ $ npx walletcap set --wallet 0xYourAgentWallet --per-payment 0.05 --per-day 0.15
179
+ $ npx walletcap awal x402 pay https://api.example.com/data # instead of: npx awal x402 pay …
180
+ $ npx walletcap awal send 0.05 0xRecipient --chain base # instead of: npx awal send …
181
+ ```
182
+
183
+ For an MCP client such as Claude Desktop, start Coinbase's server behind walletcap. It runs
184
+ `npx -y @coinbase/payments-mcp`; put another command after `--`.
185
+
186
+ ```json
187
+ { "mcpServers": { "payments": { "command": "npx", "args": ["walletcap", "awal-mcp", "--wallet", "0xYourAgentWallet"] } } }
188
+ ```
189
+
190
+ - `send` and `x402 pay` (over MCP, `send` and `make_http_request_with_x402`) are checked against
191
+ the wallet's budget before the wallet signs anything, and counted. Every other command and tool
192
+ is the wallet's own.
193
+ - An x402 payment is priced from the endpoint's own 402 answer, and the wallet is capped at exactly
194
+ that amount, so it refuses to sign for more.
195
+ - A payment the payee confirms as settled counts for one window. One it doesn't confirm keeps
196
+ counting until you release it: the wallet signs whatever validity the payee asks for, and
197
+ walletcap can't see it. Check the chain, then `npx walletcap release <id>`.
198
+ - The wallet's own refusal, before it signs, gives the amount back. Any other failure keeps
199
+ counting.
200
+ - Only USDC is counted; a send in any other asset is refused.
201
+ - Machines share a budget when they share its file (a shared volume). Separate hosts without one
202
+ wait for the Postgres backend.
203
+ - It binds the payments that go through it. The wallet app takes requests from any process of
204
+ the same user, so keep the real `awal` out of an agent's reach.
205
+
206
+ ### Coinbase AgentKit
207
+
208
+ AgentKit's x402 actions build their own x402 client on every call. Put the budget on that client
209
+ with a three-line subclass, and every payment is checked against the wallet's budget before it
210
+ is signed, whatever terms the merchant sends:
211
+
212
+ ```js
213
+ import { X402ActionProvider } from '@coinbase/agentkit';
214
+
215
+ class BudgetedX402ActionProvider extends X402ActionProvider {
216
+ async createX402Client(walletProvider) {
217
+ return budget.guard(await super.createX402Client(walletProvider));
218
+ }
219
+ }
220
+ // AgentKit.from({ walletProvider, actionProviders: [new BudgetedX402ActionProvider()] })
221
+ ```
222
+
223
+ ### Coinbase Spend Permissions, on chain
224
+
225
+ For a limit the agent's own code can't go around, `solidity/src/AccountCapSpender.sol` is the
226
+ spender for Spend Permissions. Deploy it with the SpendPermissionManager, the agent's
227
+ address, the cap, the window, the per-payment max and the payees allowed. Grant Spend
228
+ Permissions to the contract instead of the agent; the agent calls
229
+ `pay(permission, amount, payee)`. Every permission the account grants it then shares one
230
+ rolling cap.
231
+
232
+ ## The owner's controls
233
+
234
+ ```console
235
+ $ npx walletcap status
236
+ Budget file /home/you/.local/state/walletcap/budget.sqlite
237
+
238
+ 0xd797…ca02: $0.15 spent of $0.15 per 24h, $0.00 left · at most $0.05 a payment
239
+
240
+ $ npx walletcap set --per-day 0.20 # what was spent still counts
241
+ $ npx walletcap freeze # every process, every rail, now
242
+ $ npx walletcap unfreeze
243
+ $ npx walletcap history
244
+ $ npx walletcap release <id> # an in-flight payment you know never left
245
+ ```
246
+
247
+ Python ships the same commands as `walletcap`. Amounts are dollars; leave out the `$`
248
+ sign, which your shell would expand. Add `--wallet` when the file holds more than one budget,
249
+ and `--json` for scripts.
250
+
251
+ ## Where the budget lives
252
+
253
+ The default file is per user: `~/.local/state/walletcap/budget.sqlite` on Linux,
254
+ `~/Library/Application Support/walletcap/budget.sqlite` on macOS, and
255
+ `%LOCALAPPDATA%\walletcap\budget.sqlite` on Windows. Set `WALLETCAP_PATH`, or pass `path`, to put it elsewhere. Both languages
256
+ compute the same default.
257
+
258
+ The budget is as durable as that file. In a container, put it on a volume that outlives the
259
+ container, or a redeploy starts a new file. For agents that can touch the filesystem, open the
260
+ budget with `create: false` and create it once, as the owner, with `npx walletcap set`:
261
+ then a missing file refuses every payment instead of starting a new budget.
262
+
263
+ ## What it doesn't do
264
+
265
+ - **It binds the payments that go through it.** An agent with a shell on the same machine can
266
+ go around it: call the wallet directly, or delete the file. To bound an agent like that, keep
267
+ the budget out of its reach (another OS user, another service) or enforce on chain, as
268
+ `AccountCapSpender` does for Spend Permissions.
269
+ - **One file is one host, or a shared volume.** A Postgres backend for replicas on separate
270
+ hosts is next.
271
+ - **It can't see a revoke that lands later in the same block,** and for x402 schemes that settle
272
+ later (batch vouchers), a payment counts from when the server confirms it.
273
+ - **Some x402 payments can't be confirmed.** `@x402/fetch` doesn't report v1 settlements
274
+ back, and only the exact EVM scheme carries an expiry the guard can read. An unconfirmed
275
+ payment keeps counting until its authorization expires plus one window, or, with no expiry,
276
+ until you `release` it.
277
+ - Amounts are micro-dollars and must fit in 64 bits.
278
+
279
+ ## This repository
280
+
281
+ | Path | What it is |
282
+ |---|---|
283
+ | `js/` | the npm package `walletcap`: the budget, the x402 guard, the CDP store, the CLI, and the Agentic Wallet CLI wrapper and MCP proxy |
284
+ | `src/walletcap/` | the PyPI package `walletcap`, on the same file format |
285
+ | `solidity/` | `AccountCapSpender`, the on-chain cap for Coinbase Spend Permissions |
286
+ | `tests/` | the Python suite, including the tests that drive both languages against one budget file |
287
+
288
+ ```console
289
+ $ cd js && npm install && node --test # 50 tests
290
+ $ uv run --with pytest pytest -q # 53 tests, from the repository root
291
+ $ cd solidity/evm && npm install && node check.mjs .. # the contract on an EVM, no Foundry needed
292
+ $ cd solidity && forge test # the contract's canonical tests, if you have Foundry
293
+ ```
294
+
295
+ The JavaScript suite runs the x402 guard against x402's own client and fetch wrapper, and runs
296
+ restarts and racing replicas as separate processes, and drives the Agentic Wallet wrapper and
297
+ MCP proxy against stand-ins for Coinbase's wallet and MCP server. The Python suite covers the ledger, the API,
298
+ the CLI and one budget shared between both languages. `solidity/evm/check.mjs` compiles the
299
+ contract with solc-js and drives it on an in-process EVM, covering what the Foundry tests cover.
300
+
301
+ One test is **not** here: the integration test that drives Coinbase's CDP spend controls through
302
+ the shared store. It runs against a vendored build of Coinbase's SDK and lives in ShadowPath's
303
+ own repository. Porting it to the published `@coinbase/cdp-sdk` is open work.
304
+
305
+ ## Prove it holds in your deployment
306
+
307
+ This package closes the gaps ShadowPath measured. Whether your configuration is closed is a
308
+ measurement: ShadowPath runs your agent with every control switched on and certifies, in
309
+ dollars, what can still get past your budget, signed and re-checkable offline.
310
+ [Get a certificate for your deployment](mailto:paul@coriolislabs.ca).
311
+
312
+ Apache-2.0. Copyright 2026 Coriolis Labs Inc.