-
-
Save haileyok/67bede50387c3dc57448e716c0a9be6e to your computer and use it in GitHub Desktop.
| import AtpAgent from '@atproto/api' | |
| import { Secp256k1Keypair } from '@atproto/crypto' | |
| import * as ui8 from 'uint8arrays' | |
| const OLD_PDS_URL = 'https://bsky.social' | |
| const NEW_PDS_URL = 'https://pds.haileyok.com' | |
| const CURRENT_HANDLE = 'haileyok.com' | |
| const CURRENT_PASSWORD = '' | |
| const NEW_HANDLE = 'newphone.pds.haileyok.com' | |
| const NEW_ACCOUNT_EMAIL = '' | |
| const NEW_ACCOUNT_PASSWORD = '' | |
| const NEW_PDS_INVITE_CODE = '' | |
| const TOKEN = '' // Use `getEmail` first, and set this to the token that you receive. | |
| // You probably need to bump your blob size if you're a real poster. | |
| // Add to /pds/pds.env | |
| // PDS_BLOB_UPLOAD_LIMIT=1000000000 | |
| // Save the private key that's output below. If you trust your PDS opsec more, add the new key | |
| // to the end (push). | |
| // If you trust your own password manager more, put it in front [newKey, ...keys] | |
| const getEmail = async () => { | |
| const oldAgent = new AtpAgent.AtpAgent({ service: OLD_PDS_URL }) | |
| await oldAgent.login({ | |
| identifier: CURRENT_HANDLE, | |
| password: CURRENT_PASSWORD, | |
| }) | |
| await oldAgent.com.atproto.identity.requestPlcOperationSignature() | |
| } | |
| const migrateAccount = async () => { | |
| const oldAgent = new AtpAgent.AtpAgent({ service: OLD_PDS_URL }) | |
| const newAgent = new AtpAgent.AtpAgent({ service: NEW_PDS_URL }) | |
| await oldAgent.login({ | |
| identifier: CURRENT_HANDLE, | |
| password: CURRENT_PASSWORD, | |
| }) | |
| const accountDid = oldAgent.session?.did | |
| if (!accountDid) { | |
| throw new Error('Could not get DID for old account') | |
| } | |
| // Create account | |
| // ------------------ | |
| const describeRes = await newAgent.api.com.atproto.server.describeServer() | |
| const newServerDid = describeRes.data.did | |
| const serviceJwtRes = await oldAgent.com.atproto.server.getServiceAuth({ | |
| aud: newServerDid, | |
| }) | |
| const serviceJwt = serviceJwtRes.data.token | |
| await newAgent.api.com.atproto.server.createAccount( | |
| { | |
| handle: NEW_HANDLE, | |
| email: NEW_ACCOUNT_EMAIL, | |
| password: NEW_ACCOUNT_PASSWORD, | |
| did: accountDid, | |
| inviteCode: NEW_PDS_INVITE_CODE, | |
| }, | |
| { | |
| headers: { authorization: `Bearer ${serviceJwt}` }, | |
| encoding: 'application/json', | |
| }, | |
| ) | |
| await newAgent.login({ | |
| identifier: NEW_HANDLE, | |
| password: NEW_ACCOUNT_PASSWORD, | |
| }) | |
| // Migrate Data | |
| // ------------------ | |
| const repoRes = await oldAgent.com.atproto.sync.getRepo({ did: accountDid }) | |
| await newAgent.com.atproto.repo.importRepo(repoRes.data, { | |
| encoding: 'application/vnd.ipld.car', | |
| }) | |
| let blobCursor = undefined | |
| do { | |
| const listedBlobs = await oldAgent.com.atproto.sync.listBlobs({ | |
| did: accountDid, | |
| cursor: blobCursor, | |
| }) | |
| for (const cid of listedBlobs.data.cids) { | |
| const blobRes = await oldAgent.com.atproto.sync.getBlob({ | |
| did: accountDid, | |
| cid, | |
| }) | |
| await newAgent.com.atproto.repo.uploadBlob(blobRes.data, { | |
| encoding: blobRes.headers['content-type'], | |
| }) | |
| } | |
| blobCursor = listedBlobs.data.cursor | |
| } while (blobCursor) | |
| const prefs = await oldAgent.api.app.bsky.actor.getPreferences() | |
| await newAgent.api.app.bsky.actor.putPreferences(prefs.data) | |
| // Migrate Identity | |
| // ------------------ | |
| const getDidCredentials = | |
| await newAgent.com.atproto.identity.getRecommendedDidCredentials() | |
| console.log(JSON.stringify(getDidCredentials)) | |
| // @NOTE, this token will need to come from the email from the previous step | |
| const keypair = await Secp256k1Keypair.create({ exportable: true }) | |
| const privateKey = await keypair.export() | |
| const rotationKey = keypair.did() | |
| console.log('SAVE THIS PRIVATE KEY!!!') | |
| console.log('rotation key: ', rotationKey) | |
| console.log('private key: ', ui8.toString(privateKey, 'hex')) | |
| if(getDidCredentials.data.rotationKeys == null) { | |
| console.log("Nope") | |
| return | |
| } | |
| getDidCredentials.data.rotationKeys = [rotationKey, ...getDidCredentials.data.rotationKeys] | |
| const plcOp = await oldAgent.com.atproto.identity.signPlcOperation({ | |
| token: TOKEN, | |
| ...getDidCredentials.data, | |
| }) | |
| await newAgent.com.atproto.identity.submitPlcOperation({ | |
| operation: plcOp.data.operation, | |
| }) | |
| // Finalize Migration | |
| // ------------------ | |
| await newAgent.com.atproto.server.activateAccount() | |
| await oldAgent.com.atproto.server.deactivateAccount({}) | |
| } | |
| // STEP ONE IS HERE | |
| getEmail() | |
| // STEP TWO IS HERE. COMMENT OUT THE ABOVE AND UNCOMMENT THIS ONCE YOU HAVE YOUR CODE | |
| // migrateAccount() |
fixed it!
Needs a couple of adjustments.
I changed import AtpAgent from '@atproto/api' to import { AtpAgent } from '@atproto/api' then adjusted all of the constructor references from new AtpAgent.AtpAgent( to new AtpAgent(.
I removed all the .api parts of calls, for example oldAgent.api.app.bsky.actor.getPreferences() changed to oldAgent.app.bsky.actor.getPreferences().
The last one is oldAgent.com.atproto.server.getServiceAuth apparently needs lxm provided with value com.atproto.server.createAccount, see error: XRPCError: missing jwt lexicon method ("lxm"). must match: com.atproto.server.createAccount.
Thanks for the script! It worked really well on a test account, working up the courage to throw the main account off the edge. 😮💨
oh yea, there's been a lot of changes to the api package since i made this...should probably update it lol
I don't think line 112 is supposed to be there lol