@thru/passkey 0.2.13 → 0.2.15
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/README.md +73 -90
- package/dist/auth.cjs +672 -0
- package/dist/auth.cjs.map +1 -0
- package/dist/auth.d.cts +60 -0
- package/dist/auth.d.ts +60 -0
- package/dist/auth.js +422 -0
- package/dist/auth.js.map +1 -0
- package/dist/chunk-2JHC7OOH.js +250 -0
- package/dist/chunk-2JHC7OOH.js.map +1 -0
- package/dist/chunk-75G2FPYW.js +54 -0
- package/dist/chunk-75G2FPYW.js.map +1 -0
- package/dist/chunk-B5SN7AS7.js +586 -0
- package/dist/chunk-B5SN7AS7.js.map +1 -0
- package/dist/chunk-LNDWK3FA.js +163 -0
- package/dist/chunk-LNDWK3FA.js.map +1 -0
- package/dist/index.cjs +27 -94
- package/dist/index.cjs.map +1 -1
- package/dist/index.d.cts +4 -187
- package/dist/index.d.ts +4 -187
- package/dist/index.js +47 -810
- package/dist/index.js.map +1 -1
- package/dist/mobile.cjs +301 -0
- package/dist/mobile.cjs.map +1 -0
- package/dist/mobile.d.cts +49 -0
- package/dist/mobile.d.ts +49 -0
- package/dist/mobile.js +41 -0
- package/dist/mobile.js.map +1 -0
- package/dist/popup.cjs +247 -0
- package/dist/popup.cjs.map +1 -0
- package/dist/popup.d.cts +22 -0
- package/dist/popup.d.ts +22 -0
- package/dist/popup.js +31 -0
- package/dist/popup.js.map +1 -0
- package/dist/server.cjs +351 -0
- package/dist/server.cjs.map +1 -0
- package/dist/server.d.cts +119 -0
- package/dist/server.d.ts +119 -0
- package/dist/server.js +340 -0
- package/dist/server.js.map +1 -0
- package/dist/types-_HRzmn-j.d.cts +125 -0
- package/dist/types-_HRzmn-j.d.ts +125 -0
- package/dist/web.cjs +758 -0
- package/dist/web.cjs.map +1 -0
- package/dist/web.d.cts +32 -0
- package/dist/web.d.ts +32 -0
- package/dist/web.js +60 -0
- package/dist/web.js.map +1 -0
- package/package.json +47 -2
- package/src/auth/execute-tx.ts +87 -0
- package/src/auth/index.ts +18 -0
- package/src/auth/types.ts +56 -0
- package/src/auth/use-passkey-auth.ts +428 -0
- package/src/index.ts +37 -39
- package/src/mobile/errors.ts +31 -0
- package/src/mobile/index.ts +33 -0
- package/src/mobile/passkey.ts +154 -0
- package/src/mobile/storage.ts +115 -0
- package/src/mobile/types.ts +24 -0
- package/src/popup-entry.ts +33 -0
- package/src/popup-service.ts +0 -103
- package/src/server/challenge.ts +26 -0
- package/src/server/create-wallet.ts +149 -0
- package/src/server/handlers.ts +93 -0
- package/src/server/index.ts +13 -0
- package/src/server/submit.ts +47 -0
- package/src/server/types.ts +70 -0
- package/src/server/utils.ts +69 -0
- package/src/types.ts +1 -0
- package/src/web.ts +51 -0
- package/tsconfig.json +6 -1
- 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
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": "
|
|
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: {
|
|
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,
|