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