Skip to content

Commit 2feb331

Browse files
committed
Change default tokenlist to custom one
1 parent ee98955 commit 2feb331

File tree

8 files changed

+299
-4
lines changed

8 files changed

+299
-4
lines changed

bun.lockb

12.5 KB
Binary file not shown.

packages/evmcrispr/src/modules/std/helpers/token.ts

+7-4
Original file line numberDiff line numberDiff line change
@@ -14,14 +14,17 @@ import {
1414
} from "../../../utils";
1515
import type { Module } from "../../../Module";
1616
import type { Std } from "../Std";
17+
import type { BindingsManager } from "../../../BindingsManager";
1718

1819
const ENV_TOKENLIST = "$token.tokenlist";
19-
const DEFAULT_TOKEN_LIST = "https://tokens.uniswap.org/";
2020

21-
const getTokenList = ({ bindingsManager }: Module): string => {
21+
const getTokenList = async (
22+
bindingsManager: BindingsManager,
23+
chainId: number,
24+
): Promise<string> => {
2225
const tokenList = String(
2326
bindingsManager.getBindingValue(ENV_TOKENLIST, BindingsSpace.USER) ??
24-
DEFAULT_TOKEN_LIST,
27+
`https://tokens.functions.on-fleek.app/v0/${chainId}`,
2528
);
2629

2730
// Always check user data inputs:
@@ -41,7 +44,7 @@ export const _token = async (
4144
return tokenSymbolOrAddress;
4245
}
4346
const chainId = await module.getChainId();
44-
const tokenList = getTokenList(module);
47+
const tokenList = await getTokenList(module.bindingsManager, chainId);
4548
const {
4649
tokens,
4750
}: { tokens: { symbol: string; chainId: number; address: string }[] } =

packages/token-list/README.md

+88
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,88 @@
1+
# Fleek Image Optimizer
2+
3+
## Overview
4+
5+
Fleek Image Optimizer is a tool designed to enhance images for web delivery. It supports various image formats and provides functionalities to resize and re-encode images into more efficient formats like AVIF and WebP.
6+
7+
## Similar (propertary) services
8+
These are all services that allow image optimization among other features.
9+
- [Cloudinary](https://cloudinary.com/developers)
10+
- [Imagekit](https://imagekit.io/docs/overview)
11+
- [Imgix](https://www.imgix.com/solutions/compression-and-performance)
12+
- [Akami](https://techdocs.akamai.com/ivm/docs/optimize-images)
13+
14+
## Optimizing images in Fleek
15+
16+
A very interesting resource for image optimization that uses WebAssembly and browser technologies is [Squoosh.app](https://squoosh.app) from Google Chrome Labs. The app [repo](https://github.com/GoogleChromeLabs/squoosh) is open source and up to date with support for the latest file formats.
17+
18+
For anyone who want to use the wasm modules underneath, there is an unofficial set of npm packages under [@jsquash/\*](https://github.com/jamsinclair/jSquash) that include the WASM modules with support for encoding, resizing, and decoding avif, jpeg, jxl, png and webp.
19+
20+
In order to make WASM work on Fleek Functions, I needed to configure the rollup bundler in a specific way to inline dynamic imports and WASM modules. I created an independent repo so anyone who needs to use WASM in Fleek can find how: [Fleek Functions WebAssembly Starter Kit](https://github.com/BlossomLabs/fleek-function-wasm-starter).
21+
22+
Using that configuration, I could import the wasm modules from jsquash, and use them in a fleek function that receives the image URI and the desired width, and converts it to the most efficient image type that is available for the requesting browser.
23+
24+
### Usage
25+
26+
You can call the function by passing the image URL and optional `width` and `to` parameters to the query string like this:
27+
28+
```
29+
https://image-optimizer.functions.on-fleek.app/<url>/?w=<width>&to=<type>
30+
```
31+
32+
If the `w` parameter is not provided, it will deliver the image size as is.
33+
34+
If the `to` parameter is not provided, it will check the image types accepted by the client and deliver a format that fits, prioritizing AVIF, and then WebP.
35+
36+
**Example:**
37+
38+
```
39+
https://image-optimizer.functions.on-fleek.app/https://fleek.network/share-image.png/?w=500
40+
```
41+
42+
It converts a 260 kB PNG image to a 5.21 kB WEBP image and delivers it as a response that takes less than a second.
43+
44+
## Installation and Usage
45+
46+
### Installation
47+
48+
First, install the required dependencies using:
49+
50+
```sh
51+
bun i
52+
```
53+
54+
### Building the Project
55+
56+
To build the project, use the following command:
57+
58+
```sh
59+
bun run build
60+
```
61+
62+
This will create a `./dist/main.js` bundle that includes all tree-shaken dependencies and inline WASM modules.
63+
64+
### Deployment
65+
66+
To deploy the project, follow these steps:
67+
68+
1. Create a function:
69+
70+
```sh
71+
bun create-func $NAME
72+
```
73+
74+
2. Deploy the function:
75+
76+
```sh
77+
bun deploy-func $NAME
78+
```
79+
80+
Replace `$NAME` with the desired name of your function.
81+
82+
## Contributing
83+
84+
We welcome contributions! If you have suggestions for improvements or encounter any issues, please open an issue or submit a pull request on our GitHub repository.
85+
86+
## Contact
87+
88+
This project was developed as part of the Fleek Hacker House at ETH Brussels 2024. It's a proof of concept (PoC) and its future maintenance is uncertain. For questions or support, please reach out to [Blossom Labs](https://blossom.software).

packages/token-list/biome.json

+22
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,22 @@
1+
{
2+
"$schema": "https://biomejs.dev/schemas/1.8.3/schema.json",
3+
"vcs": {
4+
"enabled": true,
5+
"clientKind": "git",
6+
"useIgnoreFile": true
7+
},
8+
"organizeImports": {
9+
"enabled": false
10+
},
11+
"linter": {
12+
"enabled": true,
13+
"rules": {
14+
"recommended": true
15+
}
16+
},
17+
"formatter": {
18+
"enabled": true,
19+
"indentWidth": 2,
20+
"indentStyle": "space"
21+
}
22+
}

packages/token-list/package.json

+21
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,21 @@
1+
{
2+
"name": "evmcrispr-token-list",
3+
"private": true,
4+
"version": "0.0.0",
5+
"type": "module",
6+
"main": "./src/main.ts",
7+
"scripts": {
8+
"build": "tsc src/main.ts && rollup -c",
9+
"create-func": "fleek functions create --name",
10+
"deploy-func": "fleek functions deploy --noBundle --path dist/main.js --name",
11+
"lint": "biome check --write && biome format --write && biome lint --write"
12+
},
13+
"devDependencies": {
14+
"@biomejs/biome": "^1.8.3",
15+
"@rollup/plugin-node-resolve": "^13.0.6",
16+
"@rollup/plugin-typescript": "^11.1.6",
17+
"rollup": "^2.60.0",
18+
"typescript": "^5.2.2"
19+
},
20+
"license": "MIT"
21+
}

packages/token-list/rollup.config.js

+16
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,16 @@
1+
import { nodeResolve } from "@rollup/plugin-node-resolve";
2+
import typescript from "@rollup/plugin-typescript";
3+
4+
export default {
5+
input: "src/main.ts",
6+
output: {
7+
dir: "dist",
8+
format: "es",
9+
inlineDynamicImports: true,
10+
banner: 'import { Buffer } from "node:buffer";',
11+
},
12+
plugins: [
13+
nodeResolve(), // Needed to bundle the assets from node_modules
14+
typescript(),
15+
],
16+
};

packages/token-list/src/coingecko.ts

+25
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,25 @@
1+
type Network = {
2+
id: string;
3+
chain_identifier: number | null;
4+
name: string;
5+
shortname: string;
6+
native_coin_id: string;
7+
};
8+
9+
export async function getNetworkName(
10+
chainId: number,
11+
): Promise<{ name: string; id: string }> {
12+
const networks: Network[] = await fetch(
13+
"https://api.coingecko.com/api/v3/asset_platforms",
14+
).then((res) => res.json());
15+
const network = networks.find(
16+
(network) => network.chain_identifier === chainId,
17+
);
18+
if (!network) {
19+
throw {
20+
status: 404,
21+
body: "Network not found",
22+
};
23+
}
24+
return { name: network.name, id: network.id };
25+
}

packages/token-list/src/main.ts

+120
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,120 @@
1+
import { getNetworkName } from "./coingecko";
2+
3+
type RequestObject = {
4+
method: "GET" | "POST" | "PUT" | "DELETE" | "PATCH" | "OPTIONS" | "HEAD";
5+
headers?: {
6+
[key: string]: string;
7+
} | null;
8+
path: string;
9+
query?: {
10+
[key: string]: string | string[];
11+
} | null;
12+
body?: string | null;
13+
};
14+
15+
type ResponseObject =
16+
| {
17+
status: number;
18+
headers?: {
19+
[key: string]: string;
20+
} | null;
21+
body?: string;
22+
}
23+
| string
24+
| ArrayBuffer;
25+
26+
export async function main(params: RequestObject): Promise<ResponseObject> {
27+
try {
28+
const { chainId } = processParams(params);
29+
const { name: networkName, id: networkId } = await getNetworkName(chainId);
30+
const coingeckoTokenList = `https://tokens.coingecko.com/${networkId}/all.json`;
31+
const superfluidTokenList =
32+
"https://raw.githubusercontent.com/superfluid-finance/tokenlist/main/superfluid.extended.tokenlist.json";
33+
const tokenLists = await Promise.all([
34+
fetch(coingeckoTokenList).then((res) => res.json()),
35+
fetch(superfluidTokenList).then((res) => res.json()),
36+
]);
37+
const lastTimestamp = Math.max(
38+
...tokenLists.map((tokenList) => new Date(tokenList.timestamp).getTime()),
39+
);
40+
const tokenList = {
41+
name: `EVMcrispr Token List (${networkName})`,
42+
logoURI: "https://evmcrispr.com/favicon.ico",
43+
timestamp: new Date(lastTimestamp).toISOString(),
44+
tokens: tokenLists[0].tokens
45+
.concat(
46+
tokenLists[1].tokens.filter((token) => token.chainId === chainId),
47+
)
48+
.reduce((acc, token) => {
49+
if (acc.find((t) => t.address === token.address)) {
50+
return acc;
51+
}
52+
acc.push(token);
53+
return acc;
54+
}, []),
55+
version: {
56+
patch: 1,
57+
},
58+
};
59+
return {
60+
status: 200,
61+
headers: {
62+
"Content-Type": "application/json",
63+
"Access-Control-Allow-Origin": "*",
64+
},
65+
body: JSON.stringify(tokenList),
66+
};
67+
} catch (error) {
68+
if (isErrorWithStatusAndBody(error)) {
69+
return error;
70+
}
71+
return {
72+
status: 500,
73+
body: "Internal Server Error",
74+
};
75+
}
76+
}
77+
78+
// main({ method: "GET", path: "/v0/1" }).then((res) => {
79+
// console.log(res);
80+
// });
81+
82+
function isErrorWithStatusAndBody(
83+
error: unknown,
84+
): error is { status: number; body: string } {
85+
const err = error as { [key: string]: unknown };
86+
return (
87+
err &&
88+
typeof err === "object" &&
89+
typeof err.status === "number" &&
90+
typeof err.body === "string"
91+
);
92+
}
93+
94+
function processParams(params: RequestObject): { chainId: number } {
95+
const { method, path } = params;
96+
97+
if (method !== "GET") {
98+
throw {
99+
status: 405,
100+
body: "Method Not Allowed",
101+
};
102+
}
103+
104+
if (!path.startsWith("/v0/")) {
105+
throw {
106+
status: 400,
107+
body: "Invalid URL",
108+
};
109+
}
110+
const [, , chainId] = path.split("/");
111+
112+
if (!Number(chainId)) {
113+
throw {
114+
status: 400,
115+
body: "Invalid chainId",
116+
};
117+
}
118+
119+
return { chainId: Number(chainId) };
120+
}

0 commit comments

Comments
 (0)