@solana/keychain-dfns 0.0.0 → 0.6.1
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 +136 -0
- package/dist/__tests__/dfns-signer.integration.test.d.ts +2 -0
- package/dist/__tests__/dfns-signer.integration.test.d.ts.map +1 -0
- package/dist/__tests__/dfns-signer.integration.test.js +17 -0
- package/dist/__tests__/dfns-signer.integration.test.js.map +1 -0
- package/dist/__tests__/dfns-signer.test.d.ts +2 -0
- package/dist/__tests__/dfns-signer.test.d.ts.map +1 -0
- package/dist/__tests__/dfns-signer.test.js +157 -0
- package/dist/__tests__/dfns-signer.test.js.map +1 -0
- package/dist/__tests__/setup.d.ts +45 -0
- package/dist/__tests__/setup.d.ts.map +1 -0
- package/dist/__tests__/setup.js +64 -0
- package/dist/__tests__/setup.js.map +1 -0
- package/dist/auth.d.ts +7 -0
- package/dist/auth.d.ts.map +1 -0
- package/dist/auth.js +117 -0
- package/dist/auth.js.map +1 -0
- package/dist/dfns-signer.d.ts +65 -0
- package/dist/dfns-signer.d.ts.map +1 -0
- package/dist/dfns-signer.js +331 -0
- package/dist/dfns-signer.js.map +1 -0
- package/dist/index.d.ts +3 -0
- package/dist/index.d.ts.map +1 -0
- package/dist/index.js +2 -0
- package/dist/index.js.map +1 -0
- package/dist/types.d.ts +101 -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 +61 -8
- package/src/__tests__/dfns-signer.integration.test.ts +17 -0
- package/src/__tests__/dfns-signer.test.ts +217 -0
- package/src/__tests__/setup.ts +76 -0
- package/src/auth.ts +136 -0
- package/src/dfns-signer.ts +421 -0
- package/src/index.ts +2 -0
- package/src/types.ts +113 -0
|
@@ -0,0 +1,217 @@
|
|
|
1
|
+
import { describe, it, expect, vi, beforeEach } from 'vitest';
|
|
2
|
+
|
|
3
|
+
vi.mock('@solana/keychain-core', async importOriginal => {
|
|
4
|
+
const mod = await importOriginal<typeof import('@solana/keychain-core')>();
|
|
5
|
+
return { ...mod, assertSignatureValid: vi.fn() };
|
|
6
|
+
});
|
|
7
|
+
|
|
8
|
+
import { DfnsSigner } from '../dfns-signer.js';
|
|
9
|
+
import {
|
|
10
|
+
TEST_AUTH_TOKEN,
|
|
11
|
+
TEST_CRED_ID,
|
|
12
|
+
TEST_ED25519_PEM,
|
|
13
|
+
TEST_WALLET_ID,
|
|
14
|
+
createSignatureResponse,
|
|
15
|
+
createUserActionInitResponse,
|
|
16
|
+
createUserActionResponse,
|
|
17
|
+
createWalletResponse,
|
|
18
|
+
} from './setup.js';
|
|
19
|
+
|
|
20
|
+
global.fetch = vi.fn();
|
|
21
|
+
const mockFetch = global.fetch as ReturnType<typeof vi.fn>;
|
|
22
|
+
|
|
23
|
+
const defaultConfig = {
|
|
24
|
+
authToken: TEST_AUTH_TOKEN,
|
|
25
|
+
credId: TEST_CRED_ID,
|
|
26
|
+
privateKeyPem: TEST_ED25519_PEM,
|
|
27
|
+
walletId: TEST_WALLET_ID,
|
|
28
|
+
};
|
|
29
|
+
|
|
30
|
+
function mockWalletFetch(overrides?: Parameters<typeof createWalletResponse>[0]) {
|
|
31
|
+
mockFetch.mockResolvedValueOnce({
|
|
32
|
+
json: async () => createWalletResponse(overrides),
|
|
33
|
+
ok: true,
|
|
34
|
+
});
|
|
35
|
+
}
|
|
36
|
+
|
|
37
|
+
describe('DfnsSigner', () => {
|
|
38
|
+
beforeEach(() => {
|
|
39
|
+
vi.resetAllMocks();
|
|
40
|
+
});
|
|
41
|
+
|
|
42
|
+
describe('create', () => {
|
|
43
|
+
it('creates a DfnsSigner with valid config', async () => {
|
|
44
|
+
mockWalletFetch();
|
|
45
|
+
const signer = await DfnsSigner.create(defaultConfig);
|
|
46
|
+
expect(signer).toBeDefined();
|
|
47
|
+
expect(signer.address).toBeDefined();
|
|
48
|
+
});
|
|
49
|
+
|
|
50
|
+
it('throws error for missing authToken', async () => {
|
|
51
|
+
await expect(DfnsSigner.create({ ...defaultConfig, authToken: '' })).rejects.toThrow(
|
|
52
|
+
'Missing required authToken field',
|
|
53
|
+
);
|
|
54
|
+
});
|
|
55
|
+
|
|
56
|
+
it('throws error for missing credId', async () => {
|
|
57
|
+
await expect(DfnsSigner.create({ ...defaultConfig, credId: '' })).rejects.toThrow(
|
|
58
|
+
'Missing required credId field',
|
|
59
|
+
);
|
|
60
|
+
});
|
|
61
|
+
|
|
62
|
+
it('throws error for missing privateKeyPem', async () => {
|
|
63
|
+
await expect(DfnsSigner.create({ ...defaultConfig, privateKeyPem: '' })).rejects.toThrow(
|
|
64
|
+
'Missing required privateKeyPem field',
|
|
65
|
+
);
|
|
66
|
+
});
|
|
67
|
+
|
|
68
|
+
it('throws error for missing walletId', async () => {
|
|
69
|
+
await expect(DfnsSigner.create({ ...defaultConfig, walletId: '' })).rejects.toThrow(
|
|
70
|
+
'Missing required walletId field',
|
|
71
|
+
);
|
|
72
|
+
});
|
|
73
|
+
|
|
74
|
+
it('throws error for inactive wallet', async () => {
|
|
75
|
+
mockWalletFetch({ status: 'Inactive' });
|
|
76
|
+
await expect(DfnsSigner.create(defaultConfig)).rejects.toThrow('not active');
|
|
77
|
+
});
|
|
78
|
+
|
|
79
|
+
it('throws error for non-EdDSA scheme', async () => {
|
|
80
|
+
mockWalletFetch({ scheme: 'ECDSA' });
|
|
81
|
+
await expect(DfnsSigner.create(defaultConfig)).rejects.toThrow('Unsupported key scheme');
|
|
82
|
+
});
|
|
83
|
+
|
|
84
|
+
it('throws error for API failure', async () => {
|
|
85
|
+
mockFetch.mockResolvedValueOnce({
|
|
86
|
+
ok: false,
|
|
87
|
+
status: 401,
|
|
88
|
+
});
|
|
89
|
+
await expect(DfnsSigner.create(defaultConfig)).rejects.toThrow();
|
|
90
|
+
});
|
|
91
|
+
|
|
92
|
+
it('throws error for negative requestDelayMs', async () => {
|
|
93
|
+
await expect(DfnsSigner.create({ ...defaultConfig, requestDelayMs: -1 })).rejects.toThrow(
|
|
94
|
+
'requestDelayMs must not be negative',
|
|
95
|
+
);
|
|
96
|
+
});
|
|
97
|
+
|
|
98
|
+
it('warns for high requestDelayMs', async () => {
|
|
99
|
+
const warnSpy = vi.spyOn(console, 'warn').mockImplementation(() => {});
|
|
100
|
+
mockWalletFetch();
|
|
101
|
+
await DfnsSigner.create({ ...defaultConfig, requestDelayMs: 5000 });
|
|
102
|
+
expect(warnSpy).toHaveBeenCalledWith(expect.stringContaining('requestDelayMs is greater than 3000ms'));
|
|
103
|
+
warnSpy.mockRestore();
|
|
104
|
+
});
|
|
105
|
+
|
|
106
|
+
it('throws HTTP_ERROR when fetch fails during create', async () => {
|
|
107
|
+
mockFetch.mockRejectedValueOnce(new Error('Network timeout'));
|
|
108
|
+
await expect(DfnsSigner.create(defaultConfig)).rejects.toMatchObject({
|
|
109
|
+
code: 'SIGNER_HTTP_ERROR',
|
|
110
|
+
message: expect.stringContaining('Dfns network request failed'),
|
|
111
|
+
});
|
|
112
|
+
});
|
|
113
|
+
});
|
|
114
|
+
|
|
115
|
+
describe('signMessages', () => {
|
|
116
|
+
it('throws HTTP_ERROR when fetch fails during signing', async () => {
|
|
117
|
+
mockWalletFetch();
|
|
118
|
+
const signer = await DfnsSigner.create(defaultConfig);
|
|
119
|
+
|
|
120
|
+
// The auth flow init request fails with network error
|
|
121
|
+
mockFetch.mockRejectedValueOnce(new Error('Network timeout'));
|
|
122
|
+
|
|
123
|
+
await expect(
|
|
124
|
+
signer.signMessages([{ content: new Uint8Array([1, 2, 3]), signatures: {} }]),
|
|
125
|
+
).rejects.toMatchObject({
|
|
126
|
+
code: 'SIGNER_HTTP_ERROR',
|
|
127
|
+
message: expect.stringContaining('Dfns network request failed'),
|
|
128
|
+
});
|
|
129
|
+
});
|
|
130
|
+
|
|
131
|
+
it('signs a message successfully', async () => {
|
|
132
|
+
const rHex = '11'.repeat(32);
|
|
133
|
+
const sHex = '22'.repeat(32);
|
|
134
|
+
|
|
135
|
+
mockWalletFetch();
|
|
136
|
+
|
|
137
|
+
mockFetch.mockResolvedValueOnce({
|
|
138
|
+
json: async () => createUserActionInitResponse(),
|
|
139
|
+
ok: true,
|
|
140
|
+
});
|
|
141
|
+
|
|
142
|
+
mockFetch.mockResolvedValueOnce({
|
|
143
|
+
json: async () => createUserActionResponse(),
|
|
144
|
+
ok: true,
|
|
145
|
+
});
|
|
146
|
+
|
|
147
|
+
mockFetch.mockResolvedValueOnce({
|
|
148
|
+
json: async () => createSignatureResponse(rHex, sHex),
|
|
149
|
+
ok: true,
|
|
150
|
+
});
|
|
151
|
+
|
|
152
|
+
const signer = await DfnsSigner.create(defaultConfig);
|
|
153
|
+
|
|
154
|
+
const result = await signer.signMessages([{ content: new Uint8Array([1, 2, 3]), signatures: {} }]);
|
|
155
|
+
|
|
156
|
+
expect(result).toHaveLength(1);
|
|
157
|
+
expect(result[0]?.[signer.address]).toBeDefined();
|
|
158
|
+
|
|
159
|
+
const sig = result[0]![signer.address]!;
|
|
160
|
+
expect(sig.length).toBe(64);
|
|
161
|
+
});
|
|
162
|
+
|
|
163
|
+
it('left-pads short signature components', async () => {
|
|
164
|
+
// r is 31 bytes (short by 1), s is 32 bytes
|
|
165
|
+
const rHex = 'ff'.repeat(31);
|
|
166
|
+
const sHex = 'aa'.repeat(32);
|
|
167
|
+
|
|
168
|
+
mockWalletFetch();
|
|
169
|
+
|
|
170
|
+
mockFetch.mockResolvedValueOnce({
|
|
171
|
+
json: async () => createUserActionInitResponse(),
|
|
172
|
+
ok: true,
|
|
173
|
+
});
|
|
174
|
+
|
|
175
|
+
mockFetch.mockResolvedValueOnce({
|
|
176
|
+
json: async () => createUserActionResponse(),
|
|
177
|
+
ok: true,
|
|
178
|
+
});
|
|
179
|
+
|
|
180
|
+
mockFetch.mockResolvedValueOnce({
|
|
181
|
+
json: async () => createSignatureResponse(rHex, sHex),
|
|
182
|
+
ok: true,
|
|
183
|
+
});
|
|
184
|
+
|
|
185
|
+
const signer = await DfnsSigner.create(defaultConfig);
|
|
186
|
+
|
|
187
|
+
const result = await signer.signMessages([{ content: new Uint8Array([1, 2, 3]), signatures: {} }]);
|
|
188
|
+
|
|
189
|
+
const sig = result[0]![signer.address]!;
|
|
190
|
+
expect(sig.length).toBe(64);
|
|
191
|
+
// First byte should be 0x00 (left-pad), then 31 bytes of 0xff
|
|
192
|
+
expect(sig[0]).toBe(0x00);
|
|
193
|
+
expect(sig[1]).toBe(0xff);
|
|
194
|
+
});
|
|
195
|
+
});
|
|
196
|
+
|
|
197
|
+
describe('isAvailable', () => {
|
|
198
|
+
it('returns true when API responds', async () => {
|
|
199
|
+
mockWalletFetch();
|
|
200
|
+
// isAvailable doesn't need create(), but we need a signer instance
|
|
201
|
+
mockWalletFetch(); // for the isAvailable call
|
|
202
|
+
const signer = await DfnsSigner.create(defaultConfig);
|
|
203
|
+
expect(await signer.isAvailable()).toBe(true);
|
|
204
|
+
});
|
|
205
|
+
|
|
206
|
+
it('returns false when API fails', async () => {
|
|
207
|
+
mockWalletFetch(); // for create()
|
|
208
|
+
const signer = await DfnsSigner.create(defaultConfig);
|
|
209
|
+
|
|
210
|
+
mockFetch.mockResolvedValueOnce({
|
|
211
|
+
ok: false,
|
|
212
|
+
status: 500,
|
|
213
|
+
});
|
|
214
|
+
expect(await signer.isAvailable()).toBe(false);
|
|
215
|
+
});
|
|
216
|
+
});
|
|
217
|
+
});
|
|
@@ -0,0 +1,76 @@
|
|
|
1
|
+
import type { SolanaSigner } from '@solana/keychain-core';
|
|
2
|
+
import { SignerTestConfig, TestScenario } from '@solana/keychain-test-utils';
|
|
3
|
+
import { createDfnsSigner } from '../dfns-signer.js';
|
|
4
|
+
|
|
5
|
+
export const TEST_AUTH_TOKEN = 'test-auth-token';
|
|
6
|
+
export const TEST_CRED_ID = 'test-cred-id';
|
|
7
|
+
export const TEST_WALLET_ID = 'test-wallet-id';
|
|
8
|
+
export const TEST_KEY_ID = 'test-key-id';
|
|
9
|
+
|
|
10
|
+
// Ed25519 test key in PKCS#8 PEM format
|
|
11
|
+
export const TEST_ED25519_PEM = `-----BEGIN PRIVATE KEY-----
|
|
12
|
+
MC4CAQAwBQYDK2VwBCIEIJ+DYvh6SEqVTm50DFtMDoQikUmifl1yiWd+IiYyoHBD
|
|
13
|
+
-----END PRIVATE KEY-----`;
|
|
14
|
+
|
|
15
|
+
export const TEST_PUBKEY_HEX = '5da30b28c87836b0ee76ae7b07e3a2e3be1a4c12e48fce3aee18de0a13040b9a';
|
|
16
|
+
|
|
17
|
+
export function createWalletResponse(overrides?: Partial<{ status: string; scheme: string; curve: string }>) {
|
|
18
|
+
return {
|
|
19
|
+
id: TEST_WALLET_ID,
|
|
20
|
+
network: 'Solana',
|
|
21
|
+
signingKey: {
|
|
22
|
+
id: TEST_KEY_ID,
|
|
23
|
+
curve: overrides?.curve ?? 'ed25519',
|
|
24
|
+
publicKey: TEST_PUBKEY_HEX,
|
|
25
|
+
scheme: overrides?.scheme ?? 'EdDSA',
|
|
26
|
+
},
|
|
27
|
+
status: overrides?.status ?? 'Active',
|
|
28
|
+
};
|
|
29
|
+
}
|
|
30
|
+
|
|
31
|
+
export function createUserActionInitResponse() {
|
|
32
|
+
return {
|
|
33
|
+
allowCredentials: {
|
|
34
|
+
key: [{ id: TEST_CRED_ID }],
|
|
35
|
+
},
|
|
36
|
+
challenge: 'test-challenge',
|
|
37
|
+
challengeIdentifier: 'test-challenge-id',
|
|
38
|
+
};
|
|
39
|
+
}
|
|
40
|
+
|
|
41
|
+
export function createUserActionResponse() {
|
|
42
|
+
return {
|
|
43
|
+
userAction: 'test-user-action-token',
|
|
44
|
+
};
|
|
45
|
+
}
|
|
46
|
+
|
|
47
|
+
export function createSignatureResponse(r: string, s: string) {
|
|
48
|
+
return {
|
|
49
|
+
id: 'sig-123',
|
|
50
|
+
signature: { r, s },
|
|
51
|
+
status: 'Signed',
|
|
52
|
+
};
|
|
53
|
+
}
|
|
54
|
+
|
|
55
|
+
const SIGNER_TYPE = 'dfns';
|
|
56
|
+
const REQUIRED_ENV_VARS = ['DFNS_AUTH_TOKEN', 'DFNS_CRED_ID', 'DFNS_PRIVATE_KEY_PEM', 'DFNS_WALLET_ID'];
|
|
57
|
+
|
|
58
|
+
const CONFIG: SignerTestConfig<SolanaSigner> = {
|
|
59
|
+
signerType: SIGNER_TYPE,
|
|
60
|
+
requiredEnvVars: REQUIRED_ENV_VARS,
|
|
61
|
+
createSigner: () =>
|
|
62
|
+
createDfnsSigner({
|
|
63
|
+
authToken: process.env.DFNS_AUTH_TOKEN!,
|
|
64
|
+
credId: process.env.DFNS_CRED_ID!,
|
|
65
|
+
privateKeyPem: process.env.DFNS_PRIVATE_KEY_PEM!,
|
|
66
|
+
walletId: process.env.DFNS_WALLET_ID!,
|
|
67
|
+
apiBaseUrl: process.env.DFNS_API_BASE_URL,
|
|
68
|
+
}),
|
|
69
|
+
};
|
|
70
|
+
|
|
71
|
+
export async function getConfig(scenarios: TestScenario[]): Promise<SignerTestConfig<SolanaSigner>> {
|
|
72
|
+
return {
|
|
73
|
+
...CONFIG,
|
|
74
|
+
testScenarios: scenarios,
|
|
75
|
+
};
|
|
76
|
+
}
|
package/src/auth.ts
ADDED
|
@@ -0,0 +1,136 @@
|
|
|
1
|
+
import * as crypto from 'node:crypto';
|
|
2
|
+
|
|
3
|
+
import { base64UrlDecoder, SignerErrorCode, throwSignerError } from '@solana/keychain-core';
|
|
4
|
+
|
|
5
|
+
import type { UserActionInitResponse, UserActionResponse } from './types.js';
|
|
6
|
+
|
|
7
|
+
/**
|
|
8
|
+
* Perform the Dfns User Action Signing flow. For more details, see https://docs.dfns.co/api-reference/auth/signing-flows#asymetric-keys-signing-flow
|
|
9
|
+
*
|
|
10
|
+
* @returns The `userAction` token to include as `x-dfns-useraction` header.
|
|
11
|
+
*/
|
|
12
|
+
export async function signUserAction(
|
|
13
|
+
apiBaseUrl: string,
|
|
14
|
+
authToken: string,
|
|
15
|
+
credId: string,
|
|
16
|
+
privateKeyPem: string,
|
|
17
|
+
httpMethod: string,
|
|
18
|
+
httpPath: string,
|
|
19
|
+
body: string,
|
|
20
|
+
): Promise<string> {
|
|
21
|
+
// Request a challenge
|
|
22
|
+
const initUrl = `${apiBaseUrl}/auth/action/init`;
|
|
23
|
+
let initResponse: Response;
|
|
24
|
+
try {
|
|
25
|
+
initResponse = await fetch(initUrl, {
|
|
26
|
+
body: JSON.stringify({
|
|
27
|
+
userActionHttpMethod: httpMethod,
|
|
28
|
+
userActionHttpPath: httpPath,
|
|
29
|
+
userActionPayload: body,
|
|
30
|
+
userActionServerKind: 'Api',
|
|
31
|
+
}),
|
|
32
|
+
headers: {
|
|
33
|
+
Authorization: `Bearer ${authToken}`,
|
|
34
|
+
'Content-Type': 'application/json',
|
|
35
|
+
},
|
|
36
|
+
method: 'POST',
|
|
37
|
+
});
|
|
38
|
+
} catch (error) {
|
|
39
|
+
throwSignerError(SignerErrorCode.HTTP_ERROR, {
|
|
40
|
+
cause: error,
|
|
41
|
+
message: 'Dfns network request failed',
|
|
42
|
+
url: initUrl,
|
|
43
|
+
});
|
|
44
|
+
}
|
|
45
|
+
|
|
46
|
+
if (!initResponse.ok) {
|
|
47
|
+
const errorText = await initResponse.text().catch(() => 'Failed to read error response');
|
|
48
|
+
throwSignerError(SignerErrorCode.REMOTE_API_ERROR, {
|
|
49
|
+
message: `Dfns auth/action/init failed: ${initResponse.status}`,
|
|
50
|
+
response: errorText,
|
|
51
|
+
status: initResponse.status,
|
|
52
|
+
});
|
|
53
|
+
}
|
|
54
|
+
|
|
55
|
+
let challenge: UserActionInitResponse;
|
|
56
|
+
try {
|
|
57
|
+
challenge = (await initResponse.json()) as UserActionInitResponse;
|
|
58
|
+
} catch (error) {
|
|
59
|
+
throwSignerError(SignerErrorCode.PARSING_ERROR, {
|
|
60
|
+
cause: error,
|
|
61
|
+
message: 'Failed to parse Dfns auth challenge response',
|
|
62
|
+
});
|
|
63
|
+
}
|
|
64
|
+
|
|
65
|
+
// Verify credential is allowed
|
|
66
|
+
const allowed = challenge.allowCredentials.key.some(c => c.id === credId);
|
|
67
|
+
if (!allowed) {
|
|
68
|
+
throwSignerError(SignerErrorCode.CONFIG_ERROR, {
|
|
69
|
+
message: `Credential ${credId} not in allowed credentials`,
|
|
70
|
+
});
|
|
71
|
+
}
|
|
72
|
+
|
|
73
|
+
// Sign the challenge
|
|
74
|
+
const clientData = new TextEncoder().encode(
|
|
75
|
+
JSON.stringify({
|
|
76
|
+
challenge: challenge.challenge,
|
|
77
|
+
type: 'key.get',
|
|
78
|
+
}),
|
|
79
|
+
);
|
|
80
|
+
|
|
81
|
+
const signature = crypto.sign(undefined, clientData, privateKeyPem);
|
|
82
|
+
|
|
83
|
+
const clientDataB64 = base64UrlDecoder(clientData);
|
|
84
|
+
const signatureB64 = base64UrlDecoder(new Uint8Array(signature));
|
|
85
|
+
|
|
86
|
+
// Submit the signed challenge
|
|
87
|
+
const actionUrl = `${apiBaseUrl}/auth/action`;
|
|
88
|
+
let signResponse: Response;
|
|
89
|
+
try {
|
|
90
|
+
signResponse = await fetch(actionUrl, {
|
|
91
|
+
body: JSON.stringify({
|
|
92
|
+
challengeIdentifier: challenge.challengeIdentifier,
|
|
93
|
+
firstFactor: {
|
|
94
|
+
credentialAssertion: {
|
|
95
|
+
clientData: clientDataB64,
|
|
96
|
+
credId,
|
|
97
|
+
signature: signatureB64,
|
|
98
|
+
},
|
|
99
|
+
kind: 'Key',
|
|
100
|
+
},
|
|
101
|
+
}),
|
|
102
|
+
headers: {
|
|
103
|
+
Authorization: `Bearer ${authToken}`,
|
|
104
|
+
'Content-Type': 'application/json',
|
|
105
|
+
},
|
|
106
|
+
method: 'POST',
|
|
107
|
+
});
|
|
108
|
+
} catch (error) {
|
|
109
|
+
throwSignerError(SignerErrorCode.HTTP_ERROR, {
|
|
110
|
+
cause: error,
|
|
111
|
+
message: 'Dfns network request failed',
|
|
112
|
+
url: actionUrl,
|
|
113
|
+
});
|
|
114
|
+
}
|
|
115
|
+
|
|
116
|
+
if (!signResponse.ok) {
|
|
117
|
+
const errorText = await signResponse.text().catch(() => 'Failed to read error response');
|
|
118
|
+
throwSignerError(SignerErrorCode.REMOTE_API_ERROR, {
|
|
119
|
+
message: `Dfns auth/action failed: ${signResponse.status}`,
|
|
120
|
+
response: errorText,
|
|
121
|
+
status: signResponse.status,
|
|
122
|
+
});
|
|
123
|
+
}
|
|
124
|
+
|
|
125
|
+
let actionResponse: UserActionResponse;
|
|
126
|
+
try {
|
|
127
|
+
actionResponse = (await signResponse.json()) as UserActionResponse;
|
|
128
|
+
} catch (error) {
|
|
129
|
+
throwSignerError(SignerErrorCode.PARSING_ERROR, {
|
|
130
|
+
cause: error,
|
|
131
|
+
message: 'Failed to parse Dfns auth action response',
|
|
132
|
+
});
|
|
133
|
+
}
|
|
134
|
+
|
|
135
|
+
return actionResponse.userAction;
|
|
136
|
+
}
|