import { WalletClient, PushDrop, Transaction, Beef, Utils, Random, BigNumber } from '@bsv/sdk'
import { MessageBoxClient } from '@bsv/sdk'
export function SendTokens({ wallet, messageBoxClient }: { wallet: WalletClient, messageBoxClient: MessageBoxClient }) {
const handleSend = async (tokenId: string, sendAmount: number, recipient: string, label: string) => {
const token = new PushDrop(wallet)
const protocolID: [0 | 1 | 2, string] = [2, 'mytoken']
const recipientKeyID = Utils.toBase64(Random(8))
const changeKeyID = Utils.toBase64(Random(8))
// 1. Collect UTXOs
const outputs = await wallet.listOutputs({
basket: 'mytokens',
include: 'entire transactions',
includeCustomInstructions: true
})
const beef = new Beef()
let inAmountTotal = 0
const inputs = []
const customInstructions: string[] = []
for (const output of outputs.outputs) {
if (inAmountTotal >= sendAmount) break
const [txid, vout] = output.outpoint.split('.')
const tx = Transaction.fromBEEF(Utils.toArray(output.beef, 'base64'))
const script = tx.outputs[Number(vout)].lockingScript
const decoded = PushDrop.decode(script)
let outTokenId = Utils.toUTF8(decoded.fields[0])
if (outTokenId === '___mint___') outTokenId = output.outpoint
if (outTokenId !== tokenId) continue
const amount = Number(new Utils.Reader(decoded.fields[1]).readUInt64LEBn())
inAmountTotal += amount
beef.mergeBeef(tx.toBEEF())
inputs.push({ outpoint: output.outpoint, unlockingScriptLength: 73 })
customInstructions.push(output.customInstructions!)
}
// 2. Create recipient output
const recipientFields = [
Utils.toArray(tokenId, 'utf8'),
new Utils.Writer().writeUInt64LEBn(new BigNumber(sendAmount)).toArray(),
Utils.toArray(JSON.stringify([{ name: 'label', value: label }]), 'utf8')
]
const recipientScript = await token.lock(recipientFields, protocolID, recipientKeyID, recipient, false, false)
const txOutputs = [{ satoshis: 1, lockingScript: recipientScript.toHex() }]
// 3. Create change output (if needed)
const changeAmount = inAmountTotal - sendAmount
if (changeAmount > 0) {
const changeFields = [
Utils.toArray(tokenId, 'utf8'),
new Utils.Writer().writeUInt64LEBn(new BigNumber(changeAmount)).toArray(),
Utils.toArray(JSON.stringify([{ name: 'label', value: label }]), 'utf8')
]
const changeScript = await token.lock(changeFields, protocolID, changeKeyID, 'self', true, false)
txOutputs.push({
satoshis: 1,
lockingScript: changeScript.toHex(),
basket: 'mytokens',
customInstructions: JSON.stringify({ protocolID, keyID: changeKeyID, counterparty: 'self' }),
tags: ['mytokens', 'change']
})
}
// 4. Phase 1: Create transaction
const response = await wallet.createAction({
description: `Send ${sendAmount} tokens`,
inputBEEF: beef.toBinary(),
inputs,
outputs: txOutputs,
options: { randomizeOutputs: false }
})
// 5. Phase 2: Sign token inputs manually
const txToSign = Transaction.fromBEEF(response.signableTransaction!.tx)
for (let i = 0; i < inputs.length; i++) {
const cu = JSON.parse(customInstructions[i])
txToSign.inputs[i].unlockingScriptTemplate = new PushDrop(wallet).unlock(
cu.protocolID,
cu.keyID,
cu.counterparty
)
}
await txToSign.sign()
// 6. Phase 3: Submit signatures
const spends: Record<string, { unlockingScript: string }> = {}
for (let i = 0; i < inputs.length; i++) {
spends[String(i)] = { unlockingScript: txToSign.inputs[i].unlockingScript!.toHex() }
}
const finalTx = await wallet.signAction({
reference: response.signableTransaction!.reference,
spends
})
// 7. Send via MessageBox
const { publicKey: sender } = await wallet.getPublicKey({ identityKey: true })
await messageBoxClient.sendMessage({
recipient,
messageBox: 'mytokenpayments',
body: { tokenId, amount: sendAmount, transaction: finalTx.tx, keyID: recipientKeyID, protocolID, sender }
})
}
// ... UI code ...
}