@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.
- package/README.md +103 -51
- package/dist/__tests__/create-keychain-signer.test.d.ts +2 -0
- package/dist/__tests__/create-keychain-signer.test.d.ts.map +1 -0
- package/dist/__tests__/create-keychain-signer.test.js +137 -0
- package/dist/__tests__/create-keychain-signer.test.js.map +1 -0
- package/dist/__tests__/resolve-address.test.d.ts +2 -0
- package/dist/__tests__/resolve-address.test.d.ts.map +1 -0
- package/dist/__tests__/resolve-address.test.js +106 -0
- package/dist/__tests__/resolve-address.test.js.map +1 -0
- package/dist/create-keychain-signer.d.ts +20 -0
- package/dist/create-keychain-signer.d.ts.map +1 -0
- package/dist/create-keychain-signer.js +61 -0
- package/dist/create-keychain-signer.js.map +1 -0
- package/dist/index.d.ts +13 -0
- package/dist/index.d.ts.map +1 -1
- package/dist/index.js +3 -0
- package/dist/index.js.map +1 -1
- package/dist/resolve-address.d.ts +26 -0
- package/dist/resolve-address.d.ts.map +1 -0
- package/dist/resolve-address.js +56 -0
- package/dist/resolve-address.js.map +1 -0
- package/dist/types.d.ts +38 -0
- package/dist/types.d.ts.map +1 -0
- package/dist/types.js +2 -0
- package/dist/types.js.map +1 -0
- package/package.json +14 -13
- package/src/__tests__/create-keychain-signer.test.ts +154 -0
- package/src/__tests__/resolve-address.test.ts +131 -0
- package/src/create-keychain-signer.ts +65 -0
- package/src/index.ts +17 -0
- package/src/resolve-address.ts +61 -0
- package/src/types.ts +29 -0
|
@@ -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"}
|
package/dist/types.d.ts
ADDED
|
@@ -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 @@
|
|
|
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": "
|
|
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/
|
|
40
|
-
"@solana/keychain-
|
|
41
|
-
"@solana/keychain-
|
|
42
|
-
"@solana/keychain-
|
|
43
|
-
"@solana/keychain-dfns": "
|
|
44
|
-
"@solana/keychain-
|
|
45
|
-
"@solana/keychain-
|
|
46
|
-
"@solana/keychain-
|
|
47
|
-
"@solana/keychain-
|
|
48
|
-
"@solana/keychain-
|
|
49
|
-
"@solana/keychain-vault": "
|
|
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'];
|