@cheqd/sdk 1.0.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/.github/ISSUE_TEMPLATE/bug-report.yml +74 -0
- package/.github/ISSUE_TEMPLATE/config.yml +14 -0
- package/.github/ISSUE_TEMPLATE/feature-request.yaml +27 -0
- package/.github/dependabot.yml +42 -0
- package/.github/linters/.ansible-lint.yml +2 -0
- package/.github/linters/.checkov.yaml +13 -0
- package/.github/linters/.commitlint.rules.js +37 -0
- package/.github/linters/.eslintrc.json +18 -0
- package/.github/linters/.golangci.yaml +25 -0
- package/.github/linters/.hadolint.yml +15 -0
- package/.github/linters/.markdown-lint.yml +139 -0
- package/.github/linters/mlc_config.json +13 -0
- package/.github/workflows/build.yml +31 -0
- package/.github/workflows/codeql.yml +40 -0
- package/.github/workflows/dispatch.yml +24 -0
- package/.github/workflows/lint.yml +54 -0
- package/.github/workflows/pull-request.yml +33 -0
- package/.github/workflows/release.yml +42 -0
- package/.releaserc.json +55 -0
- package/CHANGELOG.md +20 -0
- package/CODE_OF_CONDUCT.md +81 -0
- package/LICENSE +190 -0
- package/NOTICE.md +10 -0
- package/README.md +3 -0
- package/SECURITY.md +12 -0
- package/diagrams/sdk-modules.drawio +152 -0
- package/jest.config.js +6 -0
- package/package.json +62 -0
- package/src/index.ts +90 -0
- package/src/modules/_.ts +58 -0
- package/src/modules/did.ts +87 -0
- package/src/modules/resources.ts +11 -0
- package/src/registry.ts +71 -0
- package/src/signer.ts +229 -0
- package/src/types.ts +67 -0
- package/tests/index.test.ts +63 -0
- package/tests/modules/did.test.ts +158 -0
- package/tests/signer.test.ts +153 -0
- package/tests/testutils.test.ts +110 -0
- package/tsconfig.json +79 -0
package/src/index.ts
ADDED
|
@@ -0,0 +1,90 @@
|
|
|
1
|
+
import { OfflineSigner } from '@cosmjs/proto-signing';
|
|
2
|
+
import { DIDModule, MinimalImportableDIDModule } from './modules/did'
|
|
3
|
+
import { MinimalImportableResourcesModule, ResourcesModule } from './modules/resources'
|
|
4
|
+
import { AbstractCheqdSDKModule, applyMixins, instantiateCheqdSDKModule, } from './modules/_'
|
|
5
|
+
import { CheqdSigningStargateClient } from './signer'
|
|
6
|
+
import { CheqdNetwork, IContext, IModuleMethodMap } from './types'
|
|
7
|
+
|
|
8
|
+
export interface ICheqdSDKOptions {
|
|
9
|
+
modules: AbstractCheqdSDKModule[]
|
|
10
|
+
authorizedMethods?: string[]
|
|
11
|
+
network?: CheqdNetwork
|
|
12
|
+
rpcUrl: string
|
|
13
|
+
readonly wallet: OfflineSigner
|
|
14
|
+
}
|
|
15
|
+
|
|
16
|
+
export type DefaultCheqdSDKModules = MinimalImportableDIDModule & MinimalImportableResourcesModule
|
|
17
|
+
|
|
18
|
+
export interface CheqdSDK extends DefaultCheqdSDKModules {}
|
|
19
|
+
|
|
20
|
+
export class CheqdSDK {
|
|
21
|
+
methods: IModuleMethodMap
|
|
22
|
+
signer: CheqdSigningStargateClient
|
|
23
|
+
options: ICheqdSDKOptions
|
|
24
|
+
private protectedMethods: string[] = ['constructor', 'build', 'loadModules']
|
|
25
|
+
|
|
26
|
+
constructor(options: ICheqdSDKOptions) {
|
|
27
|
+
if (!options?.wallet) {
|
|
28
|
+
throw new Error('No wallet provided')
|
|
29
|
+
}
|
|
30
|
+
|
|
31
|
+
this.options = {
|
|
32
|
+
authorizedMethods: [],
|
|
33
|
+
network: CheqdNetwork.Testnet,
|
|
34
|
+
...options
|
|
35
|
+
}
|
|
36
|
+
|
|
37
|
+
this.methods = {}
|
|
38
|
+
this.signer = new CheqdSigningStargateClient(undefined, this.options.wallet, {})
|
|
39
|
+
}
|
|
40
|
+
|
|
41
|
+
async execute<P = any, R = any>(method: string, ...params: P[]): Promise<R> {
|
|
42
|
+
if (!Object.keys(this.methods).includes(method)) {
|
|
43
|
+
throw new Error(`Method ${method} is not authorized`)
|
|
44
|
+
}
|
|
45
|
+
return await this.methods[method](...params, { sdk: this } as IContext)
|
|
46
|
+
}
|
|
47
|
+
|
|
48
|
+
private loadModules(modules: AbstractCheqdSDKModule[]): CheqdSDK {
|
|
49
|
+
this.options.modules = this.options.modules.map((module: any) => instantiateCheqdSDKModule(module, this.signer, { sdk: this } as IContext) as unknown as AbstractCheqdSDKModule)
|
|
50
|
+
|
|
51
|
+
const methods = applyMixins(this, modules)
|
|
52
|
+
this.methods = { ...this.methods, ...filterUnauthorizedMethods(methods, this.options.authorizedMethods || [], this.protectedMethods) }
|
|
53
|
+
|
|
54
|
+
for(const method of Object.keys(this.methods)) {
|
|
55
|
+
// @ts-ignore
|
|
56
|
+
this[method] = async (...params: any[]) => {
|
|
57
|
+
return await this.execute(method, ...params)
|
|
58
|
+
}
|
|
59
|
+
}
|
|
60
|
+
|
|
61
|
+
return this
|
|
62
|
+
}
|
|
63
|
+
|
|
64
|
+
async build() {
|
|
65
|
+
this.signer = await CheqdSigningStargateClient.connectWithSigner(
|
|
66
|
+
this.options.rpcUrl,
|
|
67
|
+
this.options.wallet
|
|
68
|
+
)
|
|
69
|
+
|
|
70
|
+
return this.loadModules(this.options.modules)
|
|
71
|
+
}
|
|
72
|
+
}
|
|
73
|
+
|
|
74
|
+
export function filterUnauthorizedMethods(methods: IModuleMethodMap, authorizedMethods: string[], protectedMethods: string[]): IModuleMethodMap {
|
|
75
|
+
let _methods = Object.keys(methods)
|
|
76
|
+
if (authorizedMethods.length === 0)
|
|
77
|
+
return _methods
|
|
78
|
+
.filter(method => !protectedMethods.includes(method))
|
|
79
|
+
.reduce((acc, method) => ({ ...acc, [method]: methods[method] }), {})
|
|
80
|
+
|
|
81
|
+
return _methods
|
|
82
|
+
.filter(method => authorizedMethods.includes(method) && !protectedMethods.includes(method))
|
|
83
|
+
.reduce((acc, method) => ({ ...acc, [method]: methods[method] }), {})
|
|
84
|
+
}
|
|
85
|
+
|
|
86
|
+
export async function createCheqdSDK(options: ICheqdSDKOptions): Promise<CheqdSDK> {
|
|
87
|
+
return await (new CheqdSDK(options)).build()
|
|
88
|
+
}
|
|
89
|
+
|
|
90
|
+
export { DIDModule, ResourcesModule }
|
package/src/modules/_.ts
ADDED
|
@@ -0,0 +1,58 @@
|
|
|
1
|
+
import { GeneratedType, Registry } from "@cosmjs/proto-signing"
|
|
2
|
+
import { QueryClient } from "@cosmjs/stargate"
|
|
3
|
+
import { CheqdSigningStargateClient } from '../signer'
|
|
4
|
+
import { IModuleMethodMap } from "../types"
|
|
5
|
+
import { setupDidExtension } from './did'
|
|
6
|
+
|
|
7
|
+
export abstract class AbstractCheqdSDKModule {
|
|
8
|
+
_signer: CheqdSigningStargateClient
|
|
9
|
+
methods: IModuleMethodMap = {}
|
|
10
|
+
readonly _protectedMethods: string[] = ['constructor', 'exportMethods', 'registryTypes']
|
|
11
|
+
|
|
12
|
+
constructor(signer: CheqdSigningStargateClient) {
|
|
13
|
+
if (!signer) {
|
|
14
|
+
throw new Error("signer is required")
|
|
15
|
+
}
|
|
16
|
+
this._signer = signer
|
|
17
|
+
}
|
|
18
|
+
|
|
19
|
+
static registryTypes(): Iterable<[string, GeneratedType]> {
|
|
20
|
+
return []
|
|
21
|
+
}
|
|
22
|
+
}
|
|
23
|
+
|
|
24
|
+
export type MinimalImportableCheqdSDKModule<T extends AbstractCheqdSDKModule> = Omit<T, '_signer' | '_protectedMethods'>
|
|
25
|
+
|
|
26
|
+
export function instantiateCheqdSDKModule<T extends new (...args: any[]) => T>(module: T, ...args: ConstructorParameters<T>): T {
|
|
27
|
+
return new module(...args)
|
|
28
|
+
}
|
|
29
|
+
|
|
30
|
+
export function applyMixins(derivedCtor: any, constructors: any[]): IModuleMethodMap {
|
|
31
|
+
let methods: IModuleMethodMap = {}
|
|
32
|
+
|
|
33
|
+
constructors.forEach((baseCtor) => {
|
|
34
|
+
Object.getOwnPropertyNames(baseCtor.prototype).forEach((name) => {
|
|
35
|
+
const property = baseCtor.prototype[name]
|
|
36
|
+
if (typeof property !== 'function' || derivedCtor.hasOwnProperty(name) || derivedCtor?.protectedMethods.includes(name) || baseCtor.prototype?._protectedMethods?.includes(name)) return
|
|
37
|
+
|
|
38
|
+
methods = { ...methods, [name]: property }
|
|
39
|
+
});
|
|
40
|
+
});
|
|
41
|
+
|
|
42
|
+
return methods
|
|
43
|
+
}
|
|
44
|
+
|
|
45
|
+
export type CheqdExtension<K extends string, V = any> = {
|
|
46
|
+
[P in K]: (Record<P, V> & Partial<Record<Exclude<K, P>, never>>) extends infer O
|
|
47
|
+
? { [Q in keyof O]: O[Q] }
|
|
48
|
+
: never
|
|
49
|
+
}[K]
|
|
50
|
+
|
|
51
|
+
export type CheqdExtensions = CheqdExtension<'did' | 'resources', any>
|
|
52
|
+
|
|
53
|
+
export const setupCheqdExtensions = (base: QueryClient): CheqdExtensions => {
|
|
54
|
+
return {
|
|
55
|
+
...setupDidExtension(base),
|
|
56
|
+
/** setupResourcesExtension(base) */
|
|
57
|
+
}
|
|
58
|
+
}
|
|
@@ -0,0 +1,87 @@
|
|
|
1
|
+
import { createProtobufRpcClient, DeliverTxResponse, QueryClient, StdFee } from "@cosmjs/stargate"
|
|
2
|
+
/* import { QueryClientImpl } from '@cheqd/ts-proto/cheqd/v1/query' */
|
|
3
|
+
import { CheqdExtension, AbstractCheqdSDKModule, MinimalImportableCheqdSDKModule } from "./_"
|
|
4
|
+
import { CheqdSigningStargateClient } from "../signer"
|
|
5
|
+
import { DidStdFee, IContext, ISignInputs } from "../types"
|
|
6
|
+
import { MsgCreateDid, MsgCreateDidPayload, MsgUpdateDid, MsgUpdateDidPayload } from "@cheqd/ts-proto/cheqd/v1/tx"
|
|
7
|
+
import { MsgCreateDidEncodeObject, MsgUpdateDidEncodeObject, typeUrlMsgCreateDid, typeUrlMsgUpdateDid } from "../registry"
|
|
8
|
+
|
|
9
|
+
export class DIDModule extends AbstractCheqdSDKModule {
|
|
10
|
+
constructor(signer: CheqdSigningStargateClient){
|
|
11
|
+
super(signer)
|
|
12
|
+
this.methods = {
|
|
13
|
+
createDidTx: this.createDidTx.bind(this),
|
|
14
|
+
updateDidTx: this.updateDidTx.bind(this)
|
|
15
|
+
}
|
|
16
|
+
}
|
|
17
|
+
|
|
18
|
+
async createDidTx(signInputs: ISignInputs[], didPayload: Partial<MsgCreateDidPayload>, address: string, fee: DidStdFee | 'auto' | number, memo?: string, context?: IContext): Promise<DeliverTxResponse> {
|
|
19
|
+
if (!this._signer) {
|
|
20
|
+
this._signer = context!.sdk!.signer
|
|
21
|
+
}
|
|
22
|
+
|
|
23
|
+
const payload = MsgCreateDidPayload.fromPartial(didPayload)
|
|
24
|
+
const signatures = await this._signer.signCreateDidTx(signInputs, payload)
|
|
25
|
+
|
|
26
|
+
const value: MsgCreateDid = {
|
|
27
|
+
payload,
|
|
28
|
+
signatures
|
|
29
|
+
}
|
|
30
|
+
|
|
31
|
+
const createDidMsg: MsgCreateDidEncodeObject = {
|
|
32
|
+
typeUrl: typeUrlMsgCreateDid,
|
|
33
|
+
value
|
|
34
|
+
}
|
|
35
|
+
|
|
36
|
+
return this._signer.signAndBroadcast(
|
|
37
|
+
address,
|
|
38
|
+
[createDidMsg],
|
|
39
|
+
fee,
|
|
40
|
+
memo
|
|
41
|
+
)
|
|
42
|
+
}
|
|
43
|
+
|
|
44
|
+
async updateDidTx(signInputs: ISignInputs[], didPayload: Partial<MsgUpdateDidPayload>, address: string, fee: DidStdFee | 'auto' | number, memo?: string, context?: IContext): Promise<DeliverTxResponse> {
|
|
45
|
+
if (!this._signer) {
|
|
46
|
+
this._signer = context!.sdk!.signer
|
|
47
|
+
}
|
|
48
|
+
|
|
49
|
+
const payload = MsgUpdateDidPayload.fromPartial(didPayload)
|
|
50
|
+
const signatures = await this._signer.signUpdateDidTx(signInputs, payload)
|
|
51
|
+
|
|
52
|
+
const value: MsgUpdateDid = {
|
|
53
|
+
payload,
|
|
54
|
+
signatures
|
|
55
|
+
}
|
|
56
|
+
|
|
57
|
+
const updateDidMsg: MsgUpdateDidEncodeObject = {
|
|
58
|
+
typeUrl: typeUrlMsgUpdateDid,
|
|
59
|
+
value
|
|
60
|
+
}
|
|
61
|
+
|
|
62
|
+
return this._signer.signAndBroadcast(
|
|
63
|
+
address,
|
|
64
|
+
[updateDidMsg],
|
|
65
|
+
fee,
|
|
66
|
+
memo
|
|
67
|
+
)
|
|
68
|
+
}
|
|
69
|
+
}
|
|
70
|
+
|
|
71
|
+
export type MinimalImportableDIDModule = MinimalImportableCheqdSDKModule<DIDModule>
|
|
72
|
+
|
|
73
|
+
export interface DidExtension extends CheqdExtension<string, {}> {
|
|
74
|
+
did: {}
|
|
75
|
+
}
|
|
76
|
+
|
|
77
|
+
export const setupDidExtension = (base: QueryClient): DidExtension => {
|
|
78
|
+
const rpc = createProtobufRpcClient(base)
|
|
79
|
+
|
|
80
|
+
/* const queryService = new QueryClientImpl(rpc) */
|
|
81
|
+
|
|
82
|
+
return {
|
|
83
|
+
did: {
|
|
84
|
+
//...
|
|
85
|
+
}
|
|
86
|
+
}
|
|
87
|
+
}
|
|
@@ -0,0 +1,11 @@
|
|
|
1
|
+
import { AbstractCheqdSDKModule, MinimalImportableCheqdSDKModule } from "./_"
|
|
2
|
+
import { CheqdSigningStargateClient } from "../signer"
|
|
3
|
+
|
|
4
|
+
|
|
5
|
+
export class ResourcesModule extends AbstractCheqdSDKModule {
|
|
6
|
+
constructor(signer: CheqdSigningStargateClient) {
|
|
7
|
+
super(signer)
|
|
8
|
+
}
|
|
9
|
+
}
|
|
10
|
+
|
|
11
|
+
export type MinimalImportableResourcesModule = MinimalImportableCheqdSDKModule<ResourcesModule>
|
package/src/registry.ts
ADDED
|
@@ -0,0 +1,71 @@
|
|
|
1
|
+
import {
|
|
2
|
+
Registry,
|
|
3
|
+
GeneratedType,
|
|
4
|
+
EncodeObject
|
|
5
|
+
} from '@cosmjs/proto-signing'
|
|
6
|
+
|
|
7
|
+
import {
|
|
8
|
+
defaultRegistryTypes
|
|
9
|
+
} from '@cosmjs/stargate'
|
|
10
|
+
|
|
11
|
+
import {
|
|
12
|
+
MsgCreateDid, MsgCreateDidResponse, MsgUpdateDid, MsgUpdateDidResponse
|
|
13
|
+
} from '@cheqd/ts-proto/cheqd/v1/tx'
|
|
14
|
+
|
|
15
|
+
export const typeUrlMsgCreateDid = '/cheqdid.cheqdnode.cheqd.v1.MsgCreateDid'
|
|
16
|
+
export const typeUrlMsgCreateDidResponse = '/cheqdid.cheqdnode.cheqd.v1.MsgCreateDidResponse'
|
|
17
|
+
export const typeUrlMsgUpdateDid = '/cheqdid.cheqdnode.cheqd.v1.MsgUpdateDid'
|
|
18
|
+
export const typeUrlMsgUpdateDidResponse = '/cheqdid.cheqdnode.cheqd.v1.MsgUpdateDidResponse'
|
|
19
|
+
|
|
20
|
+
const defaultCheqdRegistryTypes: Iterable<[string, GeneratedType]> = [
|
|
21
|
+
...defaultRegistryTypes,
|
|
22
|
+
|
|
23
|
+
/** Move them as registrations to each module's constructor. */
|
|
24
|
+
|
|
25
|
+
[typeUrlMsgCreateDid, MsgCreateDid],
|
|
26
|
+
[typeUrlMsgCreateDidResponse, MsgCreateDidResponse],
|
|
27
|
+
[typeUrlMsgUpdateDid, MsgUpdateDid],
|
|
28
|
+
[typeUrlMsgUpdateDidResponse, MsgUpdateDidResponse],
|
|
29
|
+
]
|
|
30
|
+
|
|
31
|
+
export function createDefaultCheqdRegistry(): Registry {
|
|
32
|
+
return new Registry(defaultCheqdRegistryTypes)
|
|
33
|
+
}
|
|
34
|
+
|
|
35
|
+
export const CheqdRegistry = new Registry(defaultCheqdRegistryTypes)
|
|
36
|
+
|
|
37
|
+
export interface MsgCreateDidEncodeObject extends EncodeObject {
|
|
38
|
+
readonly typeUrl: typeof typeUrlMsgCreateDid,
|
|
39
|
+
readonly value: Partial<MsgCreateDid>
|
|
40
|
+
}
|
|
41
|
+
|
|
42
|
+
export function isMsgCreateDidEncodeObject(obj: EncodeObject): obj is MsgCreateDidEncodeObject {
|
|
43
|
+
return obj.typeUrl === typeUrlMsgCreateDid
|
|
44
|
+
}
|
|
45
|
+
|
|
46
|
+
export interface MsgCreateDidResponseEncodeObject extends EncodeObject {
|
|
47
|
+
readonly typeUrl: typeof typeUrlMsgCreateDidResponse,
|
|
48
|
+
readonly value: Partial<MsgCreateDidResponse>
|
|
49
|
+
}
|
|
50
|
+
|
|
51
|
+
export function MsgCreateDidResponseEncodeObject(obj: EncodeObject): obj is MsgCreateDidResponseEncodeObject {
|
|
52
|
+
return obj.typeUrl === typeUrlMsgCreateDidResponse
|
|
53
|
+
}
|
|
54
|
+
|
|
55
|
+
export interface MsgUpdateDidEncodeObject extends EncodeObject {
|
|
56
|
+
readonly typeUrl: typeof typeUrlMsgUpdateDid,
|
|
57
|
+
readonly value: Partial<MsgUpdateDid>
|
|
58
|
+
}
|
|
59
|
+
|
|
60
|
+
export function MsgUpdateDidEncodeObject(obj: EncodeObject): obj is MsgUpdateDidEncodeObject {
|
|
61
|
+
return obj.typeUrl === typeUrlMsgUpdateDid
|
|
62
|
+
}
|
|
63
|
+
|
|
64
|
+
export interface MsgUpdateDidResponseEncodeObject extends EncodeObject {
|
|
65
|
+
readonly typeUrl: typeof typeUrlMsgUpdateDidResponse,
|
|
66
|
+
readonly value: Partial<MsgUpdateDidResponse>
|
|
67
|
+
}
|
|
68
|
+
|
|
69
|
+
export function MsgUpdateDidResponseEncodeObject(obj: EncodeObject): obj is MsgUpdateDidResponseEncodeObject {
|
|
70
|
+
return obj.typeUrl === typeUrlMsgUpdateDidResponse
|
|
71
|
+
}
|
package/src/signer.ts
ADDED
|
@@ -0,0 +1,229 @@
|
|
|
1
|
+
import { CheqdExtensions } from './modules/_'
|
|
2
|
+
import { EncodeObject, isOfflineDirectSigner, OfflineSigner, encodePubkey, TxBodyEncodeObject, makeSignDoc } from "@cosmjs/proto-signing"
|
|
3
|
+
import { DeliverTxResponse, GasPrice, HttpEndpoint, QueryClient, SigningStargateClient, SigningStargateClientOptions, calculateFee, SignerData } from "@cosmjs/stargate"
|
|
4
|
+
import { Tendermint34Client } from "@cosmjs/tendermint-rpc"
|
|
5
|
+
import { createDefaultCheqdRegistry } from "./registry"
|
|
6
|
+
import { MsgCreateDidPayload, SignInfo, MsgUpdateDidPayload } from '@cheqd/ts-proto/cheqd/v1/tx';
|
|
7
|
+
import { DidStdFee, ISignInputs, TSignerAlgo, VerificationMethods } from './types'
|
|
8
|
+
import { VerificationMethod } from '@cheqd/ts-proto/cheqd/v1/did'
|
|
9
|
+
import { base64ToBytes, EdDSASigner, hexToBytes, Signer } from 'did-jwt'
|
|
10
|
+
import { toString } from 'uint8arrays'
|
|
11
|
+
import { assert, assertDefined } from '@cosmjs/utils'
|
|
12
|
+
import { encodeSecp256k1Pubkey } from '@cosmjs/amino'
|
|
13
|
+
import { Int53 } from '@cosmjs/math'
|
|
14
|
+
import { fromBase64 } from '@cosmjs/encoding'
|
|
15
|
+
import { AuthInfo, SignerInfo, TxRaw } from 'cosmjs-types/cosmos/tx/v1beta1/tx'
|
|
16
|
+
import { SignMode } from 'cosmjs-types/cosmos/tx/signing/v1beta1/signing'
|
|
17
|
+
import { Any } from 'cosmjs-types/google/protobuf/any'
|
|
18
|
+
import { Coin } from 'cosmjs-types/cosmos/base/v1beta1/coin'
|
|
19
|
+
import Long from 'long'
|
|
20
|
+
|
|
21
|
+
export function calculateDidFee(gasLimit: number, gasPrice: string | GasPrice): DidStdFee {
|
|
22
|
+
return calculateFee(gasLimit, gasPrice)
|
|
23
|
+
}
|
|
24
|
+
|
|
25
|
+
export function makeSignerInfos(
|
|
26
|
+
signers: ReadonlyArray<{ readonly pubkey: Any; readonly sequence: number }>,
|
|
27
|
+
signMode: SignMode,
|
|
28
|
+
): SignerInfo[] {
|
|
29
|
+
return signers.map(
|
|
30
|
+
({ pubkey, sequence }): SignerInfo => ({
|
|
31
|
+
publicKey: pubkey,
|
|
32
|
+
modeInfo: {
|
|
33
|
+
single: { mode: signMode },
|
|
34
|
+
},
|
|
35
|
+
sequence: Long.fromNumber(sequence),
|
|
36
|
+
}),
|
|
37
|
+
);
|
|
38
|
+
}
|
|
39
|
+
|
|
40
|
+
export function makeDidAuthInfoBytes(
|
|
41
|
+
signers: ReadonlyArray<{ readonly pubkey: Any; readonly sequence: number }>,
|
|
42
|
+
feeAmount: readonly Coin[],
|
|
43
|
+
gasLimit: number,
|
|
44
|
+
feePayer: string,
|
|
45
|
+
signMode = SignMode.SIGN_MODE_DIRECT,
|
|
46
|
+
): Uint8Array {
|
|
47
|
+
const authInfo = {
|
|
48
|
+
signerInfos: makeSignerInfos(signers, signMode),
|
|
49
|
+
fee: {
|
|
50
|
+
amount: [...feeAmount],
|
|
51
|
+
gasLimit: Long.fromNumber(gasLimit),
|
|
52
|
+
payer: feePayer
|
|
53
|
+
}
|
|
54
|
+
}
|
|
55
|
+
return AuthInfo.encode(AuthInfo.fromPartial(authInfo)).finish()
|
|
56
|
+
}
|
|
57
|
+
|
|
58
|
+
|
|
59
|
+
export class CheqdSigningStargateClient extends SigningStargateClient {
|
|
60
|
+
public readonly cheqdExtensions: CheqdExtensions | undefined
|
|
61
|
+
private didSigners: TSignerAlgo = {}
|
|
62
|
+
private readonly _gasPrice: GasPrice | undefined
|
|
63
|
+
private readonly _signer: OfflineSigner
|
|
64
|
+
|
|
65
|
+
public static async connectWithSigner(endpoint: string | HttpEndpoint, signer: OfflineSigner, options?: SigningStargateClientOptions | undefined): Promise<CheqdSigningStargateClient> {
|
|
66
|
+
const tmClient = await Tendermint34Client.connect(endpoint)
|
|
67
|
+
return new CheqdSigningStargateClient(tmClient, signer, {
|
|
68
|
+
registry: createDefaultCheqdRegistry(),
|
|
69
|
+
...options
|
|
70
|
+
})
|
|
71
|
+
}
|
|
72
|
+
|
|
73
|
+
constructor(
|
|
74
|
+
tmClient: Tendermint34Client | undefined,
|
|
75
|
+
signer: OfflineSigner,
|
|
76
|
+
options: SigningStargateClientOptions = {}
|
|
77
|
+
) {
|
|
78
|
+
super(tmClient, signer, options)
|
|
79
|
+
this._signer = signer
|
|
80
|
+
if (options.gasPrice) this._gasPrice = options.gasPrice
|
|
81
|
+
|
|
82
|
+
/** GRPC Connection */
|
|
83
|
+
|
|
84
|
+
/* if (tmClient) {
|
|
85
|
+
this.cheqdExtensions = QueryClient.withExtensions(tmClient, setupCheqdExtensions)
|
|
86
|
+
} */
|
|
87
|
+
}
|
|
88
|
+
|
|
89
|
+
async signAndBroadcast(
|
|
90
|
+
signerAddress: string,
|
|
91
|
+
messages: readonly EncodeObject[],
|
|
92
|
+
fee: DidStdFee | "auto" | number,
|
|
93
|
+
memo = "",
|
|
94
|
+
): Promise<DeliverTxResponse> {
|
|
95
|
+
let usedFee: DidStdFee
|
|
96
|
+
if (fee == "auto" || typeof fee === "number") {
|
|
97
|
+
assertDefined(this._gasPrice, "Gas price must be set in the client options when auto gas is used.")
|
|
98
|
+
const gasEstimation = await this.simulate(signerAddress, messages, memo)
|
|
99
|
+
const multiplier = typeof fee === "number" ? fee : 1.3
|
|
100
|
+
usedFee = calculateDidFee(Math.round(gasEstimation * multiplier), this._gasPrice)
|
|
101
|
+
usedFee.payer = signerAddress
|
|
102
|
+
} else {
|
|
103
|
+
usedFee = fee
|
|
104
|
+
assertDefined(usedFee.payer, "Payer address must be set when fee is not auto.")
|
|
105
|
+
signerAddress = usedFee.payer!
|
|
106
|
+
}
|
|
107
|
+
const txRaw = await this.sign(signerAddress, messages, usedFee, memo)
|
|
108
|
+
const txBytes = TxRaw.encode(txRaw).finish()
|
|
109
|
+
return this.broadcastTx(txBytes, this.broadcastTimeoutMs, this.broadcastPollIntervalMs)
|
|
110
|
+
}
|
|
111
|
+
|
|
112
|
+
public async sign(
|
|
113
|
+
signerAddress: string,
|
|
114
|
+
messages: readonly EncodeObject[],
|
|
115
|
+
fee: DidStdFee,
|
|
116
|
+
memo: string,
|
|
117
|
+
explicitSignerData?: SignerData,
|
|
118
|
+
): Promise<TxRaw> {
|
|
119
|
+
let signerData: SignerData
|
|
120
|
+
if (explicitSignerData) {
|
|
121
|
+
signerData = explicitSignerData
|
|
122
|
+
} else {
|
|
123
|
+
const { accountNumber, sequence } = await this.getSequence(signerAddress)
|
|
124
|
+
const chainId = await this.getChainId()
|
|
125
|
+
signerData = {
|
|
126
|
+
accountNumber: accountNumber,
|
|
127
|
+
sequence: sequence,
|
|
128
|
+
chainId: chainId,
|
|
129
|
+
}
|
|
130
|
+
}
|
|
131
|
+
|
|
132
|
+
return this._signDirect(signerAddress, messages, fee, memo, signerData)
|
|
133
|
+
|
|
134
|
+
// TODO: override signAmino as well
|
|
135
|
+
/* return isOfflineDirectSigner(this._signer)
|
|
136
|
+
? this._signDirect(signerAddress, messages, fee, memo, signerData)
|
|
137
|
+
: this._signAmino(signerAddress, messages, fee, memo, signerData) */
|
|
138
|
+
}
|
|
139
|
+
|
|
140
|
+
private async _signDirect(
|
|
141
|
+
signerAddress: string,
|
|
142
|
+
messages: readonly EncodeObject[],
|
|
143
|
+
fee: DidStdFee,
|
|
144
|
+
memo: string,
|
|
145
|
+
{ accountNumber, sequence, chainId }: SignerData,
|
|
146
|
+
): Promise<TxRaw> {
|
|
147
|
+
assert(isOfflineDirectSigner(this._signer))
|
|
148
|
+
const accountFromSigner = (await this._signer.getAccounts()).find(
|
|
149
|
+
(account) => account.address === signerAddress,
|
|
150
|
+
)
|
|
151
|
+
if (!accountFromSigner) {
|
|
152
|
+
throw new Error("Failed to retrieve account from signer")
|
|
153
|
+
}
|
|
154
|
+
const pubkey = encodePubkey(encodeSecp256k1Pubkey(accountFromSigner.pubkey))
|
|
155
|
+
const txBodyEncodeObject: TxBodyEncodeObject = {
|
|
156
|
+
typeUrl: "/cosmos.tx.v1beta1.TxBody",
|
|
157
|
+
value: {
|
|
158
|
+
messages: messages,
|
|
159
|
+
memo: memo,
|
|
160
|
+
},
|
|
161
|
+
}
|
|
162
|
+
const txBodyBytes = this.registry.encode(txBodyEncodeObject)
|
|
163
|
+
const gasLimit = Int53.fromString(fee.gas).toNumber()
|
|
164
|
+
const authInfoBytes = makeDidAuthInfoBytes([{ pubkey, sequence }], fee.amount, gasLimit, fee.payer!)
|
|
165
|
+
const signDoc = makeSignDoc(txBodyBytes, authInfoBytes, chainId, accountNumber)
|
|
166
|
+
const { signature, signed } = await this._signer.signDirect(signerAddress, signDoc)
|
|
167
|
+
return TxRaw.fromPartial({
|
|
168
|
+
bodyBytes: signed.bodyBytes,
|
|
169
|
+
authInfoBytes: signed.authInfoBytes,
|
|
170
|
+
signatures: [fromBase64(signature.signature)],
|
|
171
|
+
})
|
|
172
|
+
}
|
|
173
|
+
|
|
174
|
+
async checkDidSigners(verificationMethods: Partial<VerificationMethod>[] = []): Promise<TSignerAlgo> {
|
|
175
|
+
if (verificationMethods.length === 0) {
|
|
176
|
+
throw new Error('No verification methods provided')
|
|
177
|
+
}
|
|
178
|
+
|
|
179
|
+
verificationMethods.forEach((verificationMethod) => {
|
|
180
|
+
if (!(Object.values(VerificationMethods) as string[]).includes(verificationMethod.type ?? '')) {
|
|
181
|
+
throw new Error(`Unsupported verification method type: ${verificationMethod.type}`)
|
|
182
|
+
}
|
|
183
|
+
if (!this.didSigners[verificationMethod.type ?? '']) {
|
|
184
|
+
this.didSigners[verificationMethod.type ?? ''] = EdDSASigner
|
|
185
|
+
}
|
|
186
|
+
})
|
|
187
|
+
|
|
188
|
+
return this.didSigners
|
|
189
|
+
}
|
|
190
|
+
|
|
191
|
+
async getDidSigner(verificationMethodId: string, verificationMethods: Partial<VerificationMethod>[]): Promise<(secretKey: Uint8Array) => Signer> {
|
|
192
|
+
await this.checkDidSigners(verificationMethods)
|
|
193
|
+
const verificationMethod = verificationMethods.find(method => method.id === verificationMethodId)?.type
|
|
194
|
+
if (!verificationMethod) {
|
|
195
|
+
throw new Error(`Verification method for ${verificationMethodId} not found`)
|
|
196
|
+
}
|
|
197
|
+
return this.didSigners[verificationMethod]!
|
|
198
|
+
}
|
|
199
|
+
|
|
200
|
+
async signCreateDidTx(signInputs: ISignInputs[], payload: MsgCreateDidPayload): Promise<SignInfo[]> {
|
|
201
|
+
await this.checkDidSigners(payload?.verificationMethod)
|
|
202
|
+
|
|
203
|
+
const signBytes = MsgCreateDidPayload.encode(payload).finish()
|
|
204
|
+
const signInfos: SignInfo[] = await Promise.all(signInputs.map(async (signInput) => {
|
|
205
|
+
return {
|
|
206
|
+
verificationMethodId: signInput.verificationMethodId,
|
|
207
|
+
// TODO: We can't rely on `payload.verificationMethod` here because `CreateResourceTx` doesn't have it
|
|
208
|
+
signature: toString(base64ToBytes((await (await this.getDidSigner(signInput.verificationMethodId, payload.verificationMethod))(hexToBytes(signInput.privateKeyHex))(signBytes)) as string), 'base64pad')
|
|
209
|
+
}
|
|
210
|
+
}))
|
|
211
|
+
|
|
212
|
+
return signInfos
|
|
213
|
+
}
|
|
214
|
+
|
|
215
|
+
async signUpdateDidTx(signInputs: ISignInputs[], payload: MsgUpdateDidPayload): Promise<SignInfo[]> {
|
|
216
|
+
await this.checkDidSigners(payload?.verificationMethod)
|
|
217
|
+
|
|
218
|
+
const signBytes = MsgUpdateDidPayload.encode(payload).finish()
|
|
219
|
+
const signInfos: SignInfo[] = await Promise.all(signInputs.map(async (signInput) => {
|
|
220
|
+
return {
|
|
221
|
+
verificationMethodId: signInput.verificationMethodId,
|
|
222
|
+
// TODO: We can't rely on `payload.verificationMethod` here because `CreateResourceTx` doesn't have it
|
|
223
|
+
signature: toString(base64ToBytes((await (await this.getDidSigner(signInput.verificationMethodId, payload.verificationMethod))(hexToBytes(signInput.privateKeyHex))(signBytes)) as string), 'base64pad')
|
|
224
|
+
}
|
|
225
|
+
}))
|
|
226
|
+
|
|
227
|
+
return signInfos
|
|
228
|
+
}
|
|
229
|
+
}
|
package/src/types.ts
ADDED
|
@@ -0,0 +1,67 @@
|
|
|
1
|
+
import { CheqdSDK } from "."
|
|
2
|
+
import { EdDSASigner, Signer } from 'did-jwt'
|
|
3
|
+
import { Coin } from "@cosmjs/proto-signing"
|
|
4
|
+
|
|
5
|
+
export enum CheqdNetwork {
|
|
6
|
+
Mainnet = 'mainnet',
|
|
7
|
+
Testnet = 'testnet',
|
|
8
|
+
}
|
|
9
|
+
|
|
10
|
+
export interface IModuleMethod {
|
|
11
|
+
(...args: any[]): Promise<any>
|
|
12
|
+
}
|
|
13
|
+
|
|
14
|
+
export interface IModuleMethodMap extends Record<string, IModuleMethod> {}
|
|
15
|
+
|
|
16
|
+
export interface IContext {
|
|
17
|
+
sdk: CheqdSDK
|
|
18
|
+
}
|
|
19
|
+
|
|
20
|
+
export enum VerificationMethods {
|
|
21
|
+
Base58 = 'Ed25519VerificationKey2020',
|
|
22
|
+
JWK = 'JsonWebKey2020',
|
|
23
|
+
}
|
|
24
|
+
|
|
25
|
+
export enum MethodSpecificIdAlgo {
|
|
26
|
+
Base58 = 'base58btc',
|
|
27
|
+
Uuid = 'uuid',
|
|
28
|
+
}
|
|
29
|
+
|
|
30
|
+
export type TSignerAlgo = {
|
|
31
|
+
[key in VerificationMethods as string]?: (secretKey: Uint8Array) => Signer
|
|
32
|
+
}
|
|
33
|
+
|
|
34
|
+
export interface ISignInputs {
|
|
35
|
+
verificationMethodId: string
|
|
36
|
+
privateKeyHex: string
|
|
37
|
+
}
|
|
38
|
+
|
|
39
|
+
export interface IKeyPair {
|
|
40
|
+
publicKey: string
|
|
41
|
+
privateKey: string
|
|
42
|
+
}
|
|
43
|
+
|
|
44
|
+
export interface IKeyValuePair {
|
|
45
|
+
key: string
|
|
46
|
+
value: any
|
|
47
|
+
}
|
|
48
|
+
|
|
49
|
+
export type TVerificationKeyPrefix = string
|
|
50
|
+
|
|
51
|
+
export type TVerificationKey<K extends TVerificationKeyPrefix, N extends number> = `${K}-${N}`
|
|
52
|
+
|
|
53
|
+
export interface IVerificationKeys {
|
|
54
|
+
readonly methodSpecificId: TMethodSpecificId
|
|
55
|
+
readonly didUrl: `did:cheqd:${CheqdNetwork}:${IVerificationKeys['methodSpecificId']}` extends string ? string : never
|
|
56
|
+
readonly keyId: `${IVerificationKeys['didUrl']}#${TVerificationKey<TVerificationKeyPrefix, number>}`
|
|
57
|
+
readonly publicKey: string
|
|
58
|
+
}
|
|
59
|
+
|
|
60
|
+
export type TMethodSpecificId = string
|
|
61
|
+
|
|
62
|
+
export interface DidStdFee {
|
|
63
|
+
readonly amount: readonly Coin[]
|
|
64
|
+
readonly gas: string
|
|
65
|
+
payer?: string
|
|
66
|
+
granter?: string
|
|
67
|
+
}
|
|
@@ -0,0 +1,63 @@
|
|
|
1
|
+
import { DirectSecp256k1HdWallet } from '@cosmjs/proto-signing'
|
|
2
|
+
import { createCheqdSDK, DIDModule, ICheqdSDKOptions } from '../src/index'
|
|
3
|
+
import { exampleCheqdNetwork, faucet } from './testutils.test'
|
|
4
|
+
import { AbstractCheqdSDKModule } from '../src/modules/_'
|
|
5
|
+
import { CheqdSigningStargateClient } from '../src/signer'
|
|
6
|
+
|
|
7
|
+
describe(
|
|
8
|
+
'CheqdSDK', () => {
|
|
9
|
+
describe('constructor', () => {
|
|
10
|
+
it('can be instantiated with modules', async () => {
|
|
11
|
+
const options = {
|
|
12
|
+
modules: [DIDModule as unknown as AbstractCheqdSDKModule],
|
|
13
|
+
rpcUrl: exampleCheqdNetwork.rpcUrl,
|
|
14
|
+
wallet: await DirectSecp256k1HdWallet.fromMnemonic(faucet.mnemonic)
|
|
15
|
+
} as ICheqdSDKOptions
|
|
16
|
+
const cheqdSDK = await createCheqdSDK(options)
|
|
17
|
+
|
|
18
|
+
const sdkMethods = Object.keys(cheqdSDK.methods)
|
|
19
|
+
const testSigner = await CheqdSigningStargateClient.connectWithSigner(options.rpcUrl, options.wallet)
|
|
20
|
+
const moduleMethods = Object.keys(new DIDModule(testSigner).methods)
|
|
21
|
+
|
|
22
|
+
moduleMethods.forEach((method) => {
|
|
23
|
+
expect(sdkMethods).toContain(method)
|
|
24
|
+
})
|
|
25
|
+
})
|
|
26
|
+
|
|
27
|
+
it('should use module methods', async () => {
|
|
28
|
+
const rpcUrl = exampleCheqdNetwork.rpcUrl
|
|
29
|
+
const wallet = await DirectSecp256k1HdWallet.fromMnemonic(faucet.mnemonic)
|
|
30
|
+
const testSigner = await CheqdSigningStargateClient.connectWithSigner(rpcUrl, wallet)
|
|
31
|
+
|
|
32
|
+
class TestModule extends AbstractCheqdSDKModule {
|
|
33
|
+
methods = {
|
|
34
|
+
doSomething: this.doSomething.bind(this)
|
|
35
|
+
}
|
|
36
|
+
constructor(signer: CheqdSigningStargateClient) {
|
|
37
|
+
super(signer)
|
|
38
|
+
}
|
|
39
|
+
async doSomething(): Promise<string> {
|
|
40
|
+
return 'did something'
|
|
41
|
+
}
|
|
42
|
+
}
|
|
43
|
+
const options = {
|
|
44
|
+
modules: [TestModule as unknown as AbstractCheqdSDKModule],
|
|
45
|
+
rpcUrl,
|
|
46
|
+
wallet
|
|
47
|
+
} as ICheqdSDKOptions
|
|
48
|
+
|
|
49
|
+
const cheqdSDK = await createCheqdSDK(options)
|
|
50
|
+
|
|
51
|
+
//@ts-ignore
|
|
52
|
+
const doSomething = await cheqdSDK.doSomething()
|
|
53
|
+
expect(doSomething).toBe('did something')
|
|
54
|
+
|
|
55
|
+
//@ts-ignore
|
|
56
|
+
const spy = jest.spyOn(cheqdSDK.methods, 'doSomething')
|
|
57
|
+
//@ts-ignore
|
|
58
|
+
await cheqdSDK.doSomething()
|
|
59
|
+
expect(spy).toHaveBeenCalled()
|
|
60
|
+
})
|
|
61
|
+
})
|
|
62
|
+
}
|
|
63
|
+
)
|