Deploy a Smart Contract
The following guide deploys an ERC-721 contract on the Polygon Mumbai Testnet
Smart Contract Platform provides an API for deploying, exploring, and interacting with smart contracts. The platform offers a powerful toolset for developers to build decentralized applications and for businesses to transition to web3.
This guide can also be followed to deploy smart contracts on Ethereum and Avalanche networks other than Mumbai by changing the blockchain
parameter in your request. Additionally, you can deploy to Mainnet by swapping out the Testnet API key for a Mainnet API key. See the Testnet vs Mainnet guide for more detail.
OpenZeppelin
This tutorial will use OpenZeppelin’s Contract Wizard to generate an ERC-721 smart contract. OpenZeppelin offers a secure smart contract development library - built on top of a foundation of community-vetted code.
The Contract Wizard includes:
- Implementations of standards like ERC20 and ERC721.
- Flexible role-based permissions scheme.
- Reusable Solidity components to build custom contracts and complex decentralized systems.
Prerequisites
- Create a Developer Account and acquire an API key in the Console
- Have Python or Golang installed.
Part One: Create a Wallet and Acquire Gas
If you already have a Circle developer-controlled wallet on Polygon Mumbai and the wallet is funded with MATIC, you can go directly to part two of this tutorial.
1. Register an Entity Secret Ciphertext
When using a developer-controlled wallet for critical API requests - like creating a wallet or deploying a contract, a ciphertext must be added to the body of the API request. The ciphertext is an additional layer of security for your developer-controlled wallet. You will need to generate the entity secret yourself, encrypt it, and then register the entity secret in the developer dashboard:
a. Generate Hex-Encoded Entity Secret
First, we will generate a 32-byte hex-encoded entity secret using Circle's Go or Python w3s entity secret sample code.
- Clone the sample app using one of the following commands in the terminal.
git clone https://github.com/circlefin/w3s-entity-secret-sample-code.git
git clone [email protected]:circlefin/w3s-entity-secret-sample-code.git
gh repo clone circlefin/w3s-entity-secret-sample-code
- In the terminal, navigate to the root of the cloned repository
- If you are using Python, you must install the pycryptodome dependency. You can do this by running the following command in the terminal.
pip install pycryptodome
- In the terminal, run the hex-encode script in your preferred programming language
python python/generate_hex_encoded_entity_secret.py
go run golang/generate_hex_encoded_entity_secret.go
- Copy the printed-out result. For example:
Hex encoded entity secret: 049eb963641dee155e7402f7fcdf33bd4ff12313222c9zfa39de7219296a8fd2
- Add the hex-encoded entity secret into the
generate_entity_secret_ciphertext.file
.
# python/generate_entity_secret_ciphertext.py
# If you already have a hex encoded entity secret, you can paste it here. the length of the hex string should be 64.
hex_encoded_entity_secret = '049eb963641dee155e7402f7fcdf33bd4ff12313222c9zfa39de7219296a8fd2'
// golang/generate_entity_secret_ciphertext.go
// If you already have a hex encoded entity secret, you can paste it here. the length of the hex string should be 64.
var hexEncodedEntitySecret = "049eb963641dee155e7402f7fcdf33bd4ff12313222c9zfa39de7219296a8fd2"
IMPORTANT:
Please store the hex-encoded entity secret carefully, as it is required for critical API requests and Circle does not store the information. Please refrain from directly embedding the hex-encoded entity secret within the code.
b. Acquire a Public Key
Next, you will acquire a public key to generate the entity's secret cipher text.
- Make a request to
GET /config/entity/publicKey
to get an entity public key that will be used to encrypt the entity secret.
curl --request GET \
--url 'https://api.circle.com/v1/w3s/config/entity/publicKey' \
--header 'accept: application/json' \
--header 'authorization: Bearer <API_KEY>'
{
"data": {
"publicKey": "-----BEGIN RSA PUBLIC KEY-----\nMIICIjANBgkqhkiG9w0BAQEFAAOCAg8AMIICCgKCAgEAsL4dzMMQX8pYbiyj0g5H\nFGwdygAA2xDm2VquY8Sk0xlOC0yKr+rqUrqnZCj09vLuXbg1BreO/BP4F4GEIHgR\nBNT+o5Q8k0OqxLXmcm5sz6CPlsFCom+MiOj6s7RD0SXg91WF8MrN88GyN53xkemA\nOYU1AlIt4dVIrFyGY8aQ57sbWHyIjim+do1kBX+svIA/FLHG/sycoGiPU1E+Kydf\nlEDga4iR2DSbW6Zte9cGDg9Ivw/seNd0TLzJz6oC9XgSK5Et6/ZpOmqJgvISQ6rT\nK15DJ8EzIOzZZuEVOefgy1S7rLdSH7DexuR4W7T+KpP/f8Px0bxd4N6MT5V5kBYa\ngYHHIvqlJvXe5EzwidIWk1rg1X+YJt2M48h3Pr9HeECcmrnEYOgp32m/9lJ8vKp9\nhNh0rEKww/ULd1HqCEm/I0QGuji13XcGxVo5+7KCb/C76CNdW3pdRMn6fwFh4WVu\nu99iRc9OZhlkphysWm44hs1ZPpMCAkKttWjhnLZwIatN27x2JUqoCEUOho19iT+F\nwlPFA7E0Ju9Rqm68AkCXxHsJsAuGT8m6FLQZLHv4JyO/QEVzD7vY08A2I5dz1mVt\ngVam1/05Axju6poRomx/DUxiR0QH1+0Kg15+2A0fRkBggTTn7kvGsgz0cqk9cTm0\nEITpIVGcSGrVNRrmSye2OW0CAwEAAQ==\n-----END RSA PUBLIC KEY-----\n"
}
}
- Add the public key into
generate_entity_secret_ciphertext.file
.
# python/generate_entity_secret_ciphertext.py
# Paste your entity public key here.
public_key_string = '-----BEGIN RSA PUBLIC KEY-----\nMIICIjANBgkqhkiG9w0BAQEFAAOCAg8AMIICCgKCAgEA5e7nx8XJw7Sbzn0146K/\nzbTQvOrWlsdhqmifGfvl8IKMwGfvaMqjaRhDxTAZs4/zuLJPQyOP1JopZwWsW10B\nIw6OydfXLbBQeMO3U03nSOcNrqPgrCAzKJyRgrtpMtrnbBdhZu3GoMVH7Ixx7XXl\nOCJ7N3nLlQ+kGBSRameMckYzxEaIDe0/GbKeh3oGqYVHIbckKsGyNxmAmwgyAPCp\n4pcc2oK8DtJsBvNMUPkdN4zONKsfTTIjIjNPPVxInS+OFHdrFREY59uGVkU+W5fi\nbM6bJnAFdjOcIII9JWkvfrbzVneTS2IPxCakfzBFRteVEhZKQIuba/adut00Qnu1\nA0HetKNIdKfriscqPwXO+OfP0dKLAmIgZu7bkAxwUiuDWoMJREae0KdjxV95DFUr\nnnIUIHGXDBx9nE4pbKBzNGxUZeWnkFp///FyEtx4l5fe9vGO/GhZHSoOBHG0BOi1\npzaU23geINpq+Fp5iL6RifCvtaaXP4f6TyvhBmJKd3ADbsr+hk9xEzZgciSmwYo9\ny2zZ7nlMM3VqEz5visz8Id+ecoiBnAAJS+7x8C3MQWpVV5hPwzqBMzvrROyKo94b\n0g0SwWLRZV+MFV/szl8iNbh46esrz9HR51r8F7xnkHZm7Tno1STKHFXLKO/IRSBs\nFQ1kHe0Kp6WbimXvEC8mWisCAwEAAQ==\n-----END RSA PUBLIC KEY-----\n'
// golang/generate_entity_secret_ciphertext.go
// Paste your entity public key here.
var publicKeyString = "-----BEGIN RSA PUBLIC KEY-----\nMIICIjANBgkqhkiG9w0BAQEFAAOCAg8AMIICCgKCAgEAsL4dzMMQX8pYbiyj0g5H\nFGwdygAA2xDm2VquY8Sk0xlOC0yKr+rqUrqnZCj09vLuXbg1BreO/BP4F4GEIHgR\nBNT+o5Q8k0OqxLXmcm5sz6CPlsFCom+MiOj6s7RD0SXg91WF8MrN88GyN53xkemA\nOYU1AlIt4dVIrFyGY8aQ57sbWHyIjim+do1kBX+svIA/FLHG/sycoGiPU1E+Kydf\nlEDga4iR2DSbW6Zte9cGDg9Ivw/seNd0TLzJz6oC9XgSK5Et6/ZpOmqJgvISQ6rT\nK15DJ8EzIOzZZuEVOefgy1S7rLdSH7DexuR4W7T+KpP/f8Px0bxd4N6MT5V5kBYa\ngYHHIvqlJvXe5EzwidIWk1rg1X+YJt2M48h3Pr9HeECcmrnEYOgp32m/9lJ8vKp9\nhNh0rEKww/ULd1HqCEm/I0QGuji13XcGxVo5+7KCb/C76CNdW3pdRMn6fwFh4WVu\nu99iRc9OZhlkphysWm44hs1ZPpMCAkKttWjhnLZwIatN27x2JUqoCEUOho19iT+F\nwlPFA7E0Ju9Rqm68AkCXxHsJsAuGT8m6FLQZLHv4JyO/QEVzD7vY08A2I5dz1mVt\ngVam1/05Axju6poRomx/DUxiR0QH1+0Kg15+2A0fRkBggTTn7kvGsgz0cqk9cTm0\nEITpIVGcSGrVNRrmSye2OW0CAwEAAQ==\n-----END RSA PUBLIC KEY-----\n"
c. Generate the Entity Secret Ciphertext
- In the terminal, run the following command:
python python/generate_entity_secret_ciphertext.py
go run golang/generate_entity_secret_ciphertext.go
- As a result, you will receive a printout of the hex-encoded entity secret (64 characters) and the entity secret ciphertext (684 characters). Note down the entity secret ciphertext, we will be registering it to the developer console in the next step.
Hex encoded entity secret: 4707ece3188f69697a6c18f224720e016232e74a4b989aac97421c4b62b4527b
***
Entity secret ciphertext: gH1jOuNVbrjAAnqvu36aH5PmtxbG9ZDhnYuUzvpmGoQPTnl88XYN66Qcnp1fsI3Bp9YubJq9kDZQq6fZRhLv9/9gT0waae5mlTZX+t7rABfueX7aXZe2Yoz4xpoQ29/XOZ9HOzr79YOdY6++X91TpbaN5pPeJCqAFaRN4yh0Uh8SzokTMiBfTgY+FytD8kcLdUV1QFvYzalq2Lq3tYvi5QG4rr0ONqsQeQpQiq2mKI1gH1/flYxGq0O2p93j68KiOX4J93qEkevnSWdGnJdOaMFBGOFQ26mjn97TeKKnbABOvRxTDlQuBDpQu4/Djhe4XtaSuAH2OU3PZNTERbivoHYNUIVk7rOxGcbeIzdii9JLrhU1cb2ncfBDpKXPzsNMZLbROSJUBMNnUvjcYvBvvjxPFv5Q83CZLk9/y4HH0MomEPyO8ZLkFmEQS8HpB7E89fIc6hdfrYx9e64dOX7Rvyn/SKkee9vTZEs8D0SVLca3lBbg3YKVseWWXEysvH0PGzr/oHVY5ivGNV91imXbmjczi+L3mXHNBsv3/rX9H1/RCNPWHVSPJdji/oKH9/UZxB23ciBfu1sy6EYsp6JAjUoVCv7i7W4qfI+7HHaZrx9JomAH7Vrjd4ygITWyN5zH+tIuS4QhXv6H974OA+PTtFXxF4CAqUI8Bvsxf0FREjM=
d. Register the Entity Secret Ciphertext
- Input the entity secret ciphertext in the developer console Configurator Page and click Register.

- Once registered, you will be provided a recovery file. Make sure to store this in a safe place.
e. Generate Unique Entity Secret Ciphertext
To prevent replay attacks, Circle mandates that the entity secret ciphertext is unique for each API request where the entitySecretCiphertext
is applicable. Using the same entity secret for multiple requests will lead to a failed response.
To produce a new entity secret ciphertext for each API request, run the generate_entity_secret_ciphertext.file
or embed similar logic into your application. This will result in a unique entity secret ciphertext for each applicable API request. As long as the entity secret ciphertext comes from the same registered hex-encoded entity secret, you are not required to register a new entity secret ciphertext in the developer console.
Using a unique entity secret ciphertext for each related API request ensures that the ciphertext is a one-time-use token. Even if an attacker captures a ciphertext from a previous communication, they cannot exploit it in subsequent interactions.
2. Create a Wallet Set
Make a request to POST /developer/walletSets
and create a wallet set providing the entity secret ciphertext created in step 1.
curl --request POST \
--url 'https://api.circle.com/v1/w3s/developer/walletSets' \
--header 'accept: application/json' \
--header 'content-type: application/json' \
--header 'authorization: Bearer <API_KEY>' \
--data '
{
"idempotencyKey": "8f459a01-fa23-479d-8647-6fe05526c0df",
"name": "Entity WalletSet A",
"entitySecretCiphertext": "<ENTITY_SECRET_CIPHERTEXT>"
}
'
{
"data": {
"walletSet": {
"id": "0189bc61-7fe4-70f3-8a1b-0d14426397cb",
"custodyType": "DEVELOPER",
"updateDate": "2023-08-03T17:10:51Z",
"createDate": "2023-08-03T17:10:51Z"
}
}
}
3. Create a Wallet
Make a request to POST /developer/wallets
using the walletSet.id
from step 2 and a count
of 1
as request parameters. This will create 1 new wallet on Mumbai. NOTE: don't forget to generate a new entity secret ciphertext.
curl --request POST \
--url 'https://api.circle.com/v1/w3s/developer/walletSets' \
--header 'accept: application/json' \
--header 'content-type: application/json' \
--header 'authorization: Bearer <API_KEY>' \
--data '
{
"idempotencyKey": "0189bc61-7fe4-70f3-8a1b-0d14426397cb",
"blockchains": [
"MATIC-MUMBAI"
],
"count": 1,
"entitySecretCiphertext": "<ENTITY_SECRET_CIPHERTEXT>",
"walletSetId": "71f2a6b4-ffa7-417a-ad5b-fb928753edc8"
}
'
{
"data": {
"wallets": [
{
"id": "ce714f5b-0d8e-4062-9454-61aa1154869b",
"state": "LIVE",
"walletSetId": "0189bc61-7fe4-70f3-8a1b-0d14426397cb",
"custodyType": "DEVELOPER",
"address": "0xf5c83e5fede8456929d0f90e8c541dcac3d63835",
"addressIndex": 0,
"blockchain": "MATIC-MUMBAI",
"accountType": "EOA",
"updateDate": "2023-08-03T19:33:14Z",
"createDate": "2023-08-03T19:33:14Z"
}
]
}
}
4. Acquire Gas Tokens
Obtain MATIC tokens from the Polygon Faucet and send them to the wallets wallets.address
from the previous steps response body.
5. Check the Wallet's Balance
Check the wallet balance by making a request to GET /wallets/{id}/balances
ensuring the wallet successfully received the MATIC tokens.
curl --request GET \
--url 'https://api.circle.com/v1/w3s/wallets/{id}/balances' \
--header 'accept: application/json' \
--header 'authorization: Bearer <API_KEY>'
{
"data": {
"tokenBalances": [
{
"token": {
"id": "e4f549f9-a910-59b1-b5cd-8f972871f5db",
"blockchain": "MATIC-MUMBAI",
"name": "Polygon-Mumbai",
"symbol": "MATIC-MUMBAI",
"decimals": 18,
"isNative": true,
"updateDate": "2023-06-29T02:37:14Z",
"createDate": "2023-06-29T02:37:14Z"
},
"amount": "0.2",
"updateDate": "2023-08-03T22:22:07Z"
}
]
}
}
Part Two: Write and Deploy Smart Contract
6. Writing the smart contract.
In the OpenZeppelin Contract Wizard
- Select the ERC721 template and the additional features you want to add to your contract.
- Select Mintable. By default, this selects the Ownable access control, restricting the Mintable function to the wallet owner.

- Select Open in Remix button to create a new workspace in Remix and compile the contract.
- Select Compile contract-* to compile the Solidity code into bytecode for the Ethereum Virtual Machine (EVM). We recommend using the default compiler settings which are appropriate for most use cases.
- In Remix, you don’t need to update the code at all.
- If you don't see compile contract ensure you are in the Solidity Compiler section. If not select it in the left-hand navigation bar.

- Once the compilation is complete, note down the ABI and bytecode. These values will be used in the next step.
- The compiler output is available under Compilation Details. For more information on the Solidity compiler’s outputs, see using the compiler.
- The Application Binary Interface (ABI) is the standard way to interact with contracts on an EVM from outside the blockchain and for contract-to-contract interaction.

7. Deploy Smart Contract
To deploy a smart contract we will use the POST /contracts/deploy
API. In the request body, you will provide the following values gathered from the prior steps:
entitySecretCiphertext
: unique generated entity secret cipher text from step 1.walletId
acquired from step 3.abiJSON
acquired from the Solidity Compiler in step 6.bytecode
acquired from the Solidity Compiler in step 6.
abiJSON
must be stringified i.e. quote escaped. To stringfy the JSON you can use the JsonFormatter's Stringfy tool.bytecode
must be prefixed with0x
.
curl --request POST \
--url 'https://api.circle.com/v1/w3s/contracts/deploy' \
--header 'accept: application/json' \
--header 'content-type: application/json' \
--header 'authorization: Bearer <API_KEY>' \
--data '
{
"idempotencyKey": "046b6c7f-0b8a-43b9-b35d-6489e6daee91",
"name": "First Contract Name",
"description": "First Contract Description",
"walletId": "046b6c7f-0b8a-43b9-b35d-6489e6daee91",
"blockchain": "MATIC-MUMBAI",
"feeLevel": "MEDIUM",
"constructorParameters": []
"entitySecretCiphertext": "0NtD3d3+nmgb4GqYQXzAjKF8h5Zq6sHM2k/...",
"abiJSON": "[\n\t{\n\t\t\"inputs\": [],\n\t\t\"stateMutability\": \"nonpayable\",\n\t\t\"type\": \"constructor\"\n\t},\n\t{\n\t\t\"anonymous\": false,",
"bytecode": "0x...",
}
'
If the call is successful, you will receive a transactionId
and a contractId
in the response.
{
"data": {
"contractId": "0189db12-4089-72eb-b4a8-2aee38cc38fe",
"transactionId": "7b989c65-9678-56d8-a998-d295b8b04535"
}
}
You can now call GET /contracts/{id}
to check the status of your transaction. When the transaction has been confirmed on-chain, the contract response object will contain the contractAddress
and have a status of PENDING
. After 12 block confirmations, the contract will have a status of COMPLETE
- as seen below.
curl --request GET \
--url 'https://api.circle.com/v1/w3s/contracts/{id}' \
--header 'accept: application/json' \
--header 'authorization: Bearer <API_KEY>'
{
"data": {
"contract": {
"id": "0189db84-72b7-7fcc-832b-5bf886b9a0ef",
"deploymentTransactionId": "4f5bfa38-c598-56a6-932e-8b5bbd3d5fc9",
"name": "First Contract Name",
"description": "First Contract Description",
"contractInputType": "BYTECODE",
"createDate": "2023-08-09T18:17:17Z",
"updateDate": "2023-08-09T18:17:17Z",
"archived": false,
"contractAddress": "0x1e124d7384cd34448ea5907bd0052a79355ab5eb",
"blockchain": "MATIC-MUMBAI",
"status": "COMPLETE",
"deployerAddress": "0x1bf9ad0cc2ad298c69a2995aa806ee832788218c",
"txHash": "0x241c4df6f08f9ed2b569c9f9b1cc48fb6074ffffaeee7552e716ce059161a743",
"abiJSON": "[\n\t{\n\t\t\"inputs\": [],\n\t\t\"stateMutability\": \"nonpayable\",\n\t\t\"type\": \"constructor\"\n\t},\n\t{\n\t\t\"anonymous\": false,",
"functions": [
{
"name": "approve",
"type": "function",
"inputs": [
{
"name": "to",
"type": "address"
},
{
"name": "tokenId",
"type": "uint256"
}
],
"stateMutability": "nonpayable"
}
],
"verificationStatus": "UNVERIFIED"
}
}
}
If you have configured Webhooks, then you will receive notifications on the status of the deployment transaction. Additionally, you can view your deployed contract on the Polygon Mumbai block explorer Polygonscan by running a search using contractAddress
.
Updated 9 days ago
Now that you have deployed a smart contract, you can interact with it and create your first NFT!