Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

feat: support Explorer Asset API #3648

Merged
merged 23 commits into from
Feb 3, 2025
Merged
Show file tree
Hide file tree
Changes from 22 commits
Commits
Show all changes
23 commits
Select commit Hold shift + click to select a range
b6218af
chore: shuffled assets directory
petertonysmith94 Jan 24, 2025
3cc102b
chore: changeset
petertonysmith94 Jan 24, 2025
4d1236e
chore: removed assets.json
petertonysmith94 Jan 24, 2025
7d7fb0c
chore: fix release path
petertonysmith94 Jan 24, 2025
b0929f9
chore: added basic API with live tests
petertonysmith94 Jan 30, 2025
888cfca
chore: finalized API w/ mocks
petertonysmith94 Jan 30, 2025
80dcac2
Merge branch 'master' of github.com:FuelLabs/fuels-ts into ps/chore/m…
petertonysmith94 Jan 30, 2025
15e0145
Merge branch 'ps/chore/moved-account-assets-namespace' into ps/feat/s…
petertonysmith94 Jan 30, 2025
bd44407
chore: changeset
petertonysmith94 Jan 30, 2025
8e3e9be
chore: alter the API
petertonysmith94 Jan 30, 2025
31266e4
Merge branch 'master' of github.com:FuelLabs/fuels-ts into ps/feat/su…
petertonysmith94 Jan 31, 2025
59caf7f
chore: fix tests
petertonysmith94 Jan 31, 2025
fffc1ef
chore: finalizing functionality
petertonysmith94 Jan 31, 2025
5b63085
chore: added docs
petertonysmith94 Jan 31, 2025
07a3947
chore: added speeling
petertonysmith94 Jan 31, 2025
3eb2a56
chore: added explorer api endpoints to ignore
petertonysmith94 Jan 31, 2025
3f994b8
chore: removed now deployed docs
petertonysmith94 Jan 31, 2025
2a6818e
chore: fix tests
petertonysmith94 Jan 31, 2025
78bd6ff
docs: moved asset API
petertonysmith94 Feb 3, 2025
3dc037d
docs: moved asset api snippets
petertonysmith94 Feb 3, 2025
1454a03
chore: refactored to use `network`
petertonysmith94 Feb 3, 2025
3b9d228
Merge branch 'master' into ps/feat/support-asset-api
arboleya Feb 3, 2025
9a378b7
Merge branch 'master' of github.com:FuelLabs/fuels-ts into ps/feat/su…
petertonysmith94 Feb 3, 2025
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
5 changes: 5 additions & 0 deletions .changeset/rich-items-argue.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,5 @@
---
"@fuel-ts/account": patch
---

feat: support Explorer Asset API
4 changes: 4 additions & 0 deletions apps/docs/.vitepress/config.ts
Original file line number Diff line number Diff line change
Expand Up @@ -390,6 +390,10 @@ export default defineConfig({
text: 'Using assets',
link: '/guide/utilities/using-assets',
},
{
text: 'Asset API',
link: '/guide/utilities/asset-api',
},
],
},
{
Expand Down
3 changes: 2 additions & 1 deletion apps/docs/spell-check-custom-words.txt
Original file line number Diff line number Diff line change
Expand Up @@ -343,4 +343,5 @@ Workspaces
WSL
XOR
XORs
YAML
YAML
RESTful
30 changes: 30 additions & 0 deletions apps/docs/src/guide/utilities/asset-api.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,30 @@
# Asset API

The Asset API is a RESTful API that allows you to query the assets on the Fuel blockchain. We allow for querying the Asset API on both the Mainnet and Testnet.

| | Endpoint |
| ------- | --------------------------------------------- |
| Mainnet | https://mainnet-explorer.fuel.network |
| Testnet | https://explorer-indexer-testnet.fuel.network |

For more information about the API, please refer to the [Wiki](https://github.com/FuelLabs/fuel-explorer/wiki/Assets-API#) page.

## Asset by ID

We can request information about an asset by its asset ID, using the `getAssetById` function. This will leverage the endpoint `/assets/<assetId>` to fetch the asset information.

<<< @./snippets/asset-api/asset-by-id.ts#full{ts:line-numbers}

By default, we will request the asset information for `mainnet`. If you want to request the asset information from other networks, you can pass the `network` parameter (this is the same for the [`getAssetsByOwner`](#assets-by-owner) function).

<<< @./snippets/asset-api/asset-by-id.ts#testnet{ts:line-numbers}

## Assets by Owner

We can request information about an asset by its owner, using the `getAssetsByOwner` function. This will leverage the endpoint `/accounts/<owner>/assets` to fetch the asset information.

<<< @./snippets/asset-api/assets-by-owner.ts#full{ts:line-numbers}

You can change the pagination parameters to fetch more assets (up to 100 assets per request).

<<< @./snippets/asset-api/assets-by-owner.ts#pagination{ts:line-numbers}
18 changes: 18 additions & 0 deletions apps/docs/src/guide/utilities/snippets/asset-api/asset-by-id.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,18 @@
// #region full
import type { AssetInfo } from 'fuels';
import { getAssetById } from 'fuels';

const asset: AssetInfo | null = await getAssetById({
assetId: '0xf8f8b6283d7fa5b672b530cbb84fcccb4ff8dc40f8176ef4544ddb1f1952ad07',
});

console.log('AssetInfo', asset);
// AssetInfo { ... }
// #endregion full

// #region testnet
await getAssetById({
assetId: '0xf8f8b6283d7fa5b672b530cbb84fcccb4ff8dc40f8176ef4544ddb1f1952ad07',
network: 'testnet',
});
// #endregion testnet
Original file line number Diff line number Diff line change
@@ -0,0 +1,18 @@
// #region full
import type { AssetsByOwner } from 'fuels';
import { getAssetsByOwner } from 'fuels';

const assets: AssetsByOwner = await getAssetsByOwner({
owner: '0x0000000000000000000000000000000000000000000000000000000000000000',
});

console.log('AssetsByOwner', assets);
// AssetsByOwner { data: [], pageInfo: { count: 0 } }
// #endregion full

// #region pagination
await getAssetsByOwner({
owner: '0x0000000000000000000000000000000000000000000000000000000000000000',
pagination: { last: 100 },
});
// #endregion pagination
4 changes: 2 additions & 2 deletions link-check.config.json
Original file line number Diff line number Diff line change
Expand Up @@ -9,10 +9,10 @@
"pattern": "^http://localhost:3000"
},
{
"pattern": "https://docs.fuel.network/docs/fuels-ts/getting-started/connecting-to-the-network"
"pattern": "https://mainnet-explorer.fuel.network"
},
{
"pattern": "https://docs.fuel.network/docs/fuels-ts/getting-started/running-a-local-fuel-node"
"pattern": "https://explorer-indexer-testnet.fuel.network"
}
]
}
194 changes: 194 additions & 0 deletions packages/account/src/assets/asset-api.test.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,194 @@
import { MOCK_ASSET_INFO_BY_OWNER, MOCK_BASE_ASSET, MOCK_FUEL_ASSET, MOCK_NFT_ASSET } from "../../test/fixtures/assets";
import { getAssetById, getAssetsByOwner, AssetInfo } from "./asset-api";

const mockFetch = () => {
const jsonResponse = vi.fn();
const fetchSpy = vi.spyOn(global, 'fetch').mockResolvedValueOnce({
json: jsonResponse
} as unknown as Response);

return {
fetch: fetchSpy,
json: jsonResponse
}
}

/**
* @group node
* @group browser
*/
describe('Asset API', () => {
describe('getAssetById', () => {
it('should get an asset by id [Base Asset - Verified]', async () => {
const expected = {
assetId: expect.any(String),
name: expect.any(String),
symbol: expect.any(String),
decimals: expect.any(Number),
rate: expect.any(Number),
icon: expect.any(String),
suspicious: expect.any(Boolean),
verified: expect.any(Boolean),
networks: expect.any(Array),
}
const { fetch, json } = mockFetch();
json.mockResolvedValueOnce(MOCK_BASE_ASSET);

const response = await getAssetById({
assetId: MOCK_BASE_ASSET.assetId
});

const assetInfo = response as AssetInfo;
expect(assetInfo).toEqual(MOCK_BASE_ASSET)
expect(assetInfo).toMatchObject(expected)
expect(Object.keys(assetInfo).sort()).toEqual(Object.keys(expected).sort())
})

it('should get an asset by id [Fuel - Verified]', async () => {
const expected = {
assetId: expect.any(String),
contractId: expect.any(String),
subId: expect.any(String),
icon: expect.any(String),
name: expect.any(String),
symbol: expect.any(String),
decimals: expect.any(Number),
rate: expect.any(Number),
suspicious: expect.any(Boolean),
verified: expect.any(Boolean),
networks: expect.any(Array),
metadata: expect.any(Object),
totalSupply: expect.any(String),
}
const { json } = mockFetch();
json.mockResolvedValueOnce(MOCK_FUEL_ASSET);

const response = await getAssetById({
assetId: MOCK_FUEL_ASSET.assetId
});

const assetInfo = response as AssetInfo;
expect(response).toEqual(MOCK_FUEL_ASSET)
expect(assetInfo).toMatchObject(expected)
expect(Object.keys(assetInfo).sort()).toEqual(Object.keys(expected).sort())
})

it('should get an asset by id [NFT]', async () => {
const expected = {
assetId: expect.any(String),
contractId: expect.any(String),
subId: expect.any(String),

name: expect.any(String),
symbol: expect.any(String),
decimals: expect.any(Number),
totalSupply: expect.any(String),

suspicious: expect.any(Boolean),
verified: expect.any(Boolean),
isNFT: expect.any(Boolean),

metadata: expect.any(Object),
collection: expect.any(String),

owner: expect.any(String),
amount: expect.any(String),
amountInUsd: null,
uri: expect.any(String),
}

const { json } = mockFetch();
json.mockResolvedValueOnce(MOCK_NFT_ASSET);

const response = await getAssetById({
assetId: MOCK_NFT_ASSET.assetId
});

const assetInfo = response as AssetInfo;
expect(response).toEqual(MOCK_NFT_ASSET)
expect(assetInfo).toMatchObject(expected)
expect(Object.keys(assetInfo).sort()).toEqual(Object.keys(expected).sort())
});

it('should use the correct network [mainnet]', async () => {
const { fetch } = mockFetch();
const assetId = '0x0000000000000000000000000000000000000000000000000000000000000000';

await getAssetById({ assetId });

expect(fetch.mock.calls[0][0]).toMatch(`https://mainnet-explorer.fuel.network/assets/${assetId}`);
});

it('should use the correct network [testnet]', async () => {
const { fetch } = mockFetch();
const assetId = '0x0000000000000000000000000000000000000000000000000000000000000000';

await getAssetById({ assetId, network: 'testnet' });

expect(fetch.mock.calls[0][0]).toMatch(`https://explorer-indexer-testnet.fuel.network/assets/${assetId}`);
});

it('should return null if asset not found', async () => {
const { json } = mockFetch();
// The API returns a 200 status code but the response is not valid JSON
json.mockRejectedValueOnce(null);

const response = await getAssetById({
assetId: '0x0000000000000000000000000000000000000000000000000000000000000000'
});

expect(response).toBeNull();
});
});

describe('getAssetByOwner', () => {
it('should get assets by owner', async () => {
const { json } = mockFetch();
json.mockResolvedValueOnce(MOCK_ASSET_INFO_BY_OWNER);

const response = await getAssetsByOwner({
owner: MOCK_NFT_ASSET.owner,
});

expect(response.data).toEqual([MOCK_NFT_ASSET]);
expect(response.pageInfo).toEqual({
count: 1,
})
});


it('should use the correct network [mainnet]', async () => {
const { fetch } = mockFetch();
const owner = '0x0000000000000000000000000000000000000000000000000000000000000000';

await getAssetsByOwner({ owner });

expect(fetch.mock.calls[0][0]).toMatch(`https://mainnet-explorer.fuel.network/accounts/${owner}/assets`);
});

it('should use the correct network [testnet]', async () => {
const { fetch } = mockFetch();
const owner = '0x0000000000000000000000000000000000000000000000000000000000000000';

await getAssetsByOwner({ owner, network: 'testnet' });

expect(fetch.mock.calls[0][0]).toMatch(`https://explorer-indexer-testnet.fuel.network/accounts/${owner}/assets`);
});

it('should return response if no owner is found', async () => {
const { json } = mockFetch();
// The API returns a 200 status code but the response is not valid JSON
json.mockRejectedValueOnce(null);

const response = await getAssetsByOwner({
owner: '0x0000000000000000000000000000000000000000000000000000000000000000'
});

expect(response.data).toEqual([]);
expect(response.pageInfo).toEqual({
count: 0,
})
});
})
});

Loading
Loading