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:

  1. Implementations of standards like ERC20 and ERC721.
  2. Flexible role-based permissions scheme.
  3. Reusable Solidity components to build custom contracts and complex decentralized systems.

Prerequisites

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.

  1. 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
  1. In the terminal, navigate to the root of the cloned repository
  2. 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
  1. 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
  1. Copy the printed-out result. For example:
Hex encoded entity secret: 049eb963641dee155e7402f7fcdf33bd4ff12313222c9zfa39de7219296a8fd2
  1. 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.

  1. 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"
  }
}
  1. 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

  1. In the terminal, run the following command:
python python/generate_entity_secret_ciphertext.py
go run golang/generate_entity_secret_ciphertext.go
  1. 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

  1. Input the entity secret ciphertext in the developer console Configurator Page and click Register
  1. 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

  1. Select the ERC721 template and the additional features you want to add to your contract.
  2. Select Mintable. By default, this selects the Ownable access control, restricting the Mintable function to the wallet owner.
  1. Select Open in Remix button to create a new workspace in Remix and compile the contract.
  2. 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.
    1. In Remix, you don’t need to update the code at all.
    2. 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.
  1. Once the compilation is complete, note down the ABI and bytecode. These values will be used in the next step.
    1. The compiler output is available under Compilation Details. For more information on the Solidity compiler’s outputs, see using the compiler.
    2. 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:

  1. entitySecretCiphertext: unique generated entity secret cipher text from step 1.
  2. walletId acquired from step 3.
  3. abiJSON acquired from the Solidity Compiler in step 6.
  4. 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 with 0x.
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.


What’s Next

Now that you have deployed a smart contract, you can interact with it and create your first NFT!