ftUtxos = tokenInputArray; const defaultFtUtxo = tokenInputArray[0]; const ftUtxoTx = new mvc.Transaction(defaultFtUtxo.satotxInfo.txHex); const tokenLockingScript = ftUtxoTx.outputs[defaultFtUtxo.outputIndex].script; //create transferCheck contract let tokenTransferCheckContract = tokenTransferCheck_1.TokenTransferCheckFactory.createContract(tokenTransferType); tokenTransferCheckContract.setFormatedDataPart({ nSenders: tokenInputArray.length, receiverTokenAmountArray: tokenOutputArray.map((v) => v.tokenAmount), receiverArray: tokenOutputArray.map((v) => v.address), nReceivers: tokenOutputArray.length, tokenCodeHash: (0, scryptlib_1.toHex)(ftProto.getContractCodeHash(tokenLockingScript.toBuffer())), tokenID: (0, scryptlib_1.toHex)(ftProto.getTokenID(tokenLockingScript.toBuffer())), }); const transferCheckTxComposer = new tx_composer_1.TxComposer(); // add utxo to provide fee for transfer check transaction const transferCheck_p2pkhInputIndexs = utxos.map((utxo) => { const inputIndex = transferCheckTxComposer.appendP2PKHInput(utxo); transferCheckTxComposer.addSigHashInfo({ inputIndex, address: utxo.address.toString(), sighashType: exports.sighashType, contractType: utils_1.CONTRACT_TYPE.P2PKH, }); return inputIndex; }); // add outputs for transfer check transaction const transferCheckOutputIndex = transferCheckTxComposer.appendOutput({ lockingScript: tokenTransferCheckContract.lockingScript, satoshis: this.getDustThreshold(tokenTransferCheckContract.lockingScript.toBuffer().length), }); // add change output for transfer check transaction let changeOutputIndex = transferCheckTxComposer.appendChangeOutput(middleChangeAddress, this.feeb); // unlock the fee utxo for transfer check transaction let unsignSigPlaceHolderSize = 0; if (utxoPrivateKeys && utxoPrivateKeys.length > 0) { transferCheck_p2pkhInputIndexs.forEach((inputIndex) => { let privateKey = utxoPrivateKeys.splice(0, 1)[0]; transferCheckTxComposer.unlockP2PKHInput(privateKey, inputIndex); }); } else { //To supplement the size calculation when unsigned transferCheck_p2pkhInputIndexs.forEach((v) => { unsignSigPlaceHolderSize += utils_1.P2PKH_UNLOCK_SIZE; }); //Each ftUtxo need to unlock with the size unsignSigPlaceHolderSize = unsignSigPlaceHolderSize * ftUtxos.length; } // change utxo to the output of transfer check transaction utxos = [ { txId: transferCheckTxComposer.getTxId(), satoshis: transferCheckTxComposer.getOutput(changeOutputIndex).satoshis, outputIndex: changeOutputIndex, address: middleChangeAddress, }, ]; utxoPrivateKeys = utxos.map((v) => middlePrivateKey).filter((v) => v); // transfer check utxo in order to unlock the token utxo let transferCheckUtxo = { txId: transferCheckTxComposer.getTxId(), outputIndex: transferCheckOutputIndex, satoshis: transferCheckTxComposer.getOutput(transferCheckOutputIndex).satoshis, lockingScript: transferCheckTxComposer.getOutput(transferCheckOutputIndex).script, }; // build token transfer transaction const txComposer = new tx_composer_1.TxComposer(); let prevouts = new Prevouts_1.Prevouts(); // concat the token addresses and amounts for check let inputTokenScript; let inputTokenAmountArray = buffer_1.Buffer.alloc(0); let inputTokenAddressArray = buffer_1.Buffer.alloc(0); const ftUtxoInputIndexs = ftUtxos.map((ftUtxo) => { const inputIndex = txComposer.appendInput(ftUtxo); prevouts.addVout(ftUtxo.txId, ftUtxo.outputIndex); txComposer.addSigHashInfo({ inputIndex, address: ftUtxo.tokenAddress.toString(), sighashType: exports.sighashType, contractType: utils_1.CONTRACT_TYPE.BCP02_TOKEN, }); inputTokenScript = ftUtxo.lockingScript; inputTokenAddressArray = buffer_1.Buffer.concat([inputTokenAddressArray, ftUtxo.tokenAddress.hashBuffer]); inputTokenAmountArray = buffer_1.Buffer.concat([ inputTokenAmountArray, ftUtxo.tokenAmount.toBuffer({ endian: 'little', size: 8, }), ]); return inputIndex; }); //tx addInput utxo const p2pkhInputIndexs = utxos.map((utxo) => { const inputIndex = txComposer.appendP2PKHInput(utxo); prevouts.addVout(utxo.txId, utxo.outputIndex); txComposer.addSigHashInfo({ inputIndex, address: utxo.address.toString(), sighashType: exports.sighashType, contractType: utils_1.CONTRACT_TYPE.P2PKH, }); return inputIndex; }); //添加transferCheck为最后一个输入 const transferCheckInputIndex = txComposer.appendInput(transferCheckUtxo); prevouts.addVout(transferCheckUtxo.txId, transferCheckUtxo.outputIndex); // concat the token addresses and amounts for check let receiverArray = buffer_1.Buffer.alloc(0); let receiverTokenAmountArray = buffer_1.Buffer.alloc(0); let outputSatoshiArray = buffer_1.Buffer.alloc(0); const tokenOutputLen = tokenOutputArray.length; for (let i = 0; i < tokenOutputLen; i++) { const tokenOutput = tokenOutputArray[i]; const address = tokenOutput.address; const outputTokenAmount = tokenOutput.tokenAmount; const lockingScriptBuf = ftProto.getNewTokenScript(inputTokenScript.toBuffer(), address.hashBuffer, outputTokenAmount); let outputIndex = txComposer.appendOutput({ lockingScript: mvc.Script.fromBuffer(lockingScriptBuf), satoshis: this.getDustThreshold(lockingScriptBuf.length), }); receiverArray = buffer_1.Buffer.concat([receiverArray, address.hashBuffer]); const tokenBuf = outputTokenAmount.toBuffer({ endian: 'little', size: 8, }); receiverTokenAmountArray = buffer_1.Buffer.concat([receiverTokenAmountArray, tokenBuf]); const satoshiBuf = BN.fromNumber(txComposer.getOutput(outputIndex).satoshis).toBuffer({ endian: 'little', size: 8, }); outputSatoshiArray = buffer_1.Buffer.concat([outputSatoshiArray, satoshiBuf]); } //tx addOutput OpReturn let opreturnScriptHex = ''; if (opreturnData) { const opreturnOutputIndex = txComposer.appendOpReturnOutput(opreturnData); opreturnScriptHex = txComposer.getOutput(opreturnOutputIndex).script.toHex(); } //The first round of calculations get the exact size of the final transaction, and then change again //Due to the change, the script needs to be unlocked again in the second round //let the fee be exact in the second round for (let c = 0; c < 2; c++) { txComposer.clearChangeOutput(); const changeOutputIndex = txComposer.appendChangeOutput(changeAddress, this.feeb, unsignSigPlaceHolderSize); let tokenTxHeaderArray = buffer_1.Buffer.alloc(0); let tokenTxHashProofArray = buffer_1.Buffer.alloc(0); let tokenSatoshiBytesArray = buffer_1.Buffer.alloc(0); // process each ft utxo input, unlock the token utxo ftUtxoInputIndexs.forEach((inputIndex, idx) => { let ftUtxo = ftUtxos[idx]; let senderPrivateKey = ftPrivateKeys[idx]; let dataPartObj = ftProto.parseDataPart(ftUtxo.lockingScript.toBuffer()); const dataPart = ftProto.newDataPart(dataPartObj); const tokenContract = token_1.TokenFactory.createContract(this.transferCheckCodeHashArray, this.unlockContractCodeHashArray, version); const amountCheckTx = transferCheckTxComposer.getTx(); const amountCheckOutputIndex = 0; const amountCheckTxOutputProofInfo = new TxOutputProof(TokenUtil.getTxOutputProof(amountCheckTx, amountCheckOutputIndex)); const amountCheckScriptBuf = amountCheckTx.outputs[amountCheckOutputIndex].script.toBuffer(); const prevTokenInputIndex = ftUtxo.prevTokenInputIndex; // ??? const prevTokenAddress = new scryptlib_1.Bytes((0, scryptlib_1.toHex)(ftUtxo.preTokenAddress.hashBuffer)); // const prevTokenAddress = new Bytes(TokenProto.getTokenAddress(scriptBuf).toString('hex')) const prevTokenAmount = BigInt(ftUtxo.preTokenAmount.toString(10)); // const prevTokenAmount = TokenProto.getTokenAmount(scriptBuf) const tokenTx = new mvc.Transaction(ftUtxo.satotxInfo.txHex); const inputRes = TokenUtil.getTxInputProof(tokenTx, prevTokenInputIndex); const tokenTxInputProof = new TxInputProof(inputRes[0]); const tokenTxHeader = inputRes[1]; // TODO: const prevTokenTxOutputProof = new TxOutputProof(TokenUtil.getTxOutputProof(ftUtxo.prevTokenTx, ftUtxo.prevTokenOutputIndex)); const tokenTxInfoHex = TokenUtil.getTxInfoHex(tokenTx, ftUtxo.outputIndex);