Skip to content

Commit

Permalink
Merge pull request #560 from MoralisWeb3/improve-erc20Value
Browse files Browse the repository at this point in the history
feat: improve Erc20Value logic
  • Loading branch information
ErnoW authored Aug 8, 2022
2 parents 6eea755 + 167ab16 commit 919afcb
Show file tree
Hide file tree
Showing 7 changed files with 342 additions and 34 deletions.
22 changes: 22 additions & 0 deletions .changeset/flat-swans-type.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,22 @@
---
'@moralisweb3/api-utils': minor
'@moralisweb3/auth': minor
'@moralisweb3/core': minor
'@moralisweb3/evm-api': minor
'@moralisweb3/evm-utils': minor
'moralis': minor
'@moralisweb3/sol-api': minor
'@moralisweb3/sol-utils': minor
---

Improve Erc20Value logic by:

- geters for Erc20 for: `token.decimals`, `token.name`, `token.symbol`, `token.contractAddress`, `token.chain`, `token.logo`, `token.logoHash` and `token.thumbnail`,
- adding an optional token reference for `Erc20Value`. This can be used by calling `Erc20Value.create(amount, { token })`
- fixes and additions for output of `Erc20Value`:
- `erc20Value.value` now returns the value in a decimal string `"123.567"`
- `erc20Value.amount` returns the Bignumber value withtout taking decimals into account
- `erc20Value.decimals` returns the decimals
- `erc20Value.toNumber()` returns the value in a decimal number (or throws an error if the value is too big): `123.456`
- `erc20Value.display()` returns the value in a readable string with the token symbol if available: `"123.456 LINK"` (or `"123.456"`)
- `Moralis.EvmApi.getTokenBalances()` now returns an `Erc20Value` object with associated token information.
1 change: 1 addition & 0 deletions packages/core/src/Error/ErrorCode.ts
Original file line number Diff line number Diff line change
Expand Up @@ -18,6 +18,7 @@ export enum CoreErrorCode {
CONFIG_KEY_NOT_EXIST = 'C0013',
CONFIG_INVALID_VALUE = 'C0014',
CONFIG_KEY_ALREADY_EXIST = 'C0015',
INVALID_DATA = 'C0016',

BIG_NUMBER_ERROR = 'C0500',

Expand Down
2 changes: 1 addition & 1 deletion packages/core/src/version.ts
Original file line number Diff line number Diff line change
@@ -1 +1 @@
export const LIB_VERSION = "2.0.0-alpha.3";
export const LIB_VERSION = "2.0.0-beta.7";
32 changes: 18 additions & 14 deletions packages/evmApi/src/resolvers/account/getTokenBalances.ts
Original file line number Diff line number Diff line change
@@ -1,5 +1,5 @@
import { Camelize } from '@moralisweb3/core';
import { Erc20Token, Erc20Value, EvmAddressish, EvmChainish, EvmAddress } from '@moralisweb3/evm-utils';
import { Erc20Value, EvmAddressish, EvmChainish, EvmAddress } from '@moralisweb3/evm-utils';
import { operations } from '../../generated/types';
import { BASE_URL } from '../../EvmApi';
import { createEndpoint, createEndpointFactory } from '@moralisweb3/api-utils';
Expand All @@ -23,20 +23,24 @@ export const getTokenBalances = createEndpointFactory((core) =>
name: 'getTokenBalances',
urlParams: ['address'],
getUrl: (params: ApiParams) => `${BASE_URL}/${params.address}/erc20`,
apiToResult: (data: ApiResult, params: ApiParams) =>
data.map((token) => ({
token: new Erc20Token({
apiToResult: (data: ApiResult, params: ApiParams) => {
return data.map((token) => {
return new Erc20Value(token.balance, {
decimals: token.decimals,
name: token.name,
symbol: token.symbol,
contractAddress: token.token_address,
logo: token.logo,
thumbnail: token.thumbnail,
chain: EvmChainResolver.resolve(params.chain, core),
}),
value: new Erc20Value(token.balance, token.decimals),
})),
resultToJson: (data) => data.map(({ token, value }) => ({ token: token.toJSON(), value: value.format() })),
token: {
decimals: token.decimals,
name: token.name,
symbol: token.symbol,
contractAddress: token.token_address,
logo: token.logo,
thumbnail: token.thumbnail,
chain: EvmChainResolver.resolve(params.chain, core),
},
});
});
},

resultToJson: (data) => data.map((tokenValue) => tokenValue.toJSON()),
parseParams: (params: Params): ApiParams => ({
to_block: params.toBlock,
token_addresses: params.tokenAddresses,
Expand Down
32 changes: 32 additions & 0 deletions packages/evmUtils/src/dataTypes/Erc20/Erc20.ts
Original file line number Diff line number Diff line change
Expand Up @@ -71,6 +71,38 @@ export class Erc20Token implements MoralisDataObject {
return true;
}

get decimals() {
return this._value.decimals;
}

get name() {
return this._value.name;
}

get symbol() {
return this._value.symbol;
}

get contractAddress() {
return this._value.contractAddress;
}

get chain() {
return this._value.chain;
}

get logo() {
return this._value.logo;
}

get logoHash() {
return this._value.logoHash;
}

get thumbnail() {
return this._value.thumbnail;
}

equals(value: Erc20Tokenish): boolean {
return Erc20Token.equals(this, value);
}
Expand Down
197 changes: 197 additions & 0 deletions packages/evmUtils/src/dataTypes/Erc20Value/Erc20Value.test.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,197 @@
import { BigNumber } from '@moralisweb3/core';
import { setupEvmUtils } from '../../test/setup';
import { Erc20Token } from '../Erc20/Erc20';
import { Erc20Value } from './Erc20Value';

const makeTestToken = (
options: {
decimals?: number;
chain?: string;
name?: string;
symbol?: string;
contractAddress?: string;
} = {},
) => {
const contractAddress = options.contractAddress ?? '0x0000000000000000000000000000000000000123';
const symbol = options.symbol ?? 'TEST';
const name = options.name ?? 'Test Token';
const decimals = options.decimals ?? 18;
const chain = options.chain ?? '0x1';

const token = Erc20Token.create({
chain: chain,
contractAddress: contractAddress,
decimals: decimals,
symbol: symbol,
name: name,
});

return {
token,
contractAddress,
symbol,
name,
decimals,
chain,
};
};
describe('Erc20Value', () => {
beforeAll(() => {
setupEvmUtils();
});

describe('create', () => {
it('creates without decimals', () => {
const erc20Value = Erc20Value.create(1000);

expect(erc20Value.decimals).toBe(18);
});

it('creates with decimals', () => {
const erc20Value = Erc20Value.create(1000, { decimals: 10 });

expect(erc20Value.decimals).toBe(10);
});

it('creates based on a number', () => {
const erc20Value = Erc20Value.create(123);

expect(erc20Value.amount.toString()).toBe('123');
});

it('creates based on a string', () => {
const erc20Value = Erc20Value.create('123');

expect(erc20Value.amount.toString()).toBe('123');
});

it('creates based on a big number string', () => {
const erc20Value = Erc20Value.create('10000000000000000');

expect(erc20Value.amount.toString()).toBe('10000000000000000');
});

it('creates based on a BigNumber', () => {
const erc20Value = Erc20Value.create(BigNumber.create('10000000000000000'));

expect(erc20Value.amount.toString()).toBe('10000000000000000');
});

it('creates a value with associated token', () => {
const { token, contractAddress, symbol, name, decimals, chain } = makeTestToken();
const erc20Value = Erc20Value.create('10000000000000000', { token });

expect(erc20Value.token?.contractAddress.equals(contractAddress)).toBe(true);
expect(erc20Value.token?.symbol).toBe(symbol);
expect(erc20Value.token?.name).toBe(name);
expect(erc20Value.token?.decimals).toBe(decimals);
expect(erc20Value.token?.chain.equals(chain)).toBe(true);
});

it('creates a value with associated token', () => {
const { token, contractAddress, symbol, name, decimals, chain } = makeTestToken();
const erc20Value = Erc20Value.create('10000000000000000', { token });

expect(erc20Value.token?.contractAddress.equals(contractAddress)).toBe(true);
expect(erc20Value.token?.symbol).toBe(symbol);
expect(erc20Value.token?.name).toBe(name);
expect(erc20Value.token?.decimals).toBe(decimals);
expect(erc20Value.token?.chain.equals(chain)).toBe(true);
});

it('fails to create a token with associated token that has different decimals', () => {
const { token } = makeTestToken({ decimals: 12 });
expect(() => Erc20Value.create('10000000000000000', { token, decimals: 13 })).toThrowErrorMatchingInlineSnapshot(
`"[C0016] Decimals do not match"`,
);
});
});

describe('format', () => {
const tokenValue = Erc20Value.create(123456780000, { decimals: 10 });

it('formats the value to a correct string', () => {
expect(tokenValue.value).toBe('12.345678');
});

it('formats via toString() to a correct string', () => {
expect(tokenValue.toString()).toBe('12.345678');
});

it('formats via format() to a correct string', () => {
expect(tokenValue.format()).toBe('12.345678');
});

it('formats to number', () => {
expect(tokenValue.toNumber()).toBe(12.345678);
});

it('returns value in an object on toJSON()', () => {
expect(tokenValue.toJSON()).toStrictEqual({ value: '12.345678' });
});

it('returns value and token data in an object on toJSON() when an token is associated', () => {
const { token } = makeTestToken({ decimals: 10 });
const tokenValueWithToken = Erc20Value.create(123456780000, { decimals: 10, token });

expect(tokenValueWithToken.toJSON()).toStrictEqual({
value: '12.345678',
token: {
chain: '0x1',
contractAddress: '0x0000000000000000000000000000000000000123',
decimals: 10,
name: 'Test Token',
symbol: 'TEST',
logo: undefined,
logoHash: undefined,
thumbnail: undefined,
},
});
});

it('formats a readable string with token, when a token is provided', () => {
const { token, symbol } = makeTestToken({ decimals: 10 });
const tokenValueWithToken = Erc20Value.create(123456780000, { decimals: 10, token });

expect(tokenValueWithToken.display()).toBe(`12.345678 ${symbol}`);
});

it('throws an error when trying to call display without a token', () => {
const tokenValueWithToken = Erc20Value.create(123456780000, { decimals: 10 });

expect(tokenValueWithToken.display()).toBe(`12.345678`);
});
});

describe('equals', () => {
const tokenA = Erc20Value.create('12345678000', { decimals: 18 });
// Same value as tokenA (just different decimals)
const tokenB = Erc20Value.create('123456780000', { decimals: 19 });
// Different value as tokenA
const tokenC = Erc20Value.create('223456780000', { decimals: 18 });

it('should pass equallity with the same token', () => {
expect(tokenA.equals(tokenA)).toBe(true);
});

it('should pass equallity a number input', () => {
expect(tokenA.equals(12345678000)).toBe(true);
});

it('should pass equallity a string input', () => {
expect(tokenA.equals('12345678000')).toBe(true);
});

it('should pass BigNumber a string input', () => {
expect(tokenA.equals(BigNumber.create('12345678000'))).toBe(true);
});

it('should pass with the same value', () => {
expect(tokenA.equals(tokenB)).toBe(true);
});

it('should fail with a different value', () => {
expect(tokenA.equals(tokenC)).toBe(false);
});
});
});
Loading

0 comments on commit 919afcb

Please sign in to comment.