@tallyforagents/sdk 0.1.0
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.
- package/LICENSE +21 -0
- package/README.md +97 -0
- package/dist/index.cjs +280 -0
- package/dist/index.cjs.map +1 -0
- package/dist/index.d.cts +196 -0
- package/dist/index.d.ts +196 -0
- package/dist/index.js +271 -0
- package/dist/index.js.map +1 -0
- package/package.json +61 -0
package/LICENSE
ADDED
|
@@ -0,0 +1,21 @@
|
|
|
1
|
+
MIT License
|
|
2
|
+
|
|
3
|
+
Copyright (c) 2026 Tally
|
|
4
|
+
|
|
5
|
+
Permission is hereby granted, free of charge, to any person obtaining a copy
|
|
6
|
+
of this software and associated documentation files (the "Software"), to deal
|
|
7
|
+
in the Software without restriction, including without limitation the rights
|
|
8
|
+
to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
|
|
9
|
+
copies of the Software, and to permit persons to whom the Software is
|
|
10
|
+
furnished to do so, subject to the following conditions:
|
|
11
|
+
|
|
12
|
+
The above copyright notice and this permission notice shall be included in all
|
|
13
|
+
copies or substantial portions of the Software.
|
|
14
|
+
|
|
15
|
+
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
|
|
16
|
+
IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
|
|
17
|
+
FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
|
|
18
|
+
AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
|
|
19
|
+
LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
|
|
20
|
+
OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
|
|
21
|
+
SOFTWARE.
|
package/README.md
ADDED
|
@@ -0,0 +1,97 @@
|
|
|
1
|
+
# @tallyforagents/sdk
|
|
2
|
+
|
|
3
|
+
The official TypeScript SDK for [Tally](https://www.tallyforagents.com) — give AI agents USDC wallets and scoped programmable spending on Base.
|
|
4
|
+
|
|
5
|
+
Tally is non-custodial: you hold your wallet, you set the spending rules, and you grant any external agent scoped permission to send. Tally never custodies funds and never runs the agent.
|
|
6
|
+
|
|
7
|
+
> **Public preview** on Base Sepolia today. Base mainnet support is on the way — [join the waitlist](https://www.tallyforagents.com/#mainnet).
|
|
8
|
+
|
|
9
|
+
## Install
|
|
10
|
+
|
|
11
|
+
```bash
|
|
12
|
+
npm install @tallyforagents/sdk
|
|
13
|
+
# or
|
|
14
|
+
pnpm add @tallyforagents/sdk
|
|
15
|
+
# or
|
|
16
|
+
yarn add @tallyforagents/sdk
|
|
17
|
+
```
|
|
18
|
+
|
|
19
|
+
Targets Node 18+ and uses the platform's native `fetch` — zero runtime dependencies.
|
|
20
|
+
|
|
21
|
+
## Quickstart
|
|
22
|
+
|
|
23
|
+
```ts
|
|
24
|
+
import { Tally } from "@tallyforagents/sdk";
|
|
25
|
+
|
|
26
|
+
const tally = new Tally({
|
|
27
|
+
apiKey: process.env.TALLY_API_KEY!, // tly_test_… or tly_live_…
|
|
28
|
+
});
|
|
29
|
+
|
|
30
|
+
// Register (or update) an agent
|
|
31
|
+
await tally.agents.upsert({
|
|
32
|
+
id: "research-bot",
|
|
33
|
+
});
|
|
34
|
+
|
|
35
|
+
// Send a payment from one of your wallets
|
|
36
|
+
const payment = await tally.payments.create({
|
|
37
|
+
agent: "research-bot",
|
|
38
|
+
wallet: "wlt_…",
|
|
39
|
+
to: "0xRecipient…",
|
|
40
|
+
amount_usdc: "1.50",
|
|
41
|
+
});
|
|
42
|
+
|
|
43
|
+
console.log(payment.tx_hash);
|
|
44
|
+
```
|
|
45
|
+
|
|
46
|
+
Get an API key from the [Tally dashboard](https://app.tallyforagents.com) → **API keys**. Test-mode keys (`tly_test_…`) send on Base Sepolia; live-mode keys (`tly_live_…`) target Base mainnet once that's enabled on your account.
|
|
47
|
+
|
|
48
|
+
## What's in the box
|
|
49
|
+
|
|
50
|
+
| Resource | Methods |
|
|
51
|
+
|---|---|
|
|
52
|
+
| `tally.agents` | `upsert`, `list`, `get` |
|
|
53
|
+
| `tally.payments` | `create`, `get` |
|
|
54
|
+
| `tally.webhooks` | `verifySignature` (HMAC-SHA256, Stripe-style `t=…,v1=…` header) |
|
|
55
|
+
|
|
56
|
+
Typed errors map server responses to exception classes:
|
|
57
|
+
|
|
58
|
+
```ts
|
|
59
|
+
import {
|
|
60
|
+
TallyError,
|
|
61
|
+
AuthenticationError,
|
|
62
|
+
NotFoundError,
|
|
63
|
+
ValidationError,
|
|
64
|
+
RateLimitError,
|
|
65
|
+
ConflictError,
|
|
66
|
+
} from "@tallyforagents/sdk";
|
|
67
|
+
```
|
|
68
|
+
|
|
69
|
+
More method wrappers (`wallets.*`, `permissions.*`, `payments.list`, `agents.delete`, `webhooks.list/create/revoke`) are on the roadmap — see the [changelog](https://www.tallyforagents.com/docs/changelog).
|
|
70
|
+
|
|
71
|
+
## Full docs
|
|
72
|
+
|
|
73
|
+
Concepts, guides, and the complete API reference live at **https://www.tallyforagents.com/docs**:
|
|
74
|
+
|
|
75
|
+
- [Quickstart](https://www.tallyforagents.com/docs/quickstart) — first payment in under five minutes
|
|
76
|
+
- [Build a paying agent](https://www.tallyforagents.com/docs/guides/build-a-paying-agent) — OpenAI function-calling end-to-end example
|
|
77
|
+
- [Handling failures](https://www.tallyforagents.com/docs/guides/handling-failures) — error codes, retries, idempotency
|
|
78
|
+
- [Local webhook dev](https://www.tallyforagents.com/docs/guides/local-webhook-dev) — ngrok + `verifySignature` workflow
|
|
79
|
+
|
|
80
|
+
## Configuration
|
|
81
|
+
|
|
82
|
+
```ts
|
|
83
|
+
new Tally({
|
|
84
|
+
apiKey: "tly_test_…",
|
|
85
|
+
baseUrl: "https://app.tallyforagents.com", // override for local dev or self-hosting
|
|
86
|
+
fetch: customFetch, // optional custom fetch impl
|
|
87
|
+
});
|
|
88
|
+
```
|
|
89
|
+
|
|
90
|
+
## Support
|
|
91
|
+
|
|
92
|
+
- Issues: https://github.com/pkohler95/tally-v2/issues
|
|
93
|
+
- Email: `support@tallyforagents.com`
|
|
94
|
+
|
|
95
|
+
## License
|
|
96
|
+
|
|
97
|
+
MIT © Tally
|
package/dist/index.cjs
ADDED
|
@@ -0,0 +1,280 @@
|
|
|
1
|
+
'use strict';
|
|
2
|
+
|
|
3
|
+
var crypto = require('crypto');
|
|
4
|
+
|
|
5
|
+
// src/errors.ts
|
|
6
|
+
var TallyError = class extends Error {
|
|
7
|
+
type;
|
|
8
|
+
code;
|
|
9
|
+
status;
|
|
10
|
+
details;
|
|
11
|
+
constructor(opts) {
|
|
12
|
+
super(opts.message);
|
|
13
|
+
this.name = "TallyError";
|
|
14
|
+
this.type = opts.type;
|
|
15
|
+
this.code = opts.code;
|
|
16
|
+
this.status = opts.status;
|
|
17
|
+
this.details = opts.details;
|
|
18
|
+
}
|
|
19
|
+
};
|
|
20
|
+
var AuthenticationError = class extends TallyError {
|
|
21
|
+
constructor(payload, status) {
|
|
22
|
+
super({ ...payload, status });
|
|
23
|
+
this.name = "AuthenticationError";
|
|
24
|
+
}
|
|
25
|
+
};
|
|
26
|
+
var NotFoundError = class extends TallyError {
|
|
27
|
+
constructor(payload, status) {
|
|
28
|
+
super({ ...payload, status });
|
|
29
|
+
this.name = "NotFoundError";
|
|
30
|
+
}
|
|
31
|
+
};
|
|
32
|
+
var ValidationError = class extends TallyError {
|
|
33
|
+
constructor(payload, status) {
|
|
34
|
+
super({ ...payload, status });
|
|
35
|
+
this.name = "ValidationError";
|
|
36
|
+
}
|
|
37
|
+
};
|
|
38
|
+
var RateLimitError = class extends TallyError {
|
|
39
|
+
constructor(payload, status) {
|
|
40
|
+
super({ ...payload, status });
|
|
41
|
+
this.name = "RateLimitError";
|
|
42
|
+
}
|
|
43
|
+
};
|
|
44
|
+
var ConflictError = class extends TallyError {
|
|
45
|
+
constructor(payload, status) {
|
|
46
|
+
super({ ...payload, status });
|
|
47
|
+
this.name = "ConflictError";
|
|
48
|
+
}
|
|
49
|
+
};
|
|
50
|
+
function makeError(payload, status) {
|
|
51
|
+
switch (payload.type) {
|
|
52
|
+
case "unauthenticated":
|
|
53
|
+
case "forbidden":
|
|
54
|
+
return new AuthenticationError(payload, status);
|
|
55
|
+
case "not_found":
|
|
56
|
+
return new NotFoundError(payload, status);
|
|
57
|
+
case "validation_failed":
|
|
58
|
+
return new ValidationError(payload, status);
|
|
59
|
+
case "rate_limited":
|
|
60
|
+
return new RateLimitError(payload, status);
|
|
61
|
+
case "conflict":
|
|
62
|
+
return new ConflictError(payload, status);
|
|
63
|
+
default:
|
|
64
|
+
return new TallyError({ ...payload, status });
|
|
65
|
+
}
|
|
66
|
+
}
|
|
67
|
+
|
|
68
|
+
// src/client.ts
|
|
69
|
+
var TallyClient = class {
|
|
70
|
+
apiKey;
|
|
71
|
+
baseUrl;
|
|
72
|
+
#fetch;
|
|
73
|
+
constructor(opts) {
|
|
74
|
+
if (!opts.apiKey || !opts.apiKey.startsWith("tly_test_") && !opts.apiKey.startsWith("tly_live_")) {
|
|
75
|
+
throw new TallyError({
|
|
76
|
+
type: "validation_failed",
|
|
77
|
+
message: "Invalid API key. Tally keys start with 'tly_test_' (test mode) or 'tly_live_' (live mode).",
|
|
78
|
+
status: 0
|
|
79
|
+
});
|
|
80
|
+
}
|
|
81
|
+
this.apiKey = opts.apiKey;
|
|
82
|
+
this.baseUrl = (opts.baseUrl ?? "https://app.tallyforagents.com").replace(/\/$/, "");
|
|
83
|
+
this.#fetch = opts.fetch ?? globalThis.fetch;
|
|
84
|
+
if (typeof this.#fetch !== "function") {
|
|
85
|
+
throw new TallyError({
|
|
86
|
+
type: "internal",
|
|
87
|
+
message: "fetch is not available in this runtime. Provide one via `fetch` option.",
|
|
88
|
+
status: 0
|
|
89
|
+
});
|
|
90
|
+
}
|
|
91
|
+
}
|
|
92
|
+
async request(method, path, body) {
|
|
93
|
+
const url = `${this.baseUrl}${path}`;
|
|
94
|
+
const res = await this.#fetch(url, {
|
|
95
|
+
method,
|
|
96
|
+
headers: {
|
|
97
|
+
Authorization: `Bearer ${this.apiKey}`,
|
|
98
|
+
"Content-Type": "application/json",
|
|
99
|
+
Accept: "application/json"
|
|
100
|
+
},
|
|
101
|
+
body: body === void 0 ? void 0 : JSON.stringify(body)
|
|
102
|
+
});
|
|
103
|
+
if (!res.ok) {
|
|
104
|
+
let payload = {};
|
|
105
|
+
try {
|
|
106
|
+
payload = await res.json();
|
|
107
|
+
} catch {
|
|
108
|
+
}
|
|
109
|
+
const err = payload.error ?? {
|
|
110
|
+
type: "internal",
|
|
111
|
+
message: `Request failed with status ${res.status}.`
|
|
112
|
+
};
|
|
113
|
+
throw makeError(err, res.status);
|
|
114
|
+
}
|
|
115
|
+
if (res.status === 204) return void 0;
|
|
116
|
+
return await res.json();
|
|
117
|
+
}
|
|
118
|
+
};
|
|
119
|
+
|
|
120
|
+
// src/resources/agents.ts
|
|
121
|
+
var AgentsResource = class {
|
|
122
|
+
constructor(client) {
|
|
123
|
+
this.client = client;
|
|
124
|
+
}
|
|
125
|
+
client;
|
|
126
|
+
/**
|
|
127
|
+
* Creates the agent if it doesn't exist; no-op if it does.
|
|
128
|
+
*
|
|
129
|
+
* Scope: the active API key's account + mode. Same id can exist
|
|
130
|
+
* independently in test and live mode for the same account.
|
|
131
|
+
*/
|
|
132
|
+
async upsert(input) {
|
|
133
|
+
const { agent } = await this.client.request(
|
|
134
|
+
"POST",
|
|
135
|
+
"/v1/agents",
|
|
136
|
+
input
|
|
137
|
+
);
|
|
138
|
+
return agent;
|
|
139
|
+
}
|
|
140
|
+
async list() {
|
|
141
|
+
const { agents } = await this.client.request(
|
|
142
|
+
"GET",
|
|
143
|
+
"/v1/agents"
|
|
144
|
+
);
|
|
145
|
+
return agents;
|
|
146
|
+
}
|
|
147
|
+
async get(id) {
|
|
148
|
+
const { agent } = await this.client.request(
|
|
149
|
+
"GET",
|
|
150
|
+
`/v1/agents/${encodeURIComponent(id)}`
|
|
151
|
+
);
|
|
152
|
+
return agent;
|
|
153
|
+
}
|
|
154
|
+
};
|
|
155
|
+
|
|
156
|
+
// src/resources/payments.ts
|
|
157
|
+
var PaymentsResource = class {
|
|
158
|
+
constructor(client) {
|
|
159
|
+
this.client = client;
|
|
160
|
+
}
|
|
161
|
+
client;
|
|
162
|
+
/**
|
|
163
|
+
* Submits a USDC payment from one of the agent's granted wallets.
|
|
164
|
+
*
|
|
165
|
+
* Enforcement happens in two layers:
|
|
166
|
+
* 1. Tally checks policy (per-tx max, recipient/contract allowlist,
|
|
167
|
+
* expiry, daily cap) and rejects with a structured error before
|
|
168
|
+
* any Privy call.
|
|
169
|
+
* 2. Privy's secure enclave independently checks the per-tx max and
|
|
170
|
+
* recipient/contract conditions encoded in the signer's policy.
|
|
171
|
+
*
|
|
172
|
+
* Returns immediately with `status: "pending"` and the on-chain
|
|
173
|
+
* `tx_hash` once Privy accepts the signed RPC. The SDK does not wait
|
|
174
|
+
* for block confirmation; the Transaction row is updated by a
|
|
175
|
+
* separate confirmation flow (forthcoming).
|
|
176
|
+
*/
|
|
177
|
+
async create(input) {
|
|
178
|
+
const { payment } = await this.client.request(
|
|
179
|
+
"POST",
|
|
180
|
+
"/v1/payments",
|
|
181
|
+
input
|
|
182
|
+
);
|
|
183
|
+
return payment;
|
|
184
|
+
}
|
|
185
|
+
/**
|
|
186
|
+
* Fetches the current state of a payment by id. If still pending on
|
|
187
|
+
* Tally's side, this call lazily refreshes from the chain — so polling
|
|
188
|
+
* this is the canonical way to wait for `confirmed` / `failed`.
|
|
189
|
+
*/
|
|
190
|
+
async get(id) {
|
|
191
|
+
const { payment } = await this.client.request(
|
|
192
|
+
"GET",
|
|
193
|
+
`/v1/payments/${encodeURIComponent(id)}`
|
|
194
|
+
);
|
|
195
|
+
return payment;
|
|
196
|
+
}
|
|
197
|
+
};
|
|
198
|
+
var SIGNATURE_VERSION = "v1";
|
|
199
|
+
var DEFAULT_TOLERANCE_SECONDS = 300;
|
|
200
|
+
var WebhooksResource = class {
|
|
201
|
+
/**
|
|
202
|
+
* Verify a `tally-signature` header against the request body. Use in
|
|
203
|
+
* your webhook handler before processing the payload.
|
|
204
|
+
*
|
|
205
|
+
* @example
|
|
206
|
+
* ```ts
|
|
207
|
+
* const body = await req.text(); // raw, not JSON-parsed
|
|
208
|
+
* const result = tally.webhooks.verifySignature({
|
|
209
|
+
* body,
|
|
210
|
+
* header: req.headers.get("tally-signature"),
|
|
211
|
+
* secret: process.env.TALLY_WEBHOOK_SECRET!,
|
|
212
|
+
* });
|
|
213
|
+
* if (!result.ok) return new Response("invalid signature", { status: 400 });
|
|
214
|
+
* const event = JSON.parse(body);
|
|
215
|
+
* // ... handle event
|
|
216
|
+
* ```
|
|
217
|
+
*/
|
|
218
|
+
verifySignature(input) {
|
|
219
|
+
return verifySignature(input);
|
|
220
|
+
}
|
|
221
|
+
};
|
|
222
|
+
function verifySignature(input) {
|
|
223
|
+
const { body, header, secret } = input;
|
|
224
|
+
const now = input.now ?? Math.floor(Date.now() / 1e3);
|
|
225
|
+
const tolerance = input.toleranceSeconds ?? DEFAULT_TOLERANCE_SECONDS;
|
|
226
|
+
if (!header) return { ok: false, reason: "missing_header" };
|
|
227
|
+
let timestamp = null;
|
|
228
|
+
let v1 = null;
|
|
229
|
+
for (const part of header.split(",")) {
|
|
230
|
+
const [k, v] = part.split("=", 2);
|
|
231
|
+
if (k === "t") {
|
|
232
|
+
const n = Number(v);
|
|
233
|
+
if (Number.isFinite(n)) timestamp = n;
|
|
234
|
+
} else if (k === SIGNATURE_VERSION) {
|
|
235
|
+
v1 = v;
|
|
236
|
+
}
|
|
237
|
+
}
|
|
238
|
+
if (timestamp === null) return { ok: false, reason: "malformed_header" };
|
|
239
|
+
if (!v1) return { ok: false, reason: "no_v1_signature" };
|
|
240
|
+
if (Math.abs(now - timestamp) > tolerance) {
|
|
241
|
+
return { ok: false, reason: "timestamp_out_of_tolerance" };
|
|
242
|
+
}
|
|
243
|
+
const expected = crypto.createHmac("sha256", secret).update(`${timestamp}.${body}`).digest("hex");
|
|
244
|
+
if (expected.length !== v1.length) {
|
|
245
|
+
return { ok: false, reason: "signature_mismatch" };
|
|
246
|
+
}
|
|
247
|
+
const a = Buffer.from(expected, "utf8");
|
|
248
|
+
const b = Buffer.from(v1, "utf8");
|
|
249
|
+
if (!crypto.timingSafeEqual(a, b)) {
|
|
250
|
+
return { ok: false, reason: "signature_mismatch" };
|
|
251
|
+
}
|
|
252
|
+
return { ok: true };
|
|
253
|
+
}
|
|
254
|
+
|
|
255
|
+
// src/index.ts
|
|
256
|
+
var Tally = class {
|
|
257
|
+
agents;
|
|
258
|
+
payments;
|
|
259
|
+
webhooks;
|
|
260
|
+
// Expose the underlying client for advanced use (custom retries, etc.).
|
|
261
|
+
// Internal callers go through the resource classes instead.
|
|
262
|
+
client;
|
|
263
|
+
constructor(opts) {
|
|
264
|
+
this.client = new TallyClient(opts);
|
|
265
|
+
this.agents = new AgentsResource(this.client);
|
|
266
|
+
this.payments = new PaymentsResource(this.client);
|
|
267
|
+
this.webhooks = new WebhooksResource();
|
|
268
|
+
}
|
|
269
|
+
};
|
|
270
|
+
|
|
271
|
+
exports.AuthenticationError = AuthenticationError;
|
|
272
|
+
exports.ConflictError = ConflictError;
|
|
273
|
+
exports.NotFoundError = NotFoundError;
|
|
274
|
+
exports.RateLimitError = RateLimitError;
|
|
275
|
+
exports.Tally = Tally;
|
|
276
|
+
exports.TallyError = TallyError;
|
|
277
|
+
exports.ValidationError = ValidationError;
|
|
278
|
+
exports.verifySignature = verifySignature;
|
|
279
|
+
//# sourceMappingURL=index.cjs.map
|
|
280
|
+
//# sourceMappingURL=index.cjs.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"sources":["../src/errors.ts","../src/client.ts","../src/resources/agents.ts","../src/resources/payments.ts","../src/resources/webhooks.ts","../src/index.ts"],"names":["createHmac","timingSafeEqual"],"mappings":"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","file":"index.cjs","sourcesContent":["// Tally SDK error types. The server returns a consistent shape on every\n// failure — see src/lib/api-errors.ts in the web app — and the SDK turns\n// it into typed exceptions consumers can catch and branch on.\n//\n// Usage in consumer code:\n//\n// try {\n// await tally.agents.upsert({ id: \"research-bot\", display_name: \"...\" });\n// } catch (e) {\n// if (e instanceof AuthenticationError) { /* rotate key */ }\n// if (e instanceof ValidationError) { /* fix input */ }\n// throw e;\n// }\n\nexport type ApiErrorPayload = {\n type: string;\n message: string;\n code?: string;\n details?: unknown;\n};\n\nexport class TallyError extends Error {\n readonly type: string;\n readonly code?: string;\n readonly status: number;\n readonly details?: unknown;\n\n constructor(opts: {\n type: string;\n message: string;\n status: number;\n code?: string;\n details?: unknown;\n }) {\n super(opts.message);\n this.name = \"TallyError\";\n this.type = opts.type;\n this.code = opts.code;\n this.status = opts.status;\n this.details = opts.details;\n }\n}\n\nexport class AuthenticationError extends TallyError {\n constructor(payload: ApiErrorPayload, status: number) {\n super({ ...payload, status });\n this.name = \"AuthenticationError\";\n }\n}\n\nexport class NotFoundError extends TallyError {\n constructor(payload: ApiErrorPayload, status: number) {\n super({ ...payload, status });\n this.name = \"NotFoundError\";\n }\n}\n\nexport class ValidationError extends TallyError {\n constructor(payload: ApiErrorPayload, status: number) {\n super({ ...payload, status });\n this.name = \"ValidationError\";\n }\n}\n\nexport class RateLimitError extends TallyError {\n constructor(payload: ApiErrorPayload, status: number) {\n super({ ...payload, status });\n this.name = \"RateLimitError\";\n }\n}\n\nexport class ConflictError extends TallyError {\n constructor(payload: ApiErrorPayload, status: number) {\n super({ ...payload, status });\n this.name = \"ConflictError\";\n }\n}\n\n// Maps server error.type → typed exception class.\nexport function makeError(payload: ApiErrorPayload, status: number): TallyError {\n switch (payload.type) {\n case \"unauthenticated\":\n case \"forbidden\":\n return new AuthenticationError(payload, status);\n case \"not_found\":\n return new NotFoundError(payload, status);\n case \"validation_failed\":\n return new ValidationError(payload, status);\n case \"rate_limited\":\n return new RateLimitError(payload, status);\n case \"conflict\":\n return new ConflictError(payload, status);\n default:\n return new TallyError({ ...payload, status });\n }\n}\n","import { makeError, TallyError, type ApiErrorPayload } from \"./errors\";\n\n// Thin HTTP client used by every resource. Centralizes:\n// - Authorization header\n// - JSON request/response handling\n// - Error mapping (server error.type → typed exception)\n//\n// Uses Node 18+'s native fetch. Works in browsers too if you ever want to\n// hit the API from one — though the more common pattern is server-to-server\n// from the consumer's agent infrastructure.\n\nexport type ClientOptions = {\n /** Tally API key (begins with `tly_`). */\n apiKey: string;\n /** Base URL of the Tally API. Defaults to https://app.tallyforagents.com. Override for local dev or self-hosting. */\n baseUrl?: string;\n /** Custom fetch implementation. Useful for tests or non-Node runtimes. */\n fetch?: typeof fetch;\n};\n\nexport class TallyClient {\n readonly apiKey: string;\n readonly baseUrl: string;\n readonly #fetch: typeof fetch;\n\n constructor(opts: ClientOptions) {\n if (\n !opts.apiKey ||\n (!opts.apiKey.startsWith(\"tly_test_\") && !opts.apiKey.startsWith(\"tly_live_\"))\n ) {\n throw new TallyError({\n type: \"validation_failed\",\n message:\n \"Invalid API key. Tally keys start with 'tly_test_' (test mode) or 'tly_live_' (live mode).\",\n status: 0,\n });\n }\n this.apiKey = opts.apiKey;\n this.baseUrl = (opts.baseUrl ?? \"https://app.tallyforagents.com\").replace(/\\/$/, \"\");\n this.#fetch = opts.fetch ?? globalThis.fetch;\n if (typeof this.#fetch !== \"function\") {\n throw new TallyError({\n type: \"internal\",\n message: \"fetch is not available in this runtime. Provide one via `fetch` option.\",\n status: 0,\n });\n }\n }\n\n async request<T>(\n method: \"GET\" | \"POST\" | \"DELETE\",\n path: string,\n body?: unknown,\n ): Promise<T> {\n const url = `${this.baseUrl}${path}`;\n const res = await this.#fetch(url, {\n method,\n headers: {\n Authorization: `Bearer ${this.apiKey}`,\n \"Content-Type\": \"application/json\",\n Accept: \"application/json\",\n },\n body: body === undefined ? undefined : JSON.stringify(body),\n });\n\n if (!res.ok) {\n let payload: { error?: ApiErrorPayload } = {};\n try {\n payload = await res.json();\n } catch {\n // Non-JSON error body — fall back to a generic message.\n }\n const err = payload.error ?? {\n type: \"internal\",\n message: `Request failed with status ${res.status}.`,\n };\n throw makeError(err, res.status);\n }\n\n // 204 No Content (e.g., future delete endpoints)\n if (res.status === 204) return undefined as T;\n return (await res.json()) as T;\n }\n}\n","import type { TallyClient } from \"../client\";\n\nexport type Agent = {\n /** Stable user-provided identifier (e.g., \"research-bot\"). */\n id: string;\n /** \"active\" — at least one activated (non-revoked) signer is attached.\n * \"no_permissions\" — no activated signer (may still have pending\n * grants — see pending_signers). */\n status: \"no_permissions\" | \"active\";\n mode: \"test\" | \"live\";\n /** ISO 8601 timestamp. */\n created_at: string;\n /** Count of non-revoked, activated signers. */\n active_signers: number;\n /** Count of non-revoked, not-yet-activated signers. */\n pending_signers: number;\n};\n\nexport type AgentUpsertInput = {\n /** User-provided stable string identifier (e.g., \"research-bot\"). */\n id: string;\n};\n\n// agents.upsert / list / get — Phase 2's full surface.\n// Future phases will add: agents.delete, agents.permissions.*, etc.\n\nexport class AgentsResource {\n constructor(private readonly client: TallyClient) {}\n\n /**\n * Creates the agent if it doesn't exist; no-op if it does.\n *\n * Scope: the active API key's account + mode. Same id can exist\n * independently in test and live mode for the same account.\n */\n async upsert(input: AgentUpsertInput): Promise<Agent> {\n const { agent } = await this.client.request<{ agent: Agent }>(\n \"POST\",\n \"/v1/agents\",\n input,\n );\n return agent;\n }\n\n async list(): Promise<Agent[]> {\n const { agents } = await this.client.request<{ agents: Agent[] }>(\n \"GET\",\n \"/v1/agents\",\n );\n return agents;\n }\n\n async get(id: string): Promise<Agent> {\n const { agent } = await this.client.request<{ agent: Agent }>(\n \"GET\",\n `/v1/agents/${encodeURIComponent(id)}`,\n );\n return agent;\n }\n}\n","import type { TallyClient } from \"../client\";\n\nexport type Payment = {\n id: string;\n /** \"pending\" until the on-chain tx confirms; updated by a follow-up\n * GET (planned) or webhook (Phase 6). */\n status: \"pending\" | \"confirmed\" | \"failed\";\n /** Transaction hash on the underlying chain. Set as soon as Privy\n * accepts the signed RPC; the SDK does not wait for receipt. */\n tx_hash: string | null;\n amount_usdc: string;\n to: string;\n from: string;\n memo: string | null;\n idempotency_key: string | null;\n created_at: string;\n};\n\nexport type PaymentCreateInput = {\n /** Agent's user-provided id (the externalId), e.g. \"research-bot\". */\n agent_id: string;\n /** Sender wallet address. Must be in this API key's account+mode and\n * must have an active permission grant for the agent. */\n wallet: string;\n /** Recipient EVM address. */\n to: string;\n /** Decimal USDC amount as a string, e.g. \"10\" or \"2.50\". */\n amount_usdc: string;\n /** Optional memo (max 200 chars), stored alongside the transaction. */\n memo?: string;\n /** Optional idempotency key (max 64 chars). Scoped to (account, mode);\n * retries with the same key return the original Payment without\n * resubmitting on-chain. */\n idempotency_key?: string;\n};\n\n// payments.create — single endpoint for Phase 4.\n// Future phases will add: list, get (with chain status refresh), retry.\n\nexport class PaymentsResource {\n constructor(private readonly client: TallyClient) {}\n\n /**\n * Submits a USDC payment from one of the agent's granted wallets.\n *\n * Enforcement happens in two layers:\n * 1. Tally checks policy (per-tx max, recipient/contract allowlist,\n * expiry, daily cap) and rejects with a structured error before\n * any Privy call.\n * 2. Privy's secure enclave independently checks the per-tx max and\n * recipient/contract conditions encoded in the signer's policy.\n *\n * Returns immediately with `status: \"pending\"` and the on-chain\n * `tx_hash` once Privy accepts the signed RPC. The SDK does not wait\n * for block confirmation; the Transaction row is updated by a\n * separate confirmation flow (forthcoming).\n */\n async create(input: PaymentCreateInput): Promise<Payment> {\n const { payment } = await this.client.request<{ payment: Payment }>(\n \"POST\",\n \"/v1/payments\",\n input,\n );\n return payment;\n }\n\n /**\n * Fetches the current state of a payment by id. If still pending on\n * Tally's side, this call lazily refreshes from the chain — so polling\n * this is the canonical way to wait for `confirmed` / `failed`.\n */\n async get(id: string): Promise<Payment> {\n const { payment } = await this.client.request<{ payment: Payment }>(\n \"GET\",\n `/v1/payments/${encodeURIComponent(id)}`,\n );\n return payment;\n }\n}\n","import { createHmac, timingSafeEqual } from \"node:crypto\";\n\n// Webhook helpers for SDK consumers. Tally signs every delivery with\n// HMAC-SHA256 over `${timestamp}.${body}` and sends the signature in\n// the `tally-signature` header:\n//\n// tally-signature: t=<unix_seconds>,v1=<hex_lowercase>\n//\n// Receivers MUST verify before trusting any payload — otherwise a\n// known URL is forge-able. Pass the RAW request body (not the\n// JSON-parsed object) so the bytes match what Tally signed.\n//\n// The verifier is intentionally duplicated from the server-side\n// implementation (src/lib/webhook-sign.ts) to keep the SDK\n// self-contained — external consumers shouldn't depend on Tally's\n// server code.\n\nconst SIGNATURE_VERSION = \"v1\";\nexport const DEFAULT_TOLERANCE_SECONDS = 300; // 5 minutes\n\nexport type WebhookVerifyResult =\n | { ok: true }\n | {\n ok: false;\n reason:\n | \"missing_header\"\n | \"malformed_header\"\n | \"no_v1_signature\"\n | \"timestamp_out_of_tolerance\"\n | \"signature_mismatch\";\n };\n\nexport type WebhookVerifyInput = {\n /** Raw request body the receiver got — string, exactly the bytes\n * Tally signed. Don't pass a JSON-parsed object. */\n body: string;\n /** Value of the `tally-signature` header on the incoming request. */\n header: string | null | undefined;\n /** The plaintext signing secret you saved at webhook creation\n * (`whsec_<mode>_<base64url>`). */\n secret: string;\n /** Unix seconds. Defaults to now. Pin for tests. */\n now?: number;\n /** How far the header timestamp may drift from `now`. Defaults to\n * 300s (5 min). Tighten or loosen to taste; the wider the window,\n * the longer a stolen signature stays replayable. */\n toleranceSeconds?: number;\n};\n\nexport class WebhooksResource {\n /**\n * Verify a `tally-signature` header against the request body. Use in\n * your webhook handler before processing the payload.\n *\n * @example\n * ```ts\n * const body = await req.text(); // raw, not JSON-parsed\n * const result = tally.webhooks.verifySignature({\n * body,\n * header: req.headers.get(\"tally-signature\"),\n * secret: process.env.TALLY_WEBHOOK_SECRET!,\n * });\n * if (!result.ok) return new Response(\"invalid signature\", { status: 400 });\n * const event = JSON.parse(body);\n * // ... handle event\n * ```\n */\n verifySignature(input: WebhookVerifyInput): WebhookVerifyResult {\n return verifySignature(input);\n }\n}\n\nexport function verifySignature(input: WebhookVerifyInput): WebhookVerifyResult {\n const { body, header, secret } = input;\n const now = input.now ?? Math.floor(Date.now() / 1000);\n const tolerance = input.toleranceSeconds ?? DEFAULT_TOLERANCE_SECONDS;\n\n if (!header) return { ok: false, reason: \"missing_header\" };\n\n let timestamp: number | null = null;\n let v1: string | null = null;\n for (const part of header.split(\",\")) {\n const [k, v] = part.split(\"=\", 2);\n if (k === \"t\") {\n const n = Number(v);\n if (Number.isFinite(n)) timestamp = n;\n } else if (k === SIGNATURE_VERSION) {\n v1 = v;\n }\n }\n\n if (timestamp === null) return { ok: false, reason: \"malformed_header\" };\n if (!v1) return { ok: false, reason: \"no_v1_signature\" };\n\n if (Math.abs(now - timestamp) > tolerance) {\n return { ok: false, reason: \"timestamp_out_of_tolerance\" };\n }\n\n const expected = createHmac(\"sha256\", secret)\n .update(`${timestamp}.${body}`)\n .digest(\"hex\");\n\n if (expected.length !== v1.length) {\n return { ok: false, reason: \"signature_mismatch\" };\n }\n const a = Buffer.from(expected, \"utf8\");\n const b = Buffer.from(v1, \"utf8\");\n if (!timingSafeEqual(a, b)) {\n return { ok: false, reason: \"signature_mismatch\" };\n }\n return { ok: true };\n}\n","// @tallyforagents/sdk — the financial OS for AI agents.\n//\n// Usage:\n// import { Tally } from \"@tallyforagents/sdk\";\n//\n// const tally = new Tally({ apiKey: process.env.TALLY_API_KEY! });\n// await tally.agents.upsert({ id: \"research-bot\", display_name: \"Research Bot\" });\n\nimport { TallyClient, type ClientOptions } from \"./client\";\nimport { AgentsResource } from \"./resources/agents\";\nimport { PaymentsResource } from \"./resources/payments\";\nimport { WebhooksResource } from \"./resources/webhooks\";\n\nexport class Tally {\n readonly agents: AgentsResource;\n readonly payments: PaymentsResource;\n readonly webhooks: WebhooksResource;\n\n // Expose the underlying client for advanced use (custom retries, etc.).\n // Internal callers go through the resource classes instead.\n private readonly client: TallyClient;\n\n constructor(opts: ClientOptions) {\n this.client = new TallyClient(opts);\n this.agents = new AgentsResource(this.client);\n this.payments = new PaymentsResource(this.client);\n this.webhooks = new WebhooksResource();\n }\n}\n\nexport type { ClientOptions } from \"./client\";\nexport type { Agent, AgentUpsertInput } from \"./resources/agents\";\nexport type { Payment, PaymentCreateInput } from \"./resources/payments\";\nexport type {\n WebhookVerifyInput,\n WebhookVerifyResult,\n} from \"./resources/webhooks\";\nexport { verifySignature } from \"./resources/webhooks\";\nexport {\n TallyError,\n AuthenticationError,\n NotFoundError,\n ValidationError,\n RateLimitError,\n ConflictError,\n} from \"./errors\";\n"]}
|
package/dist/index.d.cts
ADDED
|
@@ -0,0 +1,196 @@
|
|
|
1
|
+
type ClientOptions = {
|
|
2
|
+
/** Tally API key (begins with `tly_`). */
|
|
3
|
+
apiKey: string;
|
|
4
|
+
/** Base URL of the Tally API. Defaults to https://app.tallyforagents.com. Override for local dev or self-hosting. */
|
|
5
|
+
baseUrl?: string;
|
|
6
|
+
/** Custom fetch implementation. Useful for tests or non-Node runtimes. */
|
|
7
|
+
fetch?: typeof fetch;
|
|
8
|
+
};
|
|
9
|
+
declare class TallyClient {
|
|
10
|
+
#private;
|
|
11
|
+
readonly apiKey: string;
|
|
12
|
+
readonly baseUrl: string;
|
|
13
|
+
constructor(opts: ClientOptions);
|
|
14
|
+
request<T>(method: "GET" | "POST" | "DELETE", path: string, body?: unknown): Promise<T>;
|
|
15
|
+
}
|
|
16
|
+
|
|
17
|
+
type Agent = {
|
|
18
|
+
/** Stable user-provided identifier (e.g., "research-bot"). */
|
|
19
|
+
id: string;
|
|
20
|
+
/** "active" — at least one activated (non-revoked) signer is attached.
|
|
21
|
+
* "no_permissions" — no activated signer (may still have pending
|
|
22
|
+
* grants — see pending_signers). */
|
|
23
|
+
status: "no_permissions" | "active";
|
|
24
|
+
mode: "test" | "live";
|
|
25
|
+
/** ISO 8601 timestamp. */
|
|
26
|
+
created_at: string;
|
|
27
|
+
/** Count of non-revoked, activated signers. */
|
|
28
|
+
active_signers: number;
|
|
29
|
+
/** Count of non-revoked, not-yet-activated signers. */
|
|
30
|
+
pending_signers: number;
|
|
31
|
+
};
|
|
32
|
+
type AgentUpsertInput = {
|
|
33
|
+
/** User-provided stable string identifier (e.g., "research-bot"). */
|
|
34
|
+
id: string;
|
|
35
|
+
};
|
|
36
|
+
declare class AgentsResource {
|
|
37
|
+
private readonly client;
|
|
38
|
+
constructor(client: TallyClient);
|
|
39
|
+
/**
|
|
40
|
+
* Creates the agent if it doesn't exist; no-op if it does.
|
|
41
|
+
*
|
|
42
|
+
* Scope: the active API key's account + mode. Same id can exist
|
|
43
|
+
* independently in test and live mode for the same account.
|
|
44
|
+
*/
|
|
45
|
+
upsert(input: AgentUpsertInput): Promise<Agent>;
|
|
46
|
+
list(): Promise<Agent[]>;
|
|
47
|
+
get(id: string): Promise<Agent>;
|
|
48
|
+
}
|
|
49
|
+
|
|
50
|
+
type Payment = {
|
|
51
|
+
id: string;
|
|
52
|
+
/** "pending" until the on-chain tx confirms; updated by a follow-up
|
|
53
|
+
* GET (planned) or webhook (Phase 6). */
|
|
54
|
+
status: "pending" | "confirmed" | "failed";
|
|
55
|
+
/** Transaction hash on the underlying chain. Set as soon as Privy
|
|
56
|
+
* accepts the signed RPC; the SDK does not wait for receipt. */
|
|
57
|
+
tx_hash: string | null;
|
|
58
|
+
amount_usdc: string;
|
|
59
|
+
to: string;
|
|
60
|
+
from: string;
|
|
61
|
+
memo: string | null;
|
|
62
|
+
idempotency_key: string | null;
|
|
63
|
+
created_at: string;
|
|
64
|
+
};
|
|
65
|
+
type PaymentCreateInput = {
|
|
66
|
+
/** Agent's user-provided id (the externalId), e.g. "research-bot". */
|
|
67
|
+
agent_id: string;
|
|
68
|
+
/** Sender wallet address. Must be in this API key's account+mode and
|
|
69
|
+
* must have an active permission grant for the agent. */
|
|
70
|
+
wallet: string;
|
|
71
|
+
/** Recipient EVM address. */
|
|
72
|
+
to: string;
|
|
73
|
+
/** Decimal USDC amount as a string, e.g. "10" or "2.50". */
|
|
74
|
+
amount_usdc: string;
|
|
75
|
+
/** Optional memo (max 200 chars), stored alongside the transaction. */
|
|
76
|
+
memo?: string;
|
|
77
|
+
/** Optional idempotency key (max 64 chars). Scoped to (account, mode);
|
|
78
|
+
* retries with the same key return the original Payment without
|
|
79
|
+
* resubmitting on-chain. */
|
|
80
|
+
idempotency_key?: string;
|
|
81
|
+
};
|
|
82
|
+
declare class PaymentsResource {
|
|
83
|
+
private readonly client;
|
|
84
|
+
constructor(client: TallyClient);
|
|
85
|
+
/**
|
|
86
|
+
* Submits a USDC payment from one of the agent's granted wallets.
|
|
87
|
+
*
|
|
88
|
+
* Enforcement happens in two layers:
|
|
89
|
+
* 1. Tally checks policy (per-tx max, recipient/contract allowlist,
|
|
90
|
+
* expiry, daily cap) and rejects with a structured error before
|
|
91
|
+
* any Privy call.
|
|
92
|
+
* 2. Privy's secure enclave independently checks the per-tx max and
|
|
93
|
+
* recipient/contract conditions encoded in the signer's policy.
|
|
94
|
+
*
|
|
95
|
+
* Returns immediately with `status: "pending"` and the on-chain
|
|
96
|
+
* `tx_hash` once Privy accepts the signed RPC. The SDK does not wait
|
|
97
|
+
* for block confirmation; the Transaction row is updated by a
|
|
98
|
+
* separate confirmation flow (forthcoming).
|
|
99
|
+
*/
|
|
100
|
+
create(input: PaymentCreateInput): Promise<Payment>;
|
|
101
|
+
/**
|
|
102
|
+
* Fetches the current state of a payment by id. If still pending on
|
|
103
|
+
* Tally's side, this call lazily refreshes from the chain — so polling
|
|
104
|
+
* this is the canonical way to wait for `confirmed` / `failed`.
|
|
105
|
+
*/
|
|
106
|
+
get(id: string): Promise<Payment>;
|
|
107
|
+
}
|
|
108
|
+
|
|
109
|
+
type WebhookVerifyResult = {
|
|
110
|
+
ok: true;
|
|
111
|
+
} | {
|
|
112
|
+
ok: false;
|
|
113
|
+
reason: "missing_header" | "malformed_header" | "no_v1_signature" | "timestamp_out_of_tolerance" | "signature_mismatch";
|
|
114
|
+
};
|
|
115
|
+
type WebhookVerifyInput = {
|
|
116
|
+
/** Raw request body the receiver got — string, exactly the bytes
|
|
117
|
+
* Tally signed. Don't pass a JSON-parsed object. */
|
|
118
|
+
body: string;
|
|
119
|
+
/** Value of the `tally-signature` header on the incoming request. */
|
|
120
|
+
header: string | null | undefined;
|
|
121
|
+
/** The plaintext signing secret you saved at webhook creation
|
|
122
|
+
* (`whsec_<mode>_<base64url>`). */
|
|
123
|
+
secret: string;
|
|
124
|
+
/** Unix seconds. Defaults to now. Pin for tests. */
|
|
125
|
+
now?: number;
|
|
126
|
+
/** How far the header timestamp may drift from `now`. Defaults to
|
|
127
|
+
* 300s (5 min). Tighten or loosen to taste; the wider the window,
|
|
128
|
+
* the longer a stolen signature stays replayable. */
|
|
129
|
+
toleranceSeconds?: number;
|
|
130
|
+
};
|
|
131
|
+
declare class WebhooksResource {
|
|
132
|
+
/**
|
|
133
|
+
* Verify a `tally-signature` header against the request body. Use in
|
|
134
|
+
* your webhook handler before processing the payload.
|
|
135
|
+
*
|
|
136
|
+
* @example
|
|
137
|
+
* ```ts
|
|
138
|
+
* const body = await req.text(); // raw, not JSON-parsed
|
|
139
|
+
* const result = tally.webhooks.verifySignature({
|
|
140
|
+
* body,
|
|
141
|
+
* header: req.headers.get("tally-signature"),
|
|
142
|
+
* secret: process.env.TALLY_WEBHOOK_SECRET!,
|
|
143
|
+
* });
|
|
144
|
+
* if (!result.ok) return new Response("invalid signature", { status: 400 });
|
|
145
|
+
* const event = JSON.parse(body);
|
|
146
|
+
* // ... handle event
|
|
147
|
+
* ```
|
|
148
|
+
*/
|
|
149
|
+
verifySignature(input: WebhookVerifyInput): WebhookVerifyResult;
|
|
150
|
+
}
|
|
151
|
+
declare function verifySignature(input: WebhookVerifyInput): WebhookVerifyResult;
|
|
152
|
+
|
|
153
|
+
type ApiErrorPayload = {
|
|
154
|
+
type: string;
|
|
155
|
+
message: string;
|
|
156
|
+
code?: string;
|
|
157
|
+
details?: unknown;
|
|
158
|
+
};
|
|
159
|
+
declare class TallyError extends Error {
|
|
160
|
+
readonly type: string;
|
|
161
|
+
readonly code?: string;
|
|
162
|
+
readonly status: number;
|
|
163
|
+
readonly details?: unknown;
|
|
164
|
+
constructor(opts: {
|
|
165
|
+
type: string;
|
|
166
|
+
message: string;
|
|
167
|
+
status: number;
|
|
168
|
+
code?: string;
|
|
169
|
+
details?: unknown;
|
|
170
|
+
});
|
|
171
|
+
}
|
|
172
|
+
declare class AuthenticationError extends TallyError {
|
|
173
|
+
constructor(payload: ApiErrorPayload, status: number);
|
|
174
|
+
}
|
|
175
|
+
declare class NotFoundError extends TallyError {
|
|
176
|
+
constructor(payload: ApiErrorPayload, status: number);
|
|
177
|
+
}
|
|
178
|
+
declare class ValidationError extends TallyError {
|
|
179
|
+
constructor(payload: ApiErrorPayload, status: number);
|
|
180
|
+
}
|
|
181
|
+
declare class RateLimitError extends TallyError {
|
|
182
|
+
constructor(payload: ApiErrorPayload, status: number);
|
|
183
|
+
}
|
|
184
|
+
declare class ConflictError extends TallyError {
|
|
185
|
+
constructor(payload: ApiErrorPayload, status: number);
|
|
186
|
+
}
|
|
187
|
+
|
|
188
|
+
declare class Tally {
|
|
189
|
+
readonly agents: AgentsResource;
|
|
190
|
+
readonly payments: PaymentsResource;
|
|
191
|
+
readonly webhooks: WebhooksResource;
|
|
192
|
+
private readonly client;
|
|
193
|
+
constructor(opts: ClientOptions);
|
|
194
|
+
}
|
|
195
|
+
|
|
196
|
+
export { type Agent, type AgentUpsertInput, AuthenticationError, type ClientOptions, ConflictError, NotFoundError, type Payment, type PaymentCreateInput, RateLimitError, Tally, TallyError, ValidationError, type WebhookVerifyInput, type WebhookVerifyResult, verifySignature };
|
package/dist/index.d.ts
ADDED
|
@@ -0,0 +1,196 @@
|
|
|
1
|
+
type ClientOptions = {
|
|
2
|
+
/** Tally API key (begins with `tly_`). */
|
|
3
|
+
apiKey: string;
|
|
4
|
+
/** Base URL of the Tally API. Defaults to https://app.tallyforagents.com. Override for local dev or self-hosting. */
|
|
5
|
+
baseUrl?: string;
|
|
6
|
+
/** Custom fetch implementation. Useful for tests or non-Node runtimes. */
|
|
7
|
+
fetch?: typeof fetch;
|
|
8
|
+
};
|
|
9
|
+
declare class TallyClient {
|
|
10
|
+
#private;
|
|
11
|
+
readonly apiKey: string;
|
|
12
|
+
readonly baseUrl: string;
|
|
13
|
+
constructor(opts: ClientOptions);
|
|
14
|
+
request<T>(method: "GET" | "POST" | "DELETE", path: string, body?: unknown): Promise<T>;
|
|
15
|
+
}
|
|
16
|
+
|
|
17
|
+
type Agent = {
|
|
18
|
+
/** Stable user-provided identifier (e.g., "research-bot"). */
|
|
19
|
+
id: string;
|
|
20
|
+
/** "active" — at least one activated (non-revoked) signer is attached.
|
|
21
|
+
* "no_permissions" — no activated signer (may still have pending
|
|
22
|
+
* grants — see pending_signers). */
|
|
23
|
+
status: "no_permissions" | "active";
|
|
24
|
+
mode: "test" | "live";
|
|
25
|
+
/** ISO 8601 timestamp. */
|
|
26
|
+
created_at: string;
|
|
27
|
+
/** Count of non-revoked, activated signers. */
|
|
28
|
+
active_signers: number;
|
|
29
|
+
/** Count of non-revoked, not-yet-activated signers. */
|
|
30
|
+
pending_signers: number;
|
|
31
|
+
};
|
|
32
|
+
type AgentUpsertInput = {
|
|
33
|
+
/** User-provided stable string identifier (e.g., "research-bot"). */
|
|
34
|
+
id: string;
|
|
35
|
+
};
|
|
36
|
+
declare class AgentsResource {
|
|
37
|
+
private readonly client;
|
|
38
|
+
constructor(client: TallyClient);
|
|
39
|
+
/**
|
|
40
|
+
* Creates the agent if it doesn't exist; no-op if it does.
|
|
41
|
+
*
|
|
42
|
+
* Scope: the active API key's account + mode. Same id can exist
|
|
43
|
+
* independently in test and live mode for the same account.
|
|
44
|
+
*/
|
|
45
|
+
upsert(input: AgentUpsertInput): Promise<Agent>;
|
|
46
|
+
list(): Promise<Agent[]>;
|
|
47
|
+
get(id: string): Promise<Agent>;
|
|
48
|
+
}
|
|
49
|
+
|
|
50
|
+
type Payment = {
|
|
51
|
+
id: string;
|
|
52
|
+
/** "pending" until the on-chain tx confirms; updated by a follow-up
|
|
53
|
+
* GET (planned) or webhook (Phase 6). */
|
|
54
|
+
status: "pending" | "confirmed" | "failed";
|
|
55
|
+
/** Transaction hash on the underlying chain. Set as soon as Privy
|
|
56
|
+
* accepts the signed RPC; the SDK does not wait for receipt. */
|
|
57
|
+
tx_hash: string | null;
|
|
58
|
+
amount_usdc: string;
|
|
59
|
+
to: string;
|
|
60
|
+
from: string;
|
|
61
|
+
memo: string | null;
|
|
62
|
+
idempotency_key: string | null;
|
|
63
|
+
created_at: string;
|
|
64
|
+
};
|
|
65
|
+
type PaymentCreateInput = {
|
|
66
|
+
/** Agent's user-provided id (the externalId), e.g. "research-bot". */
|
|
67
|
+
agent_id: string;
|
|
68
|
+
/** Sender wallet address. Must be in this API key's account+mode and
|
|
69
|
+
* must have an active permission grant for the agent. */
|
|
70
|
+
wallet: string;
|
|
71
|
+
/** Recipient EVM address. */
|
|
72
|
+
to: string;
|
|
73
|
+
/** Decimal USDC amount as a string, e.g. "10" or "2.50". */
|
|
74
|
+
amount_usdc: string;
|
|
75
|
+
/** Optional memo (max 200 chars), stored alongside the transaction. */
|
|
76
|
+
memo?: string;
|
|
77
|
+
/** Optional idempotency key (max 64 chars). Scoped to (account, mode);
|
|
78
|
+
* retries with the same key return the original Payment without
|
|
79
|
+
* resubmitting on-chain. */
|
|
80
|
+
idempotency_key?: string;
|
|
81
|
+
};
|
|
82
|
+
declare class PaymentsResource {
|
|
83
|
+
private readonly client;
|
|
84
|
+
constructor(client: TallyClient);
|
|
85
|
+
/**
|
|
86
|
+
* Submits a USDC payment from one of the agent's granted wallets.
|
|
87
|
+
*
|
|
88
|
+
* Enforcement happens in two layers:
|
|
89
|
+
* 1. Tally checks policy (per-tx max, recipient/contract allowlist,
|
|
90
|
+
* expiry, daily cap) and rejects with a structured error before
|
|
91
|
+
* any Privy call.
|
|
92
|
+
* 2. Privy's secure enclave independently checks the per-tx max and
|
|
93
|
+
* recipient/contract conditions encoded in the signer's policy.
|
|
94
|
+
*
|
|
95
|
+
* Returns immediately with `status: "pending"` and the on-chain
|
|
96
|
+
* `tx_hash` once Privy accepts the signed RPC. The SDK does not wait
|
|
97
|
+
* for block confirmation; the Transaction row is updated by a
|
|
98
|
+
* separate confirmation flow (forthcoming).
|
|
99
|
+
*/
|
|
100
|
+
create(input: PaymentCreateInput): Promise<Payment>;
|
|
101
|
+
/**
|
|
102
|
+
* Fetches the current state of a payment by id. If still pending on
|
|
103
|
+
* Tally's side, this call lazily refreshes from the chain — so polling
|
|
104
|
+
* this is the canonical way to wait for `confirmed` / `failed`.
|
|
105
|
+
*/
|
|
106
|
+
get(id: string): Promise<Payment>;
|
|
107
|
+
}
|
|
108
|
+
|
|
109
|
+
type WebhookVerifyResult = {
|
|
110
|
+
ok: true;
|
|
111
|
+
} | {
|
|
112
|
+
ok: false;
|
|
113
|
+
reason: "missing_header" | "malformed_header" | "no_v1_signature" | "timestamp_out_of_tolerance" | "signature_mismatch";
|
|
114
|
+
};
|
|
115
|
+
type WebhookVerifyInput = {
|
|
116
|
+
/** Raw request body the receiver got — string, exactly the bytes
|
|
117
|
+
* Tally signed. Don't pass a JSON-parsed object. */
|
|
118
|
+
body: string;
|
|
119
|
+
/** Value of the `tally-signature` header on the incoming request. */
|
|
120
|
+
header: string | null | undefined;
|
|
121
|
+
/** The plaintext signing secret you saved at webhook creation
|
|
122
|
+
* (`whsec_<mode>_<base64url>`). */
|
|
123
|
+
secret: string;
|
|
124
|
+
/** Unix seconds. Defaults to now. Pin for tests. */
|
|
125
|
+
now?: number;
|
|
126
|
+
/** How far the header timestamp may drift from `now`. Defaults to
|
|
127
|
+
* 300s (5 min). Tighten or loosen to taste; the wider the window,
|
|
128
|
+
* the longer a stolen signature stays replayable. */
|
|
129
|
+
toleranceSeconds?: number;
|
|
130
|
+
};
|
|
131
|
+
declare class WebhooksResource {
|
|
132
|
+
/**
|
|
133
|
+
* Verify a `tally-signature` header against the request body. Use in
|
|
134
|
+
* your webhook handler before processing the payload.
|
|
135
|
+
*
|
|
136
|
+
* @example
|
|
137
|
+
* ```ts
|
|
138
|
+
* const body = await req.text(); // raw, not JSON-parsed
|
|
139
|
+
* const result = tally.webhooks.verifySignature({
|
|
140
|
+
* body,
|
|
141
|
+
* header: req.headers.get("tally-signature"),
|
|
142
|
+
* secret: process.env.TALLY_WEBHOOK_SECRET!,
|
|
143
|
+
* });
|
|
144
|
+
* if (!result.ok) return new Response("invalid signature", { status: 400 });
|
|
145
|
+
* const event = JSON.parse(body);
|
|
146
|
+
* // ... handle event
|
|
147
|
+
* ```
|
|
148
|
+
*/
|
|
149
|
+
verifySignature(input: WebhookVerifyInput): WebhookVerifyResult;
|
|
150
|
+
}
|
|
151
|
+
declare function verifySignature(input: WebhookVerifyInput): WebhookVerifyResult;
|
|
152
|
+
|
|
153
|
+
type ApiErrorPayload = {
|
|
154
|
+
type: string;
|
|
155
|
+
message: string;
|
|
156
|
+
code?: string;
|
|
157
|
+
details?: unknown;
|
|
158
|
+
};
|
|
159
|
+
declare class TallyError extends Error {
|
|
160
|
+
readonly type: string;
|
|
161
|
+
readonly code?: string;
|
|
162
|
+
readonly status: number;
|
|
163
|
+
readonly details?: unknown;
|
|
164
|
+
constructor(opts: {
|
|
165
|
+
type: string;
|
|
166
|
+
message: string;
|
|
167
|
+
status: number;
|
|
168
|
+
code?: string;
|
|
169
|
+
details?: unknown;
|
|
170
|
+
});
|
|
171
|
+
}
|
|
172
|
+
declare class AuthenticationError extends TallyError {
|
|
173
|
+
constructor(payload: ApiErrorPayload, status: number);
|
|
174
|
+
}
|
|
175
|
+
declare class NotFoundError extends TallyError {
|
|
176
|
+
constructor(payload: ApiErrorPayload, status: number);
|
|
177
|
+
}
|
|
178
|
+
declare class ValidationError extends TallyError {
|
|
179
|
+
constructor(payload: ApiErrorPayload, status: number);
|
|
180
|
+
}
|
|
181
|
+
declare class RateLimitError extends TallyError {
|
|
182
|
+
constructor(payload: ApiErrorPayload, status: number);
|
|
183
|
+
}
|
|
184
|
+
declare class ConflictError extends TallyError {
|
|
185
|
+
constructor(payload: ApiErrorPayload, status: number);
|
|
186
|
+
}
|
|
187
|
+
|
|
188
|
+
declare class Tally {
|
|
189
|
+
readonly agents: AgentsResource;
|
|
190
|
+
readonly payments: PaymentsResource;
|
|
191
|
+
readonly webhooks: WebhooksResource;
|
|
192
|
+
private readonly client;
|
|
193
|
+
constructor(opts: ClientOptions);
|
|
194
|
+
}
|
|
195
|
+
|
|
196
|
+
export { type Agent, type AgentUpsertInput, AuthenticationError, type ClientOptions, ConflictError, NotFoundError, type Payment, type PaymentCreateInput, RateLimitError, Tally, TallyError, ValidationError, type WebhookVerifyInput, type WebhookVerifyResult, verifySignature };
|
package/dist/index.js
ADDED
|
@@ -0,0 +1,271 @@
|
|
|
1
|
+
import { createHmac, timingSafeEqual } from 'crypto';
|
|
2
|
+
|
|
3
|
+
// src/errors.ts
|
|
4
|
+
var TallyError = class extends Error {
|
|
5
|
+
type;
|
|
6
|
+
code;
|
|
7
|
+
status;
|
|
8
|
+
details;
|
|
9
|
+
constructor(opts) {
|
|
10
|
+
super(opts.message);
|
|
11
|
+
this.name = "TallyError";
|
|
12
|
+
this.type = opts.type;
|
|
13
|
+
this.code = opts.code;
|
|
14
|
+
this.status = opts.status;
|
|
15
|
+
this.details = opts.details;
|
|
16
|
+
}
|
|
17
|
+
};
|
|
18
|
+
var AuthenticationError = class extends TallyError {
|
|
19
|
+
constructor(payload, status) {
|
|
20
|
+
super({ ...payload, status });
|
|
21
|
+
this.name = "AuthenticationError";
|
|
22
|
+
}
|
|
23
|
+
};
|
|
24
|
+
var NotFoundError = class extends TallyError {
|
|
25
|
+
constructor(payload, status) {
|
|
26
|
+
super({ ...payload, status });
|
|
27
|
+
this.name = "NotFoundError";
|
|
28
|
+
}
|
|
29
|
+
};
|
|
30
|
+
var ValidationError = class extends TallyError {
|
|
31
|
+
constructor(payload, status) {
|
|
32
|
+
super({ ...payload, status });
|
|
33
|
+
this.name = "ValidationError";
|
|
34
|
+
}
|
|
35
|
+
};
|
|
36
|
+
var RateLimitError = class extends TallyError {
|
|
37
|
+
constructor(payload, status) {
|
|
38
|
+
super({ ...payload, status });
|
|
39
|
+
this.name = "RateLimitError";
|
|
40
|
+
}
|
|
41
|
+
};
|
|
42
|
+
var ConflictError = class extends TallyError {
|
|
43
|
+
constructor(payload, status) {
|
|
44
|
+
super({ ...payload, status });
|
|
45
|
+
this.name = "ConflictError";
|
|
46
|
+
}
|
|
47
|
+
};
|
|
48
|
+
function makeError(payload, status) {
|
|
49
|
+
switch (payload.type) {
|
|
50
|
+
case "unauthenticated":
|
|
51
|
+
case "forbidden":
|
|
52
|
+
return new AuthenticationError(payload, status);
|
|
53
|
+
case "not_found":
|
|
54
|
+
return new NotFoundError(payload, status);
|
|
55
|
+
case "validation_failed":
|
|
56
|
+
return new ValidationError(payload, status);
|
|
57
|
+
case "rate_limited":
|
|
58
|
+
return new RateLimitError(payload, status);
|
|
59
|
+
case "conflict":
|
|
60
|
+
return new ConflictError(payload, status);
|
|
61
|
+
default:
|
|
62
|
+
return new TallyError({ ...payload, status });
|
|
63
|
+
}
|
|
64
|
+
}
|
|
65
|
+
|
|
66
|
+
// src/client.ts
|
|
67
|
+
var TallyClient = class {
|
|
68
|
+
apiKey;
|
|
69
|
+
baseUrl;
|
|
70
|
+
#fetch;
|
|
71
|
+
constructor(opts) {
|
|
72
|
+
if (!opts.apiKey || !opts.apiKey.startsWith("tly_test_") && !opts.apiKey.startsWith("tly_live_")) {
|
|
73
|
+
throw new TallyError({
|
|
74
|
+
type: "validation_failed",
|
|
75
|
+
message: "Invalid API key. Tally keys start with 'tly_test_' (test mode) or 'tly_live_' (live mode).",
|
|
76
|
+
status: 0
|
|
77
|
+
});
|
|
78
|
+
}
|
|
79
|
+
this.apiKey = opts.apiKey;
|
|
80
|
+
this.baseUrl = (opts.baseUrl ?? "https://app.tallyforagents.com").replace(/\/$/, "");
|
|
81
|
+
this.#fetch = opts.fetch ?? globalThis.fetch;
|
|
82
|
+
if (typeof this.#fetch !== "function") {
|
|
83
|
+
throw new TallyError({
|
|
84
|
+
type: "internal",
|
|
85
|
+
message: "fetch is not available in this runtime. Provide one via `fetch` option.",
|
|
86
|
+
status: 0
|
|
87
|
+
});
|
|
88
|
+
}
|
|
89
|
+
}
|
|
90
|
+
async request(method, path, body) {
|
|
91
|
+
const url = `${this.baseUrl}${path}`;
|
|
92
|
+
const res = await this.#fetch(url, {
|
|
93
|
+
method,
|
|
94
|
+
headers: {
|
|
95
|
+
Authorization: `Bearer ${this.apiKey}`,
|
|
96
|
+
"Content-Type": "application/json",
|
|
97
|
+
Accept: "application/json"
|
|
98
|
+
},
|
|
99
|
+
body: body === void 0 ? void 0 : JSON.stringify(body)
|
|
100
|
+
});
|
|
101
|
+
if (!res.ok) {
|
|
102
|
+
let payload = {};
|
|
103
|
+
try {
|
|
104
|
+
payload = await res.json();
|
|
105
|
+
} catch {
|
|
106
|
+
}
|
|
107
|
+
const err = payload.error ?? {
|
|
108
|
+
type: "internal",
|
|
109
|
+
message: `Request failed with status ${res.status}.`
|
|
110
|
+
};
|
|
111
|
+
throw makeError(err, res.status);
|
|
112
|
+
}
|
|
113
|
+
if (res.status === 204) return void 0;
|
|
114
|
+
return await res.json();
|
|
115
|
+
}
|
|
116
|
+
};
|
|
117
|
+
|
|
118
|
+
// src/resources/agents.ts
|
|
119
|
+
var AgentsResource = class {
|
|
120
|
+
constructor(client) {
|
|
121
|
+
this.client = client;
|
|
122
|
+
}
|
|
123
|
+
client;
|
|
124
|
+
/**
|
|
125
|
+
* Creates the agent if it doesn't exist; no-op if it does.
|
|
126
|
+
*
|
|
127
|
+
* Scope: the active API key's account + mode. Same id can exist
|
|
128
|
+
* independently in test and live mode for the same account.
|
|
129
|
+
*/
|
|
130
|
+
async upsert(input) {
|
|
131
|
+
const { agent } = await this.client.request(
|
|
132
|
+
"POST",
|
|
133
|
+
"/v1/agents",
|
|
134
|
+
input
|
|
135
|
+
);
|
|
136
|
+
return agent;
|
|
137
|
+
}
|
|
138
|
+
async list() {
|
|
139
|
+
const { agents } = await this.client.request(
|
|
140
|
+
"GET",
|
|
141
|
+
"/v1/agents"
|
|
142
|
+
);
|
|
143
|
+
return agents;
|
|
144
|
+
}
|
|
145
|
+
async get(id) {
|
|
146
|
+
const { agent } = await this.client.request(
|
|
147
|
+
"GET",
|
|
148
|
+
`/v1/agents/${encodeURIComponent(id)}`
|
|
149
|
+
);
|
|
150
|
+
return agent;
|
|
151
|
+
}
|
|
152
|
+
};
|
|
153
|
+
|
|
154
|
+
// src/resources/payments.ts
|
|
155
|
+
var PaymentsResource = class {
|
|
156
|
+
constructor(client) {
|
|
157
|
+
this.client = client;
|
|
158
|
+
}
|
|
159
|
+
client;
|
|
160
|
+
/**
|
|
161
|
+
* Submits a USDC payment from one of the agent's granted wallets.
|
|
162
|
+
*
|
|
163
|
+
* Enforcement happens in two layers:
|
|
164
|
+
* 1. Tally checks policy (per-tx max, recipient/contract allowlist,
|
|
165
|
+
* expiry, daily cap) and rejects with a structured error before
|
|
166
|
+
* any Privy call.
|
|
167
|
+
* 2. Privy's secure enclave independently checks the per-tx max and
|
|
168
|
+
* recipient/contract conditions encoded in the signer's policy.
|
|
169
|
+
*
|
|
170
|
+
* Returns immediately with `status: "pending"` and the on-chain
|
|
171
|
+
* `tx_hash` once Privy accepts the signed RPC. The SDK does not wait
|
|
172
|
+
* for block confirmation; the Transaction row is updated by a
|
|
173
|
+
* separate confirmation flow (forthcoming).
|
|
174
|
+
*/
|
|
175
|
+
async create(input) {
|
|
176
|
+
const { payment } = await this.client.request(
|
|
177
|
+
"POST",
|
|
178
|
+
"/v1/payments",
|
|
179
|
+
input
|
|
180
|
+
);
|
|
181
|
+
return payment;
|
|
182
|
+
}
|
|
183
|
+
/**
|
|
184
|
+
* Fetches the current state of a payment by id. If still pending on
|
|
185
|
+
* Tally's side, this call lazily refreshes from the chain — so polling
|
|
186
|
+
* this is the canonical way to wait for `confirmed` / `failed`.
|
|
187
|
+
*/
|
|
188
|
+
async get(id) {
|
|
189
|
+
const { payment } = await this.client.request(
|
|
190
|
+
"GET",
|
|
191
|
+
`/v1/payments/${encodeURIComponent(id)}`
|
|
192
|
+
);
|
|
193
|
+
return payment;
|
|
194
|
+
}
|
|
195
|
+
};
|
|
196
|
+
var SIGNATURE_VERSION = "v1";
|
|
197
|
+
var DEFAULT_TOLERANCE_SECONDS = 300;
|
|
198
|
+
var WebhooksResource = class {
|
|
199
|
+
/**
|
|
200
|
+
* Verify a `tally-signature` header against the request body. Use in
|
|
201
|
+
* your webhook handler before processing the payload.
|
|
202
|
+
*
|
|
203
|
+
* @example
|
|
204
|
+
* ```ts
|
|
205
|
+
* const body = await req.text(); // raw, not JSON-parsed
|
|
206
|
+
* const result = tally.webhooks.verifySignature({
|
|
207
|
+
* body,
|
|
208
|
+
* header: req.headers.get("tally-signature"),
|
|
209
|
+
* secret: process.env.TALLY_WEBHOOK_SECRET!,
|
|
210
|
+
* });
|
|
211
|
+
* if (!result.ok) return new Response("invalid signature", { status: 400 });
|
|
212
|
+
* const event = JSON.parse(body);
|
|
213
|
+
* // ... handle event
|
|
214
|
+
* ```
|
|
215
|
+
*/
|
|
216
|
+
verifySignature(input) {
|
|
217
|
+
return verifySignature(input);
|
|
218
|
+
}
|
|
219
|
+
};
|
|
220
|
+
function verifySignature(input) {
|
|
221
|
+
const { body, header, secret } = input;
|
|
222
|
+
const now = input.now ?? Math.floor(Date.now() / 1e3);
|
|
223
|
+
const tolerance = input.toleranceSeconds ?? DEFAULT_TOLERANCE_SECONDS;
|
|
224
|
+
if (!header) return { ok: false, reason: "missing_header" };
|
|
225
|
+
let timestamp = null;
|
|
226
|
+
let v1 = null;
|
|
227
|
+
for (const part of header.split(",")) {
|
|
228
|
+
const [k, v] = part.split("=", 2);
|
|
229
|
+
if (k === "t") {
|
|
230
|
+
const n = Number(v);
|
|
231
|
+
if (Number.isFinite(n)) timestamp = n;
|
|
232
|
+
} else if (k === SIGNATURE_VERSION) {
|
|
233
|
+
v1 = v;
|
|
234
|
+
}
|
|
235
|
+
}
|
|
236
|
+
if (timestamp === null) return { ok: false, reason: "malformed_header" };
|
|
237
|
+
if (!v1) return { ok: false, reason: "no_v1_signature" };
|
|
238
|
+
if (Math.abs(now - timestamp) > tolerance) {
|
|
239
|
+
return { ok: false, reason: "timestamp_out_of_tolerance" };
|
|
240
|
+
}
|
|
241
|
+
const expected = createHmac("sha256", secret).update(`${timestamp}.${body}`).digest("hex");
|
|
242
|
+
if (expected.length !== v1.length) {
|
|
243
|
+
return { ok: false, reason: "signature_mismatch" };
|
|
244
|
+
}
|
|
245
|
+
const a = Buffer.from(expected, "utf8");
|
|
246
|
+
const b = Buffer.from(v1, "utf8");
|
|
247
|
+
if (!timingSafeEqual(a, b)) {
|
|
248
|
+
return { ok: false, reason: "signature_mismatch" };
|
|
249
|
+
}
|
|
250
|
+
return { ok: true };
|
|
251
|
+
}
|
|
252
|
+
|
|
253
|
+
// src/index.ts
|
|
254
|
+
var Tally = class {
|
|
255
|
+
agents;
|
|
256
|
+
payments;
|
|
257
|
+
webhooks;
|
|
258
|
+
// Expose the underlying client for advanced use (custom retries, etc.).
|
|
259
|
+
// Internal callers go through the resource classes instead.
|
|
260
|
+
client;
|
|
261
|
+
constructor(opts) {
|
|
262
|
+
this.client = new TallyClient(opts);
|
|
263
|
+
this.agents = new AgentsResource(this.client);
|
|
264
|
+
this.payments = new PaymentsResource(this.client);
|
|
265
|
+
this.webhooks = new WebhooksResource();
|
|
266
|
+
}
|
|
267
|
+
};
|
|
268
|
+
|
|
269
|
+
export { AuthenticationError, ConflictError, NotFoundError, RateLimitError, Tally, TallyError, ValidationError, verifySignature };
|
|
270
|
+
//# sourceMappingURL=index.js.map
|
|
271
|
+
//# sourceMappingURL=index.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"sources":["../src/errors.ts","../src/client.ts","../src/resources/agents.ts","../src/resources/payments.ts","../src/resources/webhooks.ts","../src/index.ts"],"names":[],"mappings":";;;AAqBO,IAAM,UAAA,GAAN,cAAyB,KAAA,CAAM;AAAA,EAC3B,IAAA;AAAA,EACA,IAAA;AAAA,EACA,MAAA;AAAA,EACA,OAAA;AAAA,EAET,YAAY,IAAA,EAMT;AACD,IAAA,KAAA,CAAM,KAAK,OAAO,CAAA;AAClB,IAAA,IAAA,CAAK,IAAA,GAAO,YAAA;AACZ,IAAA,IAAA,CAAK,OAAO,IAAA,CAAK,IAAA;AACjB,IAAA,IAAA,CAAK,OAAO,IAAA,CAAK,IAAA;AACjB,IAAA,IAAA,CAAK,SAAS,IAAA,CAAK,MAAA;AACnB,IAAA,IAAA,CAAK,UAAU,IAAA,CAAK,OAAA;AAAA,EACtB;AACF;AAEO,IAAM,mBAAA,GAAN,cAAkC,UAAA,CAAW;AAAA,EAClD,WAAA,CAAY,SAA0B,MAAA,EAAgB;AACpD,IAAA,KAAA,CAAM,EAAE,GAAG,OAAA,EAAS,MAAA,EAAQ,CAAA;AAC5B,IAAA,IAAA,CAAK,IAAA,GAAO,qBAAA;AAAA,EACd;AACF;AAEO,IAAM,aAAA,GAAN,cAA4B,UAAA,CAAW;AAAA,EAC5C,WAAA,CAAY,SAA0B,MAAA,EAAgB;AACpD,IAAA,KAAA,CAAM,EAAE,GAAG,OAAA,EAAS,MAAA,EAAQ,CAAA;AAC5B,IAAA,IAAA,CAAK,IAAA,GAAO,eAAA;AAAA,EACd;AACF;AAEO,IAAM,eAAA,GAAN,cAA8B,UAAA,CAAW;AAAA,EAC9C,WAAA,CAAY,SAA0B,MAAA,EAAgB;AACpD,IAAA,KAAA,CAAM,EAAE,GAAG,OAAA,EAAS,MAAA,EAAQ,CAAA;AAC5B,IAAA,IAAA,CAAK,IAAA,GAAO,iBAAA;AAAA,EACd;AACF;AAEO,IAAM,cAAA,GAAN,cAA6B,UAAA,CAAW;AAAA,EAC7C,WAAA,CAAY,SAA0B,MAAA,EAAgB;AACpD,IAAA,KAAA,CAAM,EAAE,GAAG,OAAA,EAAS,MAAA,EAAQ,CAAA;AAC5B,IAAA,IAAA,CAAK,IAAA,GAAO,gBAAA;AAAA,EACd;AACF;AAEO,IAAM,aAAA,GAAN,cAA4B,UAAA,CAAW;AAAA,EAC5C,WAAA,CAAY,SAA0B,MAAA,EAAgB;AACpD,IAAA,KAAA,CAAM,EAAE,GAAG,OAAA,EAAS,MAAA,EAAQ,CAAA;AAC5B,IAAA,IAAA,CAAK,IAAA,GAAO,eAAA;AAAA,EACd;AACF;AAGO,SAAS,SAAA,CAAU,SAA0B,MAAA,EAA4B;AAC9E,EAAA,QAAQ,QAAQ,IAAA;AAAM,IACpB,KAAK,iBAAA;AAAA,IACL,KAAK,WAAA;AACH,MAAA,OAAO,IAAI,mBAAA,CAAoB,OAAA,EAAS,MAAM,CAAA;AAAA,IAChD,KAAK,WAAA;AACH,MAAA,OAAO,IAAI,aAAA,CAAc,OAAA,EAAS,MAAM,CAAA;AAAA,IAC1C,KAAK,mBAAA;AACH,MAAA,OAAO,IAAI,eAAA,CAAgB,OAAA,EAAS,MAAM,CAAA;AAAA,IAC5C,KAAK,cAAA;AACH,MAAA,OAAO,IAAI,cAAA,CAAe,OAAA,EAAS,MAAM,CAAA;AAAA,IAC3C,KAAK,UAAA;AACH,MAAA,OAAO,IAAI,aAAA,CAAc,OAAA,EAAS,MAAM,CAAA;AAAA,IAC1C;AACE,MAAA,OAAO,IAAI,UAAA,CAAW,EAAE,GAAG,OAAA,EAAS,QAAQ,CAAA;AAAA;AAElD;;;AC3EO,IAAM,cAAN,MAAkB;AAAA,EACd,MAAA;AAAA,EACA,OAAA;AAAA,EACA,MAAA;AAAA,EAET,YAAY,IAAA,EAAqB;AAC/B,IAAA,IACE,CAAC,IAAA,CAAK,MAAA,IACL,CAAC,KAAK,MAAA,CAAO,UAAA,CAAW,WAAW,CAAA,IAAK,CAAC,IAAA,CAAK,MAAA,CAAO,UAAA,CAAW,WAAW,CAAA,EAC5E;AACA,MAAA,MAAM,IAAI,UAAA,CAAW;AAAA,QACnB,IAAA,EAAM,mBAAA;AAAA,QACN,OAAA,EACE,4FAAA;AAAA,QACF,MAAA,EAAQ;AAAA,OACT,CAAA;AAAA,IACH;AACA,IAAA,IAAA,CAAK,SAAS,IAAA,CAAK,MAAA;AACnB,IAAA,IAAA,CAAK,WAAW,IAAA,CAAK,OAAA,IAAW,gCAAA,EAAkC,OAAA,CAAQ,OAAO,EAAE,CAAA;AACnF,IAAA,IAAA,CAAK,MAAA,GAAS,IAAA,CAAK,KAAA,IAAS,UAAA,CAAW,KAAA;AACvC,IAAA,IAAI,OAAO,IAAA,CAAK,MAAA,KAAW,UAAA,EAAY;AACrC,MAAA,MAAM,IAAI,UAAA,CAAW;AAAA,QACnB,IAAA,EAAM,UAAA;AAAA,QACN,OAAA,EAAS,yEAAA;AAAA,QACT,MAAA,EAAQ;AAAA,OACT,CAAA;AAAA,IACH;AAAA,EACF;AAAA,EAEA,MAAM,OAAA,CACJ,MAAA,EACA,IAAA,EACA,IAAA,EACY;AACZ,IAAA,MAAM,GAAA,GAAM,CAAA,EAAG,IAAA,CAAK,OAAO,GAAG,IAAI,CAAA,CAAA;AAClC,IAAA,MAAM,GAAA,GAAM,MAAM,IAAA,CAAK,MAAA,CAAO,GAAA,EAAK;AAAA,MACjC,MAAA;AAAA,MACA,OAAA,EAAS;AAAA,QACP,aAAA,EAAe,CAAA,OAAA,EAAU,IAAA,CAAK,MAAM,CAAA,CAAA;AAAA,QACpC,cAAA,EAAgB,kBAAA;AAAA,QAChB,MAAA,EAAQ;AAAA,OACV;AAAA,MACA,MAAM,IAAA,KAAS,MAAA,GAAY,MAAA,GAAY,IAAA,CAAK,UAAU,IAAI;AAAA,KAC3D,CAAA;AAED,IAAA,IAAI,CAAC,IAAI,EAAA,EAAI;AACX,MAAA,IAAI,UAAuC,EAAC;AAC5C,MAAA,IAAI;AACF,QAAA,OAAA,GAAU,MAAM,IAAI,IAAA,EAAK;AAAA,MAC3B,CAAA,CAAA,MAAQ;AAAA,MAER;AACA,MAAA,MAAM,GAAA,GAAM,QAAQ,KAAA,IAAS;AAAA,QAC3B,IAAA,EAAM,UAAA;AAAA,QACN,OAAA,EAAS,CAAA,2BAAA,EAA8B,GAAA,CAAI,MAAM,CAAA,CAAA;AAAA,OACnD;AACA,MAAA,MAAM,SAAA,CAAU,GAAA,EAAK,GAAA,CAAI,MAAM,CAAA;AAAA,IACjC;AAGA,IAAA,IAAI,GAAA,CAAI,MAAA,KAAW,GAAA,EAAK,OAAO,MAAA;AAC/B,IAAA,OAAQ,MAAM,IAAI,IAAA,EAAK;AAAA,EACzB;AACF,CAAA;;;ACzDO,IAAM,iBAAN,MAAqB;AAAA,EAC1B,YAA6B,MAAA,EAAqB;AAArB,IAAA,IAAA,CAAA,MAAA,GAAA,MAAA;AAAA,EAAsB;AAAA,EAAtB,MAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA,EAQ7B,MAAM,OAAO,KAAA,EAAyC;AACpD,IAAA,MAAM,EAAE,KAAA,EAAM,GAAI,MAAM,KAAK,MAAA,CAAO,OAAA;AAAA,MAClC,MAAA;AAAA,MACA,YAAA;AAAA,MACA;AAAA,KACF;AACA,IAAA,OAAO,KAAA;AAAA,EACT;AAAA,EAEA,MAAM,IAAA,GAAyB;AAC7B,IAAA,MAAM,EAAE,MAAA,EAAO,GAAI,MAAM,KAAK,MAAA,CAAO,OAAA;AAAA,MACnC,KAAA;AAAA,MACA;AAAA,KACF;AACA,IAAA,OAAO,MAAA;AAAA,EACT;AAAA,EAEA,MAAM,IAAI,EAAA,EAA4B;AACpC,IAAA,MAAM,EAAE,KAAA,EAAM,GAAI,MAAM,KAAK,MAAA,CAAO,OAAA;AAAA,MAClC,KAAA;AAAA,MACA,CAAA,WAAA,EAAc,kBAAA,CAAmB,EAAE,CAAC,CAAA;AAAA,KACtC;AACA,IAAA,OAAO,KAAA;AAAA,EACT;AACF,CAAA;;;ACpBO,IAAM,mBAAN,MAAuB;AAAA,EAC5B,YAA6B,MAAA,EAAqB;AAArB,IAAA,IAAA,CAAA,MAAA,GAAA,MAAA;AAAA,EAAsB;AAAA,EAAtB,MAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA,EAiB7B,MAAM,OAAO,KAAA,EAA6C;AACxD,IAAA,MAAM,EAAE,OAAA,EAAQ,GAAI,MAAM,KAAK,MAAA,CAAO,OAAA;AAAA,MACpC,MAAA;AAAA,MACA,cAAA;AAAA,MACA;AAAA,KACF;AACA,IAAA,OAAO,OAAA;AAAA,EACT;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA,EAOA,MAAM,IAAI,EAAA,EAA8B;AACtC,IAAA,MAAM,EAAE,OAAA,EAAQ,GAAI,MAAM,KAAK,MAAA,CAAO,OAAA;AAAA,MACpC,KAAA;AAAA,MACA,CAAA,aAAA,EAAgB,kBAAA,CAAmB,EAAE,CAAC,CAAA;AAAA,KACxC;AACA,IAAA,OAAO,OAAA;AAAA,EACT;AACF,CAAA;AC7DA,IAAM,iBAAA,GAAoB,IAAA;AACnB,IAAM,yBAAA,GAA4B,GAAA;AA+BlC,IAAM,mBAAN,MAAuB;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA,EAkB5B,gBAAgB,KAAA,EAAgD;AAC9D,IAAA,OAAO,gBAAgB,KAAK,CAAA;AAAA,EAC9B;AACF,CAAA;AAEO,SAAS,gBAAgB,KAAA,EAAgD;AAC9E,EAAA,MAAM,EAAE,IAAA,EAAM,MAAA,EAAQ,MAAA,EAAO,GAAI,KAAA;AACjC,EAAA,MAAM,GAAA,GAAM,MAAM,GAAA,IAAO,IAAA,CAAK,MAAM,IAAA,CAAK,GAAA,KAAQ,GAAI,CAAA;AACrD,EAAA,MAAM,SAAA,GAAY,MAAM,gBAAA,IAAoB,yBAAA;AAE5C,EAAA,IAAI,CAAC,MAAA,EAAQ,OAAO,EAAE,EAAA,EAAI,KAAA,EAAO,QAAQ,gBAAA,EAAiB;AAE1D,EAAA,IAAI,SAAA,GAA2B,IAAA;AAC/B,EAAA,IAAI,EAAA,GAAoB,IAAA;AACxB,EAAA,KAAA,MAAW,IAAA,IAAQ,MAAA,CAAO,KAAA,CAAM,GAAG,CAAA,EAAG;AACpC,IAAA,MAAM,CAAC,CAAA,EAAG,CAAC,IAAI,IAAA,CAAK,KAAA,CAAM,KAAK,CAAC,CAAA;AAChC,IAAA,IAAI,MAAM,GAAA,EAAK;AACb,MAAA,MAAM,CAAA,GAAI,OAAO,CAAC,CAAA;AAClB,MAAA,IAAI,MAAA,CAAO,QAAA,CAAS,CAAC,CAAA,EAAG,SAAA,GAAY,CAAA;AAAA,IACtC,CAAA,MAAA,IAAW,MAAM,iBAAA,EAAmB;AAClC,MAAA,EAAA,GAAK,CAAA;AAAA,IACP;AAAA,EACF;AAEA,EAAA,IAAI,cAAc,IAAA,EAAM,OAAO,EAAE,EAAA,EAAI,KAAA,EAAO,QAAQ,kBAAA,EAAmB;AACvE,EAAA,IAAI,CAAC,EAAA,EAAI,OAAO,EAAE,EAAA,EAAI,KAAA,EAAO,QAAQ,iBAAA,EAAkB;AAEvD,EAAA,IAAI,IAAA,CAAK,GAAA,CAAI,GAAA,GAAM,SAAS,IAAI,SAAA,EAAW;AACzC,IAAA,OAAO,EAAE,EAAA,EAAI,KAAA,EAAO,MAAA,EAAQ,4BAAA,EAA6B;AAAA,EAC3D;AAEA,EAAA,MAAM,QAAA,GAAW,UAAA,CAAW,QAAA,EAAU,MAAM,CAAA,CACzC,MAAA,CAAO,CAAA,EAAG,SAAS,CAAA,CAAA,EAAI,IAAI,CAAA,CAAE,CAAA,CAC7B,OAAO,KAAK,CAAA;AAEf,EAAA,IAAI,QAAA,CAAS,MAAA,KAAW,EAAA,CAAG,MAAA,EAAQ;AACjC,IAAA,OAAO,EAAE,EAAA,EAAI,KAAA,EAAO,MAAA,EAAQ,oBAAA,EAAqB;AAAA,EACnD;AACA,EAAA,MAAM,CAAA,GAAI,MAAA,CAAO,IAAA,CAAK,QAAA,EAAU,MAAM,CAAA;AACtC,EAAA,MAAM,CAAA,GAAI,MAAA,CAAO,IAAA,CAAK,EAAA,EAAI,MAAM,CAAA;AAChC,EAAA,IAAI,CAAC,eAAA,CAAgB,CAAA,EAAG,CAAC,CAAA,EAAG;AAC1B,IAAA,OAAO,EAAE,EAAA,EAAI,KAAA,EAAO,MAAA,EAAQ,oBAAA,EAAqB;AAAA,EACnD;AACA,EAAA,OAAO,EAAE,IAAI,IAAA,EAAK;AACpB;;;AClGO,IAAM,QAAN,MAAY;AAAA,EACR,MAAA;AAAA,EACA,QAAA;AAAA,EACA,QAAA;AAAA;AAAA;AAAA,EAIQ,MAAA;AAAA,EAEjB,YAAY,IAAA,EAAqB;AAC/B,IAAA,IAAA,CAAK,MAAA,GAAS,IAAI,WAAA,CAAY,IAAI,CAAA;AAClC,IAAA,IAAA,CAAK,MAAA,GAAS,IAAI,cAAA,CAAe,IAAA,CAAK,MAAM,CAAA;AAC5C,IAAA,IAAA,CAAK,QAAA,GAAW,IAAI,gBAAA,CAAiB,IAAA,CAAK,MAAM,CAAA;AAChD,IAAA,IAAA,CAAK,QAAA,GAAW,IAAI,gBAAA,EAAiB;AAAA,EACvC;AACF","file":"index.js","sourcesContent":["// Tally SDK error types. The server returns a consistent shape on every\n// failure — see src/lib/api-errors.ts in the web app — and the SDK turns\n// it into typed exceptions consumers can catch and branch on.\n//\n// Usage in consumer code:\n//\n// try {\n// await tally.agents.upsert({ id: \"research-bot\", display_name: \"...\" });\n// } catch (e) {\n// if (e instanceof AuthenticationError) { /* rotate key */ }\n// if (e instanceof ValidationError) { /* fix input */ }\n// throw e;\n// }\n\nexport type ApiErrorPayload = {\n type: string;\n message: string;\n code?: string;\n details?: unknown;\n};\n\nexport class TallyError extends Error {\n readonly type: string;\n readonly code?: string;\n readonly status: number;\n readonly details?: unknown;\n\n constructor(opts: {\n type: string;\n message: string;\n status: number;\n code?: string;\n details?: unknown;\n }) {\n super(opts.message);\n this.name = \"TallyError\";\n this.type = opts.type;\n this.code = opts.code;\n this.status = opts.status;\n this.details = opts.details;\n }\n}\n\nexport class AuthenticationError extends TallyError {\n constructor(payload: ApiErrorPayload, status: number) {\n super({ ...payload, status });\n this.name = \"AuthenticationError\";\n }\n}\n\nexport class NotFoundError extends TallyError {\n constructor(payload: ApiErrorPayload, status: number) {\n super({ ...payload, status });\n this.name = \"NotFoundError\";\n }\n}\n\nexport class ValidationError extends TallyError {\n constructor(payload: ApiErrorPayload, status: number) {\n super({ ...payload, status });\n this.name = \"ValidationError\";\n }\n}\n\nexport class RateLimitError extends TallyError {\n constructor(payload: ApiErrorPayload, status: number) {\n super({ ...payload, status });\n this.name = \"RateLimitError\";\n }\n}\n\nexport class ConflictError extends TallyError {\n constructor(payload: ApiErrorPayload, status: number) {\n super({ ...payload, status });\n this.name = \"ConflictError\";\n }\n}\n\n// Maps server error.type → typed exception class.\nexport function makeError(payload: ApiErrorPayload, status: number): TallyError {\n switch (payload.type) {\n case \"unauthenticated\":\n case \"forbidden\":\n return new AuthenticationError(payload, status);\n case \"not_found\":\n return new NotFoundError(payload, status);\n case \"validation_failed\":\n return new ValidationError(payload, status);\n case \"rate_limited\":\n return new RateLimitError(payload, status);\n case \"conflict\":\n return new ConflictError(payload, status);\n default:\n return new TallyError({ ...payload, status });\n }\n}\n","import { makeError, TallyError, type ApiErrorPayload } from \"./errors\";\n\n// Thin HTTP client used by every resource. Centralizes:\n// - Authorization header\n// - JSON request/response handling\n// - Error mapping (server error.type → typed exception)\n//\n// Uses Node 18+'s native fetch. Works in browsers too if you ever want to\n// hit the API from one — though the more common pattern is server-to-server\n// from the consumer's agent infrastructure.\n\nexport type ClientOptions = {\n /** Tally API key (begins with `tly_`). */\n apiKey: string;\n /** Base URL of the Tally API. Defaults to https://app.tallyforagents.com. Override for local dev or self-hosting. */\n baseUrl?: string;\n /** Custom fetch implementation. Useful for tests or non-Node runtimes. */\n fetch?: typeof fetch;\n};\n\nexport class TallyClient {\n readonly apiKey: string;\n readonly baseUrl: string;\n readonly #fetch: typeof fetch;\n\n constructor(opts: ClientOptions) {\n if (\n !opts.apiKey ||\n (!opts.apiKey.startsWith(\"tly_test_\") && !opts.apiKey.startsWith(\"tly_live_\"))\n ) {\n throw new TallyError({\n type: \"validation_failed\",\n message:\n \"Invalid API key. Tally keys start with 'tly_test_' (test mode) or 'tly_live_' (live mode).\",\n status: 0,\n });\n }\n this.apiKey = opts.apiKey;\n this.baseUrl = (opts.baseUrl ?? \"https://app.tallyforagents.com\").replace(/\\/$/, \"\");\n this.#fetch = opts.fetch ?? globalThis.fetch;\n if (typeof this.#fetch !== \"function\") {\n throw new TallyError({\n type: \"internal\",\n message: \"fetch is not available in this runtime. Provide one via `fetch` option.\",\n status: 0,\n });\n }\n }\n\n async request<T>(\n method: \"GET\" | \"POST\" | \"DELETE\",\n path: string,\n body?: unknown,\n ): Promise<T> {\n const url = `${this.baseUrl}${path}`;\n const res = await this.#fetch(url, {\n method,\n headers: {\n Authorization: `Bearer ${this.apiKey}`,\n \"Content-Type\": \"application/json\",\n Accept: \"application/json\",\n },\n body: body === undefined ? undefined : JSON.stringify(body),\n });\n\n if (!res.ok) {\n let payload: { error?: ApiErrorPayload } = {};\n try {\n payload = await res.json();\n } catch {\n // Non-JSON error body — fall back to a generic message.\n }\n const err = payload.error ?? {\n type: \"internal\",\n message: `Request failed with status ${res.status}.`,\n };\n throw makeError(err, res.status);\n }\n\n // 204 No Content (e.g., future delete endpoints)\n if (res.status === 204) return undefined as T;\n return (await res.json()) as T;\n }\n}\n","import type { TallyClient } from \"../client\";\n\nexport type Agent = {\n /** Stable user-provided identifier (e.g., \"research-bot\"). */\n id: string;\n /** \"active\" — at least one activated (non-revoked) signer is attached.\n * \"no_permissions\" — no activated signer (may still have pending\n * grants — see pending_signers). */\n status: \"no_permissions\" | \"active\";\n mode: \"test\" | \"live\";\n /** ISO 8601 timestamp. */\n created_at: string;\n /** Count of non-revoked, activated signers. */\n active_signers: number;\n /** Count of non-revoked, not-yet-activated signers. */\n pending_signers: number;\n};\n\nexport type AgentUpsertInput = {\n /** User-provided stable string identifier (e.g., \"research-bot\"). */\n id: string;\n};\n\n// agents.upsert / list / get — Phase 2's full surface.\n// Future phases will add: agents.delete, agents.permissions.*, etc.\n\nexport class AgentsResource {\n constructor(private readonly client: TallyClient) {}\n\n /**\n * Creates the agent if it doesn't exist; no-op if it does.\n *\n * Scope: the active API key's account + mode. Same id can exist\n * independently in test and live mode for the same account.\n */\n async upsert(input: AgentUpsertInput): Promise<Agent> {\n const { agent } = await this.client.request<{ agent: Agent }>(\n \"POST\",\n \"/v1/agents\",\n input,\n );\n return agent;\n }\n\n async list(): Promise<Agent[]> {\n const { agents } = await this.client.request<{ agents: Agent[] }>(\n \"GET\",\n \"/v1/agents\",\n );\n return agents;\n }\n\n async get(id: string): Promise<Agent> {\n const { agent } = await this.client.request<{ agent: Agent }>(\n \"GET\",\n `/v1/agents/${encodeURIComponent(id)}`,\n );\n return agent;\n }\n}\n","import type { TallyClient } from \"../client\";\n\nexport type Payment = {\n id: string;\n /** \"pending\" until the on-chain tx confirms; updated by a follow-up\n * GET (planned) or webhook (Phase 6). */\n status: \"pending\" | \"confirmed\" | \"failed\";\n /** Transaction hash on the underlying chain. Set as soon as Privy\n * accepts the signed RPC; the SDK does not wait for receipt. */\n tx_hash: string | null;\n amount_usdc: string;\n to: string;\n from: string;\n memo: string | null;\n idempotency_key: string | null;\n created_at: string;\n};\n\nexport type PaymentCreateInput = {\n /** Agent's user-provided id (the externalId), e.g. \"research-bot\". */\n agent_id: string;\n /** Sender wallet address. Must be in this API key's account+mode and\n * must have an active permission grant for the agent. */\n wallet: string;\n /** Recipient EVM address. */\n to: string;\n /** Decimal USDC amount as a string, e.g. \"10\" or \"2.50\". */\n amount_usdc: string;\n /** Optional memo (max 200 chars), stored alongside the transaction. */\n memo?: string;\n /** Optional idempotency key (max 64 chars). Scoped to (account, mode);\n * retries with the same key return the original Payment without\n * resubmitting on-chain. */\n idempotency_key?: string;\n};\n\n// payments.create — single endpoint for Phase 4.\n// Future phases will add: list, get (with chain status refresh), retry.\n\nexport class PaymentsResource {\n constructor(private readonly client: TallyClient) {}\n\n /**\n * Submits a USDC payment from one of the agent's granted wallets.\n *\n * Enforcement happens in two layers:\n * 1. Tally checks policy (per-tx max, recipient/contract allowlist,\n * expiry, daily cap) and rejects with a structured error before\n * any Privy call.\n * 2. Privy's secure enclave independently checks the per-tx max and\n * recipient/contract conditions encoded in the signer's policy.\n *\n * Returns immediately with `status: \"pending\"` and the on-chain\n * `tx_hash` once Privy accepts the signed RPC. The SDK does not wait\n * for block confirmation; the Transaction row is updated by a\n * separate confirmation flow (forthcoming).\n */\n async create(input: PaymentCreateInput): Promise<Payment> {\n const { payment } = await this.client.request<{ payment: Payment }>(\n \"POST\",\n \"/v1/payments\",\n input,\n );\n return payment;\n }\n\n /**\n * Fetches the current state of a payment by id. If still pending on\n * Tally's side, this call lazily refreshes from the chain — so polling\n * this is the canonical way to wait for `confirmed` / `failed`.\n */\n async get(id: string): Promise<Payment> {\n const { payment } = await this.client.request<{ payment: Payment }>(\n \"GET\",\n `/v1/payments/${encodeURIComponent(id)}`,\n );\n return payment;\n }\n}\n","import { createHmac, timingSafeEqual } from \"node:crypto\";\n\n// Webhook helpers for SDK consumers. Tally signs every delivery with\n// HMAC-SHA256 over `${timestamp}.${body}` and sends the signature in\n// the `tally-signature` header:\n//\n// tally-signature: t=<unix_seconds>,v1=<hex_lowercase>\n//\n// Receivers MUST verify before trusting any payload — otherwise a\n// known URL is forge-able. Pass the RAW request body (not the\n// JSON-parsed object) so the bytes match what Tally signed.\n//\n// The verifier is intentionally duplicated from the server-side\n// implementation (src/lib/webhook-sign.ts) to keep the SDK\n// self-contained — external consumers shouldn't depend on Tally's\n// server code.\n\nconst SIGNATURE_VERSION = \"v1\";\nexport const DEFAULT_TOLERANCE_SECONDS = 300; // 5 minutes\n\nexport type WebhookVerifyResult =\n | { ok: true }\n | {\n ok: false;\n reason:\n | \"missing_header\"\n | \"malformed_header\"\n | \"no_v1_signature\"\n | \"timestamp_out_of_tolerance\"\n | \"signature_mismatch\";\n };\n\nexport type WebhookVerifyInput = {\n /** Raw request body the receiver got — string, exactly the bytes\n * Tally signed. Don't pass a JSON-parsed object. */\n body: string;\n /** Value of the `tally-signature` header on the incoming request. */\n header: string | null | undefined;\n /** The plaintext signing secret you saved at webhook creation\n * (`whsec_<mode>_<base64url>`). */\n secret: string;\n /** Unix seconds. Defaults to now. Pin for tests. */\n now?: number;\n /** How far the header timestamp may drift from `now`. Defaults to\n * 300s (5 min). Tighten or loosen to taste; the wider the window,\n * the longer a stolen signature stays replayable. */\n toleranceSeconds?: number;\n};\n\nexport class WebhooksResource {\n /**\n * Verify a `tally-signature` header against the request body. Use in\n * your webhook handler before processing the payload.\n *\n * @example\n * ```ts\n * const body = await req.text(); // raw, not JSON-parsed\n * const result = tally.webhooks.verifySignature({\n * body,\n * header: req.headers.get(\"tally-signature\"),\n * secret: process.env.TALLY_WEBHOOK_SECRET!,\n * });\n * if (!result.ok) return new Response(\"invalid signature\", { status: 400 });\n * const event = JSON.parse(body);\n * // ... handle event\n * ```\n */\n verifySignature(input: WebhookVerifyInput): WebhookVerifyResult {\n return verifySignature(input);\n }\n}\n\nexport function verifySignature(input: WebhookVerifyInput): WebhookVerifyResult {\n const { body, header, secret } = input;\n const now = input.now ?? Math.floor(Date.now() / 1000);\n const tolerance = input.toleranceSeconds ?? DEFAULT_TOLERANCE_SECONDS;\n\n if (!header) return { ok: false, reason: \"missing_header\" };\n\n let timestamp: number | null = null;\n let v1: string | null = null;\n for (const part of header.split(\",\")) {\n const [k, v] = part.split(\"=\", 2);\n if (k === \"t\") {\n const n = Number(v);\n if (Number.isFinite(n)) timestamp = n;\n } else if (k === SIGNATURE_VERSION) {\n v1 = v;\n }\n }\n\n if (timestamp === null) return { ok: false, reason: \"malformed_header\" };\n if (!v1) return { ok: false, reason: \"no_v1_signature\" };\n\n if (Math.abs(now - timestamp) > tolerance) {\n return { ok: false, reason: \"timestamp_out_of_tolerance\" };\n }\n\n const expected = createHmac(\"sha256\", secret)\n .update(`${timestamp}.${body}`)\n .digest(\"hex\");\n\n if (expected.length !== v1.length) {\n return { ok: false, reason: \"signature_mismatch\" };\n }\n const a = Buffer.from(expected, \"utf8\");\n const b = Buffer.from(v1, \"utf8\");\n if (!timingSafeEqual(a, b)) {\n return { ok: false, reason: \"signature_mismatch\" };\n }\n return { ok: true };\n}\n","// @tallyforagents/sdk — the financial OS for AI agents.\n//\n// Usage:\n// import { Tally } from \"@tallyforagents/sdk\";\n//\n// const tally = new Tally({ apiKey: process.env.TALLY_API_KEY! });\n// await tally.agents.upsert({ id: \"research-bot\", display_name: \"Research Bot\" });\n\nimport { TallyClient, type ClientOptions } from \"./client\";\nimport { AgentsResource } from \"./resources/agents\";\nimport { PaymentsResource } from \"./resources/payments\";\nimport { WebhooksResource } from \"./resources/webhooks\";\n\nexport class Tally {\n readonly agents: AgentsResource;\n readonly payments: PaymentsResource;\n readonly webhooks: WebhooksResource;\n\n // Expose the underlying client for advanced use (custom retries, etc.).\n // Internal callers go through the resource classes instead.\n private readonly client: TallyClient;\n\n constructor(opts: ClientOptions) {\n this.client = new TallyClient(opts);\n this.agents = new AgentsResource(this.client);\n this.payments = new PaymentsResource(this.client);\n this.webhooks = new WebhooksResource();\n }\n}\n\nexport type { ClientOptions } from \"./client\";\nexport type { Agent, AgentUpsertInput } from \"./resources/agents\";\nexport type { Payment, PaymentCreateInput } from \"./resources/payments\";\nexport type {\n WebhookVerifyInput,\n WebhookVerifyResult,\n} from \"./resources/webhooks\";\nexport { verifySignature } from \"./resources/webhooks\";\nexport {\n TallyError,\n AuthenticationError,\n NotFoundError,\n ValidationError,\n RateLimitError,\n ConflictError,\n} from \"./errors\";\n"]}
|
package/package.json
ADDED
|
@@ -0,0 +1,61 @@
|
|
|
1
|
+
{
|
|
2
|
+
"name": "@tallyforagents/sdk",
|
|
3
|
+
"version": "0.1.0",
|
|
4
|
+
"description": "Tally TypeScript SDK — give AI agents USDC wallets and scoped programmable spending on Base.",
|
|
5
|
+
"keywords": [
|
|
6
|
+
"tally",
|
|
7
|
+
"agents",
|
|
8
|
+
"ai",
|
|
9
|
+
"usdc",
|
|
10
|
+
"stablecoin",
|
|
11
|
+
"payments",
|
|
12
|
+
"base",
|
|
13
|
+
"ethereum",
|
|
14
|
+
"non-custodial",
|
|
15
|
+
"sdk",
|
|
16
|
+
"typescript"
|
|
17
|
+
],
|
|
18
|
+
"homepage": "https://www.tallyforagents.com",
|
|
19
|
+
"bugs": {
|
|
20
|
+
"url": "https://github.com/pkohler95/tally-v2/issues"
|
|
21
|
+
},
|
|
22
|
+
"repository": {
|
|
23
|
+
"type": "git",
|
|
24
|
+
"url": "git+https://github.com/pkohler95/tally-v2.git",
|
|
25
|
+
"directory": "packages/sdk"
|
|
26
|
+
},
|
|
27
|
+
"license": "MIT",
|
|
28
|
+
"author": "Tally <hello@tallyforagents.com>",
|
|
29
|
+
"type": "module",
|
|
30
|
+
"main": "./dist/index.cjs",
|
|
31
|
+
"module": "./dist/index.js",
|
|
32
|
+
"types": "./dist/index.d.ts",
|
|
33
|
+
"exports": {
|
|
34
|
+
".": {
|
|
35
|
+
"types": "./dist/index.d.ts",
|
|
36
|
+
"import": "./dist/index.js",
|
|
37
|
+
"require": "./dist/index.cjs"
|
|
38
|
+
},
|
|
39
|
+
"./package.json": "./package.json"
|
|
40
|
+
},
|
|
41
|
+
"files": [
|
|
42
|
+
"dist",
|
|
43
|
+
"README.md",
|
|
44
|
+
"LICENSE"
|
|
45
|
+
],
|
|
46
|
+
"sideEffects": false,
|
|
47
|
+
"engines": {
|
|
48
|
+
"node": ">=18"
|
|
49
|
+
},
|
|
50
|
+
"publishConfig": {
|
|
51
|
+
"access": "public"
|
|
52
|
+
},
|
|
53
|
+
"devDependencies": {
|
|
54
|
+
"tsup": "^8.3.5",
|
|
55
|
+
"typescript": "^5"
|
|
56
|
+
},
|
|
57
|
+
"scripts": {
|
|
58
|
+
"build": "tsup",
|
|
59
|
+
"typecheck": "tsc --noEmit"
|
|
60
|
+
}
|
|
61
|
+
}
|