Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
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
16 changes: 0 additions & 16 deletions .claude/commands/README.md

This file was deleted.

22 changes: 0 additions & 22 deletions .claude/commands/check-airtable.md

This file was deleted.

32 changes: 0 additions & 32 deletions .claude/commands/decode.md

This file was deleted.

35 changes: 0 additions & 35 deletions .claude/commands/test-fixture.md

This file was deleted.

37 changes: 0 additions & 37 deletions .claude/commands/verify-tx.md

This file was deleted.

10 changes: 0 additions & 10 deletions .env.example

This file was deleted.

14 changes: 11 additions & 3 deletions .github/workflows/publish-sdk.yml
Original file line number Diff line number Diff line change
Expand Up @@ -13,15 +13,23 @@ jobs:
run:
working-directory: sdk
steps:
- uses: actions/checkout@v6
- uses: pnpm/action-setup@v4
- uses: actions/checkout@df4cb1c069e1874edd31b4311f1884172cec0e10 # v6
- uses: pnpm/action-setup@b906affcce14559ad1aafd4ab0e942779e9f58b1 # v4
with:
package_json_file: sdk/package.json
- uses: actions/setup-node@v6
- uses: actions/setup-node@48b55a011bda9f5d6aeb4c2d9c7362e8dae4041e # v6
with:
node-version: "24"
registry-url: "https://registry.npmjs.org"
cache: pnpm
cache-dependency-path: sdk/pnpm-lock.yaml
- name: Check tag matches package.json version
run: |
tag_version="${GITHUB_REF_NAME#v}"
pkg_version="$(node -p "require('./package.json').version")"
if [ "$tag_version" != "$pkg_version" ]; then
echo "Tag v$tag_version does not match package.json version $pkg_version" >&2
exit 1
fi
- run: pnpm install --frozen-lockfile
- run: pnpm publish --access public --no-git-checks
28 changes: 28 additions & 0 deletions .github/workflows/test-sdk.yml
Original file line number Diff line number Diff line change
@@ -0,0 +1,28 @@
name: Test SDK
on:
push:
branches:
- main
pull_request:
permissions:
contents: read
jobs:
test:
runs-on: ubuntu-latest
defaults:
run:
working-directory: sdk
steps:
- uses: actions/checkout@df4cb1c069e1874edd31b4311f1884172cec0e10 # v6
- uses: pnpm/action-setup@b906affcce14559ad1aafd4ab0e942779e9f58b1 # v4
with:
package_json_file: sdk/package.json
- uses: actions/setup-node@48b55a011bda9f5d6aeb4c2d9c7362e8dae4041e # v6
with:
node-version: "24"
cache: pnpm
cache-dependency-path: sdk/pnpm-lock.yaml
- run: pnpm install --frozen-lockfile
- run: pnpm typecheck
- run: pnpm test
- run: pnpm build
5 changes: 5 additions & 0 deletions .gitignore
Original file line number Diff line number Diff line change
Expand Up @@ -5,3 +5,8 @@ dist
.env
.env.local
coverage

# Local-only tooling and internal context — not part of the public repo
CLAUDE.md
.claude/
.env.example
31 changes: 21 additions & 10 deletions BUILDERS.md
Original file line number Diff line number Diff line change
@@ -1,7 +1,5 @@
# Celo Attribution Tags — for app builders

**Lena Hierzi, DevRel Lead, Celo Core Co — 8 May 2026**

You're shipping an app on Celo and you want your transactions to be attributable to your app. This is the guide for that. Two steps: install the SDK, append the suffix.

**Who this is for:** MiniPay app builders, Proof of Ship cohort projects, and Celo ecosystem projects that want their on-chain activity attributed to them. The rest of this guide assumes you're sending transactions to Celo Mainnet (or Celo Sepolia for testing).
Expand All @@ -16,11 +14,13 @@ ERC-8021 is the standard for appending a small attribution suffix to a transacti

```ts
toDataSuffix(code | [codes]) // → encoded suffix (Hex)
codeFromHostname(hostname) // → "celo_xxxxxxxx" derived from a hostname
fromDataSuffix(suffix) // → { codes, schemaId } | null
codeFromHostname(hostname) // → "celo_" + 12 hex chars, derived from a hostname
fromDataSuffix(data) // → { codes, schemaId } | null
verifyTx({ client, hash }) // → { codes, schemaId } | null
```

There are two ways to get a code, and both are fully supported: derive one from your hostname (zero registration), or bring your own — an issued `celo_xxxxxxxx` code or any custom code you pick for your app. Tagging is open to everyone; which codes get *credited* on the attribution dashboard is resolved at the registry/indexer layer, not at the tagging step.

## Install

```bash
Expand Down Expand Up @@ -53,18 +53,21 @@ printf "%s" "mondeto.app" | shasum -a 256 | cut -c1-12

Apps not in MiniPay's approved-app list will still produce a code on-chain, but the attribution dashboard only credits codes whose hostnames are on the list — so the credit step is gated, not the tagging step.

## Quickstart — issued codes (Proof of Ship and others)
## Quickstart — your own code (issued or custom)

If you've been issued a code (`celo_xxxxxxxx`) through Proof of Ship onboarding or another path, pass it directly:
If you've been issued a code (`celo_xxxxxxxx`) through Proof of Ship onboarding or another path — or you simply want to pick your own — pass it directly:

```ts
import { toDataSuffix } from '@celo/attribution-tags'

const tag = toDataSuffix('celo_b7k3p9da')
const tag = toDataSuffix('celo_b7k3p9da') // issued code — or a custom
// one, e.g. toDataSuffix('myapp')

await wallet.sendTransaction({ to, value, data: tag })
```

Any string matching `[a-z0-9_]` (1–32 chars) is a valid code on the wire. Custom codes tag your transactions just as well; getting them recognized on the attribution dashboard is a registry-layer step — reach out via the contact at the bottom if you want your custom code credited.

For local development before you have a real code, hardcode `celo_test1234` so you can iterate.

## If you're calling a contract method
Expand Down Expand Up @@ -120,16 +123,24 @@ export function getAttributionSuffix(): Hex | undefined {

The `typeof window === 'undefined'` check makes the function a no-op on the server. The cache means SHA-256 runs once per session, not once per render.

**Pattern B — derive at module init in a `"use client"` file:**
**Pattern B — derive inside an event handler or effect:**

```tsx
'use client'
import { toDataSuffix, codeFromHostname } from '@celo/attribution-tags'

export const ATTRIBUTION_SUFFIX = toDataSuffix(codeFromHostname(window.location.hostname))
function SendButton() {
async function onSend() {
const tag = toDataSuffix(codeFromHostname(window.location.hostname))
await wallet.sendTransaction({ to, value, data: tag })
}
return <button onClick={onSend}>Send</button>
}
```

Only do this in a file marked `"use client"`. Importing it from a server component will throw at build time.
Event handlers and `useEffect` bodies only run in the browser, so `window` is always defined there.

One thing that does **not** work: deriving at module init (top level) in a `"use client"` file. `"use client"` components are still executed on the server during SSR/prerendering — the directive marks the client boundary, it doesn't make the module browser-only — so top-level `window.location` throws `ReferenceError` the first time the server renders the page.

## The layering rule — apps don't add platform codes

Expand Down
88 changes: 0 additions & 88 deletions CLAUDE.md

This file was deleted.

Loading
Loading