Skip to content

Commit

Permalink
feat: deploy_account transaction
Browse files Browse the repository at this point in the history
  • Loading branch information
dhruvkelawala committed Oct 12, 2022
1 parent 2050912 commit 8e5d414
Show file tree
Hide file tree
Showing 14 changed files with 421 additions and 16 deletions.
116 changes: 114 additions & 2 deletions src/account/default.ts
Expand Up @@ -7,6 +7,7 @@ import {
Abi,
Call,
DeclareContractResponse,
DeployContractResponse,
EstimateFeeResponse,
InvocationsDetails,
InvocationsSignerDetails,
Expand All @@ -15,8 +16,8 @@ import {
Signature,
} from '../types';
import { EstimateFee, EstimateFeeDetails } from '../types/account';
import { DeclareContractPayload } from '../types/lib';
import { transactionVersion } from '../utils/hash';
import { DeclareContractPayload, DeployAccountContractPayload } from '../types/lib';
import { calculateContractAddressFromHash, transactionVersion } from '../utils/hash';
import { BigNumberish, toBN } from '../utils/number';
import { parseContract } from '../utils/provider';
import { compileCalldata, estimatedFeeToMaxFee } from '../utils/stark';
Expand Down Expand Up @@ -116,6 +117,56 @@ export class Account extends Provider implements AccountInterface {
};
}

public async estimateAccountDeployFee(
{
classHash,
addressSalt = 0,
constructorCalldata = [],
contractAddress: providedContractAddress,
}: DeployAccountContractPayload,
{ blockIdentifier, nonce: providedNonce }: EstimateFeeDetails = {}
): Promise<EstimateFee> {
const nonce = toBN(providedNonce ?? (await this.getNonce()));
const version = toBN(transactionVersion);
const chainId = await this.getChainId();

let contractAddress = ZERO.toString();

if (!providedContractAddress) {
contractAddress = calculateContractAddressFromHash(
addressSalt,
classHash,
constructorCalldata,
0
);
} else {
contractAddress = providedContractAddress;
}

const signature = await this.signer.signDeployAccountTransaction({
classHash,
contractAddress,
chainId,
maxFee: ZERO,
version,
nonce,
addressSalt,
constructorCalldata,
});

const response = await super.getDeployAccountEstimateFee(
{ classHash, addressSalt, constructorCalldata, signature },
{ version, nonce },
blockIdentifier
);
const suggestedMaxFee = estimatedFeeToMaxFee(response.overall_fee);

return {
...response,
suggestedMaxFee,
};
}

/**
* Invoke execute function in account contract
*
Expand Down Expand Up @@ -209,6 +260,67 @@ export class Account extends Provider implements AccountInterface {
);
}

public async deployAccount(
{
classHash,
constructorCalldata = [],
addressSalt = 0,
contractAddress: providedContractAddress,
}: DeployAccountContractPayload,
transactionsDetail: InvocationsDetails = {}
): Promise<DeployContractResponse> {
const nonce = toBN(transactionsDetail.nonce ?? (await this.getNonce()));
let maxFee: BigNumberish = '0';

if (transactionsDetail.maxFee || transactionsDetail.maxFee === 0) {
maxFee = transactionsDetail.maxFee;
} else {
const { suggestedMaxFee } = await this.estimateAccountDeployFee(
{ addressSalt, classHash, constructorCalldata },
{
nonce,
}
);
maxFee = suggestedMaxFee.toString();
}

const version = toBN(transactionVersion);
const chainId = await this.getChainId();

let contractAddress = ZERO.toString();

if (!providedContractAddress) {
contractAddress = calculateContractAddressFromHash(
addressSalt,
classHash,
constructorCalldata,
0
);
} else {
contractAddress = providedContractAddress;
}

const signature = await this.signer.signDeployAccountTransaction({
classHash,
constructorCalldata,
contractAddress,
addressSalt,
chainId,
maxFee,
version,
nonce,
});

return this.deployAccountContract(
{ classHash, addressSalt, constructorCalldata, signature },
{
nonce,
maxFee,
version,
}
);
}

/**
* Sign an JSON object with the starknet private key and return the signature
*
Expand Down
39 changes: 38 additions & 1 deletion src/account/interface.ts
Expand Up @@ -5,13 +5,18 @@ import {
Abi,
Call,
DeclareContractResponse,
DeployContractResponse,
EstimateFeeDetails,
EstimateFeeResponse,
InvocationsDetails,
InvokeFunctionResponse,
Signature,
} from '../types';
import { DeclareContractPayload } from '../types/lib';
import {
DeclareContractPayload,
DeployAccountContractPayload,
DeployAccountContractTransaction,
} from '../types/lib';
import { BigNumberish } from '../utils/number';
import { TypedData } from '../utils/typedData/types';

Expand Down Expand Up @@ -65,6 +70,20 @@ export abstract class AccountInterface extends ProviderInterface {
estimateFeeDetails?: EstimateFeeDetails
): Promise<EstimateFeeResponse>;

/**
* Estimate Fee for executing a DEPLOY_ACCOUNT transaction on starknet
*
* @param contractPayload the payload object containing:
* - contract - the compiled contract to be declared
* - classHash - the class hash of the compiled contract. This can be obtained by using starknet-cli.
*
* @returns response from estimate_fee
*/
public abstract estimateAccountDeployFee(
contractPayload: DeployAccountContractPayload,
estimateFeeDetails?: EstimateFeeDetails
): Promise<EstimateFeeResponse>;

/**
* Invoke execute function in account contract
*
Expand Down Expand Up @@ -100,6 +119,24 @@ export abstract class AccountInterface extends ProviderInterface {
transactionsDetail?: InvocationsDetails
): Promise<DeclareContractResponse>;

/**
* Deploy the account on Starknet
* @param contractPayload transaction payload to be deployed containing:
* - classHash: computed class hash of compiled contract
* - constructor calldata
* - address salt
- signature
* @param transactionsDetail Invocation Details containing:
- optional nonce
- optional version
- optional maxFee
* @returns a confirmation of sending a transaction on the starknet contract
*/
public abstract deployAccount(
contractPayload: DeployAccountContractTransaction,
transactionsDetail?: InvocationsDetails
): Promise<DeployContractResponse>;

/**
* Sign an JSON object for off-chain usage with the starknet private key and return the signature
* This adds a message prefix so it cant be interchanged with transactions
Expand Down
1 change: 1 addition & 0 deletions src/constants.ts
Expand Up @@ -15,6 +15,7 @@ export enum StarknetChainId {
export enum TransactionHashPrefix {
DECLARE = '0x6465636c617265', // encodeShortString('declare'),
DEPLOY = '0x6465706c6f79', // encodeShortString('deploy'),
DEPLOY_ACCOUNT = '0x6465706c6f795f6163636f756e74', // encodeShortString('deploy_account'),
INVOKE = '0x696e766f6b65', // encodeShortString('invoke'),
L1_HANDLER = '0x6c315f68616e646c6572', // encodeShortString('l1_handler'),
}
Expand Down
17 changes: 16 additions & 1 deletion src/provider/default.ts
Expand Up @@ -15,7 +15,7 @@ import {
InvocationsDetailsWithNonce,
InvokeFunctionResponse,
} from '../types';
import { DeclareContractTransaction } from '../types/lib';
import { DeclareContractTransaction, DeployAccountContractTransaction } from '../types/lib';
import { BigNumberish } from '../utils/number';
import { ProviderInterface } from './interface';
import { RpcProvider, RpcProviderOptions } from './rpc';
Expand Down Expand Up @@ -122,6 +122,13 @@ export class Provider implements ProviderInterface {
return this.provider.deployContract(payload);
}

public async deployAccountContract(
payload: DeployAccountContractTransaction,
details: InvocationsDetailsWithNonce
): Promise<DeployContractResponse> {
return this.provider.deployAccountContract(payload, details);
}

public async declareContract(
transaction: DeclareContractTransaction,
details: InvocationsDetailsWithNonce
Expand All @@ -137,6 +144,14 @@ export class Provider implements ProviderInterface {
return this.provider.getDeclareEstimateFee(transaction, details, blockIdentifier);
}

public getDeployAccountEstimateFee(
transaction: DeployAccountContractTransaction,
details: InvocationsDetailsWithNonce,
blockIdentifier: BlockIdentifier = 'pending'
): Promise<EstimateFeeResponse> {
return this.provider.getDeployAccountEstimateFee(transaction, details, blockIdentifier);
}

public async getCode(
contractAddress: string,
blockIdentifier?: BlockIdentifier
Expand Down
43 changes: 41 additions & 2 deletions src/provider/interface.ts
Expand Up @@ -15,7 +15,11 @@ import type {
InvocationsDetailsWithNonce,
InvokeFunctionResponse,
} from '../types';
import { DeclareContractTransaction } from '../types/lib';
import {
DeclareContractTransaction,
DeployAccountContractPayload,
DeployAccountContractTransaction,
} from '../types/lib';
import type { BigNumberish } from '../utils/number';
import { BlockIdentifier } from './utils';

Expand Down Expand Up @@ -123,6 +127,20 @@ export abstract class ProviderInterface {
*/
public abstract deployContract(payload: DeployContractPayload): Promise<DeployContractResponse>;

/**
* Deploys a given compiled Account contract (json) to starknet
*
* @param payload payload to be deployed containing:
* - compiled contract code
* - constructor calldata
* - address salt
* @returns a confirmation of sending a transaction on the starknet contract
*/
public abstract deployAccountContract(
payload: DeployAccountContractPayload,
details: InvocationsDetailsWithNonce
): Promise<DeployContractResponse>;

/**
* Invokes a function on starknet
* @deprecated This method wont be supported as soon as fees are mandatory
Expand Down Expand Up @@ -204,7 +222,7 @@ export abstract class ProviderInterface {
/**
* Estimates the fee for a given DECLARE transaction
*
* @param transaction transaction payload to be deployed containing:
* @param transaction transaction payload to be declared containing:
* - compiled contract code
* - sender address
* - signature - (defaults to []) the signature
Expand All @@ -221,6 +239,27 @@ export abstract class ProviderInterface {
blockIdentifier: BlockIdentifier
): Promise<EstimateFeeResponse>;

/**
* Estimates the fee for a given DEPLOY_ACCOUNT transaction
*
* @param transaction transaction payload to be deployed containing:
* - classHash
* - constructorCalldata
* - addressSalt
* - signature - (defaults to []) the signature
* @param details - optional details containing:
* - nonce
* - version - optional version
* - optional maxFee
* @param blockIdentifier - block identifier
* @returns the estimated fee
*/
public abstract getDeployAccountEstimateFee(
transaction: DeployAccountContractTransaction,
details: InvocationsDetailsWithNonce,
blockIdentifier: BlockIdentifier
): Promise<EstimateFeeResponse>;

/**
* Wait for the transaction to be accepted
* @param txHash - transaction hash
Expand Down
39 changes: 38 additions & 1 deletion src/provider/rpc.ts
Expand Up @@ -15,7 +15,11 @@ import {
InvokeFunctionResponse,
} from '../types';
import { RPC } from '../types/api';
import { DeclareContractTransaction } from '../types/lib';
import {
DeclareContractTransaction,
DeployAccountContractPayload,
DeployAccountContractTransaction,
} from '../types/lib';
import fetch from '../utils/fetchPonyfill';
import { getSelectorFromName } from '../utils/hash';
import { stringify } from '../utils/json';
Expand Down Expand Up @@ -264,6 +268,27 @@ export class RpcProvider implements ProviderInterface {
}).then(this.responseParser.parseFeeEstimateResponse);
}

public async getDeployAccountEstimateFee(
{ classHash, constructorCalldata, addressSalt, signature }: DeployAccountContractTransaction,
details: InvocationsDetailsWithNonce,
blockIdentifier: BlockIdentifier = 'pending'
): Promise<EstimateFeeResponse> {
const block_id = new Block(blockIdentifier).identifier;
return this.fetchEndpoint('starknet_estimateFee', {
request: {
type: 'DEPLOY_ACCOUNT',
constructor_calldata: bigNumberishArrayToHexadecimalStringArray(constructorCalldata || []),
class_hash: toHex(toBN(classHash)),
contract_address_salt: toHex(toBN(addressSalt || 0)),
signature: bigNumberishArrayToHexadecimalStringArray(signature || []),
version: toHex(toBN(details?.version || 0)),
nonce: toHex(toBN(details.nonce)),
max_fee: toHex(toBN(details?.maxFee || 0)),
},
block_id,
}).then(this.responseParser.parseFeeEstimateResponse);
}

// TODO: Revisit after Pathfinder release with JSON-RPC v0.2.1 RPC Spec
public async declareContract(
{ contractDefinition, signature, senderAddress }: DeclareContractTransaction,
Expand Down Expand Up @@ -301,6 +326,18 @@ export class RpcProvider implements ProviderInterface {
});
}

public async deployAccountContract({
classHash,
constructorCalldata,
addressSalt,
}: DeployAccountContractPayload): Promise<DeployContractResponse> {
return this.fetchEndpoint('starknet_addDeployAccountTransaction', {
constructor_calldata: bigNumberishArrayToHexadecimalStringArray(constructorCalldata || []),
class_hash: toHex(toBN(classHash)),
contract_address_salt: toHex(toBN(addressSalt || 0)),
});
}

public async invokeFunction(
functionInvocation: Invocation,
details: InvocationsDetailsWithNonce
Expand Down

0 comments on commit 8e5d414

Please sign in to comment.