diff --git a/packages/snaps-sdk/package.json b/packages/snaps-sdk/package.json
index bea57e44ef..f4dc71d79a 100644
--- a/packages/snaps-sdk/package.json
+++ b/packages/snaps-sdk/package.json
@@ -66,6 +66,7 @@
"eslint-plugin-promise": "^6.1.1",
"expect-type": "^0.17.3",
"jest": "^29.0.2",
+ "jest-fetch-mock": "^3.0.3",
"jest-it-up": "^2.0.0",
"prettier": "^2.7.1",
"prettier-plugin-packagejson": "^2.2.11",
diff --git a/packages/snaps-sdk/src/images.test.ts b/packages/snaps-sdk/src/images.test.ts
new file mode 100644
index 0000000000..11dc8e62e6
--- /dev/null
+++ b/packages/snaps-sdk/src/images.test.ts
@@ -0,0 +1,102 @@
+import fetchMock from 'jest-fetch-mock';
+
+import { getImageComponent, getImageData } from './images';
+
+fetchMock.enableMocks();
+
+describe('getImageData', () => {
+ beforeEach(() => {
+ fetchMock.resetMocks();
+ });
+
+ it('returns the image data as a data-string for a PNG image', async () => {
+ fetchMock.mockResponse('image data', {
+ headers: {
+ 'Content-Type': 'image/png',
+ },
+ });
+
+ const imageData = await getImageData('https://example.com/image.png');
+ expect(imageData).toMatchInlineSnapshot(
+ `"data:image/png;base64,aW1hZ2UgZGF0YQ=="`,
+ );
+ });
+
+ it('returns the image data as a data-string for a JPEG image', async () => {
+ fetchMock.mockResponse('image data', {
+ headers: {
+ 'Content-Type': 'image/jpeg',
+ },
+ });
+
+ const imageData = await getImageData('https://example.com/image.jpeg');
+ expect(imageData).toMatchInlineSnapshot(
+ `"data:image/jpeg;base64,aW1hZ2UgZGF0YQ=="`,
+ );
+ });
+
+ it('throws an error if the image is not a PNG or JPEG image', async () => {
+ fetchMock.mockResponse('image data', {
+ headers: {
+ 'Content-Type': 'image/gif',
+ },
+ });
+
+ await expect(getImageData('https://example.com/image.gif')).rejects.toThrow(
+ 'Expected image data to be a JPEG or PNG image.',
+ );
+ });
+
+ it('throws an error if the fetch request fails', async () => {
+ fetchMock.mockResponse(async () => ({
+ status: 404,
+ statusText: 'Not Found',
+ }));
+
+ await expect(getImageData('https://example.com/image.gif')).rejects.toThrow(
+ 'Failed to fetch image data from "https://example.com/image.gif": 404 Not Found',
+ );
+ });
+});
+
+describe('getImage', () => {
+ beforeEach(() => {
+ fetchMock.resetMocks();
+ });
+
+ it('returns the image data as an image component', async () => {
+ fetchMock.mockResponse('image data', {
+ headers: {
+ 'Content-Type': 'image/png',
+ },
+ });
+
+ const result = await getImageComponent('https://example.com/image.png');
+ expect(result).toMatchInlineSnapshot(`
+ {
+ "type": "image",
+ "value": "",
+ }
+ `);
+ });
+
+ it('returns the image data as an image component with width and height', async () => {
+ fetchMock.mockResponse('image data', {
+ headers: {
+ 'Content-Type': 'image/png',
+ },
+ });
+
+ const result = await getImageComponent('https://example.com/image.png', {
+ width: 100,
+ height: 100,
+ });
+
+ expect(result).toMatchInlineSnapshot(`
+ {
+ "type": "image",
+ "value": "",
+ }
+ `);
+ });
+});
diff --git a/packages/snaps-sdk/src/images.ts b/packages/snaps-sdk/src/images.ts
new file mode 100644
index 0000000000..87df60f7ef
--- /dev/null
+++ b/packages/snaps-sdk/src/images.ts
@@ -0,0 +1,125 @@
+import { assert, bytesToBase64 } from '@metamask/utils';
+
+import { image } from './ui';
+
+/**
+ * Get raw image data from a URL.
+ *
+ * @param url - The URL to get the image data from.
+ * @param options - The options to use when fetching the image data. This is
+ * passed directly to `fetch`.
+ * @returns A promise that resolves to the image data as a blob.
+ */
+async function getRawImageData(url: string, options?: RequestInit) {
+ return fetch(url, options).then(async (response) => {
+ if (!response.ok) {
+ throw new Error(
+ `Failed to fetch image data from "${url}": ${response.status} ${response.statusText}`,
+ );
+ }
+
+ const blob = await response.blob();
+ assert(
+ blob.type === 'image/jpeg' || blob.type === 'image/png',
+ 'Expected image data to be a JPEG or PNG image.',
+ );
+
+ return blob;
+ });
+}
+
+/**
+ * Get image data as data-string from a URL. This is useful for embedding images
+ * inside of SVGs. Only JPEG and PNG images are supported.
+ *
+ * Note: This function uses `fetch` to get the image data. This means that using
+ * it requires the `endowment:network-access` permission.
+ *
+ * @example
+ * const imageData = await getImageData('https://cataas.com/cat');
+ * const svg = `
+ *
+ * `;
+ *
+ * // Render the SVG in a Snap UI.
+ * const ui = image(svg);
+ * @param url - The URL to get the image data from.
+ * @param options - The options to use when fetching the image data. This is
+ * passed directly to `fetch`.
+ * @returns A promise that resolves to the image data as a data-string.
+ */
+export async function getImageData(url: string, options?: RequestInit) {
+ const blob = await getRawImageData(url, options);
+ const bytes = new Uint8Array(await blob.arrayBuffer());
+
+ return `data:${blob.type};base64,${bytesToBase64(bytes)}`;
+}
+
+/**
+ * Options for getting an SVG image element from a URL.
+ *
+ * @property width - The width of the image. If this is not provided, the image
+ * will be rendered at its original width.
+ * @property height - The height of the image. If this is not provided, the
+ * image will be rendered at its original height.
+ * @property request - The options to use when fetching the image data. This is
+ * passed directly to `fetch`.
+ */
+export type ImageOptions = {
+ width?: number;
+ height?: number;
+ request?: RequestInit;
+};
+
+/**
+ * Get an image component from a URL. This is useful for embedding images inside
+ * Snap UIs. Only JPEG and PNG images are supported.
+ *
+ * Note: This function uses `fetch` to get the image data. This means that using
+ * it requires the `endowment:network-access` permission.
+ *
+ * @example
+ * const component = await getImage('https://cataas.com/cat');
+ *
+ * return await snap.request({
+ * method: 'snap_dialog',
+ * params: {
+ * type: 'alert',
+ * content: panel([
+ * component,
+ * ]),
+ * },
+ * });
+ * @param url - The URL to get the image data from.
+ * @param options - The options to use when fetching and rendering the image.
+ * @param options.width - The width of the image. If this is not provided, the
+ * image will be rendered at its original width.
+ * @param options.height - The height of the image. If this is not provided, the
+ * image will be rendered at its original height.
+ * @param options.request - The options to use when fetching the image data.
+ * This is passed directly to `fetch`.
+ * @returns A promise that resolves to the image data as an image component.
+ */
+export async function getImageComponent(
+ url: string,
+ { width, height, request }: ImageOptions = {},
+) {
+ const imageData = await getImageData(url, request);
+
+ let size = '';
+ if (width) {
+ assert(width > 0, 'Expected width to be greater than 0.');
+ size += `width="${width}" `;
+ }
+
+ if (height) {
+ assert(height > 0, 'Expected height to be greater than 0.');
+ size += `height="${height}"`;
+ }
+
+ return image(
+ ``,
+ );
+}
diff --git a/packages/snaps-sdk/src/index.ts b/packages/snaps-sdk/src/index.ts
index cb353f11ab..49ac68bab1 100644
--- a/packages/snaps-sdk/src/index.ts
+++ b/packages/snaps-sdk/src/index.ts
@@ -18,7 +18,9 @@ export type {
JsonRpcRequest,
JsonRpcParams,
} from '@metamask/utils';
+export { assert } from '@metamask/utils';
export * from './errors';
+export * from './images';
export * from './types';
export * from './ui';
diff --git a/yarn.lock b/yarn.lock
index 59fa28afa9..caf7c5b21e 100644
--- a/yarn.lock
+++ b/yarn.lock
@@ -5518,6 +5518,7 @@ __metadata:
expect-type: ^0.17.3
is-svg: ^4.4.0
jest: ^29.0.2
+ jest-fetch-mock: ^3.0.3
jest-it-up: ^2.0.0
prettier: ^2.7.1
prettier-plugin-packagejson: ^2.2.11