Files
spaces-startos/startos/actions/importWallet.ts
T
spacesops 2cdc4a5f34
Build Service / BuildPackage (push) Has been cancelled
imp/exp/show/cookie
2026-05-14 18:27:20 -04:00

134 lines
3.6 KiB
TypeScript

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 COOKIE_PATH = `${dataDir}/${SPACED_CHAIN}/.cookie`
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: null,
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,
}
}
// base64-encode so shell quoting can never break on user content.
const b64 = Buffer.from(input.walletJson, 'utf8').toString('base64')
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} --rpc-cookie '${COOKIE_PATH}' importwallet "$BACKUP"
/root/.cargo/bin/space-cli --chain ${SPACED_CHAIN} --rpc-cookie '${COOKIE_PATH}' 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,
}
},
)