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.
- walletcap-0.2.0/.circleci/config.yml +35 -0
- walletcap-0.2.0/.github/workflows/ci.yml +47 -0
- walletcap-0.2.0/.github/workflows/publish.yml +35 -0
- walletcap-0.2.0/.gitignore +17 -0
- walletcap-0.2.0/LICENSE +201 -0
- walletcap-0.2.0/NOTICE +11 -0
- walletcap-0.2.0/PKG-INFO +312 -0
- walletcap-0.2.0/README.md +289 -0
- walletcap-0.2.0/js/LICENSE +201 -0
- walletcap-0.2.0/js/NOTICE +11 -0
- walletcap-0.2.0/js/README.md +36 -0
- walletcap-0.2.0/js/agentic.mjs +434 -0
- walletcap-0.2.0/js/cdp.mjs +129 -0
- walletcap-0.2.0/js/cli.mjs +257 -0
- walletcap-0.2.0/js/errors.mjs +40 -0
- walletcap-0.2.0/js/index.d.ts +237 -0
- walletcap-0.2.0/js/index.mjs +319 -0
- walletcap-0.2.0/js/ledger.mjs +513 -0
- walletcap-0.2.0/js/money.mjs +195 -0
- walletcap-0.2.0/js/package.json +65 -0
- walletcap-0.2.0/js/tests/agentic.test.mjs +224 -0
- walletcap-0.2.0/js/tests/budget.test.mjs +204 -0
- walletcap-0.2.0/js/tests/cli.test.mjs +80 -0
- walletcap-0.2.0/js/tests/money.test.mjs +66 -0
- walletcap-0.2.0/js/tests/processes.test.mjs +49 -0
- walletcap-0.2.0/js/tests/support/fake-awal/dist/index.js +6 -0
- walletcap-0.2.0/js/tests/support/fake-awal/dist/ipcClient.js +22 -0
- walletcap-0.2.0/js/tests/support/fake-awal/package.json +1 -0
- walletcap-0.2.0/js/tests/support/fake-payments-mcp.mjs +24 -0
- walletcap-0.2.0/js/tests/support/payer.mjs +29 -0
- walletcap-0.2.0/js/tests/support/race.mjs +35 -0
- walletcap-0.2.0/js/tests/support/x402-kit.mjs +39 -0
- walletcap-0.2.0/js/tests/support/x402-payer.mjs +33 -0
- walletcap-0.2.0/js/tests/x402-fetch.test.mjs +59 -0
- walletcap-0.2.0/js/tests/x402.test.mjs +124 -0
- walletcap-0.2.0/js/x402.mjs +207 -0
- walletcap-0.2.0/pyproject.toml +37 -0
- walletcap-0.2.0/pytest.ini +2 -0
- walletcap-0.2.0/scripts/ci.sh +34 -0
- walletcap-0.2.0/solidity/evm/check.mjs +185 -0
- walletcap-0.2.0/solidity/evm/package.json +23 -0
- walletcap-0.2.0/solidity/foundry.toml +8 -0
- walletcap-0.2.0/solidity/src/AccountCapSpender.sol +151 -0
- walletcap-0.2.0/solidity/test/AccountCapSpender.t.sol +135 -0
- walletcap-0.2.0/solidity/test/Mocks.sol +55 -0
- walletcap-0.2.0/src/walletcap/__init__.py +74 -0
- walletcap-0.2.0/src/walletcap/__main__.py +284 -0
- walletcap-0.2.0/src/walletcap/budget.py +537 -0
- walletcap-0.2.0/src/walletcap/errors.py +52 -0
- walletcap-0.2.0/src/walletcap/ledger.py +762 -0
- walletcap-0.2.0/src/walletcap/money.py +236 -0
- walletcap-0.2.0/src/walletcap/py.typed +0 -0
- walletcap-0.2.0/tests/test_budget.py +312 -0
- 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/**
|
walletcap-0.2.0/LICENSE
ADDED
|
@@ -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.
|
walletcap-0.2.0/PKG-INFO
ADDED
|
@@ -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
|
+
[](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.
|