Skip to content
Prividium

Contract Deployment and Scripting

Learn how to deploy smart contracts and write scripts that interact with Prividium™ chains.

Table of Contents

  1. Obtaining Authentication Tokens (SIWE Flow)
  2. Scripting with Viem and Ethers
  3. Sample Contract
  4. Deploying Smart Contracts
  5. Configuring Contract Function Permissions
  6. Full Examples
  7. Next Steps

Overview

Prividium™ chains enforce authentication on all RPC interactions. Unlike public blockchains where anyone can read state or submit transactions, Prividium™ requires valid JWT tokens for both read and write operations.

Scripting libraries (Ethers, Viem) support custom HTTP headers, so you can add authentication tokens to requests. This works for deployment scripts, backend services, and test suites.

This guide covers:

  • Obtaining authentication tokens via the SIWE (Sign-In With Ethereum) flow
  • Configuring Ethers and Viem to use authenticated RPC endpoints
  • Three deployment methods for smart contracts
  • Configuring contract permissions after deployment

Obtaining Authentication Tokens (SIWE Flow)

The first stop is to get a valid token. You can follow this guide to programmatically get and refresh valid tokens.

Once you have a valid auth token, you can use it to interact with your Prividium™ chain.

Scripting with Viem and Ethers

Scripting tools inject custom headers into HTTP requests, enabling direct access to the Prividium™ RPC endpoint via token-based authentication (Authorization: Bearer {token} headers).

Use scripts for:

  • Backend services
  • Contract deployments
  • Automated transactions

Viem can use the authenticated transport returned by createPrividiumSiweChain():

basic-siwe.ts
import { createPrividiumClient } from 'prividium';
import { createPrividiumSiweChain } from 'prividium/siwe';
basic-siwe.ts
    const prividium = createPrividiumSiweChain({
        account,
        chain,
        prividiumApiBaseUrl,
        domain: process.env.DOMAIN ?? 'localhost:3000'
    });

    await prividium.authorize();

    const client = createPrividiumClient({
        chain: prividium.chain,
        transport: prividium.transport,
        account: prividium.address
    });

    const balance = await client.getBalance({ address: prividium.address });

See Full Examples for the complete runnable Viem script.

Sample Contract

The examples in this guide use a minimal Greeter contract. Create a file called Greeter.sol:

Greeter.sol
// SPDX-License-Identifier: MIT
pragma solidity ^0.8.19;

contract Greeter {
    string private greeting;

    constructor(string memory _initialGreeting) {
        greeting = _initialGreeting;
    }

    function getGreeting() external view returns (string memory) {
        return greeting;
    }

    function updateGreeting(string memory _newGreeting) external {
        greeting = _newGreeting;
    }
}

Compile it with Foundry to get the deployment bytecode:

forge build
export GREETER_BYTECODE=$(jq -r '.bytecode.object' out/Greeter.sol/Greeter.json)

The GREETER_BYTECODE value is used by the deployment scripts in the Full Examples section.

Deploying Smart Contracts

Prerequisites

Contract deployment requires:

  • User account with a role that has the appropriate system permission (see below)
  • Authentication token

Deployment attempts without these permissions fail with an error.

Required System Permissions

To deploy contracts, your user must have a role with one of the following system permissions:

PermissionKeyScope
Contract Deploymentcontract_deploymentGrants only contract deployment capability
Full Sequencer RPC Accessfull_sequencer_rpc_accessGrants full access including deployment

Deployment Options

Choose one of three deployment methods:

Option 1: Use the Prividium™ Local Proxy

The Prividium™ NPM package provides a CLI tool that runs a local proxy. The proxy automatically injects authentication headers into your RPC requests.

Steps:

  1. Start the local proxy. For browser-based login (interactive):

    npx prividium proxy -u {{USER_PANEL_URL}}

    Or for non-interactive use (CI, scripts), authenticate with a private key:

    npx prividium proxy --api-url {{PRIVIDIUM_API_URL}} --private-key 0xYOUR_KEY
  2. The proxy will:

    • Discover the API URL from the User Panel (browser mode) or use the provided --api-url (private key mode)
    • Authenticate via browser sign-in or SIWE
    • Start a local proxy server that injects auth headers
  3. Deploy contracts using the local endpoint. The proxy injects authentication headers and forwards requests to the Prividium™ RPC.

Example usage:

# After starting the proxy, use the local endpoint
forge script DeployScript \
  --rpc-url http://127.0.0.1:24101 \
  --broadcast

Registering Deployed Contracts Programmatically

Deployment alone is not enough: until a contract is registered with the permissions API, all of its functions remain Forbidden. Chain administrators can register contracts manually in the Admin Panel (see next section), but a service holding admin credentials can also register them in-process via the SDK's admin namespace — useful for fully-automated deployment pipelines.

admin.ts
import { createPrividiumSiweChain } from 'prividium/siwe';
admin.ts
    const admin = createPrividiumSiweChain({
        account: privateKeyToAccount(ADMIN_PRIVATE_KEY),
        chain: { id: CHAIN_ID, name: 'Prividium™ Chain' },
        prividiumApiBaseUrl: PRIVIDIUM_API_BASE_URL,
        domain: DOMAIN
    });

    await admin.authorize();

    // Whitelist the deployed contract against a permissions template.
    // The template controls which methods are exposed and to whom.
    const response = await admin.admin.contracts.create({
        contractAddress: DEPLOYED_CONTRACT_ADDRESS,
        templateKey: 'erc20-token',
        abi: JSON.stringify(erc20Abi),
        name: null,
        description: null,
        discloseErc20TotalSupply: false,
        discloseBytecode: false,
        disclosureStartBlock: '0x0'
    });

Configuring Contract Function Permissions

All contract functions default to "Forbidden" until configured. Chain administrators must register new contracts and configure function permissions in the Prividium™ Admin Panel after deployment. An example of configuring contract permissions programmatically can be found in the developer quickstart.

  1. Register the contract: Provide the contract name, address, and ABI in the admin panel

  2. View Function List: Review all functions from the contract ABI

  3. Edit Permissions: For each function:

    • Select permission type (Forbidden, All Users, Check Role, etc.)
    • Configure role requirements (if using role-based permissions)
    • Set argument restrictions (if needed)
    • Save changes
  4. Verify Configuration: Permissions activate immediately. Test by calling functions from your application.

Permission Enforcement

Read Functions (view/pure)

  • Permission check occurs during eth_call
  • Unauthorized calls return an error

Write Functions (nonpayable/payable)

  • Permission checks occur twice:
    • Simulation phase (eth_call for gas estimation)
    • Execution phase (eth_sendRawTransaction)
  • Both checks must pass for transaction success

Full Examples

Authenticated Balance Script

balance.ts
import { createPrividiumClient } from 'prividium';
import { createPrividiumSiweChain } from 'prividium/siwe';
import { defineChain, formatEther, type Hex } from 'viem';
import { privateKeyToAccount } from 'viem/accounts';

const PRIVIDIUM_API_BASE_URL = process.env.PRIVIDIUM_API_BASE_URL ?? 'http://localhost:8000';
const PRIVATE_KEY = process.env.PRIVATE_KEY;
const DOMAIN = process.env.DOMAIN ?? 'localhost:3000';
const CHAIN_ID = Number(process.env.CHAIN_ID ?? '6565');
const CHAIN_NAME = process.env.CHAIN_NAME ?? 'Prividium Local';
const TARGET_ADDRESS = process.env.TARGET_ADDRESS ?? '0xf39Fd6e51aad88F6F4ce6aB8827279cffFb92266';

if (!PRIVATE_KEY) throw new Error('MISSING PRIVATE KEY');

export const account = privateKeyToAccount(PRIVATE_KEY as Hex);
export const chain = defineChain({
    id: CHAIN_ID,
    name: CHAIN_NAME,
    nativeCurrency: {
        name: 'Ether',
        symbol: 'ETH',
        decimals: 18
    },
    rpcUrls: {
        default: {
            http: [new URL('/rpc', PRIVIDIUM_API_BASE_URL).toString()]
        }
    }
});

export const prividium = createPrividiumSiweChain({
    account,
    chain,
    prividiumApiBaseUrl: PRIVIDIUM_API_BASE_URL,
    domain: DOMAIN
});

export async function main() {
    await prividium.authorize();

    const client = createPrividiumClient({
        chain: prividium.chain,
        transport: prividium.transport,
        account: prividium.address
    });

    const balance = await client.getBalance({ address: TARGET_ADDRESS });
    console.log(`Balance: ${formatEther(balance)} ETH`);
}

main().catch(console.error);

Contract Deployment Script

deploy.ts
import { createPrividiumClient } from 'prividium';
import { createPrividiumSiweChain } from 'prividium/siwe';
import { createWalletClient, defineChain, type Hex } from 'viem';
import { privateKeyToAccount } from 'viem/accounts';

const PRIVIDIUM_API_BASE_URL = process.env.PRIVIDIUM_API_BASE_URL ?? 'http://localhost:8000';
const PRIVATE_KEY = process.env.PRIVATE_KEY;
const DOMAIN = process.env.DOMAIN ?? 'localhost:3000';
const CHAIN_ID = Number(process.env.CHAIN_ID ?? '6565');
const CHAIN_NAME = process.env.CHAIN_NAME ?? 'Prividium Local';
const GREETER_BYTECODE = process.env.GREETER_BYTECODE;

const greeterAbi = [
    {
        type: 'constructor',
        inputs: [{ name: 'initialGreeting', type: 'string' }],
        stateMutability: 'nonpayable'
    }
];

if (!PRIVATE_KEY) throw new Error('MISSING PRIVATE KEY');
if (!GREETER_BYTECODE) throw new Error('MISSING GREETER_BYTECODE');

export async function main() {
    const account = privateKeyToAccount(PRIVATE_KEY as Hex);
    const chain = defineChain({
        id: CHAIN_ID,
        name: CHAIN_NAME,
        nativeCurrency: {
            name: 'Ether',
            symbol: 'ETH',
            decimals: 18
        },
        rpcUrls: {
            default: {
                http: [new URL('/rpc', PRIVIDIUM_API_BASE_URL).toString()]
            }
        }
    });

    const prividium = createPrividiumSiweChain({
        account,
        chain,
        prividiumApiBaseUrl: PRIVIDIUM_API_BASE_URL,
        domain: DOMAIN
    });

    const rpcClient = createPrividiumClient({
        chain: prividium.chain,
        transport: prividium.transport,
        account: account.address
    });

    await prividium.authorize();

    const walletClient = createWalletClient({
        account,
        chain: prividium.chain,
        transport: prividium.transport
    });

    const hash = await walletClient.deployContract({
        abi: greeterAbi,
        bytecode: GREETER_BYTECODE,
        args: ['Hello from Prividium']
    });

    const receipt = await rpcClient.waitForTransactionReceipt({ hash });
    console.log(`Contract deployed at: ${receipt.contractAddress}`);
    return receipt.contractAddress;
}

main().catch(console.error);

Next Steps