@@ -5,6 +5,7 @@ import type { Observable } from 'https://esm.sh/rxjs@7.8.1';
5
5
import type { Address , ApplyExtrinsicResult , Call , Extrinsic , ExtrinsicEra , ExtrinsicStatus , Hash , Header , Index , RuntimeDispatchInfo , SignerPayload } from 'https://deno.land/x/polkadot/types/interfaces/index.ts' ;
6
6
import type { Callback , Codec , CodecClass , ISubmittableResult , SignatureOptions } from 'https://deno.land/x/polkadot/types/types/index.ts' ;
7
7
import type { Registry } from 'https://deno.land/x/polkadot/types-codec/types/index.ts' ;
8
+ import type { HexString } from 'https://deno.land/x/polkadot/util/types.ts' ;
8
9
import type { ApiBase } from '../base/index.ts' ;
9
10
import type { ApiInterfaceRx , ApiTypes , PromiseOrObs , SignerResult } from '../types/index.ts' ;
10
11
import type { AddressOrPair , SignerOptions , SubmittableDryRunResult , SubmittableExtrinsic , SubmittablePaymentResult , SubmittableResultResult , SubmittableResultSubscription } from './types.ts' ;
@@ -26,6 +27,12 @@ interface SubmittableOptions<ApiType extends ApiTypes> {
26
27
interface UpdateInfo {
27
28
options : SignatureOptions ;
28
29
updateId : number ;
30
+ signedTransaction : HexString | Uint8Array | null ;
31
+ }
32
+
33
+ interface SignerInfo {
34
+ id : number ;
35
+ signedTransaction ?: HexString | Uint8Array ;
29
36
}
30
37
31
38
function makeEraOptions ( api : ApiInterfaceRx , registry : Registry , partialOptions : Partial < SignerOptions > , { header, mortalLength, nonce } : { header : Header | null ; mortalLength : number ; nonce : Index } ) : SignatureOptions {
@@ -244,14 +251,21 @@ export function createClass <ApiType extends ApiTypes> ({ api, apiType, blockHas
244
251
mergeMap ( async ( signingInfo ) : Promise < UpdateInfo > => {
245
252
const eraOptions = makeEraOptions ( api , this . registry , options , signingInfo ) ;
246
253
let updateId = - 1 ;
254
+ let signedTx = null ;
247
255
248
256
if ( isKeyringPair ( account ) ) {
249
257
this . sign ( account , eraOptions ) ;
250
258
} else {
251
- updateId = await this . #signViaSigner( address , eraOptions , signingInfo . header ) ;
259
+ const result = await this . #signViaSigner( address , eraOptions , signingInfo . header ) ;
260
+
261
+ updateId = result . id ;
262
+
263
+ if ( result . signedTransaction ) {
264
+ signedTx = result . signedTransaction ;
265
+ }
252
266
}
253
267
254
- return { options : eraOptions , updateId } ;
268
+ return { options : eraOptions , signedTransaction : signedTx , updateId } ;
255
269
} )
256
270
) ;
257
271
} ;
@@ -286,18 +300,18 @@ export function createClass <ApiType extends ApiTypes> ({ api, apiType, blockHas
286
300
) ;
287
301
} ;
288
302
289
- #observeSend = ( info : UpdateInfo ) : Observable < Hash > => {
290
- return api . rpc . author . submitExtrinsic ( this ) . pipe (
303
+ #observeSend = ( info ? : UpdateInfo ) : Observable < Hash > => {
304
+ return api . rpc . author . submitExtrinsic ( info ?. signedTransaction || this ) . pipe (
291
305
tap ( ( hash ) : void => {
292
306
this . #updateSigner( hash , info ) ;
293
307
} )
294
308
) ;
295
309
} ;
296
310
297
- #observeSubscribe = ( info : UpdateInfo ) : Observable < ISubmittableResult > => {
311
+ #observeSubscribe = ( info ? : UpdateInfo ) : Observable < ISubmittableResult > => {
298
312
const txHash = this . hash ;
299
313
300
- return api . rpc . author . submitAndWatchExtrinsic ( this ) . pipe (
314
+ return api . rpc . author . submitAndWatchExtrinsic ( info ?. signedTransaction || this ) . pipe (
301
315
switchMap ( ( status ) : Observable < ISubmittableResult > =>
302
316
this . #observeStatus( txHash , status )
303
317
) ,
@@ -307,7 +321,7 @@ export function createClass <ApiType extends ApiTypes> ({ api, apiType, blockHas
307
321
) ;
308
322
} ;
309
323
310
- #signViaSigner = async ( address : Address | string | Uint8Array , options : SignatureOptions , header : Header | null ) : Promise < number > => {
324
+ #signViaSigner = async ( address : Address | string | Uint8Array , options : SignatureOptions , header : Header | null ) : Promise < SignerInfo > => {
311
325
const signer = options . signer || api . signer ;
312
326
313
327
if ( ! signer ) {
@@ -323,6 +337,20 @@ export function createClass <ApiType extends ApiTypes> ({ api, apiType, blockHas
323
337
324
338
if ( isFunction ( signer . signPayload ) ) {
325
339
result = await signer . signPayload ( payload . toPayload ( ) ) ;
340
+
341
+ // When the signedTransaction is included by the signer, we no longer add
342
+ // the signature to the parent class, but instead broadcast the signed transaction directly.
343
+ if ( result . signedTransaction ) {
344
+ const ext = this . registry . createTypeUnsafe < Extrinsic > ( 'Extrinsic' , [ result . signedTransaction ] ) ;
345
+
346
+ if ( ! ext . isSigned ) {
347
+ throw new Error ( `When using the signedTransaction field, the transaction must be signed. Recieved isSigned: ${ ext . isSigned } ` ) ;
348
+ }
349
+
350
+ this . #validateSignedTransaction( payload , ext ) ;
351
+
352
+ return { id : result . id , signedTransaction : result . signedTransaction } ;
353
+ }
326
354
} else if ( isFunction ( signer . signRaw ) ) {
327
355
result = await signer . signRaw ( payload . toRaw ( ) ) ;
328
356
} else {
@@ -334,7 +362,7 @@ export function createClass <ApiType extends ApiTypes> ({ api, apiType, blockHas
334
362
// payload data is not modified from our inputs, but the signer
335
363
super . addSignature ( address , result . signature , payload . toPayload ( ) ) ;
336
364
337
- return result . id ;
365
+ return { id : result . id } ;
338
366
} ;
339
367
340
368
#updateSigner = ( status : Hash | ISubmittableResult , info ?: UpdateInfo ) : void => {
@@ -347,6 +375,20 @@ export function createClass <ApiType extends ApiTypes> ({ api, apiType, blockHas
347
375
}
348
376
}
349
377
} ;
378
+
379
+ /**
380
+ * When a signer includes `signedTransaction` within the SignerResult this will validate
381
+ * specific fields within the signed extrinsic against the original payload that was passed
382
+ * to the signer.
383
+ */
384
+ #validateSignedTransaction = ( signerPayload : SignerPayload , signedExt : Extrinsic ) : void => {
385
+ const payload = signerPayload . toPayload ( ) ;
386
+ const errMsg = ( field : string ) => `signAndSend: ${ field } does not match the original payload` ;
387
+
388
+ if ( payload . method !== signedExt . method . toHex ( ) ) {
389
+ throw new Error ( errMsg ( 'call data' ) ) ;
390
+ }
391
+ } ;
350
392
}
351
393
352
394
return Submittable ;
0 commit comments