Skip to content
Open
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
3 changes: 2 additions & 1 deletion packages/swift-sdk/.gitignore
Original file line number Diff line number Diff line change
@@ -1 +1,2 @@
logs-*/
logs-*/
spv-storage-*/
254 changes: 254 additions & 0 deletions packages/swift-sdk/get_spv_storage.sh
Original file line number Diff line number Diff line change
@@ -0,0 +1,254 @@
#!/bin/bash
Comment thread
ZocoLini marked this conversation as resolved.
set -euo pipefail

# Extract the SPV storage of SwiftExampleApp from an iOS Simulator
#
# `CoreContentView.startSync()` points the platform-wallet SPV client at
# a per-network directory inside the app's Documents sandbox:
#
# <sandbox>/Documents/SPV/<network>/
# headers / filters / masternodes / state ...
#
# (`network` is one of mainnet/testnet/devnet/regtest etc.) This script
# picks a simulator + app container, then lets the developer pick which
# network's SPV store to extract (defaults to the only one present, or
# an interactive picker when there are several).
#
# Usage:
# ./get_spv_storage.sh [--bundle-id <id>] [--out <dir>]
# [--device <name|udid>]
# [--network <name|all>]
#
# Defaults:
# bundle-id: org.dashfoundation.SwiftExampleApp
# out: ./spv-storage-<device-name>-<network>-<timestamp>
# device: interactive picker when more than one is available
# network: interactive picker when more than one is available
# (use --network all to grab every network at once)

BUNDLE_ID="org.dashfoundation.SwiftExampleApp"
OUT_DIR=""
DEVICE=""
NETWORK=""

need_value() {
if [ "$#" -lt 2 ]; then
echo "Missing value for $1" >&2; exit 2
fi
}

while [ "$#" -gt 0 ]; do
case "$1" in
--bundle-id) need_value "$@"; BUNDLE_ID="$2"; shift 2 ;;
--out) need_value "$@"; OUT_DIR="$2"; shift 2 ;;
--device) need_value "$@"; DEVICE="$2"; shift 2 ;;
--network) need_value "$@"; NETWORK="$2"; shift 2 ;;
-h|--help)
sed -n '4,27p' "$0"; exit 0 ;;
*)
echo "Unknown argument: $1" >&2; exit 2 ;;
esac
done

# ---------- simulator picker ----------

json="$(xcrun simctl list devices --json available)"
table="$(printf '%s' "$json" | jq -r '
.devices
| to_entries[]
| .key as $rt
| .value[]
| select(.isAvailable)
| [.udid, .name, .state, ($rt | sub("com.apple.CoreSimulator.SimRuntime."; ""))]
| @tsv
')"

if [ -z "$table" ]; then
echo "No available simulators found." >&2
exit 1
fi

pick_device() {
if [ -n "$DEVICE" ]; then
matched="$(printf '%s\n' "$table" | awk -F'\t' -v q="$DEVICE" '$1==q || index($2,q)')"

if [ -z "$matched" ]; then
echo "No simulator matches '$DEVICE'." >&2
exit 1
fi

booted="$(printf '%s\n' "$matched" | awk -F'\t' '$3=="Booted"')"

if [ -n "$booted" ]; then
printf '%s\n' "$booted" | head -1
else
printf '%s\n' "$matched" | head -1
fi

return
fi
Comment on lines +71 to +89

Copy link
Copy Markdown
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

💬 Nitpick: --device substring match silently picks first of multiple matches

pick_device accepts --device as either an exact UDID ($1==q) or a substring of the name (index($2,q)). With a non-unique substring such as --device iPhone, multiple rows match and the script silently selects the first via head -1 (preferring booted). A developer expecting a specific device may export the wrong one without warning. pick_network already errors out on ambiguous prefix matches (lines 194–198); mirror that behavior here, or require an exact name/UDID match when --device is supplied. Low impact since the output filename embeds the chosen device name, making the mistake recoverable.

source: ['claude']


sorted="$(printf '%s\n' "$table" | awk -F'\t' '
BEGIN { OFS="\t" }
{ print ($3=="Booted" ? "0" : "1"), $0 }
' | sort -k1,1 | cut -f2-)"

booted_count="$(printf '%s\n' "$sorted" | awk -F'\t' '$3=="Booted"' | wc -l | tr -d ' ')"
if [ "$booted_count" = "1" ]; then
printf '%s\n' "$sorted" | awk -F'\t' '$3=="Booted"' | head -1
return
fi

echo "Available simulators:" >&2

i=1
while IFS=$'\t' read -r udid name state runtime; do
printf " [%d] %-32s %-9s %-22s %s\n" "$i" "$name" "$state" "$runtime" "$udid" >&2
i=$((i + 1))
done <<< "$sorted"

printf "Pick one [1]: " >&2

read -r choice
choice="${choice:-1}"

if ! [[ "$choice" =~ ^[0-9]+$ ]]; then
echo "Not a number: $choice" >&2; exit 1
fi

row="$(printf '%s\n' "$sorted" | sed -n "${choice}p")"

if [ -z "$row" ]; then
echo "Choice out of range." >&2; exit 1
fi

printf '%s\n' "$row"
}

row="$(pick_device)"
UDID="$(printf '%s' "$row" | cut -f1)"
NAME="$(printf '%s' "$row" | cut -f2)"
STATE="$(printf '%s' "$row" | cut -f3)"
echo "Using simulator: $NAME ($UDID, $STATE)"

# ---------- resolve sandbox ----------

if ! container="$(xcrun simctl get_app_container "$UDID" "$BUNDLE_ID" data 2>/dev/null)"; then
echo "Could not resolve the data container for $BUNDLE_ID on $NAME." >&2
echo "Is the app installed? Try installing + launching it first" >&2
exit 1
fi

root="$container/Documents/SPV"
if [ ! -d "$root" ]; then
echo "No SPV storage directory at $root." >&2
echo "The app hasn't started an SPV sync yet (CoreContentView.startSync())." >&2
echo "Launch the app, start a sync once, and re-run." >&2
exit 1
fi

# ---------- network picker ----------

# Networks are subdirectories directly under the SPV root
# (mainnet/testnet/devnet/...). Sort by name for a stable picker.
#
# For macOS compability:
# macOS still ships Bash 3.2 where `mapfile` is unavailable, use an
# explicit loop.
networks=()
while IFS= read -r n; do
networks+=("$n")
done < <(find "$root" -mindepth 1 -maxdepth 1 -type d \
-exec basename {} \; | sort)

if [ "${#networks[@]}" -eq 0 ]; then
echo "No network directories under $root." >&2
echo "Start an SPV sync once so a <network> store is created." >&2
exit 1
fi

pick_network() {
if [ "$NETWORK" = "all" ]; then
printf '%s\n' "__all__"
return
fi

if [ -z "$NETWORK" ] && [ "${#networks[@]}" -eq 1 ]; then
printf '%s\n' "${networks[0]}"
return
fi

if [ -n "$NETWORK" ]; then
# Match exactly or by unique prefix.
matched=()
for n in "${networks[@]}"; do
case "$n" in
"$NETWORK"*) matched+=("$n") ;;
esac
done

if [ "${#matched[@]}" -eq 0 ]; then
echo "No network matches '$NETWORK'." >&2; exit 1
fi

if [ "${#matched[@]}" -gt 1 ]; then
echo "Network prefix '$NETWORK' is ambiguous; matches:" >&2
printf ' %s\n' "${matched[@]}" >&2
exit 1
fi

printf '%s\n' "${matched[0]}"

return
fi

echo "Available networks:" >&2

i=1
for n in "${networks[@]}"; do
# Annotate with on-disk size so the picker doubles as a
# "how much store is here" view.
size="$(du -sh "$root/$n" 2>/dev/null | cut -f1 || true)"
printf " [%d] %-12s (%s)\n" "$i" "$n" "${size:-?}" >&2
i=$((i + 1))
done

printf "Pick one [1]: " >&2

read -r choice
choice="${choice:-1}"

if ! [[ "$choice" =~ ^[0-9]+$ ]] \
|| [ "$choice" -lt 1 ] || [ "$choice" -gt "${#networks[@]}" ]; then
echo "Choice out of range." >&2; exit 1
fi

pick="${networks[$((choice - 1))]}"

printf '%s\n' "$pick"
}

NETWORK_ID="$(pick_network)"

# ---------- copy ----------

safe_name="$(printf '%s' "$NAME" | tr ' /' '__')"
Comment thread
ZocoLini marked this conversation as resolved.

if [ "$NETWORK_ID" = "__all__" ]; then
src="$root"
label="all"
else
src="$root/$NETWORK_ID"
label="$NETWORK_ID"
fi

if [ -z "$OUT_DIR" ]; then
stamp="$(date +%Y-%m-%dT%H-%M-%S)"
OUT_DIR="spv-storage-${safe_name}-${label}-${stamp}"
fi

mkdir -p "$OUT_DIR"
cp -R "$src/." "$OUT_DIR/"
Comment thread
ZocoLini marked this conversation as resolved.
Comment thread
ZocoLini marked this conversation as resolved.

echo
echo "Copied ${label} SPV storage to $OUT_DIR/"
Loading