generated from PolymeshAssociation/typescript-boilerplate
-
Notifications
You must be signed in to change notification settings - Fork 11
/
Polymesh.ts
345 lines (310 loc) · 10.6 KB
/
Polymesh.ts
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
219
220
221
222
223
224
225
226
227
228
229
230
231
232
233
234
235
236
237
238
239
240
241
242
243
244
245
246
247
248
249
250
251
252
253
254
255
256
257
258
259
260
261
262
263
264
265
266
267
268
269
270
271
272
273
274
275
276
277
278
279
280
281
282
283
284
285
286
287
288
289
290
291
292
293
294
295
296
297
298
299
300
301
302
303
304
305
306
307
308
309
310
311
312
313
314
315
316
317
318
319
320
321
322
323
324
325
326
327
328
329
330
331
332
333
334
335
336
337
338
339
340
341
342
343
344
345
import {
ApolloClient,
createHttpLink,
InMemoryCache,
NormalizedCacheObject,
} from '@apollo/client/core';
import { ApiPromise, WsProvider } from '@polkadot/api';
import { SigningManager } from '@polymeshassociation/signing-manager-types';
import fetch from 'cross-fetch';
import schema from 'polymesh-types/schema';
import { Account, Context, createTransactionBatch, Identity, PolymeshError } from '~/internal';
import {
CreateTransactionBatchProcedureMethod,
ErrorCode,
MiddlewareConfig,
PolkadotConfig,
UnsubCallback,
} from '~/types';
import { signerToString } from '~/utils/conversion';
import {
assertExpectedChainVersion,
assertExpectedSqVersion,
createProcedureMethod,
} from '~/utils/internal';
import { AccountManagement } from './AccountManagement';
import { Assets } from './Assets';
import { Claims } from './Claims';
import { Identities } from './Identities';
import { Network } from './Network';
import { Settlements } from './Settlements';
export interface ConnectParams {
/**
* The websocket URL for the Polymesh node to connect to
*/
nodeUrl: string;
/**
* Handles signing of transactions. Required to be set before submitting transactions
*/
signingManager?: SigningManager;
/**
* Allows for historical data to be queried. Required for some methods to work
*/
middlewareV2?: MiddlewareConfig;
/**
* Advanced options that will be used with the underling polkadot.js instance
*/
polkadot?: PolkadotConfig;
}
/**
* @hidden
*/
function createMiddlewareApi(
middleware?: MiddlewareConfig
): ApolloClient<NormalizedCacheObject> | null {
return middleware
? new ApolloClient({
link: createHttpLink({
uri: middleware.link,
fetch,
// eslint-disable-next-line @typescript-eslint/naming-convention
headers: { 'x-api-key': middleware.key },
}),
cache: new InMemoryCache(),
defaultOptions: {
watchQuery: {
fetchPolicy: 'no-cache',
},
query: {
fetchPolicy: 'no-cache',
},
},
})
: null;
}
/**
* Main entry point of the Polymesh SDK
*/
export class Polymesh {
private context: Context = {} as Context;
// Namespaces
/**
* A set of methods to deal with Claims
*/
public claims: Claims;
/**
* A set of methods to interact with the Polymesh network. This includes transferring POLYX, reading network properties and querying for historical events
*/
public network: Network;
/**
* A set of methods for exchanging Assets
*/
public settlements: Settlements;
/**
* A set of methods for managing a Polymesh Identity's Accounts and their permissions
*/
public accountManagement: AccountManagement;
/**
* A set of methods for interacting with Polymesh Identities.
*/
public identities: Identities;
/**
* A set of methods for interacting with Assets
*/
public assets: Assets;
/**
* @hidden
*/
private constructor(context: Context) {
this.context = context;
this.claims = new Claims(context);
this.network = new Network(context);
this.settlements = new Settlements(context);
this.accountManagement = new AccountManagement(context);
this.identities = new Identities(context);
this.assets = new Assets(context);
this.createTransactionBatch = createProcedureMethod(
{
getProcedureAndArgs: args => [createTransactionBatch, { ...args }],
},
context
) as CreateTransactionBatchProcedureMethod;
}
/**
* Create an SDK instance and connect to a Polymesh node
*
* @param params.nodeUrl - URL of the Polymesh node this instance will be connecting to
* @param params.signingManager - object in charge of managing keys and signing transactions
* (optional, if not passed the SDK will not be able to submit transactions). Can be set later with
* `setSigningManager`
* @param params.middlewareV2 - middleware V2 API URL (optional, used for historic queries)
* @param params.polkadot - optional config for polkadot `ApiPromise`
*/
static async connect(params: ConnectParams): Promise<Polymesh> {
const { nodeUrl, signingManager, middlewareV2, polkadot } = params;
let context: Context;
let polymeshApi: ApiPromise;
const { metadata, noInitWarn, typesBundle } = polkadot ?? {};
// Defer `await` on any checks to minimize total startup time
const requiredChecks: Promise<void>[] = [assertExpectedChainVersion(nodeUrl)];
try {
const { types, rpc, signedExtensions } = schema;
polymeshApi = await ApiPromise.create({
provider: new WsProvider(nodeUrl),
types,
rpc,
signedExtensions,
metadata,
noInitWarn,
typesBundle,
});
context = await Context.create({
polymeshApi,
middlewareApiV2: createMiddlewareApi(middlewareV2),
signingManager,
});
} catch (err) {
const { message, code } = err;
throw new PolymeshError({
code,
message: `Error while connecting to "${nodeUrl}": "${
message || 'The node couldn’t be reached'
}"`,
});
}
if (middlewareV2) {
let middlewareMetadata = null;
const checkMiddleware = async (): Promise<void> => {
try {
middlewareMetadata = await context.getMiddlewareMetadata();
} catch (err) {
throw new PolymeshError({
code: ErrorCode.FatalError,
message: 'Could not query for middleware V2 metadata',
});
}
if (
!middlewareMetadata ||
middlewareMetadata.genesisHash !== polymeshApi.genesisHash.toString()
) {
throw new PolymeshError({
code: ErrorCode.FatalError,
message: 'Middleware V2 URL is for a different chain than the given node URL',
});
}
};
requiredChecks.push(checkMiddleware(), assertExpectedSqVersion(context));
}
await Promise.all(requiredChecks);
return new Polymesh(context);
}
/**
* Retrieve the Identity associated to the signing Account (null if there is none)
*
* @throws if there is no signing Account associated to the SDK
*/
public getSigningIdentity(): Promise<Identity | null> {
return this.context.getSigningAccount().getIdentity();
}
/**
* Handle connection errors
*
* @returns an unsubscribe callback
*/
public onConnectionError(callback: (...args: unknown[]) => unknown): UnsubCallback {
const {
context: { polymeshApi },
} = this;
polymeshApi.on('error', callback);
return (): void => {
polymeshApi.off('error', callback);
};
}
/**
* Handle disconnection
*
* @returns an unsubscribe callback
*/
public onDisconnect(callback: (...args: unknown[]) => unknown): UnsubCallback {
const {
context: { polymeshApi },
} = this;
polymeshApi.on('disconnected', callback);
return (): void => {
polymeshApi.off('disconnected', callback);
};
}
/**
* Disconnect the client and close all open connections and subscriptions
*
* @note the SDK will become unusable after this operation. It will throw an error when attempting to
* access any chain or middleware data. If you wish to continue using the SDK, you must
* create a new instance by calling {@link connect}
*/
public disconnect(): Promise<void> {
return this.context.disconnect();
}
/**
* Set the SDK's signing Account to the provided one
*
* @throws if the passed Account is not present in the Signing Manager (or there is no Signing Manager)
*/
public setSigningAccount(signer: string | Account): Promise<void> {
return this.context.setSigningAddress(signerToString(signer));
}
/**
* Set the SDK's Signing Manager to the provided one.
*
* @note Pass `null` to unset the current signing manager
*/
public setSigningManager(signingManager: SigningManager | null): Promise<void> {
return this.context.setSigningManager(signingManager);
}
/**
* Create a batch transaction from a list of separate transactions. The list can contain batch transactions as well.
* The result of running this transaction will be an array of the results of each transaction in the list, in the same order.
* Transactions with no return value will produce `undefined` in the resulting array
*
* @param args.transactions - list of {@link base/PolymeshTransaction!PolymeshTransaction} or {@link base/PolymeshTransactionBatch!PolymeshTransactionBatch}
*
* @example Batching 3 ticker reservation transactions
*
* ```typescript
* const tx1 = await sdk.assets.reserveTicker({ ticker: 'FOO' });
* const tx2 = await sdk.assets.reserveTicker({ ticker: 'BAR' });
* const tx3 = await sdk.assets.reserveTicker({ ticker: 'BAZ' });
*
* const batch = sdk.createTransactionBatch({ transactions: [tx1, tx2, tx3] as const });
*
* const [res1, res2, res3] = await batch.run();
* ```
*
* @example Specifying the signer account for the whole batch
*
* ```typescript
* const batch = sdk.createTransactionBatch({ transactions: [tx1, tx2, tx3] as const }, { signingAccount: 'someAddress' });
*
* const [res1, res2, res3] = await batch.run();
* ```
*
* @note it is mandatory to use the `as const` type assertion when passing in the transaction array to the method in order to get the correct types
* for the results of running the batch
* @note if a signing Account is not specified, the default one will be used (the one returned by `sdk.accountManagement.getSigningAccount()`)
* @note all fees in the resulting batch must be paid by the calling Account, regardless of any exceptions that would normally be made for
* the individual transactions (such as subsidies or accepting invitations to join an Identity)
*/
public createTransactionBatch: CreateTransactionBatchProcedureMethod;
/* eslint-disable @typescript-eslint/naming-convention */
/* istanbul ignore next: not part of the official public API */
/**
* Polkadot client
*/
public get _polkadotApi(): ApiPromise {
return this.context.getPolymeshApi();
}
/* istanbul ignore next: not part of the official public API */
/**
* signing address (to manually submit transactions with the polkadot API)
*/
public get _signingAddress(): string {
return this.context.getSigningAddress();
}
/* istanbul ignore next: not part of the official public API */
/**
* MiddlewareV2 client
*/
public get _middlewareApiV2(): ApolloClient<NormalizedCacheObject> {
return this.context.middlewareApi;
}
/* eslint-enable @typescript-eslint/naming-convention */
}