Skip to content

Commit

Permalink
add some more utility functions (wip)
Browse files Browse the repository at this point in the history
  • Loading branch information
moodysalem committed Jul 22, 2020
1 parent 50dcd5d commit d3f8bdd
Show file tree
Hide file tree
Showing 6 changed files with 248 additions and 19 deletions.
124 changes: 124 additions & 0 deletions src/diffTokenLists.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,124 @@
import { TokenInfo } from './types';

export type TokenInfoChangeKey = Exclude<
keyof TokenInfo,
'address' | 'chainId'
>;
export type TokenInfoChanges = Array<TokenInfoChangeKey>;

/**
* compares two token info key values
* this subset of full deep equal functionality does not work on objects or object arrays
* @param a comparison item a
* @param b comparison item b
*/
function compareTokenInfoProperty(a: unknown, b: unknown): boolean {
if (a === b) return true;
if (typeof a !== typeof b) return false;
if (Array.isArray(a) && Array.isArray(b)) {
return a.every((el, i) => b[i] === el);
}
return false;
}

/**
* Differences between a base list and an updated list.
*/
export interface TokenListDiff {
/**
* Tokens from updated with chainId/address not present in base list
*/
readonly added: TokenInfo[];
/**
* Tokens from base with chainId/address not present in the updated list
*/
readonly removed: TokenInfo[];
/**
* The token info that changed
*/
readonly changed: {
[chainId: number]: {
[address: string]: TokenInfoChanges;
};
};
}

/**
* Computes the diff of a token list where the first argument is the base and the second argument is the updated list.
* @param base base list
* @param update updated list
*/
export function diffTokenLists(
base: TokenInfo[],
update: TokenInfo[]
): TokenListDiff {
const indexedBase = base.reduce<{
[chainId: number]: { [address: string]: TokenInfo };
}>((memo, tokenInfo) => {
if (!memo[tokenInfo.chainId]) memo[tokenInfo.chainId] = {};
memo[tokenInfo.chainId][tokenInfo.address] = tokenInfo;
return memo;
}, {});

const newListUpdates = update.reduce<{
added: TokenInfo[];
changed: {
[chainId: number]: {
[address: string]: TokenInfoChanges;
};
};
index: {
[chainId: number]: {
[address: string]: true;
};
};
}>(
(memo, tokenInfo) => {
const baseToken = indexedBase[tokenInfo.chainId]?.[tokenInfo.address];
if (!baseToken) {
memo.added.push(tokenInfo);
} else {
const changes: TokenInfoChanges = Object.keys(tokenInfo)
.filter(
(s): s is TokenInfoChangeKey => s !== 'address' && s !== 'chainId'
)
.filter(s => {
return !compareTokenInfoProperty(tokenInfo[s], baseToken[s]);
});
if (changes.length > 0) {
if (!memo.changed[tokenInfo.chainId]) {
memo.changed[tokenInfo.chainId] = {};
}
memo.changed[tokenInfo.chainId][tokenInfo.address] = changes;
}
}

if (!memo.index[tokenInfo.chainId]) {
memo.index[tokenInfo.chainId] = {
[tokenInfo.address]: true,
};
} else {
memo.index[tokenInfo.chainId][tokenInfo.address] = true;
}

return memo;
},
{ added: [], changed: {}, index: {} }
);

const removed = base.reduce<TokenInfo[]>((list, curr) => {
if (
!newListUpdates.index[curr.chainId] ||
!newListUpdates.index[curr.chainId][curr.address]
) {
list.push(curr);
}
return list;
}, []);

return {
added: newListUpdates.added,
changed: newListUpdates.changed,
removed,
};
}
3 changes: 3 additions & 0 deletions src/index.ts
Original file line number Diff line number Diff line change
Expand Up @@ -3,5 +3,8 @@ import schema from './tokenlist.schema.json';
export * from './types';
export * from './isVersionUpdate';
export * from './getVersionUpgrade';
export * from './diffTokenLists';
export * from './minVersionBump';
export * from './nextVersion';

export { schema };
19 changes: 19 additions & 0 deletions src/minVersionBump.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,19 @@
import { diffTokenLists } from './diffTokenLists';
import { VersionUpgrade } from './getVersionUpgrade';
import { TokenInfo } from './types';

/**
* Returns the minimum version bump for the given list
* @param baseList the base list of tokens
* @param updatedList the updated list of tokens
*/
export function minVersionBump(
baseList: TokenInfo[],
updatedList: TokenInfo[]
): VersionUpgrade {
const diff = diffTokenLists(baseList, updatedList);
if (diff.added.length > 0) return VersionUpgrade.MAJOR;
if (diff.removed.length > 0) return VersionUpgrade.MINOR;
if (Object.keys(diff.changed).length > 0) return VersionUpgrade.PATCH;
return VersionUpgrade.NONE;
}
36 changes: 36 additions & 0 deletions src/nextVersion.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,36 @@
import { VersionUpgrade } from './getVersionUpgrade';
import { minVersionBump } from './minVersionBump';
import { TokenInfo, TokenList, Version } from './types';

/**
* Returns the next version of the list given a base list and the updated token list.
* @param base base list
* @param updatedList updated list of tokens for the next list
*/
export function nextVersion(
base: TokenList,
updatedList: TokenInfo[]
): Version {
const bump = minVersionBump(base.tokens, updatedList);
switch (bump) {
case VersionUpgrade.NONE:
return base.version;

case VersionUpgrade.MAJOR:
return { major: base.version.major + 1, minor: 0, patch: 0 };

case VersionUpgrade.MINOR:
return {
major: base.version.major,
minor: base.version.minor + 1,
patch: 0,
};

case VersionUpgrade.PATCH:
return {
major: base.version.major,
minor: base.version.minor,
patch: base.version.patch + 1,
};
}
}
38 changes: 19 additions & 19 deletions src/types.ts
Original file line number Diff line number Diff line change
@@ -1,31 +1,31 @@
export interface TokenInfo {
chainId: number;
address: string;
name: string;
decimals: number;
symbol: string;
logoURI?: string;
tags?: string[];
readonly chainId: number;
readonly address: string;
readonly name: string;
readonly decimals: number;
readonly symbol: string;
readonly logoURI?: string;
readonly tags?: string[];
}

export interface Version {
major: number;
minor: number;
patch: number;
readonly major: number;
readonly minor: number;
readonly patch: number;
}

export interface Tags {
[tagId: string]: {
name: string;
description: string;
readonly [tagId: string]: {
readonly name: string;
readonly description: string;
};
}

export interface TokenList {
name: string;
timestamp: string;
version: Version;
tokens: TokenInfo[];
keywords?: string[];
tags?: Tags;
readonly name: string;
readonly timestamp: string;
readonly version: Version;
readonly tokens: TokenInfo[];
readonly keywords?: string[];
readonly tags?: Tags;
}
47 changes: 47 additions & 0 deletions test/diffTokenLists.test.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,47 @@
import { diffTokenLists, TokenInfo } from '../src';

const tokenA: TokenInfo = {
chainId: 1,
address: '0x0a',
logoURI: 'ipfs://test',
symbol: 'abcd',
name: 'token a',
decimals: 18,
tags: ['hello', 'world'],
};
const tokenAChanged: TokenInfo = {
...tokenA,
name: 'blah',
decimals: 12,
};
const tokenB: TokenInfo = {
chainId: 1,
address: '0x0b',
logoURI: 'ipfs://blah',
symbol: 'defg',
name: 'token b',
decimals: 9,
tags: ['hello', 'world'],
};

describe('#diffTokenLists', () => {
it('change address', () => {
expect(diffTokenLists([tokenA], [tokenB])).toEqual({
added: [tokenB],
removed: [tokenA],
changed: {},
});
});

it('change name', () => {
expect(diffTokenLists([tokenB, tokenA], [tokenB, tokenAChanged])).toEqual({
added: [],
removed: [],
changed: {
1: {
'0x0a': ['name', 'decimals'],
},
},
});
});
});

0 comments on commit d3f8bdd

Please sign in to comment.