passkey-kit 0.2.0 → 0.2.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/cheatsheet +5 -0
- package/package.json +3 -3
- package/packages/passkey-factory-sdk/package.json +1 -1
- package/packages/passkey-factory-sdk/src/index.ts +3 -5
- package/packages/passkey-factory-sdk/types/index.d.ts +2 -3
- package/packages/passkey-kit-sdk/package.json +1 -1
- package/packages/passkey-kit-sdk/src/index.ts +25 -5
- package/packages/passkey-kit-sdk/types/index.d.ts +22 -3
- package/src/base.ts +18 -30
- package/src/kit.ts +119 -112
- package/types/base.d.ts +5 -11
- package/types/kit.d.ts +15 -10
package/cheatsheet
ADDED
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "passkey-kit",
|
|
3
|
-
"version": "0.2.
|
|
3
|
+
"version": "0.2.3",
|
|
4
4
|
"description": "A helper library for creating and using passkey accounts on the Stellar blockchain.",
|
|
5
5
|
"author": "Tyler van der Hoeven",
|
|
6
6
|
"license": "MIT",
|
|
@@ -14,8 +14,8 @@
|
|
|
14
14
|
"bigint-conversion": "^2.4.3",
|
|
15
15
|
"buffer": "^6.0.3",
|
|
16
16
|
"cbor-x": "^1.5.9",
|
|
17
|
-
"passkey-factory-sdk": "0.0.
|
|
18
|
-
"passkey-kit-sdk": "0.0.
|
|
17
|
+
"passkey-factory-sdk": "0.0.8",
|
|
18
|
+
"passkey-kit-sdk": "0.0.8"
|
|
19
19
|
},
|
|
20
20
|
"devDependencies": {
|
|
21
21
|
"typescript": "^5.4.5"
|
|
@@ -1,12 +1,10 @@
|
|
|
1
1
|
import { Buffer } from "buffer";
|
|
2
2
|
import {
|
|
3
|
-
Client as ContractClient,
|
|
4
|
-
Spec as ContractSpec,
|
|
5
|
-
} from '@stellar/stellar-sdk/contract';
|
|
6
|
-
import type {
|
|
7
3
|
AssembledTransaction,
|
|
4
|
+
Client as ContractClient,
|
|
8
5
|
ClientOptions as ContractClientOptions,
|
|
9
6
|
Result,
|
|
7
|
+
Spec as ContractSpec,
|
|
10
8
|
} from '@stellar/stellar-sdk/contract';
|
|
11
9
|
|
|
12
10
|
if (typeof window !== 'undefined') {
|
|
@@ -17,7 +15,7 @@ if (typeof window !== 'undefined') {
|
|
|
17
15
|
export const networks = {
|
|
18
16
|
testnet: {
|
|
19
17
|
networkPassphrase: "Test SDF Network ; September 2015",
|
|
20
|
-
contractId: "
|
|
18
|
+
contractId: "CBXQAM53YVVPKAPS2NCSOCXTOK6HOXA3JLURVLD6VFQUHJPKFVBJIBHQ",
|
|
21
19
|
}
|
|
22
20
|
} as const
|
|
23
21
|
|
|
@@ -1,10 +1,9 @@
|
|
|
1
1
|
import { Buffer } from "buffer";
|
|
2
|
-
import { Client as ContractClient } from '@stellar/stellar-sdk/contract';
|
|
3
|
-
import type { AssembledTransaction, ClientOptions as ContractClientOptions, Result } from '@stellar/stellar-sdk/contract';
|
|
2
|
+
import { AssembledTransaction, Client as ContractClient, ClientOptions as ContractClientOptions, Result } from '@stellar/stellar-sdk/contract';
|
|
4
3
|
export declare const networks: {
|
|
5
4
|
readonly testnet: {
|
|
6
5
|
readonly networkPassphrase: "Test SDF Network ; September 2015";
|
|
7
|
-
readonly contractId: "
|
|
6
|
+
readonly contractId: "CBXQAM53YVVPKAPS2NCSOCXTOK6HOXA3JLURVLD6VFQUHJPKFVBJIBHQ";
|
|
8
7
|
};
|
|
9
8
|
};
|
|
10
9
|
export declare const Errors: {
|
|
@@ -1,12 +1,10 @@
|
|
|
1
1
|
import { Buffer } from "buffer";
|
|
2
2
|
import {
|
|
3
|
-
Client as ContractClient,
|
|
4
|
-
Spec as ContractSpec,
|
|
5
|
-
} from '@stellar/stellar-sdk/contract';
|
|
6
|
-
import type {
|
|
7
3
|
AssembledTransaction,
|
|
4
|
+
Client as ContractClient,
|
|
8
5
|
ClientOptions as ContractClientOptions,
|
|
9
6
|
Result,
|
|
7
|
+
Spec as ContractSpec,
|
|
10
8
|
} from '@stellar/stellar-sdk/contract';
|
|
11
9
|
|
|
12
10
|
if (typeof window !== 'undefined') {
|
|
@@ -17,7 +15,7 @@ if (typeof window !== 'undefined') {
|
|
|
17
15
|
export const networks = {
|
|
18
16
|
testnet: {
|
|
19
17
|
networkPassphrase: "Test SDF Network ; September 2015",
|
|
20
|
-
contractId: "
|
|
18
|
+
contractId: "CBXQAM53YVVPKAPS2NCSOCXTOK6HOXA3JLURVLD6VFQUHJPKFVBJIBHQ",
|
|
21
19
|
}
|
|
22
20
|
} as const
|
|
23
21
|
|
|
@@ -82,6 +80,26 @@ export interface Client {
|
|
|
82
80
|
simulate?: boolean;
|
|
83
81
|
}) => Promise<AssembledTransaction<Result<void>>>
|
|
84
82
|
|
|
83
|
+
/**
|
|
84
|
+
* Construct and simulate a upgrade transaction. Returns an `AssembledTransaction` object which will have a `result` field containing the result of the simulation. If this transaction changes contract state, you will need to call `signAndSend()` on the returned object.
|
|
85
|
+
*/
|
|
86
|
+
upgrade: ({ hash }: { hash: Buffer }, options?: {
|
|
87
|
+
/**
|
|
88
|
+
* The fee to pay for the transaction. Default: BASE_FEE
|
|
89
|
+
*/
|
|
90
|
+
fee?: number;
|
|
91
|
+
|
|
92
|
+
/**
|
|
93
|
+
* The maximum amount of time to wait for the transaction to complete. Default: DEFAULT_TIMEOUT
|
|
94
|
+
*/
|
|
95
|
+
timeoutInSeconds?: number;
|
|
96
|
+
|
|
97
|
+
/**
|
|
98
|
+
* Whether to automatically simulate the transaction when constructing the AssembledTransaction. Default: true
|
|
99
|
+
*/
|
|
100
|
+
simulate?: boolean;
|
|
101
|
+
}) => Promise<AssembledTransaction<Result<void>>>
|
|
102
|
+
|
|
85
103
|
/**
|
|
86
104
|
* Construct and simulate a resudo transaction. Returns an `AssembledTransaction` object which will have a `result` field containing the result of the simulation. If this transaction changes contract state, you will need to call `signAndSend()` on the returned object.
|
|
87
105
|
*/
|
|
@@ -148,6 +166,7 @@ export class Client extends ContractClient {
|
|
|
148
166
|
new ContractSpec(["AAAABAAAAAAAAAAAAAAABUVycm9yAAAAAAAACgAAAAAAAAAJTm90SW5pdGVkAAAAAAAAAQAAAAAAAAAITm90Rm91bmQAAAACAAAAAAAAAAxOb3RQZXJtaXR0ZWQAAAADAAAAAAAAAA1BbHJlYWR5SW5pdGVkAAAAAAAABAAAAAAAAAAOSnNvblBhcnNlRXJyb3IAAAAAAAUAAAAAAAAADkludmFsaWRDb250ZXh0AAAAAAAGAAAAAAAAACBDbGllbnREYXRhSnNvbkNoYWxsZW5nZUluY29ycmVjdAAAAAcAAAAAAAAAF1NlY3AyNTZyMVB1YmxpY0tleVBhcnNlAAAAAAgAAAAAAAAAF1NlY3AyNTZyMVNpZ25hdHVyZVBhcnNlAAAAAAkAAAAAAAAAFVNlY3AyNTZyMVZlcmlmeUZhaWxlZAAAAAAAAAo=",
|
|
149
167
|
"AAAAAAAAAAAAAAAKZXh0ZW5kX3R0bAAAAAAAAAAAAAA=",
|
|
150
168
|
"AAAAAAAAAAAAAAAEaW5pdAAAAAMAAAAAAAAAAmlkAAAAAAAOAAAAAAAAAAJwawAAAAAD7gAAAEEAAAAAAAAAB2ZhY3RvcnkAAAAAEwAAAAEAAAPpAAAD7QAAAAAAAAAD",
|
|
169
|
+
"AAAAAAAAAAAAAAAHdXBncmFkZQAAAAABAAAAAAAAAARoYXNoAAAD7gAAACAAAAABAAAD6QAAA+0AAAAAAAAAAw==",
|
|
151
170
|
"AAAAAAAAAAAAAAAGcmVzdWRvAAAAAAABAAAAAAAAAAJpZAAAAAAADgAAAAEAAAPpAAAD7QAAAAAAAAAD",
|
|
152
171
|
"AAAAAAAAAAAAAAAGcm1fc2lnAAAAAAABAAAAAAAAAAJpZAAAAAAADgAAAAEAAAPpAAAD7QAAAAAAAAAD",
|
|
153
172
|
"AAAAAAAAAAAAAAAHYWRkX3NpZwAAAAACAAAAAAAAAAJpZAAAAAAADgAAAAAAAAACcGsAAAAAA+4AAABBAAAAAQAAA+kAAAPtAAAAAAAAAAM=",
|
|
@@ -159,6 +178,7 @@ export class Client extends ContractClient {
|
|
|
159
178
|
public readonly fromJSON = {
|
|
160
179
|
extend_ttl: this.txFromJSON<null>,
|
|
161
180
|
init: this.txFromJSON<Result<void>>,
|
|
181
|
+
upgrade: this.txFromJSON<Result<void>>,
|
|
162
182
|
resudo: this.txFromJSON<Result<void>>,
|
|
163
183
|
rm_sig: this.txFromJSON<Result<void>>,
|
|
164
184
|
add_sig: this.txFromJSON<Result<void>>,
|
|
@@ -1,10 +1,9 @@
|
|
|
1
1
|
import { Buffer } from "buffer";
|
|
2
|
-
import { Client as ContractClient } from '@stellar/stellar-sdk/contract';
|
|
3
|
-
import type { AssembledTransaction, ClientOptions as ContractClientOptions, Result } from '@stellar/stellar-sdk/contract';
|
|
2
|
+
import { AssembledTransaction, Client as ContractClient, ClientOptions as ContractClientOptions, Result } from '@stellar/stellar-sdk/contract';
|
|
4
3
|
export declare const networks: {
|
|
5
4
|
readonly testnet: {
|
|
6
5
|
readonly networkPassphrase: "Test SDF Network ; September 2015";
|
|
7
|
-
readonly contractId: "
|
|
6
|
+
readonly contractId: "CBXQAM53YVVPKAPS2NCSOCXTOK6HOXA3JLURVLD6VFQUHJPKFVBJIBHQ";
|
|
8
7
|
};
|
|
9
8
|
};
|
|
10
9
|
export declare const Errors: {
|
|
@@ -84,6 +83,25 @@ export interface Client {
|
|
|
84
83
|
*/
|
|
85
84
|
simulate?: boolean;
|
|
86
85
|
}) => Promise<AssembledTransaction<Result<void>>>;
|
|
86
|
+
/**
|
|
87
|
+
* Construct and simulate a upgrade transaction. Returns an `AssembledTransaction` object which will have a `result` field containing the result of the simulation. If this transaction changes contract state, you will need to call `signAndSend()` on the returned object.
|
|
88
|
+
*/
|
|
89
|
+
upgrade: ({ hash }: {
|
|
90
|
+
hash: Buffer;
|
|
91
|
+
}, options?: {
|
|
92
|
+
/**
|
|
93
|
+
* The fee to pay for the transaction. Default: BASE_FEE
|
|
94
|
+
*/
|
|
95
|
+
fee?: number;
|
|
96
|
+
/**
|
|
97
|
+
* The maximum amount of time to wait for the transaction to complete. Default: DEFAULT_TIMEOUT
|
|
98
|
+
*/
|
|
99
|
+
timeoutInSeconds?: number;
|
|
100
|
+
/**
|
|
101
|
+
* Whether to automatically simulate the transaction when constructing the AssembledTransaction. Default: true
|
|
102
|
+
*/
|
|
103
|
+
simulate?: boolean;
|
|
104
|
+
}) => Promise<AssembledTransaction<Result<void>>>;
|
|
87
105
|
/**
|
|
88
106
|
* Construct and simulate a resudo transaction. Returns an `AssembledTransaction` object which will have a `result` field containing the result of the simulation. If this transaction changes contract state, you will need to call `signAndSend()` on the returned object.
|
|
89
107
|
*/
|
|
@@ -149,6 +167,7 @@ export declare class Client extends ContractClient {
|
|
|
149
167
|
readonly fromJSON: {
|
|
150
168
|
extend_ttl: (json: string) => AssembledTransaction<null>;
|
|
151
169
|
init: (json: string) => AssembledTransaction<Result<void, import("@stellar/stellar-sdk/contract").ErrorMessage>>;
|
|
170
|
+
upgrade: (json: string) => AssembledTransaction<Result<void, import("@stellar/stellar-sdk/contract").ErrorMessage>>;
|
|
152
171
|
resudo: (json: string) => AssembledTransaction<Result<void, import("@stellar/stellar-sdk/contract").ErrorMessage>>;
|
|
153
172
|
rm_sig: (json: string) => AssembledTransaction<Result<void, import("@stellar/stellar-sdk/contract").ErrorMessage>>;
|
|
154
173
|
add_sig: (json: string) => AssembledTransaction<Result<void, import("@stellar/stellar-sdk/contract").ErrorMessage>>;
|
package/src/base.ts
CHANGED
|
@@ -1,54 +1,42 @@
|
|
|
1
|
-
import { Networks, Transaction, Horizon, FeeBumpTransaction } from '@stellar/stellar-sdk'
|
|
2
|
-
|
|
3
1
|
export class PasskeyBase {
|
|
4
|
-
public
|
|
5
|
-
public
|
|
6
|
-
public horizon: Horizon.Server
|
|
7
|
-
public feeBumpUrl: string | undefined
|
|
8
|
-
public feeBumpJwt: string | undefined
|
|
2
|
+
public launchtubeUrl: string | undefined
|
|
3
|
+
public launchtubeJwt: string | undefined
|
|
9
4
|
|
|
10
5
|
constructor(options: {
|
|
11
|
-
|
|
12
|
-
|
|
13
|
-
feeBumpUrl?: string,
|
|
14
|
-
feeBumpJwt?: string,
|
|
6
|
+
launchtubeUrl?: string,
|
|
7
|
+
launchtubeJwt?: string,
|
|
15
8
|
}) {
|
|
16
9
|
const {
|
|
17
|
-
|
|
18
|
-
|
|
19
|
-
feeBumpUrl,
|
|
20
|
-
feeBumpJwt,
|
|
10
|
+
launchtubeUrl,
|
|
11
|
+
launchtubeJwt,
|
|
21
12
|
} = options
|
|
22
13
|
|
|
23
|
-
|
|
24
|
-
|
|
25
|
-
this.horizon = new Horizon.Server(horizonUrl)
|
|
26
|
-
|
|
27
|
-
if (feeBumpUrl)
|
|
28
|
-
this.feeBumpUrl = feeBumpUrl
|
|
14
|
+
if (launchtubeUrl)
|
|
15
|
+
this.launchtubeUrl = launchtubeUrl
|
|
29
16
|
|
|
30
|
-
if (
|
|
31
|
-
this.
|
|
17
|
+
if (launchtubeJwt)
|
|
18
|
+
this.launchtubeJwt = launchtubeJwt
|
|
32
19
|
}
|
|
33
20
|
|
|
34
|
-
public async send(
|
|
21
|
+
public async send(xdr: string, fee: number = 10_000) {
|
|
22
|
+
if (!this.launchtubeUrl || !this.launchtubeJwt)
|
|
23
|
+
throw new Error('Launchtube service not configured')
|
|
24
|
+
|
|
35
25
|
const data = new FormData();
|
|
36
26
|
|
|
37
|
-
data.set('xdr',
|
|
27
|
+
data.set('xdr', xdr);
|
|
38
28
|
data.set('fee', fee.toString());
|
|
39
29
|
|
|
40
|
-
|
|
30
|
+
return fetch(this.launchtubeUrl, {
|
|
41
31
|
method: 'POST',
|
|
42
32
|
headers: {
|
|
43
|
-
authorization: `Bearer ${this.
|
|
33
|
+
authorization: `Bearer ${this.launchtubeJwt}`,
|
|
44
34
|
},
|
|
45
35
|
body: data
|
|
46
36
|
}).then(async (res) => {
|
|
47
37
|
if (res.ok)
|
|
48
|
-
return res.
|
|
38
|
+
return res.json()
|
|
49
39
|
else throw await res.json()
|
|
50
40
|
})
|
|
51
|
-
|
|
52
|
-
return this.horizon.submitTransaction(new FeeBumpTransaction(bumptxn, this.networkPassphrase))
|
|
53
41
|
}
|
|
54
42
|
}
|
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, networks } from 'passkey-factory-sdk'
|
|
3
|
-
import { Address, Networks, StrKey, hash, xdr, Transaction,
|
|
3
|
+
import { Address, Networks, StrKey, hash, xdr, Transaction, SorobanRpc, Operation, scValToNative, TransactionBuilder, authorizeEntry } from '@stellar/stellar-sdk'
|
|
4
4
|
import { bufToBigint, bigintToBuf } from 'bigint-conversion'
|
|
5
5
|
import base64url from 'base64url'
|
|
6
6
|
import { startRegistration, startAuthentication } from "@simplewebauthn/browser"
|
|
@@ -12,12 +12,7 @@ import { PasskeyBase } from './base'
|
|
|
12
12
|
- Clean up these params and the interface as a whole
|
|
13
13
|
Might put wallet activities and maybe factory as well into the root of the class vs buried inside this.wallet and this.factory
|
|
14
14
|
@Later
|
|
15
|
-
|
|
16
15
|
- Right now publicKey can mean a Stellar public key or a passkey public key, there should be a noted difference
|
|
17
|
-
|
|
18
|
-
- It's finally time to make a server and a client version of this package
|
|
19
|
-
Maybe. I want to support passing "bad" secret values on the client.
|
|
20
|
-
It's maybe only the server that could be smaller and cleaner by opting out out WebAuthN stuff
|
|
21
16
|
*/
|
|
22
17
|
|
|
23
18
|
export class PasskeyKit extends PasskeyBase {
|
|
@@ -25,65 +20,46 @@ export class PasskeyKit extends PasskeyBase {
|
|
|
25
20
|
public sudoKeyId: string | undefined
|
|
26
21
|
public wallet: PasskeyClient | undefined
|
|
27
22
|
public factory: FactoryClient
|
|
28
|
-
public
|
|
29
|
-
// public networkPassphrase: Networks
|
|
30
|
-
// public horizonUrl: string
|
|
31
|
-
public horizon: Horizon.Server
|
|
23
|
+
public networkPassphrase: Networks
|
|
32
24
|
public rpcUrl: string
|
|
33
25
|
public rpc: SorobanRpc.Server
|
|
34
|
-
// public feeBumpUrl: string | undefined
|
|
35
|
-
// public feeBumpJwt: string | undefined
|
|
36
26
|
public factoryContractId: string = networks.testnet.contractId
|
|
37
27
|
|
|
38
|
-
/*
|
|
28
|
+
/* NOTE
|
|
39
29
|
- Consider adding the ability to pass in a keyId and maybe even a contractId in order to preconnect to a wallet
|
|
40
30
|
If just a keyId call `connectWallet` in order to get the contractId
|
|
41
31
|
If both keyId and contractId are passed in then we can skip the connectWallet call (though we won't get the sudoKeyId in that case)
|
|
42
|
-
We don't
|
|
43
|
-
|
|
32
|
+
We don't strictly _need_ this as a dev can just call `connectWallet` after class instantiation but it might be a nice convenience
|
|
33
|
+
`connectWallet` is async making it tricky to know when the wallet is "ready". Thus I think it's better for `connectWallet` to be called externally
|
|
34
|
+
@No
|
|
44
35
|
*/
|
|
45
36
|
constructor(options: {
|
|
46
|
-
sequencePublicKey: string,
|
|
47
|
-
networkPassphrase: Networks,
|
|
48
|
-
horizonUrl: string,
|
|
49
37
|
rpcUrl: string,
|
|
50
|
-
|
|
51
|
-
|
|
52
|
-
|
|
53
|
-
|
|
54
|
-
On the other hand once we have a standard interface the factory interface only uses the `deploy` method right now inside the interface
|
|
55
|
-
@Later
|
|
56
|
-
*/
|
|
57
|
-
factoryContractId?: string,
|
|
38
|
+
launchtubeUrl?: string,
|
|
39
|
+
launchtubeJwt?: string,
|
|
40
|
+
factoryContractId?: string,
|
|
41
|
+
networkPassphrase: Networks,
|
|
58
42
|
}) {
|
|
59
43
|
const {
|
|
60
|
-
sequencePublicKey,
|
|
61
|
-
networkPassphrase,
|
|
62
|
-
horizonUrl,
|
|
63
44
|
rpcUrl,
|
|
64
|
-
|
|
65
|
-
|
|
66
|
-
factoryContractId
|
|
45
|
+
launchtubeUrl,
|
|
46
|
+
launchtubeJwt,
|
|
47
|
+
factoryContractId,
|
|
48
|
+
networkPassphrase,
|
|
67
49
|
} = options
|
|
68
50
|
|
|
69
51
|
super({
|
|
70
|
-
|
|
71
|
-
|
|
72
|
-
feeBumpUrl,
|
|
73
|
-
feeBumpJwt,
|
|
52
|
+
launchtubeUrl,
|
|
53
|
+
launchtubeJwt,
|
|
74
54
|
})
|
|
75
55
|
|
|
76
|
-
this.sequencePublicKey = sequencePublicKey
|
|
77
|
-
|
|
78
|
-
this.horizon = new Horizon.Server(horizonUrl)
|
|
79
|
-
this.rpcUrl = rpcUrl
|
|
80
|
-
this.rpc = new SorobanRpc.Server(rpcUrl)
|
|
81
|
-
|
|
82
56
|
if (factoryContractId)
|
|
83
57
|
this.factoryContractId = factoryContractId
|
|
84
58
|
|
|
59
|
+
this.rpcUrl = rpcUrl
|
|
60
|
+
this.rpc = new SorobanRpc.Server(rpcUrl)
|
|
61
|
+
this.networkPassphrase = networkPassphrase
|
|
85
62
|
this.factory = new FactoryClient({
|
|
86
|
-
publicKey: sequencePublicKey,
|
|
87
63
|
contractId: this.factoryContractId,
|
|
88
64
|
networkPassphrase,
|
|
89
65
|
rpcUrl
|
|
@@ -101,7 +77,6 @@ export class PasskeyKit extends PasskeyBase {
|
|
|
101
77
|
const contractId = result.unwrap() as string
|
|
102
78
|
|
|
103
79
|
this.wallet = new PasskeyClient({
|
|
104
|
-
publicKey: this.sequencePublicKey,
|
|
105
80
|
contractId,
|
|
106
81
|
networkPassphrase: this.networkPassphrase,
|
|
107
82
|
rpcUrl: this.rpcUrl
|
|
@@ -157,34 +132,21 @@ export class PasskeyKit extends PasskeyBase {
|
|
|
157
132
|
}
|
|
158
133
|
}
|
|
159
134
|
|
|
160
|
-
public async connectWallet(
|
|
161
|
-
|
|
162
|
-
|
|
163
|
-
Maybe not as we wouldn't have a keyId which could have interesting consequences
|
|
164
|
-
Also not sure what the use case would be for this where keyId wouldn't also be possible and better
|
|
165
|
-
@No
|
|
166
|
-
*/
|
|
167
|
-
|
|
168
|
-
// @ts-ignore
|
|
169
|
-
// https://github.com/stellar/js-stellar-base/issues/750
|
|
170
|
-
// if (id && StrKey.isValidContract(id)) {
|
|
171
|
-
|
|
172
|
-
// } else {
|
|
173
|
-
|
|
174
|
-
// }
|
|
175
|
-
|
|
176
|
-
const startAuthenticationResponse = id
|
|
177
|
-
? { id }
|
|
178
|
-
: await startAuthentication({
|
|
135
|
+
public async connectWallet(keyId?: string) {
|
|
136
|
+
if (!keyId) {
|
|
137
|
+
const { id } = await startAuthentication({
|
|
179
138
|
challenge: base64url("stellaristhebetterblockchain"),
|
|
180
139
|
// rpId: undefined,
|
|
181
140
|
userVerification: "discouraged",
|
|
182
141
|
});
|
|
183
142
|
|
|
143
|
+
keyId = id
|
|
144
|
+
}
|
|
145
|
+
|
|
184
146
|
if (!this.keyId)
|
|
185
|
-
this.keyId =
|
|
147
|
+
this.keyId = keyId
|
|
186
148
|
|
|
187
|
-
const keyIdBuffer = base64url.toBuffer(
|
|
149
|
+
const keyIdBuffer = base64url.toBuffer(keyId)
|
|
188
150
|
|
|
189
151
|
// NOTE might not need this for derivation as all signers are stored in the factory and we can use that lookup as both primary and secondary
|
|
190
152
|
let contractId = StrKey.encodeContract(hash(xdr.HashIdPreimage.envelopeTypeContractId(
|
|
@@ -199,25 +161,26 @@ export class PasskeyKit extends PasskeyBase {
|
|
|
199
161
|
})
|
|
200
162
|
).toXDR()));
|
|
201
163
|
|
|
164
|
+
let contractData: SorobanRpc.Api.LedgerEntryResult | undefined
|
|
165
|
+
|
|
202
166
|
// attempt passkey id derivation
|
|
203
167
|
try {
|
|
204
|
-
await this.rpc.getContractData(contractId, xdr.ScVal.scvLedgerKeyContractInstance())
|
|
168
|
+
contractData = await this.rpc.getContractData(contractId, xdr.ScVal.scvLedgerKeyContractInstance())
|
|
205
169
|
}
|
|
206
170
|
// if that fails look up from the factory mapper
|
|
207
171
|
catch {
|
|
208
|
-
|
|
209
|
-
contractId = scValToNative(val.contractData().val())
|
|
172
|
+
contractData = await this.rpc.getContractData(this.factoryContractId, xdr.ScVal.scvBytes(keyIdBuffer))
|
|
173
|
+
contractId = scValToNative(contractData.val.contractData().val())
|
|
210
174
|
}
|
|
211
175
|
|
|
212
176
|
this.wallet = new PasskeyClient({
|
|
213
|
-
publicKey: this.sequencePublicKey,
|
|
214
177
|
contractId,
|
|
215
178
|
networkPassphrase: this.networkPassphrase,
|
|
216
179
|
rpcUrl: this.rpcUrl
|
|
217
180
|
})
|
|
218
181
|
|
|
219
|
-
// get and set the sudo signer
|
|
220
|
-
await this.getData()
|
|
182
|
+
// get and set the sudo signer (passing in `contractData` from the `getContractData` call above to avoid dupe requests)
|
|
183
|
+
await this.getData(contractData)
|
|
221
184
|
|
|
222
185
|
return {
|
|
223
186
|
keyId: keyIdBuffer,
|
|
@@ -225,9 +188,8 @@ export class PasskeyKit extends PasskeyBase {
|
|
|
225
188
|
}
|
|
226
189
|
}
|
|
227
190
|
|
|
228
|
-
|
|
229
|
-
|
|
230
|
-
txn: Transaction | string,
|
|
191
|
+
public async signAuthEntry(
|
|
192
|
+
entry: xdr.SorobanAuthorizationEntry,
|
|
231
193
|
options?: {
|
|
232
194
|
keyId?: 'any' | 'sudo' | string | Uint8Array
|
|
233
195
|
ledgersToLive?: number
|
|
@@ -236,39 +198,29 @@ export class PasskeyKit extends PasskeyBase {
|
|
|
236
198
|
// Default mirrors DEFAULT_TIMEOUT (currently 5 minutes) https://github.com/stellar/js-stellar-sdk/blob/master/src/contract/utils.ts#L7
|
|
237
199
|
let { keyId, ledgersToLive = 60 } = options || {}
|
|
238
200
|
|
|
239
|
-
// hack to ensure we don't stack fees when simulating and assembling multiple times
|
|
240
|
-
txn = TransactionBuilder.cloneFrom(new Transaction(
|
|
241
|
-
typeof txn === 'string'
|
|
242
|
-
? txn
|
|
243
|
-
: txn.toXDR(),
|
|
244
|
-
this.networkPassphrase
|
|
245
|
-
), { fee: '0' }).build()
|
|
246
|
-
|
|
247
|
-
// NOTE hard coded to sign only Soroban transactions and only and always the first auth
|
|
248
|
-
// TODO switch to support signing multiple and specific auth entries. Possibly pass in auth entry vs the entire txn
|
|
249
|
-
const op = txn.operations[0] as Operation.InvokeHostFunction
|
|
250
|
-
const auth = op.auth![0]
|
|
251
201
|
const lastLedger = await this.rpc.getLatestLedger().then(({ sequence }) => sequence)
|
|
252
|
-
const
|
|
253
|
-
|
|
254
|
-
|
|
255
|
-
|
|
256
|
-
|
|
257
|
-
|
|
258
|
-
|
|
259
|
-
|
|
260
|
-
).toXDR()
|
|
202
|
+
const credentials = entry.credentials().address();
|
|
203
|
+
const preimage = xdr.HashIdPreimage.envelopeTypeSorobanAuthorization(
|
|
204
|
+
new xdr.HashIdPreimageSorobanAuthorization({
|
|
205
|
+
networkId: hash(Buffer.from(this.networkPassphrase)),
|
|
206
|
+
nonce: credentials.nonce(),
|
|
207
|
+
signatureExpirationLedger: lastLedger + ledgersToLive,
|
|
208
|
+
invocation: entry.rootInvocation()
|
|
209
|
+
})
|
|
261
210
|
)
|
|
211
|
+
const payload = hash(preimage.toXDR())
|
|
262
212
|
|
|
263
213
|
const authenticationResponse = await startAuthentication(
|
|
264
|
-
keyId === 'any'
|
|
214
|
+
keyId === 'any'
|
|
215
|
+
|| (keyId === 'sudo' && !this.sudoKeyId)
|
|
216
|
+
|| (!keyId && !this.keyId)
|
|
265
217
|
? {
|
|
266
|
-
challenge: base64url(
|
|
218
|
+
challenge: base64url(payload),
|
|
267
219
|
// rpId: undefined,
|
|
268
220
|
userVerification: "discouraged",
|
|
269
221
|
}
|
|
270
222
|
: {
|
|
271
|
-
challenge: base64url(
|
|
223
|
+
challenge: base64url(payload),
|
|
272
224
|
// rpId: undefined,
|
|
273
225
|
allowCredentials: [
|
|
274
226
|
{
|
|
@@ -284,19 +236,11 @@ export class PasskeyKit extends PasskeyBase {
|
|
|
284
236
|
}
|
|
285
237
|
);
|
|
286
238
|
|
|
287
|
-
// set sudo if this is a sudo request
|
|
288
|
-
if (keyId === 'sudo')
|
|
289
|
-
this.sudoKeyId = authenticationResponse.id
|
|
290
|
-
|
|
291
|
-
// reset this.keyId to be the most recently used passkey
|
|
292
|
-
this.keyId = authenticationResponse.id
|
|
293
|
-
|
|
294
239
|
const signatureRaw = base64url.toBuffer(authenticationResponse.response.signature);
|
|
295
240
|
const signature = this.convertEcdsaSignatureAsnToCompact(signatureRaw);
|
|
296
|
-
|
|
297
|
-
|
|
298
|
-
|
|
299
|
-
creds.signature(xdr.ScVal.scvMap([
|
|
241
|
+
|
|
242
|
+
credentials.signatureExpirationLedger(lastLedger + ledgersToLive)
|
|
243
|
+
credentials.signature(xdr.ScVal.scvMap([
|
|
300
244
|
new xdr.ScMapEntry({
|
|
301
245
|
key: xdr.ScVal.scvSymbol('authenticator_data'),
|
|
302
246
|
val: xdr.ScVal.scvBytes(base64url.toBuffer(authenticationResponse.response.authenticatorData)),
|
|
@@ -314,21 +258,84 @@ export class PasskeyKit extends PasskeyBase {
|
|
|
314
258
|
val: xdr.ScVal.scvBytes(signature),
|
|
315
259
|
}),
|
|
316
260
|
]))
|
|
261
|
+
|
|
262
|
+
return entry
|
|
263
|
+
}
|
|
264
|
+
public async signAuthEntries(
|
|
265
|
+
entries: xdr.SorobanAuthorizationEntry[],
|
|
266
|
+
options?: {
|
|
267
|
+
keyId?: 'any' | 'sudo' | string | Uint8Array
|
|
268
|
+
ledgersToLive?: number
|
|
269
|
+
}
|
|
270
|
+
) {
|
|
271
|
+
for (const auth of entries) {
|
|
272
|
+
if (
|
|
273
|
+
auth.credentials().switch().name === 'sorobanCredentialsAddress'
|
|
274
|
+
&& auth.credentials().address().address().switch().name === 'scAddressTypeContract'
|
|
275
|
+
) {
|
|
276
|
+
// If auth entry matches our Smart Wallet move forward with the signature request
|
|
277
|
+
if (Address.contract(auth.credentials().address().address().contractId()).toString() === this.wallet!.options.contractId)
|
|
278
|
+
await this.signAuthEntry(auth, options)
|
|
279
|
+
}
|
|
280
|
+
}
|
|
281
|
+
|
|
282
|
+
return entries
|
|
283
|
+
}
|
|
284
|
+
/* NOTE
|
|
285
|
+
- Is there anything to be done with using the TS bindings `signAuthEntries` logic? Likely not but worth exploring
|
|
286
|
+
Perhaps not but maybe the js-sdk's `authorizeEntry` or `authorizeInvocation`
|
|
287
|
+
https://stellar.github.io/js-stellar-sdk/global.html#authorizeInvocation
|
|
288
|
+
These methods all only support ed25519 Keypairs
|
|
289
|
+
@No
|
|
290
|
+
*/
|
|
291
|
+
public async sign(
|
|
292
|
+
txn: Transaction | string,
|
|
293
|
+
options?: {
|
|
294
|
+
keyId?: 'any' | 'sudo' | string | Uint8Array
|
|
295
|
+
ledgersToLive?: number
|
|
296
|
+
}
|
|
297
|
+
) {
|
|
298
|
+
// Hack to ensure we don't stack fees when simulating and assembling multiple times
|
|
299
|
+
txn = TransactionBuilder.cloneFrom(new Transaction(
|
|
300
|
+
typeof txn === 'string'
|
|
301
|
+
? txn
|
|
302
|
+
: txn.toXDR(),
|
|
303
|
+
this.networkPassphrase
|
|
304
|
+
), { fee: '0' }).build()
|
|
305
|
+
|
|
306
|
+
if (txn.operations.length !== 1)
|
|
307
|
+
throw new Error('Must include only one Soroban operation')
|
|
308
|
+
|
|
309
|
+
for (const op of txn.operations) {
|
|
310
|
+
if (
|
|
311
|
+
op.type !== 'invokeHostFunction'
|
|
312
|
+
&& op.type !== 'extendFootprintTtl'
|
|
313
|
+
&& op.type !== 'restoreFootprint'
|
|
314
|
+
) throw new Error('Must include only one operation of type `invokeHostFunction` or `extendFootprintTtl` or `restoreFootprint`')
|
|
315
|
+
}
|
|
316
|
+
|
|
317
|
+
// Only need to sign auth for `invokeHostFunction` operations
|
|
318
|
+
if (txn.operations[0].type === 'invokeHostFunction') {
|
|
319
|
+
const entries = (txn.operations[0] as Operation.InvokeHostFunction).auth
|
|
320
|
+
|
|
321
|
+
if (entries)
|
|
322
|
+
await this.signAuthEntries(entries, options)
|
|
323
|
+
}
|
|
317
324
|
|
|
318
325
|
const sim = await this.rpc.simulateTransaction(txn)
|
|
319
326
|
|
|
320
327
|
if (
|
|
321
328
|
SorobanRpc.Api.isSimulationError(sim)
|
|
322
|
-
|| SorobanRpc.Api.isSimulationRestore(sim) // TODO handle
|
|
329
|
+
|| SorobanRpc.Api.isSimulationRestore(sim) // TODO handle state archival
|
|
323
330
|
) throw sim
|
|
324
331
|
|
|
325
332
|
return SorobanRpc.assembleTransaction(txn, sim).build().toXDR()
|
|
326
333
|
}
|
|
327
334
|
|
|
328
|
-
public async getData() {
|
|
335
|
+
public async getData(contractData?: SorobanRpc.Api.LedgerEntryResult) {
|
|
329
336
|
const data: Map<string, any> = new Map()
|
|
330
337
|
|
|
331
|
-
const { val } = await this.rpc.getContractData(
|
|
338
|
+
const { val } = contractData || await this.rpc.getContractData(
|
|
332
339
|
this.wallet!.options.contractId,
|
|
333
340
|
xdr.ScVal.scvLedgerKeyContractInstance(),
|
|
334
341
|
);
|
package/types/base.d.ts
CHANGED
|
@@ -1,15 +1,9 @@
|
|
|
1
|
-
import { Networks, Transaction, Horizon } from '@stellar/stellar-sdk';
|
|
2
1
|
export declare class PasskeyBase {
|
|
3
|
-
|
|
4
|
-
|
|
5
|
-
horizon: Horizon.Server;
|
|
6
|
-
feeBumpUrl: string | undefined;
|
|
7
|
-
feeBumpJwt: string | undefined;
|
|
2
|
+
launchtubeUrl: string | undefined;
|
|
3
|
+
launchtubeJwt: string | undefined;
|
|
8
4
|
constructor(options: {
|
|
9
|
-
|
|
10
|
-
|
|
11
|
-
feeBumpUrl?: string;
|
|
12
|
-
feeBumpJwt?: string;
|
|
5
|
+
launchtubeUrl?: string;
|
|
6
|
+
launchtubeJwt?: string;
|
|
13
7
|
});
|
|
14
|
-
send(
|
|
8
|
+
send(xdr: string, fee?: number): Promise<any>;
|
|
15
9
|
}
|
package/types/kit.d.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 { Networks,
|
|
3
|
+
import { Networks, xdr, Transaction, SorobanRpc } from '@stellar/stellar-sdk';
|
|
4
4
|
import { Buffer } from 'buffer';
|
|
5
5
|
import { PasskeyBase } from './base';
|
|
6
6
|
export declare class PasskeyKit extends PasskeyBase {
|
|
@@ -8,19 +8,16 @@ export declare class PasskeyKit extends PasskeyBase {
|
|
|
8
8
|
sudoKeyId: string | undefined;
|
|
9
9
|
wallet: PasskeyClient | undefined;
|
|
10
10
|
factory: FactoryClient;
|
|
11
|
-
|
|
12
|
-
horizon: Horizon.Server;
|
|
11
|
+
networkPassphrase: Networks;
|
|
13
12
|
rpcUrl: string;
|
|
14
13
|
rpc: SorobanRpc.Server;
|
|
15
14
|
factoryContractId: string;
|
|
16
15
|
constructor(options: {
|
|
17
|
-
sequencePublicKey: string;
|
|
18
|
-
networkPassphrase: Networks;
|
|
19
|
-
horizonUrl: string;
|
|
20
16
|
rpcUrl: string;
|
|
21
|
-
|
|
22
|
-
|
|
17
|
+
launchtubeUrl?: string;
|
|
18
|
+
launchtubeJwt?: string;
|
|
23
19
|
factoryContractId?: string;
|
|
20
|
+
networkPassphrase: Networks;
|
|
24
21
|
});
|
|
25
22
|
createWallet(name: string, user: string): Promise<{
|
|
26
23
|
keyId: Buffer;
|
|
@@ -31,15 +28,23 @@ export declare class PasskeyKit extends PasskeyBase {
|
|
|
31
28
|
keyId: Buffer;
|
|
32
29
|
publicKey: Buffer;
|
|
33
30
|
}>;
|
|
34
|
-
connectWallet(
|
|
31
|
+
connectWallet(keyId?: string): Promise<{
|
|
35
32
|
keyId: Buffer;
|
|
36
33
|
contractId: string;
|
|
37
34
|
}>;
|
|
35
|
+
signAuthEntry(entry: xdr.SorobanAuthorizationEntry, options?: {
|
|
36
|
+
keyId?: 'any' | 'sudo' | string | Uint8Array;
|
|
37
|
+
ledgersToLive?: number;
|
|
38
|
+
}): Promise<xdr.SorobanAuthorizationEntry>;
|
|
39
|
+
signAuthEntries(entries: xdr.SorobanAuthorizationEntry[], options?: {
|
|
40
|
+
keyId?: 'any' | 'sudo' | string | Uint8Array;
|
|
41
|
+
ledgersToLive?: number;
|
|
42
|
+
}): Promise<xdr.SorobanAuthorizationEntry[]>;
|
|
38
43
|
sign(txn: Transaction | string, options?: {
|
|
39
44
|
keyId?: 'any' | 'sudo' | string | Uint8Array;
|
|
40
45
|
ledgersToLive?: number;
|
|
41
46
|
}): Promise<string>;
|
|
42
|
-
getData(): Promise<Map<string, any>>;
|
|
47
|
+
getData(contractData?: SorobanRpc.Api.LedgerEntryResult): Promise<Map<string, any>>;
|
|
43
48
|
private getPublicKeyObject;
|
|
44
49
|
private convertEcdsaSignatureAsnToCompact;
|
|
45
50
|
}
|