Skip to content

Commit

Permalink
feat: Deployment Script (keep-starknet-strange#59)
Browse files Browse the repository at this point in the history
* feat: initialize deployment script

* feat: deployment script file system utils

* feat: deployment script contract utils
  • Loading branch information
ugur-eren authored Sep 9, 2024
1 parent ec5f6fa commit 980cccf
Show file tree
Hide file tree
Showing 12 changed files with 1,122 additions and 0 deletions.
5 changes: 5 additions & 0 deletions contracts/scripts/.env.example
Original file line number Diff line number Diff line change
@@ -0,0 +1,5 @@
NETWORK=sepolia # mainnet | sepolia
INFURA_API_KEY=

ACCOUNT_ADDRESS=0x0
ACCOUNT_PRIVATE_KEY=0x0
175 changes: 175 additions & 0 deletions contracts/scripts/.gitignore
Original file line number Diff line number Diff line change
@@ -0,0 +1,175 @@
# Based on https://raw.githubusercontent.com/github/gitignore/main/Node.gitignore

# Logs

logs
_.log
npm-debug.log_
yarn-debug.log*
yarn-error.log*
lerna-debug.log*
.pnpm-debug.log*

# Caches

.cache

# Diagnostic reports (https://nodejs.org/api/report.html)

report.[0-9]_.[0-9]_.[0-9]_.[0-9]_.json

# Runtime data

pids
_.pid
_.seed
*.pid.lock

# Directory for instrumented libs generated by jscoverage/JSCover

lib-cov

# Coverage directory used by tools like istanbul

coverage
*.lcov

# nyc test coverage

.nyc_output

# Grunt intermediate storage (https://gruntjs.com/creating-plugins#storing-task-files)

.grunt

# Bower dependency directory (https://bower.io/)

bower_components

# node-waf configuration

.lock-wscript

# Compiled binary addons (https://nodejs.org/api/addons.html)

build/Release

# Dependency directories

node_modules/
jspm_packages/

# Snowpack dependency directory (https://snowpack.dev/)

web_modules/

# TypeScript cache

*.tsbuildinfo

# Optional npm cache directory

.npm

# Optional eslint cache

.eslintcache

# Optional stylelint cache

.stylelintcache

# Microbundle cache

.rpt2_cache/
.rts2_cache_cjs/
.rts2_cache_es/
.rts2_cache_umd/

# Optional REPL history

.node_repl_history

# Output of 'npm pack'

*.tgz

# Yarn Integrity file

.yarn-integrity

# dotenv environment variable files

.env
.env.development.local
.env.test.local
.env.production.local
.env.local

# parcel-bundler cache (https://parceljs.org/)

.parcel-cache

# Next.js build output

.next
out

# Nuxt.js build / generate output

.nuxt
dist

# Gatsby files

# Comment in the public line in if your project uses Gatsby and not Next.js

# https://nextjs.org/blog/next-9-1#public-directory-support

# public

# vuepress build output

.vuepress/dist

# vuepress v2.x temp and cache directory

.temp

# Docusaurus cache and generated files

.docusaurus

# Serverless directories

.serverless/

# FuseBox cache

.fusebox/

# DynamoDB Local files

.dynamodb/

# TernJS port file

.tern-port

# Stores VSCode versions used for testing VSCode extensions

.vscode-test

# yarn v2

.yarn/cache
.yarn/unplugged
.yarn/build-state.yml
.yarn/install-state.gz
.pnp.*

# IntelliJ based IDEs
.idea

# Finder (MacOS) folder config
.DS_Store
5 changes: 5 additions & 0 deletions contracts/scripts/.prettierrc
Original file line number Diff line number Diff line change
@@ -0,0 +1,5 @@
{
"semi": false,
"singleQuote": true,
"printWidth": 120
}
20 changes: 20 additions & 0 deletions contracts/scripts/package.json
Original file line number Diff line number Diff line change
@@ -0,0 +1,20 @@
{
"name": "scripts",
"type": "module",
"scripts": {
"deploy": "tsx src/deploy.ts"
},
"dependencies": {
"chalk": "^5.3.0",
"dotenv": "^16.4.5",
"signale": "^1.4.0",
"starknet": "^6.11.0"
},
"devDependencies": {
"@types/node": "^22.5.4",
"@types/signale": "^1.4.7",
"prettier": "^3.3.3",
"tsx": "^4.19.0",
"typescript": "^5.5.4"
}
}
13 changes: 13 additions & 0 deletions contracts/scripts/src/deploy.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,13 @@
import 'dotenv/config'

import chalk from 'chalk'

const main = async () => {
console.log(chalk.red` ____ _ `)
console.log(chalk.red` | \\ ___ ___| |___ _ _ `)
console.log(chalk.red` | | | -_| . | | . | | | `)
console.log(chalk.red` |____/|___| _|_|___|_ | `)
console.log(chalk.red` |_| |___| `)
}

main()
16 changes: 16 additions & 0 deletions contracts/scripts/src/services/account.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,16 @@
import { Account } from 'starknet'
import signale from 'signale'

import { provider } from './provider'

if (!process.env.ACCOUNT_ADDRESS) {
signale.fatal(new Error('ACCOUNT_ADDRESS env variable is required'))
process.exit(1)
}

if (!process.env.ACCOUNT_PRIVATE_KEY) {
signale.fatal(new Error('ACCOUNT_PRIVATE_KEY env variable is required'))
process.exit(1)
}

export const account = new Account(provider, process.env.ACCOUNT_ADDRESS, process.env.ACCOUNT_PRIVATE_KEY, '1')
30 changes: 30 additions & 0 deletions contracts/scripts/src/services/provider.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,30 @@
import { RpcProvider, type RpcProviderOptions } from 'starknet'
import signale from 'signale'

if (!process.env.INFURA_API_KEY) {
signale.fatal(new Error('INFURA_API_KEY env variable is required'))
process.exit(1)
}

if (!process.env.NETWORK) {
signale.fatal(new Error('NETWORK env variable is required'))
process.exit(1)
}

const NETWORKS = {
mainnet: {
nodeUrl: `https://starknet-mainnet.infura.io/v3/${process.env.INFURA_API_KEY}`,
},
sepolia: {
nodeUrl: `https://starknet-sepolia.infura.io/v3/${process.env.INFURA_API_KEY}`,
},
} satisfies Record<string, RpcProviderOptions>

export const network = NETWORKS[process.env.NETWORK as keyof typeof NETWORKS]

if (!network) {
signale.fatal(new Error(`Unsupported network: ${process.env.NETWORK}`))
process.exit(1)
}

export const provider = new RpcProvider(network)
7 changes: 7 additions & 0 deletions contracts/scripts/src/types/contract.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,7 @@
export type Contract = {
name: string
classPath: string
compiledClassPath: string
classFile: any
compiledClassFile: any
}
50 changes: 50 additions & 0 deletions contracts/scripts/src/utils/contract.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,50 @@
import signale from 'signale'
import { Calldata } from 'starknet'

import { account } from '../services/account'
import { Contract } from '../types/contract'

export const declareContract = async (contract: Contract) => {
signale.info(`Declaring ${contract.name} contract...`)

const transaction = await account.declareIfNot({
contract: contract.classFile,
casm: contract.compiledClassFile,
})

signale.pending(`- Class Hash: ${transaction.class_hash}`)

if (transaction.transaction_hash) {
signale.pending(`- Transaction Hash: ${transaction.transaction_hash}`)

await account.waitForTransaction(transaction.transaction_hash)

signale.success(`Contract ${contract.name} declared successfully!`)
} else {
signale.success(`Contract ${contract.name} already declared!`)
}

return transaction.class_hash
}

export const deployContract = async (contract: Contract, classHash: string, constructorCalldata: Calldata) => {
signale.info(`Deploying ${contract.name} contract...`)

const transaction = await account.deployContract({
classHash,
constructorCalldata,
})

signale.pending(`- Transaction Hash: ${transaction.transaction_hash}`)

await account.waitForTransaction(transaction.transaction_hash)

signale.success(`Contract ${contract.name} deployed successfully!`)

return transaction
}

export const declareAndDeployContract = async (contract: Contract, constructorCalldata: Calldata) => {
const classHash = await declareContract(contract)
return deployContract(contract, classHash, constructorCalldata)
}
53 changes: 53 additions & 0 deletions contracts/scripts/src/utils/fs.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,53 @@
import { dirname, join } from 'node:path'
import fs from 'node:fs/promises'
import { existsSync } from 'node:fs'
import { fileURLToPath } from 'node:url'
import signale from 'signale'
import { json } from 'starknet'

import { Contract } from '../types/contract'

const __filename = fileURLToPath(import.meta.url)
const __dirname = dirname(__filename)

export const TARGET_PATH = join(__dirname, '..', '..', 'target', 'dev')

export const getContracts = async () => {
if (!existsSync(TARGET_PATH)) {
signale.error(`Target directory could not be found at ${TARGET_PATH}. Please run "scarb build" first.`)
process.exit(1)
}

const contracts = (await fs.readdir(TARGET_PATH))
.filter((file) => file.endsWith('.contract_class.json'))
.map((file) => file.replace('.contract_class.json', ''))

if (contracts.length === 0) {
signale.error('No contracts found in the target directory. Please run "scarb build" first.')
process.exit(1)
}

return contracts
}

export const getContract = async (contractName: string): Promise<Contract> => {
const contracts = await getContracts()

const contract = contracts.find((contract) => contract.includes(contractName))

if (!contract) {
signale.error(`Contract ${contractName} not found in the target directory. Please run "scarb build" first.`)
process.exit(1)
}

const classPath = join(TARGET_PATH, `${contract}.contract_class.json`)
const compiledClassPath = join(TARGET_PATH, `${contract}.compiled_contract_class.json`)

return {
name: contractName,
classPath,
compiledClassPath,
classFile: json.parse(await fs.readFile(classPath, 'ascii')),
compiledClassFile: json.parse(await fs.readFile(compiledClassPath, 'ascii')),
}
}
Loading

0 comments on commit 980cccf

Please sign in to comment.