// this code sucks // don't blame me if this kills your plc identity <3 // - kris import { ACCESS_JWT, EMAIL, HANDLE, NEW_PDS, OLD_PDS, PASSWORD, REFRESH_JWT } from "./secrets" import { AtpAgent } from "@atproto/api"; import { Secp256k1Keypair } from '@atproto/crypto' import { sleepSync } from "bun"; import { writeFileSync } from "fs"; import * as ui8 from 'uint8arrays' const oldAgent = new AtpAgent({ service: OLD_PDS }) const newAgent = new AtpAgent({ service: NEW_PDS }) console.log(`logging in on old pds - ${OLD_PDS}`) await oldAgent.login({ identifier: EMAIL, password: PASSWORD, }); const accountDid = oldAgent.session?.did if (!accountDid) { throw new Error('wtf?') } console.log(`logging in on new pds - ${NEW_PDS}`) newAgent.resumeSession({ accessJwt: ACCESS_JWT, refreshJwt: REFRESH_JWT, handle: HANDLE, did: accountDid, active: false, status: "deactivated" }) // try { // await newAgent.login({ // identifier: EMAIL, // password: PASSWORD // }) // } catch { // console.log(`ok nvm it needs ur email`) // sleepSync(1000) // await newAgent.login({ // identifier: EMAIL, // password: PASSWORD, // authFactorToken: prompt("enter code:")! // }) // } console.log("get repo"); const repoRes = await oldAgent.com.atproto.sync.getRepo({ did: accountDid }); console.log("import repo"); await newAgent.com.atproto.repo.importRepo(repoRes.data, { encoding: 'application/vnd.ipld.car', }) console.log("get missing blobs"); let missingBlobs: string[] = []; console.log("list blobs new pds"); await (async () => { let blobCursor: string | undefined = undefined do { const blobs = await newAgent.com.atproto.repo.listMissingBlobs({ cursor: blobCursor, }); missingBlobs = [...blobs.data.blobs.map(a=>a.cid), ...missingBlobs] blobCursor = blobs.data.cursor } while (blobCursor) })() console.log("missing blobs:", missingBlobs); for (const blob of missingBlobs) { console.log("get blob", blob); const blobRes = await oldAgent.com.atproto.sync.getBlob({ did: accountDid, cid: blob, }) console.log("upload blob", blob); await newAgent.com.atproto.repo.uploadBlob(blobRes.data, { encoding: blobRes.headers['content-type'], }) } console.log("sync prefs"); const prefs = await oldAgent.app.bsky.actor.getPreferences() await newAgent.app.bsky.actor.putPreferences(prefs.data) console.log("create secp256k1 keypair") const recoveryKey = await Secp256k1Keypair.create({ exportable: true }) const privateKeyBytes = await recoveryKey.export() const privateKey = ui8.toString(privateKeyBytes, 'hex') writeFileSync("DID_PLC_RECOVERY_KEY",privateKeyBytes) writeFileSync("DID_PLC_RECOVERY_KEY.hex",privateKey) console.log("saved the private keys, do not share!!!") const getDidCredentials = await newAgent.com.atproto.identity.getRecommendedDidCredentials() const rotationKeys = getDidCredentials.data.rotationKeys ?? [] if (!rotationKeys) { throw new Error('no rotation key, wtf???') } console.log("new pds's recommended did creds", rotationKeys) const credentials = { ...getDidCredentials.data, rotationKeys: [recoveryKey.did(), ...rotationKeys], } console.log("new creds", credentials) console.log("requesting key") await oldAgent.com.atproto.identity.requestPlcOperationSignature() const TOKEN = prompt("enter key in email:") console.log("signing plc op") const plcOp = await oldAgent.com.atproto.identity.signPlcOperation({ token: TOKEN!, ...credentials, }) console.log( `❗ Your private recovery key is: ${privateKey}. Please store this in a secure location! ❗`, ) console.log("Signed PLC operation",plcOp.data) console.log("!!! Are you 100% sure you want to migrate your new PLC identity?") const yN = prompt("Migrate PLC identity? [yN]"); if (!yN || !yN.toLowerCase().startsWith("y")) { process.exit(1); } const yN2 = prompt("Are you sure? [yN]"); if (!yN2 || !yN2.toLowerCase().startsWith("y")) { process.exit(1); } const yN3 = prompt("THERE IS NO GOING BACK AFTER THIS. Are you really really sure? [yN]"); if (!yN3 || !yN3.toLowerCase().startsWith("y")) { process.exit(1); } console.log("You have 5 seconds to CTRL+C to abort!") sleepSync(1000) console.log("You have 4 seconds to CTRL+C to abort!") sleepSync(1000) console.log("You have 3 seconds to CTRL+C to abort!") sleepSync(1000) console.log("You have 2 seconds to CTRL+C to abort!") sleepSync(1000) console.log("You have 1 second to CTRL+C to abort!") sleepSync(1000) const yN4 = prompt("Final chance, are you actually sure? [yN]"); if (!yN4 || !yN4.toLowerCase().startsWith("y")) { process.exit(1); } console.log("Welp, no going back now!") console.log("submitting plc operation") await newAgent.com.atproto.identity.submitPlcOperation({ operation: plcOp.data.operation, }) console.log("activate new pds acc") await newAgent.com.atproto.server.activateAccount() console.log("deactivate new old acc") await oldAgent.com.atproto.server.deactivateAccount({}) console.log("successfully migrated pds! DO NOT SHARE THE SECRET KEYS, YOU HAVE BEEN WARNED.")