diff --git a/packages/transactions/src/builders.ts b/packages/transactions/src/builders.ts index 4234deb85..120b84fbf 100644 --- a/packages/transactions/src/builders.ts +++ b/packages/transactions/src/builders.ts @@ -25,6 +25,7 @@ import { publicKeyToAddress, pubKeyfromPrivKey, publicKeyFromBuffer, + createStacksPublicKey, } from './keys'; import { TransactionSigner } from './signer'; @@ -672,12 +673,10 @@ export async function makeSTXTokenTransfer( /** * Contract deploy transaction options */ -export interface ContractDeployOptions { +export interface BaseContractDeployOptions { contractName: string; /** the Clarity code to be deployed */ codeBody: string; - /** a hex string of the private key of the transaction sender */ - senderKey: string; /** transaction fee in microstacks */ fee?: IntegerType; /** the transaction nonce, which must be increased monotonically with each new transaction */ @@ -696,6 +695,16 @@ export interface ContractDeployOptions { sponsored?: boolean; } +export interface ContractDeployOptions extends BaseContractDeployOptions { + /** a hex string of the private key of the transaction sender */ + senderKey: string; +} + +export interface UnsignedContractDeployOptions extends BaseContractDeployOptions { + /** a hex string of the public key of the transaction sender */ + publicKey: string; +} + /** * @deprecated Use the new {@link estimateTransaction} function insterad. * @@ -761,6 +770,23 @@ export async function estimateContractDeploy( */ export async function makeContractDeploy( txOptions: ContractDeployOptions +): Promise { + const privKey = createStacksPrivateKey(txOptions.senderKey); + const stacksPublicKey = getPublicKey(privKey); + const publicKey = publicKeyToString(stacksPublicKey); + const unsignedTxOptions: UnsignedContractDeployOptions = { ...txOptions, publicKey }; + const transaction: StacksTransaction = await makeUnsignedContractDeploy(unsignedTxOptions); + + if (txOptions.senderKey) { + const signer = new TransactionSigner(transaction); + signer.signOrigin(privKey); + } + + return transaction; +} + +export async function makeUnsignedContractDeploy( + txOptions: UnsignedContractDeployOptions ): Promise { const defaultOptions = { fee: BigInt(0), @@ -775,8 +801,7 @@ export async function makeContractDeploy( const payload = createSmartContractPayload(options.contractName, options.codeBody); const addressHashMode = AddressHashMode.SerializeP2PKH; - const privKey = createStacksPrivateKey(options.senderKey); - const pubKey = getPublicKey(privKey); + const pubKey = createStacksPublicKey(options.publicKey); let authorization = null; @@ -827,11 +852,6 @@ export async function makeContractDeploy( transaction.setNonce(txNonce); } - if (options.senderKey) { - const signer = new TransactionSigner(transaction); - signer.signOrigin(privKey); - } - return transaction; } diff --git a/packages/transactions/tests/builder.test.ts b/packages/transactions/tests/builder.test.ts index d356488da..c632eeed1 100644 --- a/packages/transactions/tests/builder.test.ts +++ b/packages/transactions/tests/builder.test.ts @@ -3,6 +3,7 @@ import * as fs from 'fs'; import { makeUnsignedSTXTokenTransfer, makeContractDeploy, + makeUnsignedContractDeploy, makeContractCall, makeStandardSTXPostCondition, makeContractSTXPostCondition, @@ -630,6 +631,67 @@ test('Make smart contract deploy', async () => { expect(serialized).toBe(tx); }); +test('Make smart contract deploy unsigned', async () => { + const contractName = 'kv-store'; + const codeBody = fs.readFileSync('./tests/contracts/kv-store.clar').toString(); + const publicKey = '03797dd653040d344fd048c1ad05d4cbcb2178b30c6a0c4276994795f3e833da41'; + const fee = 0; + const nonce = 0; + + const authType = AuthType.Standard; + const addressHashMode = AddressHashMode.SerializeP2PKH; + const transaction = await makeUnsignedContractDeploy({ + contractName, + codeBody, + publicKey, + fee, + nonce, + network: new StacksTestnet(), + anchorMode: AnchorMode.Any + }); + + const serializedTx = transaction.serialize(); + + const bufferReader = new BufferReader(serializedTx); + const deserializedTx = deserializeTransaction(bufferReader); + + expect(deserializedTx.auth.authType).toBe(authType); + + expect(deserializedTx.auth.spendingCondition!.hashMode).toBe(addressHashMode); + expect(deserializedTx.auth.spendingCondition!.nonce!.toString()).toBe(nonce.toString()); + expect(deserializedTx.auth.spendingCondition!.fee!.toString()).toBe(fee.toString()); +}); + +test('Make smart contract deploy signed', async () => { + const contractName = 'kv-store'; + const codeBody = fs.readFileSync('./tests/contracts/kv-store.clar').toString(); + const senderKey = 'e494f188c2d35887531ba474c433b1e41fadd8eb824aca983447fd4bb8b277a801'; + const fee = 0; + const nonce = 0; + + const authType = AuthType.Standard; + const addressHashMode = AddressHashMode.SerializeP2PKH; + const transaction = await makeContractDeploy({ + contractName, + codeBody, + senderKey, + fee, + nonce, + network: new StacksTestnet(), + anchorMode: AnchorMode.Any + }); + + const serializedTx = transaction.serialize(); + + const bufferReader = new BufferReader(serializedTx); + const deserializedTx = deserializeTransaction(bufferReader); + expect(deserializedTx.auth.authType).toBe(authType); + + expect(deserializedTx.auth.spendingCondition!.hashMode).toBe(addressHashMode); + expect(deserializedTx.auth.spendingCondition!.nonce!.toString()).toBe(nonce.toString()); + expect(deserializedTx.auth.spendingCondition!.fee!.toString()).toBe(fee.toString()); +}); + test('Make contract-call', async () => { const contractAddress = 'ST3KC0MTNW34S1ZXD36JYKFD3JJMWA01M55DSJ4JE'; const contractName = 'kv-store';