@solana/keychain-core 0.0.0 → 0.3.0

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 ADDED
@@ -0,0 +1,124 @@
1
+ # @solana/keychain-core
2
+
3
+ Core interfaces and utilities for building external Solana signers.
4
+
5
+ ## Installation
6
+
7
+ ```bash
8
+ pnpm add @solana/keychain-core
9
+ ```
10
+
11
+ ## What's Included
12
+
13
+ ### Interfaces
14
+
15
+ **`SolanaSigner`** - Unified interface that all signer implementations extend:
16
+
17
+ ```typescript
18
+ import { SolanaSigner } from '@solana/keychain-core';
19
+
20
+ interface SolanaSigner {
21
+ address: Address;
22
+ isAvailable(): Promise<boolean>;
23
+ signMessages(messages: readonly SignableMessage[]): Promise<readonly SignatureDictionary[]>;
24
+ signTransactions(transactions: readonly Transaction[]): Promise<readonly SignatureDictionary[]>;
25
+ }
26
+ ```
27
+
28
+ ### Error Handling
29
+
30
+ ```typescript
31
+ import { SignerError, SignerErrorCode, throwSignerError } from '@solana/keychain-core';
32
+
33
+ // Check error type
34
+ if (error instanceof SignerError) {
35
+ console.log(error.code); // e.g., 'SIGNER_SIGNING_FAILED'
36
+ console.log(error.context); // Additional error details
37
+ }
38
+
39
+ // Throw typed errors
40
+ throwSignerError(SignerErrorCode.SIGNING_FAILED, {
41
+ address: 'signer-address',
42
+ message: 'Custom error message'
43
+ });
44
+ ```
45
+
46
+ **Available error codes:**
47
+ - `INVALID_PRIVATE_KEY` - Invalid private key format
48
+ - `INVALID_PUBLIC_KEY` - Invalid public key format
49
+ - `SIGNING_FAILED` - Signing operation failed
50
+ - `REMOTE_API_ERROR` - Remote signer API error
51
+ - `HTTP_ERROR` - HTTP request failed
52
+ - `SERIALIZATION_ERROR` - Transaction serialization failed
53
+ - `CONFIG_ERROR` - Invalid configuration
54
+ - `NOT_AVAILABLE` - Signer not available/healthy
55
+ - `IO_ERROR` - File I/O error
56
+ - `PRIVY_NOT_INITIALIZED` - Privy signer not initialized
57
+
58
+ ### Utilities
59
+
60
+ **`extractSignatureFromWireTransaction`** - Extract a specific signer's signature from a signed transaction:
61
+
62
+ ```typescript
63
+ import { extractSignatureFromWireTransaction } from '@solana/keychain-core';
64
+
65
+ // When a remote API returns a fully signed base64 transaction, we need to extract the signature to use Kit's native methods (which rely on .signTransactions to return a SignatureDictionary)
66
+ const signedTx = await remoteApi.signTransaction(...);
67
+ const sigDict = extractSignatureFromWireTransaction({
68
+ base64WireTransaction: signedTx,
69
+ signerAddress: myAddress
70
+ });
71
+ ```
72
+
73
+ **`createSignatureDictionary`** - Create a signature dictionary from raw signature bytes:
74
+
75
+ ```typescript
76
+ import { createSignatureDictionary } from '@solana/keychain-core';
77
+
78
+ const sigDict = createSignatureDictionary({
79
+ signature: signatureBytes,
80
+ signerAddress: myAddress
81
+ });
82
+ ```
83
+
84
+ ## Usage
85
+
86
+ This package is typically used as a dependency when building custom signer implementations. See [@solana/keychain-privy](https://www.npmjs.com/package/@solana/keychain-privy) for an example implementation.
87
+
88
+ ```typescript
89
+ import { SolanaSigner, SignerErrorCode, throwSignerError } from '@solana/keychain-core';
90
+
91
+ class MyCustomSigner implements SolanaSigner {
92
+ readonly address: Address;
93
+
94
+ async isAvailable(): Promise<boolean> {
95
+ // Check if backend is healthy
96
+ }
97
+
98
+ async signMessages(messages: readonly SignableMessage[]) {
99
+ // Sign messages using your backend
100
+ }
101
+
102
+ async signTransactions(transactions: readonly Transaction[]) {
103
+ // Sign transactions using your backend
104
+ }
105
+ }
106
+ ```
107
+
108
+ ## Type Guards
109
+
110
+ **`isSolanaSigner`** - Check if a value is a SolanaSigner:
111
+
112
+ ```typescript
113
+ import { isSolanaSigner } from '@solana/keychain-core';
114
+
115
+ const isSigner = isSolanaSigner(value); // true or false
116
+ ```
117
+
118
+ **`assertIsSolanaSigner`** - Assert that a value is a SolanaSigner:
119
+
120
+ ```typescript
121
+ import { assertIsSolanaSigner } from '@solana/keychain-core';
122
+
123
+ assertIsSolanaSigner(value); // void (throws if not a SolanaSigner)
124
+ ```
@@ -0,0 +1,35 @@
1
+ /**
2
+ * Custom error codes for solana-keychain, specific to this library
3
+ */
4
+ export declare enum SignerErrorCode {
5
+ INVALID_PRIVATE_KEY = "SIGNER_INVALID_PRIVATE_KEY",
6
+ INVALID_PUBLIC_KEY = "SIGNER_INVALID_PUBLIC_KEY",
7
+ SIGNING_FAILED = "SIGNER_SIGNING_FAILED",
8
+ REMOTE_API_ERROR = "SIGNER_REMOTE_API_ERROR",
9
+ HTTP_ERROR = "SIGNER_HTTP_ERROR",
10
+ SERIALIZATION_ERROR = "SIGNER_SERIALIZATION_ERROR",
11
+ PARSING_ERROR = "SIGNER_PARSING_ERROR",
12
+ CONFIG_ERROR = "SIGNER_CONFIG_ERROR",
13
+ NOT_AVAILABLE = "SIGNER_NOT_AVAILABLE",
14
+ IO_ERROR = "SIGNER_IO_ERROR",
15
+ SIGNER_NOT_INITIALIZED = "SIGNER_NOT_INITIALIZED",
16
+ EXPECTED_SOLANA_SIGNER = "SIGNER_EXPECTED_SOLANA_SIGNER"
17
+ }
18
+ /**
19
+ * Custom error class for signer-specific errors
20
+ * Extends Error with code and context properties
21
+ */
22
+ export declare class SignerError extends Error {
23
+ readonly code: SignerErrorCode;
24
+ readonly context?: Record<string, unknown>;
25
+ constructor(code: SignerErrorCode, context?: Record<string, unknown>);
26
+ }
27
+ /**
28
+ * Helper function to create signer-specific errors
29
+ */
30
+ export declare function createSignerError(code: SignerErrorCode, context?: Record<string, unknown>): SignerError;
31
+ /**
32
+ * Helper function to throw signer-specific errors
33
+ */
34
+ export declare function throwSignerError(code: SignerErrorCode, context?: Record<string, unknown>): never;
35
+ //# sourceMappingURL=errors.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"errors.d.ts","sourceRoot":"","sources":["../src/errors.ts"],"names":[],"mappings":"AAAA;;GAEG;AACH,oBAAY,eAAe;IACvB,mBAAmB,+BAA+B;IAClD,kBAAkB,8BAA8B;IAChD,cAAc,0BAA0B;IACxC,gBAAgB,4BAA4B;IAC5C,UAAU,sBAAsB;IAChC,mBAAmB,+BAA+B;IAClD,aAAa,yBAAyB;IACtC,YAAY,wBAAwB;IACpC,aAAa,yBAAyB;IACtC,QAAQ,oBAAoB;IAC5B,sBAAsB,2BAA2B;IACjD,sBAAsB,kCAAkC;CAC3D;AAED;;;GAGG;AACH,qBAAa,WAAY,SAAQ,KAAK;IAClC,QAAQ,CAAC,IAAI,EAAE,eAAe,CAAC;IAC/B,QAAQ,CAAC,OAAO,CAAC,EAAE,MAAM,CAAC,MAAM,EAAE,OAAO,CAAC,CAAC;gBAE/B,IAAI,EAAE,eAAe,EAAE,OAAO,CAAC,EAAE,MAAM,CAAC,MAAM,EAAE,OAAO,CAAC;CAQvE;AAED;;GAEG;AACH,wBAAgB,iBAAiB,CAAC,IAAI,EAAE,eAAe,EAAE,OAAO,CAAC,EAAE,MAAM,CAAC,MAAM,EAAE,OAAO,CAAC,GAAG,WAAW,CAEvG;AAED;;GAEG;AACH,wBAAgB,gBAAgB,CAAC,IAAI,EAAE,eAAe,EAAE,OAAO,CAAC,EAAE,MAAM,CAAC,MAAM,EAAE,OAAO,CAAC,GAAG,KAAK,CAEhG"}
package/dist/errors.js ADDED
@@ -0,0 +1,44 @@
1
+ /**
2
+ * Custom error codes for solana-keychain, specific to this library
3
+ */
4
+ export var SignerErrorCode;
5
+ (function (SignerErrorCode) {
6
+ SignerErrorCode["INVALID_PRIVATE_KEY"] = "SIGNER_INVALID_PRIVATE_KEY";
7
+ SignerErrorCode["INVALID_PUBLIC_KEY"] = "SIGNER_INVALID_PUBLIC_KEY";
8
+ SignerErrorCode["SIGNING_FAILED"] = "SIGNER_SIGNING_FAILED";
9
+ SignerErrorCode["REMOTE_API_ERROR"] = "SIGNER_REMOTE_API_ERROR";
10
+ SignerErrorCode["HTTP_ERROR"] = "SIGNER_HTTP_ERROR";
11
+ SignerErrorCode["SERIALIZATION_ERROR"] = "SIGNER_SERIALIZATION_ERROR";
12
+ SignerErrorCode["PARSING_ERROR"] = "SIGNER_PARSING_ERROR";
13
+ SignerErrorCode["CONFIG_ERROR"] = "SIGNER_CONFIG_ERROR";
14
+ SignerErrorCode["NOT_AVAILABLE"] = "SIGNER_NOT_AVAILABLE";
15
+ SignerErrorCode["IO_ERROR"] = "SIGNER_IO_ERROR";
16
+ SignerErrorCode["SIGNER_NOT_INITIALIZED"] = "SIGNER_NOT_INITIALIZED";
17
+ SignerErrorCode["EXPECTED_SOLANA_SIGNER"] = "SIGNER_EXPECTED_SOLANA_SIGNER";
18
+ })(SignerErrorCode || (SignerErrorCode = {}));
19
+ /**
20
+ * Custom error class for signer-specific errors
21
+ * Extends Error with code and context properties
22
+ */
23
+ export class SignerError extends Error {
24
+ constructor(code, context) {
25
+ const message = context?.message && typeof context.message === 'string' ? context.message : `Signer error: ${code}`;
26
+ super(message);
27
+ this.name = 'SignerError';
28
+ this.code = code;
29
+ this.context = context;
30
+ }
31
+ }
32
+ /**
33
+ * Helper function to create signer-specific errors
34
+ */
35
+ export function createSignerError(code, context) {
36
+ return new SignerError(code, context);
37
+ }
38
+ /**
39
+ * Helper function to throw signer-specific errors
40
+ */
41
+ export function throwSignerError(code, context) {
42
+ throw createSignerError(code, context);
43
+ }
44
+ //# sourceMappingURL=errors.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"errors.js","sourceRoot":"","sources":["../src/errors.ts"],"names":[],"mappings":"AAAA;;GAEG;AACH,MAAM,CAAN,IAAY,eAaX;AAbD,WAAY,eAAe;IACvB,qEAAkD,CAAA;IAClD,mEAAgD,CAAA;IAChD,2DAAwC,CAAA;IACxC,+DAA4C,CAAA;IAC5C,mDAAgC,CAAA;IAChC,qEAAkD,CAAA;IAClD,yDAAsC,CAAA;IACtC,uDAAoC,CAAA;IACpC,yDAAsC,CAAA;IACtC,+CAA4B,CAAA;IAC5B,oEAAiD,CAAA;IACjD,2EAAwD,CAAA;AAC5D,CAAC,EAbW,eAAe,KAAf,eAAe,QAa1B;AAED;;;GAGG;AACH,MAAM,OAAO,WAAY,SAAQ,KAAK;IAIlC,YAAY,IAAqB,EAAE,OAAiC;QAChE,MAAM,OAAO,GACT,OAAO,EAAE,OAAO,IAAI,OAAO,OAAO,CAAC,OAAO,KAAK,QAAQ,CAAC,CAAC,CAAC,OAAO,CAAC,OAAO,CAAC,CAAC,CAAC,iBAAiB,IAAI,EAAE,CAAC;QACxG,KAAK,CAAC,OAAO,CAAC,CAAC;QACf,IAAI,CAAC,IAAI,GAAG,aAAa,CAAC;QAC1B,IAAI,CAAC,IAAI,GAAG,IAAI,CAAC;QACjB,IAAI,CAAC,OAAO,GAAG,OAAO,CAAC;IAC3B,CAAC;CACJ;AAED;;GAEG;AACH,MAAM,UAAU,iBAAiB,CAAC,IAAqB,EAAE,OAAiC;IACtF,OAAO,IAAI,WAAW,CAAC,IAAI,EAAE,OAAO,CAAC,CAAC;AAC1C,CAAC;AAED;;GAEG;AACH,MAAM,UAAU,gBAAgB,CAAC,IAAqB,EAAE,OAAiC;IACrF,MAAM,iBAAiB,CAAC,IAAI,EAAE,OAAO,CAAC,CAAC;AAC3C,CAAC"}
@@ -0,0 +1,4 @@
1
+ export * from './errors.js';
2
+ export * from './types.js';
3
+ export * from './utils.js';
4
+ //# sourceMappingURL=index.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"index.d.ts","sourceRoot":"","sources":["../src/index.ts"],"names":[],"mappings":"AAAA,cAAc,aAAa,CAAC;AAC5B,cAAc,YAAY,CAAC;AAC3B,cAAc,YAAY,CAAC"}
package/dist/index.js ADDED
@@ -0,0 +1,4 @@
1
+ export * from './errors.js';
2
+ export * from './types.js';
3
+ export * from './utils.js';
4
+ //# sourceMappingURL=index.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"index.js","sourceRoot":"","sources":["../src/index.ts"],"names":[],"mappings":"AAAA,cAAc,aAAa,CAAC;AAC5B,cAAc,YAAY,CAAC;AAC3B,cAAc,YAAY,CAAC"}
@@ -0,0 +1,35 @@
1
+ import type { Address } from '@solana/addresses';
2
+ import type { MessagePartialSigner, SignableMessage, SignatureDictionary, TransactionPartialSigner } from '@solana/signers';
3
+ import type { Transaction, TransactionWithinSizeLimit, TransactionWithLifetime } from '@solana/transactions';
4
+ /**
5
+ * Unified signer interface that extends both transaction and message signers.
6
+ * Provides both high-level (simple) and low-level (@solana/kit compatible) APIs.
7
+ */
8
+ export interface SolanaSigner<TAddress extends string = string> extends TransactionPartialSigner<TAddress>, MessagePartialSigner<TAddress> {
9
+ /**
10
+ * Get the public key address of this signer
11
+ */
12
+ readonly address: Address<TAddress>;
13
+ /**
14
+ * Check if the signer is available and healthy.
15
+ * For remote signers (Vault, Privy, Turnkey), this performs an API health check.
16
+ */
17
+ isAvailable(): Promise<boolean>;
18
+ /**
19
+ * Signs multiple messages and returns signature dictionaries
20
+ * for @solana/kit signing compatibility.
21
+ *
22
+ * @param messages - Array of signable messages
23
+ * @returns Array of signature dictionaries (address -> signature mapping)
24
+ */
25
+ signMessages(messages: readonly SignableMessage[]): Promise<readonly SignatureDictionary[]>;
26
+ /**
27
+ * Signs multiple transactions and returns signature dictionaries.
28
+ * for @solana/kit signing compatibility.
29
+ *
30
+ * @param transactions - Array of transactions to sign
31
+ * @returns Array of signature dictionaries (address -> signature mapping)
32
+ */
33
+ signTransactions(transactions: readonly (Transaction & TransactionWithinSizeLimit & TransactionWithLifetime)[]): Promise<readonly SignatureDictionary[]>;
34
+ }
35
+ //# sourceMappingURL=types.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"types.d.ts","sourceRoot":"","sources":["../src/types.ts"],"names":[],"mappings":"AAAA,OAAO,KAAK,EAAE,OAAO,EAAE,MAAM,mBAAmB,CAAC;AACjD,OAAO,KAAK,EACR,oBAAoB,EACpB,eAAe,EACf,mBAAmB,EACnB,wBAAwB,EAC3B,MAAM,iBAAiB,CAAC;AACzB,OAAO,KAAK,EAAE,WAAW,EAAE,0BAA0B,EAAE,uBAAuB,EAAE,MAAM,sBAAsB,CAAC;AAE7G;;;GAGG;AACH,MAAM,WAAW,YAAY,CAAC,QAAQ,SAAS,MAAM,GAAG,MAAM,CAC1D,SAAQ,wBAAwB,CAAC,QAAQ,CAAC,EACtC,oBAAoB,CAAC,QAAQ,CAAC;IAClC;;OAEG;IACH,QAAQ,CAAC,OAAO,EAAE,OAAO,CAAC,QAAQ,CAAC,CAAC;IAEpC;;;OAGG;IACH,WAAW,IAAI,OAAO,CAAC,OAAO,CAAC,CAAC;IAEhC;;;;;;OAMG;IACH,YAAY,CAAC,QAAQ,EAAE,SAAS,eAAe,EAAE,GAAG,OAAO,CAAC,SAAS,mBAAmB,EAAE,CAAC,CAAC;IAE5F;;;;;;OAMG;IACH,gBAAgB,CACZ,YAAY,EAAE,SAAS,CAAC,WAAW,GAAG,0BAA0B,GAAG,uBAAuB,CAAC,EAAE,GAC9F,OAAO,CAAC,SAAS,mBAAmB,EAAE,CAAC,CAAC;CAC9C"}
package/dist/types.js ADDED
@@ -0,0 +1,2 @@
1
+ export {};
2
+ //# sourceMappingURL=types.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"types.js","sourceRoot":"","sources":["../src/types.ts"],"names":[],"mappings":""}
@@ -0,0 +1,61 @@
1
+ import { Address } from '@solana/addresses';
2
+ import { SignatureBytes } from '@solana/keys';
3
+ import { SignatureDictionary } from '@solana/signers';
4
+ import { Base64EncodedWireTransaction } from '@solana/transactions';
5
+ import { SolanaSigner } from './types.js';
6
+ interface ExtractSignatureFromWireTransactionOptions {
7
+ base64WireTransaction: Base64EncodedWireTransaction;
8
+ signerAddress: Address;
9
+ }
10
+ /**
11
+ * Extracts a specific signer's signature from a base64-encoded wire transaction.
12
+ * Useful for remote signers that return fully signed transactions from their APIs.
13
+ *
14
+ * @param base64WireTransaction - Base64 encoded transaction string
15
+ * @param signerAddress - The address of the signer whose signature to extract
16
+ * @returns SignatureDictionary with only the specified signer's signature
17
+ * @throws {SignerError} If no signature is found for the given address
18
+ *
19
+ * @example
20
+ * ```typescript
21
+ * // Privy API returns a signed transaction
22
+ * const signedTx = await privyApi.signTransaction(...);
23
+ * const sigDict = extractSignatureFromWireTransaction(signedTx, this.address);
24
+ * ```
25
+ */
26
+ export declare function extractSignatureFromWireTransaction({ base64WireTransaction, signerAddress, }: ExtractSignatureFromWireTransactionOptions): SignatureDictionary;
27
+ interface CreateSignatureDictionaryArgs {
28
+ signature: SignatureBytes;
29
+ signerAddress: Address;
30
+ }
31
+ /**
32
+ * Creates a signature dictionary from a signature and signer address.
33
+ * @param signature - The signature to create the dictionary from
34
+ * @param signerAddress - The address of the signer whose signature to create the dictionary from
35
+ * @returns SignatureDictionary with only the specified signer's signature
36
+ * @throws {SignerError} If no signature is found for the given address
37
+ *
38
+ * @example
39
+ * ```typescript
40
+ * const sigDict = createSignatureDictionary({ signature, signerAddress });
41
+ * ```
42
+ */
43
+ export declare function createSignatureDictionary({ signature, signerAddress, }: CreateSignatureDictionaryArgs): SignatureDictionary;
44
+ /**
45
+ * Checks if the given value is a SolanaSigner.
46
+ * @param value - The value to check
47
+ * @returns True if the value is a SolanaSigner, false otherwise
48
+ */
49
+ export declare function isSolanaSigner<TAddress extends string>(value: {
50
+ address: Address<TAddress>;
51
+ }): value is SolanaSigner<TAddress>;
52
+ /**
53
+ * Asserts that the given value is a SolanaSigner, throwing an error if it is not.
54
+ * @param value - The value to check
55
+ * @throws {SignerError} If the value is not a SolanaSigner
56
+ */
57
+ export declare function assertIsSolanaSigner<TAddress extends string>(value: {
58
+ address: Address<TAddress>;
59
+ }): asserts value is SolanaSigner<TAddress>;
60
+ export {};
61
+ //# sourceMappingURL=utils.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"utils.d.ts","sourceRoot":"","sources":["../src/utils.ts"],"names":[],"mappings":"AAAA,OAAO,EAAE,OAAO,EAAmB,MAAM,mBAAmB,CAAC;AAE7D,OAAO,EAAE,cAAc,EAAE,MAAM,cAAc,CAAC;AAC9C,OAAO,EAAsD,mBAAmB,EAAE,MAAM,iBAAiB,CAAC;AAC1G,OAAO,EAAE,4BAA4B,EAAyB,MAAM,sBAAsB,CAAC;AAG3F,OAAO,EAAE,YAAY,EAAE,MAAM,YAAY,CAAC;AAE1C,UAAU,0CAA0C;IAChD,qBAAqB,EAAE,4BAA4B,CAAC;IACpD,aAAa,EAAE,OAAO,CAAC;CAC1B;AAED;;;;;;;;;;;;;;;GAeG;AACH,wBAAgB,mCAAmC,CAAC,EAChD,qBAAqB,EACrB,aAAa,GAChB,EAAE,0CAA0C,GAAG,mBAAmB,CAmBlE;AAED,UAAU,6BAA6B;IACnC,SAAS,EAAE,cAAc,CAAC;IAC1B,aAAa,EAAE,OAAO,CAAC;CAC1B;AAED;;;;;;;;;;;GAWG;AACH,wBAAgB,yBAAyB,CAAC,EACtC,SAAS,EACT,aAAa,GAChB,EAAE,6BAA6B,GAAG,mBAAmB,CASrD;AAED;;;;GAIG;AACH,wBAAgB,cAAc,CAAC,QAAQ,SAAS,MAAM,EAAE,KAAK,EAAE;IAC3D,OAAO,EAAE,OAAO,CAAC,QAAQ,CAAC,CAAC;CAC9B,GAAG,KAAK,IAAI,YAAY,CAAC,QAAQ,CAAC,CAOlC;AAED;;;;GAIG;AACH,wBAAgB,oBAAoB,CAAC,QAAQ,SAAS,MAAM,EAAE,KAAK,EAAE;IACjE,OAAO,EAAE,OAAO,CAAC,QAAQ,CAAC,CAAC;CAC9B,GAAG,OAAO,CAAC,KAAK,IAAI,YAAY,CAAC,QAAQ,CAAC,CAM1C"}
package/dist/utils.js ADDED
@@ -0,0 +1,85 @@
1
+ import { assertIsAddress } from '@solana/addresses';
2
+ import { getBase64Encoder } from '@solana/codecs-strings';
3
+ import { isMessagePartialSigner, isTransactionPartialSigner } from '@solana/signers';
4
+ import { getTransactionDecoder } from '@solana/transactions';
5
+ import { SignerErrorCode, throwSignerError } from './errors.js';
6
+ /**
7
+ * Extracts a specific signer's signature from a base64-encoded wire transaction.
8
+ * Useful for remote signers that return fully signed transactions from their APIs.
9
+ *
10
+ * @param base64WireTransaction - Base64 encoded transaction string
11
+ * @param signerAddress - The address of the signer whose signature to extract
12
+ * @returns SignatureDictionary with only the specified signer's signature
13
+ * @throws {SignerError} If no signature is found for the given address
14
+ *
15
+ * @example
16
+ * ```typescript
17
+ * // Privy API returns a signed transaction
18
+ * const signedTx = await privyApi.signTransaction(...);
19
+ * const sigDict = extractSignatureFromWireTransaction(signedTx, this.address);
20
+ * ```
21
+ */
22
+ export function extractSignatureFromWireTransaction({ base64WireTransaction, signerAddress, }) {
23
+ assertIsAddress(signerAddress);
24
+ const encoder = getBase64Encoder();
25
+ const decoder = getTransactionDecoder();
26
+ const transactionBytes = encoder.encode(base64WireTransaction);
27
+ const { signatures } = decoder.decode(transactionBytes);
28
+ const signature = signatures[signerAddress];
29
+ if (!signature) {
30
+ throwSignerError(SignerErrorCode.SIGNING_FAILED, {
31
+ address: signerAddress,
32
+ message: `No signature found for address ${signerAddress}`,
33
+ });
34
+ }
35
+ return createSignatureDictionary({
36
+ signature,
37
+ signerAddress,
38
+ });
39
+ }
40
+ /**
41
+ * Creates a signature dictionary from a signature and signer address.
42
+ * @param signature - The signature to create the dictionary from
43
+ * @param signerAddress - The address of the signer whose signature to create the dictionary from
44
+ * @returns SignatureDictionary with only the specified signer's signature
45
+ * @throws {SignerError} If no signature is found for the given address
46
+ *
47
+ * @example
48
+ * ```typescript
49
+ * const sigDict = createSignatureDictionary({ signature, signerAddress });
50
+ * ```
51
+ */
52
+ export function createSignatureDictionary({ signature, signerAddress, }) {
53
+ assertIsAddress(signerAddress);
54
+ if (!signature) {
55
+ throwSignerError(SignerErrorCode.SIGNING_FAILED, {
56
+ address: signerAddress,
57
+ message: `No signature found for address ${signerAddress}`,
58
+ });
59
+ }
60
+ return Object.freeze({ [signerAddress]: signature });
61
+ }
62
+ /**
63
+ * Checks if the given value is a SolanaSigner.
64
+ * @param value - The value to check
65
+ * @returns True if the value is a SolanaSigner, false otherwise
66
+ */
67
+ export function isSolanaSigner(value) {
68
+ return ('address' in value &&
69
+ 'isAvailable' in value &&
70
+ isMessagePartialSigner(value) &&
71
+ isTransactionPartialSigner(value));
72
+ }
73
+ /**
74
+ * Asserts that the given value is a SolanaSigner, throwing an error if it is not.
75
+ * @param value - The value to check
76
+ * @throws {SignerError} If the value is not a SolanaSigner
77
+ */
78
+ export function assertIsSolanaSigner(value) {
79
+ if (!isSolanaSigner(value)) {
80
+ throwSignerError(SignerErrorCode.EXPECTED_SOLANA_SIGNER, {
81
+ address: value.address,
82
+ });
83
+ }
84
+ }
85
+ //# sourceMappingURL=utils.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"utils.js","sourceRoot":"","sources":["../src/utils.ts"],"names":[],"mappings":"AAAA,OAAO,EAAW,eAAe,EAAE,MAAM,mBAAmB,CAAC;AAC7D,OAAO,EAAE,gBAAgB,EAAE,MAAM,wBAAwB,CAAC;AAE1D,OAAO,EAAE,sBAAsB,EAAE,0BAA0B,EAAuB,MAAM,iBAAiB,CAAC;AAC1G,OAAO,EAAgC,qBAAqB,EAAE,MAAM,sBAAsB,CAAC;AAE3F,OAAO,EAAE,eAAe,EAAE,gBAAgB,EAAE,MAAM,aAAa,CAAC;AAQhE;;;;;;;;;;;;;;;GAeG;AACH,MAAM,UAAU,mCAAmC,CAAC,EAChD,qBAAqB,EACrB,aAAa,GAC4B;IACzC,eAAe,CAAC,aAAa,CAAC,CAAC;IAC/B,MAAM,OAAO,GAAG,gBAAgB,EAAE,CAAC;IACnC,MAAM,OAAO,GAAG,qBAAqB,EAAE,CAAC;IACxC,MAAM,gBAAgB,GAAG,OAAO,CAAC,MAAM,CAAC,qBAAqB,CAAC,CAAC;IAC/D,MAAM,EAAE,UAAU,EAAE,GAAG,OAAO,CAAC,MAAM,CAAC,gBAAgB,CAAC,CAAC;IAExD,MAAM,SAAS,GAAG,UAAU,CAAC,aAAa,CAAC,CAAC;IAC5C,IAAI,CAAC,SAAS,EAAE,CAAC;QACb,gBAAgB,CAAC,eAAe,CAAC,cAAc,EAAE;YAC7C,OAAO,EAAE,aAAa;YACtB,OAAO,EAAE,kCAAkC,aAAa,EAAE;SAC7D,CAAC,CAAC;IACP,CAAC;IAED,OAAO,yBAAyB,CAAC;QAC7B,SAAS;QACT,aAAa;KAChB,CAAC,CAAC;AACP,CAAC;AAOD;;;;;;;;;;;GAWG;AACH,MAAM,UAAU,yBAAyB,CAAC,EACtC,SAAS,EACT,aAAa,GACe;IAC5B,eAAe,CAAC,aAAa,CAAC,CAAC;IAC/B,IAAI,CAAC,SAAS,EAAE,CAAC;QACb,gBAAgB,CAAC,eAAe,CAAC,cAAc,EAAE;YAC7C,OAAO,EAAE,aAAa;YACtB,OAAO,EAAE,kCAAkC,aAAa,EAAE;SAC7D,CAAC,CAAC;IACP,CAAC;IACD,OAAO,MAAM,CAAC,MAAM,CAAC,EAAE,CAAC,aAAa,CAAC,EAAE,SAAS,EAAE,CAAC,CAAC;AACzD,CAAC;AAED;;;;GAIG;AACH,MAAM,UAAU,cAAc,CAA0B,KAEvD;IACG,OAAO,CACH,SAAS,IAAI,KAAK;QAClB,aAAa,IAAI,KAAK;QACtB,sBAAsB,CAAC,KAAK,CAAC;QAC7B,0BAA0B,CAAC,KAAK,CAAC,CACpC,CAAC;AACN,CAAC;AAED;;;;GAIG;AACH,MAAM,UAAU,oBAAoB,CAA0B,KAE7D;IACG,IAAI,CAAC,cAAc,CAAC,KAAK,CAAC,EAAE,CAAC;QACzB,gBAAgB,CAAC,eAAe,CAAC,sBAAsB,EAAE;YACrD,OAAO,EAAE,KAAK,CAAC,OAAO;SACzB,CAAC,CAAC;IACP,CAAC;AACL,CAAC"}
package/package.json CHANGED
@@ -1,12 +1,47 @@
1
1
  {
2
2
  "name": "@solana/keychain-core",
3
- "version": "0.0.0",
4
- "main": "index.js",
5
- "scripts": {
6
- "test": "echo \"Error: no test specified\" && exit 1"
3
+ "author": "Solana Foundation",
4
+ "license": "MIT",
5
+ "repository": "https://github.com/solana-foundation/solana-keychain",
6
+ "version": "0.3.0",
7
+ "description": "Core interfaces and types for external Solana signers",
8
+ "keywords": [
9
+ "solana",
10
+ "signing",
11
+ "wallet",
12
+ "privy",
13
+ "vault",
14
+ "turnkey"
15
+ ],
16
+ "type": "module",
17
+ "sideEffects": false,
18
+ "main": "./dist/index.js",
19
+ "types": "./dist/index.d.ts",
20
+ "exports": {
21
+ ".": {
22
+ "types": "./dist/index.d.ts",
23
+ "import": "./dist/index.js"
24
+ }
25
+ },
26
+ "files": [
27
+ "dist",
28
+ "src"
29
+ ],
30
+ "dependencies": {
31
+ "@solana/addresses": "^5.0.0",
32
+ "@solana/codecs-core": "^5.0.0",
33
+ "@solana/codecs-strings": "^5.0.0",
34
+ "@solana/keys": "^5.0.0",
35
+ "@solana/signers": "^5.0.0",
36
+ "@solana/transactions": "^5.0.0"
7
37
  },
8
- "keywords": [],
9
- "author": "",
10
- "license": "ISC",
11
- "description": ""
12
- }
38
+ "publishConfig": {
39
+ "access": "public"
40
+ },
41
+ "scripts": {
42
+ "build": "tsc --build",
43
+ "clean": "rm -rf dist *.tsbuildinfo",
44
+ "test": "pnpm run typecheck",
45
+ "typecheck": "tsc --noEmit"
46
+ }
47
+ }
package/src/errors.ts ADDED
@@ -0,0 +1,49 @@
1
+ /**
2
+ * Custom error codes for solana-keychain, specific to this library
3
+ */
4
+ export enum SignerErrorCode {
5
+ INVALID_PRIVATE_KEY = 'SIGNER_INVALID_PRIVATE_KEY',
6
+ INVALID_PUBLIC_KEY = 'SIGNER_INVALID_PUBLIC_KEY',
7
+ SIGNING_FAILED = 'SIGNER_SIGNING_FAILED',
8
+ REMOTE_API_ERROR = 'SIGNER_REMOTE_API_ERROR',
9
+ HTTP_ERROR = 'SIGNER_HTTP_ERROR',
10
+ SERIALIZATION_ERROR = 'SIGNER_SERIALIZATION_ERROR',
11
+ PARSING_ERROR = 'SIGNER_PARSING_ERROR',
12
+ CONFIG_ERROR = 'SIGNER_CONFIG_ERROR',
13
+ NOT_AVAILABLE = 'SIGNER_NOT_AVAILABLE',
14
+ IO_ERROR = 'SIGNER_IO_ERROR',
15
+ SIGNER_NOT_INITIALIZED = 'SIGNER_NOT_INITIALIZED',
16
+ EXPECTED_SOLANA_SIGNER = 'SIGNER_EXPECTED_SOLANA_SIGNER',
17
+ }
18
+
19
+ /**
20
+ * Custom error class for signer-specific errors
21
+ * Extends Error with code and context properties
22
+ */
23
+ export class SignerError extends Error {
24
+ readonly code: SignerErrorCode;
25
+ readonly context?: Record<string, unknown>;
26
+
27
+ constructor(code: SignerErrorCode, context?: Record<string, unknown>) {
28
+ const message =
29
+ context?.message && typeof context.message === 'string' ? context.message : `Signer error: ${code}`;
30
+ super(message);
31
+ this.name = 'SignerError';
32
+ this.code = code;
33
+ this.context = context;
34
+ }
35
+ }
36
+
37
+ /**
38
+ * Helper function to create signer-specific errors
39
+ */
40
+ export function createSignerError(code: SignerErrorCode, context?: Record<string, unknown>): SignerError {
41
+ return new SignerError(code, context);
42
+ }
43
+
44
+ /**
45
+ * Helper function to throw signer-specific errors
46
+ */
47
+ export function throwSignerError(code: SignerErrorCode, context?: Record<string, unknown>): never {
48
+ throw createSignerError(code, context);
49
+ }
package/src/index.ts ADDED
@@ -0,0 +1,3 @@
1
+ export * from './errors.js';
2
+ export * from './types.js';
3
+ export * from './utils.js';
package/src/types.ts ADDED
@@ -0,0 +1,47 @@
1
+ import type { Address } from '@solana/addresses';
2
+ import type {
3
+ MessagePartialSigner,
4
+ SignableMessage,
5
+ SignatureDictionary,
6
+ TransactionPartialSigner,
7
+ } from '@solana/signers';
8
+ import type { Transaction, TransactionWithinSizeLimit, TransactionWithLifetime } from '@solana/transactions';
9
+
10
+ /**
11
+ * Unified signer interface that extends both transaction and message signers.
12
+ * Provides both high-level (simple) and low-level (@solana/kit compatible) APIs.
13
+ */
14
+ export interface SolanaSigner<TAddress extends string = string>
15
+ extends TransactionPartialSigner<TAddress>,
16
+ MessagePartialSigner<TAddress> {
17
+ /**
18
+ * Get the public key address of this signer
19
+ */
20
+ readonly address: Address<TAddress>;
21
+
22
+ /**
23
+ * Check if the signer is available and healthy.
24
+ * For remote signers (Vault, Privy, Turnkey), this performs an API health check.
25
+ */
26
+ isAvailable(): Promise<boolean>;
27
+
28
+ /**
29
+ * Signs multiple messages and returns signature dictionaries
30
+ * for @solana/kit signing compatibility.
31
+ *
32
+ * @param messages - Array of signable messages
33
+ * @returns Array of signature dictionaries (address -> signature mapping)
34
+ */
35
+ signMessages(messages: readonly SignableMessage[]): Promise<readonly SignatureDictionary[]>;
36
+
37
+ /**
38
+ * Signs multiple transactions and returns signature dictionaries.
39
+ * for @solana/kit signing compatibility.
40
+ *
41
+ * @param transactions - Array of transactions to sign
42
+ * @returns Array of signature dictionaries (address -> signature mapping)
43
+ */
44
+ signTransactions(
45
+ transactions: readonly (Transaction & TransactionWithinSizeLimit & TransactionWithLifetime)[],
46
+ ): Promise<readonly SignatureDictionary[]>;
47
+ }
package/src/utils.ts ADDED
@@ -0,0 +1,115 @@
1
+ import { Address, assertIsAddress } from '@solana/addresses';
2
+ import { getBase64Encoder } from '@solana/codecs-strings';
3
+ import { SignatureBytes } from '@solana/keys';
4
+ import { isMessagePartialSigner, isTransactionPartialSigner, SignatureDictionary } from '@solana/signers';
5
+ import { Base64EncodedWireTransaction, getTransactionDecoder } from '@solana/transactions';
6
+
7
+ import { SignerErrorCode, throwSignerError } from './errors.js';
8
+ import { SolanaSigner } from './types.js';
9
+
10
+ interface ExtractSignatureFromWireTransactionOptions {
11
+ base64WireTransaction: Base64EncodedWireTransaction;
12
+ signerAddress: Address;
13
+ }
14
+
15
+ /**
16
+ * Extracts a specific signer's signature from a base64-encoded wire transaction.
17
+ * Useful for remote signers that return fully signed transactions from their APIs.
18
+ *
19
+ * @param base64WireTransaction - Base64 encoded transaction string
20
+ * @param signerAddress - The address of the signer whose signature to extract
21
+ * @returns SignatureDictionary with only the specified signer's signature
22
+ * @throws {SignerError} If no signature is found for the given address
23
+ *
24
+ * @example
25
+ * ```typescript
26
+ * // Privy API returns a signed transaction
27
+ * const signedTx = await privyApi.signTransaction(...);
28
+ * const sigDict = extractSignatureFromWireTransaction(signedTx, this.address);
29
+ * ```
30
+ */
31
+ export function extractSignatureFromWireTransaction({
32
+ base64WireTransaction,
33
+ signerAddress,
34
+ }: ExtractSignatureFromWireTransactionOptions): SignatureDictionary {
35
+ assertIsAddress(signerAddress);
36
+ const encoder = getBase64Encoder();
37
+ const decoder = getTransactionDecoder();
38
+ const transactionBytes = encoder.encode(base64WireTransaction);
39
+ const { signatures } = decoder.decode(transactionBytes);
40
+
41
+ const signature = signatures[signerAddress];
42
+ if (!signature) {
43
+ throwSignerError(SignerErrorCode.SIGNING_FAILED, {
44
+ address: signerAddress,
45
+ message: `No signature found for address ${signerAddress}`,
46
+ });
47
+ }
48
+
49
+ return createSignatureDictionary({
50
+ signature,
51
+ signerAddress,
52
+ });
53
+ }
54
+
55
+ interface CreateSignatureDictionaryArgs {
56
+ signature: SignatureBytes;
57
+ signerAddress: Address;
58
+ }
59
+
60
+ /**
61
+ * Creates a signature dictionary from a signature and signer address.
62
+ * @param signature - The signature to create the dictionary from
63
+ * @param signerAddress - The address of the signer whose signature to create the dictionary from
64
+ * @returns SignatureDictionary with only the specified signer's signature
65
+ * @throws {SignerError} If no signature is found for the given address
66
+ *
67
+ * @example
68
+ * ```typescript
69
+ * const sigDict = createSignatureDictionary({ signature, signerAddress });
70
+ * ```
71
+ */
72
+ export function createSignatureDictionary({
73
+ signature,
74
+ signerAddress,
75
+ }: CreateSignatureDictionaryArgs): SignatureDictionary {
76
+ assertIsAddress(signerAddress);
77
+ if (!signature) {
78
+ throwSignerError(SignerErrorCode.SIGNING_FAILED, {
79
+ address: signerAddress,
80
+ message: `No signature found for address ${signerAddress}`,
81
+ });
82
+ }
83
+ return Object.freeze({ [signerAddress]: signature });
84
+ }
85
+
86
+ /**
87
+ * Checks if the given value is a SolanaSigner.
88
+ * @param value - The value to check
89
+ * @returns True if the value is a SolanaSigner, false otherwise
90
+ */
91
+ export function isSolanaSigner<TAddress extends string>(value: {
92
+ address: Address<TAddress>;
93
+ }): value is SolanaSigner<TAddress> {
94
+ return (
95
+ 'address' in value &&
96
+ 'isAvailable' in value &&
97
+ isMessagePartialSigner(value) &&
98
+ isTransactionPartialSigner(value)
99
+ );
100
+ }
101
+
102
+ /**
103
+ * Asserts that the given value is a SolanaSigner, throwing an error if it is not.
104
+ * @param value - The value to check
105
+ * @throws {SignerError} If the value is not a SolanaSigner
106
+ */
107
+ export function assertIsSolanaSigner<TAddress extends string>(value: {
108
+ address: Address<TAddress>;
109
+ }): asserts value is SolanaSigner<TAddress> {
110
+ if (!isSolanaSigner(value)) {
111
+ throwSignerError(SignerErrorCode.EXPECTED_SOLANA_SIGNER, {
112
+ address: value.address,
113
+ });
114
+ }
115
+ }
package/index.js DELETED
@@ -1 +0,0 @@
1
- module.exports = {};