@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 +1 -1
- package/dist/index.d.ts +88 -5
- package/dist/index.js +369 -9
- package/dist/types.d.ts +1 -0
- package/package.json +1 -1
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
|
|
149
|
+
* Create a Privy signer envelope with reusable transport and request-signing primitives.
|
|
67
150
|
*
|
|
68
|
-
*
|
|
69
|
-
*
|
|
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<
|
|
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) =>
|
|
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
|
-
|
|
84
|
-
|
|
85
|
-
|
|
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