Files
spaces-startos/startos/actions/importWallet.ts
T
spacesopsandCursor 073344d4d1
Build Service / BuildPackage (push) Canceled after 0s
0.3.0:1
Group Actions and Config by category (Space-CLI, Bitcoin, Spaced,
Subspaces, Certrelay, Nacho) and build x86 + arm only.

Co-authored-by: Cursor <cursoragent@cursor.com>
2026-08-24 03:34:34 -04:00

149 lines
4.0 KiB
TypeScript

import { storeJson } from '../fileModels/storeJson'
import { actionGroups } from '../actionGroups'
import { i18n } from '../i18n'
import { sdk } from '../sdk'
import { dataDir, SPACED_CHAIN } from '../utils'
const { InputSpec, Value } = sdk
const BACKUP_DIR = `${dataDir}/${SPACED_CHAIN}/wallets_backup`
const BACKUP_PATH = `${BACKUP_DIR}/default.json`
const WALLET_DIR = `${dataDir}/${SPACED_CHAIN}/wallets/default`
const inputSpec = InputSpec.of({
walletJson: Value.textarea({
name: i18n('Wallet JSON'),
description: i18n('Paste the JSON contents of a default.json wallet export.'),
warning: null,
footnote: null,
default: null,
required: true,
minLength: 1,
maxLength: null,
placeholder: '{...}',
}),
})
export const importWallet = sdk.Action.withInput(
// id
'import-wallet',
// metadata
async ({ effects }) => ({
name: i18n('Import Wallet'),
description: i18n(
'Restore a previously-exported default.json wallet into spaced.',
),
warning: i18n(
'Existing /data/mainnet/wallets_backup/default.json and /data/mainnet/wallets/default will be renamed with .bakNNN suffixes before the import. The active spaced daemon will load the imported wallet on success.',
),
allowedStatuses: 'only-running',
group: actionGroups.spaced,
visibility: 'enabled',
}),
// input
inputSpec,
// prefill
async ({ effects }) => {},
// run
async ({ effects, input }) => {
// Validate JSON before touching disk.
try {
JSON.parse(input.walletJson)
} catch (e) {
return {
version: '1',
title: i18n('Failure'),
message: i18n('Could not parse wallet JSON: ${error}', {
error: (e as Error).message,
}),
result: null,
}
}
const spacedAuth = await storeJson.read((s) => s.spacedAuth).once()
if (!spacedAuth) {
return {
version: '1',
title: i18n('Failure'),
message: i18n('Could not import wallet: ${error}', {
error: 'spacedAuth missing from store.json',
}),
result: null,
}
}
// base64-encode so shell quoting can never break on user content.
const b64 = Buffer.from(input.walletJson, 'utf8').toString('base64')
const cliAuth = `--rpc-user '${spacedAuth.username}' --rpc-password '${spacedAuth.password}'`
const script = `set -e
mkdir -p '${BACKUP_DIR}'
# Rotate /data/mainnet/wallets_backup/default.json -> .bakNNN if exists.
BACKUP='${BACKUP_PATH}'
if [ -e "$BACKUP" ]; then
i=0
while [ -e "$(printf '%s.bak%03d' "$BACKUP" "$i")" ]; do
i=$((i+1))
done
mv "$BACKUP" "$(printf '%s.bak%03d' "$BACKUP" "$i")"
fi
# Write fresh default.json from base64 payload.
printf '%s' '${b64}' | base64 -d > "$BACKUP"
# Rotate /data/mainnet/wallets/default folder -> .bakNNN if exists.
WDIR='${WALLET_DIR}'
if [ -d "$WDIR" ]; then
j=0
while [ -d "$(printf '%s.bak%03d' "$WDIR" "$j")" ]; do
j=$((j+1))
done
mv "$WDIR" "$(printf '%s.bak%03d' "$WDIR" "$j")"
fi
# Import + load via spaced RPC.
/root/.cargo/bin/space-cli --chain ${SPACED_CHAIN} ${cliAuth} importwallet "$BACKUP"
/root/.cargo/bin/space-cli --chain ${SPACED_CHAIN} ${cliAuth} loadwallet
`
const res = await sdk.SubContainer.withTemp(
effects,
{ imageId: 'spaces' },
sdk.Mounts.of().mountVolume({
volumeId: 'main',
subpath: null,
mountpoint: dataDir,
readonly: false,
}),
'spaces-import-wallet',
(subc) => subc.exec(['bash', '-c', script], { user: 'root' }),
)
if (res.exitCode !== 0) {
const stderr = (res.stderr ?? '').toString()
const stdout = (res.stdout ?? '').toString()
return {
version: '1',
title: i18n('Failure'),
message: i18n('Could not import wallet: ${error}', {
error: stderr || stdout || `exit ${res.exitCode}`,
}),
result: null,
}
}
return {
version: '1',
title: i18n('Success'),
message: i18n('Wallet imported and loaded.'),
result: null,
}
},
)