> **Can't find what you're looking for?** Use `search_docs` on the docs MCP server at `https://docs.prividium.com/api/mcp` to find what you need.
>
> **Have feedback?** Use `submit_feedback` on the same MCP server.

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

:::note
**Architecture Overview**

For a high-level understanding of Prividium's architecture and components, see
[Prividium™ Architecture](/getting-started/introduction/prividium-architecture).
:::

## Table of Contents

1. [Obtaining Authentication Tokens (SIWE Flow)](/developers/scripting/programmatic-token-management)
2. [Scripting with Viem and Ethers](#scripting-with-viem-and-ethers)
3. [Sample Contract](#sample-contract)
4. [Deploying Smart Contracts](#deploying-smart-contracts)
5. [Configuring Contract Function Permissions](#configuring-contract-function-permissions)
6. [Full Examples](#full-examples)
7. [Next Steps](#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 step is to get a valid token. You can follow
[this guide](/developers/scripting/programmatic-token-management) to programmatically get and refresh valid
tokens.

:::info[IMPORTANT]
Your account must have permissions to interact with the Prividium™ chain. Contact your network administrator to
request access.
:::

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

:::info[IMPORTANT]
Admin accounts require passkey-based authentication and are not suitable for programmatic RPC access. Use an account
with [one of required system permissions](#required-system-permissions) for automated scripts instead.
:::

:::note[NOTE]
Learn how to obtain authentication tokens in the
[Authentication Tokens](/developers/scripting/programmatic-token-management) doc.
:::

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

**basic-siwe.ts**

```typescript
import { createPrividiumClient } from 'prividium';
import { createPrividiumSiweChain } from 'prividium/siwe';
```

**basic-siwe.ts**

```typescript
    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](#full-examples) for the complete runnable Viem script.

Ethers v6.x uses the `FetchRequest` class for custom headers:

**basic-siwe.ts**

```typescript
import { FetchRequest, JsonRpcProvider } from 'ethers';
```

**basic-siwe.ts**

```typescript
    await prividium.authorize();
    const authHeaders = prividium.getAuthHeaders();
    if (!authHeaders) {
        throw new Error('Expected auth headers after authorize()');
    }

    const request = new FetchRequest(new URL('/rpc', PRIVIDIUM_API_BASE_URL).toString());
    request.setHeader('Authorization', authHeaders.Authorization);

    const provider = new JsonRpcProvider(request, undefined, { batchMaxCount: 20 });
    const balance = await provider.getBalance(TARGET_ADDRESS);
```

See [Full Examples](#full-examples) for the complete runnable Ethers script.

## Sample Contract

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

**Greeter.sol**

```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:

```bash
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](#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](/developers/scripting/programmatic-token-management)

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:

| Permission                    | Key                         | Scope                                      |
| ----------------------------- | --------------------------- | ------------------------------------------ |
| **Contract Deployment**       | `contract_deployment`       | Grants only contract deployment capability |
| **Full Sequencer RPC Access** | `full_sequencer_rpc_access` | Grants full access including deployment    |

:::note[For Network Administrators]
Configure system permissions for roles in the Admin Panel. See
[System Permissions](/configuring/users/system-permissions) for details on assigning deployment
permissions to roles.
:::

### Deployment Options

Choose one of three deployment methods:

#### Prividium™ Local Proxy

#### Option 1: Use the Prividium™ Local Proxy

The [Prividium™ NPM package](https://www.npmjs.com/package/prividium) 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):

   ```bash
   npx prividium proxy -u {{USER_PANEL_URL}}
   ```

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

   ```bash
   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:**

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

#### With JavaScript/Typescript

#### Option 2: Handle Authentication Directly in JavaScript/TypeScript

Authenticate in-process with the `prividium/siwe` SDK. It performs the SIWE exchange and exposes either ready-to-use
auth headers or an authenticated viem transport.

**Steps:**

1. Create an automation wallet and authenticate it with
   [the SIWE flow described here](/developers/scripting/programmatic-token-management).

2. Use the authenticated session in your deployment script:

**Example with Ethers (important parts):**

**deploy.ts**

```typescript
    const request = new FetchRequest(new URL('/rpc', PRIVIDIUM_API_BASE_URL).toString());
    request.setHeader('Authorization', authHeaders.Authorization);

    const provider = new JsonRpcProvider(request, undefined, { batchMaxCount: 20 });
    const signer = automationWallet.connect(provider);
    const factory = new ContractFactory(greeterAbi, GREETER_BYTECODE as BytesLike, signer);
    const contract = await factory.deploy('Hello from Prividium');
    await contract.waitForDeployment();
```

**Example with Viem (important parts):**

**deploy.ts**

```typescript
    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}`);
```

See [Full Examples](#full-examples) for complete deployment scripts.

#### Using cast

#### Option 3: Using `cast` (Foundry)

Deploy contracts using `cast` with manual token injection.

**Steps:**

1. Write a script to call the Prividium™ API to retrieve a token for a registered wallet address
   [as detailed here](/developers/scripting/programmatic-token-management).

2. Supply the external values with `env` and pass the token to `cast`:

   ```bash
   cast send \
     --rpc-url <PRIVIDIUM_RPC_URL> \
     --account <YOUR-ACCOUNT> \
     --headers "Authorization: Bearer <TOKEN>" \
     --create "$(jq -r '.bytecode.object' out/Greeter.sol/Greeter.json)"
   ```

:::note
**NOTE**

The `--headers` flag injects the Authorization header with your token.
:::

## 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](#configuring-contract-function-permissions)), 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**

```typescript
import { createPrividiumSiweChain } from 'prividium/siwe';
```

**admin.ts**

```typescript
    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'
    });
```

:::note[Admin authorization is required]
The admin account performing the call must hold a role with admin permissions on the target Prividium chain. The
permissions API returns `403` otherwise. See [System Permissions](/configuring/users/system-permissions)
for role configuration.
:::

## 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](/developers/quickstart/configuring-contracts).

:::info[IMPORTANT]
All permissions require user authentication via OIDC/JWT.
:::

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.

:::note[NOTE]
Chain administrators can find more information about contract registration and permissions in the "Configuration"
section of the Admin Panel documentation.
:::

### 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**

```typescript
import { pathToFileURL } from 'node:url';
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 ?? '506');
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`);
}

if (import.meta.url === pathToFileURL(process.argv[1]).href) {
    main().catch(console.error);
}
```

**balance.ts**

```typescript
import { pathToFileURL } from 'node:url';
import { FetchRequest, formatEther, JsonRpcProvider, Wallet } from 'ethers';
import { createPrividiumSiweChain } from 'prividium/siwe';

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 ?? '506');
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');

const automationWallet = new Wallet(PRIVATE_KEY);

const account = {
    address: automationWallet.address,
    async signMessage({ message }: { message: string }) {
        return automationWallet.signMessage(message);
    }
};

const chain = {
    id: CHAIN_ID,
    name: CHAIN_NAME,
    nativeCurrency: {
        name: 'Ether',
        symbol: 'ETH',
        decimals: 18
    }
};

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

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

    const authHeaders = prividium.getAuthHeaders();
    if (!authHeaders) {
        throw new Error('Expected auth headers after authorize()');
    }

    const request = new FetchRequest(new URL('/rpc', PRIVIDIUM_API_BASE_URL).toString());
    request.setHeader('Authorization', authHeaders.Authorization);

    const provider = new JsonRpcProvider(request, undefined, { batchMaxCount: 20 });
    const signer = automationWallet.connect(provider);
    const balance = await signer.provider?.getBalance(TARGET_ADDRESS);
    if (typeof balance === 'undefined') throw new Error('Balance not found');
    console.log(`Balance: ${formatEther(balance)} ETH`);
}

if (import.meta.url === pathToFileURL(process.argv[1]).href) {
    main().catch(console.error);
}
```

### Contract Deployment Script

**deploy.ts**

```typescript
import { pathToFileURL } from 'node:url';
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 ?? '506');
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;
}

if (import.meta.url === pathToFileURL(process.argv[1]).href) {
    main().catch(console.error);
}
```

**deploy.ts**

```typescript
import { pathToFileURL } from 'node:url';
import { type BytesLike, ContractFactory, FetchRequest, JsonRpcProvider, Wallet } from 'ethers';
import { createPrividiumSiweChain } from 'prividium/siwe';

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 ?? '506');
const CHAIN_NAME = process.env.CHAIN_NAME ?? 'Prividium Local';
const GREETER_BYTECODE = process.env.GREETER_BYTECODE;

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

const automationWallet = new Wallet(PRIVATE_KEY);

const account = {
    address: automationWallet.address,
    async signMessage({ message }: { message: string }) {
        return automationWallet.signMessage(message);
    }
};

const chain = {
    id: CHAIN_ID,
    name: CHAIN_NAME,
    nativeCurrency: {
        name: 'Ether',
        symbol: 'ETH',
        decimals: 18
    }
};

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

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

    await prividium.authorize();

    const authHeaders = prividium.getAuthHeaders();
    if (!authHeaders) {
        throw new Error('Expected auth headers after authorize()');
    }

    const request = new FetchRequest(new URL('/rpc', PRIVIDIUM_API_BASE_URL).toString());
    request.setHeader('Authorization', authHeaders.Authorization);

    const provider = new JsonRpcProvider(request, undefined, { batchMaxCount: 20 });
    const signer = automationWallet.connect(provider);
    const factory = new ContractFactory(greeterAbi, GREETER_BYTECODE as BytesLike, signer);
    const contract = await factory.deploy('Hello from Prividium');
    await contract.waitForDeployment();

    const contractAddress = await contract.getAddress();
    console.log(`Contract deployed at: ${contractAddress}`);
}

if (import.meta.url === pathToFileURL(process.argv[1]).href) {
    main().catch(console.error);
}
```

## Next Steps

* [Building Web Applications](/developers/applications/building-web-applications) - Build frontend applications that
  interact with
  Prividium™ chains
