From 442a463ab50c96a4c1c419d6a04be88f248a0a9b Mon Sep 17 00:00:00 2001 From: friedger Date: Tue, 14 Apr 2026 16:32:18 +0200 Subject: [PATCH 1/4] feat: add sip for identicons for contracts --- .../sip-xxx-identicon-for-contracts.md | 138 ++++++++++++++++++ 1 file changed, 138 insertions(+) create mode 100644 sips/sip-xxx/sip-xxx-identicon-for-contracts.md diff --git a/sips/sip-xxx/sip-xxx-identicon-for-contracts.md b/sips/sip-xxx/sip-xxx-identicon-for-contracts.md new file mode 100644 index 00000000..2fa7fb2c --- /dev/null +++ b/sips/sip-xxx/sip-xxx-identicon-for-contracts.md @@ -0,0 +1,138 @@ +# Preamble + +SIP Number: XXX + +Title: Identicons for Contracts + +Author(s): + +- Friedger Müffke + +Status: Draft + +Consideration: Technical + +Type: Standard + +Layer: Traits + +Created: 2026-04-14 + +License: CC0-1.0 + +Sign-off: + +Discussions-To: + +- https://forum.stacks.org/t/identicon-for-contracts/18637 + +# Abstract + +This SIP specifies a deterministic way to derive a visual identifier (an "identicon") for any Clarity smart contract deployed to the Stacks blockchain. The identicon is a pure function of the contract's source code after canonicalization: two deployments of the same source — on any address, at any height, on any network — render the same icon. Wallets, explorers, and apps that implement the specification allow users to recognize known code at a glance and to notice when contracts that share a name or author address do not share code. + +The specification pins three things: the canonical form of the source (the output of `clarinet fmt` with default settings), the hash function (`SHA-512/256` over the UTF-8 bytes of the canonical form), and the rendering library and configuration (`minidenticons`, default options, seeded with the lowercase hex-encoded hash). The output is an SVG suitable for inline rendering. + +# Copyright + +This SIP is made available under the terms of the Creative Commons CC0 1.0 Universal license, available at https://creativecommons.org/publicdomain/zero/1.0/. + +# Introduction + +Clarity contracts are addressed by `{deployer}.{name}`. That pair is useful for on-chain references but is a poor user-facing identifier: a user reading `SP2J….transfer-helper` cannot tell from the name alone whether this is the audited, widely-deployed helper they have used before, a fork with a subtle change, or a phishing lookalike deployed to a confusable address. + +Hash-based identicons are a well-understood solution to this problem (see `identicon`, `jdenticon`, `blockies`, the GitHub default avatar). What is missing on Stacks is a consensus on *which* bytes get hashed and *which* renderer is used, so that the same contract produces the same icon everywhere it is shown. + +This SIP proposes that consensus. It does not propose any consensus change to the Stacks blockchain — the specification is an off-chain convention followed by wallets, explorers, and apps. + +# Specification + +## 1. Canonical source + +The canonical form of a contract's source code is the byte-for-byte output of `clarinet fmt` invoked with default settings, against the contract's `.clar` source file. + +- Formatters MUST normalize line endings to `\n` (LF). +- Formatters MUST produce a final trailing `\n`. +- Comments (`;;` and `;;;`) are part of the source and are preserved by `clarinet fmt`. They are hashed. +- The canonical form is UTF-8 encoded. + +Implementations that do not have access to a Clarinet formatter (for example, browser-only apps fetching source from `/v2/contracts/source`) SHOULD hash the source as returned by the node, assuming the contract author deployed `clarinet fmt`-formatted source. Implementations MAY display a "source not canonicalized" warning next to the identicon when the returned source deviates from a heuristic check (trailing whitespace, mixed line endings, tabs mixed with spaces). + +Contract authors who wish to participate in the identicon convention MUST run `clarinet fmt` before deploying. Two authors deploying identical logic with different whitespace will produce different identicons. + +## 2. Hash + +The identicon hash is: + +``` +identicon_hash = SHA-512/256(utf8_bytes(canonical_source)) +``` + +- `SHA-512/256` is the SHA-512 algorithm with the 512/256 initialization vector, truncated to 256 bits (FIPS 180-4 §5.3.6). +- The output is a 32-byte buffer. +- When passed to the renderer, the hash is lowercase hex-encoded without a `0x` prefix (64 characters). + +This is the same hash exposed in Clarity as the `sha512/256` function, so a contract MAY compute and expose its own identicon hash on-chain: + +```clarity +(define-read-only (get-identicon-hash (source (buff 65535))) + (sha512/256 source)) +``` + +Off-chain implementations MUST NOT rely on any on-chain value; the hash is always derived from the deployed source. + +## 3. Rendering + +The identicon is rendered with the **`minidenticons`** library, used in its default configuration: + +- Library: `minidenticons` (https://github.com/laurentpayot/minidenticons), version 4.x or later. +- Function: `minidenticonSvg(seed, saturation, lightness)`. +- Arguments: + - `seed`: the lowercase hex-encoded identicon hash from §2. + - `saturation`: default (`50`). + - `lightness`: default (`50`). +- Output: an SVG string, 5×5 symmetric grid. + +Implementations MAY override saturation and lightness to match their theme, but the seed MUST remain the hex-encoded hash so the grid pattern stays constant. A light and a dark theme that agree on the seed will display the same silhouette in different colors, which is the intended behavior. + +## 4. Contract ID display + +When space permits, implementations SHOULD display the identicon adjacent to the contract principal (`{deployer}.{name}`). The identicon is advisory: it supplements, not replaces, the full principal. + +## 5. Network isolation + +Identicons are a function of source code only, not of network. A contract deployed on testnet and mainnet from identical formatted source will render the same icon. Implementations SHOULD label the network separately. + +# Related Work + +- **Ethereum Blockies** (`ethereum-blockies`): hashes the lowercase hex address, not the bytecode. Identical code at different addresses renders differently — the opposite trade-off from this SIP. +- **Jdenticon**: another open-source identicon library. Produces ~874k distinct icons (vs. ~295k for minidenticons). A future revision of this SIP MAY switch renderers; the hash specification is independent of the renderer. +- **Sourcify**: verifies EVM contract source against deployed bytecode. Useful precedent for canonical-source conventions. This SIP intentionally skips the verification step because Stacks deploys source directly, not bytecode. + +# Backwards Compatibility + +This is a new off-chain convention. There is no on-chain consensus change and no breaking impact on existing contracts, wallets, or explorers. Implementations that do not adopt the SIP continue to display contracts as before. + +# Activation + +This SIP activates once 10 ecosystem participants who display a set of contracts have implemented this specification. + +# Reference Implementations + +## TypeScript / browser + +A reference implementation lives in this repository at `src/utils/contractIdenticon.ts` and is wired into the wallet selector at `src/components/wallet-selector/WalletCard.tsx`. The module: + +1. fetches contract source via `GET /v2/contracts/source/{address}/{name}` against the appropriate Hiro API host for the network; +2. hashes the returned source with `sha512_256` from `@noble/hashes/sha2`; +3. renders via `minidenticonSvg` from the `minidenticons` package. + +Canonicalization via `clarinet fmt` is not performed in the browser implementation; see §1 on assumptions and heuristic warnings. + +## Clarity (self-declaration) + +```clarity +(define-read-only (identicon-hash) + contract-hash? current-contract) +``` + +A contract MAY call this off-chain to emit its own hash as an event for indexers that prefer to read the hash rather than recompute it. From 656faae4628990a29ef1eb7a45cc8ffe42a17209 Mon Sep 17 00:00:00 2001 From: friedger Date: Tue, 14 Apr 2026 16:37:39 +0200 Subject: [PATCH 2/4] chore: use clarinet format, add TODO for ts implementation --- .../sip-xxx/sip-xxx-identicon-for-contracts.md | 18 ++++++------------ 1 file changed, 6 insertions(+), 12 deletions(-) diff --git a/sips/sip-xxx/sip-xxx-identicon-for-contracts.md b/sips/sip-xxx/sip-xxx-identicon-for-contracts.md index 2fa7fb2c..7d0acd57 100644 --- a/sips/sip-xxx/sip-xxx-identicon-for-contracts.md +++ b/sips/sip-xxx/sip-xxx-identicon-for-contracts.md @@ -30,7 +30,7 @@ Discussions-To: This SIP specifies a deterministic way to derive a visual identifier (an "identicon") for any Clarity smart contract deployed to the Stacks blockchain. The identicon is a pure function of the contract's source code after canonicalization: two deployments of the same source — on any address, at any height, on any network — render the same icon. Wallets, explorers, and apps that implement the specification allow users to recognize known code at a glance and to notice when contracts that share a name or author address do not share code. -The specification pins three things: the canonical form of the source (the output of `clarinet fmt` with default settings), the hash function (`SHA-512/256` over the UTF-8 bytes of the canonical form), and the rendering library and configuration (`minidenticons`, default options, seeded with the lowercase hex-encoded hash). The output is an SVG suitable for inline rendering. +The specification pins three things: the canonical form of the source (the output of `clarinet format` with default settings), the hash function (`SHA-512/256` over the UTF-8 bytes of the canonical form), and the rendering library and configuration (`minidenticons`, default options, seeded with the lowercase hex-encoded hash). The output is an SVG suitable for inline rendering. # Copyright @@ -48,16 +48,16 @@ This SIP proposes that consensus. It does not propose any consensus change to th ## 1. Canonical source -The canonical form of a contract's source code is the byte-for-byte output of `clarinet fmt` invoked with default settings, against the contract's `.clar` source file. +The canonical form of a contract's source code is the byte-for-byte output of `clarinet format` invoked with default settings, against the contract's `.clar` source file. - Formatters MUST normalize line endings to `\n` (LF). - Formatters MUST produce a final trailing `\n`. -- Comments (`;;` and `;;;`) are part of the source and are preserved by `clarinet fmt`. They are hashed. +- Comments (`;;` and `;;;`) are part of the source and are preserved by `clarinet format`. They are hashed. - The canonical form is UTF-8 encoded. -Implementations that do not have access to a Clarinet formatter (for example, browser-only apps fetching source from `/v2/contracts/source`) SHOULD hash the source as returned by the node, assuming the contract author deployed `clarinet fmt`-formatted source. Implementations MAY display a "source not canonicalized" warning next to the identicon when the returned source deviates from a heuristic check (trailing whitespace, mixed line endings, tabs mixed with spaces). +Implementations that do not have access to a Clarinet formatter (for example, browser-only apps fetching source from `/v2/contracts/source`) SHOULD hash the source as returned by the node, assuming the contract author deployed `clarinet format`-formatted source. Implementations MAY display a "source not canonicalized" warning next to the identicon when the returned source deviates from a heuristic check (trailing whitespace, mixed line endings, tabs mixed with spaces). -Contract authors who wish to participate in the identicon convention MUST run `clarinet fmt` before deploying. Two authors deploying identical logic with different whitespace will produce different identicons. +Contract authors who wish to participate in the identicon convention MUST run `clarinet format` before deploying. Two authors deploying identical logic with different whitespace will produce different identicons. ## 2. Hash @@ -120,13 +120,7 @@ This SIP activates once 10 ecosystem participants who display a set of contracts ## TypeScript / browser -A reference implementation lives in this repository at `src/utils/contractIdenticon.ts` and is wired into the wallet selector at `src/components/wallet-selector/WalletCard.tsx`. The module: - -1. fetches contract source via `GET /v2/contracts/source/{address}/{name}` against the appropriate Hiro API host for the network; -2. hashes the returned source with `sha512_256` from `@noble/hashes/sha2`; -3. renders via `minidenticonSvg` from the `minidenticons` package. - -Canonicalization via `clarinet fmt` is not performed in the browser implementation; see §1 on assumptions and heuristic warnings. +TODO ## Clarity (self-declaration) From 78e1c77ed95e515e2cf506f92a93f63c47b27493 Mon Sep 17 00:00:00 2001 From: friedger Date: Sat, 16 May 2026 08:24:37 +0200 Subject: [PATCH 3/4] chore: clarify language --- sips/sip-xxx/sip-xxx-identicon-for-contracts.md | 15 ++++++++------- 1 file changed, 8 insertions(+), 7 deletions(-) diff --git a/sips/sip-xxx/sip-xxx-identicon-for-contracts.md b/sips/sip-xxx/sip-xxx-identicon-for-contracts.md index 7d0acd57..24185035 100644 --- a/sips/sip-xxx/sip-xxx-identicon-for-contracts.md +++ b/sips/sip-xxx/sip-xxx-identicon-for-contracts.md @@ -40,9 +40,9 @@ This SIP is made available under the terms of the Creative Commons CC0 1.0 Unive Clarity contracts are addressed by `{deployer}.{name}`. That pair is useful for on-chain references but is a poor user-facing identifier: a user reading `SP2J….transfer-helper` cannot tell from the name alone whether this is the audited, widely-deployed helper they have used before, a fork with a subtle change, or a phishing lookalike deployed to a confusable address. -Hash-based identicons are a well-understood solution to this problem (see `identicon`, `jdenticon`, `blockies`, the GitHub default avatar). What is missing on Stacks is a consensus on *which* bytes get hashed and *which* renderer is used, so that the same contract produces the same icon everywhere it is shown. +Hash-based identicons are a well-understood solution to this problem (see `identicon`, `jdenticon`, `blockies`, the GitHub default avatar). What is missing on Stacks is a convention on *which* bytes get hashed and *which* renderer is used, so that the same contract produces the same icon everywhere it is shown. -This SIP proposes that consensus. It does not propose any consensus change to the Stacks blockchain — the specification is an off-chain convention followed by wallets, explorers, and apps. +This SIP proposes adoption of this convention as a standard across the Stacks ecosystem. It does not propose any consensus change to the Stacks blockchain — the specification is an off-chain convention followed by wallets, explorers, and apps. # Specification @@ -55,7 +55,7 @@ The canonical form of a contract's source code is the byte-for-byte output of `c - Comments (`;;` and `;;;`) are part of the source and are preserved by `clarinet format`. They are hashed. - The canonical form is UTF-8 encoded. -Implementations that do not have access to a Clarinet formatter (for example, browser-only apps fetching source from `/v2/contracts/source`) SHOULD hash the source as returned by the node, assuming the contract author deployed `clarinet format`-formatted source. Implementations MAY display a "source not canonicalized" warning next to the identicon when the returned source deviates from a heuristic check (trailing whitespace, mixed line endings, tabs mixed with spaces). +Implementations without access to a local Clarinet formatter SHOULD attempt to retrieve pre-formatted source and SHOULD emit a warning if the source deviates from the canonical heuristic (trailing whitespace, line-ending consistency, tab usage). Contract authors who wish to participate in the identicon convention MUST run `clarinet format` before deploying. Two authors deploying identical logic with different whitespace will produce different identicons. @@ -78,7 +78,8 @@ This is the same hash exposed in Clarity as the `sha512/256` function, so a cont (sha512/256 source)) ``` -Off-chain implementations MUST NOT rely on any on-chain value; the hash is always derived from the deployed source. +Off-chain implementations MUST compute the identicon hash directly from the canonical source code fetched via /v2/contracts/source, +not from any contract copy or on-chain-published or cached hash value. The source, once deployed, is immutable and authoritative. ## 3. Rendering @@ -90,7 +91,7 @@ The identicon is rendered with the **`minidenticons`** library, used in its defa - `seed`: the lowercase hex-encoded identicon hash from §2. - `saturation`: default (`50`). - `lightness`: default (`50`). -- Output: an SVG string, 5×5 symmetric grid. +- Output: an SVG element (default viewBox) rendering a 5×5 symmetric pixel grid. Implementations MAY override saturation and lightness to match their theme, but the seed MUST remain the hex-encoded hash so the grid pattern stays constant. A light and a dark theme that agree on the seed will display the same silhouette in different colors, which is the intended behavior. @@ -114,13 +115,13 @@ This is a new off-chain convention. There is no on-chain consensus change and no # Activation -This SIP activates once 10 ecosystem participants who display a set of contracts have implemented this specification. +This SIP activates once 10 ecosystem participants (wallets, explorers, developer tools or dApps) have publicly implemented and deployed this specification in production. # Reference Implementations ## TypeScript / browser -TODO +JavaScript/TypeScript: Forthcoming (target: May 2026). ## Clarity (self-declaration) From b60ba9dcbdde347f1439515873a7ee4c4425e588 Mon Sep 17 00:00:00 2001 From: friedger Date: Sat, 16 May 2026 08:43:48 +0200 Subject: [PATCH 4/4] fix: clarify contract id display --- sips/sip-xxx/sip-xxx-identicon-for-contracts.md | 11 +++++++++-- 1 file changed, 9 insertions(+), 2 deletions(-) diff --git a/sips/sip-xxx/sip-xxx-identicon-for-contracts.md b/sips/sip-xxx/sip-xxx-identicon-for-contracts.md index 24185035..1a113ad5 100644 --- a/sips/sip-xxx/sip-xxx-identicon-for-contracts.md +++ b/sips/sip-xxx/sip-xxx-identicon-for-contracts.md @@ -95,9 +95,16 @@ The identicon is rendered with the **`minidenticons`** library, used in its defa Implementations MAY override saturation and lightness to match their theme, but the seed MUST remain the hex-encoded hash so the grid pattern stays constant. A light and a dark theme that agree on the seed will display the same silhouette in different colors, which is the intended behavior. -## 4. Contract ID display +## 4. Contract identifier display + +When space permits, implementations SHOULD display the identicon adjacent to the qualified contract identifier (contract principal, `{deployer}.{name}`). The identicon is advisory: it supplements, not replaces, the identifier. + +To prevent visual conflation of code identity with contract principal, implementations: + +* SHOULD display the contract identicon adjacent to the contract identifier ({deployer}.{name}) with a clear visual hierarchy or label indicating it represents source code, not contract address. + +* MAY display a separate visual representation of the contract identifier to make address divergence visually apparent. The visual representation MAY be derived from the BNS name owned by the contract principal (e.g. profile image), a minidenticon derived from SHA-256(contract principal) or SHA-256(deployer), or a similar representation. Two deployments of identical code to different principals would then show: identical code icons, distinct contract identifier icons. -When space permits, implementations SHOULD display the identicon adjacent to the contract principal (`{deployer}.{name}`). The identicon is advisory: it supplements, not replaces, the full principal. ## 5. Network isolation