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
14 changes: 13 additions & 1 deletion appveyor.yml
Original file line number Diff line number Diff line change
@@ -1,6 +1,7 @@
version: 0.1.34.{build}
version: 0.1.35.{build}

image:
- Ubuntu2404
- macos-sonoma
- Visual Studio 2022

Expand Down Expand Up @@ -35,3 +36,14 @@ for:
- path: walletapp/src-tauri/target/direct/btc09-wallet-macos-universal-preview.zip
name: btc09-wallet-macos-universal-preview.zip
type: File

-
matrix:
only:
- image: Ubuntu2404
build_script:
- sh: bash tools/release/run_linux_appveyor.sh
artifacts:
- path: walletapp/src-tauri/target/direct/btc09-wallet-linux-x64.AppImage
name: btc09-wallet-linux-x64.AppImage
type: File
2 changes: 1 addition & 1 deletion cmd/btc09/main.go
Original file line number Diff line number Diff line change
Expand Up @@ -40,7 +40,7 @@ import (
)

// nodeVersion is the release version; bump alongside git tags.
const nodeVersion = "v0.1.34"
const nodeVersion = "v0.1.35"

func defaultDataDir() string {
home, _ := os.UserHomeDir()
Expand Down
4 changes: 2 additions & 2 deletions cmd/btc09/main_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -763,8 +763,8 @@ func TestReleaseNewer(t *testing.T) {
}

func TestNodeVersionMatchesV034Release(t *testing.T) {
if nodeVersion != "v0.1.34" {
t.Fatalf("nodeVersion = %q, want v0.1.34", nodeVersion)
if nodeVersion != "v0.1.35" {
t.Fatalf("nodeVersion = %q, want v0.1.35", nodeVersion)
}
}

Expand Down
2 changes: 1 addition & 1 deletion cmd/btc09/main_wallet.go
Original file line number Diff line number Diff line change
Expand Up @@ -14,7 +14,7 @@ import (
)

// nodeVersion is the release version; bump alongside git tags.
const nodeVersion = "v0.1.34"
const nodeVersion = "v0.1.35"

func main() {
log.SetFlags(log.Ltime)
Expand Down
47 changes: 47 additions & 0 deletions docs/RELEASE-v0.1.35.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,47 @@
# Bitcoin 09 v0.1.35

v0.1.35 is an application and usability release. It does not change consensus,
proof of work, supply, rewards, addresses, wallet files, or the ASERT rules
activated at height 12,096.

## Wallet

- Rebuilt the Windows, macOS, Linux, Android, and iPhone wallet interface around
the same clear receive, send, activity, backup, and cleanup flow.
- Uses the full desktop window on computers instead of presenting a phone-sized
page inside it.
- Keeps the balance, network state, and next safe action visible.
- Shortens setup and payment copy while preserving recovery-word confirmation
and transaction review.
- Keeps private keys, recovery words, passwords, and signing on the device.

## Desktop client

- The full desktop client keeps its CPU miner and node tools.
- The wallet-only edition cannot start a miner and does not contain mining
commands.
- Native package checks now exercise the actual embedded desktop interface,
including onboarding, receive, send, activity, and the optional full-client
miner.

## Upgrade notes

- Existing wallet files and addresses continue to work.
- Back up the wallet file or recovery words before upgrading.
- This is not a mandatory network fork. Nodes running v0.1.34 remain
consensus-compatible, although v0.1.35 is recommended for the app fixes.
- Install only files attached to the official GitHub release and verify
`SHA256SUMS`.

## Planned release files

- `btc09-wallet-windows-x64-setup.exe`
- `btc09-wallet-macos-universal-preview.zip`
- `btc09-wallet-linux-x64.AppImage`
- `btc09-wallet-android-arm64.apk`
- `btc09-windows-amd64.exe`
- `btc09-linux-amd64`
- `btc09-linux-arm64`
- `btc09-macos-apple.zip`
- `btc09-macos-intel.zip`
- `SHA256SUMS`
2 changes: 1 addition & 1 deletion tools/desktop/native-smoke.mjs
Original file line number Diff line number Diff line change
Expand Up @@ -94,7 +94,7 @@ try {
});
const launch = JSON.parse(await firstLine(backend.stdout, 15000));
assert.equal(launch.schema_version, 1);
assert.equal(launch.version, "v0.1.34");
assert.equal(launch.version, "v0.1.35");
assert.match(launch.launch_url, /^http:\/\/127\.0\.0\.1:\d+\?token=[a-f0-9]{64}$/);

browser = await chromium.launch({ headless: true });
Expand Down
2 changes: 1 addition & 1 deletion tools/mobile/run-ios-simulator.sh
Original file line number Diff line number Diff line change
Expand Up @@ -50,7 +50,7 @@ case " $actual_archs " in
*) echo "Simulator app is missing $expected_arch: $actual_archs" >&2; exit 1 ;;
esac

test "$(plutil -extract CFBundleShortVersionString raw "$APP/Info.plist")" = "0.1.34"
test "$(plutil -extract CFBundleShortVersionString raw "$APP/Info.plist")" = "0.1.35"
symbols="$APPLE_DIR/build/app-symbols.txt"
nm -gU "$APP_BINARY" > "$symbols"
grep -q ' _MobilewalletNewEngine$' "$symbols"
Expand Down
18 changes: 17 additions & 1 deletion tools/mobile/verify-artifact.mjs
Original file line number Diff line number Diff line change
Expand Up @@ -46,9 +46,25 @@ function jarCommand() {
return "jar";
}

export function resolveAndroidSdkRoot(options = {}) {
const platform = options.platform || process.platform;
const env = options.env || process.env;
const directoryExists = options.directoryExists || existsSync;
const paths = platform === "win32" ? path.win32 : path;
const candidates = [
env.ANDROID_HOME,
env.ANDROID_SDK_ROOT,
platform === "win32" && env.LOCALAPPDATA
? paths.join(env.LOCALAPPDATA, "Android", "Sdk")
: undefined,
platform !== "win32" ? paths.join(os.homedir(), "Android", "Sdk") : undefined,
].filter(Boolean);
return candidates.find((candidate) => directoryExists(candidate));
}

function apksignerCommand() {
const executable = process.platform === "win32" ? "apksigner.bat" : "apksigner";
const sdkRoot = process.env.ANDROID_HOME || process.env.ANDROID_SDK_ROOT;
const sdkRoot = resolveAndroidSdkRoot();
if (!sdkRoot) return executable;
const buildToolsRoot = path.join(sdkRoot, "build-tools");
if (!existsSync(buildToolsRoot)) return executable;
Expand Down
12 changes: 12 additions & 0 deletions tools/mobile/verify-artifact.test.mjs
Original file line number Diff line number Diff line change
Expand Up @@ -19,6 +19,18 @@ test("Windows runs the Android signer jar directly instead of spawning a batch f
});
});

test("Windows discovers the default Android SDK for release verification", () => {
const expected = "C:\\Users\\builder\\AppData\\Local\\Android\\Sdk";
assert.equal(
artifactGate.resolveAndroidSdkRoot({
platform: "win32",
env: { LOCALAPPDATA: "C:\\Users\\builder\\AppData\\Local" },
directoryExists: (candidate) => candidate === expected,
}),
expected,
);
});

test("mobile artifact gate accepts ordinary packaged wallet files", () => {
assert.doesNotThrow(() => assertMobileArtifactEntries([
{ name: "assets/mobile.html", bytes: Buffer.from("BTC09 Wallet Receive Send") },
Expand Down
66 changes: 66 additions & 0 deletions tools/mobile/verify-release-outputs.mjs
Original file line number Diff line number Diff line change
@@ -0,0 +1,66 @@
import { existsSync } from "node:fs";
import path from "node:path";
import { fileURLToPath, pathToFileURL } from "node:url";
import {
verifyAndroidApkSignature,
verifyMobileArtifact,
} from "./verify-artifact.mjs";

const root = path.resolve(path.dirname(fileURLToPath(import.meta.url)), "..", "..");

export function selectReleaseApk(directory, fileExists = existsSync) {
const signed = path.join(directory, "app-universal-release.apk");
const unsigned = path.join(directory, "app-universal-release-unsigned.apk");
const existing = [signed, unsigned].filter((candidate) => fileExists(candidate));
if (existing.length !== 1) {
throw new Error(
"Android verification requires exactly one fresh signed or unsigned release APK.",
);
}
return {
path: existing[0],
signed: existing[0] === signed,
};
}

const apkDirectory = path.join(
root,
"walletapp",
"src-tauri",
"gen",
"android",
"app",
"build",
"outputs",
"apk",
"universal",
"release",
);
const aab = path.join(
root,
"walletapp",
"src-tauri",
"gen",
"android",
"app",
"build",
"outputs",
"bundle",
"universalRelease",
"app-universal-release.aab",
);

const invokedPath = process.argv[1] ? pathToFileURL(path.resolve(process.argv[1])).href : "";
if (import.meta.url === invokedPath) {
const selected = selectReleaseApk(apkDirectory);
verifyMobileArtifact(selected.path);
verifyMobileArtifact(aab);
process.stdout.write(`Verified Android release APK: ${selected.path}\n`);
process.stdout.write(`Verified Android release bundle: ${aab}\n`);
if (selected.signed) {
const fingerprint = verifyAndroidApkSignature(selected.path);
process.stdout.write(`Verified Android release signer: ${fingerprint}\n`);
} else {
process.stdout.write("Verified unsigned Android CI preflight boundary.\n");
}
}
34 changes: 34 additions & 0 deletions tools/mobile/verify-release-outputs.test.mjs
Original file line number Diff line number Diff line change
@@ -0,0 +1,34 @@
import assert from "node:assert/strict";
import test from "node:test";
import path from "node:path";
import { selectReleaseApk } from "./verify-release-outputs.mjs";

test("Android release verification accepts one signed output", () => {
const directory = path.resolve("android-output");
const signed = path.join(directory, "app-universal-release.apk");
assert.deepEqual(
selectReleaseApk(directory, (candidate) => candidate === signed),
{ path: signed, signed: true },
);
});

test("Android release verification accepts one unsigned CI output", () => {
const directory = path.resolve("android-output");
const unsigned = path.join(directory, "app-universal-release-unsigned.apk");
assert.deepEqual(
selectReleaseApk(directory, (candidate) => candidate === unsigned),
{ path: unsigned, signed: false },
);
});

test("Android release verification rejects missing or stale ambiguous outputs", () => {
const directory = path.resolve("android-output");
assert.throws(
() => selectReleaseApk(directory, () => false),
/exactly one fresh signed or unsigned/,
);
assert.throws(
() => selectReleaseApk(directory, () => true),
/exactly one fresh signed or unsigned/,
);
});
Loading
Loading