@thru/passkey 0.2.13 → 0.2.14

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (71) hide show
  1. package/README.md +73 -90
  2. package/dist/auth.cjs +672 -0
  3. package/dist/auth.cjs.map +1 -0
  4. package/dist/auth.d.cts +60 -0
  5. package/dist/auth.d.ts +60 -0
  6. package/dist/auth.js +422 -0
  7. package/dist/auth.js.map +1 -0
  8. package/dist/chunk-2JHC7OOH.js +250 -0
  9. package/dist/chunk-2JHC7OOH.js.map +1 -0
  10. package/dist/chunk-75G2FPYW.js +54 -0
  11. package/dist/chunk-75G2FPYW.js.map +1 -0
  12. package/dist/chunk-B5SN7AS7.js +586 -0
  13. package/dist/chunk-B5SN7AS7.js.map +1 -0
  14. package/dist/chunk-LNDWK3FA.js +163 -0
  15. package/dist/chunk-LNDWK3FA.js.map +1 -0
  16. package/dist/index.cjs +27 -94
  17. package/dist/index.cjs.map +1 -1
  18. package/dist/index.d.cts +4 -187
  19. package/dist/index.d.ts +4 -187
  20. package/dist/index.js +47 -810
  21. package/dist/index.js.map +1 -1
  22. package/dist/mobile.cjs +301 -0
  23. package/dist/mobile.cjs.map +1 -0
  24. package/dist/mobile.d.cts +49 -0
  25. package/dist/mobile.d.ts +49 -0
  26. package/dist/mobile.js +41 -0
  27. package/dist/mobile.js.map +1 -0
  28. package/dist/popup.cjs +247 -0
  29. package/dist/popup.cjs.map +1 -0
  30. package/dist/popup.d.cts +22 -0
  31. package/dist/popup.d.ts +22 -0
  32. package/dist/popup.js +31 -0
  33. package/dist/popup.js.map +1 -0
  34. package/dist/server.cjs +351 -0
  35. package/dist/server.cjs.map +1 -0
  36. package/dist/server.d.cts +119 -0
  37. package/dist/server.d.ts +119 -0
  38. package/dist/server.js +340 -0
  39. package/dist/server.js.map +1 -0
  40. package/dist/types-_HRzmn-j.d.cts +125 -0
  41. package/dist/types-_HRzmn-j.d.ts +125 -0
  42. package/dist/web.cjs +758 -0
  43. package/dist/web.cjs.map +1 -0
  44. package/dist/web.d.cts +32 -0
  45. package/dist/web.d.ts +32 -0
  46. package/dist/web.js +60 -0
  47. package/dist/web.js.map +1 -0
  48. package/package.json +47 -2
  49. package/src/auth/execute-tx.ts +87 -0
  50. package/src/auth/index.ts +18 -0
  51. package/src/auth/types.ts +56 -0
  52. package/src/auth/use-passkey-auth.ts +428 -0
  53. package/src/index.ts +37 -39
  54. package/src/mobile/errors.ts +31 -0
  55. package/src/mobile/index.ts +33 -0
  56. package/src/mobile/passkey.ts +154 -0
  57. package/src/mobile/storage.ts +115 -0
  58. package/src/mobile/types.ts +24 -0
  59. package/src/popup-entry.ts +33 -0
  60. package/src/popup-service.ts +0 -103
  61. package/src/server/challenge.ts +26 -0
  62. package/src/server/create-wallet.ts +149 -0
  63. package/src/server/handlers.ts +93 -0
  64. package/src/server/index.ts +13 -0
  65. package/src/server/submit.ts +47 -0
  66. package/src/server/types.ts +70 -0
  67. package/src/server/utils.ts +69 -0
  68. package/src/types.ts +1 -0
  69. package/src/web.ts +51 -0
  70. package/tsconfig.json +6 -1
  71. package/tsup.config.ts +9 -1
@@ -0,0 +1,93 @@
1
+ import type { PasskeyContextResult } from './types';
2
+ import { createPasskeyChallenge } from './challenge';
3
+ import { submitPasskeyTransaction } from './submit';
4
+ import type {
5
+ PasskeyChallengeSubmitPayload,
6
+ ThruClient,
7
+ TransactionResult,
8
+ } from './types';
9
+
10
+ export function createPasskeyHandlers<P>(opts: {
11
+ buildContext: (params: P) => Promise<PasskeyContextResult>;
12
+ adminPublicKey: Uint8Array;
13
+ adminPrivateKey: string;
14
+ client: ThruClient;
15
+ challengeTtlMs?: number;
16
+ }) {
17
+ const pendingContexts = new Map<
18
+ string,
19
+ { context: PasskeyContextResult; createdAt: number }
20
+ >();
21
+ const challengeTtlMs = opts.challengeTtlMs ?? 5 * 60_000;
22
+
23
+ function createPendingContextKey(
24
+ walletAddress: string,
25
+ nonce: string,
26
+ challenge: string
27
+ ): string {
28
+ return `${walletAddress}:${nonce}:${challenge}`;
29
+ }
30
+
31
+ function prunePendingContexts(now = Date.now()): void {
32
+ for (const [nonce, entry] of pendingContexts.entries()) {
33
+ if (now - entry.createdAt > challengeTtlMs) {
34
+ pendingContexts.delete(nonce);
35
+ }
36
+ }
37
+ }
38
+
39
+ return {
40
+ challenge: async (walletAddress: string, params: P) => {
41
+ prunePendingContexts();
42
+
43
+ const context = await opts.buildContext(params);
44
+ const challenge = await createPasskeyChallenge({
45
+ client: opts.client,
46
+ walletAddress,
47
+ accountCtx: context.accountCtx,
48
+ invokeIx: context.invokeIx,
49
+ });
50
+
51
+ pendingContexts.set(
52
+ createPendingContextKey(walletAddress, challenge.nonce, challenge.challenge),
53
+ {
54
+ context,
55
+ createdAt: Date.now(),
56
+ }
57
+ );
58
+
59
+ return challenge;
60
+ },
61
+ submit: async (
62
+ walletAddress: string,
63
+ params: P,
64
+ payload: PasskeyChallengeSubmitPayload
65
+ ): Promise<TransactionResult> => {
66
+ void params;
67
+ prunePendingContexts();
68
+
69
+ const pendingKey = createPendingContextKey(
70
+ walletAddress,
71
+ payload.nonce,
72
+ payload.challenge
73
+ );
74
+ const pending = pendingContexts.get(pendingKey);
75
+ if (!pending) {
76
+ throw new Error('Missing or expired challenge nonce');
77
+ }
78
+
79
+ pendingContexts.delete(pendingKey);
80
+ const { nonce: _nonce, challenge: _challenge, ...signaturePayload } = payload;
81
+
82
+ return submitPasskeyTransaction({
83
+ client: opts.client,
84
+ adminPublicKey: opts.adminPublicKey,
85
+ adminPrivateKey: opts.adminPrivateKey,
86
+ walletAddress,
87
+ accountCtx: pending.context.accountCtx,
88
+ invokeIx: pending.context.invokeIx,
89
+ ...signaturePayload,
90
+ });
91
+ },
92
+ };
93
+ }
@@ -0,0 +1,13 @@
1
+ export type {
2
+ ThruClient,
3
+ PasskeySignaturePayload,
4
+ PasskeyChallengeSubmitPayload,
5
+ TransactionResult,
6
+ PasskeyChallengeResult,
7
+ PasskeyContextResult,
8
+ } from './types';
9
+
10
+ export { createPasskeyWallet } from './create-wallet';
11
+ export { createPasskeyChallenge } from './challenge';
12
+ export { submitPasskeyTransaction } from './submit';
13
+ export { createPasskeyHandlers } from './handlers';
@@ -0,0 +1,47 @@
1
+ import {
2
+ PASSKEY_MANAGER_PROGRAM_ADDRESS,
3
+ concatenateInstructions,
4
+ encodeValidateInstruction,
5
+ hexToBytes,
6
+ } from '@thru/passkey-manager';
7
+ import type { AccountContext } from '@thru/passkey-manager';
8
+ import { trackTransaction } from './utils';
9
+ import type {
10
+ PasskeySignaturePayload,
11
+ ThruClient,
12
+ TransactionResult,
13
+ } from './types';
14
+
15
+ export async function submitPasskeyTransaction(opts: {
16
+ client: ThruClient;
17
+ adminPublicKey: Uint8Array;
18
+ adminPrivateKey: string;
19
+ walletAddress: string;
20
+ accountCtx: AccountContext;
21
+ invokeIx: Uint8Array;
22
+ } & PasskeySignaturePayload): Promise<TransactionResult> {
23
+ const validateIx = encodeValidateInstruction({
24
+ walletAccountIdx: opts.accountCtx.walletAccountIdx,
25
+ authIdx: 0,
26
+ signatureR: hexToBytes(opts.signatureR),
27
+ signatureS: hexToBytes(opts.signatureS),
28
+ authenticatorData: Buffer.from(opts.authenticatorData, 'base64'),
29
+ clientDataJSON: Buffer.from(opts.clientDataJSON, 'base64'),
30
+ });
31
+
32
+ const instructionData = concatenateInstructions([validateIx, opts.invokeIx]);
33
+ const transaction = await opts.client.transactions.build({
34
+ feePayer: { publicKey: opts.adminPublicKey },
35
+ program: PASSKEY_MANAGER_PROGRAM_ADDRESS,
36
+ instructionData,
37
+ accounts: {
38
+ readWrite: opts.accountCtx.readWriteAddresses,
39
+ readOnly: opts.accountCtx.readOnlyAddresses,
40
+ },
41
+ header: { fee: 0n },
42
+ });
43
+
44
+ await transaction.sign(opts.adminPrivateKey);
45
+ const signature = await opts.client.transactions.send(transaction.toWire());
46
+ return trackTransaction(opts.client, signature);
47
+ }
@@ -0,0 +1,70 @@
1
+ import type { AccountContext } from '@thru/passkey-manager';
2
+
3
+ export interface ThruClient {
4
+ accounts: {
5
+ get: (address: string) => Promise<{ data?: { data?: Uint8Array } }>;
6
+ };
7
+ blocks: {
8
+ getBlockHeight: () => Promise<{ finalized: bigint }>;
9
+ };
10
+ proofs: {
11
+ generate: (params: {
12
+ address: string;
13
+ proofType: number;
14
+ targetSlot: bigint;
15
+ }) => Promise<{ proof?: Uint8Array }>;
16
+ };
17
+ transactions: {
18
+ build: (params: {
19
+ feePayer: { publicKey: Uint8Array };
20
+ program: string;
21
+ instructionData: Uint8Array;
22
+ accounts: {
23
+ readWrite: string[];
24
+ readOnly: string[];
25
+ };
26
+ header: { fee: bigint };
27
+ }) => Promise<{
28
+ sign: (privateKey: string) => Promise<unknown>;
29
+ toWire: () => Uint8Array;
30
+ }>;
31
+ send: (transaction: Uint8Array) => Promise<string>;
32
+ track: (
33
+ signature: string,
34
+ opts: { timeoutMs: number }
35
+ ) => AsyncIterable<{
36
+ executionResult?: {
37
+ userErrorCode: bigint;
38
+ };
39
+ statusCode?: number;
40
+ }>;
41
+ };
42
+ }
43
+
44
+ export interface PasskeySignaturePayload {
45
+ signatureR: string;
46
+ signatureS: string;
47
+ authenticatorData: string;
48
+ clientDataJSON: string;
49
+ }
50
+
51
+ export interface PasskeyChallengeSubmitPayload extends PasskeySignaturePayload {
52
+ challenge: string;
53
+ nonce: string;
54
+ }
55
+
56
+ export interface TransactionResult {
57
+ signature: string;
58
+ status: 'finalized' | 'failed' | 'timeout';
59
+ errorCode?: bigint;
60
+ }
61
+
62
+ export interface PasskeyChallengeResult {
63
+ challenge: string;
64
+ nonce: string;
65
+ }
66
+
67
+ export interface PasskeyContextResult {
68
+ accountCtx: AccountContext;
69
+ invokeIx: Uint8Array;
70
+ }
@@ -0,0 +1,69 @@
1
+ import { encodeAddress } from '@thru/helpers';
2
+ import type { ThruClient, TransactionResult } from './types';
3
+
4
+ export async function getStateProof(
5
+ client: ThruClient,
6
+ address: string,
7
+ proofType: number = 1,
8
+ targetSlot?: bigint
9
+ ): Promise<Uint8Array> {
10
+ const proofRequest: {
11
+ address: string;
12
+ proofType: number;
13
+ targetSlot?: bigint;
14
+ } = {
15
+ address,
16
+ proofType,
17
+ };
18
+
19
+ if (targetSlot !== undefined) {
20
+ proofRequest.targetSlot = targetSlot;
21
+ }
22
+
23
+ const proof = await client.proofs.generate(proofRequest);
24
+
25
+ if (!proof.proof || proof.proof.length === 0) {
26
+ throw new Error(`No state proof returned for ${address}`);
27
+ }
28
+
29
+ return proof.proof;
30
+ }
31
+
32
+ export async function trackTransaction(
33
+ client: ThruClient,
34
+ signature: string,
35
+ timeoutMs: number = 5000
36
+ ): Promise<TransactionResult> {
37
+ try {
38
+ for await (const update of client.transactions.track(signature, { timeoutMs })) {
39
+ if (update.executionResult) {
40
+ return {
41
+ signature,
42
+ status: update.executionResult.userErrorCode === 0n ? 'finalized' : 'failed',
43
+ errorCode: update.executionResult.userErrorCode,
44
+ };
45
+ }
46
+
47
+ if (update.statusCode === 3) {
48
+ return {
49
+ signature,
50
+ status: 'finalized',
51
+ };
52
+ }
53
+ }
54
+ } catch {
55
+ return {
56
+ signature,
57
+ status: 'timeout',
58
+ };
59
+ }
60
+
61
+ return {
62
+ signature,
63
+ status: 'timeout',
64
+ };
65
+ }
66
+
67
+ export function toThruAddress(bytes: Uint8Array): string {
68
+ return encodeAddress(bytes);
69
+ }
package/src/types.ts CHANGED
@@ -69,6 +69,7 @@ export interface PasskeyPopupGetRequestPayload {
69
69
  export interface PasskeyPopupCreateRequestPayload {
70
70
  alias: string;
71
71
  userId: string;
72
+ rpId: string;
72
73
  }
73
74
 
74
75
  export interface PasskeyPopupGetStoredRequestPayload {
package/src/web.ts ADDED
@@ -0,0 +1,51 @@
1
+ export type {
2
+ PasskeyRegistrationResult,
3
+ PasskeySigningResult,
4
+ PasskeyDiscoverableSigningResult,
5
+ PasskeyStoredSigningResult,
6
+ PasskeyMetadata,
7
+ PasskeyClientCapabilities,
8
+ PasskeyPopupContext,
9
+ PasskeyPopupAccount,
10
+ } from './types';
11
+
12
+ export { registerPasskey } from './register';
13
+
14
+ export {
15
+ signWithPasskey,
16
+ signWithStoredPasskey,
17
+ signWithDiscoverablePasskey,
18
+ } from './sign';
19
+
20
+ export {
21
+ parseDerSignature,
22
+ normalizeLowS,
23
+ normalizeSignatureComponent,
24
+ P256_N,
25
+ P256_HALF_N,
26
+ bytesToBigIntBE,
27
+ bigIntToBytesBE,
28
+ } from '@thru/passkey-manager';
29
+
30
+ export {
31
+ isWebAuthnSupported,
32
+ preloadPasskeyClientCapabilities,
33
+ getPasskeyClientCapabilities,
34
+ getCachedPasskeyClientCapabilities,
35
+ shouldUsePasskeyPopup,
36
+ isInIframe,
37
+ type PasskeyPromptAction,
38
+ } from './capabilities';
39
+
40
+ export {
41
+ arrayBufferToBase64Url,
42
+ base64UrlToArrayBuffer,
43
+ bytesToBase64,
44
+ bytesToBase64Url,
45
+ base64UrlToBytes,
46
+ bytesToHex,
47
+ hexToBytes,
48
+ bytesEqual,
49
+ compareBytes,
50
+ uniqueAccounts,
51
+ } from '@thru/passkey-manager';
package/tsconfig.json CHANGED
@@ -2,7 +2,12 @@
2
2
  "extends": "../tsconfig.base.json",
3
3
  "compilerOptions": {
4
4
  "outDir": "./dist",
5
- "rootDir": "./src"
5
+ "rootDir": "..",
6
+ "baseUrl": ".",
7
+ "paths": {
8
+ "@thru/helpers": ["../helpers/src/index.ts"],
9
+ "@thru/passkey-manager": ["../passkey-manager/src/index.ts"]
10
+ }
6
11
  },
7
12
  "include": ["src/**/*"],
8
13
  "exclude": ["node_modules", "dist"]
package/tsup.config.ts CHANGED
@@ -2,7 +2,15 @@ import { defineConfig } from 'tsup';
2
2
 
3
3
  export default defineConfig({
4
4
  format: ['esm', 'cjs'],
5
- entry: { index: 'src/index.ts' },
5
+ entry: {
6
+ index: 'src/index.ts',
7
+ web: 'src/web.ts',
8
+ popup: 'src/popup-entry.ts',
9
+ mobile: 'src/mobile/index.ts',
10
+ auth: 'src/auth/index.ts',
11
+ server: 'src/server/index.ts',
12
+ },
13
+ external: ['expo-secure-store', 'react-native', 'react-native-passkeys', 'zustand'],
6
14
  dts: true,
7
15
  sourcemap: true,
8
16
  clean: true,