Skip to content

Commit 116d642

Browse files
Dhaiwat10arboleya
andauthored
feat: migrate over @fuels/assets package into the TS SDK (FuelLabs#1747)
* feat: migrate over @fuels/assets package into the TS SDK * add tests, add some docstrings * Update tsup.config.ts * Update network.test.ts * Update resolveIconPaths.test.ts * Update url.test.ts * updater test docstrings * Update tweaking-the-blockchain.test.ts * fixup --------- Co-authored-by: Anderson Arboleya <[email protected]>
1 parent 9dc6a83 commit 116d642

18 files changed

+411
-0
lines changed

.changeset/loud-rocks-walk.md

+5
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,5 @@
1+
---
2+
"@fuel-ts/account": patch
3+
---
4+
5+
feat: migrate over @fuels/assets package into the TS SDK

.eslintignore

+1
Original file line numberDiff line numberDiff line change
@@ -13,6 +13,7 @@ apps/demo-react-vite
1313

1414
packages/fuels/test/fixtures/project
1515
packages/account/src/providers/__generated__
16+
packages/account/src/providers/assets
1617

1718
out
1819
CHANGELOG.md

.github/workflows/release.yaml

+22
Original file line numberDiff line numberDiff line change
@@ -116,3 +116,25 @@ jobs:
116116
git commit -m "docs: API docs - v${{ env.BUILD_VERSION }}"
117117
git push
118118
git restore apps/docs/.gitignore
119+
120+
# Upload assets to S3
121+
- uses: unfor19/[email protected]
122+
if: steps.changesets.outputs.published != 'true'
123+
with:
124+
version: 2
125+
verbose: false
126+
arch: amd64
127+
rootdir: ""
128+
workdir: ""
129+
- uses: aws-actions/configure-aws-credentials@v1
130+
if: steps.changesets.outputs.published != 'true'
131+
with:
132+
aws-access-key-id: ${{ secrets.S3_CDN_ACCESS_KEY }}
133+
aws-secret-access-key: ${{ secrets.S3_CDN_SECRET_KEY }}
134+
aws-region: us-east-1
135+
- name: Upload assets to s3
136+
if: steps.changesets.outputs.published != 'true'
137+
run: |
138+
aws s3 cp ./packages/account/src/providers/assets/images/ s3://${S3_CDN_BUCKET}/assets/ --recursive
139+
env:
140+
S3_CDN_BUCKET: ${{ secrets.S3_CDN_BUCKET }}

.prettierignore

+1
Original file line numberDiff line numberDiff line change
@@ -14,6 +14,7 @@ apps/demo-typegen/src/predicate-types
1414
apps/docs/.vitepress/cache/
1515

1616
packages/fuels/test/fixtures/project
17+
packages/account/src/providers/assets
1718

1819
__generated__
1920
out

packages/account/.gitignore

+1
Original file line numberDiff line numberDiff line change
@@ -0,0 +1 @@
1+
src/providers/assets/images/assets.json
Loading
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,37 @@
1+
import { CHAIN_IDS } from '../chains';
2+
3+
import type { Assets } from './types';
4+
5+
export const assets: Assets = [
6+
{
7+
name: 'Ethereum',
8+
symbol: 'ETH',
9+
icon: 'eth.svg',
10+
networks: [
11+
{
12+
type: 'ethereum',
13+
chainId: CHAIN_IDS.eth.sepolia,
14+
decimals: 18,
15+
},
16+
{
17+
type: 'ethereum',
18+
chainId: CHAIN_IDS.eth.foundry,
19+
decimals: 18,
20+
},
21+
{
22+
type: 'fuel',
23+
chainId: CHAIN_IDS.fuel.beta5,
24+
decimals: 9,
25+
assetId: '0x0000000000000000000000000000000000000000000000000000000000000000',
26+
},
27+
{
28+
type: 'fuel',
29+
chainId: CHAIN_IDS.fuel.devnet,
30+
decimals: 9,
31+
assetId: '0x0000000000000000000000000000000000000000000000000000000000000000',
32+
},
33+
],
34+
},
35+
];
36+
37+
export * from './utils';
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,39 @@
1+
export type Ethereum = {
2+
/** type of network */
3+
type: 'ethereum';
4+
/** chain id of the network */
5+
chainId: number;
6+
/** number of decimals of the asset */
7+
decimals: number;
8+
/** address of the asset contract */
9+
address?: string;
10+
};
11+
12+
export type Fuel = {
13+
/** type of network */
14+
type: 'fuel';
15+
/** chain id of the network */
16+
chainId: number;
17+
/** number of decimals of the asset */
18+
decimals: number;
19+
/** assetId on the Fuel Network */
20+
assetId: string;
21+
/** the contractId of that generated the Asset on the Fuel Network */
22+
contractId?: string;
23+
};
24+
25+
export type Asset = {
26+
/** name of the asset */
27+
name: string;
28+
/** description of the asset */
29+
symbol: string;
30+
/** icon of the asset */
31+
icon: string;
32+
/** asset id on Fuel Network */
33+
networks: Array<Ethereum | Fuel>;
34+
};
35+
36+
export type Assets = Array<Asset>;
37+
38+
export type AssetEth = Omit<Asset, 'networks'> & Ethereum;
39+
export type AssetFuel = Omit<Asset, 'networks'> & Fuel;
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,3 @@
1+
export * from './network';
2+
export * from './resolveIconPaths';
3+
export * from './url';
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,75 @@
1+
import { Asset } from '../types'
2+
import { getAssetEth, getAssetFuel, getAssetWithNetwork, getDefaultChainId } from '../utils/network';
3+
import { assets } from '../index'
4+
import { CHAIN_IDS } from '../../chains'
5+
6+
/**
7+
* @group node
8+
*/
9+
describe('Network Utils', () => {
10+
test('getDefaultChainId', async () => {
11+
expect(getDefaultChainId('ethereum')).toBe(11155111);
12+
expect(getDefaultChainId('fuel')).toBe(0);
13+
})
14+
15+
test('getAssetWithNetwork - Ethereum', async () => {
16+
const asset = assets[0] as Asset
17+
const assetEth = getAssetWithNetwork({ asset, networkType: 'ethereum', chainId: CHAIN_IDS.eth.sepolia })
18+
expect(assetEth).toEqual({
19+
type: 'ethereum',
20+
chainId: CHAIN_IDS.eth.sepolia,
21+
decimals: 18,
22+
icon: 'eth.svg',
23+
name: 'Ethereum',
24+
symbol: 'ETH'
25+
})
26+
})
27+
28+
test('getAssetWithNetwork - Fuel', async () => {
29+
const asset = assets[0] as Asset
30+
const assetFuel = getAssetWithNetwork({ asset, networkType: 'fuel', chainId: CHAIN_IDS.fuel.beta5 })
31+
expect(assetFuel).toEqual({
32+
type: 'fuel',
33+
chainId: CHAIN_IDS.fuel.beta5,
34+
decimals: 9,
35+
assetId: '0x0000000000000000000000000000000000000000000000000000000000000000',
36+
icon: 'eth.svg',
37+
name: 'Ethereum',
38+
symbol: 'ETH'
39+
})
40+
})
41+
42+
test('getAssetWithNetwork - invalid network', async () => {
43+
const asset = assets[0] as Asset
44+
const assetUndefined = getAssetWithNetwork({ asset, networkType: 'ethereum', chainId: 0 })
45+
expect(assetUndefined).toBeUndefined()
46+
})
47+
48+
test('getAssetEth', async () => {
49+
const asset = assets[0] as Asset
50+
const assetEth = getAssetEth(asset)
51+
expect(assetEth).toEqual({
52+
type: 'ethereum',
53+
chainId: CHAIN_IDS.eth.sepolia,
54+
decimals: 18,
55+
icon: 'eth.svg',
56+
name: 'Ethereum',
57+
symbol: 'ETH',
58+
})
59+
})
60+
61+
test('getAssetFuel', async () => {
62+
const asset = assets[0] as Asset
63+
const assetFuel = getAssetFuel(asset)
64+
65+
expect(assetFuel).toEqual({
66+
type: 'fuel',
67+
chainId: CHAIN_IDS.fuel.beta5,
68+
decimals: 9,
69+
assetId: '0x0000000000000000000000000000000000000000000000000000000000000000',
70+
icon: 'eth.svg',
71+
name: 'Ethereum',
72+
symbol: 'ETH',
73+
})
74+
})
75+
})
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,95 @@
1+
import { CHAIN_IDS } from '../../chains';
2+
import type { Asset, AssetEth, AssetFuel, Ethereum, Fuel } from '../types';
3+
4+
type Network = Ethereum | Fuel;
5+
export type NetworkTypes = Ethereum['type'] | Fuel['type'];
6+
type NetworkTypeToNetwork<T> = T extends 'ethereum' ? Ethereum : T extends 'fuel' ? Fuel : Network;
7+
8+
/**
9+
* Returns the default chainId for the given network
10+
*/
11+
export const getDefaultChainId = (networkType: NetworkTypes): number | undefined => {
12+
if (networkType === 'ethereum') {
13+
return CHAIN_IDS.eth.sepolia;
14+
}
15+
if (networkType === 'fuel') {
16+
return CHAIN_IDS.fuel.beta5;
17+
}
18+
19+
return undefined;
20+
};
21+
22+
export type GetAssetNetworkParams<T extends NetworkTypes | undefined> = {
23+
asset: Asset;
24+
chainId?: number;
25+
networkType: T;
26+
};
27+
28+
/**
29+
* Returns the asset's network on the given network
30+
* eg. getAssetNetwork({ asset, chainId: 1, networkType: 'ethereum' }) will return the asset's details on Ethereum mainnet
31+
*/
32+
export const getAssetNetwork = <T extends NetworkTypes | undefined>({
33+
asset,
34+
chainId,
35+
networkType,
36+
}: GetAssetNetworkParams<T>): NetworkTypeToNetwork<T> => {
37+
const network = asset.networks.find(
38+
(item) => item.chainId === chainId && item.type === networkType
39+
) as NetworkTypeToNetwork<T>;
40+
41+
return network;
42+
};
43+
44+
/**
45+
* Returns the asset's details on the given network alongwith the asset itself
46+
* eg. getAssetWithNetwork({ asset, chainId: 1, networkType: 'ethereum' }) will return the asset's details on Ethereum mainnet and the asset itself
47+
*/
48+
export const getAssetWithNetwork = <T extends NetworkTypes>({
49+
asset,
50+
chainId,
51+
networkType,
52+
}: GetAssetNetworkParams<T>): AssetEth | AssetFuel | undefined => {
53+
const { networks: _, ...assetRest } = asset;
54+
55+
const chainIdToUse = chainId ?? getDefaultChainId(networkType);
56+
// use two equals(==) cuz we wan't to keep 0 as a valid chainId
57+
if (chainIdToUse === undefined) {
58+
return undefined;
59+
}
60+
61+
const assetNetwork = getAssetNetwork({
62+
asset,
63+
chainId: chainIdToUse,
64+
networkType,
65+
});
66+
67+
if (!assetNetwork) {
68+
return undefined;
69+
}
70+
71+
return {
72+
...assetRest,
73+
...assetNetwork,
74+
};
75+
};
76+
77+
/**
78+
* Returns the asset's details on Ethereum
79+
*/
80+
export const getAssetEth = (asset: Asset, chainId?: number): AssetEth | undefined =>
81+
getAssetWithNetwork({
82+
asset,
83+
networkType: 'ethereum',
84+
chainId,
85+
}) as AssetEth;
86+
87+
/**
88+
* Returns the asset's details on Fuel
89+
*/
90+
export const getAssetFuel = (asset: Asset, chainId?: number): AssetFuel | undefined =>
91+
getAssetWithNetwork({
92+
asset,
93+
networkType: 'fuel',
94+
chainId,
95+
}) as AssetFuel;
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,23 @@
1+
import { resolveIconPaths } from './resolveIconPaths';
2+
import { assets } from '..';
3+
4+
/**
5+
* @group node
6+
*/
7+
describe('resolveIconPaths', () => {
8+
test('without basePath', () => {
9+
const result = resolveIconPaths(assets);
10+
11+
assets.forEach((asset, index) => {
12+
expect(result[index].icon).toBe(`./${asset.icon}`);
13+
});
14+
});
15+
16+
test('with basePath', () => {
17+
const result = resolveIconPaths(assets, 'https://some-url.com');
18+
19+
assets.forEach((asset, index) => {
20+
expect(result[index].icon).toBe(`https://some-url.com/${asset.icon}`);
21+
});
22+
});
23+
});
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,16 @@
1+
import type { Assets } from '../types';
2+
3+
import { urlJoin } from './url';
4+
5+
/**
6+
* Returns the list of assets with the icon paths 'resolved'. eg. `./eth.svg` -> `https://some-url.com/eth.svg`
7+
* @param assets - List of assets
8+
* @param basePath - Base path for the icon URLs (default: './')
9+
* @returns The assets with the icon paths resolved
10+
*/
11+
export function resolveIconPaths(assets: Assets, basePath = './') {
12+
return assets.map((asset) => ({
13+
...asset,
14+
icon: urlJoin(basePath, asset.icon),
15+
}));
16+
}

0 commit comments

Comments
 (0)