@mandatum/sdk 1.0.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/README.md ADDED
@@ -0,0 +1,212 @@
1
+ # @mandatum/sdk
2
+
3
+ > **Zero-trust authorization and spending limits for AI agents.** One line of code to verify permissions, enforce budgets, and generate tamper-proof audit receipts.
4
+
5
+ [![npm](https://img.shields.io/npm/v/@mandatum/sdk)](https://www.npmjs.com/package/@mandatum/sdk)
6
+ [![License: MIT](https://img.shields.io/badge/License-MIT-blue.svg)](https://opensource.org/licenses/MIT)
7
+
8
+ ## Install
9
+
10
+ ```bash
11
+ npm install @mandatum/sdk
12
+ ```
13
+
14
+ ## Quick Start
15
+
16
+ ```javascript
17
+ import { Mandatum } from '@mandatum/sdk';
18
+
19
+ const mandatum = new Mandatum({
20
+ apiKey: 'mdt_live_xxxxxxxxxxxxxxxx',
21
+ baseUrl: 'https://mandatum-fbg9.onrender.com', // or your self-hosted URL
22
+ });
23
+
24
+ // ─── 1. Register an Agent ───────────────────────────────
25
+ const agent = await mandatum.agents.create({
26
+ name: 'PaymentBot',
27
+ type: 'payment',
28
+ delegator: 'cfo@yourcompany.com',
29
+ });
30
+
31
+ // ─── 2. Create a Spending Policy ────────────────────────
32
+ const policy = await mandatum.policies.create({
33
+ name: 'cloud-budget',
34
+ resource: 'payments',
35
+ actions: ['payments:execute'],
36
+ constraints: {
37
+ max_amount: 5000,
38
+ daily_limit: 10000,
39
+ currency: 'INR',
40
+ },
41
+ });
42
+ await mandatum.policies.assign(agent.id, policy.id);
43
+
44
+ // ─── 3. Issue a Scoped Token ────────────────────────────
45
+ const { token } = await mandatum.tokens.issue({
46
+ agentId: agent.id,
47
+ scopes: ['payments:execute'],
48
+ constraints: { max_amount: 5000, currency: 'INR' },
49
+ expiresIn: '1h',
50
+ });
51
+
52
+ // ─── 4. The 1-Line Permission Gate ─────────────────────
53
+ const result = await mandatum.check(token, 'payments:execute', {
54
+ amount: 350,
55
+ currency: 'INR',
56
+ vendor: 'DigitalOcean',
57
+ });
58
+
59
+ if (result.allowed) {
60
+ console.log('✅ Approved — receipt:', result.receipt.receiptId);
61
+ // Proceed with payment
62
+ } else {
63
+ console.log('❌ Blocked:', result.reason);
64
+ }
65
+ ```
66
+
67
+ ## Core Concepts
68
+
69
+ ### The Permission Gate
70
+
71
+ Every agent action goes through `mandatum.check()`. It verifies the JWT signature (Ed25519), checks scopes, evaluates spending constraints, enforces rate limits, and returns a cryptographically signed receipt — all in one call.
72
+
73
+ ```javascript
74
+ const result = await mandatum.check(token, action, context);
75
+ // result.allowed → boolean
76
+ // result.reason → why it was blocked (if denied)
77
+ // result.receipt → { receiptId, signature, timestamp }
78
+ // result.checks → detailed constraint evaluations
79
+ // result.rateLimit → current usage stats
80
+ ```
81
+
82
+ ### Quick Boolean Check
83
+
84
+ If you just need a true/false gate:
85
+
86
+ ```javascript
87
+ if (await mandatum.isAllowed(token, 'email:send', { to: 'user@example.com' })) {
88
+ sendEmail();
89
+ }
90
+ ```
91
+
92
+ ## API Reference
93
+
94
+ ### `new Mandatum(config)`
95
+
96
+ | Option | Type | Default | Description |
97
+ |---|---|---|---|
98
+ | `apiKey` | `string` | *required* | Your API key (`mdt_live_...`) |
99
+ | `baseUrl` | `string` | `http://localhost:3001` | Mandatum API URL |
100
+ | `timeout` | `number` | `10000` | Request timeout in ms |
101
+
102
+ ### Agents
103
+
104
+ ```javascript
105
+ mandatum.agents.create({ name, type, delegator, description })
106
+ mandatum.agents.list({ status, search, environment })
107
+ mandatum.agents.get(id)
108
+ mandatum.agents.suspend(id)
109
+ mandatum.agents.revoke(id) // cascading — revokes all child agents + tokens
110
+ mandatum.agents.spend(id, { amount, currency, vendor, category })
111
+ mandatum.agents.spending(id) // returns today/total spent + remaining budget
112
+ ```
113
+
114
+ ### Tokens
115
+
116
+ ```javascript
117
+ mandatum.tokens.issue({ agentId, scopes, constraints, expiresIn })
118
+ mandatum.tokens.verify(token, action, context)
119
+ mandatum.tokens.revoke(jti)
120
+ mandatum.tokens.list({ agentId, status })
121
+ ```
122
+
123
+ ### Policies
124
+
125
+ ```javascript
126
+ mandatum.policies.create({ name, resource, actions, constraints })
127
+ mandatum.policies.list()
128
+ mandatum.policies.get(id)
129
+ mandatum.policies.assign(agentId, policyId)
130
+ ```
131
+
132
+ ### Audit
133
+
134
+ ```javascript
135
+ mandatum.audit.list({ limit, agentId })
136
+ mandatum.audit.verifyChain() // verify hash-chain integrity
137
+ mandatum.audit.getReceipt(eventId) // get signed receipt for an event
138
+ ```
139
+
140
+ ## Payment Gateway Wrappers
141
+
142
+ Pre-built wrappers that call `mandatum.check()` before creating payments:
143
+
144
+ ### Razorpay
145
+
146
+ ```javascript
147
+ import { MandatumRazorpay } from '@mandatum/sdk/payments/razorpay';
148
+
149
+ const pay = new MandatumRazorpay(razorpayClient, mandatum, agent.id);
150
+
151
+ const result = await pay.createPayment({
152
+ amount: 50000, // paise
153
+ currency: 'INR',
154
+ description: 'Cloud hosting',
155
+ });
156
+ // Automatically checks limits → creates Razorpay order → records spending
157
+ ```
158
+
159
+ ### Stripe
160
+
161
+ ```javascript
162
+ import { MandatumStripe } from '@mandatum/sdk/payments/stripe';
163
+
164
+ const pay = new MandatumStripe(stripeClient, mandatum, agent.id);
165
+
166
+ const result = await pay.createPaymentIntent({
167
+ amount: 5000, // cents
168
+ currency: 'usd',
169
+ });
170
+ ```
171
+
172
+ ## Local Verification (Sub-1ms)
173
+
174
+ For latency-critical paths, verify token signatures locally without a network round-trip:
175
+
176
+ ```javascript
177
+ const mandatum = new Mandatum({
178
+ apiKey: 'mdt_live_...',
179
+ localVerification: true, // fetches public key on init
180
+ });
181
+
182
+ // check() now verifies the signature locally first,
183
+ // then calls the server only for constraint evaluation
184
+ ```
185
+
186
+ ## Error Handling
187
+
188
+ All errors throw `MandatumError` with a `code` property:
189
+
190
+ ```javascript
191
+ import { MandatumError } from '@mandatum/sdk';
192
+
193
+ try {
194
+ await mandatum.check(token, 'payments:execute', { amount: 999999 });
195
+ } catch (err) {
196
+ if (err instanceof MandatumError) {
197
+ console.log(err.code); // 'RATE_LIMIT_MINUTE', 'TIMEOUT', 'API_ERROR'
198
+ console.log(err.status); // HTTP status code (if applicable)
199
+ }
200
+ }
201
+ ```
202
+
203
+ ## Security
204
+
205
+ - All tokens are **Ed25519-signed JWTs** — tamper-proof and verifiable with the public key
206
+ - API keys are **SHA-256 hashed** before storage — Mandatum never stores raw keys
207
+ - Audit events are **hash-chained** — any tampering breaks the chain
208
+ - Receipts are **cryptographically signed** — non-repudiable proof of authorization
209
+
210
+ ## License
211
+
212
+ MIT © [Mandatum](https://mandatumio.vercel.app)
package/index.js ADDED
@@ -0,0 +1,318 @@
1
+ /**
2
+ * @mandatum/sdk — Mandatum SDK for Agent Platforms
3
+ *
4
+ * Install: npm install @mandatum/sdk
5
+ *
6
+ * Usage:
7
+ * import { Mandatum } from '@mandatum/sdk';
8
+ * const m = new Mandatum({ apiKey: 'mdt_live_...' });
9
+ *
10
+ * // The 1-line permission gate
11
+ * const ok = await m.check(token, 'payments:execute', { amount: 350 });
12
+ * if (ok.allowed) { ... }
13
+ */
14
+
15
+ class MandatumError extends Error {
16
+ constructor(message, code, status) {
17
+ super(message);
18
+ this.name = 'MandatumError';
19
+ this.code = code;
20
+ this.status = status;
21
+ }
22
+ }
23
+
24
+ export class Mandatum {
25
+ /**
26
+ * @param {object} config
27
+ * @param {string} config.apiKey - Mandatum API key (mdt_live_...)
28
+ * @param {string} [config.baseUrl] - API base URL (default: https://api.mandatum.io)
29
+ * @param {number} [config.timeout] - Request timeout in ms (default: 10000)
30
+ * @param {boolean} [config.localVerification] - Enable local token verification (default: false)
31
+ */
32
+ constructor({ apiKey, baseUrl, timeout = 10000, localVerification = false } = {}) {
33
+ if (!apiKey) throw new MandatumError('API key is required', 'MISSING_API_KEY');
34
+ this.apiKey = apiKey;
35
+ this.baseUrl = (baseUrl || 'http://localhost:3001').replace(/\/$/, '');
36
+ this.timeout = timeout;
37
+
38
+ // Sub-modules
39
+ this.agents = new AgentsAPI(this);
40
+ this.tokens = new TokensAPI(this);
41
+ this.policies = new PoliciesAPI(this);
42
+ this.audit = new AuditAPI(this);
43
+
44
+ // Local verification — store as ready promise (no async in constructor)
45
+ this._localVerifierReady = localVerification
46
+ ? this._initLocalVerifier()
47
+ : Promise.resolve();
48
+ }
49
+
50
+ /**
51
+ * ─── THE 1-LINE PERMISSION GATE ───────────────────────
52
+ *
53
+ * Call this before every agent action.
54
+ * Returns { allowed, reason, receipt, checks }
55
+ *
56
+ * @param {string} token - The agent's JWT token
57
+ * @param {string} action - What the agent wants to do (e.g., 'payments:execute')
58
+ * @param {object} [context] - Action context (amount, currency, vendor, etc.)
59
+ * @returns {Promise<object>}
60
+ *
61
+ * @example
62
+ * const result = await mandatum.check(agentToken, 'payments:execute', {
63
+ * amount: 350,
64
+ * currency: 'INR',
65
+ * vendor: 'DigitalOcean'
66
+ * });
67
+ *
68
+ * if (result.allowed) {
69
+ * // Proceed with payment
70
+ * razorpay.pay(350);
71
+ * } else {
72
+ * console.log('Blocked:', result.reason);
73
+ * }
74
+ */
75
+ async check(token, action, context = {}) {
76
+ // Use local verification if available (sub-1ms)
77
+ if (this._localVerifier) {
78
+ const local = await this._localVerifier.verify(token);
79
+ if (!local.valid) {
80
+ return { allowed: false, valid: false, reason: local.error, code: local.code };
81
+ }
82
+ // Local can't check constraints fully — call server for that
83
+ }
84
+
85
+ const result = await this._request('POST', '/tokens/verify', { token, action, context });
86
+ const CHECK_DEFAULTS = {
87
+ allowed: false, valid: false, agent: undefined, delegator: undefined,
88
+ reason: null, receipt: null, checks: [], scopes: [],
89
+ constraints: {}, expiresAt: undefined, error: null, code: null,
90
+ };
91
+ return { ...CHECK_DEFAULTS, ...result };
92
+ }
93
+
94
+ /**
95
+ * Quick permission check — returns just true/false
96
+ */
97
+ async isAllowed(token, action, context = {}) {
98
+ const result = await this.check(token, action, context);
99
+ return result.allowed === true;
100
+ }
101
+
102
+ /**
103
+ * Get server health status
104
+ */
105
+ async health() {
106
+ return this._request('GET', '/health', null, false);
107
+ }
108
+
109
+ /**
110
+ * Get server public key (for local verification)
111
+ */
112
+ async getPublicKey() {
113
+ return this._request('GET', '/.well-known/public-key', null, false);
114
+ }
115
+
116
+ // ─── Internal HTTP Client ─────────────────────────────
117
+
118
+ async _request(method, path, body, authenticated = true) {
119
+ const url = `${this.baseUrl}/v1${path}`;
120
+ const headers = { 'Content-Type': 'application/json' };
121
+
122
+ if (authenticated) {
123
+ headers['Authorization'] = `Bearer ${this.apiKey}`;
124
+ }
125
+
126
+ const controller = new AbortController();
127
+ const timeoutId = setTimeout(() => controller.abort(), this.timeout);
128
+
129
+ try {
130
+ const res = await fetch(url, {
131
+ method,
132
+ headers,
133
+ body: body ? JSON.stringify(body) : undefined,
134
+ signal: controller.signal,
135
+ });
136
+
137
+ clearTimeout(timeoutId);
138
+ const data = await res.json();
139
+
140
+ if (!res.ok) {
141
+ throw new MandatumError(
142
+ data.error || data.message || `HTTP ${res.status}`,
143
+ data.code || 'API_ERROR',
144
+ res.status
145
+ );
146
+ }
147
+
148
+ return data;
149
+ } catch (err) {
150
+ clearTimeout(timeoutId);
151
+ if (err.name === 'AbortError') {
152
+ throw new MandatumError('Request timed out', 'TIMEOUT');
153
+ }
154
+ if (err instanceof MandatumError) throw err;
155
+ throw new MandatumError(err.message, 'NETWORK_ERROR');
156
+ }
157
+ }
158
+
159
+ async _initLocalVerifier() {
160
+ try {
161
+ const { LocalVerifier } = await import('./local-verifier.js');
162
+ this._localVerifier = new LocalVerifier(this);
163
+ await this._localVerifier.init();
164
+ } catch (err) {
165
+ console.warn('Mandatum: Local verification not available, using remote only');
166
+ }
167
+ }
168
+ }
169
+
170
+ // ─── Agents Sub-API ─────────────────────────────────────
171
+
172
+ class AgentsAPI {
173
+ constructor(client) { this.client = client; }
174
+
175
+ /**
176
+ * Register a new AI agent.
177
+ * Generates an Ed25519 key pair on the server.
178
+ *
179
+ * @param {object} params
180
+ * @param {string} params.name - Agent name
181
+ * @param {string} params.type - Agent type (payment, email, calendar, etc.)
182
+ * @param {string} [params.description]
183
+ * @param {string} [params.delegator] - Human who authorized this agent
184
+ */
185
+ async create({ name, type, description, delegator }) {
186
+ return this.client._request('POST', '/agents', { name, type, description, delegator });
187
+ }
188
+
189
+ /** List all agents */
190
+ async list(params = {}) {
191
+ const qs = new URLSearchParams(params).toString();
192
+ return this.client._request('GET', `/agents${qs ? '?' + qs : ''}`);
193
+ }
194
+
195
+ /** Get agent details (includes keys, policies, tokens, activity) */
196
+ async get(id) {
197
+ return this.client._request('GET', `/agents/${id}`);
198
+ }
199
+
200
+ /** Update agent status (active, suspended, revoked) */
201
+ async update(id, body) {
202
+ return this.client._request('PATCH', `/agents/${id}`, body);
203
+ }
204
+
205
+ /** Suspend an agent */
206
+ async suspend(id) { return this.update(id, { status: 'suspended' }); }
207
+
208
+ /** Revoke an agent (and all its tokens) */
209
+ async revoke(id) { return this.update(id, { status: 'revoked' }); }
210
+
211
+ /**
212
+ * Record a spending event — the SDK calls this before/after payment.
213
+ * Returns { allowed, eventId, amount, spending } or { allowed: false, reason }.
214
+ *
215
+ * @param {string} id - Agent ID
216
+ * @param {object} params
217
+ * @param {number} params.amount - Spending amount
218
+ * @param {string} [params.currency] - Currency (default: INR)
219
+ * @param {string} [params.vendor] - Vendor name
220
+ * @param {string} [params.category] - Category (e.g., 'cloud', 'groceries')
221
+ * @param {string} [params.description] - Human-readable description
222
+ *
223
+ * @example
224
+ * const result = await mandatum.agents.spend('agt_abc123', {
225
+ * amount: 350, currency: 'INR', vendor: 'DigitalOcean'
226
+ * });
227
+ * if (result.allowed) { // proceed with payment }
228
+ */
229
+ async spend(id, { amount, currency, vendor, category, description } = {}) {
230
+ return this.client._request('POST', `/agents/${id}/spend`, {
231
+ amount, currency, vendor, category, description,
232
+ });
233
+ }
234
+
235
+ /**
236
+ * Get spending summary for an agent.
237
+ * Returns today/total spent, remaining budget, recent events.
238
+ */
239
+ async spending(id) {
240
+ return this.client._request('GET', `/agents/${id}/spending`);
241
+ }
242
+ }
243
+
244
+ // ─── Tokens Sub-API ─────────────────────────────────────
245
+
246
+ class TokensAPI {
247
+ constructor(client) { this.client = client; }
248
+
249
+ /**
250
+ * Issue a signed JWT token for an agent.
251
+ *
252
+ * @param {object} params
253
+ * @param {string} params.agentId - Agent to issue token for
254
+ * @param {string[]} params.scopes - Allowed actions
255
+ * @param {object} [params.constraints] - Limits (max_amount, currency, etc.)
256
+ * @param {string} [params.expiresIn] - Duration ('15m', '1h', '30d')
257
+ */
258
+ async issue({ agentId, scopes, constraints, policyId, expiresIn }) {
259
+ return this.client._request('POST', '/tokens/issue', {
260
+ agentId, scopes, constraints, policyId, expiresIn,
261
+ });
262
+ }
263
+
264
+ /**
265
+ * Verify a token (signature + constraints + revocation).
266
+ * This is what mandatum.check() calls internally.
267
+ */
268
+ async verify(token, action, context) {
269
+ return this.client._request('POST', '/tokens/verify', { token, action, context });
270
+ }
271
+
272
+ /** Revoke a token immediately */
273
+ async revoke(jti) {
274
+ return this.client._request('POST', '/tokens/revoke', { jti });
275
+ }
276
+
277
+ /** List all tokens */
278
+ async list(params = {}) {
279
+ const qs = new URLSearchParams(params).toString();
280
+ return this.client._request('GET', `/tokens${qs ? '?' + qs : ''}`);
281
+ }
282
+ }
283
+
284
+ // ─── Policies Sub-API ───────────────────────────────────
285
+
286
+ class PoliciesAPI {
287
+ constructor(client) { this.client = client; }
288
+
289
+ async create({ name, resource, actions, constraints, description }) {
290
+ return this.client._request('POST', '/policies', { name, resource, actions, constraints, description });
291
+ }
292
+
293
+ async list() { return this.client._request('GET', '/policies'); }
294
+
295
+ async get(id) { return this.client._request('GET', `/policies/${id}`); }
296
+
297
+ async assign(agentId, policyId) {
298
+ return this.client._request('POST', '/policies/assign', { agentId, policyId });
299
+ }
300
+ }
301
+
302
+ // ─── Audit Sub-API ──────────────────────────────────────
303
+
304
+ class AuditAPI {
305
+ constructor(client) { this.client = client; }
306
+
307
+ async list(params = {}) {
308
+ const qs = new URLSearchParams(params).toString();
309
+ return this.client._request('GET', `/audit${qs ? '?' + qs : ''}`);
310
+ }
311
+
312
+ async verifyChain() { return this.client._request('GET', '/audit/verify-chain'); }
313
+
314
+ async getReceipt(eventId) { return this.client._request('GET', `/audit/${eventId}/receipt`); }
315
+ }
316
+
317
+ export default Mandatum;
318
+ export { MandatumError };
@@ -0,0 +1,102 @@
1
+ /**
2
+ * Local Token Verifier
3
+ *
4
+ * Verifies Mandatum JWT tokens locally without a network call.
5
+ * Caches the server's Ed25519 public key and verifies signatures in <1ms.
6
+ *
7
+ * Limitations:
8
+ * - Cannot check revocation locally (needs server for real-time revocation)
9
+ * - Cannot evaluate complex constraints
10
+ * - Best used as a fast pre-check; fall back to server for full verification
11
+ *
12
+ * Usage:
13
+ * const m = new Mandatum({ apiKey: '...', localVerification: true });
14
+ * // SDK automatically uses local verification when possible
15
+ */
16
+
17
+ import * as jose from 'jose';
18
+
19
+ export class LocalVerifier {
20
+ constructor(client) {
21
+ this.client = client;
22
+ this.publicKey = null;
23
+ this.publicKeyPem = null;
24
+ this.lastRefresh = 0;
25
+ this.refreshInterval = 5 * 60 * 1000; // Refresh public key every 5 minutes
26
+ }
27
+
28
+ /**
29
+ * Initialize: fetch and cache the server's Ed25519 public key.
30
+ */
31
+ async init() {
32
+ await this.refreshPublicKey();
33
+ console.log('Mandatum: Local verification initialized (sub-1ms token checks)');
34
+ }
35
+
36
+ /**
37
+ * Refresh the cached public key from the server.
38
+ */
39
+ async refreshPublicKey() {
40
+ try {
41
+ const data = await this.client.getPublicKey();
42
+ this.publicKeyPem = data.publicKey;
43
+ this.publicKey = await jose.importSPKI(data.publicKey, 'EdDSA');
44
+ this.lastRefresh = Date.now();
45
+ } catch (err) {
46
+ console.warn('Mandatum: Failed to refresh public key:', err.message);
47
+ }
48
+ }
49
+
50
+ /**
51
+ * Verify a token locally.
52
+ * Checks: signature validity, expiration, issuer.
53
+ * Does NOT check: revocation, complex constraints.
54
+ *
55
+ * @param {string} token - JWT string
56
+ * @returns {Promise<object>} { valid, payload, error }
57
+ */
58
+ async verify(token) {
59
+ // Refresh key if stale
60
+ if (Date.now() - this.lastRefresh > this.refreshInterval) {
61
+ await this.refreshPublicKey();
62
+ }
63
+
64
+ if (!this.publicKey) {
65
+ return { valid: false, error: 'No public key cached', code: 'NO_PUBLIC_KEY' };
66
+ }
67
+
68
+ try {
69
+ const { payload } = await jose.jwtVerify(token, this.publicKey, {
70
+ issuer: 'mandatum.io',
71
+ });
72
+
73
+ return {
74
+ valid: true,
75
+ payload,
76
+ agent: payload.sub,
77
+ scopes: payload.scopes,
78
+ constraints: payload.constraints,
79
+ delegator: payload.delegator,
80
+ expiresAt: new Date(payload.exp * 1000).toISOString(),
81
+ _note: 'Verified locally. Revocation status not checked — use server verify for full check.',
82
+ };
83
+ } catch (err) {
84
+ if (err.code === 'ERR_JWT_EXPIRED') {
85
+ return { valid: false, error: 'Token expired', code: 'TOKEN_EXPIRED' };
86
+ }
87
+ if (err.code === 'ERR_JWS_SIGNATURE_VERIFICATION_FAILED') {
88
+ return { valid: false, error: 'Invalid signature', code: 'INVALID_SIGNATURE' };
89
+ }
90
+ return { valid: false, error: err.message, code: 'VERIFICATION_FAILED' };
91
+ }
92
+ }
93
+
94
+ /**
95
+ * Decode a token without verifying (for debugging).
96
+ */
97
+ decode(token) {
98
+ return jose.decodeJwt(token);
99
+ }
100
+ }
101
+
102
+ export default LocalVerifier;
package/package.json ADDED
@@ -0,0 +1,48 @@
1
+ {
2
+ "name": "@mandatum/sdk",
3
+ "version": "1.0.0",
4
+ "description": "Zero-trust authorization and spending limits for AI agents. Ed25519-signed JWTs, policy enforcement, and payment gating in one line.",
5
+ "type": "module",
6
+ "main": "index.js",
7
+ "exports": {
8
+ ".": "./index.js",
9
+ "./local": "./local-verifier.js",
10
+ "./payments/razorpay": "./payments/razorpay.js",
11
+ "./payments/stripe": "./payments/stripe.js"
12
+ },
13
+ "files": [
14
+ "index.js",
15
+ "local-verifier.js",
16
+ "payments/",
17
+ "README.md"
18
+ ],
19
+ "keywords": [
20
+ "ai",
21
+ "agents",
22
+ "security",
23
+ "authorization",
24
+ "jwt",
25
+ "ed25519",
26
+ "spending-limits",
27
+ "razorpay",
28
+ "stripe",
29
+ "payments",
30
+ "mandatum"
31
+ ],
32
+ "author": "Mandatum <hello@mandatum.io>",
33
+ "license": "MIT",
34
+ "homepage": "https://mandatumio.vercel.app",
35
+ "repository": {
36
+ "type": "git",
37
+ "url": "https://github.com/sagevedant/mandatum"
38
+ },
39
+ "bugs": {
40
+ "url": "https://github.com/sagevedant/mandatum/issues"
41
+ },
42
+ "dependencies": {
43
+ "jose": "^6.1.3"
44
+ },
45
+ "engines": {
46
+ "node": ">=18.0.0"
47
+ }
48
+ }
@@ -0,0 +1,118 @@
1
+ /**
2
+ * Mandatum Razorpay Payment Wrapper
3
+ *
4
+ * Wraps Razorpay API calls with Mandatum authorization.
5
+ * The agent must get approval from Mandatum before any payment goes through.
6
+ *
7
+ * @example
8
+ * import Razorpay from 'razorpay';
9
+ * import { Mandatum } from '@mandatum/sdk';
10
+ * import { MandatumRazorpay } from '@mandatum/sdk/payments/razorpay';
11
+ *
12
+ * const razorpay = new Razorpay({ key_id: '...', key_secret: '...' });
13
+ * const mandatum = new Mandatum({ apiKey: 'mdt_live_...' });
14
+ *
15
+ * const pay = new MandatumRazorpay(razorpay, mandatum, 'agt_abc123');
16
+ * const result = await pay.createPayment({ amount: 50000, currency: 'INR' });
17
+ * // Mandatum checks limits FIRST, then Razorpay creates the payment
18
+ */
19
+
20
+ export class MandatumRazorpay {
21
+ /**
22
+ * @param {object} razorpayClient - Initialized Razorpay instance
23
+ * @param {object} mandatumClient - Initialized Mandatum SDK instance
24
+ * @param {string} agentId - The agent making the payment
25
+ */
26
+ constructor(razorpayClient, mandatumClient, agentId) {
27
+ this.razorpay = razorpayClient;
28
+ this.mandatum = mandatumClient;
29
+ this.agentId = agentId;
30
+ }
31
+
32
+ /**
33
+ * Create a payment — checks Mandatum limits first.
34
+ *
35
+ * @param {object} params
36
+ * @param {number} params.amount - Amount in smallest currency unit (paise for INR)
37
+ * @param {string} [params.currency='INR'] - Currency
38
+ * @param {string} [params.description] - Payment description
39
+ * @param {object} [params.notes] - Razorpay notes
40
+ * @returns {Promise<{ allowed: boolean, payment?: object, reason?: string, eventId: string }>}
41
+ */
42
+ async createPayment({ amount, currency = 'INR', description, notes, ...rest }) {
43
+ // Convert paise to rupees for Mandatum (Razorpay uses paise, Mandatum uses rupees)
44
+ const amountInRupees = amount / 100;
45
+
46
+ // Step 1: Check with Mandatum
47
+ const approval = await this.mandatum.agents.spend(this.agentId, {
48
+ amount: amountInRupees,
49
+ currency,
50
+ vendor: 'razorpay',
51
+ category: 'payment',
52
+ description: description || `Razorpay payment of ${currency} ${amountInRupees}`,
53
+ });
54
+
55
+ if (!approval.allowed) {
56
+ return {
57
+ allowed: false,
58
+ reason: approval.reason,
59
+ eventId: approval.eventId,
60
+ spending: approval.spending,
61
+ };
62
+ }
63
+
64
+ // Step 2: Mandatum approved — proceed with Razorpay
65
+ try {
66
+ const payment = await this.razorpay.orders.create({
67
+ amount,
68
+ currency,
69
+ notes: { ...notes, mandatum_event_id: approval.eventId },
70
+ ...rest,
71
+ });
72
+
73
+ return {
74
+ allowed: true,
75
+ payment,
76
+ eventId: approval.eventId,
77
+ spending: approval.spending,
78
+ };
79
+ } catch (err) {
80
+ // Payment failed after Mandatum approved — should ideally refund the spending event
81
+ return {
82
+ allowed: true,
83
+ paymentFailed: true,
84
+ error: err.message,
85
+ eventId: approval.eventId,
86
+ };
87
+ }
88
+ }
89
+
90
+ /**
91
+ * Create a payment link — checks Mandatum limits first.
92
+ */
93
+ async createPaymentLink({ amount, currency = 'INR', description, customer, ...rest }) {
94
+ const amountInRupees = amount / 100;
95
+
96
+ const approval = await this.mandatum.agents.spend(this.agentId, {
97
+ amount: amountInRupees,
98
+ currency,
99
+ vendor: 'razorpay',
100
+ category: 'payment-link',
101
+ description: description || `Payment link for ${currency} ${amountInRupees}`,
102
+ });
103
+
104
+ if (!approval.allowed) {
105
+ return { allowed: false, reason: approval.reason, eventId: approval.eventId };
106
+ }
107
+
108
+ const link = await this.razorpay.paymentLink.create({
109
+ amount, currency, description, customer,
110
+ notes: { mandatum_event_id: approval.eventId },
111
+ ...rest,
112
+ });
113
+
114
+ return { allowed: true, paymentLink: link, eventId: approval.eventId };
115
+ }
116
+ }
117
+
118
+ export default MandatumRazorpay;
@@ -0,0 +1,109 @@
1
+ /**
2
+ * Mandatum Stripe Payment Wrapper
3
+ *
4
+ * Wraps Stripe API calls with Mandatum authorization.
5
+ *
6
+ * @example
7
+ * import Stripe from 'stripe';
8
+ * import { Mandatum } from '@mandatum/sdk';
9
+ * import { MandatumStripe } from '@mandatum/sdk/payments/stripe';
10
+ *
11
+ * const stripe = new Stripe('sk_live_...');
12
+ * const mandatum = new Mandatum({ apiKey: 'mdt_live_...' });
13
+ *
14
+ * const pay = new MandatumStripe(stripe, mandatum, 'agt_abc123');
15
+ * const result = await pay.createPaymentIntent({ amount: 5000, currency: 'usd' });
16
+ */
17
+
18
+ export class MandatumStripe {
19
+ constructor(stripeClient, mandatumClient, agentId) {
20
+ this.stripe = stripeClient;
21
+ this.mandatum = mandatumClient;
22
+ this.agentId = agentId;
23
+ }
24
+
25
+ /**
26
+ * Create a PaymentIntent — checks Mandatum limits first.
27
+ *
28
+ * @param {object} params
29
+ * @param {number} params.amount - Amount in smallest currency unit (cents for USD)
30
+ * @param {string} [params.currency='usd'] - Currency
31
+ * @param {string} [params.description] - Payment description
32
+ * @param {object} [params.metadata] - Stripe metadata
33
+ */
34
+ async createPaymentIntent({ amount, currency = 'usd', description, metadata, ...rest }) {
35
+ // Convert cents to dollars for Mandatum
36
+ const amountInMajor = amount / 100;
37
+
38
+ const approval = await this.mandatum.agents.spend(this.agentId, {
39
+ amount: amountInMajor,
40
+ currency: currency.toUpperCase(),
41
+ vendor: 'stripe',
42
+ category: 'payment',
43
+ description: description || `Stripe payment of ${currency.toUpperCase()} ${amountInMajor}`,
44
+ });
45
+
46
+ if (!approval.allowed) {
47
+ return {
48
+ allowed: false,
49
+ reason: approval.reason,
50
+ eventId: approval.eventId,
51
+ spending: approval.spending,
52
+ };
53
+ }
54
+
55
+ try {
56
+ const paymentIntent = await this.stripe.paymentIntents.create({
57
+ amount,
58
+ currency,
59
+ description,
60
+ metadata: { ...metadata, mandatum_event_id: approval.eventId },
61
+ ...rest,
62
+ });
63
+
64
+ return {
65
+ allowed: true,
66
+ paymentIntent,
67
+ eventId: approval.eventId,
68
+ spending: approval.spending,
69
+ };
70
+ } catch (err) {
71
+ return {
72
+ allowed: true,
73
+ paymentFailed: true,
74
+ error: err.message,
75
+ eventId: approval.eventId,
76
+ };
77
+ }
78
+ }
79
+
80
+ /**
81
+ * Create a Checkout Session — checks Mandatum limits first.
82
+ */
83
+ async createCheckoutSession({ lineItems, totalAmount, currency = 'usd', ...rest }) {
84
+ const amountInMajor = totalAmount / 100;
85
+
86
+ const approval = await this.mandatum.agents.spend(this.agentId, {
87
+ amount: amountInMajor,
88
+ currency: currency.toUpperCase(),
89
+ vendor: 'stripe',
90
+ category: 'checkout',
91
+ description: `Stripe checkout for ${currency.toUpperCase()} ${amountInMajor}`,
92
+ });
93
+
94
+ if (!approval.allowed) {
95
+ return { allowed: false, reason: approval.reason, eventId: approval.eventId };
96
+ }
97
+
98
+ const session = await this.stripe.checkout.sessions.create({
99
+ line_items: lineItems,
100
+ mode: 'payment',
101
+ metadata: { mandatum_event_id: approval.eventId },
102
+ ...rest,
103
+ });
104
+
105
+ return { allowed: true, session, eventId: approval.eventId };
106
+ }
107
+ }
108
+
109
+ export default MandatumStripe;