@0xsequence/wallet-wdk 0.0.0-20250520201059
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/.env.test +3 -0
- package/.turbo/turbo-build.log +5 -0
- package/CHANGELOG.md +11 -0
- package/LICENSE +202 -0
- package/dist/dbs/auth-commitments.d.ts +17 -0
- package/dist/dbs/auth-commitments.d.ts.map +1 -0
- package/dist/dbs/auth-commitments.js +13 -0
- package/dist/dbs/auth-keys.d.ts +19 -0
- package/dist/dbs/auth-keys.d.ts.map +1 -0
- package/dist/dbs/auth-keys.js +67 -0
- package/dist/dbs/generic.d.ts +33 -0
- package/dist/dbs/generic.d.ts.map +1 -0
- package/dist/dbs/generic.js +170 -0
- package/dist/dbs/index.d.ts +12 -0
- package/dist/dbs/index.d.ts.map +1 -0
- package/dist/dbs/index.js +8 -0
- package/dist/dbs/messages.d.ts +6 -0
- package/dist/dbs/messages.d.ts.map +1 -0
- package/dist/dbs/messages.js +13 -0
- package/dist/dbs/recovery.d.ts +6 -0
- package/dist/dbs/recovery.d.ts.map +1 -0
- package/dist/dbs/recovery.js +13 -0
- package/dist/dbs/signatures.d.ts +6 -0
- package/dist/dbs/signatures.d.ts.map +1 -0
- package/dist/dbs/signatures.js +13 -0
- package/dist/dbs/transactions.d.ts +6 -0
- package/dist/dbs/transactions.d.ts.map +1 -0
- package/dist/dbs/transactions.js +13 -0
- package/dist/dbs/wallets.d.ts +6 -0
- package/dist/dbs/wallets.d.ts.map +1 -0
- package/dist/dbs/wallets.js +13 -0
- package/dist/identity/signer.d.ts +17 -0
- package/dist/identity/signer.d.ts.map +1 -0
- package/dist/identity/signer.js +58 -0
- package/dist/index.d.ts +3 -0
- package/dist/index.d.ts.map +1 -0
- package/dist/index.js +2 -0
- package/dist/sequence/cron.d.ts +19 -0
- package/dist/sequence/cron.d.ts.map +1 -0
- package/dist/sequence/cron.js +118 -0
- package/dist/sequence/devices.d.ts +14 -0
- package/dist/sequence/devices.d.ts.map +1 -0
- package/dist/sequence/devices.js +43 -0
- package/dist/sequence/handlers/authcode-pkce.d.ts +14 -0
- package/dist/sequence/handlers/authcode-pkce.d.ts.map +1 -0
- package/dist/sequence/handlers/authcode-pkce.js +48 -0
- package/dist/sequence/handlers/authcode.d.ts +25 -0
- package/dist/sequence/handlers/authcode.d.ts.map +1 -0
- package/dist/sequence/handlers/authcode.js +91 -0
- package/dist/sequence/handlers/devices.d.ts +14 -0
- package/dist/sequence/handlers/devices.d.ts.map +1 -0
- package/dist/sequence/handlers/devices.js +39 -0
- package/dist/sequence/handlers/handler.d.ts +8 -0
- package/dist/sequence/handlers/handler.d.ts.map +1 -0
- package/dist/sequence/handlers/handler.js +1 -0
- package/dist/sequence/handlers/identity.d.ts +21 -0
- package/dist/sequence/handlers/identity.d.ts.map +1 -0
- package/dist/sequence/handlers/identity.js +86 -0
- package/dist/sequence/handlers/index.d.ts +7 -0
- package/dist/sequence/handlers/index.d.ts.map +1 -0
- package/dist/sequence/handlers/index.js +5 -0
- package/dist/sequence/handlers/mnemonic.d.ts +19 -0
- package/dist/sequence/handlers/mnemonic.d.ts.map +1 -0
- package/dist/sequence/handlers/mnemonic.js +67 -0
- package/dist/sequence/handlers/otp.d.ts +20 -0
- package/dist/sequence/handlers/otp.d.ts.map +1 -0
- package/dist/sequence/handlers/otp.js +83 -0
- package/dist/sequence/handlers/passkeys.d.ts +17 -0
- package/dist/sequence/handlers/passkeys.d.ts.map +1 -0
- package/dist/sequence/handlers/passkeys.js +63 -0
- package/dist/sequence/handlers/recovery.d.ts +15 -0
- package/dist/sequence/handlers/recovery.d.ts.map +1 -0
- package/dist/sequence/handlers/recovery.js +72 -0
- package/dist/sequence/index.d.ts +12 -0
- package/dist/sequence/index.d.ts.map +1 -0
- package/dist/sequence/index.js +9 -0
- package/dist/sequence/logger.d.ts +7 -0
- package/dist/sequence/logger.d.ts.map +1 -0
- package/dist/sequence/logger.js +11 -0
- package/dist/sequence/manager.d.ts +287 -0
- package/dist/sequence/manager.d.ts.map +1 -0
- package/dist/sequence/manager.js +356 -0
- package/dist/sequence/messages.d.ts +18 -0
- package/dist/sequence/messages.d.ts.map +1 -0
- package/dist/sequence/messages.js +115 -0
- package/dist/sequence/recovery.d.ts +30 -0
- package/dist/sequence/recovery.d.ts.map +1 -0
- package/dist/sequence/recovery.js +314 -0
- package/dist/sequence/sessions.d.ts +26 -0
- package/dist/sequence/sessions.d.ts.map +1 -0
- package/dist/sequence/sessions.js +169 -0
- package/dist/sequence/signatures.d.ts +21 -0
- package/dist/sequence/signatures.d.ts.map +1 -0
- package/dist/sequence/signatures.js +192 -0
- package/dist/sequence/signers.d.ts +14 -0
- package/dist/sequence/signers.d.ts.map +1 -0
- package/dist/sequence/signers.js +74 -0
- package/dist/sequence/transactions.d.ts +26 -0
- package/dist/sequence/transactions.d.ts.map +1 -0
- package/dist/sequence/transactions.js +201 -0
- package/dist/sequence/types/index.d.ts +9 -0
- package/dist/sequence/types/index.d.ts.map +1 -0
- package/dist/sequence/types/index.js +2 -0
- package/dist/sequence/types/message-request.d.ts +23 -0
- package/dist/sequence/types/message-request.d.ts.map +1 -0
- package/dist/sequence/types/message-request.js +1 -0
- package/dist/sequence/types/recovery.d.ts +15 -0
- package/dist/sequence/types/recovery.d.ts.map +1 -0
- package/dist/sequence/types/recovery.js +1 -0
- package/dist/sequence/types/signature-request.d.ts +76 -0
- package/dist/sequence/types/signature-request.d.ts.map +1 -0
- package/dist/sequence/types/signature-request.js +11 -0
- package/dist/sequence/types/signer.d.ts +28 -0
- package/dist/sequence/types/signer.d.ts.map +1 -0
- package/dist/sequence/types/signer.js +10 -0
- package/dist/sequence/types/transaction-request.d.ts +41 -0
- package/dist/sequence/types/transaction-request.d.ts.map +1 -0
- package/dist/sequence/types/transaction-request.js +1 -0
- package/dist/sequence/types/wallet.d.ts +21 -0
- package/dist/sequence/types/wallet.d.ts.map +1 -0
- package/dist/sequence/types/wallet.js +1 -0
- package/dist/sequence/wallets.d.ts +121 -0
- package/dist/sequence/wallets.d.ts.map +1 -0
- package/dist/sequence/wallets.js +632 -0
- package/package.json +40 -0
- package/src/dbs/auth-commitments.ts +26 -0
- package/src/dbs/auth-keys.ts +85 -0
- package/src/dbs/generic.ts +194 -0
- package/src/dbs/index.ts +13 -0
- package/src/dbs/messages.ts +16 -0
- package/src/dbs/recovery.ts +15 -0
- package/src/dbs/signatures.ts +15 -0
- package/src/dbs/transactions.ts +16 -0
- package/src/dbs/wallets.ts +16 -0
- package/src/identity/signer.ts +78 -0
- package/src/index.ts +2 -0
- package/src/sequence/cron.ts +134 -0
- package/src/sequence/devices.ts +53 -0
- package/src/sequence/handlers/authcode-pkce.ts +70 -0
- package/src/sequence/handlers/authcode.ts +116 -0
- package/src/sequence/handlers/devices.ts +53 -0
- package/src/sequence/handlers/handler.ts +14 -0
- package/src/sequence/handlers/identity.ts +101 -0
- package/src/sequence/handlers/index.ts +6 -0
- package/src/sequence/handlers/mnemonic.ts +88 -0
- package/src/sequence/handlers/otp.ts +107 -0
- package/src/sequence/handlers/passkeys.ts +84 -0
- package/src/sequence/handlers/recovery.ts +88 -0
- package/src/sequence/index.ts +25 -0
- package/src/sequence/logger.ts +11 -0
- package/src/sequence/manager.ts +634 -0
- package/src/sequence/messages.ts +146 -0
- package/src/sequence/recovery.ts +429 -0
- package/src/sequence/sessions.ts +238 -0
- package/src/sequence/signatures.ts +263 -0
- package/src/sequence/signers.ts +88 -0
- package/src/sequence/transactions.ts +281 -0
- package/src/sequence/types/index.ts +27 -0
- package/src/sequence/types/message-request.ts +26 -0
- package/src/sequence/types/recovery.ts +15 -0
- package/src/sequence/types/signature-request.ts +89 -0
- package/src/sequence/types/signer.ts +32 -0
- package/src/sequence/types/transaction-request.ts +47 -0
- package/src/sequence/types/wallet.ts +24 -0
- package/src/sequence/wallets.ts +853 -0
- package/test/constants.ts +62 -0
- package/test/recovery.test.ts +211 -0
- package/test/sessions.test.ts +324 -0
- package/test/setup.ts +63 -0
- package/test/transactions.test.ts +464 -0
- package/test/wallets.test.ts +381 -0
- package/tsconfig.json +10 -0
- package/vitest.config.ts +11 -0
|
@@ -0,0 +1,70 @@
|
|
|
1
|
+
import { Hex, Address, Bytes } from 'ox'
|
|
2
|
+
import { Handler } from './handler.js'
|
|
3
|
+
import * as Db from '../../dbs/index.js'
|
|
4
|
+
import { Signatures } from '../signatures.js'
|
|
5
|
+
import * as Identity from '@0xsequence/identity-instrument'
|
|
6
|
+
import { IdentitySigner } from '../../identity/signer.js'
|
|
7
|
+
import { AuthCodeHandler } from './authcode.js'
|
|
8
|
+
|
|
9
|
+
export class AuthCodePkceHandler extends AuthCodeHandler implements Handler {
|
|
10
|
+
constructor(
|
|
11
|
+
signupKind: 'google-pkce',
|
|
12
|
+
issuer: string,
|
|
13
|
+
audience: string,
|
|
14
|
+
nitro: Identity.IdentityInstrument,
|
|
15
|
+
signatures: Signatures,
|
|
16
|
+
commitments: Db.AuthCommitments,
|
|
17
|
+
authKeys: Db.AuthKeys,
|
|
18
|
+
) {
|
|
19
|
+
super(signupKind, issuer, audience, nitro, signatures, commitments, authKeys)
|
|
20
|
+
}
|
|
21
|
+
|
|
22
|
+
public async commitAuth(target: string, isSignUp: boolean, state?: string, signer?: string) {
|
|
23
|
+
let challenge = new Identity.AuthCodePkceChallenge(this.issuer, this.audience, this.redirectUri)
|
|
24
|
+
if (signer) {
|
|
25
|
+
challenge = challenge.withSigner(signer)
|
|
26
|
+
}
|
|
27
|
+
const { verifier, loginHint, challenge: codeChallenge } = await this.nitroCommitVerifier(challenge)
|
|
28
|
+
if (!state) {
|
|
29
|
+
state = Hex.fromBytes(Bytes.random(32))
|
|
30
|
+
}
|
|
31
|
+
|
|
32
|
+
await this.commitments.set({
|
|
33
|
+
id: state,
|
|
34
|
+
kind: this.signupKind,
|
|
35
|
+
verifier,
|
|
36
|
+
challenge: codeChallenge,
|
|
37
|
+
target,
|
|
38
|
+
metadata: {},
|
|
39
|
+
isSignUp,
|
|
40
|
+
})
|
|
41
|
+
|
|
42
|
+
const searchParams = new URLSearchParams({
|
|
43
|
+
code_challenge: codeChallenge,
|
|
44
|
+
code_challenge_method: 'S256',
|
|
45
|
+
client_id: this.audience,
|
|
46
|
+
redirect_uri: this.redirectUri,
|
|
47
|
+
login_hint: loginHint,
|
|
48
|
+
response_type: 'code',
|
|
49
|
+
scope: 'openid profile email',
|
|
50
|
+
state,
|
|
51
|
+
})
|
|
52
|
+
|
|
53
|
+
const oauthUrl = this.oauthUrl()
|
|
54
|
+
return `${oauthUrl}?${searchParams.toString()}`
|
|
55
|
+
}
|
|
56
|
+
|
|
57
|
+
public async completeAuth(
|
|
58
|
+
commitment: Db.AuthCommitment,
|
|
59
|
+
code: string,
|
|
60
|
+
): Promise<[IdentitySigner, { [key: string]: string }]> {
|
|
61
|
+
const challenge = new Identity.AuthCodePkceChallenge('', '', '')
|
|
62
|
+
if (!commitment.verifier) {
|
|
63
|
+
throw new Error('Missing verifier in commitment')
|
|
64
|
+
}
|
|
65
|
+
const signer = await this.nitroCompleteAuth(challenge.withAnswer(commitment.verifier, code))
|
|
66
|
+
await this.commitments.del(commitment.id)
|
|
67
|
+
|
|
68
|
+
return [signer, commitment.metadata]
|
|
69
|
+
}
|
|
70
|
+
}
|
|
@@ -0,0 +1,116 @@
|
|
|
1
|
+
import { Hex, Address, Bytes } from 'ox'
|
|
2
|
+
import { Handler } from './handler.js'
|
|
3
|
+
import * as Db from '../../dbs/index.js'
|
|
4
|
+
import { Signatures } from '../signatures.js'
|
|
5
|
+
import * as Identity from '@0xsequence/identity-instrument'
|
|
6
|
+
import { SignerUnavailable, SignerReady, SignerActionable, BaseSignatureRequest } from '../types/signature-request.js'
|
|
7
|
+
import { IdentitySigner } from '../../identity/signer.js'
|
|
8
|
+
import { IdentityHandler } from './identity.js'
|
|
9
|
+
|
|
10
|
+
export class AuthCodeHandler extends IdentityHandler implements Handler {
|
|
11
|
+
protected redirectUri: string = ''
|
|
12
|
+
|
|
13
|
+
constructor(
|
|
14
|
+
public readonly signupKind: 'apple' | 'google-pkce',
|
|
15
|
+
public readonly issuer: string,
|
|
16
|
+
public readonly audience: string,
|
|
17
|
+
nitro: Identity.IdentityInstrument,
|
|
18
|
+
signatures: Signatures,
|
|
19
|
+
protected readonly commitments: Db.AuthCommitments,
|
|
20
|
+
authKeys: Db.AuthKeys,
|
|
21
|
+
) {
|
|
22
|
+
super(nitro, authKeys, signatures, Identity.IdentityType.OIDC)
|
|
23
|
+
}
|
|
24
|
+
|
|
25
|
+
public get kind() {
|
|
26
|
+
return 'login-' + this.signupKind
|
|
27
|
+
}
|
|
28
|
+
|
|
29
|
+
public setRedirectUri(redirectUri: string) {
|
|
30
|
+
this.redirectUri = redirectUri
|
|
31
|
+
}
|
|
32
|
+
|
|
33
|
+
public async commitAuth(target: string, isSignUp: boolean, state?: string, signer?: string) {
|
|
34
|
+
if (!state) {
|
|
35
|
+
state = Hex.fromBytes(Bytes.random(32))
|
|
36
|
+
}
|
|
37
|
+
|
|
38
|
+
await this.commitments.set({
|
|
39
|
+
id: state,
|
|
40
|
+
kind: this.signupKind,
|
|
41
|
+
signer,
|
|
42
|
+
target,
|
|
43
|
+
metadata: {},
|
|
44
|
+
isSignUp,
|
|
45
|
+
})
|
|
46
|
+
|
|
47
|
+
const searchParams = new URLSearchParams({
|
|
48
|
+
client_id: this.audience,
|
|
49
|
+
redirect_uri: this.redirectUri,
|
|
50
|
+
response_type: 'code',
|
|
51
|
+
scope: 'openid',
|
|
52
|
+
state,
|
|
53
|
+
})
|
|
54
|
+
|
|
55
|
+
const oauthUrl = this.oauthUrl()
|
|
56
|
+
return `${oauthUrl}?${searchParams.toString()}`
|
|
57
|
+
}
|
|
58
|
+
|
|
59
|
+
public async completeAuth(
|
|
60
|
+
commitment: Db.AuthCommitment,
|
|
61
|
+
code: string,
|
|
62
|
+
): Promise<[IdentitySigner, { [key: string]: string }]> {
|
|
63
|
+
let challenge = new Identity.AuthCodeChallenge(this.issuer, this.audience, this.redirectUri, code)
|
|
64
|
+
if (commitment.signer) {
|
|
65
|
+
challenge = challenge.withSigner(commitment.signer)
|
|
66
|
+
}
|
|
67
|
+
await this.nitroCommitVerifier(challenge)
|
|
68
|
+
const signer = await this.nitroCompleteAuth(challenge)
|
|
69
|
+
|
|
70
|
+
return [signer, {}]
|
|
71
|
+
}
|
|
72
|
+
|
|
73
|
+
async status(
|
|
74
|
+
address: Address.Address,
|
|
75
|
+
_imageHash: Hex.Hex | undefined,
|
|
76
|
+
request: BaseSignatureRequest,
|
|
77
|
+
): Promise<SignerUnavailable | SignerReady | SignerActionable> {
|
|
78
|
+
// Normalize address
|
|
79
|
+
const normalizedAddress = Address.checksum(address)
|
|
80
|
+
const signer = await this.getAuthKeySigner(normalizedAddress)
|
|
81
|
+
if (signer) {
|
|
82
|
+
return {
|
|
83
|
+
address: normalizedAddress,
|
|
84
|
+
handler: this,
|
|
85
|
+
status: 'ready',
|
|
86
|
+
handle: async () => {
|
|
87
|
+
await this.sign(signer, request)
|
|
88
|
+
return true
|
|
89
|
+
},
|
|
90
|
+
}
|
|
91
|
+
}
|
|
92
|
+
|
|
93
|
+
return {
|
|
94
|
+
address: normalizedAddress,
|
|
95
|
+
handler: this,
|
|
96
|
+
status: 'actionable',
|
|
97
|
+
message: 'request-redirect',
|
|
98
|
+
handle: async () => {
|
|
99
|
+
const url = await this.commitAuth(window.location.pathname, false, request.id, normalizedAddress)
|
|
100
|
+
window.location.href = url
|
|
101
|
+
return true
|
|
102
|
+
},
|
|
103
|
+
}
|
|
104
|
+
}
|
|
105
|
+
|
|
106
|
+
protected oauthUrl() {
|
|
107
|
+
switch (this.issuer) {
|
|
108
|
+
case 'https://accounts.google.com':
|
|
109
|
+
return 'https://accounts.google.com/o/oauth2/v2/auth'
|
|
110
|
+
case 'https://appleid.apple.com':
|
|
111
|
+
return 'https://appleid.apple.com/auth/authorize'
|
|
112
|
+
default:
|
|
113
|
+
throw new Error('unsupported-issuer')
|
|
114
|
+
}
|
|
115
|
+
}
|
|
116
|
+
}
|
|
@@ -0,0 +1,53 @@
|
|
|
1
|
+
import { Kinds } from '../types/signer.js'
|
|
2
|
+
import { Signatures } from '../signatures.js'
|
|
3
|
+
import { Address, Hex } from 'ox'
|
|
4
|
+
import { Devices } from '../devices.js'
|
|
5
|
+
import { Handler } from './handler.js'
|
|
6
|
+
import { SignerReady, SignerUnavailable, BaseSignatureRequest } from '../types/index.js'
|
|
7
|
+
|
|
8
|
+
export class DevicesHandler implements Handler {
|
|
9
|
+
kind = Kinds.LocalDevice
|
|
10
|
+
|
|
11
|
+
constructor(
|
|
12
|
+
private readonly signatures: Signatures,
|
|
13
|
+
private readonly devices: Devices,
|
|
14
|
+
) {}
|
|
15
|
+
|
|
16
|
+
onStatusChange(cb: () => void): () => void {
|
|
17
|
+
return () => {}
|
|
18
|
+
}
|
|
19
|
+
|
|
20
|
+
async status(
|
|
21
|
+
address: Address.Address,
|
|
22
|
+
_imageHash: Hex.Hex | undefined,
|
|
23
|
+
request: BaseSignatureRequest,
|
|
24
|
+
): Promise<SignerUnavailable | SignerReady> {
|
|
25
|
+
const signer = await this.devices.get(address)
|
|
26
|
+
if (!signer) {
|
|
27
|
+
const status: SignerUnavailable = {
|
|
28
|
+
address,
|
|
29
|
+
handler: this,
|
|
30
|
+
reason: 'not-local-key',
|
|
31
|
+
status: 'unavailable',
|
|
32
|
+
}
|
|
33
|
+
return status
|
|
34
|
+
}
|
|
35
|
+
|
|
36
|
+
const status: SignerReady = {
|
|
37
|
+
address,
|
|
38
|
+
handler: this,
|
|
39
|
+
status: 'ready',
|
|
40
|
+
handle: async () => {
|
|
41
|
+
const signature = await signer.sign(request.envelope.wallet, request.envelope.chainId, request.envelope.payload)
|
|
42
|
+
|
|
43
|
+
await this.signatures.addSignature(request.id, {
|
|
44
|
+
address,
|
|
45
|
+
signature,
|
|
46
|
+
})
|
|
47
|
+
|
|
48
|
+
return true
|
|
49
|
+
},
|
|
50
|
+
}
|
|
51
|
+
return status
|
|
52
|
+
}
|
|
53
|
+
}
|
|
@@ -0,0 +1,14 @@
|
|
|
1
|
+
import { Address, Hex } from 'ox'
|
|
2
|
+
import { SignerActionable, SignerReady, SignerUnavailable, BaseSignatureRequest } from '../types/index.js'
|
|
3
|
+
|
|
4
|
+
export interface Handler {
|
|
5
|
+
kind: string
|
|
6
|
+
|
|
7
|
+
onStatusChange(cb: () => void): () => void
|
|
8
|
+
|
|
9
|
+
status(
|
|
10
|
+
address: Address.Address,
|
|
11
|
+
imageHash: Hex.Hex | undefined,
|
|
12
|
+
request: BaseSignatureRequest,
|
|
13
|
+
): Promise<SignerUnavailable | SignerReady | SignerActionable>
|
|
14
|
+
}
|
|
@@ -0,0 +1,101 @@
|
|
|
1
|
+
import { Hex, Bytes } from 'ox'
|
|
2
|
+
import * as Db from '../../dbs/index.js'
|
|
3
|
+
import * as Identity from '@0xsequence/identity-instrument'
|
|
4
|
+
import { Signatures } from '../signatures.js'
|
|
5
|
+
import { BaseSignatureRequest } from '../types/signature-request.js'
|
|
6
|
+
import { IdentitySigner, toIdentityAuthKey } from '../../identity/signer.js'
|
|
7
|
+
|
|
8
|
+
export const identityTypeToHex = (identityType?: Identity.IdentityType): Hex.Hex => {
|
|
9
|
+
// Bytes4
|
|
10
|
+
switch (identityType) {
|
|
11
|
+
case Identity.IdentityType.Guest:
|
|
12
|
+
return '0x00000000'
|
|
13
|
+
case Identity.IdentityType.Email:
|
|
14
|
+
return '0x00000001'
|
|
15
|
+
case Identity.IdentityType.OIDC:
|
|
16
|
+
return '0x00000002'
|
|
17
|
+
default:
|
|
18
|
+
// Unknown identity type
|
|
19
|
+
return '0xffffffff'
|
|
20
|
+
}
|
|
21
|
+
}
|
|
22
|
+
|
|
23
|
+
export class IdentityHandler {
|
|
24
|
+
constructor(
|
|
25
|
+
private readonly nitro: Identity.IdentityInstrument,
|
|
26
|
+
private readonly authKeys: Db.AuthKeys,
|
|
27
|
+
private readonly signatures: Signatures,
|
|
28
|
+
public readonly identityType: Identity.IdentityType,
|
|
29
|
+
) {}
|
|
30
|
+
|
|
31
|
+
public onStatusChange(cb: () => void): () => void {
|
|
32
|
+
return this.authKeys.addListener(cb)
|
|
33
|
+
}
|
|
34
|
+
|
|
35
|
+
protected async nitroCommitVerifier(challenge: Identity.Challenge) {
|
|
36
|
+
await this.authKeys.delBySigner('')
|
|
37
|
+
const authKey = await this.getAuthKey('')
|
|
38
|
+
if (!authKey) {
|
|
39
|
+
throw new Error('no-auth-key')
|
|
40
|
+
}
|
|
41
|
+
|
|
42
|
+
const res = await this.nitro.commitVerifier(toIdentityAuthKey(authKey), challenge)
|
|
43
|
+
return res
|
|
44
|
+
}
|
|
45
|
+
|
|
46
|
+
protected async nitroCompleteAuth(challenge: Identity.Challenge) {
|
|
47
|
+
const authKey = await this.getAuthKey('')
|
|
48
|
+
if (!authKey) {
|
|
49
|
+
throw new Error('no-auth-key')
|
|
50
|
+
}
|
|
51
|
+
|
|
52
|
+
const res = await this.nitro.completeAuth(toIdentityAuthKey(authKey), challenge)
|
|
53
|
+
|
|
54
|
+
authKey.identitySigner = res.signer
|
|
55
|
+
authKey.expiresAt = new Date(Date.now() + 1000 * 60 * 3) // 3 minutes
|
|
56
|
+
await this.authKeys.delBySigner('')
|
|
57
|
+
await this.authKeys.set(authKey)
|
|
58
|
+
|
|
59
|
+
const signer = new IdentitySigner(this.nitro, authKey)
|
|
60
|
+
return signer
|
|
61
|
+
}
|
|
62
|
+
|
|
63
|
+
protected async sign(signer: IdentitySigner, request: BaseSignatureRequest) {
|
|
64
|
+
const signature = await signer.sign(request.envelope.wallet, request.envelope.chainId, request.envelope.payload)
|
|
65
|
+
await this.signatures.addSignature(request.id, {
|
|
66
|
+
address: signer.address,
|
|
67
|
+
signature,
|
|
68
|
+
})
|
|
69
|
+
}
|
|
70
|
+
|
|
71
|
+
protected async getAuthKeySigner(address: string): Promise<IdentitySigner | undefined> {
|
|
72
|
+
const authKey = await this.getAuthKey(address)
|
|
73
|
+
if (!authKey) {
|
|
74
|
+
return undefined
|
|
75
|
+
}
|
|
76
|
+
return new IdentitySigner(this.nitro, authKey)
|
|
77
|
+
}
|
|
78
|
+
|
|
79
|
+
private async getAuthKey(signer: string): Promise<Db.AuthKey | undefined> {
|
|
80
|
+
let authKey = await this.authKeys.getBySigner(signer)
|
|
81
|
+
if (!signer && !authKey) {
|
|
82
|
+
const keyPair = await window.crypto.subtle.generateKey(
|
|
83
|
+
{
|
|
84
|
+
name: 'ECDSA',
|
|
85
|
+
namedCurve: 'P-256',
|
|
86
|
+
},
|
|
87
|
+
false,
|
|
88
|
+
['sign', 'verify'],
|
|
89
|
+
)
|
|
90
|
+
const publicKey = await window.crypto.subtle.exportKey('raw', keyPair.publicKey)
|
|
91
|
+
authKey = {
|
|
92
|
+
address: Hex.fromBytes(new Uint8Array(publicKey)),
|
|
93
|
+
identitySigner: '',
|
|
94
|
+
expiresAt: new Date(Date.now() + 1000 * 60 * 60), // 1 hour
|
|
95
|
+
privateKey: keyPair.privateKey,
|
|
96
|
+
}
|
|
97
|
+
await this.authKeys.set(authKey)
|
|
98
|
+
}
|
|
99
|
+
return authKey
|
|
100
|
+
}
|
|
101
|
+
}
|
|
@@ -0,0 +1,6 @@
|
|
|
1
|
+
export type { Handler } from './handler.js'
|
|
2
|
+
export { DevicesHandler } from './devices.js'
|
|
3
|
+
export { PasskeysHandler } from './passkeys.js'
|
|
4
|
+
export { OtpHandler } from './otp.js'
|
|
5
|
+
export { AuthCodePkceHandler } from './authcode-pkce.js'
|
|
6
|
+
export { MnemonicHandler } from './mnemonic.js'
|
|
@@ -0,0 +1,88 @@
|
|
|
1
|
+
import { Signers } from '@0xsequence/wallet-core'
|
|
2
|
+
import { Address, Hex, Mnemonic } from 'ox'
|
|
3
|
+
import { Handler } from './handler.js'
|
|
4
|
+
import { Signatures } from '../signatures.js'
|
|
5
|
+
import { Kinds } from '../types/signer.js'
|
|
6
|
+
import { SignerReady, SignerUnavailable, BaseSignatureRequest, SignerActionable } from '../types/index.js'
|
|
7
|
+
|
|
8
|
+
type RespondFn = (mnemonic: string) => Promise<void>
|
|
9
|
+
|
|
10
|
+
export class MnemonicHandler implements Handler {
|
|
11
|
+
kind = Kinds.LoginMnemonic
|
|
12
|
+
|
|
13
|
+
private onPromptMnemonic: undefined | ((respond: RespondFn) => Promise<void>)
|
|
14
|
+
|
|
15
|
+
constructor(private readonly signatures: Signatures) {}
|
|
16
|
+
|
|
17
|
+
public registerUI(onPromptMnemonic: (respond: RespondFn) => Promise<void>) {
|
|
18
|
+
this.onPromptMnemonic = onPromptMnemonic
|
|
19
|
+
return () => {
|
|
20
|
+
this.onPromptMnemonic = undefined
|
|
21
|
+
}
|
|
22
|
+
}
|
|
23
|
+
|
|
24
|
+
public unregisterUI() {
|
|
25
|
+
this.onPromptMnemonic = undefined
|
|
26
|
+
}
|
|
27
|
+
|
|
28
|
+
onStatusChange(_cb: () => void): () => void {
|
|
29
|
+
return () => {}
|
|
30
|
+
}
|
|
31
|
+
|
|
32
|
+
public static toSigner(mnemonic: string): Signers.Pk.Pk | undefined {
|
|
33
|
+
try {
|
|
34
|
+
const pk = Mnemonic.toPrivateKey(mnemonic)
|
|
35
|
+
return new Signers.Pk.Pk(Hex.from(pk))
|
|
36
|
+
} catch {
|
|
37
|
+
return undefined
|
|
38
|
+
}
|
|
39
|
+
}
|
|
40
|
+
|
|
41
|
+
async status(
|
|
42
|
+
address: Address.Address,
|
|
43
|
+
_imageHash: Hex.Hex | undefined,
|
|
44
|
+
request: BaseSignatureRequest,
|
|
45
|
+
): Promise<SignerUnavailable | SignerActionable> {
|
|
46
|
+
const onPromptMnemonic = this.onPromptMnemonic
|
|
47
|
+
if (!onPromptMnemonic) {
|
|
48
|
+
return {
|
|
49
|
+
address,
|
|
50
|
+
handler: this,
|
|
51
|
+
reason: 'ui-not-registered',
|
|
52
|
+
status: 'unavailable',
|
|
53
|
+
}
|
|
54
|
+
}
|
|
55
|
+
|
|
56
|
+
return {
|
|
57
|
+
address,
|
|
58
|
+
handler: this,
|
|
59
|
+
status: 'actionable',
|
|
60
|
+
message: 'enter-mnemonic',
|
|
61
|
+
handle: () =>
|
|
62
|
+
new Promise(async (resolve, reject) => {
|
|
63
|
+
const respond = async (mnemonic: string) => {
|
|
64
|
+
const signer = MnemonicHandler.toSigner(mnemonic)
|
|
65
|
+
if (!signer) {
|
|
66
|
+
return reject('invalid-mnemonic')
|
|
67
|
+
}
|
|
68
|
+
|
|
69
|
+
if (signer.address !== address) {
|
|
70
|
+
return reject('wrong-mnemonic')
|
|
71
|
+
}
|
|
72
|
+
|
|
73
|
+
const signature = await signer.sign(
|
|
74
|
+
request.envelope.wallet,
|
|
75
|
+
request.envelope.chainId,
|
|
76
|
+
request.envelope.payload,
|
|
77
|
+
)
|
|
78
|
+
await this.signatures.addSignature(request.id, {
|
|
79
|
+
address,
|
|
80
|
+
signature,
|
|
81
|
+
})
|
|
82
|
+
resolve(true)
|
|
83
|
+
}
|
|
84
|
+
await onPromptMnemonic(respond)
|
|
85
|
+
}),
|
|
86
|
+
}
|
|
87
|
+
}
|
|
88
|
+
}
|
|
@@ -0,0 +1,107 @@
|
|
|
1
|
+
import { Hex, Address } from 'ox'
|
|
2
|
+
import { Signers } from '@0xsequence/wallet-core'
|
|
3
|
+
import * as Identity from '@0xsequence/identity-instrument'
|
|
4
|
+
import { Handler } from './handler.js'
|
|
5
|
+
import * as Db from '../../dbs/index.js'
|
|
6
|
+
import { Signatures } from '../signatures.js'
|
|
7
|
+
import { SignerUnavailable, SignerReady, SignerActionable, BaseSignatureRequest } from '../types/signature-request.js'
|
|
8
|
+
import { Kinds } from '../types/signer.js'
|
|
9
|
+
import { IdentityHandler } from './identity.js'
|
|
10
|
+
|
|
11
|
+
type RespondFn = (otp: string) => Promise<void>
|
|
12
|
+
|
|
13
|
+
export class OtpHandler extends IdentityHandler implements Handler {
|
|
14
|
+
kind = Kinds.LoginEmailOtp
|
|
15
|
+
|
|
16
|
+
private onPromptOtp: undefined | ((recipient: string, respond: RespondFn) => Promise<void>)
|
|
17
|
+
|
|
18
|
+
constructor(nitro: Identity.IdentityInstrument, signatures: Signatures, authKeys: Db.AuthKeys) {
|
|
19
|
+
super(nitro, authKeys, signatures, Identity.IdentityType.Email)
|
|
20
|
+
}
|
|
21
|
+
|
|
22
|
+
public registerUI(onPromptOtp: (recipient: string, respond: RespondFn) => Promise<void>) {
|
|
23
|
+
this.onPromptOtp = onPromptOtp
|
|
24
|
+
return () => {
|
|
25
|
+
this.onPromptOtp = undefined
|
|
26
|
+
}
|
|
27
|
+
}
|
|
28
|
+
|
|
29
|
+
public unregisterUI() {
|
|
30
|
+
this.onPromptOtp = undefined
|
|
31
|
+
}
|
|
32
|
+
|
|
33
|
+
public async getSigner(email: string): Promise<Signers.Signer & Signers.Witnessable> {
|
|
34
|
+
const onPromptOtp = this.onPromptOtp
|
|
35
|
+
if (!onPromptOtp) {
|
|
36
|
+
throw new Error('otp-handler-ui-not-registered')
|
|
37
|
+
}
|
|
38
|
+
|
|
39
|
+
const challenge = Identity.OtpChallenge.fromRecipient(this.identityType, email)
|
|
40
|
+
const { loginHint, challenge: codeChallenge } = await this.nitroCommitVerifier(challenge)
|
|
41
|
+
|
|
42
|
+
return new Promise(async (resolve, reject) => {
|
|
43
|
+
const respond = async (otp: string) => {
|
|
44
|
+
try {
|
|
45
|
+
const signer = await this.nitroCompleteAuth(challenge.withAnswer(codeChallenge, otp))
|
|
46
|
+
resolve(signer)
|
|
47
|
+
} catch (e) {
|
|
48
|
+
reject(e)
|
|
49
|
+
}
|
|
50
|
+
}
|
|
51
|
+
await onPromptOtp(loginHint, respond)
|
|
52
|
+
})
|
|
53
|
+
}
|
|
54
|
+
|
|
55
|
+
async status(
|
|
56
|
+
address: Address.Address,
|
|
57
|
+
_imageHash: Hex.Hex | undefined,
|
|
58
|
+
request: BaseSignatureRequest,
|
|
59
|
+
): Promise<SignerUnavailable | SignerReady | SignerActionable> {
|
|
60
|
+
const onPromptOtp = this.onPromptOtp
|
|
61
|
+
if (!onPromptOtp) {
|
|
62
|
+
return {
|
|
63
|
+
address,
|
|
64
|
+
handler: this,
|
|
65
|
+
reason: 'ui-not-registered',
|
|
66
|
+
status: 'unavailable',
|
|
67
|
+
}
|
|
68
|
+
}
|
|
69
|
+
|
|
70
|
+
const signer = await this.getAuthKeySigner(address)
|
|
71
|
+
if (signer) {
|
|
72
|
+
return {
|
|
73
|
+
address,
|
|
74
|
+
handler: this,
|
|
75
|
+
status: 'ready',
|
|
76
|
+
handle: async () => {
|
|
77
|
+
await this.sign(signer, request)
|
|
78
|
+
return true
|
|
79
|
+
},
|
|
80
|
+
}
|
|
81
|
+
}
|
|
82
|
+
|
|
83
|
+
return {
|
|
84
|
+
address,
|
|
85
|
+
handler: this,
|
|
86
|
+
status: 'actionable',
|
|
87
|
+
message: 'request-otp',
|
|
88
|
+
handle: () =>
|
|
89
|
+
new Promise(async (resolve, reject) => {
|
|
90
|
+
const challenge = Identity.OtpChallenge.fromSigner(this.identityType, address)
|
|
91
|
+
const { loginHint, challenge: codeChallenge } = await this.nitroCommitVerifier(challenge)
|
|
92
|
+
|
|
93
|
+
const respond = async (otp: string) => {
|
|
94
|
+
try {
|
|
95
|
+
await this.nitroCompleteAuth(challenge.withAnswer(codeChallenge, otp))
|
|
96
|
+
resolve(true)
|
|
97
|
+
} catch (e) {
|
|
98
|
+
resolve(false)
|
|
99
|
+
throw e
|
|
100
|
+
}
|
|
101
|
+
}
|
|
102
|
+
|
|
103
|
+
await onPromptOtp(loginHint, respond)
|
|
104
|
+
}),
|
|
105
|
+
}
|
|
106
|
+
}
|
|
107
|
+
}
|
|
@@ -0,0 +1,84 @@
|
|
|
1
|
+
import { Signers, State } from '@0xsequence/wallet-core'
|
|
2
|
+
import { Address, Hex } from 'ox'
|
|
3
|
+
import { Kinds } from '../types/signer.js'
|
|
4
|
+
import { Signatures } from '../signatures.js'
|
|
5
|
+
import { Extensions } from '@0xsequence/wallet-primitives'
|
|
6
|
+
import { Handler } from './handler.js'
|
|
7
|
+
import { SignerActionable, SignerUnavailable, BaseSignatureRequest } from '../types/index.js'
|
|
8
|
+
|
|
9
|
+
export class PasskeysHandler implements Handler {
|
|
10
|
+
kind = Kinds.LoginPasskey
|
|
11
|
+
|
|
12
|
+
constructor(
|
|
13
|
+
private readonly signatures: Signatures,
|
|
14
|
+
private readonly extensions: Pick<Extensions.Extensions, 'passkeys'>,
|
|
15
|
+
private readonly stateReader: State.Reader,
|
|
16
|
+
) {}
|
|
17
|
+
|
|
18
|
+
onStatusChange(cb: () => void): () => void {
|
|
19
|
+
return () => {}
|
|
20
|
+
}
|
|
21
|
+
|
|
22
|
+
private async loadPasskey(wallet: Address.Address, imageHash: Hex.Hex): Promise<Signers.Passkey.Passkey | undefined> {
|
|
23
|
+
try {
|
|
24
|
+
return await Signers.Passkey.Passkey.loadFromWitness(this.stateReader, this.extensions, wallet, imageHash)
|
|
25
|
+
} catch (e) {
|
|
26
|
+
console.warn('Failed to load passkey:', e)
|
|
27
|
+
return undefined
|
|
28
|
+
}
|
|
29
|
+
}
|
|
30
|
+
|
|
31
|
+
async status(
|
|
32
|
+
address: Address.Address,
|
|
33
|
+
imageHash: Hex.Hex | undefined,
|
|
34
|
+
request: BaseSignatureRequest,
|
|
35
|
+
): Promise<SignerActionable | SignerUnavailable> {
|
|
36
|
+
const base = { address, imageHash, handler: this }
|
|
37
|
+
if (address !== this.extensions.passkeys) {
|
|
38
|
+
console.warn(
|
|
39
|
+
'PasskeySigner: status address does not match passkey module address',
|
|
40
|
+
address,
|
|
41
|
+
this.extensions.passkeys,
|
|
42
|
+
)
|
|
43
|
+
const status: SignerUnavailable = {
|
|
44
|
+
...base,
|
|
45
|
+
status: 'unavailable',
|
|
46
|
+
reason: 'unknown-error',
|
|
47
|
+
}
|
|
48
|
+
return status
|
|
49
|
+
}
|
|
50
|
+
|
|
51
|
+
const passkey = imageHash && (await this.loadPasskey(request.envelope.wallet, imageHash))
|
|
52
|
+
if (!passkey) {
|
|
53
|
+
console.warn('PasskeySigner: status failed to load passkey', address, imageHash)
|
|
54
|
+
const status: SignerUnavailable = {
|
|
55
|
+
...base,
|
|
56
|
+
status: 'unavailable',
|
|
57
|
+
reason: 'unknown-error',
|
|
58
|
+
}
|
|
59
|
+
return status
|
|
60
|
+
}
|
|
61
|
+
|
|
62
|
+
const status: SignerActionable = {
|
|
63
|
+
...base,
|
|
64
|
+
status: 'actionable',
|
|
65
|
+
message: 'request-interaction-with-passkey',
|
|
66
|
+
imageHash: imageHash,
|
|
67
|
+
handle: async () => {
|
|
68
|
+
const signature = await passkey.signSapient(
|
|
69
|
+
request.envelope.wallet,
|
|
70
|
+
request.envelope.chainId,
|
|
71
|
+
request.envelope.payload,
|
|
72
|
+
imageHash,
|
|
73
|
+
)
|
|
74
|
+
await this.signatures.addSignature(request.id, {
|
|
75
|
+
address,
|
|
76
|
+
imageHash,
|
|
77
|
+
signature,
|
|
78
|
+
})
|
|
79
|
+
return true
|
|
80
|
+
},
|
|
81
|
+
}
|
|
82
|
+
return status
|
|
83
|
+
}
|
|
84
|
+
}
|