@envarai/envarpay 0.2.0-alpha.2 → 0.2.0-alpha.20
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 +18 -56
- package/dist/commerce/agent-setup.d.ts +19 -0
- package/dist/commerce/agent-setup.js +210 -0
- package/dist/commerce/alerts.d.ts +27 -0
- package/dist/commerce/alerts.js +71 -0
- package/dist/commerce/card.js +3 -3
- package/dist/commerce/cli.js +91 -119
- package/dist/commerce/config.d.ts +3 -2
- package/dist/commerce/config.js +19 -77
- package/dist/commerce/configure.d.ts +2 -0
- package/dist/commerce/configure.js +78 -0
- package/dist/commerce/discovery.d.ts +20 -0
- package/dist/commerce/discovery.js +162 -0
- package/dist/commerce/envar-balance.d.ts +25 -0
- package/dist/commerce/envar-balance.js +65 -0
- package/dist/commerce/envar-runtime.d.ts +3 -1
- package/dist/commerce/envar-runtime.js +28 -1
- package/dist/commerce/envar.d.ts +13 -31
- package/dist/commerce/envar.js +35 -83
- package/dist/commerce/external-agent.d.ts +26 -0
- package/dist/commerce/external-agent.js +129 -0
- package/dist/commerce/external-events.d.ts +76 -0
- package/dist/commerce/external-events.js +273 -0
- package/dist/commerce/external-mcp.d.ts +10 -0
- package/dist/commerce/external-mcp.js +199 -0
- package/dist/commerce/external-oauth.d.ts +12 -0
- package/dist/commerce/external-oauth.js +230 -0
- package/dist/commerce/external-runtime.d.ts +25 -0
- package/dist/commerce/external-runtime.js +244 -0
- package/dist/commerce/index.d.ts +1 -0
- package/dist/commerce/index.js +1 -0
- package/dist/commerce/managed.d.ts +3 -0
- package/dist/commerce/managed.js +143 -0
- package/dist/commerce/native-inventory.d.ts +9 -0
- package/dist/commerce/native-inventory.js +75 -0
- package/dist/commerce/native-skills.d.ts +32 -0
- package/dist/commerce/native-skills.js +174 -0
- package/dist/commerce/openclaw-setup.d.ts +28 -0
- package/dist/commerce/openclaw-setup.js +43 -0
- package/dist/commerce/operations.d.ts +8 -3
- package/dist/commerce/operations.js +21 -18
- package/dist/commerce/pricing.d.ts +0 -1
- package/dist/commerce/pricing.js +3 -5
- package/dist/commerce/runtime.d.ts +4 -2
- package/dist/commerce/runtime.js +4 -2
- package/dist/commerce/server.d.ts +3 -2
- package/dist/commerce/server.js +26 -10
- package/dist/commerce/setup.d.ts +25 -0
- package/dist/commerce/setup.js +379 -0
- package/dist/commerce/skill-gate.d.ts +54 -0
- package/dist/commerce/skill-gate.js +153 -0
- package/dist/commerce/store.d.ts +11 -1
- package/dist/commerce/store.js +36 -2
- package/dist/commerce/types.d.ts +6 -57
- package/examples/envar-integration-policy.json +1 -9
- package/examples/hermes-setup-probe.py +109 -0
- package/examples/hermes-skill-inventory.py +108 -0
- package/examples/native-skill-runner.py +197 -0
- package/examples/seller.json +143 -26
- package/package.json +5 -16
- package/schemas/envarpay.config.schema.json +28 -87
- package/dist/commerce/buyer-store.d.ts +0 -101
- package/dist/commerce/buyer-store.js +0 -168
- package/dist/commerce/buyer.d.ts +0 -132
- package/dist/commerce/buyer.js +0 -755
- package/dist/commerce/chain.d.ts +0 -24
- package/dist/commerce/chain.js +0 -146
- package/dist/commerce/client-runtime.d.ts +0 -8
- package/dist/commerce/client-runtime.js +0 -7
- package/dist/commerce/management.d.ts +0 -14
- package/dist/commerce/management.js +0 -138
- package/dist/commerce/mpp-client.d.ts +0 -59
- package/dist/commerce/mpp-client.js +0 -24
- package/dist/commerce/mpp-runtime.d.ts +0 -3
- package/dist/commerce/mpp-runtime.js +0 -3
- package/dist/commerce/mpp.d.ts +0 -53
- package/dist/commerce/mpp.js +0 -163
- package/dist/commerce/peer-proxy.d.ts +0 -44
- package/dist/commerce/peer-proxy.js +0 -673
- package/dist/commerce/stripe-provider.d.ts +0 -9
- package/dist/commerce/stripe-provider.js +0 -97
- package/dist/commerce/x402.d.ts +0 -37
- package/dist/commerce/x402.js +0 -174
- package/examples/buyer-policy.json +0 -18
- package/schemas/buyer-policy.schema.json +0 -253
|
@@ -1,168 +0,0 @@
|
|
|
1
|
-
import { DatabaseSync } from 'node:sqlite';
|
|
2
|
-
import { chmodSync, mkdirSync } from 'node:fs';
|
|
3
|
-
import { dirname } from 'node:path';
|
|
4
|
-
import { randomUUID } from 'node:crypto';
|
|
5
|
-
import { atomic } from './config.js';
|
|
6
|
-
import { CommerceError } from './types.js';
|
|
7
|
-
/** One wallet ledger, one process. Unknown authorizations retain cumulative budget. */
|
|
8
|
-
export class BuyerStore {
|
|
9
|
-
db;
|
|
10
|
-
owner;
|
|
11
|
-
constructor(path) {
|
|
12
|
-
if (path !== ':memory:') {
|
|
13
|
-
mkdirSync(dirname(path), { recursive: true, mode: 0o700 });
|
|
14
|
-
this.owner = new DatabaseSync(path + '.owner.sqlite3');
|
|
15
|
-
chmodSync(path + '.owner.sqlite3', 0o600);
|
|
16
|
-
try {
|
|
17
|
-
this.owner.exec('PRAGMA busy_timeout=0; BEGIN EXCLUSIVE');
|
|
18
|
-
}
|
|
19
|
-
catch {
|
|
20
|
-
this.owner.close();
|
|
21
|
-
throw new CommerceError('store_locked', 'Buyer ledger already has an active process');
|
|
22
|
-
}
|
|
23
|
-
}
|
|
24
|
-
try {
|
|
25
|
-
this.db = new DatabaseSync(path);
|
|
26
|
-
if (path !== ':memory:')
|
|
27
|
-
chmodSync(path, 0o600);
|
|
28
|
-
this.db.exec(`PRAGMA journal_mode=WAL; PRAGMA synchronous=FULL; PRAGMA busy_timeout=5000;
|
|
29
|
-
CREATE TABLE IF NOT EXISTS buyer_meta(version INTEGER NOT NULL);
|
|
30
|
-
INSERT INTO buyer_meta SELECT 1 WHERE NOT EXISTS(SELECT 1 FROM buyer_meta);
|
|
31
|
-
CREATE TABLE IF NOT EXISTS buyer_purchases(
|
|
32
|
-
id TEXT PRIMARY KEY, caller TEXT NOT NULL, message_id TEXT NOT NULL,
|
|
33
|
-
currency TEXT NOT NULL, amount TEXT NOT NULL, budget_state TEXT NOT NULL,
|
|
34
|
-
record_json TEXT NOT NULL, UNIQUE(caller,message_id), UNIQUE(message_id)
|
|
35
|
-
);`);
|
|
36
|
-
if (this.db.prepare('SELECT version FROM buyer_meta').get()?.version !== 1)
|
|
37
|
-
throw new CommerceError('store_version', 'Unsupported buyer ledger version');
|
|
38
|
-
// Never resume signing after a crash: no second nonce may be created.
|
|
39
|
-
for (const row of this.db.prepare('SELECT record_json FROM buyer_purchases').all()) {
|
|
40
|
-
const record = JSON.parse(String(row.record_json));
|
|
41
|
-
if (record.state === 'signing' || record.state === 'submitted')
|
|
42
|
-
this.update(record.id, { state: 'unknown', paymentState: ['confirmed', 'not_required'].includes(record.paymentState) ? record.paymentState : 'unknown', errorCode: 'interrupted_purchase' });
|
|
43
|
-
}
|
|
44
|
-
}
|
|
45
|
-
catch (error) {
|
|
46
|
-
this.owner?.close();
|
|
47
|
-
throw error;
|
|
48
|
-
}
|
|
49
|
-
}
|
|
50
|
-
close() { this.db.close(); if (this.owner) {
|
|
51
|
-
this.owner.exec('ROLLBACK');
|
|
52
|
-
this.owner.close();
|
|
53
|
-
} }
|
|
54
|
-
transaction(fn) {
|
|
55
|
-
this.db.exec('BEGIN IMMEDIATE');
|
|
56
|
-
try {
|
|
57
|
-
const result = fn();
|
|
58
|
-
this.db.exec('COMMIT');
|
|
59
|
-
return result;
|
|
60
|
-
}
|
|
61
|
-
catch (error) {
|
|
62
|
-
this.db.exec('ROLLBACK');
|
|
63
|
-
throw error;
|
|
64
|
-
}
|
|
65
|
-
}
|
|
66
|
-
get(id, caller) {
|
|
67
|
-
const row = this.db.prepare('SELECT record_json FROM buyer_purchases WHERE id=? AND caller=?').get(id, caller);
|
|
68
|
-
return row ? JSON.parse(String(row.record_json)) : undefined;
|
|
69
|
-
}
|
|
70
|
-
assertMessageOwner(caller, messageId) {
|
|
71
|
-
const row = this.db.prepare('SELECT caller FROM buyer_purchases WHERE message_id=?').get(messageId);
|
|
72
|
-
if (row && row.caller !== caller)
|
|
73
|
-
throw new CommerceError('purchase_conflict', 'Message ID is already reserved to another caller');
|
|
74
|
-
}
|
|
75
|
-
find(caller, messageId) {
|
|
76
|
-
const row = this.db.prepare('SELECT record_json FROM buyer_purchases WHERE caller=? AND message_id=?').get(caller, messageId);
|
|
77
|
-
return row ? JSON.parse(String(row.record_json)) : undefined;
|
|
78
|
-
}
|
|
79
|
-
insert(value) {
|
|
80
|
-
return this.transaction(() => {
|
|
81
|
-
this.assertMessageOwner(value.caller, value.messageId);
|
|
82
|
-
const prior = this.find(value.caller, value.messageId);
|
|
83
|
-
if (prior) {
|
|
84
|
-
if (prior.fingerprint !== value.fingerprint)
|
|
85
|
-
throw new CommerceError('purchase_conflict', 'Message ID already identifies another purchase');
|
|
86
|
-
return prior;
|
|
87
|
-
}
|
|
88
|
-
const now = new Date().toISOString();
|
|
89
|
-
const record = { ...value, id: randomUUID(), createdAt: now, updatedAt: now, state: 'previewed', paymentState: 'quoted', executionState: 'not_started' };
|
|
90
|
-
this.db.prepare('INSERT INTO buyer_purchases VALUES(?,?,?,?,?,?,?)').run(record.id, record.caller, record.messageId, record.quote.currency ?? '', record.quote.amount, 'none', JSON.stringify(record));
|
|
91
|
-
return record;
|
|
92
|
-
});
|
|
93
|
-
}
|
|
94
|
-
update(id, patch) {
|
|
95
|
-
const row = this.db.prepare('SELECT record_json FROM buyer_purchases WHERE id=?').get(id);
|
|
96
|
-
if (!row)
|
|
97
|
-
throw new CommerceError('purchase_not_found', 'Purchase not found');
|
|
98
|
-
const record = { ...JSON.parse(String(row.record_json)), ...patch, updatedAt: new Date().toISOString() };
|
|
99
|
-
this.db.prepare('UPDATE buyer_purchases SET record_json=? WHERE id=?').run(JSON.stringify(record), id);
|
|
100
|
-
return record;
|
|
101
|
-
}
|
|
102
|
-
pendingTasks() {
|
|
103
|
-
return this.db.prepare('SELECT record_json FROM buyer_purchases').all()
|
|
104
|
-
.map(row => JSON.parse(String(row.record_json)))
|
|
105
|
-
.filter(record => record.task?.id && ['working', 'unknown'].includes(record.executionState));
|
|
106
|
-
}
|
|
107
|
-
usage(currency) {
|
|
108
|
-
let reserved = 0n, spent = 0n;
|
|
109
|
-
for (const row of this.db.prepare("SELECT amount,budget_state FROM buyer_purchases WHERE currency=? AND budget_state!='none'").all(currency)) {
|
|
110
|
-
if (row.budget_state === 'spent')
|
|
111
|
-
spent += BigInt(String(row.amount));
|
|
112
|
-
else
|
|
113
|
-
reserved += BigInt(String(row.amount));
|
|
114
|
-
}
|
|
115
|
-
return { reserved: reserved.toString(), spent: spent.toString() };
|
|
116
|
-
}
|
|
117
|
-
reserve(id, caller, policy) {
|
|
118
|
-
return this.transaction(() => {
|
|
119
|
-
const record = this.get(id, caller);
|
|
120
|
-
if (!record)
|
|
121
|
-
throw new CommerceError('purchase_not_found', 'Purchase not found');
|
|
122
|
-
if (record.state !== 'previewed')
|
|
123
|
-
return { record, fresh: false };
|
|
124
|
-
const limit = policy.budgets.find(b => b.currency === record.quote.currency);
|
|
125
|
-
const usage = this.usage(record.quote.currency ?? '');
|
|
126
|
-
if (!limit || BigInt(usage.reserved) + BigInt(usage.spent) + atomic(record.quote.amount) > atomic(limit.maxTotal))
|
|
127
|
-
throw new CommerceError('budget_exceeded', 'Cumulative wallet budget is exhausted or reserved');
|
|
128
|
-
this.db.prepare("UPDATE buyer_purchases SET budget_state='reserved' WHERE id=?").run(id);
|
|
129
|
-
return { record: this.update(id, { state: 'signing', paymentState: 'reserved' }), fresh: true };
|
|
130
|
-
});
|
|
131
|
-
}
|
|
132
|
-
beginFree(id, caller) {
|
|
133
|
-
return this.transaction(() => {
|
|
134
|
-
const record = this.get(id, caller);
|
|
135
|
-
if (!record || record.protocol !== 'free' || record.quote.amount !== '0' || record.quote.currency !== null || record.quote.recipient !== null)
|
|
136
|
-
throw new CommerceError('free_purchase_binding', 'Invalid original free purchase');
|
|
137
|
-
if (record.state !== 'previewed')
|
|
138
|
-
return { record, fresh: false };
|
|
139
|
-
return { record: this.update(id, { state: 'submitted', paymentState: 'not_required' }), fresh: true };
|
|
140
|
-
});
|
|
141
|
-
}
|
|
142
|
-
rejectBeforeSigning(id, caller, errorCode) {
|
|
143
|
-
return this.transaction(() => {
|
|
144
|
-
const record = this.get(id, caller);
|
|
145
|
-
if (!record || record.state !== 'signing' || record.credentialRef || record.nonce)
|
|
146
|
-
throw new CommerceError('authorization_may_exist', 'Cannot release an existing or ambiguous authorization');
|
|
147
|
-
this.db.prepare("UPDATE buyer_purchases SET budget_state='none' WHERE id=? AND budget_state='reserved'").run(id);
|
|
148
|
-
return this.update(id, { state: 'failed', paymentState: 'rejected', executionState: 'not_started', errorCode });
|
|
149
|
-
});
|
|
150
|
-
}
|
|
151
|
-
rejectExpiredAuthorization(id, caller) {
|
|
152
|
-
return this.transaction(() => {
|
|
153
|
-
const record = this.get(id, caller);
|
|
154
|
-
if (!record || record.protocol === 'mpp' || record.protocol === 'free' || !record.credentialRef || !record.nonce)
|
|
155
|
-
throw new CommerceError('authorization_recovery_required', 'Original x402 authorization is required');
|
|
156
|
-
if (record.paymentState === 'confirmed')
|
|
157
|
-
return record;
|
|
158
|
-
this.db.prepare("UPDATE buyer_purchases SET budget_state='none' WHERE id=? AND budget_state='reserved'").run(id);
|
|
159
|
-
return this.update(id, { state: 'failed', paymentState: 'rejected', errorCode: 'authorization_expired_unused', rejectionEvidence: { kind: 'finalized_expired_unused', checkedAt: new Date().toISOString(), nonce: record.nonce } });
|
|
160
|
-
});
|
|
161
|
-
}
|
|
162
|
-
confirmed(id, receipt) {
|
|
163
|
-
return this.transaction(() => {
|
|
164
|
-
this.db.prepare("UPDATE buyer_purchases SET budget_state='spent' WHERE id=? AND budget_state IN ('reserved','spent')").run(id);
|
|
165
|
-
return this.update(id, { paymentState: 'confirmed', receipt });
|
|
166
|
-
});
|
|
167
|
-
}
|
|
168
|
-
}
|
package/dist/commerce/buyer.d.ts
DELETED
|
@@ -1,132 +0,0 @@
|
|
|
1
|
-
import type { ClientEvmSigner } from '@x402/evm';
|
|
2
|
-
import type { PaymentPayload, PaymentRequirements, SettleResponse } from '@x402/core/types';
|
|
3
|
-
import { type BuyerPolicy, type Input, type StandardA2APeer } from './types.js';
|
|
4
|
-
import { CredentialVault } from './vault.js';
|
|
5
|
-
import { BuyerStore, type BuyerQuote } from './buyer-store.js';
|
|
6
|
-
import { type MppBuyerOptions, type MppReceipt } from './mpp-client.js';
|
|
7
|
-
export interface BuyerPreviewInput {
|
|
8
|
-
cardUrl: string;
|
|
9
|
-
messageId: string;
|
|
10
|
-
input: Input;
|
|
11
|
-
offerId: string;
|
|
12
|
-
}
|
|
13
|
-
export interface BuyerConfirmInput {
|
|
14
|
-
previewId: string;
|
|
15
|
-
quoteToken: string;
|
|
16
|
-
messageId: string;
|
|
17
|
-
}
|
|
18
|
-
export interface CommerceBuyerOptions {
|
|
19
|
-
policy: BuyerPolicy;
|
|
20
|
-
store: BuyerStore;
|
|
21
|
-
vault?: CredentialVault;
|
|
22
|
-
signer?: ClientEvmSigner;
|
|
23
|
-
mpp?: MppBuyerOptions;
|
|
24
|
-
peerTokens: Readonly<Record<string, string>>;
|
|
25
|
-
/** Override sibling /a2a only when the local wallet owner explicitly allows it. */
|
|
26
|
-
peerEndpoints?: Readonly<Record<string, string>>;
|
|
27
|
-
verifyReceipt?: (receipt: SettleResponse, payload: PaymentPayload, requirements: PaymentRequirements) => Promise<boolean>;
|
|
28
|
-
checkpoint?: (requirements: PaymentRequirements) => Promise<string>;
|
|
29
|
-
proveExpiredUnused?: (payload: PaymentPayload, requirements: PaymentRequirements) => Promise<boolean>;
|
|
30
|
-
findOriginalReceipt?: (payload: PaymentPayload, requirements: PaymentRequirements, fromBlock: string) => Promise<SettleResponse | undefined>;
|
|
31
|
-
fetchImpl?: typeof fetch;
|
|
32
|
-
}
|
|
33
|
-
export interface BuyerSnapshot {
|
|
34
|
-
id: string;
|
|
35
|
-
quoteToken: string;
|
|
36
|
-
messageId: string;
|
|
37
|
-
payer: string;
|
|
38
|
-
state: string;
|
|
39
|
-
quote: BuyerQuote;
|
|
40
|
-
paymentState: string;
|
|
41
|
-
executionState: string;
|
|
42
|
-
task?: Record<string, unknown>;
|
|
43
|
-
result?: unknown;
|
|
44
|
-
errorCode?: string;
|
|
45
|
-
continuation?: {
|
|
46
|
-
messageId: string;
|
|
47
|
-
state: 'pending' | 'unknown' | 'resolved';
|
|
48
|
-
input: Input;
|
|
49
|
-
};
|
|
50
|
-
receipt?: SettleResponse | MppReceipt;
|
|
51
|
-
nonce?: string;
|
|
52
|
-
createdAt: string;
|
|
53
|
-
updatedAt: string;
|
|
54
|
-
}
|
|
55
|
-
/** Independent x402 wallet; payment and execution state are deliberately separate. */
|
|
56
|
-
export declare class CommerceBuyer {
|
|
57
|
-
private readonly options;
|
|
58
|
-
readonly policy: BuyerPolicy;
|
|
59
|
-
private readonly fetchImpl;
|
|
60
|
-
private readonly active;
|
|
61
|
-
private timer?;
|
|
62
|
-
private readonly previews;
|
|
63
|
-
constructor(options: CommerceBuyerOptions);
|
|
64
|
-
/** Background observation only; never signs, replays credentials or changes reservations. */
|
|
65
|
-
start(intervalMs?: number): void;
|
|
66
|
-
stop(): Promise<void>;
|
|
67
|
-
policySnapshot(): {
|
|
68
|
-
capabilities: ("free" | "x402" | "mpp")[];
|
|
69
|
-
state: string;
|
|
70
|
-
payer: string | undefined;
|
|
71
|
-
budgets: {
|
|
72
|
-
reserved: string;
|
|
73
|
-
spent: string;
|
|
74
|
-
currency: string;
|
|
75
|
-
maxTotal: string;
|
|
76
|
-
period: "cumulative";
|
|
77
|
-
}[];
|
|
78
|
-
policyVersion: 1;
|
|
79
|
-
paymentsEnabled: boolean;
|
|
80
|
-
approval: "per_purchase" | "within_preapproved_limits";
|
|
81
|
-
peers: (StandardA2APeer | {
|
|
82
|
-
id: string;
|
|
83
|
-
cardUrl: string;
|
|
84
|
-
mode?: "envarpay";
|
|
85
|
-
protocol: "x402" | "mpp";
|
|
86
|
-
currency: string;
|
|
87
|
-
recipient: string;
|
|
88
|
-
maxPerPurchase: string;
|
|
89
|
-
} | {
|
|
90
|
-
id: string;
|
|
91
|
-
cardUrl: string;
|
|
92
|
-
mode?: "envarpay";
|
|
93
|
-
protocol: "free";
|
|
94
|
-
currency: null;
|
|
95
|
-
recipient: null;
|
|
96
|
-
maxPerPurchase: "0";
|
|
97
|
-
})[];
|
|
98
|
-
};
|
|
99
|
-
private peer;
|
|
100
|
-
private endpoint;
|
|
101
|
-
private scopedFetch;
|
|
102
|
-
private record;
|
|
103
|
-
private snapshot;
|
|
104
|
-
get(caller: string, id: string): BuyerSnapshot;
|
|
105
|
-
preview(caller: string, input: BuyerPreviewInput): Promise<BuyerSnapshot>;
|
|
106
|
-
private prepare;
|
|
107
|
-
private prepareStandard;
|
|
108
|
-
private prepareFree;
|
|
109
|
-
private replayFree;
|
|
110
|
-
private prepareMpp;
|
|
111
|
-
private mppCredential;
|
|
112
|
-
private purchaseMpp;
|
|
113
|
-
private savedMpp;
|
|
114
|
-
private verifyMpp;
|
|
115
|
-
private replayMpp;
|
|
116
|
-
private recoverMpp;
|
|
117
|
-
private assertCurrentPolicy;
|
|
118
|
-
confirm(caller: string, input: BuyerConfirmInput): Promise<BuyerSnapshot>;
|
|
119
|
-
private exclusive;
|
|
120
|
-
private purchase;
|
|
121
|
-
private saved;
|
|
122
|
-
private verify;
|
|
123
|
-
private task;
|
|
124
|
-
private replay;
|
|
125
|
-
private client;
|
|
126
|
-
private observe;
|
|
127
|
-
continue(caller: string, id: string, input: {
|
|
128
|
-
messageId: string;
|
|
129
|
-
input: Input;
|
|
130
|
-
}): Promise<BuyerSnapshot>;
|
|
131
|
-
recover(caller: string, id: string): Promise<BuyerSnapshot>;
|
|
132
|
-
}
|