Skip to content

Commit

Permalink
Extensions field (Uniswap#32)
Browse files Browse the repository at this point in the history
* Add new "extensions" property to TokenInfo definition

The new "extensions" field is meant to contain any vendor-specific token metadata or any other extraneous data which a Token List creator might want to ship with their Token List.

example usecases:
- adding a "color" for each token
- adding a "description" for each token
- adding a "verified" flag on tokens

😘️🦄️🗒️

* bump package.json version

* extension values

Co-authored-by: Michael Demarais <m.demarais@gmail.com>
  • Loading branch information
moodysalem and mikedemarais authored Dec 10, 2020
1 parent 7ddb6b3 commit 0a9f46c
Show file tree
Hide file tree
Showing 7 changed files with 308 additions and 3 deletions.
2 changes: 1 addition & 1 deletion package-lock.json

Some generated files are not rendered by default. Learn more about how customized files appear on GitHub.

2 changes: 1 addition & 1 deletion package.json
Original file line number Diff line number Diff line change
Expand Up @@ -2,7 +2,7 @@
"name": "@uniswap/token-lists",
"author": "Moody Salem",
"description": "📚 The Token Lists specification",
"version": "1.0.0-beta.18",
"version": "1.0.0-beta.19",
"license": "MIT",
"main": "dist/index.js",
"typings": "dist/index.d.ts",
Expand Down
56 changes: 56 additions & 0 deletions src/tokenlist.schema.json
Original file line number Diff line number Diff line change
Expand Up @@ -61,6 +61,45 @@
"stablecoin"
]
},
"ExtensionIdentifier": {
"type": "string",
"description": "The name of a token extension property",
"minLength": 1,
"maxLength": 30,
"pattern": "^[\\w]+$",
"examples": [
"color",
"is_fee_on_transfer",
"aliases"
]
},
"ExtensionValue": {
"anyOf": [
{
"type": "string",
"minLength": 1,
"maxLength": 30,
"examples": [
"#00000"
]
},
{
"type": "boolean",
"examples": [
true
]
},
{
"type": "number",
"examples": [
15
]
},
{
"type": "null"
}
]
},
"TagDefinition": {
"type": "object",
"description": "Definition of a tag that can be associated with a token via its identifier",
Expand Down Expand Up @@ -162,6 +201,23 @@
"stablecoin",
"compound"
]
},
"extensions": {
"type": "object",
"description": "An object containing any arbitrary or vendor-specific token metadata",
"propertyNames": {
"$ref": "#/definitions/ExtensionIdentifier"
},
"additionalProperties": {
"$ref": "#/definitions/ExtensionValue"
},
"maxProperties": 10,
"examples": [
{
"color": "#000000",
"is_verified_by_me": true
}
]
}
},
"required": [
Expand Down
179 changes: 179 additions & 0 deletions test/__snapshots__/tokenlist.schema.test.ts.snap
Original file line number Diff line number Diff line change
Expand Up @@ -2,6 +2,185 @@

exports[`schema allows up to 10k tokens 1`] = `null`;

exports[`schema checks extensions 1`] = `null`;

exports[`schema checks extensions 2`] = `
Array [
Object {
"dataPath": ".tokens[0].extensions['abc']",
"keyword": "type",
"message": "should be string",
"params": Object {
"type": "string",
},
"schemaPath": "#/definitions/ExtensionValue/anyOf/0/type",
},
Object {
"dataPath": ".tokens[0].extensions['abc']",
"keyword": "type",
"message": "should be boolean",
"params": Object {
"type": "boolean",
},
"schemaPath": "#/definitions/ExtensionValue/anyOf/1/type",
},
Object {
"dataPath": ".tokens[0].extensions['abc']",
"keyword": "type",
"message": "should be number",
"params": Object {
"type": "number",
},
"schemaPath": "#/definitions/ExtensionValue/anyOf/2/type",
},
Object {
"dataPath": ".tokens[0].extensions['abc']",
"keyword": "type",
"message": "should be null",
"params": Object {
"type": "null",
},
"schemaPath": "#/definitions/ExtensionValue/anyOf/3/type",
},
Object {
"dataPath": ".tokens[0].extensions['abc']",
"keyword": "anyOf",
"message": "should match some schema in anyOf",
"params": Object {},
"schemaPath": "#/definitions/ExtensionValue/anyOf",
},
Object {
"dataPath": ".tokens[0].extensions['def']",
"keyword": "type",
"message": "should be string",
"params": Object {
"type": "string",
},
"schemaPath": "#/definitions/ExtensionValue/anyOf/0/type",
},
Object {
"dataPath": ".tokens[0].extensions['def']",
"keyword": "type",
"message": "should be boolean",
"params": Object {
"type": "boolean",
},
"schemaPath": "#/definitions/ExtensionValue/anyOf/1/type",
},
Object {
"dataPath": ".tokens[0].extensions['def']",
"keyword": "type",
"message": "should be number",
"params": Object {
"type": "number",
},
"schemaPath": "#/definitions/ExtensionValue/anyOf/2/type",
},
Object {
"dataPath": ".tokens[0].extensions['def']",
"keyword": "type",
"message": "should be null",
"params": Object {
"type": "null",
},
"schemaPath": "#/definitions/ExtensionValue/anyOf/3/type",
},
Object {
"dataPath": ".tokens[0].extensions['def']",
"keyword": "anyOf",
"message": "should match some schema in anyOf",
"params": Object {},
"schemaPath": "#/definitions/ExtensionValue/anyOf",
},
Object {
"dataPath": ".tokens[0].extensions['isBoolean']",
"keyword": "type",
"message": "should be string",
"params": Object {
"type": "string",
},
"schemaPath": "#/definitions/ExtensionValue/anyOf/0/type",
},
Object {
"dataPath": ".tokens[0].extensions['isBoolean']",
"keyword": "type",
"message": "should be boolean",
"params": Object {
"type": "boolean",
},
"schemaPath": "#/definitions/ExtensionValue/anyOf/1/type",
},
Object {
"dataPath": ".tokens[0].extensions['isBoolean']",
"keyword": "type",
"message": "should be number",
"params": Object {
"type": "number",
},
"schemaPath": "#/definitions/ExtensionValue/anyOf/2/type",
},
Object {
"dataPath": ".tokens[0].extensions['isBoolean']",
"keyword": "type",
"message": "should be null",
"params": Object {
"type": "null",
},
"schemaPath": "#/definitions/ExtensionValue/anyOf/3/type",
},
Object {
"dataPath": ".tokens[0].extensions['isBoolean']",
"keyword": "anyOf",
"message": "should match some schema in anyOf",
"params": Object {},
"schemaPath": "#/definitions/ExtensionValue/anyOf",
},
Object {
"dataPath": ".tokens[0].extensions['isNull']",
"keyword": "type",
"message": "should be string",
"params": Object {
"type": "string",
},
"schemaPath": "#/definitions/ExtensionValue/anyOf/0/type",
},
Object {
"dataPath": ".tokens[0].extensions['isNull']",
"keyword": "type",
"message": "should be boolean",
"params": Object {
"type": "boolean",
},
"schemaPath": "#/definitions/ExtensionValue/anyOf/1/type",
},
Object {
"dataPath": ".tokens[0].extensions['isNull']",
"keyword": "type",
"message": "should be number",
"params": Object {
"type": "number",
},
"schemaPath": "#/definitions/ExtensionValue/anyOf/2/type",
},
Object {
"dataPath": ".tokens[0].extensions['isNull']",
"keyword": "type",
"message": "should be null",
"params": Object {
"type": "null",
},
"schemaPath": "#/definitions/ExtensionValue/anyOf/3/type",
},
Object {
"dataPath": ".tokens[0].extensions['isNull']",
"keyword": "anyOf",
"message": "should match some schema in anyOf",
"params": Object {},
"schemaPath": "#/definitions/ExtensionValue/anyOf",
},
]
`;

exports[`schema checks token address 1`] = `
Array [
Object {
Expand Down
31 changes: 31 additions & 0 deletions test/schema/extensions-invalid.tokenlist.json
Original file line number Diff line number Diff line change
@@ -0,0 +1,31 @@
{
"name": "My Token List",
"timestamp": "2020-06-12T00:00:00+00:00",
"tokens": [
{
"chainId": 1,
"address": "0xA0b86991c6218b36c1d19D4a2e9Eb0cE3606eB48",
"decimals": 18,
"name": "required name",
"symbol": "symbol",
"extensions": {
"abc": ["1234"],
"def": [1235],
"isBoolean": {"abc": 534},
"isNull": {"thing": null}
}
},
{
"chainId": 1,
"address": "0x39AA39c021dfbaE8faC545936693aC917d5E7563",
"decimals": 18,
"name": "required name",
"symbol": "symbol"
}
],
"version": {
"major": 1,
"minor": 0,
"patch": 0
}
}
31 changes: 31 additions & 0 deletions test/schema/extensions-valid.tokenlist.json
Original file line number Diff line number Diff line change
@@ -0,0 +1,31 @@
{
"name": "My Token List",
"timestamp": "2020-06-12T00:00:00+00:00",
"tokens": [
{
"chainId": 1,
"address": "0xA0b86991c6218b36c1d19D4a2e9Eb0cE3606eB48",
"decimals": 18,
"name": "required name",
"symbol": "symbol",
"extensions": {
"abc": "1234",
"def": 1234,
"isBoolean": false,
"isNull": null
}
},
{
"chainId": 1,
"address": "0x39AA39c021dfbaE8faC545936693aC917d5E7563",
"decimals": 18,
"name": "required name",
"symbol": "symbol"
}
],
"version": {
"major": 1,
"minor": 0,
"patch": 0
}
}
10 changes: 9 additions & 1 deletion test/tokenlist.schema.test.ts
Original file line number Diff line number Diff line change
Expand Up @@ -15,6 +15,8 @@ import invalidVersion2 from './schema/invalidversion.2.tokenlist.json';
import invalidVersion3 from './schema/invalidversion.3.tokenlist.json';
import invalidDecimals1 from './schema/invaliddecimals.1.tokenlist.json';
import invalidDecimals2 from './schema/invaliddecimals.2.tokenlist.json';
import extensionsValid from './schema/extensions-valid.tokenlist.json';
import extensionsInvalid from './schema/extensions-invalid.tokenlist.json';

const ajv = new Ajv({ allErrors: true, format: 'full' });
const validator = ajv.compile(schema);
Expand All @@ -25,8 +27,9 @@ describe('schema', () => {
});

function checkSchema(schema: any, valid: boolean): void {
expect(validator(schema)).toEqual(valid);
const isValid = validator(schema);
expect(validator.errors).toMatchSnapshot();
expect(isValid).toEqual(valid);
}

it('works for example schema', () => {
Expand Down Expand Up @@ -81,6 +84,11 @@ describe('schema', () => {
checkSchema(invalidVersion3, false);
});

it('checks extensions', () => {
checkSchema(extensionsValid, true);
checkSchema(extensionsInvalid, false);
});

it('allows up to 10k tokens', () => {
const exampleListWith10kTokens = {
...exampleList,
Expand Down

0 comments on commit 0a9f46c

Please sign in to comment.