### --------- helpers ---------- function _need --description "ensure a command exists" for c in $argv if not type -q $c echo (set_color red)"Error:"(set_color normal)" missing command: $c" >&2 return 1 end end end function _banner --description "pretty section header" -a MSG set_color cyan; echo; echo "==> $MSG"; set_color normal end # 32-char alphanumeric generator (fast, no shell-specials) function _rand_alnum32 if type -q openssl openssl rand -base64 48 2>/dev/null | tr -dc 'A-Za-z0-9' | head -c 32 else dd if=/dev/urandom bs=48 count=1 2>/dev/null | base64 | tr -dc 'A-Za-z0-9' | head -c 32 end end # Default image chooser (you should override with your own multi-arch image) function _sui_default_image -a NET echo registry.bstein.dev/infra/sui-tools:1.53.2 end # Convert any string to a k8s-safe name (RFC-1123 label-ish) function _k8s_name --description "sanitize to RFC-1123 label" -a S set s (string lower -- $S) set s (string replace -ar -- '[^a-z0-9-]' '-' $s) set s (string replace -r -- '^-+' '' $s) set s (string replace -r -- '-+$' '' $s) echo $s end # Simple non-empty check function _require --description "require a non-empty value" -a WHAT VAL if test -z "$VAL" echo (set_color red)"Error:"(set_color normal)" $WHAT cannot be empty. Aborting." >&2 return 1 end end function _sc_list --description "list storageclasses as menu" kubectl get storageclass -o json | jq -r ' .items[] | {name: .metadata.name, provisioner: .provisioner, reclaim: (.reclaimPolicy // "Delete"), default: ( .metadata.annotations["storageclass.kubernetes.io/is-default-class"] == "true" or .metadata.annotations["storageclass.beta.kubernetes.io/is-default-class"] == "true") } | "\(.name)\t\(.provisioner)\t\(.reclaim)\t\((.default|tostring))" ' 2>/dev/null end # Choose StorageClass by index or name function _choose_sc --description "interactive StorageClass picker" set lines (_sc_list) if test (count $lines) -eq 0 echo (set_color red)"Error:"(set_color normal)" No StorageClasses found." >&2 return 1 end echo "" >&2 echo "==> Available StorageClasses" >&2 echo " # | name | provisioner | reclaim | default" >&2 echo "----+----------------------+------------------------+---------+--------" >&2 set i 1 for l in $lines set name (echo $l | awk -F'\t' '{print $1}') set prov (echo $l | awk -F'\t' '{print $2}') set rec (echo $l | awk -F'\t' '{print $3}') set def (echo $l | awk -F'\t' '{print $4}') printf " %2d | %-20s | %-22s | %-7s | %s\n" $i $name $prov $rec $def >&2 set i (math $i + 1) end set prefer (printf "%s\n" $lines | awk -F'\t' '$4=="true"{print $1}' | head -n1) if test -z "$prefer" set prefer (printf "%s\n" $lines | awk -F'\t' '{print $1}' | head -n1) end read -P "Pick StorageClass by number or name (blank = $prefer): " choice if test -z "$choice" echo "Using StorageClass: $prefer" >&2 echo $prefer return 0 end if string match -qr '^[0-9]+$' -- $choice set idx (math $choice) if test $idx -lt 1 -o $idx -gt (count $lines) echo (set_color red)"Error:"(set_color normal)" Choice out of range." >&2 return 1 end set sel (printf "%s\n" $lines[$idx] | awk -F'\t' '{print $1}') echo "Using StorageClass: $sel" >&2 echo $sel return 0 end set found (printf "%s\n" $lines | awk -F'\t' -v want="$choice" '$1==want{print $1}') if test -z "$found" echo (set_color red)"Error:"(set_color normal)" No StorageClass named '$choice'." >&2 return 1 end echo "Using StorageClass: $found" >&2 echo $found end # ---- probe Sui JSON-RPC endpoint function _probe_sui_rpc --description "probe Sui JSON-RPC endpoint" -a URL if test -z "$URL" echo (set_color red)"Error:"(set_color normal)" _probe_sui_rpc needs an URL" >&2 return 1 end set payload '{"jsonrpc":"2.0","id":1,"method":"sui_getLatestCheckpointSequenceNumber","params":[]}' set tmp (mktemp) set code (curl -sS -o $tmp -w "%{http_code}" -H "Content-Type: application/json" -d "$payload" "$URL") set rc $status set body (cat $tmp); rm -f $tmp if test $rc -ne 0 echo (set_color red)"Transport error probing $URL (curl rc=$rc)."(set_color normal) >&2 return 1 end if test "$code" != "200" echo (set_color red)"HTTP $code from $URL"(set_color normal) >&2 echo $body >&2 return 1 end set seq (printf "%s" $body | jq -r '.result // empty' 2>/dev/null) if test -z "$seq" echo (set_color red)"No result from RPC probe at $URL."(set_color normal) >&2 return 1 end echo "Sui RPC OK: latest checkpoint seq=$seq" end ### --------- main workflow ---------- function suiwallet_bootstrap --description "Interactive setup of a Sui wallet pod with PVC; creates address and prints recovery phrase" _need kubectl jq curl awk; or return 1 _banner "Sui wallet bootstrap" # --- Namespace read -P "Namespace [crypto]: " ns_raw if test -z "$ns_raw"; set ns_raw crypto; end set ns (_k8s_name $ns_raw) _require "Namespace" $ns; or return 1 echo "Ensuring namespace '$ns' exists…" if kubectl get ns $ns >/dev/null 2>&1 echo "Namespace $ns already exists." else kubectl create ns $ns; or return 1 echo "Created namespace $ns." end # --- StorageClass kubectl get storageclass echo "TIP: Prefer a class with reclaimPolicy=Retain so a PVC delete won't delete the PV." set sc (_choose_sc); or return 1 # --- Unified name (prefix wallet-sui-) read -P "Wallet name (no spaces, e.g. 'brad', base will be wallet-sui-brad): " wallet_raw _require "Wallet name" $wallet_raw; or return 1 if string match -rq '\s' -- $wallet_raw echo (set_color red)"Error:"(set_color normal)" Wallet name cannot contain spaces." >&2 return 1 end set base (_k8s_name $wallet_raw) set base (string replace -r '^wallet-sui-' '' -- $base) set name "wallet-sui-$base" # Bind all k8s object names to the unified base set pvc_name $name set app_name $name # --- Detect existing resources (idempotence) set exists_any 0 set found_list for r in "deploy/$app_name" (printf "netpol/%s-deny-ingress" $app_name) if kubectl -n $ns get $r >/dev/null 2>&1 set -a found_list $r set exists_any 1 end end if kubectl -n $ns get pvc $pvc_name >/dev/null 2>&1 set -a found_list "pvc/$pvc_name" set exists_any 1 end if kubectl -n $ns get secret (printf "%s-mnemonic" $app_name) >/dev/null 2>&1 set -a found_list (printf "secret/%s-mnemonic" $app_name) set exists_any 1 end if test $exists_any -eq 1 _banner "Existing resources detected for $name" for r in $found_list echo " - $ns/$r" end read -P "Remove EVERYTHING (deploy/netpol/PVC/secret) and recreate? (y/N): " wipe if string match -qi 'y*' -- $wipe kubectl -n $ns delete deploy $app_name --ignore-not-found --wait=true >/dev/null 2>&1; or true kubectl -n $ns delete netpol (printf "%s-deny-ingress" $app_name) --ignore-not-found --wait=true >/dev/null 2>&1; or true kubectl -n $ns delete pvc $pvc_name --ignore-not-found --wait=true >/dev/null 2>&1; or true kubectl -n $ns delete secret (printf "%s-mnemonic" $app_name) --ignore-not-found >/dev/null 2>&1; or true echo "Cleaned up previous resources." else echo "Reusing existing resources; they will be applied in-place." end end # --- Node scheduling (worker-only by default) read -P "Schedule only on worker nodes? (y/N) [y]: " pin_workers if test -z "$pin_workers"; set pin_workers y; end set use_pin 0 if string match -qi 'y*' -- $pin_workers set use_pin 1 read -P "Worker label key [node-role.kubernetes.io/worker]: " node_label_key if test -z "$node_label_key"; set node_label_key node-role.kubernetes.io/worker; end read -P "Worker label value [true]: " node_label_val if test -z "$node_label_val"; set node_label_val true; end end # --- Network and RPC read -P "Network [mainnet|testnet|devnet] [mainnet]: " net if test -z "$net"; set net mainnet; end switch $net case mainnet set rpc_url https://fullnode.mainnet.sui.io:443 case testnet set rpc_url https://fullnode.testnet.sui.io:443 case devnet set rpc_url https://fullnode.devnet.sui.io:443 case '*' echo (set_color red)"Error:"(set_color normal)" network must be mainnet|testnet|devnet"; return 1 end _banner "Probing Sui RPC endpoint" if not _probe_sui_rpc $rpc_url echo (set_color red)"Aborting due to RPC probe failure."(set_color normal) return 1 end # --- Image and address alias set image_default (_sui_default_image $net) echo "NOTE: Prefer a multi-arch image you built (see instructions below)." read -P "Container image [$image_default]: " image if test -z "$image"; set image $image_default; end read -P "Address alias to create [main]: " alias if test -z "$alias"; set alias main; end read -P "Mnemonic word length [24]: " wl if test -z "$wl"; set wl 24; end read -P "Store mnemonic as a Kubernetes Secret? (NOT recommended) (y/N): " store_mn set store_mn (string lower -- $store_mn) # --- Optional NetworkPolicy read -P "Add NetworkPolicy (deny all ingress; allow egress) ? (y/N): " want_np set want_np (string lower -- $want_np) _banner "Summary" echo " Namespace: $ns" echo " StorageClass: $sc" echo " Base name: $name" echo " PVC: $pvc_name" echo " App/Deployment:$app_name" echo " Image: $image" echo " Network: $net" echo " RPC URL: $rpc_url" echo " New alias: $alias (word length: $wl)" if test $use_pin -eq 1 echo " NodeSelector: $node_label_key=$node_label_val" end if string match -qi 'y*' -- $want_np echo " NetworkPolicy: deny-all ingress (egress allowed)" end read -P "Proceed? [y/N]: " proceed if not string match -qi 'y*' -- $proceed echo (set_color yellow)"Aborted by user at confirmation step."(set_color normal) return 1 end _banner "Applying PVC" if kubectl -n $ns get pvc $pvc_name >/dev/null 2>&1 echo "PVC $ns/$pvc_name exists; keeping as-is." else set pvcfile (mktemp -t $pvc_name-pvc.XXXX.yaml) begin echo "apiVersion: v1" echo "kind: PersistentVolumeClaim" echo "metadata:" echo " name: $pvc_name" echo " namespace: $ns" echo "spec:" echo " accessModes: [\"ReadWriteOnce\"]" echo " storageClassName: $sc" echo " resources:" echo " requests:" echo " storage: 1Gi" end > $pvcfile echo "--- PVC manifest ($pvcfile) ---" cat $pvcfile kubectl apply -f $pvcfile; or return 1 end _banner "Applying Deployment" set dply (mktemp -t $app_name-deploy.XXXX.yaml) begin echo "apiVersion: apps/v1" echo "kind: Deployment" echo "metadata:" echo " name: $app_name" echo " namespace: $ns" echo " labels: { app: $app_name }" echo "spec:" echo " replicas: 1" echo " strategy: { type: Recreate }" echo " selector: { matchLabels: { app: $app_name } }" echo " template:" echo " metadata:" echo " labels: { app: $app_name }" echo " spec:" if test $use_pin -eq 1 echo " nodeSelector:" echo " $node_label_key: \"$node_label_val\"" end echo " securityContext:" echo " fsGroup: 1000" echo " fsGroupChangePolicy: OnRootMismatch" echo " initContainers:" echo " - name: volume-permissions" echo " image: busybox:1.36" echo " command: [\"/bin/sh\",\"-lc\",\"mkdir -p /home/sui/.sui/sui_config && chown -R 1000:1000 /home/sui/.sui && chmod -R 0770 /home/sui/.sui\"]" echo " securityContext: { runAsUser: 0 }" echo " volumeMounts: [ { name: data, mountPath: /home/sui/.sui } ]" echo " containers:" echo " - name: sui-tools" echo " image: $image" echo " imagePullPolicy: IfNotPresent" echo " command: [\"/bin/sh\",\"-lc\"]" echo " args:" echo " - |" echo " # keep container ready for execs" echo " sleep infinity" echo " volumeMounts:" echo " - name: data" echo " mountPath: /home/sui/.sui" echo " resources:" echo " requests: { cpu: \"50m\", memory: \"128Mi\" }" echo " limits: { cpu: \"1\", memory: \"512Mi\" }" echo " volumes:" echo " - name: data" echo " persistentVolumeClaim: { claimName: $pvc_name }" end > $dply echo "--- Deployment manifest ($dply) ---" head -n 70 $dply echo "..." kubectl apply -f $dply; or return 1 if string match -qi 'y*' -- $want_np _banner "Applying NetworkPolicy (deny-all ingress; allow all egress)" set np (mktemp -t $app_name-netpol.XXXX.yaml) begin echo "apiVersion: networking.k8s.io/v1" echo "kind: NetworkPolicy" echo "metadata:" printf " name: %s-deny-ingress\n" $app_name echo " namespace: $ns" echo "spec:" echo " podSelector: { matchLabels: { app: $app_name } }" echo " policyTypes: [\"Ingress\",\"Egress\"]" echo " ingress: []" echo " egress:" echo " - {}" end > $np echo "--- NetworkPolicy manifest ($np) ---" cat $np kubectl apply -f $np; or return 1 end _banner "Waiting for rollout" if not kubectl -n $ns rollout status deploy/$app_name --timeout=300s echo (set_color red)"Rollout did not finish in time. Recent events:"(set_color normal) >&2 kubectl -n $ns get events --sort-by=.lastTimestamp | tail -n 40 return 1 end _banner "Validating Sui CLI inside the pod" set ARCH (kubectl -n $ns exec deploy/$app_name -- sh -lc 'uname -m' 2>/dev/null) set GLIBC (kubectl -n $ns exec deploy/$app_name -- sh -lc 'ldd --version 2>&1 | head -n1 || true' 2>/dev/null) set SUIV (kubectl -n $ns exec deploy/$app_name -- sh -lc 'sui --version 2>&1 || true' 2>/dev/null) echo "Node arch: $ARCH" echo "GLIBC: $GLIBC" echo "sui: $SUIV" if test -z "$SUIV"; or string match -q "*not found*" -- $SUIV echo (set_color red)"Error:"(set_color normal)" 'sui' did not run inside the pod." echo "If you see GLIBC < 2.38 here, rebuild the image on Ubuntu 24.04 as shown in section A." return 1 end _banner "Initializing Sui client env ($net) and creating a new address" # Create/switch env, then create a new address with alias. # Try JSON first; fall back to parsing text. set TMP (mktemp) # Ensure config dir exists (inside the pod) and env points to your chosen RPC. kubectl -n $ns exec deploy/$app_name -- sh -lc \ (printf 'mkdir -p /home/sui/.sui/sui_config ; sui client new-env --alias %s --rpc %s >/dev/null 2>&1 || true ; sui client switch --env %s >/dev/null 2>&1 || true' $net $rpc_url $net) # Try JSON (preferred) set JSON (kubectl -n $ns exec deploy/$app_name -- sh -lc \ (printf 'sui client new-address ed25519 --word-length %s --alias %s --json 2>/dev/null || true' $wl $alias)) set MN (printf "%s" $JSON | jq -r '.secretRecoveryPhrase // .mnemonic // .mnemonicPhrase // empty' 2>/dev/null) set ADDR (printf "%s" $JSON | jq -r '.address // empty' 2>/dev/null) if test -n "$MN" kubectl -n $ns logs deploy/$app_name --since=10s >/dev/null 2>&1 || true kubectl -n $ns exec deploy/$app_name -- sh -lc \ (printf 'echo "SECRET RECOVERY PHRASE: %s" >> /home/sui/README_MNEMONIC && chmod 0600 /home/sui/README_MNEMONIC' "$MN") end if test -z "$MN" -o -z "$ADDR" # Fall back to plain text (robust across CLI changes) kubectl -n $ns exec deploy/$app_name -- sh -lc \ (printf 'sui client new-address ed25519 --word-length %s --alias %s 2>&1 || true' $wl $alias) | tee $TMP >/dev/null if test -z "$MN" # Usually phrase is printed on the line right after the "Secret Recovery Phrase" header set MN (awk '/Secret Recovery Phrase/ {getline; print; exit}' $TMP) end if test -z "$ADDR" # Typical line: "Address: 0xabc..."; extract the RHS set ADDR (awk -F': ' '/^Address:/ {print $2; exit}' $TMP) end end # Make the generated address active (address wins; alias as backup) if test -n "$ADDR" kubectl -n $ns exec deploy/$app_name -- sh -lc (printf 'sui client switch --address %s >/dev/null 2>&1 || true' $ADDR) else kubectl -n $ns exec deploy/$app_name -- sh -lc (printf 'sui client switch --alias %s >/dev/null 2>&1 || true' $alias) end echo echo (set_color yellow)"IMPORTANT — Secret Recovery Phrase (write down & store offline):"(set_color normal) if test -n "$MN" echo $MN else echo "(mnemonic not detected — check 'kubectl -n $ns logs deploy/$app_name' output)" end echo echo "Address alias '$alias' appears to be: $ADDR" # Optional: store mnemonic in a Secret (not recommended) if test "$store_mn" = "y" -o "$store_mn" = "yes" if test -n "$MN" kubectl -n $ns create secret generic "$app_name-mnemonic" \ --from-literal=mnemonic="$MN" --dry-run=client -o yaml | kubectl -n $ns apply -f - echo (set_color yellow)"Stored mnemonic in Secret $ns/{$app_name}-mnemonic. Treat your cluster as sensitive."(set_color normal) else echo (set_color red)"Warning:"(set_color normal)" mnemonic empty; not storing." end end echo echo "Done. Files inside the pod:" kubectl -n $ns exec deploy/$app_name -- sh -lc 'ls -l /home/sui/.sui/sui_config || true' echo echo "Useful commands:" echo " suiwallet_addresses $ns $app_name" echo " suiwallet_gas $ns $app_name" echo " suiwallet_export_keystore $ns $app_name ./sui.keystore.$base" echo " suiwallet_metrics $ns $app_name $base" echo " kubectl -n $ns exec -it deploy/$app_name -- sh # interactive CLI" end ### --------- utilities ---------- function suiwallet_status -a NS APP if test -z "$NS"; set NS crypto; end if test -z "$APP"; set APP wallet-sui-main; end kubectl -n $NS get deploy,po,pvc,netpol -l app=$APP -o wide end function suiwallet_logs -a NS APP LINES if test -z "$NS"; set NS crypto; end if test -z "$APP"; set APP wallet-sui-main; end if test -z "$LINES"; set LINES 200; end kubectl -n $NS logs deploy/$APP --tail=$LINES end function suiwallet_wallet_ls -a NS APP if test -z "$NS"; set NS crypto; end if test -z "$APP"; set APP wallet-sui-main; end echo "Listing /home/sui/.sui/sui_config in $NS/$APP…" >&2 kubectl -n $NS exec deploy/$APP -- sh -lc 'ls -la /home/sui/.sui/sui_config || true' end function suiwallet_addresses -a NS APP if test -z "$NS"; set NS crypto; end if test -z "$APP"; set APP wallet-sui-main; end kubectl -n $NS exec deploy/$APP -- sh -lc 'sui client addresses || true' end function suiwallet_export_keystore -a NS APP DEST if test -z "$NS"; set NS crypto; end if test -z "$APP"; set APP wallet-sui-main; end if test -z "$DEST"; set DEST ./sui.keystore; end set POD (kubectl -n $NS get pod -l app=$APP -o jsonpath='{.items[0].metadata.name}' 2>/dev/null) if test -z "$POD" echo (set_color red)"Error:"(set_color normal)" no pod found for app=$APP in ns=$NS" >&2 return 1 end echo "Copying keystore from $POD to $DEST (keep it safe!)" kubectl -n $NS cp $POD:/home/sui/.sui/sui_config/sui.keystore $DEST end # Summarize gas coins and total balance; tolerant to schema differences function suiwallet_gas -a NS APP if test -z "$NS"; set NS crypto; end if test -z "$APP"; set APP wallet-sui-main; end set J (kubectl -n $NS exec deploy/$APP -- sh -lc 'sui client gas --json' 2>/dev/null) if test -z "$J" echo "No JSON from 'sui client gas' (is the address set and env working?)" >&2 return 1 end set NUM (printf "%s" $J | jq -r '[.. | .gasObjects? // empty | length] | add // 0') if test -z "$NUM" -o "$NUM" = "null" set NUM (printf "%s" $J | jq -r '[.. | .coins? // empty | length] | add // 0') end set SUM (printf "%s" $J | jq -r ' (.. | .totalBalance? // empty) as $t | if $t != null then ($t|tonumber) else [.. | .gasObjects? // empty | .[]? | .balance? | tonumber] | add end // 0 ') # Convert Mist -> SUI using python -c set SUM_SUI (python3 -c 'from decimal import Decimal; import sys; q=Decimal(sys.argv[1]) if sys.argv[1] else Decimal(0); print((q/Decimal("1e9")).quantize(Decimal("0.000000001")))' -- $SUM) echo "Gas coins: $NUM Total: $SUM ($SUM_SUI SUI)" end # Wait until balance >= threshold (SUI), timeout seconds function suiwallet_wait_balance -a NS APP MIN_SUI TIMEOUT _need python3; or return 1 if test -z "$NS"; set NS crypto; end if test -z "$APP"; set APP wallet-sui-main; end if test -z "$MIN_SUI"; set MIN_SUI 0; end if test -z "$TIMEOUT"; set TIMEOUT 300; end set start (date +%s) while true set J (kubectl -n $NS exec deploy/$APP -- sh -lc 'sui client gas --json' 2>/dev/null) set SUM (printf "%s" $J | jq -r ' (.. | .totalBalance? // empty) as $t | if $t != null then ($t|tonumber) else [.. | .gasObjects? // empty | .[]? | .balance? | tonumber] | add end // 0 ') set SUM_SUI (python3 -c 'from decimal import Decimal; import sys; q=Decimal(sys.argv[1]) if sys.argv[1] else Decimal(0); print(q/Decimal("1e9"))' -- $SUM) set ge (python3 -c 'from decimal import Decimal; import sys; print(1 if Decimal(sys.argv[1]) >= Decimal(sys.argv[2]) else 0)' -- $SUM_SUI $MIN_SUI) if test "$ge" = "1" echo "Balance ready: $SUM_SUI SUI (>= $MIN_SUI)" return 0 end if test (math (date +%s) - $start) -ge $TIMEOUT echo "Timed out waiting for balance >= $MIN_SUI SUI (current $SUM_SUI SUI)." return 1 end sleep 6 end end # Send SUI using `transfer-sui` picking the largest coin # Usage: suiwallet_transfer_sui [ns] [app] [gas_budget_mist=10000000] function suiwallet_transfer_sui -a NS APP TO AMT_SUI GAS_BUDGET _need jq python3; or return 1 if test -z "$NS"; set NS crypto; end if test -z "$APP"; set APP wallet-sui-main; end if test -z "$TO" -o -z "$AMT_SUI" echo "Usage: suiwallet_transfer_sui [ns] [app] [gas_budget_mist]" >&2 return 1 end if test -z "$GAS_BUDGET"; set GAS_BUDGET 10000000; end set AMT_MIST (python3 -c 'from decimal import Decimal; import sys; print(int((Decimal(sys.argv[1])*Decimal("1e9")).to_integral_value()))' -- $AMT_SUI) set J (kubectl -n $NS exec deploy/$APP -- sh -lc 'sui client gas --json' 2>/dev/null) set COIN (printf "%s" $J | jq -r ' ([.. | .gasObjects? // empty | .[]?] // []) | sort_by(.balance|tonumber) | last | .coinObjectId // empty ') if test -z "$COIN" echo (set_color red)"No gas coins found to send from."(set_color normal) >&2 return 1 end echo "Using coin: $COIN" kubectl -n $NS exec deploy/$APP -- sh -lc \ (printf 'sui client transfer-sui --to %s --sui-coin-object-id %s --amount %s --gas-budget %s' $TO $COIN $AMT_MIST $GAS_BUDGET) end # Merge all gas coins into the largest one function suiwallet_merge_gas -a NS APP _need jq; or return 1 if test -z "$NS"; set NS crypto; end if test -z "$APP"; set APP wallet-sui-main; end set J (kubectl -n $NS exec deploy/$APP -- sh -lc 'sui client gas --json' 2>/dev/null) set IDS (printf "%s" $J | jq -r '([.. | .gasObjects? // empty | .[]?] // []) | sort_by(.balance|tonumber) | reverse | .[].coinObjectId') set coins $IDS if test (count $coins) -lt 2 echo "Nothing to merge (need >=2 gas coins)." return 0 end set primary $coins[1] for i in (seq 2 (count $coins)) set c $coins[$i] echo "Merging $c -> $primary" kubectl -n $NS exec deploy/$APP -- sh -lc (printf 'sui client merge-coin --primary-coin %s --coin-to-merge %s --gas-budget 10000000' $primary $c) end end # Restore an address from a Secret Recovery Phrase into the running pod # Usage: suiwallet_restore [ns] [app] [alias] [network=mainnet] function suiwallet_restore -a NS APP ALIAS NET _need kubectl jq; or return 1 if test -z "$NS"; set NS crypto; end if test -z "$APP"; set APP wallet-sui-main; end if test -z "$ALIAS"; set ALIAS restored; end if test -z "$NET"; set NET mainnet; end switch $NET case mainnet set RPC https://fullnode.mainnet.sui.io:443 case testnet set RPC https://fullnode.testnet.sui.io:443 case devnet set RPC https://fullnode.devnet.sui.io:443 case '*' echo (set_color red)"Error:"(set_color normal)" NET must be mainnet|testnet|devnet"; return 1 end echo (set_color yellow)"Paste your 12/24-word Sui Secret Recovery Phrase (input hidden):"(set_color normal) read -s MNEMONIC echo if test -z "$MNEMONIC" echo (set_color red)"Error:"(set_color normal)" empty mnemonic."; return 1 end # Ensure env is configured and active kubectl -n $NS exec deploy/$APP -- sh -lc \ (printf 'mkdir -p /home/sui/.sui/sui_config ; sui client new-env --alias %s --rpc %s >/dev/null 2>&1 || true ; sui client switch --env %s >/dev/null 2>&1 || true' $NET $RPC $NET); or return 1 # Import mnemonic into keystore (non-interactive; enter scheme then phrase) # keytool is interactive; we feed "ed25519" then the phrase via stdin. set TMP (mktemp) printf "ed25519\n%s\n" "$MNEMONIC" \ | kubectl -n $NS exec -i deploy/$APP -- sh -lc 'sui keytool import 2>&1' \ | tee $TMP >/dev/null # Create a named alias pointing to one of the imported keys by re-using new-address --recover # (new-address --recover will prompt for phrase; we feed it again to bind an alias) set OUT (mktemp) printf "%s\n" "$MNEMONIC" \ | kubectl -n $NS exec -i deploy/$APP -- sh -lc \ (printf 'sui client new-address ed25519 --alias %s --recover 2>&1' $ALIAS) \ | tee $OUT >/dev/null # Try to extract the resulting address set ADDR (awk -F': ' '/^Address:/ {print $2; exit}' $OUT) if test -z "$ADDR" # fallback: pick the address for the alias from the address table set ADDR (kubectl -n $NS exec deploy/$APP -- sh -lc (printf 'sui client addresses | awk "/ %s\\s*$/ {print \\$NF}" | tail -n1' $ALIAS) 2>/dev/null) end echo echo "Restored alias: $ALIAS" echo "Address: $ADDR" echo (set_color yellow)"IMPORTANT:"(set_color normal)" store your recovery phrase offline. Do NOT keep it in the cluster." # Make it active for the client if test -n "$ADDR" kubectl -n $NS exec deploy/$APP -- sh -lc (printf 'sui client switch --address %s >/dev/null 2>&1 || true' $ADDR) end end function suiwallet_stop -a NS APP if test -z "$NS"; set NS crypto; end if test -z "$APP"; set APP wallet-sui-main; end kubectl -n $NS delete deploy $APP --ignore-not-found echo "Stopped $NS/$APP. PVC retained." end # Print the primary (first) address by alias search function suiwallet_primary_address -a NS APP if test -z "$NS"; set NS crypto; end if test -z "$APP"; set APP wallet-sui-main; end kubectl -n $NS exec deploy/$APP -- sh -lc \ "sui client addresses | awk 'NF{print \$NF}' | head -n1" 2>/dev/null end # Quick overview function suiwallet_overview -a NS APP if test -z "$NS"; set NS crypto; end if test -z "$APP"; set APP wallet-sui-main; end echo "== Sui wallet overview (ns=$NS app=$APP)" kubectl -n $NS exec deploy/$APP -- sh -lc 'echo "-- Addresses:"; sui client addresses || true' kubectl -n $NS exec deploy/$APP -- sh -lc 'echo "-- Gas objects:"; sui client gas 2>/dev/null || true' end # Sui keystore size (for Grafana/Prometheus) function suiwallet_keystore_size_bytes -a NS APP if test -z "$NS"; set NS crypto; end if test -z "$APP"; set APP wallet-sui-main; end set SZ (kubectl -n $NS exec deploy/$APP -- sh -lc \ 'test -e /home/sui/.sui/sui_config/sui.keystore && stat -c %s /home/sui/.sui/sui_config/sui.keystore || echo 0' 2>/dev/null) echo $SZ end # Prometheus-friendly one-liner metric # Example usage: suiwallet_metrics crypto wallet-sui-test mywallet function suiwallet_metrics -a NS APP WALLET_NAME if test -z "$NS"; set NS crypto; end if test -z "$APP"; set APP wallet-sui-main; end if test -z "$WALLET_NAME"; set WALLET_NAME main; end set BYTES (suiwallet_keystore_size_bytes $NS $APP) printf "wallet_size_bytes{chain=\"sui\",namespace=\"%s\",app=\"%s\",wallet=\"%s\"} %s\n" $NS $APP $WALLET_NAME $BYTES end function suiwallet_purge -a NS APP PVC if test -z "$NS"; set NS crypto; end if test -z "$APP"; set APP wallet-sui-main; end if test -z "$PVC"; set PVC $APP; end echo "Type to confirm: PURGE $NS $PVC" read CONFIRM if test "$CONFIRM" != "PURGE $NS $PVC" echo "Aborted." return 1 end kubectl -n $NS delete deploy $APP --ignore-not-found kubectl -n $NS delete pvc $PVC --ignore-not-found --wait=true kubectl -n $NS delete netpol (printf "%s-deny-ingress" $APP) --ignore-not-found >/dev/null 2>&1 echo "Purged. Ensure you have the mnemonic backed up if you ever need to restore." end function suiwallet_help echo "suiwallet_bootstrap # interactive deploy + PVC + optional NP; creates env+address, prints mnemonic" echo "suiwallet_status [ns] [app] # show deploy/pods/pvc/netpol" echo "suiwallet_logs [ns] [app] [N] # tail N (default 200) lines of pod logs" echo "suiwallet_wallet_ls [ns] [app] # list ~/.sui/sui_config inside pod" echo "suiwallet_addresses [ns] [app] # list addresses" echo "suiwallet_gas [ns] [app] # summarize gas coins and total (Mist & SUI)" echo "suiwallet_wait_balance [ns] [app] [timeout_s=300]" echo "suiwallet_transfer_sui [ns] [app] [gas_budget_mist]" echo "suiwallet_merge_gas [ns] [app] # merge many gas coins into one" echo "suiwallet_export_keystore [ns] [app] [dest=./sui.keystore]" echo "suiwallet_metrics [ns] [app] [walletname] # Prometheus line: wallet_size_bytes{chain=\"sui\",...}" echo "suiwallet_stop [ns] [app] # delete deployment (keep PVC)" echo "suiwallet_purge [ns] [app] [pvc] # delete deployment + PVC (danger!)" end