Deploy Contracts Skill
Overview
This skill guides safe deployment of Move contracts to Aptos networks. Always deploy to testnet before mainnet.
Pre-Deployment Checklist
Before deploying, verify ALL items:
Security Audit ⭐ CRITICAL - See SECURITY.md
-
Security audit completed (use security-audit skill)
-
All critical vulnerabilities fixed
-
All security patterns verified (arithmetic safety, storage scoping, reference safety, business logic)
-
Access control verified (signer checks, object ownership)
-
Input validation implemented (minimum thresholds, fee validation)
-
No unbounded iterations (per-user storage, not global vectors)
-
Atomic operations (no front-running opportunities)
-
Randomness security (if applicable - entry functions, gas balanced)
Testing
-
100% test coverage achieved: aptos move test --coverage
-
All tests passing: aptos move test
-
Coverage report shows 100.0%
-
Edge cases tested
Code Quality
-
Code compiles without errors: aptos move compile
-
No hardcoded addresses (use named addresses)
-
Error codes clearly defined
-
Functions properly documented
Configuration
-
Move.toml configured correctly
-
Named addresses set up: my_addr = "_"
-
Dependencies specified with correct versions
-
Network URLs configured
Object Deployment (Modern Pattern)
CRITICAL: Use Correct Deployment Command
There are TWO ways to deploy contracts. For modern object-based contracts, use deploy-object :
✅ CORRECT: Object Deployment (Modern Pattern)
aptos move deploy-object
--address-name my_addr
--profile devnet
--assume-yes
What this does:
-
Creates an object to host your contract code
-
Deploys the package to that object's address
-
Returns the object address (deterministic, based on deployer + package name)
-
Object address becomes your contract address
❌ WRONG: Using Regular Publish for Object Contracts
❌ Don't use this for object-based contracts
aptos move publish
--named-addresses my_addr=<address>
When to use each:
-
deploy-object : Modern contracts using objects (RECOMMENDED)
-
publish : Legacy account-based deployment (older pattern)
How to tell if you need object deployment:
-
Your contract creates named objects in init_module
-
Your contract uses object::create_named_object()
-
You want a deterministic contract address
-
Documentation says "deploy as object"
Alternative Object Deployment Commands
Option 1: deploy-object (Recommended - Simplest)
aptos move deploy-object --address-name my_addr --profile devnet
-
Automatically creates object and deploys code
-
Object address is deterministic
-
Best for most use cases
Option 2: create-object-and-publish-package (Advanced)
aptos move create-object-and-publish-package
--address-name my_addr
--named-addresses my_addr=default
-
More complex command with more options
-
Use only if you need specific object configuration
-
Generally not needed
Recommendation: Always use deploy-object unless you have a specific reason to use the alternative.
Deployment Workflow
Step 1: Test Locally
Ensure all tests pass
aptos move test
Verify 100% coverage
aptos move test --coverage aptos move coverage summary
Expected output: "coverage: 100.0%"
Step 2: Compile
Compile contract
aptos move compile --named-addresses my_addr=<your_address>
Verify compilation succeeds
echo $?
Expected: 0 (success)
Step 3: Deploy to Devnet (Optional)
Devnet is for quick testing and experimentation. Account is auto-funded on aptos init .
Check if a profile exists before initializing:
Check if default profile exists (look for "default" in output)
aptos config show-profiles
If no profile exists, initialize one (auto-funds on devnet)
aptos init --network devnet --assume-yes
Deploy as object (modern pattern)
aptos move deploy-object
--address-name my_addr
--profile default
--assume-yes
Save the object address from output for future upgrades
Output: "Code was successfully deployed to object address 0x..."
Verify deployment
aptos account list --account <object_address> --profile default
Step 4: Deploy to Testnet (REQUIRED)
Testnet is for final testing before mainnet.
Check if a profile exists before initializing:
Check if default profile exists
aptos config show-profiles
If no profile exists, initialize one
aptos init --network testnet --assume-yes
Fund your account via web faucet (required — testnet faucet needs Google login):
-
Get your account address: aptos config show-profiles
-
Go to: https://aptos.dev/network/faucet?address=<your_address>
-
Login and request testnet APT
-
Return here and confirm you've funded the account
Verify balance
aptos account balance --profile default
Deploy to testnet as object (modern pattern)
aptos move deploy-object
--address-name my_addr
--profile default
--assume-yes
IMPORTANT: Save the object address from output
You'll need it for upgrades and function calls
Output: "Code was successfully deployed to object address 0x..."
Step 5: Test on Testnet
Run entry functions to verify deployment
aptos move run
--profile default
--function-id <deployed_address>::<module>::<function>
--args ...
Test multiple scenarios
- Happy paths
- Error cases (should abort with correct error codes)
- Access control
- Edge cases
Verify using explorer
https://explorer.aptoslabs.com/?network=testnet
Step 6: Deploy to Mainnet (After Testnet Success)
Only deploy to mainnet after thorough testnet testing.
Check if a profile exists before initializing:
Check if default profile exists
aptos config show-profiles
If no profile exists, initialize one
IMPORTANT: Warn user that this generates a private key — store it securely
aptos init --network mainnet --assume-yes
Fund your account: Transfer real APT to your account address from an exchange or wallet.
Verify balance
aptos account balance --profile default
Deploy to mainnet as object (modern pattern)
aptos move deploy-object
--address-name my_addr
--profile default
--max-gas 20000 # Optional: set gas limit
Review prompts carefully before confirming:
1. Gas confirmation: Review gas costs
2. Object address: Note the object address for future reference
OR use --assume-yes to auto-confirm (only if you're confident)
aptos move deploy-object
--address-name my_addr
--profile default
--assume-yes
SAVE THE OBJECT ADDRESS from output
You'll need it for upgrades and documentation
Confirm deployment
Review transaction in explorer:
https://explorer.aptoslabs.com/?network=mainnet
Step 7: Verify Deployment
Check module is published
aptos account list --account <deployed_address> --profile default
Look for your module in the output
"0x...::my_module": { ... }
Run view function to verify
aptos move view
--profile default
--function-id <deployed_address>::<module>::<view_function>
--args ...
Step 8: Document Deployment
Create deployment record:
Deployment Record
Date: 2026-01-23 Network: Mainnet Module: my_module Address: 0x123abc... Transaction: 0x456def...
Verification
- Deployed successfully
- Module visible in explorer
- View functions working
- Entry functions tested
Links
- Explorer: https://explorer.aptoslabs.com/account/0x123abc...?network=mainnet
- Transaction: https://explorer.aptoslabs.com/txn/0x456def...?network=mainnet
Notes
- All security checks passed
- 100% test coverage verified
- Tested on testnet for 1 week before mainnet
Module Upgrades
Upgrading Existing Object Deployment
Object-deployed modules are upgradeable by default for the deployer.
Upgrade existing object deployment
aptos move upgrade-object
--address-name my_addr
--object-address <object_address_from_initial_deploy>
--profile mainnet
Upgrade with auto-confirm
aptos move upgrade-object
--address-name my_addr
--object-address <object_address>
--profile mainnet
--assume-yes
Verify upgrade
aptos account list --account <object_address> --profile mainnet
IMPORTANT: Save the object address from your initial deploy-object output - you need it for upgrades.
Upgrade Compatibility Rules:
-
✅ CAN: Add new functions
-
✅ CAN: Add new structs
-
✅ CAN: Add new fields to structs (with care)
-
❌ CANNOT: Remove existing functions (breaks compatibility)
-
❌ CANNOT: Change function signatures (breaks compatibility)
-
❌ CANNOT: Remove struct fields (breaks existing data)
Making Modules Immutable
To prevent future upgrades:
// In your module fun init_module(account: &signer) { // After deployment, burn upgrade capability // (implementation depends on your setup) }
Cost Estimation
Gas Costs
Typical deployment costs:
- Small module: ~500-1000 gas units
- Medium module: ~1000-5000 gas units
- Large module: ~5000-20000 gas units
When you run deploy-object, the CLI shows gas estimate before confirming
Use --assume-yes only after you've verified costs on testnet first
Mainnet Costs
Gas costs are paid in APT:
-
Gas units × Gas price = Total cost
-
Example: 5000 gas units × 100 Octas/gas = 500,000 Octas = 0.005 APT
Multi-Module Deployment
Deploying Multiple Modules
Option 1: Single package (Recommended)
project/ ├── Move.toml └── sources/ ├── module1.move ├── module2.move └── module3.move
Deploys all modules at once as a single object
aptos move deploy-object --address-name my_addr --profile testnet
Option 2: Separate packages with dependencies
Deploy dependency package first
cd dependency-package aptos move deploy-object --address-name dep_addr --profile testnet
Note the object address from output
Update main package Move.toml to reference dependency address
Then deploy main package
cd ../main-package aptos move deploy-object --address-name main_addr --profile testnet
Troubleshooting Deployment
"Insufficient APT balance"
Devnet: Auto-funded on aptos init . If needed, run:
aptos account fund-with-faucet --account default --amount 100000000 --profile default
Testnet: Use the web faucet (requires Google login):
-
Get your account address: aptos config show-profiles
-
Go to: https://aptos.dev/network/faucet?address=<your_address>
-
Login and request testnet APT
-
Verify balance: aptos account balance --profile default
Mainnet: Transfer real APT to your account from an exchange or wallet.
"Module already exists" (for object deployments)
Use upgrade-object with the original object address
aptos move upgrade-object
--address-name my_addr
--object-address <object_address_from_initial_deploy>
--profile testnet
"Compilation failed"
Fix compilation errors first
aptos move compile
Fix all errors shown, then retry deployment
"Gas limit exceeded"
Increase max gas
aptos move deploy-object
--address-name my_addr
--profile testnet
--max-gas 50000
Deployment Checklist
Before Deployment:
-
Security audit passed
-
100% test coverage
-
All tests passing
-
Code compiles successfully
-
Named addresses configured
-
Target network selected (testnet first!)
During Deployment:
-
Correct network selected
-
Correct address specified
-
Transaction submitted
-
Transaction hash recorded
After Deployment:
-
Module visible in explorer
-
View functions work
-
Entry functions tested
-
Deployment documented
-
Team notified
CLI Argument Types
When calling entry or view functions via the CLI, use these type prefixes:
Primitive Types
--args u64:1000 # u8, u16, u32, u64, u128, u256 --args bool:true # boolean --args address:0x1 # address
Complex Types
--args string:"Hello World" # UTF-8 string --args hex:0x48656c6c6f # raw bytes --args "u64:[1,2,3,4,5]" # vector<u64> --args "string:["one","two","three"]" # vector<String>
Object Types
For Object<T> parameters, pass the object address
--args address:0x123abc...
ALWAYS Rules
-
✅ ALWAYS run comprehensive security audit before deployment (use security-audit skill)
-
✅ ALWAYS verify 100% test coverage with security tests
-
✅ ALWAYS verify all SECURITY.md patterns (arithmetic, storage scoping, reference safety, business logic)
-
✅ ALWAYS use deploy-object (NOT resource_account::create_resource_account() which is legacy)
-
✅ ALWAYS deploy to testnet before mainnet
-
✅ ALWAYS test on testnet thoroughly (happy paths, error cases, security scenarios)
-
✅ ALWAYS backup private keys securely
-
✅ ALWAYS document deployment addresses
-
✅ ALWAYS verify deployment in explorer
-
✅ ALWAYS test functions after deployment
-
✅ ALWAYS use separate keys for testnet and mainnet (SECURITY.md - Operations)
NEVER Rules
-
❌ NEVER deploy without comprehensive security audit
-
❌ NEVER deploy with < 100% test coverage
-
❌ NEVER deploy without security test verification
-
❌ NEVER deploy directly to mainnet without testnet testing
-
❌ NEVER deploy without testing on testnet first
-
❌ NEVER commit private keys to version control
-
❌ NEVER skip post-deployment verification
-
❌ NEVER rush mainnet deployment
-
❌ NEVER reuse publishing keys between testnet and mainnet (security risk)
-
❌ NEVER read or display contents of ~/.aptos/config.yaml or .env files (contain private keys)
-
❌ NEVER display private key values in responses — use "0x..." placeholders
-
❌ NEVER run cat ~/.aptos/config.yaml , echo $VITE_MODULE_PUBLISHER_ACCOUNT_PRIVATE_KEY , or similar commands
-
❌ NEVER run git add . or git add -A without confirming .env is in .gitignore
References
Official Documentation:
-
CLI Publishing: https://aptos.dev/build/cli/working-with-move-contracts
-
Network Endpoints: https://aptos.dev/nodes/networks
-
Gas and Fees: https://aptos.dev/concepts/gas-txn-fee
Explorers:
Related Skills:
-
security-audit
-
Audit before deployment
-
generate-tests
-
Ensure tests exist
Remember: Security audit → 100% tests → Testnet → Thorough testing → Mainnet. Never skip testnet.