@opensea/wallet-adapters 0.1.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/README.md +145 -0
- package/dist/ethers.d.ts +56 -0
- package/dist/ethers.js +62 -0
- package/dist/ethers.js.map +1 -0
- package/dist/index-DqqC-Sa8.d.ts +80 -0
- package/dist/index.d.ts +198 -0
- package/dist/index.js +1376 -0
- package/dist/index.js.map +1 -0
- package/dist/viem.d.ts +6 -0
- package/dist/viem.js +41 -0
- package/dist/viem.js.map +1 -0
- package/package.json +71 -0
package/dist/index.js
ADDED
|
@@ -0,0 +1,1376 @@
|
|
|
1
|
+
// src/util/eip712.ts
|
|
2
|
+
import { keccak_256 } from "@noble/hashes/sha3.js";
|
|
3
|
+
function hashTypedData(domain, primaryType, message, types) {
|
|
4
|
+
const domainSeparator = hashStruct("EIP712Domain", domain, {
|
|
5
|
+
...types,
|
|
6
|
+
EIP712Domain: buildDomainType(domain)
|
|
7
|
+
});
|
|
8
|
+
const messageHash = hashStruct(primaryType, message, types);
|
|
9
|
+
const packed = new Uint8Array(66);
|
|
10
|
+
packed[0] = 25;
|
|
11
|
+
packed[1] = 1;
|
|
12
|
+
packed.set(domainSeparator, 2);
|
|
13
|
+
packed.set(messageHash, 34);
|
|
14
|
+
return keccak_256(packed);
|
|
15
|
+
}
|
|
16
|
+
function hashPersonalMessage(message) {
|
|
17
|
+
const msgBytes = typeof message === "string" ? new TextEncoder().encode(message) : message;
|
|
18
|
+
const prefix = new TextEncoder().encode(
|
|
19
|
+
`Ethereum Signed Message:
|
|
20
|
+
${msgBytes.length}`
|
|
21
|
+
);
|
|
22
|
+
const payload = new Uint8Array(prefix.length + msgBytes.length);
|
|
23
|
+
payload.set(prefix, 0);
|
|
24
|
+
payload.set(msgBytes, prefix.length);
|
|
25
|
+
return keccak_256(payload);
|
|
26
|
+
}
|
|
27
|
+
function buildDomainType(domain) {
|
|
28
|
+
const fields = [];
|
|
29
|
+
if ("name" in domain) fields.push({ name: "name", type: "string" });
|
|
30
|
+
if ("version" in domain) fields.push({ name: "version", type: "string" });
|
|
31
|
+
if ("chainId" in domain) fields.push({ name: "chainId", type: "uint256" });
|
|
32
|
+
if ("verifyingContract" in domain)
|
|
33
|
+
fields.push({ name: "verifyingContract", type: "address" });
|
|
34
|
+
if ("salt" in domain) fields.push({ name: "salt", type: "bytes32" });
|
|
35
|
+
return fields;
|
|
36
|
+
}
|
|
37
|
+
function hashStruct(primaryType, data, types) {
|
|
38
|
+
const typeHash = hashType(primaryType, types);
|
|
39
|
+
const encodedValues = encodeData(primaryType, data, types);
|
|
40
|
+
const combined = new Uint8Array(32 + encodedValues.length);
|
|
41
|
+
combined.set(typeHash, 0);
|
|
42
|
+
combined.set(encodedValues, 32);
|
|
43
|
+
return keccak_256(combined);
|
|
44
|
+
}
|
|
45
|
+
function hashType(primaryType, types) {
|
|
46
|
+
return keccak_256(new TextEncoder().encode(encodeType(primaryType, types)));
|
|
47
|
+
}
|
|
48
|
+
function encodeType(primaryType, types) {
|
|
49
|
+
const fields = types[primaryType];
|
|
50
|
+
if (!fields) throw new Error(`Unknown type: ${primaryType}`);
|
|
51
|
+
const deps = /* @__PURE__ */ new Set();
|
|
52
|
+
findTypeDeps(primaryType, types, deps);
|
|
53
|
+
deps.delete(primaryType);
|
|
54
|
+
const sorted = [...deps].sort();
|
|
55
|
+
let result = `${primaryType}(${fields.map((f) => `${f.type} ${f.name}`).join(",")})`;
|
|
56
|
+
for (const dep of sorted) {
|
|
57
|
+
const depFields = types[dep];
|
|
58
|
+
if (depFields) {
|
|
59
|
+
result += `${dep}(${depFields.map((f) => `${f.type} ${f.name}`).join(",")})`;
|
|
60
|
+
}
|
|
61
|
+
}
|
|
62
|
+
return result;
|
|
63
|
+
}
|
|
64
|
+
function findTypeDeps(type, types, deps) {
|
|
65
|
+
if (deps.has(type)) return;
|
|
66
|
+
const fields = types[type];
|
|
67
|
+
if (!fields) return;
|
|
68
|
+
deps.add(type);
|
|
69
|
+
for (const field of fields) {
|
|
70
|
+
const baseType = field.type.replace(/\[\d*\]$/, "");
|
|
71
|
+
if (types[baseType]) {
|
|
72
|
+
findTypeDeps(baseType, types, deps);
|
|
73
|
+
}
|
|
74
|
+
}
|
|
75
|
+
}
|
|
76
|
+
function encodeData(primaryType, data, types) {
|
|
77
|
+
const fields = types[primaryType];
|
|
78
|
+
if (!fields) throw new Error(`Unknown type: ${primaryType}`);
|
|
79
|
+
const chunks = [];
|
|
80
|
+
for (const field of fields) {
|
|
81
|
+
const value = data[field.name];
|
|
82
|
+
chunks.push(encodeValue(field.type, value, types));
|
|
83
|
+
}
|
|
84
|
+
const result = new Uint8Array(chunks.length * 32);
|
|
85
|
+
for (let i = 0; i < chunks.length; i++) {
|
|
86
|
+
result.set(chunks[i], i * 32);
|
|
87
|
+
}
|
|
88
|
+
return result;
|
|
89
|
+
}
|
|
90
|
+
function encodeValue(type, value, types) {
|
|
91
|
+
if (type === "string") {
|
|
92
|
+
return keccak_256(new TextEncoder().encode(value));
|
|
93
|
+
}
|
|
94
|
+
if (type === "bytes") {
|
|
95
|
+
const v = value;
|
|
96
|
+
const bytes = hexToBytes(v.startsWith("0x") ? v.slice(2) : v);
|
|
97
|
+
return keccak_256(bytes);
|
|
98
|
+
}
|
|
99
|
+
if (type.endsWith("]")) {
|
|
100
|
+
const baseType = type.replace(/\[\d*\]$/, "");
|
|
101
|
+
const arr = value;
|
|
102
|
+
const encoded = arr.map((item) => encodeValue(baseType, item, types));
|
|
103
|
+
const flat = new Uint8Array(encoded.length * 32);
|
|
104
|
+
for (let i = 0; i < encoded.length; i++) flat.set(encoded[i], i * 32);
|
|
105
|
+
return keccak_256(flat);
|
|
106
|
+
}
|
|
107
|
+
if (types[type]) {
|
|
108
|
+
return hashStruct(type, value, types);
|
|
109
|
+
}
|
|
110
|
+
if (type === "address") {
|
|
111
|
+
const addr = value.toLowerCase().replace("0x", "");
|
|
112
|
+
const padded = new Uint8Array(32);
|
|
113
|
+
padded.set(hexToBytes(addr.padStart(40, "0")), 12);
|
|
114
|
+
return padded;
|
|
115
|
+
}
|
|
116
|
+
if (type === "bool") {
|
|
117
|
+
const padded = new Uint8Array(32);
|
|
118
|
+
padded[31] = value ? 1 : 0;
|
|
119
|
+
return padded;
|
|
120
|
+
}
|
|
121
|
+
if (type.startsWith("uint") || type.startsWith("int")) {
|
|
122
|
+
const padded = new Uint8Array(32);
|
|
123
|
+
const n = BigInt(value);
|
|
124
|
+
const hex = (n < 0n ? n + (1n << 256n) : n).toString(16).padStart(64, "0");
|
|
125
|
+
padded.set(hexToBytes(hex), 0);
|
|
126
|
+
return padded;
|
|
127
|
+
}
|
|
128
|
+
if (type.startsWith("bytes")) {
|
|
129
|
+
const v = value;
|
|
130
|
+
const bytes = hexToBytes(v.startsWith("0x") ? v.slice(2) : v);
|
|
131
|
+
const padded = new Uint8Array(32);
|
|
132
|
+
padded.set(bytes, 0);
|
|
133
|
+
return padded;
|
|
134
|
+
}
|
|
135
|
+
throw new Error(`Unsupported EIP-712 type: ${type}`);
|
|
136
|
+
}
|
|
137
|
+
function hexToBytes(hex) {
|
|
138
|
+
const clean = hex.startsWith("0x") ? hex.slice(2) : hex;
|
|
139
|
+
const bytes = new Uint8Array(clean.length / 2);
|
|
140
|
+
for (let i = 0; i < bytes.length; i++) {
|
|
141
|
+
bytes[i] = Number.parseInt(clean.slice(i * 2, i * 2 + 2), 16);
|
|
142
|
+
}
|
|
143
|
+
return bytes;
|
|
144
|
+
}
|
|
145
|
+
|
|
146
|
+
// src/adapters/fireblocks.ts
|
|
147
|
+
var CHAIN_TO_FIREBLOCKS_ASSET = {
|
|
148
|
+
1: "ETH",
|
|
149
|
+
10: "ETH-OPT",
|
|
150
|
+
130: "UNICHAIN_ETH",
|
|
151
|
+
137: "MATIC_POLYGON",
|
|
152
|
+
360: "SHAPE_ETH",
|
|
153
|
+
1329: "SEI_EVM",
|
|
154
|
+
1868: "SONEIUM_ETH",
|
|
155
|
+
2741: "ABSTRACT_ETH",
|
|
156
|
+
8453: "BASECHAIN_ETH",
|
|
157
|
+
33139: "APE_CHAIN",
|
|
158
|
+
42161: "ETH-AETH",
|
|
159
|
+
43114: "AVAX",
|
|
160
|
+
80094: "BERA_CHAIN",
|
|
161
|
+
81457: "BLAST_ETH",
|
|
162
|
+
7777777: "ZORA_ETH"
|
|
163
|
+
};
|
|
164
|
+
var FIREBLOCKS_API_BASE = "https://api.fireblocks.io";
|
|
165
|
+
var FireblocksAdapter = class _FireblocksAdapter {
|
|
166
|
+
name = "fireblocks";
|
|
167
|
+
capabilities = {
|
|
168
|
+
signMessage: true,
|
|
169
|
+
signTypedData: true,
|
|
170
|
+
managedGas: true,
|
|
171
|
+
managedNonce: true
|
|
172
|
+
};
|
|
173
|
+
onRequest;
|
|
174
|
+
onResponse;
|
|
175
|
+
config;
|
|
176
|
+
cachedAddress;
|
|
177
|
+
constructor(config) {
|
|
178
|
+
this.config = config;
|
|
179
|
+
}
|
|
180
|
+
static fromEnv() {
|
|
181
|
+
const apiKey = process.env.FIREBLOCKS_API_KEY;
|
|
182
|
+
const apiSecret = process.env.FIREBLOCKS_API_SECRET;
|
|
183
|
+
const vaultId = process.env.FIREBLOCKS_VAULT_ID;
|
|
184
|
+
if (!apiKey) {
|
|
185
|
+
throw new Error("FIREBLOCKS_API_KEY environment variable is required");
|
|
186
|
+
}
|
|
187
|
+
if (!apiSecret) {
|
|
188
|
+
throw new Error("FIREBLOCKS_API_SECRET environment variable is required");
|
|
189
|
+
}
|
|
190
|
+
if (!vaultId) {
|
|
191
|
+
throw new Error("FIREBLOCKS_VAULT_ID environment variable is required");
|
|
192
|
+
}
|
|
193
|
+
const maxPollAttempts = process.env.FIREBLOCKS_MAX_POLL_ATTEMPTS ? Number.parseInt(process.env.FIREBLOCKS_MAX_POLL_ATTEMPTS, 10) : void 0;
|
|
194
|
+
return new _FireblocksAdapter({
|
|
195
|
+
apiKey,
|
|
196
|
+
apiSecret,
|
|
197
|
+
vaultId,
|
|
198
|
+
assetId: process.env.FIREBLOCKS_ASSET_ID,
|
|
199
|
+
baseUrl: process.env.FIREBLOCKS_API_BASE_URL,
|
|
200
|
+
maxPollAttempts
|
|
201
|
+
});
|
|
202
|
+
}
|
|
203
|
+
get baseUrl() {
|
|
204
|
+
return this.config.baseUrl ?? FIREBLOCKS_API_BASE;
|
|
205
|
+
}
|
|
206
|
+
async createJwt(path, bodyHash) {
|
|
207
|
+
const now = Math.floor(Date.now() / 1e3);
|
|
208
|
+
const header = { alg: "RS256", typ: "JWT" };
|
|
209
|
+
const payload = {
|
|
210
|
+
uri: path,
|
|
211
|
+
nonce: crypto.randomUUID(),
|
|
212
|
+
iat: now,
|
|
213
|
+
exp: now + 30,
|
|
214
|
+
sub: this.config.apiKey,
|
|
215
|
+
bodyHash
|
|
216
|
+
};
|
|
217
|
+
const b64url = (obj) => Buffer.from(JSON.stringify(obj)).toString("base64url");
|
|
218
|
+
const unsigned = `${b64url(header)}.${b64url(payload)}`;
|
|
219
|
+
const key = await crypto.subtle.importKey(
|
|
220
|
+
"pkcs8",
|
|
221
|
+
this.pemToBuffer(this.config.apiSecret),
|
|
222
|
+
{ name: "RSASSA-PKCS1-v1_5", hash: "SHA-256" },
|
|
223
|
+
false,
|
|
224
|
+
["sign"]
|
|
225
|
+
);
|
|
226
|
+
const sig = await crypto.subtle.sign(
|
|
227
|
+
"RSASSA-PKCS1-v1_5",
|
|
228
|
+
key,
|
|
229
|
+
new TextEncoder().encode(unsigned)
|
|
230
|
+
);
|
|
231
|
+
return `${unsigned}.${Buffer.from(sig).toString("base64url")}`;
|
|
232
|
+
}
|
|
233
|
+
pemToBuffer(pem) {
|
|
234
|
+
const lines = pem.replace(/-----BEGIN .*-----/, "").replace(/-----END .*-----/, "").replace(/\s/g, "");
|
|
235
|
+
const buf = Buffer.from(lines, "base64");
|
|
236
|
+
return buf.buffer.slice(buf.byteOffset, buf.byteOffset + buf.byteLength);
|
|
237
|
+
}
|
|
238
|
+
async hashBody(body) {
|
|
239
|
+
const hash = await crypto.subtle.digest(
|
|
240
|
+
"SHA-256",
|
|
241
|
+
new TextEncoder().encode(body)
|
|
242
|
+
);
|
|
243
|
+
return Buffer.from(hash).toString("hex");
|
|
244
|
+
}
|
|
245
|
+
resolveAssetId(chainId) {
|
|
246
|
+
if (this.config.assetId) return this.config.assetId;
|
|
247
|
+
const asset = CHAIN_TO_FIREBLOCKS_ASSET[chainId];
|
|
248
|
+
if (!asset) {
|
|
249
|
+
throw new Error(
|
|
250
|
+
`No Fireblocks asset ID mapping for chain ${chainId}. Set FIREBLOCKS_ASSET_ID explicitly or use a supported chain: ${Object.keys(CHAIN_TO_FIREBLOCKS_ASSET).join(", ")}`
|
|
251
|
+
);
|
|
252
|
+
}
|
|
253
|
+
return asset;
|
|
254
|
+
}
|
|
255
|
+
async getAddress() {
|
|
256
|
+
if (this.cachedAddress) return this.cachedAddress;
|
|
257
|
+
const assetId = this.config.assetId ?? "ETH";
|
|
258
|
+
const path = `/v1/vault/accounts/${this.config.vaultId}/${assetId}/addresses`;
|
|
259
|
+
const bodyHash = await this.hashBody("");
|
|
260
|
+
const jwt = await this.createJwt(path, bodyHash);
|
|
261
|
+
const response = await fetch(`${this.baseUrl}${path}`, {
|
|
262
|
+
headers: {
|
|
263
|
+
"X-API-Key": this.config.apiKey,
|
|
264
|
+
Authorization: `Bearer ${jwt}`
|
|
265
|
+
}
|
|
266
|
+
});
|
|
267
|
+
if (!response.ok) {
|
|
268
|
+
const body = await response.text();
|
|
269
|
+
throw new Error(
|
|
270
|
+
`Fireblocks getAddress failed (${response.status}): ${body}`
|
|
271
|
+
);
|
|
272
|
+
}
|
|
273
|
+
const data = await response.json();
|
|
274
|
+
if (!data[0]?.address) {
|
|
275
|
+
throw new Error("Fireblocks returned no addresses for vault");
|
|
276
|
+
}
|
|
277
|
+
this.cachedAddress = data[0].address;
|
|
278
|
+
return data[0].address;
|
|
279
|
+
}
|
|
280
|
+
async sendTransaction(tx) {
|
|
281
|
+
this.onRequest?.("sendTransaction", tx);
|
|
282
|
+
const startTime = Date.now();
|
|
283
|
+
const assetId = this.resolveAssetId(tx.chainId);
|
|
284
|
+
const path = "/v1/transactions";
|
|
285
|
+
const requestBody = {
|
|
286
|
+
assetId,
|
|
287
|
+
operation: "CONTRACT_CALL",
|
|
288
|
+
source: {
|
|
289
|
+
type: "VAULT_ACCOUNT",
|
|
290
|
+
id: this.config.vaultId
|
|
291
|
+
},
|
|
292
|
+
destination: {
|
|
293
|
+
type: "ONE_TIME_ADDRESS",
|
|
294
|
+
oneTimeAddress: { address: tx.to }
|
|
295
|
+
},
|
|
296
|
+
amount: tx.value === "0" ? "0" : tx.value,
|
|
297
|
+
extraParameters: {
|
|
298
|
+
contractCallData: tx.data
|
|
299
|
+
}
|
|
300
|
+
};
|
|
301
|
+
const bodyStr = JSON.stringify(requestBody);
|
|
302
|
+
const bodyHash = await this.hashBody(bodyStr);
|
|
303
|
+
const jwt = await this.createJwt(path, bodyHash);
|
|
304
|
+
const response = await fetch(`${this.baseUrl}${path}`, {
|
|
305
|
+
method: "POST",
|
|
306
|
+
headers: {
|
|
307
|
+
"Content-Type": "application/json",
|
|
308
|
+
"X-API-Key": this.config.apiKey,
|
|
309
|
+
Authorization: `Bearer ${jwt}`
|
|
310
|
+
},
|
|
311
|
+
body: bodyStr
|
|
312
|
+
});
|
|
313
|
+
if (!response.ok) {
|
|
314
|
+
const body = await response.text();
|
|
315
|
+
throw new Error(
|
|
316
|
+
`Fireblocks sendTransaction failed (${response.status}): ${body}`
|
|
317
|
+
);
|
|
318
|
+
}
|
|
319
|
+
const data = await response.json();
|
|
320
|
+
if (data.txHash) {
|
|
321
|
+
const result2 = { hash: data.txHash };
|
|
322
|
+
this.onResponse?.("sendTransaction", result2, Date.now() - startTime);
|
|
323
|
+
return result2;
|
|
324
|
+
}
|
|
325
|
+
const result = await this.waitForTransaction(data.id);
|
|
326
|
+
this.onResponse?.("sendTransaction", result, Date.now() - startTime);
|
|
327
|
+
return result;
|
|
328
|
+
}
|
|
329
|
+
async signMessage(request) {
|
|
330
|
+
this.onRequest?.("signMessage", request);
|
|
331
|
+
const startTime = Date.now();
|
|
332
|
+
const hash = Buffer.from(hashPersonalMessage(request.message)).toString(
|
|
333
|
+
"hex"
|
|
334
|
+
);
|
|
335
|
+
const signature = await this.signRawMessage(hash);
|
|
336
|
+
this.onResponse?.("signMessage", signature, Date.now() - startTime);
|
|
337
|
+
return signature;
|
|
338
|
+
}
|
|
339
|
+
async signTypedData(request) {
|
|
340
|
+
this.onRequest?.("signTypedData", request);
|
|
341
|
+
const startTime = Date.now();
|
|
342
|
+
const hash = Buffer.from(
|
|
343
|
+
hashTypedData(
|
|
344
|
+
request.domain,
|
|
345
|
+
request.primaryType,
|
|
346
|
+
request.message,
|
|
347
|
+
request.types
|
|
348
|
+
)
|
|
349
|
+
).toString("hex");
|
|
350
|
+
const signature = await this.signRawMessage(hash);
|
|
351
|
+
this.onResponse?.("signTypedData", signature, Date.now() - startTime);
|
|
352
|
+
return signature;
|
|
353
|
+
}
|
|
354
|
+
async signRawMessage(hashHex) {
|
|
355
|
+
const path = "/v1/transactions";
|
|
356
|
+
const requestBody = {
|
|
357
|
+
assetId: this.config.assetId ?? "ETH",
|
|
358
|
+
operation: "RAW",
|
|
359
|
+
source: {
|
|
360
|
+
type: "VAULT_ACCOUNT",
|
|
361
|
+
id: this.config.vaultId
|
|
362
|
+
},
|
|
363
|
+
extraParameters: {
|
|
364
|
+
rawMessageData: {
|
|
365
|
+
messages: [
|
|
366
|
+
{
|
|
367
|
+
content: hashHex
|
|
368
|
+
}
|
|
369
|
+
]
|
|
370
|
+
}
|
|
371
|
+
}
|
|
372
|
+
};
|
|
373
|
+
const bodyStr = JSON.stringify(requestBody);
|
|
374
|
+
const bodyHash = await this.hashBody(bodyStr);
|
|
375
|
+
const jwt = await this.createJwt(path, bodyHash);
|
|
376
|
+
const response = await fetch(`${this.baseUrl}${path}`, {
|
|
377
|
+
method: "POST",
|
|
378
|
+
headers: {
|
|
379
|
+
"Content-Type": "application/json",
|
|
380
|
+
"X-API-Key": this.config.apiKey,
|
|
381
|
+
Authorization: `Bearer ${jwt}`
|
|
382
|
+
},
|
|
383
|
+
body: bodyStr
|
|
384
|
+
});
|
|
385
|
+
if (!response.ok) {
|
|
386
|
+
const body = await response.text();
|
|
387
|
+
throw new Error(
|
|
388
|
+
`Fireblocks signRawMessage failed (${response.status}): ${body}`
|
|
389
|
+
);
|
|
390
|
+
}
|
|
391
|
+
const data = await response.json();
|
|
392
|
+
return this.waitForSignature(data.id);
|
|
393
|
+
}
|
|
394
|
+
async waitForSignature(txId) {
|
|
395
|
+
const maxAttempts = this.config.maxPollAttempts ?? 60;
|
|
396
|
+
const pollIntervalMs = 2e3;
|
|
397
|
+
for (let i = 0; i < maxAttempts; i++) {
|
|
398
|
+
const path = `/v1/transactions/${txId}`;
|
|
399
|
+
const bodyHash = await this.hashBody("");
|
|
400
|
+
const jwt = await this.createJwt(path, bodyHash);
|
|
401
|
+
const response = await fetch(`${this.baseUrl}${path}`, {
|
|
402
|
+
headers: {
|
|
403
|
+
"X-API-Key": this.config.apiKey,
|
|
404
|
+
Authorization: `Bearer ${jwt}`
|
|
405
|
+
}
|
|
406
|
+
});
|
|
407
|
+
if (!response.ok) {
|
|
408
|
+
const body = await response.text();
|
|
409
|
+
throw new Error(
|
|
410
|
+
`Fireblocks signature poll failed (${response.status}): ${body}`
|
|
411
|
+
);
|
|
412
|
+
}
|
|
413
|
+
const data = await response.json();
|
|
414
|
+
if (data.status === "COMPLETED" && data.signedMessages?.[0]) {
|
|
415
|
+
const { r, s, v } = data.signedMessages[0].signature;
|
|
416
|
+
const rHex = r.padStart(64, "0");
|
|
417
|
+
const sHex = s.padStart(64, "0");
|
|
418
|
+
const vHex = (v + 27).toString(16).padStart(2, "0");
|
|
419
|
+
return `0x${rHex}${sHex}${vHex}`;
|
|
420
|
+
}
|
|
421
|
+
if (data.status === "FAILED" || data.status === "REJECTED" || data.status === "CANCELLED" || data.status === "BLOCKED") {
|
|
422
|
+
throw new Error(
|
|
423
|
+
`Fireblocks signing ${txId} ended with status: ${data.status}`
|
|
424
|
+
);
|
|
425
|
+
}
|
|
426
|
+
await new Promise((resolve) => setTimeout(resolve, pollIntervalMs));
|
|
427
|
+
}
|
|
428
|
+
throw new Error(
|
|
429
|
+
`Fireblocks signing ${txId} did not complete within ${maxAttempts * pollIntervalMs / 1e3}s`
|
|
430
|
+
);
|
|
431
|
+
}
|
|
432
|
+
async waitForTransaction(txId) {
|
|
433
|
+
const maxAttempts = this.config.maxPollAttempts ?? 60;
|
|
434
|
+
const pollIntervalMs = 2e3;
|
|
435
|
+
for (let i = 0; i < maxAttempts; i++) {
|
|
436
|
+
const path = `/v1/transactions/${txId}`;
|
|
437
|
+
const bodyHash = await this.hashBody("");
|
|
438
|
+
const jwt = await this.createJwt(path, bodyHash);
|
|
439
|
+
const response = await fetch(`${this.baseUrl}${path}`, {
|
|
440
|
+
headers: {
|
|
441
|
+
"X-API-Key": this.config.apiKey,
|
|
442
|
+
Authorization: `Bearer ${jwt}`
|
|
443
|
+
}
|
|
444
|
+
});
|
|
445
|
+
if (!response.ok) {
|
|
446
|
+
const body = await response.text();
|
|
447
|
+
throw new Error(`Fireblocks poll failed (${response.status}): ${body}`);
|
|
448
|
+
}
|
|
449
|
+
const data = await response.json();
|
|
450
|
+
if (data.status === "COMPLETED" && data.txHash) {
|
|
451
|
+
return { hash: data.txHash };
|
|
452
|
+
}
|
|
453
|
+
if (data.status === "FAILED" || data.status === "REJECTED" || data.status === "CANCELLED" || data.status === "BLOCKED") {
|
|
454
|
+
throw new Error(
|
|
455
|
+
`Fireblocks transaction ${txId} ended with status: ${data.status}`
|
|
456
|
+
);
|
|
457
|
+
}
|
|
458
|
+
await new Promise((resolve) => setTimeout(resolve, pollIntervalMs));
|
|
459
|
+
}
|
|
460
|
+
throw new Error(
|
|
461
|
+
`Fireblocks transaction ${txId} did not complete within ${maxAttempts * pollIntervalMs / 1e3}s`
|
|
462
|
+
);
|
|
463
|
+
}
|
|
464
|
+
};
|
|
465
|
+
|
|
466
|
+
// src/adapters/private-key.ts
|
|
467
|
+
import { secp256k1 } from "@noble/curves/secp256k1";
|
|
468
|
+
import { keccak_256 as keccak_2562 } from "@noble/hashes/sha3.js";
|
|
469
|
+
import { bytesToHex as nobleToHex } from "@noble/hashes/utils.js";
|
|
470
|
+
|
|
471
|
+
// src/adapters/turnkey.ts
|
|
472
|
+
var TURNKEY_API_BASE = "https://api.turnkey.com";
|
|
473
|
+
var TurnkeyAdapter = class _TurnkeyAdapter {
|
|
474
|
+
name = "turnkey";
|
|
475
|
+
capabilities = {
|
|
476
|
+
signMessage: true,
|
|
477
|
+
signTypedData: true,
|
|
478
|
+
managedGas: false,
|
|
479
|
+
managedNonce: false
|
|
480
|
+
};
|
|
481
|
+
onRequest;
|
|
482
|
+
onResponse;
|
|
483
|
+
config;
|
|
484
|
+
constructor(config) {
|
|
485
|
+
this.config = config;
|
|
486
|
+
}
|
|
487
|
+
static fromEnv() {
|
|
488
|
+
const apiPublicKey = process.env.TURNKEY_API_PUBLIC_KEY;
|
|
489
|
+
const apiPrivateKey = process.env.TURNKEY_API_PRIVATE_KEY;
|
|
490
|
+
const organizationId = process.env.TURNKEY_ORGANIZATION_ID;
|
|
491
|
+
const walletAddress = process.env.TURNKEY_WALLET_ADDRESS;
|
|
492
|
+
if (!apiPublicKey) {
|
|
493
|
+
throw new Error("TURNKEY_API_PUBLIC_KEY environment variable is required");
|
|
494
|
+
}
|
|
495
|
+
if (!apiPrivateKey) {
|
|
496
|
+
throw new Error(
|
|
497
|
+
"TURNKEY_API_PRIVATE_KEY environment variable is required"
|
|
498
|
+
);
|
|
499
|
+
}
|
|
500
|
+
if (!organizationId) {
|
|
501
|
+
throw new Error(
|
|
502
|
+
"TURNKEY_ORGANIZATION_ID environment variable is required"
|
|
503
|
+
);
|
|
504
|
+
}
|
|
505
|
+
if (!walletAddress) {
|
|
506
|
+
throw new Error("TURNKEY_WALLET_ADDRESS environment variable is required");
|
|
507
|
+
}
|
|
508
|
+
const rpcUrl = process.env.TURNKEY_RPC_URL;
|
|
509
|
+
if (!rpcUrl) {
|
|
510
|
+
throw new Error(
|
|
511
|
+
"TURNKEY_RPC_URL environment variable is required. It is used for gas estimation and transaction broadcasting."
|
|
512
|
+
);
|
|
513
|
+
}
|
|
514
|
+
return new _TurnkeyAdapter({
|
|
515
|
+
apiPublicKey,
|
|
516
|
+
apiPrivateKey,
|
|
517
|
+
organizationId,
|
|
518
|
+
walletAddress,
|
|
519
|
+
rpcUrl,
|
|
520
|
+
privateKeyId: process.env.TURNKEY_PRIVATE_KEY_ID,
|
|
521
|
+
baseUrl: process.env.TURNKEY_API_BASE_URL
|
|
522
|
+
});
|
|
523
|
+
}
|
|
524
|
+
getRpcUrl() {
|
|
525
|
+
return this.config.rpcUrl;
|
|
526
|
+
}
|
|
527
|
+
get baseUrl() {
|
|
528
|
+
return this.config.baseUrl ?? TURNKEY_API_BASE;
|
|
529
|
+
}
|
|
530
|
+
async stamp(body) {
|
|
531
|
+
const encoder = new TextEncoder();
|
|
532
|
+
const bodyHash = await crypto.subtle.digest("SHA-256", encoder.encode(body));
|
|
533
|
+
const keyData = hexToBytes2(this.config.apiPrivateKey);
|
|
534
|
+
const cryptoKey = await crypto.subtle.importKey(
|
|
535
|
+
"pkcs8",
|
|
536
|
+
derEncodeP256PrivateKey(keyData),
|
|
537
|
+
{ name: "ECDSA", namedCurve: "P-256" },
|
|
538
|
+
false,
|
|
539
|
+
["sign"]
|
|
540
|
+
);
|
|
541
|
+
const p1363Sig = await crypto.subtle.sign(
|
|
542
|
+
{ name: "ECDSA", hash: "SHA-256" },
|
|
543
|
+
cryptoKey,
|
|
544
|
+
bodyHash
|
|
545
|
+
);
|
|
546
|
+
const derSig = p1363ToDer(new Uint8Array(p1363Sig));
|
|
547
|
+
const signatureHex = bytesToHex(derSig);
|
|
548
|
+
const stampJson = JSON.stringify({
|
|
549
|
+
publicKey: this.config.apiPublicKey,
|
|
550
|
+
scheme: "SIGNATURE_SCHEME_TK_API_P256",
|
|
551
|
+
signature: signatureHex
|
|
552
|
+
});
|
|
553
|
+
return Buffer.from(stampJson).toString("base64url");
|
|
554
|
+
}
|
|
555
|
+
async signedRequest(path, body) {
|
|
556
|
+
const bodyStr = JSON.stringify(body);
|
|
557
|
+
const stampValue = await this.stamp(bodyStr);
|
|
558
|
+
return fetch(`${this.baseUrl}${path}`, {
|
|
559
|
+
method: "POST",
|
|
560
|
+
headers: {
|
|
561
|
+
"Content-Type": "application/json",
|
|
562
|
+
"X-Stamp": stampValue
|
|
563
|
+
},
|
|
564
|
+
body: bodyStr
|
|
565
|
+
});
|
|
566
|
+
}
|
|
567
|
+
async getAddress() {
|
|
568
|
+
return this.config.walletAddress;
|
|
569
|
+
}
|
|
570
|
+
async sendTransaction(tx) {
|
|
571
|
+
this.onRequest?.("sendTransaction", tx);
|
|
572
|
+
const startTime = Date.now();
|
|
573
|
+
const { rpcUrl } = this.config;
|
|
574
|
+
const gasParams = await this.estimateGasParams(rpcUrl, tx);
|
|
575
|
+
const rlpHex = rlpEncodeEip1559Tx({
|
|
576
|
+
chainId: tx.chainId,
|
|
577
|
+
nonce: gasParams.nonce,
|
|
578
|
+
maxPriorityFeePerGas: gasParams.maxPriorityFeePerGas,
|
|
579
|
+
maxFeePerGas: gasParams.maxFeePerGas,
|
|
580
|
+
gasLimit: gasParams.gasLimit,
|
|
581
|
+
to: tx.to,
|
|
582
|
+
data: tx.data,
|
|
583
|
+
value: tx.value
|
|
584
|
+
});
|
|
585
|
+
const signWith = this.config.privateKeyId ?? this.config.walletAddress;
|
|
586
|
+
const response = await this.signedRequest(
|
|
587
|
+
"/public/v1/submit/sign_transaction",
|
|
588
|
+
{
|
|
589
|
+
type: "ACTIVITY_TYPE_SIGN_TRANSACTION_V2",
|
|
590
|
+
organizationId: this.config.organizationId,
|
|
591
|
+
timestampMs: Date.now().toString(),
|
|
592
|
+
parameters: {
|
|
593
|
+
signWith,
|
|
594
|
+
type: "TRANSACTION_TYPE_ETHEREUM",
|
|
595
|
+
unsignedTransaction: rlpHex
|
|
596
|
+
}
|
|
597
|
+
}
|
|
598
|
+
);
|
|
599
|
+
if (!response.ok) {
|
|
600
|
+
const body = await response.text();
|
|
601
|
+
throw new Error(
|
|
602
|
+
`Turnkey sendTransaction failed (${response.status}): ${body}`
|
|
603
|
+
);
|
|
604
|
+
}
|
|
605
|
+
const data = await response.json();
|
|
606
|
+
const signedTx = data.activity.result?.signTransactionResult?.signedTransaction;
|
|
607
|
+
if (!signedTx) {
|
|
608
|
+
throw new Error(
|
|
609
|
+
`Turnkey sign transaction did not return a signed payload (activity status: ${data.activity.status})`
|
|
610
|
+
);
|
|
611
|
+
}
|
|
612
|
+
const rpcResponse = await fetch(rpcUrl, {
|
|
613
|
+
method: "POST",
|
|
614
|
+
headers: { "Content-Type": "application/json" },
|
|
615
|
+
body: JSON.stringify({
|
|
616
|
+
jsonrpc: "2.0",
|
|
617
|
+
id: 1,
|
|
618
|
+
method: "eth_sendRawTransaction",
|
|
619
|
+
params: [signedTx]
|
|
620
|
+
})
|
|
621
|
+
});
|
|
622
|
+
if (!rpcResponse.ok) {
|
|
623
|
+
const rpcBody = await rpcResponse.text();
|
|
624
|
+
throw new Error(
|
|
625
|
+
`Turnkey broadcast failed (${rpcResponse.status}): ${rpcBody}`
|
|
626
|
+
);
|
|
627
|
+
}
|
|
628
|
+
const rpcData = await rpcResponse.json();
|
|
629
|
+
if (rpcData.error) {
|
|
630
|
+
throw new Error(`Turnkey broadcast RPC error: ${rpcData.error.message}`);
|
|
631
|
+
}
|
|
632
|
+
if (!rpcData.result) {
|
|
633
|
+
throw new Error("Turnkey broadcast returned no tx hash");
|
|
634
|
+
}
|
|
635
|
+
const result = { hash: rpcData.result };
|
|
636
|
+
this.onResponse?.("sendTransaction", result, Date.now() - startTime);
|
|
637
|
+
return result;
|
|
638
|
+
}
|
|
639
|
+
async signMessage(request) {
|
|
640
|
+
this.onRequest?.("signMessage", request);
|
|
641
|
+
const startTime = Date.now();
|
|
642
|
+
const hash = bytesToHex(hashPersonalMessage(request.message));
|
|
643
|
+
const signature = await this.signRawPayload(hash);
|
|
644
|
+
this.onResponse?.("signMessage", signature, Date.now() - startTime);
|
|
645
|
+
return signature;
|
|
646
|
+
}
|
|
647
|
+
async signTypedData(request) {
|
|
648
|
+
this.onRequest?.("signTypedData", request);
|
|
649
|
+
const startTime = Date.now();
|
|
650
|
+
const hash = bytesToHex(
|
|
651
|
+
hashTypedData(
|
|
652
|
+
request.domain,
|
|
653
|
+
request.primaryType,
|
|
654
|
+
request.message,
|
|
655
|
+
request.types
|
|
656
|
+
)
|
|
657
|
+
);
|
|
658
|
+
const signature = await this.signRawPayload(hash);
|
|
659
|
+
this.onResponse?.("signTypedData", signature, Date.now() - startTime);
|
|
660
|
+
return signature;
|
|
661
|
+
}
|
|
662
|
+
async signRawPayload(hashHex) {
|
|
663
|
+
const signWith = this.config.privateKeyId ?? this.config.walletAddress;
|
|
664
|
+
const response = await this.signedRequest(
|
|
665
|
+
"/public/v1/submit/sign_raw_payload",
|
|
666
|
+
{
|
|
667
|
+
type: "ACTIVITY_TYPE_SIGN_RAW_PAYLOAD_V2",
|
|
668
|
+
organizationId: this.config.organizationId,
|
|
669
|
+
timestampMs: Date.now().toString(),
|
|
670
|
+
parameters: {
|
|
671
|
+
signWith,
|
|
672
|
+
payload: hashHex,
|
|
673
|
+
encoding: "PAYLOAD_ENCODING_HEXADECIMAL",
|
|
674
|
+
hashFunction: "HASH_FUNCTION_NO_OP"
|
|
675
|
+
}
|
|
676
|
+
}
|
|
677
|
+
);
|
|
678
|
+
if (!response.ok) {
|
|
679
|
+
const body = await response.text();
|
|
680
|
+
throw new Error(
|
|
681
|
+
`Turnkey signRawPayload failed (${response.status}): ${body}`
|
|
682
|
+
);
|
|
683
|
+
}
|
|
684
|
+
const data = await response.json();
|
|
685
|
+
const sigResult = data.activity.result?.signRawPayloadResult;
|
|
686
|
+
if (!sigResult) {
|
|
687
|
+
throw new Error(
|
|
688
|
+
`Turnkey sign raw payload did not return a result (status: ${data.activity.status})`
|
|
689
|
+
);
|
|
690
|
+
}
|
|
691
|
+
const r = sigResult.r.padStart(64, "0");
|
|
692
|
+
const s = sigResult.s.padStart(64, "0");
|
|
693
|
+
const vNum = Number.parseInt(sigResult.v, 16) + 27;
|
|
694
|
+
const v = vNum.toString(16).padStart(2, "0");
|
|
695
|
+
return `0x${r}${s}${v}`;
|
|
696
|
+
}
|
|
697
|
+
async estimateGasParams(rpcUrl, tx) {
|
|
698
|
+
if (tx.gas && tx.nonce !== void 0 && tx.maxFeePerGas) {
|
|
699
|
+
return {
|
|
700
|
+
nonce: BigInt(tx.nonce),
|
|
701
|
+
gasLimit: BigInt(tx.gas),
|
|
702
|
+
maxFeePerGas: BigInt(tx.maxFeePerGas),
|
|
703
|
+
maxPriorityFeePerGas: tx.maxPriorityFeePerGas ? BigInt(tx.maxPriorityFeePerGas) : 1500000000n
|
|
704
|
+
};
|
|
705
|
+
}
|
|
706
|
+
const from = this.config.walletAddress;
|
|
707
|
+
const txValue = tx.value === "0" ? "0x0" : `0x${BigInt(tx.value).toString(16)}`;
|
|
708
|
+
const [nonceResult, gasEstimateResult, feeDataResult] = await Promise.all([
|
|
709
|
+
this.rpcCall(rpcUrl, "eth_getTransactionCount", [from, "pending"]),
|
|
710
|
+
this.rpcCall(rpcUrl, "eth_estimateGas", [
|
|
711
|
+
{
|
|
712
|
+
from,
|
|
713
|
+
to: tx.to,
|
|
714
|
+
data: tx.data || "0x",
|
|
715
|
+
value: txValue
|
|
716
|
+
}
|
|
717
|
+
]),
|
|
718
|
+
this.rpcCall(rpcUrl, "eth_feeHistory", [1, "latest", [50]])
|
|
719
|
+
]);
|
|
720
|
+
const nonce = BigInt(nonceResult);
|
|
721
|
+
const rawGasLimit = BigInt(gasEstimateResult);
|
|
722
|
+
const gasLimit = rawGasLimit * 120n / 100n;
|
|
723
|
+
const feeHistory = feeDataResult;
|
|
724
|
+
const latestBaseFee = BigInt(
|
|
725
|
+
feeHistory.baseFeePerGas[1] ?? feeHistory.baseFeePerGas[0]
|
|
726
|
+
);
|
|
727
|
+
const maxPriorityFeePerGas = feeHistory.reward?.[0]?.[0] ? BigInt(feeHistory.reward[0][0]) : 1500000000n;
|
|
728
|
+
const maxFeePerGas = latestBaseFee * 2n + maxPriorityFeePerGas;
|
|
729
|
+
return { nonce, gasLimit, maxFeePerGas, maxPriorityFeePerGas };
|
|
730
|
+
}
|
|
731
|
+
async rpcCall(rpcUrl, method, params) {
|
|
732
|
+
const response = await fetch(rpcUrl, {
|
|
733
|
+
method: "POST",
|
|
734
|
+
headers: { "Content-Type": "application/json" },
|
|
735
|
+
body: JSON.stringify({
|
|
736
|
+
jsonrpc: "2.0",
|
|
737
|
+
id: 1,
|
|
738
|
+
method,
|
|
739
|
+
params
|
|
740
|
+
})
|
|
741
|
+
});
|
|
742
|
+
if (!response.ok) {
|
|
743
|
+
const body = await response.text();
|
|
744
|
+
throw new Error(
|
|
745
|
+
`Turnkey RPC ${method} failed (${response.status}): ${body}`
|
|
746
|
+
);
|
|
747
|
+
}
|
|
748
|
+
const data = await response.json();
|
|
749
|
+
if (data.error) {
|
|
750
|
+
throw new Error(`Turnkey RPC ${method} error: ${data.error.message}`);
|
|
751
|
+
}
|
|
752
|
+
return data.result;
|
|
753
|
+
}
|
|
754
|
+
};
|
|
755
|
+
function rlpEncodeEip1559Tx(tx) {
|
|
756
|
+
const chainIdBytes = bigIntToBytes(BigInt(tx.chainId));
|
|
757
|
+
const nonce = bigIntToBytes(tx.nonce);
|
|
758
|
+
const maxPriorityFeePerGas = bigIntToBytes(tx.maxPriorityFeePerGas);
|
|
759
|
+
const maxFeePerGas = bigIntToBytes(tx.maxFeePerGas);
|
|
760
|
+
const gasLimit = bigIntToBytes(tx.gasLimit);
|
|
761
|
+
const toBytes = hexToBytes2(tx.to);
|
|
762
|
+
const valueBytes = tx.value === "0" ? new Uint8Array(0) : bigIntToBytes(BigInt(tx.value));
|
|
763
|
+
const dataBytes = tx.data ? hexToBytes2(tx.data) : new Uint8Array(0);
|
|
764
|
+
const fields = [
|
|
765
|
+
rlpEncodeBytes(chainIdBytes),
|
|
766
|
+
rlpEncodeBytes(nonce),
|
|
767
|
+
rlpEncodeBytes(maxPriorityFeePerGas),
|
|
768
|
+
rlpEncodeBytes(maxFeePerGas),
|
|
769
|
+
rlpEncodeBytes(gasLimit),
|
|
770
|
+
rlpEncodeBytes(toBytes),
|
|
771
|
+
rlpEncodeBytes(valueBytes),
|
|
772
|
+
rlpEncodeBytes(dataBytes),
|
|
773
|
+
rlpEncodeList([])
|
|
774
|
+
];
|
|
775
|
+
const rlpList = rlpEncodeList(fields);
|
|
776
|
+
const result = new Uint8Array(1 + rlpList.length);
|
|
777
|
+
result[0] = 2;
|
|
778
|
+
result.set(rlpList, 1);
|
|
779
|
+
return bytesToHex(result);
|
|
780
|
+
}
|
|
781
|
+
function rlpEncodeBytes(bytes) {
|
|
782
|
+
if (bytes.length === 1 && bytes[0] < 128) {
|
|
783
|
+
return bytes;
|
|
784
|
+
}
|
|
785
|
+
if (bytes.length === 0) {
|
|
786
|
+
return new Uint8Array([128]);
|
|
787
|
+
}
|
|
788
|
+
if (bytes.length <= 55) {
|
|
789
|
+
const result2 = new Uint8Array(1 + bytes.length);
|
|
790
|
+
result2[0] = 128 + bytes.length;
|
|
791
|
+
result2.set(bytes, 1);
|
|
792
|
+
return result2;
|
|
793
|
+
}
|
|
794
|
+
const lenBytes = bigIntToBytes(BigInt(bytes.length));
|
|
795
|
+
const result = new Uint8Array(1 + lenBytes.length + bytes.length);
|
|
796
|
+
result[0] = 183 + lenBytes.length;
|
|
797
|
+
result.set(lenBytes, 1);
|
|
798
|
+
result.set(bytes, 1 + lenBytes.length);
|
|
799
|
+
return result;
|
|
800
|
+
}
|
|
801
|
+
function rlpEncodeList(items) {
|
|
802
|
+
let totalLen = 0;
|
|
803
|
+
for (const item of items) totalLen += item.length;
|
|
804
|
+
if (totalLen <= 55) {
|
|
805
|
+
const result2 = new Uint8Array(1 + totalLen);
|
|
806
|
+
result2[0] = 192 + totalLen;
|
|
807
|
+
let offset2 = 1;
|
|
808
|
+
for (const item of items) {
|
|
809
|
+
result2.set(item, offset2);
|
|
810
|
+
offset2 += item.length;
|
|
811
|
+
}
|
|
812
|
+
return result2;
|
|
813
|
+
}
|
|
814
|
+
const lenBytes = bigIntToBytes(BigInt(totalLen));
|
|
815
|
+
const result = new Uint8Array(1 + lenBytes.length + totalLen);
|
|
816
|
+
result[0] = 247 + lenBytes.length;
|
|
817
|
+
result.set(lenBytes, 1);
|
|
818
|
+
let offset = 1 + lenBytes.length;
|
|
819
|
+
for (const item of items) {
|
|
820
|
+
result.set(item, offset);
|
|
821
|
+
offset += item.length;
|
|
822
|
+
}
|
|
823
|
+
return result;
|
|
824
|
+
}
|
|
825
|
+
function bigIntToBytes(value) {
|
|
826
|
+
if (value === 0n) return new Uint8Array(0);
|
|
827
|
+
const hex = value.toString(16);
|
|
828
|
+
const padded = hex.length % 2 === 0 ? hex : `0${hex}`;
|
|
829
|
+
return hexToBytes2(padded);
|
|
830
|
+
}
|
|
831
|
+
function hexToBytes2(hex) {
|
|
832
|
+
const clean = hex.startsWith("0x") ? hex.slice(2) : hex;
|
|
833
|
+
const bytes = new Uint8Array(clean.length / 2);
|
|
834
|
+
for (let i = 0; i < clean.length; i += 2) {
|
|
835
|
+
bytes[i / 2] = Number.parseInt(clean.slice(i, i + 2), 16);
|
|
836
|
+
}
|
|
837
|
+
return bytes;
|
|
838
|
+
}
|
|
839
|
+
function bytesToHex(bytes) {
|
|
840
|
+
return Array.from(bytes).map((b) => b.toString(16).padStart(2, "0")).join("");
|
|
841
|
+
}
|
|
842
|
+
function p1363ToDer(p1363) {
|
|
843
|
+
const r = p1363.subarray(0, 32);
|
|
844
|
+
const s = p1363.subarray(32, 64);
|
|
845
|
+
const rDer = integerToDer(r);
|
|
846
|
+
const sDer = integerToDer(s);
|
|
847
|
+
const seqLen = rDer.length + sDer.length;
|
|
848
|
+
const result = new Uint8Array(2 + seqLen);
|
|
849
|
+
result[0] = 48;
|
|
850
|
+
result[1] = seqLen;
|
|
851
|
+
result.set(rDer, 2);
|
|
852
|
+
result.set(sDer, 2 + rDer.length);
|
|
853
|
+
return result;
|
|
854
|
+
}
|
|
855
|
+
function integerToDer(bytes) {
|
|
856
|
+
let start = 0;
|
|
857
|
+
while (start < bytes.length - 1 && bytes[start] === 0) start++;
|
|
858
|
+
const stripped = bytes.subarray(start);
|
|
859
|
+
const needsPad = stripped[0] >= 128;
|
|
860
|
+
const len = stripped.length + (needsPad ? 1 : 0);
|
|
861
|
+
const result = new Uint8Array(2 + len);
|
|
862
|
+
result[0] = 2;
|
|
863
|
+
result[1] = len;
|
|
864
|
+
if (needsPad) {
|
|
865
|
+
result[2] = 0;
|
|
866
|
+
result.set(stripped, 3);
|
|
867
|
+
} else {
|
|
868
|
+
result.set(stripped, 2);
|
|
869
|
+
}
|
|
870
|
+
return result;
|
|
871
|
+
}
|
|
872
|
+
function derEncodeP256PrivateKey(rawKey) {
|
|
873
|
+
const header = new Uint8Array([
|
|
874
|
+
48,
|
|
875
|
+
65,
|
|
876
|
+
2,
|
|
877
|
+
1,
|
|
878
|
+
0,
|
|
879
|
+
48,
|
|
880
|
+
19,
|
|
881
|
+
6,
|
|
882
|
+
7,
|
|
883
|
+
42,
|
|
884
|
+
134,
|
|
885
|
+
72,
|
|
886
|
+
206,
|
|
887
|
+
61,
|
|
888
|
+
2,
|
|
889
|
+
1,
|
|
890
|
+
6,
|
|
891
|
+
8,
|
|
892
|
+
42,
|
|
893
|
+
134,
|
|
894
|
+
72,
|
|
895
|
+
206,
|
|
896
|
+
61,
|
|
897
|
+
3,
|
|
898
|
+
1,
|
|
899
|
+
7,
|
|
900
|
+
4,
|
|
901
|
+
39,
|
|
902
|
+
48,
|
|
903
|
+
37,
|
|
904
|
+
2,
|
|
905
|
+
1,
|
|
906
|
+
1,
|
|
907
|
+
4,
|
|
908
|
+
32
|
|
909
|
+
]);
|
|
910
|
+
const result = new Uint8Array(header.length + rawKey.length);
|
|
911
|
+
result.set(header);
|
|
912
|
+
result.set(rawKey, header.length);
|
|
913
|
+
return result.buffer;
|
|
914
|
+
}
|
|
915
|
+
|
|
916
|
+
// src/adapters/private-key.ts
|
|
917
|
+
var PrivateKeyAdapter = class _PrivateKeyAdapter {
|
|
918
|
+
name = "private-key";
|
|
919
|
+
capabilities = {
|
|
920
|
+
signMessage: true,
|
|
921
|
+
signTypedData: true,
|
|
922
|
+
managedGas: false,
|
|
923
|
+
managedNonce: false
|
|
924
|
+
};
|
|
925
|
+
onRequest;
|
|
926
|
+
onResponse;
|
|
927
|
+
config;
|
|
928
|
+
cachedAddress;
|
|
929
|
+
constructor(config) {
|
|
930
|
+
this.config = config;
|
|
931
|
+
this.cachedAddress = config.address;
|
|
932
|
+
}
|
|
933
|
+
static fromEnv() {
|
|
934
|
+
const privateKey = process.env.PRIVATE_KEY;
|
|
935
|
+
const rpcUrl = process.env.RPC_URL;
|
|
936
|
+
if (!privateKey) {
|
|
937
|
+
throw new Error("PRIVATE_KEY environment variable is required");
|
|
938
|
+
}
|
|
939
|
+
if (!rpcUrl) {
|
|
940
|
+
throw new Error("RPC_URL environment variable is required");
|
|
941
|
+
}
|
|
942
|
+
const clean = privateKey.startsWith("0x") ? privateKey.slice(2) : privateKey;
|
|
943
|
+
if (!/^[0-9a-fA-F]{64}$/.test(clean)) {
|
|
944
|
+
throw new Error(
|
|
945
|
+
"PRIVATE_KEY must be a 64-character hex string (with or without 0x prefix)"
|
|
946
|
+
);
|
|
947
|
+
}
|
|
948
|
+
return new _PrivateKeyAdapter({
|
|
949
|
+
privateKey,
|
|
950
|
+
rpcUrl,
|
|
951
|
+
address: process.env.WALLET_ADDRESS
|
|
952
|
+
});
|
|
953
|
+
}
|
|
954
|
+
getRpcUrl() {
|
|
955
|
+
return this.config.rpcUrl;
|
|
956
|
+
}
|
|
957
|
+
async getAddress() {
|
|
958
|
+
if (this.cachedAddress) return this.cachedAddress;
|
|
959
|
+
const clean = this.config.privateKey.startsWith("0x") ? this.config.privateKey.slice(2) : this.config.privateKey;
|
|
960
|
+
const pubKey = secp256k1.getPublicKey(clean, false);
|
|
961
|
+
const hash = keccak_2562(pubKey.subarray(1));
|
|
962
|
+
this.cachedAddress = `0x${nobleToHex(hash.subarray(12))}`;
|
|
963
|
+
return this.cachedAddress;
|
|
964
|
+
}
|
|
965
|
+
async sendTransaction(tx) {
|
|
966
|
+
this.onRequest?.("sendTransaction", tx);
|
|
967
|
+
const startTime = Date.now();
|
|
968
|
+
const from = await this.getAddress();
|
|
969
|
+
const { rpcUrl } = this.config;
|
|
970
|
+
let nonce;
|
|
971
|
+
let gasLimit;
|
|
972
|
+
let maxFeePerGas;
|
|
973
|
+
let maxPriorityFeePerGas;
|
|
974
|
+
if (tx.gas && tx.nonce !== void 0 && tx.maxFeePerGas) {
|
|
975
|
+
nonce = BigInt(tx.nonce);
|
|
976
|
+
gasLimit = BigInt(tx.gas);
|
|
977
|
+
maxFeePerGas = BigInt(tx.maxFeePerGas);
|
|
978
|
+
maxPriorityFeePerGas = tx.maxPriorityFeePerGas ? BigInt(tx.maxPriorityFeePerGas) : 1500000000n;
|
|
979
|
+
} else {
|
|
980
|
+
const txValue = tx.value === "0" ? "0x0" : `0x${BigInt(tx.value).toString(16)}`;
|
|
981
|
+
const [nonceResult, gasEstimateResult, feeDataResult] = await Promise.all(
|
|
982
|
+
[
|
|
983
|
+
this.rpcCall(rpcUrl, "eth_getTransactionCount", [from, "pending"]),
|
|
984
|
+
this.rpcCall(rpcUrl, "eth_estimateGas", [
|
|
985
|
+
{ from, to: tx.to, data: tx.data || "0x", value: txValue }
|
|
986
|
+
]),
|
|
987
|
+
this.rpcCall(rpcUrl, "eth_feeHistory", [1, "latest", [50]])
|
|
988
|
+
]
|
|
989
|
+
);
|
|
990
|
+
nonce = BigInt(nonceResult);
|
|
991
|
+
const rawGas = BigInt(gasEstimateResult);
|
|
992
|
+
gasLimit = rawGas * 120n / 100n;
|
|
993
|
+
const feeHistory = feeDataResult;
|
|
994
|
+
const latestBaseFee = BigInt(
|
|
995
|
+
feeHistory.baseFeePerGas[1] ?? feeHistory.baseFeePerGas[0]
|
|
996
|
+
);
|
|
997
|
+
maxPriorityFeePerGas = feeHistory.reward?.[0]?.[0] ? BigInt(feeHistory.reward[0][0]) : 1500000000n;
|
|
998
|
+
maxFeePerGas = latestBaseFee * 2n + maxPriorityFeePerGas;
|
|
999
|
+
}
|
|
1000
|
+
const unsignedHex = rlpEncodeEip1559Tx({
|
|
1001
|
+
chainId: tx.chainId,
|
|
1002
|
+
nonce,
|
|
1003
|
+
maxPriorityFeePerGas,
|
|
1004
|
+
maxFeePerGas,
|
|
1005
|
+
gasLimit,
|
|
1006
|
+
to: tx.to,
|
|
1007
|
+
data: tx.data,
|
|
1008
|
+
value: tx.value
|
|
1009
|
+
});
|
|
1010
|
+
const unsignedBytes = hexToBytes2(unsignedHex);
|
|
1011
|
+
const txHash = keccak_2562(unsignedBytes);
|
|
1012
|
+
const clean = this.config.privateKey.startsWith("0x") ? this.config.privateKey.slice(2) : this.config.privateKey;
|
|
1013
|
+
const sig = secp256k1.sign(txHash, clean);
|
|
1014
|
+
const signedHex = encodeSignedEip1559Tx(
|
|
1015
|
+
{
|
|
1016
|
+
chainId: tx.chainId,
|
|
1017
|
+
nonce,
|
|
1018
|
+
maxPriorityFeePerGas,
|
|
1019
|
+
maxFeePerGas,
|
|
1020
|
+
gasLimit,
|
|
1021
|
+
to: tx.to,
|
|
1022
|
+
data: tx.data,
|
|
1023
|
+
value: tx.value
|
|
1024
|
+
},
|
|
1025
|
+
sig.recovery,
|
|
1026
|
+
sig.r,
|
|
1027
|
+
sig.s
|
|
1028
|
+
);
|
|
1029
|
+
const hash = await this.rpcCall(rpcUrl, "eth_sendRawTransaction", [
|
|
1030
|
+
`0x${signedHex}`
|
|
1031
|
+
]);
|
|
1032
|
+
const result = { hash };
|
|
1033
|
+
this.onResponse?.("sendTransaction", result, Date.now() - startTime);
|
|
1034
|
+
return result;
|
|
1035
|
+
}
|
|
1036
|
+
async signMessage(request) {
|
|
1037
|
+
this.onRequest?.("signMessage", request);
|
|
1038
|
+
const startTime = Date.now();
|
|
1039
|
+
const hash = hashPersonalMessage(request.message);
|
|
1040
|
+
const clean = this.config.privateKey.startsWith("0x") ? this.config.privateKey.slice(2) : this.config.privateKey;
|
|
1041
|
+
const sig = secp256k1.sign(hash, clean);
|
|
1042
|
+
const result = encodeEcdsaSignature(sig.r, sig.s, sig.recovery);
|
|
1043
|
+
this.onResponse?.("signMessage", result, Date.now() - startTime);
|
|
1044
|
+
return result;
|
|
1045
|
+
}
|
|
1046
|
+
async signTypedData(request) {
|
|
1047
|
+
this.onRequest?.("signTypedData", request);
|
|
1048
|
+
const startTime = Date.now();
|
|
1049
|
+
const hash = hashTypedData(
|
|
1050
|
+
request.domain,
|
|
1051
|
+
request.primaryType,
|
|
1052
|
+
request.message,
|
|
1053
|
+
request.types
|
|
1054
|
+
);
|
|
1055
|
+
const clean = this.config.privateKey.startsWith("0x") ? this.config.privateKey.slice(2) : this.config.privateKey;
|
|
1056
|
+
const sig = secp256k1.sign(hash, clean);
|
|
1057
|
+
const result = encodeEcdsaSignature(sig.r, sig.s, sig.recovery);
|
|
1058
|
+
this.onResponse?.("signTypedData", result, Date.now() - startTime);
|
|
1059
|
+
return result;
|
|
1060
|
+
}
|
|
1061
|
+
async rpcCall(rpcUrl, method, params) {
|
|
1062
|
+
const response = await fetch(rpcUrl, {
|
|
1063
|
+
method: "POST",
|
|
1064
|
+
headers: { "Content-Type": "application/json" },
|
|
1065
|
+
body: JSON.stringify({ jsonrpc: "2.0", id: 1, method, params })
|
|
1066
|
+
});
|
|
1067
|
+
if (!response.ok) {
|
|
1068
|
+
const body = await response.text();
|
|
1069
|
+
throw new Error(`RPC ${method} failed (${response.status}): ${body}`);
|
|
1070
|
+
}
|
|
1071
|
+
const data = await response.json();
|
|
1072
|
+
if (data.error) {
|
|
1073
|
+
throw new Error(`RPC ${method} error: ${data.error.message}`);
|
|
1074
|
+
}
|
|
1075
|
+
return data.result;
|
|
1076
|
+
}
|
|
1077
|
+
};
|
|
1078
|
+
function bigIntToMinBytes(value) {
|
|
1079
|
+
if (value === 0n) return new Uint8Array(0);
|
|
1080
|
+
const hex = value.toString(16);
|
|
1081
|
+
const padded = hex.length % 2 === 0 ? hex : `0${hex}`;
|
|
1082
|
+
return hexToBytes2(padded);
|
|
1083
|
+
}
|
|
1084
|
+
function rlpEncodeBytes2(bytes) {
|
|
1085
|
+
if (bytes.length === 1 && bytes[0] < 128) return bytes;
|
|
1086
|
+
if (bytes.length === 0) return new Uint8Array([128]);
|
|
1087
|
+
if (bytes.length <= 55) {
|
|
1088
|
+
const result2 = new Uint8Array(1 + bytes.length);
|
|
1089
|
+
result2[0] = 128 + bytes.length;
|
|
1090
|
+
result2.set(bytes, 1);
|
|
1091
|
+
return result2;
|
|
1092
|
+
}
|
|
1093
|
+
const lenBytes = bigIntToMinBytes(BigInt(bytes.length));
|
|
1094
|
+
const result = new Uint8Array(1 + lenBytes.length + bytes.length);
|
|
1095
|
+
result[0] = 183 + lenBytes.length;
|
|
1096
|
+
result.set(lenBytes, 1);
|
|
1097
|
+
result.set(bytes, 1 + lenBytes.length);
|
|
1098
|
+
return result;
|
|
1099
|
+
}
|
|
1100
|
+
function rlpEncodeList2(items) {
|
|
1101
|
+
let totalLen = 0;
|
|
1102
|
+
for (const item of items) totalLen += item.length;
|
|
1103
|
+
if (totalLen <= 55) {
|
|
1104
|
+
const result2 = new Uint8Array(1 + totalLen);
|
|
1105
|
+
result2[0] = 192 + totalLen;
|
|
1106
|
+
let offset2 = 1;
|
|
1107
|
+
for (const item of items) {
|
|
1108
|
+
result2.set(item, offset2);
|
|
1109
|
+
offset2 += item.length;
|
|
1110
|
+
}
|
|
1111
|
+
return result2;
|
|
1112
|
+
}
|
|
1113
|
+
const lenBytes = bigIntToMinBytes(BigInt(totalLen));
|
|
1114
|
+
const result = new Uint8Array(1 + lenBytes.length + totalLen);
|
|
1115
|
+
result[0] = 247 + lenBytes.length;
|
|
1116
|
+
result.set(lenBytes, 1);
|
|
1117
|
+
let offset = 1 + lenBytes.length;
|
|
1118
|
+
for (const item of items) {
|
|
1119
|
+
result.set(item, offset);
|
|
1120
|
+
offset += item.length;
|
|
1121
|
+
}
|
|
1122
|
+
return result;
|
|
1123
|
+
}
|
|
1124
|
+
function encodeSignedEip1559Tx(tx, v, r, s) {
|
|
1125
|
+
const fields = [
|
|
1126
|
+
rlpEncodeBytes2(bigIntToMinBytes(BigInt(tx.chainId))),
|
|
1127
|
+
rlpEncodeBytes2(bigIntToMinBytes(tx.nonce)),
|
|
1128
|
+
rlpEncodeBytes2(bigIntToMinBytes(tx.maxPriorityFeePerGas)),
|
|
1129
|
+
rlpEncodeBytes2(bigIntToMinBytes(tx.maxFeePerGas)),
|
|
1130
|
+
rlpEncodeBytes2(bigIntToMinBytes(tx.gasLimit)),
|
|
1131
|
+
rlpEncodeBytes2(hexToBytes2(tx.to)),
|
|
1132
|
+
rlpEncodeBytes2(
|
|
1133
|
+
tx.value === "0" ? new Uint8Array(0) : bigIntToMinBytes(BigInt(tx.value))
|
|
1134
|
+
),
|
|
1135
|
+
rlpEncodeBytes2(tx.data ? hexToBytes2(tx.data) : new Uint8Array(0)),
|
|
1136
|
+
rlpEncodeList2([]),
|
|
1137
|
+
// empty access list
|
|
1138
|
+
rlpEncodeBytes2(v === 0 ? new Uint8Array(0) : new Uint8Array([v])),
|
|
1139
|
+
rlpEncodeBytes2(bigIntToMinBytes(r)),
|
|
1140
|
+
rlpEncodeBytes2(bigIntToMinBytes(s))
|
|
1141
|
+
];
|
|
1142
|
+
const rlpList = rlpEncodeList2(fields);
|
|
1143
|
+
const signed = new Uint8Array(1 + rlpList.length);
|
|
1144
|
+
signed[0] = 2;
|
|
1145
|
+
signed.set(rlpList, 1);
|
|
1146
|
+
return bytesToHex(signed);
|
|
1147
|
+
}
|
|
1148
|
+
function encodeEcdsaSignature(r, s, v) {
|
|
1149
|
+
const rHex = r.toString(16).padStart(64, "0");
|
|
1150
|
+
const sHex = s.toString(16).padStart(64, "0");
|
|
1151
|
+
const vHex = (v + 27).toString(16).padStart(2, "0");
|
|
1152
|
+
return `0x${rHex}${sHex}${vHex}`;
|
|
1153
|
+
}
|
|
1154
|
+
|
|
1155
|
+
// src/adapters/privy.ts
|
|
1156
|
+
var PRIVY_API_BASE = "https://api.privy.io";
|
|
1157
|
+
var PrivyAdapter = class _PrivyAdapter {
|
|
1158
|
+
name = "privy";
|
|
1159
|
+
capabilities = {
|
|
1160
|
+
signMessage: true,
|
|
1161
|
+
signTypedData: true,
|
|
1162
|
+
managedGas: true,
|
|
1163
|
+
managedNonce: true
|
|
1164
|
+
};
|
|
1165
|
+
onRequest;
|
|
1166
|
+
onResponse;
|
|
1167
|
+
config;
|
|
1168
|
+
cachedAddress;
|
|
1169
|
+
constructor(config) {
|
|
1170
|
+
this.config = config;
|
|
1171
|
+
}
|
|
1172
|
+
static fromEnv() {
|
|
1173
|
+
const appId = process.env.PRIVY_APP_ID;
|
|
1174
|
+
const appSecret = process.env.PRIVY_APP_SECRET;
|
|
1175
|
+
const walletId = process.env.PRIVY_WALLET_ID;
|
|
1176
|
+
if (!appId) {
|
|
1177
|
+
throw new Error("PRIVY_APP_ID environment variable is required");
|
|
1178
|
+
}
|
|
1179
|
+
if (!appSecret) {
|
|
1180
|
+
throw new Error("PRIVY_APP_SECRET environment variable is required");
|
|
1181
|
+
}
|
|
1182
|
+
if (!walletId) {
|
|
1183
|
+
throw new Error("PRIVY_WALLET_ID environment variable is required");
|
|
1184
|
+
}
|
|
1185
|
+
return new _PrivyAdapter({
|
|
1186
|
+
appId,
|
|
1187
|
+
appSecret,
|
|
1188
|
+
walletId,
|
|
1189
|
+
baseUrl: process.env.PRIVY_API_BASE_URL
|
|
1190
|
+
});
|
|
1191
|
+
}
|
|
1192
|
+
get baseUrl() {
|
|
1193
|
+
return this.config.baseUrl ?? PRIVY_API_BASE;
|
|
1194
|
+
}
|
|
1195
|
+
get authHeaders() {
|
|
1196
|
+
const credentials = Buffer.from(
|
|
1197
|
+
`${this.config.appId}:${this.config.appSecret}`
|
|
1198
|
+
).toString("base64");
|
|
1199
|
+
return {
|
|
1200
|
+
Authorization: `Basic ${credentials}`,
|
|
1201
|
+
"privy-app-id": this.config.appId,
|
|
1202
|
+
"Content-Type": "application/json"
|
|
1203
|
+
};
|
|
1204
|
+
}
|
|
1205
|
+
async getAddress() {
|
|
1206
|
+
if (this.cachedAddress) return this.cachedAddress;
|
|
1207
|
+
const response = await fetch(
|
|
1208
|
+
`${this.baseUrl}/v1/wallets/${this.config.walletId}`,
|
|
1209
|
+
{ headers: this.authHeaders }
|
|
1210
|
+
);
|
|
1211
|
+
if (!response.ok) {
|
|
1212
|
+
const body = await response.text();
|
|
1213
|
+
throw new Error(`Privy getAddress failed (${response.status}): ${body}`);
|
|
1214
|
+
}
|
|
1215
|
+
const data = await response.json();
|
|
1216
|
+
this.cachedAddress = data.address;
|
|
1217
|
+
return data.address;
|
|
1218
|
+
}
|
|
1219
|
+
async sendTransaction(tx) {
|
|
1220
|
+
this.onRequest?.("sendTransaction", tx);
|
|
1221
|
+
const startTime = Date.now();
|
|
1222
|
+
const caip2 = `eip155:${tx.chainId}`;
|
|
1223
|
+
const response = await fetch(
|
|
1224
|
+
`${this.baseUrl}/v1/wallets/${this.config.walletId}/rpc`,
|
|
1225
|
+
{
|
|
1226
|
+
method: "POST",
|
|
1227
|
+
headers: this.authHeaders,
|
|
1228
|
+
body: JSON.stringify({
|
|
1229
|
+
method: "eth_sendTransaction",
|
|
1230
|
+
caip2,
|
|
1231
|
+
params: {
|
|
1232
|
+
transaction: {
|
|
1233
|
+
to: tx.to,
|
|
1234
|
+
data: tx.data,
|
|
1235
|
+
value: tx.value
|
|
1236
|
+
}
|
|
1237
|
+
}
|
|
1238
|
+
})
|
|
1239
|
+
}
|
|
1240
|
+
);
|
|
1241
|
+
if (!response.ok) {
|
|
1242
|
+
const body = await response.text();
|
|
1243
|
+
throw new Error(
|
|
1244
|
+
`Privy sendTransaction failed (${response.status}): ${body}`
|
|
1245
|
+
);
|
|
1246
|
+
}
|
|
1247
|
+
const data = await response.json();
|
|
1248
|
+
const result = { hash: data.data.hash };
|
|
1249
|
+
this.onResponse?.("sendTransaction", result, Date.now() - startTime);
|
|
1250
|
+
return result;
|
|
1251
|
+
}
|
|
1252
|
+
async signMessage(request) {
|
|
1253
|
+
this.onRequest?.("signMessage", request);
|
|
1254
|
+
const startTime = Date.now();
|
|
1255
|
+
const message = typeof request.message === "string" ? request.message : `0x${Buffer.from(request.message).toString("hex")}`;
|
|
1256
|
+
const response = await fetch(
|
|
1257
|
+
`${this.baseUrl}/v1/wallets/${this.config.walletId}/rpc`,
|
|
1258
|
+
{
|
|
1259
|
+
method: "POST",
|
|
1260
|
+
headers: this.authHeaders,
|
|
1261
|
+
body: JSON.stringify({
|
|
1262
|
+
method: "personal_sign",
|
|
1263
|
+
params: { message }
|
|
1264
|
+
})
|
|
1265
|
+
}
|
|
1266
|
+
);
|
|
1267
|
+
if (!response.ok) {
|
|
1268
|
+
const body = await response.text();
|
|
1269
|
+
throw new Error(`Privy signMessage failed (${response.status}): ${body}`);
|
|
1270
|
+
}
|
|
1271
|
+
const data = await response.json();
|
|
1272
|
+
this.onResponse?.(
|
|
1273
|
+
"signMessage",
|
|
1274
|
+
data.data.signature,
|
|
1275
|
+
Date.now() - startTime
|
|
1276
|
+
);
|
|
1277
|
+
return data.data.signature;
|
|
1278
|
+
}
|
|
1279
|
+
async signTypedData(request) {
|
|
1280
|
+
this.onRequest?.("signTypedData", request);
|
|
1281
|
+
const startTime = Date.now();
|
|
1282
|
+
const response = await fetch(
|
|
1283
|
+
`${this.baseUrl}/v1/wallets/${this.config.walletId}/rpc`,
|
|
1284
|
+
{
|
|
1285
|
+
method: "POST",
|
|
1286
|
+
headers: this.authHeaders,
|
|
1287
|
+
body: JSON.stringify({
|
|
1288
|
+
method: "eth_signTypedData_v4",
|
|
1289
|
+
params: {
|
|
1290
|
+
typedData: JSON.stringify({
|
|
1291
|
+
domain: request.domain,
|
|
1292
|
+
types: request.types,
|
|
1293
|
+
primaryType: request.primaryType,
|
|
1294
|
+
message: request.message
|
|
1295
|
+
})
|
|
1296
|
+
}
|
|
1297
|
+
})
|
|
1298
|
+
}
|
|
1299
|
+
);
|
|
1300
|
+
if (!response.ok) {
|
|
1301
|
+
const body = await response.text();
|
|
1302
|
+
throw new Error(
|
|
1303
|
+
`Privy signTypedData failed (${response.status}): ${body}`
|
|
1304
|
+
);
|
|
1305
|
+
}
|
|
1306
|
+
const data = await response.json();
|
|
1307
|
+
this.onResponse?.(
|
|
1308
|
+
"signTypedData",
|
|
1309
|
+
data.data.signature,
|
|
1310
|
+
Date.now() - startTime
|
|
1311
|
+
);
|
|
1312
|
+
return data.data.signature;
|
|
1313
|
+
}
|
|
1314
|
+
};
|
|
1315
|
+
|
|
1316
|
+
// src/factory.ts
|
|
1317
|
+
function createWalletFromEnv() {
|
|
1318
|
+
if (process.env.PRIVY_APP_ID && process.env.PRIVY_WALLET_ID) {
|
|
1319
|
+
return PrivyAdapter.fromEnv();
|
|
1320
|
+
}
|
|
1321
|
+
if (process.env.FIREBLOCKS_API_KEY && process.env.FIREBLOCKS_VAULT_ID) {
|
|
1322
|
+
return FireblocksAdapter.fromEnv();
|
|
1323
|
+
}
|
|
1324
|
+
if (process.env.TURNKEY_API_PUBLIC_KEY && process.env.TURNKEY_WALLET_ADDRESS) {
|
|
1325
|
+
return TurnkeyAdapter.fromEnv();
|
|
1326
|
+
}
|
|
1327
|
+
if (process.env.PRIVATE_KEY) {
|
|
1328
|
+
return PrivateKeyAdapter.fromEnv();
|
|
1329
|
+
}
|
|
1330
|
+
throw new Error(
|
|
1331
|
+
"No wallet provider configured. Set environment variables for one of:\n \u2022 Privy: PRIVY_APP_ID, PRIVY_APP_SECRET, PRIVY_WALLET_ID\n \u2022 Fireblocks: FIREBLOCKS_API_KEY, FIREBLOCKS_API_SECRET, FIREBLOCKS_VAULT_ID\n \u2022 Turnkey: TURNKEY_API_PUBLIC_KEY, TURNKEY_API_PRIVATE_KEY, TURNKEY_ORGANIZATION_ID, TURNKEY_WALLET_ADDRESS, TURNKEY_RPC_URL\n \u2022 PrivateKey: PRIVATE_KEY, RPC_URL"
|
|
1332
|
+
);
|
|
1333
|
+
}
|
|
1334
|
+
function createWalletForProvider(provider) {
|
|
1335
|
+
switch (provider) {
|
|
1336
|
+
case "privy":
|
|
1337
|
+
return PrivyAdapter.fromEnv();
|
|
1338
|
+
case "fireblocks":
|
|
1339
|
+
return FireblocksAdapter.fromEnv();
|
|
1340
|
+
case "turnkey":
|
|
1341
|
+
return TurnkeyAdapter.fromEnv();
|
|
1342
|
+
case "private-key":
|
|
1343
|
+
return PrivateKeyAdapter.fromEnv();
|
|
1344
|
+
default:
|
|
1345
|
+
throw new Error(`Unknown wallet provider: ${provider}`);
|
|
1346
|
+
}
|
|
1347
|
+
}
|
|
1348
|
+
function detectProvider() {
|
|
1349
|
+
if (process.env.PRIVY_APP_ID && process.env.PRIVY_WALLET_ID) return "privy";
|
|
1350
|
+
if (process.env.FIREBLOCKS_API_KEY && process.env.FIREBLOCKS_VAULT_ID)
|
|
1351
|
+
return "fireblocks";
|
|
1352
|
+
if (process.env.TURNKEY_API_PUBLIC_KEY && process.env.TURNKEY_WALLET_ADDRESS)
|
|
1353
|
+
return "turnkey";
|
|
1354
|
+
if (process.env.PRIVATE_KEY) return "private-key";
|
|
1355
|
+
return null;
|
|
1356
|
+
}
|
|
1357
|
+
|
|
1358
|
+
// src/types/index.ts
|
|
1359
|
+
var WALLET_PROVIDERS = [
|
|
1360
|
+
"privy",
|
|
1361
|
+
"turnkey",
|
|
1362
|
+
"fireblocks",
|
|
1363
|
+
"private-key"
|
|
1364
|
+
];
|
|
1365
|
+
export {
|
|
1366
|
+
CHAIN_TO_FIREBLOCKS_ASSET,
|
|
1367
|
+
FireblocksAdapter,
|
|
1368
|
+
PrivateKeyAdapter,
|
|
1369
|
+
PrivyAdapter,
|
|
1370
|
+
TurnkeyAdapter,
|
|
1371
|
+
WALLET_PROVIDERS,
|
|
1372
|
+
createWalletForProvider,
|
|
1373
|
+
createWalletFromEnv,
|
|
1374
|
+
detectProvider
|
|
1375
|
+
};
|
|
1376
|
+
//# sourceMappingURL=index.js.map
|