@@ -14,6 +14,7 @@ import {
14
14
SigningStargateClientOptions ,
15
15
calculateFee ,
16
16
SignerData ,
17
+ createProtobufRpcClient ,
17
18
} from '@cosmjs/stargate-cjs' ;
18
19
import { Tendermint37Client } from '@cosmjs/tendermint-rpc-cjs' ;
19
20
import { createDefaultCheqdRegistry } from './registry' ;
@@ -25,17 +26,34 @@ import {
25
26
VerificationMethod ,
26
27
DidDoc ,
27
28
} from '@cheqd/ts-proto-cjs/cheqd/did/v2' ;
28
- import { DIDDocument , DidStdFee , ISignInputs , TSignerAlgo , VerificationMethods } from './types' ;
29
+ import {
30
+ DIDDocument ,
31
+ DidStdFee ,
32
+ ISignInputs ,
33
+ MessageBatch ,
34
+ TSignerAlgo ,
35
+ TxExtension ,
36
+ VerificationMethods ,
37
+ } from './types' ;
29
38
import { base64ToBytes , EdDSASigner , hexToBytes , Signer , ES256Signer , ES256KSigner } from 'did-jwt-cjs' ;
30
39
import { assert , assertDefined } from '@cosmjs/utils-cjs' ;
31
- import { encodeSecp256k1Pubkey } from '@cosmjs/amino-cjs' ;
40
+ import { encodeSecp256k1Pubkey , Pubkey } from '@cosmjs/amino-cjs' ;
32
41
import { Int53 } from '@cosmjs/math-cjs' ;
33
42
import { fromBase64 } from '@cosmjs/encoding-cjs' ;
34
43
import { AuthInfo , SignerInfo , TxRaw } from 'cosmjs-types-cjs/cosmos/tx/v1beta1/tx' ;
35
44
import { SignMode } from 'cosmjs-types-cjs/cosmos/tx/signing/v1beta1/signing' ;
36
45
import { Any } from 'cosmjs-types-cjs/google/protobuf/any' ;
37
46
import { Coin } from 'cosmjs-types-cjs/cosmos/base/v1beta1/coin' ;
38
47
import Long from 'long-cjs' ;
48
+ import { CheqdQuerier } from './querier' ;
49
+ import { Uint53 } from '@cosmjs/math-cjs' ;
50
+ import {
51
+ GetTxResponse ,
52
+ ServiceClientImpl ,
53
+ SimulateRequest ,
54
+ SimulateResponse ,
55
+ } from 'cosmjs-types-cjs/cosmos/tx/v1beta1/service' ;
56
+ import { Tx , TxBody , Fee } from 'cosmjs-types/cosmos/tx/v1beta1/tx.js' ;
39
57
40
58
export function calculateDidFee ( gasLimit : number , gasPrice : string | GasPrice ) : DidStdFee {
41
59
return calculateFee ( gasLimit , gasPrice ) ;
@@ -78,27 +96,34 @@ export class CheqdSigningStargateClient extends SigningStargateClient {
78
96
private didSigners : TSignerAlgo = { } ;
79
97
private readonly _gasPrice : GasPrice | undefined ;
80
98
private readonly _signer : OfflineSigner ;
99
+ private readonly endpoint ?: string ;
81
100
82
101
public static async connectWithSigner (
83
102
endpoint : string | HttpEndpoint ,
84
103
signer : OfflineSigner ,
85
- options ?: SigningStargateClientOptions | undefined
104
+ options ?: ( SigningStargateClientOptions & { endpoint ?: string } ) | undefined
86
105
) : Promise < CheqdSigningStargateClient > {
87
- const tmClient = await Tendermint37Client . connect ( endpoint ) ;
88
- return new CheqdSigningStargateClient ( tmClient , signer , {
106
+ const cometClient = await Tendermint37Client . connect ( endpoint ) ;
107
+ return new CheqdSigningStargateClient ( cometClient , signer , {
89
108
registry : options ?. registry ? options . registry : createDefaultCheqdRegistry ( ) ,
109
+ endpoint : options ?. endpoint
110
+ ? typeof options . endpoint === 'string'
111
+ ? options . endpoint
112
+ : ( options . endpoint as HttpEndpoint ) . url
113
+ : undefined ,
90
114
...options ,
91
115
} ) ;
92
116
}
93
117
94
118
constructor (
95
- tmClient : Tendermint37Client | undefined ,
119
+ cometClient : Tendermint37Client | undefined ,
96
120
signer : OfflineSigner ,
97
- options : SigningStargateClientOptions = { }
121
+ options : SigningStargateClientOptions & { endpoint ?: string } = { }
98
122
) {
99
- super ( tmClient , signer , options ) ;
123
+ super ( cometClient , signer , options ) ;
100
124
this . _signer = signer ;
101
- if ( options . gasPrice ) this . _gasPrice = options . gasPrice ;
125
+ this . _gasPrice = options . gasPrice ;
126
+ this . endpoint = options . endpoint ;
102
127
}
103
128
104
129
async signAndBroadcast (
@@ -181,6 +206,152 @@ export class CheqdSigningStargateClient extends SigningStargateClient {
181
206
} ) ;
182
207
}
183
208
209
+ async simulate (
210
+ signerAddress : string ,
211
+ messages : readonly EncodeObject [ ] ,
212
+ memo : string | undefined
213
+ ) : Promise < number > {
214
+ if ( ! this . endpoint ) {
215
+ throw new Error ( 'querier: endpoint is not set' ) ;
216
+ }
217
+ const querier = await CheqdQuerier . connect ( this . endpoint ) ;
218
+ const anyMsgs = messages . map ( ( msg ) => this . registry . encodeAsAny ( msg ) ) ;
219
+ const accountFromSigner = ( await this . _signer . getAccounts ( ) ) . find (
220
+ ( account ) => account . address === signerAddress
221
+ ) ;
222
+ if ( ! accountFromSigner ) {
223
+ throw new Error ( 'Failed to retrieve account from signer' ) ;
224
+ }
225
+ const pubkey = encodeSecp256k1Pubkey ( accountFromSigner . pubkey ) ;
226
+ const { sequence } = await this . getSequence ( signerAddress ) ;
227
+ const gasLimit = ( await CheqdQuerier . getConsensusParameters ( this . endpoint ) ) ! . block . maxGas ;
228
+ const { gasInfo } = await (
229
+ await this . constructSimulateExtension ( querier )
230
+ ) . tx . simulate ( anyMsgs , memo , pubkey , signerAddress , sequence , gasLimit ) ;
231
+ assertDefined ( gasInfo ) ;
232
+ return Uint53 . fromString ( gasInfo . gasUsed . toString ( ) ) . toNumber ( ) ;
233
+ }
234
+
235
+ async constructSimulateExtension ( querier : CheqdQuerier ) : Promise < TxExtension > {
236
+ // setup rpc client
237
+ const rpc = createProtobufRpcClient ( querier ) ;
238
+
239
+ // setup query tx query service
240
+ const queryService = new ServiceClientImpl ( rpc ) ;
241
+
242
+ // setup + return tx extension
243
+ return {
244
+ tx : {
245
+ getTx : async ( txId : string ) : Promise < GetTxResponse > => {
246
+ // construct request
247
+ const request = { hash : txId } ;
248
+
249
+ // query + return tx
250
+ return await queryService . GetTx ( request ) ;
251
+ } ,
252
+ simulate : async (
253
+ messages : readonly Any [ ] ,
254
+ memo : string | undefined ,
255
+ signer : Pubkey ,
256
+ signerAddress : string ,
257
+ sequence : number ,
258
+ gasLimit : number
259
+ ) : Promise < SimulateResponse > => {
260
+ // encode public key
261
+ const publicKey = encodePubkey ( signer ) ;
262
+
263
+ // construct max gas limit
264
+ const maxGasLimit = Int53 . fromString ( gasLimit . toString ( ) ) . toNumber ( ) ;
265
+
266
+ // construct unsigned tx
267
+ const tx = Tx . fromPartial ( {
268
+ body : TxBody . fromPartial ( {
269
+ messages : Array . from ( messages ) ,
270
+ memo,
271
+ } ) ,
272
+ authInfo : AuthInfo . fromPartial ( {
273
+ fee : Fee . fromPartial ( {
274
+ amount : [ ] ,
275
+ gasLimit : maxGasLimit ,
276
+ payer : signerAddress ,
277
+ } ) ,
278
+ signerInfos : [
279
+ {
280
+ publicKey,
281
+ modeInfo : {
282
+ single : { mode : SignMode . SIGN_MODE_DIRECT } ,
283
+ } ,
284
+ sequence : sequence ,
285
+ } ,
286
+ ] ,
287
+ } ) ,
288
+ signatures : [ new Uint8Array ( ) ] ,
289
+ } ) ;
290
+
291
+ // construct request
292
+ const request = SimulateRequest . fromPartial ( {
293
+ txBytes : Tx . encode ( tx ) . finish ( ) ,
294
+ } ) ;
295
+
296
+ // query + return simulation response
297
+ return await queryService . Simulate ( request ) ;
298
+ } ,
299
+ } ,
300
+ } ;
301
+ }
302
+
303
+ async batchMessages (
304
+ messages : readonly EncodeObject [ ] ,
305
+ signerAddress : string ,
306
+ memo ?: string ,
307
+ maxGasLimit : number = 30000000 // default gas limit, use consensus params if available
308
+ ) : Promise < MessageBatch > {
309
+ // simulate
310
+ const gasEstimates = await Promise . all (
311
+ messages . map ( async ( message ) => this . simulate ( signerAddress , [ message ] , memo ) )
312
+ ) ;
313
+
314
+ // batch messages
315
+ const { batches, gasPerBatch, currentBatch, currentBatchGas } = gasEstimates . reduce (
316
+ ( acc , gasUsed , index ) => {
317
+ // finalise current batch, if limit is surpassed
318
+ if ( acc . currentBatchGas + gasUsed > maxGasLimit ) {
319
+ return {
320
+ batches : [ ...acc . batches , acc . currentBatch ] ,
321
+ gasPerBatch : [ ...acc . gasPerBatch , acc . currentBatchGas ] ,
322
+ currentBatch : [ messages [ index ] ] , // Start a new batch with the current message
323
+ currentBatchGas : gasUsed , // Reset the gas counter for the new batch
324
+ } ;
325
+ }
326
+
327
+ // otherwise, add to current batch
328
+ return {
329
+ batches : acc . batches ,
330
+ gasPerBatch : acc . gasPerBatch ,
331
+ currentBatch : [ ...acc . currentBatch , messages [ index ] ] ,
332
+ currentBatchGas : acc . currentBatchGas + gasUsed ,
333
+ } ;
334
+ } ,
335
+ {
336
+ batches : [ ] as EncodeObject [ ] [ ] ,
337
+ gasPerBatch : [ ] as number [ ] ,
338
+ currentBatch : [ ] as EncodeObject [ ] ,
339
+ currentBatchGas : 0 ,
340
+ }
341
+ ) ;
342
+
343
+ // push final batch to batches, if not empty + return
344
+ return currentBatch . length > 0
345
+ ? {
346
+ batches : [ ...batches , currentBatch ] ,
347
+ gas : [ ...gasPerBatch , currentBatchGas ] ,
348
+ }
349
+ : {
350
+ batches,
351
+ gas : gasPerBatch ,
352
+ } ;
353
+ }
354
+
184
355
async checkDidSigners ( verificationMethods : Partial < VerificationMethod > [ ] = [ ] ) : Promise < TSignerAlgo > {
185
356
if ( verificationMethods . length === 0 ) {
186
357
throw new Error ( 'No verification methods provided' ) ;
0 commit comments