@vinaystwt/xmpp-wallet 0.1.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
package/LICENSE ADDED
@@ -0,0 +1,21 @@
1
+ MIT License
2
+
3
+ Copyright (c) 2026 xMPP contributors
4
+
5
+ Permission is hereby granted, free of charge, to any person obtaining a copy
6
+ of this software and associated documentation files (the "Software"), to deal
7
+ in the Software without restriction, including without limitation the rights
8
+ to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
9
+ copies of the Software, and to permit persons to whom the Software is
10
+ furnished to do so, subject to the following conditions:
11
+
12
+ The above copyright notice and this permission notice shall be included in all
13
+ copies or substantial portions of the Software.
14
+
15
+ THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
16
+ IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
17
+ FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
18
+ AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
19
+ LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
20
+ OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
21
+ SOFTWARE.
@@ -0,0 +1,49 @@
1
+ export declare const config: {
2
+ readonly nodeEnv: string;
3
+ readonly network: string;
4
+ readonly paymentExecutionMode: "mock" | "testnet";
5
+ readonly rpcUrl: string;
6
+ readonly networkPassphrase: string;
7
+ readonly gatewayPort: number;
8
+ readonly dashboardPort: number;
9
+ readonly dashboardGatewayUrl: string | undefined;
10
+ readonly dailyBudgetUsd: number;
11
+ readonly facilitatorUrl: string;
12
+ readonly wallet: {
13
+ readonly agentSecretKey: string | undefined;
14
+ readonly smartAccountContractId: string | undefined;
15
+ readonly smartAccountWasmHash: string;
16
+ readonly webauthnVerifierAddress: string;
17
+ readonly ed25519VerifierAddress: string;
18
+ readonly spendingLimitPolicyAddress: string;
19
+ readonly thresholdPolicyAddress: string;
20
+ };
21
+ readonly x402: {
22
+ readonly facilitatorUrl: string;
23
+ readonly facilitatorApiKey: string | undefined;
24
+ readonly maxTransactionFeeStroops: number;
25
+ readonly facilitatorPrivateKey: string | undefined;
26
+ readonly recipientAddress: string | undefined;
27
+ };
28
+ readonly mpp: {
29
+ readonly secretKey: string | undefined;
30
+ readonly recipientAddress: string | undefined;
31
+ readonly channelContractId: string | undefined;
32
+ readonly feeSponsorshipEnabled: boolean;
33
+ readonly feeSponsorship: {
34
+ readonly chargeEnabled: boolean;
35
+ readonly sessionEnabled: boolean;
36
+ };
37
+ readonly feeSponsorSecretKey: string | undefined;
38
+ readonly feeBumpSecretKey: string | undefined;
39
+ };
40
+ readonly services: {
41
+ readonly research: string;
42
+ readonly market: string;
43
+ readonly stream: string;
44
+ };
45
+ readonly contracts: {
46
+ readonly policyContractId: string | undefined;
47
+ readonly sessionRegistryContractId: string | undefined;
48
+ };
49
+ };
@@ -0,0 +1,117 @@
1
+ import { config as loadEnv } from 'dotenv';
2
+ import { dirname, resolve } from 'node:path';
3
+ import { fileURLToPath } from 'node:url';
4
+ import { z } from 'zod';
5
+ const moduleDir = dirname(fileURLToPath(import.meta.url));
6
+ const repoRoot = resolve(moduleDir, '../../../');
7
+ loadEnv({ path: resolve(repoRoot, '.env') });
8
+ loadEnv({ path: resolve(repoRoot, '.env.local'), override: true });
9
+ loadEnv();
10
+ const envSchema = z.object({
11
+ NODE_ENV: z.string().default('development'),
12
+ XMPP_NETWORK: z.string().default('stellar:testnet'),
13
+ XMPP_PAYMENT_EXECUTION_MODE: z.enum(['mock', 'testnet']).default('mock'),
14
+ XMPP_RPC_URL: z.string().default('https://soroban-testnet.stellar.org'),
15
+ XMPP_NETWORK_PASSPHRASE: z.string().default('Test SDF Network ; September 2015'),
16
+ XMPP_GATEWAY_PORT: z.coerce.number().default(4300),
17
+ XMPP_DASHBOARD_PORT: z.coerce.number().default(4310),
18
+ XMPP_DASHBOARD_GATEWAY_URL: z.string().optional(),
19
+ XMPP_DAILY_BUDGET_USD: z.coerce.number().default(0.5),
20
+ XMPP_AGENT_SECRET_KEY: z.string().optional(),
21
+ XMPP_SMART_ACCOUNT_CONTRACT_ID: z.string().optional(),
22
+ XMPP_SMART_ACCOUNT_WASM_HASH: z
23
+ .string()
24
+ .default('3e51f5b222dec74650f0b33367acb42a41ce497f72639230463070e666abba2c'),
25
+ XMPP_WEBAUTHN_VERIFIER_ADDRESS: z
26
+ .string()
27
+ .default('CATPTBRWVMH5ZCIKO5HN2F4FMPXVZEXC56RKGHRXCM7EEZGGXK7PICEH'),
28
+ XMPP_ED25519_VERIFIER_ADDRESS: z
29
+ .string()
30
+ .default('CAIKK32K3BZJYTWVTXHZFPIEEDBR6YCVTGPABH4UQUQ4XFA3OLYXG27G'),
31
+ XMPP_SPENDING_LIMIT_POLICY_ADDRESS: z
32
+ .string()
33
+ .default('CBYLPYZGLQ6JVY2IQ5P23QLQPR3KAMMKMZLNWG6RUUKJDNYGPLVHK7U4'),
34
+ XMPP_THRESHOLD_POLICY_ADDRESS: z
35
+ .string()
36
+ .default('CDDQLFG7CV74QHWPSP6NZIPNBR2PPCMTUVYCJF4P3ONDYHODRFGR7LWC'),
37
+ X402_FACILITATOR_URL: z.string().default('http://localhost:4022'),
38
+ X402_FACILITATOR_API_KEY: z.string().optional(),
39
+ X402_MAX_TRANSACTION_FEE_STROOPS: z.coerce.number().default(2000000),
40
+ X402_RECIPIENT_ADDRESS: z.string().optional(),
41
+ FACILITATOR_STELLAR_PRIVATE_KEY: z.string().optional(),
42
+ MPP_SECRET_KEY: z.string().optional(),
43
+ MPP_RECIPIENT_ADDRESS: z.string().optional(),
44
+ MPP_CHANNEL_CONTRACT_ID: z.string().optional(),
45
+ XMPP_ENABLE_MPP_FEE_SPONSORSHIP: z
46
+ .enum(['true', 'false'])
47
+ .default('false')
48
+ .transform((value) => value === 'true'),
49
+ XMPP_ENABLE_MPP_CHARGE_FEE_SPONSORSHIP: z
50
+ .enum(['true', 'false'])
51
+ .optional()
52
+ .transform((value) => value === 'true'),
53
+ XMPP_ENABLE_MPP_SESSION_FEE_SPONSORSHIP: z
54
+ .enum(['true', 'false'])
55
+ .optional()
56
+ .transform((value) => value === 'true'),
57
+ XMPP_MPP_FEE_SPONSOR_SECRET_KEY: z.string().optional(),
58
+ XMPP_MPP_FEE_BUMP_SECRET_KEY: z.string().optional(),
59
+ XMPP_RESEARCH_API_URL: z.string().default('http://localhost:4101'),
60
+ XMPP_MARKET_API_URL: z.string().default('http://localhost:4102'),
61
+ XMPP_STREAM_API_URL: z.string().default('http://localhost:4103'),
62
+ XMPP_POLICY_CONTRACT_ID: z.string().optional(),
63
+ XMPP_SESSION_REGISTRY_CONTRACT_ID: z.string().optional(),
64
+ });
65
+ const env = envSchema.parse(process.env);
66
+ const mppFeeSponsorshipEnabled = env.XMPP_ENABLE_MPP_FEE_SPONSORSHIP;
67
+ const mppChargeFeeSponsorshipEnabled = env.XMPP_ENABLE_MPP_CHARGE_FEE_SPONSORSHIP ?? mppFeeSponsorshipEnabled;
68
+ const mppSessionFeeSponsorshipEnabled = env.XMPP_ENABLE_MPP_SESSION_FEE_SPONSORSHIP ?? mppFeeSponsorshipEnabled;
69
+ export const config = {
70
+ nodeEnv: env.NODE_ENV,
71
+ network: env.XMPP_NETWORK,
72
+ paymentExecutionMode: env.XMPP_PAYMENT_EXECUTION_MODE,
73
+ rpcUrl: env.XMPP_RPC_URL,
74
+ networkPassphrase: env.XMPP_NETWORK_PASSPHRASE,
75
+ gatewayPort: env.XMPP_GATEWAY_PORT,
76
+ dashboardPort: env.XMPP_DASHBOARD_PORT,
77
+ dashboardGatewayUrl: env.XMPP_DASHBOARD_GATEWAY_URL,
78
+ dailyBudgetUsd: env.XMPP_DAILY_BUDGET_USD,
79
+ facilitatorUrl: env.X402_FACILITATOR_URL,
80
+ wallet: {
81
+ agentSecretKey: env.XMPP_AGENT_SECRET_KEY,
82
+ smartAccountContractId: env.XMPP_SMART_ACCOUNT_CONTRACT_ID,
83
+ smartAccountWasmHash: env.XMPP_SMART_ACCOUNT_WASM_HASH,
84
+ webauthnVerifierAddress: env.XMPP_WEBAUTHN_VERIFIER_ADDRESS,
85
+ ed25519VerifierAddress: env.XMPP_ED25519_VERIFIER_ADDRESS,
86
+ spendingLimitPolicyAddress: env.XMPP_SPENDING_LIMIT_POLICY_ADDRESS,
87
+ thresholdPolicyAddress: env.XMPP_THRESHOLD_POLICY_ADDRESS,
88
+ },
89
+ x402: {
90
+ facilitatorUrl: env.X402_FACILITATOR_URL,
91
+ facilitatorApiKey: env.X402_FACILITATOR_API_KEY,
92
+ maxTransactionFeeStroops: env.X402_MAX_TRANSACTION_FEE_STROOPS,
93
+ facilitatorPrivateKey: env.FACILITATOR_STELLAR_PRIVATE_KEY,
94
+ recipientAddress: env.X402_RECIPIENT_ADDRESS,
95
+ },
96
+ mpp: {
97
+ secretKey: env.MPP_SECRET_KEY,
98
+ recipientAddress: env.MPP_RECIPIENT_ADDRESS,
99
+ channelContractId: env.MPP_CHANNEL_CONTRACT_ID,
100
+ feeSponsorshipEnabled: mppFeeSponsorshipEnabled,
101
+ feeSponsorship: {
102
+ chargeEnabled: mppChargeFeeSponsorshipEnabled,
103
+ sessionEnabled: mppSessionFeeSponsorshipEnabled,
104
+ },
105
+ feeSponsorSecretKey: env.XMPP_MPP_FEE_SPONSOR_SECRET_KEY,
106
+ feeBumpSecretKey: env.XMPP_MPP_FEE_BUMP_SECRET_KEY,
107
+ },
108
+ services: {
109
+ research: env.XMPP_RESEARCH_API_URL,
110
+ market: env.XMPP_MARKET_API_URL,
111
+ stream: env.XMPP_STREAM_API_URL,
112
+ },
113
+ contracts: {
114
+ policyContractId: env.XMPP_POLICY_CONTRACT_ID,
115
+ sessionRegistryContractId: env.XMPP_SESSION_REGISTRY_CONTRACT_ID,
116
+ },
117
+ };
@@ -0,0 +1,343 @@
1
+ export type RouteKind = 'x402' | 'mpp-charge' | 'mpp-session-open' | 'mpp-session-reuse';
2
+ export type PaymentExecutionMode = 'mock' | 'testnet';
3
+ export type PaymentExecutionStatus = 'mock-paid' | 'ready-for-testnet' | 'settled-testnet' | 'missing-config';
4
+ export type RouteContext = {
5
+ url: string;
6
+ method: string;
7
+ serviceId?: string;
8
+ projectedRequests?: number;
9
+ streaming?: boolean;
10
+ };
11
+ export type ServiceCatalogEntry = {
12
+ serviceId: string;
13
+ displayName: string;
14
+ description: string;
15
+ baseUrl: string;
16
+ source?: 'static' | 'discovered' | 'hybrid' | 'fallback';
17
+ capabilities: {
18
+ x402: boolean;
19
+ mppCharge: boolean;
20
+ mppSession: boolean;
21
+ };
22
+ pricing: {
23
+ x402PerCallUsd: number;
24
+ mppChargePerCallUsd: number;
25
+ mppSessionOpenUsd: number;
26
+ mppSessionPerCallUsd: number;
27
+ };
28
+ routingHints: {
29
+ breakEvenCalls: number;
30
+ streamingPreferred: boolean;
31
+ preferredSingleCall: Extract<RouteKind, 'x402' | 'mpp-charge'>;
32
+ };
33
+ };
34
+ export type RouteScoreBreakdown = {
35
+ route: RouteKind;
36
+ supported: boolean;
37
+ estimatedTotalUsd: number;
38
+ savingsVsNaiveUsd: number;
39
+ totalScore: number;
40
+ reasons: string[];
41
+ };
42
+ export type RouteDecision = {
43
+ route: RouteKind;
44
+ reason: string;
45
+ score: number;
46
+ projectedRequests?: number;
47
+ estimatedTotalUsd?: number;
48
+ savingsVsNaiveUsd?: number;
49
+ service?: ServiceCatalogEntry;
50
+ breakdown?: RouteScoreBreakdown[];
51
+ };
52
+ export type ChallengeKind = 'x402' | 'mpp-charge' | 'mpp-session';
53
+ export type PaymentChallenge = {
54
+ kind: ChallengeKind;
55
+ service: string;
56
+ amountUsd: number;
57
+ asset: 'USDC_TESTNET';
58
+ retryHeaderName: string;
59
+ retryHeaderValue: string;
60
+ sessionId?: string;
61
+ };
62
+ export type XmppFetchOptions = Omit<RouteContext, 'url' | 'method'> & {
63
+ agentId?: string;
64
+ maxAutoPayUsd?: number;
65
+ idempotencyKey?: string;
66
+ };
67
+ export type XmppAgentProfile = {
68
+ agentId: string;
69
+ displayName: string;
70
+ role: 'shared' | 'research' | 'market';
71
+ description: string;
72
+ dailyBudgetUsd: number;
73
+ allowedServices: string[];
74
+ preferredRoutes: RouteKind[];
75
+ autopayMethods: string[];
76
+ enabled?: boolean;
77
+ policySource?: 'local' | 'contract' | 'fallback' | 'merged';
78
+ };
79
+ export type XmppSignedReceipt = {
80
+ receiptId: string;
81
+ issuedAt: string;
82
+ network: string;
83
+ agent: string;
84
+ serviceId: string;
85
+ url: string;
86
+ method: string;
87
+ route: RouteKind;
88
+ amountUsd: number;
89
+ txHash?: string;
90
+ explorerUrl?: string;
91
+ paymentReference?: string;
92
+ signature: string;
93
+ };
94
+ export type XmppSmartAccountExecution = {
95
+ configured: boolean;
96
+ preferred: boolean;
97
+ supported: boolean;
98
+ used: boolean;
99
+ contractId?: string | null;
100
+ fallbackReason?: string;
101
+ };
102
+ export type PaymentExecutionMetadata = {
103
+ mode: PaymentExecutionMode;
104
+ status: PaymentExecutionStatus;
105
+ route: RouteKind;
106
+ receiptId: string;
107
+ missingConfig?: string[];
108
+ evidenceHeaders?: Record<string, string>;
109
+ signedReceipt?: XmppSignedReceipt;
110
+ settlementStrategy?: 'keypair' | 'smart-account' | 'keypair-fallback';
111
+ executionNote?: string;
112
+ feeSponsored?: boolean;
113
+ feeSponsorPublicKey?: string;
114
+ feeBumpPublicKey?: string;
115
+ smartAccount?: XmppSmartAccountExecution;
116
+ };
117
+ export type PolicyDecision = {
118
+ allowed: boolean;
119
+ reason: string;
120
+ code: 'allowed' | 'blocked-domain' | 'blocked-path' | 'blocked-method' | 'blocked-service' | 'blocked-agent' | 'blocked-budget' | 'blocked-idempotency' | 'paused';
121
+ source?: 'local' | 'contract' | 'fallback';
122
+ };
123
+ export type PaymentExecutionResult = {
124
+ response: Response;
125
+ metadata: PaymentExecutionMetadata;
126
+ };
127
+ export type XmppFetchMetadata = {
128
+ route: RouteKind;
129
+ challenge?: PaymentChallenge;
130
+ retried: boolean;
131
+ execution?: PaymentExecutionMetadata;
132
+ policy?: PolicyDecision;
133
+ budget?: XmppBudgetSnapshot;
134
+ idempotentReplay?: boolean;
135
+ };
136
+ export type XmppSessionRecord = {
137
+ sessionId: string;
138
+ serviceId: string;
139
+ agent: string;
140
+ channelContractId: string;
141
+ route: string;
142
+ status: string;
143
+ totalAmountUsdCents: number;
144
+ callCount: number;
145
+ lastReceiptId: string;
146
+ updatedAtLedger: number;
147
+ };
148
+ export type WorkflowEstimateStep = {
149
+ url: string;
150
+ method?: string;
151
+ serviceId?: string;
152
+ projectedRequests: number;
153
+ streaming?: boolean;
154
+ };
155
+ export type WorkflowEstimateLineItem = {
156
+ serviceId: string;
157
+ displayName: string;
158
+ route: RouteKind;
159
+ projectedRequests: number;
160
+ estimatedCostUsd: number;
161
+ savingsVsNaiveUsd: number;
162
+ reason: string;
163
+ };
164
+ export type WorkflowEstimateResult = {
165
+ totalEstimatedCostUsd: number;
166
+ naiveX402CostUsd: number;
167
+ savingsVsNaiveUsd: number;
168
+ breakdown: WorkflowEstimateLineItem[];
169
+ };
170
+ export type XmppBudgetSnapshot = {
171
+ agentId: string;
172
+ agentDisplayName: string;
173
+ agentSpentThisSessionUsd: number;
174
+ agentRemainingDailyBudgetUsd: number;
175
+ spentThisSessionUsd: number;
176
+ remainingDailyBudgetUsd: number;
177
+ callsThisService: number;
178
+ projectedCostIfRepeated5xUsd: number;
179
+ recommendation: string;
180
+ };
181
+ export type XmppRouteEvent = {
182
+ id: string;
183
+ timestamp: string;
184
+ agentId: string;
185
+ url: string;
186
+ method: string;
187
+ serviceId: string;
188
+ route: RouteKind;
189
+ status: 'settled' | 'denied' | 'preview';
190
+ amountUsd: number;
191
+ projectedRequests: number;
192
+ policyCode?: PolicyDecision['code'];
193
+ receiptId?: string;
194
+ txHash?: string;
195
+ explorerUrl?: string;
196
+ sessionId?: string;
197
+ signedReceipt?: XmppSignedReceipt;
198
+ feeSponsored?: boolean;
199
+ feeSponsorPublicKey?: string;
200
+ settlementStrategy?: PaymentExecutionMetadata['settlementStrategy'];
201
+ executionNote?: string;
202
+ };
203
+ export type XmppAgentStateSummary = {
204
+ agentId: string;
205
+ displayName: string;
206
+ role: XmppAgentProfile['role'];
207
+ description: string;
208
+ dailyBudgetUsd: number;
209
+ spentThisSessionUsd: number;
210
+ remainingDailyBudgetUsd: number;
211
+ routeCounts: Record<RouteKind, number>;
212
+ allowedServices: string[];
213
+ preferredRoutes: RouteKind[];
214
+ enabled?: boolean;
215
+ policySource?: XmppAgentProfile['policySource'];
216
+ autopayMethods?: string[];
217
+ };
218
+ export type XmppAgentPolicySnapshot = {
219
+ agentId: string;
220
+ enabled: boolean;
221
+ dailyBudgetUsd: number;
222
+ allowedServices: string[];
223
+ preferredRoutes: RouteKind[];
224
+ autopayMethods: string[];
225
+ source: 'contract' | 'local' | 'fallback';
226
+ };
227
+ export type XmppContractTreasurySnapshot = {
228
+ sharedTreasuryUsd: number;
229
+ totalSpentUsd: number;
230
+ remainingUsd: number;
231
+ paymentCount: number;
232
+ source: 'contract' | 'local' | 'fallback';
233
+ };
234
+ export type XmppContractAgentTreasuryState = {
235
+ agentId: string;
236
+ spentUsd: number;
237
+ paymentCount: number;
238
+ lastServiceId: string;
239
+ lastRoute: string;
240
+ source: 'contract' | 'local' | 'fallback';
241
+ };
242
+ export type XmppOperatorState = {
243
+ sharedTreasuryUsd: number;
244
+ sharedTreasuryRemainingUsd: number;
245
+ dailyBudgetUsd: number;
246
+ spentThisSessionUsd: number;
247
+ remainingDailyBudgetUsd: number;
248
+ sessionSavingsUsd: number;
249
+ routeCounts: Record<RouteKind, number>;
250
+ serviceSpendUsd: Record<string, number>;
251
+ serviceCallCounts: Record<string, number>;
252
+ agentProfiles: XmppAgentProfile[];
253
+ agentStates: XmppAgentStateSummary[];
254
+ contractAgentPolicies?: XmppAgentPolicySnapshot[];
255
+ contractTreasury?: XmppContractTreasurySnapshot | null;
256
+ contractAgentTreasuryStates?: XmppContractAgentTreasuryState[];
257
+ openSessions: Array<Pick<XmppSessionRecord, 'sessionId' | 'serviceId' | 'callCount'>>;
258
+ recentEvents: XmppRouteEvent[];
259
+ };
260
+ export type XmppReceiptVerificationResult = {
261
+ valid: boolean;
262
+ agent: string;
263
+ receiptId: string;
264
+ };
265
+ export type XmppWalletInfo = {
266
+ connected: boolean;
267
+ paymentExecutionMode: PaymentExecutionMode;
268
+ network: string;
269
+ rpcUrl: string;
270
+ agentPublicKey: string | null;
271
+ settlementStrategy: 'smart-account-ready' | 'smart-account-x402-preferred' | 'smart-account-partial-fallback' | 'keypair-live';
272
+ smartAccount: {
273
+ ready: boolean;
274
+ mode: 'inactive' | 'x402-only' | 'full';
275
+ routeCoverage: 'inactive' | 'x402-only';
276
+ demoReady: boolean;
277
+ guardedFallback: boolean;
278
+ contractId: string | null;
279
+ wasmHash: string;
280
+ webauthnVerifierAddress: string;
281
+ ed25519VerifierAddress: string;
282
+ spendingLimitPolicyAddress: string;
283
+ thresholdPolicyAddress: string;
284
+ preferredRoutes: RouteKind[];
285
+ fallbackRoutes: RouteKind[];
286
+ supportedRoutes: RouteKind[];
287
+ unsupportedRoutes: RouteKind[];
288
+ unsupportedReason: string | null;
289
+ configuredMaxTransactionFeeStroops: number;
290
+ effectiveMaxTransactionFeeStroops: number;
291
+ feeFloorApplied: boolean;
292
+ preflightFailures: string[];
293
+ coverageMessage: string;
294
+ message: string;
295
+ operatorNotes: string[];
296
+ };
297
+ feeSponsorship: {
298
+ enabled: boolean;
299
+ available: boolean;
300
+ mppChargeEnabled: boolean;
301
+ mppSessionEnabled: boolean;
302
+ sponsorPublicKey: string | null;
303
+ feeBumpPublicKey: string | null;
304
+ message: string;
305
+ };
306
+ missingSecrets: string[];
307
+ message: string;
308
+ };
309
+ export type XmppHealthStatus = {
310
+ ok: boolean;
311
+ service: string;
312
+ network: string;
313
+ paymentExecutionMode: PaymentExecutionMode;
314
+ services: Record<string, string>;
315
+ smartAccount: {
316
+ configured: boolean;
317
+ routeCoverage: 'inactive' | 'x402-only';
318
+ x402Preferred: boolean;
319
+ mppFallback: boolean;
320
+ demoReady: boolean;
321
+ guardedFallback: boolean;
322
+ unsupportedRoutes: RouteKind[];
323
+ unsupportedReason: string | null;
324
+ configuredMaxTransactionFeeStroops: number;
325
+ effectiveMaxTransactionFeeStroops: number;
326
+ feeFloorApplied: boolean;
327
+ preflightFailures: string[];
328
+ };
329
+ };
330
+ export type XmppCatalogResponse = {
331
+ services: ServiceCatalogEntry[];
332
+ };
333
+ export type XmppPolicyPreviewResponse = {
334
+ policy: PolicyDecision;
335
+ routePreview: RouteDecision;
336
+ };
337
+ export type XmppGatewayFetchResponse = {
338
+ status: number;
339
+ routePreview: RouteDecision;
340
+ payment?: XmppFetchMetadata;
341
+ responseHeaders: Record<string, string>;
342
+ body: unknown;
343
+ };
@@ -0,0 +1 @@
1
+ export {};
@@ -0,0 +1,14 @@
1
+ import type { PaymentExecutionMetadata, RouteKind, XmppReceiptVerificationResult, XmppSignedReceipt, XmppSmartAccountExecution, XmppWalletInfo } from '@xmpp/types';
2
+ export declare const SMART_ACCOUNT_MIN_TRANSACTION_FEE_STROOPS = 2000000;
3
+ export declare function signXmppReceipt(input: Omit<XmppSignedReceipt, 'signature' | 'agent' | 'issuedAt' | 'network'> & {
4
+ issuedAt?: string;
5
+ network?: string;
6
+ }): XmppSignedReceipt | null;
7
+ export declare function verifyXmppReceipt(receipt: XmppSignedReceipt): XmppReceiptVerificationResult;
8
+ export declare function getEffectiveSmartAccountFeeCeiling(): number;
9
+ export declare function getRouteExecutionPlan(route: RouteKind): {
10
+ settlementStrategy: PaymentExecutionMetadata['settlementStrategy'];
11
+ executionNote: string;
12
+ smartAccount: XmppSmartAccountExecution;
13
+ };
14
+ export declare function getWalletInfo(): Promise<XmppWalletInfo>;
@@ -0,0 +1,250 @@
1
+ import { config } from '@xmpp/config';
2
+ import { Keypair } from '@stellar/stellar-sdk';
3
+ export const SMART_ACCOUNT_MIN_TRANSACTION_FEE_STROOPS = 2_000_000;
4
+ function stableReceiptPayload(receipt) {
5
+ return JSON.stringify({
6
+ receiptId: receipt.receiptId,
7
+ issuedAt: receipt.issuedAt,
8
+ network: receipt.network,
9
+ agent: receipt.agent,
10
+ serviceId: receipt.serviceId,
11
+ url: receipt.url,
12
+ method: receipt.method,
13
+ route: receipt.route,
14
+ amountUsd: receipt.amountUsd,
15
+ txHash: receipt.txHash ?? null,
16
+ explorerUrl: receipt.explorerUrl ?? null,
17
+ paymentReference: receipt.paymentReference ?? null,
18
+ });
19
+ }
20
+ export function signXmppReceipt(input) {
21
+ if (!config.wallet.agentSecretKey) {
22
+ return null;
23
+ }
24
+ const keypair = Keypair.fromSecret(config.wallet.agentSecretKey);
25
+ const receipt = {
26
+ ...input,
27
+ issuedAt: input.issuedAt ?? new Date().toISOString(),
28
+ network: input.network ?? config.network,
29
+ agent: keypair.publicKey(),
30
+ };
31
+ const payload = Buffer.from(stableReceiptPayload(receipt), 'utf8');
32
+ const signature = keypair.sign(payload).toString('base64');
33
+ return {
34
+ ...receipt,
35
+ signature,
36
+ };
37
+ }
38
+ export function verifyXmppReceipt(receipt) {
39
+ const keypair = Keypair.fromPublicKey(receipt.agent);
40
+ const payload = Buffer.from(stableReceiptPayload({
41
+ receiptId: receipt.receiptId,
42
+ issuedAt: receipt.issuedAt,
43
+ network: receipt.network,
44
+ agent: receipt.agent,
45
+ serviceId: receipt.serviceId,
46
+ url: receipt.url,
47
+ method: receipt.method,
48
+ route: receipt.route,
49
+ amountUsd: receipt.amountUsd,
50
+ txHash: receipt.txHash,
51
+ explorerUrl: receipt.explorerUrl,
52
+ paymentReference: receipt.paymentReference,
53
+ }), 'utf8');
54
+ return {
55
+ valid: keypair.verify(payload, Buffer.from(receipt.signature, 'base64')),
56
+ agent: receipt.agent,
57
+ receiptId: receipt.receiptId,
58
+ };
59
+ }
60
+ function smartAccountConfigured() {
61
+ return Boolean(config.wallet.smartAccountContractId);
62
+ }
63
+ export function getEffectiveSmartAccountFeeCeiling() {
64
+ if (!smartAccountConfigured()) {
65
+ return config.x402.maxTransactionFeeStroops;
66
+ }
67
+ return Math.max(config.x402.maxTransactionFeeStroops, SMART_ACCOUNT_MIN_TRANSACTION_FEE_STROOPS);
68
+ }
69
+ function smartAccountFeeFloorApplied() {
70
+ return smartAccountConfigured() &&
71
+ config.x402.maxTransactionFeeStroops < SMART_ACCOUNT_MIN_TRANSACTION_FEE_STROOPS;
72
+ }
73
+ function getSmartAccountPreflightFailures() {
74
+ if (!smartAccountConfigured()) {
75
+ return ['XMPP_SMART_ACCOUNT_CONTRACT_ID'];
76
+ }
77
+ return [
78
+ !config.wallet.agentSecretKey ? 'XMPP_AGENT_SECRET_KEY' : null,
79
+ !config.x402.facilitatorPrivateKey ? 'FACILITATOR_STELLAR_PRIVATE_KEY' : null,
80
+ ].filter((value) => value !== null);
81
+ }
82
+ function smartAccountPrimaryReady() {
83
+ return Boolean(config.wallet.smartAccountContractId &&
84
+ config.wallet.agentSecretKey &&
85
+ config.x402.facilitatorPrivateKey);
86
+ }
87
+ export function getRouteExecutionPlan(route) {
88
+ const configured = smartAccountConfigured();
89
+ const liveReady = smartAccountPrimaryReady();
90
+ if (route === 'x402') {
91
+ if (liveReady) {
92
+ return {
93
+ settlementStrategy: 'smart-account',
94
+ executionNote: 'x402 prefers the configured smart account, with an automatic fallback to keypair settlement if delegated auth becomes unavailable.',
95
+ smartAccount: {
96
+ configured: true,
97
+ preferred: true,
98
+ supported: true,
99
+ used: true,
100
+ contractId: config.wallet.smartAccountContractId ?? null,
101
+ },
102
+ };
103
+ }
104
+ return {
105
+ settlementStrategy: 'keypair',
106
+ executionNote: configured
107
+ ? 'Smart account is configured, but x402 is staying on the agent keypair until the smart-account demo preconditions are fully satisfied.'
108
+ : 'x402 is executing with the agent keypair.',
109
+ smartAccount: {
110
+ configured,
111
+ preferred: configured,
112
+ supported: true,
113
+ used: false,
114
+ contractId: config.wallet.smartAccountContractId ?? null,
115
+ fallbackReason: configured
116
+ ? 'Smart-account x402 is guarded until the delegated signer, facilitator, and fee-cap preconditions are all ready.'
117
+ : 'No smart account is configured.',
118
+ },
119
+ };
120
+ }
121
+ return {
122
+ settlementStrategy: configured ? 'keypair-fallback' : 'keypair',
123
+ executionNote: configured
124
+ ? 'MPP is using explicit keypair execution because the current MPP SDK requires Keypair signers.'
125
+ : 'MPP is executing with the configured keypair signer.',
126
+ smartAccount: {
127
+ configured,
128
+ preferred: configured,
129
+ supported: false,
130
+ used: false,
131
+ contractId: config.wallet.smartAccountContractId ?? null,
132
+ fallbackReason: configured
133
+ ? 'Current MPP client flows require Keypair signers.'
134
+ : 'No smart account is configured.',
135
+ },
136
+ };
137
+ }
138
+ export async function getWalletInfo() {
139
+ const agentKeypair = config.wallet.agentSecretKey
140
+ ? Keypair.fromSecret(config.wallet.agentSecretKey)
141
+ : null;
142
+ const feeSponsorSecret = config.mpp.feeSponsorSecretKey ??
143
+ ((config.mpp.feeSponsorship.chargeEnabled || config.mpp.feeSponsorship.sessionEnabled)
144
+ ? config.mpp.secretKey
145
+ : undefined);
146
+ const feeSponsorKeypair = feeSponsorSecret ? Keypair.fromSecret(feeSponsorSecret) : null;
147
+ const feeBumpKeypair = config.mpp.feeBumpSecretKey
148
+ ? Keypair.fromSecret(config.mpp.feeBumpSecretKey)
149
+ : null;
150
+ const smartAccountReady = smartAccountConfigured();
151
+ const smartAccountActive = smartAccountPrimaryReady();
152
+ const smartAccountPreflightFailures = getSmartAccountPreflightFailures();
153
+ const smartAccountEffectiveFeeCeiling = getEffectiveSmartAccountFeeCeiling();
154
+ const smartAccountFeeFloorWasApplied = smartAccountFeeFloorApplied();
155
+ const smartAccountMode = smartAccountActive
156
+ ? 'x402-only'
157
+ : smartAccountReady
158
+ ? 'x402-only'
159
+ : 'inactive';
160
+ const smartAccountRouteCoverage = smartAccountReady
161
+ ? 'x402-only'
162
+ : 'inactive';
163
+ const smartAccountOperatorNotes = smartAccountActive
164
+ ? [
165
+ 'Smart-account execution is enabled for x402 only.',
166
+ 'If delegated x402 settlement becomes unavailable, xMPP falls back to the stable keypair path instead of surfacing a smart-account-specific failure.',
167
+ 'MPP charge and session flows still use keypair execution because the current MPP client requires Keypair signers.',
168
+ smartAccountFeeFloorWasApplied
169
+ ? `The facilitator is enforcing a safe x402 fee ceiling of at least ${SMART_ACCOUNT_MIN_TRANSACTION_FEE_STROOPS.toLocaleString()} stroops for smart-account execution.`
170
+ : `The facilitator fee ceiling is set to ${smartAccountEffectiveFeeCeiling.toLocaleString()} stroops for smart-account x402 execution.`,
171
+ ]
172
+ : smartAccountReady
173
+ ? [
174
+ 'A smart-account contract id is configured, but x402 is still guarded behind the stable keypair path until all demo preconditions are satisfied.',
175
+ 'MPP charge and session flows remain explicit keypair routes.',
176
+ ]
177
+ : ['Smart-account execution is not configured yet.'];
178
+ const missingSecrets = [
179
+ !config.wallet.agentSecretKey ? 'XMPP_AGENT_SECRET_KEY' : null,
180
+ !config.x402.facilitatorPrivateKey ? 'FACILITATOR_STELLAR_PRIVATE_KEY' : null,
181
+ !config.mpp.secretKey ? 'MPP_SECRET_KEY' : null,
182
+ ].filter((value) => value !== null);
183
+ return {
184
+ connected: missingSecrets.length === 0,
185
+ paymentExecutionMode: config.paymentExecutionMode,
186
+ network: config.network,
187
+ rpcUrl: config.rpcUrl,
188
+ agentPublicKey: agentKeypair?.publicKey() ?? null,
189
+ settlementStrategy: smartAccountActive
190
+ ? 'smart-account-x402-preferred'
191
+ : smartAccountReady
192
+ ? 'smart-account-partial-fallback'
193
+ : 'keypair-live',
194
+ smartAccount: {
195
+ ready: smartAccountReady,
196
+ mode: smartAccountMode,
197
+ routeCoverage: smartAccountRouteCoverage,
198
+ demoReady: smartAccountActive,
199
+ guardedFallback: smartAccountReady,
200
+ contractId: config.wallet.smartAccountContractId ?? null,
201
+ wasmHash: config.wallet.smartAccountWasmHash,
202
+ webauthnVerifierAddress: config.wallet.webauthnVerifierAddress,
203
+ ed25519VerifierAddress: config.wallet.ed25519VerifierAddress,
204
+ spendingLimitPolicyAddress: config.wallet.spendingLimitPolicyAddress,
205
+ thresholdPolicyAddress: config.wallet.thresholdPolicyAddress,
206
+ preferredRoutes: smartAccountActive ? ['x402'] : [],
207
+ fallbackRoutes: smartAccountReady ? ['mpp-charge', 'mpp-session-open', 'mpp-session-reuse'] : [],
208
+ supportedRoutes: ['x402'],
209
+ unsupportedRoutes: ['mpp-charge', 'mpp-session-open', 'mpp-session-reuse'],
210
+ unsupportedReason: smartAccountReady
211
+ ? 'MPP charge and MPP session routes still require explicit Keypair signers in the current client stack.'
212
+ : null,
213
+ configuredMaxTransactionFeeStroops: config.x402.maxTransactionFeeStroops,
214
+ effectiveMaxTransactionFeeStroops: smartAccountEffectiveFeeCeiling,
215
+ feeFloorApplied: smartAccountFeeFloorWasApplied,
216
+ preflightFailures: smartAccountPreflightFailures,
217
+ coverageMessage: smartAccountReady
218
+ ? 'Smart-account execution is intentionally limited to x402. All MPP routes remain keypair-backed.'
219
+ : 'Smart-account execution is not configured.',
220
+ message: smartAccountActive
221
+ ? 'Smart-account execution is enabled for x402 only, with guarded fallback to the stable keypair path if the delegated route becomes unavailable.'
222
+ : smartAccountReady
223
+ ? 'Smart-account identifiers are configured, but x402 stays on the stable keypair path until the delegated flow is fully demo-ready.'
224
+ : 'Smart account execution is not configured yet.',
225
+ operatorNotes: smartAccountOperatorNotes,
226
+ },
227
+ feeSponsorship: {
228
+ enabled: config.mpp.feeSponsorship.chargeEnabled || config.mpp.feeSponsorship.sessionEnabled,
229
+ available: (config.mpp.feeSponsorship.chargeEnabled || config.mpp.feeSponsorship.sessionEnabled) &&
230
+ Boolean(feeSponsorKeypair),
231
+ mppChargeEnabled: config.mpp.feeSponsorship.chargeEnabled,
232
+ mppSessionEnabled: config.mpp.feeSponsorship.sessionEnabled,
233
+ sponsorPublicKey: feeSponsorKeypair?.publicKey() ?? null,
234
+ feeBumpPublicKey: feeBumpKeypair?.publicKey() ?? null,
235
+ message: feeSponsorKeypair
236
+ ? config.mpp.feeSponsorship.chargeEnabled && config.mpp.feeSponsorship.sessionEnabled
237
+ ? 'MPP charge and session services can sponsor gas for agent-side flows.'
238
+ : config.mpp.feeSponsorship.sessionEnabled
239
+ ? 'MPP session services can sponsor gas for agent-side flows; charge stays agent-funded.'
240
+ : config.mpp.feeSponsorship.chargeEnabled
241
+ ? 'MPP charge services can sponsor gas for agent-side flows; sessions stay agent-funded.'
242
+ : 'Fee sponsorship is disabled; the agent pays its own network fees.'
243
+ : 'Fee sponsorship is disabled; the agent pays its own network fees.',
244
+ },
245
+ missingSecrets,
246
+ message: missingSecrets.length === 0
247
+ ? 'xMPP has the core Stellar testnet secrets required to continue live payment integration.'
248
+ : 'xMPP is still missing the three core Stellar testnet secrets required for live payment execution.',
249
+ };
250
+ }
@@ -0,0 +1 @@
1
+ export {};
@@ -0,0 +1,140 @@
1
+ import { afterEach, describe, expect, it, vi } from 'vitest';
2
+ import { Keypair } from '@stellar/stellar-sdk';
3
+ async function loadWalletModule(secret, smartAccountContractId, maxTransactionFeeStroops = 2_000_000) {
4
+ vi.resetModules();
5
+ vi.doMock('@xmpp/config', () => ({
6
+ config: {
7
+ paymentExecutionMode: 'testnet',
8
+ rpcUrl: 'https://soroban-testnet.stellar.org',
9
+ network: 'stellar:testnet',
10
+ x402: {
11
+ facilitatorPrivateKey: 'SFACILITATOR',
12
+ maxTransactionFeeStroops,
13
+ },
14
+ mpp: {
15
+ secretKey: undefined,
16
+ feeSponsorSecretKey: undefined,
17
+ feeBumpSecretKey: undefined,
18
+ feeSponsorship: {
19
+ chargeEnabled: false,
20
+ sessionEnabled: false,
21
+ },
22
+ },
23
+ wallet: {
24
+ agentSecretKey: secret,
25
+ smartAccountContractId,
26
+ smartAccountWasmHash: 'wasm-hash',
27
+ webauthnVerifierAddress: 'CWEBAUTHN',
28
+ ed25519VerifierAddress: 'CED25519',
29
+ spendingLimitPolicyAddress: 'CSPEND',
30
+ thresholdPolicyAddress: 'CTHRESH',
31
+ },
32
+ },
33
+ }));
34
+ return import('./index.js');
35
+ }
36
+ describe('wallet receipt signing', () => {
37
+ afterEach(() => {
38
+ vi.restoreAllMocks();
39
+ vi.resetModules();
40
+ });
41
+ it('signs and verifies an xMPP receipt', async () => {
42
+ const secret = Keypair.random().secret();
43
+ const { signXmppReceipt, verifyXmppReceipt } = await loadWalletModule(secret);
44
+ const receipt = signXmppReceipt({
45
+ receiptId: 'xmpp_x402_test',
46
+ serviceId: 'research-api',
47
+ url: 'http://localhost:4101/research?q=stellar',
48
+ method: 'GET',
49
+ route: 'x402',
50
+ amountUsd: 0.01,
51
+ txHash: 'abc123',
52
+ explorerUrl: 'https://stellar.expert/explorer/testnet/tx/abc123',
53
+ paymentReference: 'abc123',
54
+ issuedAt: '2026-04-03T00:00:00.000Z',
55
+ network: 'stellar:testnet',
56
+ });
57
+ expect(receipt).not.toBeNull();
58
+ expect(receipt?.agent).toBe(Keypair.fromSecret(secret).publicKey());
59
+ expect(verifyXmppReceipt(receipt)).toMatchObject({
60
+ valid: true,
61
+ receiptId: 'xmpp_x402_test',
62
+ });
63
+ });
64
+ it('returns null when the agent key is unavailable', async () => {
65
+ const { signXmppReceipt } = await loadWalletModule(undefined);
66
+ expect(signXmppReceipt({
67
+ receiptId: 'xmpp_x402_test',
68
+ serviceId: 'research-api',
69
+ url: 'http://localhost:4101/research?q=stellar',
70
+ method: 'GET',
71
+ route: 'x402',
72
+ amountUsd: 0.01,
73
+ })).toBeNull();
74
+ });
75
+ it('prefers smart-account execution for x402 when configured', async () => {
76
+ const secret = Keypair.random().secret();
77
+ const { getRouteExecutionPlan, getWalletInfo } = await loadWalletModule(secret, 'CSMARTACCOUNT');
78
+ expect(getRouteExecutionPlan('x402')).toMatchObject({
79
+ settlementStrategy: 'smart-account',
80
+ smartAccount: {
81
+ configured: true,
82
+ supported: true,
83
+ used: true,
84
+ },
85
+ });
86
+ expect(getRouteExecutionPlan('mpp-charge')).toMatchObject({
87
+ settlementStrategy: 'keypair-fallback',
88
+ smartAccount: {
89
+ configured: true,
90
+ supported: false,
91
+ used: false,
92
+ },
93
+ });
94
+ await expect(getWalletInfo()).resolves.toMatchObject({
95
+ settlementStrategy: 'smart-account-x402-preferred',
96
+ smartAccount: {
97
+ ready: true,
98
+ mode: 'x402-only',
99
+ routeCoverage: 'x402-only',
100
+ demoReady: true,
101
+ guardedFallback: true,
102
+ preferredRoutes: ['x402'],
103
+ fallbackRoutes: ['mpp-charge', 'mpp-session-open', 'mpp-session-reuse'],
104
+ supportedRoutes: ['x402'],
105
+ unsupportedRoutes: ['mpp-charge', 'mpp-session-open', 'mpp-session-reuse'],
106
+ unsupportedReason: 'MPP charge and MPP session routes still require explicit Keypair signers in the current client stack.',
107
+ feeFloorApplied: false,
108
+ preflightFailures: [],
109
+ },
110
+ });
111
+ });
112
+ it('exposes honest smart-account fallback messaging when only the contract id is configured', async () => {
113
+ const { getWalletInfo } = await loadWalletModule(undefined, 'CSMARTACCOUNT');
114
+ await expect(getWalletInfo()).resolves.toMatchObject({
115
+ settlementStrategy: 'smart-account-partial-fallback',
116
+ smartAccount: {
117
+ ready: true,
118
+ mode: 'x402-only',
119
+ routeCoverage: 'x402-only',
120
+ demoReady: false,
121
+ guardedFallback: true,
122
+ preflightFailures: ['XMPP_AGENT_SECRET_KEY'],
123
+ message: 'Smart-account identifiers are configured, but x402 stays on the stable keypair path until the delegated flow is fully demo-ready.',
124
+ },
125
+ });
126
+ });
127
+ it('enforces the smart-account fee floor in readiness output', async () => {
128
+ const secret = Keypair.random().secret();
129
+ const { getWalletInfo, getEffectiveSmartAccountFeeCeiling } = await loadWalletModule(secret, 'CSMARTACCOUNT', 500_000);
130
+ expect(getEffectiveSmartAccountFeeCeiling()).toBe(2_000_000);
131
+ await expect(getWalletInfo()).resolves.toMatchObject({
132
+ smartAccount: {
133
+ demoReady: true,
134
+ feeFloorApplied: true,
135
+ configuredMaxTransactionFeeStroops: 500_000,
136
+ effectiveMaxTransactionFeeStroops: 2_000_000,
137
+ },
138
+ });
139
+ });
140
+ });
package/package.json ADDED
@@ -0,0 +1,53 @@
1
+ {
2
+ "name": "@vinaystwt/xmpp-wallet",
3
+ "version": "0.1.0",
4
+ "description": "Wallet, receipt-signing, and execution-planning helpers for xMPP on Stellar.",
5
+ "type": "module",
6
+ "license": "MIT",
7
+ "main": "dist/wallet/src/index.js",
8
+ "types": "dist/wallet/src/index.d.ts",
9
+ "files": [
10
+ "dist",
11
+ "LICENSE"
12
+ ],
13
+ "sideEffects": false,
14
+ "homepage": "https://github.com/Vinaystwt/xMPP#readme",
15
+ "repository": {
16
+ "type": "git",
17
+ "url": "git+https://github.com/Vinaystwt/xMPP.git",
18
+ "directory": "packages/wallet"
19
+ },
20
+ "bugs": {
21
+ "url": "https://github.com/Vinaystwt/xMPP/issues"
22
+ },
23
+ "keywords": [
24
+ "xmpp",
25
+ "wallet",
26
+ "stellar",
27
+ "receipts",
28
+ "agents"
29
+ ],
30
+ "exports": {
31
+ ".": {
32
+ "types": "./dist/wallet/src/index.d.ts",
33
+ "import": "./dist/wallet/src/index.js",
34
+ "default": "./dist/wallet/src/index.js"
35
+ }
36
+ },
37
+ "publishConfig": {
38
+ "access": "public"
39
+ },
40
+ "scripts": {
41
+ "build": "tsc -p tsconfig.json",
42
+ "dev": "tsc -p tsconfig.json --watch",
43
+ "lint": "eslint src",
44
+ "typecheck": "tsc -p tsconfig.json --noEmit",
45
+ "test": "vitest run src --passWithNoTests"
46
+ },
47
+ "dependencies": {
48
+ "@stellar/stellar-sdk": "^14.6.1",
49
+ "@vinaystwt/xmpp-config": "0.1.0",
50
+ "@vinaystwt/xmpp-types": "0.1.0",
51
+ "smart-account-kit": "0.2.10"
52
+ }
53
+ }