@subly_fi/pay 0.6.1 → 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 +2625 -1946
- package/dist/setup-link.js +741 -78
- package/dist/withdraw.js +741 -78
- package/package.json +21 -4
package/dist/withdraw.js
CHANGED
|
@@ -1,9 +1,38 @@
|
|
|
1
|
+
// ../../src/client/signer-env.ts
|
|
2
|
+
import { createKeyPairSignerFromBytes as createKeyPairSignerFromBytes2 } from "@solana/kit";
|
|
3
|
+
|
|
4
|
+
// ../../src/solana/keys.ts
|
|
5
|
+
import { readFileSync } from "node:fs";
|
|
6
|
+
import bs58 from "bs58";
|
|
7
|
+
import {
|
|
8
|
+
createKeyPairSignerFromBytes
|
|
9
|
+
} from "@solana/kit";
|
|
10
|
+
function loadSecretKeyBytes(params) {
|
|
11
|
+
const { base58Secret, jsonFilePath, label } = params;
|
|
12
|
+
if (base58Secret !== void 0 && base58Secret.length > 0) {
|
|
13
|
+
const bytes = bs58.decode(base58Secret);
|
|
14
|
+
if (bytes.length !== 64) {
|
|
15
|
+
throw new Error(`${label} base58 secret must decode to 64 bytes`);
|
|
16
|
+
}
|
|
17
|
+
return bytes;
|
|
18
|
+
}
|
|
19
|
+
if (jsonFilePath !== void 0 && jsonFilePath.length > 0) {
|
|
20
|
+
const raw = JSON.parse(readFileSync(jsonFilePath, "utf8"));
|
|
21
|
+
if (!Array.isArray(raw) || raw.length !== 64) {
|
|
22
|
+
throw new Error(`${label} keypair file must be a 64-byte JSON array`);
|
|
23
|
+
}
|
|
24
|
+
return Uint8Array.from(raw);
|
|
25
|
+
}
|
|
26
|
+
throw new Error(`${label} keypair is not configured`);
|
|
27
|
+
}
|
|
28
|
+
|
|
1
29
|
// ../../src/client/agent-wallet-signer.ts
|
|
2
30
|
import { signBytes } from "@solana/kit";
|
|
3
|
-
import
|
|
31
|
+
import bs586 from "bs58";
|
|
32
|
+
import nacl2 from "tweetnacl";
|
|
4
33
|
|
|
5
34
|
// ../../src/solana/tx.ts
|
|
6
|
-
import
|
|
35
|
+
import bs582 from "bs58";
|
|
7
36
|
import {
|
|
8
37
|
appendTransactionMessageInstructions,
|
|
9
38
|
compileTransaction,
|
|
@@ -46,6 +75,24 @@ function hashStableJson(value) {
|
|
|
46
75
|
function decodeSerializedTransaction(serializedBase64) {
|
|
47
76
|
return getTransactionDecoder().decode(Buffer.from(serializedBase64, "base64"));
|
|
48
77
|
}
|
|
78
|
+
function attachExternalSignatureToTransaction(params) {
|
|
79
|
+
if (!(params.signer in params.transaction.signatures)) {
|
|
80
|
+
throw new Error(
|
|
81
|
+
`transaction does not expect a signature from ${params.signer}`
|
|
82
|
+
);
|
|
83
|
+
}
|
|
84
|
+
const transaction = Object.freeze({
|
|
85
|
+
...params.transaction,
|
|
86
|
+
signatures: Object.freeze({
|
|
87
|
+
...params.transaction.signatures,
|
|
88
|
+
[params.signer]: params.signature
|
|
89
|
+
})
|
|
90
|
+
});
|
|
91
|
+
return {
|
|
92
|
+
serializedBase64: getBase64EncodedWireTransaction(transaction),
|
|
93
|
+
transaction
|
|
94
|
+
};
|
|
95
|
+
}
|
|
49
96
|
async function addSignaturesToSerializedTransaction(params) {
|
|
50
97
|
const decoded = decodeSerializedTransaction(params.serializedBase64);
|
|
51
98
|
const signed = await partiallySignTransaction(params.signers, decoded);
|
|
@@ -59,11 +106,148 @@ function signatureBase58ForSigner(transaction, signer2) {
|
|
|
59
106
|
if (signature === null || signature === void 0) {
|
|
60
107
|
return null;
|
|
61
108
|
}
|
|
62
|
-
return
|
|
109
|
+
return bs582.encode(signature);
|
|
63
110
|
}
|
|
64
111
|
|
|
65
|
-
// ../../src/client/
|
|
112
|
+
// ../../src/client/remote-signer-transport.ts
|
|
66
113
|
import bs583 from "bs58";
|
|
114
|
+
import nacl from "tweetnacl";
|
|
115
|
+
var RemoteSigningError = class extends Error {
|
|
116
|
+
constructor(provider, message, detail = null) {
|
|
117
|
+
super(`[${provider}] ${message}`);
|
|
118
|
+
this.provider = provider;
|
|
119
|
+
this.detail = detail;
|
|
120
|
+
this.name = "RemoteSigningError";
|
|
121
|
+
}
|
|
122
|
+
provider;
|
|
123
|
+
detail;
|
|
124
|
+
};
|
|
125
|
+
function ed25519PublicKeyBytes(provider, walletAddress) {
|
|
126
|
+
let bytes;
|
|
127
|
+
try {
|
|
128
|
+
bytes = bs583.decode(walletAddress);
|
|
129
|
+
} catch {
|
|
130
|
+
throw new RemoteSigningError(
|
|
131
|
+
provider,
|
|
132
|
+
`wallet address ${walletAddress} is not base58`
|
|
133
|
+
);
|
|
134
|
+
}
|
|
135
|
+
if (bytes.length !== 32) {
|
|
136
|
+
throw new RemoteSigningError(
|
|
137
|
+
provider,
|
|
138
|
+
`wallet address ${walletAddress} is not a 32-byte ed25519 key`
|
|
139
|
+
);
|
|
140
|
+
}
|
|
141
|
+
return bytes;
|
|
142
|
+
}
|
|
143
|
+
function verifiedEd25519Signature(params) {
|
|
144
|
+
const publicKey = ed25519PublicKeyBytes(params.provider, params.walletAddress);
|
|
145
|
+
const encoded = params.encodedSignature.trim();
|
|
146
|
+
for (const candidate of decodeSignatureCandidates(encoded)) {
|
|
147
|
+
if (nacl.sign.detached.verify(params.message, candidate, publicKey)) {
|
|
148
|
+
return candidate;
|
|
149
|
+
}
|
|
150
|
+
}
|
|
151
|
+
throw new RemoteSigningError(
|
|
152
|
+
params.provider,
|
|
153
|
+
`signature did not verify for wallet ${params.walletAddress}`
|
|
154
|
+
);
|
|
155
|
+
}
|
|
156
|
+
function decodeSignatureCandidates(encoded) {
|
|
157
|
+
const candidates = [];
|
|
158
|
+
const hex = encoded.startsWith("0x") ? encoded.slice(2) : encoded;
|
|
159
|
+
if (/^[0-9a-fA-F]{128}$/.test(hex)) {
|
|
160
|
+
candidates.push(Uint8Array.from(Buffer.from(hex, "hex")));
|
|
161
|
+
}
|
|
162
|
+
try {
|
|
163
|
+
const fromBase58 = bs583.decode(encoded);
|
|
164
|
+
if (fromBase58.length === 64) {
|
|
165
|
+
candidates.push(fromBase58);
|
|
166
|
+
}
|
|
167
|
+
} catch {
|
|
168
|
+
}
|
|
169
|
+
if (/^[A-Za-z0-9+/=_-]+$/.test(encoded)) {
|
|
170
|
+
const fromBase64 = Uint8Array.from(
|
|
171
|
+
Buffer.from(encoded.replace(/-/g, "+").replace(/_/g, "/"), "base64")
|
|
172
|
+
);
|
|
173
|
+
if (fromBase64.length === 64) {
|
|
174
|
+
candidates.push(fromBase64);
|
|
175
|
+
}
|
|
176
|
+
}
|
|
177
|
+
return candidates;
|
|
178
|
+
}
|
|
179
|
+
async function requestVerifiedTransactionSignature(params) {
|
|
180
|
+
const { transport } = params;
|
|
181
|
+
const signedBase64 = await transport.signTransaction(
|
|
182
|
+
params.serializedTransactionBase64
|
|
183
|
+
);
|
|
184
|
+
let returned;
|
|
185
|
+
try {
|
|
186
|
+
returned = decodeSerializedTransaction(signedBase64);
|
|
187
|
+
} catch (error) {
|
|
188
|
+
throw new RemoteSigningError(
|
|
189
|
+
transport.provider,
|
|
190
|
+
"provider returned an undecodable signed transaction",
|
|
191
|
+
error
|
|
192
|
+
);
|
|
193
|
+
}
|
|
194
|
+
const signature = returned.signatures[transport.walletAddress] ?? null;
|
|
195
|
+
if (signature === null) {
|
|
196
|
+
throw new RemoteSigningError(
|
|
197
|
+
transport.provider,
|
|
198
|
+
`signed transaction is missing the signature for ${transport.walletAddress}`
|
|
199
|
+
);
|
|
200
|
+
}
|
|
201
|
+
const publicKey = params.publicKey ?? ed25519PublicKeyBytes(transport.provider, transport.walletAddress);
|
|
202
|
+
if (!nacl.sign.detached.verify(params.messageBytes, signature, publicKey)) {
|
|
203
|
+
throw new RemoteSigningError(
|
|
204
|
+
transport.provider,
|
|
205
|
+
"returned signature does not verify over the requested transaction"
|
|
206
|
+
);
|
|
207
|
+
}
|
|
208
|
+
return signature;
|
|
209
|
+
}
|
|
210
|
+
async function externallySignedAgentTransaction(params) {
|
|
211
|
+
const original = decodeSerializedTransaction(params.serializedTransaction);
|
|
212
|
+
const signature = await requestVerifiedTransactionSignature({
|
|
213
|
+
transport: params.transport,
|
|
214
|
+
serializedTransactionBase64: params.serializedTransaction,
|
|
215
|
+
messageBytes: original.messageBytes
|
|
216
|
+
});
|
|
217
|
+
const attached = attachExternalSignatureToTransaction({
|
|
218
|
+
transaction: original,
|
|
219
|
+
signer: params.transport.walletAddress,
|
|
220
|
+
signature
|
|
221
|
+
});
|
|
222
|
+
return {
|
|
223
|
+
serializedTransaction: attached.serializedBase64,
|
|
224
|
+
agentSignature: bs583.encode(signature)
|
|
225
|
+
};
|
|
226
|
+
}
|
|
227
|
+
async function providerJsonRequest(params) {
|
|
228
|
+
const response = await params.fetchImpl(`${params.baseUrl}${params.path}`, {
|
|
229
|
+
method: params.method,
|
|
230
|
+
headers: { ...params.headers, "content-type": "application/json" },
|
|
231
|
+
...params.body === void 0 ? {} : { body: JSON.stringify(params.body) }
|
|
232
|
+
});
|
|
233
|
+
let json = null;
|
|
234
|
+
try {
|
|
235
|
+
json = await response.json();
|
|
236
|
+
} catch {
|
|
237
|
+
json = null;
|
|
238
|
+
}
|
|
239
|
+
if (!response.ok) {
|
|
240
|
+
throw new RemoteSigningError(
|
|
241
|
+
params.provider,
|
|
242
|
+
`${params.method} ${params.path} failed with ${response.status}`,
|
|
243
|
+
json
|
|
244
|
+
);
|
|
245
|
+
}
|
|
246
|
+
return json;
|
|
247
|
+
}
|
|
248
|
+
|
|
249
|
+
// ../../src/client/transaction-intent-validator.ts
|
|
250
|
+
import bs585 from "bs58";
|
|
67
251
|
import { getCompiledTransactionMessageDecoder } from "@solana/kit";
|
|
68
252
|
|
|
69
253
|
// ../../src/config/constants.ts
|
|
@@ -71,14 +255,30 @@ var PAYMENT_SCHEME = "subly-yield-exact";
|
|
|
71
255
|
var SOLANA_MAINNET_NETWORK = "solana:5eykt4UsFv8P8NJdTREpY1vzqKqZKvdp";
|
|
72
256
|
var SPL_TOKEN_PROGRAM_ID = "TokenkegQfeZyiNwAJbNbGKPFXCWuBvf9Ss623VQ5DA";
|
|
73
257
|
var ASSOCIATED_TOKEN_PROGRAM_ID = "ATokenGPvbdGVxr1b2hvZbsiqW5xWH25efTNsLJA8knL";
|
|
258
|
+
var envOr = (name, fallback) => {
|
|
259
|
+
const value = process.env[name]?.trim();
|
|
260
|
+
return value ? value : fallback;
|
|
261
|
+
};
|
|
74
262
|
var SUBLY_VAULT = {
|
|
75
263
|
name: "Subly USDC Payment Vault Alpha",
|
|
76
|
-
address:
|
|
264
|
+
address: envOr(
|
|
265
|
+
"SUBLY_VAULT_ADDRESS",
|
|
266
|
+
"5kfkpQZ6AkQgizHVThqkxD4J3db2i7pE3mHdPNRbx7jr"
|
|
267
|
+
),
|
|
77
268
|
programId: "KvauGMspG5k6rtzrqqn7WNn3oZdyKqLKwK2XWQ8FLjd",
|
|
78
|
-
usdcMint:
|
|
79
|
-
|
|
269
|
+
usdcMint: envOr(
|
|
270
|
+
"SUBLY_VAULT_USDC_MINT",
|
|
271
|
+
"EPjFWdd5AufqSSqeM2qN1xzybapC8G4wEGGkZwyTDt1v"
|
|
272
|
+
),
|
|
273
|
+
shareMint: envOr(
|
|
274
|
+
"SUBLY_VAULT_SHARE_MINT",
|
|
275
|
+
"7hGX49So539MU9Rrah8nBNVYXswWVwEJvgWNYeBDYq3a"
|
|
276
|
+
),
|
|
80
277
|
lookupTable: "7UbXhDnpK7WVnwsfivzQRENoqKqAULQ5s19gS1xJrQEo",
|
|
81
|
-
farm:
|
|
278
|
+
farm: envOr(
|
|
279
|
+
"SUBLY_VAULT_FARM",
|
|
280
|
+
"E2Ct77LowkDAH1T9ubwPpb84pU2GSGrUdgH3KeTTpLX"
|
|
281
|
+
)
|
|
82
282
|
};
|
|
83
283
|
var USDC_DECIMALS = 6;
|
|
84
284
|
|
|
@@ -99,7 +299,7 @@ function computeRequestBindingHash(fields) {
|
|
|
99
299
|
|
|
100
300
|
// ../../src/lib/associated-token-account.ts
|
|
101
301
|
import { createHash as createHash2 } from "node:crypto";
|
|
102
|
-
import
|
|
302
|
+
import bs584 from "bs58";
|
|
103
303
|
var PDA_MARKER = Buffer.from("ProgramDerivedAddress", "utf8");
|
|
104
304
|
var ED25519_P = (1n << 255n) - 19n;
|
|
105
305
|
var ED25519_D = mod(-121665n * modPow(121666n, ED25519_P - 2n, ED25519_P), ED25519_P);
|
|
@@ -120,7 +320,7 @@ function deriveAssociatedTokenAddress(params) {
|
|
|
120
320
|
associatedTokenProgramId
|
|
121
321
|
);
|
|
122
322
|
if (address2 !== null) {
|
|
123
|
-
return
|
|
323
|
+
return bs584.encode(address2);
|
|
124
324
|
}
|
|
125
325
|
}
|
|
126
326
|
throw new Error("Unable to derive associated token account address");
|
|
@@ -136,7 +336,7 @@ function createProgramAddress(seeds, programId) {
|
|
|
136
336
|
return isEd25519Point(digest) ? null : new Uint8Array(digest);
|
|
137
337
|
}
|
|
138
338
|
function decodePublicKey(value, fieldName) {
|
|
139
|
-
const decoded =
|
|
339
|
+
const decoded = bs584.decode(value);
|
|
140
340
|
if (decoded.length !== 32) {
|
|
141
341
|
throw new Error(`${fieldName} must be a 32-byte public key`);
|
|
142
342
|
}
|
|
@@ -337,6 +537,9 @@ function validatePaymentIntentTransaction(params) {
|
|
|
337
537
|
if (intent.shareMint !== SUBLY_VAULT.shareMint) {
|
|
338
538
|
reject("share_mint_mismatch", "Unsupported share mint");
|
|
339
539
|
}
|
|
540
|
+
if (intent.farm !== SUBLY_VAULT.farm) {
|
|
541
|
+
reject("farm_mismatch", "Unsupported Kamino farm");
|
|
542
|
+
}
|
|
340
543
|
if (intent.asset !== SUBLY_VAULT.usdcMint) {
|
|
341
544
|
reject("asset_mismatch", "Only USDC payments are supported");
|
|
342
545
|
}
|
|
@@ -405,7 +608,7 @@ function validatePaymentIntentTransaction(params) {
|
|
|
405
608
|
expectComputeBudgetPair(ixs, policy);
|
|
406
609
|
expectCreateTemporaryAccount(ixs, intent, policy);
|
|
407
610
|
expectInitializeTemporaryAccount(ixs, intent);
|
|
408
|
-
consumeFarmInstructions(ixs, intent
|
|
611
|
+
consumeFarmInstructions(ixs, intent);
|
|
409
612
|
expectKvaultWithdraw(ixs, {
|
|
410
613
|
wallet: intent.wallet,
|
|
411
614
|
vault: intent.vault,
|
|
@@ -548,13 +751,27 @@ function validateWithdrawalIntentTransaction(params) {
|
|
|
548
751
|
reject("fee_payer_mismatch", "Transaction fee payer is not the Subly sponsor");
|
|
549
752
|
}
|
|
550
753
|
let sawWithdraw = false;
|
|
754
|
+
let farmUserState = null;
|
|
755
|
+
let farmInstructionCount = 0;
|
|
551
756
|
for (const ix of decoded.instructions) {
|
|
552
757
|
switch (ix.programAddress) {
|
|
553
758
|
case COMPUTE_BUDGET_PROGRAM_ID:
|
|
554
759
|
validateComputeBudgetInstruction(ix, policy);
|
|
555
760
|
break;
|
|
556
761
|
case MEMO_PROGRAM_ID:
|
|
762
|
+
break;
|
|
557
763
|
case KAMINO_FARMS_PROGRAM_ID:
|
|
764
|
+
farmInstructionCount += 1;
|
|
765
|
+
if (farmInstructionCount === 1) {
|
|
766
|
+
farmUserState = validateFarmUnstakeInstruction(ix, intent);
|
|
767
|
+
} else if (farmInstructionCount === 2) {
|
|
768
|
+
validateFarmWithdrawInstruction(ix, intent, farmUserState);
|
|
769
|
+
} else {
|
|
770
|
+
reject(
|
|
771
|
+
"farm_instruction_mismatch",
|
|
772
|
+
"Withdrawal may contain only one farm unstake and one farm withdrawal"
|
|
773
|
+
);
|
|
774
|
+
}
|
|
558
775
|
break;
|
|
559
776
|
case ASSOCIATED_TOKEN_PROGRAM_ID2:
|
|
560
777
|
expectAtaCreateForOwner(ix, intent.wallet);
|
|
@@ -575,6 +792,12 @@ function validateWithdrawalIntentTransaction(params) {
|
|
|
575
792
|
break;
|
|
576
793
|
}
|
|
577
794
|
case KVAULT_PROGRAM_ID: {
|
|
795
|
+
if (sawWithdraw) {
|
|
796
|
+
reject(
|
|
797
|
+
"withdraw_mismatch",
|
|
798
|
+
"Withdrawal may contain only one KVault withdraw instruction"
|
|
799
|
+
);
|
|
800
|
+
}
|
|
578
801
|
validateKvaultWithdrawInstruction(ix, {
|
|
579
802
|
wallet: intent.wallet,
|
|
580
803
|
vault: intent.vault,
|
|
@@ -597,6 +820,12 @@ function validateWithdrawalIntentTransaction(params) {
|
|
|
597
820
|
if (!sawWithdraw) {
|
|
598
821
|
reject("missing_instruction", "Withdrawal transaction has no KVault withdraw");
|
|
599
822
|
}
|
|
823
|
+
if (farmInstructionCount === 1) {
|
|
824
|
+
reject(
|
|
825
|
+
"farm_instruction_mismatch",
|
|
826
|
+
"A farm unstake must be followed by a farm withdrawal"
|
|
827
|
+
);
|
|
828
|
+
}
|
|
600
829
|
}
|
|
601
830
|
function assertVaultIntentTargets(intent) {
|
|
602
831
|
if (intent.vault !== SUBLY_VAULT.address) {
|
|
@@ -605,6 +834,9 @@ function assertVaultIntentTargets(intent) {
|
|
|
605
834
|
if (intent.shareMint !== SUBLY_VAULT.shareMint) {
|
|
606
835
|
reject("share_mint_mismatch", "Unsupported share mint");
|
|
607
836
|
}
|
|
837
|
+
if (intent.farm !== SUBLY_VAULT.farm) {
|
|
838
|
+
reject("farm_mismatch", "Unsupported Kamino farm");
|
|
839
|
+
}
|
|
608
840
|
if (intent.asset !== SUBLY_VAULT.usdcMint) {
|
|
609
841
|
reject("asset_mismatch", "Only USDC is supported");
|
|
610
842
|
}
|
|
@@ -677,7 +909,7 @@ function expectCreateTemporaryAccount(ixs, intent, policy) {
|
|
|
677
909
|
}
|
|
678
910
|
const lamports = readU64LE(ix.data, 4);
|
|
679
911
|
const space = readU64LE(ix.data, 12);
|
|
680
|
-
const owner =
|
|
912
|
+
const owner = bs585.encode(ix.data.subarray(20, 52));
|
|
681
913
|
if (space !== 165n) {
|
|
682
914
|
reject("temp_account_mismatch", "Temporary account space must be 165 bytes");
|
|
683
915
|
}
|
|
@@ -699,7 +931,7 @@ function expectInitializeTemporaryAccount(ixs, intent) {
|
|
|
699
931
|
if (ix === void 0 || ix.programAddress !== SPL_TOKEN_PROGRAM_ID || ix.data[0] !== 18) {
|
|
700
932
|
reject("temp_account_mismatch", "Expected InitializeAccount3 instruction");
|
|
701
933
|
}
|
|
702
|
-
const owner =
|
|
934
|
+
const owner = bs585.encode(ix.data.subarray(1, 33));
|
|
703
935
|
if (owner !== intent.wallet) {
|
|
704
936
|
reject(
|
|
705
937
|
"temp_account_mismatch",
|
|
@@ -713,15 +945,90 @@ function expectInitializeTemporaryAccount(ixs, intent) {
|
|
|
713
945
|
reject("temp_account_mismatch", "Temporary account mint must be USDC");
|
|
714
946
|
}
|
|
715
947
|
}
|
|
716
|
-
function consumeFarmInstructions(ixs,
|
|
717
|
-
|
|
718
|
-
|
|
719
|
-
|
|
720
|
-
|
|
721
|
-
|
|
722
|
-
|
|
723
|
-
|
|
724
|
-
|
|
948
|
+
function consumeFarmInstructions(ixs, intent) {
|
|
949
|
+
if (ixs[0]?.programAddress !== KAMINO_FARMS_PROGRAM_ID) {
|
|
950
|
+
return;
|
|
951
|
+
}
|
|
952
|
+
const unstake = ixs.shift();
|
|
953
|
+
const userState = validateFarmUnstakeInstruction(unstake, intent);
|
|
954
|
+
if (ixs[0]?.programAddress !== KAMINO_FARMS_PROGRAM_ID) {
|
|
955
|
+
reject(
|
|
956
|
+
"farm_instruction_mismatch",
|
|
957
|
+
"A farm unstake must be followed by a farm withdrawal"
|
|
958
|
+
);
|
|
959
|
+
}
|
|
960
|
+
const withdraw = ixs.shift();
|
|
961
|
+
validateFarmWithdrawInstruction(withdraw, intent, userState);
|
|
962
|
+
if (ixs[0]?.programAddress === KAMINO_FARMS_PROGRAM_ID) {
|
|
963
|
+
reject(
|
|
964
|
+
"farm_instruction_mismatch",
|
|
965
|
+
"Payment may contain only one farm unstake and one farm withdrawal"
|
|
966
|
+
);
|
|
967
|
+
}
|
|
968
|
+
}
|
|
969
|
+
var KAMINO_FARMS_UNSTAKE_DISCRIMINATOR = Uint8Array.from([
|
|
970
|
+
90,
|
|
971
|
+
95,
|
|
972
|
+
107,
|
|
973
|
+
42,
|
|
974
|
+
205,
|
|
975
|
+
124,
|
|
976
|
+
50,
|
|
977
|
+
225
|
|
978
|
+
]);
|
|
979
|
+
var KAMINO_FARMS_WITHDRAW_UNSTAKED_DISCRIMINATOR = Uint8Array.from([
|
|
980
|
+
36,
|
|
981
|
+
102,
|
|
982
|
+
187,
|
|
983
|
+
49,
|
|
984
|
+
220,
|
|
985
|
+
36,
|
|
986
|
+
132,
|
|
987
|
+
67
|
|
988
|
+
]);
|
|
989
|
+
function validateFarmUnstakeInstruction(ix, intent) {
|
|
990
|
+
if (ix.programAddress !== KAMINO_FARMS_PROGRAM_ID || !bytesStartWith(ix.data, KAMINO_FARMS_UNSTAKE_DISCRIMINATOR) || ix.data.length !== 24 || readU128LE(ix.data, 8) <= 0n) {
|
|
991
|
+
reject(
|
|
992
|
+
"farm_instruction_mismatch",
|
|
993
|
+
"Expected a non-zero Kamino farm unstake instruction"
|
|
994
|
+
);
|
|
995
|
+
}
|
|
996
|
+
if (ix.accounts.length !== 4) {
|
|
997
|
+
reject(
|
|
998
|
+
"farm_instruction_mismatch",
|
|
999
|
+
"Farm unstake account list is not canonical"
|
|
1000
|
+
);
|
|
1001
|
+
}
|
|
1002
|
+
if (ix.accounts[0] !== intent.wallet) {
|
|
1003
|
+
reject("farm_instruction_mismatch", "Farm unstake owner must be the agent wallet");
|
|
1004
|
+
}
|
|
1005
|
+
if (ix.accounts[2] !== intent.farm) {
|
|
1006
|
+
reject("farm_instruction_mismatch", "Farm unstake target is not the approved farm");
|
|
1007
|
+
}
|
|
1008
|
+
return ix.accounts[1];
|
|
1009
|
+
}
|
|
1010
|
+
function validateFarmWithdrawInstruction(ix, intent, expectedUserState) {
|
|
1011
|
+
if (ix.programAddress !== KAMINO_FARMS_PROGRAM_ID || !bytesStartWith(ix.data, KAMINO_FARMS_WITHDRAW_UNSTAKED_DISCRIMINATOR) || ix.data.length !== 8) {
|
|
1012
|
+
reject(
|
|
1013
|
+
"farm_instruction_mismatch",
|
|
1014
|
+
"Expected a canonical Kamino farm withdrawal instruction"
|
|
1015
|
+
);
|
|
1016
|
+
}
|
|
1017
|
+
if (ix.accounts.length !== 7) {
|
|
1018
|
+
reject(
|
|
1019
|
+
"farm_instruction_mismatch",
|
|
1020
|
+
"Farm withdrawal account list is not canonical"
|
|
1021
|
+
);
|
|
1022
|
+
}
|
|
1023
|
+
const expectedSharesAta = deriveAssociatedTokenAddress({
|
|
1024
|
+
owner: intent.wallet,
|
|
1025
|
+
mint: intent.shareMint
|
|
1026
|
+
});
|
|
1027
|
+
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) {
|
|
1028
|
+
reject(
|
|
1029
|
+
"farm_instruction_mismatch",
|
|
1030
|
+
"Farm withdrawal must return the approved vault shares to the agent wallet"
|
|
1031
|
+
);
|
|
725
1032
|
}
|
|
726
1033
|
}
|
|
727
1034
|
function expectKvaultWithdraw(ixs, expectation) {
|
|
@@ -856,6 +1163,16 @@ function readU32LE(data, offset) {
|
|
|
856
1163
|
}
|
|
857
1164
|
return Buffer.from(data.subarray(offset, offset + 4)).readUInt32LE(0);
|
|
858
1165
|
}
|
|
1166
|
+
function readU128LE(data, offset) {
|
|
1167
|
+
if (data.length < offset + 16) {
|
|
1168
|
+
reject("invalid_transaction_encoding", "Instruction data too short for u128");
|
|
1169
|
+
}
|
|
1170
|
+
let value = 0n;
|
|
1171
|
+
for (let index = 0; index < 16; index += 1) {
|
|
1172
|
+
value |= BigInt(data[offset + index]) << BigInt(index * 8);
|
|
1173
|
+
}
|
|
1174
|
+
return value;
|
|
1175
|
+
}
|
|
859
1176
|
function readShortVec(bytes, startOffset) {
|
|
860
1177
|
let value = 0;
|
|
861
1178
|
let shift = 0;
|
|
@@ -876,55 +1193,55 @@ function readShortVec(bytes, startOffset) {
|
|
|
876
1193
|
}
|
|
877
1194
|
|
|
878
1195
|
// ../../src/client/agent-wallet-signer.ts
|
|
879
|
-
var
|
|
1196
|
+
var IntentValidatingAgentWalletSigner = class {
|
|
880
1197
|
validationMode = "structured_intent_transaction";
|
|
881
|
-
keyPairSigner;
|
|
882
1198
|
validationPolicy;
|
|
883
|
-
constructor(
|
|
884
|
-
this.keyPairSigner = keyPairSigner2;
|
|
1199
|
+
constructor(validationPolicy) {
|
|
885
1200
|
this.validationPolicy = validationPolicy;
|
|
886
1201
|
}
|
|
887
|
-
get walletAddress() {
|
|
888
|
-
return this.keyPairSigner.address;
|
|
889
|
-
}
|
|
890
1202
|
async signPayment(params) {
|
|
891
1203
|
this.assertIntentWallet(params.intent.wallet);
|
|
892
|
-
validatePaymentIntentTransaction({
|
|
893
|
-
...params,
|
|
894
|
-
...this.validationPolicy === void 0 ? {} : { policy: this.validationPolicy }
|
|
895
|
-
});
|
|
1204
|
+
validatePaymentIntentTransaction({ ...params, ...this.policySpread() });
|
|
896
1205
|
return this.sign(params.serializedTransaction);
|
|
897
1206
|
}
|
|
898
1207
|
async signDeposit(params) {
|
|
899
1208
|
this.assertIntentWallet(params.intent.wallet);
|
|
900
|
-
validateDepositIntentTransaction({
|
|
901
|
-
...params,
|
|
902
|
-
...this.validationPolicy === void 0 ? {} : { policy: this.validationPolicy }
|
|
903
|
-
});
|
|
1209
|
+
validateDepositIntentTransaction({ ...params, ...this.policySpread() });
|
|
904
1210
|
return this.sign(params.serializedTransaction);
|
|
905
1211
|
}
|
|
906
1212
|
async signWithdrawal(params) {
|
|
907
1213
|
this.assertIntentWallet(params.intent.wallet);
|
|
908
|
-
validateWithdrawalIntentTransaction({
|
|
909
|
-
...params,
|
|
910
|
-
...this.validationPolicy === void 0 ? {} : { policy: this.validationPolicy }
|
|
911
|
-
});
|
|
1214
|
+
validateWithdrawalIntentTransaction({ ...params, ...this.policySpread() });
|
|
912
1215
|
return this.sign(params.serializedTransaction);
|
|
913
1216
|
}
|
|
1217
|
+
policySpread() {
|
|
1218
|
+
return this.validationPolicy === void 0 ? {} : { policy: this.validationPolicy };
|
|
1219
|
+
}
|
|
914
1220
|
assertIntentWallet(wallet) {
|
|
915
|
-
if (wallet !== this.
|
|
1221
|
+
if (wallet !== this.walletAddress) {
|
|
916
1222
|
throw new IntentValidationError(
|
|
917
1223
|
"wallet_mismatch",
|
|
918
1224
|
"Intent wallet does not match this signer's wallet"
|
|
919
1225
|
);
|
|
920
1226
|
}
|
|
921
1227
|
}
|
|
1228
|
+
};
|
|
1229
|
+
var LocalKeypairAgentWalletSigner = class extends IntentValidatingAgentWalletSigner {
|
|
1230
|
+
provider = "local-keypair";
|
|
1231
|
+
keyPairSigner;
|
|
1232
|
+
constructor(keyPairSigner, validationPolicy) {
|
|
1233
|
+
super(validationPolicy);
|
|
1234
|
+
this.keyPairSigner = keyPairSigner;
|
|
1235
|
+
}
|
|
1236
|
+
get walletAddress() {
|
|
1237
|
+
return this.keyPairSigner.address;
|
|
1238
|
+
}
|
|
922
1239
|
async signApiMessage(message) {
|
|
923
1240
|
const signature = await signBytes(
|
|
924
1241
|
this.keyPairSigner.keyPair.privateKey,
|
|
925
1242
|
message
|
|
926
1243
|
);
|
|
927
|
-
return
|
|
1244
|
+
return bs586.encode(signature);
|
|
928
1245
|
}
|
|
929
1246
|
async sign(serializedTransaction) {
|
|
930
1247
|
const { serializedBase64, transaction } = await addSignaturesToSerializedTransaction({
|
|
@@ -944,6 +1261,379 @@ var LocalKeypairAgentWalletSigner = class {
|
|
|
944
1261
|
return { serializedTransaction: serializedBase64, agentSignature };
|
|
945
1262
|
}
|
|
946
1263
|
};
|
|
1264
|
+
var RemoteAgentWalletSigner = class extends IntentValidatingAgentWalletSigner {
|
|
1265
|
+
transport;
|
|
1266
|
+
publicKey;
|
|
1267
|
+
constructor(transport, validationPolicy) {
|
|
1268
|
+
super(validationPolicy);
|
|
1269
|
+
this.transport = transport;
|
|
1270
|
+
this.publicKey = ed25519PublicKeyBytes(
|
|
1271
|
+
transport.provider,
|
|
1272
|
+
transport.walletAddress
|
|
1273
|
+
);
|
|
1274
|
+
}
|
|
1275
|
+
get walletAddress() {
|
|
1276
|
+
return this.transport.walletAddress;
|
|
1277
|
+
}
|
|
1278
|
+
get provider() {
|
|
1279
|
+
return this.transport.provider;
|
|
1280
|
+
}
|
|
1281
|
+
async signApiMessage(message) {
|
|
1282
|
+
const signature = await this.transport.signMessage(message);
|
|
1283
|
+
if (!nacl2.sign.detached.verify(message, signature, this.publicKey)) {
|
|
1284
|
+
throw new RemoteSigningError(
|
|
1285
|
+
this.transport.provider,
|
|
1286
|
+
"message signature did not verify for the agent wallet"
|
|
1287
|
+
);
|
|
1288
|
+
}
|
|
1289
|
+
return bs586.encode(signature);
|
|
1290
|
+
}
|
|
1291
|
+
sign(serializedTransaction) {
|
|
1292
|
+
return externallySignedAgentTransaction({
|
|
1293
|
+
transport: this.transport,
|
|
1294
|
+
serializedTransaction
|
|
1295
|
+
});
|
|
1296
|
+
}
|
|
1297
|
+
};
|
|
1298
|
+
|
|
1299
|
+
// ../../src/client/signer-transports/circle.ts
|
|
1300
|
+
import {
|
|
1301
|
+
constants,
|
|
1302
|
+
createPublicKey,
|
|
1303
|
+
publicEncrypt
|
|
1304
|
+
} from "node:crypto";
|
|
1305
|
+
var PROVIDER = "circle";
|
|
1306
|
+
var DEFAULT_BASE_URL = "https://api.circle.com";
|
|
1307
|
+
async function createCircleSignerTransport(config) {
|
|
1308
|
+
if (!/^[0-9a-fA-F]{64}$/.test(config.entitySecret)) {
|
|
1309
|
+
throw new RemoteSigningError(
|
|
1310
|
+
PROVIDER,
|
|
1311
|
+
"entity secret must be 32 bytes of hex (64 hex chars)"
|
|
1312
|
+
);
|
|
1313
|
+
}
|
|
1314
|
+
const baseUrl = (config.baseUrl ?? DEFAULT_BASE_URL).replace(/\/$/, "");
|
|
1315
|
+
const fetchImpl = config.fetchImpl ?? fetch;
|
|
1316
|
+
const request = async (method, path, body) => {
|
|
1317
|
+
const json = await providerJsonRequest({
|
|
1318
|
+
provider: PROVIDER,
|
|
1319
|
+
fetchImpl,
|
|
1320
|
+
baseUrl,
|
|
1321
|
+
path,
|
|
1322
|
+
method,
|
|
1323
|
+
headers: { authorization: `Bearer ${config.apiKey}` },
|
|
1324
|
+
body
|
|
1325
|
+
});
|
|
1326
|
+
const data = json?.data;
|
|
1327
|
+
if (data === void 0) {
|
|
1328
|
+
throw new RemoteSigningError(
|
|
1329
|
+
PROVIDER,
|
|
1330
|
+
`${method} ${path} returned no data envelope`,
|
|
1331
|
+
json
|
|
1332
|
+
);
|
|
1333
|
+
}
|
|
1334
|
+
return data;
|
|
1335
|
+
};
|
|
1336
|
+
const walletData = await request("GET", `/v1/w3s/wallets/${config.walletId}`);
|
|
1337
|
+
const wallet = walletData.wallet;
|
|
1338
|
+
if (wallet?.address === void 0) {
|
|
1339
|
+
throw new RemoteSigningError(
|
|
1340
|
+
PROVIDER,
|
|
1341
|
+
`wallet ${config.walletId} has no address`,
|
|
1342
|
+
walletData
|
|
1343
|
+
);
|
|
1344
|
+
}
|
|
1345
|
+
if (wallet.blockchain !== "SOL") {
|
|
1346
|
+
throw new RemoteSigningError(
|
|
1347
|
+
PROVIDER,
|
|
1348
|
+
`wallet ${config.walletId} is on ${String(
|
|
1349
|
+
wallet.blockchain
|
|
1350
|
+
)}, expected SOL (Solana mainnet)`
|
|
1351
|
+
);
|
|
1352
|
+
}
|
|
1353
|
+
const walletAddress = wallet.address;
|
|
1354
|
+
let entityPublicKey = null;
|
|
1355
|
+
const entitySecretCiphertext = async () => {
|
|
1356
|
+
if (entityPublicKey === null) {
|
|
1357
|
+
const data = await request("GET", "/v1/w3s/config/entity/publicKey");
|
|
1358
|
+
const publicKey = data.publicKey;
|
|
1359
|
+
if (typeof publicKey !== "string") {
|
|
1360
|
+
throw new RemoteSigningError(
|
|
1361
|
+
PROVIDER,
|
|
1362
|
+
"entity public key response has no publicKey",
|
|
1363
|
+
data
|
|
1364
|
+
);
|
|
1365
|
+
}
|
|
1366
|
+
entityPublicKey = createPublicKey(publicKey);
|
|
1367
|
+
}
|
|
1368
|
+
return publicEncrypt(
|
|
1369
|
+
{
|
|
1370
|
+
key: entityPublicKey,
|
|
1371
|
+
padding: constants.RSA_PKCS1_OAEP_PADDING,
|
|
1372
|
+
oaepHash: "sha256"
|
|
1373
|
+
},
|
|
1374
|
+
Buffer.from(config.entitySecret, "hex")
|
|
1375
|
+
).toString("base64");
|
|
1376
|
+
};
|
|
1377
|
+
return {
|
|
1378
|
+
provider: PROVIDER,
|
|
1379
|
+
walletAddress,
|
|
1380
|
+
async signMessage(message) {
|
|
1381
|
+
const data = await request("POST", "/v1/w3s/developer/sign/message", {
|
|
1382
|
+
walletId: config.walletId,
|
|
1383
|
+
message: `0x${Buffer.from(message).toString("hex")}`,
|
|
1384
|
+
encodedByHex: true,
|
|
1385
|
+
entitySecretCiphertext: await entitySecretCiphertext()
|
|
1386
|
+
});
|
|
1387
|
+
const signature = data.signature;
|
|
1388
|
+
if (typeof signature !== "string") {
|
|
1389
|
+
throw new RemoteSigningError(
|
|
1390
|
+
PROVIDER,
|
|
1391
|
+
"sign/message returned no signature",
|
|
1392
|
+
data
|
|
1393
|
+
);
|
|
1394
|
+
}
|
|
1395
|
+
return verifiedEd25519Signature({
|
|
1396
|
+
provider: PROVIDER,
|
|
1397
|
+
encodedSignature: signature,
|
|
1398
|
+
message,
|
|
1399
|
+
walletAddress
|
|
1400
|
+
});
|
|
1401
|
+
},
|
|
1402
|
+
async signTransaction(serializedTransactionBase64) {
|
|
1403
|
+
const data = await request(
|
|
1404
|
+
"POST",
|
|
1405
|
+
"/v1/w3s/developer/sign/transaction",
|
|
1406
|
+
{
|
|
1407
|
+
walletId: config.walletId,
|
|
1408
|
+
rawTransaction: serializedTransactionBase64,
|
|
1409
|
+
entitySecretCiphertext: await entitySecretCiphertext()
|
|
1410
|
+
}
|
|
1411
|
+
);
|
|
1412
|
+
const signedTransaction = data.signedTransaction;
|
|
1413
|
+
if (typeof signedTransaction !== "string") {
|
|
1414
|
+
throw new RemoteSigningError(
|
|
1415
|
+
PROVIDER,
|
|
1416
|
+
"sign/transaction returned no signedTransaction",
|
|
1417
|
+
data
|
|
1418
|
+
);
|
|
1419
|
+
}
|
|
1420
|
+
return signedTransaction;
|
|
1421
|
+
}
|
|
1422
|
+
};
|
|
1423
|
+
}
|
|
1424
|
+
|
|
1425
|
+
// ../../src/client/signer-transports/privy.ts
|
|
1426
|
+
import { createPrivateKey, createSign } from "node:crypto";
|
|
1427
|
+
var PROVIDER2 = "privy";
|
|
1428
|
+
var DEFAULT_BASE_URL2 = "https://api.privy.io";
|
|
1429
|
+
function canonicalJson(value) {
|
|
1430
|
+
if (Array.isArray(value)) {
|
|
1431
|
+
return `[${value.map(canonicalJson).join(",")}]`;
|
|
1432
|
+
}
|
|
1433
|
+
if (value !== null && typeof value === "object") {
|
|
1434
|
+
const entries = Object.entries(value).filter(([, v]) => v !== void 0).sort(([a], [b]) => a < b ? -1 : a > b ? 1 : 0);
|
|
1435
|
+
return `{${entries.map(([k, v]) => `${JSON.stringify(k)}:${canonicalJson(v)}`).join(",")}}`;
|
|
1436
|
+
}
|
|
1437
|
+
return JSON.stringify(value);
|
|
1438
|
+
}
|
|
1439
|
+
function parseAuthorizationKey(base64Pkcs8) {
|
|
1440
|
+
const stripped = base64Pkcs8.replace(/^wallet-auth:/, "").trim();
|
|
1441
|
+
try {
|
|
1442
|
+
return createPrivateKey({
|
|
1443
|
+
key: Buffer.from(stripped, "base64"),
|
|
1444
|
+
format: "der",
|
|
1445
|
+
type: "pkcs8"
|
|
1446
|
+
});
|
|
1447
|
+
} catch (error) {
|
|
1448
|
+
throw new RemoteSigningError(
|
|
1449
|
+
PROVIDER2,
|
|
1450
|
+
"authorization key is not a base64 PKCS#8 P-256 private key",
|
|
1451
|
+
error
|
|
1452
|
+
);
|
|
1453
|
+
}
|
|
1454
|
+
}
|
|
1455
|
+
function authorizationSignature(params) {
|
|
1456
|
+
const payload = {
|
|
1457
|
+
version: 1,
|
|
1458
|
+
method: params.method,
|
|
1459
|
+
url: params.url,
|
|
1460
|
+
body: params.body,
|
|
1461
|
+
headers: { "privy-app-id": params.appId }
|
|
1462
|
+
};
|
|
1463
|
+
const signer2 = createSign("sha256");
|
|
1464
|
+
signer2.update(canonicalJson(payload));
|
|
1465
|
+
return signer2.sign(params.key).toString("base64");
|
|
1466
|
+
}
|
|
1467
|
+
async function createPrivySignerTransport(config) {
|
|
1468
|
+
const baseUrl = (config.baseUrl ?? DEFAULT_BASE_URL2).replace(/\/$/, "");
|
|
1469
|
+
const fetchImpl = config.fetchImpl ?? fetch;
|
|
1470
|
+
const authorizationKey = config.authorizationPrivateKey === void 0 ? null : parseAuthorizationKey(config.authorizationPrivateKey);
|
|
1471
|
+
const baseHeaders = {
|
|
1472
|
+
authorization: `Basic ${Buffer.from(
|
|
1473
|
+
`${config.appId}:${config.appSecret}`
|
|
1474
|
+
).toString("base64")}`,
|
|
1475
|
+
"privy-app-id": config.appId
|
|
1476
|
+
};
|
|
1477
|
+
const request = async (method, path, body) => {
|
|
1478
|
+
const headers = authorizationKey !== null && method !== "GET" && body !== void 0 ? {
|
|
1479
|
+
...baseHeaders,
|
|
1480
|
+
"privy-authorization-signature": authorizationSignature({
|
|
1481
|
+
key: authorizationKey,
|
|
1482
|
+
appId: config.appId,
|
|
1483
|
+
method,
|
|
1484
|
+
url: `${baseUrl}${path}`,
|
|
1485
|
+
body
|
|
1486
|
+
})
|
|
1487
|
+
} : baseHeaders;
|
|
1488
|
+
const json = await providerJsonRequest({
|
|
1489
|
+
provider: PROVIDER2,
|
|
1490
|
+
fetchImpl,
|
|
1491
|
+
baseUrl,
|
|
1492
|
+
path,
|
|
1493
|
+
method,
|
|
1494
|
+
headers,
|
|
1495
|
+
body
|
|
1496
|
+
});
|
|
1497
|
+
if (json === null || typeof json !== "object") {
|
|
1498
|
+
throw new RemoteSigningError(
|
|
1499
|
+
PROVIDER2,
|
|
1500
|
+
`${method} ${path} returned a non-JSON body`
|
|
1501
|
+
);
|
|
1502
|
+
}
|
|
1503
|
+
return json;
|
|
1504
|
+
};
|
|
1505
|
+
const rpc2 = async (body) => {
|
|
1506
|
+
const response = await request(
|
|
1507
|
+
"POST",
|
|
1508
|
+
`/v1/wallets/${config.walletId}/rpc`,
|
|
1509
|
+
body
|
|
1510
|
+
);
|
|
1511
|
+
const data = response.data;
|
|
1512
|
+
if (data === null || typeof data !== "object") {
|
|
1513
|
+
throw new RemoteSigningError(PROVIDER2, "rpc returned no data", response);
|
|
1514
|
+
}
|
|
1515
|
+
return data;
|
|
1516
|
+
};
|
|
1517
|
+
const wallet = await request("GET", `/v1/wallets/${config.walletId}`);
|
|
1518
|
+
const walletAddress = wallet.address;
|
|
1519
|
+
if (typeof walletAddress !== "string") {
|
|
1520
|
+
throw new RemoteSigningError(
|
|
1521
|
+
PROVIDER2,
|
|
1522
|
+
`wallet ${config.walletId} has no address`,
|
|
1523
|
+
wallet
|
|
1524
|
+
);
|
|
1525
|
+
}
|
|
1526
|
+
if (wallet.chain_type !== "solana") {
|
|
1527
|
+
throw new RemoteSigningError(
|
|
1528
|
+
PROVIDER2,
|
|
1529
|
+
`wallet ${config.walletId} is ${String(
|
|
1530
|
+
wallet.chain_type
|
|
1531
|
+
)}, expected solana`
|
|
1532
|
+
);
|
|
1533
|
+
}
|
|
1534
|
+
return {
|
|
1535
|
+
provider: PROVIDER2,
|
|
1536
|
+
walletAddress,
|
|
1537
|
+
async signMessage(message) {
|
|
1538
|
+
const data = await rpc2({
|
|
1539
|
+
chain_type: "solana",
|
|
1540
|
+
method: "signMessage",
|
|
1541
|
+
params: {
|
|
1542
|
+
message: Buffer.from(message).toString("base64"),
|
|
1543
|
+
encoding: "base64"
|
|
1544
|
+
}
|
|
1545
|
+
});
|
|
1546
|
+
const signature = data.signature;
|
|
1547
|
+
if (typeof signature !== "string") {
|
|
1548
|
+
throw new RemoteSigningError(
|
|
1549
|
+
PROVIDER2,
|
|
1550
|
+
"signMessage returned no signature",
|
|
1551
|
+
data
|
|
1552
|
+
);
|
|
1553
|
+
}
|
|
1554
|
+
return verifiedEd25519Signature({
|
|
1555
|
+
provider: PROVIDER2,
|
|
1556
|
+
encodedSignature: signature,
|
|
1557
|
+
message,
|
|
1558
|
+
walletAddress
|
|
1559
|
+
});
|
|
1560
|
+
},
|
|
1561
|
+
async signTransaction(serializedTransactionBase64) {
|
|
1562
|
+
const data = await rpc2({
|
|
1563
|
+
chain_type: "solana",
|
|
1564
|
+
method: "signTransaction",
|
|
1565
|
+
params: {
|
|
1566
|
+
transaction: serializedTransactionBase64,
|
|
1567
|
+
encoding: "base64"
|
|
1568
|
+
}
|
|
1569
|
+
});
|
|
1570
|
+
const signedTransaction = data.signed_transaction;
|
|
1571
|
+
if (typeof signedTransaction !== "string") {
|
|
1572
|
+
throw new RemoteSigningError(
|
|
1573
|
+
PROVIDER2,
|
|
1574
|
+
"signTransaction returned no signed_transaction",
|
|
1575
|
+
data
|
|
1576
|
+
);
|
|
1577
|
+
}
|
|
1578
|
+
return signedTransaction;
|
|
1579
|
+
}
|
|
1580
|
+
};
|
|
1581
|
+
}
|
|
1582
|
+
|
|
1583
|
+
// ../../src/client/signer-env.ts
|
|
1584
|
+
async function agentWalletSignerFromEnv(env = process.env) {
|
|
1585
|
+
const nonEmpty = (value) => {
|
|
1586
|
+
const trimmed = value?.trim();
|
|
1587
|
+
return trimmed === void 0 || trimmed === "" ? void 0 : trimmed;
|
|
1588
|
+
};
|
|
1589
|
+
const provider = nonEmpty(env.SUBLY_SIGNER_PROVIDER)?.toLowerCase() ?? "local";
|
|
1590
|
+
const pickVar = (name) => nonEmpty(env[`SUBLY_${name}`]) ?? nonEmpty(env[name]);
|
|
1591
|
+
const requireVar = (name) => {
|
|
1592
|
+
const value = pickVar(name);
|
|
1593
|
+
if (value === void 0) {
|
|
1594
|
+
throw new Error(
|
|
1595
|
+
`${name} (or SUBLY_${name}) is required for SUBLY_SIGNER_PROVIDER=${provider}`
|
|
1596
|
+
);
|
|
1597
|
+
}
|
|
1598
|
+
return value;
|
|
1599
|
+
};
|
|
1600
|
+
if (provider === "local") {
|
|
1601
|
+
const localSecretKey = loadSecretKeyBytes({
|
|
1602
|
+
base58Secret: env.SUBLY_DEMO_AGENT_KEYPAIR,
|
|
1603
|
+
jsonFilePath: env.SUBLY_DEMO_AGENT_KEYPAIR_PATH,
|
|
1604
|
+
label: "SUBLY_DEMO_AGENT_KEYPAIR"
|
|
1605
|
+
});
|
|
1606
|
+
return {
|
|
1607
|
+
provider,
|
|
1608
|
+
signer: new LocalKeypairAgentWalletSigner(
|
|
1609
|
+
await createKeyPairSignerFromBytes2(localSecretKey)
|
|
1610
|
+
),
|
|
1611
|
+
localSecretKey
|
|
1612
|
+
};
|
|
1613
|
+
}
|
|
1614
|
+
if (provider === "circle") {
|
|
1615
|
+
const transport = await createCircleSignerTransport({
|
|
1616
|
+
apiKey: requireVar("CIRCLE_API_KEY"),
|
|
1617
|
+
entitySecret: requireVar("CIRCLE_ENTITY_SECRET"),
|
|
1618
|
+
walletId: requireVar("CIRCLE_WALLET_ID"),
|
|
1619
|
+
baseUrl: pickVar("CIRCLE_BASE_URL")
|
|
1620
|
+
});
|
|
1621
|
+
return { provider, signer: new RemoteAgentWalletSigner(transport), transport };
|
|
1622
|
+
}
|
|
1623
|
+
if (provider === "privy") {
|
|
1624
|
+
const transport = await createPrivySignerTransport({
|
|
1625
|
+
appId: requireVar("PRIVY_APP_ID"),
|
|
1626
|
+
appSecret: requireVar("PRIVY_APP_SECRET"),
|
|
1627
|
+
walletId: requireVar("PRIVY_WALLET_ID"),
|
|
1628
|
+
authorizationPrivateKey: pickVar("PRIVY_AUTHORIZATION_KEY"),
|
|
1629
|
+
baseUrl: pickVar("PRIVY_BASE_URL")
|
|
1630
|
+
});
|
|
1631
|
+
return { provider, signer: new RemoteAgentWalletSigner(transport), transport };
|
|
1632
|
+
}
|
|
1633
|
+
throw new Error(
|
|
1634
|
+
`unknown SUBLY_SIGNER_PROVIDER "${provider}" (expected local, circle, or privy)`
|
|
1635
|
+
);
|
|
1636
|
+
}
|
|
947
1637
|
|
|
948
1638
|
// ../../src/client/lookup-tables.ts
|
|
949
1639
|
import { fetchAllMaybeAddressLookupTable } from "@solana-program/address-lookup-table";
|
|
@@ -987,8 +1677,8 @@ async function fetchLookupTablesForTransaction(rpc2, serializedTransaction) {
|
|
|
987
1677
|
|
|
988
1678
|
// ../../src/api/wallet-auth.ts
|
|
989
1679
|
import { createHash as createHash3 } from "node:crypto";
|
|
990
|
-
import
|
|
991
|
-
import
|
|
1680
|
+
import bs587 from "bs58";
|
|
1681
|
+
import nacl3 from "tweetnacl";
|
|
992
1682
|
var WALLET_AUTH_WALLET_HEADER = "x-subly-wallet";
|
|
993
1683
|
var WALLET_AUTH_SIGNED_AT_HEADER = "x-subly-signed-at";
|
|
994
1684
|
var WALLET_AUTH_SIGNATURE_HEADER = "x-subly-signature";
|
|
@@ -1008,7 +1698,10 @@ async function walletAuthHeaders(params) {
|
|
|
1008
1698
|
const signedAtMs = String(Date.now());
|
|
1009
1699
|
const message = walletAuthMessage({
|
|
1010
1700
|
method: params.method,
|
|
1011
|
-
path:
|
|
1701
|
+
path: (() => {
|
|
1702
|
+
const url = new URL(params.url);
|
|
1703
|
+
return url.pathname + url.search;
|
|
1704
|
+
})(),
|
|
1012
1705
|
rawBody: params.body ?? "",
|
|
1013
1706
|
signedAtMs
|
|
1014
1707
|
});
|
|
@@ -1375,31 +2068,6 @@ function parseRelayerError(text) {
|
|
|
1375
2068
|
}
|
|
1376
2069
|
}
|
|
1377
2070
|
|
|
1378
|
-
// ../../src/solana/keys.ts
|
|
1379
|
-
import { readFileSync } from "node:fs";
|
|
1380
|
-
import bs586 from "bs58";
|
|
1381
|
-
import {
|
|
1382
|
-
createKeyPairSignerFromBytes
|
|
1383
|
-
} from "@solana/kit";
|
|
1384
|
-
async function loadKeyPairSigner(params) {
|
|
1385
|
-
const { base58Secret, jsonFilePath, label } = params;
|
|
1386
|
-
if (base58Secret !== void 0 && base58Secret.length > 0) {
|
|
1387
|
-
const bytes = bs586.decode(base58Secret);
|
|
1388
|
-
if (bytes.length !== 64) {
|
|
1389
|
-
throw new Error(`${label} base58 secret must decode to 64 bytes`);
|
|
1390
|
-
}
|
|
1391
|
-
return createKeyPairSignerFromBytes(bytes);
|
|
1392
|
-
}
|
|
1393
|
-
if (jsonFilePath !== void 0 && jsonFilePath.length > 0) {
|
|
1394
|
-
const raw = JSON.parse(readFileSync(jsonFilePath, "utf8"));
|
|
1395
|
-
if (!Array.isArray(raw) || raw.length !== 64) {
|
|
1396
|
-
throw new Error(`${label} keypair file must be a 64-byte JSON array`);
|
|
1397
|
-
}
|
|
1398
|
-
return createKeyPairSignerFromBytes(Uint8Array.from(raw));
|
|
1399
|
-
}
|
|
1400
|
-
throw new Error(`${label} keypair is not configured`);
|
|
1401
|
-
}
|
|
1402
|
-
|
|
1403
2071
|
// ../../src/solana/rpc.ts
|
|
1404
2072
|
import { createSolanaRpc } from "@solana/kit";
|
|
1405
2073
|
function createRpc(url) {
|
|
@@ -1430,12 +2098,7 @@ var approvalId = process.argv[3];
|
|
|
1430
2098
|
if (approvalId !== void 0 && !/^apr_[0-9a-f]+$/i.test(approvalId)) {
|
|
1431
2099
|
fail(`unrecognized argument: ${approvalId} (expected apr_<approvalId>)`);
|
|
1432
2100
|
}
|
|
1433
|
-
var
|
|
1434
|
-
base58Secret: process.env.SUBLY_DEMO_AGENT_KEYPAIR,
|
|
1435
|
-
jsonFilePath: process.env.SUBLY_DEMO_AGENT_KEYPAIR_PATH,
|
|
1436
|
-
label: "SUBLY_DEMO_AGENT_KEYPAIR"
|
|
1437
|
-
});
|
|
1438
|
-
var signer = new LocalKeypairAgentWalletSigner(keyPairSigner);
|
|
2101
|
+
var { signer } = await agentWalletSignerFromEnv();
|
|
1439
2102
|
var rpc = createRpc(
|
|
1440
2103
|
process.env.SOLANA_RPC_URL ?? "https://api.mainnet-beta.solana.com"
|
|
1441
2104
|
);
|