Skip to content

Commit ce9fcb8

Browse files
committed
fix: fix error when fetching 2.0 tokens
add an endpoint to fetch launched token symbols for an address
1 parent 4938ff1 commit ce9fcb8

32 files changed

+358
-95
lines changed

package.json

Lines changed: 0 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -98,7 +98,6 @@
9898
"eslint-config-prettier": "^4.1.0",
9999
"eslint-plugin-import": "^2.16.0",
100100
"eslint-plugin-jest": "^22.15.0",
101-
"eslint-plugin-tsdoc": "^0.2.0",
102101
"husky": "^1.3.1",
103102
"jest": "^24.1.0",
104103
"lint-staged": "^8.1.5",

src/Context.ts

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -44,6 +44,7 @@ export class Context {
4444

4545
public factories: Factories;
4646

47+
// eslint-disable-next-line require-jsdoc
4748
constructor(params: ConstructorParams) {
4849
const { contractWrappers } = params;
4950

src/Polymath.ts

Lines changed: 130 additions & 30 deletions
Original file line numberDiff line numberDiff line change
@@ -7,14 +7,15 @@ import {
77
} from '@0x/subproviders';
88
import P from 'bluebird';
99
import phin from 'phin';
10-
import { union, compact } from 'lodash';
10+
import { union, compact, chunk } from 'lodash';
1111
import { Context } from './Context';
1212
import { getInjectedProvider } from './browserUtils';
13-
import { ErrorCode, TransactionSpeed } from './types';
13+
import { ErrorCode, TransactionSpeed, Version } from './types';
1414
import { SecurityToken, Wallet } from './entities';
1515
import { ReserveSecurityToken } from './procedures';
1616
import { PolymathError } from './PolymathError';
1717
import { PolymathBase } from './PolymathBase';
18+
import { convertVersionToEnum } from './utils';
1819

1920
interface PolymathNetworkParams {
2021
polymathRegistryAddress?: string;
@@ -36,16 +37,10 @@ interface Connect {
3637
(params: PolymathNetworkNodeParams): Promise<Polymath>;
3738
}
3839

39-
/**
40-
* @param symbol Security Token symbol
41-
*/
4240
interface SymbolParams {
4341
symbol: string;
4442
}
4543

46-
/**
47-
* @param address Address of the Security Token contract
48-
*/
4944
interface AddressParams {
5045
address: string;
5146
}
@@ -56,6 +51,13 @@ interface GetSecurityToken {
5651
(params: string): Promise<SecurityToken>;
5752
}
5853

54+
/**
55+
* @hidden
56+
* Estimates the gas price to use in transactions
57+
*
58+
* @param speed - speed at which transactions should be mined
59+
* @param basePrice - base to multiply depending on the desired speed
60+
*/
5961
const calculateGasPrice = (speed: TransactionSpeed, basePrice: BigNumber) => {
6062
let result = basePrice;
6163
switch (speed) {
@@ -85,17 +87,35 @@ const calculateGasPrice = (speed: TransactionSpeed, basePrice: BigNumber) => {
8587
return result;
8688
};
8789

90+
/**
91+
* Main entry point of the Polymath SDK
92+
*/
8893
export class Polymath {
8994
public isUnsupported: boolean = false;
9095

9196
public isConnected: boolean = false;
9297

9398
private context: Context = {} as Context;
9499

100+
/**
101+
* Connects the client to an Ethereum node
102+
*/
95103
public connect: Connect = async ({
104+
/**
105+
* address of a custom Polymath Registry contract. Defaults to the one deployed by Polymath
106+
*/
96107
polymathRegistryAddress,
108+
/**
109+
* URL of an Ethereum node. If using Metamask, this parameter can be ignored
110+
*/
97111
providerUrl,
112+
/**
113+
* private key of the wallet that will sign transactions. If using Metamask, this parameter can be ignored
114+
*/
98115
privateKey,
116+
/**
117+
* desired transaction speed. More gas is spent if a faster speed is chosen
118+
*/
99119
speed = TransactionSpeed.Fast,
100120
}: ConnectParams) => {
101121
let provider: Provider;
@@ -150,8 +170,8 @@ export class Polymath {
150170
/**
151171
* Reserve a Security Token
152172
*
153-
* @param symbol Security Token symbol
154-
* @param owner address that will own the reservation (optional, use this if you want to reserve a token on behalf of someone else)
173+
* @param symbol - Security Token symbol
174+
* @param owner - address that will own the reservation (optional, use this if you want to reserve a token on behalf of someone else)
155175
*/
156176
public reserveSecurityToken = async (args: { symbol: string; owner?: string }) => {
157177
const procedure = new ReserveSecurityToken(args, this.context);
@@ -163,7 +183,7 @@ export class Polymath {
163183
* Retrieve all Security Token Reservations currently owned by an issuer. This includes
164184
* Security Tokens that have already been launched
165185
*
166-
* @param owner issuer's address (defaults to current address)
186+
* @param owner - issuer's address (defaults to current address)
167187
*/
168188
public getSecurityTokenReservations = async (args?: { owner: string }) => {
169189
const {
@@ -179,23 +199,23 @@ export class Polymath {
179199

180200
const symbols = await contractWrappers.securityTokenRegistry.getTickersByOwner({ owner });
181201

182-
const reservations = await P.map(symbols, symbol => {
183-
return P.resolve(this.getSecurityTokenReservation({ symbol })).catch(PolymathError, err => {
202+
const reservations = await P.map(symbols, symbol =>
203+
P.resolve(this.getSecurityTokenReservation({ symbol })).catch(PolymathError, err => {
184204
if (err.code === ErrorCode.FetcherValidationError) {
185205
return undefined;
186206
}
187207

188208
throw err;
189-
});
190-
});
209+
})
210+
);
191211

192212
return compact(reservations);
193213
};
194214

195215
/**
196216
* Retrieve a Security Token Reservation by symbol or UUID
197217
*
198-
* @param symbol Security Token symbol
218+
* @param symbol - Security Token symbol
199219
*/
200220
public getSecurityTokenReservation = async (args: { symbol: string } | string) => {
201221
let uid: string;
@@ -214,7 +234,12 @@ export class Polymath {
214234
* Retrieve all launched Security Tokens related to a wallet.
215235
* This includes tokens owned by the wallet and tokens for which the wallet holds some role
216236
*
217-
* @param walletAddress defaults to current address
237+
* **Ignores** all tokens with version 2.0 or lower
238+
*
239+
* NOTE: This method is extremely slow if the wallet in question owns more than 20 tokens.
240+
* If that is your case, use [[getSecurityTokenSymbols]]
241+
*
242+
* @param walletAddress - defaults to current address
218243
*/
219244
public getSecurityTokens = async (args?: { walletAddress: string }) => {
220245
const {
@@ -233,13 +258,62 @@ export class Polymath {
233258
contractWrappers.securityTokenRegistry.getTokensByDelegate(walletAddress),
234259
]);
235260

236-
return P.map(union(ownedAddresses, delegatedAddresses), address => {
237-
return this.getSecurityToken({ address });
261+
const addressChunks = chunk(union(ownedAddresses, delegatedAddresses), 10);
262+
263+
const result = await P.mapSeries(addressChunks, addresses =>
264+
P.map(addresses, address =>
265+
P.resolve(this.getSecurityToken({ address })).catch(PolymathError, err => {
266+
if (err.code === ErrorCode.IncorrectVersion) {
267+
return undefined;
268+
}
269+
270+
throw err;
271+
})
272+
)
273+
);
274+
275+
return compact(union(...result));
276+
};
277+
278+
/**
279+
* Retrieve the symbols of all launched Security Tokens related to a wallet.
280+
* This includes tokens owned by the wallet and tokens for which the wallet holds some role
281+
*
282+
* **Includes** token symbols for tokens with version 2.0 or lower
283+
*
284+
* @param walletAddress - defaults to current address
285+
*/
286+
public getSecurityTokenSymbols = async (args?: { walletAddress: string }) => {
287+
const {
288+
context: { currentWallet, contractWrappers },
289+
} = this;
290+
291+
let walletAddress: string;
292+
if (args) {
293+
({ walletAddress } = args);
294+
} else {
295+
walletAddress = await currentWallet.address();
296+
}
297+
298+
const [ownedTickers, delegatedAddresses] = await Promise.all([
299+
contractWrappers.securityTokenRegistry.getTickersByOwner({ owner: walletAddress }),
300+
contractWrappers.securityTokenRegistry.getTokensByDelegate(walletAddress),
301+
]);
302+
303+
const delegateTickers = await P.map(delegatedAddresses, async address => {
304+
const details = await contractWrappers.securityTokenRegistry.getSecurityTokenData({
305+
securityTokenAddress: address,
306+
});
307+
return details.ticker;
238308
});
309+
310+
return union(ownedTickers, delegateTickers);
239311
};
240312

241313
/**
242314
* Retrieve a security token by symbol, address or UUID
315+
*
316+
* @throws if the Security Token is v2.0 or lower
243317
*/
244318
public getSecurityToken: GetSecurityToken = async (
245319
args:
@@ -253,6 +327,7 @@ export class Polymath {
253327
) => {
254328
let uid: string;
255329

330+
// eslint-disable-next-line require-jsdoc
256331
const isAddressArgs = (a: any): a is { address: string } => {
257332
return typeof a.address === 'string';
258333
};
@@ -264,13 +339,17 @@ export class Polymath {
264339
},
265340
} = this;
266341

342+
let securityToken;
343+
267344
// fetch by UUID
268345
if (typeof args === 'string') {
346+
const { symbol } = SecurityToken.unserialize(args);
347+
securityToken = await tokenFactory.getSecurityTokenInstanceFromTicker(symbol);
269348
uid = args;
270349
} else if (isAddressArgs(args)) {
271350
const { address } = args;
272351
try {
273-
const securityToken = await tokenFactory.getSecurityTokenInstanceFromAddress(address);
352+
securityToken = await tokenFactory.getSecurityTokenInstanceFromAddress(address);
274353

275354
const symbol = await securityToken.symbol();
276355
uid = SecurityToken.generateId({ symbol });
@@ -281,16 +360,34 @@ export class Polymath {
281360
});
282361
}
283362
} else {
363+
securityToken = await tokenFactory.getSecurityTokenInstanceFromTicker(args.symbol);
284364
uid = SecurityToken.generateId(args);
285365
}
286366

367+
const versionArray = await securityToken.getVersion();
368+
369+
const versionError = new PolymathError({
370+
code: ErrorCode.IncorrectVersion,
371+
message: 'Security Token v2.0 not supported',
372+
});
373+
374+
try {
375+
const version = convertVersionToEnum(versionArray);
376+
377+
if (![Version.V3_0_0, Version.V3_1_0].includes(version)) {
378+
throw versionError;
379+
}
380+
} catch (err) {
381+
throw versionError;
382+
}
383+
287384
return factories.securityTokenFactory.fetch(uid);
288385
};
289386

290387
/**
291388
* Check if a token symbol (ticker) is available for reservation
292389
*
293-
* @param symbol security token symbol for which to check availability
390+
* @param symbol - Security Token symbol for which to check availability
294391
*/
295392
public isSymbolAvailable = async (args: { symbol: string }) => {
296393
const { symbol } = args;
@@ -301,7 +398,7 @@ export class Polymath {
301398
/**
302399
* Check if a token follows the ERC20 standard
303400
*
304-
* @param address address of the token contract
401+
* @param address - address of the token contract
305402
*/
306403
public isValidErc20 = async (args: { address: string }) => {
307404
const { address } = args;
@@ -315,7 +412,7 @@ export class Polymath {
315412
/**
316413
* Retrieve a Wallet by address
317414
*
318-
* @param address wallet address
415+
* @param address - wallet address
319416
*/
320417
public getWallet = (args: { address: string }): Wallet => {
321418
const { address } = args;
@@ -356,17 +453,20 @@ export class Polymath {
356453
};
357454

358455
/**
456+
* @hidden
359457
* Obtains a recommended default gas price based on the desired transaction speed
360458
*
361-
* On mainnet, the gas price is fetched from ethgasstation.info (most reliable)\
362-
* On testnets (or if ethgasstation is unavailable), the gas price is fetched from the network itself via eth_gasPrice\
459+
* On mainnet, the gas price is fetched from ethgasstation.info (most reliable)
460+
*
461+
* On testnets (or if ethgasstation is unavailable), the gas price is fetched from the network itself via eth_gasPrice
363462
* If everything else fails, we use a base default of 1 GWEI
364463
*
365-
* On the last two cases, the obtained price is multiplied by a factor depending on the speed:\
366-
* Slow = x1\
367-
* Medium = x2\
368-
* Fast = x3\
369-
* Fastest = x5
464+
* On the last two cases, the obtained price is multiplied by a factor depending on the speed:
465+
*
466+
* - Slow = x1
467+
* - Medium = x2
468+
* - Fast = x3
469+
* - Fastest = x5
370470
*/
371471
private getGasPrice = async ({
372472
provider,

src/PolymathError.ts

Lines changed: 4 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -17,9 +17,13 @@ export const ErrorMessagesPerCode: {
1717
'You must install the Metamask browser extension if attempting to use Polymath SDK from the browser',
1818
};
1919

20+
/**
21+
* Wraps an error to give more information about it's type
22+
*/
2023
export class PolymathError extends Error {
2124
public code: ErrorCode;
2225

26+
// eslint-disable-next-line require-jsdoc
2327
constructor({ message, code }: { message?: string; code: ErrorCode }) {
2428
super(message || ErrorMessagesPerCode[code] || `Unknown error, code: ${code}`);
2529
// eslint:disable-next-line

src/PostTransactionResolver.ts

Lines changed: 13 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1,5 +1,10 @@
11
import { TransactionReceiptWithDecodedLogs } from '@polymathnetwork/contract-wrappers';
22

3+
/**
4+
* @hidden
5+
* Represents a value that doesn't exist at the moment, but will exist once a certain transaction
6+
* has been run
7+
*/
38
export class PostTransactionResolver<
49
Value extends any,
510
Receipt extends any = TransactionReceiptWithDecodedLogs
@@ -8,6 +13,7 @@ export class PostTransactionResolver<
813

914
private resolver: (receipt: Receipt) => Promise<Value> | Promise<undefined>;
1015

16+
// eslint-disable-next-line require-jsdoc
1117
constructor(resolver?: (receipt: Receipt) => Promise<Value>) {
1218
if (!resolver) {
1319
this.resolver = async () => undefined;
@@ -17,13 +23,20 @@ export class PostTransactionResolver<
1723
this.resolver = resolver;
1824
}
1925

26+
/**
27+
* Run the resolver function and assign its result to this object
28+
*/
2029
public async run(receipt: Receipt) {
2130
const result = await this.resolver(receipt);
2231

2332
this.result = result;
2433
}
2534
}
2635

36+
/**
37+
* @hidden
38+
* Check if a value is of type [[PostTransactionResolver]]
39+
*/
2740
export function isPostTransactionResolver<T = any, R = TransactionReceiptWithDecodedLogs>(
2841
val: any
2942
): val is PostTransactionResolver<T, R> {

0 commit comments

Comments
 (0)