Get started
Pick the way in
The core is one file with no dependencies. Everything else (components, server, API) wraps the same call: shapeaddress(input, options) returns an SVG string.
In the browser, with a script tag
Load the version-pinned build from jsDelivr. It defines one global, shapeaddress. Put the returned SVG into any element, or use toDataUri for an <img>.
<script src="https://cdn.jsdelivr.net/npm/@shapeaddress/core@0.3.0/dist/shapeaddress.min.js"></script>
<div id="avatar"></div>
<img id="small" width="24" alt="">
<script>
var address = '0xd8dA6BF26964aF9D7eEd9e03E53415D37aA96045';
document.getElementById('avatar').innerHTML = shapeaddress(address, { size: 64 });
document.getElementById('small').src = shapeaddress.toDataUri(address, { size: 24 });
</script>The same calls, running on this page against the same build:
The same file is also served from the tagged source tree, as a fallback if the npm CDN path is unavailable:
https://cdn.jsdelivr.net/gh/ShapeAddress/shapeaddress@v0.3.0/packages/core/dist/shapeaddress.min.js
npm and ESM
npm i @shapeaddress/core
import shapeaddress from '@shapeaddress/core';
const address = '0xd8dA6BF26964aF9D7eEd9e03E53415D37aA96045';
// inline SVG: styleable, selectable, decodable from the DOM
document.querySelector('#avatar').innerHTML = shapeaddress(address, { size: 48 });
// list-safe: one data URI per <img>, no DOM parsing per row
img.src = shapeaddress.toDataUri(address, { size: 48 });
// read it back from any ShapeAddress SVG
shapeaddress.decode(svg).hex; // 'd8da6bf26964af9d7eed9e03e53415d37aa96045'
shapeaddress.legend(address)[0]; // 'purple flower'The package ships exports for import (ESM) and require (CommonJS), and index.d.ts for TypeScript. The default dictionary sa330 is built in. The optional fa330 dictionary is a separate import so it costs nothing unless used:
import shapeaddress from '@shapeaddress/core';
import fa330 from '@shapeaddress/core/dicts/fa330';
shapeaddress.dictionaries.register(fa330);
shapeaddress(address, { dictionary: 'fa330', size: 64 });Node.js
No DOM, no canvas, no native module. The same file renders on a server, in a worker or at the edge.
const shapeaddress = require('@shapeaddress/core');
const fs = require('node:fs');
const address = '0xd8dA6BF26964aF9D7eEd9e03E53415D37aA96045';
fs.writeFileSync('vitalik.svg', shapeaddress(address, { size: 256, title: 'vitalik.eth' }));
// decode any ShapeAddress SVG, from a file or a response body
const back = shapeaddress.decode(fs.readFileSync('vitalik.svg', 'utf8'));
back.hex; // 'd8da6bf26964af9d7eed9e03e53415d37aa96045'
back.dictionary; // 'sa330'
// cells as data, for your own renderer or a JSON API
shapeaddress.cells(address).cells[0];
// { i: 0, byte: 216, shape: 24, color: 6, shapeName: 'flower', colorName: 'purple', hex: '#7048e8' }An HTTP route is one line. The HTTP API package is this, plus validation and cache headers.
// Next.js route handler, edge runtime
import shapeaddress from '@shapeaddress/core';
export const runtime = 'edge';
export function GET(request, { params }) {
return new Response(shapeaddress(params.address, { size: 128 }), {
headers: { 'content-type': 'image/svg+xml', 'cache-control': 'public, max-age=31536000, immutable' },
});
}React
npm i @shapeaddress/react @shapeaddress/core
import { ShapeAddress } from '@shapeaddress/react';
// default: an <img src="data:…">, safe in long lists
<ShapeAddress address="0xd8dA6BF26964aF9D7eEd9e03E53415D37aA96045" size={40} />
// inline SVG when you want to style or animate the cells
<ShapeAddress address="0xd8dA6BF26964aF9D7eEd9e03E53415D37aA96045" layout="coin" size={96} inline />Props are the core options, plus address (or text) and inline. The render is memoised on address and options. For RainbowKit, pass the adapter as the avatar:
import { rainbowKitAvatar } from '@shapeaddress/react';
<RainbowKitProvider avatar={rainbowKitAvatar}>
{children}
</RainbowKitProvider>For custom markup, useShapeAddress(address, options) returns the memoised render for you to place.
Web component
One tag, no framework. Attributes are the option names; cell-style stands in for style, which HTML already owns.
<script src="https://cdn.jsdelivr.net/npm/@shapeaddress/core@0.3.0/dist/shapeaddress.min.js"></script> <script src="https://cdn.jsdelivr.net/npm/@shapeaddress/element@0.3.0/src/shape-address.js"></script> <shape-address address="0xd8dA6BF26964aF9D7eEd9e03E53415D37aA96045" size="48"></shape-address> <shape-address address="0xd8dA6BF26964aF9D7eEd9e03E53415D37aA96045" layout="coin" size="48" cell-style="glyph"></shape-address>
What those two tags render:
Changing an attribute re-renders. The element renders into a shadow root and never reads its own content as input, so markup inside the tag cannot change the picture. element.decode() returns the bytes of what is on screen.
HTTP API
For a page with no build step, or a language with no port yet. Responses are immutable for a year with an ETag, CORS is open, nothing is logged per user.
<img src="https://shapeaddress.com/v1/0xd8dA6BF26964aF9D7eEd9e03E53415D37aA96045.svg?size=64" width="64" alt="">
curl https://shapeaddress.com/v1/0xd8dA6BF26964aF9D7eEd9e03E53415D37aA96045.json # cells with names and hex curl https://shapeaddress.com/v1/0xd8dA6BF26964aF9D7eEd9e03E53415D37aA96045.txt # the spoken legend, comma separated curl https://shapeaddress.com/v1/dictionaries # what the server can draw
Routes, parameters, headers and self-hosting are on the HTTP API page. A wallet should bundle the 5 kB library rather than tell a server which addresses its users look at.
Other languages
The format is small enough to port in an afternoon. Read the format spec, then make the port reproduce the test vectors: for every vector, the same hex and the same legend, and a successful decode of the reference SVG.
shape = b mod m # index into the dictionary's glyphs (m = 32 for sa330) colour = (b - shape) / m # index into its colours (n = 8) b = colour * m + shape
# Python: byte to (colour index, glyph index) for the sa330 dictionary
def cells(address, m=32):
data = bytes.fromhex(address.removeprefix('0x'))
return [(b // m, b % m) for b in data]
cells('0xd8dA6BF26964aF9D7eEd9e03E53415D37aA96045')[0] # (6, 24): purple flowerColour and glyph names, and the glyph artwork, are the dictionary files in packages/core/dicts/. A port that renders must also write data-dict, data-sa and the per-cell data-shape and data-color attributes, so every reader can decode its output.