passkey-kit 0.4.1 → 0.4.2
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/PROPOSAL.md +1 -1
- package/package.json +1 -1
- package/src/base.ts +157 -0
- package/src/kit.ts +19 -40
- package/types/base.d.ts +25 -0
- package/types/kit.d.ts +9 -12
package/PROPOSAL.md
CHANGED
|
@@ -1,4 +1,4 @@
|
|
|
1
|
-
#
|
|
1
|
+
# WebAuthn smart wallet contract interface
|
|
2
2
|
|
|
3
3
|
With the release of [Protocol 21](https://stellar.org/blog/developers/announcing-protocol-21) (and specifically the inclusion of the secp256r1 verification curve) Soroban now has tremendous first class support for passkey powered smart wallets.
|
|
4
4
|
|
package/package.json
CHANGED
package/src/base.ts
CHANGED
|
@@ -1,14 +1,40 @@
|
|
|
1
|
+
import { Networks, SorobanRpc, xdr } from "@stellar/stellar-sdk"
|
|
2
|
+
import { Client as FactoryClient } from 'passkey-factory-sdk'
|
|
3
|
+
import base64url from "base64url"
|
|
4
|
+
|
|
1
5
|
export class PasskeyBase {
|
|
6
|
+
public rpc: SorobanRpc.Server
|
|
7
|
+
public factory: FactoryClient
|
|
8
|
+
public rpcUrl: string
|
|
9
|
+
public networkPassphrase: Networks
|
|
2
10
|
public launchtubeUrl: string | undefined
|
|
3
11
|
public launchtubeJwt: string | undefined
|
|
12
|
+
public mercuryUrl: string | undefined
|
|
13
|
+
public mercuryJwt: string | undefined
|
|
14
|
+
public mercuryEmail: string | undefined
|
|
15
|
+
public mercuryPassword: string | undefined
|
|
4
16
|
|
|
5
17
|
constructor(options: {
|
|
18
|
+
rpcUrl: string,
|
|
19
|
+
networkPassphrase: string,
|
|
20
|
+
factoryContractId: string,
|
|
6
21
|
launchtubeUrl?: string,
|
|
7
22
|
launchtubeJwt?: string,
|
|
23
|
+
mercuryUrl?: string,
|
|
24
|
+
mercuryJwt?: string,
|
|
25
|
+
mercuryEmail?: string,
|
|
26
|
+
mercuryPassword?: string
|
|
8
27
|
}) {
|
|
9
28
|
const {
|
|
29
|
+
rpcUrl,
|
|
30
|
+
networkPassphrase,
|
|
31
|
+
factoryContractId,
|
|
10
32
|
launchtubeUrl,
|
|
11
33
|
launchtubeJwt,
|
|
34
|
+
mercuryUrl,
|
|
35
|
+
mercuryJwt,
|
|
36
|
+
mercuryEmail,
|
|
37
|
+
mercuryPassword
|
|
12
38
|
} = options
|
|
13
39
|
|
|
14
40
|
if (launchtubeUrl)
|
|
@@ -16,8 +42,139 @@ export class PasskeyBase {
|
|
|
16
42
|
|
|
17
43
|
if (launchtubeJwt)
|
|
18
44
|
this.launchtubeJwt = launchtubeJwt
|
|
45
|
+
|
|
46
|
+
if (mercuryUrl)
|
|
47
|
+
this.mercuryUrl = mercuryUrl
|
|
48
|
+
|
|
49
|
+
if (mercuryJwt)
|
|
50
|
+
this.mercuryJwt = mercuryJwt
|
|
51
|
+
|
|
52
|
+
if (mercuryEmail)
|
|
53
|
+
this.mercuryEmail = mercuryEmail
|
|
54
|
+
|
|
55
|
+
if (mercuryPassword)
|
|
56
|
+
this.mercuryPassword = mercuryPassword
|
|
57
|
+
|
|
58
|
+
this.rpcUrl = rpcUrl
|
|
59
|
+
this.rpc = new SorobanRpc.Server(rpcUrl)
|
|
60
|
+
this.networkPassphrase = networkPassphrase as Networks
|
|
61
|
+
this.factory = new FactoryClient({
|
|
62
|
+
contractId: factoryContractId,
|
|
63
|
+
networkPassphrase,
|
|
64
|
+
rpcUrl
|
|
65
|
+
})
|
|
19
66
|
}
|
|
20
67
|
|
|
68
|
+
public async getMercuryJwt() {
|
|
69
|
+
if (!this.mercuryEmail || !this.mercuryPassword)
|
|
70
|
+
throw new Error('Mercury service not configured')
|
|
71
|
+
|
|
72
|
+
const { data: { authenticate: { jwtToken } } } = await fetch(`${this.mercuryUrl}/graphql`, {
|
|
73
|
+
method: 'POST',
|
|
74
|
+
headers: {
|
|
75
|
+
'Content-Type': 'application/json',
|
|
76
|
+
},
|
|
77
|
+
body: JSON.stringify({
|
|
78
|
+
query: `mutation {
|
|
79
|
+
authenticate(input: {
|
|
80
|
+
email: "${this.mercuryEmail}"
|
|
81
|
+
password: "${this.mercuryPassword}"
|
|
82
|
+
}) {
|
|
83
|
+
jwtToken
|
|
84
|
+
}
|
|
85
|
+
}`
|
|
86
|
+
})
|
|
87
|
+
})
|
|
88
|
+
.then(async (res) => {
|
|
89
|
+
if (res.ok)
|
|
90
|
+
return res.json()
|
|
91
|
+
|
|
92
|
+
throw await res.json()
|
|
93
|
+
})
|
|
94
|
+
|
|
95
|
+
return jwtToken
|
|
96
|
+
}
|
|
97
|
+
|
|
98
|
+
public async getSigners(contractId: string = this.factory.options.contractId) {
|
|
99
|
+
if (!this.mercuryUrl || !this.mercuryJwt)
|
|
100
|
+
throw new Error('Mercury service not configured')
|
|
101
|
+
|
|
102
|
+
const signers = await fetch(`${this.mercuryUrl}/zephyr/execute`, {
|
|
103
|
+
method: 'POST',
|
|
104
|
+
headers: {
|
|
105
|
+
'Content-Type': 'application/json',
|
|
106
|
+
Authorization: `Bearer ${this.mercuryJwt}`
|
|
107
|
+
},
|
|
108
|
+
body: JSON.stringify({
|
|
109
|
+
mode: {
|
|
110
|
+
Function: {
|
|
111
|
+
fname: "get_signers_by_address",
|
|
112
|
+
arguments: JSON.stringify({
|
|
113
|
+
address: contractId
|
|
114
|
+
})
|
|
115
|
+
}
|
|
116
|
+
}
|
|
117
|
+
})
|
|
118
|
+
})
|
|
119
|
+
.then(async (res) => {
|
|
120
|
+
if (res.ok)
|
|
121
|
+
return res.json()
|
|
122
|
+
|
|
123
|
+
throw await res.json()
|
|
124
|
+
})
|
|
125
|
+
|
|
126
|
+
for (const signer of signers) {
|
|
127
|
+
if (!signer.admin) {
|
|
128
|
+
try {
|
|
129
|
+
await this.rpc.getContractData(contractId, xdr.ScVal.scvBytes(signer.id), SorobanRpc.Durability.Temporary)
|
|
130
|
+
} catch {
|
|
131
|
+
signer.expired = true
|
|
132
|
+
}
|
|
133
|
+
}
|
|
134
|
+
|
|
135
|
+
signer.id = base64url(signer.id)
|
|
136
|
+
signer.pk = base64url(signer.pk)
|
|
137
|
+
}
|
|
138
|
+
|
|
139
|
+
return signers as { id: string, pk: string, admin: boolean, expired?: boolean }[]
|
|
140
|
+
}
|
|
141
|
+
|
|
142
|
+
public async getContractId(keyId: string) {
|
|
143
|
+
if (!this.mercuryUrl || !this.mercuryJwt)
|
|
144
|
+
return
|
|
145
|
+
|
|
146
|
+
const res = await fetch(`${this.mercuryUrl}/zephyr/execute`, {
|
|
147
|
+
method: 'POST',
|
|
148
|
+
headers: {
|
|
149
|
+
'Content-Type': 'application/json',
|
|
150
|
+
Authorization: `Bearer ${this.mercuryJwt}`
|
|
151
|
+
},
|
|
152
|
+
body: JSON.stringify({
|
|
153
|
+
mode: {
|
|
154
|
+
Function: {
|
|
155
|
+
fname: "get_address_by_signer",
|
|
156
|
+
arguments: JSON.stringify({
|
|
157
|
+
id: [...base64url.toBuffer(keyId)]
|
|
158
|
+
})
|
|
159
|
+
}
|
|
160
|
+
}
|
|
161
|
+
})
|
|
162
|
+
})
|
|
163
|
+
.then(async (res) => {
|
|
164
|
+
if (res.ok)
|
|
165
|
+
return res.json()
|
|
166
|
+
|
|
167
|
+
throw await res.json()
|
|
168
|
+
})
|
|
169
|
+
|
|
170
|
+
return res[0]?.address as string | undefined
|
|
171
|
+
}
|
|
172
|
+
|
|
173
|
+
/* TODO
|
|
174
|
+
- Add a method for getting a paginated or filtered list of all a wallet's events
|
|
175
|
+
@Later
|
|
176
|
+
*/
|
|
177
|
+
|
|
21
178
|
public async send(xdr: string, fee: number = 10_000) {
|
|
22
179
|
if (!this.launchtubeUrl || !this.launchtubeJwt)
|
|
23
180
|
throw new Error('Launchtube service not configured')
|
package/src/kit.ts
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
import { Client as PasskeyClient } from 'passkey-kit-sdk'
|
|
2
|
-
|
|
3
|
-
import { Address,
|
|
2
|
+
|
|
3
|
+
import { Address, StrKey, hash, xdr, Transaction, SorobanRpc, Operation, TransactionBuilder } from '@stellar/stellar-sdk'
|
|
4
4
|
import base64url from 'base64url'
|
|
5
5
|
import { startRegistration, startAuthentication } from "@simplewebauthn/browser"
|
|
6
6
|
import type { AuthenticatorAttestationResponseJSON } from "@simplewebauthn/types"
|
|
@@ -8,44 +8,22 @@ import { decode } from 'cbor-x/decode'
|
|
|
8
8
|
import { Buffer } from 'buffer'
|
|
9
9
|
import { PasskeyBase } from './base'
|
|
10
10
|
|
|
11
|
-
type GetContractIdFunction = (keyId: string) => Promise<string>;
|
|
12
|
-
|
|
13
11
|
export class PasskeyKit extends PasskeyBase {
|
|
14
12
|
public keyId: string | undefined
|
|
15
13
|
public wallet: PasskeyClient | undefined
|
|
16
|
-
public factory: FactoryClient
|
|
17
|
-
public networkPassphrase: Networks
|
|
18
|
-
public rpcUrl: string
|
|
19
|
-
public rpc: SorobanRpc.Server
|
|
20
14
|
|
|
21
15
|
constructor(options: {
|
|
22
16
|
rpcUrl: string,
|
|
23
|
-
launchtubeUrl?: string,
|
|
24
|
-
launchtubeJwt?: string,
|
|
25
17
|
networkPassphrase: string,
|
|
26
18
|
factoryContractId: string,
|
|
19
|
+
launchtubeUrl?: string,
|
|
20
|
+
launchtubeJwt?: string,
|
|
21
|
+
mercuryUrl?: string,
|
|
22
|
+
mercuryJwt?: string,
|
|
23
|
+
mercuryEmail?: string,
|
|
24
|
+
mercuryPassword?: string
|
|
27
25
|
}) {
|
|
28
|
-
|
|
29
|
-
rpcUrl,
|
|
30
|
-
launchtubeUrl,
|
|
31
|
-
launchtubeJwt,
|
|
32
|
-
networkPassphrase,
|
|
33
|
-
factoryContractId,
|
|
34
|
-
} = options
|
|
35
|
-
|
|
36
|
-
super({
|
|
37
|
-
launchtubeUrl,
|
|
38
|
-
launchtubeJwt,
|
|
39
|
-
})
|
|
40
|
-
|
|
41
|
-
this.rpcUrl = rpcUrl
|
|
42
|
-
this.rpc = new SorobanRpc.Server(rpcUrl)
|
|
43
|
-
this.networkPassphrase = networkPassphrase as Networks
|
|
44
|
-
this.factory = new FactoryClient({
|
|
45
|
-
contractId: factoryContractId,
|
|
46
|
-
networkPassphrase,
|
|
47
|
-
rpcUrl
|
|
48
|
-
})
|
|
26
|
+
super(options)
|
|
49
27
|
}
|
|
50
28
|
|
|
51
29
|
public async createWallet(app: string, user: string) {
|
|
@@ -75,7 +53,7 @@ export class PasskeyKit extends PasskeyBase {
|
|
|
75
53
|
public async createKey(app: string, user: string) {
|
|
76
54
|
const now = new Date()
|
|
77
55
|
const displayName = `${user} — ${now.toLocaleString()}`
|
|
78
|
-
const { id, response} = await startRegistration({
|
|
56
|
+
const { id, response } = await startRegistration({
|
|
79
57
|
challenge: base64url("stellaristhebetterblockchain"),
|
|
80
58
|
rp: {
|
|
81
59
|
// id: undefined,
|
|
@@ -93,6 +71,7 @@ export class PasskeyKit extends PasskeyBase {
|
|
|
93
71
|
},
|
|
94
72
|
pubKeyCredParams: [{ alg: -7, type: "public-key" }],
|
|
95
73
|
attestation: "none",
|
|
74
|
+
timeout: 120_000
|
|
96
75
|
});
|
|
97
76
|
|
|
98
77
|
if (!this.keyId)
|
|
@@ -104,11 +83,11 @@ export class PasskeyKit extends PasskeyBase {
|
|
|
104
83
|
}
|
|
105
84
|
}
|
|
106
85
|
|
|
107
|
-
public async connectWallet(opts
|
|
86
|
+
public async connectWallet(opts?: {
|
|
108
87
|
keyId?: string | Uint8Array,
|
|
109
|
-
getContractId?:
|
|
88
|
+
getContractId?: (keyId: string) => Promise<string | undefined>
|
|
110
89
|
}) {
|
|
111
|
-
let { keyId, getContractId } = opts
|
|
90
|
+
let { keyId, getContractId = this.getContractId } = opts || {}
|
|
112
91
|
let keyIdBuffer: Buffer
|
|
113
92
|
|
|
114
93
|
if (!keyId) {
|
|
@@ -116,6 +95,7 @@ export class PasskeyKit extends PasskeyBase {
|
|
|
116
95
|
challenge: base64url("stellaristhebetterblockchain"),
|
|
117
96
|
// rpId: undefined,
|
|
118
97
|
userVerification: "discouraged",
|
|
98
|
+
timeout: 120_000
|
|
119
99
|
});
|
|
120
100
|
|
|
121
101
|
console.log(response);
|
|
@@ -151,12 +131,9 @@ export class PasskeyKit extends PasskeyBase {
|
|
|
151
131
|
// TODO what is the error if the entry exists but is archived?
|
|
152
132
|
await this.rpc.getContractData(contractId, xdr.ScVal.scvLedgerKeyContractInstance())
|
|
153
133
|
}
|
|
154
|
-
// if that fails look up from the
|
|
134
|
+
// if that fails look up from the `getContractId` function
|
|
155
135
|
catch {
|
|
156
|
-
contractId =
|
|
157
|
-
|
|
158
|
-
if (getContractId)
|
|
159
|
-
contractId = await getContractId(keyId)
|
|
136
|
+
contractId = await getContractId(keyId)
|
|
160
137
|
}
|
|
161
138
|
|
|
162
139
|
if (!contractId)
|
|
@@ -203,6 +180,7 @@ export class PasskeyKit extends PasskeyBase {
|
|
|
203
180
|
challenge: base64url(payload),
|
|
204
181
|
// rpId: undefined,
|
|
205
182
|
userVerification: "discouraged",
|
|
183
|
+
timeout: 120_000
|
|
206
184
|
}
|
|
207
185
|
: {
|
|
208
186
|
challenge: base64url(payload),
|
|
@@ -216,6 +194,7 @@ export class PasskeyKit extends PasskeyBase {
|
|
|
216
194
|
},
|
|
217
195
|
],
|
|
218
196
|
userVerification: "discouraged",
|
|
197
|
+
timeout: 120_000
|
|
219
198
|
}
|
|
220
199
|
);
|
|
221
200
|
|
package/types/base.d.ts
CHANGED
|
@@ -1,9 +1,34 @@
|
|
|
1
|
+
import { Networks, SorobanRpc } from "@stellar/stellar-sdk";
|
|
2
|
+
import { Client as FactoryClient } from 'passkey-factory-sdk';
|
|
1
3
|
export declare class PasskeyBase {
|
|
4
|
+
rpc: SorobanRpc.Server;
|
|
5
|
+
factory: FactoryClient;
|
|
6
|
+
rpcUrl: string;
|
|
7
|
+
networkPassphrase: Networks;
|
|
2
8
|
launchtubeUrl: string | undefined;
|
|
3
9
|
launchtubeJwt: string | undefined;
|
|
10
|
+
mercuryUrl: string | undefined;
|
|
11
|
+
mercuryJwt: string | undefined;
|
|
12
|
+
mercuryEmail: string | undefined;
|
|
13
|
+
mercuryPassword: string | undefined;
|
|
4
14
|
constructor(options: {
|
|
15
|
+
rpcUrl: string;
|
|
16
|
+
networkPassphrase: string;
|
|
17
|
+
factoryContractId: string;
|
|
5
18
|
launchtubeUrl?: string;
|
|
6
19
|
launchtubeJwt?: string;
|
|
20
|
+
mercuryUrl?: string;
|
|
21
|
+
mercuryJwt?: string;
|
|
22
|
+
mercuryEmail?: string;
|
|
23
|
+
mercuryPassword?: string;
|
|
7
24
|
});
|
|
25
|
+
getMercuryJwt(): Promise<any>;
|
|
26
|
+
getSigners(contractId?: string): Promise<{
|
|
27
|
+
id: string;
|
|
28
|
+
pk: string;
|
|
29
|
+
admin: boolean;
|
|
30
|
+
expired?: boolean;
|
|
31
|
+
}[]>;
|
|
32
|
+
getContractId(keyId: string): Promise<string | undefined>;
|
|
8
33
|
send(xdr: string, fee?: number): Promise<any>;
|
|
9
34
|
}
|
package/types/kit.d.ts
CHANGED
|
@@ -1,22 +1,20 @@
|
|
|
1
1
|
import { Client as PasskeyClient } from 'passkey-kit-sdk';
|
|
2
|
-
import {
|
|
3
|
-
import { Networks, xdr, Transaction, SorobanRpc } from '@stellar/stellar-sdk';
|
|
2
|
+
import { xdr, Transaction } from '@stellar/stellar-sdk';
|
|
4
3
|
import { Buffer } from 'buffer';
|
|
5
4
|
import { PasskeyBase } from './base';
|
|
6
|
-
type GetContractIdFunction = (keyId: string) => Promise<string>;
|
|
7
5
|
export declare class PasskeyKit extends PasskeyBase {
|
|
8
6
|
keyId: string | undefined;
|
|
9
7
|
wallet: PasskeyClient | undefined;
|
|
10
|
-
factory: FactoryClient;
|
|
11
|
-
networkPassphrase: Networks;
|
|
12
|
-
rpcUrl: string;
|
|
13
|
-
rpc: SorobanRpc.Server;
|
|
14
8
|
constructor(options: {
|
|
15
9
|
rpcUrl: string;
|
|
16
|
-
launchtubeUrl?: string;
|
|
17
|
-
launchtubeJwt?: string;
|
|
18
10
|
networkPassphrase: string;
|
|
19
11
|
factoryContractId: string;
|
|
12
|
+
launchtubeUrl?: string;
|
|
13
|
+
launchtubeJwt?: string;
|
|
14
|
+
mercuryUrl?: string;
|
|
15
|
+
mercuryJwt?: string;
|
|
16
|
+
mercuryEmail?: string;
|
|
17
|
+
mercuryPassword?: string;
|
|
20
18
|
});
|
|
21
19
|
createWallet(app: string, user: string): Promise<{
|
|
22
20
|
keyId: Buffer;
|
|
@@ -27,9 +25,9 @@ export declare class PasskeyKit extends PasskeyBase {
|
|
|
27
25
|
keyId: Buffer;
|
|
28
26
|
publicKey: Buffer;
|
|
29
27
|
}>;
|
|
30
|
-
connectWallet(opts
|
|
28
|
+
connectWallet(opts?: {
|
|
31
29
|
keyId?: string | Uint8Array;
|
|
32
|
-
getContractId?:
|
|
30
|
+
getContractId?: (keyId: string) => Promise<string | undefined>;
|
|
33
31
|
}): Promise<{
|
|
34
32
|
keyId: Buffer;
|
|
35
33
|
contractId: string;
|
|
@@ -49,4 +47,3 @@ export declare class PasskeyKit extends PasskeyBase {
|
|
|
49
47
|
private getPublicKey;
|
|
50
48
|
private compactSignature;
|
|
51
49
|
}
|
|
52
|
-
export {};
|