@envarai/envarpay 0.2.0-alpha.18 → 0.2.0-alpha.19
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/dist/commerce/card.js +1 -1
- package/dist/commerce/cli.js +8 -0
- package/dist/commerce/config.js +2 -0
- package/dist/commerce/envar-balance.d.ts +25 -0
- package/dist/commerce/envar-balance.js +65 -0
- package/dist/commerce/envar.d.ts +8 -0
- package/dist/commerce/envar.js +21 -3
- package/dist/commerce/pricing.js +1 -1
- package/dist/commerce/runtime.d.ts +1 -0
- package/dist/commerce/runtime.js +1 -0
- package/dist/commerce/types.d.ts +4 -0
- package/package.json +1 -1
- package/schemas/envarpay.config.schema.json +13 -0
package/dist/commerce/card.js
CHANGED
|
@@ -14,7 +14,7 @@ export function createOfferCard(config, serviceId, offerId, origin) {
|
|
|
14
14
|
if (!service || !offer)
|
|
15
15
|
throw new CommerceError('offer_not_found', 'Service offer not found');
|
|
16
16
|
const profile = offer.paymentProfile ? config.paymentProfiles[offer.paymentProfile] : null;
|
|
17
|
-
const payment = profile?.adapter === 'x402' ? { adapter: 'x402', network: profile.network, asset: profile.asset, payTo: profile.payTo } : profile ? { adapter: 'mpp', method: 'stripe', intent: 'charge', currency: profile.currency } : null;
|
|
17
|
+
const payment = profile?.adapter === 'x402' ? { adapter: 'x402', network: profile.network, asset: profile.asset, payTo: profile.payTo } : profile?.adapter === 'envar_balance' ? { adapter: 'envar_balance', currency: 'usd', accountRef: profile.accountRef } : profile ? { adapter: 'mpp', method: 'stripe', intent: 'charge', currency: profile.currency } : null;
|
|
18
18
|
return AgentCard.fromJSON({
|
|
19
19
|
name: service.name,
|
|
20
20
|
description: `Deliverables: ${service.contract.deliverables.join(', ')}`,
|
package/dist/commerce/cli.js
CHANGED
|
@@ -17,6 +17,7 @@ import { EnvarSellerRuntime, PinnedUpstreams, restoreEnvarConfig } from './envar
|
|
|
17
17
|
import { listenCommerce } from './http.js';
|
|
18
18
|
import { CredentialVault } from './vault.js';
|
|
19
19
|
import { X402Gate } from './x402.js';
|
|
20
|
+
import { EnvarBalanceGate } from './envar-balance.js';
|
|
20
21
|
import { evmReceiptVerifier, evmSettlementRecovery } from './chain.js';
|
|
21
22
|
import { CommerceError } from './types.js';
|
|
22
23
|
import { createNativeSkillExecutor } from './native-skills.js';
|
|
@@ -311,6 +312,11 @@ async function run() {
|
|
|
311
312
|
gates.mpp = gate;
|
|
312
313
|
stripeRecipientFor = ref => gate.merchantRecipient(ref);
|
|
313
314
|
}
|
|
315
|
+
if (protocols.has('envar_balance')) {
|
|
316
|
+
if (!integration)
|
|
317
|
+
throw new CommerceError('balance_configuration', 'Envar balance requires an authenticated Envar seller integration');
|
|
318
|
+
gates.envar_balance = new EnvarBalanceGate({ store, vault: vault, claim: args => integration.claimBalancePayment(args) });
|
|
319
|
+
}
|
|
314
320
|
const nativeSkills = secrets.skills ? createNativeSkillExecutor(secrets.skills, values.origin, config.agent.name) : undefined;
|
|
315
321
|
const discovery = secrets.skills?.discovery?.enabledFromWeb ? new SkillDiscovery(secrets.skills, secrets.skills.discovery, values.origin) : undefined;
|
|
316
322
|
if (nativeSkills)
|
|
@@ -325,6 +331,8 @@ async function run() {
|
|
|
325
331
|
throw new CommerceError('x402_configuration', 'Configure the required network RPC locally');
|
|
326
332
|
if (profile.adapter === 'mpp' && !stripeRecipientFor?.(profile.accountRef))
|
|
327
333
|
throw new CommerceError('mpp_configuration', 'Configure and verify the merchant account locally');
|
|
334
|
+
if (profile.adapter === 'envar_balance' && !syncSettings?.policy.allowedBalanceAccounts?.includes(profile.accountRef))
|
|
335
|
+
throw new CommerceError('balance_configuration', 'Allow this balance receiving account locally');
|
|
328
336
|
}
|
|
329
337
|
};
|
|
330
338
|
validateAdapters(config);
|
package/dist/commerce/config.js
CHANGED
|
@@ -135,6 +135,8 @@ export function loadCommerceConfig(value) {
|
|
|
135
135
|
throw new CommerceError('invalid_amount', 'Maximum total exceeds supported integer range');
|
|
136
136
|
if (profile.adapter === 'mpp' && low < 50n)
|
|
137
137
|
throw new CommerceError('card_minimum', 'USD card orders must be at least 50 cents');
|
|
138
|
+
if (profile.adapter === 'envar_balance' && high > 2n ** 63n - 1n)
|
|
139
|
+
throw new CommerceError('balance_amount', 'Envar balance prices must fit signed micro-USD');
|
|
138
140
|
}
|
|
139
141
|
}
|
|
140
142
|
return config;
|
|
@@ -0,0 +1,25 @@
|
|
|
1
|
+
import { type PriceQuote } from './types.js';
|
|
2
|
+
import { CommerceStore, type OrderRecord } from './store.js';
|
|
3
|
+
import { CredentialVault } from './vault.js';
|
|
4
|
+
export interface BalanceClaimArgs {
|
|
5
|
+
platformOrderId: string;
|
|
6
|
+
claimToken: string;
|
|
7
|
+
taskId: string;
|
|
8
|
+
quote: PriceQuote;
|
|
9
|
+
}
|
|
10
|
+
export interface EnvarBalanceGateOptions {
|
|
11
|
+
store: CommerceStore;
|
|
12
|
+
vault: CredentialVault;
|
|
13
|
+
claim: (args: BalanceClaimArgs) => Promise<Record<string, unknown>>;
|
|
14
|
+
}
|
|
15
|
+
export declare class EnvarBalanceGate {
|
|
16
|
+
private readonly options;
|
|
17
|
+
constructor(options: EnvarBalanceGateOptions);
|
|
18
|
+
private assertReceipt;
|
|
19
|
+
private settle;
|
|
20
|
+
handle(request: Request, order: OrderRecord): Promise<{
|
|
21
|
+
response: Response;
|
|
22
|
+
} | {
|
|
23
|
+
headers: Record<string, string>;
|
|
24
|
+
}>;
|
|
25
|
+
}
|
|
@@ -0,0 +1,65 @@
|
|
|
1
|
+
/** Opt-in seller gate for a captured Envar account-balance claim. No x402 or MPP impersonation. */
|
|
2
|
+
import { digest } from './config.js';
|
|
3
|
+
import { CommerceError } from './types.js';
|
|
4
|
+
const UUID = /^[0-9a-f]{8}-[0-9a-f]{4}-[1-8][0-9a-f]{3}-[89ab][0-9a-f]{3}-[0-9a-f]{12}$/i;
|
|
5
|
+
export class EnvarBalanceGate {
|
|
6
|
+
options;
|
|
7
|
+
constructor(options) {
|
|
8
|
+
this.options = options;
|
|
9
|
+
}
|
|
10
|
+
assertReceipt(order, platformOrderId, receipt) {
|
|
11
|
+
const quote = order.quote, profile = quote.paymentProfile;
|
|
12
|
+
if (profile?.adapter !== 'envar_balance')
|
|
13
|
+
throw new CommerceError('balance_profile', 'Original quote is not an Envar balance offer');
|
|
14
|
+
if (receipt.state !== 'captured' || receipt.order_id !== platformOrderId ||
|
|
15
|
+
receipt.amount !== quote.amount || receipt.currency !== 'usd' || receipt.recipient !== profile?.accountRef ||
|
|
16
|
+
receipt.quote_id !== quote.quoteId || receipt.message_id !== quote.messageId ||
|
|
17
|
+
receipt.task_id !== order.taskId ||
|
|
18
|
+
receipt.service_id !== quote.serviceId || receipt.service_revision !== quote.serviceRevision ||
|
|
19
|
+
receipt.offer_id !== quote.offerId || receipt.input_digest !== quote.inputDigest ||
|
|
20
|
+
receipt.terms_digest !== quote.termsDigest ||
|
|
21
|
+
typeof receipt.economic_id !== 'string' || !/^ledger:[0-9a-f-]{36}$/i.test(receipt.economic_id))
|
|
22
|
+
throw new CommerceError('balance_receipt_mismatch', 'Platform capture differs from the frozen service order');
|
|
23
|
+
}
|
|
24
|
+
async settle(order, attemptId, saved) {
|
|
25
|
+
if (saved.quoteId !== order.quote.quoteId)
|
|
26
|
+
throw new CommerceError('balance_recovery_conflict', 'Original balance claim belongs to another quote');
|
|
27
|
+
let receipt;
|
|
28
|
+
try {
|
|
29
|
+
receipt = await this.options.claim({ platformOrderId: saved.platformOrderId, claimToken: saved.claimToken, taskId: order.taskId, quote: order.quote });
|
|
30
|
+
this.assertReceipt(order, saved.platformOrderId, receipt);
|
|
31
|
+
}
|
|
32
|
+
catch {
|
|
33
|
+
this.options.store.recordSettlement(attemptId, 'unknown', {});
|
|
34
|
+
return { response: Response.json({ error: 'balance_claim_unknown', taskId: order.taskId }, { status: 503, headers: { 'Cache-Control': 'no-store' } }) };
|
|
35
|
+
}
|
|
36
|
+
this.options.store.recordSettlement(attemptId, 'confirmed', receipt);
|
|
37
|
+
return { headers: { 'ENVAR-BALANCE-RECEIPT': String(receipt.economic_id) } };
|
|
38
|
+
}
|
|
39
|
+
async handle(request, order) {
|
|
40
|
+
const quote = order.quote, profile = quote.paymentProfile;
|
|
41
|
+
if (profile?.adapter !== 'envar_balance' || quote.currency !== 'usd' || quote.recipient !== profile.accountRef)
|
|
42
|
+
throw new CommerceError('balance_profile', 'This offer does not accept Envar account balance');
|
|
43
|
+
const prior = this.options.store.paymentForOrder(order.id);
|
|
44
|
+
if (order.paymentState === 'confirmed' && prior?.receipt)
|
|
45
|
+
return { headers: { 'ENVAR-BALANCE-RECEIPT': String(prior.receipt.economic_id) } };
|
|
46
|
+
if (['settling', 'unknown'].includes(order.paymentState)) {
|
|
47
|
+
if (!prior)
|
|
48
|
+
throw new CommerceError('balance_recovery_conflict', 'Original balance attempt is missing');
|
|
49
|
+
const saved = this.options.vault.get(prior.credentialRef);
|
|
50
|
+
return this.settle(order, prior.id, saved);
|
|
51
|
+
}
|
|
52
|
+
if (Date.now() >= Date.parse(quote.expiresAt))
|
|
53
|
+
return { response: Response.json({ error: 'quote_expired' }, { status: 409, headers: { 'Cache-Control': 'no-store' } }) };
|
|
54
|
+
const platformOrderId = request.headers.get('Envar-Balance-Order') ?? '';
|
|
55
|
+
const claimToken = request.headers.get('Envar-Balance-Claim') ?? '';
|
|
56
|
+
if (!platformOrderId && !claimToken)
|
|
57
|
+
return { response: Response.json({ error: 'envar_balance_required', taskId: order.taskId, quote: { quoteId: quote.quoteId, messageId: quote.messageId, serviceId: quote.serviceId, serviceRevision: quote.serviceRevision, offerId: quote.offerId, inputDigest: quote.inputDigest, termsDigest: quote.termsDigest, amount: quote.amount, currency: quote.currency, recipient: quote.recipient, expiresAt: quote.expiresAt } }, { status: 402, headers: { 'Cache-Control': 'no-store' } }) };
|
|
58
|
+
if (!UUID.test(platformOrderId) || claimToken.length < 32 || claimToken.length > 4096 || /\s/.test(claimToken))
|
|
59
|
+
throw new CommerceError('balance_claim', 'Use the original bounded platform balance claim');
|
|
60
|
+
const credentialRef = this.options.vault.put({ platformOrderId, claimToken, quoteId: quote.quoteId });
|
|
61
|
+
const economicKey = digest({ adapter: 'envar_balance', platformOrderId });
|
|
62
|
+
const attempt = this.options.store.reservePayment(order.id, order.caller, economicKey, credentialRef);
|
|
63
|
+
return this.settle(order, attempt, { platformOrderId, claimToken, quoteId: quote.quoteId });
|
|
64
|
+
}
|
|
65
|
+
}
|
package/dist/commerce/envar.d.ts
CHANGED
|
@@ -16,6 +16,8 @@ export interface EnvarPolicy {
|
|
|
16
16
|
facilitatorOrigin: string;
|
|
17
17
|
}[];
|
|
18
18
|
allowedMppAccounts: string[];
|
|
19
|
+
/** Explicit seller account references allowed to accept platform balance. */
|
|
20
|
+
allowedBalanceAccounts?: string[];
|
|
19
21
|
}
|
|
20
22
|
export interface EnvarCandidate {
|
|
21
23
|
id: string;
|
|
@@ -81,6 +83,12 @@ export declare class EnvarIntegration {
|
|
|
81
83
|
close(): void;
|
|
82
84
|
private open;
|
|
83
85
|
private request;
|
|
86
|
+
claimBalancePayment(args: {
|
|
87
|
+
platformOrderId: string;
|
|
88
|
+
claimToken: string;
|
|
89
|
+
taskId: string;
|
|
90
|
+
quote: PriceQuote;
|
|
91
|
+
}): Promise<Record<string, unknown>>;
|
|
84
92
|
allowInstalledService(name: string): void;
|
|
85
93
|
reportRuntime(value: unknown): Promise<{
|
|
86
94
|
name: string;
|
package/dist/commerce/envar.js
CHANGED
|
@@ -136,6 +136,9 @@ export class EnvarIntegration {
|
|
|
136
136
|
});
|
|
137
137
|
if (this.policy.allowedMppAccounts.some((id) => !ID.test(id)))
|
|
138
138
|
fail("envar_policy", "Configure explicit merchant account references");
|
|
139
|
+
this.policy.allowedBalanceAccounts ??= [];
|
|
140
|
+
if (this.policy.allowedBalanceAccounts.some((id) => !ID.test(id)))
|
|
141
|
+
fail("envar_policy", "Configure explicit balance account references");
|
|
139
142
|
this.timeoutMs = options.timeoutMs ?? 10000;
|
|
140
143
|
this.limitBytes = options.responseLimitBytes ?? 1048576;
|
|
141
144
|
if (!Number.isInteger(this.timeoutMs) ||
|
|
@@ -214,12 +217,12 @@ export class EnvarIntegration {
|
|
|
214
217
|
}
|
|
215
218
|
async request(path, payload, eventId) {
|
|
216
219
|
this.open();
|
|
217
|
-
if (![
|
|
220
|
+
if (!([
|
|
218
221
|
"/api/v1/agent-config/candidates",
|
|
219
222
|
"/api/v1/agent-config/acknowledgments",
|
|
220
223
|
"/api/v1/commerce-reports",
|
|
221
224
|
"/api/v1/agent-config/runtime",
|
|
222
|
-
].includes(path))
|
|
225
|
+
].includes(path) || /^\/api\/v1\/commerce\/payment-claims\/[0-9a-f-]{36}\/claim$/i.test(path)))
|
|
223
226
|
fail("envar_path", "Unsupported platform integration operation");
|
|
224
227
|
const token = await this.options.token();
|
|
225
228
|
if (typeof token !== "string" ||
|
|
@@ -298,6 +301,19 @@ export class EnvarIntegration {
|
|
|
298
301
|
fail("envar_response_json", "Platform response must be an object");
|
|
299
302
|
return value;
|
|
300
303
|
}
|
|
304
|
+
async claimBalancePayment(args) {
|
|
305
|
+
uuid(args.platformOrderId);
|
|
306
|
+
if (typeof args.claimToken !== 'string' || args.claimToken.length < 32 || args.claimToken.length > 4096 || /\s/.test(args.claimToken))
|
|
307
|
+
fail('envar_balance_claim', 'Use the original bounded platform claim');
|
|
308
|
+
return this.request(`/api/v1/commerce/payment-claims/${args.platformOrderId}/claim`, {
|
|
309
|
+
claim_token: args.claimToken, task_id: args.taskId,
|
|
310
|
+
quote_id: args.quote.quoteId, message_id: args.quote.messageId,
|
|
311
|
+
service_id: args.quote.serviceId, service_revision: args.quote.serviceRevision,
|
|
312
|
+
offer_id: args.quote.offerId, input_digest: args.quote.inputDigest,
|
|
313
|
+
terms_digest: args.quote.termsDigest, amount: args.quote.amount,
|
|
314
|
+
currency: args.quote.currency, recipient: args.quote.recipient,
|
|
315
|
+
}, args.platformOrderId);
|
|
316
|
+
}
|
|
301
317
|
allowInstalledService(name) {
|
|
302
318
|
if (!ID.test(name))
|
|
303
319
|
fail('envar_policy', 'Invalid installed Skill name');
|
|
@@ -348,8 +364,10 @@ export class EnvarIntegration {
|
|
|
348
364
|
a.facilitatorOrigin === new URL(profile.facilitatorUrl).origin))
|
|
349
365
|
fail("envar_recipient_denied", "Receiving identity or facilitator is outside the local update grant");
|
|
350
366
|
}
|
|
351
|
-
else if (!this.policy.allowedMppAccounts.includes(profile.accountRef))
|
|
367
|
+
else if (profile.adapter === "mpp" && !this.policy.allowedMppAccounts.includes(profile.accountRef))
|
|
352
368
|
fail("envar_merchant_denied", "Merchant account is outside the local update grant");
|
|
369
|
+
else if (profile.adapter === "envar_balance" && !this.policy.allowedBalanceAccounts.includes(profile.accountRef))
|
|
370
|
+
fail("envar_balance_denied", "Balance receiving account is outside the local update grant");
|
|
353
371
|
}
|
|
354
372
|
const prior = this.db
|
|
355
373
|
.prepare("SELECT * FROM revisions WHERE id=? OR (local_service=? AND revision=?)")
|
package/dist/commerce/pricing.js
CHANGED
|
@@ -33,7 +33,7 @@ export function buildQuote(config, args) {
|
|
|
33
33
|
if (amount >= 2n ** 256n)
|
|
34
34
|
throw new CommerceError('invalid_amount', 'Quoted amount is too large');
|
|
35
35
|
const profile = offer.paymentProfile ? config.paymentProfiles[offer.paymentProfile] : null;
|
|
36
|
-
const recipient = profile?.adapter === 'x402' ? profile.payTo : profile ? args.stripeRecipient : null;
|
|
36
|
+
const recipient = profile?.adapter === 'x402' ? profile.payTo : profile?.adapter === 'envar_balance' ? profile.accountRef : profile ? args.stripeRecipient : null;
|
|
37
37
|
if (profile?.adapter === 'mpp' && !recipient)
|
|
38
38
|
throw new CommerceError('merchant_not_ready', 'Resolve and verify the Stripe merchant before quoting');
|
|
39
39
|
const now = args.now ?? new Date();
|
|
@@ -6,6 +6,7 @@ export * from './skill-gate.js';
|
|
|
6
6
|
export * from './native-skills.js';
|
|
7
7
|
export * from './http.js';
|
|
8
8
|
export * from './x402.js';
|
|
9
|
+
export * from './envar-balance.js';
|
|
9
10
|
export * from './vault.js';
|
|
10
11
|
export * from './chain.js';
|
|
11
12
|
export * from './external-agent.js';
|
package/dist/commerce/runtime.js
CHANGED
|
@@ -6,6 +6,7 @@ export * from './skill-gate.js';
|
|
|
6
6
|
export * from './native-skills.js';
|
|
7
7
|
export * from './http.js';
|
|
8
8
|
export * from './x402.js';
|
|
9
|
+
export * from './envar-balance.js';
|
|
9
10
|
export * from './vault.js';
|
|
10
11
|
export * from './chain.js';
|
|
11
12
|
export * from './external-agent.js';
|
package/dist/commerce/types.d.ts
CHANGED
package/package.json
CHANGED
|
@@ -45,6 +45,9 @@
|
|
|
45
45
|
},
|
|
46
46
|
{
|
|
47
47
|
"$ref": "#/$defs/mpp"
|
|
48
|
+
},
|
|
49
|
+
{
|
|
50
|
+
"$ref": "#/$defs/envarBalance"
|
|
48
51
|
}
|
|
49
52
|
]
|
|
50
53
|
}
|
|
@@ -140,6 +143,16 @@
|
|
|
140
143
|
}
|
|
141
144
|
}
|
|
142
145
|
},
|
|
146
|
+
"envarBalance": {
|
|
147
|
+
"type": "object",
|
|
148
|
+
"additionalProperties": false,
|
|
149
|
+
"required": ["adapter", "currency", "accountRef"],
|
|
150
|
+
"properties": {
|
|
151
|
+
"adapter": { "const": "envar_balance" },
|
|
152
|
+
"currency": { "const": "usd" },
|
|
153
|
+
"accountRef": { "$ref": "#/$defs/id" }
|
|
154
|
+
}
|
|
155
|
+
},
|
|
143
156
|
"service": {
|
|
144
157
|
"type": "object",
|
|
145
158
|
"additionalProperties": false,
|