Last active
October 5, 2025 01:01
-
-
Save teidesu/1424bd8055e5a4bb021a055d84d81301 to your computer and use it in GitHub Desktop.
script to recover funds (ton and usdt) from a ledger-managed toncoin wallet. test with a small amount first!!!
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
| /* eslint-disable no-console */ | |
| import { getHttpEndpoint } from '@orbs-network/ton-access' | |
| import { keyPairFromSeed } from '@ton/crypto' | |
| import { Address, beginCell, external, internal, storeMessage, toNano, TonClient, WalletContractV4 } from '@ton/ton' | |
| import { mnemonicToSeed as mnemonicToSeedBip39 } from 'bip39' | |
| import { deriveED25519Path } from './ed25519.ts' // https://github.com/tonkeeper/tonkeeper-web/blob/0b47a3d6523065d53d2d32c4a82a644bad797fb0/packages/core/src/service/ed25519.ts | |
| // for whatever reason ledger uses a non-standard bip39 path lol. | |
| // and since there aren't any gui wallets (i know of) that support custom derivation paths, i had to resort to this | |
| // thanks https://www.reddit.com/r/ledgerwallet/comments/1g8kri0/comment/lt4vf9f/ | |
| // p.s. you might need to tweak the path further for sub-wallets | |
| const mnemonic = 'pumpkin ...' | |
| const bip39Path = "m/44'/607'/0'/0'/0'/0'" | |
| const toAddress = Address.parse('UQBt....') | |
| const amount = 1000n * 1000000n // 1000 USDT | |
| // how much ton to use for the transaction. some of it will be used as a fee, the rest is sent to the recipient | |
| const tonAmount = '0.5' | |
| const endpoint = await getHttpEndpoint({ network: 'mainnet' }) | |
| const ton = new TonClient({ endpoint }) | |
| const seed = await mnemonicToSeedBip39(mnemonic) | |
| const seedContainer = deriveED25519Path(bip39Path, seed.toString('hex')) | |
| const keyPair = keyPairFromSeed(seedContainer.key) | |
| const wallet = WalletContractV4.create({ | |
| publicKey: keyPair.publicKey, | |
| workchain: 0, | |
| }) | |
| async function getUserJettonWalletAddress(userAddress: Address, jettonMasterAddress: Address) { | |
| const userAddressCell = beginCell().storeAddress(userAddress).endCell() | |
| const response = await ton.runMethod(jettonMasterAddress, 'get_wallet_address', [ | |
| { type: 'slice', cell: userAddressCell }, | |
| ]) | |
| return response.stack.readAddress() | |
| } | |
| const myAddress = wallet.address.toString({ bounceable: false }) | |
| console.log(myAddress) | |
| console.log('verify the address matches before proceeding!') | |
| process.exit(1) | |
| // thanks https://github.com/ton-org/ton/issues/44#issuecomment-2193904906 | |
| const contract = ton.open(wallet) | |
| const balance = await contract.getBalance() | |
| const seqno = await contract.getSeqno() | |
| console.log(balance, seqno) | |
| const jettonWalletAddress = await getUserJettonWalletAddress(Address.parse(myAddress), Address.parse('EQCxE6mUtQJKFnGfaROTKOt1lZbDiiX1kCixRv7Nw2Id_sDs')) // usdt contract | |
| console.log(jettonWalletAddress) | |
| const messageBody = beginCell() | |
| .storeUint(0x0F8A7EA5, 32) // opcode for jetton transfer | |
| .storeUint(0, 64) // query id | |
| .storeCoins(amount) | |
| .storeAddress(toAddress) | |
| .storeAddress(toAddress) // response destination | |
| .storeBit(0) // no custom payload | |
| .storeCoins(0) // forward amount - if > 0, will send notification message | |
| .storeBit(0) // we store forwardPayload as a reference, set 1 and uncomment next line for have a comment | |
| // .storeRef(forwardPayload) | |
| .endCell() | |
| const internalMessage = internal({ | |
| to: jettonWalletAddress, | |
| value: toNano(tonAmount), | |
| bounce: true, | |
| body: messageBody, | |
| }) | |
| const body = wallet.createTransfer({ | |
| seqno, | |
| secretKey: keyPair.secretKey, | |
| messages: [internalMessage], | |
| }) | |
| const externalMessage = external({ | |
| to: myAddress, | |
| body, | |
| }) | |
| const externalMessageCell = beginCell().store(storeMessage(externalMessage)).endCell() | |
| const signedTransaction = externalMessageCell.toBoc() | |
| const hash = externalMessageCell.hash().toString('hex') | |
| console.log('tx hash:', hash) | |
| await ton.sendFile(signedTransaction) |
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment