@novasamatech/host-papp 0.5.4 → 0.6.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/dist/constants.d.ts +1 -0
- package/dist/constants.js +1 -0
- package/dist/crypto.d.ts +1 -1
- package/dist/index.d.ts +3 -3
- package/dist/index.js +1 -1
- package/dist/sso/auth/attestationService.d.ts +1 -0
- package/dist/sso/auth/attestationService.js +13 -4
- package/dist/sso/sessionManager/impl.d.ts +1 -1
- package/dist/sso/sessionManager/impl.js +4 -1
- package/dist/sso/sessionManager/scale/remoteMessage.d.ts +31 -16
- package/dist/sso/sessionManager/scale/remoteMessage.js +4 -4
- package/dist/sso/sessionManager/scale/signingRequest.d.ts +64 -0
- package/dist/sso/sessionManager/scale/signingRequest.js +31 -0
- package/dist/sso/sessionManager/scale/signingResponse.d.ts +14 -0
- package/dist/sso/sessionManager/scale/signingResponse.js +10 -0
- package/dist/sso/sessionManager/userSession.d.ts +3 -2
- package/dist/sso/sessionManager/userSession.js +29 -1
- package/package.json +6 -6
package/dist/constants.d.ts
CHANGED
package/dist/constants.js
CHANGED
package/dist/crypto.d.ts
CHANGED
|
@@ -18,7 +18,7 @@ export type DerivedSr25519Account = {
|
|
|
18
18
|
sign(message: Uint8Array): Uint8Array;
|
|
19
19
|
verify(message: Uint8Array, signature: Uint8Array): boolean;
|
|
20
20
|
};
|
|
21
|
-
export declare function deriveSr25519Account(mnemonic: string, derivation
|
|
21
|
+
export declare function deriveSr25519Account(mnemonic: string, derivation?: string): DerivedSr25519Account;
|
|
22
22
|
export declare function createEncrSecret(entropy: Uint8Array): EncrSecret;
|
|
23
23
|
export declare function getEncrPub(secret: EncrSecret): EncrPublicKey;
|
|
24
24
|
export declare function createSharedSecret(secret: EncrSecret, publicKey: Uint8Array): SharedSecret;
|
package/dist/index.d.ts
CHANGED
|
@@ -1,9 +1,9 @@
|
|
|
1
|
-
export { SS_STABLE_STAGE_ENDPOINTS, SS_UNSTABLE_STAGE_ENDPOINTS } from './constants.js';
|
|
1
|
+
export { SS_PREVIEW_STAGE_ENDPOINTS, SS_STABLE_STAGE_ENDPOINTS, SS_UNSTABLE_STAGE_ENDPOINTS } from './constants.js';
|
|
2
2
|
export type { PappAdapter } from './papp.js';
|
|
3
3
|
export { createPappAdapter } from './papp.js';
|
|
4
4
|
export type { AttestationStatus, PairingStatus } from './sso/auth/types.js';
|
|
5
5
|
export type { UserSession } from './sso/sessionManager/userSession.js';
|
|
6
6
|
export type { StoredUserSession } from './sso/userSessionRepository.js';
|
|
7
7
|
export type { Identity } from './identity/types.js';
|
|
8
|
-
export type { SignPayloadRequest } from './sso/sessionManager/scale/
|
|
9
|
-
export type { SignPayloadResponse } from './sso/sessionManager/scale/
|
|
8
|
+
export type { SignPayloadRequest } from './sso/sessionManager/scale/signingRequest.js';
|
|
9
|
+
export type { SignPayloadResponse } from './sso/sessionManager/scale/signingResponse.js';
|
package/dist/index.js
CHANGED
|
@@ -1,2 +1,2 @@
|
|
|
1
|
-
export { SS_STABLE_STAGE_ENDPOINTS, SS_UNSTABLE_STAGE_ENDPOINTS } from './constants.js';
|
|
1
|
+
export { SS_PREVIEW_STAGE_ENDPOINTS, SS_STABLE_STAGE_ENDPOINTS, SS_UNSTABLE_STAGE_ENDPOINTS } from './constants.js';
|
|
2
2
|
export { createPappAdapter } from './papp.js';
|
|
@@ -2,6 +2,7 @@ import type { LazyClient } from '@novasamatech/statement-store';
|
|
|
2
2
|
import type { ResultAsync } from 'neverthrow';
|
|
3
3
|
import type { DerivedSr25519Account } from '../../crypto.js';
|
|
4
4
|
export declare function createSudoAliceVerifier(): DerivedSr25519Account;
|
|
5
|
+
export declare function withRetry<T>(fn: () => Promise<T>, maxRetries?: number): Promise<T>;
|
|
5
6
|
export declare const createAttestationService: (lazyClient: LazyClient) => {
|
|
6
7
|
claimUsername(): string;
|
|
7
8
|
grantVerifierAllowance(verifier: DerivedSr25519Account): ResultAsync<void, Error>;
|
|
@@ -6,13 +6,21 @@ import { errAsync, fromAsyncThrowable, fromPromise, okAsync } from 'neverthrow';
|
|
|
6
6
|
import { AccountId, Binary } from 'polkadot-api';
|
|
7
7
|
import { getPolkadotSigner } from 'polkadot-api/signer';
|
|
8
8
|
import { Bytes, Option, Tuple, str } from 'scale-ts';
|
|
9
|
-
import { member_from_entropy, sign } from 'verifiablejs/
|
|
9
|
+
import { member_from_entropy, sign } from 'verifiablejs/nodejs';
|
|
10
10
|
import { deriveSr25519Account, getEncrPub, stringToBytes } from '../../crypto.js';
|
|
11
11
|
import { toError } from '../../helpers/utils.js';
|
|
12
12
|
const accountId = AccountId();
|
|
13
13
|
export function createSudoAliceVerifier() {
|
|
14
14
|
return deriveSr25519Account('bottom drive obey lake curtain smoke basket hold race lonely fit walk', '//Alice');
|
|
15
15
|
}
|
|
16
|
+
export function withRetry(fn, maxRetries = 1) {
|
|
17
|
+
return fn().catch(error => {
|
|
18
|
+
if (maxRetries > 0) {
|
|
19
|
+
return withRetry(fn, maxRetries - 1);
|
|
20
|
+
}
|
|
21
|
+
throw error;
|
|
22
|
+
});
|
|
23
|
+
}
|
|
16
24
|
export const createAttestationService = (lazyClient) => {
|
|
17
25
|
const service = {
|
|
18
26
|
claimUsername() {
|
|
@@ -35,7 +43,7 @@ export const createAttestationService = (lazyClient) => {
|
|
|
35
43
|
const sudoCall = api.tx.Sudo.sudo({
|
|
36
44
|
call: increaseAllowanceCall.decodedCall,
|
|
37
45
|
});
|
|
38
|
-
return sudoCall.signAndSubmit(createPeopleSigner(verifier)).then(() => undefined);
|
|
46
|
+
return withRetry(() => sudoCall.signAndSubmit(createPeopleSigner(verifier)).then(() => undefined));
|
|
39
47
|
}, toError);
|
|
40
48
|
return verifierAllowance.andThen(verifierAllowance => (verifierAllowance > 0 ? okAsync() : getAllowance()));
|
|
41
49
|
},
|
|
@@ -108,7 +116,7 @@ export const createAttestationService = (lazyClient) => {
|
|
|
108
116
|
reserved_username: undefined,
|
|
109
117
|
},
|
|
110
118
|
});
|
|
111
|
-
|
|
119
|
+
const submitAttestation = () => new Promise((resolve, reject) => {
|
|
112
120
|
const subscription = attestCall.signSubmitAndWatch(createPeopleSigner(verifier)).subscribe({
|
|
113
121
|
next(event) {
|
|
114
122
|
if (event.type === 'finalized') {
|
|
@@ -131,7 +139,8 @@ export const createAttestationService = (lazyClient) => {
|
|
|
131
139
|
},
|
|
132
140
|
error: reject,
|
|
133
141
|
});
|
|
134
|
-
})
|
|
142
|
+
});
|
|
143
|
+
return fromPromise(withRetry(submitAttestation), toError).map(() => undefined);
|
|
135
144
|
})
|
|
136
145
|
.andTee(() => console.log(`Attestation for ${accountId.dec(candidate.publicKey)} successfully passed.`));
|
|
137
146
|
},
|
|
@@ -16,7 +16,7 @@ export declare function createSsoSessionManager({ ssoSessionRepository, userSecr
|
|
|
16
16
|
read: () => UserSession[];
|
|
17
17
|
subscribe: (callback: Callback<UserSession[]>) => () => void;
|
|
18
18
|
};
|
|
19
|
-
disconnect(userSession: StoredUserSession): import("neverthrow").ResultAsync<
|
|
19
|
+
disconnect(userSession: StoredUserSession): import("neverthrow").ResultAsync<void, Error>;
|
|
20
20
|
dispose(): void;
|
|
21
21
|
};
|
|
22
22
|
export {};
|
|
@@ -49,7 +49,10 @@ export function createSsoSessionManager({ ssoSessionRepository, userSecretReposi
|
|
|
49
49
|
},
|
|
50
50
|
disconnect(userSession) {
|
|
51
51
|
const session = createSession(userSession, statementStore, storage, userSecretRepository);
|
|
52
|
-
return session
|
|
52
|
+
return session
|
|
53
|
+
.sendDisconnectMessage()
|
|
54
|
+
.andThen(() => disconnect(userSession))
|
|
55
|
+
.andThen(() => userSecretRepository.clear(userSession.id));
|
|
53
56
|
},
|
|
54
57
|
dispose() {
|
|
55
58
|
for (const session of Object.values(localSessions.read())) {
|
|
@@ -10,22 +10,37 @@ export declare const RemoteMessageCodec: import("scale-ts").Codec<{
|
|
|
10
10
|
} | {
|
|
11
11
|
tag: "SignRequest";
|
|
12
12
|
value: {
|
|
13
|
-
|
|
14
|
-
|
|
15
|
-
|
|
16
|
-
|
|
17
|
-
|
|
18
|
-
|
|
19
|
-
|
|
20
|
-
|
|
21
|
-
|
|
22
|
-
|
|
23
|
-
|
|
24
|
-
|
|
25
|
-
|
|
26
|
-
|
|
27
|
-
|
|
28
|
-
|
|
13
|
+
tag: "raw";
|
|
14
|
+
value: {
|
|
15
|
+
address: string;
|
|
16
|
+
data: {
|
|
17
|
+
tag: "Bytes";
|
|
18
|
+
value: Uint8Array<ArrayBufferLike>;
|
|
19
|
+
} | {
|
|
20
|
+
tag: "Payload";
|
|
21
|
+
value: string;
|
|
22
|
+
};
|
|
23
|
+
};
|
|
24
|
+
} | {
|
|
25
|
+
tag: "payload";
|
|
26
|
+
value: {
|
|
27
|
+
address: string;
|
|
28
|
+
blockHash: `0x${string}`;
|
|
29
|
+
blockNumber: `0x${string}`;
|
|
30
|
+
era: `0x${string}`;
|
|
31
|
+
genesisHash: `0x${string}`;
|
|
32
|
+
method: `0x${string}`;
|
|
33
|
+
nonce: `0x${string}`;
|
|
34
|
+
specVersion: `0x${string}`;
|
|
35
|
+
tip: `0x${string}`;
|
|
36
|
+
transactionVersion: `0x${string}`;
|
|
37
|
+
signedExtensions: string[];
|
|
38
|
+
version: number;
|
|
39
|
+
assetId: `0x${string}` | undefined;
|
|
40
|
+
metadataHash: `0x${string}` | undefined;
|
|
41
|
+
mode: number | undefined;
|
|
42
|
+
withSignedTransaction: boolean | undefined;
|
|
43
|
+
};
|
|
29
44
|
};
|
|
30
45
|
} | {
|
|
31
46
|
tag: "SignResponse";
|
|
@@ -1,13 +1,13 @@
|
|
|
1
1
|
import { Enum, Struct, _void, str } from 'scale-ts';
|
|
2
|
-
import {
|
|
3
|
-
import {
|
|
2
|
+
import { SigningRequestCodec } from './signingRequest.js';
|
|
3
|
+
import { SigningResponseCodec } from './signingResponse.js';
|
|
4
4
|
export const RemoteMessageCodec = Struct({
|
|
5
5
|
messageId: str,
|
|
6
6
|
data: Enum({
|
|
7
7
|
v1: Enum({
|
|
8
8
|
Disconnected: _void,
|
|
9
|
-
SignRequest:
|
|
10
|
-
SignResponse:
|
|
9
|
+
SignRequest: SigningRequestCodec,
|
|
10
|
+
SignResponse: SigningResponseCodec,
|
|
11
11
|
}),
|
|
12
12
|
}),
|
|
13
13
|
});
|
|
@@ -0,0 +1,64 @@
|
|
|
1
|
+
import type { CodecType } from 'scale-ts';
|
|
2
|
+
export type SignPayloadRequest = CodecType<typeof SigningPayloadRequestCodec>;
|
|
3
|
+
export declare const SigningPayloadRequestCodec: import("scale-ts").Codec<{
|
|
4
|
+
address: string;
|
|
5
|
+
blockHash: `0x${string}`;
|
|
6
|
+
blockNumber: `0x${string}`;
|
|
7
|
+
era: `0x${string}`;
|
|
8
|
+
genesisHash: `0x${string}`;
|
|
9
|
+
method: `0x${string}`;
|
|
10
|
+
nonce: `0x${string}`;
|
|
11
|
+
specVersion: `0x${string}`;
|
|
12
|
+
tip: `0x${string}`;
|
|
13
|
+
transactionVersion: `0x${string}`;
|
|
14
|
+
signedExtensions: string[];
|
|
15
|
+
version: number;
|
|
16
|
+
assetId: `0x${string}` | undefined;
|
|
17
|
+
metadataHash: `0x${string}` | undefined;
|
|
18
|
+
mode: number | undefined;
|
|
19
|
+
withSignedTransaction: boolean | undefined;
|
|
20
|
+
}>;
|
|
21
|
+
export type SigningRawRequest = CodecType<typeof SigningRawRequestCodec>;
|
|
22
|
+
export declare const SigningRawRequestCodec: import("scale-ts").Codec<{
|
|
23
|
+
address: string;
|
|
24
|
+
data: {
|
|
25
|
+
tag: "Bytes";
|
|
26
|
+
value: Uint8Array<ArrayBufferLike>;
|
|
27
|
+
} | {
|
|
28
|
+
tag: "Payload";
|
|
29
|
+
value: string;
|
|
30
|
+
};
|
|
31
|
+
}>;
|
|
32
|
+
export declare const SigningRequestCodec: import("scale-ts").Codec<{
|
|
33
|
+
tag: "raw";
|
|
34
|
+
value: {
|
|
35
|
+
address: string;
|
|
36
|
+
data: {
|
|
37
|
+
tag: "Bytes";
|
|
38
|
+
value: Uint8Array<ArrayBufferLike>;
|
|
39
|
+
} | {
|
|
40
|
+
tag: "Payload";
|
|
41
|
+
value: string;
|
|
42
|
+
};
|
|
43
|
+
};
|
|
44
|
+
} | {
|
|
45
|
+
tag: "payload";
|
|
46
|
+
value: {
|
|
47
|
+
address: string;
|
|
48
|
+
blockHash: `0x${string}`;
|
|
49
|
+
blockNumber: `0x${string}`;
|
|
50
|
+
era: `0x${string}`;
|
|
51
|
+
genesisHash: `0x${string}`;
|
|
52
|
+
method: `0x${string}`;
|
|
53
|
+
nonce: `0x${string}`;
|
|
54
|
+
specVersion: `0x${string}`;
|
|
55
|
+
tip: `0x${string}`;
|
|
56
|
+
transactionVersion: `0x${string}`;
|
|
57
|
+
signedExtensions: string[];
|
|
58
|
+
version: number;
|
|
59
|
+
assetId: `0x${string}` | undefined;
|
|
60
|
+
metadataHash: `0x${string}` | undefined;
|
|
61
|
+
mode: number | undefined;
|
|
62
|
+
withSignedTransaction: boolean | undefined;
|
|
63
|
+
};
|
|
64
|
+
}>;
|
|
@@ -0,0 +1,31 @@
|
|
|
1
|
+
import { Enum, Hex } from '@novasamatech/scale';
|
|
2
|
+
import { Bytes, Option, Struct, Vector, bool, str, u32 } from 'scale-ts';
|
|
3
|
+
export const SigningPayloadRequestCodec = Struct({
|
|
4
|
+
address: str,
|
|
5
|
+
blockHash: Hex(),
|
|
6
|
+
blockNumber: Hex(),
|
|
7
|
+
era: Hex(),
|
|
8
|
+
genesisHash: Hex(),
|
|
9
|
+
method: Hex(),
|
|
10
|
+
nonce: Hex(),
|
|
11
|
+
specVersion: Hex(),
|
|
12
|
+
tip: Hex(),
|
|
13
|
+
transactionVersion: Hex(),
|
|
14
|
+
signedExtensions: Vector(str),
|
|
15
|
+
version: u32,
|
|
16
|
+
assetId: Option(Hex()),
|
|
17
|
+
metadataHash: Option(Hex()),
|
|
18
|
+
mode: Option(u32),
|
|
19
|
+
withSignedTransaction: Option(bool),
|
|
20
|
+
});
|
|
21
|
+
export const SigningRawRequestCodec = Struct({
|
|
22
|
+
address: str,
|
|
23
|
+
data: Enum({
|
|
24
|
+
Bytes: Bytes(),
|
|
25
|
+
Payload: str,
|
|
26
|
+
}),
|
|
27
|
+
});
|
|
28
|
+
export const SigningRequestCodec = Enum({
|
|
29
|
+
payload: SigningPayloadRequestCodec,
|
|
30
|
+
raw: SigningRawRequestCodec,
|
|
31
|
+
});
|
|
@@ -0,0 +1,14 @@
|
|
|
1
|
+
import type { CodecType } from 'scale-ts';
|
|
2
|
+
export type SignPayloadResponseData = CodecType<typeof SignPayloadResponseDataCodec>;
|
|
3
|
+
export declare const SignPayloadResponseDataCodec: import("scale-ts").Codec<{
|
|
4
|
+
signature: Uint8Array<ArrayBufferLike>;
|
|
5
|
+
signedTransaction: Uint8Array<ArrayBufferLike> | undefined;
|
|
6
|
+
}>;
|
|
7
|
+
export type SignPayloadResponse = CodecType<typeof SigningResponseCodec>;
|
|
8
|
+
export declare const SigningResponseCodec: import("scale-ts").Codec<{
|
|
9
|
+
respondingTo: string;
|
|
10
|
+
payload: import("scale-ts").ResultPayload<{
|
|
11
|
+
signature: Uint8Array<ArrayBufferLike>;
|
|
12
|
+
signedTransaction: Uint8Array<ArrayBufferLike> | undefined;
|
|
13
|
+
}, string>;
|
|
14
|
+
}>;
|
|
@@ -0,0 +1,10 @@
|
|
|
1
|
+
import { Bytes, Option, Result, Struct, str } from 'scale-ts';
|
|
2
|
+
export const SignPayloadResponseDataCodec = Struct({
|
|
3
|
+
signature: Bytes(),
|
|
4
|
+
signedTransaction: Option(Bytes()),
|
|
5
|
+
});
|
|
6
|
+
export const SigningResponseCodec = Struct({
|
|
7
|
+
// referencing to RemoteMessage.messageId
|
|
8
|
+
respondingTo: str,
|
|
9
|
+
payload: Result(SignPayloadResponseDataCodec, str),
|
|
10
|
+
});
|
|
@@ -5,11 +5,12 @@ import type { CodecType } from 'scale-ts';
|
|
|
5
5
|
import type { Callback } from '../../types.js';
|
|
6
6
|
import type { StoredUserSession } from '../userSessionRepository.js';
|
|
7
7
|
import { RemoteMessageCodec } from './scale/remoteMessage.js';
|
|
8
|
-
import type { SignPayloadRequest } from './scale/
|
|
9
|
-
import type { SignPayloadResponseData } from './scale/
|
|
8
|
+
import type { SignPayloadRequest, SigningRawRequest } from './scale/signingRequest.js';
|
|
9
|
+
import type { SignPayloadResponseData } from './scale/signingResponse.js';
|
|
10
10
|
export type UserSession = StoredUserSession & {
|
|
11
11
|
sendDisconnectMessage(): ResultAsync<void, Error>;
|
|
12
12
|
signPayload(payload: SignPayloadRequest): ResultAsync<SignPayloadResponseData, Error>;
|
|
13
|
+
signRaw(payload: SigningRawRequest): ResultAsync<SignPayloadResponseData, Error>;
|
|
13
14
|
subscribe(callback: Callback<CodecType<typeof RemoteMessageCodec>, ResultAsync<boolean, Error>>): VoidFunction;
|
|
14
15
|
dispose(): void;
|
|
15
16
|
};
|
|
@@ -32,7 +32,35 @@ export function createUserSession({ userSession, statementStore, encryption, sto
|
|
|
32
32
|
const messageId = nanoid();
|
|
33
33
|
const request = session.request(RemoteMessageCodec, {
|
|
34
34
|
messageId,
|
|
35
|
-
data: enumValue('v1', enumValue('SignRequest', payload)),
|
|
35
|
+
data: enumValue('v1', enumValue('SignRequest', enumValue('payload', payload))),
|
|
36
|
+
});
|
|
37
|
+
const responseFilter = (message) => {
|
|
38
|
+
if (message.data.tag === 'v1' &&
|
|
39
|
+
message.data.value.tag === 'SignResponse' &&
|
|
40
|
+
message.data.value.value.respondingTo === messageId) {
|
|
41
|
+
return message.data.value.value.payload;
|
|
42
|
+
}
|
|
43
|
+
};
|
|
44
|
+
return request
|
|
45
|
+
.andThen(() => session.waitForRequestMessage(RemoteMessageCodec, responseFilter))
|
|
46
|
+
.andThen(message => {
|
|
47
|
+
if (message.success) {
|
|
48
|
+
return ok(message.value);
|
|
49
|
+
}
|
|
50
|
+
else {
|
|
51
|
+
return err(new Error(message.value));
|
|
52
|
+
}
|
|
53
|
+
});
|
|
54
|
+
},
|
|
55
|
+
signRaw(payload) {
|
|
56
|
+
const accountId = AccountId();
|
|
57
|
+
if (toHex(accountId.enc(payload.address)) !== toHex(userSession.remoteAccount.accountId)) {
|
|
58
|
+
return errAsync(new Error(`Invalid address, got ${payload.address}`));
|
|
59
|
+
}
|
|
60
|
+
const messageId = nanoid();
|
|
61
|
+
const request = session.request(RemoteMessageCodec, {
|
|
62
|
+
messageId,
|
|
63
|
+
data: enumValue('v1', enumValue('SignRequest', enumValue('raw', payload))),
|
|
36
64
|
});
|
|
37
65
|
const responseFilter = (message) => {
|
|
38
66
|
if (message.data.tag === 'v1' &&
|
package/package.json
CHANGED
|
@@ -1,12 +1,12 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@novasamatech/host-papp",
|
|
3
3
|
"type": "module",
|
|
4
|
-
"version": "0.
|
|
4
|
+
"version": "0.6.0",
|
|
5
5
|
"description": "Polkadot app integration",
|
|
6
6
|
"license": "Apache-2.0",
|
|
7
7
|
"repository": {
|
|
8
8
|
"type": "git",
|
|
9
|
-
"url": "git+https://github.com/
|
|
9
|
+
"url": "git+https://github.com/Polkadot-Community-Foundation/triangle-js-sdks.git"
|
|
10
10
|
},
|
|
11
11
|
"keywords": [
|
|
12
12
|
"polkadot"
|
|
@@ -28,10 +28,10 @@
|
|
|
28
28
|
"@noble/ciphers": "2.1.1",
|
|
29
29
|
"@noble/curves": "2.0.1",
|
|
30
30
|
"@noble/hashes": "2.0.1",
|
|
31
|
-
"@novasamatech/host-api": "0.
|
|
32
|
-
"@novasamatech/scale": "0.
|
|
33
|
-
"@novasamatech/statement-store": "0.
|
|
34
|
-
"@novasamatech/storage-adapter": "0.
|
|
31
|
+
"@novasamatech/host-api": "0.6.0",
|
|
32
|
+
"@novasamatech/scale": "0.6.0",
|
|
33
|
+
"@novasamatech/statement-store": "0.6.0",
|
|
34
|
+
"@novasamatech/storage-adapter": "0.6.0",
|
|
35
35
|
"@polkadot-api/utils": "0.2.0",
|
|
36
36
|
"@polkadot-api/substrate-bindings": "^0.17.0",
|
|
37
37
|
"@polkadot-labs/hdkd-helpers": "^0.0.27",
|