@subly_fi/pay 0.6.2 → 0.7.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/README.md +94 -152
- package/dist/budget.js +2267 -0
- package/dist/cli.js +37 -20
- package/dist/deposit.js +243 -58
- package/dist/doctor.js +188 -0
- package/dist/mcp-server.js +1537 -1227
- package/dist/pay.js +437 -225
- package/dist/setup-link.js +238 -55
- package/dist/vaults.js +129 -0
- package/dist/withdraw.js +238 -55
- package/package.json +8 -6
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 ? "" :
|
|
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
|
-
|
|
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
|
|
932
|
+
import { createHash as createHash4 } from "node:crypto";
|
|
659
933
|
|
|
660
934
|
// ../../src/lib/hash.ts
|
|
661
|
-
import { createHash as
|
|
935
|
+
import { createHash as createHash3 } from "node:crypto";
|
|
662
936
|
var EMPTY_BODY_HASH = "sha256-e3b0c44298fc1c149afbf4c8996fb92427ae41e4649b934ca495991b7852b855";
|
|
663
937
|
function sha256TaggedHex(data) {
|
|
664
|
-
return `sha256-${
|
|
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
|
|
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 =
|
|
704
|
-
scheme:
|
|
705
|
-
network:
|
|
706
|
-
asset:
|
|
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:
|
|
709
|
-
resource:
|
|
710
|
-
description:
|
|
711
|
-
mimeType:
|
|
712
|
-
payTo:
|
|
713
|
-
maxTimeoutSeconds:
|
|
714
|
-
extra:
|
|
715
|
-
sellerRequestId:
|
|
716
|
-
seller:
|
|
717
|
-
sellerUsdcAta:
|
|
718
|
-
vault:
|
|
719
|
-
shareMint:
|
|
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 =
|
|
723
|
-
x402Version:
|
|
724
|
-
accepts:
|
|
725
|
-
error:
|
|
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 =
|
|
728
|
-
x402Version:
|
|
729
|
-
scheme:
|
|
730
|
-
network:
|
|
731
|
-
payload:
|
|
732
|
-
paymentId:
|
|
733
|
-
requestBindingHash:
|
|
734
|
-
preparedMessageHash:
|
|
735
|
-
serializedTransaction:
|
|
736
|
-
agentSignature:
|
|
737
|
-
temporarySettlementSignature:
|
|
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
|
|
1041
|
+
import { z as z3 } from "zod";
|
|
768
1042
|
var STANDARD_EXACT_SCHEME = "exact";
|
|
769
|
-
var standardExactRequirementSchema =
|
|
770
|
-
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:
|
|
1046
|
+
network: z3.string().min(1),
|
|
773
1047
|
/** SPL mint (Solana) or token contract (EVM); Subly only pays USDC/Solana. */
|
|
774
|
-
asset:
|
|
1048
|
+
asset: z3.string().min(1),
|
|
775
1049
|
/** Exact price in the asset's atomic units, as a decimal string. */
|
|
776
|
-
amount:
|
|
1050
|
+
amount: z3.string().regex(/^[1-9]\d*$/),
|
|
777
1051
|
/** Recipient wallet; the transfer destination ATA is derived from it. */
|
|
778
|
-
payTo:
|
|
779
|
-
maxTimeoutSeconds:
|
|
780
|
-
extra:
|
|
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:
|
|
1056
|
+
feePayer: z3.string().min(1).optional()
|
|
783
1057
|
}).loose().optional()
|
|
784
1058
|
}).loose();
|
|
785
|
-
var standardPaymentRequiredSchema =
|
|
786
|
-
x402Version:
|
|
787
|
-
accepts:
|
|
788
|
-
error:
|
|
789
|
-
resource:
|
|
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
|
|
932
|
-
|
|
933
|
-
|
|
934
|
-
|
|
935
|
-
|
|
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
|
|
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" &&
|
|
1333
|
+
if (paymentTxSignature !== null && typeof realized.withdrawalId === "string" && realizer.reportPayment !== void 0) {
|
|
1057
1334
|
try {
|
|
1058
|
-
await
|
|
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
|
|
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 =
|
|
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(
|
|
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
|
|
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
|
|
1587
|
+
return bs584.encode(signature);
|
|
1310
1588
|
}
|
|
1311
1589
|
|
|
1312
1590
|
// ../../src/client/remote-signer-transport.ts
|
|
1313
|
-
import
|
|
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 =
|
|
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
|
|
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,
|
|
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 =
|
|
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
|
|
1402
|
-
if (!nacl2.sign.detached.verify(params.messageBytes, signature,
|
|
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:
|
|
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 !==
|
|
1888
|
+
if (intent.vault !== policy.vault.address) {
|
|
1703
1889
|
reject("vault_mismatch", "Unsupported vault");
|
|
1704
1890
|
}
|
|
1705
|
-
if (intent.shareMint !==
|
|
1891
|
+
if (intent.shareMint !== policy.vault.shareMint) {
|
|
1706
1892
|
reject("share_mint_mismatch", "Unsupported share mint");
|
|
1707
1893
|
}
|
|
1708
|
-
if (intent.farm !==
|
|
1894
|
+
if (intent.farm !== policy.vault.farm) {
|
|
1709
1895
|
reject("farm_mismatch", "Unsupported Kamino farm");
|
|
1710
1896
|
}
|
|
1711
|
-
if (intent.asset !==
|
|
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 !==
|
|
2187
|
+
function assertVaultIntentTargets(intent, vault) {
|
|
2188
|
+
if (intent.vault !== vault.address) {
|
|
2000
2189
|
reject("vault_mismatch", "Unsupported vault");
|
|
2001
2190
|
}
|
|
2002
|
-
if (intent.shareMint !==
|
|
2191
|
+
if (intent.shareMint !== vault.shareMint) {
|
|
2003
2192
|
reject("share_mint_mismatch", "Unsupported share mint");
|
|
2004
2193
|
}
|
|
2005
|
-
if (intent.farm !==
|
|
2194
|
+
if (intent.farm !== vault.farm) {
|
|
2006
2195
|
reject("farm_mismatch", "Unsupported Kamino farm");
|
|
2007
2196
|
}
|
|
2008
|
-
if (intent.asset !==
|
|
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.
|
|
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
|
|
2527
|
-
if (typeof
|
|
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(
|
|
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
|
|
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 =
|
|
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
|
|
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 })
|