@0xsequence/wallet-wdk 3.0.4 → 3.0.5

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.
Files changed (34) hide show
  1. package/.turbo/turbo-build.log +1 -1
  2. package/.turbo/turbo-lint.log +1 -1
  3. package/.turbo/turbo-typecheck.log +1 -1
  4. package/CHANGELOG.md +12 -0
  5. package/dist/dbs/auth-commitments.d.ts +21 -3
  6. package/dist/dbs/auth-commitments.d.ts.map +1 -1
  7. package/dist/dbs/index.d.ts +1 -1
  8. package/dist/dbs/index.d.ts.map +1 -1
  9. package/dist/sequence/handlers/authcode-pkce.d.ts +2 -1
  10. package/dist/sequence/handlers/authcode-pkce.d.ts.map +1 -1
  11. package/dist/sequence/handlers/authcode-pkce.js +15 -9
  12. package/dist/sequence/handlers/authcode.d.ts +2 -1
  13. package/dist/sequence/handlers/authcode.d.ts.map +1 -1
  14. package/dist/sequence/handlers/authcode.js +19 -10
  15. package/dist/sequence/index.d.ts +1 -1
  16. package/dist/sequence/index.d.ts.map +1 -1
  17. package/dist/sequence/types/signature-request.d.ts +4 -0
  18. package/dist/sequence/types/signature-request.d.ts.map +1 -1
  19. package/dist/sequence/types/signature-request.js +2 -0
  20. package/dist/sequence/wallets.d.ts +82 -0
  21. package/dist/sequence/wallets.d.ts.map +1 -1
  22. package/dist/sequence/wallets.js +173 -21
  23. package/package.json +8 -8
  24. package/src/dbs/auth-commitments.ts +6 -3
  25. package/src/dbs/index.ts +1 -1
  26. package/src/sequence/handlers/authcode-pkce.ts +16 -10
  27. package/src/sequence/handlers/authcode.ts +20 -11
  28. package/src/sequence/index.ts +3 -0
  29. package/src/sequence/types/signature-request.ts +4 -0
  30. package/src/sequence/wallets.ts +299 -21
  31. package/test/authcode-pkce.test.ts +27 -18
  32. package/test/authcode.test.ts +24 -19
  33. package/test/identity-auth-dbs.test.ts +8 -7
  34. package/test/wallets.test.ts +1 -1
@@ -4,6 +4,7 @@ import { Address, Provider, RpcTransport } from 'ox';
4
4
  import { AuthCodeHandler } from './handlers/authcode.js';
5
5
  import { IdTokenHandler } from './handlers/idtoken.js';
6
6
  import { MnemonicHandler } from './handlers/mnemonic.js';
7
+ import { Actions } from './types/index.js';
7
8
  import { Kinds } from './types/signer.js';
8
9
  function getSignupHandlerKey(kind) {
9
10
  if (kind === 'google-pkce') {
@@ -51,6 +52,19 @@ export function isAuthCodeArgs(args) {
51
52
  export function isIdTokenArgs(args) {
52
53
  return 'idToken' in args;
53
54
  }
55
+ function addLoginSignerToSignupArgs(args) {
56
+ switch (args.kind) {
57
+ case 'mnemonic':
58
+ return { kind: 'mnemonic', mnemonic: args.mnemonic };
59
+ case 'email-otp':
60
+ return { kind: 'email-otp', email: args.email };
61
+ default: {
62
+ // google-id-token, apple-id-token, custom-*
63
+ const _args = args;
64
+ return { kind: _args.kind, idToken: _args.idToken };
65
+ }
66
+ }
67
+ }
54
68
  function buildCappedTree(members) {
55
69
  const loginMemberWeight = 1n;
56
70
  if (members.length === 0) {
@@ -381,35 +395,81 @@ export class Wallets {
381
395
  if (!(handler instanceof AuthCodeHandler)) {
382
396
  throw new Error('handler-does-not-support-redirect');
383
397
  }
384
- return handler.commitAuth(args.target, true);
398
+ return handler.commitAuth(args.target, { type: 'auth' });
399
+ }
400
+ async startAddLoginSignerWithRedirect(args) {
401
+ const walletEntry = await this.get(args.wallet);
402
+ if (!walletEntry) {
403
+ throw new Error('wallet-not-found');
404
+ }
405
+ if (walletEntry.status !== 'ready') {
406
+ throw new Error('wallet-not-ready');
407
+ }
408
+ const kind = getSignupHandlerKey(args.kind);
409
+ const handler = this.shared.handlers.get(kind);
410
+ if (!handler) {
411
+ throw new Error('handler-not-registered');
412
+ }
413
+ if (!(handler instanceof AuthCodeHandler)) {
414
+ throw new Error('handler-does-not-support-redirect');
415
+ }
416
+ return handler.commitAuth(args.target, { type: 'add-signer', wallet: args.wallet });
385
417
  }
386
418
  async completeRedirect(args) {
387
419
  const commitment = await this.shared.databases.authCommitments.get(args.state);
388
420
  if (!commitment) {
389
421
  throw new Error('invalid-state');
390
422
  }
391
- // commitment.isSignUp and signUp also mean 'signIn' from wallet's perspective
392
- if (commitment.isSignUp) {
393
- await this.signUp({
394
- kind: commitment.kind,
395
- commitment,
396
- code: args.code,
397
- noGuard: args.noGuard,
398
- target: commitment.target,
399
- isRedirect: true,
400
- use4337: args.use4337,
401
- });
402
- }
403
- else {
404
- const handlerKind = getSignupHandlerKey(commitment.kind);
405
- const handler = this.shared.handlers.get(handlerKind);
406
- if (!handler) {
407
- throw new Error('handler-not-registered');
423
+ switch (commitment.type) {
424
+ case 'add-signer': {
425
+ const handlerKind = getSignupHandlerKey(commitment.kind);
426
+ const handler = this.shared.handlers.get(handlerKind);
427
+ if (!handler) {
428
+ throw new Error('handler-not-registered');
429
+ }
430
+ if (!(handler instanceof AuthCodeHandler)) {
431
+ throw new Error('handler-does-not-support-redirect');
432
+ }
433
+ const walletAddress = commitment.wallet;
434
+ const walletEntry = await this.get(walletAddress);
435
+ if (!walletEntry) {
436
+ throw new Error('wallet-not-found');
437
+ }
438
+ if (walletEntry.status !== 'ready') {
439
+ throw new Error('wallet-not-ready');
440
+ }
441
+ const [signer] = await handler.completeAuth(commitment, args.code);
442
+ const signerKind = getSignerKindForSignup(commitment.kind);
443
+ await this.addLoginSignerFromPrepared(walletAddress, {
444
+ signer,
445
+ extra: { signerKind },
446
+ });
447
+ break;
448
+ }
449
+ case 'auth': {
450
+ await this.signUp({
451
+ kind: commitment.kind,
452
+ commitment,
453
+ code: args.code,
454
+ noGuard: args.noGuard,
455
+ target: commitment.target,
456
+ isRedirect: true,
457
+ use4337: args.use4337,
458
+ });
459
+ break;
408
460
  }
409
- if (!(handler instanceof AuthCodeHandler)) {
410
- throw new Error('handler-does-not-support-redirect');
461
+ case 'reauth': {
462
+ const handlerKind = getSignupHandlerKey(commitment.kind);
463
+ const handler = this.shared.handlers.get(handlerKind);
464
+ if (!handler) {
465
+ throw new Error('handler-not-registered');
466
+ }
467
+ if (!(handler instanceof AuthCodeHandler)) {
468
+ throw new Error('handler-does-not-support-redirect');
469
+ }
470
+ await handler.completeAuth(commitment, args.code);
471
+ break;
411
472
  }
412
- await handler.completeAuth(commitment, args.code);
413
473
  }
414
474
  if (!commitment.target) {
415
475
  throw new Error('invalid-state');
@@ -741,6 +801,67 @@ export class Wallets {
741
801
  loginDate: new Date().toISOString(),
742
802
  });
743
803
  }
804
+ async addLoginSigner(args) {
805
+ const walletEntry = await this.get(args.wallet);
806
+ if (!walletEntry) {
807
+ throw new Error('wallet-not-found');
808
+ }
809
+ if (walletEntry.status !== 'ready') {
810
+ throw new Error('wallet-not-ready');
811
+ }
812
+ const signupArgs = addLoginSignerToSignupArgs(args);
813
+ const loginSigner = await this.prepareSignUp(signupArgs);
814
+ return this.addLoginSignerFromPrepared(args.wallet, loginSigner);
815
+ }
816
+ async completeAddLoginSigner(requestId) {
817
+ const request = await this.shared.modules.signatures.get(requestId);
818
+ if (request.action !== Actions.AddLoginSigner) {
819
+ throw new Error('invalid-request-action');
820
+ }
821
+ await this.completeConfigurationUpdate(requestId);
822
+ }
823
+ async removeLoginSigner(args) {
824
+ const walletEntry = await this.get(args.wallet);
825
+ if (!walletEntry) {
826
+ throw new Error('wallet-not-found');
827
+ }
828
+ if (walletEntry.status !== 'ready') {
829
+ throw new Error('wallet-not-ready');
830
+ }
831
+ const { loginTopology, modules } = await this.getConfigurationParts(args.wallet);
832
+ const existingSigners = Config.getSigners(loginTopology);
833
+ const allExistingAddresses = [...existingSigners.signers, ...existingSigners.sapientSigners.map((s) => s.address)];
834
+ if (!allExistingAddresses.some((addr) => Address.isEqual(addr, args.signerAddress))) {
835
+ throw new Error('signer-not-found');
836
+ }
837
+ const remainingMembers = [
838
+ ...existingSigners.signers
839
+ .filter((x) => x !== Constants.ZeroAddress && !Address.isEqual(x, args.signerAddress))
840
+ .map((x) => ({ address: x })),
841
+ ...existingSigners.sapientSigners
842
+ .filter((x) => !Address.isEqual(x.address, args.signerAddress))
843
+ .map((x) => ({ address: x.address, imageHash: x.imageHash })),
844
+ ];
845
+ if (remainingMembers.length < 1) {
846
+ throw new Error('cannot-remove-last-login-signer');
847
+ }
848
+ const nextLoginTopology = buildCappedTree(remainingMembers);
849
+ if (this.shared.modules.sessions.hasSessionModule(modules)) {
850
+ await this.shared.modules.sessions.removeIdentitySignerFromModules(modules, args.signerAddress);
851
+ }
852
+ if (this.shared.modules.recovery.hasRecoveryModule(modules)) {
853
+ await this.shared.modules.recovery.removeRecoverySignerFromModules(modules, args.signerAddress);
854
+ }
855
+ const requestId = await this.requestConfigurationUpdate(args.wallet, { loginTopology: nextLoginTopology, modules }, Actions.RemoveLoginSigner, 'wallet-webapp');
856
+ return requestId;
857
+ }
858
+ async completeRemoveLoginSigner(requestId) {
859
+ const request = await this.shared.modules.signatures.get(requestId);
860
+ if (request.action !== Actions.RemoveLoginSigner) {
861
+ throw new Error('invalid-request-action');
862
+ }
863
+ await this.completeConfigurationUpdate(requestId);
864
+ }
744
865
  async logout(wallet, options) {
745
866
  const walletEntry = await this.shared.databases.manager.get(wallet);
746
867
  if (!walletEntry) {
@@ -913,4 +1034,35 @@ export class Wallets {
913
1034
  }, action, 'wallet-webapp');
914
1035
  return requestId;
915
1036
  }
1037
+ async addLoginSignerFromPrepared(wallet, loginSigner) {
1038
+ const newSignerAddress = await loginSigner.signer.address;
1039
+ const { loginTopology, modules } = await this.getConfigurationParts(wallet);
1040
+ // Check for duplicate signer
1041
+ const existingSigners = Config.getSigners(loginTopology);
1042
+ const allExistingAddresses = [...existingSigners.signers, ...existingSigners.sapientSigners.map((s) => s.address)];
1043
+ if (allExistingAddresses.some((addr) => Address.isEqual(addr, newSignerAddress))) {
1044
+ throw new Error('signer-already-exists');
1045
+ }
1046
+ // Build new login topology with the additional signer
1047
+ const existingMembers = [
1048
+ ...existingSigners.signers.filter((x) => x !== Constants.ZeroAddress).map((x) => ({ address: x })),
1049
+ ...existingSigners.sapientSigners.map((x) => ({ address: x.address, imageHash: x.imageHash })),
1050
+ ];
1051
+ const newMember = {
1052
+ address: newSignerAddress,
1053
+ imageHash: Signers.isSapientSigner(loginSigner.signer) ? await loginSigner.signer.imageHash : undefined,
1054
+ };
1055
+ const nextLoginTopology = buildCappedTree([...existingMembers, newMember]);
1056
+ // Add non-sapient login signer to sessions module identity signers
1057
+ if (!Signers.isSapientSigner(loginSigner.signer) && this.shared.modules.sessions.hasSessionModule(modules)) {
1058
+ await this.shared.modules.sessions.addIdentitySignerToModules(modules, newSignerAddress);
1059
+ }
1060
+ // Add to recovery module if present
1061
+ if (this.shared.modules.recovery.hasRecoveryModule(modules)) {
1062
+ await this.shared.modules.recovery.addRecoverySignerToModules(modules, newSignerAddress);
1063
+ }
1064
+ // Witness so the wallet becomes discoverable via the new credential
1065
+ await loginSigner.signer.witness(this.shared.sequence.stateProvider, wallet, loginSigner.extra);
1066
+ return this.requestConfigurationUpdate(wallet, { loginTopology: nextLoginTopology, modules }, Actions.AddLoginSigner, 'wallet-webapp');
1067
+ }
916
1068
  }
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@0xsequence/wallet-wdk",
3
- "version": "3.0.4",
3
+ "version": "3.0.5",
4
4
  "license": "Apache-2.0",
5
5
  "type": "module",
6
6
  "publishConfig": {
@@ -21,8 +21,8 @@
21
21
  "happy-dom": "^20.7.0",
22
22
  "typescript": "^5.9.3",
23
23
  "vitest": "^4.0.18",
24
- "@repo/eslint-config": "^0.0.1",
25
- "@repo/typescript-config": "^0.0.1"
24
+ "@repo/typescript-config": "^0.0.1",
25
+ "@repo/eslint-config": "^0.0.1"
26
26
  },
27
27
  "dependencies": {
28
28
  "@0xsequence/tee-verifier": "^0.1.2",
@@ -30,11 +30,11 @@
30
30
  "jwt-decode": "^4.0.0",
31
31
  "ox": "^0.9.17",
32
32
  "uuid": "^13.0.0",
33
- "@0xsequence/guard": "^3.0.4",
34
- "@0xsequence/identity-instrument": "^3.0.4",
35
- "@0xsequence/wallet-core": "^3.0.4",
36
- "@0xsequence/relayer": "^3.0.4",
37
- "@0xsequence/wallet-primitives": "^3.0.4"
33
+ "@0xsequence/identity-instrument": "^3.0.5",
34
+ "@0xsequence/guard": "^3.0.5",
35
+ "@0xsequence/wallet-core": "^3.0.5",
36
+ "@0xsequence/relayer": "^3.0.5",
37
+ "@0xsequence/wallet-primitives": "^3.0.5"
38
38
  },
39
39
  "scripts": {
40
40
  "build": "tsc",
@@ -3,6 +3,11 @@ import { IDBPDatabase, IDBPTransaction } from 'idb'
3
3
 
4
4
  const TABLE_NAME = 'auth-commitments'
5
5
 
6
+ export type CommitAuthArgs =
7
+ | { type: 'auth'; state?: string }
8
+ | { type: 'reauth'; state: string; signer: string }
9
+ | { type: 'add-signer'; wallet: string; state?: string }
10
+
6
11
  export type AuthCommitment = {
7
12
  id: string
8
13
  kind: 'google-pkce' | 'apple' | `custom-${string}`
@@ -10,9 +15,7 @@ export type AuthCommitment = {
10
15
  verifier?: string
11
16
  challenge?: string
12
17
  target: string
13
- isSignUp: boolean
14
- signer?: string
15
- }
18
+ } & ({ type: 'auth' } | { type: 'reauth'; signer: string } | { type: 'add-signer'; wallet: string })
16
19
 
17
20
  export class AuthCommitments extends Generic<AuthCommitment, 'id'> {
18
21
  constructor(dbName: string = 'sequence-auth-commitments') {
package/src/dbs/index.ts CHANGED
@@ -1,4 +1,4 @@
1
- export type { AuthCommitment } from './auth-commitments.js'
1
+ export type { AuthCommitment, CommitAuthArgs } from './auth-commitments.js'
2
2
  export { AuthCommitments } from './auth-commitments.js'
3
3
 
4
4
  export type { AuthKey } from './auth-keys.js'
@@ -6,6 +6,7 @@ import * as Identity from '@0xsequence/identity-instrument'
6
6
  import { IdentitySigner } from '../../identity/signer.js'
7
7
  import { AuthCodeHandler } from './authcode.js'
8
8
  import type { WdkEnv } from '../../env.js'
9
+ import type { CommitAuthArgs } from '../../dbs/auth-commitments.js'
9
10
 
10
11
  export class AuthCodePkceHandler extends AuthCodeHandler implements Handler {
11
12
  constructor(
@@ -22,25 +23,30 @@ export class AuthCodePkceHandler extends AuthCodeHandler implements Handler {
22
23
  super(signupKind, issuer, oauthUrl, audience, nitro, signatures, commitments, authKeys, env)
23
24
  }
24
25
 
25
- public async commitAuth(target: string, isSignUp: boolean, state?: string, signer?: string) {
26
+ public async commitAuth(target: string, args: CommitAuthArgs) {
26
27
  let challenge = new Identity.AuthCodePkceChallenge(this.issuer, this.audience, this.redirectUri)
27
- if (signer) {
28
- challenge = challenge.withSigner({ address: signer, keyType: Identity.KeyType.Ethereum_Secp256k1 })
28
+ if (args.type === 'reauth') {
29
+ challenge = challenge.withSigner({ address: args.signer, keyType: Identity.KeyType.Ethereum_Secp256k1 })
29
30
  }
30
31
  const { verifier, loginHint, challenge: codeChallenge } = await this.nitroCommitVerifier(challenge)
31
- if (!state) {
32
- state = Hex.fromBytes(Bytes.random(32))
33
- }
32
+ const state = args.state ?? Hex.fromBytes(Bytes.random(32))
34
33
 
35
- await this.commitments.set({
34
+ const base = {
36
35
  id: state,
37
- kind: this.signupKind,
36
+ kind: this.signupKind as Db.AuthCommitment['kind'],
38
37
  verifier,
39
38
  challenge: codeChallenge,
40
39
  target,
41
40
  metadata: {},
42
- isSignUp,
43
- })
41
+ }
42
+
43
+ if (args.type === 'reauth') {
44
+ await this.commitments.set({ ...base, type: 'reauth', signer: args.signer })
45
+ } else if (args.type === 'add-signer') {
46
+ await this.commitments.set({ ...base, type: 'add-signer', wallet: args.wallet })
47
+ } else {
48
+ await this.commitments.set({ ...base, type: 'auth' })
49
+ }
44
50
 
45
51
  const searchParams = this.serializeQuery({
46
52
  code_challenge: codeChallenge,
@@ -8,6 +8,7 @@ import { IdentitySigner } from '../../identity/signer.js'
8
8
  import { IdentityHandler } from './identity.js'
9
9
  import { Kinds } from '../types/signer.js'
10
10
  import type { NavigationLike, WdkEnv } from '../../env.js'
11
+ import type { CommitAuthArgs } from '../../dbs/auth-commitments.js'
11
12
 
12
13
  export class AuthCodeHandler extends IdentityHandler implements Handler {
13
14
  protected redirectUri: string = ''
@@ -39,19 +40,23 @@ export class AuthCodeHandler extends IdentityHandler implements Handler {
39
40
  this.redirectUri = redirectUri
40
41
  }
41
42
 
42
- public async commitAuth(target: string, isSignUp: boolean, state?: string, signer?: string) {
43
- if (!state) {
44
- state = Hex.fromBytes(Bytes.random(32))
45
- }
43
+ public async commitAuth(target: string, args: CommitAuthArgs) {
44
+ const state = args.state ?? Hex.fromBytes(Bytes.random(32))
46
45
 
47
- await this.commitments.set({
46
+ const base = {
48
47
  id: state,
49
- kind: this.signupKind,
50
- signer,
48
+ kind: this.signupKind as Db.AuthCommitment['kind'],
51
49
  target,
52
50
  metadata: {},
53
- isSignUp,
54
- })
51
+ }
52
+
53
+ if (args.type === 'reauth') {
54
+ await this.commitments.set({ ...base, type: 'reauth', signer: args.signer })
55
+ } else if (args.type === 'add-signer') {
56
+ await this.commitments.set({ ...base, type: 'add-signer', wallet: args.wallet })
57
+ } else {
58
+ await this.commitments.set({ ...base, type: 'auth' })
59
+ }
55
60
 
56
61
  const searchParams = this.serializeQuery({
57
62
  client_id: this.audience,
@@ -69,7 +74,7 @@ export class AuthCodeHandler extends IdentityHandler implements Handler {
69
74
  code: string,
70
75
  ): Promise<[IdentitySigner, { [key: string]: string }]> {
71
76
  let challenge = new Identity.AuthCodeChallenge(this.issuer, this.audience, this.redirectUri, code)
72
- if (commitment.signer) {
77
+ if (commitment.type === 'reauth') {
73
78
  challenge = challenge.withSigner({ address: commitment.signer, keyType: Identity.KeyType.Ethereum_Secp256k1 })
74
79
  }
75
80
  await this.nitroCommitVerifier(challenge)
@@ -103,7 +108,11 @@ export class AuthCodeHandler extends IdentityHandler implements Handler {
103
108
  message: 'request-redirect',
104
109
  handle: async () => {
105
110
  const navigation = this.getNavigation()
106
- const url = await this.commitAuth(navigation.getPathname(), false, request.id, address)
111
+ const url = await this.commitAuth(navigation.getPathname(), {
112
+ type: 'reauth',
113
+ state: request.id,
114
+ signer: address,
115
+ })
107
116
  navigation.redirect(url)
108
117
  return true
109
118
  },
@@ -9,12 +9,15 @@ export { Sessions } from './sessions.js'
9
9
  export { Signatures } from './signatures.js'
10
10
  export type {
11
11
  StartSignUpWithRedirectArgs,
12
+ StartAddLoginSignerWithRedirectArgs,
12
13
  CommonSignupArgs,
13
14
  PasskeySignupArgs,
14
15
  MnemonicSignupArgs,
15
16
  EmailOtpSignupArgs,
16
17
  CompleteRedirectArgs,
17
18
  SignupArgs,
19
+ AddLoginSignerArgs,
20
+ RemoveLoginSignerArgs,
18
21
  LoginToWalletArgs,
19
22
  LoginToMnemonicArgs,
20
23
  LoginToPasskeyArgs,
@@ -13,6 +13,8 @@ export type ActionToPayload = {
13
13
  [Actions.Recovery]: Payload.Recovery<Payload.Calls>
14
14
  [Actions.AddRecoverySigner]: Payload.ConfigUpdate
15
15
  [Actions.RemoveRecoverySigner]: Payload.ConfigUpdate
16
+ [Actions.AddLoginSigner]: Payload.ConfigUpdate
17
+ [Actions.RemoveLoginSigner]: Payload.ConfigUpdate
16
18
  [Actions.SessionImplicitAuthorize]: Payload.SessionImplicitAuthorize
17
19
  }
18
20
 
@@ -26,6 +28,8 @@ export const Actions = {
26
28
  Recovery: 'recovery',
27
29
  AddRecoverySigner: 'add-recovery-signer',
28
30
  RemoveRecoverySigner: 'remove-recovery-signer',
31
+ AddLoginSigner: 'add-login-signer',
32
+ RemoveLoginSigner: 'remove-login-signer',
29
33
  SessionImplicitAuthorize: 'session-implicit-authorize',
30
34
  } as const
31
35