diff --git a/.changeset/forty-llamas-act.md b/.changeset/forty-llamas-act.md
new file mode 100644
index 0000000..5ade482
--- /dev/null
+++ b/.changeset/forty-llamas-act.md
@@ -0,0 +1,5 @@
+---
+"@minima-global/image": patch
+---
+
+package release
diff --git a/.gitignore b/.gitignore
index 7118f71..bb35bdf 100644
--- a/.gitignore
+++ b/.gitignore
@@ -9,6 +9,7 @@ dist-ssr
server/dist
public/dist
.vscode
+build
# Local env files
.env
diff --git a/.idea/workspace.xml b/.idea/workspace.xml
new file mode 100644
index 0000000..f301b3b
--- /dev/null
+++ b/.idea/workspace.xml
@@ -0,0 +1,63 @@
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+ {
+ "associatedIndex": 4
+}
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+ 1738599498285
+
+
+ 1738599498285
+
+
+
+
+
+
+
+
+
\ No newline at end of file
diff --git a/package.json b/package.json
index 367f4fc..17ff446 100644
--- a/package.json
+++ b/package.json
@@ -10,7 +10,9 @@
"check-format": "turbo check-format",
"local-release": "changeset version && changeset publish",
"ci": "turbo ci && turbo format && turbo check-format",
- "test": "turbo test"
+ "test": "turbo test",
+ "private-version": "changeset version",
+ "private-publish": "changeset publish --access restricted"
},
"workspaces": [
"tests/*",
diff --git a/packages/image/CHANGELOG.md b/packages/image/CHANGELOG.md
new file mode 100644
index 0000000..728af60
--- /dev/null
+++ b/packages/image/CHANGELOG.md
@@ -0,0 +1,7 @@
+# @minima-global/image
+
+## 1.2.3
+
+### Patch Changes
+
+- da84c83: test-release
diff --git a/packages/image/package-lock.json b/packages/image/package-lock.json
new file mode 100644
index 0000000..b15b12c
--- /dev/null
+++ b/packages/image/package-lock.json
@@ -0,0 +1,682 @@
+{
+ "name": "@minima-global-image",
+ "version": "1.0.0",
+ "lockfileVersion": 3,
+ "requires": true,
+ "packages": {
+ "": {
+ "name": "@minima-global-image",
+ "version": "1.0.0",
+ "license": "ISC",
+ "dependencies": {
+ "tslib": "^2.8.1"
+ },
+ "devDependencies": {
+ "@rollup/plugin-commonjs": "^28.0.2",
+ "@rollup/plugin-node-resolve": "^16.0.0",
+ "@rollup/plugin-typescript": "^12.1.2",
+ "@types/node": "^22.13.0",
+ "@types/react": "^19.0.8",
+ "@types/react-dom": "^19.0.3",
+ "react": "^19.0.0",
+ "react-dom": "^19.0.0",
+ "rollup": "^4.34.1",
+ "rollup-plugin-peer-deps-external": "^2.2.4",
+ "typescript": "^5.7.3"
+ }
+ },
+ "node_modules/@jridgewell/sourcemap-codec": {
+ "version": "1.5.0",
+ "resolved": "https://registry.npmjs.org/@jridgewell/sourcemap-codec/-/sourcemap-codec-1.5.0.tgz",
+ "integrity": "sha512-gv3ZRaISU3fjPAgNsriBRqGWQL6quFx04YMPW/zD8XMLsU32mhCCbfbO6KZFLjvYpCZ8zyDEgqsgf+PwPaM7GQ==",
+ "dev": true
+ },
+ "node_modules/@rollup/plugin-commonjs": {
+ "version": "28.0.2",
+ "resolved": "https://registry.npmjs.org/@rollup/plugin-commonjs/-/plugin-commonjs-28.0.2.tgz",
+ "integrity": "sha512-BEFI2EDqzl+vA1rl97IDRZ61AIwGH093d9nz8+dThxJNH8oSoB7MjWvPCX3dkaK1/RCJ/1v/R1XB15FuSs0fQw==",
+ "dev": true,
+ "dependencies": {
+ "@rollup/pluginutils": "^5.0.1",
+ "commondir": "^1.0.1",
+ "estree-walker": "^2.0.2",
+ "fdir": "^6.2.0",
+ "is-reference": "1.2.1",
+ "magic-string": "^0.30.3",
+ "picomatch": "^4.0.2"
+ },
+ "engines": {
+ "node": ">=16.0.0 || 14 >= 14.17"
+ },
+ "peerDependencies": {
+ "rollup": "^2.68.0||^3.0.0||^4.0.0"
+ },
+ "peerDependenciesMeta": {
+ "rollup": {
+ "optional": true
+ }
+ }
+ },
+ "node_modules/@rollup/plugin-node-resolve": {
+ "version": "16.0.0",
+ "resolved": "https://registry.npmjs.org/@rollup/plugin-node-resolve/-/plugin-node-resolve-16.0.0.tgz",
+ "integrity": "sha512-0FPvAeVUT/zdWoO0jnb/V5BlBsUSNfkIOtFHzMO4H9MOklrmQFY6FduVHKucNb/aTFxvnGhj4MNj/T1oNdDfNg==",
+ "dev": true,
+ "dependencies": {
+ "@rollup/pluginutils": "^5.0.1",
+ "@types/resolve": "1.20.2",
+ "deepmerge": "^4.2.2",
+ "is-module": "^1.0.0",
+ "resolve": "^1.22.1"
+ },
+ "engines": {
+ "node": ">=14.0.0"
+ },
+ "peerDependencies": {
+ "rollup": "^2.78.0||^3.0.0||^4.0.0"
+ },
+ "peerDependenciesMeta": {
+ "rollup": {
+ "optional": true
+ }
+ }
+ },
+ "node_modules/@rollup/plugin-typescript": {
+ "version": "12.1.2",
+ "resolved": "https://registry.npmjs.org/@rollup/plugin-typescript/-/plugin-typescript-12.1.2.tgz",
+ "integrity": "sha512-cdtSp154H5sv637uMr1a8OTWB0L1SWDSm1rDGiyfcGcvQ6cuTs4MDk2BVEBGysUWago4OJN4EQZqOTl/QY3Jgg==",
+ "dev": true,
+ "dependencies": {
+ "@rollup/pluginutils": "^5.1.0",
+ "resolve": "^1.22.1"
+ },
+ "engines": {
+ "node": ">=14.0.0"
+ },
+ "peerDependencies": {
+ "rollup": "^2.14.0||^3.0.0||^4.0.0",
+ "tslib": "*",
+ "typescript": ">=3.7.0"
+ },
+ "peerDependenciesMeta": {
+ "rollup": {
+ "optional": true
+ },
+ "tslib": {
+ "optional": true
+ }
+ }
+ },
+ "node_modules/@rollup/pluginutils": {
+ "version": "5.1.4",
+ "resolved": "https://registry.npmjs.org/@rollup/pluginutils/-/pluginutils-5.1.4.tgz",
+ "integrity": "sha512-USm05zrsFxYLPdWWq+K3STlWiT/3ELn3RcV5hJMghpeAIhxfsUIg6mt12CBJBInWMV4VneoV7SfGv8xIwo2qNQ==",
+ "dev": true,
+ "dependencies": {
+ "@types/estree": "^1.0.0",
+ "estree-walker": "^2.0.2",
+ "picomatch": "^4.0.2"
+ },
+ "engines": {
+ "node": ">=14.0.0"
+ },
+ "peerDependencies": {
+ "rollup": "^1.20.0||^2.0.0||^3.0.0||^4.0.0"
+ },
+ "peerDependenciesMeta": {
+ "rollup": {
+ "optional": true
+ }
+ }
+ },
+ "node_modules/@rollup/rollup-android-arm-eabi": {
+ "version": "4.34.1",
+ "resolved": "https://registry.npmjs.org/@rollup/rollup-android-arm-eabi/-/rollup-android-arm-eabi-4.34.1.tgz",
+ "integrity": "sha512-kwctwVlswSEsr4ljpmxKrRKp1eG1v2NAhlzFzDf1x1OdYaMjBYjDCbHkzWm57ZXzTwqn8stMXgROrnMw8dJK3w==",
+ "cpu": [
+ "arm"
+ ],
+ "dev": true,
+ "optional": true,
+ "os": [
+ "android"
+ ]
+ },
+ "node_modules/@rollup/rollup-android-arm64": {
+ "version": "4.34.1",
+ "resolved": "https://registry.npmjs.org/@rollup/rollup-android-arm64/-/rollup-android-arm64-4.34.1.tgz",
+ "integrity": "sha512-4H5ZtZitBPlbPsTv6HBB8zh1g5d0T8TzCmpndQdqq20Ugle/nroOyDMf9p7f88Gsu8vBLU78/cuh8FYHZqdXxw==",
+ "cpu": [
+ "arm64"
+ ],
+ "dev": true,
+ "optional": true,
+ "os": [
+ "android"
+ ]
+ },
+ "node_modules/@rollup/rollup-darwin-arm64": {
+ "version": "4.34.1",
+ "resolved": "https://registry.npmjs.org/@rollup/rollup-darwin-arm64/-/rollup-darwin-arm64-4.34.1.tgz",
+ "integrity": "sha512-f2AJ7Qwx9z25hikXvg+asco8Sfuc5NCLg8rmqQBIOUoWys5sb/ZX9RkMZDPdnnDevXAMJA5AWLnRBmgdXGEUiA==",
+ "cpu": [
+ "arm64"
+ ],
+ "dev": true,
+ "optional": true,
+ "os": [
+ "darwin"
+ ]
+ },
+ "node_modules/@rollup/rollup-darwin-x64": {
+ "version": "4.34.1",
+ "resolved": "https://registry.npmjs.org/@rollup/rollup-darwin-x64/-/rollup-darwin-x64-4.34.1.tgz",
+ "integrity": "sha512-+/2JBrRfISCsWE4aEFXxd+7k9nWGXA8+wh7ZUHn/u8UDXOU9LN+QYKKhd57sIn6WRcorOnlqPMYFIwie/OHXWw==",
+ "cpu": [
+ "x64"
+ ],
+ "dev": true,
+ "optional": true,
+ "os": [
+ "darwin"
+ ]
+ },
+ "node_modules/@rollup/rollup-freebsd-arm64": {
+ "version": "4.34.1",
+ "resolved": "https://registry.npmjs.org/@rollup/rollup-freebsd-arm64/-/rollup-freebsd-arm64-4.34.1.tgz",
+ "integrity": "sha512-SUeB0pYjIXwT2vfAMQ7E4ERPq9VGRrPR7Z+S4AMssah5EHIilYqjWQoTn5dkDtuIJUSTs8H+C9dwoEcg3b0sCA==",
+ "cpu": [
+ "arm64"
+ ],
+ "dev": true,
+ "optional": true,
+ "os": [
+ "freebsd"
+ ]
+ },
+ "node_modules/@rollup/rollup-freebsd-x64": {
+ "version": "4.34.1",
+ "resolved": "https://registry.npmjs.org/@rollup/rollup-freebsd-x64/-/rollup-freebsd-x64-4.34.1.tgz",
+ "integrity": "sha512-L3T66wAZiB/ooiPbxz0s6JEX6Sr2+HfgPSK+LMuZkaGZFAFCQAHiP3dbyqovYdNaiUXcl9TlgnIbcsIicAnOZg==",
+ "cpu": [
+ "x64"
+ ],
+ "dev": true,
+ "optional": true,
+ "os": [
+ "freebsd"
+ ]
+ },
+ "node_modules/@rollup/rollup-linux-arm-gnueabihf": {
+ "version": "4.34.1",
+ "resolved": "https://registry.npmjs.org/@rollup/rollup-linux-arm-gnueabihf/-/rollup-linux-arm-gnueabihf-4.34.1.tgz",
+ "integrity": "sha512-UBXdQ4+ATARuFgsFrQ+tAsKvBi/Hly99aSVdeCUiHV9dRTTpMU7OrM3WXGys1l40wKVNiOl0QYY6cZQJ2xhKlQ==",
+ "cpu": [
+ "arm"
+ ],
+ "dev": true,
+ "optional": true,
+ "os": [
+ "linux"
+ ]
+ },
+ "node_modules/@rollup/rollup-linux-arm-musleabihf": {
+ "version": "4.34.1",
+ "resolved": "https://registry.npmjs.org/@rollup/rollup-linux-arm-musleabihf/-/rollup-linux-arm-musleabihf-4.34.1.tgz",
+ "integrity": "sha512-m/yfZ25HGdcCSwmopEJm00GP7xAUyVcBPjttGLRAqZ60X/bB4Qn6gP7XTwCIU6bITeKmIhhwZ4AMh2XLro+4+w==",
+ "cpu": [
+ "arm"
+ ],
+ "dev": true,
+ "optional": true,
+ "os": [
+ "linux"
+ ]
+ },
+ "node_modules/@rollup/rollup-linux-arm64-gnu": {
+ "version": "4.34.1",
+ "resolved": "https://registry.npmjs.org/@rollup/rollup-linux-arm64-gnu/-/rollup-linux-arm64-gnu-4.34.1.tgz",
+ "integrity": "sha512-Wy+cUmFuvziNL9qWRRzboNprqSQ/n38orbjRvd6byYWridp5TJ3CD+0+HUsbcWVSNz9bxkDUkyASGP0zS7GAvg==",
+ "cpu": [
+ "arm64"
+ ],
+ "dev": true,
+ "optional": true,
+ "os": [
+ "linux"
+ ]
+ },
+ "node_modules/@rollup/rollup-linux-arm64-musl": {
+ "version": "4.34.1",
+ "resolved": "https://registry.npmjs.org/@rollup/rollup-linux-arm64-musl/-/rollup-linux-arm64-musl-4.34.1.tgz",
+ "integrity": "sha512-CQ3MAGgiFmQW5XJX5W3wnxOBxKwFlUAgSXFA2SwgVRjrIiVt5LHfcQLeNSHKq5OEZwv+VCBwlD1+YKCjDG8cpg==",
+ "cpu": [
+ "arm64"
+ ],
+ "dev": true,
+ "optional": true,
+ "os": [
+ "linux"
+ ]
+ },
+ "node_modules/@rollup/rollup-linux-loongarch64-gnu": {
+ "version": "4.34.1",
+ "resolved": "https://registry.npmjs.org/@rollup/rollup-linux-loongarch64-gnu/-/rollup-linux-loongarch64-gnu-4.34.1.tgz",
+ "integrity": "sha512-rSzb1TsY4lSwH811cYC3OC2O2mzNMhM13vcnA7/0T6Mtreqr3/qs6WMDriMRs8yvHDI54qxHgOk8EV5YRAHFbw==",
+ "cpu": [
+ "loong64"
+ ],
+ "dev": true,
+ "optional": true,
+ "os": [
+ "linux"
+ ]
+ },
+ "node_modules/@rollup/rollup-linux-powerpc64le-gnu": {
+ "version": "4.34.1",
+ "resolved": "https://registry.npmjs.org/@rollup/rollup-linux-powerpc64le-gnu/-/rollup-linux-powerpc64le-gnu-4.34.1.tgz",
+ "integrity": "sha512-fwr0n6NS0pG3QxxlqVYpfiY64Fd1Dqd8Cecje4ILAV01ROMp4aEdCj5ssHjRY3UwU7RJmeWd5fi89DBqMaTawg==",
+ "cpu": [
+ "ppc64"
+ ],
+ "dev": true,
+ "optional": true,
+ "os": [
+ "linux"
+ ]
+ },
+ "node_modules/@rollup/rollup-linux-riscv64-gnu": {
+ "version": "4.34.1",
+ "resolved": "https://registry.npmjs.org/@rollup/rollup-linux-riscv64-gnu/-/rollup-linux-riscv64-gnu-4.34.1.tgz",
+ "integrity": "sha512-4uJb9qz7+Z/yUp5RPxDGGGUcoh0PnKF33QyWgEZ3X/GocpWb6Mb+skDh59FEt5d8+Skxqs9mng6Swa6B2AmQZg==",
+ "cpu": [
+ "riscv64"
+ ],
+ "dev": true,
+ "optional": true,
+ "os": [
+ "linux"
+ ]
+ },
+ "node_modules/@rollup/rollup-linux-s390x-gnu": {
+ "version": "4.34.1",
+ "resolved": "https://registry.npmjs.org/@rollup/rollup-linux-s390x-gnu/-/rollup-linux-s390x-gnu-4.34.1.tgz",
+ "integrity": "sha512-QlIo8ndocWBEnfmkYqj8vVtIUpIqJjfqKggjy7IdUncnt8BGixte1wDON7NJEvLg3Kzvqxtbo8tk+U1acYEBlw==",
+ "cpu": [
+ "s390x"
+ ],
+ "dev": true,
+ "optional": true,
+ "os": [
+ "linux"
+ ]
+ },
+ "node_modules/@rollup/rollup-linux-x64-gnu": {
+ "version": "4.34.1",
+ "resolved": "https://registry.npmjs.org/@rollup/rollup-linux-x64-gnu/-/rollup-linux-x64-gnu-4.34.1.tgz",
+ "integrity": "sha512-hzpleiKtq14GWjz3ahWvJXgU1DQC9DteiwcsY4HgqUJUGxZThlL66MotdUEK9zEo0PK/2ADeZGM9LIondE302A==",
+ "cpu": [
+ "x64"
+ ],
+ "dev": true,
+ "optional": true,
+ "os": [
+ "linux"
+ ]
+ },
+ "node_modules/@rollup/rollup-linux-x64-musl": {
+ "version": "4.34.1",
+ "resolved": "https://registry.npmjs.org/@rollup/rollup-linux-x64-musl/-/rollup-linux-x64-musl-4.34.1.tgz",
+ "integrity": "sha512-jqtKrO715hDlvUcEsPn55tZt2TEiBvBtCMkUuU0R6fO/WPT7lO9AONjPbd8II7/asSiNVQHCMn4OLGigSuxVQA==",
+ "cpu": [
+ "x64"
+ ],
+ "dev": true,
+ "optional": true,
+ "os": [
+ "linux"
+ ]
+ },
+ "node_modules/@rollup/rollup-win32-arm64-msvc": {
+ "version": "4.34.1",
+ "resolved": "https://registry.npmjs.org/@rollup/rollup-win32-arm64-msvc/-/rollup-win32-arm64-msvc-4.34.1.tgz",
+ "integrity": "sha512-RnHy7yFf2Wz8Jj1+h8klB93N0NHNHXFhNwAmiy9zJdpY7DE01VbEVtPdrK1kkILeIbHGRJjvfBDBhnxBr8kD4g==",
+ "cpu": [
+ "arm64"
+ ],
+ "dev": true,
+ "optional": true,
+ "os": [
+ "win32"
+ ]
+ },
+ "node_modules/@rollup/rollup-win32-ia32-msvc": {
+ "version": "4.34.1",
+ "resolved": "https://registry.npmjs.org/@rollup/rollup-win32-ia32-msvc/-/rollup-win32-ia32-msvc-4.34.1.tgz",
+ "integrity": "sha512-i7aT5HdiZIcd7quhzvwQ2oAuX7zPYrYfkrd1QFfs28Po/i0q6kas/oRrzGlDhAEyug+1UfUtkWdmoVlLJj5x9Q==",
+ "cpu": [
+ "ia32"
+ ],
+ "dev": true,
+ "optional": true,
+ "os": [
+ "win32"
+ ]
+ },
+ "node_modules/@rollup/rollup-win32-x64-msvc": {
+ "version": "4.34.1",
+ "resolved": "https://registry.npmjs.org/@rollup/rollup-win32-x64-msvc/-/rollup-win32-x64-msvc-4.34.1.tgz",
+ "integrity": "sha512-k3MVFD9Oq+laHkw2N2v7ILgoa9017ZMF/inTtHzyTVZjYs9cSH18sdyAf6spBAJIGwJ5UaC7et2ZH1WCdlhkMw==",
+ "cpu": [
+ "x64"
+ ],
+ "dev": true,
+ "optional": true,
+ "os": [
+ "win32"
+ ]
+ },
+ "node_modules/@types/estree": {
+ "version": "1.0.6",
+ "resolved": "https://registry.npmjs.org/@types/estree/-/estree-1.0.6.tgz",
+ "integrity": "sha512-AYnb1nQyY49te+VRAVgmzfcgjYS91mY5P0TKUDCLEM+gNnA+3T6rWITXRLYCpahpqSQbN5cE+gHpnPyXjHWxcw==",
+ "dev": true
+ },
+ "node_modules/@types/node": {
+ "version": "22.13.0",
+ "resolved": "https://registry.npmjs.org/@types/node/-/node-22.13.0.tgz",
+ "integrity": "sha512-ClIbNe36lawluuvq3+YYhnIN2CELi+6q8NpnM7PYp4hBn/TatfboPgVSm2rwKRfnV2M+Ty9GWDFI64KEe+kysA==",
+ "dev": true,
+ "dependencies": {
+ "undici-types": "~6.20.0"
+ }
+ },
+ "node_modules/@types/react": {
+ "version": "19.0.8",
+ "resolved": "https://registry.npmjs.org/@types/react/-/react-19.0.8.tgz",
+ "integrity": "sha512-9P/o1IGdfmQxrujGbIMDyYaaCykhLKc0NGCtYcECNUr9UAaDe4gwvV9bR6tvd5Br1SG0j+PBpbKr2UYY8CwqSw==",
+ "dev": true,
+ "dependencies": {
+ "csstype": "^3.0.2"
+ }
+ },
+ "node_modules/@types/react-dom": {
+ "version": "19.0.3",
+ "resolved": "https://registry.npmjs.org/@types/react-dom/-/react-dom-19.0.3.tgz",
+ "integrity": "sha512-0Knk+HJiMP/qOZgMyNFamlIjw9OFCsyC2ZbigmEEyXXixgre6IQpm/4V+r3qH4GC1JPvRJKInw+on2rV6YZLeA==",
+ "dev": true,
+ "peerDependencies": {
+ "@types/react": "^19.0.0"
+ }
+ },
+ "node_modules/@types/resolve": {
+ "version": "1.20.2",
+ "resolved": "https://registry.npmjs.org/@types/resolve/-/resolve-1.20.2.tgz",
+ "integrity": "sha512-60BCwRFOZCQhDncwQdxxeOEEkbc5dIMccYLwbxsS4TUNeVECQ/pBJ0j09mrHOl/JJvpRPGwO9SvE4nR2Nb/a4Q==",
+ "dev": true
+ },
+ "node_modules/commondir": {
+ "version": "1.0.1",
+ "resolved": "https://registry.npmjs.org/commondir/-/commondir-1.0.1.tgz",
+ "integrity": "sha512-W9pAhw0ja1Edb5GVdIF1mjZw/ASI0AlShXM83UUGe2DVr5TdAPEA1OA8m/g8zWp9x6On7gqufY+FatDbC3MDQg==",
+ "dev": true
+ },
+ "node_modules/csstype": {
+ "version": "3.1.3",
+ "resolved": "https://registry.npmjs.org/csstype/-/csstype-3.1.3.tgz",
+ "integrity": "sha512-M1uQkMl8rQK/szD0LNhtqxIPLpimGm8sOBwU7lLnCpSbTyY3yeU1Vc7l4KT5zT4s/yOxHH5O7tIuuLOCnLADRw==",
+ "dev": true
+ },
+ "node_modules/deepmerge": {
+ "version": "4.3.1",
+ "resolved": "https://registry.npmjs.org/deepmerge/-/deepmerge-4.3.1.tgz",
+ "integrity": "sha512-3sUqbMEc77XqpdNO7FRyRog+eW3ph+GYCbj+rK+uYyRMuwsVy0rMiVtPn+QJlKFvWP/1PYpapqYn0Me2knFn+A==",
+ "dev": true,
+ "engines": {
+ "node": ">=0.10.0"
+ }
+ },
+ "node_modules/estree-walker": {
+ "version": "2.0.2",
+ "resolved": "https://registry.npmjs.org/estree-walker/-/estree-walker-2.0.2.tgz",
+ "integrity": "sha512-Rfkk/Mp/DL7JVje3u18FxFujQlTNR2q6QfMSMB7AvCBx91NGj/ba3kCfza0f6dVDbw7YlRf/nDrn7pQrCCyQ/w==",
+ "dev": true
+ },
+ "node_modules/fdir": {
+ "version": "6.4.3",
+ "resolved": "https://registry.npmjs.org/fdir/-/fdir-6.4.3.tgz",
+ "integrity": "sha512-PMXmW2y1hDDfTSRc9gaXIuCCRpuoz3Kaz8cUelp3smouvfT632ozg2vrT6lJsHKKOF59YLbOGfAWGUcKEfRMQw==",
+ "dev": true,
+ "peerDependencies": {
+ "picomatch": "^3 || ^4"
+ },
+ "peerDependenciesMeta": {
+ "picomatch": {
+ "optional": true
+ }
+ }
+ },
+ "node_modules/fsevents": {
+ "version": "2.3.3",
+ "resolved": "https://registry.npmjs.org/fsevents/-/fsevents-2.3.3.tgz",
+ "integrity": "sha512-5xoDfX+fL7faATnagmWPpbFtwh/R77WmMMqqHGS65C3vvB0YHrgF+B1YmZ3441tMj5n63k0212XNoJwzlhffQw==",
+ "dev": true,
+ "hasInstallScript": true,
+ "optional": true,
+ "os": [
+ "darwin"
+ ],
+ "engines": {
+ "node": "^8.16.0 || ^10.6.0 || >=11.0.0"
+ }
+ },
+ "node_modules/function-bind": {
+ "version": "1.1.2",
+ "resolved": "https://registry.npmjs.org/function-bind/-/function-bind-1.1.2.tgz",
+ "integrity": "sha512-7XHNxH7qX9xG5mIwxkhumTox/MIRNcOgDrxWsMt2pAr23WHp6MrRlN7FBSFpCpr+oVO0F744iUgR82nJMfG2SA==",
+ "dev": true,
+ "funding": {
+ "url": "https://github.com/sponsors/ljharb"
+ }
+ },
+ "node_modules/hasown": {
+ "version": "2.0.2",
+ "resolved": "https://registry.npmjs.org/hasown/-/hasown-2.0.2.tgz",
+ "integrity": "sha512-0hJU9SCPvmMzIBdZFqNPXWa6dqh7WdH0cII9y+CyS8rG3nL48Bclra9HmKhVVUHyPWNH5Y7xDwAB7bfgSjkUMQ==",
+ "dev": true,
+ "dependencies": {
+ "function-bind": "^1.1.2"
+ },
+ "engines": {
+ "node": ">= 0.4"
+ }
+ },
+ "node_modules/is-core-module": {
+ "version": "2.16.1",
+ "resolved": "https://registry.npmjs.org/is-core-module/-/is-core-module-2.16.1.tgz",
+ "integrity": "sha512-UfoeMA6fIJ8wTYFEUjelnaGI67v6+N7qXJEvQuIGa99l4xsCruSYOVSQ0uPANn4dAzm8lkYPaKLrrijLq7x23w==",
+ "dev": true,
+ "dependencies": {
+ "hasown": "^2.0.2"
+ },
+ "engines": {
+ "node": ">= 0.4"
+ },
+ "funding": {
+ "url": "https://github.com/sponsors/ljharb"
+ }
+ },
+ "node_modules/is-module": {
+ "version": "1.0.0",
+ "resolved": "https://registry.npmjs.org/is-module/-/is-module-1.0.0.tgz",
+ "integrity": "sha512-51ypPSPCoTEIN9dy5Oy+h4pShgJmPCygKfyRCISBI+JoWT/2oJvK8QPxmwv7b/p239jXrm9M1mlQbyKJ5A152g==",
+ "dev": true
+ },
+ "node_modules/is-reference": {
+ "version": "1.2.1",
+ "resolved": "https://registry.npmjs.org/is-reference/-/is-reference-1.2.1.tgz",
+ "integrity": "sha512-U82MsXXiFIrjCK4otLT+o2NA2Cd2g5MLoOVXUZjIOhLurrRxpEXzI8O0KZHr3IjLvlAH1kTPYSuqer5T9ZVBKQ==",
+ "dev": true,
+ "dependencies": {
+ "@types/estree": "*"
+ }
+ },
+ "node_modules/magic-string": {
+ "version": "0.30.17",
+ "resolved": "https://registry.npmjs.org/magic-string/-/magic-string-0.30.17.tgz",
+ "integrity": "sha512-sNPKHvyjVf7gyjwS4xGTaW/mCnF8wnjtifKBEhxfZ7E/S8tQ0rssrwGNn6q8JH/ohItJfSQp9mBtQYuTlH5QnA==",
+ "dev": true,
+ "dependencies": {
+ "@jridgewell/sourcemap-codec": "^1.5.0"
+ }
+ },
+ "node_modules/path-parse": {
+ "version": "1.0.7",
+ "resolved": "https://registry.npmjs.org/path-parse/-/path-parse-1.0.7.tgz",
+ "integrity": "sha512-LDJzPVEEEPR+y48z93A0Ed0yXb8pAByGWo/k5YYdYgpY2/2EsOsksJrq7lOHxryrVOn1ejG6oAp8ahvOIQD8sw==",
+ "dev": true
+ },
+ "node_modules/picomatch": {
+ "version": "4.0.2",
+ "resolved": "https://registry.npmjs.org/picomatch/-/picomatch-4.0.2.tgz",
+ "integrity": "sha512-M7BAV6Rlcy5u+m6oPhAPFgJTzAioX/6B0DxyvDlo9l8+T3nLKbrczg2WLUyzd45L8RqfUMyGPzekbMvX2Ldkwg==",
+ "dev": true,
+ "engines": {
+ "node": ">=12"
+ },
+ "funding": {
+ "url": "https://github.com/sponsors/jonschlinkert"
+ }
+ },
+ "node_modules/react": {
+ "version": "19.0.0",
+ "resolved": "https://registry.npmjs.org/react/-/react-19.0.0.tgz",
+ "integrity": "sha512-V8AVnmPIICiWpGfm6GLzCR/W5FXLchHop40W4nXBmdlEceh16rCN8O8LNWm5bh5XUX91fh7KpA+W0TgMKmgTpQ==",
+ "dev": true,
+ "engines": {
+ "node": ">=0.10.0"
+ }
+ },
+ "node_modules/react-dom": {
+ "version": "19.0.0",
+ "resolved": "https://registry.npmjs.org/react-dom/-/react-dom-19.0.0.tgz",
+ "integrity": "sha512-4GV5sHFG0e/0AD4X+ySy6UJd3jVl1iNsNHdpad0qhABJ11twS3TTBnseqsKurKcsNqCEFeGL3uLpVChpIO3QfQ==",
+ "dev": true,
+ "dependencies": {
+ "scheduler": "^0.25.0"
+ },
+ "peerDependencies": {
+ "react": "^19.0.0"
+ }
+ },
+ "node_modules/resolve": {
+ "version": "1.22.10",
+ "resolved": "https://registry.npmjs.org/resolve/-/resolve-1.22.10.tgz",
+ "integrity": "sha512-NPRy+/ncIMeDlTAsuqwKIiferiawhefFJtkNSW0qZJEqMEb+qBt/77B/jGeeek+F0uOeN05CDa6HXbbIgtVX4w==",
+ "dev": true,
+ "dependencies": {
+ "is-core-module": "^2.16.0",
+ "path-parse": "^1.0.7",
+ "supports-preserve-symlinks-flag": "^1.0.0"
+ },
+ "bin": {
+ "resolve": "bin/resolve"
+ },
+ "engines": {
+ "node": ">= 0.4"
+ },
+ "funding": {
+ "url": "https://github.com/sponsors/ljharb"
+ }
+ },
+ "node_modules/rollup": {
+ "version": "4.34.1",
+ "resolved": "https://registry.npmjs.org/rollup/-/rollup-4.34.1.tgz",
+ "integrity": "sha512-iYZ/+PcdLYSGfH3S+dGahlW/RWmsqDhLgj1BT9DH/xXJ0ggZN7xkdP9wipPNjjNLczI+fmMLmTB9pye+d2r4GQ==",
+ "dev": true,
+ "dependencies": {
+ "@types/estree": "1.0.6"
+ },
+ "bin": {
+ "rollup": "dist/bin/rollup"
+ },
+ "engines": {
+ "node": ">=18.0.0",
+ "npm": ">=8.0.0"
+ },
+ "optionalDependencies": {
+ "@rollup/rollup-android-arm-eabi": "4.34.1",
+ "@rollup/rollup-android-arm64": "4.34.1",
+ "@rollup/rollup-darwin-arm64": "4.34.1",
+ "@rollup/rollup-darwin-x64": "4.34.1",
+ "@rollup/rollup-freebsd-arm64": "4.34.1",
+ "@rollup/rollup-freebsd-x64": "4.34.1",
+ "@rollup/rollup-linux-arm-gnueabihf": "4.34.1",
+ "@rollup/rollup-linux-arm-musleabihf": "4.34.1",
+ "@rollup/rollup-linux-arm64-gnu": "4.34.1",
+ "@rollup/rollup-linux-arm64-musl": "4.34.1",
+ "@rollup/rollup-linux-loongarch64-gnu": "4.34.1",
+ "@rollup/rollup-linux-powerpc64le-gnu": "4.34.1",
+ "@rollup/rollup-linux-riscv64-gnu": "4.34.1",
+ "@rollup/rollup-linux-s390x-gnu": "4.34.1",
+ "@rollup/rollup-linux-x64-gnu": "4.34.1",
+ "@rollup/rollup-linux-x64-musl": "4.34.1",
+ "@rollup/rollup-win32-arm64-msvc": "4.34.1",
+ "@rollup/rollup-win32-ia32-msvc": "4.34.1",
+ "@rollup/rollup-win32-x64-msvc": "4.34.1",
+ "fsevents": "~2.3.2"
+ }
+ },
+ "node_modules/rollup-plugin-peer-deps-external": {
+ "version": "2.2.4",
+ "resolved": "https://registry.npmjs.org/rollup-plugin-peer-deps-external/-/rollup-plugin-peer-deps-external-2.2.4.tgz",
+ "integrity": "sha512-AWdukIM1+k5JDdAqV/Cxd+nejvno2FVLVeZ74NKggm3Q5s9cbbcOgUPGdbxPi4BXu7xGaZ8HG12F+thImYu/0g==",
+ "dev": true,
+ "peerDependencies": {
+ "rollup": "*"
+ }
+ },
+ "node_modules/scheduler": {
+ "version": "0.25.0",
+ "resolved": "https://registry.npmjs.org/scheduler/-/scheduler-0.25.0.tgz",
+ "integrity": "sha512-xFVuu11jh+xcO7JOAGJNOXld8/TcEHK/4CituBUeUb5hqxJLj9YuemAEuvm9gQ/+pgXYfbQuqAkiYu+u7YEsNA==",
+ "dev": true
+ },
+ "node_modules/supports-preserve-symlinks-flag": {
+ "version": "1.0.0",
+ "resolved": "https://registry.npmjs.org/supports-preserve-symlinks-flag/-/supports-preserve-symlinks-flag-1.0.0.tgz",
+ "integrity": "sha512-ot0WnXS9fgdkgIcePe6RHNk1WA8+muPa6cSjeR3V8K27q9BB1rTE3R1p7Hv0z1ZyAc8s6Vvv8DIyWf681MAt0w==",
+ "dev": true,
+ "engines": {
+ "node": ">= 0.4"
+ },
+ "funding": {
+ "url": "https://github.com/sponsors/ljharb"
+ }
+ },
+ "node_modules/tslib": {
+ "version": "2.8.1",
+ "resolved": "https://registry.npmjs.org/tslib/-/tslib-2.8.1.tgz",
+ "integrity": "sha512-oJFu94HQb+KVduSUQL7wnpmqnfmLsOA/nAh6b6EH0wCEoK0/mPeXU6c3wKDV83MkOuHPRHtSXKKU99IBazS/2w=="
+ },
+ "node_modules/typescript": {
+ "version": "5.7.3",
+ "resolved": "https://registry.npmjs.org/typescript/-/typescript-5.7.3.tgz",
+ "integrity": "sha512-84MVSjMEHP+FQRPy3pX9sTVV/INIex71s9TL2Gm5FG/WG1SqXeKyZ0k7/blY/4FdOzI12CBy1vGc4og/eus0fw==",
+ "dev": true,
+ "bin": {
+ "tsc": "bin/tsc",
+ "tsserver": "bin/tsserver"
+ },
+ "engines": {
+ "node": ">=14.17"
+ }
+ },
+ "node_modules/undici-types": {
+ "version": "6.20.0",
+ "resolved": "https://registry.npmjs.org/undici-types/-/undici-types-6.20.0.tgz",
+ "integrity": "sha512-Ny6QZ2Nju20vw1SRHe3d9jVu6gJ+4e3+MMpqu7pqE5HT6WsTSlce++GQmK5UXS8mzV8DSYHrQH+Xrf2jVcuKNg==",
+ "dev": true
+ }
+ }
+}
diff --git a/packages/image/package.json b/packages/image/package.json
new file mode 100644
index 0000000..f0c8710
--- /dev/null
+++ b/packages/image/package.json
@@ -0,0 +1,41 @@
+{
+ "name": "@minima-global/image",
+ "version": "1.5.2",
+ "type": "module",
+ "main": "dist/index.js",
+ "module": "dist/index.esm.js",
+ "types": "dist/index.d.ts",
+ "files": [
+ "dist"
+ ],
+ "publishConfig": {
+ "access": "public"
+ },
+ "scripts": {
+ "build": "rollup -c",
+ "prepublishOnly": "npm run build",
+ "version-pkg": "changeset version",
+ "publish-pkg": "changeset publish --access restricted"
+ },
+ "author": "",
+ "license": "ISC",
+ "peerDependencies": {
+ "react": ">=17.0.0"
+ },
+ "devDependencies": {
+ "@rollup/plugin-commonjs": "^28.0.2",
+ "@rollup/plugin-node-resolve": "^16.0.0",
+ "@rollup/plugin-typescript": "^12.1.2",
+ "@types/node": "^22.13.0",
+ "@types/react": "^19.0.8",
+ "@types/react-dom": "^19.0.3",
+ "react": "^19.0.0",
+ "react-dom": "^19.0.0",
+ "rollup": "^4.34.1",
+ "rollup-plugin-peer-deps-external": "^2.2.4",
+ "typescript": "^5.7.3"
+ },
+ "dependencies": {
+ "tslib": "^2.8.1"
+ }
+}
diff --git a/packages/image/rollup.config.js b/packages/image/rollup.config.js
new file mode 100644
index 0000000..16a89ff
--- /dev/null
+++ b/packages/image/rollup.config.js
@@ -0,0 +1,24 @@
+import resolve from "@rollup/plugin-node-resolve"
+import commonjs from "@rollup/plugin-commonjs"
+import typescript from "@rollup/plugin-typescript"
+import peerDepsExternal from "rollup-plugin-peer-deps-external"
+export default {
+ input: "src/index.ts",
+ output: [
+ {
+ file: "dist/index.js",
+ format: "cjs",
+ sourcemap: true,
+ },
+ {
+ file: "dist/index.esm.js",
+ format: "esm",
+ sourcemap: true,
+ },
+ ],
+ plugins: [
+ peerDepsExternal(),
+ resolve(),
+ commonjs(),
+ typescript({ tsconfig: "./tsconfig.json" })],
+}
diff --git a/packages/image/src/components/Image.tsx b/packages/image/src/components/Image.tsx
new file mode 100644
index 0000000..4addf8a
--- /dev/null
+++ b/packages/image/src/components/Image.tsx
@@ -0,0 +1,118 @@
+import * as React from "react"
+import { useEffect, useState } from "react"
+import { DEFAULT_BASE64_IMAGE, fetchIPFSImage, getBase64Image, isBase64Image, isIPFSOrIPNS, isValidUrl } from "../utils"
+
+interface ImageProps {
+ src: string
+ alt?: string
+ className?: string
+}
+
+type ImageType = "on-chain" | "external_url" | "ipfs" | "default"
+
+const Image: React.FC = ({ src, className = "", alt = "Custom token image" }) => {
+ const [imageData, setImageData] = useState(DEFAULT_BASE64_IMAGE)
+ const [imageType, setImageType] = useState("default")
+ const [isLoading, setIsLoading] = useState(true)
+
+ useEffect(() => {
+ const processImageSource = async () => {
+ setIsLoading(true)
+ if (isIPFSOrIPNS(src)) {
+ try {
+ const ipfsUrl = await fetchIPFSImage(src)
+ setImageType("ipfs")
+ setImageData(ipfsUrl)
+ } catch (error) {
+ console.error("Failed to fetch IPFS image:", error)
+ setImageType("default")
+ setImageData(DEFAULT_BASE64_IMAGE)
+ }
+ } else if (isValidUrl(src)) {
+ setImageType("external_url")
+ setImageData(src)
+ } else if (isBase64Image(src)) {
+ const completeBase64Image = getBase64Image(src)
+ setImageType("on-chain")
+ setImageData(completeBase64Image)
+ } else {
+ setImageType("default")
+ setImageData(DEFAULT_BASE64_IMAGE)
+ }
+ setIsLoading(false)
+ }
+
+ processImageSource()
+ }, [src])
+
+ const getWrapperStyle = (): React.CSSProperties => {
+ const baseStyle: React.CSSProperties = {
+ position: "relative",
+ overflow: "hidden",
+ }
+
+ switch (imageType) {
+ case "on-chain":
+ return { ...baseStyle, width: "50px", height: "50px" }
+ case "external_url":
+ case "ipfs":
+ return { ...baseStyle, width: "100%", maxWidth: "300px", height: "300px" }
+ default:
+ return { ...baseStyle, width: "100px", height: "100px" }
+ }
+ }
+
+ const loadingStyle: React.CSSProperties = {
+ position: "absolute",
+ top: 0,
+ left: 0,
+ width: "100%",
+ height: "100%",
+ display: "flex",
+ alignItems: "center",
+ justifyContent: "center",
+ backgroundColor: "#f3f4f6",
+ animation: "pulse 1.5s infinite ease-in-out",
+ }
+
+ const imageStyle: React.CSSProperties = {
+ width: "100%",
+ height: "100%",
+ objectFit: "contain",
+ padding: imageType === "on-chain" ? "4px" : "0",
+ }
+
+ return (
+
+ {isLoading ? (
+
+ ) : (
+

+ )}
+
+
+ )
+}
+
+export default Image
+
diff --git a/packages/image/src/components/index.ts b/packages/image/src/components/index.ts
new file mode 100644
index 0000000..4ca8649
--- /dev/null
+++ b/packages/image/src/components/index.ts
@@ -0,0 +1 @@
+export { default as Image } from './Image';
diff --git a/packages/image/src/index.ts b/packages/image/src/index.ts
new file mode 100644
index 0000000..590a7f4
--- /dev/null
+++ b/packages/image/src/index.ts
@@ -0,0 +1,2 @@
+export * from './components';
+export * from './utils';
diff --git a/packages/image/src/utils/handbook.md b/packages/image/src/utils/handbook.md
new file mode 100644
index 0000000..8dbc5f1
--- /dev/null
+++ b/packages/image/src/utils/handbook.md
@@ -0,0 +1,77 @@
+# Image Utility Functions
+
+This document outlines the various utility functions available for handling images in our application. These functions are particularly useful for processing, compressing, and fetching images from various sources, including base64 encodings and IPFS/IPNS.
+
+## Constants
+
+### DEFAULT_BASE64_IMAGE
+A base64 encoded SVG image used as a default "Image Not Found" placeholder.
+
+## Functions
+
+### fileToBase64(file: File): Promise
+Converts a File object to a base64 string.
+- **Input**: A File object
+- **Output**: A Promise that resolves to a base64 encoded string
+
+### compressImage(base64Image: string, quality = 0.7): Promise
+Compresses a base64 image, resizing if necessary to 50k.
+- **Input**:
+ - base64Image: A base64 encoded image string
+ - quality: Compression quality (default: 0.7)
+- **Output**: A Promise that resolves to a compressed base64 encoded image string
+
+### isBase64Image(url: string): string | false
+Checks if a string is a base64 encoded image.
+- **Input**: A string that might be a base64 encoded image
+- **Output**: The cleaned base64 string if valid, false otherwise
+
+### getMimeType(base64String: string): string
+Determines the MIME type of a base64 encoded image.
+- **Input**: A base64 encoded image string
+- **Output**: The MIME type of the image
+
+### getBase64Image(imageData: string, defaultImage?: string): string
+Processes base64 image data and returns a complete data URL.
+- **Input**:
+ - imageData: Base64 encoded image data, potentially wrapped in tags
+ - defaultImage: Optional default image to return if processing fails
+- **Output**: A complete data URL for the image
+
+### isIPFSOrIPNS(uri: string): boolean
+Checks if a string is an IPFS/IPNS URI or gateway URL.
+- **Input**: A string to check
+- **Output**: Boolean indicating if the string is an IPFS/IPNS URI or gateway URL
+
+### fetchIPFSImage(uri: string, defaultImage?: string): Promise
+Fetches an image from IPFS/IPNS using multiple gateways.
+- **Input**:
+ - uri: The IPFS/IPNS URI or gateway URL
+ - defaultImage: Optional default image to return if fetching fails
+- **Output**: A Promise that resolves to the fetched image URL or the default image
+
+### isValidUrl(url: string): boolean
+Checks if a string is a valid URL.
+- **Input**: A string to check
+- **Output**: Boolean indicating if the string is a valid URL
+
+## Usage
+
+These functions can be imported and used in your application to handle various image-related tasks, such as uploading, displaying, and fetching images from decentralized storage systems.
+
+Example:
+
+```typescript
+import { compressImage, isIPFSOrIPNS, fetchIPFSImage } from './image-utils';
+
+// Compress an image before uploading
+const compressedImage = await compressImage(base64Image, 0.8);
+
+// Check if a URI is IPFS/IPNS
+const isIPFS = isIPFSOrIPNS('ipfs://QmX...');
+
+// Fetch an image from IPFS
+const imageUrl = await fetchIPFSImage('ipfs://QmX...');
+```
+
+Remember to handle errors and edge cases when using these functions in your application.
diff --git a/packages/image/src/utils/index.ts b/packages/image/src/utils/index.ts
new file mode 100644
index 0000000..90e6835
--- /dev/null
+++ b/packages/image/src/utils/index.ts
@@ -0,0 +1,297 @@
+/**
+ * Helper functions to help with compressing on-chain uploaded images
+ */
+
+/**
+ * Default base64 encoded image (a simple 1x1 pixel transparent PNG)
+ */
+export const DEFAULT_BASE64_IMAGE =
+ "data:image/svg+xml;base64,Cjxzdmcgd2lkdGg9IjIwMCIgaGVpZ2h0PSIyMDAiIHZpZXdCb3g9IjAgMCAyMDAgMjAwIiB4bWxucz0iaHR0cDovL3d3dy53My5vcmcvMjAwMC9zdmciPgogIDxyZWN0IHdpZHRoPSIyMDAiIGhlaWdodD0iMjAwIiBmaWxsPSIjZjBmMGYwIi8+CiAgPHBhdGggZD0iTTQwIDYwaDEyMHY4MEg0MFY2MHoiIGZpbGw9Im5vbmUiIHN0cm9rZT0iI2NjYyIgc3Ryb2tlLXdpZHRoPSIyIi8+CiAgPHBhdGggZD0iTTYwIDEwMGwyMC0yMGwyMCAyMGwyMC0yMGwyMCAyMCIgc3Ryb2tlPSIjY2NjIiBzdHJva2Utd2lkdGg9IjIiIGZpbGw9Im5vbmUiLz4KICA8Y2lyY2xlIGN4PSIxNDAiIGN5PSI4MCIgcj0iMTAiIGZpbGw9Im5vbmUiIHN0cm9rZT0iI2NjYyIgc3Ryb2tlLXdpZHRoPSIyIi8+CiAgPHRleHQgeD0iMTAwIiB5PSIxNTAiIGZvbnQtZmFtaWx5PSJzYW5zLXNlcmlmIiBmb250LXNpemU9IjE0IiB0ZXh0LWFuY2hvcj0ibWlkZGxlIiBmaWxsPSIjOTk5Ij5JbWFnZSBOb3QgRm91bmQ8L3RleHQ+Cjwvc3ZnPg=="
+
+/**
+ * Add an image file, it should give you back a base64 data type representation
+ * @param file
+ */
+export const fileToBase64 = (file: File): Promise => {
+ return new Promise((resolve) => {
+ const fileRead = new FileReader();
+ fileRead.readAsDataURL(file);
+ fileRead.onload = async () => {
+ const base64 = fileRead.result as string;
+ resolve(base64);
+ };
+
+ fileRead.onerror = () => {
+ resolve(
+ "data:image/svg+xml;base64,PD94bWwgdmVyc2lvbj0iMS4wIiBlbmNvZGluZz0idXRmLTgiPz4KDTwhLS0gVXBsb2FkZWQgdG86IFNWRyBSZXBvLCB3d3cuc3ZncmVwby5jb20sIEdlbmVyYXRvcjogU1ZHIFJlcG8gTWl4ZXIgVG9vbHMgLS0+Cjxzdmcgd2lkdGg9IjgwMHB4IiBoZWlnaHQ9IjgwMHB4IiB2aWV3Qm94PSIwIDAgMTYgMTYiIHhtbG5zPSJodHRwOi8vd3d3LnczLm9yZy8yMDAwL3N2ZyI+DQogICAgPGcgY29sb3I9IiMwMDAwMDAiIGZvbnQtd2VpZ2h0PSI0MDAiIGZvbnQtZmFtaWx5PSJVYnVudHUiIGxldHRlci1zcGFjaW5nPSIwIiB3b3JkLXNwYWNpbmc9IjAiIHdoaXRlLXNwYWNlPSJub3JtYWwiIGZpbGw9ImdyYXkiPg0KICAgICAgICA8cGF0aCBkPSJNOCAyYTIuODQgMi44NCAwIDAgMC0xLjEyLjIyMWMtLjM0NS4xNDEtLjY1MS4zNDgtLjkwNi42MTV2LjAwM2wtLjAwMS4wMDJjLS4yNDguMjY5LS40NC41OTItLjU3NC45Ni0uMTM3LjM2Ny0uMjAzLjc2OS0uMjAzIDEuMiAwIC40MzUuMDY1Ljg0MS4yMDMgMS4yMDkuMTM1LjM2MS4zMjcuNjguNTc0Ljk1bC4wMDEuMDAyYy4yNTQuMjY3LjU1OC40NzcuOTAxLjYyNHYuMDAzYy4zNDYuMTQxLjcyMy4yMSAxLjEyLjIxLjM5NSAwIC43Ny0uMDY5IDEuMTE3LS4yMXYtLjAwMmMuMzQzLS4xNDcuNjQ0LS4zNTcuODkyLS42MjUuMjU1LS4yNjguNDUtLjU5LjU4Ni0uOTUyLjEzOC0uMzY4LjIwNC0uNzc0LjIwNC0xLjIxaC4wMWMwLS40My0uMDY1LS44MzEtLjIwMy0xLjE5OGEyLjc3MSAyLjc3MSAwIDAgMC0uNTg1LS45NjMgMi41IDIuNSAwIDAgMC0uODk3LS42MThBMi44MzUgMi44MzUgMCAwIDAgNy45OTkgMnpNOC4wMjQgMTAuMDAyYy0yLjMxNyAwLTMuNTYxLjIxMy00LjQ4Ni45MS0uNDYyLjM1LS43NjcuODI1LS45MzkgMS4zNzgtLjE3Mi41NTMtLjIyNi45NzUtLjIyOCAxLjcxTDggMTQuMDAyaDUuNjI5Yy0uMDAxLS43MzYtLjA1Mi0xLjE1OS0uMjI1LTEuNzEyLS4xNzItLjU1My0uNDc3LTEuMDI3LS45NC0xLjM3Ni0uOTIzLS42OTctMi4xMjQtLjkxMi00LjQ0LS45MTJ6IiBzdHlsZT0ibGluZS1oZWlnaHQ6MTI1JTstaW5rc2NhcGUtZm9udC1zcGVjaWZpY2F0aW9uOidVYnVudHUsIE5vcm1hbCc7Zm9udC12YXJpYW50LWxpZ2F0dXJlczpub3JtYWw7Zm9udC12YXJpYW50LXBvc2l0aW9uOm5vcm1hbDtmb250LXZhcmlhbnQtY2Fwczpub3JtYWw7Zm9udC12YXJpYW50LW51bWVyaWM6bm9ybWFsO2ZvbnQtdmFyaWFudC1hbHRlcm5hdGVzOm5vcm1hbDtmb250LWZlYXR1cmUtc2V0dGluZ3M6bm9ybWFsO3RleHQtaW5kZW50OjA7dGV4dC1hbGlnbjpzdGFydDt0ZXh0LWRlY29yYXRpb24tbGluZTpub25lO3RleHQtZGVjb3JhdGlvbi1zdHlsZTpzb2xpZDt0ZXh0LWRlY29yYXRpb24tY29sb3I6IzAwMDAwMDt0ZXh0LXRyYW5zZm9ybTpub25lO3RleHQtb3JpZW50YXRpb246bWl4ZWQ7c2hhcGUtcGFkZGluZzowO2lzb2xhdGlvbjphdXRvO21peC1ibGVuZC1tb2RlOm5vcm1hbCIgb3ZlcmZsb3c9InZpc2libGUiLz4NCiAgICA8L2c+DQo8L3N2Zz4=",
+ );
+ };
+ });
+};
+
+
+/**
+ * Add a base64 representation of an image, it should give you back a compressed image ready for on-chain upload
+ * @param base64Image
+ * @param quality https://developer.mozilla.org/en-US/docs/Web/API/HTMLCanvasElement/toDataURL#quality
+ */
+export const compressImage = async (base64Image: string, quality = 0.7): Promise => {
+ // Extract the MIME type from the base64 string
+ const mimeType = base64Image.substring(base64Image.indexOf(":") + 1, base64Image.indexOf(";"));
+
+ return new Promise((resolve) => {
+ const img = new Image();
+ img.src = base64Image;
+
+ img.onload = () => {
+ const canvas = document.createElement('canvas');
+ const ctx = canvas.getContext('2d') as CanvasRenderingContext2D;
+
+ const MAX_IMAGE_SIZE = 50; // for that 50K HD resolution
+
+ let imageWidth = img.width;
+ let imageHeight = img.height;
+
+ if (imageWidth > MAX_IMAGE_SIZE || imageHeight > MAX_IMAGE_SIZE) {
+ // Calculate the aspect ratio
+ const aspectRatio = imageWidth / imageHeight;
+
+ // Calculate the new width and height
+ if (imageWidth > imageHeight) {
+ imageWidth = MAX_IMAGE_SIZE;
+ imageHeight = MAX_IMAGE_SIZE / aspectRatio;
+ } else {
+ imageHeight = MAX_IMAGE_SIZE;
+ imageWidth = MAX_IMAGE_SIZE * aspectRatio;
+ }
+ }
+
+ canvas.width = imageWidth;
+ canvas.height = imageHeight;
+
+ ctx.drawImage(img, 0, 0, imageWidth, imageHeight);
+
+ const compressedImage = canvas.toDataURL(mimeType, quality);
+
+ // Extract the base64 content (the part after the comma)
+ const pureCompressedImage = compressedImage.slice(compressedImage.indexOf(',') + 1);
+
+ // Create an XML structure to hold the base64 image data
+ const xmlString = '';
+ const parser = new DOMParser();
+ const xmlDoc = parser.parseFromString(xmlString, 'text/xml');
+
+ // Insert the base64 image content into the XML structure
+ xmlDoc.firstElementChild!.innerHTML = pureCompressedImage;
+
+ // Serialize the XML back into a string
+ const serializer = new XMLSerializer();
+ const serializedXML = serializer.serializeToString(xmlDoc);
+
+ // Resolve the promise with the serialized XML
+ resolve(serializedXML);
+ };
+
+ img.onerror = () => {
+ // In case of error, resolve with the original image URL as a fallback
+ resolve(base64Image);
+ };
+ });
+};
+
+/**
+ *
+ * @param url - custom token url
+ */
+export const isBase64Image = (url: string): string | false => {
+ // Remove tags if present
+ const cleanedUrl = url.replace(/(.*?)<\/artimage>/, "$1").trim()
+
+ // Check if the cleaned string is base64 encoded
+ try {
+ if (btoa(atob(cleanedUrl)) === cleanedUrl) {
+ return cleanedUrl;
+ }
+ } catch (err) {
+ return false
+ }
+
+ return false
+}
+
+/**
+ * Determines the MIME type of a base64 encoded image.
+ * @param base64String The base64 encoded image data.
+ * @returns The MIME type of the image.
+ */
+function getMimeType(base64String: string): string {
+ try {
+ // Decode the first few bytes of the base64 data
+ const decodedData = atob(base64String.substring(0, 8));
+ const uint8Array = new Uint8Array(decodedData.length);
+ for (let i = 0; i < decodedData.length; i++) {
+ uint8Array[i] = decodedData.charCodeAt(i);
+ }
+
+ // Determine the image type based on the magic numbers
+ if (uint8Array[0] === 0xff && uint8Array[1] === 0xd8 && uint8Array[2] === 0xff) {
+ return "image/jpeg";
+ } else if (uint8Array[0] === 0x89 && uint8Array[1] === 0x50 && uint8Array[2] === 0x4e && uint8Array[3] === 0x47) {
+ return "image/png";
+ } else if (uint8Array[0] === 0x47 && uint8Array[1] === 0x49 && uint8Array[2] === 0x46) {
+ return "image/gif";
+ } else if (uint8Array[0] === 0x42 && uint8Array[1] === 0x4d) {
+ return "image/bmp";
+ }
+
+ // Check for SVG (look for XML declaration or SVG tag)
+ const svgCheck = atob(base64String.substring(0, 32));
+ if (svgCheck.includes('