@spectratools/tx-shared 0.4.1 → 0.4.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/dist/errors.d.ts CHANGED
@@ -1,4 +1,4 @@
1
- type TxErrorCode = 'INSUFFICIENT_FUNDS' | 'NONCE_CONFLICT' | 'TX_REVERTED' | 'GAS_ESTIMATION_FAILED' | 'SIGNER_NOT_CONFIGURED' | 'KEYSTORE_DECRYPT_FAILED' | 'PRIVY_AUTH_FAILED';
1
+ type TxErrorCode = 'INSUFFICIENT_FUNDS' | 'NONCE_CONFLICT' | 'TX_REVERTED' | 'GAS_ESTIMATION_FAILED' | 'SIGNER_NOT_CONFIGURED' | 'KEYSTORE_DECRYPT_FAILED' | 'PRIVY_AUTH_FAILED' | 'PRIVY_TRANSPORT_FAILED';
2
2
  declare class TxError extends Error {
3
3
  readonly code: TxErrorCode;
4
4
  constructor(code: TxErrorCode, message: string, cause?: unknown);
package/dist/index.d.ts CHANGED
@@ -4,6 +4,7 @@ export { TxError, TxErrorCode, toTxError } from './errors.js';
4
4
  export { abstractMainnet, createAbstractClient } from './chain.js';
5
5
  export { DryRunResult, ExecuteTxOptions, executeTx } from './execute-tx.js';
6
6
  import { z } from 'incur';
7
+ import { KeyObject } from 'node:crypto';
7
8
  import 'viem';
8
9
 
9
10
  /**
@@ -18,6 +19,7 @@ declare const signerFlagSchema: z.ZodObject<{
18
19
  keystore: z.ZodOptional<z.ZodString>;
19
20
  password: z.ZodOptional<z.ZodString>;
20
21
  privy: z.ZodDefault<z.ZodBoolean>;
22
+ 'privy-api-url': z.ZodOptional<z.ZodString>;
21
23
  }, z.core.$strip>;
22
24
  /** Shared signer-related environment variables for write-capable commands. */
23
25
  declare const signerEnvSchema: z.ZodObject<{
@@ -26,6 +28,7 @@ declare const signerEnvSchema: z.ZodObject<{
26
28
  PRIVY_APP_ID: z.ZodOptional<z.ZodString>;
27
29
  PRIVY_WALLET_ID: z.ZodOptional<z.ZodString>;
28
30
  PRIVY_AUTHORIZATION_KEY: z.ZodOptional<z.ZodString>;
31
+ PRIVY_API_URL: z.ZodOptional<z.ZodString>;
29
32
  }, z.core.$strip>;
30
33
  type SignerFlags = z.infer<typeof signerFlagSchema>;
31
34
  type SignerEnv = z.infer<typeof signerEnvSchema>;
@@ -57,17 +60,97 @@ interface KeystoreSignerOptions {
57
60
  */
58
61
  declare function createKeystoreSigner(options: KeystoreSignerOptions): TxSigner;
59
62
 
63
+ interface PrivyRpcIntentRequest {
64
+ method: string;
65
+ params: Record<string, unknown>;
66
+ [key: string]: unknown;
67
+ }
68
+ interface PrivyRpcIntentResponse {
69
+ intent_id: string;
70
+ status: string;
71
+ resource_id?: string;
72
+ request_details?: Record<string, unknown>;
73
+ [key: string]: unknown;
74
+ }
75
+ interface PrivyWalletResponse {
76
+ id: string;
77
+ address: string;
78
+ owner_id: string | null;
79
+ policy_ids: string[];
80
+ [key: string]: unknown;
81
+ }
82
+ interface PrivyPolicyResponse {
83
+ id: string;
84
+ owner_id: string | null;
85
+ rules: unknown[];
86
+ [key: string]: unknown;
87
+ }
88
+ interface CreatePrivyClientOptions {
89
+ appId: string;
90
+ walletId: string;
91
+ authorizationKey: string;
92
+ apiUrl?: string;
93
+ fetchImplementation?: typeof fetch;
94
+ }
95
+ interface PrivyRequestOptions {
96
+ idempotencyKey?: string;
97
+ }
98
+ interface PrivyClient {
99
+ readonly appId: string;
100
+ readonly walletId: string;
101
+ readonly apiUrl: string;
102
+ createRpcIntent(request: PrivyRpcIntentRequest, options?: PrivyRequestOptions): Promise<PrivyRpcIntentResponse>;
103
+ getWallet(): Promise<PrivyWalletResponse>;
104
+ getPolicy(policyId: string): Promise<PrivyPolicyResponse>;
105
+ }
106
+ declare function createPrivyClient(options: CreatePrivyClientOptions): PrivyClient;
107
+
108
+ type PrivyAuthorizationMethod = 'POST' | 'PUT' | 'PATCH' | 'DELETE';
109
+ interface PrivyAuthorizationPayloadHeaders {
110
+ 'privy-app-id': string;
111
+ 'privy-idempotency-key'?: string;
112
+ }
113
+ interface PrivyAuthorizationPayload<TBody extends Record<string, unknown>> {
114
+ version: 1;
115
+ method: PrivyAuthorizationMethod;
116
+ url: string;
117
+ headers: PrivyAuthorizationPayloadHeaders;
118
+ body: TBody;
119
+ }
120
+ interface CreatePrivyAuthorizationPayloadOptions<TBody extends Record<string, unknown>> {
121
+ appId: string;
122
+ method: PrivyAuthorizationMethod;
123
+ url: string;
124
+ body: TBody;
125
+ idempotencyKey?: string;
126
+ }
127
+ declare function normalizePrivyApiUrl(apiUrl?: string): string;
128
+ declare function parsePrivyAuthorizationKey(authorizationKey: string): KeyObject;
129
+ declare function createPrivyAuthorizationPayload<TBody extends Record<string, unknown>>(options: CreatePrivyAuthorizationPayloadOptions<TBody>): PrivyAuthorizationPayload<TBody>;
130
+ declare function serializePrivyAuthorizationPayload(payload: PrivyAuthorizationPayload<Record<string, unknown>>): string;
131
+ declare function generatePrivyAuthorizationSignature(payload: PrivyAuthorizationPayload<Record<string, unknown>>, authorizationKey: string): string;
132
+
60
133
  interface PrivySignerOptions {
61
134
  privyAppId?: string;
62
135
  privyWalletId?: string;
63
136
  privyAuthorizationKey?: string;
137
+ privyApiUrl?: string;
138
+ }
139
+ interface PrivySigner extends TxSigner {
140
+ provider: 'privy';
141
+ privy: {
142
+ appId: string;
143
+ walletId: string;
144
+ apiUrl: string;
145
+ client: PrivyClient;
146
+ };
64
147
  }
65
148
  /**
66
- * Privy signer adapter entrypoint.
149
+ * Create a Privy signer envelope with reusable transport and request-signing primitives.
67
150
  *
68
- * Full Privy integration is tracked in issue #117. Until that lands,
69
- * this adapter provides deterministic, structured failures for callers.
151
+ * This initializes the shared client utilities used by tx-shared Privy integrations.
152
+ * Transaction account execution is implemented in follow-up work for issue #191.
70
153
  */
71
- declare function createPrivySigner(options: PrivySignerOptions): Promise<TxSigner>;
154
+ declare function createPrivySigner(options: PrivySignerOptions): Promise<PrivySigner>;
72
155
 
73
- export { type KeystoreSignerOptions, type PrivySignerOptions, type SignerEnv, type SignerFlags, SignerOptions, TxSigner, createKeystoreSigner, createPrivateKeySigner, createPrivySigner, resolveSigner, signerEnvSchema, signerFlagSchema, toSignerOptions };
156
+ export { type KeystoreSignerOptions, type PrivyAuthorizationPayload, type PrivyAuthorizationPayloadHeaders, type PrivyClient, type PrivySigner, type PrivySignerOptions, type SignerEnv, type SignerFlags, SignerOptions, TxSigner, createKeystoreSigner, createPrivateKeySigner, createPrivyAuthorizationPayload, createPrivyClient, createPrivySigner, generatePrivyAuthorizationSignature, normalizePrivyApiUrl, parsePrivyAuthorizationKey, resolveSigner, serializePrivyAuthorizationPayload, signerEnvSchema, signerFlagSchema, toSignerOptions };
package/dist/index.js CHANGED
@@ -57,14 +57,329 @@ function createKeystoreSigner(options) {
57
57
  return { account, address: account.address, provider: "keystore" };
58
58
  }
59
59
 
60
+ // src/signers/privy-signature.ts
61
+ import { createPrivateKey, sign as signWithCrypto } from "crypto";
62
+ var PRIVY_AUTHORIZATION_KEY_PREFIX = "wallet-auth:";
63
+ var PRIVY_AUTHORIZATION_KEY_REGEX = /^wallet-auth:[A-Za-z0-9+/]+={0,2}$/;
64
+ var DEFAULT_PRIVY_API_URL = "https://api.privy.io";
65
+ function normalizePrivyApiUrl(apiUrl) {
66
+ const value = (apiUrl ?? DEFAULT_PRIVY_API_URL).trim();
67
+ if (value.length === 0) {
68
+ throw new TxError(
69
+ "PRIVY_AUTH_FAILED",
70
+ "Invalid PRIVY_API_URL format: expected a non-empty http(s) URL"
71
+ );
72
+ }
73
+ let parsed;
74
+ try {
75
+ parsed = new URL(value);
76
+ } catch (cause) {
77
+ throw new TxError(
78
+ "PRIVY_AUTH_FAILED",
79
+ "Invalid PRIVY_API_URL format: expected a non-empty http(s) URL",
80
+ cause
81
+ );
82
+ }
83
+ if (parsed.protocol !== "http:" && parsed.protocol !== "https:") {
84
+ throw new TxError(
85
+ "PRIVY_AUTH_FAILED",
86
+ "Invalid PRIVY_API_URL format: expected a non-empty http(s) URL"
87
+ );
88
+ }
89
+ return parsed.toString().replace(/\/+$/, "");
90
+ }
91
+ function parsePrivyAuthorizationKey(authorizationKey) {
92
+ const normalizedKey = authorizationKey.trim();
93
+ if (!PRIVY_AUTHORIZATION_KEY_REGEX.test(normalizedKey)) {
94
+ throw new TxError(
95
+ "PRIVY_AUTH_FAILED",
96
+ "Invalid PRIVY_AUTHORIZATION_KEY format: expected wallet-auth:<base64-pkcs8-p256-private-key>"
97
+ );
98
+ }
99
+ const rawPrivateKey = normalizedKey.slice(PRIVY_AUTHORIZATION_KEY_PREFIX.length);
100
+ let derKey;
101
+ try {
102
+ derKey = Buffer.from(rawPrivateKey, "base64");
103
+ } catch (cause) {
104
+ throw new TxError(
105
+ "PRIVY_AUTH_FAILED",
106
+ "Invalid PRIVY_AUTHORIZATION_KEY format: expected wallet-auth:<base64-pkcs8-p256-private-key>",
107
+ cause
108
+ );
109
+ }
110
+ let keyObject;
111
+ try {
112
+ keyObject = createPrivateKey({
113
+ key: derKey,
114
+ format: "der",
115
+ type: "pkcs8"
116
+ });
117
+ } catch (cause) {
118
+ throw new TxError(
119
+ "PRIVY_AUTH_FAILED",
120
+ "Invalid PRIVY_AUTHORIZATION_KEY format: expected wallet-auth:<base64-pkcs8-p256-private-key>",
121
+ cause
122
+ );
123
+ }
124
+ if (keyObject.asymmetricKeyType !== "ec") {
125
+ throw new TxError(
126
+ "PRIVY_AUTH_FAILED",
127
+ "Invalid PRIVY_AUTHORIZATION_KEY format: expected wallet-auth:<base64-pkcs8-p256-private-key>"
128
+ );
129
+ }
130
+ const details = keyObject.asymmetricKeyDetails;
131
+ const namedCurve = details !== void 0 && "namedCurve" in details ? details.namedCurve : void 0;
132
+ if (namedCurve !== void 0 && namedCurve !== "prime256v1") {
133
+ throw new TxError(
134
+ "PRIVY_AUTH_FAILED",
135
+ "Invalid PRIVY_AUTHORIZATION_KEY format: expected wallet-auth:<base64-pkcs8-p256-private-key>"
136
+ );
137
+ }
138
+ return keyObject;
139
+ }
140
+ function createPrivyAuthorizationPayload(options) {
141
+ const appId = options.appId.trim();
142
+ if (appId.length === 0) {
143
+ throw new TxError(
144
+ "PRIVY_AUTH_FAILED",
145
+ "Invalid PRIVY_APP_ID format: expected non-empty string"
146
+ );
147
+ }
148
+ const url = options.url.trim().replace(/\/+$/, "");
149
+ if (url.length === 0) {
150
+ throw new TxError(
151
+ "PRIVY_TRANSPORT_FAILED",
152
+ "Failed to build Privy authorization payload: request URL is empty"
153
+ );
154
+ }
155
+ return {
156
+ version: 1,
157
+ method: options.method,
158
+ url,
159
+ headers: {
160
+ "privy-app-id": appId,
161
+ ...options.idempotencyKey !== void 0 ? { "privy-idempotency-key": options.idempotencyKey } : {}
162
+ },
163
+ body: options.body
164
+ };
165
+ }
166
+ function serializePrivyAuthorizationPayload(payload) {
167
+ return canonicalizeJson(payload);
168
+ }
169
+ function generatePrivyAuthorizationSignature(payload, authorizationKey) {
170
+ const privateKey = parsePrivyAuthorizationKey(authorizationKey);
171
+ const serializedPayload = serializePrivyAuthorizationPayload(payload);
172
+ const signature = signWithCrypto("sha256", Buffer.from(serializedPayload), privateKey);
173
+ return signature.toString("base64");
174
+ }
175
+ function canonicalizeJson(value) {
176
+ if (value === null) {
177
+ return "null";
178
+ }
179
+ if (typeof value === "string" || typeof value === "boolean") {
180
+ return JSON.stringify(value);
181
+ }
182
+ if (typeof value === "number") {
183
+ if (!Number.isFinite(value)) {
184
+ throw new TxError(
185
+ "PRIVY_TRANSPORT_FAILED",
186
+ "Failed to build Privy authorization payload: JSON payload contains a non-finite number"
187
+ );
188
+ }
189
+ return JSON.stringify(value);
190
+ }
191
+ if (Array.isArray(value)) {
192
+ return `[${value.map((item) => canonicalizeJson(item)).join(",")}]`;
193
+ }
194
+ if (typeof value === "object") {
195
+ const record = value;
196
+ const keys = Object.keys(record).sort((left, right) => left.localeCompare(right));
197
+ const entries = [];
198
+ for (const key of keys) {
199
+ const entryValue = record[key];
200
+ if (entryValue === void 0) {
201
+ continue;
202
+ }
203
+ entries.push(`${JSON.stringify(key)}:${canonicalizeJson(entryValue)}`);
204
+ }
205
+ return `{${entries.join(",")}}`;
206
+ }
207
+ throw new TxError(
208
+ "PRIVY_TRANSPORT_FAILED",
209
+ "Failed to build Privy authorization payload: JSON payload contains unsupported value type"
210
+ );
211
+ }
212
+
213
+ // src/signers/privy-client.ts
214
+ function createPrivyClient(options) {
215
+ const appId = options.appId.trim();
216
+ const walletId = options.walletId.trim();
217
+ if (appId.length === 0) {
218
+ throw new TxError(
219
+ "PRIVY_AUTH_FAILED",
220
+ "Invalid PRIVY_APP_ID format: expected non-empty string"
221
+ );
222
+ }
223
+ if (walletId.length === 0) {
224
+ throw new TxError(
225
+ "PRIVY_AUTH_FAILED",
226
+ "Invalid PRIVY_WALLET_ID format: expected non-empty string"
227
+ );
228
+ }
229
+ const apiUrl = normalizePrivyApiUrl(options.apiUrl);
230
+ const fetchImplementation = options.fetchImplementation ?? fetch;
231
+ return {
232
+ appId,
233
+ walletId,
234
+ apiUrl,
235
+ async createRpcIntent(request, requestOptions) {
236
+ const url = `${apiUrl}/v1/intents/wallets/${walletId}/rpc`;
237
+ const payload = createPrivyAuthorizationPayload({
238
+ appId,
239
+ method: "POST",
240
+ url,
241
+ body: request,
242
+ ...requestOptions?.idempotencyKey !== void 0 ? { idempotencyKey: requestOptions.idempotencyKey } : {}
243
+ });
244
+ const signature = generatePrivyAuthorizationSignature(payload, options.authorizationKey);
245
+ return sendPrivyRequest({
246
+ fetchImplementation,
247
+ method: "POST",
248
+ url,
249
+ body: request,
250
+ operation: "create rpc intent",
251
+ headers: {
252
+ "privy-app-id": appId,
253
+ "privy-authorization-signature": signature,
254
+ ...requestOptions?.idempotencyKey !== void 0 ? { "privy-idempotency-key": requestOptions.idempotencyKey } : {}
255
+ }
256
+ });
257
+ },
258
+ async getWallet() {
259
+ const url = `${apiUrl}/v1/wallets/${walletId}`;
260
+ return sendPrivyRequest({
261
+ fetchImplementation,
262
+ method: "GET",
263
+ url,
264
+ operation: "get wallet",
265
+ headers: {
266
+ "privy-app-id": appId
267
+ }
268
+ });
269
+ },
270
+ async getPolicy(policyId) {
271
+ if (policyId.trim().length === 0) {
272
+ throw new TxError(
273
+ "PRIVY_TRANSPORT_FAILED",
274
+ "Failed to build Privy policy lookup request: policy id is empty"
275
+ );
276
+ }
277
+ const url = `${apiUrl}/v1/policies/${policyId}`;
278
+ return sendPrivyRequest({
279
+ fetchImplementation,
280
+ method: "GET",
281
+ url,
282
+ operation: "get policy",
283
+ headers: {
284
+ "privy-app-id": appId
285
+ }
286
+ });
287
+ }
288
+ };
289
+ }
290
+ async function sendPrivyRequest(options) {
291
+ let response;
292
+ try {
293
+ response = await options.fetchImplementation(options.url, {
294
+ method: options.method,
295
+ headers: {
296
+ ...options.headers,
297
+ ...options.body !== void 0 ? { "content-type": "application/json" } : {}
298
+ },
299
+ ...options.body !== void 0 ? { body: JSON.stringify(options.body) } : {}
300
+ });
301
+ } catch (cause) {
302
+ throw new TxError(
303
+ "PRIVY_TRANSPORT_FAILED",
304
+ `Privy ${options.operation} request failed: network error`,
305
+ cause
306
+ );
307
+ }
308
+ const payload = await parseJsonResponse(response, options.operation);
309
+ if (!response.ok) {
310
+ const message = extractPrivyErrorMessage(payload) ?? `HTTP ${response.status}`;
311
+ if (response.status === 401 || response.status === 403) {
312
+ throw new TxError(
313
+ "PRIVY_AUTH_FAILED",
314
+ `Privy authentication failed (${response.status}): ${message}`
315
+ );
316
+ }
317
+ throw new TxError(
318
+ "PRIVY_TRANSPORT_FAILED",
319
+ `Privy ${options.operation} request failed (${response.status}): ${message}`
320
+ );
321
+ }
322
+ if (!isRecord(payload)) {
323
+ throw new TxError(
324
+ "PRIVY_TRANSPORT_FAILED",
325
+ `Privy ${options.operation} request failed: invalid JSON response shape`
326
+ );
327
+ }
328
+ return payload;
329
+ }
330
+ async function parseJsonResponse(response, operation) {
331
+ const text = await response.text();
332
+ if (text.trim().length === 0) {
333
+ return void 0;
334
+ }
335
+ try {
336
+ return JSON.parse(text);
337
+ } catch (cause) {
338
+ throw new TxError(
339
+ "PRIVY_TRANSPORT_FAILED",
340
+ `Privy ${operation} request failed: invalid JSON response`,
341
+ cause
342
+ );
343
+ }
344
+ }
345
+ function isRecord(value) {
346
+ return typeof value === "object" && value !== null;
347
+ }
348
+ function extractPrivyErrorMessage(payload) {
349
+ if (!isRecord(payload)) {
350
+ return void 0;
351
+ }
352
+ const directMessage = payload.message;
353
+ if (typeof directMessage === "string" && directMessage.length > 0) {
354
+ return directMessage;
355
+ }
356
+ const errorValue = payload.error;
357
+ if (typeof errorValue === "string" && errorValue.length > 0) {
358
+ return errorValue;
359
+ }
360
+ if (isRecord(errorValue)) {
361
+ const nestedMessage = errorValue.message;
362
+ if (typeof nestedMessage === "string" && nestedMessage.length > 0) {
363
+ return nestedMessage;
364
+ }
365
+ }
366
+ return void 0;
367
+ }
368
+
60
369
  // src/signers/privy.ts
370
+ import { zeroAddress } from "viem";
61
371
  var REQUIRED_FIELDS = [
62
372
  "privyAppId",
63
373
  "privyWalletId",
64
374
  "privyAuthorizationKey"
65
375
  ];
376
+ var APP_ID_REGEX = /^[A-Za-z0-9_-]{8,128}$/;
377
+ var WALLET_ID_REGEX = /^[A-Za-z0-9_-]{8,128}$/;
66
378
  async function createPrivySigner(options) {
67
- const missing = REQUIRED_FIELDS.filter((field) => options[field] === void 0);
379
+ const missing = REQUIRED_FIELDS.filter((field) => {
380
+ const value = options[field];
381
+ return typeof value !== "string" || value.trim().length === 0;
382
+ });
68
383
  if (missing.length > 0) {
69
384
  const missingLabels = missing.map((field) => {
70
385
  if (field === "privyAppId") {
@@ -80,10 +395,44 @@ async function createPrivySigner(options) {
80
395
  `Privy signer requires configuration: missing ${missingLabels}`
81
396
  );
82
397
  }
83
- throw new TxError(
84
- "PRIVY_AUTH_FAILED",
85
- "Privy signer is not yet available in tx-shared. Track progress in issue #117."
86
- );
398
+ const appId = options.privyAppId?.trim() ?? "";
399
+ const walletId = options.privyWalletId?.trim() ?? "";
400
+ const authorizationKey = options.privyAuthorizationKey?.trim() ?? "";
401
+ if (!APP_ID_REGEX.test(appId)) {
402
+ throw new TxError(
403
+ "PRIVY_AUTH_FAILED",
404
+ "Invalid PRIVY_APP_ID format: expected 8-128 chars using letters, numbers, hyphen, or underscore"
405
+ );
406
+ }
407
+ if (!WALLET_ID_REGEX.test(walletId)) {
408
+ throw new TxError(
409
+ "PRIVY_AUTH_FAILED",
410
+ "Invalid PRIVY_WALLET_ID format: expected 8-128 chars using letters, numbers, hyphen, or underscore"
411
+ );
412
+ }
413
+ parsePrivyAuthorizationKey(authorizationKey);
414
+ const apiUrl = normalizePrivyApiUrl(options.privyApiUrl);
415
+ const client = createPrivyClient({
416
+ appId,
417
+ walletId,
418
+ authorizationKey,
419
+ apiUrl
420
+ });
421
+ const account = {
422
+ address: zeroAddress,
423
+ type: "json-rpc"
424
+ };
425
+ return {
426
+ provider: "privy",
427
+ account,
428
+ address: account.address,
429
+ privy: {
430
+ appId,
431
+ walletId,
432
+ apiUrl,
433
+ client
434
+ }
435
+ };
87
436
  }
88
437
 
89
438
  // src/resolve-signer.ts
@@ -110,7 +459,8 @@ async function resolveSigner(opts) {
110
459
  return createPrivySigner({
111
460
  ...opts.privyAppId !== void 0 ? { privyAppId: opts.privyAppId } : {},
112
461
  ...opts.privyWalletId !== void 0 ? { privyWalletId: opts.privyWalletId } : {},
113
- ...opts.privyAuthorizationKey !== void 0 ? { privyAuthorizationKey: opts.privyAuthorizationKey } : {}
462
+ ...opts.privyAuthorizationKey !== void 0 ? { privyAuthorizationKey: opts.privyAuthorizationKey } : {},
463
+ ...opts.privyApiUrl !== void 0 ? { privyApiUrl: opts.privyApiUrl } : {}
114
464
  });
115
465
  }
116
466
  throw new TxError(
@@ -125,18 +475,21 @@ var signerFlagSchema = z.object({
125
475
  "private-key": z.string().optional().describe("Raw private key (0x-prefixed 32-byte hex) for signing transactions"),
126
476
  keystore: z.string().optional().describe("Path to an encrypted V3 keystore JSON file"),
127
477
  password: z.string().optional().describe("Keystore password (non-interactive mode)"),
128
- privy: z.boolean().default(false).describe("Use Privy server wallet signer mode")
478
+ privy: z.boolean().default(false).describe("Use Privy server wallet signer mode"),
479
+ "privy-api-url": z.string().optional().describe("Override Privy API base URL (defaults to https://api.privy.io)")
129
480
  });
130
481
  var signerEnvSchema = z.object({
131
482
  PRIVATE_KEY: z.string().optional().describe("Raw private key (0x-prefixed 32-byte hex)"),
132
483
  KEYSTORE_PASSWORD: z.string().optional().describe("Password for decrypting --keystore"),
133
484
  PRIVY_APP_ID: z.string().optional().describe("Privy app id used to authorize wallet intents"),
134
485
  PRIVY_WALLET_ID: z.string().optional().describe("Privy wallet id used for transaction intents"),
135
- PRIVY_AUTHORIZATION_KEY: z.string().optional().describe("Privy authorization private key used to sign intent requests")
486
+ PRIVY_AUTHORIZATION_KEY: z.string().optional().describe("Privy authorization private key used to sign intent requests"),
487
+ PRIVY_API_URL: z.string().optional().describe("Optional Privy API base URL override (default https://api.privy.io)")
136
488
  });
137
489
  function toSignerOptions(flags, env) {
138
490
  const privateKey = flags["private-key"] ?? env.PRIVATE_KEY;
139
491
  const keystorePassword = flags.password ?? env.KEYSTORE_PASSWORD;
492
+ const privyApiUrl = flags["privy-api-url"] ?? env.PRIVY_API_URL;
140
493
  return {
141
494
  ...privateKey !== void 0 ? { privateKey } : {},
142
495
  ...flags.keystore !== void 0 ? { keystorePath: flags.keystore } : {},
@@ -144,7 +497,8 @@ function toSignerOptions(flags, env) {
144
497
  ...flags.privy ? { privy: true } : {},
145
498
  ...env.PRIVY_APP_ID !== void 0 ? { privyAppId: env.PRIVY_APP_ID } : {},
146
499
  ...env.PRIVY_WALLET_ID !== void 0 ? { privyWalletId: env.PRIVY_WALLET_ID } : {},
147
- ...env.PRIVY_AUTHORIZATION_KEY !== void 0 ? { privyAuthorizationKey: env.PRIVY_AUTHORIZATION_KEY } : {}
500
+ ...env.PRIVY_AUTHORIZATION_KEY !== void 0 ? { privyAuthorizationKey: env.PRIVY_AUTHORIZATION_KEY } : {},
501
+ ...privyApiUrl !== void 0 ? { privyApiUrl } : {}
148
502
  };
149
503
  }
150
504
  export {
@@ -153,9 +507,15 @@ export {
153
507
  createAbstractClient,
154
508
  createKeystoreSigner,
155
509
  createPrivateKeySigner,
510
+ createPrivyAuthorizationPayload,
511
+ createPrivyClient,
156
512
  createPrivySigner,
157
513
  executeTx,
514
+ generatePrivyAuthorizationSignature,
515
+ normalizePrivyApiUrl,
516
+ parsePrivyAuthorizationKey,
158
517
  resolveSigner,
518
+ serializePrivyAuthorizationPayload,
159
519
  signerEnvSchema,
160
520
  signerFlagSchema,
161
521
  toSignerOptions,
package/dist/types.d.ts CHANGED
@@ -23,6 +23,7 @@ interface SignerOptions {
23
23
  privyAppId?: string;
24
24
  privyWalletId?: string;
25
25
  privyAuthorizationKey?: string;
26
+ privyApiUrl?: string;
26
27
  }
27
28
 
28
29
  export type { SignerOptions, SignerProvider, TxResult, TxSigner };
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@spectratools/tx-shared",
3
- "version": "0.4.1",
3
+ "version": "0.4.2",
4
4
  "description": "Shared transaction primitives, signer types, and chain config for spectra tools",
5
5
  "type": "module",
6
6
  "license": "MIT",