@subly_fi/pay 0.6.2 → 0.7.1

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/pay.js CHANGED
@@ -2,6 +2,144 @@
2
2
  import { homedir } from "node:os";
3
3
  import { join as join2 } from "node:path";
4
4
 
5
+ // ../../src/config/vault-catalog.ts
6
+ import { readFileSync } from "node:fs";
7
+ import { z } from "zod";
8
+
9
+ // ../../src/lib/solana-address.ts
10
+ var BASE58_ALPHABET = "123456789ABCDEFGHJKLMNPQRSTUVWXYZabcdefghijkmnopqrstuvwxyz";
11
+ var BASE58_LOOKUP = new Map(
12
+ [...BASE58_ALPHABET].map((character, index) => [character, BigInt(index)])
13
+ );
14
+ function assertSolanaAddress(value, fieldName) {
15
+ if (value.length < 32 || value.length > 44) {
16
+ throw new Error(`${fieldName} must be a valid Solana public key`);
17
+ }
18
+ if (decodeBase58(value).length !== 32) {
19
+ throw new Error(`${fieldName} must be a valid Solana public key`);
20
+ }
21
+ return value;
22
+ }
23
+ function decodeBase58(value) {
24
+ if (value.length === 0) {
25
+ return new Uint8Array();
26
+ }
27
+ let decoded = 0n;
28
+ for (const character of value) {
29
+ const digit = BASE58_LOOKUP.get(character);
30
+ if (digit === void 0) {
31
+ return new Uint8Array();
32
+ }
33
+ decoded = decoded * 58n + digit;
34
+ }
35
+ const bytes = [];
36
+ while (decoded > 0n) {
37
+ bytes.push(Number(decoded & 0xffn));
38
+ decoded >>= 8n;
39
+ }
40
+ for (const character of value) {
41
+ if (character !== "1") {
42
+ break;
43
+ }
44
+ bytes.push(0);
45
+ }
46
+ return Uint8Array.from(bytes.reverse());
47
+ }
48
+
49
+ // ../../src/config/vault.ts
50
+ var MAINNET_USDC_MINT = "EPjFWdd5AufqSSqeM2qN1xzybapC8G4wEGGkZwyTDt1v";
51
+ var KAMINO_VAULT_PROGRAM_ID = "KvauGMspG5k6rtzrqqn7WNn3oZdyKqLKwK2XWQ8FLjd";
52
+ var NO_VAULT_FARM = "11111111111111111111111111111111";
53
+ var DEFAULT_VAULT_CONFIG = Object.freeze({
54
+ address: "5kfkpQZ6AkQgizHVThqkxD4J3db2i7pE3mHdPNRbx7jr",
55
+ programId: KAMINO_VAULT_PROGRAM_ID,
56
+ usdcMint: MAINNET_USDC_MINT,
57
+ shareMint: "7hGX49So539MU9Rrah8nBNVYXswWVwEJvgWNYeBDYq3a",
58
+ farm: "E2Ct77LowkDAH1T9ubwPpb84pU2GSGrUdgH3KeTTpLX"
59
+ });
60
+ function vaultConfigFromEnv(env = process.env) {
61
+ const value = (name) => env[name]?.trim() || void 0;
62
+ const vaultAddress = value("SUBLY_VAULT_ADDRESS") ?? DEFAULT_VAULT_CONFIG.address;
63
+ const customVault = vaultAddress !== DEFAULT_VAULT_CONFIG.address;
64
+ const anchor = (name, fallback) => {
65
+ const configured = value(name);
66
+ if (customVault && configured === void 0) {
67
+ throw new Error(
68
+ `${name} is required for a custom vault. Generate its settings with npm run configure:vault -- <vault-address>; use ${NO_VAULT_FARM} for a vault without a farm.`
69
+ );
70
+ }
71
+ return assertSolanaAddress(configured ?? fallback, name);
72
+ };
73
+ const usdcMint = value("SUBLY_VAULT_USDC_MINT") ?? MAINNET_USDC_MINT;
74
+ if (usdcMint !== MAINNET_USDC_MINT) {
75
+ throw new Error(
76
+ "SUBLY_VAULT_USDC_MINT must be mainnet USDC; other deposit assets are not supported"
77
+ );
78
+ }
79
+ return Object.freeze({
80
+ address: assertSolanaAddress(vaultAddress, "SUBLY_VAULT_ADDRESS"),
81
+ programId: KAMINO_VAULT_PROGRAM_ID,
82
+ usdcMint,
83
+ shareMint: anchor("SUBLY_VAULT_SHARE_MINT", DEFAULT_VAULT_CONFIG.shareMint),
84
+ farm: anchor("SUBLY_VAULT_FARM", DEFAULT_VAULT_CONFIG.farm)
85
+ });
86
+ }
87
+
88
+ // ../../src/config/vault-catalog.ts
89
+ var publicKey = z.string().refine((value) => {
90
+ try {
91
+ assertSolanaAddress(value, "vault catalog address");
92
+ return true;
93
+ } catch {
94
+ return false;
95
+ }
96
+ }, "Invalid Solana public key");
97
+ var catalogSchema = z.object({
98
+ version: z.literal(1),
99
+ defaultVault: publicKey,
100
+ vaults: z.array(z.object({
101
+ address: publicKey,
102
+ programId: z.literal(KAMINO_VAULT_PROGRAM_ID),
103
+ usdcMint: z.literal(MAINNET_USDC_MINT),
104
+ shareMint: publicKey,
105
+ farm: publicKey,
106
+ name: z.string().min(1).max(128).optional(),
107
+ depositsEnabled: z.boolean().optional(),
108
+ extraLookupTables: z.array(publicKey).max(16).optional()
109
+ }).strict()).min(1).max(100)
110
+ }).strict();
111
+ function parseVaultCatalog(value) {
112
+ const catalog = catalogSchema.parse(value);
113
+ const addresses = new Set(catalog.vaults.map((vault) => vault.address));
114
+ if (addresses.size !== catalog.vaults.length) throw new Error("Duplicate vault in catalog");
115
+ if (!addresses.has(catalog.defaultVault)) throw new Error("defaultVault must be in the vault catalog");
116
+ return { ...catalog, vaults: catalog.vaults.map((vault) => Object.freeze(vault)) };
117
+ }
118
+ function vaultCatalogFromEnv(env = process.env) {
119
+ const path = env.SUBLY_VAULTS_FILE?.trim();
120
+ if (!path) {
121
+ const vault = vaultConfigFromEnv(env);
122
+ return { version: 1, defaultVault: vault.address, vaults: [vault] };
123
+ }
124
+ const catalog = parseVaultCatalog(JSON.parse(readFileSync(path, "utf8")));
125
+ const selected = env.SUBLY_VAULT_ADDRESS?.trim() || catalog.defaultVault;
126
+ if (!catalog.vaults.some((vault) => vault.address === selected)) {
127
+ throw new Error("SUBLY_VAULT_ADDRESS must be in SUBLY_VAULTS_FILE");
128
+ }
129
+ return { ...catalog, defaultVault: selected };
130
+ }
131
+ function defaultCatalogVault(catalog) {
132
+ return catalog.vaults.find((vault) => vault.address === catalog.defaultVault);
133
+ }
134
+
135
+ // ../../src/config/constants.ts
136
+ var PAYMENT_SCHEME = "subly-yield-exact";
137
+ var SOLANA_MAINNET_NETWORK = "solana:5eykt4UsFv8P8NJdTREpY1vzqKqZKvdp";
138
+ var SPL_TOKEN_PROGRAM_ID = "TokenkegQfeZyiNwAJbNbGKPFXCWuBvf9Ss623VQ5DA";
139
+ var ASSOCIATED_TOKEN_PROGRAM_ID = "ATokenGPvbdGVxr1b2hvZbsiqW5xWH25efTNsLJA8knL";
140
+ var SUBLY_VAULT = defaultCatalogVault(vaultCatalogFromEnv());
141
+ var USDC_DECIMALS = 6;
142
+
5
143
  // ../../src/api/wallet-auth.ts
6
144
  import { createHash } from "node:crypto";
7
145
  import bs58 from "bs58";
@@ -85,15 +223,148 @@ async function ensureWalletOnboarded(params) {
85
223
  }
86
224
  };
87
225
  const wallet = params.signer.walletAddress;
226
+ const vault = params.vault ?? params.signer.vault?.address ?? SUBLY_VAULT.address;
88
227
  await post("register", "/v1/wallets/agent", {
89
228
  wallet,
229
+ vault,
90
230
  signingPolicyId: SELF_SERVE_POLICY_ID,
91
231
  signingMode: "non_interactive",
92
232
  signerValidationMode: params.signer.validationMode,
93
233
  signerProvider: params.signer.provider ?? "local-keypair",
94
234
  activateForPayments: true
95
235
  });
96
- await post("sync", `/v1/wallets/${wallet}/sync`, { source: "chain" });
236
+ await post("sync", `/v1/wallets/${wallet}/sync`, { source: "chain", vault });
237
+ }
238
+
239
+ // ../../src/lib/associated-token-account.ts
240
+ import { createHash as createHash2 } from "node:crypto";
241
+ import bs582 from "bs58";
242
+ var PDA_MARKER = Buffer.from("ProgramDerivedAddress", "utf8");
243
+ var ED25519_P = (1n << 255n) - 19n;
244
+ var ED25519_D = mod(-121665n * modPow(121666n, ED25519_P - 2n, ED25519_P), ED25519_P);
245
+ function deriveAssociatedTokenAddress(params) {
246
+ const owner = decodePublicKey(params.owner, "owner");
247
+ const mint = decodePublicKey(params.mint ?? SUBLY_VAULT.usdcMint, "mint");
248
+ const tokenProgramId = decodePublicKey(
249
+ params.tokenProgramId ?? SPL_TOKEN_PROGRAM_ID,
250
+ "tokenProgramId"
251
+ );
252
+ const associatedTokenProgramId = decodePublicKey(
253
+ ASSOCIATED_TOKEN_PROGRAM_ID,
254
+ "associatedTokenProgramId"
255
+ );
256
+ for (let bump = 255; bump >= 0; bump -= 1) {
257
+ const address3 = createProgramAddress(
258
+ [owner, tokenProgramId, mint, Uint8Array.of(bump)],
259
+ associatedTokenProgramId
260
+ );
261
+ if (address3 !== null) {
262
+ return bs582.encode(address3);
263
+ }
264
+ }
265
+ throw new Error("Unable to derive associated token account address");
266
+ }
267
+ function createProgramAddress(seeds, programId) {
268
+ const hash = createHash2("sha256");
269
+ for (const seed of seeds) {
270
+ hash.update(seed);
271
+ }
272
+ hash.update(programId);
273
+ hash.update(PDA_MARKER);
274
+ const digest = hash.digest();
275
+ return isEd25519Point(digest) ? null : new Uint8Array(digest);
276
+ }
277
+ function decodePublicKey(value, fieldName) {
278
+ const decoded = bs582.decode(value);
279
+ if (decoded.length !== 32) {
280
+ throw new Error(`${fieldName} must be a 32-byte public key`);
281
+ }
282
+ return decoded;
283
+ }
284
+ function isEd25519Point(bytes) {
285
+ if (bytes.length !== 32) {
286
+ return false;
287
+ }
288
+ const yBytes = Uint8Array.from(bytes);
289
+ yBytes[31] = yBytes[31] & 127;
290
+ const y = littleEndianToBigInt(yBytes);
291
+ if (y >= ED25519_P) {
292
+ return false;
293
+ }
294
+ const ySquared = mod(y * y, ED25519_P);
295
+ const numerator = mod(ySquared - 1n, ED25519_P);
296
+ const denominator = mod(ED25519_D * ySquared + 1n, ED25519_P);
297
+ if (denominator === 0n) {
298
+ return false;
299
+ }
300
+ const xSquared = mod(
301
+ numerator * modPow(denominator, ED25519_P - 2n, ED25519_P),
302
+ ED25519_P
303
+ );
304
+ return xSquared === 0n || modPow(xSquared, (ED25519_P - 1n) / 2n, ED25519_P) === 1n;
305
+ }
306
+ function littleEndianToBigInt(bytes) {
307
+ let value = 0n;
308
+ for (let index = bytes.length - 1; index >= 0; index -= 1) {
309
+ value = (value << 8n) + BigInt(bytes[index]);
310
+ }
311
+ return value;
312
+ }
313
+ function mod(value, modulus) {
314
+ const result = value % modulus;
315
+ return result >= 0n ? result : result + modulus;
316
+ }
317
+ function modPow(base, exponent, modulus) {
318
+ let result = 1n;
319
+ let nextBase = mod(base, modulus);
320
+ let nextExponent = exponent;
321
+ while (nextExponent > 0n) {
322
+ if ((nextExponent & 1n) === 1n) {
323
+ result = mod(result * nextBase, modulus);
324
+ }
325
+ nextBase = mod(nextBase * nextBase, modulus);
326
+ nextExponent >>= 1n;
327
+ }
328
+ return result;
329
+ }
330
+
331
+ // ../../src/client/withdrawal-preview.ts
332
+ var ROUNDING_RAW_USDC = 10n;
333
+ async function assertWithdrawalPreview(input) {
334
+ const destination = deriveAssociatedTokenAddress({ owner: input.wallet, mint: input.vault.usdcMint });
335
+ const simulation = await input.rpc.simulateTransaction(
336
+ input.serializedTransaction,
337
+ {
338
+ encoding: "base64",
339
+ commitment: "confirmed",
340
+ sigVerify: false,
341
+ replaceRecentBlockhash: false,
342
+ innerInstructions: true
343
+ }
344
+ ).send({ abortSignal: AbortSignal.timeout(15e3) });
345
+ if (simulation.value.err !== null) {
346
+ throw new Error("Withdrawal preview failed on the client RPC; no transaction was signed. Check liquidity, RPC and blockhash, then prepare again.");
347
+ }
348
+ let received = 0n;
349
+ for (const group of simulation.value.innerInstructions ?? []) {
350
+ for (const instruction of group.instructions) {
351
+ if (!("parsed" in instruction) || instruction.programId !== SPL_TOKEN_PROGRAM_ID) continue;
352
+ const parsed = instruction.parsed;
353
+ if (parsed.type !== "transfer" && parsed.type !== "transferChecked") continue;
354
+ const info = parsed.info;
355
+ if (!info || info.destination !== destination && info.source !== destination) continue;
356
+ const raw = parsed.type === "transferChecked" ? info.tokenAmount?.amount : info.amount;
357
+ if (typeof raw !== "string" || !/^\d+$/.test(raw)) {
358
+ throw new Error("Withdrawal preview returned an invalid token amount");
359
+ }
360
+ const amount = BigInt(raw);
361
+ if (info.destination === destination) received += amount;
362
+ if (info.source === destination) received -= amount;
363
+ }
364
+ }
365
+ if (received <= 0n || received > input.amountRawUsdc + ROUNDING_RAW_USDC || received < input.amountRawUsdc - ROUNDING_RAW_USDC) {
366
+ throw new Error("Withdrawal preview differs from the requested USDC amount; no transaction was signed");
367
+ }
97
368
  }
98
369
 
99
370
  // ../../src/client/lookup-tables.ts
@@ -152,6 +423,8 @@ var VaultFlowClientError = class extends Error {
152
423
  errorDetails;
153
424
  };
154
425
  var VaultFlowClient = class {
426
+ vault;
427
+ rpc;
155
428
  baseUrl;
156
429
  signer;
157
430
  fetchImpl;
@@ -159,6 +432,11 @@ var VaultFlowClient = class {
159
432
  pollTimeoutMs;
160
433
  pollIntervalMs;
161
434
  constructor(config) {
435
+ this.rpc = config.rpc;
436
+ this.vault = config.vault ?? config.signer.vault ?? SUBLY_VAULT;
437
+ if (config.signer.vault && config.signer.vault.address !== this.vault.address) {
438
+ throw new Error("Vault flow client and signer must select the same vault");
439
+ }
162
440
  this.baseUrl = config.relayerBaseUrl.replace(/\/$/, "");
163
441
  this.signer = config.signer;
164
442
  this.fetchImpl = config.fetchImpl ?? fetch;
@@ -180,6 +458,7 @@ var VaultFlowClient = class {
180
458
  try {
181
459
  prepared = await this.postJson("prepare", "/v1/deposits/prepare", {
182
460
  wallet: this.signer.walletAddress,
461
+ vault: this.vault.address,
183
462
  amountRawUsdc: input.amountRawUsdc.toString(),
184
463
  ...approvalId2 === void 0 ? {} : { approvalId: approvalId2 }
185
464
  });
@@ -193,10 +472,14 @@ var VaultFlowClient = class {
193
472
  }
194
473
  prepared = await this.postJson("prepare", "/v1/deposits/prepare", {
195
474
  wallet: this.signer.walletAddress,
475
+ vault: this.vault.address,
196
476
  amountRawUsdc: input.amountRawUsdc.toString(),
197
477
  approvalId: approvalId2
198
478
  });
199
479
  }
480
+ if (prepared.signingIntent?.wallet !== this.signer.walletAddress || prepared.signingIntent.vault !== this.vault.address || prepared.signingIntent.amountRawUsdc !== input.amountRawUsdc.toString()) {
481
+ throw new VaultFlowClientError("prepare", "Prepared deposit differs from the requested wallet, vault or amount");
482
+ }
200
483
  const signed = await this.signer.signDeposit({
201
484
  intent: prepared.signingIntent,
202
485
  serializedTransaction: prepared.serializedTransaction,
@@ -234,12 +517,23 @@ var VaultFlowClient = class {
234
517
  "/v1/withdrawals/prepare",
235
518
  {
236
519
  wallet: this.signer.walletAddress,
520
+ vault: this.vault.address,
237
521
  amountRawUsdc: input.amountRawUsdc.toString(),
238
522
  ...input.purpose === void 0 ? {} : { purpose: input.purpose },
239
523
  ...input.payment === void 0 ? {} : { payment: input.payment },
240
524
  ...input.approvalId === void 0 ? {} : { approvalId: input.approvalId }
241
525
  }
242
526
  );
527
+ if (prepared.signingIntent?.wallet !== this.signer.walletAddress || prepared.signingIntent.vault !== this.vault.address || prepared.requestedWithdrawRawUsdc !== input.amountRawUsdc.toString() || prepared.purpose !== (input.purpose ?? "normal") || input.purpose === "yield_realize" && prepared.signingIntent.allowFullExit) {
528
+ throw new VaultFlowClientError("prepare", "Prepared withdrawal differs from the requested operation");
529
+ }
530
+ await assertWithdrawalPreview({
531
+ rpc: this.rpc,
532
+ serializedTransaction: prepared.serializedTransaction,
533
+ wallet: this.signer.walletAddress,
534
+ vault: this.vault,
535
+ amountRawUsdc: input.amountRawUsdc
536
+ });
243
537
  const signed = await this.signer.signWithdrawal({
244
538
  intent: prepared.signingIntent,
245
539
  serializedTransaction: prepared.serializedTransaction,
@@ -277,12 +571,12 @@ var VaultFlowClient = class {
277
571
  await this.postJson(
278
572
  "sync",
279
573
  `/v1/wallets/${this.signer.walletAddress}/sync`,
280
- { source: "chain" }
574
+ { source: "chain", vault: this.vault.address }
281
575
  );
282
576
  } catch {
283
577
  }
284
578
  }
285
- const url2 = `${this.baseUrl}/v1/wallets/${this.signer.walletAddress}/budget`;
579
+ const url2 = `${this.baseUrl}/v1/wallets/${this.signer.walletAddress}/budget?vault=${this.vault.address}`;
286
580
  const response = await this.fetchImpl(url2, {
287
581
  headers: await walletAuthHeaders({
288
582
  signer: this.signer,
@@ -308,8 +602,12 @@ var VaultFlowClient = class {
308
602
  );
309
603
  }
310
604
  const body2 = parsed;
605
+ if (body2.position?.vault !== void 0 && body2.position.vault !== this.vault.address) {
606
+ throw new VaultFlowClientError("budget", "Relayer returned the budget for a different vault");
607
+ }
311
608
  return {
312
609
  wallet: this.signer.walletAddress,
610
+ vault: this.vault.address,
313
611
  principalBasisRawUsdc: body2.position?.principalBasisRawUsdc ?? "0",
314
612
  positionValueRawUsdc: body2.budget?.positionValueRawUsdc ?? "0",
315
613
  grossYieldRawUsdc: body2.budget?.grossYieldRawUsdc ?? "0",
@@ -327,7 +625,7 @@ var VaultFlowClient = class {
327
625
  /** Wallet's approvals as the relayer sees them (optionally by status). */
328
626
  async listApprovals(status) {
329
627
  const body2 = await this.getJson(
330
- `/v1/wallets/${this.signer.walletAddress}/approvals${status === void 0 ? "" : `?status=${encodeURIComponent(status)}`}`
628
+ `/v1/wallets/${this.signer.walletAddress}/approvals${`?vault=${this.vault.address}${status === void 0 ? "" : `&status=${encodeURIComponent(status)}`}`}`
331
629
  );
332
630
  return body2.approvals ?? [];
333
631
  }
@@ -336,16 +634,21 @@ var VaultFlowClient = class {
336
634
  * policy + initial deposit). Paste `setupUrl` into the chat verbatim.
337
635
  */
338
636
  async createSetupSession(input) {
339
- return await this.postJson(
637
+ const session = await this.postJson(
340
638
  "prepare",
341
639
  `/v1/wallets/${this.signer.walletAddress}/setup-sessions`,
342
640
  {
641
+ vault: this.vault.address,
343
642
  ...input.policy === void 0 ? {} : { policy: input.policy },
344
643
  ...input.enforcementMode === void 0 ? {} : { enforcementMode: input.enforcementMode },
345
644
  ...input.mandateTtlDays === void 0 ? {} : { mandateTtlDays: input.mandateTtlDays },
346
645
  ...input.initialDepositRawUsdc === void 0 ? {} : { initialDepositRawUsdc: input.initialDepositRawUsdc }
347
646
  }
348
647
  );
648
+ if (session.vault !== this.vault.address || session.wallet !== this.signer.walletAddress) {
649
+ throw new VaultFlowClientError("prepare", "Relayer returned a setup session for a different wallet or vault");
650
+ }
651
+ return session;
349
652
  }
350
653
  /** Polls a setup session (public capability URL — no auth needed). */
351
654
  async getSetupSession(sessionId) {
@@ -505,6 +808,9 @@ var RelayerRealizeError = class extends Error {
505
808
  detail;
506
809
  };
507
810
  var RelayerYieldRealizer = class {
811
+ get vault() {
812
+ return this.vaultFlows.vault.address;
813
+ }
508
814
  vaultFlows;
509
815
  constructor(config) {
510
816
  this.vaultFlows = new VaultFlowClient({
@@ -622,46 +928,14 @@ function errorCodeFrom(detail) {
622
928
  }
623
929
  }
624
930
 
625
- // ../../src/config/constants.ts
626
- var PAYMENT_SCHEME = "subly-yield-exact";
627
- var SOLANA_MAINNET_NETWORK = "solana:5eykt4UsFv8P8NJdTREpY1vzqKqZKvdp";
628
- var SPL_TOKEN_PROGRAM_ID = "TokenkegQfeZyiNwAJbNbGKPFXCWuBvf9Ss623VQ5DA";
629
- var ASSOCIATED_TOKEN_PROGRAM_ID = "ATokenGPvbdGVxr1b2hvZbsiqW5xWH25efTNsLJA8knL";
630
- var envOr = (name, fallback) => {
631
- const value = process.env[name]?.trim();
632
- return value ? value : fallback;
633
- };
634
- var SUBLY_VAULT = {
635
- name: "Subly USDC Payment Vault Alpha",
636
- address: envOr(
637
- "SUBLY_VAULT_ADDRESS",
638
- "5kfkpQZ6AkQgizHVThqkxD4J3db2i7pE3mHdPNRbx7jr"
639
- ),
640
- programId: "KvauGMspG5k6rtzrqqn7WNn3oZdyKqLKwK2XWQ8FLjd",
641
- usdcMint: envOr(
642
- "SUBLY_VAULT_USDC_MINT",
643
- "EPjFWdd5AufqSSqeM2qN1xzybapC8G4wEGGkZwyTDt1v"
644
- ),
645
- shareMint: envOr(
646
- "SUBLY_VAULT_SHARE_MINT",
647
- "7hGX49So539MU9Rrah8nBNVYXswWVwEJvgWNYeBDYq3a"
648
- ),
649
- lookupTable: "7UbXhDnpK7WVnwsfivzQRENoqKqAULQ5s19gS1xJrQEo",
650
- farm: envOr(
651
- "SUBLY_VAULT_FARM",
652
- "E2Ct77LowkDAH1T9ubwPpb84pU2GSGrUdgH3KeTTpLX"
653
- )
654
- };
655
- var USDC_DECIMALS = 6;
656
-
657
931
  // ../../src/lib/canonical-json.ts
658
- import { createHash as createHash3 } from "node:crypto";
932
+ import { createHash as createHash4 } from "node:crypto";
659
933
 
660
934
  // ../../src/lib/hash.ts
661
- import { createHash as createHash2 } from "node:crypto";
935
+ import { createHash as createHash3 } from "node:crypto";
662
936
  var EMPTY_BODY_HASH = "sha256-e3b0c44298fc1c149afbf4c8996fb92427ae41e4649b934ca495991b7852b855";
663
937
  function sha256TaggedHex(data) {
664
- return `sha256-${createHash2("sha256").update(data).digest("hex")}`;
938
+ return `sha256-${createHash3("sha256").update(data).digest("hex")}`;
665
939
  }
666
940
  function stableStringify(value) {
667
941
  if (value === null) {
@@ -685,11 +959,11 @@ function hashStableJson(value) {
685
959
 
686
960
  // ../../src/lib/canonical-json.ts
687
961
  function sha256HexOf(data) {
688
- return createHash3("sha256").update(data, "utf8").digest("hex");
962
+ return createHash4("sha256").update(data, "utf8").digest("hex");
689
963
  }
690
964
 
691
965
  // ../../src/x402/headers.ts
692
- import { z } from "zod";
966
+ import { z as z2 } from "zod";
693
967
  var PAYMENT_REQUIRED_HEADER = "payment-required";
694
968
  var MAX_HEADER_JSON_BYTES = 16384;
695
969
  var X402HeaderError = class extends Error {
@@ -700,41 +974,41 @@ var X402HeaderError = class extends Error {
700
974
  this.reason = reason;
701
975
  }
702
976
  };
703
- var sublyPaymentRequirementsSchema = z.object({
704
- scheme: z.literal(PAYMENT_SCHEME),
705
- network: z.string().min(1),
706
- asset: z.string().min(32),
977
+ var sublyPaymentRequirementsSchema = z2.object({
978
+ scheme: z2.literal(PAYMENT_SCHEME),
979
+ network: z2.string().min(1),
980
+ asset: z2.string().min(32),
707
981
  /** Exact seller amount in raw USDC; the scheme settles exactly this. */
708
- amountRawUsdc: z.string().regex(/^[1-9]\d*$/),
709
- resource: z.string().url(),
710
- description: z.string().optional(),
711
- mimeType: z.string().optional(),
712
- payTo: z.string().min(32),
713
- maxTimeoutSeconds: z.number().int().positive(),
714
- extra: z.object({
715
- sellerRequestId: z.string().min(1),
716
- seller: z.string().min(32),
717
- sellerUsdcAta: z.string().min(32),
718
- vault: z.string().min(32),
719
- shareMint: z.string().min(32)
982
+ amountRawUsdc: z2.string().regex(/^[1-9]\d*$/),
983
+ resource: z2.string().url(),
984
+ description: z2.string().optional(),
985
+ mimeType: z2.string().optional(),
986
+ payTo: z2.string().min(32),
987
+ maxTimeoutSeconds: z2.number().int().positive(),
988
+ extra: z2.object({
989
+ sellerRequestId: z2.string().min(1),
990
+ seller: z2.string().min(32),
991
+ sellerUsdcAta: z2.string().min(32),
992
+ vault: z2.string().min(32),
993
+ shareMint: z2.string().min(32)
720
994
  })
721
995
  }).loose();
722
- var paymentRequiredSchema = z.object({
723
- x402Version: z.number().int(),
724
- accepts: z.array(z.unknown()),
725
- error: z.string().optional()
996
+ var paymentRequiredSchema = z2.object({
997
+ x402Version: z2.number().int(),
998
+ accepts: z2.array(z2.unknown()),
999
+ error: z2.string().optional()
726
1000
  }).loose();
727
- var sublyPaymentPayloadSchema = z.object({
728
- x402Version: z.number().int(),
729
- scheme: z.literal(PAYMENT_SCHEME),
730
- network: z.string().min(1),
731
- payload: z.object({
732
- paymentId: z.string().min(1),
733
- requestBindingHash: z.string().min(1),
734
- preparedMessageHash: z.string().min(1),
735
- serializedTransaction: z.string().min(1).max(4096),
736
- agentSignature: z.string().min(1).max(128),
737
- temporarySettlementSignature: z.string().min(1).max(128)
1001
+ var sublyPaymentPayloadSchema = z2.object({
1002
+ x402Version: z2.number().int(),
1003
+ scheme: z2.literal(PAYMENT_SCHEME),
1004
+ network: z2.string().min(1),
1005
+ payload: z2.object({
1006
+ paymentId: z2.string().min(1),
1007
+ requestBindingHash: z2.string().min(1),
1008
+ preparedMessageHash: z2.string().min(1),
1009
+ serializedTransaction: z2.string().min(1).max(4096),
1010
+ agentSignature: z2.string().min(1).max(128),
1011
+ temporarySettlementSignature: z2.string().min(1).max(128)
738
1012
  })
739
1013
  }).loose();
740
1014
  function decodeX402Header(headerValue) {
@@ -764,29 +1038,29 @@ function requestBodyHashFor(body2) {
764
1038
  }
765
1039
 
766
1040
  // ../../src/x402/standard-requirements.ts
767
- import { z as z2 } from "zod";
1041
+ import { z as z3 } from "zod";
768
1042
  var STANDARD_EXACT_SCHEME = "exact";
769
- var standardExactRequirementSchema = z2.object({
770
- scheme: z2.literal(STANDARD_EXACT_SCHEME),
1043
+ var standardExactRequirementSchema = z3.object({
1044
+ scheme: z3.literal(STANDARD_EXACT_SCHEME),
771
1045
  /** CAIP-2 chain id, e.g. "solana:5eykt4UsFv8P8NJdTREpY1vzqKqZKvdp". */
772
- network: z2.string().min(1),
1046
+ network: z3.string().min(1),
773
1047
  /** SPL mint (Solana) or token contract (EVM); Subly only pays USDC/Solana. */
774
- asset: z2.string().min(1),
1048
+ asset: z3.string().min(1),
775
1049
  /** Exact price in the asset's atomic units, as a decimal string. */
776
- amount: z2.string().regex(/^[1-9]\d*$/),
1050
+ amount: z3.string().regex(/^[1-9]\d*$/),
777
1051
  /** Recipient wallet; the transfer destination ATA is derived from it. */
778
- payTo: z2.string().min(1),
779
- maxTimeoutSeconds: z2.number().int().positive().optional(),
780
- extra: z2.object({
1052
+ payTo: z3.string().min(1),
1053
+ maxTimeoutSeconds: z3.number().int().positive().optional(),
1054
+ extra: z3.object({
781
1055
  /** Facilitator address that pays the tx fee (gas sponsorship). */
782
- feePayer: z2.string().min(1).optional()
1056
+ feePayer: z3.string().min(1).optional()
783
1057
  }).loose().optional()
784
1058
  }).loose();
785
- var standardPaymentRequiredSchema = z2.object({
786
- x402Version: z2.number().int(),
787
- accepts: z2.array(z2.unknown()),
788
- error: z2.string().optional(),
789
- resource: z2.object({ url: z2.string().optional() }).loose().optional()
1059
+ var standardPaymentRequiredSchema = z3.object({
1060
+ x402Version: z3.number().int(),
1061
+ accepts: z3.array(z3.unknown()),
1062
+ error: z3.string().optional(),
1063
+ resource: z3.object({ url: z3.string().optional() }).loose().optional()
790
1064
  }).loose();
791
1065
  var StandardX402ChallengeError = class extends Error {
792
1066
  reason;
@@ -916,7 +1190,7 @@ var StandardX402Payer = class {
916
1190
  }
917
1191
  }
918
1192
  }
919
- pay(input) {
1193
+ pay(input, realizer = this.realizer) {
920
1194
  const method2 = (input.method ?? "GET").toUpperCase();
921
1195
  const requestBodyHash = requestBodyHashFor(input.body ?? null);
922
1196
  const pendingKey = pendingPaymentKey({
@@ -928,17 +1202,20 @@ var StandardX402Payer = class {
928
1202
  if (existingFlow !== void 0) {
929
1203
  return existingFlow;
930
1204
  }
931
- const flow = this.run(input, {
932
- method: method2,
933
- requestBodyHash,
934
- pendingKey
935
- }).finally(() => {
1205
+ const run = async () => {
1206
+ if (this.stateStore?.withExclusiveLock) {
1207
+ this.pending.clear();
1208
+ for (const record of this.stateStore.load()) this.pending.set(record.key, record);
1209
+ }
1210
+ return this.run(input, { method: method2, requestBodyHash, pendingKey }, realizer);
1211
+ };
1212
+ const flow = (this.stateStore?.withExclusiveLock ? this.stateStore.withExclusiveLock(run) : run()).finally(() => {
936
1213
  this.inFlight.delete(pendingKey);
937
1214
  });
938
1215
  this.inFlight.set(pendingKey, flow);
939
1216
  return flow;
940
1217
  }
941
- async run(input, computed) {
1218
+ async run(input, computed, realizer) {
942
1219
  const { method: method2, requestBodyHash, pendingKey } = computed;
943
1220
  const existingPending = this.pending.get(pendingKey);
944
1221
  if (existingPending !== void 0 && input.forceNewPayment !== true) {
@@ -979,7 +1256,7 @@ var StandardX402Payer = class {
979
1256
  }
980
1257
  let realized;
981
1258
  try {
982
- realized = await this.realizer.ensureUsdcAvailable({
1259
+ realized = await realizer.ensureUsdcAvailable({
983
1260
  amountRawUsdc: selected.amountRawUsdc,
984
1261
  payment: {
985
1262
  payTo: selected.payTo,
@@ -1053,9 +1330,9 @@ var StandardX402Payer = class {
1053
1330
  }
1054
1331
  this.clearDelivered(pendingKey);
1055
1332
  const paymentTxSignature = extractSettledPaymentTxSignature(response);
1056
- if (paymentTxSignature !== null && typeof realized.withdrawalId === "string" && this.realizer.reportPayment !== void 0) {
1333
+ if (paymentTxSignature !== null && typeof realized.withdrawalId === "string" && realizer.reportPayment !== void 0) {
1057
1334
  try {
1058
- await this.realizer.reportPayment({
1335
+ await realizer.reportPayment({
1059
1336
  withdrawalId: realized.withdrawalId,
1060
1337
  paymentTxSignature
1061
1338
  });
@@ -1067,6 +1344,7 @@ var StandardX402Payer = class {
1067
1344
  }
1068
1345
  return {
1069
1346
  paid: true,
1347
+ ...realizer.vault === void 0 ? {} : { fundingVault: realizer.vault },
1070
1348
  status: response.status,
1071
1349
  body: bodyText,
1072
1350
  payment: {
@@ -1229,22 +1507,22 @@ function createRelayerX402Payer(config) {
1229
1507
  import { createKeyPairSignerFromBytes as createKeyPairSignerFromBytes2 } from "@solana/kit";
1230
1508
 
1231
1509
  // ../../src/solana/keys.ts
1232
- import { readFileSync } from "node:fs";
1233
- import bs582 from "bs58";
1510
+ import { readFileSync as readFileSync2 } from "node:fs";
1511
+ import bs583 from "bs58";
1234
1512
  import {
1235
1513
  createKeyPairSignerFromBytes
1236
1514
  } from "@solana/kit";
1237
1515
  function loadSecretKeyBytes(params) {
1238
1516
  const { base58Secret, jsonFilePath, label } = params;
1239
1517
  if (base58Secret !== void 0 && base58Secret.length > 0) {
1240
- const bytes = bs582.decode(base58Secret);
1518
+ const bytes = bs583.decode(base58Secret);
1241
1519
  if (bytes.length !== 64) {
1242
1520
  throw new Error(`${label} base58 secret must decode to 64 bytes`);
1243
1521
  }
1244
1522
  return bytes;
1245
1523
  }
1246
1524
  if (jsonFilePath !== void 0 && jsonFilePath.length > 0) {
1247
- const raw = JSON.parse(readFileSync(jsonFilePath, "utf8"));
1525
+ const raw = JSON.parse(readFileSync2(jsonFilePath, "utf8"));
1248
1526
  if (!Array.isArray(raw) || raw.length !== 64) {
1249
1527
  throw new Error(`${label} keypair file must be a 64-byte JSON array`);
1250
1528
  }
@@ -1259,7 +1537,7 @@ import bs587 from "bs58";
1259
1537
  import nacl3 from "tweetnacl";
1260
1538
 
1261
1539
  // ../../src/solana/tx.ts
1262
- import bs583 from "bs58";
1540
+ import bs584 from "bs58";
1263
1541
  import {
1264
1542
  appendTransactionMessageInstructions,
1265
1543
  compileTransaction,
@@ -1306,11 +1584,11 @@ function signatureBase58ForSigner(transaction, signer2) {
1306
1584
  if (signature === null || signature === void 0) {
1307
1585
  return null;
1308
1586
  }
1309
- return bs583.encode(signature);
1587
+ return bs584.encode(signature);
1310
1588
  }
1311
1589
 
1312
1590
  // ../../src/client/remote-signer-transport.ts
1313
- import bs584 from "bs58";
1591
+ import bs585 from "bs58";
1314
1592
  import nacl2 from "tweetnacl";
1315
1593
  var RemoteSigningError = class extends Error {
1316
1594
  constructor(provider, message, detail = null) {
@@ -1325,7 +1603,7 @@ var RemoteSigningError = class extends Error {
1325
1603
  function ed25519PublicKeyBytes(provider, walletAddress) {
1326
1604
  let bytes;
1327
1605
  try {
1328
- bytes = bs584.decode(walletAddress);
1606
+ bytes = bs585.decode(walletAddress);
1329
1607
  } catch {
1330
1608
  throw new RemoteSigningError(
1331
1609
  provider,
@@ -1341,10 +1619,10 @@ function ed25519PublicKeyBytes(provider, walletAddress) {
1341
1619
  return bytes;
1342
1620
  }
1343
1621
  function verifiedEd25519Signature(params) {
1344
- const publicKey = ed25519PublicKeyBytes(params.provider, params.walletAddress);
1622
+ const publicKey2 = ed25519PublicKeyBytes(params.provider, params.walletAddress);
1345
1623
  const encoded = params.encodedSignature.trim();
1346
1624
  for (const candidate of decodeSignatureCandidates(encoded)) {
1347
- if (nacl2.sign.detached.verify(params.message, candidate, publicKey)) {
1625
+ if (nacl2.sign.detached.verify(params.message, candidate, publicKey2)) {
1348
1626
  return candidate;
1349
1627
  }
1350
1628
  }
@@ -1360,7 +1638,7 @@ function decodeSignatureCandidates(encoded) {
1360
1638
  candidates.push(Uint8Array.from(Buffer.from(hex, "hex")));
1361
1639
  }
1362
1640
  try {
1363
- const fromBase58 = bs584.decode(encoded);
1641
+ const fromBase58 = bs585.decode(encoded);
1364
1642
  if (fromBase58.length === 64) {
1365
1643
  candidates.push(fromBase58);
1366
1644
  }
@@ -1398,8 +1676,8 @@ async function requestVerifiedTransactionSignature(params) {
1398
1676
  `signed transaction is missing the signature for ${transport.walletAddress}`
1399
1677
  );
1400
1678
  }
1401
- const publicKey = params.publicKey ?? ed25519PublicKeyBytes(transport.provider, transport.walletAddress);
1402
- if (!nacl2.sign.detached.verify(params.messageBytes, signature, publicKey)) {
1679
+ const publicKey2 = params.publicKey ?? ed25519PublicKeyBytes(transport.provider, transport.walletAddress);
1680
+ if (!nacl2.sign.detached.verify(params.messageBytes, signature, publicKey2)) {
1403
1681
  throw new RemoteSigningError(
1404
1682
  transport.provider,
1405
1683
  "returned signature does not verify over the requested transaction"
@@ -1421,7 +1699,7 @@ async function externallySignedAgentTransaction(params) {
1421
1699
  });
1422
1700
  return {
1423
1701
  serializedTransaction: attached.serializedBase64,
1424
- agentSignature: bs584.encode(signature)
1702
+ agentSignature: bs585.encode(signature)
1425
1703
  };
1426
1704
  }
1427
1705
  async function providerJsonRequest(params) {
@@ -1465,98 +1743,6 @@ function computeRequestBindingHash(fields) {
1465
1743
  });
1466
1744
  }
1467
1745
 
1468
- // ../../src/lib/associated-token-account.ts
1469
- import { createHash as createHash4 } from "node:crypto";
1470
- import bs585 from "bs58";
1471
- var PDA_MARKER = Buffer.from("ProgramDerivedAddress", "utf8");
1472
- var ED25519_P = (1n << 255n) - 19n;
1473
- var ED25519_D = mod(-121665n * modPow(121666n, ED25519_P - 2n, ED25519_P), ED25519_P);
1474
- function deriveAssociatedTokenAddress(params) {
1475
- const owner = decodePublicKey(params.owner, "owner");
1476
- const mint = decodePublicKey(params.mint ?? SUBLY_VAULT.usdcMint, "mint");
1477
- const tokenProgramId = decodePublicKey(
1478
- params.tokenProgramId ?? SPL_TOKEN_PROGRAM_ID,
1479
- "tokenProgramId"
1480
- );
1481
- const associatedTokenProgramId = decodePublicKey(
1482
- ASSOCIATED_TOKEN_PROGRAM_ID,
1483
- "associatedTokenProgramId"
1484
- );
1485
- for (let bump = 255; bump >= 0; bump -= 1) {
1486
- const address3 = createProgramAddress(
1487
- [owner, tokenProgramId, mint, Uint8Array.of(bump)],
1488
- associatedTokenProgramId
1489
- );
1490
- if (address3 !== null) {
1491
- return bs585.encode(address3);
1492
- }
1493
- }
1494
- throw new Error("Unable to derive associated token account address");
1495
- }
1496
- function createProgramAddress(seeds, programId) {
1497
- const hash = createHash4("sha256");
1498
- for (const seed of seeds) {
1499
- hash.update(seed);
1500
- }
1501
- hash.update(programId);
1502
- hash.update(PDA_MARKER);
1503
- const digest = hash.digest();
1504
- return isEd25519Point(digest) ? null : new Uint8Array(digest);
1505
- }
1506
- function decodePublicKey(value, fieldName) {
1507
- const decoded = bs585.decode(value);
1508
- if (decoded.length !== 32) {
1509
- throw new Error(`${fieldName} must be a 32-byte public key`);
1510
- }
1511
- return decoded;
1512
- }
1513
- function isEd25519Point(bytes) {
1514
- if (bytes.length !== 32) {
1515
- return false;
1516
- }
1517
- const yBytes = Uint8Array.from(bytes);
1518
- yBytes[31] = yBytes[31] & 127;
1519
- const y = littleEndianToBigInt(yBytes);
1520
- if (y >= ED25519_P) {
1521
- return false;
1522
- }
1523
- const ySquared = mod(y * y, ED25519_P);
1524
- const numerator = mod(ySquared - 1n, ED25519_P);
1525
- const denominator = mod(ED25519_D * ySquared + 1n, ED25519_P);
1526
- if (denominator === 0n) {
1527
- return false;
1528
- }
1529
- const xSquared = mod(
1530
- numerator * modPow(denominator, ED25519_P - 2n, ED25519_P),
1531
- ED25519_P
1532
- );
1533
- return xSquared === 0n || modPow(xSquared, (ED25519_P - 1n) / 2n, ED25519_P) === 1n;
1534
- }
1535
- function littleEndianToBigInt(bytes) {
1536
- let value = 0n;
1537
- for (let index = bytes.length - 1; index >= 0; index -= 1) {
1538
- value = (value << 8n) + BigInt(bytes[index]);
1539
- }
1540
- return value;
1541
- }
1542
- function mod(value, modulus) {
1543
- const result = value % modulus;
1544
- return result >= 0n ? result : result + modulus;
1545
- }
1546
- function modPow(base, exponent, modulus) {
1547
- let result = 1n;
1548
- let nextBase = mod(base, modulus);
1549
- let nextExponent = exponent;
1550
- while (nextExponent > 0n) {
1551
- if ((nextExponent & 1n) === 1n) {
1552
- result = mod(result * nextBase, modulus);
1553
- }
1554
- nextBase = mod(nextBase * nextBase, modulus);
1555
- nextExponent >>= 1n;
1556
- }
1557
- return result;
1558
- }
1559
-
1560
1746
  // ../../src/client/transaction-intent-validator.ts
1561
1747
  var COMPUTE_BUDGET_PROGRAM_ID = "ComputeBudget111111111111111111111111111111";
1562
1748
  var SYSTEM_PROGRAM_ID = "11111111111111111111111111111111";
@@ -1699,16 +1885,16 @@ function validatePaymentIntentTransaction(params) {
1699
1885
  if (intent.network !== SOLANA_MAINNET_NETWORK) {
1700
1886
  reject("network_mismatch", "Unsupported network");
1701
1887
  }
1702
- if (intent.vault !== SUBLY_VAULT.address) {
1888
+ if (intent.vault !== policy.vault.address) {
1703
1889
  reject("vault_mismatch", "Unsupported vault");
1704
1890
  }
1705
- if (intent.shareMint !== SUBLY_VAULT.shareMint) {
1891
+ if (intent.shareMint !== policy.vault.shareMint) {
1706
1892
  reject("share_mint_mismatch", "Unsupported share mint");
1707
1893
  }
1708
- if (intent.farm !== SUBLY_VAULT.farm) {
1894
+ if (intent.farm !== policy.vault.farm) {
1709
1895
  reject("farm_mismatch", "Unsupported Kamino farm");
1710
1896
  }
1711
- if (intent.asset !== SUBLY_VAULT.usdcMint) {
1897
+ if (intent.asset !== policy.vault.usdcMint) {
1712
1898
  reject("asset_mismatch", "Only USDC payments are supported");
1713
1899
  }
1714
1900
  if (intent.memo !== intent.paymentId) {
@@ -1824,7 +2010,7 @@ function validateDepositIntentTransaction(params) {
1824
2010
  if (new Date(intent.expiresAt).getTime() <= now) {
1825
2011
  reject("expired", "Deposit intent has expired");
1826
2012
  }
1827
- assertVaultIntentTargets(intent);
2013
+ assertVaultIntentTargets(intent, policy.vault);
1828
2014
  const decoded = decodeIntentTransaction({
1829
2015
  serializedTransaction: params.serializedTransaction,
1830
2016
  ...params.lookupTables === void 0 ? {} : { lookupTables: params.lookupTables }
@@ -1847,6 +2033,9 @@ function validateDepositIntentTransaction(params) {
1847
2033
  case MEMO_PROGRAM_ID:
1848
2034
  break;
1849
2035
  case KVAULT_PROGRAM_ID: {
2036
+ if (sawDeposit) {
2037
+ reject("duplicate_deposit", "A deposit intent authorizes exactly one KVault deposit");
2038
+ }
1850
2039
  if (!bytesStartWith(ix.data, KVAULT_DEPOSIT_DISCRIMINATOR)) {
1851
2040
  reject("unexpected_instruction", "Unexpected KVault instruction in deposit");
1852
2041
  }
@@ -1897,7 +2086,7 @@ function validateWithdrawalIntentTransaction(params) {
1897
2086
  if (new Date(intent.expiresAt).getTime() <= now) {
1898
2087
  reject("expired", "Withdrawal intent has expired");
1899
2088
  }
1900
- assertVaultIntentTargets(intent);
2089
+ assertVaultIntentTargets(intent, policy.vault);
1901
2090
  const expectedDestination = deriveAssociatedTokenAddress({
1902
2091
  owner: intent.wallet,
1903
2092
  mint: intent.asset
@@ -1995,22 +2184,23 @@ function validateWithdrawalIntentTransaction(params) {
1995
2184
  );
1996
2185
  }
1997
2186
  }
1998
- function assertVaultIntentTargets(intent) {
1999
- if (intent.vault !== SUBLY_VAULT.address) {
2187
+ function assertVaultIntentTargets(intent, vault) {
2188
+ if (intent.vault !== vault.address) {
2000
2189
  reject("vault_mismatch", "Unsupported vault");
2001
2190
  }
2002
- if (intent.shareMint !== SUBLY_VAULT.shareMint) {
2191
+ if (intent.shareMint !== vault.shareMint) {
2003
2192
  reject("share_mint_mismatch", "Unsupported share mint");
2004
2193
  }
2005
- if (intent.farm !== SUBLY_VAULT.farm) {
2194
+ if (intent.farm !== vault.farm) {
2006
2195
  reject("farm_mismatch", "Unsupported Kamino farm");
2007
2196
  }
2008
- if (intent.asset !== SUBLY_VAULT.usdcMint) {
2197
+ if (intent.asset !== vault.usdcMint) {
2009
2198
  reject("asset_mismatch", "Only USDC is supported");
2010
2199
  }
2011
2200
  }
2012
2201
  function resolveIntentValidationPolicy(policy) {
2013
2202
  const resolved = {
2203
+ vault: policy?.vault ?? SUBLY_VAULT,
2014
2204
  maxComputeUnitLimit: policy?.maxComputeUnitLimit ?? DEFAULT_MAX_COMPUTE_UNIT_LIMIT,
2015
2205
  maxComputeUnitPriceMicroLamports: policy?.maxComputeUnitPriceMicroLamports ?? DEFAULT_MAX_COMPUTE_UNIT_PRICE_MICRO_LAMPORTS,
2016
2206
  maxTemporaryAccountLamports: policy?.maxTemporaryAccountLamports ?? MAX_TEMP_ACCOUNT_LAMPORTS
@@ -2362,10 +2552,12 @@ function readShortVec(bytes, startOffset) {
2362
2552
 
2363
2553
  // ../../src/client/agent-wallet-signer.ts
2364
2554
  var IntentValidatingAgentWalletSigner = class {
2555
+ vault;
2365
2556
  validationMode = "structured_intent_transaction";
2366
2557
  validationPolicy;
2367
2558
  constructor(validationPolicy) {
2368
- this.validationPolicy = validationPolicy;
2559
+ this.vault = Object.freeze({ ...validationPolicy?.vault ?? SUBLY_VAULT });
2560
+ this.validationPolicy = { ...validationPolicy, vault: this.vault };
2369
2561
  }
2370
2562
  async signPayment(params) {
2371
2563
  this.assertIntentWallet(params.intent.wallet);
@@ -2523,15 +2715,15 @@ async function createCircleSignerTransport(config) {
2523
2715
  const entitySecretCiphertext = async () => {
2524
2716
  if (entityPublicKey === null) {
2525
2717
  const data = await request("GET", "/v1/w3s/config/entity/publicKey");
2526
- const publicKey = data.publicKey;
2527
- if (typeof publicKey !== "string") {
2718
+ const publicKey2 = data.publicKey;
2719
+ if (typeof publicKey2 !== "string") {
2528
2720
  throw new RemoteSigningError(
2529
2721
  PROVIDER,
2530
2722
  "entity public key response has no publicKey",
2531
2723
  data
2532
2724
  );
2533
2725
  }
2534
- entityPublicKey = createPublicKey(publicKey);
2726
+ entityPublicKey = createPublicKey(publicKey2);
2535
2727
  }
2536
2728
  return publicEncrypt(
2537
2729
  {
@@ -2804,14 +2996,34 @@ async function agentWalletSignerFromEnv(env = process.env) {
2804
2996
  }
2805
2997
 
2806
2998
  // ../../src/client/standard-x402-state-store.ts
2807
- import { mkdirSync, readFileSync as readFileSync2, renameSync, writeFileSync } from "node:fs";
2999
+ import { closeSync, mkdirSync, openSync, readFileSync as readFileSync3, renameSync, unlinkSync, writeFileSync } from "node:fs";
2808
3000
  import { basename, dirname, join } from "node:path";
2809
3001
  function fileStandardX402StateStore(path) {
2810
3002
  return {
3003
+ async withExclusiveLock(operation) {
3004
+ mkdirSync(dirname(path), { recursive: true, mode: 448 });
3005
+ const lockPath = `${path}.lock`;
3006
+ let fd;
3007
+ try {
3008
+ fd = openSync(lockPath, "wx", 384);
3009
+ } catch (error) {
3010
+ if (error && typeof error === "object" && "code" in error && error.code === "EEXIST") {
3011
+ throw new Error(`Payment state is locked: ${lockPath}. Another client may be paying. After a crash, stop all clients before removing only this .lock file; preserve the payment state JSON.`);
3012
+ }
3013
+ throw error;
3014
+ }
3015
+ try {
3016
+ writeFileSync(fd, JSON.stringify({ pid: process.pid, startedAt: (/* @__PURE__ */ new Date()).toISOString() }));
3017
+ return await operation();
3018
+ } finally {
3019
+ closeSync(fd);
3020
+ unlinkSync(lockPath);
3021
+ }
3022
+ },
2811
3023
  load() {
2812
3024
  let text;
2813
3025
  try {
2814
- text = readFileSync2(path, "utf8");
3026
+ text = readFileSync3(path, "utf8");
2815
3027
  } catch (error) {
2816
3028
  if (isMissingFileError(error)) {
2817
3029
  return [];
@@ -2833,12 +3045,12 @@ function fileStandardX402StateStore(path) {
2833
3045
  },
2834
3046
  save(records) {
2835
3047
  const directory = dirname(path);
2836
- mkdirSync(directory, { recursive: true });
3048
+ mkdirSync(directory, { recursive: true, mode: 448 });
2837
3049
  const tempPath = join(
2838
3050
  directory,
2839
3051
  `.${basename(path)}.${process.pid}.${Date.now()}.tmp`
2840
3052
  );
2841
- writeFileSync(tempPath, JSON.stringify(records, null, 2));
3053
+ writeFileSync(tempPath, JSON.stringify(records, null, 2), { mode: 384, flag: "wx" });
2842
3054
  renameSync(tempPath, path);
2843
3055
  }
2844
3056
  };
@@ -2874,7 +3086,7 @@ async function svmTransactionSignerFromBundle(bundle2) {
2874
3086
  }
2875
3087
  function remoteSvmTransactionSigner(transport) {
2876
3088
  const signerAddress = address2(transport.walletAddress);
2877
- const publicKey = ed25519PublicKeyBytes(
3089
+ const publicKey2 = ed25519PublicKeyBytes(
2878
3090
  transport.provider,
2879
3091
  transport.walletAddress
2880
3092
  );
@@ -2887,7 +3099,7 @@ function remoteSvmTransactionSigner(transport) {
2887
3099
  transport,
2888
3100
  serializedTransactionBase64: getBase64EncodedWireTransaction2(transaction),
2889
3101
  messageBytes: transaction.messageBytes,
2890
- publicKey
3102
+ publicKey: publicKey2
2891
3103
  });
2892
3104
  dictionaries.push(
2893
3105
  Object.freeze({ [signerAddress]: signature })