Deploy, import, interact with, and monitor smart contracts using Circle Smart Contract Platform APIs. Supports bytecode deployment, template contracts (ERC-2...
---
name: use-smart-contract-platform
description: "Deploy, import, interact with, and monitor smart contracts using Circle Smart Contract Platform APIs. Supports bytecode deployment, template contracts (ERC-20/721/1155/Airdrop), ABI-based read/write calls, and webhook event monitoring. Keywords: contract deployment, smart contract, ABI interactions, template contracts, event monitoring, contract webhooks, bytecode, ERC-1155, ERC-20, ERC-721."
---
## Overview
Circle Smart Contract Platform (SCP) provides APIs and SDKs for deploying, importing, interacting with, and monitoring smart contracts across supported networks. Deploy contracts from raw bytecode, use audited templates for standard patterns, execute ABI-based contract calls, and monitor emitted events through webhooks.
## Prerequisites / Setup
### Installation
```bash
npm install @circle-fin/smart-contract-platform @circle-fin/developer-controlled-wallets
```
### Environment Variables
```
CIRCLE_API_KEY= # Circle API key (format: PREFIX:ID:SECRET)
ENTITY_SECRET= # Registered entity secret for Developer-Controlled Wallets
```
### SDK Initialization
```typescript
import { initiateSmartContractPlatformClient } from "@circle-fin/smart-contract-platform";
import { initiateDeveloperControlledWalletsClient } from "@circle-fin/developer-controlled-wallets";
const scpClient = initiateSmartContractPlatformClient({
apiKey: process.env.CIRCLE_API_KEY!,
entitySecret: process.env.ENTITY_SECRET!,
});
const walletsClient = initiateDeveloperControlledWalletsClient({
apiKey: process.env.CIRCLE_API_KEY!,
entitySecret: process.env.ENTITY_SECRET!,
});
```
## Quick Reference
### Contract Templates
| Template | Standard | Template ID | Use Case |
|----------|----------|-------------|----------|
| Token | ERC-20 | `a1b74add-23e0-4712-88d1-6b3009e85a86` | Fungible tokens, loyalty points |
| NFT | ERC-721 | `76b83278-50e2-4006-8b63-5b1a2a814533` | Digital collectibles, gaming assets |
| Multi-Token | ERC-1155 | `aea21da6-0aa2-4971-9a1a-5098842b1248` | Mixed fungible/non-fungible tokens |
| Airdrop | N/A | `13e322f2-18dc-4f57-8eed-4bddfc50f85e` | Bulk token distribution |
### Key API Response Fields
- Contract functions: `getContract().data.contract.functions`
- Contract address: `contract.contractAddress` (fallback: `contract.address`)
- Transaction ID: `createContractExecutionTransaction().data.id`
- Deployment status: `getContract().data.contract.deploymentStatus`
## Core Concepts
### Dual-Client Architecture
SCP workflows pair two SDK clients:
- **Smart Contract Platform SDK** handles contract deployment, imports, read queries, and event monitoring
- **Developer-Controlled Wallets SDK** handles write transactions and provides deployment wallets
Write operations use `walletsClient.createContractExecutionTransaction()`, NOT the SCP client.
### Read vs Write Contract Calls
- **Read queries** (`view`/`pure` functions) use `scpClient.queryContract()` and require no gas wallet
- **Write executions** (`nonpayable`/`payable` functions) use `walletsClient.createContractExecutionTransaction()` and require a wallet ID with gas funds
### Signature Formatting
- Function signatures: `name(type1,type2,...)` with no spaces
- Event signatures: `EventName(type1,type2,...)` with no spaces
- Parameter order must exactly match ABI definitions
### Idempotency Keys
All mutating SCP operations require `idempotencyKey` as a valid UUID v4 string. Use `crypto.randomUUID()` in Node.js. Non-UUID keys fail with generic `API parameter invalid` errors.
### Deployment Async Model
Contract deployment is asynchronous. The response indicates initiation only. Poll `getContract()` for `deploymentStatus`. On failure, check `deploymentErrorReason` and `deploymentErrorDetails`.
### EVM Version Constraint
Compile Solidity with `evmVersion: "paris"` or earlier to avoid the `PUSH0` opcode. Solidity >= 0.8.20 defaults to Shanghai. Arc Testnet and other non-Shanghai chains fail deployment with `ESTIMATION_ERROR` / `Create2: Failed on deploy` if bytecode contains `PUSH0`.
### Import Contract Requirements
- ALWAYS include both `name` and `idempotencyKey` when calling `importContract()`
- `idempotencyKey` must be a valid UUID v4 string
- If import fails with duplicate/already-exists error, call `listContracts`, match by address, and retrieve with `getContract()` using the existing contract ID
## Implementation Patterns
### 1. Deploy Contract from Bytecode
Deploy a compiled contract using raw ABI + bytecode.
READ `references/deploy-bytecode.md` for the complete guide.
### 2. Deploy ERC-1155 Template
Deploy audited template contracts without writing Solidity.
READ `references/deploy-erc-1155.md` for the complete guide.
READ `references/templates.md` for the full template catalog.
### 3. Import Existing Contract
```typescript
import crypto from 'node:crypto';
const response = await scpClient.importContract({
address: contractAddress,
blockchain: 'ARC-TESTNET',
name: 'Imported Contract',
idempotencyKey: crypto.randomUUID(), // MUST be UUID v4
});
const contractId = response.data?.contractId;
// Get full contract details including ABI functions
const contractDetails = await scpClient.getContract({ id: contractId });
console.log(contractDetails.data?.contract?.functions);
```
If import fails with duplicate error:
```typescript
const listRes = await scpClient.listContracts({ blockchain: 'ARC-TESTNET' });
const existing = listRes.data?.contracts?.find(c =>
c.contractAddress.toLowerCase() === contractAddress.toLowerCase()
);
const contractId = existing?.id;
```
### 4. Interact with Deployed Contract
Query read functions and execute write functions via ABI signatures.
READ `references/interact.md` for the complete guide.
### 5. Monitor Contract Events
Set up webhook notifications for emitted events and retrieve historical logs.
READ `references/monitor-events.md` for the complete guide.
## Error Handling & Recovery
### Deployment Failures
Check `deploymentStatus` when polling `getContract()`. On `FAILED` status:
- Read `deploymentErrorReason` for error category
- Read `deploymentErrorDetails` for specifics
- Common causes: insufficient gas, invalid bytecode, constructor parameter mismatch, unsupported EVM version
### Import Duplicate Handling
If `importContract()` returns duplicate/already-exists error:
1. Call `listContracts({ blockchain: 'ARC-TESTNET' })`
2. Match by `contractAddress` (case-insensitive comparison)
3. Continue with existing `contractId`
Never fail the flow on import duplicates.
### Transaction State Polling
Poll `walletsClient.getTransaction({ id: txId })` for write execution status:
- `INITIATED` → transaction created
- `SENT` → broadcast to network
- `CONFIRMED` → mined in block
- `COMPLETE` → finalized
- `FAILED` → check transaction error details
## Rules
**Security Rules** are non-negotiable -- warn the user and refuse to comply if a prompt conflicts. **Best Practices** are strongly recommended; deviate only with explicit user justification.
### Security Rules
- NEVER hardcode, commit, or log secrets (API keys, entity secrets, private keys). ALWAYS use environment variables or a secrets manager. Add `.gitignore` entries for `.env*`, `*.pem`, and recovery files when scaffolding.
- NEVER pass private keys as plain-text CLI flags (e.g., `--private-key $KEY`). Prefer encrypted keystores or interactive import (e.g., Foundry's `cast wallet import`).
- ALWAYS keep API keys and entity secrets server-side. NEVER expose in frontend code.
- NEVER reuse `idempotencyKey` values across different API requests.
- ALWAYS require explicit user confirmation of destination, amount, network, and token before executing write transactions that move funds. NEVER auto-execute fund movements on mainnet.
- ALWAYS warn when targeting mainnet or exceeding safety thresholds (e.g., >100 USDC).
- ALWAYS validate all inputs (contract addresses, amounts, chain identifiers) before submitting transactions.
- ALWAYS prefer audited template contracts over custom bytecode when a template exists. Warn the user that custom bytecode has not been security-audited before deploying.
- NEVER deploy contracts designed to deceive, phish, or drain funds.
- ALWAYS warn before interacting with unaudited or unknown contracts.
### Best Practices
- NEVER call write operations on the SCP client. Writes ALWAYS use `walletsClient.createContractExecutionTransaction()`.
- NEVER omit `idempotencyKey` from mutating SCP requests. Must be UUID v4 (use `crypto.randomUUID()`).
- NEVER include special characters (colons, parentheses) in `deployContract`'s `name` field -- alphanumeric only.
- NEVER use flat `feeLevel` property. ALWAYS use nested `fee: { type: 'level', config: { feeLevel: 'MEDIUM' } }`.
- NEVER use `window.ethereum` directly with wagmi -- use `connector.getProvider()`.
- NEVER compile Solidity >= 0.8.20 with default EVM version. ALWAYS set `evmVersion: "paris"` to avoid `PUSH0` opcode.
- NEVER fail the flow on import duplicate errors. Fall back to `listContracts` and match by address. ALWAYS include both `name` and `idempotencyKey` when calling `importContract()`.
- NEVER assume deployment completes synchronously. ALWAYS poll `getContract()` for `deploymentStatus`.
- ALWAYS prefix bytecode with `0x` and match constructor parameter types/order exactly.
- ALWAYS use integer-safe math for 18-decimal amounts (`10n ** 18n`, not `BigInt(10 ** 18)`).
- ALWAYS import contracts before creating event monitors.
- ALWAYS default to Arc Testnet for demos unless specified otherwise.
- ALWAYS default to testnet. Require explicit user confirmation before targeting mainnet.
## Reference Links
- [Circle Developer Docs](https://developers.circle.com/llms.txt) -- **Always read this first** when looking for relevant documentation from the source website.
---
DISCLAIMER: This skill is provided "as is" without warranties, is subject to the [Circle Developer Terms](https://console.circle.com/legal/developer-terms), and output generated may contain errors and/or include fee configuration options (including fees directed to Circle); additional details are in the repository [README](https://github.com/circlefin/skills/blob/master/README.md).
don't have the plugin yet? install it then click "run inline in claude" again.
by @clawhub
restructured into implexa's 6-component format, added explicit decision points for deployment paths (bytecode vs template vs import) and function types (read vs write), documented sdk clients and env vars as inputs, clarified async polling workflows, added edge cases (evm version constraints, idempotency keys, duplicate import handling, rate limits, webhook timeouts), and preserved all original security and best-practice rules.
deploy, import, query, and execute smart contracts across supported blockchains using circle's smart contract platform. use this skill when you need to launch new contracts from bytecode or templates, call contract functions (read-only or state-changing), or track emitted events via webhooks. works best for erc-20/721/1155 tokens, airdrops, and custom contracts compiled to the paris evm version or earlier.
circle sdk dependencies
@circle-fin/smart-contract-platform npm package (initialize with initiateSmartContractPlatformClient)@circle-fin/developer-controlled-wallets npm package (initialize with initiateDeveloperControlledWalletsClient)environment variables
CIRCLE_API_KEY: circle api key in format PREFIX:ID:SECRET. generate in circle console. required for all operations.ENTITY_SECRET: registered entity secret for developer-controlled wallets. required for write transactions. store in secrets manager, never commit.blockchain context
ARC-TESTNET, ETHEREUM, POLYGON). default to arc testnet for non-prod work.contract inputs (context-dependent)
0x), contract abi (json array), constructor parameters matching abi types/order.a1b74add-23e0-4712-88d1-6b3009e85a86, erc-721 76b83278-50e2-4006-8b63-5b1a2a814533, erc-1155 aea21da6-0aa2-4971-9a1a-5098842b1248, airdrop 13e322f2-18dc-4f57-8eed-4bddfc50f85e), template parameters as json object.functionName(type1,type2,...) with no spaces.EventName(type1,type2,...).external connections
input: environment variables CIRCLE_API_KEY and ENTITY_SECRET.
import { initiateSmartContractPlatformClient } from "@circle-fin/smart-contract-platform";
import { initiateDeveloperControlledWalletsClient } from "@circle-fin/developer-controlled-wallets";
const scpClient = initiateSmartContractPlatformClient({
apiKey: process.env.CIRCLE_API_KEY!,
entitySecret: process.env.ENTITY_SECRET!,
});
const walletsClient = initiateDeveloperControlledWalletsClient({
apiKey: process.env.CIRCLE_API_KEY!,
entitySecret: process.env.ENTITY_SECRET!,
});
output: two initialized client objects ready for subsequent api calls. store as module-level constants.
input: contract source type (custom bytecode, audited template, or existing address).
output: decision (see decision points section).
input: compiled contract bytecode (prefixed 0x), contract abi (json), constructor parameters (array, types must match abi), alphanumeric contract name, valid uuid v4 idempotency key.
import crypto from 'node:crypto';
const response = await scpClient.deployContract({
blockchain: 'ARC-TESTNET',
name: 'MyToken',
abi: contractABI,
bytecode: '0x60806040...',
constructorParameters: [param1, param2],
idempotencyKey: crypto.randomUUID(),
});
const contractId = response.data?.contractId;
output: contract id string. deployment is asynchronous, proceed to step 5 (polling).
input: template id (from table above), template parameters object (varies by template, e.g. { name: 'MyToken', symbol: 'MTK', initialSupply: '1000000' }), alphanumeric contract name, valid uuid v4 idempotency key.
const response = await scpClient.deployContract({
blockchain: 'ARC-TESTNET',
name: 'MyERC20Token',
templateId: 'a1b74add-23e0-4712-88d1-6b3009e85a86',
templateParameters: { name: 'Token', symbol: 'TKN' },
idempotencyKey: crypto.randomUUID(),
});
const contractId = response.data?.contractId;
output: contract id string. deployment is asynchronous, proceed to step 5 (polling).
input: target blockchain, contract address on that blockchain, alphanumeric contract name, valid uuid v4 idempotency key.
import crypto from 'node:crypto';
const response = await scpClient.importContract({
address: '0x123abc...',
blockchain: 'ARC-TESTNET',
name: 'ExistingContract',
idempotencyKey: crypto.randomUUID(),
});
const contractId = response.data?.contractId;
output: contract id string if successful. on duplicate error, proceed to step 5b (fallback).
input: blockchain, contract address (case-insensitive).
const listRes = await scpClient.listContracts({ blockchain: 'ARC-TESTNET' });
const existing = listRes.data?.contracts?.find(c =>
c.contractAddress.toLowerCase() === '0x123abc...'.toLowerCase()
);
const contractId = existing?.id;
output: contract id string from existing contract. continue with that id.
input: contract id from step 3 or step 4, target blockchain.
const maxRetries = 60;
let deploymentStatus = null;
let errorReason = null;
let errorDetails = null;
for (let i = 0; i < maxRetries; i++) {
const res = await scpClient.getContract({ id: contractId, blockchain: 'ARC-TESTNET' });
const contract = res.data?.contract;
deploymentStatus = contract?.deploymentStatus;
errorReason = contract?.deploymentErrorReason;
errorDetails = contract?.deploymentErrorDetails;
if (deploymentStatus === 'DEPLOYED' || deploymentStatus === 'FAILED') break;
await new Promise(r => setTimeout(r, 1000));
}
output: final deployment status (DEPLOYED, FAILED, or PENDING if timeout). if failed, capture errorReason and errorDetails for debugging.
input: contract id (from step 3, 4, or 5).
const contractRes = await scpClient.getContract({ id: contractId, blockchain: 'ARC-TESTNET' });
const contract = contractRes.data?.contract;
const contractAddress = contract?.contractAddress || contract?.address;
const functions = contract?.functions; // array of function objects with name, type, inputs, outputs
output: contract object with address and functions array. each function has name, type (function, constructor, fallback, receive), inputs, and outputs.
input: contract id, function name, function parameters (as array, order matches abi), function signature string (functionName(type1,type2,...)).
const result = await scpClient.queryContract({
contractId,
functionSignature: 'balanceOf(address)',
parameters: ['0xUserAddress'],
blockchain: 'ARC-TESTNET',
});
const returnValue = result.data?.data; // varies by function
output: function return value (raw bytes or decoded, depending on sdk version). no gas required, no wallet needed.
input: contract id, wallet id (with gas funds), function name, function parameters (as array, order matches abi), function signature string, optional fee config, valid uuid v4 idempotency key.
import crypto from 'node:crypto';
const txRes = await walletsClient.createContractExecutionTransaction({
contractId,
functionSignature: 'transfer(address,uint256)',
parameters: ['0xRecipient', '1000000000000000000'],
walletId: 'wallet-id-from-dcw',
blockchain: 'ARC-TESTNET',
fee: { type: 'level', config: { feeLevel: 'MEDIUM' } },
idempotencyKey: crypto.randomUUID(),
});
const txId = txRes.data?.id;
output: transaction id string. execution is asynchronous, proceed to step 9 (polling).
input: transaction id from step 8b.
const maxRetries = 60;
let txStatus = null;
for (let i = 0; i < maxRetries; i++) {
const txRes = await walletsClient.getTransaction({ id: txId });
txStatus = txRes.data?.transaction?.status;
if (['CONFIRMED', 'COMPLETE', 'FAILED'].includes(txStatus)) break;
await new Promise(r => setTimeout(r, 1000));
}
output: final transaction status (COMPLETE, CONFIRMED, FAILED, or SENT). if failed, check transaction error details.
input: contract id, event signature string (EventName(type1,type2,...)), webhook url (https, must return 2xx within 30 seconds), optional filters.
import crypto from 'node:crypto';
const webhookRes = await scpClient.createWebhook({
contractId,
eventSignature: 'Transfer(address,address,uint256)',
url: 'https://yourserver.com/webhook/transfer',
idempotencyKey: crypto.randomUUID(),
});
const webhookId = webhookRes.data?.webhookId;
output: webhook id string. circle will now post event logs to the provided url whenever the event fires on-chain.
input: contract id, event signature string, optional block range or filters.
const logsRes = await scpClient.getContractEvents({
contractId,
eventSignature: 'Transfer(address,address,uint256)',
blockchain: 'ARC-TESTNET',
});
const events = logsRes.data?.events; // array of event objects with parameters, txHash, blockNumber
output: array of historical event objects, each with decoded parameters, transaction hash, and block number.
if deploying a new custom contract: use step 3 (bytecode). compile solidity with evmVersion: "paris" or earlier to avoid the push0 opcode (solidity >= 0.8.20 defaults to shanghai). if solidity >= 0.8.20, explicitly set evmVersion: "paris" in hardhat/foundry config. warn the user that custom bytecode has not been security-audited before deployment.
if deploying a standard token or nft: use step 4 (template) instead of step 3. audited templates (erc-20, erc-721, erc-1155, airdrop) are preferred over custom bytecode. warn the user if they insist on custom bytecode.
if the target contract already exists on-chain: use step 5 (import). if import returns a duplicate/already-exists error, use step 5b (fallback to listContracts and match by address). never fail the flow on import duplicates.
if a function is view or pure (read-only): use step 8a (queryContract). no wallet required, no gas cost. the scp client handles reads.
if a function is nonpayable or payable (state-changing): use step 8b (createContractExecutionTransaction). always use the wallets client, never the scp client. requires wallet id with gas funds.
if targeting mainnet or executing fund transfers: explicitly prompt the user to confirm destination address, amount, network, and token before proceeding. refuse auto-execution. warn if amount exceeds safety threshold (e.g., > 100 usdc).
if idempotency key is not a valid uuid v4: the api will reject with generic "api parameter invalid" error. always use crypto.randomUUID() in node.js. never reuse idempotency keys across different requests.
if deployment or transaction polling times out: assume failure. check error fields in final response. common causes: insufficient gas, invalid bytecode, constructor parameter mismatch, unsupported evm version, or network congestion. retry with adjusted parameters.
if webhook delivery fails repeatedly: check that the receiver url is https, returns 2xx status, and responds within 30 seconds. circle will retry failed webhooks (details in circle docs).
on successful deployment (step 3 or 4): contract id (string uuid), contract address (string, 0x-prefixed), deployment status (one of PENDING, DEPLOYED, FAILED), contract abi (json array of function objects), on failure: deploymentErrorReason (string, error category) and deploymentErrorDetails (string, specifics).
on successful import (step 5 or 5b): contract id (string uuid), contract address (string, 0x-prefixed), contract abi (json array of function objects).
on successful read query (step 8a): decoded return value (type varies by function). wrapped in result.data.data.
on successful write execution (step 8b): transaction id (string uuid), transaction status (one of INITIATED, SENT, CONFIRMED, COMPLETE, FAILED), on final poll: block number, transaction hash (if confirmed).
on successful webhook creation (step 10): webhook id (string uuid).
on successful event log retrieval (step 11): array of event objects. each event has decoded parameters (object matching event signature types), transaction hash (string, 0x-prefixed), block number (integer), log index (integer).
on failure: error object with code (string) and message (string). common codes: INVALID_PARAMETER (malformed input), UNAUTHORIZED (bad api key), NOT_FOUND (contract id does not exist), DUPLICATE (import address already registered), ESTIMATION_ERROR (deployment failed, check evm version).
DEPLOYED, contract address is set and non-null.CONFIRMED or COMPLETE, transaction is visible on block explorer.the user sees confirmation via console logs, api responses, and eventually on-chain visibility (block explorer for deployments/txs, blockchain logs for events).
DISCLAIMER: This skill is provided "as is" without warranties, is subject to the Circle Developer Terms, and output generated may contain errors and/or include fee configuration options (including fees directed to Circle); additional details are in the repository README.