Getting Started

Automated Web3 Security & Compliance

Shield3 provides automated security & compliance tools for developers to protect user transactions through a customizable policy engine delivered as a custom RPC.

Reporting

All transactions are saved along with full audit trails for policy execution results. Logs can be exported to assist with compliance filing processes.

Dashboard(3)

Policy Library

Shield3 offers both standard policies, and custom policies for compliance, security, and org specific rules.

Default Policy Library

The following policies are a sample of the pre-configured policies which are available to Shield3 customers.

Policy Dashboard

1 - Strict / Permissive Mode

If strict mode, transactions are blocked by default except those allowed by specific policies. If permissive mode, transactions are allowed by default except those blocked by specific policies.

2 - Unverified Contracts (Block | Flag | Alert)

If enabled, interactions with unverified contracts that cannot be decoded are forbidden. User can configure this policy to result in block, flag for MFA, or permit with an alert.

3 - OFAC SDN Block Native and ERC20 Transactions

Block native transfers, ERC20 transfers, and ERC20 approvals to OFAC addresses. As part of its enforcement efforts, OFAC publishes a list of individuals and companies owned or controlled by, or acting for or on behalf of, targeted countries. It also lists individuals, groups, and entities, such as terrorists and narcotics traffickers designated under programs that are not country-specific. Collectively, such individuals and companies are called "Specially Designated Nationals" or "SDNs." Their assets are blocked and U.S. persons are generally prohibited from dealing with them.

4 - Flagged Bad Actor Block Native and ERC20

Blocks any transaction with a bad actor flagged by Shield3 or our partners. Flagged addresses include those associated with malicious behavior including phishing, address poisoning, fraud, impersonation, etc.

5 - Native Transfers Spending Limits (Block | MFA)

If enabled, transactions with native values (ex. ETH) over a customizable threshold are forbidden. User can configure this policy to result in block, or flag for MFA.

6 - ERC20 Stablecoin Transfers Spending Limits (Block | MFA)

If enabled, transfers and approvals with verified stablecoins (ex. USDC, DAI, USDT) over a customizable threshold are forbidden. User can configure this policy to result in block, or flag for MFA.

7 - DEX Token Swap Protection

If enabled, token swaps are permitted with verified token pairs. Users can customize which pairs are allowed, and set a slippage limit to prevent trades that might result in MEV exploitation.

Integration Details

Shield3 integrates as a custom RPC. Any transaction that would normally be broadcast via an Ethereum node like Infura or Alchemy first passes through Shield3 before being sent to the customer's preferred provider.

Policy Anatomy

On Shield3 the contract method allowlists can be described as a simple policy. NOTE this shows the inner workings of the policy engine, the dashboard provides a simple editor to set these parameters without needing to learn the policy language.

@name("Mint Method")
@message("Allow mint on specific contract on Mumbai")
@action("Notify")
permit(
  principal,
  action == Action::"0x6a627842",
  resource == Address::"0xbec332e1eb3ee582b36f979bf803f98591bb9e24"
) when {  context.transaction.network == Network::"0x013881" };

permit | forbid defines the only conditions that the RPC should broadcast the transaction. Policies can be configured to either allow everything except specific conditions, or allow nothing except specific conditions.

action Can be blank, Notify or MFA. This instructs the RPC what to do in the event that the transaction is allowed. Alerts and MFA requests are sent via webhooks.

principal defines which senders this policy applies to. Principal by itself means that it applies to all senders.

action defines which transaction types this policy applies to. In this case the function signature of mint is used.

resource defines which contracts this policy applies to. In this case it is restricted to the mint contract address.

when defines additional context. In this case it is restricting the policy to transactions on the Mumbai network.

Block List Policies

Below is a sample production policy which is used to validate transactions against block lists. Customers don't have to edit or deal with this level of detail but this is included to show how it works on a more technical level.

@name("Standard ERC20 TransferFrom Block Custom List")
@dependency("shared_addresses:block_list")
@message("Allow ERC20 transfer from unless blocked sender or recipient")
forbid(
  principal,
  action == Action::"0x23b872dd",
  resource
) when {
  (((((context["args"])["arg_0"]) has "groups") && ((((context["args"])["arg_0"])["groups"]).contains(Group::"block_list"))) || ((((context["args"])["arg_1"]) has "groups") && ((((context["args"])["arg_1"])["groups"]).contains(Group::"block_list")))) || ((principal has "groups") && ((principal["groups"]).contains(Group::"block_list")))
};
@name("Standard ERC20 TransferFrom Block Custom List")
@message("Allow ERC20 transfer from unless blocked sender or recipient")
@dependency("shared_addresses:block_list")
forbid(
  principal,
  action == Action::"0x23b872dd",
  resource
) when {
  (((((context["args"])["arg_0"]) has "groups") && ((((context["args"])["arg_0"])["groups"]).contains(Group::"block_list"))) || ((((context["args"])["arg_1"]) has "groups") && ((((context["args"])["arg_1"])["groups"]).contains(Group::"block_list")))) || ((principal has "groups") && ((principal["groups"]).contains(Group::"block_list")))
};
@dependency("shared_addresses:block_list")
@message("Allow transactions unless blocked sender or recipient or contract")
@name("Address or Contract Block Custom List")
permit(
  principal,
  action,
  resource
) when {
  !(((resource has "groups") && ((resource["groups"]).contains(Group::"block_list"))) || ((principal has "groups") && ((principal["groups"]).contains(Group::"block_list"))))
};
@dependency("shared_addresses:block_list")
@message("Allow ERC20 transfer unless blocked sender or recipient")
@name("Standard ERC20 Transfer Block Custom List")
forbid(
  principal,
  action == Action::"0xa9059cbb",
  resource
) when {
  ((((context["args"])["arg_0"]) has "groups") && ((((context["args"])["arg_0"])["groups"]).contains(Group::"block_list"))) || ((principal has "groups") && ((principal["groups"]).contains(Group::"block_list")))
};
@name("Non-Standard ERC20 Approve Block Custom List")
@message("Allow ERC20 approve unless blocked sender or recipient")
@dependency("shared_addresses:block_list")
forbid(
  principal,
  action == Action::"0x095ea7b3",
  resource
) when {
  ((((context["args"])["arg_0"]) has "groups") && ((((context["args"])["arg_0"])["groups"]).contains(Group::"block_list"))) || ((principal has "groups") && ((principal["groups"]).contains(Group::"block_list")))
};
@name("Standard ERC20 Approve Block Custom List")
@message("Allow ERC20 approve unless blocked sender or spender")
@dependency("shared_addresses:block_list")
forbid(
  principal,
  action == Action::"0x095ea7b3",
  resource
) when {
  ((((context["args"])["arg_0"]) has "groups") && ((((context["args"])["arg_0"])["groups"]).contains(Group::"block_list"))) || ((principal has "groups") && ((principal["groups"]).contains(Group::"block_list")))
};
@name("Non-Standard ERC20 Transfer Block Custom List")
@message("Allow ERC20 transfer unless blocked sender or recipient")
@dependency("shared_addresses:block_list")
forbid(
  principal,
  action == Action::"0xa9059cbb",
  resource
) when {
  ((((context["args"])["arg_0"]) has "groups") && ((((context["args"])["arg_0"])["groups"]).contains(Group::"block_list"))) || ((principal has "groups") && ((principal["groups"]).contains(Group::"block_list")))
};

Example Requests

Shield3 provides both broadcast and simulate capabilities to test the policy decision for a transaction. A broadcast call follows the JSON RPC specification for all Ethereum nodes and no additional configuration is required except using the Shield3 RPC URL. Simulate uses a custom RPC method. The api request looks like this for a valid mint request (function data is serialized using standard eth libs)

Both simulate and broadcast return & record rich context about policy execution and conditions which provide detailed audit records for customers.

{
    "jsonrpc": "2.0",
    "method": "eth_simulateTransaction",
    "params": [
        "0xf8505a85e8d4a510008307a12094bec332e1eb3ee582b36f979bf803f98591bb9e24840f71b3fba46a6278420000000000000000000000000000000000000000000000000000000000000001830138818080",
        "0x01B2f8877f3e8F366eF4D4F48230949123733897"
    ],
    "id": 42
}

When we call the simulate method with the above policy enabled on the account we get this response.

{
    "jsonrpc": "2.0",
    "result": {
        "transaction": {
            "id": "3595d30c-84e3-4ea9-9bb3-fa8fff6b238c",
            "team_id": "b9027b9d-9a3c-4dd9-807d-b0aa5c307ea0",
            "network": "0x013881",
            "tx_status": "Simulated",
            "decoded_type": "contract_with_data",
            "workflow_results": {
                "policyResults": [
                    {
                        "name": "Demo - Mint Method",
                        "result": {
                            "errors": [],
                            "reasons": [
                                {
                                    "invoked": true,
                                    "policy_id": "policy0",
                                    "annotations": {
                                        "name": "Mint Method",
                                        "action": "Notify",
                                        "message": "Allow mint on specific contract on Mumbai"
                                    }
                                }
                            ],
                            "decision": "Allow"
                        },
                        "policyId": "52fc621e-067f-476b-b493-97a261d34181",
                        "policySource": "Team"
                    }
                ],
                "routingDecision": "Notify"
            },
            "created_at": "2023-11-09 01:17:17.59499",
            "updated_at": "2023-11-09 01:17:17.797489"
        },
        "decision": "Notify"
    },
    "id": 42
}

With an invalid request (such as a different contract method or a blocked recipient) we get this response

{
    "jsonrpc": "2.0",
    "result": {
        "transaction": {
            "id": "75b43ec1-4065-40c8-9b4e-962e3a22141c",
            "team_id": "b9027b9d-9a3c-4dd9-807d-b0aa5c307ea0",
            "network": "0x013881",
            "tx_status": "Simulated",
            "decoded_type": "contract_with_data",
            "workflow_results": {
                "policyResults": [
                    {
                        "name": "Demo - Mint Method",
                        "result": {
                            "errors": [],
                            "reasons": [
                                {
                                    "invoked": false,
                                    "policy_id": "policy0",
                                    "annotations": {
                                        "name": "Mint Method",
                                        "action": "Notify",
                                        "message": "Allow mint on specific contract on Mumbai"
                                    }
                                }
                            ],
                            "decision": "Deny"
                        },
                        "policyId": "52fc621e-067f-476b-b493-97a261d34181",
                        "policySource": "Team"
                    }
                ],
                "routingDecision": "Block"
            },
            "created_at": "2023-11-09 01:33:52.252141",
            "updated_at": "2023-11-09 01:33:52.458323"
        },
        "decision": "Block"
    },
    "id": 43
}

Additional Resources

Automating Compliance and Security with Shield3

Open source policy language library