Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
38 changes: 38 additions & 0 deletions infra/aws/secrets.tf
Original file line number Diff line number Diff line change
Expand Up @@ -115,6 +115,13 @@ resource "aws_kms_key" "executor" {
# means a setTradeExecutor call, not a terraform destroy.
deletion_window_in_days = 30
enable_key_rotation = false
# Terraform must never be able to delete this: the venue settles every trade and every
# withdrawal with this key, and Matching gates settlement on its address.
# Retiring it is a deliberate act (sweep the balance, re-point what signs), which means
# removing this block first -- not a flag someone forgot on a routine plan.
lifecycle {
prevent_destroy = true
}
}

resource "aws_kms_alias" "executor" {
Expand All @@ -140,3 +147,34 @@ resource "aws_iam_role_policy" "executor_kms_sign" {
role = aws_iam_role.task.id
policy = data.aws_iam_policy_document.executor_kms_sign[0].json
}

# Working capital for the cNGN rebalance. The market maker's flow is one-directional -- it sells
# cNGN for USDC -- so without a routine that buys cNGN back the bid side eventually goes dark.
# HyperFX fills that swap same-chain on Base against the identical cNGN the venue settles
# (0x46C85152bFe9f96829aA94755D9f915F9B10EF5F), so the round trip never leaves the chain.
#
# The address this key derives to needs ETH for gas and its own USDC allowance to the gateway
# before it can swap. Neither is terraform's to give it.
resource "aws_kms_key" "rebalance" {
count = var.rebalance_kms_enabled ? 1 : 0
description = "${var.name} cNGN rebalance signing key (secp256k1)"
key_usage = "SIGN_VERIFY"
customer_master_key_spec = "ECC_SECG_P256K1"
# Same reasoning as the executor key: a signing key is an on-chain identity holding value, and
# deleting it strands whatever it holds. Retiring it means sweeping the balance first.
deletion_window_in_days = 30
enable_key_rotation = false
# Terraform must never be able to delete this: it holds the rebalance float, and deleting it
# strands whatever USDC and cNGN sit at its address.
# Retiring it is a deliberate act (sweep the balance, re-point what signs), which means
# removing this block first -- not a flag someone forgot on a routine plan.
lifecycle {
prevent_destroy = true
}
}

resource "aws_kms_alias" "rebalance" {
count = var.rebalance_kms_enabled ? 1 : 0
name = "alias/${var.name}-rebalance"
target_key_id = aws_kms_key.rebalance[0].key_id
}
33 changes: 31 additions & 2 deletions infra/aws/variables.tf
Original file line number Diff line number Diff line change
Expand Up @@ -227,9 +227,13 @@ variable "executor_kms_enabled" {
The two are separate because the new address must be funded and authorised on Matching with
setTradeExecutor (owner-only) BEFORE it signs anything, and that happens between the two
applies.

Defaults true because the key exists and is authorised on Matching. A plan that resolves
this to false proposes destroying the venue's settlement key; aws_kms_key.executor carries
prevent_destroy so that fails loudly instead of succeeding quietly.
EOT
type = bool
default = false
default = true
}

variable "executor_kms_signing" {
Expand All @@ -249,7 +253,32 @@ variable "executor_kms_signing" {

Rollback is this flag back to false plus a rollout; the old key stays authorised until it is
retired deliberately.

Defaults true because #64 deleted the stored PRIVATE_KEY. There is no second signing path:
resolved false, execution-service gets neither EXECUTOR_KMS_KEY_ID nor PRIVATE_KEY and
refuses to boot. The rollout sequence above is history, kept because it explains the split.
EOT
type = bool
default = false
default = true
}

variable "rebalance_kms_enabled" {
description = <<-EOT
Create the KMS signing key the cNGN rebalance uses to swap USDC for cNGN on the HyperFX
IntentGateway (0xAe041F7B0CB581876832830baeB6a2Aa2a3C9716 on Base).

Deliberately NOT the executor key. The executor is authorised on Matching by
setTradeExecutor and settles every trade and withdrawal on the venue; this one only ever
holds a few hundred dollars of working capital and signs placeOrder. Keeping them apart
means a compromised rebalance key costs the float, not the venue's settlement authority --
the same reason the executor was moved off the personal wallet on 2026-09-16.

Creating the key grants nothing and starts nothing. Until the loop is automated the swap is
run as a one-shot by an operator, who already holds kms:Sign through their own role, so no
task role gets Sign on this key yet.
Defaults true for the same reason as the executor key: it exists, it holds working capital,
and a plan that resolves it to false proposes deleting it out from under that balance.
EOT
type = bool
default = true
}
Loading