diff --git a/package.json b/package.json index 3754a54..9d6afcb 100644 --- a/package.json +++ b/package.json @@ -7,6 +7,7 @@ "type": "module", "scripts": { "multisig-plan": "node --no-warnings=ExperimentalWarning --loader ts-node/esm ./scripts/create-multisig-deployment-plan.ts", + "multisig-stx-transfer": "node --no-warnings=ExperimentalWarning --loader ts-node/esm ./scripts/create-multisig-stx-transfer-plan.ts", "multisig-sign": "node --no-warnings=ExperimentalWarning --loader ts-node/esm ./scripts/sign-multisig-deployment-plan.ts", "multisig-broadcast": "node --no-warnings=ExperimentalWarning --loader ts-node/esm ./scripts/broadcast-multisig-deployment-plan.ts", "multisig-analyse": "node --no-warnings=ExperimentalWarning --loader ts-node/esm ./scripts/analyse-multisig-deployment-plan.ts", @@ -22,7 +23,7 @@ "license": "BSL", "prettier": "@stacks/prettier-config", "dependencies": { - "@hirosystems/clarinet-sdk": "2.4.0-beta2", + "@hirosystems/clarinet-sdk": "2.4.0-beta3", "@stacks/stacking": "6.11.4-pr.36558cf.0", "@stacks/prettier-config": "^0.0.10", "@stacks/transactions": "^6.9.0", diff --git a/scripts/create-multisig-stx-transfer-plan.ts b/scripts/create-multisig-stx-transfer-plan.ts new file mode 100755 index 0000000..67eb05f --- /dev/null +++ b/scripts/create-multisig-stx-transfer-plan.ts @@ -0,0 +1,97 @@ +// SPDX-License-Identifier: BUSL-1.1 + +import { bytesToHex } from '@stacks/common'; +import { + Address, + AddressHashMode, + AnchorMode, + StacksPublicKey, + StacksTransaction, + addressToString, + createMultiSigSpendingCondition, + makeUnsignedSTXTokenTransfer, +} from '@stacks/transactions'; +import fs from 'fs'; +import { getNetwork, getStacksAddress, getStacksPubkeys } from './config.ts'; +import { assertSigner, planFile, verboseLog } from './utils.ts'; + +const lisaDaoContractName = 'lisa-dao'; + +const network = getNetwork(); +const address = getStacksAddress(); +const pubKeys = getStacksPubkeys(); +let nonce = 27; +const feeMultiplier = 100; // transaction bytes * feeMultiplier +const feeAddition = 1; // add a flat amount on top +const feeCap = 0; //15 * 1000000; // 15 STX + +const multisigSpendConditionByteLength = 66; // don't change + +let tempTotalFee = 0n; + +verboseLog(`Using address ${addressToString(address)}`); + +async function createMultisigStxTransaction( + amount: bigint, + recipient: string, + feeMultiplier: number, + nonce: number, + numSignatures: number, + pubkeys: StacksPublicKey[], + signer: Address +) { + const publicKeys = pubkeys.map(pk => bytesToHex(pk.data)); + const tx = await makeUnsignedSTXTokenTransfer({ + numSignatures, + publicKeys, + recipient, + fee: 1, + nonce, + network, + amount, + anchorMode: AnchorMode.OnChainOnly, + }); + // makeUnsignedContractCall() forces a AddressHashMode.SerializeP2SH spending condition, so we construct it manually + // and replace it. + tx.auth.spendingCondition = createMultiSigSpendingCondition( + AddressHashMode.SerializeP2WSH, + numSignatures, + publicKeys, + nonce, + 1 + ); + assertSigner(tx.auth.spendingCondition, signer); + let calculatedFee = + (tx.serialize().byteLength + multisigSpendConditionByteLength * pubKeys.length) * + feeMultiplier + + feeAddition; + if (feeCap > 0 && calculatedFee > feeCap) calculatedFee = feeCap; + tx.setFee(calculatedFee); + tempTotalFee += BigInt(calculatedFee); + return tx; +} + +// adding fields on the unsigned tx makes it easier to manage +function addPubkeyFields(tx: StacksTransaction, pubKeys: StacksPublicKey[]) { + for (const pk of pubKeys) tx.appendPubkey(pk); + return tx; +} + +const addressString = addressToString(address); + +(async () => { + const fundingTx = await createMultisigStxTransaction( + 394399976n - 52501n, + `${addressString}.${lisaDaoContractName}`, + feeMultiplier, + nonce++, + pubKeys.length, + pubKeys, + address + ); + return [bytesToHex(addPubkeyFields(fundingTx, pubKeys).serialize())]; +})().then(plan => { + fs.writeFileSync(planFile, JSON.stringify(plan), 'utf-8'); + verboseLog(`Last nonce is ${nonce}, total fee: ${Number(tempTotalFee) / 1000000} STX`); + console.log(`Deploy plan written to ${planFile}, total of ${plan.length} transactions`); +});