import { storeJson } from '../fileModels/storeJson' 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: 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, } } 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, } }, )