|
1 | 1 | import { ApiPromise, HttpProvider, WsProvider } from "@polkadot/api"; |
2 | 2 | import { RunNodeState } from "./node"; |
3 | 3 | import { AddCleanup } from "./cleanup"; |
| 4 | +import { SubmittableExtrinsic } from "@polkadot/api/types/submittable"; |
| 5 | +import { KeyringPair } from "@polkadot/keyring/types"; |
| 6 | +import { Callback, ISubmittableResult } from "@polkadot/types/types"; |
4 | 7 |
|
5 | 8 | export type Provider = HttpProvider | WsProvider; |
6 | 9 | export type Api = ApiPromise; |
@@ -50,3 +53,95 @@ export const apiFromNodeWebSocket = ( |
50 | 53 | node: RunNodeState, |
51 | 54 | addCleanup: AddCleanup, |
52 | 55 | ): Promise<Api> => apiWebSocket(node.meta.rpcUrlWs, addCleanup); |
| 56 | + |
| 57 | +class TxFailedError extends Error { |
| 58 | + constructor( |
| 59 | + readonly extrinsic: SubmittableExtrinsic<"promise">, |
| 60 | + readonly result: ISubmittableResult, |
| 61 | + ) { |
| 62 | + let message = `Extrinsic ${extrinsic.method.section}.${extrinsic.method.method} failed`; |
| 63 | + |
| 64 | + if (result.dispatchError) { |
| 65 | + message = `${message}: ${JSON.stringify(result.dispatchError.asModule.toHuman())}`; |
| 66 | + } |
| 67 | + |
| 68 | + super(message); |
| 69 | + |
| 70 | + // Hide the uselessly long properties from the error inspect printing. |
| 71 | + Object.defineProperty(this, "extrinsic", { enumerable: false }); |
| 72 | + Object.defineProperty(this, "result", { enumerable: false }); |
| 73 | + } |
| 74 | +} |
| 75 | + |
| 76 | +type ExecuteFn<E, R> = (extrinsic: E, cb: Callback<R>) => Promise<() => void>; |
| 77 | + |
| 78 | +const execute = < |
| 79 | + E extends SubmittableExtrinsic<"promise">, |
| 80 | + R extends ISubmittableResult, |
| 81 | +>( |
| 82 | + extrinsic: E, |
| 83 | + f: ExecuteFn<E, R>, |
| 84 | +) => |
| 85 | + new Promise<ISubmittableResult>((resolve, reject) => { |
| 86 | + console.log( |
| 87 | + `==> ${extrinsic.method.section}.${extrinsic.method.method} / ${extrinsic.hash.toHex()}`, |
| 88 | + ); |
| 89 | + let fired = false; |
| 90 | + f(extrinsic, (result) => { |
| 91 | + if (fired) return; |
| 92 | + |
| 93 | + const { isCompleted, internalError, events, status, dispatchError } = |
| 94 | + result; |
| 95 | + |
| 96 | + if (internalError) { |
| 97 | + reject(internalError); |
| 98 | + fired = true; |
| 99 | + return; |
| 100 | + } |
| 101 | + |
| 102 | + if (isCompleted) { |
| 103 | + console.log(` Complete!`); |
| 104 | + console.log(` Status: ${status.type}`); |
| 105 | + console.log(` Error: ${dispatchError ? "yes" : "no"}`); |
| 106 | + console.log(` Events:`); |
| 107 | + |
| 108 | + for (const item of events) { |
| 109 | + console.log(` ${item.event.section} / ${item.event.method}:`); |
| 110 | + console.log( |
| 111 | + ` ${JSON.stringify(item.event.data.toHuman(), null, 2).replaceAll("\n", "\n ")}\n`, |
| 112 | + ); |
| 113 | + } |
| 114 | + |
| 115 | + console.log(" ... events end\n"); |
| 116 | + |
| 117 | + fired = true; |
| 118 | + |
| 119 | + if (dispatchError) { |
| 120 | + const error = new TxFailedError(extrinsic, result); |
| 121 | + reject(error); |
| 122 | + return; |
| 123 | + } |
| 124 | + |
| 125 | + resolve(result); |
| 126 | + } |
| 127 | + }).catch((err: unknown) => { |
| 128 | + reject(err as Error); |
| 129 | + }); |
| 130 | + }); |
| 131 | + |
| 132 | +export type Params = { |
| 133 | + signWith?: KeyringPair; |
| 134 | +}; |
| 135 | + |
| 136 | +export const sendAndWait = ( |
| 137 | + extrinsic: SubmittableExtrinsic<"promise">, |
| 138 | + params: Params = {}, |
| 139 | +) => { |
| 140 | + const { signWith } = params; |
| 141 | + |
| 142 | + return signWith === undefined |
| 143 | + ? execute(extrinsic, (extrinsic, cb) => extrinsic.send(cb)) |
| 144 | + : execute(extrinsic, (extrinsic, cb) => |
| 145 | + extrinsic.signAndSend(signWith, cb), |
| 146 | + ); |
| 147 | +}; |
0 commit comments