1Sat Wallet Desktop — Release Workflow
This skill covers the full release lifecycle for the 1Sat wallet-desktop Electrobun app: changelog, version bump, tagging, CI monitoring, and debugging build failures.
Release Steps (in order)
1. Update the changelog
Edit CHANGELOG.md in the wallet-desktop package root. Follow the existing format:
## [X.Y.Z] - YYYY-MM-DD
### Added
- Feature descriptions
### Fixed
- Bug fix descriptions
Use git log v<previous>..HEAD --oneline to gather commit history since the last tag. Group by Added/Fixed/Changed/Removed.
2. Bump the version in package.json
This is for internal tracking only — CI also syncs it from the git tag. But keeping it in sync avoids confusion.
# In packages/wallet-desktop/package.json, update "version"
Do NOT touch electrobun.config.ts — CI handles that automatically via sed from the tag name.
3. Commit
cd ~/code/1sat-sdk
git add packages/wallet-desktop/CHANGELOG.md packages/wallet-desktop/package.json
git commit -m "chore: bump wallet-desktop to X.Y.Z"
4. Tag and push
Both the commit AND the tag must be pushed. The tag triggers CI.
git push
git tag vX.Y.Z
git push origin vX.Y.Z
5. Monitor the build
CI runs on CircleCI. The workflow is release, job is build-macos.
What CI Does
The full pipeline, triggered by a v* tag push:
- Install Bun and project dependencies (
bun installfrom monorepo root) - Sync version from tag into
package.jsonandelectrobun.config.ts - Setup code signing — imports Apple Developer ID cert from
CSC_LINKenv var - Build workspace deps — types, utils, client, templates, wallet, actions, connect, react
- Vite build — frontend bundle to
dist/ - Electrobun build (
--env=stable) — bundles bun entrypoint, copies files, creates.app, codesigns, notarizes, generates DMG - GitHub Release — creates release, uploads DMG +
latest.json - Vercel Blob — uploads
download.jsonand update artifacts for the auto-updater
CI Environment Variables
These are set in the CircleCI project settings under the 1sat-wallet-signing context:
| Variable | Purpose |
|---|---|
CSC_LINK |
Base64-encoded .p12 developer certificate |
CSC_KEY_PASSWORD |
Certificate password |
APPLE_ID |
Apple ID for notarization |
APPLE_APP_SPECIFIC_PASSWORD |
App-specific password for notarization |
APPLE_TEAM_ID |
Apple Developer team ID |
RELEASE_BUCKET_URL |
Update bucket URL |
GITHUB_TOKEN |
For gh release create/upload |
BLOB_READ_WRITE_TOKEN |
Vercel Blob storage for update artifacts |
Electrobun Config
electrobun.config.ts defines the app bundle. Key details:
- External packages (resolved at runtime, not bundled):
@1sat/wallet-mac,@1sat/vault,@1sat/actions,@1sat/client,@1sat/types,@1sat/utils,@bsv/sdk,@bsv/wallet-toolbox,knex - Copy map:
dist/index.htmlanddist/assets/into views,src/preloads/cwi.tsinto preload - Mac: codesign + notarize enabled, icons from
icon.iconset/
Monitoring Builds via CircleCI API
The CircleCI CLI must be authenticated first (circleci setup with a personal API token from https://circleci.com/account/api). Config lives at ~/.circleci/cli.yml.
Find the pipeline for a tag
TOKEN=$(grep token ~/.circleci/cli.yml | awk '{print $2}')
# List recent pipelines, filter for tags
curl -s -H "Circle-Token: $TOKEN" \
"https://circleci.com/api/v2/project/gh/b-open-io/1sat-sdk/pipeline" \
| python3 -c "
import json,sys
for p in json.load(sys.stdin).get('items',[])[:10]:
tag = p.get('vcs',{}).get('tag','')
if tag: print(f\"{p['id']} tag={tag} {p['state']} {p['created_at']}\")
"
Get workflow status from pipeline
curl -s -H "Circle-Token: $TOKEN" \
"https://circleci.com/api/v2/pipeline/<PIPELINE_ID>/workflow" \
| python3 -c "
import json,sys
for w in json.load(sys.stdin).get('items',[]):
print(f\"{w['id']} {w['name']} {w['status']}\")
"
Get job details from workflow
curl -s -H "Circle-Token: $TOKEN" \
"https://circleci.com/api/v2/workflow/<WORKFLOW_ID>/job" \
| python3 -c "
import json,sys
for j in json.load(sys.stdin).get('items',[]):
print(f\"{j['id']} {j['name']} {j['status']} job_number={j.get('job_number','')}\")
"
Get step-by-step results and failure output
Use the v1.1 API — it returns step output URLs that the v2 API doesn't:
curl -s -H "Circle-Token: $TOKEN" \
"https://circleci.com/api/v1.1/project/github/b-open-io/1sat-sdk/<JOB_NUMBER>" \
| python3 -c "
import json,sys
for s in json.load(sys.stdin).get('steps',[]):
for a in s.get('actions',[]):
icon = 'x' if a.get('failed') else 'ok'
print(f' {icon} {a.get(\"name\",\"\")} [{a.get(\"status\",\"\")}]')
if a.get('failed') and a.get('output_url'):
print(f' output: {a[\"output_url\"]}')
"
Then fetch the output URL with curl -s "<output_url>" to get the actual build log.
Debugging Build Failures
"Bundle failed" with no detail
The Electrobun bundler is opaque about errors. The CI config includes diagnostics that run on failure:
- Standalone bun build of
src/bun/index.tswith the same externals — isolates whether the issue is bun bundling or Electrobun packaging - dist file listing — verifies Vite output exists
- icon.iconset listing — verifies icon assets
- Version info — bun and electrobun versions
If the standalone bun build succeeds but Electrobun still fails, the issue is in app packaging (codesign, icons, file copy). If both fail, it's a dependency resolution issue.
Common failure patterns
- Missing dependency: A new import in
src/bun/that isn't installed on CI. Check thatbun installran from the monorepo root and the package is inpackage.json. - Codesign failure: Missing or expired Apple certificate. Check the
Setup code signingstep output. Certs are stored in the1sat-wallet-signingCircleCI context. - Notarization timeout: Apple's notary service can be slow. The
no_output_timeout: 30mgives it room, but extreme delays happen. - Icon issues:
icon.iconset/must contain valid PNGs at the required sizes (16, 32, 128, 256, 512 + @2x variants). Corrupted or wrong-size icons fail silently.
Retagging after a fix
If you need to fix something and retag:
git tag -d vX.Y.Z # delete local tag
git push origin :refs/tags/vX.Y.Z # delete remote tag
git add <fixed-files>
git commit -m "fix: description"
git push
git tag vX.Y.Z # recreate tag on new commit
git push origin vX.Y.Z # push new tag — triggers CI again
Local Build Testing
Test the build locally before tagging. The codesign step will fail without ELECTROBUN_DEVELOPER_ID, but the Vite + Electrobun bundling will run:
cd packages/wallet-desktop
bun run build
For just the bun entrypoint (fastest check for import/bundling issues):
bun build src/bun/index.ts --outdir /tmp/test --target bun \
--external electrobun/bun --external electrobun \
--external '@1sat/wallet-mac' --external '@1sat/vault' \
--external '@1sat/actions' --external '@1sat/client' \
--external '@1sat/types' --external '@1sat/utils' \
--external '@bsv/sdk' --external '@bsv/wallet-toolbox' \
--external knex
Debugging Runtime Failures
After installing a release, if the app fails to launch or gets stuck.
Check evlog file output
tail -30 ~/.1sat-wallet/logs/$(date +%Y-%m-%d).jsonl
Log files are NDJSON (one JSON object per line), organized by date, with 7-day retention.
Startup event sequence
Events fire in this order during normal startup:
url_resolved— getMainViewUrl() succeededwindow_created— BrowserWindow createddom_ready— webview loaded (includeshasKeyfor vault state)http_listening— BRC-100 server on :3321mcp_listening— MCP server on :3322setup_completeoronboarding_required— stack status
If the log file exists but stops at a specific event, that's where it failed. If no log file exists, the bun process crashed before evlog initialized (likely a missing dependency).
Common runtime issues
- No log file at all: Missing dependency in the bundle. Run the standalone bun build diagnostic.
- Stops at
url_resolved: BrowserWindow constructor failed. dom_readyfires but UI shows skeletons: Stack didn't start or wallet state wasn't sent. Check forstart_failedevents.onboarding_requiredbut user sees nothing: Frontend didn't receive the RPC message.
MCP tool for live debugging
If the app is running and MCP is connected (1sat mcp-proxy), use the wallet_logs tool to query the ring buffer (last 500 events) without touching the filesystem.