> ## Documentation Index
> Fetch the complete documentation index at: https://docs.layerzero.network/llms.txt
> Use this file to discover all available pages before exploring further.

# Gasolina Implementation Guide

> Step-by-step instructions for deploying Gasolina on AWS or Google Cloud Platform, including configuration, signer setup, and integration with LayerZero.

This guide provides step-by-step instructions for deploying your own Gasolina instance on AWS or Google Cloud Platform. Gasolina is a REST API service that verifies LayerZero messages and produces signatures for DVN contracts.

## Prerequisites

Before deploying Gasolina, ensure you have:

| Requirement | Details |
| - | - |
| **Cloud Provider Account** | AWS account with CDK permissions, or GCP account with billing enabled |
| **Node.js** | Version 18 or higher |
| **Package Manager** | npm, yarn, or pnpm |
| **Cloud CLI** | AWS CLI + CDK CLI, or gcloud CLI + Terraform |
| **RPC Providers** | Reliable endpoints for each supported chain. Production requires at least 3 to 4 independent provider entities per chain (multiple URLs from one vendor count as one); see the [production hardening checklist](#production-hardening-checklist). |

***

## AWS Implementation

### Step 1: Clone and Setup

```bash wrap theme={null}
# Clone the repository
git clone https://github.com/LayerZero-Labs/gasolina-aws.git
cd gasolina-aws

# Install dependencies
pnpm install
# or: yarn install
```

### Step 2: Configure AWS Authentication

```bash wrap theme={null}
# Configure AWS CLI with your credentials
aws configure

# Verify authentication
aws sts get-caller-identity
```

### Step 3: Choose Signer Type

You have two options for managing signing keys:

<Tabs>
  <Tab title="AWS KMS (Recommended)">
    AWS KMS provides HSM-backed signing keys that are automatically created and managed:

    1. Set `signerType: 'KMS'` in your configuration
    2. Specify `kmsNumOfSigners` for the number of keys to create
    3. Keys are automatically created during CDK deployment

    **Benefits:**

    * Hardware security module protection
    * Automatic key rotation support
    * No manual secret management
  </Tab>

  <Tab title="Mnemonic-based">
    <Warning>
      Mnemonic-based signing is for local development and testing only. Mnemonics are extractable secrets and must never be used for production signers; production deployments must use AWS KMS (HSM-backed). Even for development, store mnemonics in AWS Secrets Manager or similar and never commit them to version control. See the [production hardening checklist](#production-hardening-checklist).
    </Warning>

    For mnemonic-based signers, create secrets in AWS Secrets Manager:

    ```bash wrap theme={null}
    # Create a secret for each signer
    aws secretsmanager create-secret \
        --name "gasolina-signer-1" \
        --secret-string '{
            "LAYERZERO_WALLET_MNEMONIC": "your twelve word mnemonic phrase here",
            "LAYERZERO_WALLET_PATH": "m/44'"'"'/60'"'"'/0'"'"'/0/0"
        }'
    ```
  </Tab>
</Tabs>

### Step 4: Configure Infrastructure

Edit `cdk/gasolina/config/index.ts`:

```typescript wrap theme={null}
export const CONFIG = {
  '123456789012': {
    // Your AWS account number
    projectName: 'my-gasolina-mainnet', // Must be globally unique
    environment: 'mainnet', // or "testnet"
    availableChainNames: 'ethereum,bsc,avalanche,polygon,arbitrum,optimism',
    signerType: 'KMS', // production must use KMS; MNEMONIC is for local development/testing only
    kmsNumOfSigners: 3, // Only if using KMS
    // Optional: Extra context verification
    extraContextGasolinaUrl: 'https://your-verification-api.com/verify',
  },
};
```

| Config Option | Description |
| - | - |
| `projectName` | Unique project identifier (used for S3 bucket naming) |
| `environment` | `mainnet` or `testnet` |
| `availableChainNames` | Comma-separated list of supported chains |
| `signerType` | `KMS` (HSM-backed, required for production) or `MNEMONIC` (development/testing only) |
| `kmsNumOfSigners` | Number of KMS signing keys to create |
| `extraContextGasolinaUrl` | Optional custom verification endpoint |

### Step 5: Configure RPC Providers

Edit `cdk/gasolina/config/providers/mainnet/providers.json`. Key each entry by chain name, list the independent provider entities under `uris`, and set `quorum` to how many must return matching responses:

```json wrap theme={null}
{
  "ethereum": {
    "uris": [
      "https://eth-mainnet.g.alchemy.com/v2/YOUR-API-KEY",
      "https://mainnet.infura.io/v3/YOUR-PROJECT-ID",
      "https://ethereum-rpc.publicnode.com"
    ],
    "quorum": 3
  },
  "bsc": {
    "uris": [
      "https://bsc-dataseed1.binance.org",
      "https://bsc-rpc.publicnode.com",
      "https://bsc-mainnet.nodereal.io/v1/YOUR-API-KEY"
    ],
    "quorum": 3
  }
}
```

<Tip>
  * Use at least 3 to 4 independent provider entities per chain for production quorum, not just failover; multiple URLs from a single vendor count as one fault domain
  * Prioritize reliable, independent providers (different vendors such as Alchemy, Infura, QuickNode, or an operator-run node)
  * Set `quorum` to the number of providers that must return matching responses (production: at least 3)
</Tip>

### Step 6: Configure Wallet Definitions (Mnemonic Only)

<Note>
  This step applies only to the development and testing mnemonic flow. Production deployments use KMS (Step 3) and do not configure mnemonic wallet definitions.
</Note>

If using mnemonics, edit `cdk/gasolina/config/walletConfig/mainnet.json`:

```json wrap theme={null}
{
  "definitions": [
    {
      "address": "0x1234567890123456789012345678901234567890",
      "secretName": "gasolina-signer-1"
    },
    {
      "address": "0x0987654321098765432109876543210987654321",
      "secretName": "gasolina-signer-2"
    }
  ]
}
```

### Step 7: Bootstrap CDK (First Time Only)

```bash wrap theme={null}
cd cdk/gasolina
cdk bootstrap
```

### Step 8: Deploy Infrastructure

```bash wrap theme={null}
# Review the deployment plan
cdk diff

# Deploy the infrastructure
cdk deploy
```

After successful deployment, you'll see:

```
Outputs:
Oracle.ApiGatewayUrl = https://xxxxxxxxxx.execute-api.region.amazonaws.com
```

### Step 9: Test Deployment

<Warning>
  For production (`mainnet`), Gasolina must sit behind an authenticated gateway (IAM/SigV4, mTLS, or private networking) and status routes such as `/signer-info` and `/provider-health` must not be reachable by unauthenticated callers (see the [production hardening checklist](#production-hardening-checklist)). Run these probes through your authenticated control plane, not against a public URL.
</Warning>

```bash wrap theme={null}
# Test the health endpoint
curl https://xxxxxxxxxx.execute-api.region.amazonaws.com

# Test signer info
curl "https://xxxxxxxxxx.execute-api.region.amazonaws.com/signer-info?chainName=ethereum"

# Run comprehensive test
cd ../../  # Back to repository root
ts-node scripts/testDeployment.ts -u https://xxxxxxxxxx.execute-api.region.amazonaws.com -e mainnet
```

A successful response looks like:

```
--- [200] Successful request ---
Response: {
  signatures: [
    { signature: '<signature>', address: '<address>' },
    { signature: '<signature>', address: '<address>' }
  ]
}
```

***

## Google Cloud Platform Implementation

### Step 1: Clone and Setup

```bash wrap theme={null}
# Clone the repository
git clone https://github.com/LayerZero-Labs/gasolina-gcp.git
cd gasolina-gcp

# Install dependencies
yarn install
```

### Step 2: Configure GCP Project

```bash wrap theme={null}
# Set your project
gcloud config set project YOUR-PROJECT-ID

# Authenticate
gcloud auth application-default login

# Enable required APIs
gcloud services enable cloudkms.googleapis.com
gcloud services enable run.googleapis.com
gcloud services enable secretmanager.googleapis.com
```

### Step 3: Create Terraform Backend Storage

```bash wrap theme={null}
# Create a GCS bucket for Terraform state
gsutil mb -p YOUR-PROJECT-ID -l US-EAST1 gs://your-project-gasolina-tfstate
```

### Step 4: Configure Terraform Backend

Edit `terraform/lz-mainnet-verifier.backend.conf`:

```hcl wrap theme={null}
bucket = "your-project-gasolina-tfstate"
prefix = "mainnet"
```

### Step 5: Configure Infrastructure Variables

Edit `terraform/lz-mainnet-verifier.tfvars`:

```hcl wrap theme={null}
/* Project variables */
project    = "your-gcp-project"
project_id = "123456789012"
region     = "us-east1"
zone       = "us-east1-c"

/* General variables */
env = "mainnet"

/* KMS-HSM variables */
num_signers = 3

/* App variables */
app_name = "gasolina-api"
available_chain_names = "ethereum,bsc,avalanche,polygon,arbitrum,optimism"
```

### Step 6: Configure RPC Providers

Edit `terraform/providers-mainnet.json`. Key each entry by chain name, list the independent provider entities under `uris`, and set `quorum` to how many must return matching responses:

```json wrap theme={null}
{
  "ethereum": {
    "uris": [
      "https://eth-mainnet.g.alchemy.com/v2/YOUR-API-KEY",
      "https://mainnet.infura.io/v3/YOUR-PROJECT-ID",
      "https://ethereum-rpc.publicnode.com"
    ],
    "quorum": 3
  },
  "bsc": {
    "uris": [
      "https://bsc-dataseed1.binance.org",
      "https://bsc-rpc.publicnode.com",
      "https://bsc-mainnet.nodereal.io/v1/YOUR-API-KEY"
    ],
    "quorum": 3
  }
}
```

<Tip>
  * Use at least 3 to 4 independent provider entities per chain for production quorum; multiple URLs from a single vendor count as one fault domain
  * Set `quorum` to the number of providers that must return matching responses (production: at least 3)
</Tip>

### Step 7: Deploy with Terraform

```bash wrap theme={null}
cd terraform

# Initialize Terraform
terraform init -backend-config=lz-mainnet-verifier.backend.conf -reconfigure

# Review the deployment plan
terraform plan --var-file=lz-mainnet-verifier.tfvars

# Apply the deployment
terraform apply --var-file=lz-mainnet-verifier.tfvars
```

### Step 8: Test Deployment

<Warning>
  For production (`mainnet`), the Cloud Run service must require authentication (deploy with `--no-allow-unauthenticated` and front it with an authenticated gateway or private ingress) so these probes run through the authenticated control plane rather than the public default Cloud Run URL, and status routes such as `/signer-info` and `/provider-health` are not anonymously reachable (see the [production hardening checklist](#production-hardening-checklist)).
</Warning>

```bash wrap theme={null}
# Get the Cloud Run URL
GASOLINA_URL=$(gcloud run services describe gasolina-api \
    --region=us-east1 \
    --format='value(status.url)')

# Test health endpoint
curl $GASOLINA_URL

# Test signer info
curl "$GASOLINA_URL/signer-info?chainName=ethereum"

# Run comprehensive test
cd ..  # Back to repository root
ts-node scripts/testDeployment.ts -u $GASOLINA_URL -e mainnet
```

***

## Integration with LayerZero

Once your Gasolina instance is deployed and tested:

### Step 1: Share Gasolina URL

Provide your Gasolina API endpoint to LayerZero Labs. This must be the authenticated gateway endpoint (IAM/SigV4, mTLS, or private-network ingress), not a publicly open URL; coordinate the required caller credentials or role with LayerZero so requests are authenticated per the [production hardening checklist](#production-hardening-checklist):

```
https://your-gasolina-instance.com
```

### Step 2: DVN Contract Deployment

LayerZero will:

1. Query your `/signer-info` endpoint to retrieve signer addresses
2. Deploy DVN contracts on all supported chains with:
   * Your signer addresses registered
   * Agreed-upon signer threshold
   * Essence wallet as initial `ADMIN_ROLE` holder
3. Provide you with the DVN contract addresses for each chain

```mermaid wrap theme={null}
sequenceDiagram
participant LZ as LayerZero
participant G as Your Gasolina
participant C as Chains

    LZ->>G: GET /signer-info
    G->>LZ: Signer addresses
    LZ->>C: Deploy DVN contracts
    C->>LZ: Contract addresses
    LZ->>You: DVN addresses for OApp config
```

### Step 3: OApp Configuration

OApps configure your DVN using the contract addresses:

```solidity wrap theme={null}
// Example UlnConfig for OApp
UlnConfig({
    confirmations: 15,
    requiredDVNCount: 2,
    optionalDVNCount: 0,
    optionalDVNThreshold: 0,
    requiredDVNs: [
        0xYourDVNContractAddress,  // Your Gasolina DVN
        0xOtherDVNAddress          // Another DVN
    ],
    optionalDVNs: []
})
```

***

## Advanced Configuration

### Extra Context Verification

Extra-context verification is optional and owned by the Gasolina operator, who runs the verification endpoint and decides what it accepts or rejects. It runs in addition to Gasolina's mandatory chain-derived checks, never instead of them — it can only make signing stricter, never looser. Enable it deliberately, and only with a policy whose intent the asset issuer / OApp has agreed to; a generic DVN should not impose app-specific signing decisions on its own (see [Additional recommendations for OApp-owned DVNs](#additional-recommendations-for-oapp-owned-dvns)).

Add custom verification logic by implementing an API endpoint:

```typescript wrap theme={null}
// Your custom verification API
app.post('/verify', async (req, res) => {
  const {sentEvent, from} = req.body;

  // Implement your verification rules
  const isValid = await verifyCustomRules(sentEvent, from);

  res.json(isValid);
});
```

**API Input Schema:**

```typescript wrap theme={null}
{
  sentEvent: {
    lzMessageId: {
      pathwayId: {
        srcEid: number,    // Source endpoint ID
        dstEid: number,    // Destination endpoint ID
        sender: string,    // Sender OApp address
        receiver: string,  // Receiver OApp address
      },
      nonce: number,
    },
    guid: string,
    message: string,
    options: { /* ... */ },
    onChainEvent: {
      chainName: string,
      txHash: string,
      blockNumber: number,
    }
  },
  from: string  // Transaction initiator
}
```

The endpoint must return a bare JSON boolean: `true` allows Gasolina to sign, and `false` refuses signing.

Configure the URL in your infrastructure config:

```typescript wrap theme={null}
extraContextGasolinaUrl: 'https://your-api.com/verify';
```

<Warning>
  Returning `false` refuses to sign. If Gasolina is configured as a required DVN for the pathway, the message cannot reach `VERIFIABLE` because the destination ULN waits for every required DVN stamp. It will not be committed or delivered until the message is signed, the OApp changes its ULN config, or the nonce is skipped or unblocked through the appropriate Endpoint action, such as skip, nilify, or burn, depending on state.

  A refused nonce keeps later messages on the same pathway from becoming executable, because delivery depends on preceding nonces being verified or skipped. Before enabling extra-context checks, define asset-issuer and ecosystem impact, who can change or disable the policy, and how refused messages are unblocked. Log and alert on policy-check failures.
</Warning>

### Multi-Signer Setup

For enhanced security and availability, deploy multiple Gasolina instances with different signers:

| Instance | Endpoint | Signers |
| - | - | - |
| Gasolina 1 | `https://gasolina1.example.com` | Signers A, B |
| Gasolina 2 | `https://gasolina2.example.com` | Signers C, D |
| Gasolina 3 | `https://gasolina3.example.com` | Signers E, F |

Essence requests signatures from all instances in parallel, then combines them for submission.

Configure the signer threshold to require signatures from multiple instances (e.g., 4 of 6 signers across 3 instances).

***

## Managing Configuration Changes

Operator control over DVN configuration is split into two steps: producing a quorum-signed change payload, then submitting that payload on-chain. The scripts below generate the `ExecuteParam`/calldata and signer quorum signatures for signer-threshold and signer-set changes. They do not execute the change, submit a transaction, or pay gas.

After generation, the payload must be submitted before its expiration. For normal signer and threshold changes, submission goes through the DVN `execute(ExecuteParam[])` path and must be sent by an account with the DVN admin role; today operators typically send the generated payload to LayerZero Labs for submission through Essence. The separate `quorumChangeAdmin` recovery path is different: any account can submit a valid quorum-signed admin-change payload, because the contract verifies the signatures rather than trusting the sender.

The signer quorum retains ultimate control: via `quorumChangeAdmin`, it can reassign the admin role without the current admin's cooperation. See [Emergency Admin Takeover](#emergency-admin-takeover).

### Change Signer Threshold

<Warning>
  This procedure changes the on-chain DVN signer threshold, not the RPC provider quorum in `providers.json`. Never set the production signer threshold below 2. Lowering the threshold below 2 should happen only as a documented emergency risk exception, and all threshold changes should go through reviewed governance or operational procedures.
</Warning>

```bash wrap theme={null}
ts-node scripts/configChangePayloads/createSetQuorumSignatures.ts \
    -e mainnet \
    -c ethereum,bsc,avalanche \
    --oldQuorum 2 \
    --newQuorum 3
```

### Add a Signer

```bash wrap theme={null}
ts-node scripts/configChangePayloads/createAddOrRemoveSignerSignatures.ts \
    -e mainnet \
    -c ethereum,bsc,avalanche \
    -q 2 \
    --signerAddress 0xNewSignerAddress \
    --shouldRevoke 0
```

### Remove a Signer

```bash wrap theme={null}
ts-node scripts/configChangePayloads/createAddOrRemoveSignerSignatures.ts \
    -e mainnet \
    -c ethereum,bsc,avalanche \
    -q 2 \
    --signerAddress 0xOldSignerAddress \
    --shouldRevoke 1
```

### Emergency Admin Takeover

The DVN contract includes a `quorumChangeAdmin` function that allows the signer quorum to reassign the admin role **without requiring the current admin's permission**. This ensures signers maintain ultimate control over the DVN even if the admin role has been delegated to a service like Essence.

#### ExecuteParam Structure

The function accepts a single `ExecuteParam` struct:

```solidity wrap theme={null}
struct ExecuteParam {
    uint32 vid;           // DVN instance identifier (endpoint v1 eid or v2 eid % 30000)
    address target;       // DVN contract address (must equal address(this))
    bytes callData;       // abi.encode(newAdminAddress)
    uint256 expiration;   // Unix timestamp (must be in the future)
    bytes signatures;     // Concatenated quorum signatures, sorted by signer address
}
```

#### Step-by-Step Implementation

**Step 1: Prepare the Parameters**

```javascript wrap theme={null}
const ethers = require('ethers');

// Configuration
const dvnAddress = '0xYourDVNContractAddress';
const newAdminAddress = '0xYourControlledAddress';
const vid = 101; // Your DVN's vid (check contract or use endpoint v1 eid)
const expiration = Math.floor(Date.now() / 1000) + 7 * 24 * 60 * 60; // 1 week from now

// Encode the new admin address as callData
const callData = ethers.utils.defaultAbiCoder.encode(['address'], [newAdminAddress]);
```

**Step 2: Generate the Hash for Signing**

```javascript wrap theme={null}
// Hash follows the DVN contract's hashCallData format
const hash = ethers.utils.keccak256(
  ethers.utils.solidityPack(
    ['uint32', 'address', 'uint256', 'bytes'],
    [vid, dvnAddress, expiration, callData],
  ),
);
```

**Step 3: Collect Quorum Signatures**

Each signer must sign the hash. Signatures must be sorted by signer address (ascending) before concatenation:

```javascript wrap theme={null}
// Each signer signs the hash
const signers = [signer1, signer2, signer3]; // Your Wallet instances
const signatures = [];

for (const signer of signers) {
  const sig = await signer.signMessage(ethers.utils.arrayify(hash));
  signatures.push({
    address: await signer.getAddress(),
    signature: sig,
  });
}

// Sort by address (required by contract)
signatures.sort((a, b) => a.address.toLowerCase().localeCompare(b.address.toLowerCase()));

// Concatenate signatures
const concatenatedSignatures = ethers.utils.solidityPack(
  signatures.map(() => 'bytes'),
  signatures.map((s) => s.signature),
);
```

**Step 4: Submit the Transaction**

```javascript wrap theme={null}
const dvnAbi = [
  'function quorumChangeAdmin((uint32 vid, address target, bytes callData, uint256 expiration, bytes signatures) _param) external',
];

const dvnContract = new ethers.Contract(dvnAddress, dvnAbi, provider);

// Anyone can submit this transaction - no special permissions required
const tx = await dvnContract.quorumChangeAdmin({
  vid: vid,
  target: dvnAddress,
  callData: callData,
  expiration: expiration,
  signatures: concatenatedSignatures,
});

await tx.wait();
console.log('Admin role transferred to:', newAdminAddress);
```

#### Validation Requirements

The contract validates:

| Check | Requirement |
| - | - |
| Expiration | `expiration > block.timestamp` |
| Target | `target == address(this)` (the DVN contract) |
| VID | `vid == contract.vid` |
| Signatures | Must meet quorum, sorted by signer address |
| Replay | Hash must not have been used before |

<Warning>
  Before Taking Over Admin:

  * **Gas infrastructure**: Ensure you have gas management ready on all chains where you'll submit transactions
  * **Signer-threshold coordination**: Collect signatures from enough signers to meet the signer threshold
  * **Transaction systems**: Have reliable transaction submission infrastructure prepared
  * **Test first**: Test the process on testnet before executing on mainnet
</Warning>

<Tip>
  The `quorumChangeAdmin` function is defined in [DVN.sol](https://github.com/LayerZero-Labs/LayerZero-v2/blob/main/packages/layerzero-v2/evm/messagelib/contracts/uln/dvn/DVN.sol) lines 137-160.
</Tip>

***

## Operational Management

### Monitoring

<Tabs>
  <Tab title="AWS">
    * **CloudWatch Logs**: Automatic log collection
    * **CloudWatch Alarms**: Set up error rate monitoring
    * **SNS Notifications**: Configure alerts

    ```bash wrap theme={null}
    # Tail logs
    aws logs tail /ecs/gasolina-api --follow
    ```
  </Tab>

  <Tab title="GCP">
    * **Cloud Logging**: Automatic log collection
    * **Alerting Policies**: Configure error notifications
    * **Notification Channels**: Set up email/Slack alerts

    ```bash wrap theme={null}
    # View recent logs
    gcloud logging read "resource.type=cloud_run_revision" --limit 50
    ```
  </Tab>
</Tabs>

### Scaling

| Platform | Scaling Method |
| - | - |
| AWS | ECS auto-scales based on CPU/memory thresholds |
| GCP | Cloud Run auto-scales based on request volume |

### Key Rotation

**KMS Keys (Recommended)**

* AWS KMS supports automatic key rotation
* Update DVN contract if key ID changes

**Mnemonic Keys (development/testing only)**

Production signers use KMS/HSM and must not be operated as mnemonics; migrate any mnemonic signer to KMS before production use.

1. Generate new mnemonic
2. Update Secrets Manager
3. Update wallet configuration
4. Redeploy application
5. Update DVN contract with new signer addresses

***

## Troubleshooting

### Common Issues

| Issue | Solution |
| - | - |
| "API has not been used in project" (GCP) | Wait a few minutes for API enablement to propagate |
| "Resource already exists" (AWS) | Delete `GasolinaMetricLogGroup` and S3 bucket, retry |
| RPC connection failures | Verify endpoints, check API keys, add independent quorum providers |
| Signature verification failures | Ensure signer addresses match DVN contract config |

### Debug Commands

```bash wrap theme={null}
# AWS: Check ECS logs
aws logs tail /ecs/gasolina-api --follow

# GCP: Check Cloud Run logs
gcloud logging read "resource.type=cloud_run_revision" --limit 50

# Test signing manually (issue through your authenticated gateway, e.g. SigV4-signed
# or mTLS; the signing route must not be reachable by unauthenticated callers)
curl -X POST https://your-instance.com \
    -H "Content-Type: application/json" \
    -d @test-payload.json
```

***

## Production hardening checklist

Gasolina holds DVN signing authority, so its job is simple: produce a signature only after the operator has independently verified that the LayerZero message is real, the source transaction has enough confirmations for the pathway's finality and reorg-risk requirements, the request is inside its validity window, and the signed payload is derived from trustworthy chain data.

A hardened DVN should satisfy the controls in each checklist below before it signs traffic. The short version: diversify your clients, use non-extractable keys, require at least 2 independent signatures, isolate signers, authenticate callers, use independent RPC quorum, validate caller-supplied security context, instrument audit logs, alert on anomalies, and keep an incident runbook.

<Warning>
  The items below describe the production hardening posture every Gasolina-derived DVN deployment should meet. The reference deployment templates in [gasolina-aws](https://github.com/LayerZero-Labs/gasolina-aws) and [gasolina-gcp](https://github.com/LayerZero-Labs/gasolina-gcp) may not enable all of these by default; treat this checklist as the *required* posture for any DVN attesting production messages, regardless of the template defaults. Items marked `<verify default in templates>` require security-team confirmation of the current default state in the reference templates.
</Warning>

### Signers and threshold

Gasolina can return signatures from the signer keys configured for the destination chain. The on-chain DVN signer threshold determines how many signatures are required before verification succeeds.

* **Multiple independent signer identities.** Configure more than one signer identity, and each signer must use a distinct key.
* **Threshold `>= 2` in production.** Require at least 2 signatures for production. Do not lower the production threshold below 2 except as a documented emergency risk exception. A threshold of 2 or more reduces the impact of any single signer key compromise.
* **Signer isolation.** Run signers in separate compute or trust domains rather than hosting multiple signer containers on a single machine.
* **Signer inventory.** Maintain an inventory of the signer addresses and public keys the operator owns and expects `GET /signer-info` to return for each production pathway.
* **Reviewed signer-key changes.** Keep Gasolina signer-key changes behind reviewed deployment and key-management procedures.
* **Reviewed threshold changes.** Keep on-chain DVN signer threshold changes behind reviewed governance or operational procedures.
* **Documented rotation policy.** Keys rotate on a defined cadence and on suspicion of compromise.

### Managed keys vs mnemonics

<Warning>
  Never use mnemonics. They are extractable secrets: anyone who can read the secret, dump process memory, or capture a deployment artifact can reuse the key outside Gasolina. Prefer managed signing services.
</Warning>

The mnemonic-based signer option shown earlier in this guide (Step 3: Choose Signer Type) is intended for local development and testing only. Production deployments must use cloud KMS or HSM-backed keys.

* **Use cloud KMS or HSM-backed keys** for production signers. No long-lived keys in plaintext on disk.
* **Grant routine signing permission only to the expected Gasolina runtime identity.**
* **Define any human, CI, or admin signing access as time-bound break-glass access** with documented approval, immutable logs, and appropriate alerts.
* **Keep KMS/HSM policy changes behind reviewed change control.**

<Info>
  Gasolina (TypeScript) supports AWS KMS and GCP Cloud KMS for HSM-backed signing. The KMS signer is selected through environment variables on the running service:

  * `SIGNER_TYPE=KMS`
  * `KMS_CLOUD_TYPE=AWS` or `KMS_CLOUD_TYPE=GCP`
  * `LAYERZERO_KMS_IDS`: comma-separated list of KMS key IDs, one per signer key
  * For GCP Cloud KMS: `GCP_PROJECT_ID` and `GCP_KEY_RING_ID`

  In the AWS CDK deployment these are derived from the `signerType: 'KMS'` and `kmsNumOfSigners` settings (see Step 3: Choose Signer Type above); the GCP Terraform deployment sets them on the Cloud Run service. Operators using Azure Key Vault, another HSM, or a custom signer should provide an equivalent non-extractable signer adapter and apply the same permission, monitoring, and alerting requirements.
</Info>

### Access control and audit trail

Gasolina should be reachable only through an authenticated control plane. It does not sign arbitrary payloads: it reconstructs the message from chain data and runs validation before signing. The risk of public exposure is that attackers can directly exercise the signing workflow, abuse expensive RPC-backed validation paths, probe validation edge cases, and turn any caller-context or validation bug into a signing or availability incident.

* **Authenticated gateway.** Put Gasolina behind IAM/SigV4, mTLS, VPN, private networking, or an equivalent authenticated gateway. No anonymous endpoints in production. `<verify default in templates>`
* **No public exposure.** Do not expose the Gasolina task, container, or internal load balancer directly to the public internet. The signer process must sit in a private subnet, reachable only via the gateway or load balancer. `<verify default in templates>`
* **Restrict signing-route access** to the expected caller role or service, not a broad account or network range. `<verify default in templates>`
* **Rate-limit signing and status routes** (such as `GET /signer-info` and `GET /provider-health`) per caller at the gateway or equivalent network control where supported. Attach payload-size limits and common-injection rule sets where a WAF is available. `<verify default in templates>`
* **TLS 1.2+ enforced** end-to-end, including internal hops between the gateway and worker.
* **No introspection or health endpoints** that disclose the configured RPC providers, signer counts, or service identifiers to unauthenticated callers. `<verify default in templates>`
* **Instrument audit logs.** Add application, gateway, or sidecar instrumentation to log caller identity, source transaction hash, LayerZero message ID, source/destination chain, `dvnAddress`, signer addresses, outcome, and rejection reason.
* **Retain audit logs** long enough to investigate delayed reports of fraudulent or anomalous signatures.

### Client and deployment diversity

Client diversity reduces common-mode risk from implementation bugs, compromised dependencies, or compromised deployment infrastructure.

* **Run more than one independent Gasolina deployment** when possible.
* **Make deployments independent.** Make deployments independent across release pipelines, deploy credentials, runtime credentials, hosts, and registries or image digests.
* **Add independent implementations** when available, such as Rust or partner-written clients.
* **Expose resolved payloads, signer addresses, and signature results** from each deployment so downstream policy can compare them for high-value pathways.
* **Pin production images by immutable digest** where possible and require reviewed deploys.

<Info>
  Supply-chain and binary integrity controls support deployment diversity. Where supported, pin package manifests to specific versions or hashes and fail CI on drift, use reproducible builds with a publicly documented build pipeline, publish release artifacts with cryptographic signatures from the build pipeline, verify a SHA256 attestation before each signer binary runs, and use file-integrity monitoring on the running node to detect post-deployment binary swaps.
</Info>

### RPC quorum and provider configuration

A compromised or faulty RPC provider can lie about receipts, blocks, timestamps, contract state, or transaction contents. Quorum only helps if the providers and quorum strategy are independent and protected from tampering. This RPC provider quorum (how many independent providers must agree on source-chain data) is a separate control from the on-chain signer threshold in [Signers and threshold](#signers-and-threshold): the quorum governs the data Gasolina reads, while the threshold governs how many signatures the DVN requires.

<Info>
  Security-critical data includes:

  * source transaction receipts and emitted packet events,
  * message hash inputs reconstructed from source-chain state,
  * source block confirmations,
  * destination block timestamps used for expiration checks,
  * destination ULN/DVN config and verification state,
  * read-message time markers and resolved read payload inputs.
</Info>

* **Use RPC quorum, not a single provider,** for security-critical reads.
* **Require matching responses from at least 3 or 4 independent provider entities** for every chain in production. Multiple URLs from one vendor count as one fault domain. A single compromised or faulty RPC can otherwise feed forged source-chain data and the DVN will sign attestations of events that did not occur, so a single-provider (`quorum: 1`) configuration is never acceptable. `<verify default in templates>`
* **Treat provider entities as separate fault domains,** for example operator-run nodes, dedicated external providers, and shared third-party providers.
* **Treat all configured RPCs as primary,** not as failover. Backup-only RPCs do not contribute to consensus and reduce the effective quorum.
* **Prefer including an operator-run node in quorum,** especially for high-value pathways.
* **Fail closed when quorum (at least 3 independent providers) cannot be reached** or providers disagree on security-critical data.
* **Use reorg-aware confirmation depth.** Your DVN's `confirmations` value must exceed the source chain's typical reorg depth by a margin appropriate to the attestation value at stake.
* **Export RPC health, block lag, latency, disagreement rate, and quorum-failure metrics.**
* **Document RPC selection rationale.** Record why each RPC was chosen, when it was last reviewed, and the conditions under which it would be replaced.
* **Restrict writes to provider lists and quorum strategy files** to the deploy pipeline or another reviewed change path. Configuration changes should require a multi-party process so no single operator can rotate provider endpoints unilaterally.
* **Enable versioning or audit trails for provider configuration.** Where supported, store configuration on immutable object storage (for example, Object Lock) and keep versioned access logs on every read and write. `<verify default in templates>`
* **Do not put RPC API keys or credentials in logs or public config.**

### Required checks before every signature

Gasolina reconstructs the source message from chain state and should not trust caller-provided data by itself. If caller-provided identifiers, hashes, or security context do not match the chain-derived data and configured pathway requirements, Gasolina should reject the request instead of signing a payload built from different verified data.

<Info>
  The checks below are what every Gasolina client is expected to perform before signing. Operators should verify that each deployed client performs these checks and that operator-controlled settings, such as supported chains, RPC/quorum configuration, and enabled extra policy checks, are configured as intended.
</Info>

Every signature must pass all of the following checks:

* **Supported chains:** source and destination chains are supported.
* **Valid protocol type:** protocol type is valid for the request (`MESSAGE` or `READ`) and matches the requested ULN/version.
* **Source transaction exists** on the source chain.
* **Packet event matches:** packet event exists and matches the requested message ID, pathway, nonce, and version.
* **Message hash matches** packet data reconstructed from source-chain state.
* **Block confirmations (message verification):** for message verification, the source transaction has at least the caller-supplied `blockConfirmation`.
* **Time markers (Read verification):** for Read verification, resolved time markers are valid and their referenced blocks have the required confirmations.
* **Expiration window valid:** request expiration is still valid and is not more than the configured maximum window in the future.
* **Destination config read via RPC stack:** destination ULN/DVN config and current verification state are read through the configured RPC stack. Production deployments should configure that stack for quorum as described above.
* **Payload built only from verified state:** final hash call data and resolved payload are built only from verified source-chain and destination-chain state.

<Warning>
  Only after all required client checks and enabled extra policy checks pass should signer keys produce signatures.
</Warning>

* **Client consistency.** Verify every Gasolina client, deployment, or implementation whose signatures may be accepted implements these checks and uses the intended supported-chain, RPC/quorum, and extra-policy configuration.

### Additional recommendations for OApp-owned DVNs

OApp-owned or app-specific DVNs may have enough application context to apply business, risk, or compliance checks beyond the base protocol checks. Generic DVNs are usually blind to application semantics and should not make app-specific signing decisions unless the OApp owner has explicitly provided the required payload context, policy, and authority.

Before an OApp-owned or app-specific DVN enables these checks, define:

* **Implementation:** where the check runs, for example a Gasolina extra-context endpoint, sidecar, upstream verifier/orchestrator, or independent client-comparison service.
* **OApp-owner policy:** which apps, pathways, payloads, or message types the DVN is expected to sign or refuse.
* **Asset-issuer impact:** whether refusing to sign can affect minting, burning, unlocking, withdrawals, or other asset movement.
* **Ecosystem impact:** whether refusals can affect pathway liveness, ordered nonce progress, composability, or downstream user experience.
* **Business and ecosystem alignment:** whether the policy changes the relationship between the DVN, the OApp owner, asset issuers, or other ecosystem participants.
* **Operational process:** who can change or disable the policy, how emergency exceptions work, how refused messages can resume or be unblocked, and how refusals are communicated.

<Info>
  Generic DVNs should treat the examples below as application-owner responsibilities unless they have a specific integration that gives them reliable decoding, expected payload semantics, and an agreed policy.
</Info>

Examples for OApp-owned or app-specific DVNs include:

* **Inflow/outflow controls** for applications where the OApp owner understands the message format and asset semantics well enough to make the check reliable.
* **Emergency controls** that allow the operator to intentionally fail closed for a compromised pathway, chain, RPC provider, or app.

Consider allowlists for approved pathways, apps, message types, token routes, or specific payloads, only when this restriction is defined by the OApp owner, and its ecosystem impact is understood.

<Warning>
  Policy-check failures should be logged and alerted. OApp owners and DVN operators should understand that refusing to sign a nonce can block later ordered messages on the same pathway until the OApp or protocol operators take corrective action.
</Warning>

### Monitoring and alerts

<Info>
  Gasolina clients do not emit structured success-path signing audit logs for all of the fields below. Operators should add application, gateway, or sidecar instrumentation before treating these as covered controls.
</Info>

* Alert on unexpected signer address changes or public-key changes.
* Alert on KMS/HSM key-policy, grant, or permission changes on signing keys.
* Alert on KMS/HSM signing attempts from unexpected principals or infrastructure.
* Alert on abnormal signing volume by caller, pathway, destination chain, or signer key.
* Track per-pathway attestation latency, alerting on attestations that are unusually fast (possible pre-positioning), unusually slow (possible degradation), or in non-canonical block ranges.
* Alert on duplicate-sign attempts and repeated requests missing `dvnAddress`.
* Alert on RPC quorum failures, provider disagreement, provider lag, and provider-config changes.
* Alert when extra policy checks reject unusually often or stop responding.
* Alert when health-check or status routes (such as `GET /signer-info` and `GET /provider-health`) are called at unusual volume.
* Alert on authentication-failure and authorization-failure rates. Probe activity is an attack precursor.
* Run continuous source-chain to destination-chain reconciliation, alerting on attestations that do not correspond to source-chain emit events.
* Ensure alerts route to a staffed on-call channel or paging system. A monitoring dashboard nobody is paged on is not a control.
* Keep a runbook for key compromise, signer rotation, bad RPC/provider data, provider-config tampering, deploy rollback, and emergency pathway disablement. Include a kill-switch to stop signing within minutes, tested in a non-production environment at least quarterly; an isolation playbook to remove a compromised RPC, signer, or node without taking the whole DVN offline; and a current communication path to integrators using your DVN.

***

## Repository Links

| Resource | Link |
| - | - |
| AWS Infrastructure | [gasolina-aws](https://github.com/LayerZero-Labs/gasolina-aws) |
| GCP Infrastructure | [gasolina-gcp](https://github.com/LayerZero-Labs/gasolina-gcp) |
| DVN Contract | [DVN.sol](https://github.com/LayerZero-Labs/LayerZero-v2/blob/main/packages/layerzero-v2/evm/messagelib/contracts/uln/dvn/DVN.sol) |

***

## Next Steps

* [Gasolina Overview](/v2/workers/off-chain/gasolina-overview) - Architecture and security model
* [DVN Technical Reference](/v2/workers/off-chain/dvn-technical-reference) - Contract methods and events
* [DVN Overview](/v2/workers/off-chain/dvn-overview) - General DVN concepts

<Info>
  For implementation support, reach out through [LayerZero Discord](https://discord.com/invite/ktbvm8Nkcr) or open an issue in the respective GitHub repository.
</Info>
