@cavos/kit 0.0.1 → 0.0.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/README.md +113 -42
- package/dist/Cavos-C2KHZxxu.d.mts +590 -0
- package/dist/Cavos-C2KHZxxu.d.ts +590 -0
- package/dist/{chunk-XWBX2ZIO.mjs → chunk-PEUQQZB5.mjs} +690 -79
- package/dist/chunk-PEUQQZB5.mjs.map +1 -0
- package/dist/index.d.mts +36 -242
- package/dist/index.d.ts +36 -242
- package/dist/index.js +703 -76
- package/dist/index.js.map +1 -1
- package/dist/index.mjs +1 -1
- package/dist/react/index.d.mts +18 -5
- package/dist/react/index.d.ts +18 -5
- package/dist/react/index.js +718 -82
- package/dist/react/index.js.map +1 -1
- package/dist/react/index.mjs +28 -7
- package/dist/react/index.mjs.map +1 -1
- package/package.json +2 -1
- package/dist/chunk-XWBX2ZIO.mjs.map +0 -1
- package/dist/constants-C530TZFF.d.mts +0 -89
- package/dist/constants-C530TZFF.d.ts +0 -89
package/dist/react/index.js
CHANGED
|
@@ -4,6 +4,7 @@ var react = require('react');
|
|
|
4
4
|
var starknet = require('starknet');
|
|
5
5
|
var sha256 = require('@noble/hashes/sha256');
|
|
6
6
|
var p256 = require('@noble/curves/p256');
|
|
7
|
+
var web3_js = require('@solana/web3.js');
|
|
7
8
|
var hkdf = require('@noble/hashes/hkdf');
|
|
8
9
|
var pbkdf2 = require('@noble/hashes/pbkdf2');
|
|
9
10
|
var utils = require('@noble/hashes/utils');
|
|
@@ -158,8 +159,8 @@ var CAVOS_PAYMASTER_URL = {
|
|
|
158
159
|
mainnet: "https://paymaster.cavos.xyz"
|
|
159
160
|
};
|
|
160
161
|
var DEVICE_ACCOUNT_CLASS_HASH = {
|
|
161
|
-
sepolia: "
|
|
162
|
-
mainnet: ""
|
|
162
|
+
sepolia: "0x1840aded59e8a0d2b440a134cb9079a7fc11b06c77f58ed189ab436a034ca6a",
|
|
163
|
+
mainnet: "0x1840aded59e8a0d2b440a134cb9079a7fc11b06c77f58ed189ab436a034ca6a"
|
|
163
164
|
};
|
|
164
165
|
|
|
165
166
|
// src/chains/starknet/StarknetAdapter.ts
|
|
@@ -308,76 +309,386 @@ var HttpWalletRegistry = class {
|
|
|
308
309
|
async addDevice(params) {
|
|
309
310
|
}
|
|
310
311
|
};
|
|
312
|
+
function deriveAddressSeed({ userId, appSalt }) {
|
|
313
|
+
const h = starknet.hash.computePoseidonHashOnElements([feltFromString(userId), feltFromString(appSalt)]);
|
|
314
|
+
return BigInt(h);
|
|
315
|
+
}
|
|
316
|
+
function deriveAddressSeedSolana({ userId, appSalt }) {
|
|
317
|
+
return sha256.sha256(new TextEncoder().encode(`cavos:solana:v1:${userId}:${appSalt}`));
|
|
318
|
+
}
|
|
319
|
+
function feltFromString(s) {
|
|
320
|
+
const bytes = new TextEncoder().encode(s);
|
|
321
|
+
const chunks = [];
|
|
322
|
+
for (let i = 0; i < bytes.length; i += 31) {
|
|
323
|
+
let w = 0n;
|
|
324
|
+
for (const b of bytes.subarray(i, i + 31)) w = w << 8n | BigInt(b);
|
|
325
|
+
chunks.push(w);
|
|
326
|
+
}
|
|
327
|
+
if (chunks.length === 0) return 0n;
|
|
328
|
+
if (chunks.length === 1) return chunks[0];
|
|
329
|
+
return BigInt(starknet.hash.computePoseidonHashOnElements(chunks));
|
|
330
|
+
}
|
|
311
331
|
|
|
312
|
-
// src/
|
|
313
|
-
|
|
314
|
-
|
|
332
|
+
// src/chains/solana/constants.ts
|
|
333
|
+
var DEVICE_ACCOUNT_PROGRAM_ID = "FHnoYNfYAmFrwt18gcBGG7G1S5q3RAbCBvrV2D29izNJ";
|
|
334
|
+
var SECP256R1_PROGRAM_ID = "Secp256r1SigVerify1111111111111111111111111";
|
|
335
|
+
var ACCOUNT_SEED = "cavos-account";
|
|
336
|
+
var DOMAIN_ADD = "cavos:add_signer:v1";
|
|
337
|
+
var DOMAIN_REMOVE = "cavos:remove_signer:v1";
|
|
338
|
+
var DOMAIN_TRANSFER = "cavos:transfer:v1";
|
|
339
|
+
var DOMAIN_EXECUTE = "cavos:execute:v1";
|
|
340
|
+
var SECP256R1_N = 0xffffffff00000000ffffffffffffffffbce6faada7179e84f3b9cac2fc632551n;
|
|
341
|
+
var SOLANA_NETWORKS = {
|
|
342
|
+
"solana-devnet": "https://api.devnet.solana.com",
|
|
343
|
+
"solana-mainnet": "https://api.mainnet-beta.solana.com",
|
|
344
|
+
"solana-localnet": "http://127.0.0.1:8899"
|
|
345
|
+
};
|
|
346
|
+
|
|
347
|
+
// src/chains/solana/SolanaAdapter.ts
|
|
348
|
+
var COMPRESSED_PUBKEY_SIZE = 33;
|
|
349
|
+
var SIGNATURE_SIZE = 64;
|
|
350
|
+
var CURRENT_IX = 65535;
|
|
351
|
+
var SolanaAdapter = class {
|
|
352
|
+
constructor(opts = {}) {
|
|
353
|
+
this.opts = opts;
|
|
354
|
+
this.chain = "solana";
|
|
355
|
+
this.programId = new web3_js.PublicKey(opts.programId ?? DEVICE_ACCOUNT_PROGRAM_ID);
|
|
356
|
+
}
|
|
357
|
+
/** Deterministic account address: PDA of [seed, address_seed, initial_signer_x]. */
|
|
358
|
+
computeAddress(addressSeed, initialSigner) {
|
|
359
|
+
return this.pda(addressSeed, compressedPubkey(initialSigner)).toBase58();
|
|
360
|
+
}
|
|
361
|
+
pda(addressSeed, initialCompressed) {
|
|
362
|
+
const [pda] = web3_js.PublicKey.findProgramAddressSync(
|
|
363
|
+
[
|
|
364
|
+
Buffer.from(ACCOUNT_SEED),
|
|
365
|
+
Buffer.from(addressSeed),
|
|
366
|
+
Buffer.from(initialCompressed.slice(1, 33))
|
|
367
|
+
// x-coordinate
|
|
368
|
+
],
|
|
369
|
+
this.programId
|
|
370
|
+
);
|
|
371
|
+
return pda;
|
|
372
|
+
}
|
|
373
|
+
/** `initialize` instruction creating the account with its first device signer. */
|
|
374
|
+
buildInitialize(addressSeed, payer, initialSigner) {
|
|
375
|
+
const initialCompressed = compressedPubkey(initialSigner);
|
|
376
|
+
const account = this.pda(addressSeed, initialCompressed);
|
|
377
|
+
const data = Buffer.concat([
|
|
378
|
+
anchorDiscriminator("initialize"),
|
|
379
|
+
Buffer.from(addressSeed),
|
|
380
|
+
// [u8;32]
|
|
381
|
+
Buffer.from(initialCompressed)
|
|
382
|
+
// [u8;33]
|
|
383
|
+
]);
|
|
384
|
+
return new web3_js.TransactionInstruction({
|
|
385
|
+
programId: this.programId,
|
|
386
|
+
keys: [
|
|
387
|
+
{ pubkey: account, isSigner: false, isWritable: true },
|
|
388
|
+
{ pubkey: new web3_js.PublicKey(payer), isSigner: true, isWritable: true },
|
|
389
|
+
{ pubkey: web3_js.SystemProgram.programId, isSigner: false, isWritable: false }
|
|
390
|
+
],
|
|
391
|
+
data
|
|
392
|
+
});
|
|
393
|
+
}
|
|
394
|
+
/** `[precompile, add_signer]` bundle, authorized by an existing device signer. */
|
|
395
|
+
async buildAddSigner(account, newSigner) {
|
|
396
|
+
const accountPk = new web3_js.PublicKey(account);
|
|
397
|
+
const newCompressed = compressedPubkey(newSigner);
|
|
398
|
+
const nonce = await this.fetchNonce(accountPk);
|
|
399
|
+
const message = concatBytes(
|
|
400
|
+
Buffer.from(DOMAIN_ADD),
|
|
401
|
+
accountPk.toBuffer(),
|
|
402
|
+
newCompressed,
|
|
403
|
+
u64le(nonce)
|
|
404
|
+
);
|
|
405
|
+
const { precompileIx } = await this.signToPrecompile(message);
|
|
406
|
+
const ix = new web3_js.TransactionInstruction({
|
|
407
|
+
programId: this.programId,
|
|
408
|
+
keys: this.guardedKeys(accountPk),
|
|
409
|
+
data: Buffer.concat([anchorDiscriminator("add_signer"), Buffer.from(newCompressed)])
|
|
410
|
+
});
|
|
411
|
+
return [precompileIx, ix];
|
|
412
|
+
}
|
|
413
|
+
/** `[precompile, remove_signer]` bundle, authorized by an existing device signer. */
|
|
414
|
+
async buildRemoveSigner(account, signer) {
|
|
415
|
+
const accountPk = new web3_js.PublicKey(account);
|
|
416
|
+
const compressed = compressedPubkey(signer);
|
|
417
|
+
const nonce = await this.fetchNonce(accountPk);
|
|
418
|
+
const message = concatBytes(
|
|
419
|
+
Buffer.from(DOMAIN_REMOVE),
|
|
420
|
+
accountPk.toBuffer(),
|
|
421
|
+
compressed,
|
|
422
|
+
u64le(nonce)
|
|
423
|
+
);
|
|
424
|
+
const { precompileIx } = await this.signToPrecompile(message);
|
|
425
|
+
const ix = new web3_js.TransactionInstruction({
|
|
426
|
+
programId: this.programId,
|
|
427
|
+
keys: this.guardedKeys(accountPk),
|
|
428
|
+
data: Buffer.concat([anchorDiscriminator("remove_signer"), Buffer.from(compressed)])
|
|
429
|
+
});
|
|
430
|
+
return [precompileIx, ix];
|
|
431
|
+
}
|
|
432
|
+
/** `[precompile, execute_transfer]` bundle moving lamports out of the account. */
|
|
433
|
+
async buildExecuteTransfer(account, destination, amount) {
|
|
434
|
+
const accountPk = new web3_js.PublicKey(account);
|
|
435
|
+
const destPk = new web3_js.PublicKey(destination);
|
|
436
|
+
const nonce = await this.fetchNonce(accountPk);
|
|
437
|
+
const message = concatBytes(
|
|
438
|
+
Buffer.from(DOMAIN_TRANSFER),
|
|
439
|
+
accountPk.toBuffer(),
|
|
440
|
+
destPk.toBuffer(),
|
|
441
|
+
u64le(amount),
|
|
442
|
+
u64le(nonce)
|
|
443
|
+
);
|
|
444
|
+
const { precompileIx } = await this.signToPrecompile(message);
|
|
445
|
+
const ix = new web3_js.TransactionInstruction({
|
|
446
|
+
programId: this.programId,
|
|
447
|
+
keys: [
|
|
448
|
+
{ pubkey: accountPk, isSigner: false, isWritable: true },
|
|
449
|
+
{ pubkey: destPk, isSigner: false, isWritable: true },
|
|
450
|
+
{ pubkey: web3_js.SYSVAR_INSTRUCTIONS_PUBKEY, isSigner: false, isWritable: false }
|
|
451
|
+
],
|
|
452
|
+
data: Buffer.concat([anchorDiscriminator("execute_transfer"), u64le(amount)])
|
|
453
|
+
});
|
|
454
|
+
return [precompileIx, ix];
|
|
455
|
+
}
|
|
456
|
+
/**
|
|
457
|
+
* `[precompile, execute]` bundle running arbitrary CPI instructions with the
|
|
458
|
+
* account PDA as signer. The device key signs over
|
|
459
|
+
* `DOMAIN_EXECUTE || account || sha256(canonical(instructions)) || nonce`, so
|
|
460
|
+
* the signature commits to the EXACT instruction set the program will invoke —
|
|
461
|
+
* no account/data substitution is possible after signing.
|
|
462
|
+
*
|
|
463
|
+
* The instructions' accounts are passed to the program via `remaining_accounts`
|
|
464
|
+
* (flattened, in order); the program enforces an exact, ordered mapping.
|
|
465
|
+
*/
|
|
466
|
+
async buildExecute(account, instructions) {
|
|
467
|
+
if (instructions.length === 0) throw new Error("kit/solana: execute requires at least one instruction");
|
|
468
|
+
const accountPk = new web3_js.PublicKey(account);
|
|
469
|
+
const nonce = await this.fetchNonce(accountPk);
|
|
470
|
+
const blob = serializeInstructions(instructions);
|
|
471
|
+
const ixsHash = sha256.sha256(blob);
|
|
472
|
+
const message = concatBytes(
|
|
473
|
+
Buffer.from(DOMAIN_EXECUTE),
|
|
474
|
+
accountPk.toBuffer(),
|
|
475
|
+
Buffer.from(ixsHash),
|
|
476
|
+
u64le(nonce)
|
|
477
|
+
);
|
|
478
|
+
const { precompileIx } = await this.signToPrecompile(message);
|
|
479
|
+
const blobLen = Buffer.alloc(4);
|
|
480
|
+
new DataView(blobLen.buffer).setUint32(0, blob.length, true);
|
|
481
|
+
const data = Buffer.concat([anchorDiscriminator("execute"), blobLen, blob]);
|
|
482
|
+
const remainingAccounts = [];
|
|
483
|
+
for (const ix2 of instructions) {
|
|
484
|
+
for (const acc of ix2.accounts) {
|
|
485
|
+
remainingAccounts.push({
|
|
486
|
+
pubkey: new web3_js.PublicKey(acc.pubkey),
|
|
487
|
+
isSigner: false,
|
|
488
|
+
// signer flags are part of the signed InstructionData
|
|
489
|
+
isWritable: acc.isWritable
|
|
490
|
+
});
|
|
491
|
+
}
|
|
492
|
+
remainingAccounts.push({
|
|
493
|
+
pubkey: new web3_js.PublicKey(ix2.programId),
|
|
494
|
+
isSigner: false,
|
|
495
|
+
isWritable: false
|
|
496
|
+
});
|
|
497
|
+
}
|
|
498
|
+
const ix = new web3_js.TransactionInstruction({
|
|
499
|
+
programId: this.programId,
|
|
500
|
+
keys: [
|
|
501
|
+
{ pubkey: accountPk, isSigner: false, isWritable: true },
|
|
502
|
+
{ pubkey: web3_js.SYSVAR_INSTRUCTIONS_PUBKEY, isSigner: false, isWritable: false },
|
|
503
|
+
...remainingAccounts
|
|
504
|
+
],
|
|
505
|
+
data
|
|
506
|
+
});
|
|
507
|
+
return [precompileIx, ix];
|
|
508
|
+
}
|
|
509
|
+
/** Read whether `signer` is currently an authorized signer of `account`. */
|
|
510
|
+
async isAuthorizedSigner(account, signer) {
|
|
511
|
+
const signers = await this.fetchSigners(new web3_js.PublicKey(account));
|
|
512
|
+
const target = Buffer.from(compressedPubkey(signer)).toString("hex");
|
|
513
|
+
return signers.some((s) => Buffer.from(s).toString("hex") === target);
|
|
514
|
+
}
|
|
515
|
+
guardedKeys(account) {
|
|
516
|
+
return [
|
|
517
|
+
{ pubkey: account, isSigner: false, isWritable: true },
|
|
518
|
+
{ pubkey: web3_js.SYSVAR_INSTRUCTIONS_PUBKEY, isSigner: false, isWritable: false }
|
|
519
|
+
];
|
|
520
|
+
}
|
|
521
|
+
/** Sign `message` with the device key and build the matching precompile ix. */
|
|
522
|
+
async signToPrecompile(message) {
|
|
523
|
+
if (!this.opts.signer) throw new Error("kit/solana: signer required to authorize");
|
|
524
|
+
const pubkey = await this.opts.signer.getPublicKey();
|
|
525
|
+
const sig = await this.opts.signer.sign(message);
|
|
526
|
+
const signature = encodeLowSSignature(sig.r, sig.s);
|
|
527
|
+
const precompileIx = buildSecp256r1Instruction(
|
|
528
|
+
compressedPubkey(pubkey),
|
|
529
|
+
signature,
|
|
530
|
+
message
|
|
531
|
+
);
|
|
532
|
+
return { precompileIx };
|
|
533
|
+
}
|
|
534
|
+
async fetchNonce(account) {
|
|
535
|
+
const info = await this.requireConnection().getAccountInfo(account);
|
|
536
|
+
if (!info) return 0n;
|
|
537
|
+
return readU64le(info.data, 41);
|
|
538
|
+
}
|
|
539
|
+
async fetchSigners(account) {
|
|
540
|
+
const info = await this.requireConnection().getAccountInfo(account);
|
|
541
|
+
if (!info) return [];
|
|
542
|
+
const d = info.data;
|
|
543
|
+
const lenOffset = 8 + 32 + 1 + 8 + COMPRESSED_PUBKEY_SIZE;
|
|
544
|
+
const count = d.readUInt32LE(lenOffset);
|
|
545
|
+
const out = [];
|
|
546
|
+
let off = lenOffset + 4;
|
|
547
|
+
for (let i = 0; i < count; i++) {
|
|
548
|
+
out.push(Uint8Array.from(d.subarray(off, off + COMPRESSED_PUBKEY_SIZE)));
|
|
549
|
+
off += COMPRESSED_PUBKEY_SIZE;
|
|
550
|
+
}
|
|
551
|
+
return out;
|
|
552
|
+
}
|
|
553
|
+
requireConnection() {
|
|
554
|
+
if (!this.opts.connection) throw new Error("kit/solana: connection required for reads");
|
|
555
|
+
return this.opts.connection;
|
|
556
|
+
}
|
|
557
|
+
};
|
|
558
|
+
function compressedPubkey(pk) {
|
|
559
|
+
const out = new Uint8Array(COMPRESSED_PUBKEY_SIZE);
|
|
560
|
+
out[0] = pk.y % 2n === 0n ? 2 : 3;
|
|
561
|
+
out.set(bigIntTo32Bytes(pk.x), 1);
|
|
562
|
+
return out;
|
|
315
563
|
}
|
|
316
|
-
function
|
|
317
|
-
|
|
564
|
+
function encodeLowSSignature(r, s) {
|
|
565
|
+
const lowS = s > SECP256R1_N / 2n ? SECP256R1_N - s : s;
|
|
566
|
+
const out = new Uint8Array(SIGNATURE_SIZE);
|
|
567
|
+
out.set(bigIntTo32Bytes(r), 0);
|
|
568
|
+
out.set(bigIntTo32Bytes(lowS), 32);
|
|
569
|
+
return out;
|
|
318
570
|
}
|
|
319
|
-
function
|
|
320
|
-
|
|
321
|
-
|
|
571
|
+
function buildSecp256r1Instruction(compressed, signature, message) {
|
|
572
|
+
const headerLen = 2;
|
|
573
|
+
const offsetsLen = 14;
|
|
574
|
+
const pubkeyOffset = headerLen + offsetsLen;
|
|
575
|
+
const sigOffset = pubkeyOffset + COMPRESSED_PUBKEY_SIZE;
|
|
576
|
+
const msgOffset = sigOffset + SIGNATURE_SIZE;
|
|
577
|
+
const data = Buffer.alloc(msgOffset + message.length);
|
|
578
|
+
data.writeUInt8(1, 0);
|
|
579
|
+
data.writeUInt8(0, 1);
|
|
580
|
+
let o = headerLen;
|
|
581
|
+
data.writeUInt16LE(sigOffset, o);
|
|
582
|
+
o += 2;
|
|
583
|
+
data.writeUInt16LE(CURRENT_IX, o);
|
|
584
|
+
o += 2;
|
|
585
|
+
data.writeUInt16LE(pubkeyOffset, o);
|
|
586
|
+
o += 2;
|
|
587
|
+
data.writeUInt16LE(CURRENT_IX, o);
|
|
588
|
+
o += 2;
|
|
589
|
+
data.writeUInt16LE(msgOffset, o);
|
|
590
|
+
o += 2;
|
|
591
|
+
data.writeUInt16LE(message.length, o);
|
|
592
|
+
o += 2;
|
|
593
|
+
data.writeUInt16LE(CURRENT_IX, o);
|
|
594
|
+
o += 2;
|
|
595
|
+
Buffer.from(compressed).copy(data, pubkeyOffset);
|
|
596
|
+
Buffer.from(signature).copy(data, sigOffset);
|
|
597
|
+
Buffer.from(message).copy(data, msgOffset);
|
|
598
|
+
return new web3_js.TransactionInstruction({
|
|
599
|
+
keys: [],
|
|
600
|
+
programId: new web3_js.PublicKey(SECP256R1_PROGRAM_ID),
|
|
601
|
+
data
|
|
602
|
+
});
|
|
603
|
+
}
|
|
604
|
+
function anchorDiscriminator(name) {
|
|
605
|
+
return Buffer.from(sha256.sha256(`global:${name}`).slice(0, 8));
|
|
606
|
+
}
|
|
607
|
+
function u64le(n) {
|
|
608
|
+
const b = Buffer.alloc(8);
|
|
609
|
+
new DataView(b.buffer, b.byteOffset, 8).setBigUint64(0, BigInt(n), true);
|
|
610
|
+
return b;
|
|
611
|
+
}
|
|
612
|
+
function readU64le(buf, offset) {
|
|
613
|
+
return new DataView(buf.buffer, buf.byteOffset, buf.length).getBigUint64(
|
|
614
|
+
offset,
|
|
615
|
+
true
|
|
616
|
+
);
|
|
617
|
+
}
|
|
618
|
+
function concatBytes(...parts) {
|
|
619
|
+
const total = parts.reduce((n, p) => n + p.length, 0);
|
|
620
|
+
const out = new Uint8Array(total);
|
|
621
|
+
let off = 0;
|
|
622
|
+
for (const p of parts) {
|
|
623
|
+
out.set(p, off);
|
|
624
|
+
off += p.length;
|
|
322
625
|
}
|
|
323
|
-
return
|
|
626
|
+
return out;
|
|
324
627
|
}
|
|
325
|
-
|
|
628
|
+
function serializeInstruction(ix) {
|
|
629
|
+
const programId = new web3_js.PublicKey(ix.programId).toBuffer();
|
|
630
|
+
const accounts = serializeAccounts(ix.accounts);
|
|
631
|
+
const data = serializeVecU8(ix.data);
|
|
632
|
+
return Buffer.concat([programId, accounts, data]);
|
|
633
|
+
}
|
|
634
|
+
function serializeAccounts(metas) {
|
|
635
|
+
const len = Buffer.alloc(4);
|
|
636
|
+
new DataView(len.buffer).setUint32(0, metas.length, true);
|
|
637
|
+
const parts = metas.map(serializeAccountMeta);
|
|
638
|
+
return Buffer.concat([len, ...parts]);
|
|
639
|
+
}
|
|
640
|
+
function serializeAccountMeta(meta) {
|
|
641
|
+
const pubkey = new web3_js.PublicKey(meta.pubkey).toBuffer();
|
|
642
|
+
return Buffer.concat([pubkey, Buffer.from([meta.isSigner ? 1 : 0, meta.isWritable ? 1 : 0])]);
|
|
643
|
+
}
|
|
644
|
+
function serializeVecU8(data) {
|
|
645
|
+
const len = Buffer.alloc(4);
|
|
646
|
+
new DataView(len.buffer).setUint32(0, data.length, true);
|
|
647
|
+
return Buffer.concat([len, Buffer.from(data)]);
|
|
648
|
+
}
|
|
649
|
+
function serializeInstructions(instructions) {
|
|
650
|
+
return Buffer.concat(instructions.map(serializeInstruction));
|
|
651
|
+
}
|
|
652
|
+
var SolanaRelayer = class {
|
|
326
653
|
constructor(opts) {
|
|
327
654
|
this.opts = opts;
|
|
328
655
|
}
|
|
329
|
-
|
|
330
|
-
|
|
656
|
+
/** The relayer's fee-payer pubkey (fetched + cached from the backend). */
|
|
657
|
+
async getFeePayer() {
|
|
658
|
+
if (this.feePayer) return this.feePayer;
|
|
659
|
+
const res = await fetch(`${this.opts.baseUrl}/api/solana/relay?network=${this.opts.network}`);
|
|
660
|
+
if (!res.ok) throw new Error(`kit/solana: relayer fee-payer lookup failed (${res.status})`);
|
|
661
|
+
const { fee_payer } = await res.json();
|
|
662
|
+
this.feePayer = new web3_js.PublicKey(fee_payer);
|
|
663
|
+
return this.feePayer;
|
|
664
|
+
}
|
|
665
|
+
/**
|
|
666
|
+
* Build a tx with the relayer as fee payer, serialize it unsigned, and POST it
|
|
667
|
+
* to the relayer to co-sign + submit. Returns the confirmed signature.
|
|
668
|
+
*/
|
|
669
|
+
async send(instructions) {
|
|
670
|
+
const feePayer = await this.getFeePayer();
|
|
671
|
+
const { blockhash } = await this.opts.connection.getLatestBlockhash("confirmed");
|
|
672
|
+
const tx2 = new web3_js.Transaction();
|
|
673
|
+
tx2.feePayer = feePayer;
|
|
674
|
+
tx2.recentBlockhash = blockhash;
|
|
675
|
+
tx2.add(...instructions);
|
|
676
|
+
const serialized = tx2.serialize({ requireAllSignatures: false, verifySignatures: false }).toString("base64");
|
|
677
|
+
const res = await fetch(`${this.opts.baseUrl}/api/solana/relay`, {
|
|
331
678
|
method: "POST",
|
|
332
679
|
headers: { "Content-Type": "application/json" },
|
|
333
680
|
body: JSON.stringify({
|
|
334
681
|
app_id: this.opts.appId,
|
|
335
|
-
|
|
336
|
-
|
|
337
|
-
new_pub_y: toHex2(params.newSigner.y),
|
|
338
|
-
device_label: params.deviceLabel ?? deviceLabel(),
|
|
339
|
-
...params.email ? { email: params.email } : {}
|
|
682
|
+
network: this.opts.network,
|
|
683
|
+
transaction: serialized
|
|
340
684
|
})
|
|
341
685
|
});
|
|
342
686
|
if (!res.ok) {
|
|
343
|
-
const
|
|
344
|
-
throw new Error(`
|
|
345
|
-
}
|
|
346
|
-
const data = await res.json();
|
|
347
|
-
return { requestId: data.request_id };
|
|
348
|
-
}
|
|
349
|
-
async getPendingRequest(requestId) {
|
|
350
|
-
const url = new URL("/api/devices/request", this.opts.baseUrl);
|
|
351
|
-
url.searchParams.set("id", requestId);
|
|
352
|
-
const res = await fetch(url, { headers: { "Content-Type": "application/json" } });
|
|
353
|
-
if (!res.ok) throw new Error(`getPendingRequest failed: ${res.status}`);
|
|
354
|
-
const data = await res.json();
|
|
355
|
-
if (!data.found) return null;
|
|
356
|
-
const status = data.status;
|
|
357
|
-
return {
|
|
358
|
-
requestId: data.request_id,
|
|
359
|
-
appId: data.app_id,
|
|
360
|
-
userId: "",
|
|
361
|
-
// the approving device already knows its own identity
|
|
362
|
-
accountAddress: data.wallet_address,
|
|
363
|
-
newSigner: { x: fromHex2(data.new_pub_x), y: fromHex2(data.new_pub_y) },
|
|
364
|
-
createdAt: data.created_at,
|
|
365
|
-
status
|
|
366
|
-
};
|
|
367
|
-
}
|
|
368
|
-
async confirmDeviceAddition(params) {
|
|
369
|
-
const res = await fetch(
|
|
370
|
-
new URL(`/api/devices/request/${params.requestId}/confirm`, this.opts.baseUrl),
|
|
371
|
-
{
|
|
372
|
-
method: "POST",
|
|
373
|
-
headers: { "Content-Type": "application/json" },
|
|
374
|
-
body: JSON.stringify({ tx_hash: params.txHash })
|
|
375
|
-
}
|
|
376
|
-
);
|
|
377
|
-
if (!res.ok) {
|
|
378
|
-
const t = await res.text().catch(() => "");
|
|
379
|
-
throw new Error(`confirmDeviceAddition failed: ${res.status} ${t}`);
|
|
687
|
+
const detail = await res.text().catch(() => "");
|
|
688
|
+
throw new Error(`kit/solana: relay failed (${res.status}) ${detail}`);
|
|
380
689
|
}
|
|
690
|
+
const { signature } = await res.json();
|
|
691
|
+
return signature;
|
|
381
692
|
}
|
|
382
693
|
};
|
|
383
694
|
var BACKUP_KDF_SALT = "cavos-recovery-v1";
|
|
@@ -687,24 +998,279 @@ var WORDLIST = [
|
|
|
687
998
|
"beach",
|
|
688
999
|
"dusk"
|
|
689
1000
|
];
|
|
690
|
-
|
|
691
|
-
|
|
692
|
-
|
|
1001
|
+
|
|
1002
|
+
// src/chains/solana/CavosSolana.ts
|
|
1003
|
+
var CavosSolana = class _CavosSolana {
|
|
1004
|
+
constructor(identity, address, status, connection, adapter, devicePubkey, relayer, feePayer) {
|
|
1005
|
+
this.identity = identity;
|
|
1006
|
+
this.address = address;
|
|
1007
|
+
this.status = status;
|
|
1008
|
+
this.connection = connection;
|
|
1009
|
+
this.adapter = adapter;
|
|
1010
|
+
this.devicePubkey = devicePubkey;
|
|
1011
|
+
this.relayer = relayer;
|
|
1012
|
+
this.feePayer = feePayer;
|
|
1013
|
+
/** Discriminant for the `CavosWallet` union — narrows `execute()` per chain. */
|
|
1014
|
+
this.chain = "solana";
|
|
1015
|
+
}
|
|
1016
|
+
get publicKey() {
|
|
1017
|
+
return this.devicePubkey;
|
|
1018
|
+
}
|
|
1019
|
+
static async connect(opts) {
|
|
1020
|
+
const identity = opts.identity ?? await opts.auth?.authenticate();
|
|
1021
|
+
if (!identity) throw new Error("kit/solana: connect requires `identity` or `auth`");
|
|
1022
|
+
if (opts.network === "solana-mainnet" && !opts.rpcUrl) {
|
|
1023
|
+
console.warn(
|
|
1024
|
+
"[cavos] Using the public mainnet-beta RPC. Pass `rpcUrl` with your own provider (Helius/Triton/QuickNode) for production \u2014 the public endpoint is rate-limited."
|
|
1025
|
+
);
|
|
1026
|
+
}
|
|
1027
|
+
const connection = new web3_js.Connection(opts.rpcUrl ?? SOLANA_NETWORKS[opts.network], "confirmed");
|
|
1028
|
+
const signer = opts.createSigner ? await opts.createSigner(`${identity.userId}:${opts.appSalt}`) : await WebCryptoSigner.loadOrCreate({ keyId: `${identity.userId}:${opts.appSalt}` });
|
|
1029
|
+
const devicePubkey = await signer.getPublicKey();
|
|
1030
|
+
const adapter = new SolanaAdapter({ programId: opts.programId, connection, signer });
|
|
1031
|
+
const addressSeed = deriveAddressSeedSolana({ userId: identity.userId, appSalt: opts.appSalt });
|
|
1032
|
+
const backendUrl = opts.backendUrl ?? "https://cavos.xyz";
|
|
1033
|
+
const registry = opts.registry ?? (opts.appId ? new HttpWalletRegistry({ baseUrl: backendUrl, appId: opts.appId, network: opts.network }) : defaultRegistry);
|
|
1034
|
+
const relayer = opts.relayer ?? (opts.appId ? new SolanaRelayer({ baseUrl: backendUrl, appId: opts.appId, network: opts.network, connection }) : void 0);
|
|
1035
|
+
const existing = await registry.lookup(identity.userId);
|
|
1036
|
+
if (existing) {
|
|
1037
|
+
const isSigner2 = await adapter.isAuthorizedSigner(existing.address, devicePubkey);
|
|
1038
|
+
return new _CavosSolana(
|
|
1039
|
+
identity,
|
|
1040
|
+
existing.address,
|
|
1041
|
+
isSigner2 ? "ready" : "needs-device-approval",
|
|
1042
|
+
connection,
|
|
1043
|
+
adapter,
|
|
1044
|
+
devicePubkey,
|
|
1045
|
+
relayer,
|
|
1046
|
+
opts.feePayer
|
|
1047
|
+
);
|
|
1048
|
+
}
|
|
1049
|
+
const address = adapter.computeAddress(addressSeed, devicePubkey);
|
|
1050
|
+
const deployed = await connection.getAccountInfo(new web3_js.PublicKey(address)) !== null;
|
|
1051
|
+
if (!deployed) {
|
|
1052
|
+
if (relayer) {
|
|
1053
|
+
const payer = await relayer.getFeePayer();
|
|
1054
|
+
const ix = adapter.buildInitialize(addressSeed, payer.toBase58(), devicePubkey);
|
|
1055
|
+
await relayer.send([ix]);
|
|
1056
|
+
} else if (opts.feePayer) {
|
|
1057
|
+
const ix = adapter.buildInitialize(addressSeed, opts.feePayer.publicKey.toBase58(), devicePubkey);
|
|
1058
|
+
await web3_js.sendAndConfirmTransaction(connection, new web3_js.Transaction().add(ix), [opts.feePayer]);
|
|
1059
|
+
} else {
|
|
1060
|
+
throw new Error("kit/solana: a relayer (appId) or feePayer is required to initialize a new account");
|
|
1061
|
+
}
|
|
1062
|
+
}
|
|
1063
|
+
await registry.register({ userId: identity.userId, address, initialSigner: devicePubkey });
|
|
1064
|
+
const isSigner = await adapter.isAuthorizedSigner(address, devicePubkey);
|
|
1065
|
+
return new _CavosSolana(
|
|
1066
|
+
identity,
|
|
1067
|
+
address,
|
|
1068
|
+
isSigner ? "ready" : "needs-device-approval",
|
|
1069
|
+
connection,
|
|
1070
|
+
adapter,
|
|
1071
|
+
devicePubkey,
|
|
1072
|
+
relayer,
|
|
1073
|
+
opts.feePayer
|
|
1074
|
+
);
|
|
1075
|
+
}
|
|
1076
|
+
/** Authorize an additional device signer (device-signed via precompile). */
|
|
1077
|
+
async addSigner(pubkey) {
|
|
1078
|
+
const ixs = await this.adapter.buildAddSigner(this.address, pubkey);
|
|
1079
|
+
return this.send(ixs);
|
|
1080
|
+
}
|
|
1081
|
+
/** Move `amount` lamports out of the account to `destination` (device-signed). */
|
|
1082
|
+
async execute(amount, destination) {
|
|
1083
|
+
if (this.status !== "ready") {
|
|
1084
|
+
throw new Error("kit/solana: this device is not yet an authorized signer of the wallet");
|
|
1085
|
+
}
|
|
1086
|
+
const ixs = await this.adapter.buildExecuteTransfer(this.address, destination, amount);
|
|
1087
|
+
return this.send(ixs);
|
|
1088
|
+
}
|
|
1089
|
+
/**
|
|
1090
|
+
* Run arbitrary CPI `instructions` with the account PDA as signer (device-
|
|
1091
|
+
* signed). The signature commits to sha256 of the canonical Borsh
|
|
1092
|
+
* serialization of the instructions, so it binds exactly the operations the
|
|
1093
|
+
* program will invoke. Unlocks SPL transfers, swaps, staking, etc.
|
|
1094
|
+
*
|
|
1095
|
+
* What the relayer will sponsor is constrained by the app's Solana program
|
|
1096
|
+
* allowlist (configured in the dashboard) — programs outside the allowlist are
|
|
1097
|
+
* rejected before co-signing.
|
|
1098
|
+
*/
|
|
1099
|
+
async executeInstructions(instructions) {
|
|
1100
|
+
if (this.status !== "ready") {
|
|
1101
|
+
throw new Error("kit/solana: this device is not yet an authorized signer of the wallet");
|
|
1102
|
+
}
|
|
1103
|
+
const ixs = await this.adapter.buildExecute(this.address, instructions);
|
|
1104
|
+
return this.send(ixs);
|
|
1105
|
+
}
|
|
1106
|
+
/**
|
|
1107
|
+
* Register the backup signer derived from `code` as an authorized signer of this
|
|
1108
|
+
* account (device-signed via precompile). Idempotent: returns without a tx if
|
|
1109
|
+
* the backup signer is already registered. The code never leaves the device —
|
|
1110
|
+
* only the derived public key travels on-chain.
|
|
1111
|
+
*
|
|
1112
|
+
* Self-custodial: anyone who can re-derive the backup key from the code (i.e.
|
|
1113
|
+
* the rightful owner) can later recover the account with `CavosSolana.recover`.
|
|
1114
|
+
* Run this once, on a registered device, and have the user store the code.
|
|
1115
|
+
*/
|
|
1116
|
+
async setupRecovery(code) {
|
|
1117
|
+
if (this.status !== "ready") {
|
|
1118
|
+
throw new Error("kit/solana: setupRecovery requires a ready, registered device");
|
|
1119
|
+
}
|
|
1120
|
+
const { publicKey: backupPubkey } = deriveBackupKey(code);
|
|
1121
|
+
const already = await this.adapter.isAuthorizedSigner(this.address, backupPubkey);
|
|
1122
|
+
if (already) return void 0;
|
|
1123
|
+
return this.addSigner(backupPubkey);
|
|
1124
|
+
}
|
|
1125
|
+
/**
|
|
1126
|
+
* Recover an account after losing every device signer. Derives the backup key
|
|
1127
|
+
* from `code`, uses it (not the new device key) to sign an `add_signer` for the
|
|
1128
|
+
* new device, and returns a ready CavosSolana bound to the new device. The
|
|
1129
|
+
* account address is unchanged.
|
|
1130
|
+
*
|
|
1131
|
+
* Self-custodial: only someone holding the code (i.e. the rightful owner) can
|
|
1132
|
+
* re-derive the backup key. The backend never sees the code.
|
|
1133
|
+
*
|
|
1134
|
+
* This mirrors `Cavos.recover` (Starknet): the backup key is just another
|
|
1135
|
+
* authorized signer, so recovery is an `add_signer(newDevice)` bundle signed by
|
|
1136
|
+
* the backup key. The on-chain program needs no recovery-specific entrypoint.
|
|
1137
|
+
*/
|
|
1138
|
+
static async recover(opts) {
|
|
1139
|
+
if (opts.network === "solana-mainnet" && !opts.rpcUrl) {
|
|
1140
|
+
console.warn(
|
|
1141
|
+
"[cavos] Using the public mainnet-beta RPC. Pass `rpcUrl` with your own provider (Helius/Triton/QuickNode) for production \u2014 the public endpoint is rate-limited."
|
|
1142
|
+
);
|
|
1143
|
+
}
|
|
1144
|
+
const connection = new web3_js.Connection(opts.rpcUrl ?? SOLANA_NETWORKS[opts.network], "confirmed");
|
|
1145
|
+
const signer = opts.createSigner ? await opts.createSigner(`${opts.identity.userId}:${opts.appSalt}`) : await WebCryptoSigner.loadOrCreate({ keyId: `${opts.identity.userId}:${opts.appSalt}` });
|
|
1146
|
+
const devicePubkey = await signer.getPublicKey();
|
|
1147
|
+
const backup = BackupSigner.fromCode(opts.code);
|
|
1148
|
+
const backupAdapter = new SolanaAdapter({
|
|
1149
|
+
programId: opts.programId,
|
|
1150
|
+
connection,
|
|
1151
|
+
signer: backup
|
|
1152
|
+
});
|
|
1153
|
+
const backendUrl = opts.backendUrl ?? "https://cavos.xyz";
|
|
1154
|
+
const registry = opts.registry ?? (opts.appId ? new HttpWalletRegistry({ baseUrl: backendUrl, appId: opts.appId, network: opts.network }) : defaultRegistry);
|
|
1155
|
+
const existing = await registry.lookup(opts.identity.userId);
|
|
1156
|
+
if (!existing) {
|
|
1157
|
+
throw new Error("kit/solana: no account found for this identity \u2014 nothing to recover");
|
|
1158
|
+
}
|
|
1159
|
+
const relayer = opts.relayer ?? (opts.appId ? new SolanaRelayer({ baseUrl: backendUrl, appId: opts.appId, network: opts.network, connection }) : void 0);
|
|
1160
|
+
const alreadyAuthed = await backupAdapter.isAuthorizedSigner(existing.address, devicePubkey);
|
|
1161
|
+
if (!alreadyAuthed) {
|
|
1162
|
+
const ixs = await backupAdapter.buildAddSigner(existing.address, devicePubkey);
|
|
1163
|
+
if (relayer) {
|
|
1164
|
+
await relayer.send(ixs);
|
|
1165
|
+
} else if (opts.feePayer) {
|
|
1166
|
+
await web3_js.sendAndConfirmTransaction(connection, new web3_js.Transaction().add(...ixs), [opts.feePayer]);
|
|
1167
|
+
} else {
|
|
1168
|
+
throw new Error("kit/solana: a relayer (appId) or feePayer is required to recover");
|
|
1169
|
+
}
|
|
1170
|
+
}
|
|
1171
|
+
const adapter = new SolanaAdapter({ programId: opts.programId, connection, signer });
|
|
1172
|
+
return new _CavosSolana(
|
|
1173
|
+
opts.identity,
|
|
1174
|
+
existing.address,
|
|
1175
|
+
"ready",
|
|
1176
|
+
connection,
|
|
1177
|
+
adapter,
|
|
1178
|
+
devicePubkey,
|
|
1179
|
+
relayer,
|
|
1180
|
+
opts.feePayer
|
|
1181
|
+
);
|
|
1182
|
+
}
|
|
1183
|
+
async send(ixs) {
|
|
1184
|
+
if (this.relayer) return this.relayer.send(ixs);
|
|
1185
|
+
if (this.feePayer) {
|
|
1186
|
+
return web3_js.sendAndConfirmTransaction(this.connection, new web3_js.Transaction().add(...ixs), [this.feePayer]);
|
|
1187
|
+
}
|
|
1188
|
+
throw new Error("kit/solana: no relayer or feePayer configured to submit transactions");
|
|
1189
|
+
}
|
|
1190
|
+
};
|
|
1191
|
+
var defaultRegistry = new InMemoryWalletRegistry();
|
|
1192
|
+
|
|
1193
|
+
// src/recovery/HttpRecoveryClient.ts
|
|
1194
|
+
function toHex2(n) {
|
|
1195
|
+
return "0x" + n.toString(16);
|
|
693
1196
|
}
|
|
694
|
-
function
|
|
695
|
-
|
|
696
|
-
|
|
697
|
-
|
|
698
|
-
|
|
699
|
-
|
|
700
|
-
chunks.push(w);
|
|
1197
|
+
function fromHex2(s) {
|
|
1198
|
+
return BigInt(s);
|
|
1199
|
+
}
|
|
1200
|
+
function deviceLabel() {
|
|
1201
|
+
if (typeof navigator !== "undefined") {
|
|
1202
|
+
return navigator.userAgent || "a new device";
|
|
701
1203
|
}
|
|
702
|
-
|
|
703
|
-
if (chunks.length === 1) return chunks[0];
|
|
704
|
-
return BigInt(starknet.hash.computePoseidonHashOnElements(chunks));
|
|
1204
|
+
return "a new device";
|
|
705
1205
|
}
|
|
1206
|
+
var HttpRecoveryClient = class {
|
|
1207
|
+
constructor(opts) {
|
|
1208
|
+
this.opts = opts;
|
|
1209
|
+
}
|
|
1210
|
+
async requestDeviceAddition(params) {
|
|
1211
|
+
const res = await fetch(new URL("/api/devices/request", this.opts.baseUrl), {
|
|
1212
|
+
method: "POST",
|
|
1213
|
+
headers: { "Content-Type": "application/json" },
|
|
1214
|
+
body: JSON.stringify({
|
|
1215
|
+
app_id: this.opts.appId,
|
|
1216
|
+
wallet_address: params.accountAddress,
|
|
1217
|
+
new_pub_x: toHex2(params.newSigner.x),
|
|
1218
|
+
new_pub_y: toHex2(params.newSigner.y),
|
|
1219
|
+
device_label: params.deviceLabel ?? deviceLabel(),
|
|
1220
|
+
...params.email ? { email: params.email } : {}
|
|
1221
|
+
})
|
|
1222
|
+
});
|
|
1223
|
+
if (!res.ok) {
|
|
1224
|
+
const t = await res.text().catch(() => "");
|
|
1225
|
+
throw new Error(`requestDeviceAddition failed: ${res.status} ${t}`);
|
|
1226
|
+
}
|
|
1227
|
+
const data = await res.json();
|
|
1228
|
+
return { requestId: data.request_id };
|
|
1229
|
+
}
|
|
1230
|
+
async getPendingRequest(requestId) {
|
|
1231
|
+
const url = new URL("/api/devices/request", this.opts.baseUrl);
|
|
1232
|
+
url.searchParams.set("id", requestId);
|
|
1233
|
+
const res = await fetch(url, { headers: { "Content-Type": "application/json" } });
|
|
1234
|
+
if (!res.ok) throw new Error(`getPendingRequest failed: ${res.status}`);
|
|
1235
|
+
const data = await res.json();
|
|
1236
|
+
if (!data.found) return null;
|
|
1237
|
+
const status = data.status;
|
|
1238
|
+
return {
|
|
1239
|
+
requestId: data.request_id,
|
|
1240
|
+
appId: data.app_id,
|
|
1241
|
+
userId: "",
|
|
1242
|
+
// the approving device already knows its own identity
|
|
1243
|
+
accountAddress: data.wallet_address,
|
|
1244
|
+
newSigner: { x: fromHex2(data.new_pub_x), y: fromHex2(data.new_pub_y) },
|
|
1245
|
+
createdAt: data.created_at,
|
|
1246
|
+
status
|
|
1247
|
+
};
|
|
1248
|
+
}
|
|
1249
|
+
async confirmDeviceAddition(params) {
|
|
1250
|
+
const res = await fetch(
|
|
1251
|
+
new URL(`/api/devices/request/${params.requestId}/confirm`, this.opts.baseUrl),
|
|
1252
|
+
{
|
|
1253
|
+
method: "POST",
|
|
1254
|
+
headers: { "Content-Type": "application/json" },
|
|
1255
|
+
body: JSON.stringify({ tx_hash: params.txHash })
|
|
1256
|
+
}
|
|
1257
|
+
);
|
|
1258
|
+
if (!res.ok) {
|
|
1259
|
+
const t = await res.text().catch(() => "");
|
|
1260
|
+
throw new Error(`confirmDeviceAddition failed: ${res.status} ${t}`);
|
|
1261
|
+
}
|
|
1262
|
+
}
|
|
1263
|
+
};
|
|
706
1264
|
|
|
707
1265
|
// src/Cavos.ts
|
|
1266
|
+
var STARKNET_ENV = {
|
|
1267
|
+
mainnet: "mainnet",
|
|
1268
|
+
testnet: "sepolia"
|
|
1269
|
+
};
|
|
1270
|
+
var SOLANA_ENV = {
|
|
1271
|
+
mainnet: "solana-mainnet",
|
|
1272
|
+
testnet: "solana-devnet"
|
|
1273
|
+
};
|
|
708
1274
|
var Cavos = class _Cavos {
|
|
709
1275
|
constructor(identity, address, status, account, adapter, devicePubkey) {
|
|
710
1276
|
this.identity = identity;
|
|
@@ -713,10 +1279,58 @@ var Cavos = class _Cavos {
|
|
|
713
1279
|
this.account = account;
|
|
714
1280
|
this.adapter = adapter;
|
|
715
1281
|
this.devicePubkey = devicePubkey;
|
|
1282
|
+
/** Discriminant for the `CavosWallet` union — narrows `execute()` per chain. */
|
|
1283
|
+
this.chain = "starknet";
|
|
716
1284
|
/** Request id of the pending device-addition, when status is needs-device-approval. */
|
|
717
1285
|
this.pendingRequestId = null;
|
|
718
1286
|
}
|
|
1287
|
+
/**
|
|
1288
|
+
* Unified entry point. Pick a `chain` and an `network` environment; the kit
|
|
1289
|
+
* resolves the concrete network (sepolia/devnet for testnet, mainnet for
|
|
1290
|
+
* mainnet) and returns a chain-native wallet. The result is a discriminated
|
|
1291
|
+
* union (`wallet.chain`), so `execute()` keeps each chain's native signature:
|
|
1292
|
+
*
|
|
1293
|
+
* const wallet = await Cavos.connect({ chain: "solana", network: "testnet", identity, appSalt, appId });
|
|
1294
|
+
* if (wallet.chain === "starknet") await wallet.execute(calls);
|
|
1295
|
+
* else await wallet.execute(amount, dest);
|
|
1296
|
+
*/
|
|
719
1297
|
static async connect(opts) {
|
|
1298
|
+
if (opts.chain === "solana") {
|
|
1299
|
+
return CavosSolana.connect({
|
|
1300
|
+
network: SOLANA_ENV[opts.network],
|
|
1301
|
+
...opts.auth ? { auth: opts.auth } : {},
|
|
1302
|
+
...opts.identity ? { identity: opts.identity } : {},
|
|
1303
|
+
appSalt: opts.appSalt,
|
|
1304
|
+
...opts.appId ? { appId: opts.appId } : {},
|
|
1305
|
+
...opts.backendUrl ? { backendUrl: opts.backendUrl } : {},
|
|
1306
|
+
...opts.registry ? { registry: opts.registry } : {},
|
|
1307
|
+
...opts.rpcUrl ? { rpcUrl: opts.rpcUrl } : {},
|
|
1308
|
+
...opts.programId ? { programId: opts.programId } : {},
|
|
1309
|
+
...opts.createSigner ? { createSigner: opts.createSigner } : {},
|
|
1310
|
+
...opts.relayer ? { relayer: opts.relayer } : {},
|
|
1311
|
+
...opts.feePayer ? { feePayer: opts.feePayer } : {}
|
|
1312
|
+
});
|
|
1313
|
+
}
|
|
1314
|
+
if (!opts.paymasterApiKey) {
|
|
1315
|
+
throw new Error("kit: `paymasterApiKey` is required for Starknet connections");
|
|
1316
|
+
}
|
|
1317
|
+
return _Cavos.connectStarknet({
|
|
1318
|
+
network: STARKNET_ENV[opts.network],
|
|
1319
|
+
auth: opts.auth,
|
|
1320
|
+
identity: opts.identity,
|
|
1321
|
+
appSalt: opts.appSalt,
|
|
1322
|
+
appId: opts.appId,
|
|
1323
|
+
backendUrl: opts.backendUrl,
|
|
1324
|
+
registry: opts.registry,
|
|
1325
|
+
recovery: opts.recovery,
|
|
1326
|
+
paymasterApiKey: opts.paymasterApiKey,
|
|
1327
|
+
paymasterUrl: opts.paymasterUrl,
|
|
1328
|
+
rpcUrl: opts.rpcUrl,
|
|
1329
|
+
classHash: opts.classHash,
|
|
1330
|
+
createSigner: opts.createSigner
|
|
1331
|
+
});
|
|
1332
|
+
}
|
|
1333
|
+
static async connectStarknet(opts) {
|
|
720
1334
|
const identity = opts.identity ?? await opts.auth?.authenticate();
|
|
721
1335
|
if (!identity) throw new Error("kit: connect requires `identity` or `auth`");
|
|
722
1336
|
const classHash = opts.classHash ?? DEVICE_ACCOUNT_CLASS_HASH[opts.network];
|
|
@@ -740,7 +1354,7 @@ var Cavos = class _Cavos {
|
|
|
740
1354
|
cairoVersion: "1"
|
|
741
1355
|
});
|
|
742
1356
|
const backendUrl = opts.backendUrl ?? "https://cavos.xyz";
|
|
743
|
-
const registry = opts.registry ?? (opts.appId ? new HttpWalletRegistry({ baseUrl: backendUrl, appId: opts.appId, network: opts.network }) :
|
|
1357
|
+
const registry = opts.registry ?? (opts.appId ? new HttpWalletRegistry({ baseUrl: backendUrl, appId: opts.appId, network: opts.network }) : defaultRegistry2);
|
|
744
1358
|
const recovery = opts.recovery ?? (opts.appId ? new HttpRecoveryClient({ baseUrl: backendUrl, appId: opts.appId }) : null);
|
|
745
1359
|
const existing = await registry.lookup(identity.userId);
|
|
746
1360
|
if (existing) {
|
|
@@ -858,13 +1472,14 @@ var Cavos = class _Cavos {
|
|
|
858
1472
|
* re-derive the backup key. The backend never sees the code.
|
|
859
1473
|
*/
|
|
860
1474
|
static async recover(opts) {
|
|
861
|
-
const
|
|
862
|
-
|
|
1475
|
+
const network = STARKNET_ENV[opts.network];
|
|
1476
|
+
const classHash = opts.classHash ?? DEVICE_ACCOUNT_CLASS_HASH[network];
|
|
1477
|
+
if (!classHash) throw new Error(`kit: no DeviceAccount class hash for ${network}`);
|
|
863
1478
|
const provider = new starknet.RpcProvider({
|
|
864
|
-
nodeUrl: opts.rpcUrl ?? STARKNET_NETWORKS[
|
|
1479
|
+
nodeUrl: opts.rpcUrl ?? STARKNET_NETWORKS[network].rpcUrl
|
|
865
1480
|
});
|
|
866
1481
|
const paymaster = new starknet.PaymasterRpc({
|
|
867
|
-
nodeUrl: opts.paymasterUrl ?? CAVOS_PAYMASTER_URL[
|
|
1482
|
+
nodeUrl: opts.paymasterUrl ?? CAVOS_PAYMASTER_URL[network],
|
|
868
1483
|
headers: { "x-paymaster-api-key": opts.paymasterApiKey }
|
|
869
1484
|
});
|
|
870
1485
|
const signer = opts.createSigner ? await opts.createSigner(`${opts.identity.userId}:${opts.appSalt}`) : await WebCryptoSigner.loadOrCreate({ keyId: `${opts.identity.userId}:${opts.appSalt}` });
|
|
@@ -872,7 +1487,7 @@ var Cavos = class _Cavos {
|
|
|
872
1487
|
const backup = BackupSigner.fromCode(opts.code);
|
|
873
1488
|
const backupAdapter = new StarknetAdapter({ classHash, signer: backup, provider });
|
|
874
1489
|
const backendUrl = opts.backendUrl ?? "https://cavos.xyz";
|
|
875
|
-
const registry = opts.registry ?? (opts.appId ? new HttpWalletRegistry({ baseUrl: backendUrl, appId: opts.appId, network
|
|
1490
|
+
const registry = opts.registry ?? (opts.appId ? new HttpWalletRegistry({ baseUrl: backendUrl, appId: opts.appId, network }) : defaultRegistry2);
|
|
876
1491
|
const existing = await registry.lookup(opts.identity.userId);
|
|
877
1492
|
if (!existing) {
|
|
878
1493
|
throw new Error("kit: no account found for this identity \u2014 nothing to recover");
|
|
@@ -907,7 +1522,7 @@ var Cavos = class _Cavos {
|
|
|
907
1522
|
return new _Cavos(opts.identity, existing.address, "ready", account, adapter, devicePubkey);
|
|
908
1523
|
}
|
|
909
1524
|
};
|
|
910
|
-
var
|
|
1525
|
+
var defaultRegistry2 = new InMemoryWalletRegistry();
|
|
911
1526
|
var DEVICE_REQUEST_DEDUP_MS = 5 * 60 * 1e3;
|
|
912
1527
|
var lastDeviceRequest = /* @__PURE__ */ new Map();
|
|
913
1528
|
async function isDeployed(provider, address) {
|
|
@@ -1844,7 +2459,7 @@ function CavosProvider({ config, modal, children }) {
|
|
|
1844
2459
|
const closeModal = react.useCallback(() => setModalOpen(false), []);
|
|
1845
2460
|
const clearAuthError = react.useCallback(() => setAuthError(null), []);
|
|
1846
2461
|
const resendDeviceApproval = react.useCallback(async () => {
|
|
1847
|
-
if (!identity || !cavos || !cavos.pendingRequestId) return;
|
|
2462
|
+
if (!identity || !cavos || cavos.chain !== "starknet" || !cavos.pendingRequestId) return;
|
|
1848
2463
|
const backendUrl = configRef.current.authBackendUrl ?? "https://cavos.xyz";
|
|
1849
2464
|
if (!configRef.current.appId) return;
|
|
1850
2465
|
const recovery = new HttpRecoveryClient({ baseUrl: backendUrl, appId: configRef.current.appId });
|
|
@@ -1858,22 +2473,24 @@ function CavosProvider({ config, modal, children }) {
|
|
|
1858
2473
|
const connect = react.useCallback(async (id) => {
|
|
1859
2474
|
setWalletStatus({ ...INITIAL_STATUS, isDeploying: true });
|
|
1860
2475
|
const c = await Cavos.connect({
|
|
2476
|
+
chain: configRef.current.chain ?? "starknet",
|
|
1861
2477
|
network: configRef.current.network,
|
|
1862
2478
|
identity: id,
|
|
1863
2479
|
appSalt: configRef.current.appSalt,
|
|
1864
|
-
paymasterApiKey: configRef.current.paymasterApiKey,
|
|
2480
|
+
...configRef.current.paymasterApiKey ? { paymasterApiKey: configRef.current.paymasterApiKey } : {},
|
|
1865
2481
|
...configRef.current.appId ? { appId: configRef.current.appId } : {},
|
|
1866
2482
|
...configRef.current.authBackendUrl ? { backendUrl: configRef.current.authBackendUrl } : {},
|
|
1867
2483
|
...configRef.current.rpcUrl ? { rpcUrl: configRef.current.rpcUrl } : {}
|
|
1868
2484
|
});
|
|
1869
2485
|
setCavos(c);
|
|
1870
2486
|
setIdentity(id);
|
|
2487
|
+
const pendingRequestId = c.chain === "starknet" ? c.pendingRequestId : null;
|
|
1871
2488
|
setWalletStatus({
|
|
1872
2489
|
isDeploying: false,
|
|
1873
2490
|
isReady: c.status === "ready",
|
|
1874
2491
|
needsDeviceApproval: c.status === "needs-device-approval",
|
|
1875
|
-
awaitingApproval: c.status === "needs-device-approval" && !!
|
|
1876
|
-
pendingRequestId
|
|
2492
|
+
awaitingApproval: c.status === "needs-device-approval" && !!pendingRequestId,
|
|
2493
|
+
pendingRequestId
|
|
1877
2494
|
});
|
|
1878
2495
|
modal?.onSuccess?.(c.address);
|
|
1879
2496
|
return c;
|
|
@@ -1929,11 +2546,19 @@ function CavosProvider({ config, modal, children }) {
|
|
|
1929
2546
|
}, [auth, connect]);
|
|
1930
2547
|
const execute = react.useCallback(async (calls) => {
|
|
1931
2548
|
if (!cavos) throw new Error("Not logged in");
|
|
2549
|
+
if (cavos.chain !== "starknet") {
|
|
2550
|
+
throw new Error(
|
|
2551
|
+
"kit: useCavos().execute(calls) is Starknet-only. On Solana use the `wallet` handle: wallet.execute(amount, dest)."
|
|
2552
|
+
);
|
|
2553
|
+
}
|
|
1932
2554
|
return cavos.execute(calls);
|
|
1933
2555
|
}, [cavos]);
|
|
1934
2556
|
const addSigner = react.useCallback(
|
|
1935
2557
|
async (pubkey) => {
|
|
1936
2558
|
if (!cavos) throw new Error("Not logged in");
|
|
2559
|
+
if (cavos.chain !== "starknet") {
|
|
2560
|
+
throw new Error("kit: addSigner via useCavos() is Starknet-only; use the `wallet` handle on Solana.");
|
|
2561
|
+
}
|
|
1937
2562
|
return cavos.addSigner(pubkey);
|
|
1938
2563
|
},
|
|
1939
2564
|
[cavos]
|
|
@@ -1949,12 +2574,21 @@ function CavosProvider({ config, modal, children }) {
|
|
|
1949
2574
|
setAuthError(null);
|
|
1950
2575
|
setWalletStatus({ ...INITIAL_STATUS, isDeploying: true });
|
|
1951
2576
|
try {
|
|
1952
|
-
const
|
|
2577
|
+
const chain = configRef.current.chain ?? "starknet";
|
|
2578
|
+
const c = chain === "solana" ? await CavosSolana.recover({
|
|
2579
|
+
code,
|
|
2580
|
+
identity,
|
|
2581
|
+
network: configRef.current.network === "mainnet" ? "solana-mainnet" : "solana-devnet",
|
|
2582
|
+
appSalt: configRef.current.appSalt,
|
|
2583
|
+
...configRef.current.appId ? { appId: configRef.current.appId } : {},
|
|
2584
|
+
...configRef.current.authBackendUrl ? { backendUrl: configRef.current.authBackendUrl } : {},
|
|
2585
|
+
...configRef.current.rpcUrl ? { rpcUrl: configRef.current.rpcUrl } : {}
|
|
2586
|
+
}) : await Cavos.recover({
|
|
1953
2587
|
code,
|
|
1954
2588
|
identity,
|
|
1955
2589
|
network: configRef.current.network,
|
|
1956
2590
|
appSalt: configRef.current.appSalt,
|
|
1957
|
-
paymasterApiKey: configRef.current.paymasterApiKey,
|
|
2591
|
+
paymasterApiKey: configRef.current.paymasterApiKey ?? "",
|
|
1958
2592
|
...configRef.current.appId ? { appId: configRef.current.appId } : {},
|
|
1959
2593
|
...configRef.current.authBackendUrl ? { backendUrl: configRef.current.authBackendUrl } : {},
|
|
1960
2594
|
...configRef.current.rpcUrl ? { rpcUrl: configRef.current.rpcUrl } : {}
|
|
@@ -2007,6 +2641,8 @@ function CavosProvider({ config, modal, children }) {
|
|
|
2007
2641
|
closeModal,
|
|
2008
2642
|
isAuthenticated: !!cavos,
|
|
2009
2643
|
user: identity ? { userId: identity.userId, email: identity.email, provider: identity.provider } : null,
|
|
2644
|
+
chain: config.chain ?? "starknet",
|
|
2645
|
+
wallet: cavos,
|
|
2010
2646
|
address: cavos?.address ?? null,
|
|
2011
2647
|
walletStatus,
|
|
2012
2648
|
isLoading,
|