@solana/keychain 0.6.1 → 1.1.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.
@@ -0,0 +1,26 @@
1
+ import { type Address } from '@solana/addresses';
2
+ import type { KeychainSignerConfig } from './types.js';
3
+ /**
4
+ * Resolve the Solana address for a signer configuration without
5
+ * setting up the full signing pipeline.
6
+ *
7
+ * For backends that include the public key in their config (AWS KMS,
8
+ * CDP, GCP KMS, Turnkey, Vault), this returns the address directly
9
+ * with no network call. For backends that must fetch the public key
10
+ * from a remote API (Crossmint, Dfns, Fireblocks, Para, Privy),
11
+ * this initialises the signer and reads its address.
12
+ *
13
+ * @example
14
+ * ```typescript
15
+ * const address = await resolveAddress({
16
+ * backend: 'vault',
17
+ * vaultAddr: 'https://vault.example.com',
18
+ * vaultToken: 'hvs.xxx',
19
+ * keyName: 'my-key',
20
+ * publicKey: '4Nd1m...',
21
+ * });
22
+ * // Returns '4Nd1m...' immediately — no network call
23
+ * ```
24
+ */
25
+ export declare function resolveAddress(config: KeychainSignerConfig): Promise<Address>;
26
+ //# sourceMappingURL=resolve-address.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"resolve-address.d.ts","sourceRoot":"","sources":["../src/resolve-address.ts"],"names":[],"mappings":"AAAA,OAAO,EAAE,KAAK,OAAO,EAAmB,MAAM,mBAAmB,CAAC;AAIlE,OAAO,KAAK,EAAE,oBAAoB,EAAE,MAAM,YAAY,CAAC;AAEvD;;;;;;;;;;;;;;;;;;;;;GAqBG;AACH,wBAAsB,cAAc,CAAC,MAAM,EAAE,oBAAoB,GAAG,OAAO,CAAC,OAAO,CAAC,CAgCnF"}
@@ -0,0 +1,56 @@
1
+ import { assertIsAddress } from '@solana/addresses';
2
+ import { SignerErrorCode, throwSignerError } from '@solana/keychain-core';
3
+ import { createKeychainSigner } from './create-keychain-signer.js';
4
+ /**
5
+ * Resolve the Solana address for a signer configuration without
6
+ * setting up the full signing pipeline.
7
+ *
8
+ * For backends that include the public key in their config (AWS KMS,
9
+ * CDP, GCP KMS, Turnkey, Vault), this returns the address directly
10
+ * with no network call. For backends that must fetch the public key
11
+ * from a remote API (Crossmint, Dfns, Fireblocks, Para, Privy),
12
+ * this initialises the signer and reads its address.
13
+ *
14
+ * @example
15
+ * ```typescript
16
+ * const address = await resolveAddress({
17
+ * backend: 'vault',
18
+ * vaultAddr: 'https://vault.example.com',
19
+ * vaultToken: 'hvs.xxx',
20
+ * keyName: 'my-key',
21
+ * publicKey: '4Nd1m...',
22
+ * });
23
+ * // Returns '4Nd1m...' immediately — no network call
24
+ * ```
25
+ */
26
+ export async function resolveAddress(config) {
27
+ switch (config.backend) {
28
+ // Backends with publicKey in config — no network call needed
29
+ case 'aws-kms':
30
+ case 'gcp-kms':
31
+ case 'turnkey':
32
+ case 'vault':
33
+ assertIsAddress(config.publicKey);
34
+ return config.publicKey;
35
+ // CDP provides address directly in config
36
+ case 'cdp':
37
+ assertIsAddress(config.address);
38
+ return config.address;
39
+ // Backends that fetch the address from a remote API
40
+ case 'crossmint':
41
+ case 'dfns':
42
+ case 'fireblocks':
43
+ case 'para':
44
+ case 'privy': {
45
+ const signer = await createKeychainSigner(config);
46
+ return signer.address;
47
+ }
48
+ default: {
49
+ const _exhaustive = config;
50
+ return throwSignerError(SignerErrorCode.CONFIG_ERROR, {
51
+ message: `Unknown backend: ${String(_exhaustive.backend)}`,
52
+ });
53
+ }
54
+ }
55
+ }
56
+ //# sourceMappingURL=resolve-address.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"resolve-address.js","sourceRoot":"","sources":["../src/resolve-address.ts"],"names":[],"mappings":"AAAA,OAAO,EAAgB,eAAe,EAAE,MAAM,mBAAmB,CAAC;AAClE,OAAO,EAAE,eAAe,EAAE,gBAAgB,EAAE,MAAM,uBAAuB,CAAC;AAE1E,OAAO,EAAE,oBAAoB,EAAE,MAAM,6BAA6B,CAAC;AAGnE;;;;;;;;;;;;;;;;;;;;;GAqBG;AACH,MAAM,CAAC,KAAK,UAAU,cAAc,CAAC,MAA4B;IAC7D,QAAQ,MAAM,CAAC,OAAO,EAAE,CAAC;QACrB,6DAA6D;QAC7D,KAAK,SAAS,CAAC;QACf,KAAK,SAAS,CAAC;QACf,KAAK,SAAS,CAAC;QACf,KAAK,OAAO;YACR,eAAe,CAAC,MAAM,CAAC,SAAS,CAAC,CAAC;YAClC,OAAO,MAAM,CAAC,SAAS,CAAC;QAE5B,0CAA0C;QAC1C,KAAK,KAAK;YACN,eAAe,CAAC,MAAM,CAAC,OAAO,CAAC,CAAC;YAChC,OAAO,MAAM,CAAC,OAAO,CAAC;QAE1B,oDAAoD;QACpD,KAAK,WAAW,CAAC;QACjB,KAAK,MAAM,CAAC;QACZ,KAAK,YAAY,CAAC;QAClB,KAAK,MAAM,CAAC;QACZ,KAAK,OAAO,CAAC,CAAC,CAAC;YACX,MAAM,MAAM,GAAG,MAAM,oBAAoB,CAAC,MAAM,CAAC,CAAC;YAClD,OAAO,MAAM,CAAC,OAAO,CAAC;QAC1B,CAAC;QAED,OAAO,CAAC,CAAC,CAAC;YACN,MAAM,WAAW,GAAU,MAAM,CAAC;YAClC,OAAO,gBAAgB,CAAC,eAAe,CAAC,YAAY,EAAE;gBAClD,OAAO,EAAE,oBAAoB,MAAM,CAAE,WAAmC,CAAC,OAAO,CAAC,EAAE;aACtF,CAAC,CAAC;QACP,CAAC;IACL,CAAC;AACL,CAAC"}
@@ -0,0 +1,38 @@
1
+ import type { AwsKmsSignerConfig } from '@solana/keychain-aws-kms';
2
+ import type { CdpSignerConfig } from '@solana/keychain-cdp';
3
+ import type { CrossmintSignerConfig } from '@solana/keychain-crossmint';
4
+ import type { DfnsSignerConfig } from '@solana/keychain-dfns';
5
+ import type { FireblocksSignerConfig } from '@solana/keychain-fireblocks';
6
+ import type { GcpKmsSignerConfig } from '@solana/keychain-gcp-kms';
7
+ import type { ParaSignerConfig } from '@solana/keychain-para';
8
+ import type { PrivySignerConfig } from '@solana/keychain-privy';
9
+ import type { TurnkeySignerConfig } from '@solana/keychain-turnkey';
10
+ import type { VaultSignerConfig } from '@solana/keychain-vault';
11
+ /**
12
+ * Discriminated union of all signer backend configurations.
13
+ * The `backend` field narrows the type to the correct config shape.
14
+ */
15
+ export type KeychainSignerConfig = (AwsKmsSignerConfig & {
16
+ backend: 'aws-kms';
17
+ }) | (CdpSignerConfig & {
18
+ backend: 'cdp';
19
+ }) | (CrossmintSignerConfig & {
20
+ backend: 'crossmint';
21
+ }) | (DfnsSignerConfig & {
22
+ backend: 'dfns';
23
+ }) | (FireblocksSignerConfig & {
24
+ backend: 'fireblocks';
25
+ }) | (GcpKmsSignerConfig & {
26
+ backend: 'gcp-kms';
27
+ }) | (ParaSignerConfig & {
28
+ backend: 'para';
29
+ }) | (PrivySignerConfig & {
30
+ backend: 'privy';
31
+ }) | (TurnkeySignerConfig & {
32
+ backend: 'turnkey';
33
+ }) | (VaultSignerConfig & {
34
+ backend: 'vault';
35
+ });
36
+ /** String literal union of all supported backend names, derived from {@link KeychainSignerConfig}. */
37
+ export type BackendName = KeychainSignerConfig['backend'];
38
+ //# 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,kBAAkB,EAAE,MAAM,0BAA0B,CAAC;AACnE,OAAO,KAAK,EAAE,eAAe,EAAE,MAAM,sBAAsB,CAAC;AAC5D,OAAO,KAAK,EAAE,qBAAqB,EAAE,MAAM,4BAA4B,CAAC;AACxE,OAAO,KAAK,EAAE,gBAAgB,EAAE,MAAM,uBAAuB,CAAC;AAC9D,OAAO,KAAK,EAAE,sBAAsB,EAAE,MAAM,6BAA6B,CAAC;AAC1E,OAAO,KAAK,EAAE,kBAAkB,EAAE,MAAM,0BAA0B,CAAC;AACnE,OAAO,KAAK,EAAE,gBAAgB,EAAE,MAAM,uBAAuB,CAAC;AAC9D,OAAO,KAAK,EAAE,iBAAiB,EAAE,MAAM,wBAAwB,CAAC;AAChE,OAAO,KAAK,EAAE,mBAAmB,EAAE,MAAM,0BAA0B,CAAC;AACpE,OAAO,KAAK,EAAE,iBAAiB,EAAE,MAAM,wBAAwB,CAAC;AAEhE;;;GAGG;AACH,MAAM,MAAM,oBAAoB,GAC1B,CAAC,kBAAkB,GAAG;IAAE,OAAO,EAAE,SAAS,CAAA;CAAE,CAAC,GAC7C,CAAC,eAAe,GAAG;IAAE,OAAO,EAAE,KAAK,CAAA;CAAE,CAAC,GACtC,CAAC,qBAAqB,GAAG;IAAE,OAAO,EAAE,WAAW,CAAA;CAAE,CAAC,GAClD,CAAC,gBAAgB,GAAG;IAAE,OAAO,EAAE,MAAM,CAAA;CAAE,CAAC,GACxC,CAAC,sBAAsB,GAAG;IAAE,OAAO,EAAE,YAAY,CAAA;CAAE,CAAC,GACpD,CAAC,kBAAkB,GAAG;IAAE,OAAO,EAAE,SAAS,CAAA;CAAE,CAAC,GAC7C,CAAC,gBAAgB,GAAG;IAAE,OAAO,EAAE,MAAM,CAAA;CAAE,CAAC,GACxC,CAAC,iBAAiB,GAAG;IAAE,OAAO,EAAE,OAAO,CAAA;CAAE,CAAC,GAC1C,CAAC,mBAAmB,GAAG;IAAE,OAAO,EAAE,SAAS,CAAA;CAAE,CAAC,GAC9C,CAAC,iBAAiB,GAAG;IAAE,OAAO,EAAE,OAAO,CAAA;CAAE,CAAC,CAAC;AAEjD,sGAAsG;AACtG,MAAM,MAAM,WAAW,GAAG,oBAAoB,CAAC,SAAS,CAAC,CAAC"}
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":""}
package/package.json CHANGED
@@ -3,7 +3,7 @@
3
3
  "author": "Solana Foundation",
4
4
  "license": "MIT",
5
5
  "repository": "https://github.com/solana-foundation/solana-keychain",
6
- "version": "0.6.1",
6
+ "version": "1.1.0",
7
7
  "description": "Unified Solana transaction signing across multiple backends",
8
8
  "keywords": [
9
9
  "solana",
@@ -36,17 +36,18 @@
36
36
  "src"
37
37
  ],
38
38
  "dependencies": {
39
- "@solana/keychain-core": "0.6.1",
40
- "@solana/keychain-cdp": "0.6.1",
41
- "@solana/keychain-crossmint": "0.6.1",
42
- "@solana/keychain-aws-kms": "0.6.1",
43
- "@solana/keychain-dfns": "0.6.1",
44
- "@solana/keychain-fireblocks": "0.6.1",
45
- "@solana/keychain-para": "0.6.1",
46
- "@solana/keychain-privy": "0.6.1",
47
- "@solana/keychain-gcp-kms": "0.6.1",
48
- "@solana/keychain-turnkey": "0.6.1",
49
- "@solana/keychain-vault": "0.6.1"
39
+ "@solana/addresses": "^6.0.1",
40
+ "@solana/keychain-core": "1.1.0",
41
+ "@solana/keychain-cdp": "1.1.0",
42
+ "@solana/keychain-crossmint": "1.1.0",
43
+ "@solana/keychain-dfns": "1.1.0",
44
+ "@solana/keychain-aws-kms": "1.1.0",
45
+ "@solana/keychain-fireblocks": "1.1.0",
46
+ "@solana/keychain-para": "1.1.0",
47
+ "@solana/keychain-turnkey": "1.1.0",
48
+ "@solana/keychain-gcp-kms": "1.1.0",
49
+ "@solana/keychain-vault": "1.1.0",
50
+ "@solana/keychain-privy": "1.1.0"
50
51
  },
51
52
  "peerDependencies": {
52
53
  "@solana/addresses": ">=6.0.1",
@@ -62,7 +63,7 @@
62
63
  "scripts": {
63
64
  "build": "tsc --build",
64
65
  "clean": "rm -rf dist *.tsbuildinfo",
65
- "test": "pnpm run typecheck",
66
+ "test": "vitest run && pnpm run typecheck",
66
67
  "typecheck": "tsc --noEmit",
67
68
  "test:treeshakability": "agadoo dist/index.js"
68
69
  }
@@ -0,0 +1,154 @@
1
+ import type { Address } from '@solana/addresses';
2
+ import type { SolanaSigner } from '@solana/keychain-core';
3
+ import { SignerErrorCode } from '@solana/keychain-core';
4
+ import { beforeEach, describe, expect, it, vi } from 'vitest';
5
+
6
+ import { createKeychainSigner } from '../create-keychain-signer.js';
7
+ import type { KeychainSignerConfig } from '../types.js';
8
+
9
+ const TEST_ADDRESS = '11111111111111111111111111111111' as Address;
10
+
11
+ function makeMockSigner(address: Address = TEST_ADDRESS): SolanaSigner {
12
+ return {
13
+ address,
14
+ isAvailable: vi.fn().mockResolvedValue(true),
15
+ signMessages: vi.fn().mockResolvedValue([]),
16
+ signTransactions: vi.fn().mockResolvedValue([]),
17
+ } as unknown as SolanaSigner;
18
+ }
19
+
20
+ // Mock all backend factories
21
+ vi.mock('@solana/keychain-aws-kms', () => ({ createAwsKmsSigner: vi.fn() }));
22
+ vi.mock('@solana/keychain-cdp', () => ({ createCdpSigner: vi.fn() }));
23
+ vi.mock('@solana/keychain-crossmint', () => ({ createCrossmintSigner: vi.fn() }));
24
+ vi.mock('@solana/keychain-dfns', () => ({ createDfnsSigner: vi.fn() }));
25
+ vi.mock('@solana/keychain-fireblocks', () => ({ createFireblocksSigner: vi.fn() }));
26
+ vi.mock('@solana/keychain-gcp-kms', () => ({ createGcpKmsSigner: vi.fn() }));
27
+ vi.mock('@solana/keychain-para', () => ({ createParaSigner: vi.fn() }));
28
+ vi.mock('@solana/keychain-privy', () => ({ createPrivySigner: vi.fn() }));
29
+ vi.mock('@solana/keychain-turnkey', () => ({ createTurnkeySigner: vi.fn() }));
30
+ vi.mock('@solana/keychain-vault', () => ({ createVaultSigner: vi.fn() }));
31
+
32
+ // Table-driven test configs — one per backend
33
+ const BACKEND_CONFIGS: Record<string, { config: KeychainSignerConfig; modulePath: string; factoryName: string }> = {
34
+ 'aws-kms': {
35
+ config: { backend: 'aws-kms', keyId: 'key-1', publicKey: TEST_ADDRESS },
36
+ factoryName: 'createAwsKmsSigner',
37
+ modulePath: '@solana/keychain-aws-kms',
38
+ },
39
+ cdp: {
40
+ config: {
41
+ backend: 'cdp',
42
+ address: TEST_ADDRESS,
43
+ cdpApiKeyId: 'id',
44
+ cdpApiKeySecret: 's',
45
+ cdpWalletSecret: 'w',
46
+ },
47
+ factoryName: 'createCdpSigner',
48
+ modulePath: '@solana/keychain-cdp',
49
+ },
50
+ crossmint: {
51
+ config: { backend: 'crossmint', apiKey: 'key', walletLocator: 'loc' },
52
+ factoryName: 'createCrossmintSigner',
53
+ modulePath: '@solana/keychain-crossmint',
54
+ },
55
+ dfns: {
56
+ config: { backend: 'dfns', authToken: 'tok', credId: 'cred', privateKeyPem: 'pem', walletId: 'w' },
57
+ factoryName: 'createDfnsSigner',
58
+ modulePath: '@solana/keychain-dfns',
59
+ },
60
+ fireblocks: {
61
+ config: { backend: 'fireblocks', apiKey: 'key', privateKeyPem: 'pem', vaultAccountId: 'v' },
62
+ factoryName: 'createFireblocksSigner',
63
+ modulePath: '@solana/keychain-fireblocks',
64
+ },
65
+ 'gcp-kms': {
66
+ config: { backend: 'gcp-kms', keyName: 'key', publicKey: TEST_ADDRESS },
67
+ factoryName: 'createGcpKmsSigner',
68
+ modulePath: '@solana/keychain-gcp-kms',
69
+ },
70
+ para: {
71
+ config: { backend: 'para', apiKey: 'key', walletId: 'w' },
72
+ factoryName: 'createParaSigner',
73
+ modulePath: '@solana/keychain-para',
74
+ },
75
+ privy: {
76
+ config: { backend: 'privy', appId: 'app', appSecret: 'secret', walletId: 'w' },
77
+ factoryName: 'createPrivySigner',
78
+ modulePath: '@solana/keychain-privy',
79
+ },
80
+ turnkey: {
81
+ config: {
82
+ backend: 'turnkey',
83
+ apiPrivateKey: 'pk',
84
+ apiPublicKey: 'pub',
85
+ organizationId: 'org',
86
+ privateKeyId: 'pkid',
87
+ publicKey: TEST_ADDRESS,
88
+ },
89
+ factoryName: 'createTurnkeySigner',
90
+ modulePath: '@solana/keychain-turnkey',
91
+ },
92
+ vault: {
93
+ config: {
94
+ backend: 'vault',
95
+ keyName: 'key',
96
+ publicKey: TEST_ADDRESS,
97
+ vaultAddr: 'https://v',
98
+ vaultToken: 'tok',
99
+ },
100
+ factoryName: 'createVaultSigner',
101
+ modulePath: '@solana/keychain-vault',
102
+ },
103
+ };
104
+
105
+ describe('createKeychainSigner', () => {
106
+ beforeEach(() => {
107
+ vi.clearAllMocks();
108
+ });
109
+
110
+ describe.each(Object.entries(BACKEND_CONFIGS))('%s', (_backend, { config, modulePath, factoryName }) => {
111
+ it('dispatches to the correct factory', async () => {
112
+ const mockSigner = makeMockSigner();
113
+ const mod = await import(modulePath);
114
+ const factory = mod[factoryName] as ReturnType<typeof vi.fn>;
115
+ factory.mockResolvedValue(mockSigner);
116
+
117
+ const result = await createKeychainSigner(config);
118
+
119
+ expect(result).toBe(mockSigner);
120
+ expect(factory).toHaveBeenCalledOnce();
121
+ });
122
+
123
+ it('passes config without the backend field', async () => {
124
+ const mockSigner = makeMockSigner();
125
+ const mod = await import(modulePath);
126
+ const factory = mod[factoryName] as ReturnType<typeof vi.fn>;
127
+ factory.mockResolvedValue(mockSigner);
128
+
129
+ await createKeychainSigner(config);
130
+
131
+ const passedConfig = factory.mock.calls[0]![0] as Record<string, unknown>;
132
+ expect(passedConfig).not.toHaveProperty('backend');
133
+ });
134
+
135
+ it('propagates factory errors', async () => {
136
+ const mod = await import(modulePath);
137
+ const factory = mod[factoryName] as ReturnType<typeof vi.fn>;
138
+ factory.mockRejectedValue(new Error('factory error'));
139
+
140
+ await expect(createKeychainSigner(config)).rejects.toThrow('factory error');
141
+ });
142
+ });
143
+
144
+ it('throws SignerError for unknown backend', async () => {
145
+ const badConfig = { backend: 'nonexistent' } as unknown as KeychainSignerConfig;
146
+
147
+ try {
148
+ await createKeychainSigner(badConfig);
149
+ expect.unreachable('should have thrown');
150
+ } catch (error) {
151
+ expect((error as { code: string }).code).toBe(SignerErrorCode.CONFIG_ERROR);
152
+ }
153
+ });
154
+ });
@@ -0,0 +1,131 @@
1
+ import type { Address } from '@solana/addresses';
2
+ import type { SolanaSigner } from '@solana/keychain-core';
3
+ import { SignerErrorCode } from '@solana/keychain-core';
4
+ import { beforeEach, describe, expect, it, vi } from 'vitest';
5
+
6
+ import type { KeychainSignerConfig } from '../types.js';
7
+
8
+ const TEST_ADDRESS = '11111111111111111111111111111111' as Address;
9
+
10
+ function makeMockSigner(address: Address = TEST_ADDRESS): SolanaSigner {
11
+ return {
12
+ address,
13
+ isAvailable: vi.fn().mockResolvedValue(true),
14
+ signMessages: vi.fn().mockResolvedValue([]),
15
+ signTransactions: vi.fn().mockResolvedValue([]),
16
+ } as unknown as SolanaSigner;
17
+ }
18
+
19
+ // Mock createKeychainSigner so we don't pull in all backend deps
20
+ vi.mock('../create-keychain-signer.js', () => ({
21
+ createKeychainSigner: vi.fn().mockResolvedValue(makeMockSigner()),
22
+ }));
23
+
24
+ // Must import after mock setup
25
+ const { resolveAddress } = await import('../resolve-address.js');
26
+ const { createKeychainSigner } = await import('../create-keychain-signer.js');
27
+
28
+ describe('resolveAddress', () => {
29
+ beforeEach(() => {
30
+ vi.clearAllMocks();
31
+ });
32
+
33
+ describe('sync backends (publicKey in config)', () => {
34
+ it.each(['aws-kms', 'gcp-kms', 'turnkey', 'vault'] as const)('%s returns publicKey directly', async backend => {
35
+ const config = {
36
+ backend,
37
+ keyId: 'k',
38
+ keyName: 'k',
39
+ publicKey: TEST_ADDRESS,
40
+ vaultAddr: 'https://v',
41
+ vaultToken: 't',
42
+ apiPrivateKey: 'pk',
43
+ apiPublicKey: 'pub',
44
+ organizationId: 'org',
45
+ privateKeyId: 'pkid',
46
+ } as KeychainSignerConfig;
47
+
48
+ const address = await resolveAddress(config);
49
+
50
+ expect(address).toBe(TEST_ADDRESS);
51
+ expect(createKeychainSigner).not.toHaveBeenCalled();
52
+ });
53
+
54
+ it('throws for invalid publicKey', async () => {
55
+ const config = {
56
+ backend: 'vault' as const,
57
+ keyName: 'k',
58
+ publicKey: 'not-a-valid-address',
59
+ vaultAddr: 'https://v',
60
+ vaultToken: 't',
61
+ };
62
+
63
+ await expect(resolveAddress(config as KeychainSignerConfig)).rejects.toThrow();
64
+ });
65
+ });
66
+
67
+ describe('cdp (address in config)', () => {
68
+ it('returns address directly', async () => {
69
+ const config = {
70
+ backend: 'cdp' as const,
71
+ address: TEST_ADDRESS,
72
+ cdpApiKeyId: 'id',
73
+ cdpApiKeySecret: 's',
74
+ cdpWalletSecret: 'w',
75
+ };
76
+
77
+ const address = await resolveAddress(config as KeychainSignerConfig);
78
+
79
+ expect(address).toBe(TEST_ADDRESS);
80
+ expect(createKeychainSigner).not.toHaveBeenCalled();
81
+ });
82
+
83
+ it('throws for invalid address', async () => {
84
+ const config = {
85
+ backend: 'cdp' as const,
86
+ address: 'bad',
87
+ cdpApiKeyId: 'id',
88
+ cdpApiKeySecret: 's',
89
+ cdpWalletSecret: 'w',
90
+ };
91
+
92
+ await expect(resolveAddress(config as KeychainSignerConfig)).rejects.toThrow();
93
+ });
94
+ });
95
+
96
+ describe('async backends (fetch from API)', () => {
97
+ it.each(['crossmint', 'dfns', 'fireblocks', 'para', 'privy'] as const)(
98
+ '%s delegates to createKeychainSigner',
99
+ async backend => {
100
+ const config = {
101
+ backend,
102
+ apiKey: 'k',
103
+ appId: 'a',
104
+ appSecret: 's',
105
+ authToken: 't',
106
+ credId: 'c',
107
+ privateKeyPem: 'p',
108
+ vaultAccountId: 'v',
109
+ walletId: 'w',
110
+ walletLocator: 'l',
111
+ } as KeychainSignerConfig;
112
+
113
+ const address = await resolveAddress(config);
114
+
115
+ expect(address).toBe(TEST_ADDRESS);
116
+ expect(createKeychainSigner).toHaveBeenCalledWith(config);
117
+ },
118
+ );
119
+ });
120
+
121
+ it('throws SignerError for unknown backend', async () => {
122
+ const badConfig = { backend: 'nonexistent' } as unknown as KeychainSignerConfig;
123
+
124
+ try {
125
+ await resolveAddress(badConfig);
126
+ expect.unreachable('should have thrown');
127
+ } catch (error) {
128
+ expect((error as { code: string }).code).toBe(SignerErrorCode.CONFIG_ERROR);
129
+ }
130
+ });
131
+ });
@@ -0,0 +1,65 @@
1
+ import { createAwsKmsSigner } from '@solana/keychain-aws-kms';
2
+ import { createCdpSigner } from '@solana/keychain-cdp';
3
+ import type { SolanaSigner } from '@solana/keychain-core';
4
+ import { SignerErrorCode, throwSignerError } from '@solana/keychain-core';
5
+ import { createCrossmintSigner } from '@solana/keychain-crossmint';
6
+ import { createDfnsSigner } from '@solana/keychain-dfns';
7
+ import { createFireblocksSigner } from '@solana/keychain-fireblocks';
8
+ import { createGcpKmsSigner } from '@solana/keychain-gcp-kms';
9
+ import { createParaSigner } from '@solana/keychain-para';
10
+ import { createPrivySigner } from '@solana/keychain-privy';
11
+ import { createTurnkeySigner } from '@solana/keychain-turnkey';
12
+ import { createVaultSigner } from '@solana/keychain-vault';
13
+
14
+ import type { KeychainSignerConfig } from './types.js';
15
+
16
+ function stripBackend<T extends { backend: string }>({ backend: _, ...rest }: T): Omit<T, 'backend'> {
17
+ return rest;
18
+ }
19
+
20
+ /**
21
+ * Create a {@link SolanaSigner} from a backend-tagged configuration.
22
+ *
23
+ * Dispatches to the correct `createXxxSigner()` factory based on
24
+ * the `backend` discriminant.
25
+ *
26
+ * @example
27
+ * ```typescript
28
+ * const signer = await createKeychainSigner({
29
+ * backend: 'privy',
30
+ * appId: '...',
31
+ * appSecret: '...',
32
+ * walletId: '...',
33
+ * });
34
+ * ```
35
+ */
36
+ export async function createKeychainSigner(config: KeychainSignerConfig): Promise<SolanaSigner> {
37
+ switch (config.backend) {
38
+ case 'aws-kms':
39
+ return createAwsKmsSigner(stripBackend(config));
40
+ case 'cdp':
41
+ return await createCdpSigner(stripBackend(config));
42
+ case 'crossmint':
43
+ return await createCrossmintSigner(stripBackend(config));
44
+ case 'dfns':
45
+ return await createDfnsSigner(stripBackend(config));
46
+ case 'fireblocks':
47
+ return await createFireblocksSigner(stripBackend(config));
48
+ case 'gcp-kms':
49
+ return createGcpKmsSigner(stripBackend(config));
50
+ case 'para':
51
+ return await createParaSigner(stripBackend(config));
52
+ case 'privy':
53
+ return await createPrivySigner(stripBackend(config));
54
+ case 'turnkey':
55
+ return createTurnkeySigner(stripBackend(config));
56
+ case 'vault':
57
+ return createVaultSigner(stripBackend(config));
58
+ default: {
59
+ const _exhaustive: never = config;
60
+ return throwSignerError(SignerErrorCode.CONFIG_ERROR, {
61
+ message: `Unknown backend: ${String((_exhaustive as { backend: string }).backend)}`,
62
+ });
63
+ }
64
+ }
65
+ }
package/src/index.ts CHANGED
@@ -1,6 +1,23 @@
1
1
  // Core types and utilities (flat export)
2
2
  export * from '@solana/keychain-core';
3
3
 
4
+ // Unified signer factory, address resolver, and config union
5
+ export { createKeychainSigner } from './create-keychain-signer.js';
6
+ export { resolveAddress } from './resolve-address.js';
7
+ export type { BackendName, KeychainSignerConfig } from './types.js';
8
+
9
+ // Individual config types (flat re-exports)
10
+ export type { AwsKmsSignerConfig } from '@solana/keychain-aws-kms';
11
+ export type { CdpSignerConfig } from '@solana/keychain-cdp';
12
+ export type { CrossmintSignerConfig } from '@solana/keychain-crossmint';
13
+ export type { DfnsSignerConfig } from '@solana/keychain-dfns';
14
+ export type { FireblocksSignerConfig } from '@solana/keychain-fireblocks';
15
+ export type { GcpKmsSignerConfig } from '@solana/keychain-gcp-kms';
16
+ export type { ParaSignerConfig } from '@solana/keychain-para';
17
+ export type { PrivySignerConfig } from '@solana/keychain-privy';
18
+ export type { TurnkeySignerConfig } from '@solana/keychain-turnkey';
19
+ export type { VaultSignerConfig } from '@solana/keychain-vault';
20
+
4
21
  // Signer implementations (namespaced to avoid conflicts)
5
22
  export * as awsKms from '@solana/keychain-aws-kms';
6
23
  export * as cdp from '@solana/keychain-cdp';
@@ -0,0 +1,61 @@
1
+ import { type Address, assertIsAddress } from '@solana/addresses';
2
+ import { SignerErrorCode, throwSignerError } from '@solana/keychain-core';
3
+
4
+ import { createKeychainSigner } from './create-keychain-signer.js';
5
+ import type { KeychainSignerConfig } from './types.js';
6
+
7
+ /**
8
+ * Resolve the Solana address for a signer configuration without
9
+ * setting up the full signing pipeline.
10
+ *
11
+ * For backends that include the public key in their config (AWS KMS,
12
+ * CDP, GCP KMS, Turnkey, Vault), this returns the address directly
13
+ * with no network call. For backends that must fetch the public key
14
+ * from a remote API (Crossmint, Dfns, Fireblocks, Para, Privy),
15
+ * this initialises the signer and reads its address.
16
+ *
17
+ * @example
18
+ * ```typescript
19
+ * const address = await resolveAddress({
20
+ * backend: 'vault',
21
+ * vaultAddr: 'https://vault.example.com',
22
+ * vaultToken: 'hvs.xxx',
23
+ * keyName: 'my-key',
24
+ * publicKey: '4Nd1m...',
25
+ * });
26
+ * // Returns '4Nd1m...' immediately — no network call
27
+ * ```
28
+ */
29
+ export async function resolveAddress(config: KeychainSignerConfig): Promise<Address> {
30
+ switch (config.backend) {
31
+ // Backends with publicKey in config — no network call needed
32
+ case 'aws-kms':
33
+ case 'gcp-kms':
34
+ case 'turnkey':
35
+ case 'vault':
36
+ assertIsAddress(config.publicKey);
37
+ return config.publicKey;
38
+
39
+ // CDP provides address directly in config
40
+ case 'cdp':
41
+ assertIsAddress(config.address);
42
+ return config.address;
43
+
44
+ // Backends that fetch the address from a remote API
45
+ case 'crossmint':
46
+ case 'dfns':
47
+ case 'fireblocks':
48
+ case 'para':
49
+ case 'privy': {
50
+ const signer = await createKeychainSigner(config);
51
+ return signer.address;
52
+ }
53
+
54
+ default: {
55
+ const _exhaustive: never = config;
56
+ return throwSignerError(SignerErrorCode.CONFIG_ERROR, {
57
+ message: `Unknown backend: ${String((_exhaustive as { backend: string }).backend)}`,
58
+ });
59
+ }
60
+ }
61
+ }
package/src/types.ts ADDED
@@ -0,0 +1,29 @@
1
+ import type { AwsKmsSignerConfig } from '@solana/keychain-aws-kms';
2
+ import type { CdpSignerConfig } from '@solana/keychain-cdp';
3
+ import type { CrossmintSignerConfig } from '@solana/keychain-crossmint';
4
+ import type { DfnsSignerConfig } from '@solana/keychain-dfns';
5
+ import type { FireblocksSignerConfig } from '@solana/keychain-fireblocks';
6
+ import type { GcpKmsSignerConfig } from '@solana/keychain-gcp-kms';
7
+ import type { ParaSignerConfig } from '@solana/keychain-para';
8
+ import type { PrivySignerConfig } from '@solana/keychain-privy';
9
+ import type { TurnkeySignerConfig } from '@solana/keychain-turnkey';
10
+ import type { VaultSignerConfig } from '@solana/keychain-vault';
11
+
12
+ /**
13
+ * Discriminated union of all signer backend configurations.
14
+ * The `backend` field narrows the type to the correct config shape.
15
+ */
16
+ export type KeychainSignerConfig =
17
+ | (AwsKmsSignerConfig & { backend: 'aws-kms' })
18
+ | (CdpSignerConfig & { backend: 'cdp' })
19
+ | (CrossmintSignerConfig & { backend: 'crossmint' })
20
+ | (DfnsSignerConfig & { backend: 'dfns' })
21
+ | (FireblocksSignerConfig & { backend: 'fireblocks' })
22
+ | (GcpKmsSignerConfig & { backend: 'gcp-kms' })
23
+ | (ParaSignerConfig & { backend: 'para' })
24
+ | (PrivySignerConfig & { backend: 'privy' })
25
+ | (TurnkeySignerConfig & { backend: 'turnkey' })
26
+ | (VaultSignerConfig & { backend: 'vault' });
27
+
28
+ /** String literal union of all supported backend names, derived from {@link KeychainSignerConfig}. */
29
+ export type BackendName = KeychainSignerConfig['backend'];