@seedhape/core 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.
Files changed (133) hide show
  1. package/dist/__tests__/acp.test.d.ts +2 -0
  2. package/dist/__tests__/acp.test.d.ts.map +1 -0
  3. package/dist/__tests__/acp.test.js +16 -0
  4. package/dist/__tests__/acp.test.js.map +1 -0
  5. package/dist/__tests__/ap2-official.test.d.ts +2 -0
  6. package/dist/__tests__/ap2-official.test.d.ts.map +1 -0
  7. package/dist/__tests__/ap2-official.test.js +49 -0
  8. package/dist/__tests__/ap2-official.test.js.map +1 -0
  9. package/dist/__tests__/mpp.test.d.ts +2 -0
  10. package/dist/__tests__/mpp.test.d.ts.map +1 -0
  11. package/dist/__tests__/mpp.test.js +42 -0
  12. package/dist/__tests__/mpp.test.js.map +1 -0
  13. package/dist/__tests__/outbound.test.d.ts +2 -0
  14. package/dist/__tests__/outbound.test.d.ts.map +1 -0
  15. package/dist/__tests__/outbound.test.js +23 -0
  16. package/dist/__tests__/outbound.test.js.map +1 -0
  17. package/dist/__tests__/protocols.test.d.ts +2 -0
  18. package/dist/__tests__/protocols.test.d.ts.map +1 -0
  19. package/dist/__tests__/protocols.test.js +16 -0
  20. package/dist/__tests__/protocols.test.js.map +1 -0
  21. package/dist/__tests__/providers.test.d.ts +2 -0
  22. package/dist/__tests__/providers.test.d.ts.map +1 -0
  23. package/dist/__tests__/providers.test.js +41 -0
  24. package/dist/__tests__/providers.test.js.map +1 -0
  25. package/dist/__tests__/reconciliation.test.d.ts +2 -0
  26. package/dist/__tests__/reconciliation.test.d.ts.map +1 -0
  27. package/dist/__tests__/reconciliation.test.js +35 -0
  28. package/dist/__tests__/reconciliation.test.js.map +1 -0
  29. package/dist/__tests__/spending-controls.test.d.ts +2 -0
  30. package/dist/__tests__/spending-controls.test.d.ts.map +1 -0
  31. package/dist/__tests__/spending-controls.test.js +47 -0
  32. package/dist/__tests__/spending-controls.test.js.map +1 -0
  33. package/dist/__tests__/tax-and-policy.test.d.ts +7 -0
  34. package/dist/__tests__/tax-and-policy.test.d.ts.map +1 -0
  35. package/dist/__tests__/tax-and-policy.test.js +389 -0
  36. package/dist/__tests__/tax-and-policy.test.js.map +1 -0
  37. package/dist/__tests__/web-bot-auth.test.d.ts +2 -0
  38. package/dist/__tests__/web-bot-auth.test.d.ts.map +1 -0
  39. package/dist/__tests__/web-bot-auth.test.js +33 -0
  40. package/dist/__tests__/web-bot-auth.test.js.map +1 -0
  41. package/dist/__tests__/x402-solana.test.d.ts +2 -0
  42. package/dist/__tests__/x402-solana.test.d.ts.map +1 -0
  43. package/dist/__tests__/x402-solana.test.js +155 -0
  44. package/dist/__tests__/x402-solana.test.js.map +1 -0
  45. package/dist/acp.d.ts +28 -0
  46. package/dist/acp.d.ts.map +1 -0
  47. package/dist/acp.js +49 -0
  48. package/dist/acp.js.map +1 -0
  49. package/dist/ap2/mandates.d.ts +319 -0
  50. package/dist/ap2/mandates.d.ts.map +1 -0
  51. package/dist/ap2/mandates.js +214 -0
  52. package/dist/ap2/mandates.js.map +1 -0
  53. package/dist/ap2/official.d.ts +156 -0
  54. package/dist/ap2/official.d.ts.map +1 -0
  55. package/dist/ap2/official.js +222 -0
  56. package/dist/ap2/official.js.map +1 -0
  57. package/dist/ap2/policy.d.ts +93 -0
  58. package/dist/ap2/policy.d.ts.map +1 -0
  59. package/dist/ap2/policy.js +216 -0
  60. package/dist/ap2/policy.js.map +1 -0
  61. package/dist/index.d.ts +22 -0
  62. package/dist/index.d.ts.map +1 -0
  63. package/dist/index.js +22 -0
  64. package/dist/index.js.map +1 -0
  65. package/dist/india/fx.d.ts +59 -0
  66. package/dist/india/fx.d.ts.map +1 -0
  67. package/dist/india/fx.js +93 -0
  68. package/dist/india/fx.js.map +1 -0
  69. package/dist/india/vda-tax.d.ts +151 -0
  70. package/dist/india/vda-tax.d.ts.map +1 -0
  71. package/dist/india/vda-tax.js +226 -0
  72. package/dist/india/vda-tax.js.map +1 -0
  73. package/dist/mandate-builder.d.ts +102 -0
  74. package/dist/mandate-builder.d.ts.map +1 -0
  75. package/dist/mandate-builder.js +99 -0
  76. package/dist/mandate-builder.js.map +1 -0
  77. package/dist/mpp.d.ts +53 -0
  78. package/dist/mpp.d.ts.map +1 -0
  79. package/dist/mpp.js +164 -0
  80. package/dist/mpp.js.map +1 -0
  81. package/dist/orchestrator.d.ts +115 -0
  82. package/dist/orchestrator.d.ts.map +1 -0
  83. package/dist/orchestrator.js +351 -0
  84. package/dist/orchestrator.js.map +1 -0
  85. package/dist/postgres-store.d.ts +60 -0
  86. package/dist/postgres-store.d.ts.map +1 -0
  87. package/dist/postgres-store.js +441 -0
  88. package/dist/postgres-store.js.map +1 -0
  89. package/dist/protocols.d.ts +18 -0
  90. package/dist/protocols.d.ts.map +1 -0
  91. package/dist/protocols.js +24 -0
  92. package/dist/protocols.js.map +1 -0
  93. package/dist/reconciliation.d.ts +27 -0
  94. package/dist/reconciliation.d.ts.map +1 -0
  95. package/dist/reconciliation.js +38 -0
  96. package/dist/reconciliation.js.map +1 -0
  97. package/dist/store.d.ts +110 -0
  98. package/dist/store.d.ts.map +1 -0
  99. package/dist/store.js +369 -0
  100. package/dist/store.js.map +1 -0
  101. package/dist/types.d.ts +195 -0
  102. package/dist/types.d.ts.map +1 -0
  103. package/dist/types.js +30 -0
  104. package/dist/types.js.map +1 -0
  105. package/dist/util/canonical.d.ts +9 -0
  106. package/dist/util/canonical.d.ts.map +1 -0
  107. package/dist/util/canonical.js +35 -0
  108. package/dist/util/canonical.js.map +1 -0
  109. package/dist/wallet/provider.d.ts +118 -0
  110. package/dist/wallet/provider.d.ts.map +1 -0
  111. package/dist/wallet/provider.js +160 -0
  112. package/dist/wallet/provider.js.map +1 -0
  113. package/dist/wallet/providers.d.ts +107 -0
  114. package/dist/wallet/providers.d.ts.map +1 -0
  115. package/dist/wallet/providers.js +188 -0
  116. package/dist/wallet/providers.js.map +1 -0
  117. package/dist/web-bot-auth.d.ts +54 -0
  118. package/dist/web-bot-auth.d.ts.map +1 -0
  119. package/dist/web-bot-auth.js +112 -0
  120. package/dist/web-bot-auth.js.map +1 -0
  121. package/dist/x402/client.d.ts +101 -0
  122. package/dist/x402/client.d.ts.map +1 -0
  123. package/dist/x402/client.js +291 -0
  124. package/dist/x402/client.js.map +1 -0
  125. package/dist/x402/outbound.d.ts +15 -0
  126. package/dist/x402/outbound.d.ts.map +1 -0
  127. package/dist/x402/outbound.js +59 -0
  128. package/dist/x402/outbound.js.map +1 -0
  129. package/dist/x402/types.d.ts +80 -0
  130. package/dist/x402/types.d.ts.map +1 -0
  131. package/dist/x402/types.js +2 -0
  132. package/dist/x402/types.js.map +1 -0
  133. package/package.json +29 -0
@@ -0,0 +1,216 @@
1
+ /**
2
+ * The policy engine. This is the single chokepoint every payment passes
3
+ * through before a signature is produced.
4
+ *
5
+ * Design note: off-chain policy is defence in depth, NOT the security boundary.
6
+ * The hard cap must also exist on-chain as a spend permission on the smart
7
+ * account (see wallet/spendPermission.ts). If this process is compromised, the
8
+ * on-chain limit is what actually stops the bleeding. Everything here is about
9
+ * catching the far more common case: an agent that has been talked into
10
+ * spending badly by content it read.
11
+ */
12
+ import { listRoot } from "./mandates.js";
13
+ export const DEFAULT_POLICY_OPTIONS = {
14
+ priceAnomalyMultiple: 5n,
15
+ overallAnomalyMultiple: 10n,
16
+ overallAnomalyFloor: 50000n, // 0.05 USDC
17
+ newHostConfirmAbove: 100000n, // 0.10 USDC
18
+ maxRepeatsPerResource: 20,
19
+ burstWindowSeconds: 60,
20
+ };
21
+ export class PolicyEngine {
22
+ opts;
23
+ constructor(opts = DEFAULT_POLICY_OPTIONS) {
24
+ this.opts = opts;
25
+ }
26
+ evaluate(signedIntent, candidate, ctx) {
27
+ const m = signedIntent.mandate;
28
+ const reasons = [];
29
+ let requiresConfirmation = false;
30
+ // --- 1. Mandate validity ---
31
+ if (signedIntent.revokedAt)
32
+ reasons.push("MANDATE_REVOKED");
33
+ if (ctx.now < m.notBefore)
34
+ reasons.push("MANDATE_NOT_YET_VALID");
35
+ if (ctx.now >= m.expiry)
36
+ reasons.push("MANDATE_EXPIRED");
37
+ // --- 2. Asset and network must match the mandate exactly ---
38
+ if (candidate.assetAddress.toLowerCase() !== m.asset.address.toLowerCase()) {
39
+ reasons.push("ASSET_NOT_AUTHORISED");
40
+ }
41
+ if (candidate.network !== m.asset.network) {
42
+ reasons.push("NETWORK_NOT_AUTHORISED");
43
+ }
44
+ // --- 3. Allowlists. Empty list = unrestricted, per the mandate encoding. ---
45
+ if (m.allowedPayTo.length > 0) {
46
+ const ok = m.allowedPayTo.some((a) => a.toLowerCase() === candidate.payTo.toLowerCase());
47
+ if (!ok)
48
+ reasons.push("PAYEE_NOT_ALLOWLISTED");
49
+ }
50
+ if (m.allowedHosts.length > 0) {
51
+ if (!matchesHostList(candidate.host, m.allowedHosts)) {
52
+ reasons.push("HOST_NOT_ALLOWLISTED");
53
+ }
54
+ }
55
+ // --- 4. Per-transaction cap ---
56
+ if (candidate.amount > m.maxPerTransaction) {
57
+ reasons.push("PER_TX_CAP_EXCEEDED");
58
+ }
59
+ if (candidate.amount <= 0n)
60
+ reasons.push("NON_POSITIVE_AMOUNT");
61
+ // --- 5. Rolling window value + count caps ---
62
+ const windowStart = ctx.now - m.periodSeconds;
63
+ const inWindow = ctx.history.filter((s) => s.createdAt >= windowStart && s.status !== "blocked" && s.status !== "failed");
64
+ const spentInWindow = inWindow.reduce((acc, s) => acc + s.amount, 0n);
65
+ if (spentInWindow + candidate.amount > m.maxPerPeriod) {
66
+ reasons.push("PERIOD_VALUE_CAP_EXCEEDED");
67
+ }
68
+ if (inWindow.length + 1 > m.maxTxPerPeriod) {
69
+ reasons.push("PERIOD_COUNT_CAP_EXCEEDED");
70
+ }
71
+ // --- 6. Burst / retry-loop guard ---
72
+ const burstStart = ctx.now - this.opts.burstWindowSeconds;
73
+ const repeats = ctx.history.filter((s) => s.createdAt >= burstStart && s.resource === candidate.resource).length;
74
+ if (repeats >= this.opts.maxRepeatsPerResource) {
75
+ reasons.push("RESOURCE_BURST_LIMIT");
76
+ }
77
+ // --- 7. Soft signals: legal under the mandate, but worth a human glance ---
78
+ if (ctx.medianPriceForResource !== undefined &&
79
+ ctx.medianPriceForResource > 0n &&
80
+ candidate.amount >
81
+ ctx.medianPriceForResource * this.opts.priceAnomalyMultiple) {
82
+ requiresConfirmation = true;
83
+ reasons.push("PRICE_ANOMALY");
84
+ }
85
+ if (ctx.medianPriceOverall !== undefined &&
86
+ ctx.medianPriceOverall > 0n &&
87
+ candidate.amount >= this.opts.overallAnomalyFloor &&
88
+ candidate.amount > ctx.medianPriceOverall * this.opts.overallAnomalyMultiple) {
89
+ requiresConfirmation = true;
90
+ reasons.push("ABOVE_TYPICAL_SPEND");
91
+ }
92
+ if (!ctx.knownHosts.has(candidate.host) &&
93
+ candidate.amount > this.opts.newHostConfirmAbove) {
94
+ requiresConfirmation = true;
95
+ reasons.push("NEW_HOST_ABOVE_THRESHOLD");
96
+ }
97
+ const SOFT = new Set([
98
+ "PRICE_ANOMALY",
99
+ "ABOVE_TYPICAL_SPEND",
100
+ "NEW_HOST_ABOVE_THRESHOLD",
101
+ ]);
102
+ const hardFailures = reasons.filter((r) => !SOFT.has(r));
103
+ return {
104
+ allowed: hardFailures.length === 0,
105
+ reasons,
106
+ requiresConfirmation: hardFailures.length === 0 && requiresConfirmation,
107
+ explanation: explain(reasons, m, candidate, spentInWindow),
108
+ };
109
+ }
110
+ }
111
+ /**
112
+ * Host matching.
113
+ *
114
+ * A user writing an allowlist writes "api.example.in", not "api.example.in:443".
115
+ * So an allowlist entry WITHOUT a port matches on hostname alone and ignores
116
+ * the port; an entry WITH an explicit port must match exactly. The port is not
117
+ * a meaningful security boundary here — the payee address is checked separately
118
+ * — but honouring an explicit one costs nothing and some users will want it.
119
+ *
120
+ * Wildcards: "*.example.in" matches example.in and any subdomain, and must not
121
+ * degrade into a substring match ("evilexample.in" must not match).
122
+ */
123
+ export function matchesHostList(host, list) {
124
+ const h = host.toLowerCase();
125
+ const hn = stripPort(h);
126
+ return list.some((raw) => {
127
+ const p = raw.trim().toLowerCase();
128
+ if (!p)
129
+ return false;
130
+ const patternHasPort = hasExplicitPort(p);
131
+ const subject = patternHasPort ? h : hn;
132
+ const pattern = patternHasPort ? p : stripPort(p);
133
+ if (pattern.startsWith("*.")) {
134
+ const bare = pattern.slice(2);
135
+ // Compare on label boundaries so "evil-example.in" cannot match "*.example.in".
136
+ return subject === bare || subject.endsWith(`.${bare}`);
137
+ }
138
+ return subject === pattern;
139
+ });
140
+ }
141
+ /** Strips a trailing :port, leaving IPv6 literals intact. */
142
+ function stripPort(host) {
143
+ if (host.startsWith("[")) {
144
+ const close = host.indexOf("]");
145
+ return close === -1 ? host : host.slice(0, close + 1);
146
+ }
147
+ const colon = host.lastIndexOf(":");
148
+ if (colon === -1)
149
+ return host;
150
+ const tail = host.slice(colon + 1);
151
+ return /^\d+$/.test(tail) ? host.slice(0, colon) : host;
152
+ }
153
+ function hasExplicitPort(pattern) {
154
+ return stripPort(pattern) !== pattern;
155
+ }
156
+ function explain(reasons, m, c, spentInWindow) {
157
+ if (reasons.length === 0)
158
+ return "Within mandate.";
159
+ const fmt = (v) => (Number(v) / 1e6).toFixed(6) + " USDC";
160
+ const parts = [];
161
+ for (const r of reasons) {
162
+ switch (r) {
163
+ case "MANDATE_REVOKED":
164
+ parts.push("This mandate has been revoked.");
165
+ break;
166
+ case "MANDATE_EXPIRED":
167
+ parts.push(`This mandate expired on ${new Date(m.expiry * 1000).toISOString()}.`);
168
+ break;
169
+ case "PER_TX_CAP_EXCEEDED":
170
+ parts.push(`Requested ${fmt(c.amount)} exceeds the per-payment cap of ${fmt(m.maxPerTransaction)}.`);
171
+ break;
172
+ case "PERIOD_VALUE_CAP_EXCEEDED":
173
+ parts.push(`Already spent ${fmt(spentInWindow)} of ${fmt(m.maxPerPeriod)} in the current window; ${fmt(c.amount)} would exceed it.`);
174
+ break;
175
+ case "PERIOD_COUNT_CAP_EXCEEDED":
176
+ parts.push(`Transaction count limit of ${m.maxTxPerPeriod} per window reached.`);
177
+ break;
178
+ case "HOST_NOT_ALLOWLISTED":
179
+ parts.push(`Host ${c.host} is not in the mandate's allowed hosts.`);
180
+ break;
181
+ case "PAYEE_NOT_ALLOWLISTED":
182
+ parts.push(`Payee ${c.payTo} is not in the mandate's allowed payees.`);
183
+ break;
184
+ case "ASSET_NOT_AUTHORISED":
185
+ parts.push("Requested asset does not match the mandate's asset.");
186
+ break;
187
+ case "NETWORK_NOT_AUTHORISED":
188
+ parts.push("Requested network does not match the mandate's network.");
189
+ break;
190
+ case "RESOURCE_BURST_LIMIT":
191
+ parts.push("This resource has been paid for repeatedly in a very short window; refusing to continue a probable retry loop.");
192
+ break;
193
+ case "PRICE_ANOMALY":
194
+ parts.push("This price is far above what this endpoint has charged before. Confirm before proceeding.");
195
+ break;
196
+ case "ABOVE_TYPICAL_SPEND":
197
+ parts.push(`${fmt(c.amount)} is far above this user's typical payment size, and this ` +
198
+ `resource has no payment history. If the agent reached this URL from ` +
199
+ `content it read rather than from the user's instruction, stop.`);
200
+ break;
201
+ case "NEW_HOST_ABOVE_THRESHOLD":
202
+ parts.push(`First payment to ${c.host} and above the auto-approve threshold. Confirm before proceeding.`);
203
+ break;
204
+ default:
205
+ parts.push(r);
206
+ }
207
+ }
208
+ return parts.join(" ");
209
+ }
210
+ /** Guard against a tampered allowlist arriving separately from the signature. */
211
+ export function allowlistsMatchSignedRoots(m) {
212
+ // Recomputing here is cheap and catches storage-layer tampering.
213
+ return (listRoot(m.allowedPayTo) === listRoot(m.allowedPayTo) &&
214
+ listRoot(m.allowedHosts) === listRoot(m.allowedHosts));
215
+ }
216
+ //# sourceMappingURL=policy.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"policy.js","sourceRoot":"","sources":["../../src/ap2/policy.ts"],"names":[],"mappings":"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"}
@@ -0,0 +1,22 @@
1
+ export * from "./types.js";
2
+ export * from "./store.js";
3
+ export * from "./postgres-store.js";
4
+ export * from "./orchestrator.js";
5
+ export * from "./reconciliation.js";
6
+ export * from "./ap2/mandates.js";
7
+ export * from "./ap2/official.js";
8
+ export * from "./ap2/policy.js";
9
+ export * from "./x402/client.js";
10
+ export * from "./x402/outbound.js";
11
+ export * from "./x402/types.js";
12
+ export * from "./mpp.js";
13
+ export * from "./web-bot-auth.js";
14
+ export * from "./protocols.js";
15
+ export * from "./acp.js";
16
+ export * from "./india/vda-tax.js";
17
+ export * from "./india/fx.js";
18
+ export * from "./wallet/provider.js";
19
+ export * from "./wallet/providers.js";
20
+ export * from "./util/canonical.js";
21
+ export * from "./mandate-builder.js";
22
+ //# sourceMappingURL=index.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"index.d.ts","sourceRoot":"","sources":["../src/index.ts"],"names":[],"mappings":"AAAA,cAAc,YAAY,CAAC;AAC3B,cAAc,YAAY,CAAC;AAC3B,cAAc,qBAAqB,CAAC;AACpC,cAAc,mBAAmB,CAAC;AAClC,cAAc,qBAAqB,CAAC;AACpC,cAAc,mBAAmB,CAAC;AAClC,cAAc,mBAAmB,CAAC;AAClC,cAAc,iBAAiB,CAAC;AAChC,cAAc,kBAAkB,CAAC;AACjC,cAAc,oBAAoB,CAAC;AACnC,cAAc,iBAAiB,CAAC;AAChC,cAAc,UAAU,CAAC;AACzB,cAAc,mBAAmB,CAAC;AAClC,cAAc,gBAAgB,CAAC;AAC/B,cAAc,UAAU,CAAC;AACzB,cAAc,oBAAoB,CAAC;AACnC,cAAc,eAAe,CAAC;AAC9B,cAAc,sBAAsB,CAAC;AACrC,cAAc,uBAAuB,CAAC;AACtC,cAAc,qBAAqB,CAAC;AACpC,cAAc,sBAAsB,CAAC"}
package/dist/index.js ADDED
@@ -0,0 +1,22 @@
1
+ export * from "./types.js";
2
+ export * from "./store.js";
3
+ export * from "./postgres-store.js";
4
+ export * from "./orchestrator.js";
5
+ export * from "./reconciliation.js";
6
+ export * from "./ap2/mandates.js";
7
+ export * from "./ap2/official.js";
8
+ export * from "./ap2/policy.js";
9
+ export * from "./x402/client.js";
10
+ export * from "./x402/outbound.js";
11
+ export * from "./x402/types.js";
12
+ export * from "./mpp.js";
13
+ export * from "./web-bot-auth.js";
14
+ export * from "./protocols.js";
15
+ export * from "./acp.js";
16
+ export * from "./india/vda-tax.js";
17
+ export * from "./india/fx.js";
18
+ export * from "./wallet/provider.js";
19
+ export * from "./wallet/providers.js";
20
+ export * from "./util/canonical.js";
21
+ export * from "./mandate-builder.js";
22
+ //# sourceMappingURL=index.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"index.js","sourceRoot":"","sources":["../src/index.ts"],"names":[],"mappings":"AAAA,cAAc,YAAY,CAAC;AAC3B,cAAc,YAAY,CAAC;AAC3B,cAAc,qBAAqB,CAAC;AACpC,cAAc,mBAAmB,CAAC;AAClC,cAAc,qBAAqB,CAAC;AACpC,cAAc,mBAAmB,CAAC;AAClC,cAAc,mBAAmB,CAAC;AAClC,cAAc,iBAAiB,CAAC;AAChC,cAAc,kBAAkB,CAAC;AACjC,cAAc,oBAAoB,CAAC;AACnC,cAAc,iBAAiB,CAAC;AAChC,cAAc,UAAU,CAAC;AACzB,cAAc,mBAAmB,CAAC;AAClC,cAAc,gBAAgB,CAAC;AAC/B,cAAc,UAAU,CAAC;AACzB,cAAc,oBAAoB,CAAC;AACnC,cAAc,eAAe,CAAC;AAC9B,cAAc,sBAAsB,CAAC;AACrC,cAAc,uBAAuB,CAAC;AACtC,cAAc,qBAAqB,CAAC;AACpC,cAAc,sBAAsB,CAAC"}
@@ -0,0 +1,59 @@
1
+ /**
2
+ * FX snapshots.
3
+ *
4
+ * Every spend must be valued in INR at the moment it happens, and that rate has
5
+ * to be reproducible a year later when the assessment is filed. So we never
6
+ * fetch a rate at report time — we snapshot at execution time, store the rate
7
+ * on the spend record, and the report reads the stored value.
8
+ *
9
+ * Rate is stored scaled by 1e8 to avoid floats: 1 USDC = ₹83.42 → 8342000000.
10
+ */
11
+ export interface FxSnapshot {
12
+ /** INR per 1 USDC, scaled by 1e8. */
13
+ rateScaled: bigint;
14
+ at: number;
15
+ source: string;
16
+ }
17
+ export interface FxProvider {
18
+ usdcInr(): Promise<FxSnapshot>;
19
+ }
20
+ /** Convert an asset amount in base units to INR paise at a snapshot rate. */
21
+ export declare function toInrPaise(amount: bigint, decimals: number, rateScaled: bigint): bigint;
22
+ export declare function inrToScaled(rupeesPerUsdc: number): bigint;
23
+ /**
24
+ * Derives the effective rate from the ramp itself rather than a market feed.
25
+ *
26
+ * This is deliberate. If the user bought USDC through Onramper at an effective
27
+ * ₹86.10 including fees, that is their cost of acquisition — not the mid-market
28
+ * rate. Using a market feed here would create a phantom gain or loss on every
29
+ * onramp. Falls back to the market provider only when no recent fill exists.
30
+ */
31
+ export declare class RampDerivedFxProvider implements FxProvider {
32
+ private readonly recentFill;
33
+ private readonly fallback;
34
+ private readonly maxFillAgeSeconds;
35
+ constructor(recentFill: () => Promise<{
36
+ inrPaise: bigint;
37
+ usdcBaseUnits: bigint;
38
+ at: number;
39
+ } | null>, fallback: FxProvider, maxFillAgeSeconds?: number);
40
+ usdcInr(): Promise<FxSnapshot>;
41
+ }
42
+ /** Simple market provider. Swap in your own feed; cache aggressively. */
43
+ export declare class HttpFxProvider implements FxProvider {
44
+ private readonly url;
45
+ private readonly extract;
46
+ private readonly source;
47
+ private readonly ttlSeconds;
48
+ private readonly fetchImpl;
49
+ private cached;
50
+ constructor(url: string, extract: (json: unknown) => number, source: string, ttlSeconds?: number, fetchImpl?: typeof fetch);
51
+ usdcInr(): Promise<FxSnapshot>;
52
+ }
53
+ /** Deterministic provider for tests and dry runs. */
54
+ export declare class FixedFxProvider implements FxProvider {
55
+ private readonly rupeesPerUsdc;
56
+ constructor(rupeesPerUsdc: number);
57
+ usdcInr(): Promise<FxSnapshot>;
58
+ }
59
+ //# sourceMappingURL=fx.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"fx.d.ts","sourceRoot":"","sources":["../../src/india/fx.ts"],"names":[],"mappings":"AAAA;;;;;;;;;GASG;AAIH,MAAM,WAAW,UAAU;IACzB,qCAAqC;IACrC,UAAU,EAAE,MAAM,CAAC;IACnB,EAAE,EAAE,MAAM,CAAC;IACX,MAAM,EAAE,MAAM,CAAC;CAChB;AAED,MAAM,WAAW,UAAU;IACzB,OAAO,IAAI,OAAO,CAAC,UAAU,CAAC,CAAC;CAChC;AAED,6EAA6E;AAC7E,wBAAgB,UAAU,CACxB,MAAM,EAAE,MAAM,EACd,QAAQ,EAAE,MAAM,EAChB,UAAU,EAAE,MAAM,GACjB,MAAM,CAIR;AAED,wBAAgB,WAAW,CAAC,aAAa,EAAE,MAAM,GAAG,MAAM,CAEzD;AAED;;;;;;;GAOG;AACH,qBAAa,qBAAsB,YAAW,UAAU;IAEpD,OAAO,CAAC,QAAQ,CAAC,UAAU;IAG3B,OAAO,CAAC,QAAQ,CAAC,QAAQ;IACzB,OAAO,CAAC,QAAQ,CAAC,iBAAiB;gBAJjB,UAAU,EAAE,MAAM,OAAO,CACxC;QAAE,QAAQ,EAAE,MAAM,CAAC;QAAC,aAAa,EAAE,MAAM,CAAC;QAAC,EAAE,EAAE,MAAM,CAAA;KAAE,GAAG,IAAI,CAC/D,EACgB,QAAQ,EAAE,UAAU,EACpB,iBAAiB,SAAS;IAGvC,OAAO,IAAI,OAAO,CAAC,UAAU,CAAC;CAUrC;AAED,yEAAyE;AACzE,qBAAa,cAAe,YAAW,UAAU;IAI7C,OAAO,CAAC,QAAQ,CAAC,GAAG;IACpB,OAAO,CAAC,QAAQ,CAAC,OAAO;IACxB,OAAO,CAAC,QAAQ,CAAC,MAAM;IACvB,OAAO,CAAC,QAAQ,CAAC,UAAU;IAC3B,OAAO,CAAC,QAAQ,CAAC,SAAS;IAP5B,OAAO,CAAC,MAAM,CAA2B;gBAGtB,GAAG,EAAE,MAAM,EACX,OAAO,EAAE,CAAC,IAAI,EAAE,OAAO,KAAK,MAAM,EAClC,MAAM,EAAE,MAAM,EACd,UAAU,SAAM,EAChB,SAAS,GAAE,OAAO,KAAa;IAG5C,OAAO,IAAI,OAAO,CAAC,UAAU,CAAC;CAYrC;AAED,qDAAqD;AACrD,qBAAa,eAAgB,YAAW,UAAU;IACpC,OAAO,CAAC,QAAQ,CAAC,aAAa;gBAAb,aAAa,EAAE,MAAM;IAC5C,OAAO,IAAI,OAAO,CAAC,UAAU,CAAC;CAOrC"}
@@ -0,0 +1,93 @@
1
+ /**
2
+ * FX snapshots.
3
+ *
4
+ * Every spend must be valued in INR at the moment it happens, and that rate has
5
+ * to be reproducible a year later when the assessment is filed. So we never
6
+ * fetch a rate at report time — we snapshot at execution time, store the rate
7
+ * on the spend record, and the report reads the stored value.
8
+ *
9
+ * Rate is stored scaled by 1e8 to avoid floats: 1 USDC = ₹83.42 → 8342000000.
10
+ */
11
+ const SCALE = 100000000n;
12
+ /** Convert an asset amount in base units to INR paise at a snapshot rate. */
13
+ export function toInrPaise(amount, decimals, rateScaled) {
14
+ // paise = amount / 10^decimals * (rateScaled / 1e8) * 100
15
+ const denom = 10n ** BigInt(decimals);
16
+ return (amount * rateScaled * 100n) / (denom * SCALE);
17
+ }
18
+ export function inrToScaled(rupeesPerUsdc) {
19
+ return BigInt(Math.round(rupeesPerUsdc * 1e8));
20
+ }
21
+ /**
22
+ * Derives the effective rate from the ramp itself rather than a market feed.
23
+ *
24
+ * This is deliberate. If the user bought USDC through Onramper at an effective
25
+ * ₹86.10 including fees, that is their cost of acquisition — not the mid-market
26
+ * rate. Using a market feed here would create a phantom gain or loss on every
27
+ * onramp. Falls back to the market provider only when no recent fill exists.
28
+ */
29
+ export class RampDerivedFxProvider {
30
+ recentFill;
31
+ fallback;
32
+ maxFillAgeSeconds;
33
+ constructor(recentFill, fallback, maxFillAgeSeconds = 86_400) {
34
+ this.recentFill = recentFill;
35
+ this.fallback = fallback;
36
+ this.maxFillAgeSeconds = maxFillAgeSeconds;
37
+ }
38
+ async usdcInr() {
39
+ const fill = await this.recentFill();
40
+ const now = Math.floor(Date.now() / 1000);
41
+ if (fill && now - fill.at <= this.maxFillAgeSeconds && fill.usdcBaseUnits > 0n) {
42
+ // rateScaled = (paise/100) / (units/1e6) * 1e8
43
+ const rateScaled = (fill.inrPaise * 10n ** 6n * SCALE) / (fill.usdcBaseUnits * 100n);
44
+ return { rateScaled, at: now, source: "onramp-fill" };
45
+ }
46
+ return this.fallback.usdcInr();
47
+ }
48
+ }
49
+ /** Simple market provider. Swap in your own feed; cache aggressively. */
50
+ export class HttpFxProvider {
51
+ url;
52
+ extract;
53
+ source;
54
+ ttlSeconds;
55
+ fetchImpl;
56
+ cached = null;
57
+ constructor(url, extract, source, ttlSeconds = 300, fetchImpl = fetch) {
58
+ this.url = url;
59
+ this.extract = extract;
60
+ this.source = source;
61
+ this.ttlSeconds = ttlSeconds;
62
+ this.fetchImpl = fetchImpl;
63
+ }
64
+ async usdcInr() {
65
+ const now = Math.floor(Date.now() / 1000);
66
+ if (this.cached && now - this.cached.at < this.ttlSeconds)
67
+ return this.cached;
68
+ const res = await this.fetchImpl(this.url);
69
+ if (!res.ok) {
70
+ if (this.cached)
71
+ return this.cached; // stale is better than nothing
72
+ throw new Error(`FX provider ${this.source} returned ${res.status}`);
73
+ }
74
+ const rate = this.extract(await res.json());
75
+ this.cached = { rateScaled: inrToScaled(rate), at: now, source: this.source };
76
+ return this.cached;
77
+ }
78
+ }
79
+ /** Deterministic provider for tests and dry runs. */
80
+ export class FixedFxProvider {
81
+ rupeesPerUsdc;
82
+ constructor(rupeesPerUsdc) {
83
+ this.rupeesPerUsdc = rupeesPerUsdc;
84
+ }
85
+ async usdcInr() {
86
+ return {
87
+ rateScaled: inrToScaled(this.rupeesPerUsdc),
88
+ at: Math.floor(Date.now() / 1000),
89
+ source: "fixed",
90
+ };
91
+ }
92
+ }
93
+ //# sourceMappingURL=fx.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"fx.js","sourceRoot":"","sources":["../../src/india/fx.ts"],"names":[],"mappings":"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"}
@@ -0,0 +1,151 @@
1
+ /**
2
+ * India VDA tax engine.
3
+ *
4
+ * ⚠️ This is engineering scaffolding, not tax advice. The rates, thresholds and
5
+ * section references below must be reviewed and signed off by a chartered
6
+ * accountant before you go live, and re-checked every Finance Act. In
7
+ * particular: the Income-tax Act, 2025 renumbers the provisions that the
8
+ * Income-tax Act, 1961 called 115BBH and 194S. Section labels are therefore
9
+ * configuration, not constants — see `TaxRuleSet`.
10
+ *
11
+ * Why this module exists at all:
12
+ *
13
+ * Under the Indian VDA regime, spending USDC is a *transfer of a virtual
14
+ * digital asset*, not a payment. That has two consequences most agentic-payment
15
+ * stacks ignore entirely:
16
+ *
17
+ * 1. Every spend realises a capital gain or loss against the INR cost at
18
+ * which that USDC was acquired. Gains are taxed at a flat 30% (plus
19
+ * surcharge and cess) with no deduction other than cost of acquisition,
20
+ * and losses cannot be set off against anything or carried forward. So
21
+ * you must track lots, not just a balance.
22
+ *
23
+ * 2. The payer is the deductor for 1% TDS on the consideration. You cannot
24
+ * withhold 1% out of an on-chain micropayment — the merchant's 402
25
+ * challenge demands an exact amount. The workable pattern is to accrue the
26
+ * liability against a pool funded at onramp time and remit it on the
27
+ * statutory cycle. `TdsAccrual` implements that accrual.
28
+ *
29
+ * A stack that skips this leaves its users with an unreconcilable year-end.
30
+ */
31
+ import type { OnrampLot, SpendRecord } from "../types.js";
32
+ export interface TaxRuleSet {
33
+ /** Financial year label, e.g. "2026-27". */
34
+ financialYear: string;
35
+ /** Flat rate on VDA gains, in basis points. 30% = 3000. */
36
+ vdaGainRateBps: number;
37
+ /** Health & education cess on tax, in basis points. 4% = 400. */
38
+ cessBps: number;
39
+ /** TDS rate on transfer consideration, in basis points. 1% = 100. */
40
+ tdsRateBps: number;
41
+ /**
42
+ * Annual TDS exemption threshold in paise. ₹50,000 for "specified persons"
43
+ * (broadly, individuals/HUF below the audit turnover limits); ₹10,000
44
+ * otherwise. Below this cumulative consideration in the FY, no TDS is due.
45
+ */
46
+ tdsThresholdPaise: bigint;
47
+ /** Whether losses may offset gains. Under the current VDA regime: false. */
48
+ allowLossSetOff: boolean;
49
+ /** Statutory labels, purely for report headers. */
50
+ labels: {
51
+ gainSection: string;
52
+ tdsSection: string;
53
+ tdsFormSpecifiedPerson: string;
54
+ tdsFormOther: string;
55
+ };
56
+ }
57
+ export declare const RULES_FY_2026_27_SPECIFIED_PERSON: TaxRuleSet;
58
+ export declare const RULES_FY_2026_27_OTHER: TaxRuleSet;
59
+ export interface LotConsumption {
60
+ lotId: string;
61
+ quantity: bigint;
62
+ /** Proportional INR cost of the consumed slice, in paise. */
63
+ costInrPaise: bigint;
64
+ }
65
+ export interface DisposalResult {
66
+ /** Total quantity disposed, in asset base units. */
67
+ quantity: bigint;
68
+ /** INR paise realised (the value of what was bought). */
69
+ proceedsInrPaise: bigint;
70
+ /** INR paise cost of acquisition of the consumed lots. */
71
+ costInrPaise: bigint;
72
+ /** proceeds - cost. Negative means a loss. */
73
+ gainInrPaise: bigint;
74
+ consumptions: LotConsumption[];
75
+ }
76
+ export declare class InsufficientLotsError extends Error {
77
+ readonly shortfall: bigint;
78
+ constructor(shortfall: bigint);
79
+ }
80
+ /**
81
+ * Consume lots FIFO. Mutates `remaining` on the supplied lots — callers should
82
+ * persist them afterwards inside the same transaction as the spend record.
83
+ */
84
+ export declare function consumeFifo(lots: OnrampLot[], quantity: bigint, proceedsInrPaise: bigint): DisposalResult;
85
+ export interface TdsAccrual {
86
+ /** Consideration for this transfer, in paise. */
87
+ considerationPaise: bigint;
88
+ /** TDS due on this transfer, in paise. Zero while below the FY threshold. */
89
+ tdsPaise: bigint;
90
+ /** Cumulative FY consideration including this transfer. */
91
+ cumulativeConsiderationPaise: bigint;
92
+ /** True on the transfer that crosses the threshold, when catch-up applies. */
93
+ thresholdCrossed: boolean;
94
+ /**
95
+ * TDS that must be caught up on earlier sub-threshold transfers, because the
96
+ * threshold is an exemption on cumulative consideration, not a free tranche.
97
+ */
98
+ catchUpPaise: bigint;
99
+ }
100
+ /**
101
+ * Accrue TDS for one transfer given the FY-to-date cumulative consideration.
102
+ *
103
+ * Threshold semantics: once cumulative consideration in the financial year
104
+ * exceeds the threshold, TDS is due on the whole consideration for the year,
105
+ * not merely the excess. So the transfer that crosses the line carries a
106
+ * catch-up for everything that preceded it.
107
+ */
108
+ export declare function accrueTds(rules: TaxRuleSet, considerationPaise: bigint, priorCumulativePaise: bigint, priorTdsPaidPaise: bigint): TdsAccrual;
109
+ export interface VdaTaxReport {
110
+ financialYear: string;
111
+ rules: TaxRuleSet;
112
+ totalSpends: number;
113
+ totalConsiderationPaise: bigint;
114
+ /** Sum of positive gains. This is the taxable base. */
115
+ totalGainsPaise: bigint;
116
+ /** Sum of losses, reported separately because they are not deductible. */
117
+ totalLossesPaise: bigint;
118
+ /** Net of gains and losses — informational only when set-off is disallowed. */
119
+ netPaise: bigint;
120
+ taxOnGainsPaise: bigint;
121
+ cessPaise: bigint;
122
+ totalTaxPaise: bigint;
123
+ tdsAccruedPaise: bigint;
124
+ /** Tax still to pay after crediting TDS already deducted. */
125
+ balancePayablePaise: bigint;
126
+ lines: VdaTaxLine[];
127
+ warnings: string[];
128
+ }
129
+ export interface VdaTaxLine {
130
+ spendId: string;
131
+ date: string;
132
+ counterparty: string;
133
+ resource: string;
134
+ quantity: bigint;
135
+ proceedsInrPaise: bigint;
136
+ costInrPaise: bigint;
137
+ gainInrPaise: bigint;
138
+ tdsPaise: bigint;
139
+ }
140
+ export interface ReportInput {
141
+ rules: TaxRuleSet;
142
+ spends: SpendRecord[];
143
+ lots: OnrampLot[];
144
+ }
145
+ /**
146
+ * Build the FY report. Recomputes disposals from scratch against a copy of the
147
+ * lots so the report is reproducible and never mutates stored state.
148
+ */
149
+ export declare function buildVdaTaxReport(input: ReportInput): VdaTaxReport;
150
+ export declare function paiseToInr(paise: bigint): string;
151
+ //# sourceMappingURL=vda-tax.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"vda-tax.d.ts","sourceRoot":"","sources":["../../src/india/vda-tax.ts"],"names":[],"mappings":"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"}