This repository provides tooling for deploying a Gasolina-based DVN on AWS. The recommended approach uses Infrastructure-As-Code (IAC) via AWS CDK, which automates the entire setup:
- Bootstraps CDK
- Creates a VPC
- Uploads providers to S3
- Sets up a CloudWatch log group
- Deploys the Gasolina API app on ECS
- Sets up a load balancer on Fargate in the VPC private subnet
- Sets up API Gateway to route to the Gasolina API (we don't expose the load balancer directly, API Gateway offers TLS without the need for a certificate)
If your organization has constraints that prevent using CDK (e.g., custom networking policies, existing infrastructure, or specific compliance requirements), a manual deployment path is also documented below.
Authenticate with AWS CLI using a valid method: https://docs.aws.amazon.com/cli/latest/userguide/cli-chap-authentication.html
In the root directory of the project, run:
pnpm iYou need to decide how the DVN will sign payloads. There are two options:
| Strategy | Description | When to use |
|---|---|---|
| Mnemonic | You supply your own mnemonic(s) stored in AWS Secrets Manager. | You already manage your own keys. |
| KMS | AWS KMS creates HSM-backed asymmetric keys for you. | You want AWS-managed key security. |
Mnemonic setup: Go to AWS Secrets Manager and create a new secret (key-value pair) for each signer:
- Key
LAYERZERO_WALLET_MNEMONIC— the mnemonic phrase - Key
LAYERZERO_WALLET_PATH— the derivation path
KMS setup: No manual key creation is needed for CDK deployments — CDK handles it. For manual deployments, see the KMS section under manual deployment.
CDK automates infrastructure provisioning and application deployment in a single command. This is the fastest and most reliable way to get running.
If this is your first time using CDK in the AWS account, run the following in cdk/gasolina/:
cdk bootstrapcdk/gasolina/config/index.ts — edit the CONFIG object:
- Configure the AWS account number as the key of the object.
projectName— a unique project name (used for the S3 bucket, which must be globally unique on AWS).environment— the LayerZero environment (mainnetortestnet).availableChainNames— comma-separated chain names, e.g.ethereum,bsc,avalanche.signerType—MNEMONICorKMS.- If
MNEMONIC, the number of signers is based on your wallet definitions inwalletConfig/<environment>.json. - If
KMS, you can optionally setkmsNumOfSignersto create and register multiple keys.
- If
cdk/gasolina/config/providers/<environment>/providers.json — add RPC provider entries for every chain listed in availableChainNames.
cdk/gasolina/config/walletConfig/<environment>.json (mnemonic only) — under definitions, add one wallet definition per signer:
address— the signer's wallet addresssecretName— the Secrets Manager secret name used at runtime to retrieve the mnemonic
In cdk/gasolina/, run:
cdk deployAfter deployment completes, the output will include Oracle.ApiGatewayUrl = <URL>. Send this URL to LayerZero Labs.
See Testing below.
Use this path only if CDK is not viable for your organization. You are responsible for provisioning each component, assigning IAM permissions, and configuring environment variables yourself.
| # | Component | AWS examples | Purpose |
|---|---|---|---|
| 1 | Container runtime | ECS, EKS, EC2 | Runs the Gasolina Docker image |
| 2 | Signer credentials | Secrets Manager, KMS | Provides signing keys to the application |
| 3 | RPC provider config | S3 | Stores providers.json for chain RPC endpoints |
| 4 | Logging (optional) | CloudWatch | Application metrics and logs |
The container (1) must have IAM permissions to access the signer credentials (2) and the provider config bucket (3). If logging is enabled, it also needs read/write access to the log group (4).
Follow the same steps described in Prerequisites > Choose a signer strategy. Note the secret name — you will reference it in environment variables.
For each signer key you need, create a KMS key with the following steps:
- Create key — choose an asymmetric key type suitable for signing.

- Set configuration — configure the key spec as required (ECC_SECG_P256K1, sign/verify usage).

- Enable service role — grant your container's IAM role permission to use the key.

- Note the Key ID — you will use this in the
LAYERZERO_KMS_IDSenvironment variable.
- Create a private S3 bucket with read access granted to your service role.
- Upload a file named
providers.jsonfollowing the format incdk/gasolina/config/providers/mainnet/providers.json. - Note the bucket name — you will use this in the
CONFIG_BUCKET_NAMEenvironment variable.
Create a CloudWatch Log Group and grant read/write permissions to your service role. Gasolina currently only supports AWS CloudWatch for logging.
Pull the latest version of the Gasolina image:
us-east1-docker.pkg.dev/lz-docker/gasolina/gasolina
Ensure the service has network/IAM permissions to:
- Be called externally (ingress)
- Reach external URLs listed in
providers.json(egress) - Access signing keys (Secrets Manager or KMS)
- Read the S3 bucket with provider config
- (if logging is configured) Read/write the CloudWatch log group
Set the following environment variables on your container:
| Variable | Required | Description |
|---|---|---|
LAYERZERO_ENVIRONMENT |
Yes | mainnet or testnet |
LAYERZERO_AVAILABLE_CHAIN_NAMES |
Yes | Comma-separated chain names, e.g. ethereum,arbitrum |
LAYERZERO_CDK_DEPLOY_REGION |
Yes | AWS region of the service, e.g. us-east-1 |
SERVER_PORT |
Yes | Port exposed by the service, e.g. 8081 |
SIGNER_TYPE |
Yes | KMS or MNEMONIC |
LAYERZERO_WALLETS |
If mnemonic | JSON-stringified wallet config (see below) |
KMS_CLOUD_TYPE |
If KMS | AWS or GCP |
LAYERZERO_KMS_IDS |
If KMS | Comma-separated KMS key IDs, e.g. abcdef,bcdefa |
LAYERZERO_SUPPORTED_ULN_VERSIONS |
Yes | ["V2"] |
LAYERZERO_METRIC_NAMESPACE |
Optional | Metric namespace, e.g. layerzero-gasolina |
CONFIG_BUCKET_NAME |
Yes | Name of the S3 bucket with providers.json |
PROVIDER_CONFIG_TYPE |
Yes | S3 or GCS |
LAYERZERO_METRIC_LOG_GROUP_NAME |
If logging | CloudWatch log group name |
LAYERZERO_WALLETS format (mnemonic signer only):
{
"definitions": [
{
"name": "signer-1",
"secretName": "my-secret-name"
}
]
}secretName is the Secrets Manager secret created earlier, and name is an arbitrary label.
See Testing below.
curl https://<service-url>/
# Expected: HEALTHYcurl https://<service-url>/available-chainscurl https://<service-url>/provider-healthIf any provider returns false, check the service logs for details. Common causes: misconfigured URLs in providers.json, or the service lacking egress permissions. If your organization requires a proxy for external domains, ensure the TLS configuration is valid and pass the full proxy address in providers.json — Gasolina does not allow unverified TLS connections.
curl https://<service-url>/signer-info?chainName=ethereumThis should return address information for your signers. If it fails, verify your signer-related environment variables and IAM permissions.
From the project root directory:
ts-node scripts/testDeployment -u <service-url> -e <environment>A successful response:
--- [200] Successful request ---
Response: {
signatures: [
{
signature: '<signature>',
address: '<address>'
}
]
}
You can enhance message verification by adding custom rules. Set up an API that Gasolina will call whenever a message is received. The API receives the full message context (including on-chain data) and returns a boolean indicating whether the message should be signed.
Setup: In cdk/gasolina/config/index.ts, set extraContextGasolinaUrl to your API's URL in the CONFIG object.
API input schema
{
sentEvent: { // PacketSent event, emitted from the Endpoint contract
lzMessageId: {
pathwayId: {
srcEid: number // Source chain eid (https://github.com/LayerZero-Labs/LayerZero-v2/blob/main/packages/layerzero-v2/evm/protocol/contracts/EndpointV2.sol#L23)
dstEid: number
sender: string // Sender oApp address on source chain
receiver: string // Receiver oApp address on destination chain
srcChainName: string // Source Chain Name
dstChainName: string // Destination Chain Name
}
nonce: number
ulnSendVersion: UlnVersion
}
guid: string // onchain guid
message: string
options: {
// Adapter Params set on the source transaction
lzReceive?: {
gas: string
value: string
}
nativeDrop?: {
amount: string
receiver: string
}[]
compose?: {
index: number
gas: string
value: string
}[]
ordered?: boolean
}
payload?: string
sendLibrary?: string
onChainEvent: { // Transaction on the source chain
chainName: string
txHash: string
blockHash: string
blockNumber: number
}
}
from: string // Address that initiated the transaction
}API output: Return a boolean — true to allow signing, false to reject.
Depending on the environment (testnet/mainnet), fill in the appropriate information regarding DVN addresses and KMS key IDs in scripts/configChangePayloads/data/. The files should be named dvn-addresses-<environment>.json and kms-keyids-<environment>.json. See the existing testnet examples in that folder for reference.
ts-node scripts/configChangePayloads/createSetQuorumSignatures.ts \
-e <environment> \
-c <comma-separated-chain-names> \
--oldQuorum <number> \
--newQuorum <number>
# Example:
# ts-node scripts/configChangePayloads/createSetQuorumSignatures.ts -e testnet -c bsc,avalanche,fantom --oldQuorum 2 --newQuorum 1Set --shouldRevoke to 0 when adding a signer, or 1 when removing.
ts-node scripts/configChangePayloads/createAddOrRemoveSignerSignatures.ts \
-e <environment> \
-c <comma-separated-chain-names> \
-q <quorum> \
--signerAddress <address> \
--shouldRevoke <0 or 1>
# Example:
# ts-node scripts/configChangePayloads/createAddOrRemoveSignerSignatures.ts -e testnet -c bsc,avalanche,fantom -q 1 --signerAddress 0x85e4857b7f15bbbbbc72d933a6357d3c22a0bbc7 --shouldRevoke 1Some resources have deletion protection policies. Delete these before redeploying:
- CloudWatch log group:
GasolinaMetricLogGroup - S3 bucket:
providerconfigs-<projectName>-<environment>-gasolina
The service does not have permissions to access the bucket specified in CONFIG_BUCKET_NAME. Verify the IAM role attached to your container has s3:GetObject permission on that bucket.
Check the service logs for specifics. Common causes:
- Misconfigured RPC URLs in
providers.json - Service lacks egress permissions to reach the provider URLs
- If using a corporate proxy, ensure TLS is properly configured and pass the full proxy address in
providers.json
It's important to note that if using multiple providers and any of them fail, the result will be false.
Verify that:
- Signer-related environment variables (
SIGNER_TYPE,LAYERZERO_WALLETSorLAYERZERO_KMS_IDS) are set correctly - The service role has permissions to access Secrets Manager or KMS
