@subly_fi/pay 0.6.0 → 0.6.2
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/LICENSE +21 -0
- package/README.md +100 -5
- package/dist/cli.js +2 -2
- package/dist/deposit.js +821 -158
- package/dist/mcp-server.js +3138 -2459
- package/dist/pay.js +2630 -1947
- package/dist/setup-link.js +750 -79
- package/dist/withdraw.js +741 -78
- package/package.json +21 -4
package/dist/deposit.js
CHANGED
|
@@ -1,9 +1,132 @@
|
|
|
1
|
+
// ../../src/api/wallet-auth.ts
|
|
2
|
+
import { createHash } from "node:crypto";
|
|
3
|
+
import bs58 from "bs58";
|
|
4
|
+
import nacl from "tweetnacl";
|
|
5
|
+
var WALLET_AUTH_WALLET_HEADER = "x-subly-wallet";
|
|
6
|
+
var WALLET_AUTH_SIGNED_AT_HEADER = "x-subly-signed-at";
|
|
7
|
+
var WALLET_AUTH_SIGNATURE_HEADER = "x-subly-signature";
|
|
8
|
+
function sha256Hex(data) {
|
|
9
|
+
return createHash("sha256").update(data, "utf8").digest("hex");
|
|
10
|
+
}
|
|
11
|
+
function walletAuthMessage(params) {
|
|
12
|
+
return new TextEncoder().encode(
|
|
13
|
+
`subly-api:${params.method.toUpperCase()}:${params.path}:${sha256Hex(
|
|
14
|
+
params.rawBody
|
|
15
|
+
)}:${params.signedAtMs}`
|
|
16
|
+
);
|
|
17
|
+
}
|
|
18
|
+
|
|
19
|
+
// ../../src/client/wallet-auth-headers.ts
|
|
20
|
+
async function walletAuthHeaders(params) {
|
|
21
|
+
const signedAtMs = String(Date.now());
|
|
22
|
+
const message = walletAuthMessage({
|
|
23
|
+
method: params.method,
|
|
24
|
+
path: (() => {
|
|
25
|
+
const url = new URL(params.url);
|
|
26
|
+
return url.pathname + url.search;
|
|
27
|
+
})(),
|
|
28
|
+
rawBody: params.body ?? "",
|
|
29
|
+
signedAtMs
|
|
30
|
+
});
|
|
31
|
+
return {
|
|
32
|
+
[WALLET_AUTH_WALLET_HEADER]: params.signer.walletAddress,
|
|
33
|
+
[WALLET_AUTH_SIGNED_AT_HEADER]: signedAtMs,
|
|
34
|
+
[WALLET_AUTH_SIGNATURE_HEADER]: await params.signer.signApiMessage(message)
|
|
35
|
+
};
|
|
36
|
+
}
|
|
37
|
+
|
|
38
|
+
// ../../src/client/onboarding.ts
|
|
39
|
+
var SELF_SERVE_POLICY_ID = "self-serve";
|
|
40
|
+
var OnboardingError = class extends Error {
|
|
41
|
+
constructor(step, message, detail = null) {
|
|
42
|
+
super(message);
|
|
43
|
+
this.step = step;
|
|
44
|
+
this.detail = detail;
|
|
45
|
+
this.name = "OnboardingError";
|
|
46
|
+
}
|
|
47
|
+
step;
|
|
48
|
+
detail;
|
|
49
|
+
};
|
|
50
|
+
async function ensureWalletOnboarded(params) {
|
|
51
|
+
const fetchImpl = params.fetchImpl ?? fetch;
|
|
52
|
+
const baseUrl = params.relayerBaseUrl.replace(/\/$/, "");
|
|
53
|
+
const post = async (step, path, body) => {
|
|
54
|
+
const url = `${baseUrl}${path}`;
|
|
55
|
+
const serialized = JSON.stringify(body);
|
|
56
|
+
const response = await fetchImpl(url, {
|
|
57
|
+
method: "POST",
|
|
58
|
+
headers: {
|
|
59
|
+
...await walletAuthHeaders({
|
|
60
|
+
signer: params.signer,
|
|
61
|
+
method: "POST",
|
|
62
|
+
url,
|
|
63
|
+
body: serialized
|
|
64
|
+
}),
|
|
65
|
+
"content-type": "application/json"
|
|
66
|
+
},
|
|
67
|
+
body: serialized
|
|
68
|
+
});
|
|
69
|
+
if (response.status !== 200) {
|
|
70
|
+
let detail = null;
|
|
71
|
+
try {
|
|
72
|
+
detail = await response.json();
|
|
73
|
+
} catch {
|
|
74
|
+
detail = null;
|
|
75
|
+
}
|
|
76
|
+
throw new OnboardingError(
|
|
77
|
+
step,
|
|
78
|
+
`wallet onboarding ${step} failed with ${response.status}`,
|
|
79
|
+
detail
|
|
80
|
+
);
|
|
81
|
+
}
|
|
82
|
+
};
|
|
83
|
+
const wallet = params.signer.walletAddress;
|
|
84
|
+
await post("register", "/v1/wallets/agent", {
|
|
85
|
+
wallet,
|
|
86
|
+
signingPolicyId: SELF_SERVE_POLICY_ID,
|
|
87
|
+
signingMode: "non_interactive",
|
|
88
|
+
signerValidationMode: params.signer.validationMode,
|
|
89
|
+
signerProvider: params.signer.provider ?? "local-keypair",
|
|
90
|
+
activateForPayments: true
|
|
91
|
+
});
|
|
92
|
+
await post("sync", `/v1/wallets/${wallet}/sync`, { source: "chain" });
|
|
93
|
+
}
|
|
94
|
+
|
|
95
|
+
// ../../src/client/signer-env.ts
|
|
96
|
+
import { createKeyPairSignerFromBytes as createKeyPairSignerFromBytes2 } from "@solana/kit";
|
|
97
|
+
|
|
98
|
+
// ../../src/solana/keys.ts
|
|
99
|
+
import { readFileSync } from "node:fs";
|
|
100
|
+
import bs582 from "bs58";
|
|
101
|
+
import {
|
|
102
|
+
createKeyPairSignerFromBytes
|
|
103
|
+
} from "@solana/kit";
|
|
104
|
+
function loadSecretKeyBytes(params) {
|
|
105
|
+
const { base58Secret, jsonFilePath, label } = params;
|
|
106
|
+
if (base58Secret !== void 0 && base58Secret.length > 0) {
|
|
107
|
+
const bytes = bs582.decode(base58Secret);
|
|
108
|
+
if (bytes.length !== 64) {
|
|
109
|
+
throw new Error(`${label} base58 secret must decode to 64 bytes`);
|
|
110
|
+
}
|
|
111
|
+
return bytes;
|
|
112
|
+
}
|
|
113
|
+
if (jsonFilePath !== void 0 && jsonFilePath.length > 0) {
|
|
114
|
+
const raw = JSON.parse(readFileSync(jsonFilePath, "utf8"));
|
|
115
|
+
if (!Array.isArray(raw) || raw.length !== 64) {
|
|
116
|
+
throw new Error(`${label} keypair file must be a 64-byte JSON array`);
|
|
117
|
+
}
|
|
118
|
+
return Uint8Array.from(raw);
|
|
119
|
+
}
|
|
120
|
+
throw new Error(`${label} keypair is not configured`);
|
|
121
|
+
}
|
|
122
|
+
|
|
1
123
|
// ../../src/client/agent-wallet-signer.ts
|
|
2
124
|
import { signBytes } from "@solana/kit";
|
|
3
|
-
import
|
|
125
|
+
import bs587 from "bs58";
|
|
126
|
+
import nacl3 from "tweetnacl";
|
|
4
127
|
|
|
5
128
|
// ../../src/solana/tx.ts
|
|
6
|
-
import
|
|
129
|
+
import bs583 from "bs58";
|
|
7
130
|
import {
|
|
8
131
|
appendTransactionMessageInstructions,
|
|
9
132
|
compileTransaction,
|
|
@@ -18,9 +141,9 @@ import {
|
|
|
18
141
|
} from "@solana/kit";
|
|
19
142
|
|
|
20
143
|
// ../../src/lib/hash.ts
|
|
21
|
-
import { createHash } from "node:crypto";
|
|
144
|
+
import { createHash as createHash2 } from "node:crypto";
|
|
22
145
|
function sha256TaggedHex(data) {
|
|
23
|
-
return `sha256-${
|
|
146
|
+
return `sha256-${createHash2("sha256").update(data).digest("hex")}`;
|
|
24
147
|
}
|
|
25
148
|
function stableStringify(value) {
|
|
26
149
|
if (value === null) {
|
|
@@ -46,6 +169,24 @@ function hashStableJson(value) {
|
|
|
46
169
|
function decodeSerializedTransaction(serializedBase64) {
|
|
47
170
|
return getTransactionDecoder().decode(Buffer.from(serializedBase64, "base64"));
|
|
48
171
|
}
|
|
172
|
+
function attachExternalSignatureToTransaction(params) {
|
|
173
|
+
if (!(params.signer in params.transaction.signatures)) {
|
|
174
|
+
throw new Error(
|
|
175
|
+
`transaction does not expect a signature from ${params.signer}`
|
|
176
|
+
);
|
|
177
|
+
}
|
|
178
|
+
const transaction = Object.freeze({
|
|
179
|
+
...params.transaction,
|
|
180
|
+
signatures: Object.freeze({
|
|
181
|
+
...params.transaction.signatures,
|
|
182
|
+
[params.signer]: params.signature
|
|
183
|
+
})
|
|
184
|
+
});
|
|
185
|
+
return {
|
|
186
|
+
serializedBase64: getBase64EncodedWireTransaction(transaction),
|
|
187
|
+
transaction
|
|
188
|
+
};
|
|
189
|
+
}
|
|
49
190
|
async function addSignaturesToSerializedTransaction(params) {
|
|
50
191
|
const decoded = decodeSerializedTransaction(params.serializedBase64);
|
|
51
192
|
const signed = await partiallySignTransaction(params.signers, decoded);
|
|
@@ -59,11 +200,148 @@ function signatureBase58ForSigner(transaction, signer2) {
|
|
|
59
200
|
if (signature === null || signature === void 0) {
|
|
60
201
|
return null;
|
|
61
202
|
}
|
|
62
|
-
return
|
|
203
|
+
return bs583.encode(signature);
|
|
204
|
+
}
|
|
205
|
+
|
|
206
|
+
// ../../src/client/remote-signer-transport.ts
|
|
207
|
+
import bs584 from "bs58";
|
|
208
|
+
import nacl2 from "tweetnacl";
|
|
209
|
+
var RemoteSigningError = class extends Error {
|
|
210
|
+
constructor(provider, message, detail = null) {
|
|
211
|
+
super(`[${provider}] ${message}`);
|
|
212
|
+
this.provider = provider;
|
|
213
|
+
this.detail = detail;
|
|
214
|
+
this.name = "RemoteSigningError";
|
|
215
|
+
}
|
|
216
|
+
provider;
|
|
217
|
+
detail;
|
|
218
|
+
};
|
|
219
|
+
function ed25519PublicKeyBytes(provider, walletAddress) {
|
|
220
|
+
let bytes;
|
|
221
|
+
try {
|
|
222
|
+
bytes = bs584.decode(walletAddress);
|
|
223
|
+
} catch {
|
|
224
|
+
throw new RemoteSigningError(
|
|
225
|
+
provider,
|
|
226
|
+
`wallet address ${walletAddress} is not base58`
|
|
227
|
+
);
|
|
228
|
+
}
|
|
229
|
+
if (bytes.length !== 32) {
|
|
230
|
+
throw new RemoteSigningError(
|
|
231
|
+
provider,
|
|
232
|
+
`wallet address ${walletAddress} is not a 32-byte ed25519 key`
|
|
233
|
+
);
|
|
234
|
+
}
|
|
235
|
+
return bytes;
|
|
236
|
+
}
|
|
237
|
+
function verifiedEd25519Signature(params) {
|
|
238
|
+
const publicKey = ed25519PublicKeyBytes(params.provider, params.walletAddress);
|
|
239
|
+
const encoded = params.encodedSignature.trim();
|
|
240
|
+
for (const candidate of decodeSignatureCandidates(encoded)) {
|
|
241
|
+
if (nacl2.sign.detached.verify(params.message, candidate, publicKey)) {
|
|
242
|
+
return candidate;
|
|
243
|
+
}
|
|
244
|
+
}
|
|
245
|
+
throw new RemoteSigningError(
|
|
246
|
+
params.provider,
|
|
247
|
+
`signature did not verify for wallet ${params.walletAddress}`
|
|
248
|
+
);
|
|
249
|
+
}
|
|
250
|
+
function decodeSignatureCandidates(encoded) {
|
|
251
|
+
const candidates = [];
|
|
252
|
+
const hex = encoded.startsWith("0x") ? encoded.slice(2) : encoded;
|
|
253
|
+
if (/^[0-9a-fA-F]{128}$/.test(hex)) {
|
|
254
|
+
candidates.push(Uint8Array.from(Buffer.from(hex, "hex")));
|
|
255
|
+
}
|
|
256
|
+
try {
|
|
257
|
+
const fromBase58 = bs584.decode(encoded);
|
|
258
|
+
if (fromBase58.length === 64) {
|
|
259
|
+
candidates.push(fromBase58);
|
|
260
|
+
}
|
|
261
|
+
} catch {
|
|
262
|
+
}
|
|
263
|
+
if (/^[A-Za-z0-9+/=_-]+$/.test(encoded)) {
|
|
264
|
+
const fromBase64 = Uint8Array.from(
|
|
265
|
+
Buffer.from(encoded.replace(/-/g, "+").replace(/_/g, "/"), "base64")
|
|
266
|
+
);
|
|
267
|
+
if (fromBase64.length === 64) {
|
|
268
|
+
candidates.push(fromBase64);
|
|
269
|
+
}
|
|
270
|
+
}
|
|
271
|
+
return candidates;
|
|
272
|
+
}
|
|
273
|
+
async function requestVerifiedTransactionSignature(params) {
|
|
274
|
+
const { transport } = params;
|
|
275
|
+
const signedBase64 = await transport.signTransaction(
|
|
276
|
+
params.serializedTransactionBase64
|
|
277
|
+
);
|
|
278
|
+
let returned;
|
|
279
|
+
try {
|
|
280
|
+
returned = decodeSerializedTransaction(signedBase64);
|
|
281
|
+
} catch (error) {
|
|
282
|
+
throw new RemoteSigningError(
|
|
283
|
+
transport.provider,
|
|
284
|
+
"provider returned an undecodable signed transaction",
|
|
285
|
+
error
|
|
286
|
+
);
|
|
287
|
+
}
|
|
288
|
+
const signature = returned.signatures[transport.walletAddress] ?? null;
|
|
289
|
+
if (signature === null) {
|
|
290
|
+
throw new RemoteSigningError(
|
|
291
|
+
transport.provider,
|
|
292
|
+
`signed transaction is missing the signature for ${transport.walletAddress}`
|
|
293
|
+
);
|
|
294
|
+
}
|
|
295
|
+
const publicKey = params.publicKey ?? ed25519PublicKeyBytes(transport.provider, transport.walletAddress);
|
|
296
|
+
if (!nacl2.sign.detached.verify(params.messageBytes, signature, publicKey)) {
|
|
297
|
+
throw new RemoteSigningError(
|
|
298
|
+
transport.provider,
|
|
299
|
+
"returned signature does not verify over the requested transaction"
|
|
300
|
+
);
|
|
301
|
+
}
|
|
302
|
+
return signature;
|
|
303
|
+
}
|
|
304
|
+
async function externallySignedAgentTransaction(params) {
|
|
305
|
+
const original = decodeSerializedTransaction(params.serializedTransaction);
|
|
306
|
+
const signature = await requestVerifiedTransactionSignature({
|
|
307
|
+
transport: params.transport,
|
|
308
|
+
serializedTransactionBase64: params.serializedTransaction,
|
|
309
|
+
messageBytes: original.messageBytes
|
|
310
|
+
});
|
|
311
|
+
const attached = attachExternalSignatureToTransaction({
|
|
312
|
+
transaction: original,
|
|
313
|
+
signer: params.transport.walletAddress,
|
|
314
|
+
signature
|
|
315
|
+
});
|
|
316
|
+
return {
|
|
317
|
+
serializedTransaction: attached.serializedBase64,
|
|
318
|
+
agentSignature: bs584.encode(signature)
|
|
319
|
+
};
|
|
320
|
+
}
|
|
321
|
+
async function providerJsonRequest(params) {
|
|
322
|
+
const response = await params.fetchImpl(`${params.baseUrl}${params.path}`, {
|
|
323
|
+
method: params.method,
|
|
324
|
+
headers: { ...params.headers, "content-type": "application/json" },
|
|
325
|
+
...params.body === void 0 ? {} : { body: JSON.stringify(params.body) }
|
|
326
|
+
});
|
|
327
|
+
let json = null;
|
|
328
|
+
try {
|
|
329
|
+
json = await response.json();
|
|
330
|
+
} catch {
|
|
331
|
+
json = null;
|
|
332
|
+
}
|
|
333
|
+
if (!response.ok) {
|
|
334
|
+
throw new RemoteSigningError(
|
|
335
|
+
params.provider,
|
|
336
|
+
`${params.method} ${params.path} failed with ${response.status}`,
|
|
337
|
+
json
|
|
338
|
+
);
|
|
339
|
+
}
|
|
340
|
+
return json;
|
|
63
341
|
}
|
|
64
342
|
|
|
65
343
|
// ../../src/client/transaction-intent-validator.ts
|
|
66
|
-
import
|
|
344
|
+
import bs586 from "bs58";
|
|
67
345
|
import { getCompiledTransactionMessageDecoder } from "@solana/kit";
|
|
68
346
|
|
|
69
347
|
// ../../src/config/constants.ts
|
|
@@ -71,14 +349,30 @@ var PAYMENT_SCHEME = "subly-yield-exact";
|
|
|
71
349
|
var SOLANA_MAINNET_NETWORK = "solana:5eykt4UsFv8P8NJdTREpY1vzqKqZKvdp";
|
|
72
350
|
var SPL_TOKEN_PROGRAM_ID = "TokenkegQfeZyiNwAJbNbGKPFXCWuBvf9Ss623VQ5DA";
|
|
73
351
|
var ASSOCIATED_TOKEN_PROGRAM_ID = "ATokenGPvbdGVxr1b2hvZbsiqW5xWH25efTNsLJA8knL";
|
|
352
|
+
var envOr = (name, fallback) => {
|
|
353
|
+
const value = process.env[name]?.trim();
|
|
354
|
+
return value ? value : fallback;
|
|
355
|
+
};
|
|
74
356
|
var SUBLY_VAULT = {
|
|
75
357
|
name: "Subly USDC Payment Vault Alpha",
|
|
76
|
-
address:
|
|
358
|
+
address: envOr(
|
|
359
|
+
"SUBLY_VAULT_ADDRESS",
|
|
360
|
+
"5kfkpQZ6AkQgizHVThqkxD4J3db2i7pE3mHdPNRbx7jr"
|
|
361
|
+
),
|
|
77
362
|
programId: "KvauGMspG5k6rtzrqqn7WNn3oZdyKqLKwK2XWQ8FLjd",
|
|
78
|
-
usdcMint:
|
|
79
|
-
|
|
363
|
+
usdcMint: envOr(
|
|
364
|
+
"SUBLY_VAULT_USDC_MINT",
|
|
365
|
+
"EPjFWdd5AufqSSqeM2qN1xzybapC8G4wEGGkZwyTDt1v"
|
|
366
|
+
),
|
|
367
|
+
shareMint: envOr(
|
|
368
|
+
"SUBLY_VAULT_SHARE_MINT",
|
|
369
|
+
"7hGX49So539MU9Rrah8nBNVYXswWVwEJvgWNYeBDYq3a"
|
|
370
|
+
),
|
|
80
371
|
lookupTable: "7UbXhDnpK7WVnwsfivzQRENoqKqAULQ5s19gS1xJrQEo",
|
|
81
|
-
farm:
|
|
372
|
+
farm: envOr(
|
|
373
|
+
"SUBLY_VAULT_FARM",
|
|
374
|
+
"E2Ct77LowkDAH1T9ubwPpb84pU2GSGrUdgH3KeTTpLX"
|
|
375
|
+
)
|
|
82
376
|
};
|
|
83
377
|
var USDC_DECIMALS = 6;
|
|
84
378
|
|
|
@@ -98,8 +392,8 @@ function computeRequestBindingHash(fields) {
|
|
|
98
392
|
}
|
|
99
393
|
|
|
100
394
|
// ../../src/lib/associated-token-account.ts
|
|
101
|
-
import { createHash as
|
|
102
|
-
import
|
|
395
|
+
import { createHash as createHash3 } from "node:crypto";
|
|
396
|
+
import bs585 from "bs58";
|
|
103
397
|
var PDA_MARKER = Buffer.from("ProgramDerivedAddress", "utf8");
|
|
104
398
|
var ED25519_P = (1n << 255n) - 19n;
|
|
105
399
|
var ED25519_D = mod(-121665n * modPow(121666n, ED25519_P - 2n, ED25519_P), ED25519_P);
|
|
@@ -120,13 +414,13 @@ function deriveAssociatedTokenAddress(params) {
|
|
|
120
414
|
associatedTokenProgramId
|
|
121
415
|
);
|
|
122
416
|
if (address2 !== null) {
|
|
123
|
-
return
|
|
417
|
+
return bs585.encode(address2);
|
|
124
418
|
}
|
|
125
419
|
}
|
|
126
420
|
throw new Error("Unable to derive associated token account address");
|
|
127
421
|
}
|
|
128
422
|
function createProgramAddress(seeds, programId) {
|
|
129
|
-
const hash =
|
|
423
|
+
const hash = createHash3("sha256");
|
|
130
424
|
for (const seed of seeds) {
|
|
131
425
|
hash.update(seed);
|
|
132
426
|
}
|
|
@@ -136,7 +430,7 @@ function createProgramAddress(seeds, programId) {
|
|
|
136
430
|
return isEd25519Point(digest) ? null : new Uint8Array(digest);
|
|
137
431
|
}
|
|
138
432
|
function decodePublicKey(value, fieldName) {
|
|
139
|
-
const decoded =
|
|
433
|
+
const decoded = bs585.decode(value);
|
|
140
434
|
if (decoded.length !== 32) {
|
|
141
435
|
throw new Error(`${fieldName} must be a 32-byte public key`);
|
|
142
436
|
}
|
|
@@ -337,6 +631,9 @@ function validatePaymentIntentTransaction(params) {
|
|
|
337
631
|
if (intent.shareMint !== SUBLY_VAULT.shareMint) {
|
|
338
632
|
reject("share_mint_mismatch", "Unsupported share mint");
|
|
339
633
|
}
|
|
634
|
+
if (intent.farm !== SUBLY_VAULT.farm) {
|
|
635
|
+
reject("farm_mismatch", "Unsupported Kamino farm");
|
|
636
|
+
}
|
|
340
637
|
if (intent.asset !== SUBLY_VAULT.usdcMint) {
|
|
341
638
|
reject("asset_mismatch", "Only USDC payments are supported");
|
|
342
639
|
}
|
|
@@ -405,7 +702,7 @@ function validatePaymentIntentTransaction(params) {
|
|
|
405
702
|
expectComputeBudgetPair(ixs, policy);
|
|
406
703
|
expectCreateTemporaryAccount(ixs, intent, policy);
|
|
407
704
|
expectInitializeTemporaryAccount(ixs, intent);
|
|
408
|
-
consumeFarmInstructions(ixs, intent
|
|
705
|
+
consumeFarmInstructions(ixs, intent);
|
|
409
706
|
expectKvaultWithdraw(ixs, {
|
|
410
707
|
wallet: intent.wallet,
|
|
411
708
|
vault: intent.vault,
|
|
@@ -548,13 +845,27 @@ function validateWithdrawalIntentTransaction(params) {
|
|
|
548
845
|
reject("fee_payer_mismatch", "Transaction fee payer is not the Subly sponsor");
|
|
549
846
|
}
|
|
550
847
|
let sawWithdraw = false;
|
|
848
|
+
let farmUserState = null;
|
|
849
|
+
let farmInstructionCount = 0;
|
|
551
850
|
for (const ix of decoded.instructions) {
|
|
552
851
|
switch (ix.programAddress) {
|
|
553
852
|
case COMPUTE_BUDGET_PROGRAM_ID:
|
|
554
853
|
validateComputeBudgetInstruction(ix, policy);
|
|
555
854
|
break;
|
|
556
855
|
case MEMO_PROGRAM_ID:
|
|
856
|
+
break;
|
|
557
857
|
case KAMINO_FARMS_PROGRAM_ID:
|
|
858
|
+
farmInstructionCount += 1;
|
|
859
|
+
if (farmInstructionCount === 1) {
|
|
860
|
+
farmUserState = validateFarmUnstakeInstruction(ix, intent);
|
|
861
|
+
} else if (farmInstructionCount === 2) {
|
|
862
|
+
validateFarmWithdrawInstruction(ix, intent, farmUserState);
|
|
863
|
+
} else {
|
|
864
|
+
reject(
|
|
865
|
+
"farm_instruction_mismatch",
|
|
866
|
+
"Withdrawal may contain only one farm unstake and one farm withdrawal"
|
|
867
|
+
);
|
|
868
|
+
}
|
|
558
869
|
break;
|
|
559
870
|
case ASSOCIATED_TOKEN_PROGRAM_ID2:
|
|
560
871
|
expectAtaCreateForOwner(ix, intent.wallet);
|
|
@@ -575,6 +886,12 @@ function validateWithdrawalIntentTransaction(params) {
|
|
|
575
886
|
break;
|
|
576
887
|
}
|
|
577
888
|
case KVAULT_PROGRAM_ID: {
|
|
889
|
+
if (sawWithdraw) {
|
|
890
|
+
reject(
|
|
891
|
+
"withdraw_mismatch",
|
|
892
|
+
"Withdrawal may contain only one KVault withdraw instruction"
|
|
893
|
+
);
|
|
894
|
+
}
|
|
578
895
|
validateKvaultWithdrawInstruction(ix, {
|
|
579
896
|
wallet: intent.wallet,
|
|
580
897
|
vault: intent.vault,
|
|
@@ -597,6 +914,12 @@ function validateWithdrawalIntentTransaction(params) {
|
|
|
597
914
|
if (!sawWithdraw) {
|
|
598
915
|
reject("missing_instruction", "Withdrawal transaction has no KVault withdraw");
|
|
599
916
|
}
|
|
917
|
+
if (farmInstructionCount === 1) {
|
|
918
|
+
reject(
|
|
919
|
+
"farm_instruction_mismatch",
|
|
920
|
+
"A farm unstake must be followed by a farm withdrawal"
|
|
921
|
+
);
|
|
922
|
+
}
|
|
600
923
|
}
|
|
601
924
|
function assertVaultIntentTargets(intent) {
|
|
602
925
|
if (intent.vault !== SUBLY_VAULT.address) {
|
|
@@ -605,6 +928,9 @@ function assertVaultIntentTargets(intent) {
|
|
|
605
928
|
if (intent.shareMint !== SUBLY_VAULT.shareMint) {
|
|
606
929
|
reject("share_mint_mismatch", "Unsupported share mint");
|
|
607
930
|
}
|
|
931
|
+
if (intent.farm !== SUBLY_VAULT.farm) {
|
|
932
|
+
reject("farm_mismatch", "Unsupported Kamino farm");
|
|
933
|
+
}
|
|
608
934
|
if (intent.asset !== SUBLY_VAULT.usdcMint) {
|
|
609
935
|
reject("asset_mismatch", "Only USDC is supported");
|
|
610
936
|
}
|
|
@@ -677,7 +1003,7 @@ function expectCreateTemporaryAccount(ixs, intent, policy) {
|
|
|
677
1003
|
}
|
|
678
1004
|
const lamports = readU64LE(ix.data, 4);
|
|
679
1005
|
const space = readU64LE(ix.data, 12);
|
|
680
|
-
const owner =
|
|
1006
|
+
const owner = bs586.encode(ix.data.subarray(20, 52));
|
|
681
1007
|
if (space !== 165n) {
|
|
682
1008
|
reject("temp_account_mismatch", "Temporary account space must be 165 bytes");
|
|
683
1009
|
}
|
|
@@ -699,7 +1025,7 @@ function expectInitializeTemporaryAccount(ixs, intent) {
|
|
|
699
1025
|
if (ix === void 0 || ix.programAddress !== SPL_TOKEN_PROGRAM_ID || ix.data[0] !== 18) {
|
|
700
1026
|
reject("temp_account_mismatch", "Expected InitializeAccount3 instruction");
|
|
701
1027
|
}
|
|
702
|
-
const owner =
|
|
1028
|
+
const owner = bs586.encode(ix.data.subarray(1, 33));
|
|
703
1029
|
if (owner !== intent.wallet) {
|
|
704
1030
|
reject(
|
|
705
1031
|
"temp_account_mismatch",
|
|
@@ -713,15 +1039,90 @@ function expectInitializeTemporaryAccount(ixs, intent) {
|
|
|
713
1039
|
reject("temp_account_mismatch", "Temporary account mint must be USDC");
|
|
714
1040
|
}
|
|
715
1041
|
}
|
|
716
|
-
function consumeFarmInstructions(ixs,
|
|
717
|
-
|
|
718
|
-
|
|
719
|
-
|
|
720
|
-
|
|
721
|
-
|
|
722
|
-
|
|
723
|
-
|
|
724
|
-
|
|
1042
|
+
function consumeFarmInstructions(ixs, intent) {
|
|
1043
|
+
if (ixs[0]?.programAddress !== KAMINO_FARMS_PROGRAM_ID) {
|
|
1044
|
+
return;
|
|
1045
|
+
}
|
|
1046
|
+
const unstake = ixs.shift();
|
|
1047
|
+
const userState = validateFarmUnstakeInstruction(unstake, intent);
|
|
1048
|
+
if (ixs[0]?.programAddress !== KAMINO_FARMS_PROGRAM_ID) {
|
|
1049
|
+
reject(
|
|
1050
|
+
"farm_instruction_mismatch",
|
|
1051
|
+
"A farm unstake must be followed by a farm withdrawal"
|
|
1052
|
+
);
|
|
1053
|
+
}
|
|
1054
|
+
const withdraw = ixs.shift();
|
|
1055
|
+
validateFarmWithdrawInstruction(withdraw, intent, userState);
|
|
1056
|
+
if (ixs[0]?.programAddress === KAMINO_FARMS_PROGRAM_ID) {
|
|
1057
|
+
reject(
|
|
1058
|
+
"farm_instruction_mismatch",
|
|
1059
|
+
"Payment may contain only one farm unstake and one farm withdrawal"
|
|
1060
|
+
);
|
|
1061
|
+
}
|
|
1062
|
+
}
|
|
1063
|
+
var KAMINO_FARMS_UNSTAKE_DISCRIMINATOR = Uint8Array.from([
|
|
1064
|
+
90,
|
|
1065
|
+
95,
|
|
1066
|
+
107,
|
|
1067
|
+
42,
|
|
1068
|
+
205,
|
|
1069
|
+
124,
|
|
1070
|
+
50,
|
|
1071
|
+
225
|
|
1072
|
+
]);
|
|
1073
|
+
var KAMINO_FARMS_WITHDRAW_UNSTAKED_DISCRIMINATOR = Uint8Array.from([
|
|
1074
|
+
36,
|
|
1075
|
+
102,
|
|
1076
|
+
187,
|
|
1077
|
+
49,
|
|
1078
|
+
220,
|
|
1079
|
+
36,
|
|
1080
|
+
132,
|
|
1081
|
+
67
|
|
1082
|
+
]);
|
|
1083
|
+
function validateFarmUnstakeInstruction(ix, intent) {
|
|
1084
|
+
if (ix.programAddress !== KAMINO_FARMS_PROGRAM_ID || !bytesStartWith(ix.data, KAMINO_FARMS_UNSTAKE_DISCRIMINATOR) || ix.data.length !== 24 || readU128LE(ix.data, 8) <= 0n) {
|
|
1085
|
+
reject(
|
|
1086
|
+
"farm_instruction_mismatch",
|
|
1087
|
+
"Expected a non-zero Kamino farm unstake instruction"
|
|
1088
|
+
);
|
|
1089
|
+
}
|
|
1090
|
+
if (ix.accounts.length !== 4) {
|
|
1091
|
+
reject(
|
|
1092
|
+
"farm_instruction_mismatch",
|
|
1093
|
+
"Farm unstake account list is not canonical"
|
|
1094
|
+
);
|
|
1095
|
+
}
|
|
1096
|
+
if (ix.accounts[0] !== intent.wallet) {
|
|
1097
|
+
reject("farm_instruction_mismatch", "Farm unstake owner must be the agent wallet");
|
|
1098
|
+
}
|
|
1099
|
+
if (ix.accounts[2] !== intent.farm) {
|
|
1100
|
+
reject("farm_instruction_mismatch", "Farm unstake target is not the approved farm");
|
|
1101
|
+
}
|
|
1102
|
+
return ix.accounts[1];
|
|
1103
|
+
}
|
|
1104
|
+
function validateFarmWithdrawInstruction(ix, intent, expectedUserState) {
|
|
1105
|
+
if (ix.programAddress !== KAMINO_FARMS_PROGRAM_ID || !bytesStartWith(ix.data, KAMINO_FARMS_WITHDRAW_UNSTAKED_DISCRIMINATOR) || ix.data.length !== 8) {
|
|
1106
|
+
reject(
|
|
1107
|
+
"farm_instruction_mismatch",
|
|
1108
|
+
"Expected a canonical Kamino farm withdrawal instruction"
|
|
1109
|
+
);
|
|
1110
|
+
}
|
|
1111
|
+
if (ix.accounts.length !== 7) {
|
|
1112
|
+
reject(
|
|
1113
|
+
"farm_instruction_mismatch",
|
|
1114
|
+
"Farm withdrawal account list is not canonical"
|
|
1115
|
+
);
|
|
1116
|
+
}
|
|
1117
|
+
const expectedSharesAta = deriveAssociatedTokenAddress({
|
|
1118
|
+
owner: intent.wallet,
|
|
1119
|
+
mint: intent.shareMint
|
|
1120
|
+
});
|
|
1121
|
+
if (ix.accounts[0] !== intent.wallet || ix.accounts[1] !== expectedUserState || ix.accounts[2] !== intent.farm || ix.accounts[3] !== expectedSharesAta || ix.accounts[6] !== SPL_TOKEN_PROGRAM_ID) {
|
|
1122
|
+
reject(
|
|
1123
|
+
"farm_instruction_mismatch",
|
|
1124
|
+
"Farm withdrawal must return the approved vault shares to the agent wallet"
|
|
1125
|
+
);
|
|
725
1126
|
}
|
|
726
1127
|
}
|
|
727
1128
|
function expectKvaultWithdraw(ixs, expectation) {
|
|
@@ -856,6 +1257,16 @@ function readU32LE(data, offset) {
|
|
|
856
1257
|
}
|
|
857
1258
|
return Buffer.from(data.subarray(offset, offset + 4)).readUInt32LE(0);
|
|
858
1259
|
}
|
|
1260
|
+
function readU128LE(data, offset) {
|
|
1261
|
+
if (data.length < offset + 16) {
|
|
1262
|
+
reject("invalid_transaction_encoding", "Instruction data too short for u128");
|
|
1263
|
+
}
|
|
1264
|
+
let value = 0n;
|
|
1265
|
+
for (let index = 0; index < 16; index += 1) {
|
|
1266
|
+
value |= BigInt(data[offset + index]) << BigInt(index * 8);
|
|
1267
|
+
}
|
|
1268
|
+
return value;
|
|
1269
|
+
}
|
|
859
1270
|
function readShortVec(bytes, startOffset) {
|
|
860
1271
|
let value = 0;
|
|
861
1272
|
let shift = 0;
|
|
@@ -876,55 +1287,55 @@ function readShortVec(bytes, startOffset) {
|
|
|
876
1287
|
}
|
|
877
1288
|
|
|
878
1289
|
// ../../src/client/agent-wallet-signer.ts
|
|
879
|
-
var
|
|
1290
|
+
var IntentValidatingAgentWalletSigner = class {
|
|
880
1291
|
validationMode = "structured_intent_transaction";
|
|
881
|
-
keyPairSigner;
|
|
882
1292
|
validationPolicy;
|
|
883
|
-
constructor(
|
|
884
|
-
this.keyPairSigner = keyPairSigner2;
|
|
1293
|
+
constructor(validationPolicy) {
|
|
885
1294
|
this.validationPolicy = validationPolicy;
|
|
886
1295
|
}
|
|
887
|
-
get walletAddress() {
|
|
888
|
-
return this.keyPairSigner.address;
|
|
889
|
-
}
|
|
890
1296
|
async signPayment(params) {
|
|
891
1297
|
this.assertIntentWallet(params.intent.wallet);
|
|
892
|
-
validatePaymentIntentTransaction({
|
|
893
|
-
...params,
|
|
894
|
-
...this.validationPolicy === void 0 ? {} : { policy: this.validationPolicy }
|
|
895
|
-
});
|
|
1298
|
+
validatePaymentIntentTransaction({ ...params, ...this.policySpread() });
|
|
896
1299
|
return this.sign(params.serializedTransaction);
|
|
897
1300
|
}
|
|
898
1301
|
async signDeposit(params) {
|
|
899
1302
|
this.assertIntentWallet(params.intent.wallet);
|
|
900
|
-
validateDepositIntentTransaction({
|
|
901
|
-
...params,
|
|
902
|
-
...this.validationPolicy === void 0 ? {} : { policy: this.validationPolicy }
|
|
903
|
-
});
|
|
1303
|
+
validateDepositIntentTransaction({ ...params, ...this.policySpread() });
|
|
904
1304
|
return this.sign(params.serializedTransaction);
|
|
905
1305
|
}
|
|
906
1306
|
async signWithdrawal(params) {
|
|
907
1307
|
this.assertIntentWallet(params.intent.wallet);
|
|
908
|
-
validateWithdrawalIntentTransaction({
|
|
909
|
-
...params,
|
|
910
|
-
...this.validationPolicy === void 0 ? {} : { policy: this.validationPolicy }
|
|
911
|
-
});
|
|
1308
|
+
validateWithdrawalIntentTransaction({ ...params, ...this.policySpread() });
|
|
912
1309
|
return this.sign(params.serializedTransaction);
|
|
913
1310
|
}
|
|
1311
|
+
policySpread() {
|
|
1312
|
+
return this.validationPolicy === void 0 ? {} : { policy: this.validationPolicy };
|
|
1313
|
+
}
|
|
914
1314
|
assertIntentWallet(wallet) {
|
|
915
|
-
if (wallet !== this.
|
|
1315
|
+
if (wallet !== this.walletAddress) {
|
|
916
1316
|
throw new IntentValidationError(
|
|
917
1317
|
"wallet_mismatch",
|
|
918
1318
|
"Intent wallet does not match this signer's wallet"
|
|
919
1319
|
);
|
|
920
1320
|
}
|
|
921
1321
|
}
|
|
1322
|
+
};
|
|
1323
|
+
var LocalKeypairAgentWalletSigner = class extends IntentValidatingAgentWalletSigner {
|
|
1324
|
+
provider = "local-keypair";
|
|
1325
|
+
keyPairSigner;
|
|
1326
|
+
constructor(keyPairSigner, validationPolicy) {
|
|
1327
|
+
super(validationPolicy);
|
|
1328
|
+
this.keyPairSigner = keyPairSigner;
|
|
1329
|
+
}
|
|
1330
|
+
get walletAddress() {
|
|
1331
|
+
return this.keyPairSigner.address;
|
|
1332
|
+
}
|
|
922
1333
|
async signApiMessage(message) {
|
|
923
1334
|
const signature = await signBytes(
|
|
924
1335
|
this.keyPairSigner.keyPair.privateKey,
|
|
925
1336
|
message
|
|
926
1337
|
);
|
|
927
|
-
return
|
|
1338
|
+
return bs587.encode(signature);
|
|
928
1339
|
}
|
|
929
1340
|
async sign(serializedTransaction) {
|
|
930
1341
|
const { serializedBase64, transaction } = await addSignaturesToSerializedTransaction({
|
|
@@ -944,96 +1355,378 @@ var LocalKeypairAgentWalletSigner = class {
|
|
|
944
1355
|
return { serializedTransaction: serializedBase64, agentSignature };
|
|
945
1356
|
}
|
|
946
1357
|
};
|
|
1358
|
+
var RemoteAgentWalletSigner = class extends IntentValidatingAgentWalletSigner {
|
|
1359
|
+
transport;
|
|
1360
|
+
publicKey;
|
|
1361
|
+
constructor(transport, validationPolicy) {
|
|
1362
|
+
super(validationPolicy);
|
|
1363
|
+
this.transport = transport;
|
|
1364
|
+
this.publicKey = ed25519PublicKeyBytes(
|
|
1365
|
+
transport.provider,
|
|
1366
|
+
transport.walletAddress
|
|
1367
|
+
);
|
|
1368
|
+
}
|
|
1369
|
+
get walletAddress() {
|
|
1370
|
+
return this.transport.walletAddress;
|
|
1371
|
+
}
|
|
1372
|
+
get provider() {
|
|
1373
|
+
return this.transport.provider;
|
|
1374
|
+
}
|
|
1375
|
+
async signApiMessage(message) {
|
|
1376
|
+
const signature = await this.transport.signMessage(message);
|
|
1377
|
+
if (!nacl3.sign.detached.verify(message, signature, this.publicKey)) {
|
|
1378
|
+
throw new RemoteSigningError(
|
|
1379
|
+
this.transport.provider,
|
|
1380
|
+
"message signature did not verify for the agent wallet"
|
|
1381
|
+
);
|
|
1382
|
+
}
|
|
1383
|
+
return bs587.encode(signature);
|
|
1384
|
+
}
|
|
1385
|
+
sign(serializedTransaction) {
|
|
1386
|
+
return externallySignedAgentTransaction({
|
|
1387
|
+
transport: this.transport,
|
|
1388
|
+
serializedTransaction
|
|
1389
|
+
});
|
|
1390
|
+
}
|
|
1391
|
+
};
|
|
947
1392
|
|
|
948
|
-
// ../../src/
|
|
949
|
-
import {
|
|
950
|
-
|
|
951
|
-
|
|
952
|
-
|
|
953
|
-
|
|
954
|
-
var
|
|
955
|
-
|
|
956
|
-
|
|
957
|
-
}
|
|
958
|
-
|
|
959
|
-
|
|
960
|
-
|
|
961
|
-
|
|
962
|
-
|
|
963
|
-
);
|
|
964
|
-
|
|
965
|
-
|
|
966
|
-
|
|
967
|
-
|
|
968
|
-
|
|
969
|
-
|
|
970
|
-
|
|
971
|
-
|
|
972
|
-
|
|
973
|
-
|
|
974
|
-
|
|
1393
|
+
// ../../src/client/signer-transports/circle.ts
|
|
1394
|
+
import {
|
|
1395
|
+
constants,
|
|
1396
|
+
createPublicKey,
|
|
1397
|
+
publicEncrypt
|
|
1398
|
+
} from "node:crypto";
|
|
1399
|
+
var PROVIDER = "circle";
|
|
1400
|
+
var DEFAULT_BASE_URL = "https://api.circle.com";
|
|
1401
|
+
async function createCircleSignerTransport(config) {
|
|
1402
|
+
if (!/^[0-9a-fA-F]{64}$/.test(config.entitySecret)) {
|
|
1403
|
+
throw new RemoteSigningError(
|
|
1404
|
+
PROVIDER,
|
|
1405
|
+
"entity secret must be 32 bytes of hex (64 hex chars)"
|
|
1406
|
+
);
|
|
1407
|
+
}
|
|
1408
|
+
const baseUrl = (config.baseUrl ?? DEFAULT_BASE_URL).replace(/\/$/, "");
|
|
1409
|
+
const fetchImpl = config.fetchImpl ?? fetch;
|
|
1410
|
+
const request = async (method, path, body) => {
|
|
1411
|
+
const json = await providerJsonRequest({
|
|
1412
|
+
provider: PROVIDER,
|
|
1413
|
+
fetchImpl,
|
|
1414
|
+
baseUrl,
|
|
1415
|
+
path,
|
|
1416
|
+
method,
|
|
1417
|
+
headers: { authorization: `Bearer ${config.apiKey}` },
|
|
1418
|
+
body
|
|
1419
|
+
});
|
|
1420
|
+
const data = json?.data;
|
|
1421
|
+
if (data === void 0) {
|
|
1422
|
+
throw new RemoteSigningError(
|
|
1423
|
+
PROVIDER,
|
|
1424
|
+
`${method} ${path} returned no data envelope`,
|
|
1425
|
+
json
|
|
1426
|
+
);
|
|
1427
|
+
}
|
|
1428
|
+
return data;
|
|
1429
|
+
};
|
|
1430
|
+
const walletData = await request("GET", `/v1/w3s/wallets/${config.walletId}`);
|
|
1431
|
+
const wallet = walletData.wallet;
|
|
1432
|
+
if (wallet?.address === void 0) {
|
|
1433
|
+
throw new RemoteSigningError(
|
|
1434
|
+
PROVIDER,
|
|
1435
|
+
`wallet ${config.walletId} has no address`,
|
|
1436
|
+
walletData
|
|
1437
|
+
);
|
|
1438
|
+
}
|
|
1439
|
+
if (wallet.blockchain !== "SOL") {
|
|
1440
|
+
throw new RemoteSigningError(
|
|
1441
|
+
PROVIDER,
|
|
1442
|
+
`wallet ${config.walletId} is on ${String(
|
|
1443
|
+
wallet.blockchain
|
|
1444
|
+
)}, expected SOL (Solana mainnet)`
|
|
1445
|
+
);
|
|
1446
|
+
}
|
|
1447
|
+
const walletAddress = wallet.address;
|
|
1448
|
+
let entityPublicKey = null;
|
|
1449
|
+
const entitySecretCiphertext = async () => {
|
|
1450
|
+
if (entityPublicKey === null) {
|
|
1451
|
+
const data = await request("GET", "/v1/w3s/config/entity/publicKey");
|
|
1452
|
+
const publicKey = data.publicKey;
|
|
1453
|
+
if (typeof publicKey !== "string") {
|
|
1454
|
+
throw new RemoteSigningError(
|
|
1455
|
+
PROVIDER,
|
|
1456
|
+
"entity public key response has no publicKey",
|
|
1457
|
+
data
|
|
1458
|
+
);
|
|
1459
|
+
}
|
|
1460
|
+
entityPublicKey = createPublicKey(publicKey);
|
|
1461
|
+
}
|
|
1462
|
+
return publicEncrypt(
|
|
1463
|
+
{
|
|
1464
|
+
key: entityPublicKey,
|
|
1465
|
+
padding: constants.RSA_PKCS1_OAEP_PADDING,
|
|
1466
|
+
oaepHash: "sha256"
|
|
1467
|
+
},
|
|
1468
|
+
Buffer.from(config.entitySecret, "hex")
|
|
1469
|
+
).toString("base64");
|
|
1470
|
+
};
|
|
975
1471
|
return {
|
|
976
|
-
|
|
977
|
-
|
|
978
|
-
|
|
1472
|
+
provider: PROVIDER,
|
|
1473
|
+
walletAddress,
|
|
1474
|
+
async signMessage(message) {
|
|
1475
|
+
const data = await request("POST", "/v1/w3s/developer/sign/message", {
|
|
1476
|
+
walletId: config.walletId,
|
|
1477
|
+
message: `0x${Buffer.from(message).toString("hex")}`,
|
|
1478
|
+
encodedByHex: true,
|
|
1479
|
+
entitySecretCiphertext: await entitySecretCiphertext()
|
|
1480
|
+
});
|
|
1481
|
+
const signature = data.signature;
|
|
1482
|
+
if (typeof signature !== "string") {
|
|
1483
|
+
throw new RemoteSigningError(
|
|
1484
|
+
PROVIDER,
|
|
1485
|
+
"sign/message returned no signature",
|
|
1486
|
+
data
|
|
1487
|
+
);
|
|
1488
|
+
}
|
|
1489
|
+
return verifiedEd25519Signature({
|
|
1490
|
+
provider: PROVIDER,
|
|
1491
|
+
encodedSignature: signature,
|
|
1492
|
+
message,
|
|
1493
|
+
walletAddress
|
|
1494
|
+
});
|
|
1495
|
+
},
|
|
1496
|
+
async signTransaction(serializedTransactionBase64) {
|
|
1497
|
+
const data = await request(
|
|
1498
|
+
"POST",
|
|
1499
|
+
"/v1/w3s/developer/sign/transaction",
|
|
1500
|
+
{
|
|
1501
|
+
walletId: config.walletId,
|
|
1502
|
+
rawTransaction: serializedTransactionBase64,
|
|
1503
|
+
entitySecretCiphertext: await entitySecretCiphertext()
|
|
1504
|
+
}
|
|
1505
|
+
);
|
|
1506
|
+
const signedTransaction = data.signedTransaction;
|
|
1507
|
+
if (typeof signedTransaction !== "string") {
|
|
1508
|
+
throw new RemoteSigningError(
|
|
1509
|
+
PROVIDER,
|
|
1510
|
+
"sign/transaction returned no signedTransaction",
|
|
1511
|
+
data
|
|
1512
|
+
);
|
|
1513
|
+
}
|
|
1514
|
+
return signedTransaction;
|
|
1515
|
+
}
|
|
979
1516
|
};
|
|
980
1517
|
}
|
|
981
1518
|
|
|
982
|
-
// ../../src/client/
|
|
983
|
-
|
|
984
|
-
var
|
|
985
|
-
|
|
986
|
-
|
|
987
|
-
|
|
988
|
-
|
|
989
|
-
this.name = "OnboardingError";
|
|
1519
|
+
// ../../src/client/signer-transports/privy.ts
|
|
1520
|
+
import { createPrivateKey, createSign } from "node:crypto";
|
|
1521
|
+
var PROVIDER2 = "privy";
|
|
1522
|
+
var DEFAULT_BASE_URL2 = "https://api.privy.io";
|
|
1523
|
+
function canonicalJson(value) {
|
|
1524
|
+
if (Array.isArray(value)) {
|
|
1525
|
+
return `[${value.map(canonicalJson).join(",")}]`;
|
|
990
1526
|
}
|
|
991
|
-
|
|
992
|
-
|
|
993
|
-
}
|
|
994
|
-
|
|
995
|
-
|
|
996
|
-
|
|
997
|
-
|
|
998
|
-
|
|
999
|
-
|
|
1000
|
-
|
|
1001
|
-
|
|
1002
|
-
|
|
1003
|
-
|
|
1004
|
-
signer: params.signer,
|
|
1005
|
-
method: "POST",
|
|
1006
|
-
url,
|
|
1007
|
-
body: serialized
|
|
1008
|
-
}),
|
|
1009
|
-
"content-type": "application/json"
|
|
1010
|
-
},
|
|
1011
|
-
body: serialized
|
|
1527
|
+
if (value !== null && typeof value === "object") {
|
|
1528
|
+
const entries = Object.entries(value).filter(([, v]) => v !== void 0).sort(([a], [b]) => a < b ? -1 : a > b ? 1 : 0);
|
|
1529
|
+
return `{${entries.map(([k, v]) => `${JSON.stringify(k)}:${canonicalJson(v)}`).join(",")}}`;
|
|
1530
|
+
}
|
|
1531
|
+
return JSON.stringify(value);
|
|
1532
|
+
}
|
|
1533
|
+
function parseAuthorizationKey(base64Pkcs8) {
|
|
1534
|
+
const stripped = base64Pkcs8.replace(/^wallet-auth:/, "").trim();
|
|
1535
|
+
try {
|
|
1536
|
+
return createPrivateKey({
|
|
1537
|
+
key: Buffer.from(stripped, "base64"),
|
|
1538
|
+
format: "der",
|
|
1539
|
+
type: "pkcs8"
|
|
1012
1540
|
});
|
|
1013
|
-
|
|
1014
|
-
|
|
1015
|
-
|
|
1016
|
-
|
|
1017
|
-
|
|
1018
|
-
|
|
1541
|
+
} catch (error) {
|
|
1542
|
+
throw new RemoteSigningError(
|
|
1543
|
+
PROVIDER2,
|
|
1544
|
+
"authorization key is not a base64 PKCS#8 P-256 private key",
|
|
1545
|
+
error
|
|
1546
|
+
);
|
|
1547
|
+
}
|
|
1548
|
+
}
|
|
1549
|
+
function authorizationSignature(params) {
|
|
1550
|
+
const payload = {
|
|
1551
|
+
version: 1,
|
|
1552
|
+
method: params.method,
|
|
1553
|
+
url: params.url,
|
|
1554
|
+
body: params.body,
|
|
1555
|
+
headers: { "privy-app-id": params.appId }
|
|
1556
|
+
};
|
|
1557
|
+
const signer2 = createSign("sha256");
|
|
1558
|
+
signer2.update(canonicalJson(payload));
|
|
1559
|
+
return signer2.sign(params.key).toString("base64");
|
|
1560
|
+
}
|
|
1561
|
+
async function createPrivySignerTransport(config) {
|
|
1562
|
+
const baseUrl = (config.baseUrl ?? DEFAULT_BASE_URL2).replace(/\/$/, "");
|
|
1563
|
+
const fetchImpl = config.fetchImpl ?? fetch;
|
|
1564
|
+
const authorizationKey = config.authorizationPrivateKey === void 0 ? null : parseAuthorizationKey(config.authorizationPrivateKey);
|
|
1565
|
+
const baseHeaders = {
|
|
1566
|
+
authorization: `Basic ${Buffer.from(
|
|
1567
|
+
`${config.appId}:${config.appSecret}`
|
|
1568
|
+
).toString("base64")}`,
|
|
1569
|
+
"privy-app-id": config.appId
|
|
1570
|
+
};
|
|
1571
|
+
const request = async (method, path, body) => {
|
|
1572
|
+
const headers = authorizationKey !== null && method !== "GET" && body !== void 0 ? {
|
|
1573
|
+
...baseHeaders,
|
|
1574
|
+
"privy-authorization-signature": authorizationSignature({
|
|
1575
|
+
key: authorizationKey,
|
|
1576
|
+
appId: config.appId,
|
|
1577
|
+
method,
|
|
1578
|
+
url: `${baseUrl}${path}`,
|
|
1579
|
+
body
|
|
1580
|
+
})
|
|
1581
|
+
} : baseHeaders;
|
|
1582
|
+
const json = await providerJsonRequest({
|
|
1583
|
+
provider: PROVIDER2,
|
|
1584
|
+
fetchImpl,
|
|
1585
|
+
baseUrl,
|
|
1586
|
+
path,
|
|
1587
|
+
method,
|
|
1588
|
+
headers,
|
|
1589
|
+
body
|
|
1590
|
+
});
|
|
1591
|
+
if (json === null || typeof json !== "object") {
|
|
1592
|
+
throw new RemoteSigningError(
|
|
1593
|
+
PROVIDER2,
|
|
1594
|
+
`${method} ${path} returned a non-JSON body`
|
|
1595
|
+
);
|
|
1596
|
+
}
|
|
1597
|
+
return json;
|
|
1598
|
+
};
|
|
1599
|
+
const rpc2 = async (body) => {
|
|
1600
|
+
const response = await request(
|
|
1601
|
+
"POST",
|
|
1602
|
+
`/v1/wallets/${config.walletId}/rpc`,
|
|
1603
|
+
body
|
|
1604
|
+
);
|
|
1605
|
+
const data = response.data;
|
|
1606
|
+
if (data === null || typeof data !== "object") {
|
|
1607
|
+
throw new RemoteSigningError(PROVIDER2, "rpc returned no data", response);
|
|
1608
|
+
}
|
|
1609
|
+
return data;
|
|
1610
|
+
};
|
|
1611
|
+
const wallet = await request("GET", `/v1/wallets/${config.walletId}`);
|
|
1612
|
+
const walletAddress = wallet.address;
|
|
1613
|
+
if (typeof walletAddress !== "string") {
|
|
1614
|
+
throw new RemoteSigningError(
|
|
1615
|
+
PROVIDER2,
|
|
1616
|
+
`wallet ${config.walletId} has no address`,
|
|
1617
|
+
wallet
|
|
1618
|
+
);
|
|
1619
|
+
}
|
|
1620
|
+
if (wallet.chain_type !== "solana") {
|
|
1621
|
+
throw new RemoteSigningError(
|
|
1622
|
+
PROVIDER2,
|
|
1623
|
+
`wallet ${config.walletId} is ${String(
|
|
1624
|
+
wallet.chain_type
|
|
1625
|
+
)}, expected solana`
|
|
1626
|
+
);
|
|
1627
|
+
}
|
|
1628
|
+
return {
|
|
1629
|
+
provider: PROVIDER2,
|
|
1630
|
+
walletAddress,
|
|
1631
|
+
async signMessage(message) {
|
|
1632
|
+
const data = await rpc2({
|
|
1633
|
+
chain_type: "solana",
|
|
1634
|
+
method: "signMessage",
|
|
1635
|
+
params: {
|
|
1636
|
+
message: Buffer.from(message).toString("base64"),
|
|
1637
|
+
encoding: "base64"
|
|
1638
|
+
}
|
|
1639
|
+
});
|
|
1640
|
+
const signature = data.signature;
|
|
1641
|
+
if (typeof signature !== "string") {
|
|
1642
|
+
throw new RemoteSigningError(
|
|
1643
|
+
PROVIDER2,
|
|
1644
|
+
"signMessage returned no signature",
|
|
1645
|
+
data
|
|
1646
|
+
);
|
|
1019
1647
|
}
|
|
1020
|
-
|
|
1021
|
-
|
|
1022
|
-
|
|
1023
|
-
|
|
1648
|
+
return verifiedEd25519Signature({
|
|
1649
|
+
provider: PROVIDER2,
|
|
1650
|
+
encodedSignature: signature,
|
|
1651
|
+
message,
|
|
1652
|
+
walletAddress
|
|
1653
|
+
});
|
|
1654
|
+
},
|
|
1655
|
+
async signTransaction(serializedTransactionBase64) {
|
|
1656
|
+
const data = await rpc2({
|
|
1657
|
+
chain_type: "solana",
|
|
1658
|
+
method: "signTransaction",
|
|
1659
|
+
params: {
|
|
1660
|
+
transaction: serializedTransactionBase64,
|
|
1661
|
+
encoding: "base64"
|
|
1662
|
+
}
|
|
1663
|
+
});
|
|
1664
|
+
const signedTransaction = data.signed_transaction;
|
|
1665
|
+
if (typeof signedTransaction !== "string") {
|
|
1666
|
+
throw new RemoteSigningError(
|
|
1667
|
+
PROVIDER2,
|
|
1668
|
+
"signTransaction returned no signed_transaction",
|
|
1669
|
+
data
|
|
1670
|
+
);
|
|
1671
|
+
}
|
|
1672
|
+
return signedTransaction;
|
|
1673
|
+
}
|
|
1674
|
+
};
|
|
1675
|
+
}
|
|
1676
|
+
|
|
1677
|
+
// ../../src/client/signer-env.ts
|
|
1678
|
+
async function agentWalletSignerFromEnv(env = process.env) {
|
|
1679
|
+
const nonEmpty = (value) => {
|
|
1680
|
+
const trimmed = value?.trim();
|
|
1681
|
+
return trimmed === void 0 || trimmed === "" ? void 0 : trimmed;
|
|
1682
|
+
};
|
|
1683
|
+
const provider = nonEmpty(env.SUBLY_SIGNER_PROVIDER)?.toLowerCase() ?? "local";
|
|
1684
|
+
const pickVar = (name) => nonEmpty(env[`SUBLY_${name}`]) ?? nonEmpty(env[name]);
|
|
1685
|
+
const requireVar = (name) => {
|
|
1686
|
+
const value = pickVar(name);
|
|
1687
|
+
if (value === void 0) {
|
|
1688
|
+
throw new Error(
|
|
1689
|
+
`${name} (or SUBLY_${name}) is required for SUBLY_SIGNER_PROVIDER=${provider}`
|
|
1024
1690
|
);
|
|
1025
1691
|
}
|
|
1692
|
+
return value;
|
|
1026
1693
|
};
|
|
1027
|
-
|
|
1028
|
-
|
|
1029
|
-
|
|
1030
|
-
|
|
1031
|
-
|
|
1032
|
-
|
|
1033
|
-
|
|
1034
|
-
|
|
1035
|
-
|
|
1036
|
-
|
|
1694
|
+
if (provider === "local") {
|
|
1695
|
+
const localSecretKey = loadSecretKeyBytes({
|
|
1696
|
+
base58Secret: env.SUBLY_DEMO_AGENT_KEYPAIR,
|
|
1697
|
+
jsonFilePath: env.SUBLY_DEMO_AGENT_KEYPAIR_PATH,
|
|
1698
|
+
label: "SUBLY_DEMO_AGENT_KEYPAIR"
|
|
1699
|
+
});
|
|
1700
|
+
return {
|
|
1701
|
+
provider,
|
|
1702
|
+
signer: new LocalKeypairAgentWalletSigner(
|
|
1703
|
+
await createKeyPairSignerFromBytes2(localSecretKey)
|
|
1704
|
+
),
|
|
1705
|
+
localSecretKey
|
|
1706
|
+
};
|
|
1707
|
+
}
|
|
1708
|
+
if (provider === "circle") {
|
|
1709
|
+
const transport = await createCircleSignerTransport({
|
|
1710
|
+
apiKey: requireVar("CIRCLE_API_KEY"),
|
|
1711
|
+
entitySecret: requireVar("CIRCLE_ENTITY_SECRET"),
|
|
1712
|
+
walletId: requireVar("CIRCLE_WALLET_ID"),
|
|
1713
|
+
baseUrl: pickVar("CIRCLE_BASE_URL")
|
|
1714
|
+
});
|
|
1715
|
+
return { provider, signer: new RemoteAgentWalletSigner(transport), transport };
|
|
1716
|
+
}
|
|
1717
|
+
if (provider === "privy") {
|
|
1718
|
+
const transport = await createPrivySignerTransport({
|
|
1719
|
+
appId: requireVar("PRIVY_APP_ID"),
|
|
1720
|
+
appSecret: requireVar("PRIVY_APP_SECRET"),
|
|
1721
|
+
walletId: requireVar("PRIVY_WALLET_ID"),
|
|
1722
|
+
authorizationPrivateKey: pickVar("PRIVY_AUTHORIZATION_KEY"),
|
|
1723
|
+
baseUrl: pickVar("PRIVY_BASE_URL")
|
|
1724
|
+
});
|
|
1725
|
+
return { provider, signer: new RemoteAgentWalletSigner(transport), transport };
|
|
1726
|
+
}
|
|
1727
|
+
throw new Error(
|
|
1728
|
+
`unknown SUBLY_SIGNER_PROVIDER "${provider}" (expected local, circle, or privy)`
|
|
1729
|
+
);
|
|
1037
1730
|
}
|
|
1038
1731
|
|
|
1039
1732
|
// ../../src/client/lookup-tables.ts
|
|
@@ -1432,31 +2125,6 @@ function parseRelayerError(text) {
|
|
|
1432
2125
|
}
|
|
1433
2126
|
}
|
|
1434
2127
|
|
|
1435
|
-
// ../../src/solana/keys.ts
|
|
1436
|
-
import { readFileSync } from "node:fs";
|
|
1437
|
-
import bs586 from "bs58";
|
|
1438
|
-
import {
|
|
1439
|
-
createKeyPairSignerFromBytes
|
|
1440
|
-
} from "@solana/kit";
|
|
1441
|
-
async function loadKeyPairSigner(params) {
|
|
1442
|
-
const { base58Secret, jsonFilePath, label } = params;
|
|
1443
|
-
if (base58Secret !== void 0 && base58Secret.length > 0) {
|
|
1444
|
-
const bytes = bs586.decode(base58Secret);
|
|
1445
|
-
if (bytes.length !== 64) {
|
|
1446
|
-
throw new Error(`${label} base58 secret must decode to 64 bytes`);
|
|
1447
|
-
}
|
|
1448
|
-
return createKeyPairSignerFromBytes(bytes);
|
|
1449
|
-
}
|
|
1450
|
-
if (jsonFilePath !== void 0 && jsonFilePath.length > 0) {
|
|
1451
|
-
const raw = JSON.parse(readFileSync(jsonFilePath, "utf8"));
|
|
1452
|
-
if (!Array.isArray(raw) || raw.length !== 64) {
|
|
1453
|
-
throw new Error(`${label} keypair file must be a 64-byte JSON array`);
|
|
1454
|
-
}
|
|
1455
|
-
return createKeyPairSignerFromBytes(Uint8Array.from(raw));
|
|
1456
|
-
}
|
|
1457
|
-
throw new Error(`${label} keypair is not configured`);
|
|
1458
|
-
}
|
|
1459
|
-
|
|
1460
2128
|
// ../../src/solana/rpc.ts
|
|
1461
2129
|
import { createSolanaRpc } from "@solana/kit";
|
|
1462
2130
|
function createRpc(url) {
|
|
@@ -1487,12 +2155,7 @@ var approvalId = process.argv[3];
|
|
|
1487
2155
|
if (approvalId !== void 0 && !/^apr_[0-9a-f]+$/i.test(approvalId)) {
|
|
1488
2156
|
fail(`unrecognized argument: ${approvalId} (expected apr_<approvalId>)`);
|
|
1489
2157
|
}
|
|
1490
|
-
var
|
|
1491
|
-
base58Secret: process.env.SUBLY_DEMO_AGENT_KEYPAIR,
|
|
1492
|
-
jsonFilePath: process.env.SUBLY_DEMO_AGENT_KEYPAIR_PATH,
|
|
1493
|
-
label: "SUBLY_DEMO_AGENT_KEYPAIR"
|
|
1494
|
-
});
|
|
1495
|
-
var signer = new LocalKeypairAgentWalletSigner(keyPairSigner);
|
|
2158
|
+
var { signer } = await agentWalletSignerFromEnv();
|
|
1496
2159
|
var rpc = createRpc(
|
|
1497
2160
|
process.env.SOLANA_RPC_URL ?? "https://api.mainnet-beta.solana.com"
|
|
1498
2161
|
);
|